Skip to content

Conversation

@Anbukannadhasan
Copy link
Contributor

No description provided.

Copilot AI review requested due to automatic review settings November 4, 2025 09:33
Copy link

Copilot AI left a 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 None checks and integer normalization
  • Rewrote _get_state() and getPowerLevel() 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)

Copy link

Copilot AI Nov 4, 2025

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.

Suggested change
raise ValueError(f"Invalid outlet value {outlet!r} (cleaned={cleaned!r})")

Copilot uses AI. Check for mistakes.
if self._outlet:
if self._outlet is not None:
self._performCommand("on", append_args=["--index", str(self._outlet)])
self._performCommand("on")
Copy link

Copilot AI Nov 4, 2025

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.

Suggested change
self._performCommand("on")
else:
self._performCommand("on")

Copilot uses AI. Check for mistakes.
else:
command_list.append("--type")
command_list.append("plug")
if self._outlet:
Copy link

Copilot AI Nov 4, 2025

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).

Suggested change
if self._outlet:
if self._outlet is not None:

Copilot uses AI. Check for mistakes.
try:
idx = int(self._outlet)
except Exception:
self._log.error(f"Invalid outlet index {self._outlet!r}; defaulting to device state")
Copy link

Copilot AI Nov 4, 2025

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'.

Suggested change
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")

Copilot uses AI. Check for mistakes.
Comment on lines 302 to 305
if not cleaned.isdigit():
self._log.error(f"Invalid outlet value: {self._outlet!r}; querying device-level power instead.")
else:
idx_val = int(cleaned)
Copy link

Copilot AI Nov 4, 2025

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.

Copilot uses AI. Check for mistakes.
# 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
Copy link

Copilot AI Nov 4, 2025

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.

Suggested change
idx_token = f"{idx_val}" # plain numeric string, no quotes
idx_token = str(idx_val)

Copilot uses AI. Check for mistakes.
@Anbukannadhasan Anbukannadhasan force-pushed the feature/gh192_TapoMultiplePowerOutlet branch from 005e68f to a736c17 Compare November 6, 2025 09:39
Copy link

Copilot AI left a 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:
Copy link

Copilot AI Nov 6, 2025

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:.

Suggested change
if self._is_on == False:
if not self._is_on:

Copilot uses AI. Check for mistakes.
#* 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)
Copy link

Copilot AI Nov 6, 2025

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.

Suggested change
result = self._performCommand("state", noArgs=True)
result = self._performCommand("state")

Copilot uses AI. Check for mistakes.
self._log.debug("Device state: OFF")
return
# Check if the this socket is off.
if powerState[self.slotIndex+1] == "OFF":
Copy link

Copilot AI Nov 6, 2025

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).

Suggested change
if powerState[self.slotIndex+1] == "OFF":
if powerState[int(self._outlet)+1] == "OFF":

Copilot uses AI. Check for mistakes.
# == Smart Plug 1 (P304M) ==
# == Primary features ==
# State(state): True
if result.find('Children') > 1: # smart extension plug with multiple outlets
Copy link

Copilot AI Nov 6, 2025

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.

Suggested change
if result.find('Children') > 1: # smart extension plug with multiple outlets
if result.find('Children') != -1: # smart extension plug with multiple outlets

Copilot uses AI. Check for mistakes.
self._device_type = info.get("mic_type", "UNKNOWN")
else:
self._device_type = "UNKNOWN"
elif result.get("get_child_device_list", {}).get('child_device_list', []):
Copy link

Copilot AI Nov 6, 2025

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.

Suggested change
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', []):

Copilot uses AI. Check for mistakes.
@Anbukannadhasan
Copy link
Contributor Author

moved to 194 Pull request, with few modificaitons on testing

@github-actions github-actions bot locked and limited conversation to collaborators Nov 6, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants