mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Extract Geniushub base entities in separate module (#122331)
This commit is contained in:
parent
be4c7291bd
commit
cd48278671
@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from geniushubclient import GeniusHub
|
from geniushubclient import GeniusHub
|
||||||
@ -21,7 +20,6 @@ from homeassistant.const import (
|
|||||||
CONF_TOKEN,
|
CONF_TOKEN,
|
||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
Platform,
|
Platform,
|
||||||
UnitOfTemperature,
|
|
||||||
)
|
)
|
||||||
from homeassistant.core import (
|
from homeassistant.core import (
|
||||||
DOMAIN as HOMEASSISTANT_DOMAIN,
|
DOMAIN as HOMEASSISTANT_DOMAIN,
|
||||||
@ -32,31 +30,16 @@ from homeassistant.core import (
|
|||||||
from homeassistant.data_entry_flow import FlowResultType
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.dispatcher import (
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
async_dispatcher_connect,
|
|
||||||
async_dispatcher_send,
|
|
||||||
)
|
|
||||||
from homeassistant.helpers.entity import Entity
|
|
||||||
from homeassistant.helpers.event import async_track_time_interval
|
from homeassistant.helpers.event import async_track_time_interval
|
||||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||||
from homeassistant.helpers.service import verify_domain_control
|
from homeassistant.helpers.service import verify_domain_control
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
import homeassistant.util.dt as dt_util
|
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
# temperature is repeated here, as it gives access to high-precision temps
|
|
||||||
GH_ZONE_ATTRS = ["mode", "temperature", "type", "occupied", "override"]
|
|
||||||
GH_DEVICE_ATTRS = {
|
|
||||||
"luminance": "luminance",
|
|
||||||
"measuredTemperature": "measured_temperature",
|
|
||||||
"occupancyTrigger": "occupancy_trigger",
|
|
||||||
"setback": "setback",
|
|
||||||
"setTemperature": "set_temperature",
|
|
||||||
"wakeupInterval": "wakeup_interval",
|
|
||||||
}
|
|
||||||
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=60)
|
SCAN_INTERVAL = timedelta(seconds=60)
|
||||||
|
|
||||||
@ -279,149 +262,3 @@ class GeniusBroker:
|
|||||||
self.client._zones, # noqa: SLF001
|
self.client._zones, # noqa: SLF001
|
||||||
self.client._devices, # noqa: SLF001
|
self.client._devices, # noqa: SLF001
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class GeniusEntity(Entity):
|
|
||||||
"""Base for all Genius Hub entities."""
|
|
||||||
|
|
||||||
_attr_should_poll = False
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
"""Initialize the entity."""
|
|
||||||
self._unique_id: str | None = None
|
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
|
||||||
"""Set up a listener when this entity is added to HA."""
|
|
||||||
self.async_on_remove(async_dispatcher_connect(self.hass, DOMAIN, self._refresh))
|
|
||||||
|
|
||||||
async def _refresh(self, payload: dict | None = None) -> None:
|
|
||||||
"""Process any signals."""
|
|
||||||
self.async_schedule_update_ha_state(force_refresh=True)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self) -> str | None:
|
|
||||||
"""Return a unique ID."""
|
|
||||||
return self._unique_id
|
|
||||||
|
|
||||||
|
|
||||||
class GeniusDevice(GeniusEntity):
|
|
||||||
"""Base for all Genius Hub devices."""
|
|
||||||
|
|
||||||
def __init__(self, broker, device) -> None:
|
|
||||||
"""Initialize the Device."""
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self._device = device
|
|
||||||
self._unique_id = f"{broker.hub_uid}_device_{device.id}"
|
|
||||||
self._last_comms: datetime | None = None
|
|
||||||
self._state_attr = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def extra_state_attributes(self) -> dict[str, Any]:
|
|
||||||
"""Return the device state attributes."""
|
|
||||||
attrs = {}
|
|
||||||
attrs["assigned_zone"] = self._device.data["assignedZones"][0]["name"]
|
|
||||||
if self._last_comms:
|
|
||||||
attrs["last_comms"] = self._last_comms.isoformat()
|
|
||||||
|
|
||||||
state = dict(self._device.data["state"])
|
|
||||||
if "_state" in self._device.data: # only via v3 API
|
|
||||||
state.update(self._device.data["_state"])
|
|
||||||
|
|
||||||
attrs["state"] = {
|
|
||||||
GH_DEVICE_ATTRS[k]: v for k, v in state.items() if k in GH_DEVICE_ATTRS
|
|
||||||
}
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
|
||||||
"""Update an entity's state data."""
|
|
||||||
if "_state" in self._device.data: # only via v3 API
|
|
||||||
self._last_comms = dt_util.utc_from_timestamp(
|
|
||||||
self._device.data["_state"]["lastComms"]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class GeniusZone(GeniusEntity):
|
|
||||||
"""Base for all Genius Hub zones."""
|
|
||||||
|
|
||||||
def __init__(self, broker, zone) -> None:
|
|
||||||
"""Initialize the Zone."""
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self._zone = zone
|
|
||||||
self._unique_id = f"{broker.hub_uid}_zone_{zone.id}"
|
|
||||||
|
|
||||||
async def _refresh(self, payload: dict | None = None) -> None:
|
|
||||||
"""Process any signals."""
|
|
||||||
if payload is None:
|
|
||||||
self.async_schedule_update_ha_state(force_refresh=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
if payload["unique_id"] != self._unique_id:
|
|
||||||
return
|
|
||||||
|
|
||||||
if payload["service"] == SVC_SET_ZONE_OVERRIDE:
|
|
||||||
temperature = round(payload["data"][ATTR_TEMPERATURE] * 10) / 10
|
|
||||||
duration = payload["data"].get(ATTR_DURATION, timedelta(hours=1))
|
|
||||||
|
|
||||||
await self._zone.set_override(temperature, int(duration.total_seconds()))
|
|
||||||
return
|
|
||||||
|
|
||||||
mode = payload["data"][ATTR_ZONE_MODE]
|
|
||||||
|
|
||||||
if mode == "footprint" and not self._zone._has_pir: # noqa: SLF001
|
|
||||||
raise TypeError(
|
|
||||||
f"'{self.entity_id}' cannot support footprint mode (it has no PIR)"
|
|
||||||
)
|
|
||||||
|
|
||||||
await self._zone.set_mode(mode)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
"""Return the name of the climate device."""
|
|
||||||
return self._zone.name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def extra_state_attributes(self) -> dict[str, Any]:
|
|
||||||
"""Return the device state attributes."""
|
|
||||||
status = {k: v for k, v in self._zone.data.items() if k in GH_ZONE_ATTRS}
|
|
||||||
return {"status": status}
|
|
||||||
|
|
||||||
|
|
||||||
class GeniusHeatingZone(GeniusZone):
|
|
||||||
"""Base for Genius Heating Zones."""
|
|
||||||
|
|
||||||
_max_temp: float
|
|
||||||
_min_temp: float
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_temperature(self) -> float | None:
|
|
||||||
"""Return the current temperature."""
|
|
||||||
return self._zone.data.get("temperature")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def target_temperature(self) -> float:
|
|
||||||
"""Return the temperature we try to reach."""
|
|
||||||
return self._zone.data["setpoint"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def min_temp(self) -> float:
|
|
||||||
"""Return max valid temperature that can be set."""
|
|
||||||
return self._min_temp
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_temp(self) -> float:
|
|
||||||
"""Return max valid temperature that can be set."""
|
|
||||||
return self._max_temp
|
|
||||||
|
|
||||||
@property
|
|
||||||
def temperature_unit(self) -> str:
|
|
||||||
"""Return the unit of measurement."""
|
|
||||||
return UnitOfTemperature.CELSIUS
|
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs) -> None:
|
|
||||||
"""Set a new target temperature for this zone."""
|
|
||||||
await self._zone.set_override(
|
|
||||||
kwargs[ATTR_TEMPERATURE], kwargs.get(ATTR_DURATION, 3600)
|
|
||||||
)
|
|
||||||
|
@ -6,7 +6,8 @@ from homeassistant.components.binary_sensor import BinarySensorEntity
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import GeniusDevice, GeniusHubConfigEntry
|
from . import GeniusHubConfigEntry
|
||||||
|
from .entity import GeniusDevice
|
||||||
|
|
||||||
GH_STATE_ATTR = "outputOnOff"
|
GH_STATE_ATTR = "outputOnOff"
|
||||||
GH_TYPE = "Receiver"
|
GH_TYPE = "Receiver"
|
||||||
|
@ -13,7 +13,8 @@ from homeassistant.components.climate import (
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import GeniusHeatingZone, GeniusHubConfigEntry
|
from . import GeniusHubConfigEntry
|
||||||
|
from .entity import GeniusHeatingZone
|
||||||
|
|
||||||
# GeniusHub Zones support: Off, Timer, Override/Boost, Footprint & Linked modes
|
# GeniusHub Zones support: Off, Timer, Override/Boost, Footprint & Linked modes
|
||||||
HA_HVAC_TO_GH = {HVACMode.OFF: "off", HVACMode.HEAT: "timer"}
|
HA_HVAC_TO_GH = {HVACMode.OFF: "off", HVACMode.HEAT: "timer"}
|
||||||
|
168
homeassistant/components/geniushub/entity.py
Normal file
168
homeassistant/components/geniushub/entity.py
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
"""Base entity for Geniushub."""
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
from . import ATTR_DURATION, ATTR_ZONE_MODE, DOMAIN, SVC_SET_ZONE_OVERRIDE
|
||||||
|
|
||||||
|
# temperature is repeated here, as it gives access to high-precision temps
|
||||||
|
GH_ZONE_ATTRS = ["mode", "temperature", "type", "occupied", "override"]
|
||||||
|
GH_DEVICE_ATTRS = {
|
||||||
|
"luminance": "luminance",
|
||||||
|
"measuredTemperature": "measured_temperature",
|
||||||
|
"occupancyTrigger": "occupancy_trigger",
|
||||||
|
"setback": "setback",
|
||||||
|
"setTemperature": "set_temperature",
|
||||||
|
"wakeupInterval": "wakeup_interval",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class GeniusEntity(Entity):
|
||||||
|
"""Base for all Genius Hub entities."""
|
||||||
|
|
||||||
|
_attr_should_poll = False
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""Initialize the entity."""
|
||||||
|
self._unique_id: str | None = None
|
||||||
|
|
||||||
|
async def async_added_to_hass(self) -> None:
|
||||||
|
"""Set up a listener when this entity is added to HA."""
|
||||||
|
self.async_on_remove(async_dispatcher_connect(self.hass, DOMAIN, self._refresh))
|
||||||
|
|
||||||
|
async def _refresh(self, payload: dict | None = None) -> None:
|
||||||
|
"""Process any signals."""
|
||||||
|
self.async_schedule_update_ha_state(force_refresh=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self) -> str | None:
|
||||||
|
"""Return a unique ID."""
|
||||||
|
return self._unique_id
|
||||||
|
|
||||||
|
|
||||||
|
class GeniusDevice(GeniusEntity):
|
||||||
|
"""Base for all Genius Hub devices."""
|
||||||
|
|
||||||
|
def __init__(self, broker, device) -> None:
|
||||||
|
"""Initialize the Device."""
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self._device = device
|
||||||
|
self._unique_id = f"{broker.hub_uid}_device_{device.id}"
|
||||||
|
self._last_comms: datetime | None = None
|
||||||
|
self._state_attr = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
|
"""Return the device state attributes."""
|
||||||
|
attrs = {}
|
||||||
|
attrs["assigned_zone"] = self._device.data["assignedZones"][0]["name"]
|
||||||
|
if self._last_comms:
|
||||||
|
attrs["last_comms"] = self._last_comms.isoformat()
|
||||||
|
|
||||||
|
state = dict(self._device.data["state"])
|
||||||
|
if "_state" in self._device.data: # only via v3 API
|
||||||
|
state.update(self._device.data["_state"])
|
||||||
|
|
||||||
|
attrs["state"] = {
|
||||||
|
GH_DEVICE_ATTRS[k]: v for k, v in state.items() if k in GH_DEVICE_ATTRS
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
async def async_update(self) -> None:
|
||||||
|
"""Update an entity's state data."""
|
||||||
|
if "_state" in self._device.data: # only via v3 API
|
||||||
|
self._last_comms = dt_util.utc_from_timestamp(
|
||||||
|
self._device.data["_state"]["lastComms"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GeniusZone(GeniusEntity):
|
||||||
|
"""Base for all Genius Hub zones."""
|
||||||
|
|
||||||
|
def __init__(self, broker, zone) -> None:
|
||||||
|
"""Initialize the Zone."""
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self._zone = zone
|
||||||
|
self._unique_id = f"{broker.hub_uid}_zone_{zone.id}"
|
||||||
|
|
||||||
|
async def _refresh(self, payload: dict | None = None) -> None:
|
||||||
|
"""Process any signals."""
|
||||||
|
if payload is None:
|
||||||
|
self.async_schedule_update_ha_state(force_refresh=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
if payload["unique_id"] != self._unique_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
if payload["service"] == SVC_SET_ZONE_OVERRIDE:
|
||||||
|
temperature = round(payload["data"][ATTR_TEMPERATURE] * 10) / 10
|
||||||
|
duration = payload["data"].get(ATTR_DURATION, timedelta(hours=1))
|
||||||
|
|
||||||
|
await self._zone.set_override(temperature, int(duration.total_seconds()))
|
||||||
|
return
|
||||||
|
|
||||||
|
mode = payload["data"][ATTR_ZONE_MODE]
|
||||||
|
|
||||||
|
if mode == "footprint" and not self._zone._has_pir: # noqa: SLF001
|
||||||
|
raise TypeError(
|
||||||
|
f"'{self.entity_id}' cannot support footprint mode (it has no PIR)"
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._zone.set_mode(mode)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Return the name of the climate device."""
|
||||||
|
return self._zone.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
|
"""Return the device state attributes."""
|
||||||
|
status = {k: v for k, v in self._zone.data.items() if k in GH_ZONE_ATTRS}
|
||||||
|
return {"status": status}
|
||||||
|
|
||||||
|
|
||||||
|
class GeniusHeatingZone(GeniusZone):
|
||||||
|
"""Base for Genius Heating Zones."""
|
||||||
|
|
||||||
|
_max_temp: float
|
||||||
|
_min_temp: float
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_temperature(self) -> float | None:
|
||||||
|
"""Return the current temperature."""
|
||||||
|
return self._zone.data.get("temperature")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature(self) -> float:
|
||||||
|
"""Return the temperature we try to reach."""
|
||||||
|
return self._zone.data["setpoint"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_temp(self) -> float:
|
||||||
|
"""Return max valid temperature that can be set."""
|
||||||
|
return self._min_temp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_temp(self) -> float:
|
||||||
|
"""Return max valid temperature that can be set."""
|
||||||
|
return self._max_temp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def temperature_unit(self) -> str:
|
||||||
|
"""Return the unit of measurement."""
|
||||||
|
return UnitOfTemperature.CELSIUS
|
||||||
|
|
||||||
|
async def async_set_temperature(self, **kwargs) -> None:
|
||||||
|
"""Set a new target temperature for this zone."""
|
||||||
|
await self._zone.set_override(
|
||||||
|
kwargs[ATTR_TEMPERATURE], kwargs.get(ATTR_DURATION, 3600)
|
||||||
|
)
|
@ -11,7 +11,8 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from . import GeniusDevice, GeniusEntity, GeniusHubConfigEntry
|
from . import GeniusHubConfigEntry
|
||||||
|
from .entity import GeniusDevice, GeniusEntity
|
||||||
|
|
||||||
GH_STATE_ATTR = "batteryLevel"
|
GH_STATE_ATTR = "batteryLevel"
|
||||||
|
|
||||||
|
@ -13,7 +13,8 @@ from homeassistant.helpers import config_validation as cv, entity_platform
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import VolDictType
|
from homeassistant.helpers.typing import VolDictType
|
||||||
|
|
||||||
from . import ATTR_DURATION, GeniusHubConfigEntry, GeniusZone
|
from . import ATTR_DURATION, GeniusHubConfigEntry
|
||||||
|
from .entity import GeniusZone
|
||||||
|
|
||||||
GH_ON_OFF_ZONE = "on / off"
|
GH_ON_OFF_ZONE = "on / off"
|
||||||
|
|
||||||
|
@ -10,7 +10,8 @@ from homeassistant.const import STATE_OFF
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import GeniusHeatingZone, GeniusHubConfigEntry
|
from . import GeniusHubConfigEntry
|
||||||
|
from .entity import GeniusHeatingZone
|
||||||
|
|
||||||
STATE_AUTO = "auto"
|
STATE_AUTO = "auto"
|
||||||
STATE_MANUAL = "manual"
|
STATE_MANUAL = "manual"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user