Skip to content

Commit

Permalink
Freezer Binary Sensors
Browse files Browse the repository at this point in the history
  • Loading branch information
Expl0dingBanana committed Oct 28, 2024
1 parent 321bd0c commit 1264e86
Show file tree
Hide file tree
Showing 7 changed files with 885 additions and 17 deletions.
1 change: 1 addition & 0 deletions custom_components/hubspace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
_LOGGER = logging.getLogger(__name__)

PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.FAN,
Platform.LIGHT,
Platform.LOCK,
Expand Down
115 changes: 115 additions & 0 deletions custom_components/hubspace/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import logging
from typing import Any

from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from hubspace_async import HubSpaceDevice, HubSpaceState

from . import HubSpaceConfigEntry
from .const import DOMAIN, ENTITY_BINARY_SENSOR
from .coordinator import HubSpaceDataUpdateCoordinator

_LOGGER = logging.getLogger(__name__)


class HubSpaceBinarySensor(CoordinatorEntity, BinarySensorEntity):
"""HubSpace child sensor component"""

def __init__(
self,
coordinator: HubSpaceDataUpdateCoordinator,
description: BinarySensorEntityDescription,
device: HubSpaceDevice,
) -> None:
super().__init__(coordinator, context=device.id)
self.coordinator = coordinator
self.entity_description = description
search_data = description.key.split("|", 1)
self._function_instance = None
try:
self._function_class, self._function_instance = search_data
except ValueError:
self._function_class = search_data
self._device = device
self._sensor_value = None

@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self.update_states()
self.async_write_ha_state()

def update_states(self) -> None:
"""Handle updated data from the coordinator."""
states: list[HubSpaceState] = self.coordinator.data[ENTITY_BINARY_SENSOR][
self._device.id
]["device"].states
if not states:
_LOGGER.debug(
"No states found for %s. Maybe hasn't polled yet?", self._device.id
)
for state in states:
if state.functionClass == self._function_class:
if (
self._function_instance
and self._function_instance != state.functionInstance
):
continue
self._sensor_value = state.value

@property
def unique_id(self) -> str:
return f"{self._device.id}_{self.entity_description.key}"

@property
def name(self) -> str:
return f"{self._device.friendly_name}: {self.entity_description.name}"

@property
def device_info(self) -> DeviceInfo:
"""Return the device info."""
model = self._device.model if self._device.model != "TBD" else None
return DeviceInfo(
identifiers={(DOMAIN, self._device.device_id)},
name=self._device.friendly_name,
model=model,
)

@property
def device_class(self) -> Any:
"""Return the state."""
return self.entity_description.device_class

@property
def is_on(self) -> bool:
return self._sensor_value != "normal"


async def async_setup_entry(
hass: HomeAssistant,
entry: HubSpaceConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Add Sensor entities from a config_entry."""
coordinator_hubspace: HubSpaceDataUpdateCoordinator = (
entry.runtime_data.coordinator_hubspace
)
entities: list[HubSpaceBinarySensor] = []
for dev_sensors in coordinator_hubspace.data[ENTITY_BINARY_SENSOR].values():
dev = dev_sensors["device"]
for sensor in dev_sensors["sensors"]:
_LOGGER.debug(
"Adding a binary sensor from %s [%s] - %s",
dev.friendly_name,
dev.id,
sensor.key,
)
ha_entity = HubSpaceBinarySensor(coordinator_hubspace, sensor, dev)
entities.append(ha_entity)
async_add_entities(entities)
37 changes: 37 additions & 0 deletions custom_components/hubspace/const.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from datetime import timedelta
from typing import Final

from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntityDescription,
)
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntityDescription,
Expand All @@ -26,6 +30,8 @@
VERSION_MINOR: Final[int] = 0


ENTITY_BINARY_SENSOR: Final[str] = "binary_sensor"
ENTITY_CLIMATE: Final[str] = "climate"
ENTITY_FAN: Final[str] = "fan"
ENTITY_LIGHT: Final[str] = "light"
ENTITY_LOCK: Final[str] = "lock"
Expand All @@ -34,6 +40,7 @@
ENTITY_VALVE: Final[str] = "valve"

DEVICE_CLASS_FAN: Final[str] = "fan"
DEVICE_CLASS_FREEZER: Final[str] = "freezer"
DEVICE_CLASS_LIGHT: Final[str] = "light"
DEVICE_CLASS_SWITCH: Final[str] = "switch"
DEVICE_CLASS_OUTLET: Final[str] = "power-outlet"
Expand All @@ -42,6 +49,7 @@
DEVICE_CLASS_WATER_TIMER: Final[str] = "water-timer"

DEVICE_CLASS_TO_ENTITY_MAP: Final[dict[str, str]] = {
DEVICE_CLASS_FREEZER: ENTITY_CLIMATE,
DEVICE_CLASS_FAN: ENTITY_FAN,
DEVICE_CLASS_LIGHT: ENTITY_LIGHT,
DEVICE_CLASS_DOOR_LOCK: ENTITY_LOCK,
Expand Down Expand Up @@ -88,3 +96,32 @@
state_class=SensorStateClass.MEASUREMENT,
),
}

BINARY_SENSORS = {
"freezer": {
"error|mcu-communication-failure": BinarySensorEntityDescription(
key="error|mcu-communication-failure",
name="MCU",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
"error|fridge-high-temperature-alert": BinarySensorEntityDescription(
key="error|fridge-high-temperature-alert",
name="Fridge High Temp Alert",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
"error|freezer-high-temperature-alert": BinarySensorEntityDescription(
key="error|freezer-high-temperature-alert",
name="Freezer High Temp Alert",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
"error|temperature-sensor-failure": BinarySensorEntityDescription(
key="error|temperature-sensor-failure",
name="Sensor Failure",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
}
}
75 changes: 58 additions & 17 deletions custom_components/hubspace/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import aiofiles
import hubspace_async
from homeassistant.components.binary_sensor import BinarySensorEntityDescription
from homeassistant.components.sensor import SensorEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
Expand Down Expand Up @@ -43,9 +44,12 @@ def __init__(
self.room_names = room_names
# We only want to perform the sensor checks once per device
# to reduce the same computing
self.sensors: defaultdict[str, list] = defaultdict(list)
self.sensors: dict[str, defaultdict] = {
const.ENTITY_BINARY_SENSOR: defaultdict(dict),
const.ENTITY_SENSOR: defaultdict(dict),
}
self._sensor_checks: list[str] = []
# HubSpace loves to duplicate data across multiple devices and
# HubSpace loves to duplicate data across multiple devices, and
# we only want to add it once
self._added_sensors: list[str] = []

Expand All @@ -56,26 +60,37 @@ def __init__(
update_interval=update_interval,
)

async def process_sensor_devs(self, dev: hubspace_async.HubSpaceDevice) -> list:
async def process_sensor_devs(self, dev: hubspace_async.HubSpaceDevice):
"""Get sensors from a device"""
if dev.id not in self._sensor_checks:
_LOGGER.debug(
"Performing sensor checks for device %s [%s]", dev.friendly_name, dev.id
)
self._sensor_checks.append(dev.id)

# sensors
if sensors := await get_sensors(dev):
de_duped_sensors = []
for sensor in sensors:
if dev.device_id:
unique = f"{dev.device_id}_{sensor.key}"
else:
unique = f"{dev.id}_{sensor.key}"
if unique not in self._added_sensors:
self._added_sensors.append(unique)
de_duped_sensors.append(sensor)
self.sensors[dev.id] = de_duped_sensors
return self.sensors[dev.id]
self.sensors[const.ENTITY_SENSOR][dev.id] = (
await self.process_sensor_dev(dev, sensors)
)
# binary sensors
if sensors := await get_binary_sensors(dev):
self.sensors[const.ENTITY_BINARY_SENSOR][dev.id] = (
await self.process_sensor_dev(dev, sensors)
)

async def process_sensor_dev(
self, dev: hubspace_async.HubSpaceDevice, sensors: list
) -> list:
de_duped = []
for sensor in sensors:
if dev.device_id:
unique = f"{dev.device_id}_{sensor.key}"
else:
unique = f"{dev.id}_{sensor.key}"
if unique not in self._added_sensors:
self._added_sensors.append(unique)
de_duped.append(sensor)
return de_duped

async def _async_update_data(
self,
Expand Down Expand Up @@ -135,10 +150,16 @@ async def process_tracked_devices(
continue
_LOGGER.debug("Adding device %s to %s", dev.friendly_name, mapped)
devices[mapped][dev.id] = dev
if dev_sensors := await self.process_sensor_devs(dev):
await self.process_sensor_devs(dev)
if dev.id in self.sensors[const.ENTITY_SENSOR]:
devices[const.ENTITY_SENSOR][dev.id] = {
"device": dev,
"sensors": dev_sensors,
"sensors": self.sensors[const.ENTITY_SENSOR][dev.id],
}
if dev.id in self.sensors[const.ENTITY_BINARY_SENSOR]:
devices[const.ENTITY_BINARY_SENSOR][dev.id] = {
"device": dev,
"sensors": self.sensors[const.ENTITY_BINARY_SENSOR][dev.id],
}
return devices

Expand All @@ -162,6 +183,26 @@ async def get_sensors(
return required_sensors


async def get_binary_sensors(
dev: hubspace_async.HubSpaceDevice,
) -> list[BinarySensorEntityDescription]:
required_sensors: list[BinarySensorEntityDescription] = []
binary_sensors = const.BINARY_SENSORS.get(dev.device_class, {})
for state in dev.states:
key = state.functionClass
if state.functionInstance:
key += f"|{state.functionInstance}"
if key in binary_sensors:
required_sensors.append(binary_sensors[key])
_LOGGER.debug(
"Found a binary sensor, %s, attached to %s [%s]",
key,
dev.friendly_name,
dev.id,
)
return required_sensors


async def create_devices_from_data(
file_name: str,
) -> list[hubspace_async.HubSpaceDevice]:
Expand Down
Loading

0 comments on commit 1264e86

Please sign in to comment.