Small additions for Homee (#137000)

* fix entity set value error handling

* Translation for node_state sensor

* add entrance gate operator to covers

* fix review comments

* Update tests/components/homee/test_cover.py

* Delete Logging statement

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Markus Adrario 2025-02-01 21:11:53 +01:00 committed by GitHub
parent ba427a1054
commit f5fd49d8cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 66 additions and 3 deletions

View File

@ -68,6 +68,7 @@ def get_device_class(node: HomeeNode) -> CoverDeviceClass | None:
"""Determine the device class a homee node based on the node profile.""" """Determine the device class a homee node based on the node profile."""
COVER_DEVICE_PROFILES = { COVER_DEVICE_PROFILES = {
NodeProfile.GARAGE_DOOR_OPERATOR: CoverDeviceClass.GARAGE, NodeProfile.GARAGE_DOOR_OPERATOR: CoverDeviceClass.GARAGE,
NodeProfile.ENTRANCE_GATE_OPERATOR: CoverDeviceClass.GATE,
NodeProfile.SHUTTER_POSITION_SWITCH: CoverDeviceClass.SHUTTER, NodeProfile.SHUTTER_POSITION_SWITCH: CoverDeviceClass.SHUTTER,
} }
@ -93,6 +94,7 @@ def is_cover_node(node: HomeeNode) -> bool:
return node.profile in [ return node.profile in [
NodeProfile.ELECTRIC_MOTOR_METERING_SWITCH, NodeProfile.ELECTRIC_MOTOR_METERING_SWITCH,
NodeProfile.ELECTRIC_MOTOR_METERING_SWITCH_WITHOUT_SLAT_POSITION, NodeProfile.ELECTRIC_MOTOR_METERING_SWITCH_WITHOUT_SLAT_POSITION,
NodeProfile.ENTRANCE_GATE_OPERATOR,
NodeProfile.GARAGE_DOOR_OPERATOR, NodeProfile.GARAGE_DOOR_OPERATOR,
NodeProfile.SHUTTER_POSITION_SWITCH, NodeProfile.SHUTTER_POSITION_SWITCH,
] ]

View File

@ -2,7 +2,9 @@
from pyHomee.const import AttributeState, AttributeType, NodeProfile, NodeState from pyHomee.const import AttributeState, AttributeType, NodeProfile, NodeState
from pyHomee.model import HomeeAttribute, HomeeNode from pyHomee.model import HomeeAttribute, HomeeNode
from websockets.exceptions import ConnectionClosed
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
@ -137,7 +139,13 @@ class HomeeNodeEntity(Entity):
async def async_set_value(self, attribute: HomeeAttribute, value: float) -> None: async def async_set_value(self, attribute: HomeeAttribute, value: float) -> None:
"""Set an attribute value on the homee node.""" """Set an attribute value on the homee node."""
homee = self._entry.runtime_data homee = self._entry.runtime_data
await homee.set_value(attribute.node_id, attribute.id, value) try:
await homee.set_value(attribute.node_id, attribute.id, value)
except ConnectionClosed as exception:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="connection_closed",
) from exception
def _on_node_updated(self, node: HomeeNode) -> None: def _on_node_updated(self, node: HomeeNode) -> None:
self.schedule_update_ha_state() self.schedule_update_ha_state()

View File

@ -177,6 +177,7 @@ SENSOR_DESCRIPTIONS: dict[AttributeType, HomeeSensorEntityDescription] = {
AttributeType.TOTAL_CURRENT: HomeeSensorEntityDescription( AttributeType.TOTAL_CURRENT: HomeeSensorEntityDescription(
key="total_current", key="total_current",
device_class=SensorDeviceClass.CURRENT, device_class=SensorDeviceClass.CURRENT,
state_class=SensorStateClass.MEASUREMENT,
), ),
AttributeType.TOTAL_CURRENT_ENERGY_USE: HomeeSensorEntityDescription( AttributeType.TOTAL_CURRENT_ENERGY_USE: HomeeSensorEntityDescription(
key="total_power", key="total_power",
@ -252,7 +253,7 @@ NODE_SENSOR_DESCRIPTIONS: tuple[HomeeNodeSensorEntityDescription, ...] = (
], ],
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
translation_key="node_sensor_state", translation_key="node_state",
value_fn=lambda node: get_name_for_enum(NodeState, node.state), value_fn=lambda node: get_name_for_enum(NodeState, node.state),
), ),
) )

View File

@ -67,7 +67,23 @@
"name": "Link quality" "name": "Link quality"
}, },
"node_state": { "node_state": {
"name": "Node state" "name": "Node state",
"state": {
"available": "Available",
"unavailable": "Unavailable",
"update_in_progress": "Update in progress",
"waiting_for_attributes": "Waiting for attributes",
"initializing": "Initializing",
"user_interaction_required": "User interaction required",
"password_required": "Password required",
"host_unavailable": "Host unavailable",
"delete_in_progress": "Delete in progress",
"cosi_connected": "Cosi connected",
"blocked": "Blocked",
"waiting_for_wakeup": "Waiting for wakeup",
"remote_node_deleted": "Remote node deleted",
"firmware_update_in_progress": "Firmware update in progress"
}
}, },
"operating_hours": { "operating_hours": {
"name": "Operating hours" "name": "Operating hours"
@ -136,5 +152,10 @@
} }
} }
} }
},
"exceptions": {
"connection_closed": {
"message": "Could not connect to Homee while setting attribute"
}
} }
} }

View File

@ -2,6 +2,10 @@
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest
from websockets import frames
from websockets.exceptions import ConnectionClosed
from homeassistant.components.cover import ( from homeassistant.components.cover import (
ATTR_POSITION, ATTR_POSITION,
ATTR_TILT_POSITION, ATTR_TILT_POSITION,
@ -9,6 +13,7 @@ from homeassistant.components.cover import (
CoverEntityFeature, CoverEntityFeature,
CoverState, CoverState,
) )
from homeassistant.components.homee.const import DOMAIN
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
SERVICE_CLOSE_COVER, SERVICE_CLOSE_COVER,
@ -20,6 +25,7 @@ from homeassistant.const import (
SERVICE_STOP_COVER, SERVICE_STOP_COVER,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from . import build_mock_node, setup_integration from . import build_mock_node, setup_integration
@ -253,3 +259,28 @@ async def test_reversed_cover(
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("cover.test_cover").state == CoverState.CLOSED assert hass.states.get("cover.test_cover").state == CoverState.CLOSED
async def test_send_error(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test failed set_value command."""
mock_homee.nodes = [build_mock_node("cover_without_position.json")]
await setup_integration(hass, mock_config_entry)
mock_homee.set_value.side_effect = ConnectionClosed(
rcvd=frames.Close(1002, "Protocol Error"), sent=None
)
with pytest.raises(HomeAssistantError) as exc_info:
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: "cover.test_cover"},
blocking=True,
)
assert exc_info.value.translation_domain == DOMAIN
assert exc_info.value.translation_key == "connection_closed"