diff --git a/homeassistant/components/emulated_kasa/manifest.json b/homeassistant/components/emulated_kasa/manifest.json index 640a2113d6f..f1a01f9d7aa 100644 --- a/homeassistant/components/emulated_kasa/manifest.json +++ b/homeassistant/components/emulated_kasa/manifest.json @@ -6,5 +6,5 @@ "iot_class": "local_push", "loggers": ["sense_energy"], "quality_scale": "internal", - "requirements": ["sense-energy==0.12.4"] + "requirements": ["sense-energy==0.13.2"] } diff --git a/homeassistant/components/sense/__init__.py b/homeassistant/components/sense/__init__.py index ea424798891..271888d7018 100644 --- a/homeassistant/components/sense/__init__.py +++ b/homeassistant/components/sense/__init__.py @@ -40,30 +40,12 @@ PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] type SenseConfigEntry = ConfigEntry[SenseData] -class SenseDevicesData: - """Data for each sense device.""" - - def __init__(self) -> None: - """Create.""" - self._data_by_device: dict[str, dict[str, Any]] = {} - - def set_devices_data(self, devices: list[dict[str, Any]]) -> None: - """Store a device update.""" - self._data_by_device = {device["id"]: device for device in devices} - - def get_device_by_id(self, sense_device_id: str) -> dict[str, Any] | None: - """Get the latest device data.""" - return self._data_by_device.get(sense_device_id) - - @dataclass(kw_only=True, slots=True) class SenseData: """Sense data type.""" data: ASyncSenseable - device_data: SenseDevicesData - trends: DataUpdateCoordinator[None] - discovered: list[dict[str, Any]] + trends: DataUpdateCoordinator[Any] async def async_setup_entry(hass: HomeAssistant, entry: SenseConfigEntry) -> bool: @@ -108,7 +90,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: SenseConfigEntry) -> boo raise ConfigEntryNotReady(str(err)) from err try: - sense_discovered_devices = await gateway.get_discovered_device_data() + await gateway.fetch_devices() await gateway.update_realtime() except SENSE_TIMEOUT_EXCEPTIONS as err: raise ConfigEntryNotReady( @@ -149,9 +131,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: SenseConfigEntry) -> boo entry.runtime_data = SenseData( data=gateway, - device_data=SenseDevicesData(), trends=trends_coordinator, - discovered=sense_discovered_devices, ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) @@ -165,9 +145,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: SenseConfigEntry) -> boo except SENSE_WEBSOCKET_EXCEPTIONS as ex: _LOGGER.error("Failed to update data: %s", ex) - data = gateway.get_realtime() - if "devices" in data: - entry.runtime_data.device_data.set_devices_data(data["devices"]) async_dispatcher_send(hass, f"{SENSE_DEVICE_UPDATE}-{gateway.sense_monitor_id}") remove_update_callback = async_track_time_interval( diff --git a/homeassistant/components/sense/binary_sensor.py b/homeassistant/components/sense/binary_sensor.py index 969dfdc565e..3c2907a2acb 100644 --- a/homeassistant/components/sense/binary_sensor.py +++ b/homeassistant/components/sense/binary_sensor.py @@ -2,6 +2,8 @@ import logging +from sense_energy.sense_api import SenseDevice + from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, @@ -11,7 +13,7 @@ from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import SenseConfigEntry, SenseDevicesData +from . import SenseConfigEntry from .const import ATTRIBUTION, DOMAIN, MDI_ICONS, SENSE_DEVICE_UPDATE _LOGGER = logging.getLogger(__name__) @@ -24,13 +26,9 @@ async def async_setup_entry( ) -> None: """Set up the Sense binary sensor.""" sense_monitor_id = config_entry.runtime_data.data.sense_monitor_id - - sense_devices = config_entry.runtime_data.discovered - device_data = config_entry.runtime_data.device_data devices = [ - SenseDevice(device_data, device, sense_monitor_id) - for device in sense_devices - if device["tags"]["DeviceListAllowed"] == "true" + SenseBinarySensor(device, sense_monitor_id) + for device in config_entry.runtime_data.data.devices ] await _migrate_old_unique_ids(hass, devices) @@ -43,7 +41,7 @@ def sense_to_mdi(sense_icon: str) -> str: return f"mdi:{MDI_ICONS.get(sense_icon, "power-plug")}" -class SenseDevice(BinarySensorEntity): +class SenseBinarySensor(BinarySensorEntity): """Implementation of a Sense energy device binary sensor.""" _attr_attribution = ATTRIBUTION @@ -51,16 +49,14 @@ class SenseDevice(BinarySensorEntity): _attr_available = False _attr_device_class = BinarySensorDeviceClass.POWER - def __init__( - self, sense_devices_data: SenseDevicesData, device: dict, sense_monitor_id: str - ) -> None: + def __init__(self, device: SenseDevice, sense_monitor_id: str) -> None: """Initialize the Sense binary sensor.""" - self._attr_name = device["name"] - self._id = device["id"] + self._attr_name = device.name + self._id = device.id self._sense_monitor_id = sense_monitor_id self._attr_unique_id = f"{sense_monitor_id}-{self._id}" - self._attr_icon = sense_to_mdi(device["icon"]) - self._sense_devices_data = sense_devices_data + self._attr_icon = sense_to_mdi(device.icon) + self._device = device @property def old_unique_id(self) -> str: @@ -80,7 +76,7 @@ class SenseDevice(BinarySensorEntity): @callback def _async_update_from_data(self) -> None: """Get the latest data, update state. Must not do I/O.""" - new_state = bool(self._sense_devices_data.get_device_by_id(self._id)) + new_state = self._device.is_on if self._attr_available and self._attr_is_on == new_state: return self._attr_available = True @@ -89,7 +85,7 @@ class SenseDevice(BinarySensorEntity): async def _migrate_old_unique_ids( - hass: HomeAssistant, devices: list[SenseDevice] + hass: HomeAssistant, devices: list[SenseBinarySensor] ) -> None: registry = er.async_get(hass) for device in devices: diff --git a/homeassistant/components/sense/manifest.json b/homeassistant/components/sense/manifest.json index 116b714ba82..72d1d045c9a 100644 --- a/homeassistant/components/sense/manifest.json +++ b/homeassistant/components/sense/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/sense", "iot_class": "cloud_polling", "loggers": ["sense_energy"], - "requirements": ["sense-energy==0.12.4"] + "requirements": ["sense-energy==0.13.2"] } diff --git a/homeassistant/components/sense/sensor.py b/homeassistant/components/sense/sensor.py index 053cc39d20c..bd6f8a4da1d 100644 --- a/homeassistant/components/sense/sensor.py +++ b/homeassistant/components/sense/sensor.py @@ -3,7 +3,8 @@ from datetime import datetime from typing import Any -from sense_energy import ASyncSenseable +from sense_energy import ASyncSenseable, Scale +from sense_energy.sense_api import SenseDevice from homeassistant.components.sensor import ( SensorDeviceClass, @@ -25,7 +26,7 @@ from homeassistant.helpers.update_coordinator import ( DataUpdateCoordinator, ) -from . import SenseConfigEntry, SenseDevicesData +from . import SenseConfigEntry from .const import ( ACTIVE_NAME, ACTIVE_TYPE, @@ -49,25 +50,13 @@ from .const import ( TO_GRID_NAME, ) - -class SensorConfig: - """Data structure holding sensor configuration.""" - - def __init__(self, name: str, sensor_type: str) -> None: - """Sensor name and type to pass to API.""" - self.name = name - self.sensor_type = sensor_type - - -# Sensor types/ranges -ACTIVE_SENSOR_TYPE = SensorConfig(ACTIVE_NAME, ACTIVE_TYPE) - # Sensor types/ranges TRENDS_SENSOR_TYPES = { - "daily": SensorConfig("Daily", "DAY"), - "weekly": SensorConfig("Weekly", "WEEK"), - "monthly": SensorConfig("Monthly", "MONTH"), - "yearly": SensorConfig("Yearly", "YEAR"), + Scale.DAY: "Daily", + Scale.WEEK: "Weekly", + Scale.MONTH: "Monthly", + Scale.YEAR: "Yearly", + Scale.CYCLE: "Bill", } # Production/consumption variants @@ -103,29 +92,19 @@ async def async_setup_entry( await trends_coordinator.async_request_refresh() sense_monitor_id = data.sense_monitor_id - sense_devices = config_entry.runtime_data.discovered - device_data = config_entry.runtime_data.device_data entities: list[SensorEntity] = [ - SenseEnergyDevice(device_data, device, sense_monitor_id) - for device in sense_devices - if device["tags"]["DeviceListAllowed"] == "true" + SenseDevicePowerSensor(device, sense_monitor_id) + for device in config_entry.runtime_data.data.devices ] for variant_id, variant_name in SENSOR_VARIANTS: - name = ACTIVE_SENSOR_TYPE.name - sensor_type = ACTIVE_SENSOR_TYPE.sensor_type - - unique_id = f"{sense_monitor_id}-active-{variant_id}" entities.append( - SenseActiveSensor( + SensePowerSensor( data, - name, - sensor_type, sense_monitor_id, variant_id, variant_name, - unique_id, ) ) @@ -134,21 +113,15 @@ async def async_setup_entry( for i in range(len(data.active_voltage)) ) - for type_id, typ in TRENDS_SENSOR_TYPES.items(): + for scale in Scale: for variant_id, variant_name in TREND_SENSOR_VARIANTS: - name = typ.name - sensor_type = typ.sensor_type - - unique_id = f"{sense_monitor_id}-{type_id}-{variant_id}" entities.append( SenseTrendsSensor( data, - name, - sensor_type, + scale, variant_id, variant_name, trends_coordinator, - unique_id, sense_monitor_id, ) ) @@ -156,7 +129,7 @@ async def async_setup_entry( async_add_entities(entities) -class SenseActiveSensor(SensorEntity): +class SensePowerSensor(SensorEntity): """Implementation of a Sense energy sensor.""" _attr_device_class = SensorDeviceClass.POWER @@ -169,19 +142,15 @@ class SenseActiveSensor(SensorEntity): def __init__( self, data: ASyncSenseable, - name: str, - sensor_type: str, sense_monitor_id: str, variant_id: str, variant_name: str, - unique_id: str, ) -> None: """Initialize the Sense sensor.""" - self._attr_name = f"{name} {variant_name}" - self._attr_unique_id = unique_id + self._attr_name = f"{ACTIVE_NAME} {variant_name}" + self._attr_unique_id = f"{sense_monitor_id}-{ACTIVE_TYPE}-{variant_id}" self._data = data self._sense_monitor_id = sense_monitor_id - self._sensor_type = sensor_type self._variant_id = variant_id self._variant_name = variant_name @@ -264,20 +233,20 @@ class SenseTrendsSensor(CoordinatorEntity, SensorEntity): def __init__( self, data: ASyncSenseable, - name: str, - sensor_type: str, + scale: Scale, variant_id: str, variant_name: str, trends_coordinator: DataUpdateCoordinator[Any], - unique_id: str, sense_monitor_id: str, ) -> None: """Initialize the Sense sensor.""" super().__init__(trends_coordinator) - self._attr_name = f"{name} {variant_name}" - self._attr_unique_id = unique_id + self._attr_name = f"{TRENDS_SENSOR_TYPES[scale]} {variant_name}" + self._attr_unique_id = ( + f"{sense_monitor_id}-{TRENDS_SENSOR_TYPES[scale].lower()}-{variant_id}" + ) self._data = data - self._sensor_type = sensor_type + self._scale = scale self._variant_id = variant_id self._had_any_update = False if variant_id in [PRODUCTION_PCT_ID, SOLAR_POWERED_ID]: @@ -300,17 +269,17 @@ class SenseTrendsSensor(CoordinatorEntity, SensorEntity): @property def native_value(self) -> float: """Return the state of the sensor.""" - return round(self._data.get_trend(self._sensor_type, self._variant_id), 1) + return round(self._data.get_stat(self._scale, self._variant_id), 1) @property def last_reset(self) -> datetime | None: """Return the time when the sensor was last reset, if any.""" if self._attr_state_class == SensorStateClass.TOTAL: - return self._data.trend_start(self._sensor_type) + return self._data.trend_start(self._scale) return None -class SenseEnergyDevice(SensorEntity): +class SenseDevicePowerSensor(SensorEntity): """Implementation of a Sense energy device.""" _attr_available = False @@ -320,16 +289,14 @@ class SenseEnergyDevice(SensorEntity): _attr_device_class = SensorDeviceClass.POWER _attr_should_poll = False - def __init__( - self, sense_devices_data: SenseDevicesData, device: dict, sense_monitor_id: str - ) -> None: + def __init__(self, device: SenseDevice, sense_monitor_id: str) -> None: """Initialize the Sense binary sensor.""" - self._attr_name = f"{device['name']} {CONSUMPTION_NAME}" - self._id = device["id"] + self._attr_name = f"{device.name} {CONSUMPTION_NAME}" + self._id = device.id self._sense_monitor_id = sense_monitor_id self._attr_unique_id = f"{sense_monitor_id}-{self._id}-{CONSUMPTION_ID}" - self._attr_icon = sense_to_mdi(device["icon"]) - self._sense_devices_data = sense_devices_data + self._attr_icon = sense_to_mdi(device.icon) + self._device = device async def async_added_to_hass(self) -> None: """Register callbacks.""" @@ -344,11 +311,7 @@ class SenseEnergyDevice(SensorEntity): @callback def _async_update_from_data(self) -> None: """Get the latest data, update state. Must not do I/O.""" - device_data = self._sense_devices_data.get_device_by_id(self._id) - if not device_data or "w" not in device_data: - new_state = 0 - else: - new_state = int(device_data["w"]) + new_state = self._device.power_w if self._attr_available and self._attr_native_value == new_state: return self._attr_native_value = new_state diff --git a/requirements_all.txt b/requirements_all.txt index 447ec04b67c..ac5b3f1d1b0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2620,7 +2620,7 @@ sendgrid==6.8.2 # homeassistant.components.emulated_kasa # homeassistant.components.sense -sense-energy==0.12.4 +sense-energy==0.13.2 # homeassistant.components.sensirion_ble sensirion-ble==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9e94c066c96..1947dc89d48 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2087,7 +2087,7 @@ securetar==2024.2.1 # homeassistant.components.emulated_kasa # homeassistant.components.sense -sense-energy==0.12.4 +sense-energy==0.13.2 # homeassistant.components.sensirion_ble sensirion-ble==0.1.1 diff --git a/tests/components/sense/conftest.py b/tests/components/sense/conftest.py index e35f477b674..805dcab2744 100644 --- a/tests/components/sense/conftest.py +++ b/tests/components/sense/conftest.py @@ -8,13 +8,16 @@ from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch import pytest +from homeassistant.components.sense.binary_sensor import SenseDevice from homeassistant.components.sense.const import DOMAIN from .const import ( - DEVICE_1_DATA, + DEVICE_1_ID, DEVICE_1_NAME, - DEVICE_2_DATA, + DEVICE_1_POWER, + DEVICE_2_ID, DEVICE_2_NAME, + DEVICE_2_POWER, MOCK_CONFIG, MONITOR_ID, ) @@ -46,25 +49,31 @@ def mock_sense() -> Generator[MagicMock]: """Mock an ASyncSenseable object with a split foundation.""" with patch("homeassistant.components.sense.ASyncSenseable", autospec=True) as mock: gateway = mock.return_value - gateway._devices = [DEVICE_1_NAME, DEVICE_2_NAME] gateway.sense_monitor_id = MONITOR_ID gateway.get_monitor_data.return_value = None - gateway.get_discovered_device_data.return_value = [DEVICE_1_DATA, DEVICE_2_DATA] gateway.update_realtime.return_value = None + gateway.fetch_devices.return_value = None + gateway.update_trend_data.return_value = None + type(gateway).active_power = PropertyMock(return_value=100) type(gateway).active_solar_power = PropertyMock(return_value=500) type(gateway).active_voltage = PropertyMock(return_value=[120, 240]) - gateway.get_trend.return_value = 15 + gateway.get_stat.return_value = 15 gateway.trend_start.return_value = datetime.datetime.fromisoformat( "2024-01-01 01:01:00+00:00" ) - def get_realtime(): - yield {"devices": []} - yield {"devices": [DEVICE_1_DATA]} - while True: - yield {"devices": [DEVICE_1_DATA, DEVICE_2_DATA]} + device_1 = SenseDevice(DEVICE_1_ID) + device_1.name = DEVICE_1_NAME + device_1.icon = "car" + device_1.is_on = False + device_1.power_w = DEVICE_1_POWER - gateway.get_realtime.side_effect = get_realtime() + device_2 = SenseDevice(DEVICE_2_ID) + device_2.name = DEVICE_2_NAME + device_2.icon = "stove" + device_2.is_on = False + device_2.power_w = DEVICE_2_POWER + type(gateway).devices = PropertyMock(return_value=[device_1, device_2]) yield gateway diff --git a/tests/components/sense/const.py b/tests/components/sense/const.py index b33578a322a..2f63d94eae9 100644 --- a/tests/components/sense/const.py +++ b/tests/components/sense/const.py @@ -16,24 +16,9 @@ DEVICE_1_ID = "abc123" DEVICE_1_ICON = "car-electric" DEVICE_1_POWER = 100.0 -DEVICE_1_DATA = { - "name": DEVICE_1_NAME, - "id": DEVICE_1_ID, - "icon": "car", - "tags": {"DeviceListAllowed": "true"}, - "w": DEVICE_1_POWER, -} - DEVICE_2_NAME = "Oven" DEVICE_2_ID = "def456" DEVICE_2_ICON = "stove" DEVICE_2_POWER = 50.0 -DEVICE_2_DATA = { - "name": DEVICE_2_NAME, - "id": DEVICE_2_ID, - "icon": "stove", - "tags": {"DeviceListAllowed": "true"}, - "w": DEVICE_2_POWER, -} MONITOR_ID = "12345" diff --git a/tests/components/sense/snapshots/test_sensor.ambr b/tests/components/sense/snapshots/test_sensor.ambr index b98cde43253..48eda8150ca 100644 --- a/tests/components/sense/snapshots/test_sensor.ambr +++ b/tests/components/sense/snapshots/test_sensor.ambr @@ -1,4 +1,365 @@ # serializer version: 1 +# name: test_sensors[sensor.bill_from_grid-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.bill_from_grid', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Bill From Grid', + 'platform': 'sense', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '12345-bill-from_grid', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.bill_from_grid-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Sense.com', + 'device_class': 'energy', + 'friendly_name': 'Bill From Grid', + 'last_reset': '2024-01-01T01:01:00+00:00', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.bill_from_grid', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '15', + }) +# --- +# name: test_sensors[sensor.bill_net_production-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.bill_net_production', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Bill Net Production', + 'platform': 'sense', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '12345-bill-net_production', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.bill_net_production-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Sense.com', + 'device_class': 'energy', + 'friendly_name': 'Bill Net Production', + 'last_reset': '2024-01-01T01:01:00+00:00', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.bill_net_production', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '15', + }) +# --- +# name: test_sensors[sensor.bill_net_production_percentage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.bill_net_production_percentage', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Bill Net Production Percentage', + 'platform': 'sense', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '12345-bill-production_pct', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensors[sensor.bill_net_production_percentage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Sense.com', + 'friendly_name': 'Bill Net Production Percentage', + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.bill_net_production_percentage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '15', + }) +# --- +# name: test_sensors[sensor.bill_production-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.bill_production', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Bill Production', + 'platform': 'sense', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '12345-bill-production', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.bill_production-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Sense.com', + 'device_class': 'energy', + 'friendly_name': 'Bill Production', + 'last_reset': '2024-01-01T01:01:00+00:00', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.bill_production', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '15', + }) +# --- +# name: test_sensors[sensor.bill_solar_powered_percentage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.bill_solar_powered_percentage', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Bill Solar Powered Percentage', + 'platform': 'sense', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '12345-bill-solar_powered', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensors[sensor.bill_solar_powered_percentage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Sense.com', + 'friendly_name': 'Bill Solar Powered Percentage', + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.bill_solar_powered_percentage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '15', + }) +# --- +# name: test_sensors[sensor.bill_to_grid-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.bill_to_grid', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Bill To Grid', + 'platform': 'sense', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '12345-bill-to_grid', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.bill_to_grid-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Sense.com', + 'device_class': 'energy', + 'friendly_name': 'Bill To Grid', + 'last_reset': '2024-01-01T01:01:00+00:00', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.bill_to_grid', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '15', + }) +# --- +# name: test_sensors[sensor.bill_usage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.bill_usage', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Bill Usage', + 'platform': 'sense', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '12345-bill-usage', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.bill_usage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Sense.com', + 'device_class': 'energy', + 'friendly_name': 'Bill Usage', + 'last_reset': '2024-01-01T01:01:00+00:00', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.bill_usage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '15', + }) +# --- # name: test_sensors[sensor.car_usage-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/sense/test_binary_sensor.py b/tests/components/sense/test_binary_sensor.py index 391368f8b8f..907d9364ce1 100644 --- a/tests/components/sense/test_binary_sensor.py +++ b/tests/components/sense/test_binary_sensor.py @@ -38,6 +38,7 @@ async def test_on_off_sensors( ) -> None: """Test the Sense binary sensors.""" await setup_platform(hass, config_entry, BINARY_SENSOR_DOMAIN) + device_1, device_2 = mock_sense.devices state = hass.states.get(f"binary_sensor.{DEVICE_1_NAME.lower()}") assert state.state == STATE_UNAVAILABLE @@ -54,6 +55,7 @@ async def test_on_off_sensors( state = hass.states.get(f"binary_sensor.{DEVICE_2_NAME.lower()}") assert state.state == STATE_OFF + device_1.is_on = True async_fire_time_changed(hass, utcnow() + timedelta(seconds=ACTIVE_UPDATE_RATE)) await hass.async_block_till_done() @@ -63,11 +65,13 @@ async def test_on_off_sensors( state = hass.states.get(f"binary_sensor.{DEVICE_2_NAME.lower()}") assert state.state == STATE_OFF + device_1.is_on = False + device_2.is_on = True async_fire_time_changed(hass, utcnow() + timedelta(seconds=ACTIVE_UPDATE_RATE)) await hass.async_block_till_done() state = hass.states.get(f"binary_sensor.{DEVICE_1_NAME.lower()}") - assert state.state == STATE_ON + assert state.state == STATE_OFF state = hass.states.get(f"binary_sensor.{DEVICE_2_NAME.lower()}") assert state.state == STATE_ON diff --git a/tests/components/sense/test_sensor.py b/tests/components/sense/test_sensor.py index bd37c970918..d3a32e87677 100644 --- a/tests/components/sense/test_sensor.py +++ b/tests/components/sense/test_sensor.py @@ -4,6 +4,7 @@ from datetime import timedelta from unittest.mock import MagicMock, PropertyMock import pytest +from sense_energy import Scale from syrupy.assertion import SnapshotAssertion from homeassistant.components.sense.const import ACTIVE_UPDATE_RATE, CONSUMPTION_ID @@ -40,6 +41,7 @@ async def test_device_power_sensors( ) -> None: """Test the Sense device power sensors.""" await setup_platform(hass, config_entry, SENSOR_DOMAIN) + device_1, device_2 = mock_sense.devices state = hass.states.get(f"sensor.{DEVICE_1_NAME.lower()}_{CONSUMPTION_ID}") assert state.state == STATE_UNAVAILABLE @@ -47,6 +49,8 @@ async def test_device_power_sensors( state = hass.states.get(f"sensor.{DEVICE_2_NAME.lower()}_{CONSUMPTION_ID}") assert state.state == STATE_UNAVAILABLE + device_1.power_w = 0 + device_2.power_w = 0 async_fire_time_changed(hass, utcnow() + timedelta(seconds=ACTIVE_UPDATE_RATE)) await hass.async_block_till_done() @@ -56,23 +60,26 @@ async def test_device_power_sensors( state = hass.states.get(f"sensor.{DEVICE_2_NAME.lower()}_{CONSUMPTION_ID}") assert state.state == "0" + device_1.power_w = DEVICE_1_POWER async_fire_time_changed(hass, utcnow() + timedelta(seconds=ACTIVE_UPDATE_RATE)) await hass.async_block_till_done() state = hass.states.get(f"sensor.{DEVICE_1_NAME.lower()}_{CONSUMPTION_ID}") - assert state.state == f"{DEVICE_1_POWER:.0f}" + assert state.state == f"{DEVICE_1_POWER:.1f}" state = hass.states.get(f"sensor.{DEVICE_2_NAME.lower()}_{CONSUMPTION_ID}") assert state.state == "0" + device_1.power_w = 0 + device_2.power_w = DEVICE_2_POWER async_fire_time_changed(hass, utcnow() + timedelta(seconds=ACTIVE_UPDATE_RATE)) await hass.async_block_till_done() state = hass.states.get(f"sensor.{DEVICE_1_NAME.lower()}_{CONSUMPTION_ID}") - assert state.state == f"{DEVICE_1_POWER:.0f}" + assert state.state == "0" state = hass.states.get(f"sensor.{DEVICE_2_NAME.lower()}_{CONSUMPTION_ID}") - assert state.state == f"{DEVICE_2_POWER:.0f}" + assert state.state == f"{DEVICE_2_POWER:.1f}" async def test_voltage_sensors( @@ -160,14 +167,14 @@ async def test_trend_energy_sensors( config_entry: MockConfigEntry, ) -> None: """Test the Sense power sensors.""" - mock_sense.get_trend.side_effect = lambda sensor_type, variant: { - ("DAY", "usage"): 100, - ("DAY", "production"): 200, - ("DAY", "from_grid"): 300, - ("DAY", "to_grid"): 400, - ("DAY", "net_production"): 500, - ("DAY", "production_pct"): 600, - ("DAY", "solar_powered"): 700, + mock_sense.get_stat.side_effect = lambda sensor_type, variant: { + (Scale.DAY, "usage"): 100, + (Scale.DAY, "production"): 200, + (Scale.DAY, "from_grid"): 300, + (Scale.DAY, "to_grid"): 400, + (Scale.DAY, "net_production"): 500, + (Scale.DAY, "production_pct"): 600, + (Scale.DAY, "solar_powered"): 700, }.get((sensor_type, variant), 0) await setup_platform(hass, config_entry, SENSOR_DOMAIN) @@ -187,14 +194,14 @@ async def test_trend_energy_sensors( state = hass.states.get("sensor.daily_net_production") assert state.state == "500" - mock_sense.get_trend.side_effect = lambda sensor_type, variant: { - ("DAY", "usage"): 1000, - ("DAY", "production"): 2000, - ("DAY", "from_grid"): 3000, - ("DAY", "to_grid"): 4000, - ("DAY", "net_production"): 5000, - ("DAY", "production_pct"): 6000, - ("DAY", "solar_powered"): 7000, + mock_sense.get_stat.side_effect = lambda sensor_type, variant: { + (Scale.DAY, "usage"): 1000, + (Scale.DAY, "production"): 2000, + (Scale.DAY, "from_grid"): 3000, + (Scale.DAY, "to_grid"): 4000, + (Scale.DAY, "net_production"): 5000, + (Scale.DAY, "production_pct"): 6000, + (Scale.DAY, "solar_powered"): 7000, }.get((sensor_type, variant), 0) async_fire_time_changed(hass, utcnow() + timedelta(seconds=600)) await hass.async_block_till_done()