Add hive boost to climate and water_heater (#26789)

* Start the Boost work

* Add services.yaml

* Added Services #2

* Start the Boost work

* Add services.yaml

* Added Services #2

* Working Services

* pyhiveapi to 0.2.19

* Update Libary to 0.2.19

* Update Water_heater boost

* Added Async hass add function

* Update Services

* Reviewed Changes

* Fixed Refresh System

* Review 2

* Moved device iteration to the platform

* update

* Updates #2

* Review#3 New Base Class

* Review #5

* Update homeassistant/components/hive/__init__.py

Co-Authored-By: Martin Hjelmare <marhje52@kth.se>

* Update homeassistant/components/hive/__init__.py

Co-Authored-By: Martin Hjelmare <marhje52@kth.se>

* Update homeassistant/components/hive/__init__.py

Co-Authored-By: Martin Hjelmare <marhje52@kth.se>

* Review 6

* Review 7

* Removed Child classes to inhertit from the parent
pull/27029/head
Khole 2019-09-27 22:18:34 +01:00 committed by Martin Hjelmare
parent 58446c79fc
commit fc3f5163f1
10 changed files with 218 additions and 187 deletions

View File

@ -1,17 +1,32 @@
"""Support for the Hive devices."""
"""Support for the Hive devices and services."""
from functools import wraps
import logging
from pyhiveapi import Pyhiveapi
import voluptuous as vol
from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_TEMPERATURE,
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_USERNAME,
)
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
DOMAIN = "hive"
DATA_HIVE = "data_hive"
SERVICES = ["Heating", "HotWater"]
SERVICE_BOOST_HOTWATER = "boost_hotwater"
SERVICE_BOOST_HEATING = "boost_heating"
ATTR_TIME_PERIOD = "time_period"
ATTR_MODE = "on_off"
DEVICETYPES = {
"binary_sensor": "device_list_binary_sensor",
"climate": "device_list_climate",
@ -34,11 +49,31 @@ CONFIG_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA,
)
BOOST_HEATING_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(ATTR_TIME_PERIOD): vol.All(
cv.time_period, cv.positive_timedelta, lambda td: td.total_seconds() // 60
),
vol.Optional(ATTR_TEMPERATURE, default="25.0"): vol.Coerce(float),
}
)
BOOST_HOTWATER_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Optional(ATTR_TIME_PERIOD, default="00:30:00"): vol.All(
cv.time_period, cv.positive_timedelta, lambda td: td.total_seconds() // 60
),
vol.Required(ATTR_MODE): cv.string,
}
)
class HiveSession:
"""Initiate Hive Session Class."""
entities = []
entity_lookup = {}
core = None
heating = None
hotwater = None
@ -51,6 +86,35 @@ class HiveSession:
def setup(hass, config):
"""Set up the Hive Component."""
def heating_boost(service):
"""Handle the service call."""
node_id = HiveSession.entity_lookup.get(service.data[ATTR_ENTITY_ID])
if not node_id:
# log or raise error
_LOGGER.error("Cannot boost entity id entered")
return
minutes = service.data[ATTR_TIME_PERIOD]
temperature = service.data[ATTR_TEMPERATURE]
session.heating.turn_boost_on(node_id, minutes, temperature)
def hotwater_boost(service):
"""Handle the service call."""
node_id = HiveSession.entity_lookup.get(service.data[ATTR_ENTITY_ID])
if not node_id:
# log or raise error
_LOGGER.error("Cannot boost entity id entered")
return
minutes = service.data[ATTR_TIME_PERIOD]
mode = service.data[ATTR_MODE]
if mode == "on":
session.hotwater.turn_boost_on(node_id, minutes)
elif mode == "off":
session.hotwater.turn_boost_off(node_id)
session = HiveSession()
session.core = Pyhiveapi()
@ -58,9 +122,9 @@ def setup(hass, config):
password = config[DOMAIN][CONF_PASSWORD]
update_interval = config[DOMAIN][CONF_SCAN_INTERVAL]
devicelist = session.core.initialise_api(username, password, update_interval)
devices = session.core.initialise_api(username, password, update_interval)
if devicelist is None:
if devices is None:
_LOGGER.error("Hive API initialization failed")
return False
@ -73,9 +137,59 @@ def setup(hass, config):
session.attributes = Pyhiveapi.Attributes()
hass.data[DATA_HIVE] = session
for ha_type, hive_type in DEVICETYPES.items():
for key, devices in devicelist.items():
if key == hive_type:
for hivedevice in devices:
load_platform(hass, ha_type, DOMAIN, hivedevice, config)
for ha_type in DEVICETYPES:
devicelist = devices.get(DEVICETYPES[ha_type])
if devicelist:
load_platform(hass, ha_type, DOMAIN, devicelist, config)
if ha_type == "climate":
hass.services.register(
DOMAIN,
SERVICE_BOOST_HEATING,
heating_boost,
schema=BOOST_HEATING_SCHEMA,
)
if ha_type == "water_heater":
hass.services.register(
DOMAIN,
SERVICE_BOOST_HEATING,
hotwater_boost,
schema=BOOST_HOTWATER_SCHEMA,
)
return True
def refresh_system(func):
"""Force update all entities after state change."""
@wraps(func)
def wrapper(self, *args, **kwargs):
func(self, *args, **kwargs)
dispatcher_send(self.hass, DOMAIN)
return wrapper
class HiveEntity(Entity):
"""Initiate Hive Base Class."""
def __init__(self, session, hive_device):
"""Initialize the instance."""
self.node_id = hive_device["Hive_NodeID"]
self.node_name = hive_device["Hive_NodeName"]
self.device_type = hive_device["HA_DeviceType"]
self.node_device_type = hive_device["Hive_DeviceType"]
self.session = session
self.attributes = {}
self._unique_id = f"{self.node_id}-{self.device_type}"
async def async_added_to_hass(self):
"""When entity is added to Home Assistant."""
async_dispatcher_connect(self.hass, DOMAIN, self._update_callback)
if self.device_type in SERVICES:
self.session.entity_lookup[self.entity_id] = self.node_id
@callback
def _update_callback(self):
"""Call update method."""
self.async_schedule_update_ha_state()

View File

@ -1,7 +1,7 @@
"""Support for the Hive binary sensors."""
from homeassistant.components.binary_sensor import BinarySensorDevice
from . import DATA_HIVE, DOMAIN
from . import DOMAIN, DATA_HIVE, HiveEntity
DEVICETYPE_DEVICE_CLASS = {"motionsensor": "motion", "contactsensor": "opening"}
@ -10,26 +10,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Hive sensor devices."""
if discovery_info is None:
return
session = hass.data.get(DATA_HIVE)
add_entities([HiveBinarySensorEntity(session, discovery_info)])
devs = []
for dev in discovery_info:
devs.append(HiveBinarySensorEntity(session, dev))
add_entities(devs)
class HiveBinarySensorEntity(BinarySensorDevice):
class HiveBinarySensorEntity(HiveEntity, BinarySensorDevice):
"""Representation of a Hive binary sensor."""
def __init__(self, hivesession, hivedevice):
"""Initialize the hive sensor."""
self.node_id = hivedevice["Hive_NodeID"]
self.node_name = hivedevice["Hive_NodeName"]
self.device_type = hivedevice["HA_DeviceType"]
self.node_device_type = hivedevice["Hive_DeviceType"]
self.session = hivesession
self.attributes = {}
self.data_updatesource = f"{self.device_type}.{self.node_id}"
self._unique_id = f"{self.node_id}-{self.device_type}"
self.session.entities.append(self)
@property
def unique_id(self):
"""Return unique ID of entity."""
@ -40,11 +31,6 @@ class HiveBinarySensorEntity(BinarySensorDevice):
"""Return device information."""
return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name}
def handle_update(self, updatesource):
"""Handle the new update request."""
if f"{self.device_type}.{self.node_id}" not in updatesource:
self.schedule_update_ha_state()
@property
def device_class(self):
"""Return the class of this sensor."""

View File

@ -5,13 +5,14 @@ from homeassistant.components.climate.const import (
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
PRESET_BOOST,
PRESET_NONE,
SUPPORT_PRESET_MODE,
SUPPORT_TARGET_TEMPERATURE,
PRESET_NONE,
)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from . import DATA_HIVE, DOMAIN
from . import DOMAIN, DATA_HIVE, HiveEntity, refresh_system
HIVE_TO_HASS_STATE = {
"SCHEDULE": HVAC_MODE_AUTO,
@ -34,28 +35,21 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Hive climate devices."""
if discovery_info is None:
return
if discovery_info["HA_DeviceType"] != "Heating":
return
session = hass.data.get(DATA_HIVE)
climate = HiveClimateEntity(session, discovery_info)
add_entities([climate])
devs = []
for dev in discovery_info:
devs.append(HiveClimateEntity(session, dev))
add_entities(devs)
class HiveClimateEntity(ClimateDevice):
class HiveClimateEntity(HiveEntity, ClimateDevice):
"""Hive Climate Device."""
def __init__(self, hivesession, hivedevice):
def __init__(self, hive_session, hive_device):
"""Initialize the Climate device."""
self.node_id = hivedevice["Hive_NodeID"]
self.node_name = hivedevice["Hive_NodeName"]
self.device_type = hivedevice["HA_DeviceType"]
self.thermostat_node_id = hivedevice["Thermostat_NodeID"]
self.session = hivesession
self.attributes = {}
self.data_updatesource = f"{self.device_type}.{self.node_id}"
self._unique_id = f"{self.node_id}-{self.device_type}"
super().__init__(hive_session, hive_device)
self.thermostat_node_id = hive_device["Thermostat_NodeID"]
@property
def unique_id(self):
@ -72,11 +66,6 @@ class HiveClimateEntity(ClimateDevice):
"""Return the list of supported features."""
return SUPPORT_FLAGS
def handle_update(self, updatesource):
"""Handle the new update request."""
if f"{self.device_type}.{self.node_id}" not in updatesource:
self.schedule_update_ha_state()
@property
def name(self):
"""Return the name of the Climate device."""
@ -99,7 +88,7 @@ class HiveClimateEntity(ClimateDevice):
return SUPPORT_HVAC
@property
def hvac_mode(self) -> str:
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
@ -143,43 +132,29 @@ class HiveClimateEntity(ClimateDevice):
"""Return a list of available preset modes."""
return SUPPORT_PRESET
async def async_added_to_hass(self):
"""When entity is added to Home Assistant."""
await super().async_added_to_hass()
self.session.entities.append(self)
@refresh_system
def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
new_mode = HASS_TO_HIVE_STATE[hvac_mode]
self.session.heating.set_mode(self.node_id, new_mode)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
@refresh_system
def set_temperature(self, **kwargs):
"""Set new target temperature."""
new_temperature = kwargs.get(ATTR_TEMPERATURE)
if new_temperature is not None:
self.session.heating.set_target_temperature(self.node_id, new_temperature)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
def set_preset_mode(self, preset_mode) -> None:
@refresh_system
def set_preset_mode(self, preset_mode):
"""Set new preset mode."""
if preset_mode == PRESET_NONE and self.preset_mode == PRESET_BOOST:
self.session.heating.turn_boost_off(self.node_id)
elif preset_mode == PRESET_BOOST:
curtemp = self.session.heating.current_temperature(self.node_id)
curtemp = round(curtemp * 2) / 2
curtemp = round(self.current_temperature * 2) / 2
temperature = curtemp + 0.5
self.session.heating.turn_boost_on(self.node_id, 30, temperature)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
def update(self):
"""Update all Node data from Hive."""
self.session.core.update_data(self.node_id)

View File

@ -10,32 +10,28 @@ from homeassistant.components.light import (
)
import homeassistant.util.color as color_util
from . import DATA_HIVE, DOMAIN
from . import DOMAIN, DATA_HIVE, HiveEntity, refresh_system
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Hive light devices."""
if discovery_info is None:
return
session = hass.data.get(DATA_HIVE)
add_entities([HiveDeviceLight(session, discovery_info)])
devs = []
for dev in discovery_info:
devs.append(HiveDeviceLight(session, dev))
add_entities(devs)
class HiveDeviceLight(Light):
class HiveDeviceLight(HiveEntity, Light):
"""Hive Active Light Device."""
def __init__(self, hivesession, hivedevice):
def __init__(self, hive_session, hive_device):
"""Initialize the Light device."""
self.node_id = hivedevice["Hive_NodeID"]
self.node_name = hivedevice["Hive_NodeName"]
self.device_type = hivedevice["HA_DeviceType"]
self.light_device_type = hivedevice["Hive_Light_DeviceType"]
self.session = hivesession
self.attributes = {}
self.data_updatesource = f"{self.device_type}.{self.node_id}"
self._unique_id = f"{self.node_id}-{self.device_type}"
self.session.entities.append(self)
super().__init__(hive_session, hive_device)
self.light_device_type = hive_device["Hive_Light_DeviceType"]
@property
def unique_id(self):
@ -47,11 +43,6 @@ class HiveDeviceLight(Light):
"""Return device information."""
return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name}
def handle_update(self, updatesource):
"""Handle the new update request."""
if f"{self.device_type}.{self.node_id}" not in updatesource:
self.schedule_update_ha_state()
@property
def name(self):
"""Return the display name of this light."""
@ -106,6 +97,7 @@ class HiveDeviceLight(Light):
"""Return true if light is on."""
return self.session.light.get_state(self.node_id)
@refresh_system
def turn_on(self, **kwargs):
"""Instruct the light to turn on."""
new_brightness = None
@ -134,14 +126,10 @@ class HiveDeviceLight(Light):
new_color,
)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
@refresh_system
def turn_off(self, **kwargs):
"""Instruct the light to turn off."""
self.session.light.turn_off(self.node_id)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
@property
def supported_features(self):

View File

@ -3,7 +3,7 @@
"name": "Hive",
"documentation": "https://www.home-assistant.io/components/hive",
"requirements": [
"pyhiveapi==0.2.18.1"
"pyhiveapi==0.2.19"
],
"dependencies": [],
"codeowners": [

View File

@ -2,7 +2,7 @@
from homeassistant.const import TEMP_CELSIUS
from homeassistant.helpers.entity import Entity
from . import DATA_HIVE, DOMAIN
from . import DOMAIN, DATA_HIVE, HiveEntity
FRIENDLY_NAMES = {
"Hub_OnlineStatus": "Hive Hub Status",
@ -19,28 +19,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Hive sensor devices."""
if discovery_info is None:
return
session = hass.data.get(DATA_HIVE)
if (
discovery_info["HA_DeviceType"] == "Hub_OnlineStatus"
or discovery_info["HA_DeviceType"] == "Hive_OutsideTemperature"
):
add_entities([HiveSensorEntity(session, discovery_info)])
devs = []
for dev in discovery_info:
if dev["HA_DeviceType"] in FRIENDLY_NAMES:
devs.append(HiveSensorEntity(session, dev))
add_entities(devs)
class HiveSensorEntity(Entity):
class HiveSensorEntity(HiveEntity, Entity):
"""Hive Sensor Entity."""
def __init__(self, hivesession, hivedevice):
"""Initialize the sensor."""
self.node_id = hivedevice["Hive_NodeID"]
self.device_type = hivedevice["HA_DeviceType"]
self.node_device_type = hivedevice["Hive_DeviceType"]
self.session = hivesession
self.data_updatesource = f"{self.device_type}.{self.node_id}"
self._unique_id = f"{self.node_id}-{self.device_type}"
self.session.entities.append(self)
@property
def unique_id(self):
"""Return unique ID of entity."""
@ -51,11 +41,6 @@ class HiveSensorEntity(Entity):
"""Return device information."""
return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name}
def handle_update(self, updatesource):
"""Handle the new update request."""
if f"{self.device_type}.{self.node_id}" not in updatesource:
self.schedule_update_ha_state()
@property
def name(self):
"""Return the name of the sensor."""
@ -82,6 +67,4 @@ class HiveSensorEntity(Entity):
def update(self):
"""Update all Node data from Hive."""
if self.session.core.update_data(self.node_id):
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
self.session.core.update_data(self.node_id)

View File

@ -0,0 +1,27 @@
boost_heating:
description: "Set the boost mode ON defining the period of time and the desired target temperature
for the boost."
fields:
entity_id:
{
description: Enter the entity_id for the device required to set the boost mode.,
example: "climate.heating",
}
time_period:
{ description: Set the time period for the boost., example: "01:30:00" }
temperature:
{
description: Set the target temperature for the boost period.,
example: "20.5",
}
boost_hotwater:
description:
"Set the boost mode ON or OFF defining the period of time for the boost."
fields:
entity_id:
{
description: Enter the entity_id for the device reuired to set the boost mode.,
example: "water_heater.hot_water",
}
time_period: { description: Set the time period for the boost., example: "01:30:00" }
on_off: { description: Set the boost function on or off., example: "on" }

View File

@ -1,32 +1,24 @@
"""Support for the Hive switches."""
from homeassistant.components.switch import SwitchDevice
from . import DATA_HIVE, DOMAIN
from . import DOMAIN, DATA_HIVE, HiveEntity, refresh_system
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Hive switches."""
if discovery_info is None:
return
session = hass.data.get(DATA_HIVE)
add_entities([HiveDevicePlug(session, discovery_info)])
devs = []
for dev in discovery_info:
devs.append(HiveDevicePlug(session, dev))
add_entities(devs)
class HiveDevicePlug(SwitchDevice):
class HiveDevicePlug(HiveEntity, SwitchDevice):
"""Hive Active Plug."""
def __init__(self, hivesession, hivedevice):
"""Initialize the Switch device."""
self.node_id = hivedevice["Hive_NodeID"]
self.node_name = hivedevice["Hive_NodeName"]
self.device_type = hivedevice["HA_DeviceType"]
self.session = hivesession
self.attributes = {}
self.data_updatesource = f"{self.device_type}.{self.node_id}"
self._unique_id = f"{self.node_id}-{self.device_type}"
self.session.entities.append(self)
@property
def unique_id(self):
"""Return unique ID of entity."""
@ -37,11 +29,6 @@ class HiveDevicePlug(SwitchDevice):
"""Return device information."""
return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name}
def handle_update(self, updatesource):
"""Handle the new update request."""
if f"{self.device_type}.{self.node_id}" not in updatesource:
self.schedule_update_ha_state()
@property
def name(self):
"""Return the name of this Switch device if any."""
@ -62,17 +49,15 @@ class HiveDevicePlug(SwitchDevice):
"""Return true if switch is on."""
return self.session.switch.get_state(self.node_id)
@refresh_system
def turn_on(self, **kwargs):
"""Turn the switch on."""
self.session.switch.turn_on(self.node_id)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
@refresh_system
def turn_off(self, **kwargs):
"""Turn the device off."""
self.session.switch.turn_off(self.node_id)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
def update(self):
"""Update all Node data from Hive."""

View File

@ -1,51 +1,36 @@
"""Support for hive water heaters."""
from homeassistant.const import TEMP_CELSIUS
from homeassistant.components.water_heater import (
STATE_ECO,
STATE_ON,
STATE_OFF,
STATE_ON,
SUPPORT_OPERATION_MODE,
WaterHeaterDevice,
)
from . import DATA_HIVE, DOMAIN
from homeassistant.const import TEMP_CELSIUS
from . import DOMAIN, DATA_HIVE, HiveEntity, refresh_system
SUPPORT_FLAGS_HEATER = SUPPORT_OPERATION_MODE
HIVE_TO_HASS_STATE = {"SCHEDULE": STATE_ECO, "ON": STATE_ON, "OFF": STATE_OFF}
HASS_TO_HIVE_STATE = {STATE_ECO: "SCHEDULE", STATE_ON: "ON", STATE_OFF: "OFF"}
SUPPORT_WATER_HEATER = [STATE_ECO, STATE_ON, STATE_OFF]
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Wink water heater devices."""
"""Set up the Hive water heater devices."""
if discovery_info is None:
return
if discovery_info["HA_DeviceType"] != "HotWater":
return
session = hass.data.get(DATA_HIVE)
water_heater = HiveWaterHeater(session, discovery_info)
add_entities([water_heater])
devs = []
for dev in discovery_info:
devs.append(HiveWaterHeater(session, dev))
add_entities(devs)
class HiveWaterHeater(WaterHeaterDevice):
class HiveWaterHeater(HiveEntity, WaterHeaterDevice):
"""Hive Water Heater Device."""
def __init__(self, hivesession, hivedevice):
"""Initialize the Water Heater device."""
self.node_id = hivedevice["Hive_NodeID"]
self.node_name = hivedevice["Hive_NodeName"]
self.device_type = hivedevice["HA_DeviceType"]
self.session = hivesession
self.data_updatesource = f"{self.device_type}.{self.node_id}"
self._unique_id = f"{self.node_id}-{self.device_type}"
self._unit_of_measurement = TEMP_CELSIUS
@property
def unique_id(self):
"""Return unique ID of entity."""
@ -61,11 +46,6 @@ class HiveWaterHeater(WaterHeaterDevice):
"""Return the list of supported features."""
return SUPPORT_FLAGS_HEATER
def handle_update(self, updatesource):
"""Handle the new update request."""
if f"{self.device_type}.{self.node_id}" not in updatesource:
self.schedule_update_ha_state()
@property
def name(self):
"""Return the name of the water heater."""
@ -76,7 +56,7 @@ class HiveWaterHeater(WaterHeaterDevice):
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return self._unit_of_measurement
return TEMP_CELSIUS
@property
def current_operation(self):
@ -88,19 +68,12 @@ class HiveWaterHeater(WaterHeaterDevice):
"""List of available operation modes."""
return SUPPORT_WATER_HEATER
async def async_added_to_hass(self):
"""When entity is added to Home Assistant."""
await super().async_added_to_hass()
self.session.entities.append(self)
@refresh_system
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
new_mode = HASS_TO_HIVE_STATE[operation_mode]
self.session.hotwater.set_mode(self.node_id, new_mode)
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
def update(self):
"""Update all Node data from Hive."""
self.session.core.update_data(self.node_id)

View File

@ -1223,7 +1223,7 @@ pyheos==0.6.0
pyhik==0.2.3
# homeassistant.components.hive
pyhiveapi==0.2.18.1
pyhiveapi==0.2.19
# homeassistant.components.homematic
pyhomematic==0.1.60