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:
parent
4de124cdd5
commit
c6d846453d
15 changed files with 410 additions and 314 deletions
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aionotion"],
|
||||
"requirements": ["aionotion==3.0.2"]
|
||||
"requirements": ["aionotion==2023.04.2"]
|
||||
}
|
||||
|
|
11
homeassistant/components/notion/model.py
Normal file
11
homeassistant/components/notion/model.py
Normal 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
|
|
@ -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],
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
55
tests/components/notion/fixtures/listener_data.json
Normal file
55
tests/components/notion/fixtures/listener_data.json
Normal 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"
|
||||
}
|
||||
]
|
|
@ -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
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
|
@ -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},
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue