Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 92 additions & 7 deletions homeassistant/components/geniushub/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import voluptuous as vol

from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_TEMPERATURE,
CONF_HOST,
CONF_MAC,
Expand All @@ -26,11 +27,10 @@
)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.service import verify_domain_control
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
import homeassistant.util.dt as dt_util

ATTR_DURATION = "duration"

_LOGGER = logging.getLogger(__name__)

DOMAIN = "geniushub"
Expand Down Expand Up @@ -68,6 +68,30 @@
{DOMAIN: vol.Any(V3_API_SCHEMA, V1_API_SCHEMA)}, extra=vol.ALLOW_EXTRA
)

ATTR_ZONE_MODE = "mode"
ATTR_DURATION = "duration"

SVC_SET_ZONE_MODE = "set_zone_mode"
SVC_SET_ZONE_OVERRIDE = "set_zone_override"

SET_ZONE_MODE_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(ATTR_ZONE_MODE): vol.In(["off", "timer", "footprint"]),
}
)
SET_ZONE_OVERRIDE_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(ATTR_TEMPERATURE): vol.All(
vol.Coerce(float), vol.Range(min=4, max=28)
),
vol.Optional(ATTR_DURATION): vol.All(
cv.time_period, vol.Range(min=timedelta(minutes=5), max=timedelta(days=1)),
),
}
)


async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
"""Create a Genius Hub system."""
Expand Down Expand Up @@ -96,9 +120,45 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
for platform in ["climate", "water_heater", "sensor", "binary_sensor", "switch"]:
hass.async_create_task(async_load_platform(hass, platform, DOMAIN, {}, config))

setup_service_functions(hass, broker)

return True


@callback
def setup_service_functions(hass: HomeAssistantType, broker):
"""Set up the service functions."""

@verify_domain_control(hass, DOMAIN)
async def set_zone_mode(call) -> None:
"""Set the system mode."""
entity_id = call.data[ATTR_ENTITY_ID]

registry = await hass.helpers.entity_registry.async_get_registry()
registry_entry = registry.async_get(entity_id)

if registry_entry is None or registry_entry.platform != DOMAIN:
raise ValueError(f"'{entity_id}' is not a known {DOMAIN} entity")

if registry_entry.domain != "climate":
raise ValueError(f"'{entity_id}' is not an {DOMAIN} zone")

payload = {
"unique_id": registry_entry.unique_id,
"service": call.service,
"data": call.data,
}

async_dispatcher_send(hass, DOMAIN, payload)

hass.services.async_register(
DOMAIN, SVC_SET_ZONE_MODE, set_zone_mode, schema=SET_ZONE_MODE_SCHEMA
)
hass.services.async_register(
DOMAIN, SVC_SET_ZONE_OVERRIDE, set_zone_mode, schema=SET_ZONE_OVERRIDE_SCHEMA
)


class GeniusBroker:
"""Container for geniushub client and data."""

Expand Down Expand Up @@ -146,8 +206,8 @@ async def async_added_to_hass(self) -> None:
"""Set up a listener when this entity is added to HA."""
async_dispatcher_connect(self.hass, DOMAIN, self._refresh)

@callback
def _refresh(self) -> None:
async def _refresh(self, payload: Optional[dict] = None) -> None:
"""Process any signals."""
self.async_schedule_update_ha_state(force_refresh=True)

@property
Expand Down Expand Up @@ -175,7 +235,6 @@ def __init__(self, broker, device) -> None:

self._device = device
self._unique_id = f"{broker.hub_uid}_device_{device.id}"

self._last_comms = self._state_attr = None

@property
Expand All @@ -188,7 +247,7 @@ def device_state_attributes(self) -> Dict[str, Any]:
attrs["last_comms"] = self._last_comms.isoformat()

state = dict(self._device.data["state"])
if "_state" in self._device.data: # only for v3 API
if "_state" in self._device.data: # only via v3 API
state.update(self._device.data["_state"])

attrs["state"] = {
Expand All @@ -199,7 +258,7 @@ def device_state_attributes(self) -> Dict[str, Any]:

async def async_update(self) -> None:
"""Update an entity's state data."""
if "_state" in self._device.data: # only for v3 API
if "_state" in self._device.data: # only via v3 API
self._last_comms = dt_util.utc_from_timestamp(
self._device.data["_state"]["lastComms"]
)
Expand All @@ -215,6 +274,32 @@ def __init__(self, broker, zone) -> None:
self._zone = zone
self._unique_id = f"{broker.hub_uid}_zone_{zone.id}"

async def _refresh(self, payload: Optional[dict] = 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]

# pylint: disable=protected-access
if mode == "footprint" and not self._zone._has_pir:
raise TypeError(
f"'{self.entity_id}' can not 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."""
Expand Down
29 changes: 29 additions & 0 deletions homeassistant/components/geniushub/services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Support for a Genius Hub system
# Describes the format for available services

set_zone_mode:
description: >-
Set the zone to an operating mode.
fields:
entity_id:
description: The zone's entity_id.
example: climate.kitchen
mode:
description: 'One of: off, timer or footprint.'
example: timer

set_zone_override:
description: >-
Override the zone's setpoint for a given duration.
fields:
entity_id:
description: The zone's entity_id.
example: climate.bathroom
temperature:
description: The target temperature, to 0.1 C.
example: 19.2
duration:
description: >-
The duration of the override. Optional, default 1 hour, maximum 24 hours.
example: '{"minutes": 135}'