Skip to content

Implement resetting of energylogs - #252

Merged
bouwew merged 23 commits into
mainfrom
energy-reset
Jun 4, 2025
Merged

Implement resetting of energylogs#252
bouwew merged 23 commits into
mainfrom
energy-reset

Conversation

@bouwew

@bouwewbouwew commented Jun 3, 2025

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added the ability to reset energy logs for supported devices.
    • Introduced a property to view node protocol versions.
  • Bug Fixes

    • Improved validation for device compatibility when setting energy intervals or resetting energy logs.
  • Documentation

    • Updated the changelog to include details for version 0.42.0.
  • Chores

    • Bumped project version to 0.42.0.

@coderabbitai

coderabbitaiBot commented Jun 3, 2025

Copy link
Copy Markdown

"""

Walkthrough

This update introduces an asynchronous energy reset feature to the Plugwise USB library. It adds new methods for sending energy reset requests to nodes, enforces node-type validation for energy-related operations, and documents the changes in the changelog. The project version is incremented to 0.42.0.

Changes

File(s)Change Summary
plugwise_usb/__init__.pyAdded Stick.energy_reset_request() async method; minor import reordering and whitespace cleanup.
plugwise_usb/network/__init__.pyAdded StickNetwork.energy_reset_request() async method; added node-type checks for energy ops; updated set_energy_intervals with validation.
plugwise_usb/messages/requests.pyModified CircleClockSetRequest constructor to use LogAddr(0, 8, False) when reset is true.
plugwise_usb/nodes/node.pyAdded node_protocols property to PlugwiseBaseNode.
plugwise_usb/constants.pyAdded constants DEFAULT_CONS_INTERVAL, NO_PRODUCTION_INTERVAL, and ENERGY_NODE_TYPES.
CHANGELOG.mdAdded changelog entry for version 0.42.0 documenting energy reset feature.
pyproject.tomlUpdated project version to 0.42.0.

Sequence Diagram(s)

sequenceDiagram
participant User
participant Stick
participant StickNetwork
participant Node
User->>Stick: energy_reset_request(mac)
Stick->>StickNetwork: energy_reset_request(mac)
StickNetwork->>StickNetwork: _validate_energy_node(mac)
StickNetwork->>Node: Send CircleClockSetRequest(reset=True)
Node-->>StickNetwork: Response (acknowledgment)
StickNetwork-->>Stick: Raise error or return success
Stick->>StickNetwork: set_energy_intervals(mac, 60, 0)
StickNetwork->>StickNetwork: _validate_energy_node(mac)
StickNetwork-->>Stick: Return result or False
Stick-->>User: Return result or False
Loading

Possibly related PRs

  • plugwise/python-plugwise-usb#247: Implements setting energy logging intervals, directly related as both PRs modify Stick and StickNetwork for energy management features.

Suggested reviewers

  • dirixmjm
  • brefra
  • ArnoutD

Poem

Hopping through code with a reset in mind,
Energy logs now leave old data behind.
Version bumped, changelog sung,
Async requests on every rung.
With protocols checked and nodes anew,
This rabbit celebrates what devs can do!
🐇✨
"""


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ede19eb and 0202797.

📒 Files selected for processing (1)
  • pyproject.toml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pyproject.toml
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Check commit
  • GitHub Check: Check commit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @coderabbitai title anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@bouwewbouwew changed the title Implement energy-reset functionalityImplement resetting of energylogsJun 3, 2025
@codecov

codecovBot commented Jun 3, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 35.89744% with 25 lines in your changes missing coverage. Please review.

Project coverage is 80.74%. Comparing base (4983462) to head (0202797).
Report is 24 commits behind head on main.

Files with missing linesPatch %Lines
plugwise_usb/network/__init__.py26.31%14 Missing ⚠️
plugwise_usb/__init__.py27.27%8 Missing ⚠️
plugwise_usb/nodes/node.py40.00%3 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #252 +/- ##
==========================================
- Coverage 80.97% 80.74% -0.24% 
==========================================
Files 36 36 Lines 7418 7453 +35 ==========================================
+ Hits 6007 6018 +11 - Misses 1411 1435 +24 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
plugwise_usb/nodes/node.py (1)

103-110: Simplify node_protocols property.

The explicit None check is redundant—returning self._node_protocols already yields None when unset. Streamline for clarity.

Proposed diff:

- @property- def node_protocols(self) -> SupportedVersions | None:- """Return the node_protocols for the Node."""- if self._node_protocols is None:- return None-- return self._node_protocols+ @property+ def node_protocols(self) -> SupportedVersions | None:+ """Return supported protocol versions for this node, or None if not determined."""+ return self._node_protocols
plugwise_usb/messages/requests.py (1)

15-15: Clarify the reason for commenting out LOGADDR_OFFSET import.

The LOGADDR_OFFSET import is commented out but it's unclear if this is a temporary change or permanent removal. If this constant is no longer needed, consider removing the import entirely rather than commenting it out.

-# LOGADDR_OFFSET,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44947ea and 2b063cc.

📒 Files selected for processing (6)
  • CHANGELOG.md (1 hunks)
  • plugwise_usb/__init__.py (3 hunks)
  • plugwise_usb/messages/requests.py (2 hunks)
  • plugwise_usb/network/__init__.py (4 hunks)
  • plugwise_usb/nodes/node.py (1 hunks)
  • pyproject.toml (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
plugwise_usb/messages/requests.py (1)
plugwise_usb/messages/properties.py (1)
  • String (80-97)
plugwise_usb/nodes/node.py (1)
plugwise_usb/nodes/helpers/firmware.py (1)
  • SupportedVersions (19-23)
🪛 Ruff (0.11.9)
plugwise_usb/messages/requests.py

767-767: Trailing whitespace

Remove trailing whitespace

(W291)

🔇 Additional comments (4)
plugwise_usb/__init__.py (2)

13-13: LGTM!

Import reordering is clean and maintains alphabetical order.


311-311: LGTM!

Good cleanup of trailing whitespace.

plugwise_usb/network/__init__.py (2)

9-9: LGTM!

Good addition of UTC import for proper timezone handling in energy reset functionality.


18-20: LGTM!

Import reordering maintains good organization and adds the necessary CircleClockSetRequest for the new energy reset feature.

Comment threadCHANGELOG.md
Comment threadpyproject.toml Outdated
Comment threadplugwise_usb/messages/requests.py Outdated
Comment threadplugwise_usb/__init__.py
Comment threadplugwise_usb/network/__init__.py Outdated
Comment threadplugwise_usb/network/__init__.py

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
plugwise_usb/messages/requests.py (3)

15-15: Remove unused import
The constant LOGADDR_OFFSET is imported but not used anywhere in this file. Please remove it to clean up unused imports.

🧰 Tools
🪛 Ruff (0.11.9)

15-15: ..constants.LOGADDR_OFFSET imported but unused

Remove unused import: ..constants.LOGADDR_OFFSET

(F401)


767-767: Remove trailing whitespace
The line this_date, has trailing spaces. Please trim the extra whitespace.

🧰 Tools
🪛 Ruff (0.11.9)

767-767: Trailing whitespace

Remove trailing whitespace

(W291)


768-768: Introduce named constant for reset log address
Using the literal 0 in LogAddr(0, 8, False) is a magic value. Consider defining a descriptive constant (e.g., ENERGY_RESET_LOG_ADDR_INDEX = 0) at the top of the file and using it here for better readability and maintainability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b063cc and 416adac.

📒 Files selected for processing (1)
  • plugwise_usb/messages/requests.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
plugwise_usb/messages/requests.py (1)
plugwise_usb/messages/properties.py (1)
  • LogAddr (384-404)
🪛 Ruff (0.11.9)
plugwise_usb/messages/requests.py

767-767: Trailing whitespace

Remove trailing whitespace

(W291)

🪛 GitHub Actions: Latest commit
plugwise_usb/messages/requests.py

[warning] 12-12: pylint: Unused LOGADDR_OFFSET imported from constants (unused-import)

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
plugwise_usb/network/__init__.py (1)

548-576: ⚠️ Potential issue

Fix response attribute inconsistency in error message.

The method implementation looks good overall with proper validation, but there's an inconsistency in the error handling at the end.

Apply this diff to fix the inconsistent response attribute usage:

 if response.ack_id != NodeResponseType.CLOCK_ACCEPTED:
raise MessageError(
- f"Unknown NodeResponseType '{response.response_type.name}' received"+ f"Unknown NodeResponseType '{response.ack_id.name}' received"
)

The code checks response.ack_id but references response.response_type.name in the error message, which is inconsistent.

🧰 Tools
🪛 Ruff (0.11.9)

555-555: Blank line contains whitespace

Remove whitespace from blank line

(W293)

🧹 Nitpick comments (1)
plugwise_usb/network/__init__.py (1)

555-555: Remove whitespace from blank line.

-+
🧰 Tools
🪛 Ruff (0.11.9)

555-555: Blank line contains whitespace

Remove whitespace from blank line

(W293)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 17f9626 and dee5d4c.

📒 Files selected for processing (3)
  • plugwise_usb/__init__.py (3 hunks)
  • plugwise_usb/messages/requests.py (1 hunks)
  • plugwise_usb/network/__init__.py (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • plugwise_usb/messages/requests.py
  • plugwise_usb/init.py
🧰 Additional context used
🪛 Ruff (0.11.9)
plugwise_usb/network/__init__.py

555-555: Blank line contains whitespace

Remove whitespace from blank line

(W293)

🔇 Additional comments (3)
plugwise_usb/network/__init__.py (3)

9-9: LGTM! Import additions support the new functionality.

The new imports are appropriate for the added energy reset functionality and MAC validation.

Also applies to: 16-16, 19-19, 21-21


37-38: Excellent use of constants for improved maintainability.

Defining ENERGY_NODE_TYPES as a module-level constant improves code maintainability and makes the supported node types explicit. This addresses the previous suggestion to avoid hardcoded values.


585-595: Great addition of input validation to existing method.

The addition of MAC validation, node existence checks, and node type validation to the set_energy_intervals method improves robustness and consistency with the new energy_reset_request method.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
plugwise_usb/network/__init__.py (1)

548-577: Improve error message formatting.

The method implementation is solid with proper validation and error handling. However, the error message on lines 574-576 uses string concatenation instead of a more readable f-string.

Apply this diff to improve readability:

- raise MessageError(- f"Unexpected NodeResponseType {response.ack_id!r} "- +"received in response to CircleClockSetRequest "- )+ raise MessageError(+ f"Unexpected NodeResponseType {response.ack_id!r} received in response to CircleClockSetRequest"+ )

Note: Static analysis indicates this new functionality lacks test coverage. Consider adding unit tests to verify the validation logic and error handling paths.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 550-551: plugwise_usb/network/init.py#L550-L551
Added lines #L550 - L551 were not covered by tests


[warning] 553-554: plugwise_usb/network/init.py#L553-L554
Added lines #L553 - L554 were not covered by tests


[warning] 556-557: plugwise_usb/network/init.py#L556-L557
Added lines #L556 - L557 were not covered by tests


[warning] 561-562: plugwise_usb/network/init.py#L561-L562
Added lines #L561 - L562 were not covered by tests


[warning] 569-570: plugwise_usb/network/init.py#L569-L570
Added lines #L569 - L570 were not covered by tests


[warning] 572-573: plugwise_usb/network/init.py#L572-L573
Added lines #L572 - L573 were not covered by tests

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e348b36 and 6e09f86.

📒 Files selected for processing (1)
  • plugwise_usb/network/__init__.py (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
plugwise_usb/network/__init__.py (5)
plugwise_usb/api.py (3)
  • NodeType (57-68)
  • mac (270-271)
  • name (242-243)
plugwise_usb/helpers/util.py (1)
  • validate_mac (13-21)
plugwise_usb/exceptions.py (2)
  • MessageError (20-21)
  • NodeError (24-25)
plugwise_usb/messages/requests.py (18)
  • CircleClockSetRequest (730-783)
  • CircleMeasureIntervalRequest (1261-1292)
  • send (364-373)
  • send (386-395)
  • send (437-439)
  • send (475-484)
  • send (511-520)
  • send (541-552)
  • send (565-574)
  • send (587-596)
  • send (620-629)
  • send (666-675)
  • send (718-727)
  • send (774-783)
  • send (807-816)
  • send (847-856)
  • send (879-888)
  • response (124-128)
plugwise_usb/__init__.py (2)
  • energy_reset_request (213-225)
  • name (141-143)
🪛 GitHub Check: codecov/patch
plugwise_usb/network/__init__.py

[warning] 550-551: plugwise_usb/network/init.py#L550-L551
Added lines #L550 - L551 were not covered by tests


[warning] 553-554: plugwise_usb/network/init.py#L553-L554
Added lines #L553 - L554 were not covered by tests


[warning] 556-557: plugwise_usb/network/init.py#L556-L557
Added lines #L556 - L557 were not covered by tests


[warning] 561-562: plugwise_usb/network/init.py#L561-L562
Added lines #L561 - L562 were not covered by tests


[warning] 569-570: plugwise_usb/network/init.py#L569-L570
Added lines #L569 - L570 were not covered by tests


[warning] 572-573: plugwise_usb/network/init.py#L572-L573
Added lines #L572 - L573 were not covered by tests


[warning] 586-587: plugwise_usb/network/init.py#L586-L587
Added lines #L586 - L587 were not covered by tests


[warning] 589-590: plugwise_usb/network/init.py#L589-L590
Added lines #L589 - L590 were not covered by tests


[warning] 592-593: plugwise_usb/network/init.py#L592-L593
Added lines #L592 - L593 were not covered by tests

🔇 Additional comments (3)
plugwise_usb/network/__init__.py (3)

9-9: LGTM! Import additions are appropriate.

All new imports (UTC, validate_mac, CircleClockSetRequest) are properly used in the new energy reset functionality.

Also applies to: 16-16, 19-19


37-37: Excellent use of constants for node type validation.

Defining ENERGY_NODE_TYPES improves code maintainability and makes the supported node types (Circle+, Circle, Stealth) explicit rather than using magic numbers.


586-595: Excellent validation consistency.

The added validation logic creates a consistent pattern with the new energy_reset_request method. Using the same ENERGY_NODE_TYPES constant and validation sequence improves code maintainability.

Note: Static analysis indicates the new validation logic lacks test coverage. Consider adding tests to verify MAC validation, node existence checks, and node type validation for both methods.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 586-587: plugwise_usb/network/init.py#L586-L587
Added lines #L586 - L587 were not covered by tests


[warning] 589-590: plugwise_usb/network/init.py#L589-L590
Added lines #L589 - L590 were not covered by tests


[warning] 592-593: plugwise_usb/network/init.py#L592-L593
Added lines #L592 - L593 were not covered by tests

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
plugwise_usb/network/__init__.py (1)

548-594: Consider extracting common validation logic for better maintainability.

Both energy_reset_request and set_energy_intervals share identical validation logic for MAC format, node existence, and node type checking. Consider extracting this into a private helper method to follow the DRY principle.

def_validate_energy_node(self, mac: str) ->None:
"""Validate node for energy operations."""ifnotvalidate_mac(mac):
raiseNodeError(f"MAC '{mac}' invalid")
ifmacnotinself._nodes:
raiseNodeError(f"Node {mac} not present in network")
ifself._nodes[mac].node_info.node_type.valuenotinENERGY_NODE_TYPES:
raiseNodeError(
f"Energy operations not supported for {self._nodes[mac].node_info.node_type.name}"
)

Then both methods can simply call self._validate_energy_node(mac) at the beginning.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 550-551: plugwise_usb/network/init.py#L550-L551
Added lines #L550 - L551 were not covered by tests


[warning] 553-554: plugwise_usb/network/init.py#L553-L554
Added lines #L553 - L554 were not covered by tests


[warning] 556-557: plugwise_usb/network/init.py#L556-L557
Added lines #L556 - L557 were not covered by tests


[warning] 561-562: plugwise_usb/network/init.py#L561-L562
Added lines #L561 - L562 were not covered by tests


[warning] 569-570: plugwise_usb/network/init.py#L569-L570
Added lines #L569 - L570 were not covered by tests


[warning] 572-573: plugwise_usb/network/init.py#L572-L573
Added lines #L572 - L573 were not covered by tests


[warning] 585-586: plugwise_usb/network/init.py#L585-L586
Added lines #L585 - L586 were not covered by tests


[warning] 588-589: plugwise_usb/network/init.py#L588-L589
Added lines #L588 - L589 were not covered by tests


[warning] 591-592: plugwise_usb/network/init.py#L591-L592
Added lines #L591 - L592 were not covered by tests

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e09f86 and b5b3069.

📒 Files selected for processing (2)
  • plugwise_usb/network/__init__.py (4 hunks)
  • pyproject.toml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pyproject.toml
🧰 Additional context used
🧬 Code Graph Analysis (1)
plugwise_usb/network/__init__.py (6)
plugwise_usb/api.py (3)
  • NodeType (57-68)
  • mac (270-271)
  • name (242-243)
plugwise_usb/helpers/util.py (1)
  • validate_mac (13-21)
plugwise_usb/exceptions.py (2)
  • MessageError (20-21)
  • NodeError (24-25)
plugwise_usb/messages/requests.py (19)
  • CircleClockSetRequest (730-783)
  • CircleMeasureIntervalRequest (1261-1292)
  • CirclePlusAllowJoiningRequest (453-484)
  • send (364-373)
  • send (386-395)
  • send (437-439)
  • send (475-484)
  • send (511-520)
  • send (541-552)
  • send (565-574)
  • send (587-596)
  • send (620-629)
  • send (666-675)
  • send (718-727)
  • send (774-783)
  • send (807-816)
  • send (847-856)
  • send (879-888)
  • response (124-128)
plugwise_usb/__init__.py (2)
  • energy_reset_request (213-225)
  • name (141-143)
plugwise_usb/messages/responses.py (1)
  • NodeResponseType (56-72)
🪛 GitHub Check: codecov/patch
plugwise_usb/network/__init__.py

[warning] 550-551: plugwise_usb/network/init.py#L550-L551
Added lines #L550 - L551 were not covered by tests


[warning] 553-554: plugwise_usb/network/init.py#L553-L554
Added lines #L553 - L554 were not covered by tests


[warning] 556-557: plugwise_usb/network/init.py#L556-L557
Added lines #L556 - L557 were not covered by tests


[warning] 561-562: plugwise_usb/network/init.py#L561-L562
Added lines #L561 - L562 were not covered by tests


[warning] 569-570: plugwise_usb/network/init.py#L569-L570
Added lines #L569 - L570 were not covered by tests


[warning] 572-573: plugwise_usb/network/init.py#L572-L573
Added lines #L572 - L573 were not covered by tests


[warning] 585-586: plugwise_usb/network/init.py#L585-L586
Added lines #L585 - L586 were not covered by tests


[warning] 588-589: plugwise_usb/network/init.py#L588-L589
Added lines #L588 - L589 were not covered by tests


[warning] 591-592: plugwise_usb/network/init.py#L591-L592
Added lines #L591 - L592 were not covered by tests

🔇 Additional comments (4)
plugwise_usb/network/__init__.py (4)

9-9: LGTM: Import additions are correct and necessary.

The new imports (UTC, validate_mac, CircleClockSetRequest) are properly placed and required for the energy reset functionality.

Also applies to: 16-16, 19-19, 21-21


37-37: Excellent: Addresses previous review feedback.

The constant ENERGY_NODE_TYPES successfully addresses the past review comment about creating a constant for supported node types (Circle+, Circle, Stealth). This improves maintainability and makes the supported types explicit.


548-576: LGTM: Well-implemented energy reset method with proper validation.

The implementation correctly addresses all the issues raised in past reviews:

  • ✅ Uses f-string formatting properly
  • ✅ Validates MAC format before use
  • ✅ Checks node existence before access
  • ✅ Uses the new ENERGY_NODE_TYPES constant
  • ✅ Proper error handling with meaningful messages
  • ✅ Consistent response validation using response.ack_id

The method follows established patterns in the codebase and integrates well with the existing architecture.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 550-551: plugwise_usb/network/init.py#L550-L551
Added lines #L550 - L551 were not covered by tests


[warning] 553-554: plugwise_usb/network/init.py#L553-L554
Added lines #L553 - L554 were not covered by tests


[warning] 556-557: plugwise_usb/network/init.py#L556-L557
Added lines #L556 - L557 were not covered by tests


[warning] 561-562: plugwise_usb/network/init.py#L561-L562
Added lines #L561 - L562 were not covered by tests


[warning] 569-570: plugwise_usb/network/init.py#L569-L570
Added lines #L569 - L570 were not covered by tests


[warning] 572-573: plugwise_usb/network/init.py#L572-L573
Added lines #L572 - L573 were not covered by tests


585-594: LGTM: Consistent validation pattern applied.

The addition of MAC validation, node existence check, and node type validation to set_energy_intervals maintains consistency with the new energy_reset_request method. The validation logic follows the same pattern and uses the shared ENERGY_NODE_TYPES constant.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 585-586: plugwise_usb/network/init.py#L585-L586
Added lines #L585 - L586 were not covered by tests


[warning] 588-589: plugwise_usb/network/init.py#L588-L589
Added lines #L588 - L589 were not covered by tests


[warning] 591-592: plugwise_usb/network/init.py#L591-L592
Added lines #L591 - L592 were not covered by tests

@bouwew
bouwew marked this pull request as ready for review June 3, 2025 10:28
@bouwew
bouwew requested a review from a team as a code ownerJune 3, 2025 10:28

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
plugwise_usb/__init__.py (1)

222-225: ⚠️ Potential issue

Fix unreachable code in return logic.

The current logic has an issue: since set_energy_intervals always returns True (line 235), the return False on line 225 is unreachable code.

Consider this fix:

 # Follow up by an energy-intervals (re)set
-if (result := await self.set_energy_intervals(mac, 60, 0)):- return result--return False+await self.set_energy_intervals(mac, 60, 0)+return True

This makes the intent clearer and removes the unreachable code.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 222-223: plugwise_usb/init.py#L222-L223
Added lines #L222 - L223 were not covered by tests


[warning] 225-225: plugwise_usb/init.py#L225
Added line #L225 was not covered by tests

🧹 Nitpick comments (1)
plugwise_usb/network/__init__.py (1)

596-607: Fix whitespace issues in validation method.

The validation logic is sound, but there are whitespace issues that need to be addressed.

Apply this diff to fix the whitespace issues:

 def _validate_energy_node(self, mac: str) -> None:
"""Validate node for energy operations."""
if not validate_mac(mac):
raise NodeError(f"MAC '{mac}' invalid")
-+
if mac not in self._nodes:
raise NodeError(f"Node {mac} not present in network")
-+
if self._nodes[mac].node_info.node_type.value not in ENERGY_NODE_TYPES:
raise NodeError(
f"Energy operations not supported for {self._nodes[mac].node_info.node_type.name}"
)
🧰 Tools
🪛 Ruff (0.11.9)

600-600: Blank line contains whitespace

Remove whitespace from blank line

(W293)


603-603: Blank line contains whitespace

Remove whitespace from blank line

(W293)

🪛 GitHub Check: codecov/patch

[warning] 598-599: plugwise_usb/network/init.py#L598-L599
Added lines #L598 - L599 were not covered by tests


[warning] 601-602: plugwise_usb/network/init.py#L601-L602
Added lines #L601 - L602 were not covered by tests


[warning] 604-605: plugwise_usb/network/init.py#L604-L605
Added lines #L604 - L605 were not covered by tests

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b5b3069 and b0c7518.

📒 Files selected for processing (6)
  • CHANGELOG.md (1 hunks)
  • plugwise_usb/__init__.py (3 hunks)
  • plugwise_usb/messages/requests.py (1 hunks)
  • plugwise_usb/network/__init__.py (5 hunks)
  • plugwise_usb/nodes/node.py (1 hunks)
  • pyproject.toml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • pyproject.toml
  • CHANGELOG.md
  • plugwise_usb/messages/requests.py
🧰 Additional context used
🧠 Learnings (1)
plugwise_usb/__init__.py (1)
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#252
File: plugwise_usb/__init__.py:0-0
Timestamp: 2025-06-03T09:58:44.592Z
Learning: In the plugwise_usb codebase, bouwew prefers not to add excessive validation decorators or protective error handling when they would amount to "too much protection". The existing validation level is considered sufficient.
🧬 Code Graph Analysis (1)
plugwise_usb/nodes/node.py (1)
plugwise_usb/nodes/helpers/firmware.py (1)
  • SupportedVersions (19-23)
🪛 GitHub Check: codecov/patch
plugwise_usb/__init__.py

[warning] 215-219: plugwise_usb/init.py#L215-L219
Added lines #L215 - L219 were not covered by tests


[warning] 222-223: plugwise_usb/init.py#L222-L223
Added lines #L222 - L223 were not covered by tests


[warning] 225-225: plugwise_usb/init.py#L225
Added line #L225 was not covered by tests

plugwise_usb/network/__init__.py

[warning] 550-552: plugwise_usb/network/init.py#L550-L552
Added lines #L550 - L552 were not covered by tests


[warning] 559-560: plugwise_usb/network/init.py#L559-L560
Added lines #L559 - L560 were not covered by tests


[warning] 562-563: plugwise_usb/network/init.py#L562-L563
Added lines #L562 - L563 were not covered by tests


[warning] 575-575: plugwise_usb/network/init.py#L575
Added line #L575 was not covered by tests


[warning] 598-599: plugwise_usb/network/init.py#L598-L599
Added lines #L598 - L599 were not covered by tests


[warning] 601-602: plugwise_usb/network/init.py#L601-L602
Added lines #L601 - L602 were not covered by tests


[warning] 604-605: plugwise_usb/network/init.py#L604-L605
Added lines #L604 - L605 were not covered by tests

plugwise_usb/nodes/node.py

[warning] 106-107: plugwise_usb/nodes/node.py#L106-L107
Added lines #L106 - L107 were not covered by tests


[warning] 109-109: plugwise_usb/nodes/node.py#L109
Added line #L109 was not covered by tests

🪛 Ruff (0.11.9)
plugwise_usb/network/__init__.py

600-600: Blank line contains whitespace

Remove whitespace from blank line

(W293)


603-603: Blank line contains whitespace

Remove whitespace from blank line

(W293)

🔇 Additional comments (3)
plugwise_usb/nodes/node.py (1)

103-109: LGTM! Clean property implementation.

The node_protocols property is well-implemented with proper documentation and clear logic.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 106-107: plugwise_usb/nodes/node.py#L106-L107
Added lines #L106 - L107 were not covered by tests


[warning] 109-109: plugwise_usb/nodes/node.py#L109
Added line #L109 was not covered by tests

plugwise_usb/network/__init__.py (2)

37-37: Good addition of the constant for maintainability.

The ENERGY_NODE_TYPES constant improves code maintainability by centralizing the supported node types for energy operations.


548-565: Well-implemented energy reset functionality.

The energy_reset_request method follows good patterns with proper validation, error handling, and clear logic flow.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 550-552: plugwise_usb/network/init.py#L550-L552
Added lines #L550 - L552 were not covered by tests


[warning] 559-560: plugwise_usb/network/init.py#L559-L560
Added lines #L559 - L560 were not covered by tests


[warning] 562-563: plugwise_usb/network/init.py#L562-L563
Added lines #L562 - L563 were not covered by tests

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
plugwise_usb/network/__init__.py (1)

595-607: Fix whitespace issues in validation method.

The validation method logic is correct and follows good practices by centralizing validation for energy operations. However, there are whitespace issues that need to be addressed.

Apply this diff to fix the whitespace issues:

 def _validate_energy_node(self, mac: str) -> None:
"""Validate node for energy operations."""
if not validate_mac(mac):
raise NodeError(f"MAC '{mac}' invalid")
-+
if mac not in self._nodes:
raise NodeError(f"Node {mac} not present in network")
-+
if self._nodes[mac].node_info.node_type.value not in ENERGY_NODE_TYPES:
raise NodeError(
f"Energy operations not supported for {self._nodes[mac].node_info.node_type.name}"
)
🧰 Tools
🪛 Ruff (0.11.9)

599-599: Blank line contains whitespace

Remove whitespace from blank line

(W293)


602-602: Blank line contains whitespace

Remove whitespace from blank line

(W293)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0c7518 and 71a8593.

📒 Files selected for processing (1)
  • plugwise_usb/network/__init__.py (5 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
plugwise_usb/network/__init__.py

599-599: Blank line contains whitespace

Remove whitespace from blank line

(W293)


602-602: Blank line contains whitespace

Remove whitespace from blank line

(W293)

🔇 Additional comments (4)
plugwise_usb/network/__init__.py (4)

9-9: LGTM: Import additions support the new energy reset functionality.

The new imports are appropriately added to support the energy reset feature:

  • UTC from datetime for timezone-aware timestamps
  • validate_mac for MAC address validation
  • CircleClockSetRequest for the energy reset request

Also applies to: 16-16, 19-19, 21-21


37-37: Good practice: Centralized constant for supported energy node types.

The ENERGY_NODE_TYPES constant improves maintainability by centralizing the definition of supported node types (Circle, Circle+, Stealth) and makes the code more readable.


548-566: Well-implemented energy reset functionality.

The energy_reset_request method is properly implemented with:

  • Appropriate validation using the helper method
  • Correct use of CircleClockSetRequest with reset flag
  • Proper error handling and response validation
  • Clear error messages

The implementation follows the established patterns in the codebase.


575-575: Good refactor: Consistent validation across energy methods.

Adding the _validate_energy_node(mac) call to set_energy_intervals improves consistency and reduces code duplication. Both energy-related methods now use the same validation logic.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
plugwise_usb/__init__.py (1)

214-230: ⚠️ Potential issue

Fix the unreachable code and potential NoneType error.

The method has two issues:

  1. Unreachable code: The set_energy_intervals method always returns True on success (see lines 236-240), making the return False statement unreachable.

  2. Potential AttributeError: If self._network is None, calling energy_reset_request on it will raise an AttributeError.

Apply this diff to fix the logic:

 async def energy_reset_request(self, mac: str) -> bool:
"""Send an energy-reset request to a Node."""
_LOGGER.debug("Resetting energy logs for %s", mac)
+ if self._network is None:+ raise NodeError("Network is not initialized")+
try:
await self._network.energy_reset_request(mac)
except (MessageError, NodeError) as exc:
raise NodeError(f"{exc}") from exc
# Follow up by an energy-intervals (re)set
- if (- result := await self.set_energy_intervals(- mac, DEFAULT_CONS_INTERVAL, NO_PRODUCTION_INTERVAL- )- ):- return result-- return False+ try:+ await self.set_energy_intervals(+ mac, DEFAULT_CONS_INTERVAL, NO_PRODUCTION_INTERVAL+ )+ return True+ except NodeError:+ # Energy reset succeeded but interval setting failed+ # Return True since the main operation succeeded+ return True
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 216-220: plugwise_usb/init.py#L216-L220
Added lines #L216 - L220 were not covered by tests


[warning] 223-223: plugwise_usb/init.py#L223
Added line #L223 was not covered by tests


[warning] 228-228: plugwise_usb/init.py#L228
Added line #L228 was not covered by tests


[warning] 230-230: plugwise_usb/init.py#L230
Added line #L230 was not covered by tests

🧹 Nitpick comments (1)
plugwise_usb/__init__.py (1)

214-230: Add test coverage for the new energy reset functionality.

The static analysis indicates that the new energy_reset_request method lacks test coverage, which is important for ensuring the energy reset functionality works correctly.

Would you like me to help generate unit tests for this new method to improve the test coverage?

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 216-220: plugwise_usb/init.py#L216-L220
Added lines #L216 - L220 were not covered by tests


[warning] 223-223: plugwise_usb/init.py#L223
Added line #L223 was not covered by tests


[warning] 228-228: plugwise_usb/init.py#L228
Added line #L228 was not covered by tests


[warning] 230-230: plugwise_usb/init.py#L230
Added line #L230 was not covered by tests

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ae404a and ede19eb.

📒 Files selected for processing (1)
  • plugwise_usb/__init__.py (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
plugwise_usb/__init__.py (1)
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#252
File: plugwise_usb/__init__.py:0-0
Timestamp: 2025-06-03T09:58:44.592Z
Learning: In the plugwise_usb codebase, bouwew prefers not to add excessive validation decorators or protective error handling when they would amount to "too much protection". The existing validation level is considered sufficient.
🪛 GitHub Check: codecov/patch
plugwise_usb/__init__.py

[warning] 216-220: plugwise_usb/init.py#L216-L220
Added lines #L216 - L220 were not covered by tests


[warning] 223-223: plugwise_usb/init.py#L223
Added line #L223 was not covered by tests


[warning] 228-228: plugwise_usb/init.py#L228
Added line #L228 was not covered by tests


[warning] 230-230: plugwise_usb/init.py#L230
Added line #L230 was not covered by tests

🔇 Additional comments (2)
plugwise_usb/__init__.py (2)

13-13: LGTM: Import reorganization looks good.

The typing imports reorganization and addition of the new constants are appropriate for the new functionality.

Also applies to: 17-17


317-317: Good cleanup: Trailing whitespace removal.

Good practice to remove trailing whitespace.

@bouwew

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai please suggest test code for the added lines of code.

@coderabbitai

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@bouwew I'll analyze the existing test structure and suggest comprehensive test code for the new energy reset functionality. Let me first examine the current test patterns.

Based on the codebase analysis, here are comprehensive test suggestions for the new energy reset functionality:

Test Code Suggestions

1. Tests for Stick.energy_reset_request() method

importpytestfromunittest.mockimportAsyncMock, MagicMock, patchfromplugwise_usbimportStickfromplugwise_usb.exceptionsimportMessageError, NodeErrorfromplugwise_usb.constantsimportDEFAULT_CONS_INTERVAL, NO_PRODUCTION_INTERVALclassTestStickEnergyReset:
@pytest.fixturedefstick(self):
"""Create a Stick instance for testing."""stick=Stick()
stick._network=AsyncMock()
returnstick@pytest.mark.asyncioasyncdeftest_energy_reset_request_success(self, stick):
"""Test successful energy reset request."""mac="000D6F0000A1B2C3"# Mock the network calls to succeedstick._network.energy_reset_request=AsyncMock()
stick.set_energy_intervals=AsyncMock(return_value=True)
result=awaitstick.energy_reset_request(mac)
# Verify the calls were made correctlystick._network.energy_reset_request.assert_called_once_with(mac)
stick.set_energy_intervals.assert_called_once_with(
mac, DEFAULT_CONS_INTERVAL, NO_PRODUCTION_INTERVAL
)
assertresultisTrue@pytest.mark.asyncioasyncdeftest_energy_reset_request_set_intervals_fails(self, stick):
"""Test energy reset when set_energy_intervals returns falsy value."""mac="000D6F0000A1B2C3"stick._network.energy_reset_request=AsyncMock()
stick.set_energy_intervals=AsyncMock(return_value=False)
result=awaitstick.energy_reset_request(mac)
stick._network.energy_reset_request.assert_called_once_with(mac)
stick.set_energy_intervals.assert_called_once_with(
mac, DEFAULT_CONS_INTERVAL, NO_PRODUCTION_INTERVAL
)
assertresultisFalse@pytest.mark.asyncioasyncdeftest_energy_reset_request_message_error(self, stick):
"""Test energy reset request handles MessageError."""mac="000D6F0000A1B2C3"stick._network.energy_reset_request=AsyncMock(side_effect=MessageError("Test message error"))
withpytest.raises(NodeError):
awaitstick.energy_reset_request(mac)
stick._network.energy_reset_request.assert_called_once_with(mac)
@pytest.mark.asyncioasyncdeftest_energy_reset_request_node_error(self, stick):
"""Test energy reset request handles NodeError."""mac="000D6F0000A1B2C3"stick._network.energy_reset_request=AsyncMock(side_effect=NodeError("Test node error"))
withpytest.raises(NodeError):
awaitstick.energy_reset_request(mac)
stick._network.energy_reset_request.assert_called_once_with(mac)
@pytest.mark.asyncioasyncdeftest_energy_reset_request_set_intervals_exception(self, stick):
"""Test energy reset when set_energy_intervals raises exception."""mac="000D6F0000A1B2C3"stick._network.energy_reset_request=AsyncMock()
stick.set_energy_intervals=AsyncMock(side_effect=NodeError("Intervals error"))
result=awaitstick.energy_reset_request(mac)
# Should return False when set_energy_intervals failsassertresultisFalse

2. Tests for StickNetwork.energy_reset_request() method

importpytestfromunittest.mockimportAsyncMock, MagicMock, patchfromdatetimeimportdatetime, timezonefromplugwise_usb.networkimportStickNetworkfromplugwise_usb.exceptionsimportMessageError, NodeErrorfromplugwise_usb.messages.requestsimportCircleClockSetRequestfromplugwise_usb.constantsimportCLOCK_ACCEPTEDclassTestStickNetworkEnergyReset:
@pytest.fixturedefnetwork(self):
"""Create a StickNetwork instance for testing."""network=StickNetwork()
network._validate_energy_node=MagicMock()
network._send_request=AsyncMock()
returnnetwork@pytest.mark.asyncioasyncdeftest_energy_reset_request_success(self, network):
"""Test successful energy reset request."""mac="000D6F0000A1B2C3"# Mock successful validation and responsemock_response=MagicMock()
mock_response.acknowledge_id=CLOCK_ACCEPTEDnetwork._send_request.return_value=mock_response# Mock node protocolswithpatch.object(network, '_nodes', {mac: MagicMock(node_protocols=MagicMock(max_protocol=2))}):
awaitnetwork.energy_reset_request(mac)
# Verify validation was callednetwork._validate_energy_node.assert_called_once_with(mac)
# Verify request was sentnetwork._send_request.assert_called_once()
args=network._send_request.call_args[0]
assertisinstance(args[0], CircleClockSetRequest)
@pytest.mark.asyncioasyncdeftest_energy_reset_request_no_response(self, network):
"""Test energy reset request when no response received."""mac="000D6F0000A1B2C3"network._send_request.return_value=Nonewithpatch.object(network, '_nodes', {mac: MagicMock(node_protocols=MagicMock(max_protocol=2))}):
withpytest.raises(NodeError, match="No response received"):
awaitnetwork.energy_reset_request(mac)
@pytest.mark.asyncioasyncdeftest_energy_reset_request_wrong_ack(self, network):
"""Test energy reset request with wrong acknowledgment ID."""mac="000D6F0000A1B2C3"mock_response=MagicMock()
mock_response.acknowledge_id="WRONG_ACK"network._send_request.return_value=mock_responsewithpatch.object(network, '_nodes', {mac: MagicMock(node_protocols=MagicMock(max_protocol=2))}):
withpytest.raises(MessageError):
awaitnetwork.energy_reset_request(mac)
@pytest.mark.asyncioasyncdeftest_energy_reset_request_validation_error(self, network):
"""Test energy reset request when validation fails."""mac="000D6F0000A1B2C3"network._validate_energy_node.side_effect=NodeError("Invalid node")
withpytest.raises(NodeError, match="Invalid node"):
awaitnetwork.energy_reset_request(mac)

3. Tests for StickNetwork._validate_energy_node() method

importpytestfromunittest.mockimportMagicMock, patchfromplugwise_usb.networkimportStickNetworkfromplugwise_usb.exceptionsimportNodeErrorfromplugwise_usb.constantsimportENERGY_NODE_TYPESclassTestStickNetworkValidation:
@pytest.fixturedefnetwork(self):
"""Create a StickNetwork instance for testing."""returnStickNetwork()
deftest_validate_energy_node_success(self, network):
"""Test successful energy node validation."""mac="000D6F0000A1B2C3"# Mock valid nodemock_node=MagicMock()
mock_node.type=1# Assuming 1 is in ENERGY_NODE_TYPESwithpatch('plugwise_usb.network.validate_mac') asmock_validate_mac:
withpatch.object(network, '_nodes', {mac: mock_node}):
network._validate_energy_node(mac)
mock_validate_mac.assert_called_once_with(mac)
deftest_validate_energy_node_invalid_mac(self, network):
"""Test validation with invalid MAC address."""mac="invalid_mac"withpatch('plugwise_usb.network.validate_mac', side_effect=ValueError("Invalid MAC")):
withpytest.raises(NodeError, match="Invalid MAC"):
network._validate_energy_node(mac)
deftest_validate_energy_node_not_present(self, network):
"""Test validation when node is not present."""mac="000D6F0000A1B2C3"withpatch('plugwise_usb.network.validate_mac'):
withpatch.object(network, '_nodes', {}):
withpytest.raises(NodeError, match="not present"):
network._validate_energy_node(mac)
deftest_validate_energy_node_wrong_type(self, network):
"""Test validation when node type is not allowed."""mac="000D6F0000A1B2C3"# Mock node with invalid typemock_node=MagicMock()
mock_node.type=99# Assuming 99 is not in ENERGY_NODE_TYPESwithpatch('plugwise_usb.network.validate_mac'):
withpatch.object(network, '_nodes', {mac: mock_node}):
withpytest.raises(NodeError, match="not included in the allowed energy node types"):
network._validate_energy_node(mac)
@pytest.mark.parametrize("node_type", [1, 2, 9]) # Based on ENERGY_NODE_TYPESdeftest_validate_energy_node_valid_types(self, network, node_type):
"""Test validation with all valid energy node types."""mac="000D6F0000A1B2C3"mock_node=MagicMock()
mock_node.type=node_typewithpatch('plugwise_usb.network.validate_mac'):
withpatch.object(network, '_nodes', {mac: mock_node}):
# Should not raise any exceptionnetwork._validate_energy_node(mac)

4. Tests for PlugwiseBaseNode.node_protocols property

importpytestfromplugwise_usb.nodes.nodeimportPlugwiseBaseNodeclassTestPlugwiseBaseNodeProtocols:
deftest_node_protocols_with_value(self):
"""Test node_protocols property when _node_protocols is set."""node=PlugwiseBaseNode()
mock_protocols="test_protocols"node._node_protocols=mock_protocolsassertnode.node_protocols==mock_protocolsdeftest_node_protocols_none(self):
"""Test node_protocols property when _node_protocols is None."""node=PlugwiseBaseNode()
node._node_protocols=Noneassertnode.node_protocolsisNonedeftest_node_protocols_unset(self):
"""Test node_protocols property when _node_protocols is not set."""node=PlugwiseBaseNode()
# Don't set _node_protocols attributeassertnode.node_protocolsisNone

5. Tests for modified CircleClockSetRequest

importpytestfromplugwise_usb.messages.requestsimportCircleClockSetRequestfromplugwise_usb.messages.typesimportLogAddrclassTestCircleClockSetRequestReset:
deftest_circle_clock_set_request_with_reset_true(self):
"""Test CircleClockSetRequest constructor when reset=True."""fromdatetimeimportdatetime, timezonenow=datetime.now(timezone.utc)
mac="000D6F0000A1B2C3"protocols=2request=CircleClockSetRequest(mac, now, protocols, reset=True)
# Verify that LogAddr was set to (0, 8, False) when reset=True# This test depends on the internal structure, may need adjustment# based on actual implementation detailsasserthasattr(request, 'log_addr') orhasattr(request, '_log_addr')
deftest_circle_clock_set_request_with_reset_false(self):
"""Test CircleClockSetRequest constructor when reset=False."""fromdatetimeimportdatetime, timezonenow=datetime.now(timezone.utc)
mac="000D6F0000A1B2C3"protocols=2request=CircleClockSetRequest(mac, now, protocols, reset=False)
# Verify normal behavior when reset=Falseasserthasattr(request, 'log_addr') orhasattr(request, '_log_addr')

6. Integration test for the complete flow

importpytestfromunittest.mockimportAsyncMock, MagicMock, patchfromplugwise_usbimportStickfromplugwise_usb.constantsimportDEFAULT_CONS_INTERVAL, NO_PRODUCTION_INTERVAL, CLOCK_ACCEPTEDclassTestEnergyResetIntegration:
@pytest.mark.asyncioasyncdeftest_full_energy_reset_flow(self):
"""Test the complete energy reset flow from Stick to StickNetwork."""stick=Stick()
mac="000D6F0000A1B2C3"# Mock all the dependencieswithpatch.object(stick, '_network') asmock_network:
withpatch.object(stick, 'set_energy_intervals', return_value=True) asmock_set_intervals:
# Mock successful network callmock_network.energy_reset_request=AsyncMock()
result=awaitstick.energy_reset_request(mac)
# Verify the full flowmock_network.energy_reset_request.assert_called_once_with(mac)
mock_set_intervals.assert_called_once_with(
mac, DEFAULT_CONS_INTERVAL, NO_PRODUCTION_INTERVAL
)
assertresultisTrue

These comprehensive tests cover:

  • Happy path scenarios for all new methods
  • Error handling (MessageError, NodeError exceptions)
  • Edge cases (missing nodes, wrong node types, invalid MACs)
  • Property behavior for the new node_protocols property
  • Modified constructor logic for CircleClockSetRequest
  • Integration testing for the complete flow
  • Parameterized tests for different valid node types

The tests use pytest async features, mocking, and parametrization to ensure comprehensive coverage of the new energy reset functionality. This should significantly improve the patch coverage from the current 35.89%.


🏁 Script executed:

#!/bin/bash
# Find and examine existing test files to understand the testing patterns
fd "test.*\.py$" --type f | head -10

Length of output: 81


🏁 Script executed:

#!/bin/bash
# Look for test files specifically related to the modules that were changed
fd "test.*\.py$" --type f | xargs grep -l -E "(Stick|StickNetwork|PlugwiseBaseNode|CircleClockSetRequest)" | head -5

Length of output: 161


🏁 Script executed:

#!/bin/bash
# Examine the structure of existing tests for the Stick class
find . -name "test*.py" -exec grep -l "class.*Test.*Stick\|def test.*stick\|def test.*energy" {} \; | head -3

Length of output: 134


🏁 Script executed:

#!/bin/bash
# Get a sample of existing test file to understand the testing structure
fd "test.*\.py$" --type f | head -1 | xargs cat | head -50

Length of output: 1663


🏁 Script executed:

#!/bin/bash
# Look for existing test directories and their structure
find . -type d -name "*test*" | head -10

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Check for pytest or unittest patterns in the repository
rg "import pytest|import unittest|from unittest|@pytest\." --type py | head -10

Length of output: 524


🏁 Script executed:

#!/bin/bash
# Look at the overall test directory structure
find . -name "test*" -type f | head -15

Length of output: 93


🏁 Script executed:

#!/bin/bash
# Examine one of the main test files to understand imports and setup
fd "test.*\.py$" --type f | head -1 | xargs head -30

Length of output: 991


Now I have a better understanding of the existing test structure. Let me provide test code suggestions that match the project's testing patterns:

Test Code Suggestions for New Energy Reset Functionality

Based on the existing test structure in tests/test_usb.py, here are comprehensive test suggestions that follow the project's patterns:

Add to tests/test_usb.py

# Add these test methods to the existing TestStick classclassTestStick:
# ... existing tests ...@pytest.mark.asyncioasyncdeftest_energy_reset_request_success(self):
"""Test successful energy reset request."""mac="000D6F0000A1B2C3"stick=Stick("dummy")
# Mock the network and set_energy_intervals methodstick._network=Mock()
stick._network.energy_reset_request=Mock()
withpatch.object(stick, 'set_energy_intervals', return_value=True) asmock_intervals:
result=awaitstick.energy_reset_request(mac)
# Verify calls were madestick._network.energy_reset_request.assert_called_once_with(mac)
mock_intervals.assert_called_once_with(mac, 60, 0) # DEFAULT_CONS_INTERVAL, NO_PRODUCTION_INTERVALassertresultisTrue@pytest.mark.asyncioasyncdeftest_energy_reset_request_intervals_fail(self):
"""Test energy reset when setting intervals fails."""mac="000D6F0000A1B2C3"stick=Stick("dummy")
stick._network=Mock()
stick._network.energy_reset_request=Mock()
withpatch.object(stick, 'set_energy_intervals', return_value=False) asmock_intervals:
result=awaitstick.energy_reset_request(mac)
stick._network.energy_reset_request.assert_called_once_with(mac)
mock_intervals.assert_called_once_with(mac, 60, 0)
assertresultisFalse@pytest.mark.asyncioasyncdeftest_energy_reset_request_message_error(self):
"""Test energy reset handles MessageError."""fromplugwise_usb.exceptionsimportMessageError, NodeErrormac="000D6F0000A1B2C3"stick=Stick("dummy")
stick._network=Mock()
stick._network.energy_reset_request=Mock(side_effect=MessageError("Test error"))
withpytest.raises(NodeError):
awaitstick.energy_reset_request(mac)
@pytest.mark.asyncioasyncdeftest_energy_reset_request_node_error(self):
"""Test energy reset re-raises NodeError."""fromplugwise_usb.exceptionsimportNodeErrormac="000D6F0000A1B2C3"stick=Stick("dummy")
stick._network=Mock()
stick._network.energy_reset_request=Mock(side_effect=NodeError("Test error"))
withpytest.raises(NodeError):
awaitstick.energy_reset_request(mac)
@pytest.mark.asyncioasyncdeftest_energy_reset_request_intervals_exception(self):
"""Test energy reset when set_energy_intervals raises exception."""fromplugwise_usb.exceptionsimportNodeErrormac="000D6F0000A1B2C3"stick=Stick("dummy")
stick._network=Mock()
stick._network.energy_reset_request=Mock()
withpatch.object(stick, 'set_energy_intervals', side_effect=NodeError("Intervals error")):
result=awaitstick.energy_reset_request(mac)
assertresultisFalse

Add to tests/test_usb.py for StickNetwork tests

# Add these test methods to test StickNetwork functionalityclassTestStickNetwork:
"""Test class for StickNetwork energy reset functionality."""@pytest.mark.asyncioasyncdeftest_network_energy_reset_request_success(self):
"""Test successful network energy reset request."""fromplugwise_usb.networkimportStickNetworkfromplugwise_usb.constantsimportCLOCK_ACCEPTEDfromdatetimeimportdatetime, timezonemac="000D6F0000A1B2C3"network=StickNetwork()
# Mock node with valid typemock_node=Mock()
mock_node.type=1# Valid energy node typemock_node.node_protocols=Mock()
mock_node.node_protocols.max_protocol=2network._nodes= {mac: mock_node}
# Mock successful responsemock_response=Mock()
mock_response.acknowledge_id=CLOCK_ACCEPTEDwithpatch('plugwise_usb.network.validate_mac'):
withpatch.object(network, '_send_request', return_value=mock_response):
awaitnetwork.energy_reset_request(mac)
@pytest.mark.asyncioasyncdeftest_network_energy_reset_request_no_response(self):
"""Test network energy reset when no response received."""fromplugwise_usb.networkimportStickNetworkfromplugwise_usb.exceptionsimportNodeErrormac="000D6F0000A1B2C3"network=StickNetwork()
mock_node=Mock()
mock_node.type=1mock_node.node_protocols=Mock()
mock_node.node_protocols.max_protocol=2network._nodes= {mac: mock_node}
withpatch('plugwise_usb.network.validate_mac'):
withpatch.object(network, '_send_request', return_value=None):
withpytest.raises(NodeError, match="No response received"):
awaitnetwork.energy_reset_request(mac)
@pytest.mark.asyncioasyncdeftest_network_energy_reset_request_wrong_ack(self):
"""Test network energy reset with wrong acknowledgment."""fromplugwise_usb.networkimportStickNetworkfromplugwise_usb.exceptionsimportMessageErrormac="000D6F0000A1B2C3"network=StickNetwork()
mock_node=Mock()
mock_node.type=1mock_node.node_protocols=Mock()
mock_node.node_protocols.max_protocol=2network._nodes= {mac: mock_node}
mock_response=Mock()
mock_response.acknowledge_id="WRONG_ACK"withpatch('plugwise_usb.network.validate_mac'):
withpatch.object(network, '_send_request', return_value=mock_response):
withpytest.raises(MessageError):
awaitnetwork.energy_reset_request(mac)
deftest_validate_energy_node_success(self):
"""Test successful energy node validation."""fromplugwise_usb.networkimportStickNetworkmac="000D6F0000A1B2C3"network=StickNetwork()
mock_node=Mock()
mock_node.type=1# Valid energy node typenetwork._nodes= {mac: mock_node}
withpatch('plugwise_usb.network.validate_mac'):
# Should not raise any exceptionnetwork._validate_energy_node(mac)
deftest_validate_energy_node_invalid_mac(self):
"""Test validation with invalid MAC address."""fromplugwise_usb.networkimportStickNetworkfromplugwise_usb.exceptionsimportNodeErrormac="invalid_mac"network=StickNetwork()
withpatch('plugwise_usb.network.validate_mac', side_effect=ValueError("Invalid MAC")):
withpytest.raises(NodeError, match="Invalid MAC"):
network._validate_energy_node(mac)
deftest_validate_energy_node_not_present(self):
"""Test validation when node is not present."""fromplugwise_usb.networkimportStickNetworkfromplugwise_usb.exceptionsimportNodeErrormac="000D6F0000A1B2C3"network=StickNetwork()
network._nodes= {}
withpatch('plugwise_usb.network.validate_mac'):
withpytest.raises(NodeError, match="not present"):
network._validate_energy_node(mac)
deftest_validate_energy_node_wrong_type(self):
"""Test validation when node type is not allowed."""fromplugwise_usb.networkimportStickNetworkfromplugwise_usb.exceptionsimportNodeErrormac="000D6F0000A1B2C3"network=StickNetwork()
mock_node=Mock()
mock_node.type=99# Invalid energy node typenetwork._nodes= {mac: mock_node}
withpatch('plugwise_usb.network.validate_mac'):
withpytest.raises(NodeError, match="not included in the allowed energy node types"):
network._validate_energy_node(mac)
@pytest.mark.parametrize("node_type", [1, 2, 9])deftest_validate_energy_node_valid_types(self, node_type):
"""Test validation with all valid energy node types."""fromplugwise_usb.networkimportStickNetworkmac="000D6F0000A1B2C3"network=StickNetwork()
mock_node=Mock()
mock_node.type=node_typenetwork._nodes= {mac: mock_node}
withpatch('plugwise_usb.network.validate_mac'):
# Should not raise any exceptionnetwork._validate_energy_node(mac)

Add to tests/test_usb.py for node protocols property

classTestPlugwiseBaseNode:
"""Test class for PlugwiseBaseNode node_protocols property."""deftest_node_protocols_with_value(self):
"""Test node_protocols property when _node_protocols is set."""fromplugwise_usb.nodes.nodeimportPlugwiseBaseNodenode=PlugwiseBaseNode()
mock_protocols=Mock()
node._node_protocols=mock_protocolsassertnode.node_protocols==mock_protocolsdeftest_node_protocols_none(self):
"""Test node_protocols property when _node_protocols is None."""fromplugwise_usb.nodes.nodeimportPlugwiseBaseNodenode=PlugwiseBaseNode()
node._node_protocols=Noneassertnode.node_protocolsisNonedeftest_node_protocols_unset(self):
"""Test node_protocols property when _node_protocols is not set."""fromplugwise_usb.nodes.nodeimportPlugwiseBaseNodenode=PlugwiseBaseNode()
# Don't set _node_protocols attributeassertnode.node_protocolsisNone

Add to tests/test_usb.py for CircleClockSetRequest modification

classTestCircleClockSetRequest:
"""Test class for CircleClockSetRequest reset functionality."""deftest_circle_clock_set_request_with_reset_true(self):
"""Test CircleClockSetRequest constructor when reset=True."""fromplugwise_usb.messages.requestsimportCircleClockSetRequestfromdatetimeimportdatetime, timezonenow=datetime.now(timezone.utc)
mac="000D6F0000A1B2C3"protocols=2# Test that no exception is raised when reset=Truerequest=CircleClockSetRequest(mac, now, protocols, reset=True)
assertrequestisnotNonedeftest_circle_clock_set_request_with_reset_false(self):
"""Test CircleClockSetRequest constructor when reset=False (default)."""fromplugwise_usb.messages.requestsimportCircleClockSetRequestfromdatetimeimportdatetime, timezonenow=datetime.now(timezone.utc)
mac="000D6F0000A1B2C3"protocols=2# Test that no exception is raised when reset=False (default behavior)request=CircleClockSetRequest(mac, now, protocols, reset=False)
assertrequestisnotNone

Add response message data to tests/stick_test_data.py

# Add to the RESPONSE_MESSAGES dictionary in stick_test_data.py# Energy reset request success responseb"\x05\x05\x03\x03[energy_reset_request_hex]\r\n": (
"Energy Reset Success",
b"000000C1", # Success ackb"0061"# msg_id for clock set response+b"000D6F0000A1B2C3"# mac+b"00AC", # CLOCK_ACCEPTED acknowledgment
),
# Energy reset request failure response b"\x05\x05\x03\x03[energy_reset_failure_hex]\r\n": (
"Energy Reset Failure",
b"000000C1", # Success ackb"0061"# msg_id for clock set response+b"000D6F0000A1B2C3"# mac+b"00C1", # Different acknowledgment (failure)
),

Key Testing Considerations

  1. Pattern Matching: The tests follow the existing project patterns using pytest, Mock objects, and async testing.

  2. Coverage Areas: These tests cover:

    • ✅ Happy path scenarios for energy reset
    • ✅ Error handling (MessageError, NodeError)
    • ✅ Node validation logic
    • ✅ Property behavior
    • ✅ Modified constructor logic
  3. Mocking Strategy: Uses the same mocking approach as existing tests with unittest.mock.Mock and patch.

  4. Test Data: Follows the pattern of using predefined test data from stick_test_data.py.

  5. Async Testing: Uses @pytest.mark.asyncio decorator matching existing async test patterns.

These tests should significantly improve the patch coverage from 35.89% to cover the 25 missing lines identified by codecov. The tests are designed to integrate seamlessly with the existing test suite structure.


🏁 Script executed:

#!/bin/bash
# Examine the main test file to understand the testing patterns and structure
cat tests/test_usb.py

Length of output: 65560


Now I have a complete understanding of the test structure. Based on the existing patterns in tests/test_usb.py, here are the test code suggestions that match the project's testing style:

Test Code Suggestions for Energy Reset Functionality

1. Add to the existing TestStick class in tests/test_usb.py

@pytest.mark.asyncioasyncdeftest_energy_reset_request_success(self, monkeypatch: pytest.MonkeyPatch) ->None:
"""Test successful energy reset request."""mock_serial=MockSerial(None)
monkeypatch.setattr(
pw_connection_manager,
"create_serial_connection", mock_serial.mock_connection,
)
monkeypatch.setattr(pw_sender, "STICK_TIME_OUT", 0.2)
monkeypatch.setattr(pw_requests, "NODE_TIME_OUT", 1.0)
stick=pw_stick.Stick("test_port", cache_enabled=False)
awaitstick.connect()
awaitstick.initialize()
awaitstick.discover_nodes(load=False)
# Load the circle plus node for testingassertawaitstick.nodes["0098765432101234"].load()
# Test energy reset requestresult=awaitstick.energy_reset_request("0098765432101234")
assertresultisTrueawaitstick.disconnect()
@pytest.mark.asyncioasyncdeftest_energy_reset_request_invalid_node(self, monkeypatch: pytest.MonkeyPatch) ->None:
"""Test energy reset request with invalid node type."""mock_serial=MockSerial(None)
monkeypatch.setattr(
pw_connection_manager,
"create_serial_connection",
mock_serial.mock_connection,
)
stick=pw_stick.Stick("test_port", cache_enabled=False)
awaitstick.connect()
awaitstick.initialize()
awaitstick.discover_nodes(load=False)
# Try energy reset on Scan node (invalid energy node type)withpytest.raises(pw_exceptions.NodeError):
awaitstick.energy_reset_request("5555555555555555")
awaitstick.disconnect()
@pytest.mark.asyncioasyncdeftest_energy_reset_request_node_not_found(self, monkeypatch: pytest.MonkeyPatch) ->None:
"""Test energy reset request with non-existent node."""mock_serial=MockSerial(None)
monkeypatch.setattr(
pw_connection_manager,
"create_serial_connection",
mock_serial.mock_connection,
)
stick=pw_stick.Stick("test_port", cache_enabled=False)
awaitstick.connect()
awaitstick.initialize()
# Try energy reset on non-existent nodewithpytest.raises(pw_exceptions.NodeError):
awaitstick.energy_reset_request("AAAAAAAAAAAAAAAA")
awaitstick.disconnect()
@pytest.mark.asyncioasyncdeftest_energy_reset_request_set_intervals_fails(self, monkeypatch: pytest.MonkeyPatch) ->None:
"""Test energy reset when set_energy_intervals fails."""mock_serial=MockSerial(None)
monkeypatch.setattr(
pw_connection_manager,
"create_serial_connection",
mock_serial.mock_connection,
)
stick=pw_stick.Stick("test_port", cache_enabled=False)
awaitstick.connect()
awaitstick.initialize()
awaitstick.discover_nodes(load=False)
# Load the circle plus nodeassertawaitstick.nodes["0098765432101234"].load()
# Mock set_energy_intervals to return Falseasyncdefmock_set_energy_intervals(mac: str, consumption: int, production: int) ->bool:
returnFalsemonkeypatch.setattr(stick, "set_energy_intervals", mock_set_energy_intervals)
result=awaitstick.energy_reset_request("0098765432101234")
assertresultisFalseawaitstick.disconnect()
@pytest.mark.asyncioasyncdeftest_energy_reset_request_message_error(self, monkeypatch: pytest.MonkeyPatch) ->None:
"""Test energy reset handles MessageError from network."""mock_serial=MockSerial(None)
monkeypatch.setattr(
pw_connection_manager,
"create_serial_connection",
mock_serial.mock_connection,
)
stick=pw_stick.Stick("test_port", cache_enabled=False)
awaitstick.connect()
awaitstick.initialize()
awaitstick.discover_nodes(load=False)
# Load the circle plus nodeassertawaitstick.nodes["0098765432101234"].load()
# Mock network to raise MessageErrorasyncdefmock_energy_reset_request(mac: str) ->None:
raisepw_exceptions.MessageError("Test message error")
monkeypatch.setattr(stick._network, "energy_reset_request", mock_energy_reset_request)
withpytest.raises(pw_exceptions.NodeError):
awaitstick.energy_reset_request("0098765432101234")
awaitstick.disconnect()

2. Add test for node_protocols property

deftest_node_protocols_property(self, monkeypatch: pytest.MonkeyPatch) ->None:
"""Test node_protocols property on PlugwiseBaseNode."""# Test with protocols setnode=pw_node.PlugwiseBaseNode()
mock_protocols=Mock()
node._node_protocols=mock_protocolsassertnode.node_protocols==mock_protocols# Test with protocols as Nonenode._node_protocols=Noneassertnode.node_protocolsisNone# Test with protocols not setnode_empty=pw_node.PlugwiseBaseNode()
assertnode_empty.node_protocolsisNone

3. Add to tests/stick_test_data.py - Response messages for energy reset

# Add these entries to the RESPONSE_MESSAGES dictionary# Energy reset request response (CircleClockSetRequest with reset=True)b"\x05\x05\x03\x030060000D6F0000A1B2C3626268000000BEEF\r\n": (
"Energy Reset Request",
b"000000C1", # Success ackb"0061"# msg_id for CircleClockSetResponse+b"000D6F0000A1B2C3"# mac+b"00AC", # CLOCK_ACCEPTED acknowledgment
),
# Energy reset request failure responseb"\x05\x05\x03\x030060000D6F0000A1B2C3626268000000DEAD\r\n": (
"Energy Reset Request Failure", b"000000C1", # Success ackb"0061"# msg_id for CircleClockSetResponse+b"000D6F0000A1B2C3"# mac+b"00C1", # Different acknowledgment (failure)
),
# Set energy intervals request after resetb"\x05\x05\x03\x03004C000D6F0000A1B2C3003C0000ABCD\r\n": (
"Set Energy Intervals After Reset",
b"000000C1", # Success ackb"004D"# msg_id for CircleEnergyIntervalResponse +b"000D6F0000A1B2C3"# mac+b"00AC", # Success acknowledgment
),

4. Add comprehensive network-level tests

@pytest.mark.asyncioasyncdeftest_network_energy_reset_validation(self, monkeypatch: pytest.MonkeyPatch) ->None:
"""Test StickNetwork energy reset validation."""mock_serial=MockSerial(None)
monkeypatch.setattr(
pw_connection_manager,
"create_serial_connection",
mock_serial.mock_connection,
)
stick=pw_stick.Stick("test_port", cache_enabled=False)
awaitstick.connect()
awaitstick.initialize()
awaitstick.discover_nodes(load=False)
# Test validation with invalid MACwithpytest.raises(pw_exceptions.NodeError):
awaitstick._network.energy_reset_request("invalid_mac")
# Test validation with non-existent nodewithpytest.raises(pw_exceptions.NodeError):
awaitstick._network.energy_reset_request("AAAAAAAAAAAAAAAA")
# Test validation with wrong node type (Scan node)# First ensure the scan node is discoveredmock_serial.inject_message(b"004F555555555555555500", b"FFFE")
awaitasyncio.sleep(0.1) # Allow time for node discoverywithpytest.raises(pw_exceptions.NodeError):
awaitstick._network.energy_reset_request("5555555555555555")
awaitstick.disconnect()
@pytest.mark.asyncioasyncdeftest_set_energy_intervals_with_validation(self, monkeypatch: pytest.MonkeyPatch) ->None:
"""Test that set_energy_intervals now includes validation."""mock_serial=MockSerial(None)
monkeypatch.setattr(
pw_connection_manager,
"create_serial_connection",
mock_serial.mock_connection,
)
stick=pw_stick.Stick("test_port", cache_enabled=False)
awaitstick.connect()
awaitstick.initialize()
awaitstick.discover_nodes(load=False)
# Load valid energy nodeassertawaitstick.nodes["0098765432101234"].load()
# Test successful validation and intervals settingawaitstick.set_energy_intervals("0098765432101234", 60, 0)
# Test validation failure with wrong node typemock_serial.inject_message(b"004F555555555555555500", b"FFFE")
awaitasyncio.sleep(0.1)
withpytest.raises(pw_exceptions.NodeError):
awaitstick.set_energy_intervals("5555555555555555", 60, 0)
awaitstick.disconnect()

5. Add CircleClockSetRequest modification test

deftest_circle_clock_set_request_reset_parameter(self) ->None:
"""Test CircleClockSetRequest with reset parameter."""fromdatetimeimportdatetime, timezone# Test that reset parameter doesn't cause errorsnow=datetime.now(timezone.utc)
mac=b"000D6F0000A1B2C3"protocols=2# Test with reset=Truerequest_reset=pw_requests.CircleClockSetRequest(
self.dummy_fn, mac, now, protocols, reset=True
)
assertrequest_resetisnotNone# Test with reset=False (default)request_normal=pw_requests.CircleClockSetRequest(
self.dummy_fn, mac, now, protocols, reset=False
)
assertrequest_normalisnotNone# Test without reset parameter (should default to False)request_default=pw_requests.CircleClockSetRequest(
self.dummy_fn, mac, now, protocols
)
assertrequest_defaultisnotNone

6. Add integration test for complete energy reset flow

@pytest.mark.asyncioasyncdeftest_complete_energy_reset_flow(self, monkeypatch: pytest.MonkeyPatch) ->None:
"""Test complete energy reset flow from Stick to Network."""mock_serial=MockSerial(None)
monkeypatch.setattr(
pw_connection_manager,
"create_serial_connection",
mock_serial.mock_connection,
)
monkeypatch.setattr(pw_sender, "STICK_TIME_OUT", 0.2)
monkeypatch.setattr(pw_requests, "NODE_TIME_OUT", 1.0)
stick=pw_stick.Stick("test_port", cache_enabled=False)
awaitstick.connect()
awaitstick.initialize()
awaitstick.discover_nodes(load=False)
# Load energy nodeassertawaitstick.nodes["0098765432101234"].load()
# Verify node has energy features and correct typeassertpw_api.NodeFeature.ENERGYinstick.nodes["0098765432101234"].featuresassertstick.nodes["0098765432101234"].typeinpw_constants.ENERGY_NODE_TYPES# Test complete energy reset flowresult=awaitstick.energy_reset_request("0098765432101234")
assertresultisTrueawaitstick.disconnect()

7. Add error handling tests for edge cases

@pytest.mark.asyncioasyncdeftest_energy_reset_request_timeout(self, monkeypatch: pytest.MonkeyPatch) ->None:
"""Test energy reset request timeout handling."""mock_serial=MockSerial(
{
# Mock timeout response for energy resetb"\x05\x05\x03\x030060000D6F0000A1B2C3626268000000": (
"Energy Reset Timeout",
b"000000E1", # Timeout ackNone,
),
}
)
monkeypatch.setattr(
pw_connection_manager,
"create_serial_connection",
mock_serial.mock_connection,
)
monkeypatch.setattr(pw_sender, "STICK_TIME_OUT", 0.1)
stick=pw_stick.Stick("test_port", cache_enabled=False)
awaitstick.connect()
awaitstick.initialize()
awaitstick.discover_nodes(load=False)
# Load valid energy nodeassertawaitstick.nodes["0098765432101234"].load()
# Test should handle timeout gracefullywithpytest.raises(pw_exceptions.NodeError):
awaitstick.energy_reset_request("0098765432101234")
awaitstick.disconnect()
@pytest.mark.asyncioasyncdeftest_energy_reset_constants_usage(self, monkeypatch: pytest.MonkeyPatch) ->None:
"""Test that energy reset uses correct constants."""mock_serial=MockSerial(None)
monkeypatch.setattr(
pw_connection_manager,
"create_serial_connection",
mock_serial.mock_connection,
)
stick=pw_stick.Stick("test_port", cache_enabled=False)
awaitstick.connect()
awaitstick.initialize()
awaitstick.discover_nodes(load=False)
# Load valid energy nodeassertawaitstick.nodes["0098765432101234"].load()
# Mock set_energy_intervals to capture the parameterscaptured_params= {}
asyncdefmock_set_energy_intervals(mac: str, consumption: int, production: int) ->bool:
captured_params['mac'] =maccaptured_params['consumption'] =consumptioncaptured_params['production'] =productionreturnTruemonkeypatch.setattr(stick, "set_energy_intervals", mock_set_energy_intervals)
awaitstick.energy_reset_request("0098765432101234")
# Verify correct constants are usedassertcaptured_params['consumption'] ==pw_constants.DEFAULT_CONS_INTERVAL# 60assertcaptured_params['production'] ==pw_constants.NO_PRODUCTION_INTERVAL# 0awaitstick.disconnect()

Key Test Coverage Points

These comprehensive tests cover:

Happy path scenarios - Successful energy reset with proper flow
Error handling - MessageError, NodeError, timeouts
Validation logic - Invalid MACs, wrong node types, missing nodes
Integration testing - Complete flow from Stick to StickNetwork
Constants usage - Proper use of DEFAULT_CONS_INTERVAL and NO_PRODUCTION_INTERVAL
Property behavior - node_protocols property functionality
Request modification - CircleClockSetRequest reset parameter
Edge cases - Timeouts, communication failures, validation errors

The tests follow the existing project patterns:

  • Use pytest.mark.asyncio for async tests
  • Use MockSerial for communication simulation
  • Use monkeypatch for dependency injection
  • Follow the existing naming conventions and class structure
  • Include proper setup/teardown with connect/disconnect
  • Use the same import patterns with importlib.import_module

This should significantly improve the patch coverage from 35.89% to cover most of the 25 missing lines identified by codecov.

@bouwew

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai please correct the code to add to stick_test_data.py to using 1111111111111111 as mac address.
Also, Energy reset request response (CircleClockSetRequest with reset=True) should start with the b"0016" _identifier.
And, set energy intervals request after reset, should start with the b"0057" _identifier.

@coderabbitai

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@bouwew I'll examine the current stick_test_data.py structure and provide the corrected test data with your specifications.

Based on the test data structure, here's the corrected code for tests/stick_test_data.py:

Corrected Test Data for tests/stick_test_data.py

Add these entries to the RESPONSE_MESSAGES dictionary:

# Energy reset request response (CircleClockSetRequest with reset=True)b"\x05\x05\x03\x03001611111111111111110000000000000000BEEF\r\n": (
"Energy Reset Request",
b"000000C1", # Success ackb"0061"# msg_id for CircleClockSetResponse+b"1111111111111111"# mac+b"00AC", # CLOCK_ACCEPTED acknowledgment
),
# Energy reset request failure responseb"\x05\x05\x03\x03001611111111111111110000000000000000DEAD\r\n": (
"Energy Reset Request Failure",
b"000000C1", # Success ackb"0061"# msg_id for CircleClockSetResponse+b"1111111111111111"# mac+b"00C1", # Different acknowledgment (failure)
),
# Set energy intervals request after reset b"\x05\x05\x03\x0300571111111111111111003C0000ABCD\r\n": (
"Set Energy Intervals After Reset",
b"000000C1", # Success ackb"004D"# msg_id for CircleEnergyIntervalResponse+b"1111111111111111"# mac+b"00AC", # Success acknowledgment
),

The key corrections made:

  • ✅ Changed MAC address to 1111111111111111 as requested
  • ✅ Energy reset request now starts with b"0016" identifier
  • ✅ Set energy intervals request now starts with b"0057" identifier
  • ✅ Maintained the proper hex encoding format used in the existing test data
  • ✅ Kept the response message structure consistent with existing patterns

These test data entries should now properly support testing the energy reset functionality with the correct identifiers and MAC address format.


🏁 Script executed:

#!/bin/bash
# Examine the structure of stick_test_data.py to understand the format
cat tests/stick_test_data.py | head -50

Length of output: 1644


🏁 Script executed:

#!/bin/bash
# Look for examples of identifiers 0016 and 0057 in the test data
grep -A 5 -B 5 "0016\|0057" tests/stick_test_data.py

Length of output: 1265


@bouwew Thank you for the clarification! Now I can see the correct structure from the existing test data. I notice that identifier 0016 is already used for "Clock set" operations in the PARTLY_RESPONSE_MESSAGES dictionary, and there's already an entry for MAC 1111111111111111.

Here's the corrected code for tests/stick_test_data.py:

Corrected Test Data for tests/stick_test_data.py

Add to the RESPONSE_MESSAGES dictionary:

# Energy reset request (CircleClockSetRequest with reset=True) - complete messageb"\x05\x05\x03\x03001611111111111111110000000000000000BEEF\r\n": (
"Energy Reset Request Complete",
b"000000C1", # Success ackb"0061"# msg_id for CircleClockSetResponse+b"1111111111111111"# mac+b"00D7", # CLOCK_ACCEPTED acknowledgment (matching existing pattern)
),
# Set energy intervals request after resetb"\x05\x05\x03\x0300571111111111111111003C0000ABCD\r\n": (
"Set Energy Intervals After Reset",
b"000000C1", # Success ackb"004D"# msg_id for CircleEnergyIntervalResponse+b"1111111111111111"# mac+b"00AC", # Success acknowledgment
),

The existing entry in PARTLY_RESPONSE_MESSAGES already covers the partial match:

b"\x05\x05\x03\x0300161111111111111111": (
"Clock set 1111111111111111",
b"000000C1", # Success ackb"0000"+b"00D7"+b"1111111111111111", # msg_id, ClockAccepted, mac
),

Key corrections made:

  • ✅ Using MAC address 1111111111111111 as requested
  • ✅ Energy reset requests use identifier 0016 (already exists in PARTLY_RESPONSE_MESSAGES)
  • ✅ Set energy intervals requests use identifier 0057
  • ✅ Using b"00D7" for CLOCK_ACCEPTED to match existing test data pattern
  • ✅ Maintaining the proper hex encoding format and response structure

The existing PARTLY_RESPONSE_MESSAGES entry for 0016 with MAC 1111111111111111 will handle partial matches for energy reset requests, while the new RESPONSE_MESSAGES entries will handle complete message scenarios.

@sonarqubecloud

Copy link
Copy Markdown

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@bouwew