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 = [
|
PLATFORMS = [
|
||||||
Platform.BUTTON,
|
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.heartbeat_task = None
|
||||||
scale.process_queue_task = None
|
scale.process_queue_task = None
|
||||||
scale.device_state = AcaiaDeviceState(
|
scale.device_state = AcaiaDeviceState(
|
||||||
battery_level=42, units=AcaiaUnitOfMass.GRAMS
|
battery_level=42, units=AcaiaUnitOfMass.OUNCES
|
||||||
)
|
)
|
||||||
scale.weight = 123.45
|
scale.weight = 123.45
|
||||||
yield scale
|
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