2023-09-23 12:04:44 -07:00
|
|
|
"""Tests for the fitbit sensor platform."""
|
|
|
|
|
|
|
|
|
|
|
|
from collections.abc import Awaitable, Callable
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
import pytest
|
2023-09-23 15:38:53 -07:00
|
|
|
from syrupy.assertion import SnapshotAssertion
|
2023-09-23 12:04:44 -07:00
|
|
|
|
|
|
|
from homeassistant.core import HomeAssistant
|
2023-09-23 15:38:53 -07:00
|
|
|
from homeassistant.helpers import entity_registry as er
|
2023-09-23 12:04:44 -07:00
|
|
|
|
2023-09-23 15:38:53 -07:00
|
|
|
from .conftest import PROFILE_USER_ID, timeseries_response
|
2023-09-23 12:04:44 -07:00
|
|
|
|
|
|
|
DEVICE_RESPONSE_CHARGE_2 = {
|
|
|
|
"battery": "Medium",
|
|
|
|
"batteryLevel": 60,
|
|
|
|
"deviceVersion": "Charge 2",
|
|
|
|
"id": "816713257",
|
|
|
|
"lastSyncTime": "2019-11-07T12:00:58.000",
|
|
|
|
"mac": "16ADD56D54GD",
|
|
|
|
"type": "TRACKER",
|
|
|
|
}
|
|
|
|
DEVICE_RESPONSE_ARIA_AIR = {
|
|
|
|
"battery": "High",
|
|
|
|
"batteryLevel": 95,
|
|
|
|
"deviceVersion": "Aria Air",
|
|
|
|
"id": "016713257",
|
|
|
|
"lastSyncTime": "2019-11-07T12:00:58.000",
|
|
|
|
"mac": "06ADD56D54GD",
|
|
|
|
"type": "SCALE",
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
2023-09-23 15:38:53 -07:00
|
|
|
(
|
|
|
|
"monitored_resources",
|
|
|
|
"entity_id",
|
|
|
|
"api_resource",
|
|
|
|
"api_value",
|
|
|
|
),
|
|
|
|
[
|
|
|
|
(
|
|
|
|
["activities/activityCalories"],
|
|
|
|
"sensor.activity_calories",
|
|
|
|
"activities/activityCalories",
|
|
|
|
"135",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["activities/calories"],
|
|
|
|
"sensor.calories",
|
|
|
|
"activities/calories",
|
|
|
|
"139",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["activities/distance"],
|
|
|
|
"sensor.distance",
|
|
|
|
"activities/distance",
|
|
|
|
"12.7",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["activities/elevation"],
|
|
|
|
"sensor.elevation",
|
|
|
|
"activities/elevation",
|
|
|
|
"7600.24",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["activities/floors"],
|
|
|
|
"sensor.floors",
|
|
|
|
"activities/floors",
|
|
|
|
"8",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["activities/heart"],
|
|
|
|
"sensor.resting_heart_rate",
|
|
|
|
"activities/heart",
|
|
|
|
{"restingHeartRate": 76},
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["activities/minutesFairlyActive"],
|
|
|
|
"sensor.minutes_fairly_active",
|
|
|
|
"activities/minutesFairlyActive",
|
|
|
|
35,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["activities/minutesLightlyActive"],
|
|
|
|
"sensor.minutes_lightly_active",
|
|
|
|
"activities/minutesLightlyActive",
|
|
|
|
95,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["activities/minutesSedentary"],
|
|
|
|
"sensor.minutes_sedentary",
|
|
|
|
"activities/minutesSedentary",
|
|
|
|
18,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["activities/minutesVeryActive"],
|
|
|
|
"sensor.minutes_very_active",
|
|
|
|
"activities/minutesVeryActive",
|
|
|
|
20,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["activities/steps"],
|
|
|
|
"sensor.steps",
|
|
|
|
"activities/steps",
|
|
|
|
"5600",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["body/weight"],
|
|
|
|
"sensor.weight",
|
|
|
|
"body/weight",
|
|
|
|
"175",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["body/fat"],
|
|
|
|
"sensor.body_fat",
|
|
|
|
"body/fat",
|
|
|
|
"18",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["body/bmi"],
|
|
|
|
"sensor.bmi",
|
|
|
|
"body/bmi",
|
|
|
|
"23.7",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["sleep/awakeningsCount"],
|
|
|
|
"sensor.awakenings_count",
|
|
|
|
"sleep/awakeningsCount",
|
|
|
|
"7",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["sleep/efficiency"],
|
|
|
|
"sensor.sleep_efficiency",
|
|
|
|
"sleep/efficiency",
|
|
|
|
"80",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["sleep/minutesAfterWakeup"],
|
|
|
|
"sensor.minutes_after_wakeup",
|
|
|
|
"sleep/minutesAfterWakeup",
|
|
|
|
"17",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["sleep/minutesAsleep"],
|
|
|
|
"sensor.sleep_minutes_asleep",
|
|
|
|
"sleep/minutesAsleep",
|
|
|
|
"360",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["sleep/minutesAwake"],
|
|
|
|
"sensor.sleep_minutes_awake",
|
|
|
|
"sleep/minutesAwake",
|
|
|
|
"35",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["sleep/minutesToFallAsleep"],
|
|
|
|
"sensor.sleep_minutes_to_fall_asleep",
|
|
|
|
"sleep/minutesToFallAsleep",
|
|
|
|
"35",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["sleep/startTime"],
|
|
|
|
"sensor.sleep_start_time",
|
|
|
|
"sleep/startTime",
|
|
|
|
"2020-01-27T00:17:30.000",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
["sleep/timeInBed"],
|
|
|
|
"sensor.sleep_time_in_bed",
|
|
|
|
"sleep/timeInBed",
|
|
|
|
"462",
|
|
|
|
),
|
|
|
|
],
|
2023-09-23 12:04:44 -07:00
|
|
|
)
|
2023-09-23 15:38:53 -07:00
|
|
|
async def test_sensors(
|
2023-09-23 12:04:44 -07:00
|
|
|
hass: HomeAssistant,
|
|
|
|
sensor_platform_setup: Callable[[], Awaitable[bool]],
|
|
|
|
register_timeseries: Callable[[str, dict[str, Any]], None],
|
2023-09-23 15:38:53 -07:00
|
|
|
entity_registry: er.EntityRegistry,
|
|
|
|
entity_id: str,
|
|
|
|
api_resource: str,
|
|
|
|
api_value: str,
|
|
|
|
snapshot: SnapshotAssertion,
|
2023-09-23 12:04:44 -07:00
|
|
|
) -> None:
|
2023-09-23 15:38:53 -07:00
|
|
|
"""Test sensors."""
|
2023-09-23 12:04:44 -07:00
|
|
|
|
|
|
|
register_timeseries(
|
2023-09-23 15:38:53 -07:00
|
|
|
api_resource, timeseries_response(api_resource.replace("/", "-"), api_value)
|
2023-09-23 12:04:44 -07:00
|
|
|
)
|
|
|
|
await sensor_platform_setup()
|
|
|
|
|
2023-09-23 15:38:53 -07:00
|
|
|
state = hass.states.get(entity_id)
|
2023-09-23 12:04:44 -07:00
|
|
|
assert state
|
2023-09-23 15:38:53 -07:00
|
|
|
entry = entity_registry.async_get(entity_id)
|
|
|
|
assert entry
|
|
|
|
assert (state.state, state.attributes, entry.unique_id) == snapshot
|
2023-09-23 12:04:44 -07:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
("devices_response", "monitored_resources"),
|
|
|
|
[([DEVICE_RESPONSE_CHARGE_2, DEVICE_RESPONSE_ARIA_AIR], ["devices/battery"])],
|
|
|
|
)
|
|
|
|
async def test_device_battery_level(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
sensor_platform_setup: Callable[[], Awaitable[bool]],
|
2023-09-23 15:38:53 -07:00
|
|
|
entity_registry: er.EntityRegistry,
|
2023-09-23 12:04:44 -07:00
|
|
|
) -> None:
|
|
|
|
"""Test battery level sensor for devices."""
|
|
|
|
|
|
|
|
await sensor_platform_setup()
|
|
|
|
|
|
|
|
state = hass.states.get("sensor.charge_2_battery")
|
|
|
|
assert state
|
|
|
|
assert state.state == "Medium"
|
|
|
|
assert state.attributes == {
|
|
|
|
"attribution": "Data provided by Fitbit.com",
|
|
|
|
"friendly_name": "Charge 2 Battery",
|
|
|
|
"icon": "mdi:battery-50",
|
|
|
|
"model": "Charge 2",
|
|
|
|
"type": "tracker",
|
|
|
|
}
|
|
|
|
|
2023-09-23 15:38:53 -07:00
|
|
|
entry = entity_registry.async_get("sensor.charge_2_battery")
|
|
|
|
assert entry
|
|
|
|
assert entry.unique_id == f"{PROFILE_USER_ID}_devices/battery_816713257"
|
|
|
|
|
2023-09-23 12:04:44 -07:00
|
|
|
state = hass.states.get("sensor.aria_air_battery")
|
|
|
|
assert state
|
|
|
|
assert state.state == "High"
|
|
|
|
assert state.attributes == {
|
|
|
|
"attribution": "Data provided by Fitbit.com",
|
|
|
|
"friendly_name": "Aria Air Battery",
|
|
|
|
"icon": "mdi:battery",
|
|
|
|
"model": "Aria Air",
|
|
|
|
"type": "scale",
|
|
|
|
}
|
2023-09-23 15:38:53 -07:00
|
|
|
|
|
|
|
entity_registry = er.async_get(hass)
|
|
|
|
entry = entity_registry.async_get("sensor.aria_air_battery")
|
|
|
|
assert entry
|
|
|
|
assert entry.unique_id == f"{PROFILE_USER_ID}_devices/battery_016713257"
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
2023-09-25 17:08:59 -07:00
|
|
|
(
|
|
|
|
"monitored_resources",
|
|
|
|
"profile_locale",
|
|
|
|
"configured_unit_system",
|
|
|
|
"expected_unit",
|
|
|
|
),
|
2023-09-23 15:38:53 -07:00
|
|
|
[
|
2023-09-25 17:08:59 -07:00
|
|
|
# Defaults to home assistant unit system unless UK
|
|
|
|
(["body/weight"], "en_US", "default", "kg"),
|
|
|
|
(["body/weight"], "en_GB", "default", "st"),
|
|
|
|
(["body/weight"], "es_ES", "default", "kg"),
|
|
|
|
# Use the configured unit system from yaml
|
|
|
|
(["body/weight"], "en_US", "en_US", "lb"),
|
|
|
|
(["body/weight"], "en_GB", "en_US", "lb"),
|
|
|
|
(["body/weight"], "es_ES", "en_US", "lb"),
|
|
|
|
(["body/weight"], "en_US", "en_GB", "st"),
|
|
|
|
(["body/weight"], "en_GB", "en_GB", "st"),
|
|
|
|
(["body/weight"], "es_ES", "en_GB", "st"),
|
|
|
|
(["body/weight"], "en_US", "metric", "kg"),
|
|
|
|
(["body/weight"], "en_GB", "metric", "kg"),
|
|
|
|
(["body/weight"], "es_ES", "metric", "kg"),
|
2023-09-23 15:38:53 -07:00
|
|
|
],
|
|
|
|
)
|
|
|
|
async def test_profile_local(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
sensor_platform_setup: Callable[[], Awaitable[bool]],
|
|
|
|
register_timeseries: Callable[[str, dict[str, Any]], None],
|
|
|
|
expected_unit: str,
|
|
|
|
) -> None:
|
|
|
|
"""Test the fitbit profile locale impact on unit of measure."""
|
|
|
|
|
|
|
|
register_timeseries("body/weight", timeseries_response("body-weight", "175"))
|
|
|
|
await sensor_platform_setup()
|
|
|
|
|
|
|
|
state = hass.states.get("sensor.weight")
|
|
|
|
assert state
|
|
|
|
assert state.attributes.get("unit_of_measurement") == expected_unit
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
("sensor_platform_config", "api_response", "expected_state"),
|
|
|
|
[
|
|
|
|
(
|
|
|
|
{"clock_format": "12H", "monitored_resources": ["sleep/startTime"]},
|
|
|
|
"17:05",
|
|
|
|
"5:05 PM",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
{"clock_format": "12H", "monitored_resources": ["sleep/startTime"]},
|
|
|
|
"5:05",
|
|
|
|
"5:05 AM",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
{"clock_format": "12H", "monitored_resources": ["sleep/startTime"]},
|
|
|
|
"00:05",
|
|
|
|
"12:05 AM",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
{"clock_format": "24H", "monitored_resources": ["sleep/startTime"]},
|
|
|
|
"17:05",
|
|
|
|
"17:05",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
{"clock_format": "12H", "monitored_resources": ["sleep/startTime"]},
|
|
|
|
"",
|
|
|
|
"-",
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
async def test_sleep_time_clock_format(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
sensor_platform_setup: Callable[[], Awaitable[bool]],
|
|
|
|
register_timeseries: Callable[[str, dict[str, Any]], None],
|
|
|
|
api_response: str,
|
|
|
|
expected_state: str,
|
|
|
|
) -> None:
|
|
|
|
"""Test the clock format configuration."""
|
|
|
|
|
|
|
|
register_timeseries(
|
|
|
|
"sleep/startTime", timeseries_response("sleep-startTime", api_response)
|
|
|
|
)
|
|
|
|
await sensor_platform_setup()
|
|
|
|
|
|
|
|
state = hass.states.get("sensor.sleep_start_time")
|
|
|
|
assert state
|
|
|
|
assert state.state == expected_state
|