1 """Sensors flow for Withings."""
3 from __future__
import annotations
5 from collections.abc
import Callable
6 from dataclasses
import dataclass
7 from datetime
import datetime
10 from aiowithings
import (
24 SensorEntityDescription,
43 from .
import WithingsConfigEntry
49 UOM_BREATHS_PER_MINUTE,
53 from .coordinator
import (
54 WithingsActivityDataUpdateCoordinator,
55 WithingsDataUpdateCoordinator,
56 WithingsDeviceDataUpdateCoordinator,
57 WithingsGoalsDataUpdateCoordinator,
58 WithingsMeasurementDataUpdateCoordinator,
59 WithingsSleepDataUpdateCoordinator,
60 WithingsWorkoutDataUpdateCoordinator,
62 from .entity
import WithingsDeviceEntity, WithingsEntity
65 @dataclass(frozen=True, kw_only=True)
67 """Immutable class for describing withings data."""
69 measurement_type: MeasurementType
70 measurement_position: MeasurementPosition |
None =
None
73 MEASUREMENT_SENSORS: dict[
74 MeasurementType, WithingsMeasurementSensorEntityDescription
78 measurement_type=MeasurementType.WEIGHT,
79 native_unit_of_measurement=UnitOfMass.KILOGRAMS,
80 suggested_display_precision=2,
81 device_class=SensorDeviceClass.WEIGHT,
82 state_class=SensorStateClass.MEASUREMENT,
86 measurement_type=MeasurementType.FAT_MASS_WEIGHT,
87 translation_key=
"fat_mass",
88 native_unit_of_measurement=UnitOfMass.KILOGRAMS,
89 suggested_display_precision=2,
90 device_class=SensorDeviceClass.WEIGHT,
91 state_class=SensorStateClass.MEASUREMENT,
94 key=
"fat_free_mass_kg",
95 measurement_type=MeasurementType.FAT_FREE_MASS,
96 translation_key=
"fat_free_mass",
97 native_unit_of_measurement=UnitOfMass.KILOGRAMS,
98 suggested_display_precision=2,
99 device_class=SensorDeviceClass.WEIGHT,
100 state_class=SensorStateClass.MEASUREMENT,
103 key=
"muscle_mass_kg",
104 measurement_type=MeasurementType.MUSCLE_MASS,
105 translation_key=
"muscle_mass",
106 native_unit_of_measurement=UnitOfMass.KILOGRAMS,
107 suggested_display_precision=2,
108 device_class=SensorDeviceClass.WEIGHT,
109 state_class=SensorStateClass.MEASUREMENT,
113 measurement_type=MeasurementType.BONE_MASS,
114 translation_key=
"bone_mass",
115 native_unit_of_measurement=UnitOfMass.KILOGRAMS,
116 suggested_display_precision=2,
117 device_class=SensorDeviceClass.WEIGHT,
118 state_class=SensorStateClass.MEASUREMENT,
122 measurement_type=MeasurementType.HEIGHT,
123 translation_key=
"height",
124 native_unit_of_measurement=UnitOfLength.METERS,
125 suggested_display_precision=1,
126 device_class=SensorDeviceClass.DISTANCE,
127 state_class=SensorStateClass.MEASUREMENT,
128 entity_registry_enabled_default=
False,
132 measurement_type=MeasurementType.TEMPERATURE,
133 native_unit_of_measurement=UnitOfTemperature.CELSIUS,
134 device_class=SensorDeviceClass.TEMPERATURE,
135 state_class=SensorStateClass.MEASUREMENT,
138 key=
"body_temperature_c",
139 measurement_type=MeasurementType.BODY_TEMPERATURE,
140 translation_key=
"body_temperature",
141 native_unit_of_measurement=UnitOfTemperature.CELSIUS,
142 device_class=SensorDeviceClass.TEMPERATURE,
143 state_class=SensorStateClass.MEASUREMENT,
146 key=
"skin_temperature_c",
147 measurement_type=MeasurementType.SKIN_TEMPERATURE,
148 translation_key=
"skin_temperature",
149 native_unit_of_measurement=UnitOfTemperature.CELSIUS,
150 device_class=SensorDeviceClass.TEMPERATURE,
151 state_class=SensorStateClass.MEASUREMENT,
155 measurement_type=MeasurementType.FAT_RATIO,
156 translation_key=
"fat_ratio",
157 native_unit_of_measurement=PERCENTAGE,
158 suggested_display_precision=2,
159 state_class=SensorStateClass.MEASUREMENT,
162 key=
"diastolic_blood_pressure_mmhg",
163 measurement_type=MeasurementType.DIASTOLIC_BLOOD_PRESSURE,
164 translation_key=
"diastolic_blood_pressure",
165 native_unit_of_measurement=UOM_MMHG,
166 state_class=SensorStateClass.MEASUREMENT,
169 key=
"systolic_blood_pressure_mmhg",
170 measurement_type=MeasurementType.SYSTOLIC_BLOOD_PRESSURE,
171 translation_key=
"systolic_blood_pressure",
172 native_unit_of_measurement=UOM_MMHG,
173 state_class=SensorStateClass.MEASUREMENT,
176 key=
"heart_pulse_bpm",
177 measurement_type=MeasurementType.HEART_RATE,
178 translation_key=
"heart_pulse",
179 native_unit_of_measurement=UOM_BEATS_PER_MINUTE,
180 state_class=SensorStateClass.MEASUREMENT,
184 measurement_type=MeasurementType.SP02,
185 translation_key=
"spo2",
186 native_unit_of_measurement=PERCENTAGE,
187 state_class=SensorStateClass.MEASUREMENT,
191 measurement_type=MeasurementType.HYDRATION,
192 translation_key=
"hydration",
193 native_unit_of_measurement=UnitOfMass.KILOGRAMS,
194 device_class=SensorDeviceClass.WEIGHT,
195 state_class=SensorStateClass.MEASUREMENT,
196 entity_registry_enabled_default=
False,
199 key=
"pulse_wave_velocity",
200 measurement_type=MeasurementType.PULSE_WAVE_VELOCITY,
201 translation_key=
"pulse_wave_velocity",
202 native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
203 device_class=SensorDeviceClass.SPEED,
204 state_class=SensorStateClass.MEASUREMENT,
208 measurement_type=MeasurementType.VO2,
209 translation_key=
"vo2_max",
210 native_unit_of_measurement=
"ml/min/kg",
211 state_class=SensorStateClass.MEASUREMENT,
212 entity_registry_enabled_default=
False,
215 key=
"extracellular_water",
216 measurement_type=MeasurementType.EXTRACELLULAR_WATER,
217 translation_key=
"extracellular_water",
218 native_unit_of_measurement=UnitOfMass.KILOGRAMS,
219 device_class=SensorDeviceClass.WEIGHT,
220 state_class=SensorStateClass.MEASUREMENT,
221 entity_registry_enabled_default=
False,
224 key=
"intracellular_water",
225 measurement_type=MeasurementType.INTRACELLULAR_WATER,
226 translation_key=
"intracellular_water",
227 native_unit_of_measurement=UnitOfMass.KILOGRAMS,
228 device_class=SensorDeviceClass.WEIGHT,
229 state_class=SensorStateClass.MEASUREMENT,
230 entity_registry_enabled_default=
False,
234 measurement_type=MeasurementType.VASCULAR_AGE,
235 translation_key=
"vascular_age",
236 entity_registry_enabled_default=
False,
240 measurement_type=MeasurementType.VISCERAL_FAT,
241 translation_key=
"visceral_fat_index",
242 entity_registry_enabled_default=
False,
245 key=
"electrodermal_activity_feet",
246 measurement_type=MeasurementType.ELECTRODERMAL_ACTIVITY_FEET,
247 translation_key=
"electrodermal_activity_feet",
248 native_unit_of_measurement=PERCENTAGE,
249 entity_registry_enabled_default=
False,
252 key=
"electrodermal_activity_left_foot",
253 measurement_type=MeasurementType.ELECTRODERMAL_ACTIVITY_LEFT_FOOT,
254 translation_key=
"electrodermal_activity_left_foot",
255 native_unit_of_measurement=PERCENTAGE,
256 entity_registry_enabled_default=
False,
259 key=
"electrodermal_activity_right_foot",
260 measurement_type=MeasurementType.ELECTRODERMAL_ACTIVITY_RIGHT_FOOT,
261 translation_key=
"electrodermal_activity_right_foot",
262 native_unit_of_measurement=PERCENTAGE,
263 entity_registry_enabled_default=
False,
269 measurement_type: MeasurementType, measurement_position: MeasurementPosition
270 ) -> WithingsMeasurementSensorEntityDescription |
None:
271 """Get the sensor description for a measurement type."""
272 if measurement_position
not in (
273 MeasurementPosition.TORSO,
274 MeasurementPosition.LEFT_ARM,
275 MeasurementPosition.RIGHT_ARM,
276 MeasurementPosition.LEFT_LEG,
277 MeasurementPosition.RIGHT_LEG,
278 )
or measurement_type
not in (
279 MeasurementType.MUSCLE_MASS_FOR_SEGMENTS,
280 MeasurementType.FAT_FREE_MASS_FOR_SEGMENTS,
281 MeasurementType.FAT_MASS_FOR_SEGMENTS,
285 key=f
"{measurement_type.name.lower()}_{measurement_position.name.lower()}",
286 measurement_type=measurement_type,
287 measurement_position=measurement_position,
288 translation_key=f
"{measurement_type.name.lower()}_{measurement_position.name.lower()}",
289 native_unit_of_measurement=UnitOfMass.KILOGRAMS,
290 suggested_display_precision=2,
291 device_class=SensorDeviceClass.WEIGHT,
292 state_class=SensorStateClass.MEASUREMENT,
293 entity_registry_enabled_default=
False,
298 measurement: tuple[MeasurementType, MeasurementPosition |
None],
299 ) -> WithingsMeasurementSensorEntityDescription |
None:
300 """Get the sensor description for a measurement type."""
301 measurement_type, measurement_position = measurement
302 if measurement_position
is not None:
304 measurement_type, measurement_position
306 return MEASUREMENT_SENSORS.get(measurement_type)
309 @dataclass(frozen=True, kw_only=True)
311 """Immutable class for describing withings data."""
313 value_fn: Callable[[SleepSummary], StateType]
318 key=
"sleep_breathing_disturbances_intensity",
319 value_fn=
lambda sleep_summary: sleep_summary.breathing_disturbances_intensity,
320 translation_key=
"breathing_disturbances_intensity",
321 state_class=SensorStateClass.MEASUREMENT,
322 entity_registry_enabled_default=
False,
325 key=
"sleep_deep_duration_seconds",
326 value_fn=
lambda sleep_summary: sleep_summary.deep_sleep_duration,
327 translation_key=
"deep_sleep",
328 native_unit_of_measurement=UnitOfTime.SECONDS,
329 device_class=SensorDeviceClass.DURATION,
330 state_class=SensorStateClass.MEASUREMENT,
333 key=
"sleep_tosleep_duration_seconds",
334 value_fn=
lambda sleep_summary: sleep_summary.sleep_latency,
335 translation_key=
"time_to_sleep",
336 native_unit_of_measurement=UnitOfTime.SECONDS,
337 device_class=SensorDeviceClass.DURATION,
338 state_class=SensorStateClass.MEASUREMENT,
339 entity_registry_enabled_default=
False,
342 key=
"sleep_towakeup_duration_seconds",
343 value_fn=
lambda sleep_summary: sleep_summary.wake_up_latency,
344 translation_key=
"time_to_wakeup",
345 native_unit_of_measurement=UnitOfTime.SECONDS,
346 device_class=SensorDeviceClass.DURATION,
347 state_class=SensorStateClass.MEASUREMENT,
348 entity_registry_enabled_default=
False,
351 key=
"sleep_heart_rate_average_bpm",
352 value_fn=
lambda sleep_summary: sleep_summary.average_heart_rate,
353 translation_key=
"average_heart_rate",
354 native_unit_of_measurement=UOM_BEATS_PER_MINUTE,
355 state_class=SensorStateClass.MEASUREMENT,
356 entity_registry_enabled_default=
False,
359 key=
"sleep_heart_rate_max_bpm",
360 value_fn=
lambda sleep_summary: sleep_summary.max_heart_rate,
361 translation_key=
"maximum_heart_rate",
362 native_unit_of_measurement=UOM_BEATS_PER_MINUTE,
363 state_class=SensorStateClass.MEASUREMENT,
364 entity_registry_enabled_default=
False,
367 key=
"sleep_heart_rate_min_bpm",
368 value_fn=
lambda sleep_summary: sleep_summary.min_heart_rate,
369 translation_key=
"minimum_heart_rate",
370 native_unit_of_measurement=UOM_BEATS_PER_MINUTE,
371 state_class=SensorStateClass.MEASUREMENT,
372 entity_registry_enabled_default=
False,
375 key=
"sleep_light_duration_seconds",
376 value_fn=
lambda sleep_summary: sleep_summary.light_sleep_duration,
377 translation_key=
"light_sleep",
378 native_unit_of_measurement=UnitOfTime.SECONDS,
379 device_class=SensorDeviceClass.DURATION,
380 state_class=SensorStateClass.MEASUREMENT,
381 entity_registry_enabled_default=
False,
384 key=
"sleep_rem_duration_seconds",
385 value_fn=
lambda sleep_summary: sleep_summary.rem_sleep_duration,
386 translation_key=
"rem_sleep",
387 native_unit_of_measurement=UnitOfTime.SECONDS,
388 device_class=SensorDeviceClass.DURATION,
389 state_class=SensorStateClass.MEASUREMENT,
390 entity_registry_enabled_default=
False,
393 key=
"sleep_respiratory_average_bpm",
394 value_fn=
lambda sleep_summary: sleep_summary.average_respiration_rate,
395 translation_key=
"average_respiratory_rate",
396 native_unit_of_measurement=UOM_BREATHS_PER_MINUTE,
397 state_class=SensorStateClass.MEASUREMENT,
398 entity_registry_enabled_default=
False,
401 key=
"sleep_respiratory_max_bpm",
402 value_fn=
lambda sleep_summary: sleep_summary.max_respiration_rate,
403 translation_key=
"maximum_respiratory_rate",
404 native_unit_of_measurement=UOM_BREATHS_PER_MINUTE,
405 state_class=SensorStateClass.MEASUREMENT,
406 entity_registry_enabled_default=
False,
409 key=
"sleep_respiratory_min_bpm",
410 value_fn=
lambda sleep_summary: sleep_summary.min_respiration_rate,
411 translation_key=
"minimum_respiratory_rate",
412 native_unit_of_measurement=UOM_BREATHS_PER_MINUTE,
413 state_class=SensorStateClass.MEASUREMENT,
414 entity_registry_enabled_default=
False,
418 value_fn=
lambda sleep_summary: sleep_summary.sleep_score,
419 translation_key=
"sleep_score",
420 native_unit_of_measurement=SCORE_POINTS,
421 state_class=SensorStateClass.MEASUREMENT,
422 entity_registry_enabled_default=
False,
426 value_fn=
lambda sleep_summary: sleep_summary.snoring,
427 translation_key=
"snoring",
428 state_class=SensorStateClass.MEASUREMENT,
429 entity_registry_enabled_default=
False,
432 key=
"sleep_snoring_eposode_count",
433 value_fn=
lambda sleep_summary: sleep_summary.snoring_count,
434 translation_key=
"snoring_episode_count",
435 state_class=SensorStateClass.MEASUREMENT,
436 entity_registry_enabled_default=
False,
439 key=
"sleep_wakeup_count",
440 value_fn=
lambda sleep_summary: sleep_summary.wake_up_count,
441 translation_key=
"wakeup_count",
442 native_unit_of_measurement=UOM_FREQUENCY,
443 state_class=SensorStateClass.MEASUREMENT,
444 entity_registry_enabled_default=
False,
447 key=
"sleep_wakeup_duration_seconds",
448 value_fn=
lambda sleep_summary: sleep_summary.total_time_awake,
449 translation_key=
"wakeup_time",
450 native_unit_of_measurement=UnitOfTime.SECONDS,
451 device_class=SensorDeviceClass.DURATION,
452 state_class=SensorStateClass.MEASUREMENT,
453 entity_registry_enabled_default=
False,
458 @dataclass(frozen=True, kw_only=True)
460 """Immutable class for describing withings data."""
462 value_fn: Callable[[Activity], StateType]
467 key=
"activity_steps_today",
468 value_fn=
lambda activity: activity.steps,
469 translation_key=
"activity_steps_today",
470 native_unit_of_measurement=
"steps",
471 state_class=SensorStateClass.TOTAL,
474 key=
"activity_distance_today",
475 value_fn=
lambda activity: activity.distance,
476 translation_key=
"activity_distance_today",
477 suggested_display_precision=0,
478 native_unit_of_measurement=UnitOfLength.METERS,
479 device_class=SensorDeviceClass.DISTANCE,
480 state_class=SensorStateClass.TOTAL,
483 key=
"activity_floors_climbed_today",
484 value_fn=
lambda activity: activity.elevation,
485 translation_key=
"activity_elevation_today",
486 native_unit_of_measurement=UnitOfLength.METERS,
487 device_class=SensorDeviceClass.DISTANCE,
488 state_class=SensorStateClass.TOTAL,
491 key=
"activity_soft_duration_today",
492 value_fn=
lambda activity: activity.soft_activity,
493 translation_key=
"activity_soft_duration_today",
494 native_unit_of_measurement=UnitOfTime.SECONDS,
495 suggested_unit_of_measurement=UnitOfTime.MINUTES,
496 device_class=SensorDeviceClass.DURATION,
497 state_class=SensorStateClass.TOTAL,
498 entity_registry_enabled_default=
False,
501 key=
"activity_moderate_duration_today",
502 value_fn=
lambda activity: activity.moderate_activity,
503 translation_key=
"activity_moderate_duration_today",
504 native_unit_of_measurement=UnitOfTime.SECONDS,
505 suggested_unit_of_measurement=UnitOfTime.MINUTES,
506 device_class=SensorDeviceClass.DURATION,
507 state_class=SensorStateClass.TOTAL,
508 entity_registry_enabled_default=
False,
511 key=
"activity_intense_duration_today",
512 value_fn=
lambda activity: activity.intense_activity,
513 translation_key=
"activity_intense_duration_today",
514 native_unit_of_measurement=UnitOfTime.SECONDS,
515 suggested_unit_of_measurement=UnitOfTime.MINUTES,
516 device_class=SensorDeviceClass.DURATION,
517 state_class=SensorStateClass.TOTAL,
518 entity_registry_enabled_default=
False,
521 key=
"activity_active_duration_today",
522 value_fn=
lambda activity: activity.total_time_active,
523 translation_key=
"activity_active_duration_today",
524 native_unit_of_measurement=UnitOfTime.SECONDS,
525 suggested_unit_of_measurement=UnitOfTime.HOURS,
526 device_class=SensorDeviceClass.DURATION,
527 state_class=SensorStateClass.TOTAL,
530 key=
"activity_active_calories_burnt_today",
531 value_fn=
lambda activity: activity.active_calories_burnt,
532 suggested_display_precision=1,
533 translation_key=
"activity_active_calories_burnt_today",
534 native_unit_of_measurement=
"calories",
535 state_class=SensorStateClass.TOTAL,
538 key=
"activity_total_calories_burnt_today",
539 value_fn=
lambda activity: activity.total_calories_burnt,
540 suggested_display_precision=1,
541 translation_key=
"activity_total_calories_burnt_today",
542 native_unit_of_measurement=
"calories",
543 state_class=SensorStateClass.TOTAL,
550 WEIGHT_GOAL =
"weight"
553 @dataclass(frozen=True, kw_only=True)
555 """Immutable class for describing withings data."""
557 value_fn: Callable[[Goals], StateType]
560 GOALS_SENSORS: dict[str, WithingsGoalsSensorEntityDescription] = {
563 value_fn=
lambda goals: goals.steps,
564 translation_key=
"step_goal",
565 native_unit_of_measurement=
"steps",
566 state_class=SensorStateClass.MEASUREMENT,
570 value_fn=
lambda goals: goals.sleep,
571 translation_key=
"sleep_goal",
572 native_unit_of_measurement=UnitOfTime.SECONDS,
573 suggested_unit_of_measurement=UnitOfTime.HOURS,
574 device_class=SensorDeviceClass.DURATION,
575 state_class=SensorStateClass.MEASUREMENT,
579 value_fn=
lambda goals: goals.weight,
580 translation_key=
"weight_goal",
581 native_unit_of_measurement=UnitOfMass.KILOGRAMS,
582 device_class=SensorDeviceClass.WEIGHT,
583 state_class=SensorStateClass.MEASUREMENT,
588 @dataclass(frozen=True, kw_only=True)
590 """Immutable class for describing withings data."""
592 value_fn: Callable[[Workout], StateType]
595 _WORKOUT_CATEGORY = [
596 workout_category.name.lower()
for workout_category
in WorkoutCategory
603 value_fn=
lambda workout: workout.category.name.lower(),
604 device_class=SensorDeviceClass.ENUM,
605 translation_key=
"workout_type",
606 options=_WORKOUT_CATEGORY,
609 key=
"workout_active_calories_burnt",
610 value_fn=
lambda workout: workout.active_calories_burnt,
611 translation_key=
"workout_active_calories_burnt",
612 suggested_display_precision=1,
613 native_unit_of_measurement=
"calories",
616 key=
"workout_distance",
617 value_fn=
lambda workout: workout.distance,
618 translation_key=
"workout_distance",
619 device_class=SensorDeviceClass.DISTANCE,
620 native_unit_of_measurement=UnitOfLength.METERS,
621 suggested_display_precision=0,
624 key=
"workout_floors_climbed",
625 value_fn=
lambda workout: workout.elevation,
626 translation_key=
"workout_elevation",
627 native_unit_of_measurement=UnitOfLength.METERS,
628 device_class=SensorDeviceClass.DISTANCE,
631 key=
"workout_intensity",
632 value_fn=
lambda workout: workout.intensity,
633 translation_key=
"workout_intensity",
636 key=
"workout_pause_duration",
637 value_fn=
lambda workout: workout.pause_duration
or 0,
638 translation_key=
"workout_pause_duration",
639 device_class=SensorDeviceClass.DURATION,
640 native_unit_of_measurement=UnitOfTime.SECONDS,
641 suggested_unit_of_measurement=UnitOfTime.MINUTES,
644 key=
"workout_duration",
645 value_fn=
lambda workout: (
646 workout.end_date - workout.start_date
648 translation_key=
"workout_duration",
649 device_class=SensorDeviceClass.DURATION,
650 native_unit_of_measurement=UnitOfTime.SECONDS,
651 suggested_unit_of_measurement=UnitOfTime.MINUTES,
656 @dataclass(frozen=True, kw_only=True)
658 """Immutable class for describing withings data."""
660 value_fn: Callable[[Device], StateType]
666 translation_key=
"battery",
667 options=[
"low",
"medium",
"high"],
668 device_class=SensorDeviceClass.ENUM,
669 value_fn=
lambda device: device.battery,
675 """Return a list of present goals."""
677 for goal
in (STEP_GOAL, SLEEP_GOAL, WEIGHT_GOAL):
678 if getattr(goals, goal):
685 entry: WithingsConfigEntry,
686 async_add_entities: AddEntitiesCallback,
688 """Set up the sensor config entry."""
689 ent_reg = er.async_get(hass)
691 withings_data = entry.runtime_data
693 measurement_coordinator = withings_data.measurement_coordinator
695 entities: list[SensorEntity] = []
698 for measurement_type
in measurement_coordinator.data
702 current_measurement_types = set(measurement_coordinator.data)
704 def _async_measurement_listener() -> None:
705 """Listen for new measurements and add sensors if they did not exist."""
706 received_measurement_types = set(measurement_coordinator.data)
707 new_measurement_types = received_measurement_types - current_measurement_types
708 if new_measurement_types:
709 current_measurement_types.update(new_measurement_types)
712 for measurement_type
in new_measurement_types
717 measurement_coordinator.async_add_listener(_async_measurement_listener)
719 goals_coordinator = withings_data.goals_coordinator
725 for goal
in current_goals
728 def _async_goals_listener() -> None:
729 """Listen for new goals and add sensors if they did not exist."""
731 new_goals = received_goals - current_goals
733 current_goals.update(new_goals)
736 for goal
in new_goals
739 goals_coordinator.async_add_listener(_async_goals_listener)
741 activity_coordinator = withings_data.activity_coordinator
743 activity_entities_setup_before = ent_reg.async_get_entity_id(
744 Platform.SENSOR, DOMAIN, f
"withings_{entry.unique_id}_activity_steps_today"
747 if activity_coordinator.data
is not None or activity_entities_setup_before:
750 for attribute
in ACTIVITY_SENSORS
753 remove_activity_listener: Callable[[],
None]
755 def _async_add_activity_entities() -> None:
756 """Add activity entities."""
757 if activity_coordinator.data
is not None:
760 for attribute
in ACTIVITY_SENSORS
762 remove_activity_listener()
764 remove_activity_listener = activity_coordinator.async_add_listener(
765 _async_add_activity_entities
768 sleep_coordinator = withings_data.sleep_coordinator
770 sleep_entities_setup_before = ent_reg.async_get_entity_id(
773 f
"withings_{entry.unique_id}_sleep_deep_duration_seconds",
776 if sleep_coordinator.data
is not None or sleep_entities_setup_before:
779 for attribute
in SLEEP_SENSORS
782 remove_sleep_listener: Callable[[],
None]
784 def _async_add_sleep_entities() -> None:
785 """Add sleep entities."""
786 if sleep_coordinator.data
is not None:
789 for attribute
in SLEEP_SENSORS
791 remove_sleep_listener()
793 remove_sleep_listener = sleep_coordinator.async_add_listener(
794 _async_add_sleep_entities
797 workout_coordinator = withings_data.workout_coordinator
799 workout_entities_setup_before = ent_reg.async_get_entity_id(
800 Platform.SENSOR, DOMAIN, f
"withings_{entry.unique_id}_workout_type"
803 if workout_coordinator.data
is not None or workout_entities_setup_before:
806 for attribute
in WORKOUT_SENSORS
809 remove_workout_listener: Callable[[],
None]
811 def _async_add_workout_entities() -> None:
812 """Add workout entities."""
813 if workout_coordinator.data
is not None:
816 for attribute
in WORKOUT_SENSORS
818 remove_workout_listener()
820 remove_workout_listener = workout_coordinator.async_add_listener(
821 _async_add_workout_entities
824 device_coordinator = withings_data.device_coordinator
826 current_devices: set[str] = set()
828 def _async_device_listener() -> None:
829 """Add device entities."""
830 received_devices = set(device_coordinator.data)
831 new_devices = received_devices - current_devices
832 old_devices = current_devices - received_devices
834 device_registry = dr.async_get(hass)
835 for device_id
in new_devices:
836 if device := device_registry.async_get_device({(DOMAIN, device_id)}):
839 config_entry := hass.config_entries.async_get_entry(
843 and config_entry.state == ConfigEntryState.LOADED
844 for config_entry_id
in device.config_entries
849 for description
in DEVICE_SENSORS
851 current_devices.add(device_id)
854 device_registry = dr.async_get(hass)
855 for device_id
in old_devices:
856 if device := device_registry.async_get_device({(DOMAIN, device_id)}):
857 device_registry.async_update_device(
858 device.id, remove_config_entry_id=entry.entry_id
860 current_devices.remove(device_id)
862 device_coordinator.async_add_listener(_async_device_listener)
864 _async_device_listener()
868 "No data found for Withings entry %s, sensors will be added when new data is available",
876 _T: WithingsDataUpdateCoordinator[Any],
877 _ED: SensorEntityDescription,
878 ](WithingsEntity[_T], SensorEntity):
879 """Implementation of a Withings sensor."""
881 entity_description: _ED
886 entity_description: _ED,
888 """Initialize sensor."""
889 super().
__init__(coordinator, entity_description.key)
890 self.entity_description = entity_description
895 WithingsMeasurementDataUpdateCoordinator,
896 WithingsMeasurementSensorEntityDescription,
899 """Implementation of a Withings measurement sensor."""
903 """Return the state of the entity."""
904 return self.coordinator.data[
906 self.entity_description.measurement_type,
907 self.entity_description.measurement_position,
913 """Return if the sensor is available."""
917 self.entity_description.measurement_type,
918 self.entity_description.measurement_position,
920 in self.coordinator.data
926 WithingsSleepDataUpdateCoordinator,
927 WithingsSleepSensorEntityDescription,
930 """Implementation of a Withings sleep sensor."""
934 """Return the state of the entity."""
935 if not self.coordinator.data:
937 return self.entity_description.
value_fn(self.coordinator.data)
942 WithingsGoalsDataUpdateCoordinator,
943 WithingsGoalsSensorEntityDescription,
946 """Implementation of a Withings goals sensor."""
950 """Return the state of the entity."""
951 assert self.coordinator.data
952 return self.entity_description.
value_fn(self.coordinator.data)
957 WithingsActivityDataUpdateCoordinator,
958 WithingsActivitySensorEntityDescription,
961 """Implementation of a Withings activity sensor."""
965 """Return the state of the entity."""
966 if not self.coordinator.data:
968 return self.entity_description.
value_fn(self.coordinator.data)
972 """These values reset every day."""
973 return dt_util.start_of_local_day()
978 WithingsWorkoutDataUpdateCoordinator,
979 WithingsWorkoutSensorEntityDescription,
982 """Implementation of a Withings workout sensor."""
986 """Return the state of the entity."""
987 if not self.coordinator.data:
989 return self.entity_description.
value_fn(self.coordinator.data)
993 """Implementation of a Withings workout sensor."""
995 entity_description: WithingsDeviceSensorEntityDescription
999 coordinator: WithingsDeviceDataUpdateCoordinator,
1000 entity_description: WithingsDeviceSensorEntityDescription,
1003 """Initialize sensor."""
1004 super().
__init__(coordinator, device_id, entity_description.key)
1009 """Return the state of the entity."""
StateType native_value(self)
datetime last_reset(self)
StateType native_value(self)
None __init__(self, WithingsDeviceDataUpdateCoordinator coordinator, WithingsDeviceSensorEntityDescription entity_description, str device_id)
StateType native_value(self)
StateType native_value(self)
StateType native_value(self)
None async_setup_entry(HomeAssistant hass, WithingsConfigEntry entry, AddEntitiesCallback async_add_entities)
WithingsMeasurementSensorEntityDescription|None get_measurement_description(tuple[MeasurementType, MeasurementPosition|None] measurement)
set[str] get_current_goals(Goals goals)
None __init__(self, _T coordinator, _ED entity_description)
WithingsMeasurementSensorEntityDescription|None get_positional_measurement_description(MeasurementType measurement_type, MeasurementPosition measurement_position)