diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index e9ea92c78e82d..108303d9d3de6 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -14,6 +14,6 @@ "documentation": "https://www.home-assistant.io/integrations/homekit_controller", "iot_class": "local_push", "loggers": ["aiohomekit", "commentjson"], - "requirements": ["aiohomekit==3.2.17"], + "requirements": ["aiohomekit==3.2.18"], "zeroconf": ["_hap._tcp.local.", "_hap._udp.local."] } diff --git a/homeassistant/components/nam/icons.json b/homeassistant/components/nam/icons.json index 5e55bf145e576..594fd5fb5b754 100644 --- a/homeassistant/components/nam/icons.json +++ b/homeassistant/components/nam/icons.json @@ -18,9 +18,6 @@ }, "sps30_caqi_level": { "default": "mdi:air-filter" - }, - "sps30_pm4": { - "default": "mdi:molecule" } } } diff --git a/homeassistant/components/nam/sensor.py b/homeassistant/components/nam/sensor.py index 45cfd313e8f72..a7e5eb7191286 100644 --- a/homeassistant/components/nam/sensor.py +++ b/homeassistant/components/nam/sensor.py @@ -324,6 +324,7 @@ class NAMSensorEntityDescription(SensorEntityDescription): translation_key="sps30_pm4", suggested_display_precision=0, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + device_class=SensorDeviceClass.PM4, state_class=SensorStateClass.MEASUREMENT, value=lambda sensors: sensors.sps30_p4, ), diff --git a/homeassistant/components/roborock/__init__.py b/homeassistant/components/roborock/__init__.py index 2e354a1e48730..39bef7b7b4209 100644 --- a/homeassistant/components/roborock/__init__.py +++ b/homeassistant/components/roborock/__init__.py @@ -256,6 +256,7 @@ async def setup_device_v1( RoborockMqttClientV1, user_data, DeviceData(device, product_info.model) ) try: + await mqtt_client.async_connect() networking = await mqtt_client.get_networking() if networking is None: # If the api does not return an error but does return None for diff --git a/homeassistant/components/roborock/coordinator.py b/homeassistant/components/roborock/coordinator.py index 02d5f68466808..507167f80cdc0 100644 --- a/homeassistant/components/roborock/coordinator.py +++ b/homeassistant/components/roborock/coordinator.py @@ -272,6 +272,7 @@ async def _verify_api(self) -> None: """Verify that the api is reachable. If it is not, switch clients.""" if isinstance(self.api, RoborockLocalClientV1): try: + await self.api.async_connect() await self.api.ping() except RoborockException: _LOGGER.warning( diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index e5deaf3314281..abf52f41393f1 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -186,6 +186,11 @@ def async_setup_rpc_attribute_entities( for key in key_instances: # Filter non-existing sensors + if description.role and description.role != coordinator.device.config[ + key + ].get("role"): + continue + if description.sub_key not in coordinator.device.status[ key ] and not description.supported(coordinator.device.status[key]): @@ -310,6 +315,7 @@ class RpcEntityDescription(EntityDescription): unit: Callable[[dict], str | None] | None = None options_fn: Callable[[dict], list[str]] | None = None entity_class: Callable | None = None + role: str | None = None @dataclass(frozen=True) diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index ad93a5f250e7e..675a2223769bf 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -32,6 +32,7 @@ UnitOfFrequency, UnitOfPower, UnitOfTemperature, + UnitOfVolume, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback @@ -1458,6 +1459,84 @@ def __init__( state_class=SensorStateClass.MEASUREMENT, entity_class=RpcPresenceSensor, ), + "object_water_consumption": RpcSensorDescription( + key="object", + sub_key="value", + value=lambda status, _: float(status["counter"]["total"]), + native_unit_of_measurement=UnitOfVolume.CUBIC_METERS, + suggested_display_precision=3, + device_class=SensorDeviceClass.WATER, + state_class=SensorStateClass.TOTAL_INCREASING, + role="water_consumption", + ), + "object_energy_consumption": RpcSensorDescription( + key="object", + sub_key="value", + value=lambda status, _: float(status["counter"]["total"]), + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + suggested_display_precision=2, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + role="phase_info", + ), + "object_total_act_energy": RpcSensorDescription( + key="object", + sub_key="value", + name="Total Active Energy", + value=lambda status, _: float(status["total_act_energy"]), + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + suggested_display_precision=2, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + role="phase_info", + ), + "object_total_power": RpcSensorDescription( + key="object", + sub_key="value", + name="Total Power", + value=lambda status, _: float(status["total_power"]), + native_unit_of_measurement=UnitOfPower.WATT, + suggested_unit_of_measurement=UnitOfPower.KILO_WATT, + suggested_display_precision=2, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + role="phase_info", + ), + "object_phase_a_voltage": RpcSensorDescription( + key="object", + sub_key="value", + name="Phase A voltage", + value=lambda status, _: float(status["phase_a"]["voltage"]), + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + suggested_display_precision=1, + device_class=SensorDeviceClass.VOLTAGE, + state_class=SensorStateClass.MEASUREMENT, + role="phase_info", + ), + "object_phase_b_voltage": RpcSensorDescription( + key="object", + sub_key="value", + name="Phase B voltage", + value=lambda status, _: float(status["phase_b"]["voltage"]), + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + suggested_display_precision=1, + device_class=SensorDeviceClass.VOLTAGE, + state_class=SensorStateClass.MEASUREMENT, + role="phase_info", + ), + "object_phase_c_voltage": RpcSensorDescription( + key="object", + sub_key="value", + name="Phase C voltage", + value=lambda status, _: float(status["phase_c"]["voltage"]), + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + suggested_display_precision=1, + device_class=SensorDeviceClass.VOLTAGE, + state_class=SensorStateClass.MEASUREMENT, + role="phase_info", + ), } diff --git a/requirements_all.txt b/requirements_all.txt index 5fe8e8199dadc..013fc122c02af 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -271,7 +271,7 @@ aiohasupervisor==0.3.2 aiohomeconnect==0.19.0 # homeassistant.components.homekit_controller -aiohomekit==3.2.17 +aiohomekit==3.2.18 # homeassistant.components.mcp_server aiohttp_sse==2.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bb973a7a5192b..a618edc2c4757 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -256,7 +256,7 @@ aiohasupervisor==0.3.2 aiohomeconnect==0.19.0 # homeassistant.components.homekit_controller -aiohomekit==3.2.17 +aiohomekit==3.2.18 # homeassistant.components.mcp_server aiohttp_sse==2.2.0 diff --git a/tests/components/nam/snapshots/test_sensor.ambr b/tests/components/nam/snapshots/test_sensor.ambr index 3071752267eca..7ad641306b501 100644 --- a/tests/components/nam/snapshots/test_sensor.ambr +++ b/tests/components/nam/snapshots/test_sensor.ambr @@ -1812,7 +1812,7 @@ 'suggested_display_precision': 0, }), }), - 'original_device_class': None, + 'original_device_class': , 'original_icon': None, 'original_name': 'SPS30 PM4', 'platform': 'nam', @@ -1827,6 +1827,7 @@ # name: test_sensor[sensor.nettigo_air_monitor_sps30_pm4-state] StateSnapshot({ 'attributes': ReadOnlyDict({ + 'device_class': 'pm4', 'friendly_name': 'Nettigo Air Monitor SPS30 PM4', 'state_class': , 'unit_of_measurement': 'μg/m³', diff --git a/tests/components/roborock/conftest.py b/tests/components/roborock/conftest.py index 09f5ac333f460..e4731c6e9f23a 100644 --- a/tests/components/roborock/conftest.py +++ b/tests/components/roborock/conftest.py @@ -95,6 +95,9 @@ def bypass_api_fixture(bypass_api_client_fixture: Any, mock_send_message: Mock) patch( "homeassistant.components.roborock.coordinator.RoborockMqttClientV1._send_command" ), + patch( + "homeassistant.components.roborock.coordinator.RoborockLocalClientV1.async_connect" + ), patch( "homeassistant.components.roborock.RoborockMqttClientV1.get_networking", return_value=NETWORK_INFO, diff --git a/tests/components/shelly/test_sensor.py b/tests/components/shelly/test_sensor.py index dd43cbce3c446..02f81e5ac3bac 100644 --- a/tests/components/shelly/test_sensor.py +++ b/tests/components/shelly/test_sensor.py @@ -34,6 +34,7 @@ UnitOfFrequency, UnitOfPower, UnitOfTemperature, + UnitOfVolume, ) from homeassistant.core import HomeAssistant, State from homeassistant.helpers.device_registry import DeviceRegistry @@ -1539,6 +1540,33 @@ async def test_rpc_device_virtual_number_sensor_with_device_class( assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.HUMIDITY +async def test_rpc_object_role_sensor( + hass: HomeAssistant, + mock_rpc_device: Mock, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test object role based sensor.""" + config = deepcopy(mock_rpc_device.config) + config["object:200"] = { + "name": "Water consumption", + "meta": {"ui": {"unit": "m3"}}, + "role": "water_consumption", + } + + monkeypatch.setattr(mock_rpc_device, "config", config) + + status = deepcopy(mock_rpc_device.status) + status["object:200"] = {"value": {"counter": {"total": 5.4}}} + monkeypatch.setattr(mock_rpc_device, "status", status) + + await init_integration(hass, 3) + + assert (state := hass.states.get("sensor.test_name_water_consumption")) + assert state.state == "5.4" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfVolume.CUBIC_METERS + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.WATER + + @pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_rpc_switch_energy_sensors( hass: HomeAssistant,