bachya/simplisafe-python

View on GitHub
simplipy/device/__init__.py

Summary

Maintainability
A
0 mins
Test Coverage
"""Define a base SimpliSafe device."""

from __future__ import annotations

from enum import Enum
from typing import TYPE_CHECKING, Any, cast

from simplipy.const import LOGGER

if TYPE_CHECKING:
    from simplipy.system import System


class DeviceTypes(Enum):
    """Device types based on internal SimpliSafe ID number."""

    REMOTE = 0
    KEYPAD = 1
    KEYCHAIN = 2
    PANIC_BUTTON = 3
    MOTION = 4
    ENTRY = 5
    GLASS_BREAK = 6
    CARBON_MONOXIDE = 7
    SMOKE = 8
    LEAK = 9
    TEMPERATURE = 10
    CAMERA = 12
    SIREN = 13
    SMOKE_AND_CARBON_MONOXIDE = 14
    DOORBELL = 15
    LOCK = 16
    OUTDOOR_CAMERA = 17
    MOTION_V2 = 20
    OUTDOOR_ALARM_SECURITY_BELL_BOX = 22
    LOCK_KEYPAD = 253
    UNKNOWN = 99


def get_device_type_from_data(device_data: dict[str, Any]) -> DeviceTypes:
    """Get the device type of a raw data payload.

    Args:
        device_data: An API response payload.

    Returns:
        The device type.
    """
    try:
        return DeviceTypes(device_data["type"])
    except ValueError:
        LOGGER.error("Unknown device type: %s", device_data["type"])
        return DeviceTypes.UNKNOWN


class Device:
    """A base SimpliSafe device.

    Note that this class shouldn't be instantiated directly; it will be instantiated as
    appropriate via :meth:`simplipy.API.async_get_systems`.

    Args:
        system: A :meth:`simplipy.system.System` object (or one of its subclasses).
        device_type: The type of device represented.
        serial: The serial number of the device.
    """

    def __init__(self, system: System, device_type: DeviceTypes, serial: str) -> None:
        """Initialize.

        Args:
            system: A :meth:`simplipy.system.System` object (or one of its subclasses).
            device_type: The type of device represented.
            serial: The serial number of the device.
        """
        self._device_type = device_type
        self._serial = serial
        self._system = system

    @property
    def name(self) -> str:
        """Return the device name.

        Returns:
            The device name.
        """
        return cast(str, self._system.sensor_data[self._serial]["name"])

    @property
    def serial(self) -> str:
        """Return the device's serial number.

        Returns:
            The device serial number.
        """
        return cast(str, self._system.sensor_data[self._serial]["serial"])

    @property
    def type(self) -> DeviceTypes:
        """Return the device type.

        Returns:
            The device type.
        """
        return self._device_type

    def as_dict(self) -> dict[str, Any]:
        """Return dictionary version of this device.

        Returns:
            Returns a dict representation of this device.
        """
        return {
            "name": self.name,
            "serial": self.serial,
            "type": self.type.value,
        }

    async def async_update(self, cached: bool = True) -> None:
        """Retrieve the latest state/properties for the device.

        The ``cached`` parameter determines whether the SimpliSafe Cloud uses the last
        known values retrieved from the base station (``True``) or retrieves new data.

        Args:
            cached: Whether to used cached data.
        """
        await self._system.async_update(
            include_subscription=False, include_settings=False, cached=cached
        )


class DeviceV3(Device):
    """A base device for V3 systems.

    Note that this class shouldn't be instantiated directly; it will be
    instantiated as appropriate via :meth:`simplipy.API.async_get_systems`.
    """

    @property
    def error(self) -> bool:
        """Return the device's error status.

        Returns:
            The device's error status.
        """
        return cast(
            bool,
            self._system.sensor_data[self._serial]["status"].get("malfunction", False),
        )

    @property
    def low_battery(self) -> bool:
        """Return whether the device's battery is low.

        Returns:
            The device's low battery status.
        """
        return cast(bool, self._system.sensor_data[self._serial]["flags"]["lowBattery"])

    @property
    def offline(self) -> bool:
        """Return whether the device is offline.

        Returns:
            The device's offline status.
        """
        return cast(bool, self._system.sensor_data[self._serial]["flags"]["offline"])

    @property
    def settings(self) -> dict[str, Any]:
        """Return the device's settings.

        Note that these can change based on what device type the device is.

        Returns:
            A settings dictionary.
        """
        return cast(dict[str, Any], self._system.sensor_data[self._serial]["setting"])

    def as_dict(self) -> dict[str, Any]:
        """Return dictionary version of this device.

        Returns:
            A dict representation of this device.
        """
        return {
            **super().as_dict(),
            "error": self.error,
            "low_battery": self.low_battery,
            "offline": self.offline,
            "settings": self.settings,
        }