feat: new sensor displaying wire quality and related sensor details, available in diagnostics#321
Conversation
WalkthroughA new "wire" quality sensor feature has been introduced to the Robonect integration. The code now calculates and exposes the minimum wire sensor quality from API responses, adds a corresponding sensor entity, and ensures proper attribute handling. Translation entries for the new sensor have been added in German, English, French, and Dutch. Changes
Sequence Diagram(s)sequenceDiagram
participant RobonectAPI
participant Client
participant SensorEntity
Client->>RobonectAPI: Send "wire" command
RobonectAPI-->>Client: Return JSON response with sensors list
Client->>Client: Calculate min "quality" in sensors
Client->>Client: Add "lowest_quality" to response
SensorEntity->>Client: Retrieve wire sensor data
Client-->>SensorEntity: Provide "lowest_quality" and sensors attributes
SensorEntity->>SensorEntity: Set state and attributes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Poem
Note 🔌 MCP (Model Context Protocol) integration is now available in Early Access!Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 2
🔭 Outside diff range comments (2)
custom_components/robonect/sensor.py (1)
325-340: Avoid calling adapt_attributes() on lists; handle list vs. dict attributes explicitlyCurrently adapt_attributes(attrs, ...) is executed before the isinstance(attrs, list) check. If attrs is a list (e.g., $.wire.sensors), this can fail or silently do nothing depending on implementation. Also, empty lists are skipped due to the truthiness check.
Apply this diff to safely support lists and preserve previous behavior for dicts; also include empty lists by checking for None instead of truthiness:
- if self.entity_description.rest_attrs: - attrs = get_json_dict_path( - self.coordinator.data, self.entity_description.rest_attrs - ) - attrs = copy.copy(attrs) - if attrs: - adapt_attributes( - attrs, self.category, self.entry.data[CONF_ATTRS_UNITS] - ) - if not isinstance(attrs, list): - attributes.update(attrs) - else: - # Get last segment after dot - key_name = self.entity_description.rest_attrs.split(".")[-1] - attributes[key_name] = attrs + if self.entity_description.rest_attrs: + attrs = get_json_dict_path( + self.coordinator.data, self.entity_description.rest_attrs + ) + attrs = copy.copy(attrs) + if attrs is not None: + if isinstance(attrs, list): + # Optionally adapt each element if they are mappings + for i, item in enumerate(attrs): + if isinstance(item, dict): + adapt_attributes( + item, self.category, self.entry.data[CONF_ATTRS_UNITS] + ) + key_name = self.entity_description.rest_attrs.split(".")[-1] + attributes[key_name] = attrs + elif isinstance(attrs, dict): + adapt_attributes( + attrs, self.category, self.entry.data[CONF_ATTRS_UNITS] + ) + attributes.update(attrs) + else: + key_name = self.entity_description.rest_attrs.split(".")[-1] + attributes[key_name] = attrscustom_components/robonect/definitions.py (1)
25-31: Type annotation mismatch: rest_attrs is used as a JSONPath string, not a dictBoth RobonectSwitchEntityDescription.rest_attrs and RobonectSensorEntityDescription.rest_attrs are annotated as dict | None but are consistently used as strings (JSON paths). This confuses type checkers and contributors.
Apply this diff to align annotations with usage:
class RobonectSwitchEntityDescription(SwitchEntityDescription): @@ - rest_attrs: dict | None = None + rest_attrs: str | None = None @@ class RobonectSensorEntityDescription(SensorEntityDescription): @@ - rest_attrs: dict | None = None + rest_attrs: str | None = NoneAlso applies to: 37-43
🧹 Nitpick comments (1)
custom_components/robonect/translations/nl.json (1)
463-470: Dutch wording and attribute translation tweaks
- Line 464: Prefer “Draadkwaliteit” as a single compound word in Dutch.
- Line 467: Translate “Sensors” to “Sensoren” for consistency with other locales.
Apply this diff:
- "name": "Draad kwaliteit", + "name": "Draadkwaliteit", @@ - "sensors": { - "name": "Sensors" - } + "sensors": { + "name": "Sensoren" + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
custom_components/robonect/client/client.py(1 hunks)custom_components/robonect/definitions.py(1 hunks)custom_components/robonect/sensor.py(1 hunks)custom_components/robonect/translations/de.json(1 hunks)custom_components/robonect/translations/en.json(1 hunks)custom_components/robonect/translations/fr.json(1 hunks)custom_components/robonect/translations/nl.json(1 hunks)
🔇 Additional comments (3)
custom_components/robonect/translations/fr.json (1)
463-470: LGTM — correct French localization“Qualité du fil” and “Capteurs” read well and align with other locales. No issues spotted.
custom_components/robonect/translations/de.json (1)
463-470: LGTM — correct German localization“Drahtqualität” and “Sensoren” are appropriate and consistent.
custom_components/robonect/translations/en.json (1)
463-470: LGTM — clear English labeling“Wire quality” and “Sensors” are accurate and consistent with feature intent.
| if command == "wire": | ||
| # Extract the lowest quality value from the sensors list | ||
| sensors = result_json.get("sensors", []) | ||
| if sensors: | ||
| min_quality = min( | ||
| sensor.get("quality", float("inf")) for sensor in sensors | ||
| ) | ||
| result_json["lowest_quality"] = min_quality | ||
| else: | ||
| result_json["lowest_quality"] = None |
There was a problem hiding this comment.
Handle None/missing/non-numeric qualities and avoid returning infinity
Current logic can:
- Raise a TypeError if any sensor has
quality=None(min compares None with float). - Return
float("inf")when all qualities are missing, which is not meaningful for a sensor.
Filter to numeric (or castable) qualities and set None if none are valid.
Apply this diff:
- if command == "wire":
- # Extract the lowest quality value from the sensors list
- sensors = result_json.get("sensors", [])
- if sensors:
- min_quality = min(
- sensor.get("quality", float("inf")) for sensor in sensors
- )
- result_json["lowest_quality"] = min_quality
- else:
- result_json["lowest_quality"] = None
+ if command == "wire":
+ # Extract the lowest quality value from the sensors list
+ sensors = result_json.get("sensors", [])
+ if sensors:
+ qualities = []
+ for sensor in sensors:
+ q = sensor.get("quality")
+ try:
+ if q is not None:
+ qualities.append(float(q))
+ except (TypeError, ValueError):
+ continue
+ result_json["lowest_quality"] = min(qualities) if qualities else None
+ else:
+ result_json["lowest_quality"] = None📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if command == "wire": | |
| # Extract the lowest quality value from the sensors list | |
| sensors = result_json.get("sensors", []) | |
| if sensors: | |
| min_quality = min( | |
| sensor.get("quality", float("inf")) for sensor in sensors | |
| ) | |
| result_json["lowest_quality"] = min_quality | |
| else: | |
| result_json["lowest_quality"] = None | |
| if command == "wire": | |
| # Extract the lowest quality value from the sensors list | |
| sensors = result_json.get("sensors", []) | |
| if sensors: | |
| qualities = [] | |
| for sensor in sensors: | |
| q = sensor.get("quality") | |
| try: | |
| if q is not None: | |
| qualities.append(float(q)) | |
| except (TypeError, ValueError): | |
| continue | |
| result_json["lowest_quality"] = min(qualities) if qualities else None | |
| else: | |
| result_json["lowest_quality"] = None |
🤖 Prompt for AI Agents
In custom_components/robonect/client/client.py around lines 177 to 186, the
current code can raise a TypeError if any sensor's quality is None and may
return float("inf") if all qualities are missing. To fix this, filter the sensor
qualities to include only numeric values before computing the minimum. If no
valid numeric qualities are found, set result_json["lowest_quality"] to None
instead of infinity.
| RobonectSensorEntityDescription( | ||
| key=".wire", | ||
| rest="$.wire.lowest_quality", | ||
| rest_attrs="$.wire.sensors", | ||
| state_class=SensorStateClass.MEASUREMENT, | ||
| native_unit_of_measurement=PERCENTAGE, | ||
| entity_category=EntityCategory.DIAGNOSTIC, | ||
| icon="mdi:check-network", | ||
| category="wire", | ||
| translation_key="wire", | ||
| ), |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
New wire quality sensor looks correct and consistent with attribute handling
State path, units, and state class make sense; attributes path returns the sensors list which matches the updated list-handling in sensor.py. Icon, translation_key, and category align.
If useful, verify the upstream client always provides a numeric 0–100 lowest_quality and a list at $.wire.sensors:
🏁 Script executed:
#!/bin/bash
rg -n --hidden -A2 -B2 'lowest_quality' custom_components
rg -n --hidden -A2 -B2 '\.wire\.sensors' custom_componentsLength of output: 1649
🏁 Script executed:
#!/bin/bash
# Inspect how "wire" and its sensors list are populated in the client
rg -n 'result_json\["wire"' -A5 custom_components/robonect/client/client.py
rg -n 'wire\s*=' -A5 custom_components/robonect/client/client.py
rg -n 'sensors\s*=' -A5 custom_components/robonect/client/client.pyLength of output: 530
🏁 Script executed:
#!/bin/bash
# Find where the client fetches data and how it structures the JSON
rg -n 'async def' -A2 custom_components/robonect/client/client.py
rg -n 'class .*Client' -A2 custom_components/robonect/client/client.py
rg -n 'await .*get' -A3 custom_components/robonect/client/client.pyLength of output: 2196
🏁 Script executed:
#!/bin/bash
# Display the full implementation of the state() method to verify JSON structure
rg -n -A30 'async def state' custom_components/robonect/client/client.pyLength of output: 1210
Adjust JSONPath for wire quality sensor
The client’s status JSON places lowest_quality and sensors at the top level—not under a wire object—so the current paths won’t match. Update the entity description in custom_components/robonect/definitions.py accordingly.
• File: custom_components/robonect/definitions.py (around line 178)
Change the JSONPath for rest and rest_attrs:
RobonectSensorEntityDescription(
key=".wire",
- rest="$.wire.lowest_quality",
- rest_attrs="$.wire.sensors",
+ rest="$.lowest_quality",
+ rest_attrs="$.sensors",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
icon="mdi:check-network",
category="wire",
translation_key="wire",
),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| RobonectSensorEntityDescription( | |
| key=".wire", | |
| rest="$.wire.lowest_quality", | |
| rest_attrs="$.wire.sensors", | |
| state_class=SensorStateClass.MEASUREMENT, | |
| native_unit_of_measurement=PERCENTAGE, | |
| entity_category=EntityCategory.DIAGNOSTIC, | |
| icon="mdi:check-network", | |
| category="wire", | |
| translation_key="wire", | |
| ), | |
| RobonectSensorEntityDescription( | |
| key=".wire", | |
| rest="$.lowest_quality", | |
| rest_attrs="$.sensors", | |
| state_class=SensorStateClass.MEASUREMENT, | |
| native_unit_of_measurement=PERCENTAGE, | |
| entity_category=EntityCategory.DIAGNOSTIC, | |
| icon="mdi:check-network", | |
| category="wire", | |
| translation_key="wire", | |
| ), |
🤖 Prompt for AI Agents
In custom_components/robonect/definitions.py around lines 178 to 188, the
JSONPath expressions for the wire quality sensor are incorrect because
`lowest_quality` and `sensors` are at the top level of the `status` JSON, not
under a `wire` object. Update the `rest` path from `$.wire.lowest_quality` to
`$.lowest_quality` and the `rest_attrs` path from `$.wire.sensors` to
`$.sensors` to correctly reflect the JSON structure.
Summary by CodeRabbit