Skip to content

Conversation

@bouwew
Copy link
Contributor

@bouwew bouwew commented Oct 19, 2025

This should help setting the correct schedule when changing hvac_mode heat to auto. A prerequisite is that a schedule has been set before disabling it.

Summary by CodeRabbit

  • New Features
    • Climate entities now persist richer restore data (last-active schedule and previous action) so schedules and actions are retained across restarts and mode changes.
  • Bug Fixes
    • Improved schedule/state transitions and clearer error handling when selecting AUTO without a stored schedule; HomeKit emulation respects restored schedule/state.
  • Tests
    • Added restore-state test validating schedule restoration and heat/cool transitions.
  • Chores
    • Bumped integration and project version; updated dependency to plugwise v1.8.3.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 19, 2025

Walkthrough

Added a dataclass to persist extra climate restore data and made the Plugwise climate entity a RestoreEntity; implemented restore/load of last_active_schedule and previous_action_mode, integrated them into hvac_mode/hvac_action and schedule flows, updated tests, and bumped the plugwise dependency to 1.8.3.

Changes

Cohort / File(s) Change Summary
Entity: restore & extra stored data
custom_components/plugwise/climate.py
Added PlugwiseClimateExtraStoredData dataclass (last_active_schedule, previous_action_mode) with as_dict/from_dict; PlugwiseClimateEntity now inherits RestoreEntity; implemented async_added_to_hass to restore data and exposed extra_restore_state_data.
Entity: HVAC/action & schedule flow
custom_components/plugwise/climate.py
Reworked hvac_mode/hvac_action logic to use _last_active_schedule and _previous_action_mode; removed legacy _previous_mode; async_set_hvac_mode captures/chooses desired_schedule, passes it to set_schedule_state, and raises ERROR_NO_SCHEDULE when AUTO requested without a stored schedule; added _homekit_translate_or_not helper and updated HVACAction comparisons.
Tests: restore & expectations updated
tests/components/plugwise/test_climate.py
Added test_adam_restore_state_climate; updated imports (PlugwiseClimateExtraStoredData, State, mock_restore_cache_with_extra_data, STATE_OFF, STATE_ON); adjusted mocked climate_mode values and assertions to reflect schedule/location propagation and restored-schedule usage.
Manifest: dependency & version
custom_components/plugwise/manifest.json
Bumped requirement from plugwise==1.8.2 to plugwise==1.8.3 and bumped integration version to 0.59.0.
Misc: coverage pragma
custom_components/plugwise/__init__.py
Added # pragma: no cover to an early-return in async_migrate_entry (coverage annotation only).
Project metadata
pyproject.toml, CHANGELOG.md
Bumped project version to 0.59.0 and added changelog entry for RestoreState in climate.

Sequence Diagram(s)

sequenceDiagram
    participant HA as Home Assistant
    participant Entity as PlugwiseClimateEntity
    participant API as Plugwise Device/API

    Note over Entity: Restore on startup
    HA->>Entity: async_added_to_hass()
    Entity->>Entity: load extra_restore_state_data -> _last_active_schedule, _previous_action_mode

    rect #f0f7ff
    Note over HA,Entity: Mode change with schedule propagation
    HA->>Entity: async_set_hvac_mode(mode [, selected_schedule])
    Entity->>Entity: determine desired_schedule (selected or _last_active_schedule)
    alt schedule present
        Entity->>API: set_schedule_state(mode, ..., desired_schedule)
        API-->>Entity: result
        Entity->>Entity: update _previous_action_mode & _last_active_schedule
    else schedule missing but required
        Entity-->>HA: raise HomeAssistantError (ERROR_NO_SCHEDULE)
    end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

enhancement, needs_upstreaming

Suggested reviewers

  • CoMPaTech

Poem

🐇 I tucked a schedule in my paw,

Saved the warmth the house once saw.
Modes return where memories stay,
I hop and hum to guard the day.
Cozy hops—your home will play.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "Store the last_used_schedule for reuse" directly aligns with the main objective and core functionality implemented in this changeset. The PR introduces a dataclass PlugwiseClimateExtraStoredData to persist the last active schedule, extends PlugwiseClimateEntity with RestoreEntity for state restoration, and modifies async_set_hvac_mode to capture and reuse the stored schedule when switching HVAC modes. The title is concise, specific, and clearly communicates the primary change without vague terminology or noise. While the PR includes secondary features like storing previous_action_mode, the title appropriately highlights the main feature that addresses the stated objective of reusing the last schedule when switching from heat to auto mode.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch save_last_option

📜 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 6c2fa60 and 25347ea.

📒 Files selected for processing (6)
  • CHANGELOG.md (1 hunks)
  • custom_components/plugwise/__init__.py (1 hunks)
  • custom_components/plugwise/climate.py (10 hunks)
  • custom_components/plugwise/manifest.json (1 hunks)
  • pyproject.toml (1 hunks)
  • tests/components/plugwise/test_climate.py (6 hunks)
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-23T16:26:34.700Z
Learnt from: bouwew
PR: plugwise/plugwise-beta#906
File: tests/components/plugwise/test_binary_sensor.py:9-16
Timestamp: 2025-08-23T16:26:34.700Z
Learning: The Plugwise integration test files import utilities like `snapshot_platform`, `MockConfigEntry`, and `async_fire_time_changed` from `tests.common`, which is from the Home Assistant core repository at https://github.com/home-assistant/core/blob/master/tests/common.py, not from local test files.

Applied to files:

  • tests/components/plugwise/test_climate.py
🧬 Code graph analysis (2)
tests/components/plugwise/test_climate.py (2)
custom_components/plugwise/climate.py (2)
  • PlugwiseClimateExtraStoredData (105-124)
  • as_dict (111-116)
tests/components/plugwise/conftest.py (2)
  • mock_smile_adam_heat_cool (185-208)
  • mock_config_entry (81-95)
custom_components/plugwise/climate.py (1)
custom_components/plugwise/entity.py (2)
  • PlugwiseEntity (31-93)
  • device (91-93)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Prepare and validate pre-commit
🔇 Additional comments (9)
pyproject.toml (1)

3-3: LGTM - Version bump aligns with new features.

The minor version bump from 0.58.2 to 0.59.0 is appropriate for this PR, which introduces new public API features (PlugwiseClimateExtraStoredData dataclass, RestoreEntity support, and the extra_restore_state_data property).

custom_components/plugwise/__init__.py (1)

149-149: LGTM - Test coverage annotation.

The pragma comment appropriately excludes the unreachable downgrade path from coverage analysis.

custom_components/plugwise/manifest.json (1)

10-11: LGTM - Dependency and version updates are aligned.

The plugwise library bump to 1.8.3 and integration version update to 0.59.0 are consistent with the new climate restore state features introduced in this PR.

tests/components/plugwise/test_climate.py (1)

146-236: LGTM - Comprehensive restore state test.

The test thoroughly exercises the climate restoration functionality:

  1. Error handling when no schedule is available for AUTO mode (lines 183-190)
  2. Restoration of previous_action_mode when switching from OFF to HEAT (lines 192-213)
  3. Restoration of last_active_schedule when switching to AUTO (lines 215-235)

The test structure properly uses mock_restore_cache_with_extra_data and verifies both state restoration and the actual API calls that result from using restored data.

Based on learnings.

custom_components/plugwise/climate.py (5)

104-124: LGTM - Properly implemented restore data class.

The PlugwiseClimateExtraStoredData dataclass correctly implements the ExtraStoredData interface with:

  • JSON-safe string fields for both last_active_schedule and previous_action_mode
  • Proper as_dict() serialization
  • Safe from_dict() deserialization using .get() to handle missing keys

140-149: LGTM - Proper state restoration implementation.

The async_added_to_hass method correctly implements the RestoreEntity pattern:

  1. Calls the parent implementation
  2. Safely retrieves extra data if available
  3. Converts to the specific dataclass type using from_dict()
  4. Restores both _last_active_schedule and _previous_action_mode attributes

197-203: LGTM - Complete state data persistence.

The extra_restore_state_data property correctly returns both last_active_schedule and previous_action_mode for persistence across restarts.


344-361: LGTM - Clean refactoring of HomeKit translation logic.

The extraction of HomeKit-related logic into _homekit_translate_or_not() improves code organization. The method correctly:

  • Uses mode.value when calling set_regulation_mode() (line 350)
  • Restores _previous_action_mode when transitioning from OFF mode (line 352)
  • Handles HomeKit emulation preset mode switching (lines 354-361)

322-340: Verify schedule handling with external plugwise library.

The concern in the original review is valid but cannot be resolved from this codebase. The set_schedule_state method is called on self.coordinator.api, which is an instance of the external Smile class from the plugwise library (not defined in this repository).

The code logic can indeed pass None for the schedule parameter when both the current schedule and _last_active_schedule are None in non-AUTO modes. However, all existing tests only pass schedule names (never None), and without access to the external library's implementation, the handling of None cannot be verified here.

Please verify with the plugwise library documentation or add a test case for this edge case to ensure the API gracefully handles None.


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

Comment @coderabbitai help to get the list of available commands and usage tips.

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7739464 and a8e66ac.

📒 Files selected for processing (2)
  • custom_components/plugwise/climate.py (6 hunks)
  • tests/components/plugwise/test_climate.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
custom_components/plugwise/climate.py (1)
custom_components/plugwise/entity.py (2)
  • PlugwiseEntity (31-93)
  • device (91-93)
🪛 GitHub Check: CodeFactor
custom_components/plugwise/climate.py

[notice] 111-111: custom_components/plugwise/climate.py#L111
Multiple spaces before operator. (E221)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Prepare
🔇 Additional comments (2)
tests/components/plugwise/test_climate.py (2)

107-109: Schedule name is correct and consistent with test fixtures.

The verification confirms that "GF7 Woonkamer" (with two spaces) is the correct schedule name from the fixture data. The device ID c50f167537524366a5af7aa3942feb1e in the test matches the fixture entry where "select_schedule": "GF7 Woonkamer" is defined. The two-space formatting is intentional and consistent across the fixture's available schedules and device configuration.


336-338: No changes needed—test fixture data confirms "standaard" is the correct schedule name.

The verification confirms that "standaard" is the correct active schedule name for the Anna climate entity. The fixture data across multiple Anna configurations (m_anna_heatpump_cooling, m_anna_heatpump_idle, anna_heatpump_heating) consistently shows "select_schedule": "standaard" and lists it in available schedules, matching the test assertion.

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

♻️ Duplicate comments (2)
custom_components/plugwise/climate.py (2)

27-27: RestoreEntity inheritance remains unused.

This duplicates a previous review comment. RestoreEntity is imported and inherited but async_added_to_hass is never overridden to restore state. Since _last_active_schedule is populated from coordinator data in __init__ and during property access, and coordinator data is refreshed on Home Assistant restarts, the RestoreEntity inheritance serves no purpose and should be removed.

Remove the import at line 27 and remove RestoreEntity from the inheritance list at line 102.

Also applies to: 102-102


291-291: _last_active_schedule may reference stale schedule data.

This duplicates a previous review comment. The _last_active_schedule attribute is set once in __init__ (line 128, though not shown in the diff) and opportunistically in the hvac_modes property, but it's never updated when the schedule actually changes via other means (e.g., through the thermostat select entity or coordinator updates). When set_schedule_state() is called with this value, it may pass an outdated schedule name.

Either reference self.device.get("select_schedule") directly at line 291 to always use current data, or implement proper state tracking that updates _last_active_schedule whenever the schedule changes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8e66ac and 0089a5b.

📒 Files selected for processing (2)
  • custom_components/plugwise/climate.py (6 hunks)
  • tests/components/plugwise/test_climate.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
custom_components/plugwise/climate.py (1)
custom_components/plugwise/entity.py (2)
  • PlugwiseEntity (31-93)
  • device (91-93)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Setup for HA-core (dev)
  • GitHub Check: Setup for HA-core (release/master)
🔇 Additional comments (1)
tests/components/plugwise/test_climate.py (1)

108-108: Test expectations updated correctly, but may need revision.

The test assertions correctly reflect the new set_schedule_state signature with the added last_active_schedule parameter. However, these tests verify the current implementation which has critical logic errors (always-True condition on line 230 of climate.py).

Once the logic errors in the main implementation are addressed, verify that these expected values remain correct, particularly if the schedule values "GF7 Woonkamer" and "standaard" should be passed or if they might be None in certain scenarios.

Also applies to: 337-337

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
custom_components/plugwise/climate.py (1)

287-292: Handle None value for _last_active_schedule before calling set_schedule_state.

The set_schedule_state method signature expects a non-optional string for the schedule name parameter: async def set_schedule_state(self, loc_id: str, name: str, state: str) -> bool. However, _last_active_schedule is initialized to None and only updated when a valid schedule is encountered. If no valid schedule is ever found, passing None to set_schedule_state will cause a type mismatch.

Add a guard before the call:

if hvac_mode != HVACMode.OFF and self._last_active_schedule is not None:
    await self.coordinator.api.set_schedule_state(
        self._location,
        STATE_ON if hvac_mode == HVACMode.AUTO else STATE_OFF,
        self._last_active_schedule,
    )

Alternatively, provide a fallback schedule name if _last_active_schedule is None.

♻️ Duplicate comments (1)
custom_components/plugwise/climate.py (1)

27-27: Remove unused RestoreEntity inheritance.

RestoreEntity is inherited but no async_added_to_hass method is implemented to restore state. The _last_active_schedule attribute is populated dynamically from device data in the hvac_modes property (lines 229-231), making persistence across restarts unnecessary for the current use case.

Since this was previously flagged and marked as addressed but still appears in the code, please verify whether this inheritance is actually needed or if it was reintroduced unintentionally.

Apply this diff to remove the unnecessary inheritance:

-from homeassistant.helpers.restore_state import RestoreEntity
-class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity, RestoreEntity):
+class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity):

Also applies to: 102-102

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0089a5b and 9f349d6.

📒 Files selected for processing (1)
  • custom_components/plugwise/climate.py (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
custom_components/plugwise/climate.py (1)
custom_components/plugwise/entity.py (2)
  • PlugwiseEntity (31-93)
  • device (91-93)
🪛 GitHub Actions: Test PR against HA-core
custom_components/plugwise/climate.py

[error] 230-230: PLR1714: Consider merging multiple comparisons: schedule not in (NONE, "off"). Use a set if the elements are hashable.

🔇 Additional comments (1)
custom_components/plugwise/climate.py (1)

111-111: LGTM: Attribute declaration is correct.

The type hint and initialization are properly formatted.

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

🧹 Nitpick comments (3)
custom_components/plugwise/climate.py (3)

144-145: Optimize attribute access after implementing from_dict.

Once the from_dict classmethod is added to PlugwiseClimateExtraStoredData, these lines can access attributes directly instead of calling as_dict() twice.

After implementing from_dict, apply this diff:

-            self._last_active_schedule = last_extra_data.as_dict()["last_active_schedule"]
-            self._previous_action_mode = last_extra_data.as_dict()["previous_action_mode"]
+            self._last_active_schedule = last_extra_data.last_active_schedule
+            self._previous_action_mode = last_extra_data.previous_action_mode

241-241: Remove or clean up debug logging.

This debug log appears to contain temporary debugging text ("HOI") and a typo ("havc_mode" should be "hvac_mode"). Consider removing it or fixing it before merging.

If you want to keep the logging, apply this diff:

-        LOGGER.debug("HOI havc_mode = %s", str(hvac))
+        LOGGER.debug("HVAC mode: %s", hvac)

Or remove it entirely if it was only for debugging:

-
-        LOGGER.debug("HOI havc_mode = %s", str(hvac))
         return hvac

272-286: Consider moving state tracking to coordinator update callback.

The hvac_action property updates _previous_action_mode as a side effect. While property getters with side effects are generally discouraged, this implementation is better than the previous approach (updating in the hvac_modes getter that was flagged in past reviews).

For cleaner architecture, consider tracking mode changes in a _handle_coordinator_update callback override instead of within a property getter. This would separate state tracking from state retrieval.

Example approach:

@callback
def _handle_coordinator_update(self) -> None:
    """Handle updated data from the coordinator."""
    if (
        REGULATION_MODES in self._gateway_data
        and HVACAction.COOLING in self._gateway_data[REGULATION_MODES]
    ):
        mode = self._gateway_data[SELECT_REGULATION_MODE]
        if mode in (HVACAction.COOLING, HVACAction.HEATING):
            self._previous_action_mode = mode
    super()._handle_coordinator_update()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f349d6 and 9ea9fa3.

📒 Files selected for processing (2)
  • custom_components/plugwise/climate.py (10 hunks)
  • tests/components/plugwise/test_climate.py (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/components/plugwise/test_climate.py
🧰 Additional context used
🧬 Code graph analysis (1)
custom_components/plugwise/climate.py (1)
custom_components/plugwise/entity.py (2)
  • PlugwiseEntity (31-93)
  • device (91-93)
🔇 Additional comments (2)
custom_components/plugwise/climate.py (2)

5-5: LGTM! Imports are appropriate for RestoreEntity implementation.

The added imports for dataclasses, restore state functionality, and state constants are all properly used in the implementation.

Also applies to: 24-25, 30-30, 44-44


312-343: LGTM! Schedule tracking logic is correctly implemented.

The async_set_hvac_mode method properly:

  • Retrieves and validates the current schedule before storing it
  • Updates _last_active_schedule only when a valid schedule exists
  • Passes the schedule to set_schedule_state for persistence
  • Uses _previous_action_mode to restore the correct heating/cooling mode

This is the appropriate place to track schedule changes.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Error while testing for Development HA-core:

⚠️ Warning: Incompatible while testing against dev HA-core.
✔️ Success: No problem with testing against released HA-core.

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

♻️ Duplicate comments (3)
custom_components/plugwise/climate.py (3)

127-127: Fix type annotation mismatch.

The field is annotated as str but initialized with HVACAction.HEATING (an enum), and the dataclass field at line 110 is typed as HVACAction | None. This creates a type mismatch.

Apply this diff:

-    _previous_action_mode: str = HVACAction.HEATING  # Upstream
+    _previous_action_mode: HVACAction = HVACAction.HEATING  # Upstream

Based on learnings


195-198: Add missing argument to constructor.

The PlugwiseClimateExtraStoredData dataclass has two fields (last_active_schedule and previous_action_mode), but only self._last_active_schedule is passed. This means previous_action_mode won't be persisted across restarts.

Apply this diff:

     def extra_restore_state_data(self) -> PlugwiseClimateExtraStoredData:
         """Return text specific state data to be restored."""
-        return PlugwiseClimateExtraStoredData(self._last_active_schedule)
+        return PlugwiseClimateExtraStoredData(
+            self._last_active_schedule, self._previous_action_mode
+        )

Based on learnings


105-115: Add from_dict classmethod for proper deserialization.

The dataclass implements as_dict() for serialization but lacks the corresponding from_dict() classmethod required by Home Assistant's RestoreEntity. Without it, async_get_last_extra_data() cannot properly reconstruct the dataclass from stored data, causing restoration to fail.

Add the classmethod:

     def as_dict(self) -> dict[str, Any]:
         """Return a dict representation of the text data."""
         return asdict(self)
+
+    @classmethod
+    def from_dict(cls, restored: dict[str, Any]) -> PlugwiseClimateExtraStoredData:
+        """Initialize a stored data object from a dict."""
+        try:
+            previous_action_mode = (
+                HVACAction(restored["previous_action_mode"])
+                if restored.get("previous_action_mode") is not None
+                else None
+            )
+        except (KeyError, ValueError):
+            previous_action_mode = None
+        
+        return cls(
+            last_active_schedule=restored.get("last_active_schedule"),
+            previous_action_mode=previous_action_mode,
+        )

Based on learnings

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ea9fa3 and ee7c532.

📒 Files selected for processing (1)
  • custom_components/plugwise/climate.py (10 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
custom_components/plugwise/climate.py (1)
custom_components/plugwise/entity.py (2)
  • PlugwiseEntity (31-93)
  • device (91-93)
🔇 Additional comments (2)
custom_components/plugwise/climate.py (2)

272-286: LGTM!

The logic correctly tracks the previous HVAC action mode when regulation modes are available, and only updates when the mode is COOLING or HEATING. This ensures the correct mode is restored when switching from OFF.


312-343: LGTM!

The HVAC mode switching logic correctly captures and stores the current schedule when it's valid, and properly restores the previous action mode when switching from OFF. The schedule is passed correctly to set_schedule_state.

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

♻️ Duplicate comments (5)
custom_components/plugwise/climate.py (5)

103-116: Past reviews comprehensively covered dataclass issues.

Previous reviews correctly identified that the from_dict classmethod is required for proper deserialization by RestoreEntity's async_get_last_extra_data(). The review provides a complete implementation example.

Based on past reviews.


131-138: Past review correctly identified inefficient attribute access.

The previous review accurately flagged that lines 137-138 unnecessarily call as_dict() twice when the dataclass attributes can be accessed directly. The suggested fix is correct and improves both performance and readability.

Based on past reviews.


186-192: Past review correctly identified missing constructor argument.

The previous review accurately caught that previous_action_mode is not being passed to the PlugwiseClimateExtraStoredData constructor, which would prevent it from being persisted across restarts. Both fields must be included for complete state restoration.

Based on past reviews.


235-235: Past review correctly identified debug code leftover.

The previous review accurately flagged this debug statement containing informal text "HOI" and a typo "havc_mode". This should be removed before merging to production.

Based on past reviews.


268-275: State mutation in property getter (already flagged).

Past reviews correctly identified that mutating _previous_action_mode inside the hvac_action property getter violates the principle that properties should be read-only. This property is called frequently by Home Assistant for UI rendering, causing unnecessary state updates.

Minor addition: Line 268 comment has a typo - "havc_action" should be "hvac_action".

Based on past reviews.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee7c532 and 1103f0c.

📒 Files selected for processing (2)
  • custom_components/plugwise/climate.py (11 hunks)
  • tests/components/plugwise/test_climate.py (6 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-23T16:26:34.700Z
Learnt from: bouwew
PR: plugwise/plugwise-beta#906
File: tests/components/plugwise/test_binary_sensor.py:9-16
Timestamp: 2025-08-23T16:26:34.700Z
Learning: The Plugwise integration test files import utilities like `snapshot_platform`, `MockConfigEntry`, and `async_fire_time_changed` from `tests.common`, which is from the Home Assistant core repository at https://github.com/home-assistant/core/blob/master/tests/common.py, not from local test files.

Applied to files:

  • tests/components/plugwise/test_climate.py
🧬 Code graph analysis (2)
tests/components/plugwise/test_climate.py (2)
custom_components/plugwise/climate.py (2)
  • PlugwiseClimateExtraStoredData (104-115)
  • as_dict (110-115)
tests/components/plugwise/conftest.py (2)
  • mock_smile_adam_heat_cool (185-208)
  • init_integration (347-356)
custom_components/plugwise/climate.py (1)
custom_components/plugwise/entity.py (2)
  • PlugwiseEntity (31-93)
  • device (91-93)
🔇 Additional comments (4)
tests/components/plugwise/test_climate.py (3)

25-25: LGTM! Imports are correctly added for restore state functionality.

The new imports support the restore state test: PlugwiseClimateExtraStoredData from the climate module, State for creating mock state objects, and mock_restore_cache_with_extra_data for mocking the restore cache.

Also applies to: 27-27, 33-38


115-115: LGTM! Additional parameter aligns with implementation changes.

The set_schedule_state calls now correctly pass the schedule/location name as a third parameter, matching the updated implementation that propagates schedule from device.get("select_schedule").

Also applies to: 367-367


204-204: LGTM! Test data updates support HVAC mode change scenarios.

The climate_mode field updates to "heat" and "cool" correctly simulate regulation mode changes in the test data, allowing proper verification of the climate entity's behavior when switching between heating and cooling modes.

Also applies to: 225-225

custom_components/plugwise/climate.py (1)

127-128: Attribute initialization looks correct.

The _last_active_schedule and _previous_action_mode attributes are properly initialized with appropriate types and default values for tracking schedule and regulation mode state.

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

♻️ Duplicate comments (3)
custom_components/plugwise/climate.py (3)

146-147: Access dataclass attributes directly instead of calling as_dict() twice.

Calling as_dict() twice is inefficient when you can access the dataclass attributes directly.

Apply this diff:

-            self._last_active_schedule = extra_data.as_dict()["last_active_schedule"]
-            self._previous_action_mode = extra_data.as_dict()["previous_action_mode"]
+            self._last_active_schedule = extra_data.last_active_schedule
+            self._previous_action_mode = extra_data.previous_action_mode

244-244: Remove debug logging leftover.

This debug statement contains informal text ("HOI") and a typo ("havc_mode" should be "hvac_mode"). It appears to be leftover debug code.

Remove the debug statement:

-        LOGGER.debug("HOI havc_mode = %s", str(hvac))

321-333: Critical: Stored schedule is never actually used.

The PR objective states: "storing the last_used_schedule so it can be reused when changing HVAC mode from heat to auto." However, the current implementation retrieves the current device schedule and always passes that to set_schedule_state, never actually using the stored _last_active_schedule value. This defeats the purpose of the restoration logic.

Current flow:

  1. Line 321: Gets current device schedule
  2. Lines 322-327: Saves to _last_active_schedule if None
  3. Line 332: Passes the current device schedule (not _last_active_schedule)

Expected flow for the stated objective:

  • When switching to AUTO, prefer the stored _last_active_schedule if available
  • Only fall back to current device schedule if no stored schedule exists
  • Save valid schedules when they're active for future reuse

Consider revising the logic:

 if hvac_mode != HVACMode.OFF:
-    schedule = self.device.get("select_schedule")
-    if self._last_active_schedule is None:
-        if schedule is not None:
-            if schedule != "off":
-                self._last_active_schedule = schedule
-            else:
-                raise HomeAssistantError("Failed setting HVACMode, set a schedule first")
+    # When switching to AUTO, use stored schedule if available
+    if hvac_mode == HVACMode.AUTO and self._last_active_schedule:
+        schedule = self._last_active_schedule
+    else:
+        schedule = self.device.get("select_schedule")
+        # Save valid schedules for future reuse
+        if schedule is not None and schedule != "off":
+            self._last_active_schedule = schedule
+        elif self._last_active_schedule is None:
+            raise HomeAssistantError("Failed setting HVACMode, set a schedule first")
 
     await self.coordinator.api.set_schedule_state(
         self._location,
         STATE_ON if hvac_mode == HVACMode.AUTO else STATE_OFF,
         schedule,
     )
🧹 Nitpick comments (1)
custom_components/plugwise/climate.py (1)

327-327: Consider extracting exception message to a constant.

Static analysis suggests avoiding long messages directly in exception constructors. While this is a minor style issue, defining error messages as module-level constants improves maintainability and makes them easier to test.

For example, define at module level:

ERROR_NO_SCHEDULE = "Failed setting HVACMode, set a schedule first"

Then use:

-                raise HomeAssistantError("Failed setting HVACMode, set a schedule first")
+                raise HomeAssistantError(ERROR_NO_SCHEDULE)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 542f85f and 9f07924.

📒 Files selected for processing (2)
  • custom_components/plugwise/climate.py (10 hunks)
  • tests/components/plugwise/test_climate.py (6 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-23T16:26:34.700Z
Learnt from: bouwew
PR: plugwise/plugwise-beta#906
File: tests/components/plugwise/test_binary_sensor.py:9-16
Timestamp: 2025-08-23T16:26:34.700Z
Learning: The Plugwise integration test files import utilities like `snapshot_platform`, `MockConfigEntry`, and `async_fire_time_changed` from `tests.common`, which is from the Home Assistant core repository at https://github.com/home-assistant/core/blob/master/tests/common.py, not from local test files.

Applied to files:

  • tests/components/plugwise/test_climate.py
🧬 Code graph analysis (2)
tests/components/plugwise/test_climate.py (2)
custom_components/plugwise/climate.py (2)
  • PlugwiseClimateExtraStoredData (105-124)
  • as_dict (111-116)
tests/components/plugwise/conftest.py (2)
  • mock_smile_adam_heat_cool (185-208)
  • init_integration (347-356)
custom_components/plugwise/climate.py (1)
custom_components/plugwise/entity.py (2)
  • PlugwiseEntity (31-93)
  • device (91-93)
🪛 GitHub Actions: Test PR against HA-core
tests/components/plugwise/test_climate.py

[error] 177-177: Ruff: Undefined name 'ANY'.


[error] 178-178: Ruff: Undefined name 'STATE_ON'.

🪛 Ruff (0.14.1)
tests/components/plugwise/test_climate.py

178-178: Undefined name ANY

(F821)


178-178: Undefined name STATE_ON

(F821)

custom_components/plugwise/climate.py

327-327: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (1)
tests/components/plugwise/test_climate.py (1)

169-179: Test may fail due to implementation logic issue.

This test verifies that the restored schedule "Weekschema" is used when switching to AUTO mode (line 178). However, based on the implementation in climate.py lines 321-332, the current code always uses the device's current schedule rather than the stored _last_active_schedule. This means the test assertion may fail unless the implementation is fixed to actually use the restored schedule value when switching to AUTO mode.

After fixing the imports, verify this test passes. If it fails, it confirms the implementation issue flagged in the climate.py review where the stored schedule is never actually used.

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

🧹 Nitpick comments (7)
tests/components/plugwise/test_climate.py (3)

115-116: Prefer STATE_OFF over HVACMode.OFF in assertion.

The implementation calls set_schedule_state(..., STATE_OFF, ...). Use the constant for tighter matching.

-    mock_smile_adam.set_schedule_state.assert_called_with(
-        "c50f167537524366a5af7aa3942feb1e", HVACMode.OFF, "GF7  Woonkamer",
-    )
+    mock_smile_adam.set_schedule_state.assert_called_with(
+        "c50f167537524366a5af7aa3942feb1e", STATE_OFF, "GF7  Woonkamer",
+    )

Add import if needed:

-from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE
+from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_OFF

147-185: Use STATE_ON constant in restore test for consistency.

The integration passes STATE_ON/STATE_OFF; avoid raw "on".

-    mock_smile_adam_heat_cool.set_schedule_state.assert_called_with(
-        "f2bf9048bef64cc5b6d5110154e33c81", "on", "Weekschema"
-    )
+    mock_smile_adam_heat_cool.set_schedule_state.assert_called_with(
+        "f2bf9048bef64cc5b6d5110154e33c81", STATE_ON, "Weekschema"
+    )

Also add import:

-from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE
+from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON

385-386: Prefer STATE_OFF over HVACMode.OFF in assertion.

-    mock_smile_anna.set_schedule_state.assert_called_with(
-        "c784ee9fdab44e1395b8dee7d7a497d5", HVACMode.OFF, "standaard",
-    )
+    mock_smile_anna.set_schedule_state.assert_called_with(
+        "c784ee9fdab44e1395b8dee7d7a497d5", STATE_OFF, "standaard",
+    )

Add import if needed:

-from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE
+from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_OFF
custom_components/plugwise/climate.py (4)

135-137: Annotate _previous_action_mode to the enum type.

Improves clarity and linting.

-    _previous_action_mode = HVACAction.HEATING  # Upstream
+    _previous_action_mode: HVACAction = HVACAction.HEATING  # Upstream

139-147: Use dataclass attributes on restore; avoid dict round‑trip.

Also ensure the restored enum is of type HVACAction.

         if (extra_data := await self.async_get_last_extra_data()):
             LOGGER.debug("Extra data: %s", extra_data)
-            self._last_active_schedule = extra_data.as_dict()["last_active_schedule"]
-            self._previous_action_mode = extra_data.as_dict()["previous_action_mode"]
+            self._last_active_schedule = extra_data.last_active_schedule
+            self._previous_action_mode = (
+                extra_data.previous_action_mode
+                if isinstance(extra_data.previous_action_mode, HVACAction)
+                else HVACAction(extra_data.previous_action_mode)
+                if extra_data.previous_action_mode
+                else self._previous_action_mode
+            )

243-243: Remove stray debug log and typo.

Leftover debug with “HOI”/“havc_mode”.

-        LOGGER.debug("HOI havc_mode = %s", str(hvac))

276-284: Avoid side effects in property getter.

Updating _previous_action_mode inside hvac_action mixes read with write. Move this into a coordinator update hook or where regulation mode changes are processed.

Would you like a patch that updates _previous_action_mode in a @callback _handle_coordinator_update?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f07924 and 74e793c.

📒 Files selected for processing (2)
  • custom_components/plugwise/climate.py (9 hunks)
  • tests/components/plugwise/test_climate.py (6 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-23T16:26:34.700Z
Learnt from: bouwew
PR: plugwise/plugwise-beta#906
File: tests/components/plugwise/test_binary_sensor.py:9-16
Timestamp: 2025-08-23T16:26:34.700Z
Learning: The Plugwise integration test files import utilities like `snapshot_platform`, `MockConfigEntry`, and `async_fire_time_changed` from `tests.common`, which is from the Home Assistant core repository at https://github.com/home-assistant/core/blob/master/tests/common.py, not from local test files.

Applied to files:

  • tests/components/plugwise/test_climate.py
🧬 Code graph analysis (2)
tests/components/plugwise/test_climate.py (2)
custom_components/plugwise/climate.py (2)
  • PlugwiseClimateExtraStoredData (104-123)
  • as_dict (110-115)
tests/components/plugwise/conftest.py (2)
  • mock_smile_adam_heat_cool (185-208)
  • mock_config_entry (81-95)
custom_components/plugwise/climate.py (1)
custom_components/plugwise/entity.py (2)
  • PlugwiseEntity (31-93)
  • device (91-93)
🪛 Ruff (0.14.1)
custom_components/plugwise/climate.py

328-328: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (4)
tests/components/plugwise/test_climate.py (4)

33-38: LGTM: correct test utilities imported.


222-223: LGTM: climate_mode test data aligned with enums.


243-244: LGTM: cooling branch test data aligned.


25-25: ****

The import path in the test file is correct. In Home Assistant custom component test infrastructure, custom components located in custom_components/plugwise/ are automatically available under the homeassistant.components.plugwise namespace during testing. This is the standard pattern used in the test fixtures themselves (see conftest.py which also imports from homeassistant.components.plugwise). Changing the import to custom_components.plugwise.climate would break the tests.

Likely an incorrect or invalid review comment.

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

🧹 Nitpick comments (1)
tests/components/plugwise/test_climate.py (1)

146-184: Good test coverage for restoration, but consider improvements.

The test correctly addresses the past review feedback by using HVACAction.HEATING at line 160. However, consider these improvements:

  1. Line 182: Use STATE_ON constant for consistency. Replace the string "on" with the STATE_ON constant (which would need to be imported from homeassistant.const). This ensures consistency with the codebase and prevents issues if the string representation changes.

  2. Test verification is limited to external behavior. The test only verifies that set_schedule_state was called correctly, but doesn't confirm that the internal state variables (_last_active_schedule, _previous_action_mode) were actually restored. Consider accessing the climate entity's attributes or internal state to verify restoration happened correctly.

Apply this diff for the constant:

+from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON
-from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE

And update the assertion:

     mock_smile_adam_heat_cool.set_schedule_state.assert_called_with(
-        "f2bf9048bef64cc5b6d5110154e33c81", "on", "Weekschema"
+        "f2bf9048bef64cc5b6d5110154e33c81", STATE_ON, "Weekschema"
     )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 74e793c and 37d5da2.

📒 Files selected for processing (1)
  • tests/components/plugwise/test_climate.py (6 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-23T16:26:34.700Z
Learnt from: bouwew
PR: plugwise/plugwise-beta#906
File: tests/components/plugwise/test_binary_sensor.py:9-16
Timestamp: 2025-08-23T16:26:34.700Z
Learning: The Plugwise integration test files import utilities like `snapshot_platform`, `MockConfigEntry`, and `async_fire_time_changed` from `tests.common`, which is from the Home Assistant core repository at https://github.com/home-assistant/core/blob/master/tests/common.py, not from local test files.

Applied to files:

  • tests/components/plugwise/test_climate.py
🧬 Code graph analysis (1)
tests/components/plugwise/test_climate.py (2)
custom_components/plugwise/climate.py (2)
  • PlugwiseClimateExtraStoredData (104-123)
  • as_dict (110-115)
tests/components/plugwise/conftest.py (2)
  • mock_smile_adam_heat_cool (185-208)
  • mock_config_entry (81-95)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Setup for HA-core (release/master)
  • GitHub Check: Setup for HA-core (dev)
🔇 Additional comments (5)
tests/components/plugwise/test_climate.py (5)

25-37: LGTM! Imports are appropriate for the new restoration test.

The new imports support the restore_state testing functionality correctly.


221-221: LGTM! Test data correctly updated for climate_mode field.

The addition of the climate_mode field aligns with the heating state being tested.


242-242: LGTM! Test data correctly updated for climate_mode field.

The addition of the climate_mode field aligns with the cooling state being tested.


114-114: LGTM! Mock call updated for new API signature.

The additional location parameter "GF7 Woonkamer" correctly reflects the updated set_schedule_state signature.


384-384: LGTM! Mock call updated for new API signature.

The additional schedule name parameter "standaard" correctly reflects the updated set_schedule_state signature.

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

♻️ Duplicate comments (3)
custom_components/plugwise/climate.py (3)

103-124: Serialize enum to string value for JSON compatibility.

The previous_action_mode field stores an HVACAction enum directly, but Home Assistant's state restoration expects JSON-serializable data. Enums are not JSON-safe and may cause serialization errors or type instability across restarts.

Apply this diff to serialize the enum as a string in as_dict() and rehydrate it in from_dict():

     def as_dict(self) -> dict[str, Any]:
         """Return a dict representation of the text data."""
         return {
             "last_active_schedule": self.last_active_schedule,
-            "previous_action_mode": self.previous_action_mode,
+            "previous_action_mode": (
+                self.previous_action_mode.value if self.previous_action_mode else None
+            ),
         }

     @classmethod
     def from_dict(cls, restored: dict[str, Any]) -> PlugwiseClimateExtraStoredData:
         """Initialize a stored data object from a dict."""
+        prev = restored.get("previous_action_mode")
         return cls(
             last_active_schedule=restored.get("last_active_schedule"),
-            previous_action_mode=restored.get("previous_action_mode"),
+            previous_action_mode=HVACAction(prev) if prev else None,
         )

145-146: Access dataclass attributes directly instead of calling as_dict() twice.

These lines inefficiently call as_dict() twice to extract values from a PlugwiseClimateExtraStoredData instance. Since extra_data is already a dataclass instance, access its attributes directly for better performance and clarity.

Apply this diff:

-            self._last_active_schedule = extra_data.as_dict()["last_active_schedule"]
-            self._previous_action_mode = extra_data.as_dict()["previous_action_mode"]
+            self._last_active_schedule = extra_data.last_active_schedule
+            self._previous_action_mode = extra_data.previous_action_mode

242-244: Remove leftover debug statement.

Line 243 contains informal text ("HOI") and a typo ("havc_mode" instead of "hvac_mode"). This appears to be debug code that should be removed before merging.

Apply this diff:

-
-        LOGGER.debug("HOI havc_mode = %s", str(hvac))
         return hvac
🧹 Nitpick comments (1)
custom_components/plugwise/climate.py (1)

136-136: Add explicit type annotation.

Line 136 assigns HVACAction.HEATING but lacks a type annotation, unlike line 135. For consistency and type safety, add an explicit annotation.

Apply this diff:

-    _previous_action_mode = HVACAction.HEATING  # Upstream
+    _previous_action_mode: HVACAction = HVACAction.HEATING  # Upstream
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 37d5da2 and 3668caa.

📒 Files selected for processing (1)
  • custom_components/plugwise/climate.py (9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
custom_components/plugwise/climate.py (1)
custom_components/plugwise/entity.py (2)
  • PlugwiseEntity (31-93)
  • device (91-93)
🪛 Ruff (0.14.1)
custom_components/plugwise/climate.py

331-333: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (4)
custom_components/plugwise/climate.py (4)

126-126: LGTM!

RestoreEntity inheritance is now properly implemented with the required async_added_to_hass and extra_restore_state_data methods.


194-200: LGTM!

The property correctly returns a PlugwiseClimateExtraStoredData instance with both fields, ensuring proper state persistence across restarts.


274-288: LGTM!

The property correctly tracks _previous_action_mode when regulation modes are available and cooling/heating is active. This enables proper restoration when switching HVAC modes back on.


319-339: Schedule selection logic correctly implements PR objectives.

The implementation now properly stores and reuses _last_active_schedule when switching HVAC modes:

  • Captures the last valid schedule when it's not "off"
  • Reuses the saved schedule when the current schedule is "off"
  • Raises an error if attempting to enable AUTO mode without a schedule

Optional: Static analysis flags TRY003 for the error message on line 332. Consider extracting it to a module-level constant if preferred:

# At module level
_NO_SCHEDULE_ERROR = "Failed setting HVACMode, set a schedule first"

# In the method
raise HomeAssistantError(_NO_SCHEDULE_ERROR)

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Error while testing for Development HA-core:

⚠️ Warning: Incompatible while testing against dev HA-core.
✔️ Success: No problem with testing against released HA-core.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Error while testing for Development HA-core:

⚠️ Warning: Incompatible while testing against dev HA-core.
✔️ Success: No problem with testing against released HA-core.

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

♻️ Duplicate comments (2)
custom_components/plugwise/climate.py (2)

103-123: Fix enum serialization and type annotation.

Two issues with the dataclass:

  1. Type mismatch: Line 108 declares previous_action_mode: HVACAction (non-optional), but from_dict at line 122 uses restored.get("previous_action_mode") which returns None for missing keys. This creates a type inconsistency.

  2. Enum serialization: Lines 114 and 122 store and retrieve the HVACAction enum directly. Home Assistant's state restoration serializes to JSON, which doesn't natively support enum objects. You must serialize to the enum's string value and deserialize back to the enum.

Apply this diff:

 @dataclass
 class PlugwiseClimateExtraStoredData(ExtraStoredData):
     """Object to hold extra stored data."""
 
     last_active_schedule: str | None
-    previous_action_mode: HVACAction
+    previous_action_mode: HVACAction | None
 
     def as_dict(self) -> dict[str, Any]:
         """Return a dict representation of the text data."""
         return {
             "last_active_schedule": self.last_active_schedule,
-            "previous_action_mode": self.previous_action_mode,
+            "previous_action_mode": (
+                self.previous_action_mode.value if self.previous_action_mode else None
+            ),
         }
 
     @classmethod
     def from_dict(cls, restored: dict[str, Any]) -> PlugwiseClimateExtraStoredData:
         """Initialize a stored data object from a dict."""
+        prev = restored.get("previous_action_mode")
         return cls(
             last_active_schedule=restored.get("last_active_schedule"),
-            previous_action_mode=restored.get("previous_action_mode"),
+            previous_action_mode=HVACAction(prev) if prev else None,
         )

145-146: Critical syntax error: as_dict is a method, not a property.

Lines 145-146 access as_dict without parentheses, treating it as a property when it's actually a method defined at line 110. This will raise an AttributeError at runtime when Home Assistant attempts to restore state.

Apply this diff to access the dataclass attributes directly:

         if (extra_data := await self.async_get_last_extra_data()):
             LOGGER.debug("Extra data: %s", extra_data)
-            self._last_active_schedule = extra_data.as_dict["last_active_schedule"]
-            self._previous_action_mode = extra_data.as_dict["previous_action_mode"]
+            self._last_active_schedule = extra_data.last_active_schedule
+            self._previous_action_mode = extra_data.previous_action_mode
🧹 Nitpick comments (1)
custom_components/plugwise/climate.py (1)

135-136: Add type annotation to _previous_action_mode.

Line 136 initializes _previous_action_mode without a type annotation. For consistency with line 135 and to match the dataclass field type, add an explicit annotation.

Apply this diff:

     _last_active_schedule: str | None = None
-    _previous_action_mode = HVACAction.HEATING  # Upstream
+    _previous_action_mode: HVACAction = HVACAction.HEATING  # Upstream

If _previous_action_mode can be None after restoration (when extra data is missing or invalid), consider using HVACAction | None instead.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d4d5f0 and c1574ac.

📒 Files selected for processing (1)
  • custom_components/plugwise/climate.py (9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
custom_components/plugwise/climate.py (1)
custom_components/plugwise/entity.py (2)
  • PlugwiseEntity (31-93)
  • device (91-93)
🪛 Ruff (0.14.1)
custom_components/plugwise/climate.py

330-332: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Setup for HA-core (dev)
  • GitHub Check: Setup for HA-core (release/master)
🔇 Additional comments (5)
custom_components/plugwise/climate.py (5)

194-200: LGTM!

The property correctly returns both fields for state restoration using clear keyword arguments.


275-282: LGTM!

The logic correctly tracks the previous action mode when the gateway supports both cooling and heating, preserving it for later restoration when switching from OFF mode.


346-346: LGTM!

Correctly uses the tracked _previous_action_mode when restoring regulation mode after exiting OFF state.


5-5: LGTM!

The new imports are necessary and properly used:

  • dataclass for PlugwiseClimateExtraStoredData
  • HomeAssistantError for validation errors
  • ExtraStoredData, RestoreEntity for state persistence

Also applies to: 27-27, 29-29


318-338: Address potential None schedule edge case and TRY003 linter warning.

The code initializes _last_active_schedule as None (line 135) and only validates schedules when enabling AUTO mode (lines 329-332). However, when switching to non-AUTO modes like HEAT or OFF, if _last_active_schedule remains None (e.g., on first boot before any schedule is set), the code calls set_schedule_state with a None schedule parameter even though tests don't cover this edge case.

Two recommendations:

  1. Verify API contract: Confirm that the Plugwise API's set_schedule_state method accepts None schedules when the state is OFF. If not, add similar validation for non-AUTO modes or handle the None case explicitly.

  2. Address TRY003 linter warning: Move the error message to a module-level constant to resolve the linter flag:

+_NO_SCHEDULE_ERROR = "Failed setting HVACMode, set a schedule first"
+
...
             if hvac_mode == HVACMode.AUTO and not desired:
-                raise HomeAssistantError(
-                    "Failed setting HVACMode, set a schedule first"
-                )
+                raise HomeAssistantError(_NO_SCHEDULE_ERROR)

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Error while testing for Development HA-core:

⚠️ Warning: Incompatible while testing against dev HA-core.
✔️ Success: No problem with testing against released HA-core.

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

♻️ Duplicate comments (1)
custom_components/plugwise/climate.py (1)

103-123: Critical bug in from_dict and missing enum serialization.

Three issues in the dataclass:

  1. Critical: Line 122 accesses restored.previous_action_mode as an attribute, but restored is a dict[str, Any]. This will raise AttributeError at runtime.

  2. Major: The enum is not serialized to a string in as_dict(). Home Assistant's state restoration requires JSON-serializable data. Storing the enum object directly (line 114) will fail or cause type issues on deserialization.

  3. Minor: Line 108 has a trailing space after the type annotation.

Apply this diff to fix all three issues:

 @dataclass
 class PlugwiseClimateExtraStoredData(ExtraStoredData):
     """Object to hold extra stored data."""
 
     last_active_schedule: str | None
-    previous_action_mode: HVACAction 
+    previous_action_mode: HVACAction | None
 
     def as_dict(self) -> dict[str, Any]:
         """Return a dict representation of the text data."""
         return {
             "last_active_schedule": self.last_active_schedule,
-            "previous_action_mode": self.previous_action_mode,
+            "previous_action_mode": (
+                self.previous_action_mode.value if self.previous_action_mode else None
+            ),
         }
 
     @classmethod
     def from_dict(cls, restored: dict[str, Any]) -> PlugwiseClimateExtraStoredData:
         """Initialize a stored data object from a dict."""
+        prev = restored.get("previous_action_mode")
         return cls(
             last_active_schedule=restored.get("last_active_schedule"),
-            previous_action_mode=HVACAction(restored.previous_action_mode),
+            previous_action_mode=HVACAction(prev) if prev else None,
         )
🧹 Nitpick comments (1)
custom_components/plugwise/climate.py (1)

145-146: Avoid redundant as_dict() calls.

These lines call as_dict() to access dictionary keys, but extra_data is already a PlugwiseClimateExtraStoredData instance. Access its attributes directly for better performance and clarity.

Apply this diff:

         if (extra_data := await self.async_get_last_extra_data()):
             LOGGER.debug("Extra data: %s", extra_data)
-            self._last_active_schedule = extra_data.as_dict()["last_active_schedule"]
-            self._previous_action_mode = extra_data.as_dict()["previous_action_mode"]
+            self._last_active_schedule = extra_data.last_active_schedule
+            self._previous_action_mode = extra_data.previous_action_mode
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c1574ac and 3a10a26.

📒 Files selected for processing (1)
  • custom_components/plugwise/climate.py (9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
custom_components/plugwise/climate.py (1)
custom_components/plugwise/entity.py (2)
  • PlugwiseEntity (31-93)
  • device (91-93)
🪛 GitHub Actions: Test PR against HA-core
custom_components/plugwise/climate.py

[error] 105-105: Ruff formatting/linting issues were automatically fixed by the pre-commit hook. The hook reported: 'Found 1 error (1 fixed, 0 remaining)'. Command: 'pre-commit run --all-files --show-diff-on-failure'.

🪛 Ruff (0.14.1)
custom_components/plugwise/climate.py

330-332: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (3)
custom_components/plugwise/climate.py (3)

318-338: Verify set_schedule_state handles None schedule with STATE_OFF.

When switching to a non-AUTO mode (e.g., HEAT) and the current schedule is "off" with no saved _last_active_schedule, the variable desired becomes None (line 326). This None is then passed to set_schedule_state with STATE_OFF (lines 334-338).

Confirm that the API method tolerates schedule=None when state=STATE_OFF, or add a fallback to prevent passing None:

# Enabling HVACMode.AUTO requires a previously set schedule
if hvac_mode == HVACMode.AUTO and not desired:
    raise HomeAssistantError(
        "Failed setting HVACMode, set a schedule first"
    )

# Fallback for non-AUTO modes when no valid schedule available
if not desired and hvac_mode != HVACMode.AUTO:
    return  # or handle appropriately based on API requirements

Also consider shortening the error message or moving it to a module constant to address the TRY003 linter hint.


194-200: LGTM: State data properly packaged for restoration.

The property correctly returns both fields for persistence. Once the dataclass serialization issues are fixed, this will work as intended.


273-287: LGTM: Previous action mode tracking implemented correctly.

The logic properly tracks _previous_action_mode when the gateway supports both heating and cooling, allowing the system to restore the appropriate regulation mode when switching back from OFF.

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

🧹 Nitpick comments (1)
custom_components/plugwise/climate.py (1)

334-336: Consider shortening or extracting the error message.

The linter flags this error message as too long per TRY003. While the message is clear, you could improve maintainability by extracting it to a module-level constant or shortening it slightly.

For example:

# At module level
_ERR_NO_SCHEDULE = "Set a schedule before enabling Auto"

# In the method
if hvac_mode == HVACMode.AUTO and not desired:
    raise HomeAssistantError(_ERR_NO_SCHEDULE)

Or simply shorten inline:

-                raise HomeAssistantError(
-                    "Failed setting HVACMode, set a schedule first"
-                )
+                raise HomeAssistantError("Set a schedule before enabling Auto")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a10a26 and 56a5d0d.

📒 Files selected for processing (1)
  • custom_components/plugwise/climate.py (8 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
custom_components/plugwise/climate.py (1)
custom_components/plugwise/entity.py (2)
  • PlugwiseEntity (31-93)
  • device (91-93)
🪛 Ruff (0.14.1)
custom_components/plugwise/climate.py

334-336: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Prepare and validate pre-commit

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Error while testing for Development HA-core:

⚠️ Warning: Incompatible while testing against dev HA-core.
✔️ Success: No problem with testing against released HA-core.

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

🧹 Nitpick comments (1)
custom_components/plugwise/climate.py (1)

321-341: LGTM! Schedule handling correctly implements restoration logic.

The implementation properly:

  • Captures the current schedule when valid
  • Reuses the saved schedule when the current is "off"
  • Raises an error when switching to AUTO without a valid schedule
  • Passes the appropriate schedule to set_schedule_state

This fulfills the PR objective of storing and reusing the last schedule when switching HVAC modes.

Optional: Address static analysis hint.

The linter suggests (TRY003) keeping error messages shorter or defining them as module constants. This is a minor style issue.

If desired, apply this diff:

+# Error message constants
+ERROR_NO_SCHEDULE = "Failed setting HVACMode, set a schedule first"
+
 # ... rest of code ...

             # Enabling HVACMode.AUTO requires a previously set schedule
             if hvac_mode == HVACMode.AUTO and not desired:
-                raise HomeAssistantError(
-                    "Failed setting HVACMode, set a schedule first"
-                )
+                raise HomeAssistantError(ERROR_NO_SCHEDULE)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6593a9f and c4fe539.

📒 Files selected for processing (3)
  • custom_components/plugwise/climate.py (8 hunks)
  • custom_components/plugwise/manifest.json (1 hunks)
  • tests/components/plugwise/test_climate.py (6 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-23T16:26:34.700Z
Learnt from: bouwew
PR: plugwise/plugwise-beta#906
File: tests/components/plugwise/test_binary_sensor.py:9-16
Timestamp: 2025-08-23T16:26:34.700Z
Learning: The Plugwise integration test files import utilities like `snapshot_platform`, `MockConfigEntry`, and `async_fire_time_changed` from `tests.common`, which is from the Home Assistant core repository at https://github.com/home-assistant/core/blob/master/tests/common.py, not from local test files.

Applied to files:

  • tests/components/plugwise/test_climate.py
🧬 Code graph analysis (2)
custom_components/plugwise/climate.py (1)
custom_components/plugwise/entity.py (2)
  • PlugwiseEntity (31-93)
  • device (91-93)
tests/components/plugwise/test_climate.py (2)
custom_components/plugwise/climate.py (2)
  • PlugwiseClimateExtraStoredData (104-123)
  • as_dict (110-115)
tests/components/plugwise/conftest.py (2)
  • mock_smile_adam_heat_cool (185-208)
  • mock_config_entry (81-95)
🪛 Ruff (0.14.1)
custom_components/plugwise/climate.py

333-335: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Setup for HA-core (release/master)
  • GitHub Check: Setup for HA-core (dev)
🔇 Additional comments (7)
custom_components/plugwise/manifest.json (1)

10-10: LGTM! Dependency version updated appropriately.

The version bump to 1.8.3 aligns with the restoration features introduced in the climate component.

tests/components/plugwise/test_climate.py (3)

25-25: LGTM! Imports correctly support restoration testing.

The new imports enable testing of the restoration flow for climate entities.

Also applies to: 27-27, 32-37


146-225: LGTM! Comprehensive restoration test.

The test effectively covers:

  • Restoration with None values (living_room entity)
  • Error handling when switching to AUTO without a saved schedule
  • Successful restoration and reuse of saved schedule (bathroom entity)
  • Verification that set_schedule_state receives the correct restored schedule

114-114: LGTM! Test updates align with implementation changes.

The additional arguments to set_schedule_state and climate_mode data additions correctly reflect the updated API signature and restoration flow.

Also applies to: 262-262, 283-283, 425-425

custom_components/plugwise/climate.py (3)

103-123: LGTM! Dataclass correctly implements serialization.

The dataclass properly defines both fields as str | None, and the serialization/deserialization methods correctly handle string values. This aligns with the usage throughout the code where _previous_action_mode stores string values like HVACAction.HEATING.value.


139-149: LGTM! Restoration properly implemented.

The async_added_to_hass method correctly:

  • Calls the parent method first
  • Retrieves and deserializes the extra data
  • Restores both _last_active_schedule and _previous_action_mode

The extra_restore_state_data property correctly returns both fields for persistence.

Also applies to: 197-203


348-349: LGTM! Correctly restores regulation mode.

The code properly uses _previous_action_mode to restore the correct regulation mode (heating or cooling) when switching from OFF to another HVAC mode.

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4fe539 and 79a29f8.

📒 Files selected for processing (2)
  • custom_components/plugwise/climate.py (10 hunks)
  • tests/components/plugwise/test_climate.py (6 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-23T16:26:34.700Z
Learnt from: bouwew
PR: plugwise/plugwise-beta#906
File: tests/components/plugwise/test_binary_sensor.py:9-16
Timestamp: 2025-08-23T16:26:34.700Z
Learning: The Plugwise integration test files import utilities like `snapshot_platform`, `MockConfigEntry`, and `async_fire_time_changed` from `tests.common`, which is from the Home Assistant core repository at https://github.com/home-assistant/core/blob/master/tests/common.py, not from local test files.

Applied to files:

  • tests/components/plugwise/test_climate.py
🧬 Code graph analysis (2)
custom_components/plugwise/climate.py (1)
custom_components/plugwise/entity.py (2)
  • PlugwiseEntity (31-93)
  • device (91-93)
tests/components/plugwise/test_climate.py (2)
custom_components/plugwise/climate.py (2)
  • PlugwiseClimateExtraStoredData (105-124)
  • as_dict (111-116)
tests/components/plugwise/conftest.py (2)
  • mock_smile_adam_heat_cool (185-208)
  • mock_config_entry (81-95)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Setup for HA-core (dev)
  • GitHub Check: Setup for HA-core (release/master)
🔇 Additional comments (3)
tests/components/plugwise/test_climate.py (3)

25-27: LGTM - Imports support restoration testing.

The new imports for PlugwiseClimateExtraStoredData and State are correctly sourced and necessary for testing the restoration functionality added in this PR.


32-37: LGTM - Test utilities properly imported.

The addition of mock_restore_cache_with_extra_data is necessary for the new restoration test and is correctly imported from tests.common.

Based on learnings


262-262: LGTM - Mock data updated to match implementation expectations.

The additions of climate_mode to the mock data ensure test data consistency with the implementation's use of device.get(CLIMATE_MODE) for determining HVAC modes.

Also applies to: 283-283

@bouwew bouwew force-pushed the save_last_option branch 2 times, most recently from 0a43fab to 55fcb61 Compare October 23, 2025 16:18
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Error while testing for Development HA-core:

⚠️ Warning: Incompatible while testing against dev HA-core.
✔️ Success: No problem with testing against released HA-core.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Error while testing for Development HA-core:

⚠️ Warning: Incompatible while testing against dev HA-core.
✔️ Success: No problem with testing against released HA-core.

@sonarqubecloud
Copy link

@bouwew bouwew merged commit 004f191 into main Oct 23, 2025
13 checks passed
@bouwew bouwew deleted the save_last_option branch October 24, 2025 05:59
Sign up for free to 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.

2 participants