mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Dynamically add sensors for new measurements in Withings (#102022)
* Dynamically add sensors for new data points in Withings * Dynamically add sensors for new data points in Withings * Add test * Change docstring * Store new measurements * Fix feedback * Add test back * Add test back * Add test back
This commit is contained in:
parent
05ee28cae5
commit
dcb5faa305
@ -57,8 +57,10 @@ class WithingsMeasurementSensorEntityDescription(
|
|||||||
"""Immutable class for describing withings data."""
|
"""Immutable class for describing withings data."""
|
||||||
|
|
||||||
|
|
||||||
MEASUREMENT_SENSORS = [
|
MEASUREMENT_SENSORS: dict[
|
||||||
WithingsMeasurementSensorEntityDescription(
|
MeasurementType, WithingsMeasurementSensorEntityDescription
|
||||||
|
] = {
|
||||||
|
MeasurementType.WEIGHT: WithingsMeasurementSensorEntityDescription(
|
||||||
key="weight_kg",
|
key="weight_kg",
|
||||||
measurement_type=MeasurementType.WEIGHT,
|
measurement_type=MeasurementType.WEIGHT,
|
||||||
native_unit_of_measurement=UnitOfMass.KILOGRAMS,
|
native_unit_of_measurement=UnitOfMass.KILOGRAMS,
|
||||||
@ -66,7 +68,7 @@ MEASUREMENT_SENSORS = [
|
|||||||
device_class=SensorDeviceClass.WEIGHT,
|
device_class=SensorDeviceClass.WEIGHT,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
WithingsMeasurementSensorEntityDescription(
|
MeasurementType.FAT_MASS_WEIGHT: WithingsMeasurementSensorEntityDescription(
|
||||||
key="fat_mass_kg",
|
key="fat_mass_kg",
|
||||||
measurement_type=MeasurementType.FAT_MASS_WEIGHT,
|
measurement_type=MeasurementType.FAT_MASS_WEIGHT,
|
||||||
translation_key="fat_mass",
|
translation_key="fat_mass",
|
||||||
@ -75,7 +77,7 @@ MEASUREMENT_SENSORS = [
|
|||||||
device_class=SensorDeviceClass.WEIGHT,
|
device_class=SensorDeviceClass.WEIGHT,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
WithingsMeasurementSensorEntityDescription(
|
MeasurementType.FAT_FREE_MASS: WithingsMeasurementSensorEntityDescription(
|
||||||
key="fat_free_mass_kg",
|
key="fat_free_mass_kg",
|
||||||
measurement_type=MeasurementType.FAT_FREE_MASS,
|
measurement_type=MeasurementType.FAT_FREE_MASS,
|
||||||
translation_key="fat_free_mass",
|
translation_key="fat_free_mass",
|
||||||
@ -84,7 +86,7 @@ MEASUREMENT_SENSORS = [
|
|||||||
device_class=SensorDeviceClass.WEIGHT,
|
device_class=SensorDeviceClass.WEIGHT,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
WithingsMeasurementSensorEntityDescription(
|
MeasurementType.MUSCLE_MASS: WithingsMeasurementSensorEntityDescription(
|
||||||
key="muscle_mass_kg",
|
key="muscle_mass_kg",
|
||||||
measurement_type=MeasurementType.MUSCLE_MASS,
|
measurement_type=MeasurementType.MUSCLE_MASS,
|
||||||
translation_key="muscle_mass",
|
translation_key="muscle_mass",
|
||||||
@ -93,7 +95,7 @@ MEASUREMENT_SENSORS = [
|
|||||||
device_class=SensorDeviceClass.WEIGHT,
|
device_class=SensorDeviceClass.WEIGHT,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
WithingsMeasurementSensorEntityDescription(
|
MeasurementType.BONE_MASS: WithingsMeasurementSensorEntityDescription(
|
||||||
key="bone_mass_kg",
|
key="bone_mass_kg",
|
||||||
measurement_type=MeasurementType.BONE_MASS,
|
measurement_type=MeasurementType.BONE_MASS,
|
||||||
translation_key="bone_mass",
|
translation_key="bone_mass",
|
||||||
@ -103,7 +105,7 @@ MEASUREMENT_SENSORS = [
|
|||||||
device_class=SensorDeviceClass.WEIGHT,
|
device_class=SensorDeviceClass.WEIGHT,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
WithingsMeasurementSensorEntityDescription(
|
MeasurementType.HEIGHT: WithingsMeasurementSensorEntityDescription(
|
||||||
key="height_m",
|
key="height_m",
|
||||||
measurement_type=MeasurementType.HEIGHT,
|
measurement_type=MeasurementType.HEIGHT,
|
||||||
translation_key="height",
|
translation_key="height",
|
||||||
@ -113,14 +115,14 @@ MEASUREMENT_SENSORS = [
|
|||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
),
|
),
|
||||||
WithingsMeasurementSensorEntityDescription(
|
MeasurementType.TEMPERATURE: WithingsMeasurementSensorEntityDescription(
|
||||||
key="temperature_c",
|
key="temperature_c",
|
||||||
measurement_type=MeasurementType.TEMPERATURE,
|
measurement_type=MeasurementType.TEMPERATURE,
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
WithingsMeasurementSensorEntityDescription(
|
MeasurementType.BODY_TEMPERATURE: WithingsMeasurementSensorEntityDescription(
|
||||||
key="body_temperature_c",
|
key="body_temperature_c",
|
||||||
measurement_type=MeasurementType.BODY_TEMPERATURE,
|
measurement_type=MeasurementType.BODY_TEMPERATURE,
|
||||||
translation_key="body_temperature",
|
translation_key="body_temperature",
|
||||||
@ -128,7 +130,7 @@ MEASUREMENT_SENSORS = [
|
|||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
WithingsMeasurementSensorEntityDescription(
|
MeasurementType.SKIN_TEMPERATURE: WithingsMeasurementSensorEntityDescription(
|
||||||
key="skin_temperature_c",
|
key="skin_temperature_c",
|
||||||
measurement_type=MeasurementType.SKIN_TEMPERATURE,
|
measurement_type=MeasurementType.SKIN_TEMPERATURE,
|
||||||
translation_key="skin_temperature",
|
translation_key="skin_temperature",
|
||||||
@ -136,7 +138,7 @@ MEASUREMENT_SENSORS = [
|
|||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
WithingsMeasurementSensorEntityDescription(
|
MeasurementType.FAT_RATIO: WithingsMeasurementSensorEntityDescription(
|
||||||
key="fat_ratio_pct",
|
key="fat_ratio_pct",
|
||||||
measurement_type=MeasurementType.FAT_RATIO,
|
measurement_type=MeasurementType.FAT_RATIO,
|
||||||
translation_key="fat_ratio",
|
translation_key="fat_ratio",
|
||||||
@ -144,21 +146,21 @@ MEASUREMENT_SENSORS = [
|
|||||||
suggested_display_precision=2,
|
suggested_display_precision=2,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
WithingsMeasurementSensorEntityDescription(
|
MeasurementType.DIASTOLIC_BLOOD_PRESSURE: WithingsMeasurementSensorEntityDescription(
|
||||||
key="diastolic_blood_pressure_mmhg",
|
key="diastolic_blood_pressure_mmhg",
|
||||||
measurement_type=MeasurementType.DIASTOLIC_BLOOD_PRESSURE,
|
measurement_type=MeasurementType.DIASTOLIC_BLOOD_PRESSURE,
|
||||||
translation_key="diastolic_blood_pressure",
|
translation_key="diastolic_blood_pressure",
|
||||||
native_unit_of_measurement=UOM_MMHG,
|
native_unit_of_measurement=UOM_MMHG,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
WithingsMeasurementSensorEntityDescription(
|
MeasurementType.SYSTOLIC_BLOOD_PRESSURE: WithingsMeasurementSensorEntityDescription(
|
||||||
key="systolic_blood_pressure_mmhg",
|
key="systolic_blood_pressure_mmhg",
|
||||||
measurement_type=MeasurementType.SYSTOLIC_BLOOD_PRESSURE,
|
measurement_type=MeasurementType.SYSTOLIC_BLOOD_PRESSURE,
|
||||||
translation_key="systolic_blood_pressure",
|
translation_key="systolic_blood_pressure",
|
||||||
native_unit_of_measurement=UOM_MMHG,
|
native_unit_of_measurement=UOM_MMHG,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
WithingsMeasurementSensorEntityDescription(
|
MeasurementType.HEART_RATE: WithingsMeasurementSensorEntityDescription(
|
||||||
key="heart_pulse_bpm",
|
key="heart_pulse_bpm",
|
||||||
measurement_type=MeasurementType.HEART_RATE,
|
measurement_type=MeasurementType.HEART_RATE,
|
||||||
translation_key="heart_pulse",
|
translation_key="heart_pulse",
|
||||||
@ -166,14 +168,14 @@ MEASUREMENT_SENSORS = [
|
|||||||
icon="mdi:heart-pulse",
|
icon="mdi:heart-pulse",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
WithingsMeasurementSensorEntityDescription(
|
MeasurementType.SP02: WithingsMeasurementSensorEntityDescription(
|
||||||
key="spo2_pct",
|
key="spo2_pct",
|
||||||
measurement_type=MeasurementType.SP02,
|
measurement_type=MeasurementType.SP02,
|
||||||
translation_key="spo2",
|
translation_key="spo2",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
WithingsMeasurementSensorEntityDescription(
|
MeasurementType.HYDRATION: WithingsMeasurementSensorEntityDescription(
|
||||||
key="hydration",
|
key="hydration",
|
||||||
measurement_type=MeasurementType.HYDRATION,
|
measurement_type=MeasurementType.HYDRATION,
|
||||||
translation_key="hydration",
|
translation_key="hydration",
|
||||||
@ -183,7 +185,7 @@ MEASUREMENT_SENSORS = [
|
|||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
),
|
),
|
||||||
WithingsMeasurementSensorEntityDescription(
|
MeasurementType.PULSE_WAVE_VELOCITY: WithingsMeasurementSensorEntityDescription(
|
||||||
key="pulse_wave_velocity",
|
key="pulse_wave_velocity",
|
||||||
measurement_type=MeasurementType.PULSE_WAVE_VELOCITY,
|
measurement_type=MeasurementType.PULSE_WAVE_VELOCITY,
|
||||||
translation_key="pulse_wave_velocity",
|
translation_key="pulse_wave_velocity",
|
||||||
@ -191,7 +193,7 @@ MEASUREMENT_SENSORS = [
|
|||||||
device_class=SensorDeviceClass.SPEED,
|
device_class=SensorDeviceClass.SPEED,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
]
|
}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -371,11 +373,32 @@ async def async_setup_entry(
|
|||||||
measurement_coordinator: WithingsMeasurementDataUpdateCoordinator = hass.data[
|
measurement_coordinator: WithingsMeasurementDataUpdateCoordinator = hass.data[
|
||||||
DOMAIN
|
DOMAIN
|
||||||
][entry.entry_id][MEASUREMENT_COORDINATOR]
|
][entry.entry_id][MEASUREMENT_COORDINATOR]
|
||||||
|
|
||||||
|
current_measurement_types = set(measurement_coordinator.data.keys())
|
||||||
|
|
||||||
entities: list[SensorEntity] = []
|
entities: list[SensorEntity] = []
|
||||||
entities.extend(
|
entities.extend(
|
||||||
WithingsMeasurementSensor(measurement_coordinator, attribute)
|
WithingsMeasurementSensor(
|
||||||
for attribute in MEASUREMENT_SENSORS
|
measurement_coordinator, MEASUREMENT_SENSORS[measurement_type]
|
||||||
)
|
)
|
||||||
|
for measurement_type in measurement_coordinator.data
|
||||||
|
if measurement_type in MEASUREMENT_SENSORS
|
||||||
|
)
|
||||||
|
|
||||||
|
def _async_measurement_listener() -> None:
|
||||||
|
"""Listen for new measurements and add sensors if they did not exist."""
|
||||||
|
received_measurement_types = set(measurement_coordinator.data.keys())
|
||||||
|
new_measurement_types = received_measurement_types - current_measurement_types
|
||||||
|
if new_measurement_types:
|
||||||
|
current_measurement_types.update(new_measurement_types)
|
||||||
|
async_add_entities(
|
||||||
|
WithingsMeasurementSensor(
|
||||||
|
measurement_coordinator, MEASUREMENT_SENSORS[measurement_type]
|
||||||
|
)
|
||||||
|
for measurement_type in new_measurement_types
|
||||||
|
)
|
||||||
|
|
||||||
|
measurement_coordinator.async_add_listener(_async_measurement_listener)
|
||||||
sleep_coordinator: WithingsSleepDataUpdateCoordinator = hass.data[DOMAIN][
|
sleep_coordinator: WithingsSleepDataUpdateCoordinator = hass.data[DOMAIN][
|
||||||
entry.entry_id
|
entry.entry_id
|
||||||
][SLEEP_COORDINATOR]
|
][SLEEP_COORDINATOR]
|
||||||
|
@ -14,11 +14,6 @@
|
|||||||
"unit": 0,
|
"unit": 0,
|
||||||
"value": 71
|
"value": 71
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": 8,
|
|
||||||
"unit": 0,
|
|
||||||
"value": 5
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": 5,
|
"type": 5,
|
||||||
"unit": 0,
|
"unit": 0,
|
||||||
|
@ -17,6 +17,7 @@ from tests.common import (
|
|||||||
MockConfigEntry,
|
MockConfigEntry,
|
||||||
async_fire_time_changed,
|
async_fire_time_changed,
|
||||||
load_json_array_fixture,
|
load_json_array_fixture,
|
||||||
|
load_json_object_fixture,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -103,3 +104,33 @@ async def test_update_updates_incrementally(
|
|||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.state == "71"
|
assert state.state == "71"
|
||||||
assert len(withings.get_measurement_in_period.call_args_list) == 1
|
assert len(withings.get_measurement_in_period.call_args_list) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update_new_measurement_creates_new_sensor(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
withings: AsyncMock,
|
||||||
|
polling_config_entry: MockConfigEntry,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test fetching a new measurement will add a new sensor."""
|
||||||
|
meas_json = load_json_array_fixture("withings/get_meas_1.json")
|
||||||
|
measurement_groups = [
|
||||||
|
MeasurementGroup.from_api(measurement) for measurement in meas_json
|
||||||
|
]
|
||||||
|
withings.get_measurement_in_period.return_value = measurement_groups
|
||||||
|
await setup_integration(hass, polling_config_entry, False)
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.henk_fat_mass") is None
|
||||||
|
|
||||||
|
meas_json = load_json_object_fixture("withings/get_meas.json")
|
||||||
|
measurement_groups = [
|
||||||
|
MeasurementGroup.from_api(measurement)
|
||||||
|
for measurement in meas_json["measuregrps"]
|
||||||
|
]
|
||||||
|
withings.get_measurement_in_period.return_value = measurement_groups
|
||||||
|
|
||||||
|
freezer.tick(timedelta(minutes=10))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.henk_fat_mass") is not None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user