-
Notifications
You must be signed in to change notification settings - Fork 2
gh #192 added the changes to support power strip p304m multi outlets. #193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR updates the Tapo power module to support newer python-kasa library versions and improve outlet handling for power strips. The changes include dependency updates (numpy, python-kasa, typing_extensions) and significant refactoring of state management and power monitoring logic to be more robust across different CLI output formats.
- Updated python-kasa from 0.6.2.1 to 0.7.7 and other dependencies
- Refactored outlet handling to use explicit
Nonechecks and integer normalization - Rewrote
_get_state()andgetPowerLevel()methods with JSON-first approach and text fallback for better compatibility
Reviewed Changes
Copilot reviewed 1 out of 2 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| requirements.txt | Updated numpy to 2.x range, python-kasa to 0.7.7, and typing_extensions to 4.12.2 |
| framework/core/powerModules/tapoControl.py | Refactored outlet parameter handling, state retrieval logic with JSON/text fallback, and power monitoring to support strips |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| self._outlet = int(cleaned) # keep numeric internally | ||
| except ValueError: | ||
| self._log.error("Invalid outlet value %r (cleaned=%r)", outlet, cleaned) | ||
|
|
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When outlet parsing fails due to ValueError, the code logs an error but continues execution with self._outlet remaining None. This creates an inconsistent state where an invalid outlet was provided but is silently treated as no outlet. Consider either raising an exception or setting a flag to indicate the configuration error, especially since downstream code may behave unexpectedly.
| raise ValueError(f"Invalid outlet value {outlet!r} (cleaned={cleaned!r})") |
| if self._outlet: | ||
| if self._outlet is not None: | ||
| self._performCommand("on", append_args=["--index", str(self._outlet)]) | ||
| self._performCommand("on") |
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an outlet is configured, the code executes two 'on' commands: one with the outlet index and one without. This is redundant and could cause unintended behavior. The second command (line 164) should only execute when self._outlet is None. Add an 'else' clause before line 164.
| self._performCommand("on") | |
| else: | |
| self._performCommand("on") |
| else: | ||
| command_list.append("--type") | ||
| command_list.append("plug") | ||
| if self._outlet: |
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent outlet check using truthiness instead of explicit None comparison. This would fail for outlet index 0 (which is valid for the first outlet in a strip). Should use 'if self._outlet is not None:' to match the pattern used elsewhere in the file (lines 78, 143, 162, 174, 300).
| if self._outlet: | |
| if self._outlet is not None: |
| try: | ||
| idx = int(self._outlet) | ||
| except Exception: | ||
| self._log.error(f"Invalid outlet index {self._outlet!r}; defaulting to device state") |
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message is misleading. This log appears when self._outlet cannot be converted to int during exception handling, but the code doesn't actually default to device state—it sets idx=-1 and continues with outlet-specific logic. Consider clarifying: 'Invalid outlet index {self._outlet!r}; treating as invalid index'.
| self._log.error(f"Invalid outlet index {self._outlet!r}; defaulting to device state") | |
| self._log.error(f"Invalid outlet index {self._outlet!r}; treating as invalid index") |
| if not cleaned.isdigit(): | ||
| self._log.error(f"Invalid outlet value: {self._outlet!r}; querying device-level power instead.") | ||
| else: | ||
| idx_val = int(cleaned) |
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicated outlet validation logic. The same cleaning and validation pattern (strip quotes, check isdigit, convert to int) appears in init (lines 79-83), _get_state (lines 199-202, 227-230), and getPowerLevel (lines 301-305). Consider extracting this into a helper method like '_normalize_outlet_index()' to reduce duplication and ensure consistent behavior.
| # Build CLI args: feature current_consumption [--index N] | ||
| args = ["current_consumption"] | ||
| if idx_val is not None: | ||
| idx_token = f"{idx_val}" # plain numeric string, no quotes |
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Creating idx_token with f-string formatting is unnecessary when str(idx_val) would suffice and be more explicit. The comment about 'no quotes' is also redundant since f-string numeric formatting never adds quotes.
| idx_token = f"{idx_val}" # plain numeric string, no quotes | |
| idx_token = str(idx_val) |
005e68f to
a736c17
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 1 out of 2 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| else: | ||
| self._performCommand("on") | ||
| self._get_state() | ||
| if self._is_on == False: |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent boolean comparison style. Line 142 uses if self._is_on: while line 161 uses if self._is_on == False:. For consistency with line 142 and best practices, change to if not self._is_on:.
| if self._is_on == False: | |
| if not self._is_on: |
| #* Socket 'Plug 1' state: ON on_since: 2022-01-26 12:17:41.423468 | ||
| #* Socket 'Plug 2' state: OFF on_since: None | ||
| #* Socket 'Plug 3' state: OFF on_since: None | ||
| result = self._performCommand("state", noArgs=True) |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The _performCommand method does not accept a noArgs parameter (line 84 signature shows only command, json, and append_args parameters). This will raise a TypeError at runtime.
| result = self._performCommand("state", noArgs=True) | |
| result = self._performCommand("state") |
| self._log.debug("Device state: OFF") | ||
| return | ||
| # Check if the this socket is off. | ||
| if powerState[self.slotIndex+1] == "OFF": |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Undefined attribute self.slotIndex is referenced. The class only defines self._outlet (line 73, 78) but never defines slotIndex. This will raise an AttributeError at runtime. Should likely be int(self._outlet).
| if powerState[self.slotIndex+1] == "OFF": | |
| if powerState[int(self._outlet)+1] == "OFF": |
| # == Smart Plug 1 (P304M) == | ||
| # == Primary features == | ||
| # State(state): True | ||
| if result.find('Children') > 1: # smart extension plug with multiple outlets |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition result.find('Children') > 1 will be true if 'Children' appears at position 2 or later. If 'Children' is at position 0 or 1, it will incorrectly be false. The intended check is likely >= 0 or != -1 to test if the string exists.
| if result.find('Children') > 1: # smart extension plug with multiple outlets | |
| if result.find('Children') != -1: # smart extension plug with multiple outlets |
| self._device_type = info.get("mic_type", "UNKNOWN") | ||
| else: | ||
| self._device_type = "UNKNOWN" | ||
| elif result.get("get_child_device_list", {}).get('child_device_list', []): |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential TypeError if self._outlet is None. While line 77-78 shows outlet is only set when not None, this code path at line 247 can execute when outlet is None since there's no guard condition checking self._outlet is not None before this elif branch.
| elif result.get("get_child_device_list", {}).get('child_device_list', []): | |
| elif self._outlet is not None and result.get("get_child_device_list", {}).get('child_device_list', []): |
|
moved to 194 Pull request, with few modificaitons on testing |
No description provided.