diff --git a/homeassistant/components/alexa_devices/manifest.json b/homeassistant/components/alexa_devices/manifest.json index fa5fb5531cc9e2..1121120d4b6873 100644 --- a/homeassistant/components/alexa_devices/manifest.json +++ b/homeassistant/components/alexa_devices/manifest.json @@ -8,5 +8,5 @@ "iot_class": "cloud_polling", "loggers": ["aioamazondevices"], "quality_scale": "platinum", - "requirements": ["aioamazondevices==6.2.7"] + "requirements": ["aioamazondevices==6.2.8"] } diff --git a/homeassistant/components/google_travel_time/sensor.py b/homeassistant/components/google_travel_time/sensor.py index 1a9b361bd33a32..1be6325d0e7395 100644 --- a/homeassistant/components/google_travel_time/sensor.py +++ b/homeassistant/components/google_travel_time/sensor.py @@ -22,6 +22,7 @@ from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, + SensorEntityDescription, SensorStateClass, ) from homeassistant.config_entries import ConfigEntry @@ -91,6 +92,16 @@ def convert_time(time_str: str) -> timestamp_pb2.Timestamp | None: return timestamp +SENSOR_DESCRIPTIONS = [ + SensorEntityDescription( + key="duration", + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.DURATION, + native_unit_of_measurement=UnitOfTime.MINUTES, + ) +] + + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -105,20 +116,20 @@ async def async_setup_entry( client_options = ClientOptions(api_key=api_key) client = RoutesAsyncClient(client_options=client_options) - sensor = GoogleTravelTimeSensor( - config_entry, name, api_key, origin, destination, client - ) + sensors = [ + GoogleTravelTimeSensor( + config_entry, name, api_key, origin, destination, client, sensor_description + ) + for sensor_description in SENSOR_DESCRIPTIONS + ] - async_add_entities([sensor], False) + async_add_entities(sensors, False) class GoogleTravelTimeSensor(SensorEntity): """Representation of a Google travel time sensor.""" _attr_attribution = ATTRIBUTION - _attr_native_unit_of_measurement = UnitOfTime.MINUTES - _attr_device_class = SensorDeviceClass.DURATION - _attr_state_class = SensorStateClass.MEASUREMENT def __init__( self, @@ -128,8 +139,10 @@ def __init__( origin: str, destination: str, client: RoutesAsyncClient, + sensor_description: SensorEntityDescription, ) -> None: """Initialize the sensor.""" + self.entity_description = sensor_description self._attr_name = name self._attr_unique_id = config_entry.entry_id self._attr_device_info = DeviceInfo( diff --git a/homeassistant/components/hassio/switch.py b/homeassistant/components/hassio/switch.py index 43fde5190e79ff..4aa7813783a6bd 100644 --- a/homeassistant/components/hassio/switch.py +++ b/homeassistant/components/hassio/switch.py @@ -73,7 +73,6 @@ async def async_turn_on(self, **kwargs: Any) -> None: try: await supervisor_client.addons.start_addon(self._addon_slug) except SupervisorError as err: - _LOGGER.error("Failed to start addon %s: %s", self._addon_slug, err) raise HomeAssistantError(err) from err await self.coordinator.force_addon_info_data_refresh(self._addon_slug) diff --git a/homeassistant/components/local_todo/todo.py b/homeassistant/components/local_todo/todo.py index 97e0d316ff55f8..e6e5ca8b18b525 100644 --- a/homeassistant/components/local_todo/todo.py +++ b/homeassistant/components/local_todo/todo.py @@ -196,11 +196,11 @@ async def async_move_todo_item( item_idx: dict[str, int] = {itm.uid: idx for idx, itm in enumerate(todos)} if uid not in item_idx: raise HomeAssistantError( - "Item '{uid}' not found in todo list {self.entity_id}" + f"Item '{uid}' not found in todo list {self.entity_id}" ) if previous_uid and previous_uid not in item_idx: raise HomeAssistantError( - "Item '{previous_uid}' not found in todo list {self.entity_id}" + f"Item '{previous_uid}' not found in todo list {self.entity_id}" ) dst_idx = item_idx[previous_uid] + 1 if previous_uid else 0 src_idx = item_idx[uid] diff --git a/homeassistant/components/nordpool/manifest.json b/homeassistant/components/nordpool/manifest.json index ca299b470ea311..fe4bcf7c2c924e 100644 --- a/homeassistant/components/nordpool/manifest.json +++ b/homeassistant/components/nordpool/manifest.json @@ -8,6 +8,6 @@ "iot_class": "cloud_polling", "loggers": ["pynordpool"], "quality_scale": "platinum", - "requirements": ["pynordpool==0.3.0"], + "requirements": ["pynordpool==0.3.1"], "single_config_entry": true } diff --git a/homeassistant/components/roborock/config_flow.py b/homeassistant/components/roborock/config_flow.py index 80b90210bf3dad..d1f582a94c88dc 100644 --- a/homeassistant/components/roborock/config_flow.py +++ b/homeassistant/components/roborock/config_flow.py @@ -82,7 +82,7 @@ async def _request_code(self) -> dict: assert self._client errors: dict[str, str] = {} try: - await self._client.request_code() + await self._client.request_code_v4() except RoborockAccountDoesNotExist: errors["base"] = "invalid_email" except RoborockUrlException: @@ -111,7 +111,7 @@ async def async_step_code( code = user_input[CONF_ENTRY_CODE] _LOGGER.debug("Logging into Roborock account using email provided code") try: - user_data = await self._client.code_login(code) + user_data = await self._client.code_login_v4(code) except RoborockInvalidCode: errors["base"] = "invalid_code" except RoborockException: diff --git a/homeassistant/components/roborock/icons.json b/homeassistant/components/roborock/icons.json index 6a96b04e12e84a..ae22a8b05d1258 100644 --- a/homeassistant/components/roborock/icons.json +++ b/homeassistant/components/roborock/icons.json @@ -52,6 +52,12 @@ "total_cleaning_time": { "default": "mdi:history" }, + "cleaning_brush_time_left": { + "default": "mdi:brush" + }, + "strainer_time_left": { + "default": "mdi:filter-variant" + }, "status": { "default": "mdi:information-outline" }, diff --git a/homeassistant/components/roborock/sensor.py b/homeassistant/components/roborock/sensor.py index a007d6fa45769c..1e716b193c1e1e 100644 --- a/homeassistant/components/roborock/sensor.py +++ b/homeassistant/components/roborock/sensor.py @@ -101,6 +101,24 @@ def _dock_error_value_fn(properties: DeviceProp) -> str | None: entity_category=EntityCategory.DIAGNOSTIC, protocol_listener=RoborockDataProtocol.FILTER_WORK_TIME, ), + RoborockSensorDescription( + native_unit_of_measurement=UnitOfTime.HOURS, + key="cleaning_brush_time_left", + device_class=SensorDeviceClass.DURATION, + translation_key="cleaning_brush_time_left", + value_fn=lambda data: data.consumable.cleaning_brush_time_left, + entity_category=EntityCategory.DIAGNOSTIC, + is_dock_entity=True, + ), + RoborockSensorDescription( + native_unit_of_measurement=UnitOfTime.HOURS, + key="strainer_time_left", + device_class=SensorDeviceClass.DURATION, + translation_key="strainer_time_left", + value_fn=lambda data: data.consumable.strainer_time_left, + entity_category=EntityCategory.DIAGNOSTIC, + is_dock_entity=True, + ), RoborockSensorDescription( native_unit_of_measurement=UnitOfTime.SECONDS, key="sensor_time_left", diff --git a/homeassistant/components/roborock/strings.json b/homeassistant/components/roborock/strings.json index ae8cb682c417a9..a8f58cf24923da 100644 --- a/homeassistant/components/roborock/strings.json +++ b/homeassistant/components/roborock/strings.json @@ -220,6 +220,12 @@ "sensor_time_left": { "name": "Sensor time left" }, + "cleaning_brush_time_left": { + "name": "Maintenance brush time left" + }, + "strainer_time_left": { + "name": "Strainer time left" + }, "status": { "name": "Status", "state": { diff --git a/homeassistant/components/roborock/vacuum.py b/homeassistant/components/roborock/vacuum.py index afdb3b19cb49ad..0de5678ea889a8 100644 --- a/homeassistant/components/roborock/vacuum.py +++ b/homeassistant/components/roborock/vacuum.py @@ -5,10 +5,6 @@ from roborock.code_mappings import RoborockStateCode from roborock.roborock_message import RoborockDataProtocol from roborock.roborock_typing import RoborockCommand -from vacuum_map_parser_base.config.color import ColorsPalette -from vacuum_map_parser_base.config.image_config import ImageConfig -from vacuum_map_parser_base.config.size import Sizes -from vacuum_map_parser_roborock.map_data_parser import RoborockMapDataParser import voluptuous as vol from homeassistant.components.vacuum import ( @@ -223,8 +219,7 @@ async def get_vacuum_current_position(self) -> ServiceResponse: translation_domain=DOMAIN, translation_key="map_failure", ) - parser = RoborockMapDataParser(ColorsPalette(), Sizes(), [], ImageConfig(), []) - parsed_map = parser.parse(map_data) + parsed_map = self.coordinator.map_parser.parse(map_data) robot_position = parsed_map.vacuum_position if robot_position is None: diff --git a/requirements_all.txt b/requirements_all.txt index 7c69d19303abdd..9ceeded4b43253 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -185,7 +185,7 @@ aioairzone-cloud==0.7.2 aioairzone==1.0.1 # homeassistant.components.alexa_devices -aioamazondevices==6.2.7 +aioamazondevices==6.2.8 # homeassistant.components.ambient_network # homeassistant.components.ambient_station @@ -2216,7 +2216,7 @@ pynina==0.3.6 pynobo==1.8.1 # homeassistant.components.nordpool -pynordpool==0.3.0 +pynordpool==0.3.1 # homeassistant.components.nuki pynuki==1.6.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 579bc064ee96cb..b585352ff22e0a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -173,7 +173,7 @@ aioairzone-cloud==0.7.2 aioairzone==1.0.1 # homeassistant.components.alexa_devices -aioamazondevices==6.2.7 +aioamazondevices==6.2.8 # homeassistant.components.ambient_network # homeassistant.components.ambient_station @@ -1852,7 +1852,7 @@ pynina==0.3.6 pynobo==1.8.1 # homeassistant.components.nordpool -pynordpool==0.3.0 +pynordpool==0.3.1 # homeassistant.components.nuki pynuki==1.6.3 diff --git a/tests/components/enphase_envoy/conftest.py b/tests/components/enphase_envoy/conftest.py index 9e94dab5a4c8f8..1d70a2984419a0 100644 --- a/tests/components/enphase_envoy/conftest.py +++ b/tests/components/enphase_envoy/conftest.py @@ -193,6 +193,22 @@ def _load_json_2_meter_data( mocked_data: EnvoyData, json_fixture: dict[str, Any] ) -> None: """Fill envoy meter data from fixture.""" + if meters := json_fixture["data"].get("ctmeters"): + mocked_data.ctmeters = {} + [ + mocked_data.ctmeters.update({meter: EnvoyMeterData(**meter_data)}) + for meter, meter_data in meters.items() + ] + if meters := json_fixture["data"].get("ctmeters_phases"): + mocked_data.ctmeters_phases = {} + for meter, meter_data in meters.items(): + meter_phase_data: dict[str, EnvoyMeterData] = {} + [ + meter_phase_data.update({phase: EnvoyMeterData(**phase_data)}) + for phase, phase_data in meter_data.items() + ] + mocked_data.ctmeters_phases.update({meter: meter_phase_data}) + if item := json_fixture["data"].get("ctmeter_production"): mocked_data.ctmeter_production = EnvoyMeterData(**item) if item := json_fixture["data"].get("ctmeter_consumption"): diff --git a/tests/components/enphase_envoy/fixtures/envoy.json b/tests/components/enphase_envoy/fixtures/envoy.json index 85d8990b1abfd9..d177559a66f509 100644 --- a/tests/components/enphase_envoy/fixtures/envoy.json +++ b/tests/components/enphase_envoy/fixtures/envoy.json @@ -27,6 +27,8 @@ "system_consumption_phases": null, "system_net_consumption_phases": null, "system_production_phases": null, + "ctmeters": {}, + "ctmeters_phases": {}, "ctmeter_production": null, "ctmeter_consumption": null, "ctmeter_storage": null, diff --git a/tests/components/enphase_envoy/fixtures/envoy_1p_metered.json b/tests/components/enphase_envoy/fixtures/envoy_1p_metered.json index 50f320edbc2897..540dc154757cf4 100644 --- a/tests/components/enphase_envoy/fixtures/envoy_1p_metered.json +++ b/tests/components/enphase_envoy/fixtures/envoy_1p_metered.json @@ -37,6 +37,39 @@ "system_consumption_phases": null, "system_net_consumption_phases": null, "system_production_phases": null, + "ctmeters": { + "production": { + "eid": "100000010", + "timestamp": 1708006110, + "energy_delivered": 11234, + "energy_received": 12345, + "active_power": 100, + "power_factor": 0.11, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": ["production-imbalance", "power-on-unused-phase"] + }, + "net-consumption": { + "eid": "100000020", + "timestamp": 1708006120, + "energy_delivered": 21234, + "energy_received": 22345, + "active_power": 101, + "power_factor": 0.21, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "net-consumption", + "metering_status": "normal", + "status_flags": [] + } + }, + "ctmeters_phases": {}, "ctmeter_production": { "eid": "100000010", "timestamp": 1708006110, diff --git a/tests/components/enphase_envoy/fixtures/envoy_acb_batt.json b/tests/components/enphase_envoy/fixtures/envoy_acb_batt.json index 5cc35d4050c17d..e83963f0a4d46f 100644 --- a/tests/components/enphase_envoy/fixtures/envoy_acb_batt.json +++ b/tests/components/enphase_envoy/fixtures/envoy_acb_batt.json @@ -87,6 +87,134 @@ "watts_now": 2341 }, "system_net_consumption_phases": null, + "ctmeters": { + "production": { + "eid": "100000010", + "timestamp": 1708006110, + "energy_delivered": 11234, + "energy_received": 12345, + "active_power": 100, + "power_factor": 0.11, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": ["production-imbalance", "power-on-unused-phase"] + }, + "net-consumption": { + "eid": "100000020", + "timestamp": 1708006120, + "energy_delivered": 21234, + "energy_received": 22345, + "active_power": 101, + "power_factor": 0.21, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "net-consumption", + "metering_status": "normal", + "status_flags": [] + } + }, + "ctmeters_phases": { + "production": { + "L1": { + "eid": "100000011", + "timestamp": 1708006111, + "energy_delivered": 112341, + "energy_received": 123451, + "active_power": 20, + "power_factor": 0.12, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": ["production-imbalance"] + }, + "L2": { + "eid": "100000012", + "timestamp": 1708006112, + "energy_delivered": 112342, + "energy_received": 123452, + "active_power": 30, + "power_factor": 0.13, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": ["power-on-unused-phase"] + }, + "L3": { + "eid": "100000013", + "timestamp": 1708006113, + "energy_delivered": 112343, + "energy_received": 123453, + "active_power": 50, + "power_factor": 0.14, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": [] + } + }, + "net-consumption": { + "L1": { + "eid": "100000021", + "timestamp": 1708006121, + "energy_delivered": 212341, + "energy_received": 223451, + "active_power": 21, + "power_factor": 0.22, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "net-consumption", + "metering_status": "normal", + "status_flags": [] + }, + "L2": { + "eid": "100000022", + "timestamp": 1708006122, + "energy_delivered": 212342, + "energy_received": 223452, + "active_power": 31, + "power_factor": 0.23, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "net-consumption", + "metering_status": "normal", + "status_flags": [] + }, + "L3": { + "eid": "100000023", + "timestamp": 1708006123, + "energy_delivered": 212343, + "energy_received": 223453, + "active_power": 51, + "power_factor": 0.24, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "net-consumption", + "metering_status": "normal", + "status_flags": [] + } + } + }, "ctmeter_production": { "eid": "100000010", "timestamp": 1708006110, diff --git a/tests/components/enphase_envoy/fixtures/envoy_eu_batt.json b/tests/components/enphase_envoy/fixtures/envoy_eu_batt.json index b9951a4c6fa1cf..42499ab400bf5e 100644 --- a/tests/components/enphase_envoy/fixtures/envoy_eu_batt.json +++ b/tests/components/enphase_envoy/fixtures/envoy_eu_batt.json @@ -75,6 +75,134 @@ "watts_now": 2341 }, "system_net_consumption_phases": null, + "ctmeters": { + "production": { + "eid": "100000010", + "timestamp": 1708006110, + "energy_delivered": 11234, + "energy_received": 12345, + "active_power": 100, + "power_factor": 0.11, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": ["production-imbalance", "power-on-unused-phase"] + }, + "net-consumption": { + "eid": "100000020", + "timestamp": 1708006120, + "energy_delivered": 21234, + "energy_received": 22345, + "active_power": 101, + "power_factor": 0.21, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "net-consumption", + "metering_status": "normal", + "status_flags": [] + } + }, + "ctmeters_phases": { + "production": { + "L1": { + "eid": "100000011", + "timestamp": 1708006111, + "energy_delivered": 112341, + "energy_received": 123451, + "active_power": 20, + "power_factor": 0.12, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": ["production-imbalance"] + }, + "L2": { + "eid": "100000012", + "timestamp": 1708006112, + "energy_delivered": 112342, + "energy_received": 123452, + "active_power": 30, + "power_factor": 0.13, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": ["power-on-unused-phase"] + }, + "L3": { + "eid": "100000013", + "timestamp": 1708006113, + "energy_delivered": 112343, + "energy_received": 123453, + "active_power": 50, + "power_factor": 0.14, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": [] + } + }, + "net-consumption": { + "L1": { + "eid": "100000021", + "timestamp": 1708006121, + "energy_delivered": 212341, + "energy_received": 223451, + "active_power": 21, + "power_factor": 0.22, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "net-consumption", + "metering_status": "normal", + "status_flags": [] + }, + "L2": { + "eid": "100000022", + "timestamp": 1708006122, + "energy_delivered": 212342, + "energy_received": 223452, + "active_power": 31, + "power_factor": 0.23, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "net-consumption", + "metering_status": "normal", + "status_flags": [] + }, + "L3": { + "eid": "100000023", + "timestamp": 1708006123, + "energy_delivered": 212343, + "energy_received": 223453, + "active_power": 51, + "power_factor": 0.24, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "net-consumption", + "metering_status": "normal", + "status_flags": [] + } + } + }, "ctmeter_production": { "eid": "100000010", "timestamp": 1708006110, diff --git a/tests/components/enphase_envoy/fixtures/envoy_metered_batt_relay.json b/tests/components/enphase_envoy/fixtures/envoy_metered_batt_relay.json index e8e0fd8ac85223..ec75a7994ae69c 100644 --- a/tests/components/enphase_envoy/fixtures/envoy_metered_batt_relay.json +++ b/tests/components/enphase_envoy/fixtures/envoy_metered_batt_relay.json @@ -151,6 +151,196 @@ "watts_now": 3234 } }, + "ctmeters": { + "production": { + "eid": "100000010", + "timestamp": 1708006110, + "energy_delivered": 11234, + "energy_received": 12345, + "active_power": 100, + "power_factor": 0.11, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": ["production-imbalance", "power-on-unused-phase"] + }, + "net-consumption": { + "eid": "100000020", + "timestamp": 1708006120, + "energy_delivered": 21234, + "energy_received": 22345, + "active_power": 101, + "power_factor": 0.21, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "net-consumption", + "metering_status": "normal", + "status_flags": [] + }, + "storage": { + "eid": "100000030", + "timestamp": 1708006120, + "energy_delivered": 31234, + "energy_received": 32345, + "active_power": 103, + "power_factor": 0.23, + "voltage": 113, + "current": 0.4, + "frequency": 50.3, + "state": "enabled", + "measurement_type": "storage", + "metering_status": "normal", + "status_flags": [] + } + }, + "ctmeters_phases": { + "production": { + "L1": { + "eid": "100000011", + "timestamp": 1708006111, + "energy_delivered": 112341, + "energy_received": 123451, + "active_power": 20, + "power_factor": 0.12, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": ["production-imbalance"] + }, + "L2": { + "eid": "100000012", + "timestamp": 1708006112, + "energy_delivered": 112342, + "energy_received": 123452, + "active_power": 30, + "power_factor": 0.13, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": ["power-on-unused-phase"] + }, + "L3": { + "eid": "100000013", + "timestamp": 1708006113, + "energy_delivered": 112343, + "energy_received": 123453, + "active_power": 50, + "power_factor": 0.14, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": [] + } + }, + "net-consumption": { + "L1": { + "eid": "100000021", + "timestamp": 1708006121, + "energy_delivered": 212341, + "energy_received": 223451, + "active_power": 21, + "power_factor": 0.22, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "net-consumption", + "metering_status": "normal", + "status_flags": [] + }, + "L2": { + "eid": "100000022", + "timestamp": 1708006122, + "energy_delivered": 212342, + "energy_received": 223452, + "active_power": 31, + "power_factor": 0.23, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "net-consumption", + "metering_status": "normal", + "status_flags": [] + }, + "L3": { + "eid": "100000023", + "timestamp": 1708006123, + "energy_delivered": 212343, + "energy_received": 223453, + "active_power": 51, + "power_factor": 0.24, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "net-consumption", + "metering_status": "normal", + "status_flags": [] + } + }, + "storage": { + "L1": { + "eid": "100000031", + "timestamp": 1708006121, + "energy_delivered": 312341, + "energy_received": 323451, + "active_power": 22, + "power_factor": 0.32, + "voltage": 113, + "current": 0.4, + "frequency": 50.3, + "state": "enabled", + "measurement_type": "storage", + "metering_status": "normal", + "status_flags": [] + }, + "L2": { + "eid": "100000032", + "timestamp": 1708006122, + "energy_delivered": 312342, + "energy_received": 323452, + "active_power": 33, + "power_factor": 0.23, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "storage", + "metering_status": "normal", + "status_flags": [] + }, + "L3": { + "eid": "100000033", + "timestamp": 1708006123, + "energy_delivered": 312343, + "energy_received": 323453, + "active_power": 53, + "power_factor": 0.24, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "storage", + "metering_status": "normal", + "status_flags": [] + } + } + }, "ctmeter_production": { "eid": "100000010", "timestamp": 1708006110, diff --git a/tests/components/enphase_envoy/fixtures/envoy_nobatt_metered_3p.json b/tests/components/enphase_envoy/fixtures/envoy_nobatt_metered_3p.json index 5a9ca140f8c5c4..edf9ef7db7ee04 100644 --- a/tests/components/enphase_envoy/fixtures/envoy_nobatt_metered_3p.json +++ b/tests/components/enphase_envoy/fixtures/envoy_nobatt_metered_3p.json @@ -94,6 +94,134 @@ "watts_now": 3234 } }, + "ctmeters": { + "production": { + "eid": "100000010", + "timestamp": 1708006110, + "energy_delivered": 11234, + "energy_received": 12345, + "active_power": 100, + "power_factor": 0.11, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": ["production-imbalance", "power-on-unused-phase"] + }, + "net-consumption": { + "eid": "100000020", + "timestamp": 1708006120, + "energy_delivered": 21234, + "energy_received": 22345, + "active_power": 101, + "power_factor": 0.21, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "net-consumption", + "metering_status": "normal", + "status_flags": [] + } + }, + "ctmeters_phases": { + "production": { + "L1": { + "eid": "100000011", + "timestamp": 1708006111, + "energy_delivered": 112341, + "energy_received": 123451, + "active_power": 20, + "power_factor": 0.12, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": ["production-imbalance"] + }, + "L2": { + "eid": "100000012", + "timestamp": 1708006112, + "energy_delivered": 112342, + "energy_received": 123452, + "active_power": 30, + "power_factor": 0.13, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": ["power-on-unused-phase"] + }, + "L3": { + "eid": "100000013", + "timestamp": 1708006113, + "energy_delivered": 112343, + "energy_received": 123453, + "active_power": 50, + "power_factor": 0.14, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": [] + } + }, + "net-consumption": { + "L1": { + "eid": "100000021", + "timestamp": 1708006121, + "energy_delivered": 212341, + "energy_received": 223451, + "active_power": 21, + "power_factor": 0.22, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "net-consumption", + "metering_status": "normal", + "status_flags": [] + }, + "L2": { + "eid": "100000022", + "timestamp": 1708006122, + "energy_delivered": 212342, + "energy_received": 223452, + "active_power": 31, + "power_factor": 0.23, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "net-consumption", + "metering_status": "normal", + "status_flags": [] + }, + "L3": { + "eid": "100000023", + "timestamp": 1708006123, + "energy_delivered": 212343, + "energy_received": 223453, + "active_power": 51, + "power_factor": 0.24, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "net-consumption", + "metering_status": "normal", + "status_flags": [] + } + } + }, "ctmeter_production": { "eid": "100000010", "timestamp": 1708006110, diff --git a/tests/components/enphase_envoy/fixtures/envoy_tot_cons_metered.json b/tests/components/enphase_envoy/fixtures/envoy_tot_cons_metered.json index 48b4de87867347..aa799f5dd3c6f6 100644 --- a/tests/components/enphase_envoy/fixtures/envoy_tot_cons_metered.json +++ b/tests/components/enphase_envoy/fixtures/envoy_tot_cons_metered.json @@ -32,6 +32,39 @@ "system_consumption_phases": null, "system_net_consumption_phases": null, "system_production_phases": null, + "ctmeters": { + "production": { + "eid": "100000010", + "timestamp": 1708006110, + "energy_delivered": 11234, + "energy_received": 12345, + "active_power": 100, + "power_factor": 0.11, + "voltage": 111, + "current": 0.2, + "frequency": 50.1, + "state": "enabled", + "measurement_type": "production", + "metering_status": "normal", + "status_flags": ["production-imbalance", "power-on-unused-phase"] + }, + "total-consumption": { + "eid": "100000020", + "timestamp": 1708006120, + "energy_delivered": 21234, + "energy_received": 22345, + "active_power": 101, + "power_factor": 0.21, + "voltage": 112, + "current": 0.3, + "frequency": 50.2, + "state": "enabled", + "measurement_type": "total-consumption", + "metering_status": "normal", + "status_flags": [] + } + }, + "ctmeters_phases": {}, "ctmeter_production": { "eid": "100000010", "timestamp": 1708006110, diff --git a/tests/components/hassio/test_switch.py b/tests/components/hassio/test_switch.py index 744a277412f746..7963389e8ca7e9 100644 --- a/tests/components/hassio/test_switch.py +++ b/tests/components/hassio/test_switch.py @@ -1,5 +1,6 @@ """The tests for the hassio switch.""" +from collections.abc import AsyncGenerator import os from unittest.mock import AsyncMock, patch @@ -18,6 +19,39 @@ MOCK_ENVIRON = {"SUPERVISOR": "127.0.0.1", "SUPERVISOR_TOKEN": "abcdefgh"} +@pytest.fixture +async def setup_integration( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, +) -> AsyncGenerator[MockConfigEntry]: + """Set up the hassio integration and enable entity.""" + config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) + config_entry.add_to_hass(hass) + + with patch.dict(os.environ, MOCK_ENVIRON): + result = await async_setup_component( + hass, + "hassio", + {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, + ) + assert result + await hass.async_block_till_done() + + yield config_entry + + +async def enable_entity( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + config_entry: MockConfigEntry, + entity_id: str, +) -> None: + """Enable an entity and reload the config entry.""" + entity_registry.async_update_entity(entity_id, disabled_by=None) + await hass.config_entries.async_reload(config_entry.entry_id) + await hass.async_block_till_done() + + @pytest.fixture(autouse=True) def mock_all( aioclient_mock: AiohttpClientMocker, @@ -170,31 +204,18 @@ async def test_switch_state( entity_id: str, expected: str, addon_state: str, - aioclient_mock: AiohttpClientMocker, entity_registry: er.EntityRegistry, addon_installed: AsyncMock, + setup_integration: MockConfigEntry, ) -> None: """Test hassio addon switch state.""" addon_installed.return_value.state = addon_state - config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) - config_entry.add_to_hass(hass) - - with patch.dict(os.environ, MOCK_ENVIRON): - result = await async_setup_component( - hass, - "hassio", - {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, - ) - assert result - await hass.async_block_till_done() # Verify that the entity is disabled by default. assert hass.states.get(entity_id) is None # Enable the entity. - entity_registry.async_update_entity(entity_id, disabled_by=None) - await hass.config_entries.async_reload(config_entry.entry_id) - await hass.async_block_till_done() + await enable_entity(hass, entity_registry, setup_integration, entity_id) # Verify that the entity have the expected state. state = hass.states.get(entity_id) @@ -210,6 +231,7 @@ async def test_switch_turn_on( aioclient_mock: AiohttpClientMocker, entity_registry: er.EntityRegistry, addon_installed: AsyncMock, + setup_integration: MockConfigEntry, ) -> None: """Test turning on addon switch.""" entity_id = "switch.test_two" @@ -218,25 +240,11 @@ async def test_switch_turn_on( # Mock the start addon API call aioclient_mock.post("http://127.0.0.1/addons/test-two/start", json={"result": "ok"}) - config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) - config_entry.add_to_hass(hass) - - with patch.dict(os.environ, MOCK_ENVIRON): - result = await async_setup_component( - hass, - "hassio", - {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, - ) - assert result - await hass.async_block_till_done() - # Verify that the entity is disabled by default. assert hass.states.get(entity_id) is None # Enable the entity. - entity_registry.async_update_entity(entity_id, disabled_by=None) - await hass.config_entries.async_reload(config_entry.entry_id) - await hass.async_block_till_done() + await enable_entity(hass, entity_registry, setup_integration, entity_id) # Verify initial state is off state = hass.states.get(entity_id) @@ -252,13 +260,8 @@ async def test_switch_turn_on( ) # Verify the API was called - assert len(aioclient_mock.mock_calls) > 0 - start_call_found = False - for call in aioclient_mock.mock_calls: - if call[1].path == "/addons/test-two/start" and call[0] == "POST": - start_call_found = True - break - assert start_call_found + assert aioclient_mock.mock_calls[-1][1].path == "/addons/test-two/start" + assert aioclient_mock.mock_calls[-1][0] == "POST" @pytest.mark.parametrize( @@ -269,6 +272,7 @@ async def test_switch_turn_off( aioclient_mock: AiohttpClientMocker, entity_registry: er.EntityRegistry, addon_installed: AsyncMock, + setup_integration: MockConfigEntry, ) -> None: """Test turning off addon switch.""" entity_id = "switch.test" @@ -277,25 +281,11 @@ async def test_switch_turn_off( # Mock the stop addon API call aioclient_mock.post("http://127.0.0.1/addons/test/stop", json={"result": "ok"}) - config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) - config_entry.add_to_hass(hass) - - with patch.dict(os.environ, MOCK_ENVIRON): - result = await async_setup_component( - hass, - "hassio", - {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, - ) - assert result - await hass.async_block_till_done() - # Verify that the entity is disabled by default. assert hass.states.get(entity_id) is None # Enable the entity. - entity_registry.async_update_entity(entity_id, disabled_by=None) - await hass.config_entries.async_reload(config_entry.entry_id) - await hass.async_block_till_done() + await enable_entity(hass, entity_registry, setup_integration, entity_id) # Verify initial state is on state = hass.states.get(entity_id) @@ -311,10 +301,5 @@ async def test_switch_turn_off( ) # Verify the API was called - assert len(aioclient_mock.mock_calls) > 0 - stop_call_found = False - for call in aioclient_mock.mock_calls: - if call[1].path == "/addons/test/stop" and call[0] == "POST": - stop_call_found = True - break - assert stop_call_found + assert aioclient_mock.mock_calls[-1][1].path == "/addons/test/stop" + assert aioclient_mock.mock_calls[-1][0] == "POST" diff --git a/tests/components/roborock/test_config_flow.py b/tests/components/roborock/test_config_flow.py index 72dd7b7fd768a3..125476b0eddc4f 100644 --- a/tests/components/roborock/test_config_flow.py +++ b/tests/components/roborock/test_config_flow.py @@ -46,7 +46,7 @@ async def test_config_flow_success( assert result["type"] is FlowResultType.FORM assert result["step_id"] == "user" with patch( - "homeassistant.components.roborock.config_flow.RoborockApiClient.request_code" + "homeassistant.components.roborock.config_flow.RoborockApiClient.request_code_v4" ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_USERNAME: USER_EMAIL} @@ -56,7 +56,7 @@ async def test_config_flow_success( assert result["step_id"] == "code" assert result["errors"] == {} with patch( - "homeassistant.components.roborock.config_flow.RoborockApiClient.code_login", + "homeassistant.components.roborock.config_flow.RoborockApiClient.code_login_v4", return_value=USER_DATA, ): result = await hass.config_entries.flow.async_configure( @@ -101,7 +101,7 @@ async def test_config_flow_failures_request_code( assert result["type"] is FlowResultType.FORM assert result["step_id"] == "user" with patch( - "homeassistant.components.roborock.config_flow.RoborockApiClient.request_code", + "homeassistant.components.roborock.config_flow.RoborockApiClient.request_code_v4", side_effect=request_code_side_effect, ): result = await hass.config_entries.flow.async_configure( @@ -111,7 +111,7 @@ async def test_config_flow_failures_request_code( assert result["errors"] == request_code_errors # Recover from error with patch( - "homeassistant.components.roborock.config_flow.RoborockApiClient.request_code" + "homeassistant.components.roborock.config_flow.RoborockApiClient.request_code_v4" ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_USERNAME: USER_EMAIL} @@ -121,7 +121,7 @@ async def test_config_flow_failures_request_code( assert result["step_id"] == "code" assert result["errors"] == {} with patch( - "homeassistant.components.roborock.config_flow.RoborockApiClient.code_login", + "homeassistant.components.roborock.config_flow.RoborockApiClient.code_login_v4", return_value=USER_DATA, ): result = await hass.config_entries.flow.async_configure( @@ -163,7 +163,7 @@ async def test_config_flow_failures_code_login( assert result["type"] is FlowResultType.FORM assert result["step_id"] == "user" with patch( - "homeassistant.components.roborock.config_flow.RoborockApiClient.request_code" + "homeassistant.components.roborock.config_flow.RoborockApiClient.request_code_v4" ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_USERNAME: USER_EMAIL} @@ -174,7 +174,7 @@ async def test_config_flow_failures_code_login( assert result["errors"] == {} # Raise exception for invalid code with patch( - "homeassistant.components.roborock.config_flow.RoborockApiClient.code_login", + "homeassistant.components.roborock.config_flow.RoborockApiClient.code_login_v4", side_effect=code_login_side_effect, ): result = await hass.config_entries.flow.async_configure( @@ -183,7 +183,7 @@ async def test_config_flow_failures_code_login( assert result["type"] is FlowResultType.FORM assert result["errors"] == code_login_errors with patch( - "homeassistant.components.roborock.config_flow.RoborockApiClient.code_login", + "homeassistant.components.roborock.config_flow.RoborockApiClient.code_login_v4", return_value=USER_DATA, ): result = await hass.config_entries.flow.async_configure( @@ -236,7 +236,7 @@ async def test_reauth_flow( # Request a new code with ( patch( - "homeassistant.components.roborock.config_flow.RoborockApiClient.request_code" + "homeassistant.components.roborock.config_flow.RoborockApiClient.request_code_v4" ), patch("homeassistant.components.roborock.async_setup_entry", return_value=True), ): @@ -250,7 +250,7 @@ async def test_reauth_flow( new_user_data.rriot.s = "new_password_hash" with ( patch( - "homeassistant.components.roborock.config_flow.RoborockApiClient.code_login", + "homeassistant.components.roborock.config_flow.RoborockApiClient.code_login_v4", return_value=new_user_data, ), patch("homeassistant.components.roborock.async_setup_entry", return_value=True), @@ -280,7 +280,7 @@ async def test_account_already_configured( assert result["type"] is FlowResultType.FORM assert result["step_id"] == "user" with patch( - "homeassistant.components.roborock.config_flow.RoborockApiClient.request_code" + "homeassistant.components.roborock.config_flow.RoborockApiClient.request_code_v4" ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_USERNAME: USER_EMAIL} @@ -289,7 +289,7 @@ async def test_account_already_configured( assert result["step_id"] == "code" assert result["type"] is FlowResultType.FORM with patch( - "homeassistant.components.roborock.config_flow.RoborockApiClient.code_login", + "homeassistant.components.roborock.config_flow.RoborockApiClient.code_login_v4", return_value=USER_DATA, ): result = await hass.config_entries.flow.async_configure( @@ -313,7 +313,7 @@ async def test_reauth_wrong_account( "homeassistant.components.roborock.async_setup_entry", return_value=True ): with patch( - "homeassistant.components.roborock.config_flow.RoborockApiClient.request_code" + "homeassistant.components.roborock.config_flow.RoborockApiClient.request_code_v4" ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_USERNAME: USER_EMAIL} @@ -324,7 +324,7 @@ async def test_reauth_wrong_account( new_user_data = deepcopy(USER_DATA) new_user_data.rruid = "new_rruid" with patch( - "homeassistant.components.roborock.config_flow.RoborockApiClient.code_login", + "homeassistant.components.roborock.config_flow.RoborockApiClient.code_login_v4", return_value=new_user_data, ): result = await hass.config_entries.flow.async_configure( @@ -354,7 +354,7 @@ async def test_discovery_not_setup( assert result["type"] is FlowResultType.FORM assert result["step_id"] == "user" with patch( - "homeassistant.components.roborock.config_flow.RoborockApiClient.request_code" + "homeassistant.components.roborock.config_flow.RoborockApiClient.request_code_v4" ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_USERNAME: USER_EMAIL} @@ -364,7 +364,7 @@ async def test_discovery_not_setup( assert result["step_id"] == "code" assert result["errors"] == {} with patch( - "homeassistant.components.roborock.config_flow.RoborockApiClient.code_login", + "homeassistant.components.roborock.config_flow.RoborockApiClient.code_login_v4", return_value=USER_DATA, ): result = await hass.config_entries.flow.async_configure( diff --git a/tests/components/roborock/test_sensor.py b/tests/components/roborock/test_sensor.py index 719b398de94be5..623fde93b1f251 100644 --- a/tests/components/roborock/test_sensor.py +++ b/tests/components/roborock/test_sensor.py @@ -5,10 +5,12 @@ import pytest from roborock import DeviceData, HomeDataDevice from roborock.const import ( + CLEANING_BRUSH_REPLACE_TIME, FILTER_REPLACE_TIME, MAIN_BRUSH_REPLACE_TIME, SENSOR_DIRTY_REPLACE_TIME, SIDE_BRUSH_REPLACE_TIME, + STRAINER_REPLACE_TIME, ) from roborock.roborock_message import RoborockMessage, RoborockMessageProtocol from roborock.version_1_apis import RoborockMqttClientV1 @@ -29,7 +31,7 @@ def platforms() -> list[Platform]: async def test_sensors(hass: HomeAssistant, setup_entry: MockConfigEntry) -> None: """Test sensors and check test values are correctly set.""" - assert len(hass.states.async_all("sensor")) == 42 + assert len(hass.states.async_all("sensor")) == 46 assert hass.states.get("sensor.roborock_s7_maxv_main_brush_time_left").state == str( MAIN_BRUSH_REPLACE_TIME - 74382 ) @@ -42,6 +44,13 @@ async def test_sensors(hass: HomeAssistant, setup_entry: MockConfigEntry) -> Non assert hass.states.get("sensor.roborock_s7_maxv_sensor_time_left").state == str( SENSOR_DIRTY_REPLACE_TIME - 74382 ) + assert hass.states.get( + "sensor.roborock_s7_2_dock_maintenance_brush_time_left" + ).state == str(CLEANING_BRUSH_REPLACE_TIME - 65) + assert hass.states.get("sensor.roborock_s7_2_dock_strainer_time_left").state == str( + STRAINER_REPLACE_TIME - 65 + ) + assert hass.states.get("sensor.roborock_s7_maxv_cleaning_time").state == "1176" assert ( hass.states.get("sensor.roborock_s7_maxv_total_cleaning_time").state == "74382"