Bump aionotion to 2023.04.2 to address imminent API change (#91786)

* Bump `aionotion` to 2023.04.0

* Bump `aionotion` to 2023.04.2 to address imminent API change

* Clean migration

* Reduce blast area

* Fix tests

* Better naming
This commit is contained in:
Aaron Bach 2023-04-21 17:52:57 -06:00 committed by GitHub
parent 4de124cdd5
commit c6d846453d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 410 additions and 314 deletions

View file

@ -2,13 +2,17 @@
from __future__ import annotations
import asyncio
from dataclasses import dataclass, field, fields
from datetime import timedelta
import logging
import traceback
from typing import Any
from uuid import UUID
from aionotion import async_get_client
from aionotion.bridge.models import Bridge
from aionotion.errors import InvalidCredentialsError, NotionError
from aionotion.sensor.models import Listener, ListenerKind, Sensor
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
@ -18,6 +22,7 @@ from homeassistant.helpers import (
aiohttp_client,
config_validation as cv,
device_registry as dr,
entity_registry as er,
)
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
from homeassistant.helpers.update_coordinator import (
@ -26,7 +31,20 @@ from homeassistant.helpers.update_coordinator import (
UpdateFailed,
)
from .const import DOMAIN, LOGGER
from .const import (
DOMAIN,
LOGGER,
SENSOR_BATTERY,
SENSOR_DOOR,
SENSOR_GARAGE_DOOR,
SENSOR_LEAK,
SENSOR_MISSING,
SENSOR_SAFE,
SENSOR_SLIDING,
SENSOR_SMOKE_CO,
SENSOR_TEMPERATURE,
SENSOR_WINDOW_HINGED,
)
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
@ -37,6 +55,51 @@ DEFAULT_SCAN_INTERVAL = timedelta(minutes=1)
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
# Define a map of old-API task types to new-API listener types:
TASK_TYPE_TO_LISTENER_MAP: dict[str, ListenerKind] = {
SENSOR_BATTERY: ListenerKind.BATTERY,
SENSOR_DOOR: ListenerKind.DOOR,
SENSOR_GARAGE_DOOR: ListenerKind.GARAGE_DOOR,
SENSOR_LEAK: ListenerKind.LEAK_STATUS,
SENSOR_MISSING: ListenerKind.CONNECTED,
SENSOR_SAFE: ListenerKind.SAFE,
SENSOR_SLIDING: ListenerKind.SLIDING_DOOR_OR_WINDOW,
SENSOR_SMOKE_CO: ListenerKind.SMOKE,
SENSOR_TEMPERATURE: ListenerKind.TEMPERATURE,
SENSOR_WINDOW_HINGED: ListenerKind.HINGED_WINDOW,
}
@callback
def is_uuid(value: str) -> bool:
"""Return whether a string is a valid UUID."""
try:
UUID(value)
except ValueError:
return False
return True
@dataclass
class NotionData:
"""Define a manager class for Notion data."""
# Define a dict of bridges, indexed by bridge ID (an integer):
bridges: dict[int, Bridge] = field(default_factory=dict)
# Define a dict of listeners, indexed by listener UUID (a string):
listeners: dict[str, Listener] = field(default_factory=dict)
# Define a dict of sensors, indexed by sensor UUID (a string):
sensors: dict[str, Sensor] = field(default_factory=dict)
def asdict(self) -> dict[str, Any]:
"""Represent this dataclass (and its Pydantic contents) as a dict."""
return {
field.name: [obj.dict() for obj in getattr(self, field.name).values()]
for field in fields(self)
}
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Notion as a config entry."""
@ -56,13 +119,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except NotionError as err:
raise ConfigEntryNotReady("Config entry failed to load") from err
async def async_update() -> dict[str, dict[str, Any]]:
async def async_update() -> NotionData:
"""Get the latest data from the Notion API."""
data: dict[str, dict[str, Any]] = {"bridges": {}, "sensors": {}, "tasks": {}}
data = NotionData()
tasks = {
"bridges": client.bridge.async_all(),
"listeners": client.sensor.async_listeners(),
"sensors": client.sensor.async_all(),
"tasks": client.task.async_all(),
}
results = await asyncio.gather(*tasks.values(), return_exceptions=True)
@ -83,10 +146,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
) from result
for item in result:
if attr == "bridges" and item["id"] not in data["bridges"]:
if attr == "bridges":
# If a new bridge is discovered, register it:
_async_register_new_bridge(hass, item, entry)
data[attr][item["id"]] = item
if item.id not in data.bridges:
_async_register_new_bridge(hass, item, entry)
data.bridges[item.id] = item
elif attr == "listeners":
data.listeners[item.id] = item
else:
data.sensors[item.uuid] = item
return data
@ -102,6 +170,36 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = coordinator
@callback
def async_migrate_entity_entry(entry: er.RegistryEntry) -> dict[str, Any] | None:
"""Migrate Notion entity entries.
This migration focuses on unique IDs, which have changed because of a Notion API
change:
Old Format: <sensor_id>_<task_type>
New Format: <listener_uuid>
"""
if is_uuid(entry.unique_id):
# If the unique ID is already a UUID, we don't need to migrate it:
return None
sensor_id_str, task_type = entry.unique_id.split("_", 1)
sensor = next(
sensor
for sensor in coordinator.data.sensors.values()
if sensor.id == int(sensor_id_str)
)
listener = next(
listener
for listener in coordinator.data.listeners.values()
if listener.sensor_id == sensor.uuid
and listener.listener_kind == TASK_TYPE_TO_LISTENER_MAP[task_type]
)
return {"new_unique_id": listener.id}
await er.async_migrate_entries(hass, entry.entry_id, async_migrate_entity_entry)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
@ -118,22 +216,22 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@callback
def _async_register_new_bridge(
hass: HomeAssistant, bridge: dict, entry: ConfigEntry
hass: HomeAssistant, bridge: Bridge, entry: ConfigEntry
) -> None:
"""Register a new bridge."""
if name := bridge["name"]:
if name := bridge.name:
bridge_name = name.capitalize()
else:
bridge_name = bridge["id"]
bridge_name = str(bridge.id)
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, bridge["hardware_id"])},
identifiers={(DOMAIN, bridge.hardware_id)},
manufacturer="Silicon Labs",
model=bridge["hardware_revision"],
model=str(bridge.hardware_revision),
name=bridge_name,
sw_version=bridge["firmware_version"]["wifi"],
sw_version=bridge.firmware_version.wifi,
)
@ -145,7 +243,7 @@ class NotionEntity(CoordinatorEntity):
def __init__(
self,
coordinator: DataUpdateCoordinator,
task_id: str,
listener_id: str,
sensor_id: str,
bridge_id: str,
system_id: str,
@ -154,25 +252,23 @@ class NotionEntity(CoordinatorEntity):
"""Initialize the entity."""
super().__init__(coordinator)
bridge = self.coordinator.data["bridges"].get(bridge_id, {})
sensor = self.coordinator.data["sensors"][sensor_id]
bridge = self.coordinator.data.bridges.get(bridge_id, {})
sensor = self.coordinator.data.sensors[sensor_id]
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, sensor["hardware_id"])},
identifiers={(DOMAIN, sensor.hardware_id)},
manufacturer="Silicon Labs",
model=sensor["hardware_revision"],
name=str(sensor["name"]).capitalize(),
sw_version=sensor["firmware_version"],
via_device=(DOMAIN, bridge.get("hardware_id")),
model=sensor.hardware_revision,
name=str(sensor.name).capitalize(),
sw_version=sensor.firmware_version,
via_device=(DOMAIN, bridge.hardware_id),
)
self._attr_extra_state_attributes = {}
self._attr_unique_id = (
f'{sensor_id}_{coordinator.data["tasks"][task_id]["task_type"]}'
)
self._attr_unique_id = listener_id
self._bridge_id = bridge_id
self._listener_id = listener_id
self._sensor_id = sensor_id
self._system_id = system_id
self._task_id = task_id
self.entity_description = description
@property
@ -180,7 +276,7 @@ class NotionEntity(CoordinatorEntity):
"""Return True if entity is available."""
return (
self.coordinator.last_update_success
and self._task_id in self.coordinator.data["tasks"]
and self._listener_id in self.coordinator.data.listeners
)
@callback
@ -189,27 +285,23 @@ class NotionEntity(CoordinatorEntity):
Sensors can move to other bridges based on signal strength, etc.
"""
sensor = self.coordinator.data["sensors"][self._sensor_id]
sensor = self.coordinator.data.sensors[self._sensor_id]
# If the sensor's bridge ID is the same as what we had before or if it points
# to a bridge that doesn't exist (which can happen due to a Notion API bug),
# return immediately:
if (
self._bridge_id == sensor["bridge"]["id"]
or sensor["bridge"]["id"] not in self.coordinator.data["bridges"]
self._bridge_id == sensor.bridge.id
or sensor.bridge.id not in self.coordinator.data.bridges
):
return
self._bridge_id = sensor["bridge"]["id"]
self._bridge_id = sensor.bridge.id
device_registry = dr.async_get(self.hass)
this_device = device_registry.async_get_device(
{(DOMAIN, sensor["hardware_id"])}
)
bridge = self.coordinator.data["bridges"][self._bridge_id]
bridge_device = device_registry.async_get_device(
{(DOMAIN, bridge["hardware_id"])}
)
this_device = device_registry.async_get_device({(DOMAIN, sensor.hardware_id)})
bridge = self.coordinator.data.bridges[self._bridge_id]
bridge_device = device_registry.async_get_device({(DOMAIN, bridge.hardware_id)})
if not bridge_device or not this_device:
return
@ -226,7 +318,7 @@ class NotionEntity(CoordinatorEntity):
@callback
def _handle_coordinator_update(self) -> None:
"""Respond to a DataUpdateCoordinator update."""
if self._task_id in self.coordinator.data["tasks"]:
if self._listener_id in self.coordinator.data.listeners:
self._async_update_bridge_id()
self._async_update_from_latest_data()

View file

@ -4,6 +4,8 @@ from __future__ import annotations
from dataclasses import dataclass
from typing import Literal
from aionotion.sensor.models import ListenerKind
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
@ -26,9 +28,9 @@ from .const import (
SENSOR_SAFE,
SENSOR_SLIDING,
SENSOR_SMOKE_CO,
SENSOR_WINDOW_HINGED_HORIZONTAL,
SENSOR_WINDOW_HINGED_VERTICAL,
SENSOR_WINDOW_HINGED,
)
from .model import NotionEntityDescriptionMixin
@dataclass
@ -40,7 +42,9 @@ class NotionBinarySensorDescriptionMixin:
@dataclass
class NotionBinarySensorDescription(
BinarySensorEntityDescription, NotionBinarySensorDescriptionMixin
BinarySensorEntityDescription,
NotionBinarySensorDescriptionMixin,
NotionEntityDescriptionMixin,
):
"""Describe a Notion binary sensor."""
@ -51,24 +55,28 @@ BINARY_SENSOR_DESCRIPTIONS = (
name="Low battery",
device_class=BinarySensorDeviceClass.BATTERY,
entity_category=EntityCategory.DIAGNOSTIC,
listener_kind=ListenerKind.BATTERY,
on_state="critical",
),
NotionBinarySensorDescription(
key=SENSOR_DOOR,
name="Door",
device_class=BinarySensorDeviceClass.DOOR,
listener_kind=ListenerKind.DOOR,
on_state="open",
),
NotionBinarySensorDescription(
key=SENSOR_GARAGE_DOOR,
name="Garage door",
device_class=BinarySensorDeviceClass.GARAGE_DOOR,
listener_kind=ListenerKind.GARAGE_DOOR,
on_state="open",
),
NotionBinarySensorDescription(
key=SENSOR_LEAK,
name="Leak detector",
device_class=BinarySensorDeviceClass.MOISTURE,
listener_kind=ListenerKind.LEAK_STATUS,
on_state="leak",
),
NotionBinarySensorDescription(
@ -76,36 +84,34 @@ BINARY_SENSOR_DESCRIPTIONS = (
name="Missing",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
entity_category=EntityCategory.DIAGNOSTIC,
listener_kind=ListenerKind.CONNECTED,
on_state="not_missing",
),
NotionBinarySensorDescription(
key=SENSOR_SAFE,
name="Safe",
device_class=BinarySensorDeviceClass.DOOR,
listener_kind=ListenerKind.SAFE,
on_state="open",
),
NotionBinarySensorDescription(
key=SENSOR_SLIDING,
name="Sliding door/window",
device_class=BinarySensorDeviceClass.DOOR,
listener_kind=ListenerKind.SLIDING_DOOR_OR_WINDOW,
on_state="open",
),
NotionBinarySensorDescription(
key=SENSOR_SMOKE_CO,
name="Smoke/Carbon monoxide detector",
device_class=BinarySensorDeviceClass.SMOKE,
listener_kind=ListenerKind.SMOKE,
on_state="alarm",
),
NotionBinarySensorDescription(
key=SENSOR_WINDOW_HINGED_HORIZONTAL,
key=SENSOR_WINDOW_HINGED,
name="Hinged window",
device_class=BinarySensorDeviceClass.WINDOW,
on_state="open",
),
NotionBinarySensorDescription(
key=SENSOR_WINDOW_HINGED_VERTICAL,
name="Hinged window",
device_class=BinarySensorDeviceClass.WINDOW,
listener_kind=ListenerKind.HINGED_WINDOW,
on_state="open",
),
)
@ -121,16 +127,16 @@ async def async_setup_entry(
[
NotionBinarySensor(
coordinator,
task_id,
sensor["id"],
sensor["bridge"]["id"],
sensor["system_id"],
listener_id,
sensor.uuid,
sensor.bridge.id,
sensor.system_id,
description,
)
for task_id, task in coordinator.data["tasks"].items()
for listener_id, listener in coordinator.data.listeners.items()
for description in BINARY_SENSOR_DESCRIPTIONS
if description.key == task["task_type"]
and (sensor := coordinator.data["sensors"][task["sensor_id"]])
if description.listener_kind == listener.listener_kind
and (sensor := coordinator.data.sensors[listener.sensor_id])
]
)
@ -143,14 +149,14 @@ class NotionBinarySensor(NotionEntity, BinarySensorEntity):
@callback
def _async_update_from_latest_data(self) -> None:
"""Fetch new state data for the sensor."""
task = self.coordinator.data["tasks"][self._task_id]
listener = self.coordinator.data.listeners[self._listener_id]
if "value" in task["status"]:
state = task["status"]["value"]
elif task["status"].get("insights", {}).get("primary"):
state = task["status"]["insights"]["primary"]["to_state"]
if listener.status.trigger_value:
state = listener.status.trigger_value
elif listener.insights.primary.value:
state = listener.insights.primary.value
else:
LOGGER.warning("Unknown data payload: %s", task["status"])
LOGGER.warning("Unknown listener structure: %s", listener)
state = None
self._attr_is_on = self.entity_description.on_state == state

View file

@ -13,5 +13,4 @@ SENSOR_SAFE = "safe"
SENSOR_SLIDING = "sliding"
SENSOR_SMOKE_CO = "alarm"
SENSOR_TEMPERATURE = "temperature"
SENSOR_WINDOW_HINGED_HORIZONTAL = "window_hinged_horizontal"
SENSOR_WINDOW_HINGED_VERTICAL = "window_hinged_vertical"
SENSOR_WINDOW_HINGED = "window_hinged"

View file

@ -35,7 +35,10 @@ async def async_get_config_entry_diagnostics(
"""Return diagnostics for a config entry."""
coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
return {
"entry": async_redact_data(entry.as_dict(), TO_REDACT),
"data": async_redact_data(coordinator.data, TO_REDACT),
}
return async_redact_data(
{
"entry": entry.as_dict(),
"data": coordinator.data.asdict(),
},
TO_REDACT,
)

View file

@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["aionotion"],
"requirements": ["aionotion==3.0.2"]
"requirements": ["aionotion==2023.04.2"]
}

View file

@ -0,0 +1,11 @@
"""Define Notion model mixins."""
from dataclasses import dataclass
from aionotion.sensor.models import ListenerKind
@dataclass
class NotionEntityDescriptionMixin:
"""Define an description mixin Notion entities."""
listener_kind: ListenerKind

View file

@ -1,4 +1,8 @@
"""Support for Notion sensors."""
from dataclasses import dataclass
from aionotion.sensor.models import ListenerKind
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
@ -12,14 +16,22 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import NotionEntity
from .const import DOMAIN, LOGGER, SENSOR_TEMPERATURE
from .model import NotionEntityDescriptionMixin
@dataclass
class NotionSensorDescription(SensorEntityDescription, NotionEntityDescriptionMixin):
"""Describe a Notion sensor."""
SENSOR_DESCRIPTIONS = (
SensorEntityDescription(
NotionSensorDescription(
key=SENSOR_TEMPERATURE,
name="Temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
listener_kind=ListenerKind.TEMPERATURE,
),
)
@ -34,16 +46,16 @@ async def async_setup_entry(
[
NotionSensor(
coordinator,
task_id,
sensor["id"],
sensor["bridge"]["id"],
sensor["system_id"],
listener_id,
sensor.uuid,
sensor.bridge.id,
sensor.system_id,
description,
)
for task_id, task in coordinator.data["tasks"].items()
for listener_id, listener in coordinator.data.listeners.items()
for description in SENSOR_DESCRIPTIONS
if description.key == task["task_type"]
and (sensor := coordinator.data["sensors"][task["sensor_id"]])
if description.listener_kind == listener.listener_kind
and (sensor := coordinator.data.sensors[listener.sensor_id])
]
)
@ -54,13 +66,12 @@ class NotionSensor(NotionEntity, SensorEntity):
@callback
def _async_update_from_latest_data(self) -> None:
"""Fetch new state data for the sensor."""
task = self.coordinator.data["tasks"][self._task_id]
listener = self.coordinator.data.listeners[self._listener_id]
if task["task_type"] == SENSOR_TEMPERATURE:
self._attr_native_value = round(float(task["status"]["value"]), 1)
if listener.listener_kind == ListenerKind.TEMPERATURE:
self._attr_native_value = round(listener.status.temperature, 1)
else:
LOGGER.error(
"Unknown task type: %s: %s",
self.coordinator.data["sensors"][self._sensor_id],
task["task_type"],
"Unknown listener type for sensor %s",
self.coordinator.data.sensors[self._sensor_id],
)

View file

@ -223,7 +223,7 @@ aionanoleaf==0.2.1
aionotify==0.2.0
# homeassistant.components.notion
aionotion==3.0.2
aionotion==2023.04.2
# homeassistant.components.oncue
aiooncue==0.3.4

View file

@ -204,7 +204,7 @@ aiomusiccast==0.14.8
aionanoleaf==0.2.1
# homeassistant.components.notion
aionotion==3.0.2
aionotion==2023.04.2
# homeassistant.components.oncue
aiooncue==0.3.4

View file

@ -3,10 +3,13 @@ from collections.abc import Generator
import json
from unittest.mock import AsyncMock, Mock, patch
from aionotion.bridge.models import Bridge
from aionotion.sensor.models import Listener, Sensor
import pytest
from homeassistant.components.notion import DOMAIN
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry, load_fixture
@ -24,17 +27,29 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]:
@pytest.fixture(name="client")
def client_fixture(data_bridge, data_sensor, data_task):
def client_fixture(data_bridge, data_listener, data_sensor):
"""Define a fixture for an aionotion client."""
return Mock(
bridge=Mock(async_all=AsyncMock(return_value=data_bridge)),
sensor=Mock(async_all=AsyncMock(return_value=data_sensor)),
task=Mock(async_all=AsyncMock(return_value=data_task)),
bridge=Mock(
async_all=AsyncMock(
return_value=[Bridge.parse_obj(bridge) for bridge in data_bridge]
)
),
sensor=Mock(
async_all=AsyncMock(
return_value=[Sensor.parse_obj(sensor) for sensor in data_sensor]
),
async_listeners=AsyncMock(
return_value=[
Listener.parse_obj(listener) for listener in data_listener
]
),
),
)
@pytest.fixture(name="config_entry")
def config_entry_fixture(hass, config):
def config_entry_fixture(hass: HomeAssistant, config):
"""Define a config entry fixture."""
entry = MockConfigEntry(domain=DOMAIN, unique_id=TEST_USERNAME, data=config)
entry.add_to_hass(hass)
@ -56,18 +71,18 @@ def data_bridge_fixture():
return json.loads(load_fixture("bridge_data.json", "notion"))
@pytest.fixture(name="data_listener", scope="package")
def data_listener_fixture():
"""Define listener data."""
return json.loads(load_fixture("listener_data.json", "notion"))
@pytest.fixture(name="data_sensor", scope="package")
def data_sensor_fixture():
"""Define sensor data."""
return json.loads(load_fixture("sensor_data.json", "notion"))
@pytest.fixture(name="data_task", scope="package")
def data_task_fixture():
"""Define task data."""
return json.loads(load_fixture("task_data.json", "notion"))
@pytest.fixture(name="get_client")
def get_client_fixture(client):
"""Define a fixture to mock the async_get_client method."""
@ -88,7 +103,7 @@ async def mock_aionotion_fixture(client):
@pytest.fixture(name="setup_config_entry")
async def setup_config_entry_fixture(hass, config_entry, mock_aionotion):
async def setup_config_entry_fixture(hass: HomeAssistant, config_entry, mock_aionotion):
"""Define a fixture to set up notion."""
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

View file

@ -1,26 +1,50 @@
[
{
"id": 12345,
"name": null,
"name": "Bridge 1",
"mode": "home",
"hardware_id": "0x1234567890abcdef",
"hardware_id": "0x0000000000000000",
"hardware_revision": 4,
"firmware_version": {
"silabs": "1.1.2",
"wifi": "0.121.0",
"wifi_app": "3.3.0"
},
"missing_at": null,
"created_at": "2019-06-27T00:18:44.337Z",
"updated_at": "2023-03-19T03:20:16.061Z",
"system_id": 11111,
"firmware": {
"silabs": "1.1.2",
"wifi": "0.121.0",
"wifi_app": "3.3.0"
},
"links": {
"system": 11111
}
},
{
"id": 67890,
"name": "Bridge 2",
"mode": "home",
"hardware_id": "0x0000000000000000",
"hardware_revision": 4,
"firmware_version": {
"wifi": "0.121.0",
"wifi_app": "3.3.0",
"silabs": "1.0.1"
"silabs": "1.1.2"
},
"missing_at": null,
"created_at": "2019-04-30T01:43:50.497Z",
"updated_at": "2019-04-30T01:44:43.749Z",
"system_id": 12345,
"updated_at": "2023-01-02T19:09:58.251Z",
"system_id": 11111,
"firmware": {
"wifi": "0.121.0",
"wifi_app": "3.3.0",
"silabs": "1.0.1"
"silabs": "1.1.2"
},
"links": {
"system": 12345
"system": 11111
}
}
]

View file

@ -0,0 +1,55 @@
[
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"definition_id": 4,
"created_at": "2019-06-28T22:12:49.651Z",
"type": "sensor",
"model_version": "2.1",
"sensor_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": {
"trigger_value": "no_leak",
"data_received_at": "2022-03-20T08:00:29.763Z"
},
"status_localized": {
"state": "No Leak",
"description": "Mar 20 at 2:00am"
},
"insights": {
"primary": {
"origin": {
"type": "Sensor",
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
"value": "no_leak",
"data_received_at": "2022-03-20T08:00:29.763Z"
}
},
"configuration": {},
"pro_monitoring_status": "eligible"
},
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"definition_id": 7,
"created_at": "2019-07-10T22:40:48.847Z",
"type": "sensor",
"model_version": "3.1",
"sensor_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": {
"trigger_value": "no_alarm",
"data_received_at": "2019-06-28T22:12:49.516Z"
},
"status_localized": {
"state": "No Sound",
"description": "Jun 28 at 4:12pm"
},
"insights": {
"primary": {
"origin": {},
"value": "no_alarm",
"data_received_at": "2019-06-28T22:12:49.516Z"
}
},
"configuration": {},
"pro_monitoring_status": "eligible"
}
]

View file

@ -7,64 +7,28 @@
"email": "user@email.com"
},
"bridge": {
"id": 12345,
"hardware_id": "0x1234567890abcdef"
"id": 67890,
"hardware_id": "0x0000000000000000"
},
"last_bridge_hardware_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"name": "Bathroom Sensor",
"name": "Sensor 1",
"location_id": 123456,
"system_id": 12345,
"hardware_id": "0x1234567890abcdef",
"firmware_version": "1.1.2",
"hardware_id": "0x0000000000000000",
"hardware_revision": 5,
"device_key": "0x1234567890abcdef",
"encryption_key": true,
"installed_at": "2019-04-30T01:57:34.443Z",
"calibrated_at": "2019-04-30T01:57:35.651Z",
"last_reported_at": "2019-04-30T02:20:04.821Z",
"missing_at": null,
"updated_at": "2019-04-30T01:57:36.129Z",
"created_at": "2019-04-30T01:56:45.932Z",
"signal_strength": 5,
"links": {
"location": 123456
},
"lqi": 0,
"rssi": -46,
"surface_type": null
},
{
"id": 132462,
"uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"user": {
"id": 12345,
"email": "user@email.com"
},
"bridge": {
"id": 12345,
"hardware_id": "0x1234567890abcdef"
},
"last_bridge_hardware_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"name": "Living Room Sensor",
"location_id": 123456,
"system_id": 12345,
"hardware_id": "0x1234567890abcdef",
"firmware_version": "1.1.2",
"hardware_revision": 5,
"device_key": "0x1234567890abcdef",
"device_key": "0x0000000000000000",
"encryption_key": true,
"installed_at": "2019-04-30T01:45:56.169Z",
"calibrated_at": "2019-04-30T01:46:06.256Z",
"last_reported_at": "2019-04-30T02:20:04.829Z",
"installed_at": "2019-06-28T22:12:51.209Z",
"calibrated_at": "2023-03-07T19:51:56.838Z",
"last_reported_at": "2023-04-19T18:09:40.479Z",
"missing_at": null,
"updated_at": "2019-04-30T01:46:07.717Z",
"created_at": "2019-04-30T01:45:14.148Z",
"signal_strength": 5,
"links": {
"location": 123456
"updated_at": "2023-03-28T13:33:33.801Z",
"created_at": "2019-06-28T22:12:20.256Z",
"signal_strength": 4,
"firmware": {
"status": "valid"
},
"lqi": 0,
"rssi": -30,
"surface_type": null
}
]

View file

@ -1,86 +0,0 @@
[
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"task_type": "missing",
"sensor_data": [],
"status": {
"value": "not_missing",
"received_at": "2020-11-11T21:18:06.613Z"
},
"created_at": "2020-11-11T21:18:06.613Z",
"updated_at": "2020-11-11T21:18:06.617Z",
"sensor_id": 525993,
"model_version": "2.0",
"configuration": {},
"links": {
"sensor": 525993
}
},
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"task_type": "leak",
"sensor_data": [],
"status": {
"insights": {
"primary": {
"from_state": null,
"to_state": "no_leak",
"data_received_at": "2020-11-11T21:19:13.755Z",
"origin": {}
}
}
},
"created_at": "2020-11-11T21:19:13.755Z",
"updated_at": "2020-11-11T21:19:13.764Z",
"sensor_id": 525993,
"model_version": "2.1",
"configuration": {},
"links": {
"sensor": 525993
}
},
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"task_type": "temperature",
"sensor_data": [],
"status": {
"value": "20.991287231445312",
"received_at": "2021-01-27T15:18:49.996Z"
},
"created_at": "2020-11-11T21:19:13.856Z",
"updated_at": "2020-11-11T21:19:13.865Z",
"sensor_id": 525993,
"model_version": "2.1",
"configuration": {
"lower": 15.56,
"upper": 29.44,
"offset": 0
},
"links": {
"sensor": 525993
}
},
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"task_type": "low_battery",
"sensor_data": [],
"status": {
"insights": {
"primary": {
"from_state": null,
"to_state": "high",
"data_received_at": "2020-11-17T18:40:27.024Z",
"origin": {}
}
}
},
"created_at": "2020-11-17T18:40:27.024Z",
"updated_at": "2020-11-17T18:40:27.033Z",
"sensor_id": 525993,
"model_version": "4.1",
"configuration": {},
"links": {
"sensor": 525993
}
}
]

View file

@ -1,5 +1,6 @@
"""Test Notion diagnostics."""
from homeassistant.components.diagnostics import REDACTED
from homeassistant.components.notion import DOMAIN
from homeassistant.core import HomeAssistant
from tests.components.diagnostics import get_diagnostics_for_config_entry
@ -17,7 +18,7 @@ async def test_entry_diagnostics(
"entry": {
"entry_id": config_entry.entry_id,
"version": 1,
"domain": "notion",
"domain": DOMAIN,
"title": REDACTED,
"data": {"username": REDACTED, "password": REDACTED},
"options": {},
@ -28,106 +29,107 @@ async def test_entry_diagnostics(
"disabled_by": None,
},
"data": {
"bridges": {
"12345": {
"bridges": [
{
"id": 12345,
"name": None,
"name": "Bridge 1",
"mode": "home",
"hardware_id": REDACTED,
"hardware_revision": 4,
"firmware_version": {
"silabs": "1.1.2",
"wifi": "0.121.0",
"wifi_app": "3.3.0",
"silabs": "1.0.1",
},
"missing_at": None,
"created_at": "2019-04-30T01:43:50.497Z",
"updated_at": "2019-04-30T01:44:43.749Z",
"system_id": 12345,
"created_at": "2019-06-27T00:18:44.337000+00:00",
"updated_at": "2023-03-19T03:20:16.061000+00:00",
"system_id": 11111,
"firmware": {
"silabs": "1.1.2",
"wifi": "0.121.0",
"wifi_app": "3.3.0",
"silabs": "1.0.1",
},
"links": {"system": 12345},
"links": {"system": 11111},
},
{
"id": 67890,
"name": "Bridge 2",
"mode": "home",
"hardware_id": REDACTED,
"hardware_revision": 4,
"firmware_version": {
"silabs": "1.1.2",
"wifi": "0.121.0",
"wifi_app": "3.3.0",
},
"missing_at": None,
"created_at": "2019-04-30T01:43:50.497000+00:00",
"updated_at": "2023-01-02T19:09:58.251000+00:00",
"system_id": 11111,
"firmware": {
"silabs": "1.1.2",
"wifi": "0.121.0",
"wifi_app": "3.3.0",
},
"links": {"system": 11111},
},
],
"listeners": [
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"listener_kind": {
"__type": "<enum 'ListenerKind'>",
"repr": "<ListenerKind.SMOKE: 7>",
},
"created_at": "2019-07-10T22:40:48.847000+00:00",
"device_type": "sensor",
"model_version": "3.1",
"sensor_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": {
"trigger_value": "no_alarm",
"data_received_at": "2019-06-28T22:12:49.516000+00:00",
},
"status_localized": {
"state": "No Sound",
"description": "Jun 28 at 4:12pm",
},
"insights": {
"primary": {
"origin": {"type": None, "id": None},
"value": "no_alarm",
"data_received_at": "2019-06-28T22:12:49.516000+00:00",
}
},
"configuration": {},
"pro_monitoring_status": "eligible",
}
},
"sensors": {
"123456": {
],
"sensors": [
{
"id": 123456,
"uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"user": {"id": 12345, "email": REDACTED},
"bridge": {"id": 12345, "hardware_id": REDACTED},
"bridge": {"id": 67890, "hardware_id": REDACTED},
"last_bridge_hardware_id": REDACTED,
"name": "Bathroom Sensor",
"name": "Sensor 1",
"location_id": 123456,
"system_id": 12345,
"hardware_id": REDACTED,
"firmware_version": "1.1.2",
"hardware_revision": 5,
"firmware_version": "1.1.2",
"device_key": REDACTED,
"encryption_key": True,
"installed_at": "2019-04-30T01:57:34.443Z",
"calibrated_at": "2019-04-30T01:57:35.651Z",
"last_reported_at": "2019-04-30T02:20:04.821Z",
"installed_at": "2019-06-28T22:12:51.209000+00:00",
"calibrated_at": "2023-03-07T19:51:56.838000+00:00",
"last_reported_at": "2023-04-19T18:09:40.479000+00:00",
"missing_at": None,
"updated_at": "2019-04-30T01:57:36.129Z",
"created_at": "2019-04-30T01:56:45.932Z",
"signal_strength": 5,
"links": {"location": 123456},
"lqi": 0,
"rssi": -46,
"updated_at": "2023-03-28T13:33:33.801000+00:00",
"created_at": "2019-06-28T22:12:20.256000+00:00",
"signal_strength": 4,
"firmware": {"status": "valid"},
"surface_type": None,
},
"132462": {
"id": 132462,
"uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"user": {"id": 12345, "email": REDACTED},
"bridge": {"id": 12345, "hardware_id": REDACTED},
"last_bridge_hardware_id": REDACTED,
"name": "Living Room Sensor",
"location_id": 123456,
"system_id": 12345,
"hardware_id": REDACTED,
"firmware_version": "1.1.2",
"hardware_revision": 5,
"device_key": REDACTED,
"encryption_key": True,
"installed_at": "2019-04-30T01:45:56.169Z",
"calibrated_at": "2019-04-30T01:46:06.256Z",
"last_reported_at": "2019-04-30T02:20:04.829Z",
"missing_at": None,
"updated_at": "2019-04-30T01:46:07.717Z",
"created_at": "2019-04-30T01:45:14.148Z",
"signal_strength": 5,
"links": {"location": 123456},
"lqi": 0,
"rssi": -30,
"surface_type": None,
},
},
"tasks": {
"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx": {
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"task_type": "low_battery",
"sensor_data": [],
"status": {
"insights": {
"primary": {
"from_state": None,
"to_state": "high",
"data_received_at": "2020-11-17T18:40:27.024Z",
"origin": {},
}
}
},
"created_at": "2020-11-17T18:40:27.024Z",
"updated_at": "2020-11-17T18:40:27.033Z",
"sensor_id": 525993,
"model_version": "4.1",
"configuration": {},
"links": {"sensor": 525993},
}
},
],
},
}