Multiple devices support for opentherm_gw (#22932)

* Breaking change: Rewrite opentherm_gw to add support for more than one OpenTherm Gateway.
Breaks config layout and child entity ids and adds a required parameter to all service calls (gateway_id).

* Add schema and parameter description for service opentherm_gw.reset_gateway.

* Add optional name attribute in config to be used for friendly names.
Fix bugs in binary_sensor and climate platforms.

* pylint fixes

* Remove unused variables.

* Update manifest.json, remove REQUIREMENTS from .py file

* Update CODEOWNERS

* Address issues that were brought up (requested changes):
- Move imports to module level
- Change certain functions from async to sync
- Move constants to const.py (new file)
- Call gateway setup from outside of __init__()
- Move validation of monitored_variables to config schema

* Address requested changes:
- Make module imports relative
- Move more functions from async to sync, decorate with @callback where necessary
- Remove monitored_variables option, add all sensors by default
This commit is contained in:
mvn23 2019-06-21 10:52:25 +02:00 committed by Martin Hjelmare
parent d9f2a406f6
commit 43a6be6471
8 changed files with 486 additions and 519 deletions

View file

@ -1,17 +1,21 @@
"""Support for OpenTherm Gateway climate devices."""
import logging
from homeassistant.components.climate import ClimateDevice
from pyotgw import vars as gw_vars
from homeassistant.components.climate import ClimateDevice, ENTITY_ID_FORMAT
from homeassistant.components.climate.const import (
STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_NAME, PRECISION_HALVES, PRECISION_TENTHS,
PRECISION_WHOLE, TEMP_CELSIUS)
ATTR_TEMPERATURE, PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE,
TEMP_CELSIUS)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import async_generate_entity_id
from .const import (
CONF_FLOOR_TEMP, CONF_PRECISION, DATA_GATEWAYS, DATA_OPENTHERM_GW)
from . import (
CONF_FLOOR_TEMP, CONF_PRECISION, DATA_DEVICE, DATA_GW_VARS,
DATA_OPENTHERM_GW, SIGNAL_OPENTHERM_GW_UPDATE)
_LOGGER = logging.getLogger(__name__)
@ -21,20 +25,22 @@ SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up the opentherm_gw device."""
gateway = OpenThermGateway(hass, discovery_info)
gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][discovery_info]
gateway = OpenThermClimate(gw_dev)
async_add_entities([gateway])
class OpenThermGateway(ClimateDevice):
class OpenThermClimate(ClimateDevice):
"""Representation of a climate device."""
def __init__(self, hass, config):
def __init__(self, gw_dev):
"""Initialize the device."""
self._gateway = hass.data[DATA_OPENTHERM_GW][DATA_DEVICE]
self._gw_vars = hass.data[DATA_OPENTHERM_GW][DATA_GW_VARS]
self.friendly_name = config.get(CONF_NAME)
self.floor_temp = config.get(CONF_FLOOR_TEMP)
self.temp_precision = config.get(CONF_PRECISION)
self._gateway = gw_dev
self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, gw_dev.gw_id, hass=gw_dev.hass)
self.friendly_name = gw_dev.name
self.floor_temp = gw_dev.climate_config[CONF_FLOOR_TEMP]
self.temp_precision = gw_dev.climate_config.get(CONF_PRECISION)
self._current_operation = STATE_IDLE
self._current_temperature = None
self._new_target_temperature = None
@ -47,36 +53,37 @@ class OpenThermGateway(ClimateDevice):
async def async_added_to_hass(self):
"""Connect to the OpenTherm Gateway device."""
_LOGGER.debug("Added device %s", self.friendly_name)
async_dispatcher_connect(self.hass, SIGNAL_OPENTHERM_GW_UPDATE,
async_dispatcher_connect(self.hass, self._gateway.update_signal,
self.receive_report)
async def receive_report(self, status):
@callback
def receive_report(self, status):
"""Receive and handle a new report from the Gateway."""
ch_active = status.get(self._gw_vars.DATA_SLAVE_CH_ACTIVE)
flame_on = status.get(self._gw_vars.DATA_SLAVE_FLAME_ON)
cooling_active = status.get(self._gw_vars.DATA_SLAVE_COOLING_ACTIVE)
ch_active = status.get(gw_vars.DATA_SLAVE_CH_ACTIVE)
flame_on = status.get(gw_vars.DATA_SLAVE_FLAME_ON)
cooling_active = status.get(gw_vars.DATA_SLAVE_COOLING_ACTIVE)
if ch_active and flame_on:
self._current_operation = STATE_HEAT
elif cooling_active:
self._current_operation = STATE_COOL
else:
self._current_operation = STATE_IDLE
self._current_temperature = status.get(self._gw_vars.DATA_ROOM_TEMP)
temp_upd = status.get(self._gw_vars.DATA_ROOM_SETPOINT)
self._current_temperature = status.get(gw_vars.DATA_ROOM_TEMP)
temp_upd = status.get(gw_vars.DATA_ROOM_SETPOINT)
if self._target_temperature != temp_upd:
self._new_target_temperature = None
self._target_temperature = temp_upd
# GPIO mode 5: 0 == Away
# GPIO mode 6: 1 == Away
gpio_a_state = status.get(self._gw_vars.OTGW_GPIO_A)
gpio_a_state = status.get(gw_vars.OTGW_GPIO_A)
if gpio_a_state == 5:
self._away_mode_a = 0
elif gpio_a_state == 6:
self._away_mode_a = 1
else:
self._away_mode_a = None
gpio_b_state = status.get(self._gw_vars.OTGW_GPIO_B)
gpio_b_state = status.get(gw_vars.OTGW_GPIO_B)
if gpio_b_state == 5:
self._away_mode_b = 0
elif gpio_b_state == 6:
@ -85,10 +92,10 @@ class OpenThermGateway(ClimateDevice):
self._away_mode_b = None
if self._away_mode_a is not None:
self._away_state_a = (status.get(
self._gw_vars.OTGW_GPIO_A_STATE) == self._away_mode_a)
gw_vars.OTGW_GPIO_A_STATE) == self._away_mode_a)
if self._away_mode_b is not None:
self._away_state_b = (status.get(
self._gw_vars.OTGW_GPIO_B_STATE) == self._away_mode_b)
gw_vars.OTGW_GPIO_B_STATE) == self._away_mode_b)
self.async_schedule_update_ha_state()
@property
@ -126,9 +133,9 @@ class OpenThermGateway(ClimateDevice):
if self._current_temperature is None:
return
if self.floor_temp is True:
if self.temp_precision == PRECISION_HALVES:
if self.precision == PRECISION_HALVES:
return int(2 * self._current_temperature) / 2
if self.temp_precision == PRECISION_TENTHS:
if self.precision == PRECISION_TENTHS:
return int(10 * self._current_temperature) / 10
return int(self._current_temperature)
return self._current_temperature
@ -141,7 +148,7 @@ class OpenThermGateway(ClimateDevice):
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
return self.temp_precision
return self.precision
@property
def is_away_mode_on(self):
@ -154,8 +161,8 @@ class OpenThermGateway(ClimateDevice):
temp = float(kwargs[ATTR_TEMPERATURE])
if temp == self.target_temperature:
return
self._new_target_temperature = await self._gateway.set_target_temp(
temp)
self._new_target_temperature = (
await self._gateway.gateway.set_target_temp(temp))
self.async_schedule_update_ha_state()
@property