Skip to content

Conversation

@bouwew
Copy link
Contributor

@bouwew bouwew commented Dec 10, 2024

All changes:

  • Add handling and testing for error-xml received and empty-xml received after a reboot for P1.
  • Fix an (up to now hidden bug in the ) P1 smartmeter info collection.
  • Implement extra checks for receiving incomplete xml-data for legacy (in legacy/smile.py, no specific tests implemented due to focus on the code covering non-legacy devices).
  • Small continued improvements

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Introduced new methods for managing smart meter information and sorting gateway entities.
    • Added a new exception for handling missing data in the legacy API.
    • Enhanced error messages for missing data in the main API.
    • Added new XML files for error handling and reboot scenarios.
    • Introduced a new constant for prioritizing device classes.
  • Bug Fixes

    • Improved error handling in tests for specific error conditions related to the Plugwise gateway.
  • Documentation

    • Updated changelog to reflect improvements in fault-handling and other enhancements across versions.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 10, 2024

Warning

Rate limit exceeded

@bouwew has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 7 minutes and 43 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 5bcc778 and 70fa1f2.

📒 Files selected for processing (2)
  • plugwise/constants.py (1 hunks)
  • plugwise/helper.py (5 hunks)

Walkthrough

The changes in this pull request involve modifications to several Python files related to the Plugwise smart home system. Key updates include the introduction of new methods in the SmileHelper class for managing smart meter information and sorting gateway entities. Additionally, error handling improvements are made in the SmileLegacyAPI and SmileAPI classes, including the introduction of a new exception, DataMissingError. Tests are enhanced to cover new error scenarios, and two new XML files are added for error handling and reboot scenarios.

Changes

File Change Summary
plugwise/helper.py - Added methods: _get_p1_smartmeter_info, _sort_gw_entities.
- Updated _p1_smartmeter_info_finder method signature.
plugwise/legacy/smile.py - Introduced DataMissingError exception.
- Enhanced error handling in async_update method.
plugwise/smile.py - Refined error handling in async_update method with more specific messages.
tests/test_adam.py - Updated error handling in test_connect_adam_plus_anna_new to log specific error messages.
tests/test_p1.py - Added new test cases in test_connect_p1v4_442_single for handling specific error scenarios.
userdata/error/p1v4_442_single/core.domain_objects.xml - New XML file created with <domain_objects> and nested <error> element.
userdata/reboot/p1v4_442_single/core.domain_objects.xml - New XML file created with an empty <domain_objects> root element.
plugwise/constants.py - Added constant: PRIORITY_DEVICE_CLASSES.
CHANGELOG.md - Updated to include new entries regarding P1 fault-handling and other version updates.

Suggested labels

bug

Suggested reviewers

  • CoMPaTech

🐰 In the land of Plugwise, where gadgets play,
New methods arise to brighten the day.
With errors now caught, and tests that are keen,
Our smart meters dance, all shiny and clean!
Hooray for the changes, let joy take its flight,
In the world of the bunnies, everything's right! 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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. (Experiment)
  • @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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
plugwise/helper.py (1)

Line range hint 357-366: Consider optimizing the entity sorting implementation.

While the current implementation works, it could be more efficient by using a single pass approach instead of multiple dictionary operations.

Consider this alternative implementation:

-    def _sort_gw_entities(self) -> None:
-        """Place the gateway and optional heater_central devices as 1st and 2nd."""
-        for dev_class in ("heater_central", "gateway"):
-            for entity_id, entity in dict(self.gw_entities).items():
-                if entity["dev_class"] == dev_class:
-                    tmp_entity = entity
-                    self.gw_entities.pop(entity_id)
-                    cleared_dict = self.gw_entities
-                    add_to_front = {entity_id: tmp_entity}
-                    self.gw_entities = {**add_to_front, **cleared_dict}
+    def _sort_gw_entities(self) -> None:
+        """Place the gateway and optional heater_central devices as 1st and 2nd."""
+        priority_entities = {}
+        other_entities = {}
+        
+        for entity_id, entity in self.gw_entities.items():
+            if entity["dev_class"] in ("gateway", "heater_central"):
+                priority_entities[entity_id] = entity
+            else:
+                other_entities[entity_id] = entity
+                
+        self.gw_entities = {**priority_entities, **other_entities}

This approach:

  1. Uses a single pass through the entities
  2. Reduces dictionary operations
  3. Is more memory efficient
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3801070 and a77b753.

📒 Files selected for processing (7)
  • plugwise/helper.py (2 hunks)
  • plugwise/legacy/smile.py (2 hunks)
  • plugwise/smile.py (2 hunks)
  • tests/test_adam.py (1 hunks)
  • tests/test_p1.py (2 hunks)
  • userdata/error/p1v4_442_single/core.domain_objects.xml (1 hunks)
  • userdata/reboot/p1v4_442_single/core.domain_objects.xml (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • userdata/reboot/p1v4_442_single/core.domain_objects.xml
  • userdata/error/p1v4_442_single/core.domain_objects.xml
🔇 Additional comments (9)
tests/test_p1.py (2)

44-51: LGTM: Good error handling test coverage for reboot scenario

The test properly verifies the handling of missing XML data after a reboot request.


53-58: LGTM: Good error handling test coverage for error XML data

The test properly verifies the handling of error XML responses from the gateway.

plugwise/legacy/smile.py (2)

123-129: LGTM: Robust error handling for full update

The code properly catches KeyError and raises a more descriptive DataMissingError, improving error handling during full updates.


132-144: LGTM: Robust error handling for incremental update

The code properly catches KeyError and raises a more descriptive DataMissingError, improving error handling during incremental updates.

tests/test_adam.py (1)

140-143: LGTM: Improved error logging

The test now includes the specific error message in the debug log, providing better context for debugging.

plugwise/smile.py (2)

Line range hint 134-145: LGTM: Clear comment and proper error handling.

The comment clearly explains the requirement for cooling_enabled and the code properly checks for failed data retrieval.


145-148: Verify P1 data retrieval fallback.

The else clause provides a fallback for P1 devices by attempting to retrieve the location from gw_entities[self.gateway_id]. However, we should verify that this fallback works correctly.

✅ Verification successful

Let me gather more context about the P1 data retrieval and error handling patterns.


Let me check the P1-specific test files to understand how the error handling is tested.


Based on the gathered information, I can now provide a final response:

P1 data retrieval fallback is properly implemented and tested.

The code shows consistent error handling patterns across both modern and legacy implementations. The P1 test suite (test_p1.py) includes comprehensive test scenarios:

  • Normal operation with single and triple phase setups
  • Data updates after intervals
  • Error scenarios including no data after reboot
  • XML error responses

The fallback mechanism using gw_entities[gateway_id]["location"] is present in both modern and legacy code paths, with appropriate error handling raising DataMissingError when the data is unavailable.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for P1-related error handling patterns
# Test: Look for similar error handling patterns in tests
rg -A 5 "DataMissingError.*No Plugwise actual data received"

Length of output: 362


Script:

#!/bin/bash
# Check for P1-related code patterns and error handling
ast-grep --pattern 'class $_ {
  $$$
  def _get_data(self, $$$) {
    $$$
  }
  $$$
}'

# Search for P1 related tests
rg -l "P1" tests/

Length of output: 327


Script:

#!/bin/bash
# Look for gw_entities usage patterns
rg -A 5 "gw_entities\[.*gateway_id\]\[\"location\"\]"

# Check for DataMissingError usage in tests
rg -A 5 "DataMissingError" tests/

Length of output: 1270


Script:

#!/bin/bash
# Check P1 test implementations
cat tests/test_p1.py

# Look for test cases involving error scenarios
rg -A 10 "def test.*error" tests/test_p1.py tests/test_legacy_p1.py

Length of output: 3072

plugwise/helper.py (2)

340-342: LGTM: Good refactoring for improved code organization.

The extraction of functionality into separate methods _get_smartmeter_info() and _sort_gw_entities() improves code organization and follows the Single Responsibility Principle.


343-356: LGTM: Well-documented and properly implemented SmartMeter info collection.

The method clearly handles the collection of SmartMeter information and properly manages the gateway_id assignment for P1 devices.

@codecov
Copy link

codecov bot commented Dec 10, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 100.00%. Comparing base (3801070) to head (5bcc778).
Report is 22 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #670   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           20        20           
  Lines         3435      3452   +17     
=========================================
+ Hits          3435      3452   +17     

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

@bouwew bouwew changed the title Improve fault-handling for P1 device Improve P1 fault-handling Dec 10, 2024
@bouwew bouwew marked this pull request as ready for review December 10, 2024 18:56
@bouwew bouwew requested a review from a team as a code owner December 10, 2024 18:56
@bouwew bouwew requested a review from CoMPaTech December 10, 2024 19:06
@bouwew bouwew added the quality label Dec 10, 2024
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
plugwise/helper.py (3)

343-356: Consider adding more detailed documentation.

While the method effectively encapsulates the P1 smartmeter info collection logic, the gateway_id update logic (lines 350-356) could benefit from more explicit documentation explaining why and when this ID switch occurs.

 def _get_smartmeter_info(self) -> None:
     """For P1 collect the connected SmartMeter info from the Home/buildinglocation.

     There is no appliance available for this device.
+    Note: For P1 devices, we switch the gateway_id to the smartmeter device_id
+    to maintain backward compatibility with existing implementations.
     """

357-366: Consider using constants for device classes.

The method effectively sorts gateway entities, but the device classes could be defined as constants at the module level for better maintainability.

+PRIORITY_DEVICE_CLASSES = ("heater_central", "gateway")

 def _sort_gw_entities(self) -> None:
     """Place the gateway and optional heater_central devices as 1st and 2nd."""
-    for dev_class in ("heater_central", "gateway"):
+    for dev_class in PRIORITY_DEVICE_CLASSES:

817-826: Consider refactoring the initialization logic.

The initialization of binary sensors could be made more concise by extracting it into a helper method.

+def _ensure_binary_sensor(self, data: GwEntityData, sensor_name: str) -> None:
+    """Initialize a binary sensor if not present."""
+    if sensor_name not in data["binary_sensors"]:
+        self._count += 1
+    data["binary_sensors"][sensor_name] = False

 def _process_on_off_device_c_heating_state(self, data: GwEntityData) -> None:
     if self.smile(ANNA):
         data["binary_sensors"]["heating_state"] = data["c_heating_state"]

     if self.smile(ADAM):
-        if "heating_state" not in data["binary_sensors"]:
-            self._count += 1
-        data["binary_sensors"]["heating_state"] = False
-
-        if "cooling_state" not in data["binary_sensors"]:
-            self._count += 1
-        data["binary_sensors"]["cooling_state"] = False
+        self._ensure_binary_sensor(data, "heating_state")
+        self._ensure_binary_sensor(data, "cooling_state")
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between fa40c50 and 066e30e.

📒 Files selected for processing (1)
  • plugwise/helper.py (3 hunks)
🔇 Additional comments (2)
plugwise/helper.py (2)

340-342: LGTM! Good separation of concerns.

The addition of these method calls improves code organization by separating smartmeter info collection and gateway entity sorting into dedicated methods.


Line range hint 380-408: LGTM! Improved error handling and encapsulation.

The changes enhance the method by:

  • Removing the external dependency on the appl parameter
  • Adding proper error handling for missing module data
  • Better encapsulation of the Munch object creation

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
plugwise/helper.py (2)

346-366: Consider adding error handling for entity switching.

While the implementation is solid, consider adding error handling for cases where the entity switching might fail, such as when the expected entities are not present in the expected order.

 def _get_smartmeter_info(self) -> None:
     if self.smile_type == "power":
         self._p1_smartmeter_info_finder()
+        # Ensure we have exactly two entities before switching
+        if len(self.gw_entities) != 2:
+            LOGGER.warning("Expected exactly 2 entities for P1 device, found %d", len(self.gw_entities))
+            return
         # Switch entity_ids
         entity_id_0 = list(self.gw_entities)[0]
         entity_0 = list(self.gw_entities.values())[0]

367-376: Clean implementation with room for minor optimization.

The sorting implementation is clear and effective. Consider using dict comprehension for a slightly more concise implementation of the priority sorting.

 def _sort_gw_entities(self) -> None:
     for dev_class in PRIORITY_DEVICE_CLASSES:
-        for entity_id, entity in dict(self.gw_entities).items():
-            if entity["dev_class"] == dev_class:
-                priority_entity = entity
-                self.gw_entities.pop(entity_id)
-                other_entities = self.gw_entities
-                priority_entities = {entity_id: priority_entity}
-                self.gw_entities = {**priority_entities, **other_entities}
+        priority_entities = {
+            entity_id: entity for entity_id, entity in dict(self.gw_entities).items()
+            if entity["dev_class"] == dev_class
+        }
+        if priority_entities:
+            other_entities = {
+                k: v for k, v in self.gw_entities.items()
+                if k not in priority_entities
+            }
+            self.gw_entities = {**priority_entities, **other_entities}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5fc725a and 971de0b.

📒 Files selected for processing (1)
  • plugwise/helper.py (6 hunks)
🔇 Additional comments (4)
plugwise/helper.py (4)

32-32: LGTM!

The new constant import is properly placed and used by the new sorting functionality.


282-286: Well-structured code organization!

The changes improve modularity by extracting SmartMeter info collection and entity sorting into dedicated methods, making the code more maintainable.

Also applies to: 340-344


827-836: LGTM! Clear state management implementation.

The binary sensor state handling is well-implemented with proper initialization and state tracking. The counter management ensures accurate tracking of sensor states.


843-845: LGTM! Clear and informative documentation.

The docstring update clearly documents the extended device support for cooling functionality.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
plugwise/helper.py (1)

Line range hint 346-380: Consider adding a return type hint

The implementation is solid with good error handling and clear comments. Consider adding a return type hint to improve code clarity.

-def _get_p1_smartmeter_info(self) -> None:
+def _get_p1_smartmeter_info(self) -> None | Literal[None]:
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 971de0b and 25177a7.

📒 Files selected for processing (1)
  • plugwise/helper.py (5 hunks)
🔇 Additional comments (7)
plugwise/helper.py (7)

32-32: LGTM!

The import of PRIORITY_DEVICE_CLASSES is correctly placed with other related constants.


282-286: LGTM!

The docstring clearly explains the method's dual responsibility of collecting appliance info and P1 smartmeter info.


341-344: LGTM!

The new method calls are logically placed and maintain a clear separation of concerns: data collection followed by data organization.


383-393: LGTM!

The implementation efficiently sorts gateway entities while maintaining priority order using dictionary operations.


394-405: LGTM!

The method is well-structured with clear documentation and efficient data handling using Munch.


814-823: LGTM!

The implementation properly handles state initialization and counter management for heating and cooling states.


830-833: LGTM!

The docstring clearly documents the method's purpose and supported devices.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
plugwise/helper.py (3)

Line range hint 282-380: LGTM! Well-structured P1 smartmeter info collection implementation.

The implementation is clean and handles backward compatibility elegantly by switching entity IDs between gateway and smartmeter. Good error handling and clear documentation.

Consider documenting the entity ID switching behavior in a central location (e.g., README or API docs) to help future maintainers understand this design decision.


383-393: Consider a more efficient sorting implementation.

The current implementation modifies the dictionary while iterating, which could be error-prone. Consider using a more efficient sorting approach.

Here's a suggested implementation:

def _sort_gw_entities(self) -> None:
-    for dev_class in PRIORITY_DEVICE_CLASSES:
-        for entity_id, entity in dict(self.gw_entities).items():
-            if entity["dev_class"] == dev_class:
-                priority_entity = entity
-                self.gw_entities.pop(entity_id)
-                other_entities = self.gw_entities
-                priority_entities = {entity_id: priority_entity}
-                self.gw_entities = {**priority_entities, **other_entities}
+    def sort_key(item):
+        entity_id, entity = item
+        try:
+            return PRIORITY_DEVICE_CLASSES.index(entity["dev_class"])
+        except ValueError:
+            return len(PRIORITY_DEVICE_CLASSES)
+    
+    self.gw_entities = dict(sorted(self.gw_entities.items(), key=sort_key))

814-823: Simplify binary sensor initialization.

The initialization and counting logic for heating_state and cooling_state could be more concise.

Consider this more concise implementation:

-            if "heating_state" not in data["binary_sensors"]:
-                self._count += 1
-            data["binary_sensors"]["heating_state"] = False
-
-            if "cooling_state" not in data["binary_sensors"]:
-                self._count += 1
-            data["binary_sensors"]["cooling_state"] = False
+            for state in ["heating_state", "cooling_state"]:
+                if state not in data["binary_sensors"]:
+                    self._count += 1
+                data["binary_sensors"][state] = False
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 25177a7 and 5bcc778.

📒 Files selected for processing (1)
  • plugwise/helper.py (5 hunks)

@sonarqubecloud
Copy link

@bouwew bouwew merged commit 71c5a02 into main Dec 12, 2024
30 of 32 checks passed
@bouwew bouwew deleted the try-something branch December 12, 2024 17:52
@coderabbitai coderabbitai bot mentioned this pull request Dec 22, 2024
@coderabbitai coderabbitai bot mentioned this pull request Jun 28, 2025
@coderabbitai coderabbitai bot mentioned this pull request Nov 1, 2025
This was referenced Dec 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants