Skip to content

Conversation

@dirixmjm
Copy link
Contributor

@dirixmjm dirixmjm commented Aug 18, 2025

Add MotionSensitivity select configuration parameter
add binary sensor to show configuration has changed

Summary by CodeRabbit

  • New Features

    • Added Motion Sensitivity selector, Ping Device and Schedule Light Sensor Calibration buttons, plus diagnostic sensors "Motion config changed" and "Battery config changed".
  • Chores

    • Removed legacy motion-scan and battery-savings services.
    • Adjusted numeric limits for sleep/awake settings.
    • Improved unload/cleanup sequencing and device-unavailable messaging; entity additions now use default update behavior.
    • Bumped integration and dependency versions.
  • Documentation

    • Updated UI strings and translations for new entities and removed services.

@coderabbitai
Copy link

coderabbitai bot commented Aug 18, 2025

Walkthrough

Adds a Select platform and two buttons, two diagnostic binary sensors; removes two services and their schemas/translations; adjusts several async_add_entities calls to default update behavior; changes number limits and set behavior; updates coordinator message; awaits coordinator.unsubscribe_all_nodefeatures() on unload; bumps dependency and versions.

Changes

Cohort / File(s) Summary
Select platform (new)
custom_components/plugwise_usb/select.py
New Select platform: PlugwiseSelectEntityDescription, SELECT_TYPES (motion sensitivity), PlugwiseUSBSelectEntity, setup/unload subscribing to NodeEvent.LOADED, PARALLEL_UPDATES, SCAN_INTERVAL.
Platform registration & service constants
custom_components/plugwise_usb/const.py
Adds Platform.SELECT to PLUGWISE_USB_PLATFORMS; removes service constants/schemas for configure_battery_savings and configure_scan.
Binary sensors & translations
custom_components/plugwise_usb/binary_sensor.py, custom_components/plugwise_usb/strings.json, custom_components/plugwise_usb/translations/en.json, custom_components/plugwise_usb/translations/nl.json
Adds diagnostic binary sensors motion_config_dirty and battery_config_dirty (device_class SAFETY, EntityCategory.DIAGNOSTIC, disabled by default) and corresponding string/translation entries.
Buttons & translations
custom_components/plugwise_usb/button.py, custom_components/plugwise_usb/strings.json, custom_components/plugwise_usb/translations/en.json, custom_components/plugwise_usb/translations/nl.json
Adds ping_node and calibrate_light button types (call ping_update and scan_calibrate_light) and corresponding string/translation entries.
Remove services (YAML & translations)
custom_components/plugwise_usb/services.yaml, custom_components/plugwise_usb/strings.json, custom_components/plugwise_usb/translations/en.json, custom_components/plugwise_usb/translations/nl.json
Removes configure_scan and configure_battery_savings service definitions and their translations/fields.
Entity add/update behavior
custom_components/plugwise_usb/binary_sensor.py, custom_components/plugwise_usb/event.py, custom_components/plugwise_usb/sensor.py, custom_components/plugwise_usb/switch.py, custom_components/plugwise_usb/number.py
Removed explicit update_before_add=True from async_add_entities(...) calls; now use default behavior.
Numbers (limits & set behavior)
custom_components/plugwise_usb/number.py
Lowered native_max_value for sleep_duration (→1440 minutes) and awake_duration (→60 seconds); async_set_native_value now awaits API call then writes state without pre-setting _attr_native_value.
Coordinator message
custom_components/plugwise_usb/coordinator.py
UpdateFailed message for unavailable initialized non-battery nodes now includes device MAC from self.node.node_info.mac.
Unload sequencing
custom_components/plugwise_usb/__init__.py
async_unload_entry() now awaits coordinator.unsubscribe_all_nodefeatures() before unloading platforms.
Metadata bumps
custom_components/plugwise_usb/manifest.json, pyproject.toml, CHANGELOG.md
Bumped plugwise-usb requirement (0.44.11 → 0.44.12) and integration/project version fields (integration 0.55.8 → 0.55.9; project version updated); updated changelog entries.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant HA as Home Assistant
  participant Integration as Plugwise USB (Select)
  participant Coord as Coordinator
  participant Node as Plugwise Node

  rect rgb(240,245,250)
  Note over HA,Integration: Config entry setup / preload
  HA->>Integration: async_setup_entry()
  Integration->>Coord: subscribe(NodeEvent.LOADED)
  loop preload loaded nodes
    Coord-->>Integration: node (with MOTION_CONFIG)
    Integration->>HA: async_add_entities(SelectEntity)
  end
  end

  rect rgb(245,255,245)
  Note over User,HA: User selects motion sensitivity
  User->>HA: select option
  HA->>Integration: async_select_option(option)
  Integration->>Node: set_motion_sensitivity_level(...)
  Node-->>Coord: MOTION_CONFIG updated
  Coord-->>Integration: data update
  Integration->>HA: state update (current_option)
  end
Loading
sequenceDiagram
  autonumber
  participant HA as Home Assistant
  participant Init as __init__.py
  participant Coord as Coordinator

  Note over HA,Init: Config entry unload
  HA->>Init: async_unload_entry()
  Init->>Coord: await unsubscribe_all_nodefeatures()
  Init->>HA: unload platforms
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • ArnoutD
  • bouwew

Poem

I hopped to tweak a tiny part,
Added selects, buttons, and a chart.
Dirty flags now softly glow,
Sensitivity choices come and go.
Services hopped away — I thumped: "All clear!" 🐇


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5264682 and ca6f969.

📒 Files selected for processing (3)
  • custom_components/plugwise_usb/strings.json (5 hunks)
  • custom_components/plugwise_usb/translations/en.json (2 hunks)
  • custom_components/plugwise_usb/translations/nl.json (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • custom_components/plugwise_usb/translations/en.json
  • custom_components/plugwise_usb/strings.json
  • custom_components/plugwise_usb/translations/nl.json
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mdi_sed

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

@dirixmjm
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Aug 18, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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

🔭 Outside diff range comments (1)
custom_components/plugwise_usb/coordinator.py (1)

85-91: Ensure robust AVAILABLE check, fix f-string typo, and avoid private node attribute

The current logic still directly indexes states[NodeFeature.AVAILABLE], uses the private _mac_in_str, and misses a space in the f-string. Please update as follows:

-        if (
-            not self.node.node_info.is_battery_powered
-            and self.node.initialized
-            and not states[NodeFeature.AVAILABLE].state
-        ):
-            raise UpdateFailed(f"Device '{self.node._mac_in_str}'is (temporarily) not available")
+        if (
+            not self.node.node_info.is_battery_powered
+            and self.node.initialized
+        ):
+            available = states.get(NodeFeature.AVAILABLE)
+            if available is not None and not available.state:
+                raise UpdateFailed(
+                    f"Device '{self.node.mac}' is (temporarily) not available"
+                )
  • Replaced private _mac_in_str with public mac
  • Fixed missing space in the message
  • Guarded against a missing AVAILABLE entry using states.get

No other self.node._… references were found in custom_components/plugwise_usb.

🧹 Nitpick comments (6)
custom_components/plugwise_usb/translations/nl.json (1)

52-57: Minor Dutch grammar improvements for new binary_sensor names

Compound nouns in Dutch prefer hyphenation. Suggested tweaks for better readability.

-      "motion_config_dirty": {
-        "name": "Instellingen bewegingsdetectie aangepast"
-      },
+      "motion_config_dirty": {
+        "name": "Bewegingsdetectie-instellingen aangepast"
+      },
-      "battery_config_dirty": {
-        "name": "Batterij instellingen aangepast"
-      }
+      "battery_config_dirty": {
+        "name": "Batterij-instellingen aangepast"
+      }
custom_components/plugwise_usb/__init__.py (1)

166-171: Optional: unsubscribe all nodefeatures concurrently to speed up unload

If many nodes are present, running unsubscriptions concurrently can reduce unload time.

Apply this diff:

-    for coordinator in config_entry.runtime_data[NODES].values():
-        await coordinator.unsubscribe_all_nodefeatures()
+    await asyncio.gather(
+        *(
+            coordinator.unsubscribe_all_nodefeatures()
+            for coordinator in config_entry.runtime_data[NODES].values()
+        )
+    )
custom_components/plugwise_usb/number.py (1)

182-187: Avoid stale UI state after set; refresh or optimistically update

After await self.async_number_fn(int(value)) you only call self.async_write_ha_state() without updating _attr_native_value or triggering a refresh. If the device doesn’t push a state update immediately, the UI can momentarily show the old value.

Two options:

  • Preferred: trigger a coordinator refresh and let _handle_coordinator_update set the value.
  • Alternative: optimistically update _attr_native_value to the requested value, then write state.

Preferred (refresh-based) approach:

-        await self.async_number_fn(int(value))
-        self.async_write_ha_state()
+        await self.async_number_fn(int(value))
+        # Refresh to pick up the authoritative value from the device
+        await self.coordinator.async_request_refresh()

Alternative (optimistic update):

-        await self.async_number_fn(int(value))
-        self.async_write_ha_state()
+        await self.async_number_fn(int(value))
+        self._attr_native_value = int(value)
+        self.async_write_ha_state()
+        # Optionally also refresh to reconcile if the device adjusts the value
+        await self.coordinator.async_request_refresh()
custom_components/plugwise_usb/binary_sensor.py (1)

46-63: Verify battery node_feature and prefer PROBLEM device class for “config dirty”.

  • Semantics: A “config changed/dirty” flag isn’t a safety condition. BinarySensorDeviceClass.PROBLEM is a closer fit than SAFETY for both new entities.
  • Correctness: Please confirm that NodeFeature.BATTERY exposes a dirty attribute. If not, this will always resolve to AttributeError or False. The corresponding “config” feature (e.g., the one used by your API for battery configuration) should be referenced here instead of NodeFeature.BATTERY.

Proposed device_class adjustment:

 PlugwiseBinarySensorEntityDescription(
   key="motion_config_dirty",
   translation_key="motion_config_dirty",
   node_feature=NodeFeature.MOTION_CONFIG,
-  device_class=BinarySensorDeviceClass.SAFETY,
+  device_class=BinarySensorDeviceClass.PROBLEM,
   entity_category=EntityCategory.DIAGNOSTIC,
   entity_registry_enabled_default=False,
   api_attribute="dirty",
 ),
 PlugwiseBinarySensorEntityDescription(
   key="battery_config_dirty",
   translation_key="battery_config_dirty",
-  node_feature=NodeFeature.BATTERY,
-  device_class=BinarySensorDeviceClass.SAFETY,
+  node_feature=NodeFeature.BATTERY,
+  device_class=BinarySensorDeviceClass.PROBLEM,
   entity_category=EntityCategory.DIAGNOSTIC,
   entity_registry_enabled_default=False,
   api_attribute="dirty",
 ),

If your API exposes a distinct “battery config” feature with a dirty flag, point this entity’s node_feature to that specific feature instead of BATTERY. I can adjust the patch once you confirm the correct feature name.

custom_components/plugwise_usb/select.py (2)

131-131: Strip trailing whitespace (ruff W291).

Remove the trailing spaces to satisfy linters.

-        self._attr_current_option = current_option.name 
+        self._attr_current_option = current_option.name
@@
-        self.async_write_ha_state() 
+        self.async_write_ha_state()

Also applies to: 148-148


53-53: Typo in docstring: “selectes”.

Small grammar fix.

-    """Set up the USB selectes from a config entry."""
+    """Set up the USB selects from a config entry."""
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c8adbea and 22dbb30.

📒 Files selected for processing (13)
  • custom_components/plugwise_usb/__init__.py (1 hunks)
  • custom_components/plugwise_usb/binary_sensor.py (3 hunks)
  • custom_components/plugwise_usb/const.py (1 hunks)
  • custom_components/plugwise_usb/coordinator.py (1 hunks)
  • custom_components/plugwise_usb/event.py (1 hunks)
  • custom_components/plugwise_usb/number.py (4 hunks)
  • custom_components/plugwise_usb/select.py (1 hunks)
  • custom_components/plugwise_usb/sensor.py (1 hunks)
  • custom_components/plugwise_usb/services.yaml (0 hunks)
  • custom_components/plugwise_usb/strings.json (1 hunks)
  • custom_components/plugwise_usb/switch.py (1 hunks)
  • custom_components/plugwise_usb/translations/en.json (1 hunks)
  • custom_components/plugwise_usb/translations/nl.json (1 hunks)
💤 Files with no reviewable changes (1)
  • custom_components/plugwise_usb/services.yaml
🧰 Additional context used
🧬 Code Graph Analysis (2)
custom_components/plugwise_usb/__init__.py (1)
custom_components/plugwise_usb/coordinator.py (1)
  • unsubscribe_all_nodefeatures (117-123)
custom_components/plugwise_usb/select.py (2)
custom_components/plugwise_usb/coordinator.py (1)
  • PlugwiseUSBDataUpdateCoordinator (27-123)
custom_components/plugwise_usb/entity.py (2)
  • PlugwiseUSBEntity (27-75)
  • PlugwiseUSBEntityDescription (21-24)
🪛 Ruff (0.12.2)
custom_components/plugwise_usb/coordinator.py

90-90: Private member accessed: _mac_in_str

(SLF001)

custom_components/plugwise_usb/select.py

131-131: Trailing whitespace

Remove trailing whitespace

(W291)


148-148: Trailing whitespace

Remove trailing whitespace

(W291)

🔇 Additional comments (11)
custom_components/plugwise_usb/const.py (1)

28-28: SELECT platform inclusion looks good

Adding Platform.SELECT to PLUGWISE_USB_PLATFORMS aligns with the new select implementation.

custom_components/plugwise_usb/strings.json (1)

52-57: LGTM on new diagnostic binary sensors

Keys and names for motion_config_dirty and battery_config_dirty look consistent with the new diagnostics.

custom_components/plugwise_usb/sensor.py (1)

191-193: Dropping update_before_add is fine for coordinator-driven entities

Given entities are added only on NodeEvent.LOADED and are coordinator-backed, omitting update_before_add should not regress initial state handling. Entities may show unknown until the first coordinator refresh, which is standard.

If you notice initial states lingering as unknown unexpectedly, consider confirming the coordinator has data at add time or triggering an immediate refresh after subscription.

custom_components/plugwise_usb/event.py (1)

94-96: Consistent removal of update_before_add

Matches the pattern used elsewhere in the PR; appropriate for coordinator-based event entities.

Please confirm there’s no dependency on a pre-add update for initial button state logic in downstream automations.

custom_components/plugwise_usb/__init__.py (1)

166-168: Good fix: await the async unsubscription

Awaiting unsubscribe_all_nodefeatures() ensures cleanup completes before unloading platforms.

custom_components/plugwise_usb/switch.py (1)

97-99: Confirm update_before_add Behavior
EntityPlatform.async_add_entities defaults to update_before_add=False across supported Home Assistant versions¹. That means entities won’t fetch their initial state before being added unless you explicitly pass update_before_add=True. Please review whether default=False matches your intended behavior or if you need to add the flag to your calls in:

  • custom_components/plugwise_usb/switch.py (line 98)
  • custom_components/plugwise_usb/sensor.py (line 192)
  • custom_components/plugwise_usb/select.py (line 70)
  • custom_components/plugwise_usb/number.py (line 125)
  • custom_components/plugwise_usb/event.py (line 95)
  • custom_components/plugwise_usb/binary_sensor.py (line 91)
  • custom_components/plugwise_usb/button.py (line 74)

If you do need entities to update before add, update the calls accordingly:

- async_add_entities(entities)
+ async_add_entities(entities, update_before_add=True)

¹https://developers.home-assistant.io/docs/integration_fetching_data

custom_components/plugwise_usb/number.py (3)

72-75: Confirm sleep_duration upper bound (1440 minutes) against device firmware limits

I inspected the implementation in
• custom_components/plugwise_usb/number.py (lines 65–75)
and related translation strings but found no alternate bounds or validation logic. Please verify against the device’s official specification or firmware to ensure 1440 minutes is supported and won’t be rejected or silently clamped at runtime.


84-87: Verify awake_duration upper bound (60 seconds)

Lowering the maximum from its previous value to 60 s is a behavioral change. Please confirm that all supported Plugwise devices enforce an awake_duration ≤ 60 s and that your frontend UIs and documentation have been updated to reflect this new limit.

Key locations to review:

  • custom_components/plugwise_usb/number.py (lines 84–87):
    native_max_value=60, native_min_value=1, api_attribute="awake_duration"
  • Translation keys in:
    • custom_components/plugwise_usb/strings.json ("sed_awake_duration")
    • custom_components/plugwise_usb/translations/en.json
    • custom_components/plugwise_usb/translations/nl.json

Ensure:

  • Hardware rejects values > 60 s (or handles them gracefully).
  • UI controls (sliders/inputs) cap at 60 s.
  • Documentation and user-facing schemas/docs call out 60 s as the maximum.

125-126: Verify default update_before_add behavior for async_add_entities

In custom_components/plugwise_usb/number.py (lines 123–127), you’re calling:

async_add_entities(entities)

By default, update_before_add is False, so newly added entities won’t fetch their initial state before being registered. Please confirm that this default aligns with all Home Assistant versions you support. If you need each entity to refresh immediately, explicitly pass:

async_add_entities(entities, update_before_add=True)

This check applies to every async_add_entities(entities) invocation in:

  • event.py
  • sensor.py
  • select.py
  • number.py
  • binary_sensor.py
  • button.py
  • switch.py
custom_components/plugwise_usb/binary_sensor.py (2)

16-16: LGTM: Importing EntityCategory and Platform is appropriate.

Needed for DIAGNOSTIC entities and storing unsubscribes in runtime_data.


91-91: LGTM: Dropping update_before_add is fine.

The default behavior suffices; avoids unnecessary pre-update calls.

@dirixmjm dirixmjm marked this pull request as ready for review August 18, 2025 15:38
@dirixmjm dirixmjm requested a review from a team as a code owner August 18, 2025 15:39
Copy link

@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

♻️ Duplicate comments (1)
custom_components/plugwise_usb/select.py (1)

26-34: Critical: Description ‘options’ is undeclared and wrong type; use an Enum field and update usage

The dataclass doesn’t declare options, yet it’s passed at construction and used later. Also, SelectEntityDescription already has an options field with a different type (list[str] | None). Passing an Enum there will raise TypeError and/or cause runtime errors.

Fix by declaring an explicit Enum type field (options_enum) and using it for option mapping. This also resolves Ruff’s unused Enum import once used in typing.

@@
 @dataclass(kw_only=True)
 class PlugwiseSelectEntityDescription(
     PlugwiseUSBEntityDescription, SelectEntityDescription
 ):
     """Describes Plugwise select entity."""
 
     api_attribute: str = ""
     async_select_fn: str = ""
+    # Enum class holding available options (e.g., MotionSensitivity)
+    options_enum: type[Enum]
@@
 SELECT_TYPES: tuple[PlugwiseSelectEntityDescription, ...] = (
     PlugwiseSelectEntityDescription(
         key="sensitivity_level",
         translation_key="motion_sensitivity_level",
         async_select_fn="set_motion_sensitivity_level",
         entity_category=EntityCategory.CONFIG,
         node_feature=NodeFeature.MOTION_CONFIG,
         api_attribute="sensitivity_level",
-        options = MotionSensitivity,
+        options_enum=MotionSensitivity,
     ),
 )
@@
         """Initialize a select entity."""
         super().__init__(node_duc, entity_description)
-        self._options: list[str] = [o.name for o in entity_description.options]
+        self._options: list[str] = [o.name for o in entity_description.options_enum]
         self._current_option = "MEDIUM"
         self.async_select_fn = getattr(
             node_duc.node, entity_description.async_select_fn
         )
         self._node_duc = node_duc
@@
     async def async_select_option(self, option: str) -> None:
         """Change to the selected entity option."""
-        value = self.entity_description.options[option]
+        # Lookup Enum by member name (e.g., "HIGH", "MEDIUM", "OFF")
+        value = self.entity_description.options_enum[option]
         await self.async_select_fn(value)
         self._current_option = option
         self._attr_current_option = option
-        self.async_write_ha_state() 
+        self.async_write_ha_state()

Also applies to: 36-46, 101-114, 145-151

🧹 Nitpick comments (3)
custom_components/plugwise_usb/select.py (2)

54-56: Nit: Typo in setup docstring

“selectes” → “selects”.

-    """Set up the USB selectes from a config entry."""
+    """Set up the USB selects from a config entry."""

131-133: Nit: Remove trailing whitespace

Trailing whitespace violates linters (Ruff W291) and CI style checks.

-        self._attr_current_option = current_option.name 
+        self._attr_current_option = current_option.name
custom_components/plugwise_usb/strings.json (1)

151-156: Align entity name casing with existing style (nitpick)

Elsewhere in this file names tend toward sentence case (e.g., “Reset energy collection”, “Motion reset timer”). Consider making these consistent.

Apply:

-      "ping":{
-        "name": "Ping Device"
-      },
-      "calibrate_light":{
-        "name": "Schedule Light Sensor Calibration"
-      }
+      "ping":{
+        "name": "Ping device"
+      },
+      "calibrate_light":{
+        "name": "Schedule light sensor calibration"
+      }
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 22dbb30 and 98c88dd.

📒 Files selected for processing (6)
  • custom_components/plugwise_usb/button.py (1 hunks)
  • custom_components/plugwise_usb/entity.py (1 hunks)
  • custom_components/plugwise_usb/select.py (1 hunks)
  • custom_components/plugwise_usb/strings.json (2 hunks)
  • custom_components/plugwise_usb/translations/en.json (2 hunks)
  • custom_components/plugwise_usb/translations/nl.json (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
custom_components/plugwise_usb/select.py (2)
custom_components/plugwise_usb/coordinator.py (1)
  • PlugwiseUSBDataUpdateCoordinator (27-123)
custom_components/plugwise_usb/entity.py (2)
  • PlugwiseUSBEntity (27-78)
  • PlugwiseUSBEntityDescription (21-24)
🪛 Ruff (0.12.2)
custom_components/plugwise_usb/select.py

6-6: enum.Enum imported but unused

Remove unused import: enum.Enum

(F401)


132-132: Trailing whitespace

Remove trailing whitespace

(W291)


151-151: Trailing whitespace

Remove trailing whitespace

(W291)

⏰ 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)
custom_components/plugwise_usb/strings.json (2)

51-57: All translation keys properly wired up and localized

Translation keys for motion_config_dirty and battery_config_dirty are correctly referenced in code and present in both English and Dutch translations.

  • binary_sensor.py
    – Lines 47–48: translation_key="motion_config_dirty"
    – Lines 56–57: translation_key="battery_config_dirty"
  • custom_components/plugwise_usb/strings.json, en.json, nl.json
    – Both motion_config_dirty and battery_config_dirty entries exist

60-66: Motion sensitivity select translation key verified

The translation_key="motion_sensitivity_level" in select.py (line 39) matches the entry in custom_components/plugwise_usb/strings.json (line 60), and all option labels (High, Medium, Off) are defined. No additional localization files are required.

Copy link

@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 (1)
custom_components/plugwise_usb/translations/nl.json (1)

60-62: Dutch phrasing nit: consider more natural label

Optional: “Bewegingsdetectie Gevoeligheid” reads a bit clunky. Consider “Gevoeligheid bewegingsdetectie” or “Gevoeligheid voor bewegingsdetectie”.

If you prefer, I can update all locales consistently once you choose the phrasing.

📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 98c88dd and 898f829.

📒 Files selected for processing (2)
  • custom_components/plugwise_usb/translations/en.json (2 hunks)
  • custom_components/plugwise_usb/translations/nl.json (2 hunks)
🧰 Additional context used
🪛 GitHub Actions: Validate with hassfest
custom_components/plugwise_usb/translations/en.json

[error] 1-1: Command 'docker run --rm -v /home/runner/work/plugwise_usb-beta/plugwise_usb-beta://github/workspace ghcr.io/home-assistant/hassfest' failed: Invalid translations/en.json: Invalid translation key 'HIGH', need to be [a-z0-9-_]+ and cannot start or end with a hyphen or underscore. for dictionary value @ data['entity']['select']['motion_sensitivity_level']['state']. Got {'HIGH': 'High', 'MEDIUM': 'Medium', 'OFF': 'Off'}

⏰ 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: Setup for HA-core (release/master)
🔇 Additional comments (4)
custom_components/plugwise_usb/translations/nl.json (2)

52-57: Good addition: diagnostic binary_sensors for config changes

The new motion/battery config-dirty sensors are clear and align with the PR’s goals.


151-156: New buttons look good

“Ping Node” and “Licht Sensor Calibratie Inplannen” fit the new functionality.

custom_components/plugwise_usb/translations/en.json (2)

52-57: Good addition: diagnostic binary_sensors for config changes

Clear naming and matches the new entities added by the PR.


151-156: Buttons translations look consistent

“Ping Device” and “Schedule Light Sensor Calibration” are clear and concise.

Copy link

@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_usb/translations/en.json (2)

60-66: Fix hassfest failure: state keys must be lowercase

Pipeline fails because translation keys must match [a-z0-9-_]+. Use lowercase keys and align emitted options in select.py accordingly.

Apply this diff:

       "motion_sensitivity_level": {
         "name": "Motion Sensitivity Level",
         "state": {
-          "HIGH": "High",
-          "MEDIUM": "Medium",
-          "OFF": "Off"
+          "high": "High",
+          "medium": "Medium",
+          "off": "Off"
         }
       }

Additionally, ensure custom_components/plugwise_usb/select.py emits and expects lowercase values:

  • Build options with [o.name.lower() for o in entity_description.options]
  • Current option via current_option.name.lower()
  • Map user selection back with option.upper() when looking up enum members.

60-66: Implement lowercase normalization for select options in select.py

The translation files are now using lowercase state keys, but select.py still emits and looks up options using uppercase names. This will prevent translations from resolving correctly for motion_sensitivity_level. Please update both the options list and the lookup:

• File: custom_components/plugwise_usb/select.py
– Around line 108, change:
```diff

  •   self._options: list[str] = [o.name for o in entity_description.options]
    
  •   self._options: list[str] = [o.name.lower() for o in entity_description.options]
    
    – Around line 147, change:  
    ```diff
    
  •   value = self.entity_description.options[option]
    
  •   value = self.entity_description.options[option.upper()]
    
    
    

These two changes ensure that UI options are presented lowercase (matching your translations) and that lookups against the Enum (which uses uppercase names) still succeed.

📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 898f829 and 377713c.

📒 Files selected for processing (4)
  • custom_components/plugwise_usb/button.py (1 hunks)
  • custom_components/plugwise_usb/strings.json (2 hunks)
  • custom_components/plugwise_usb/translations/en.json (2 hunks)
  • custom_components/plugwise_usb/translations/nl.json (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • custom_components/plugwise_usb/button.py
  • custom_components/plugwise_usb/translations/nl.json
🧰 Additional context used
🪛 GitHub Actions: Validate with hassfest
custom_components/plugwise_usb/translations/en.json

[error] 1-1: Invalid translations/en.json: Invalid translation key 'HIGH', need to be [a-z0-9-_]+ and cannot start or end with a hyphen or underscore. for dictionary value @ data['entity']['select']['motion_sensitivity_level']['state']. Got {'HIGH': 'High', 'MEDIUM': 'Medium', 'OFF': 'Off'}

⏰ 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: Setup for HA-core (release/master)
🔇 Additional comments (4)
custom_components/plugwise_usb/translations/en.json (2)

52-57: New diagnostic binary_sensors look good

Naming is clear and consistent with the new diagnostic entities. No issues spotted.


150-156: Buttons translations added correctly

Keys and naming follow the existing style. Good addition.

custom_components/plugwise_usb/strings.json (2)

52-57: New diagnostic binary_sensors look good

Naming aligns with en.json and intended diagnostics behavior.


151-156: Buttons translations added correctly

Consistent with en.json and existing naming conventions.

Copy link

@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

♻️ Duplicate comments (4)
custom_components/plugwise_usb/translations/en.json (1)

60-66: Ensure select.py emits and handles lowercase option keys

The JSON translations are now fully lowercase, but select.py still uses uppercase enum names and does not normalize to lowercase. Update custom_components/plugwise_usb/select.py as follows:

• In __init__, emit lowercase option names:

-       self._options: list[str] = [o.name for o in entity_description.options]
+       self._options: list[str] = [o.name.lower() for o in entity_description.options]

-       self._current_option = "MEDIUM"
+       # Default to first option in lowercase
+       self._current_option = entity_description.options[0].name.lower()

• In the coordinator update handler, set the current option to lowercase:

-        self._current_option = current_option.name
-        self._attr_current_option = current_option.name 
+        self._current_option = current_option.name.lower()
+        self._attr_current_option = current_option.name.lower()

• In async_select_option, accept a lowercase option string and map back to the enum by uppercasing the key:

-    async def async_select_option(self, option: str) -> None:
+    async def async_select_option(self, option: str) -> None:
         """Change to the selected entity option."""
-        value = self.entity_description.options[option]
+        # Map lowercase option back to enum member
+        value = self.entity_description.options[option.upper()]

         await self.async_select_fn(value)
         self._current_option = option
         self._attr_current_option = option
         self.async_write_ha_state()

With these changes, both the displayed options and state updates will use lowercase keys matching en.json, and selections will correctly map to the API enums.

custom_components/plugwise_usb/select.py (2)

36-46: Use the new options_enum field and keep naming consistent

Replace options= with options_enum= to match the dataclass and avoid runtime errors.

 SELECT_TYPES: tuple[PlugwiseSelectEntityDescription, ...] = (
     PlugwiseSelectEntityDescription(
         key="sensitivity_level",
         translation_key="motion_sensitivity_level",
         async_select_fn="set_motion_sensitivity_level",
         entity_category=EntityCategory.CONFIG,
         node_feature=NodeFeature.MOTION_CONFIG,
         api_attribute="sensitivity_level",
-        options = MotionSensitivity,
+        options_enum=MotionSensitivity,
     ),
 )

26-35: Critical: options field missing from description; construction will fail

SELECT_TYPES passes options = MotionSensitivity, but the dataclass has no options (or similar) field. This raises TypeError: __init__() got an unexpected keyword argument 'options'.

Introduce a field (e.g., options_enum) and type it to the enum class:

 @dataclass(kw_only=True)
 class PlugwiseSelectEntityDescription(
     PlugwiseUSBEntityDescription, SelectEntityDescription
 ):
     """Describes Plugwise select entity."""
 
     api_attribute: str = ""
     async_select_fn: str = ""
+    # Enum class that defines available options (iterable and name-addressable)
+    options_enum: type[MotionSensitivity]
custom_components/plugwise_usb/strings.json (1)

60-66: Motion Sensitivity select key and state keys are correctly named

Good catch on using motion_sensitivity_level with lowercase state keys (high/medium/off). This addresses earlier feedback about the typo and hassfest’s lowercase requirement for option keys.

Run to verify translations are consistent across files and JSON stays valid:

#!/bin/bash
set -euo pipefail

echo "Validate JSON syntax with jq:"
jq . custom_components/plugwise_usb/strings.json >/dev/null

echo "Check select key and state keys across files:"
for f in custom_components/plugwise_usb/strings.json \
         custom_components/plugwise_usb/translations/en.json \
         custom_components/plugwise_usb/translations/nl.json; do
  echo "-- $f --"
  rg -nC2 '"motion_sensitivity_level"' "$f"
  rg -nC1 '"state"\s*:\s*{\s*"high".*"medium".*"off"' "$f" || true
done
🧹 Nitpick comments (5)
custom_components/plugwise_usb/binary_sensor.py (1)

46-63: Consider PROBLEM device class for “config changed”

Using SAFETY suggests a hazard. These diagnostics represent a pending configuration change; BinarySensorDeviceClass.PROBLEM better fits semantics and UI.

     PlugwiseBinarySensorEntityDescription(
         key="motion_config_dirty",
         translation_key="motion_config_dirty",
         node_feature=NodeFeature.MOTION_CONFIG,
-        device_class=BinarySensorDeviceClass.SAFETY,
+        device_class=BinarySensorDeviceClass.PROBLEM,
         entity_category=EntityCategory.DIAGNOSTIC,
         entity_registry_enabled_default=False,
         api_attribute="dirty",
     ),
     PlugwiseBinarySensorEntityDescription(
         key="battery_config_dirty",
         translation_key="battery_config_dirty",
         node_feature=NodeFeature.BATTERY,
-        device_class=BinarySensorDeviceClass.SAFETY,
+        device_class=BinarySensorDeviceClass.PROBLEM,
         entity_category=EntityCategory.DIAGNOSTIC,
         entity_registry_enabled_default=False,
         api_attribute="dirty",
     ),

Please confirm the intended UX; if these indicate “needs user attention” rather than “safety risk,” PROBLEM is preferable.

custom_components/plugwise_usb/select.py (3)

54-54: Nit: docstring typo

-    """Set up the USB selectes from a config entry."""
+    """Set up the USB selects from a config entry."""

62-69: Minor: use node name consistently in debug log

Elsewhere you log node_duc.node.name. Use the same here, and improve message clarity.

-            _LOGGER.debug("Add select entities for %s | duc=%s", mac, node_duc.name)
+            _LOGGER.debug("Add select entities for node %s (%s)", node_duc.node.name, mac)

6-6: Remove unused import

Enum isn’t used (ruff F401). It’s not needed once options_enum is typed to MotionSensitivity.

-from enum import Enum
custom_components/plugwise_usb/strings.json (1)

53-54: Nit: prefer sentence case for entity names to match surrounding entries

Nearby names like “Network roundtrip time”, “Awake reason”, and “Motion reset timer” use sentence case. Consider matching that style.

-        "name": "Motion Config Changed"
+        "name": "Motion configuration changed"
@@
-        "name": "Battery Config Changed"
+        "name": "Battery configuration changed"

Also applies to: 56-57

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 377713c and 781b24e.

📒 Files selected for processing (14)
  • custom_components/plugwise_usb/__init__.py (1 hunks)
  • custom_components/plugwise_usb/binary_sensor.py (3 hunks)
  • custom_components/plugwise_usb/button.py (1 hunks)
  • custom_components/plugwise_usb/const.py (1 hunks)
  • custom_components/plugwise_usb/coordinator.py (1 hunks)
  • custom_components/plugwise_usb/event.py (1 hunks)
  • custom_components/plugwise_usb/number.py (4 hunks)
  • custom_components/plugwise_usb/select.py (1 hunks)
  • custom_components/plugwise_usb/sensor.py (1 hunks)
  • custom_components/plugwise_usb/services.yaml (0 hunks)
  • custom_components/plugwise_usb/strings.json (2 hunks)
  • custom_components/plugwise_usb/switch.py (1 hunks)
  • custom_components/plugwise_usb/translations/en.json (2 hunks)
  • custom_components/plugwise_usb/translations/nl.json (2 hunks)
💤 Files with no reviewable changes (1)
  • custom_components/plugwise_usb/services.yaml
🚧 Files skipped from review as they are similar to previous changes (7)
  • custom_components/plugwise_usb/init.py
  • custom_components/plugwise_usb/event.py
  • custom_components/plugwise_usb/number.py
  • custom_components/plugwise_usb/sensor.py
  • custom_components/plugwise_usb/const.py
  • custom_components/plugwise_usb/button.py
  • custom_components/plugwise_usb/translations/nl.json
🧰 Additional context used
🧬 Code graph analysis (1)
custom_components/plugwise_usb/select.py (2)
custom_components/plugwise_usb/coordinator.py (1)
  • PlugwiseUSBDataUpdateCoordinator (27-123)
custom_components/plugwise_usb/entity.py (2)
  • PlugwiseUSBEntity (27-75)
  • PlugwiseUSBEntityDescription (21-24)
🪛 Ruff (0.12.2)
custom_components/plugwise_usb/select.py

6-6: enum.Enum imported but unused

Remove unused import: enum.Enum

(F401)


132-132: Trailing whitespace

Remove trailing whitespace

(W291)


151-151: Trailing whitespace

Remove trailing whitespace

(W291)

custom_components/plugwise_usb/coordinator.py

90-90: Private member accessed: _mac_in_str

(SLF001)

🪛 GitHub Actions: Validate with hassfest
custom_components/plugwise_usb/translations/en.json

[error] 1-1: hassfest: Invalid translation key 'HIGH' in translations/en.json; keys must be lowercase and match [a-z0-9-_]+. The dictionary at data['entity']['select']['motion_sensitivity_level']['state'] has keys {'HIGH': 'High', 'MEDIUM': 'Medium', 'OFF': 'Off'}.

⏰ 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: Setup for HA-core (release/master)
🔇 Additional comments (7)
custom_components/plugwise_usb/switch.py (1)

98-98: LGTM: drop update_before_add and use default add behavior

Using async_add_entities(entities) is consistent with recent HA guidance and the rest of this PR. No concerns here.

custom_components/plugwise_usb/translations/en.json (2)

51-57: Nice additions: diagnostic binary sensors

The new translation entries for motion_config_dirty and battery_config_dirty look good and align with the new entities.


151-155: Buttons: translations OK

ping_node and calibrate_light entries align with the new button entities.

custom_components/plugwise_usb/binary_sensor.py (2)

16-16: Import of EntityCategory is appropriate

Needed for DIAGNOSTIC categorization. Looks good.


91-91: LGTM: default entity add

Dropping update_before_add matches the pattern elsewhere in this PR.

custom_components/plugwise_usb/strings.json (2)

52-57: New diagnostic binary_sensors look correct and align with the PR intent

The added translation keys for motion_config_dirty and battery_config_dirty are clear and consistent with HA conventions.


151-156: Translation keys confirmed for new buttons

  • Verified translation_key="ping_node" in custom_components/plugwise_usb/button.py (lines 49–51), matching the JSON entry for “Ping Device”.
  • Verified translation_key="calibrate_light" in the same file (lines 56–58), matching the JSON entry for “Schedule Light Sensor Calibration”.

No discrepancies found—buttons will render correctly in the UI.

@dirixmjm dirixmjm requested a review from bouwew August 24, 2025 11:34
@dirixmjm dirixmjm marked this pull request as draft August 24, 2025 11:34
@dirixmjm dirixmjm marked this pull request as ready for review August 25, 2025 07:21
Copy link

@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 (4)
custom_components/plugwise_usb/select.py (4)

25-33: Dataclass missing options field; introduce options_enum (Enum type)

The description doesn’t declare options, but instances pass options=MotionSensitivity and code accesses entity_description.options. This will raise at construction/runtime. Define options_enum with an Enum type.

Apply:

@@
-from dataclasses import dataclass
+from dataclasses import dataclass
+from enum import Enum
@@
 class PlugwiseSelectEntityDescription(
     PlugwiseUSBEntityDescription, SelectEntityDescription
 ):
     """Describes Plugwise select entity."""
 
     async_select_fn: str = ""
+    # Enum containing the available options (e.g., MotionSensitivity)
+    options_enum: type[Enum]

34-43: Use options_enum in SELECT_TYPES

Switch the constructor argument from options to options_enum to match the dataclass.

Apply:

@@
         node_feature=NodeFeature.MOTION_CONFIG,
-        options = MotionSensitivity,
+        options_enum=MotionSensitivity,

98-111: Build options from options_enum; drop redundant reassignment

Derive lowercased options from the Enum and avoid reassigning entity_description (already set by super()).

Apply:

         self.async_select_fn = getattr(
             node_duc.node, entity_description.async_select_fn
         )
-        self._attr_options: list[str] = [o.name.lower() for o in entity_description.options]
-        self.entity_description = entity_description
+        self._attr_options = [o.name.lower() for o in entity_description.options_enum]
         self._node_duc = node_duc

131-137: Normalize/validate input; map back to Enum; update state immediately

Validate the incoming string, map to Enum via uppercase key, and only expose lowercase to HA. Removes risk of KeyError and ensures UI updates instantly.

Apply:

     async def async_select_option(self, option: str) -> None:
         """Change to the selected entity option."""
-        value = self.entity_description.options[option.upper()]
-        await self.async_select_fn(value)
-        self._current_option = option
-        self._attr_current_option = option
-        self.async_write_ha_state()
+        normalized = option.strip().lower()
+        if normalized not in self._attr_options:
+            raise ValueError(f"Unsupported option: {option}")
+        value = self.entity_description.options_enum[normalized.upper()]
+        await self.async_select_fn(value)
+        self._attr_current_option = normalized
+        self.async_write_ha_state()
+        # Optionally: await self.coordinator.async_request_refresh()
🧹 Nitpick comments (5)
custom_components/plugwise_usb/sensor.py (1)

191-193: Confirm initial state without update_before_add

Dropping update_before_add relies on a prior coordinator refresh or push to populate state; otherwise entities may start as unknown. If the coordinator isn’t refreshed before entity add, consider explicitly requesting a refresh or restoring the old flag where needed.

If needed, you can explicitly request a refresh right after adding entities:

-            async_add_entities(entities)
+            async_add_entities(entities)
+            await config_entry.runtime_data[NODES][mac].async_request_refresh()
custom_components/plugwise_usb/number.py (2)

124-126: Confirm initial state without update_before_add

As with other platforms, ensure the coordinator has refreshed before/at add time so numbers don’t start unknown.


182-186: Reflect number changes immediately in UI; validate/cast input

Currently we await the device call and write state but don’t set _attr_native_value. Set it optimistically for better UX and normalize to int; optionally clamp to min/max.

Apply:

     async def async_set_native_value(self, value: float) -> None:
         """Update the current value."""
 
-        await self.async_number_fn(int(value))
-        self.async_write_ha_state()
+        new_value = int(value)
+        # Optional clamp; HA UI enforces bounds but API callers may bypass it
+        if self.native_min_value is not None:
+            new_value = max(self.native_min_value, new_value)
+        if self.native_max_value is not None:
+            new_value = min(self.native_max_value, new_value)
+        await self.async_number_fn(new_value)
+        self._attr_native_value = new_value
+        self.async_write_ha_state()
custom_components/plugwise_usb/select.py (2)

51-51: Typo: “selectes” → “selects”

Minor docstring fix.

Apply:

-    """Set up the USB selectes from a config entry."""
+    """Set up the USB selects from a config entry."""

112-130: Normalize current_option to lowercase (good); consider setting both cached and attr

You set _attr_current_option to lower() which matches emitted options—good. Optionally set a local cache if you rely on it elsewhere (not required for SelectEntity).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 781b24e and 6bf585e.

📒 Files selected for processing (16)
  • custom_components/plugwise_usb/__init__.py (1 hunks)
  • custom_components/plugwise_usb/binary_sensor.py (3 hunks)
  • custom_components/plugwise_usb/button.py (1 hunks)
  • custom_components/plugwise_usb/const.py (1 hunks)
  • custom_components/plugwise_usb/coordinator.py (1 hunks)
  • custom_components/plugwise_usb/event.py (1 hunks)
  • custom_components/plugwise_usb/manifest.json (1 hunks)
  • custom_components/plugwise_usb/number.py (4 hunks)
  • custom_components/plugwise_usb/select.py (1 hunks)
  • custom_components/plugwise_usb/sensor.py (1 hunks)
  • custom_components/plugwise_usb/services.yaml (0 hunks)
  • custom_components/plugwise_usb/strings.json (2 hunks)
  • custom_components/plugwise_usb/switch.py (1 hunks)
  • custom_components/plugwise_usb/translations/en.json (2 hunks)
  • custom_components/plugwise_usb/translations/nl.json (2 hunks)
  • pyproject.toml (1 hunks)
💤 Files with no reviewable changes (1)
  • custom_components/plugwise_usb/services.yaml
✅ Files skipped from review due to trivial changes (1)
  • pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (10)
  • custom_components/plugwise_usb/event.py
  • custom_components/plugwise_usb/strings.json
  • custom_components/plugwise_usb/switch.py
  • custom_components/plugwise_usb/coordinator.py
  • custom_components/plugwise_usb/init.py
  • custom_components/plugwise_usb/button.py
  • custom_components/plugwise_usb/translations/nl.json
  • custom_components/plugwise_usb/const.py
  • custom_components/plugwise_usb/translations/en.json
  • custom_components/plugwise_usb/binary_sensor.py
🧰 Additional context used
🧬 Code graph analysis (1)
custom_components/plugwise_usb/select.py (2)
custom_components/plugwise_usb/coordinator.py (1)
  • PlugwiseUSBDataUpdateCoordinator (27-125)
custom_components/plugwise_usb/entity.py (2)
  • PlugwiseUSBEntity (27-75)
  • PlugwiseUSBEntityDescription (21-24)
🔇 Additional comments (4)
custom_components/plugwise_usb/manifest.json (2)

12-12: Version sync

Ensure manifest "version": "0.55.9" matches your tagged release and HACS expectations (pyproject may use a pre-release like 0.55.9a0; the manifest version is what HA shows).


11-12: Confirm plugwise-usb 0.44.12 includes MotionSensitivity API with no breaking changes

  • ✅ plugwise-usb 0.44.12 is published on PyPI and its CHANGELOG shows PR #323 adding the MotionSensitivity enum (named levels Off/Medium/High) and light sensitivity calibration for scan devices (raw.githubusercontent.com)
  • ⚠️ There are no “BREAK” or “BREAKING” entries in the v0.44.12 section of the CHANGELOG, indicating no documented breaking changes in this release (raw.githubusercontent.com)
  • 🔧 Ensure your Home Assistant core environment can install and run plugwise-usb 0.44.12 (e.g. meets its Python requirement and supports USB discovery in custom integrations)
custom_components/plugwise_usb/number.py (2)

72-75: Update sleep_duration max to 1440: confirm device constraints

Changing native_max_value from device-maximum (likely 65535) to 1440 minutes is a behavior change. Validate against device specs and ensure existing user values >1440 are handled gracefully (UI will now clamp/deny).


84-87: Update awake_duration max to 60 seconds: confirm device constraints

Similar to sleep_duration: verify that 60 seconds is the correct upper bound for all supported nodes to avoid unexpected validation errors.

Copy link

@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 (5)
custom_components/plugwise_usb/select.py (5)

22-24: Nit: PARALLEL_UPDATES/SCAN_INTERVAL are likely unused with CoordinatorEntity

With CoordinatorEntity managing updates, these module-level constants are typically ignored for this platform. Consider removing to avoid confusion unless HA still reads them for this integration.

-PARALLEL_UPDATES = 2
-SCAN_INTERVAL = timedelta(seconds=30)
+# CoordinatorEntity handles updates; keep only if explicitly needed by HA.

90-96: Optional: guard unsubscribe on unload for robustness

If setup partially fails or the key isn’t present, this can KeyError. Defensive guard makes unload resilient.

-    config_entry.runtime_data[Platform.SELECT][UNSUB_NODE_LOADED]()
+    unsub = (
+        config_entry.runtime_data
+        .get(Platform.SELECT, {})
+        .get(UNSUB_NODE_LOADED)
+    )
+    if unsub:
+        unsub()

114-131: Optional: handle missing/None values from coordinator data

If the device temporarily omits the attribute, we can avoid exceptions and log once.

-        current_option = getattr(
-            data,
-            self.entity_description.key,
-        )
-        self._attr_current_option = current_option.name.lower()
+        current_option = getattr(data, self.entity_description.key, None)
+        if current_option is None:
+            _LOGGER.debug(
+                "Missing %s for %s (feature %s)",
+                self.entity_description.key,
+                self._node_info.mac,
+                self.entity_description.node_feature,
+            )
+            return
+        self._attr_current_option = current_option.name.lower()
         self.async_write_ha_state()

12-16: Prefer HomeAssistantError over ValueError for service-call feedback

Raising HomeAssistantError yields clearer feedback in HA logs and UI.

 from homeassistant.core import HomeAssistant, callback
+from homeassistant.exceptions import HomeAssistantError

132-141: Map invalid selections to HomeAssistantError; optionally refresh after change

  • Raise HomeAssistantError for better UX.
  • Optionally request a refresh after the device call to reconcile state.
-        if normalized not in self._attr_options:
-            raise ValueError(f"Unsupported option: {option}")
+        if normalized not in self._attr_options:
+            raise HomeAssistantError(f"Unsupported option: {option}")
         value = self.entity_description.options_enum[normalized.upper()]
         await self.async_select_fn(value)
         self._current_option = normalized
         self._attr_current_option = normalized
         self.async_write_ha_state()
+        # Optionally: sync back from device if it doesn’t push state
+        # await self.coordinator.async_request_refresh()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 49bbea1 and 3db9f5a.

📒 Files selected for processing (1)
  • custom_components/plugwise_usb/select.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
custom_components/plugwise_usb/select.py (2)
custom_components/plugwise_usb/coordinator.py (1)
  • PlugwiseUSBDataUpdateCoordinator (27-125)
custom_components/plugwise_usb/entity.py (2)
  • PlugwiseUSBEntity (27-75)
  • PlugwiseUSBEntityDescription (21-24)
🔇 Additional comments (6)
custom_components/plugwise_usb/select.py (6)

26-34: Dataclass shape looks correct and future-proof

The Select entity description cleanly extends the base description and HA’s SelectEntityDescription; typing options_enum: type[Enum] is appropriate given how it’s used later.


35-44: SELECT_TYPES entry is well-formed

Good use of translation_key, EntityCategory.CONFIG, and binding to NodeFeature.MOTION_CONFIG with MotionSensitivity as the options enum.


61-62: LGTM: corrected debug log uses node_duc.node.name

This addresses the earlier AttributeError risk from using node_duc.name. Thanks for fixing it.


84-88: Preloading already-loaded nodes is correct

Looping through api_stick.nodes and seeding selects for loaded nodes ensures entities appear on startup. Looks good.


101-112: Good: options derived from Enum and normalized to lowercase

Using entity_description.options_enum and emitting lowercase strings aligns with translations and keeps UI consistent.


54-72: No duplicate event handler registration for SELECT

The select.py setup clears config_entry.runtime_data[Platform.SELECT] before subscribing to node events, matching the pattern used in all other platform modules. This reset guarantees that on each reload the previous subscription is discarded and a single, fresh subscription is registered:

  • Line 76 in custom_components/plugwise_usb/select.py resets the runtime data:
    config_entry.runtime_data[Platform.SELECT] = {}
  • Line 77 then stores the unsubscribe callback returned by api_stick.subscribe_to_node_events, ensuring proper cleanup on unload.

Since the code already clears before reassigning and follows the existing, tested pattern across platforms, no further changes are needed.

Copy link

@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 (2)
custom_components/plugwise_usb/translations/en.json (2)

151-155: Align button names to sentence case for consistency

Most entity names use sentence case. These two new buttons read as title case; recommend sentence case for UI consistency.

       "ping_node":{
-        "name": "Ping Device"
+        "name": "Ping device"
       },
       "calibrate_light":{
-        "name": "Schedule Light Sensor Calibration"
+        "name": "Schedule light sensor calibration"
       }

52-57: Replace “config” with “configuration” in translation strings

Please update the user-facing names in custom_components/plugwise_usb/translations/en.json to avoid the abbreviation “config” and use the full word “configuration” for clarity and consistency:

• File: custom_components/plugwise_usb/translations/en.json
• Lines: 52–57

       "motion_config_dirty": {
-        "name": "Motion config changed"
+        "name": "Motion configuration changed"
       },
       "battery_config_dirty": {
-        "name": "Battery config changed"
+        "name": "Battery configuration changed"
       }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0195b7e and d73a67d.

📒 Files selected for processing (4)
  • CHANGELOG.md (1 hunks)
  • custom_components/plugwise_usb/translations/en.json (2 hunks)
  • custom_components/plugwise_usb/translations/nl.json (2 hunks)
  • pyproject.toml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • pyproject.toml
  • custom_components/plugwise_usb/translations/nl.json
⏰ 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 (1)
custom_components/plugwise_usb/translations/en.json (1)

60-66: LGTM: Lowercase options and mapping verified

  • Translation key remains correct (motion_sensitivity_level).
  • Options are emitted in lowercase (self._attr_options = [o.name.lower() …] at line 109).
  • Current state is stored in lowercase (self._attr_current_option = current_option.name.lower() at line 127).
  • The async_select_option method normalizes input to lowercase (normalized = option.strip().lower() at line 132).

No further changes needed.

@sonarqubecloud
Copy link

Copy link

@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)
custom_components/plugwise_usb/translations/en.json (1)

150-155: Align button label casing with surrounding style

Most strings here follow sentence case. Recommend “Ping device” (lowercase “device”) for consistency.

       "ping_node":{
-        "name": "Ping Device"
+        "name": "Ping device"
       },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d73a67d and 5264682.

📒 Files selected for processing (2)
  • custom_components/plugwise_usb/translations/en.json (2 hunks)
  • custom_components/plugwise_usb/translations/nl.json (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • custom_components/plugwise_usb/translations/nl.json
⏰ 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: Setup for HA-core (release/master)
🔇 Additional comments (3)
custom_components/plugwise_usb/translations/en.json (3)

59-66: Translations Verified and Approved

  • No remaining misspellings of motion_sensitivy_level were found.
  • All motion_sensitivity_level state keys are correctly lowercase (high, medium, off).

With these checks passing, the translation entries are valid and align with Home Assistant conventions.


51-57: Configuration terminology ready for update

Verified that the keys motion_config_dirty and battery_config_dirty exist in strings.json and nl.json and that there are no remaining "config changed" entries in en.json. Please apply the following diff to complete the wording polish:

custom_components/plugwise_usb/translations/en.json
@@ 52,56
-      "motion_config_dirty": {
-        "name": "Motion config changed"
-      },
+      "motion_config_dirty": {
+        "name": "Motion configuration changed"
+      },
-      "battery_config_dirty": {
-        "name": "Battery config changed"
-      }
+      "battery_config_dirty": {
+        "name": "Battery configuration changed"
+      }

160-170: All translation casing consistent across locales

The Dutch translations in nl.json already use sentence case (“Knop I/​O groep N gedrukt”) matching the updated English phrasing, so no further changes are needed.

Copy link
Contributor

@bouwew bouwew left a comment

Choose a reason for hiding this comment

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

Nice work @dirixmjm !

@dirixmjm dirixmjm merged commit 07f4421 into main Aug 25, 2025
10 checks passed
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.

3 participants