mirror of
https://github.com/home-assistant/core.git
synced 2025-07-29 08:07:45 +00:00
Add sensor platform to acaia (#130614)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
1ac0b006b2
commit
4c816f54bf
@ -7,6 +7,7 @@ from .coordinator import AcaiaConfigEntry, AcaiaCoordinator
|
||||
|
||||
PLATFORMS = [
|
||||
Platform.BUTTON,
|
||||
Platform.SENSOR,
|
||||
]
|
||||
|
||||
|
||||
|
135
homeassistant/components/acaia/sensor.py
Normal file
135
homeassistant/components/acaia/sensor.py
Normal file
@ -0,0 +1,135 @@
|
||||
"""Sensor platform for Acaia."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from aioacaia.acaiascale import AcaiaDeviceState, AcaiaScale
|
||||
from aioacaia.const import UnitMass as AcaiaUnitOfMass
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
RestoreSensor,
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorExtraStoredData,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import PERCENTAGE, UnitOfMass
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .coordinator import AcaiaConfigEntry
|
||||
from .entity import AcaiaEntity
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class AcaiaSensorEntityDescription(SensorEntityDescription):
|
||||
"""Description for Acaia sensor entities."""
|
||||
|
||||
value_fn: Callable[[AcaiaScale], int | float | None]
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class AcaiaDynamicUnitSensorEntityDescription(AcaiaSensorEntityDescription):
|
||||
"""Description for Acaia sensor entities with dynamic units."""
|
||||
|
||||
unit_fn: Callable[[AcaiaDeviceState], str] | None = None
|
||||
|
||||
|
||||
SENSORS: tuple[AcaiaSensorEntityDescription, ...] = (
|
||||
AcaiaDynamicUnitSensorEntityDescription(
|
||||
key="weight",
|
||||
device_class=SensorDeviceClass.WEIGHT,
|
||||
native_unit_of_measurement=UnitOfMass.GRAMS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
unit_fn=lambda data: (
|
||||
UnitOfMass.OUNCES
|
||||
if data.units == AcaiaUnitOfMass.OUNCES
|
||||
else UnitOfMass.GRAMS
|
||||
),
|
||||
value_fn=lambda scale: scale.weight,
|
||||
),
|
||||
)
|
||||
RESTORE_SENSORS: tuple[AcaiaSensorEntityDescription, ...] = (
|
||||
AcaiaSensorEntityDescription(
|
||||
key="battery",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda scale: (
|
||||
scale.device_state.battery_level if scale.device_state else None
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: AcaiaConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up sensors."""
|
||||
|
||||
coordinator = entry.runtime_data
|
||||
entities: list[SensorEntity] = [
|
||||
AcaiaSensor(coordinator, entity_description) for entity_description in SENSORS
|
||||
]
|
||||
entities.extend(
|
||||
AcaiaRestoreSensor(coordinator, entity_description)
|
||||
for entity_description in RESTORE_SENSORS
|
||||
)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AcaiaSensor(AcaiaEntity, SensorEntity):
|
||||
"""Representation of an Acaia sensor."""
|
||||
|
||||
entity_description: AcaiaDynamicUnitSensorEntityDescription
|
||||
|
||||
@property
|
||||
def native_unit_of_measurement(self) -> str | None:
|
||||
"""Return the unit of measurement of this entity."""
|
||||
if (
|
||||
self._scale.device_state is not None
|
||||
and self.entity_description.unit_fn is not None
|
||||
):
|
||||
return self.entity_description.unit_fn(self._scale.device_state)
|
||||
return self.entity_description.native_unit_of_measurement
|
||||
|
||||
@property
|
||||
def native_value(self) -> int | float | None:
|
||||
"""Return the state of the entity."""
|
||||
return self.entity_description.value_fn(self._scale)
|
||||
|
||||
|
||||
class AcaiaRestoreSensor(AcaiaEntity, RestoreSensor):
|
||||
"""Representation of an Acaia sensor with restore capabilities."""
|
||||
|
||||
entity_description: AcaiaSensorEntityDescription
|
||||
_restored_data: SensorExtraStoredData | None = None
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Handle entity which will be added."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
self._restored_data = await self.async_get_last_sensor_data()
|
||||
if self._restored_data is not None:
|
||||
self._attr_native_value = self._restored_data.native_value
|
||||
self._attr_native_unit_of_measurement = (
|
||||
self._restored_data.native_unit_of_measurement
|
||||
)
|
||||
|
||||
if self._scale.device_state is not None:
|
||||
self._attr_native_value = self.entity_description.value_fn(self._scale)
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
if self._scale.device_state is not None:
|
||||
self._attr_native_value = self.entity_description.value_fn(self._scale)
|
||||
self._async_write_ha_state()
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return super().available or self._restored_data is not None
|
@ -74,7 +74,7 @@ def mock_scale() -> Generator[MagicMock]:
|
||||
scale.heartbeat_task = None
|
||||
scale.process_queue_task = None
|
||||
scale.device_state = AcaiaDeviceState(
|
||||
battery_level=42, units=AcaiaUnitOfMass.GRAMS
|
||||
battery_level=42, units=AcaiaUnitOfMass.OUNCES
|
||||
)
|
||||
scale.weight = 123.45
|
||||
yield scale
|
||||
|
103
tests/components/acaia/snapshots/test_sensor.ambr
Normal file
103
tests/components/acaia/snapshots/test_sensor.ambr
Normal file
@ -0,0 +1,103 @@
|
||||
# serializer version: 1
|
||||
# name: test_sensors[sensor.lunar_ddeeff_battery-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.lunar_ddeeff_battery',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Battery',
|
||||
'platform': 'acaia',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_battery',
|
||||
'unit_of_measurement': '%',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.lunar_ddeeff_battery-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'battery',
|
||||
'friendly_name': 'LUNAR-DDEEFF Battery',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.lunar_ddeeff_battery',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '42',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.lunar_ddeeff_weight-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.lunar_ddeeff_weight',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.WEIGHT: 'weight'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Weight',
|
||||
'platform': 'acaia',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_weight',
|
||||
'unit_of_measurement': <UnitOfMass.OUNCES: 'oz'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.lunar_ddeeff_weight-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'weight',
|
||||
'friendly_name': 'LUNAR-DDEEFF Weight',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfMass.OUNCES: 'oz'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.lunar_ddeeff_weight',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '123.45',
|
||||
})
|
||||
# ---
|
63
tests/components/acaia/test_sensor.py
Normal file
63
tests/components/acaia/test_sensor.py
Normal file
@ -0,0 +1,63 @@
|
||||
"""Test sensors for acaia integration."""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import PERCENTAGE, Platform
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
mock_restore_cache_with_extra_data,
|
||||
snapshot_platform,
|
||||
)
|
||||
|
||||
|
||||
async def test_sensors(
|
||||
hass: HomeAssistant,
|
||||
mock_scale: MagicMock,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the Acaia sensors."""
|
||||
with patch("homeassistant.components.acaia.PLATFORMS", [Platform.SENSOR]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
async def test_restore_state(
|
||||
hass: HomeAssistant,
|
||||
mock_scale: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test battery sensor restore state."""
|
||||
mock_scale.device_state = None
|
||||
entity_id = "sensor.lunar_ddeeff_battery"
|
||||
|
||||
mock_restore_cache_with_extra_data(
|
||||
hass,
|
||||
(
|
||||
(
|
||||
State(
|
||||
entity_id,
|
||||
"1",
|
||||
),
|
||||
{
|
||||
"native_value": 65,
|
||||
"native_unit_of_measurement": PERCENTAGE,
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == "65"
|
Loading…
x
Reference in New Issue
Block a user