From 7baffed7b7e09e3126984ab282e0ecdcd6ec225d Mon Sep 17 00:00:00 2001 From: mvn23 Date: Fri, 19 Oct 2018 07:31:19 +0200 Subject: [PATCH] Add sensor support to opentherm_gw (#17314) * Add OpenTherm Gateway sensor platform. * Add OTGW_ variables to list of supported sensors. * Order imports. * Add OpenTherm Gateway binary sensor support. * Revert "Add OpenTherm Gateway binary sensor support." This reverts commit 115acaa912f4f9ff47408a04f4a770bebd071f26. * Import COMP_SENSOR from sensor component rather than defining it. * Update opentherm_gw sensor platform docs url. * Update dependency to v0.2b1 Old version had incorrect variable names for some of the sensors * Update requirements_all.txt * Address review findings. * Update .coveragerc --- .coveragerc | 2 +- homeassistant/components/opentherm_gw.py | 94 +++++++- .../components/sensor/opentherm_gw.py | 211 ++++++++++++++++++ requirements_all.txt | 2 +- 4 files changed, 304 insertions(+), 5 deletions(-) create mode 100644 homeassistant/components/sensor/opentherm_gw.py diff --git a/.coveragerc b/.coveragerc index 42939e71704..04299609bbb 100644 --- a/.coveragerc +++ b/.coveragerc @@ -249,7 +249,7 @@ omit = homeassistant/components/*/opencv.py homeassistant/components/opentherm_gw.py - homeassistant/components/climate/opentherm_gw.py + homeassistant/components/*/opentherm_gw.py homeassistant/components/openuv/__init__.py homeassistant/components/*/openuv.py diff --git a/homeassistant/components/opentherm_gw.py b/homeassistant/components/opentherm_gw.py index 7bc2bbeaa8a..9379e2c2b31 100644 --- a/homeassistant/components/opentherm_gw.py +++ b/homeassistant/components/opentherm_gw.py @@ -8,8 +8,10 @@ import logging import voluptuous as vol -from homeassistant.const import (CONF_DEVICE, CONF_NAME, PRECISION_HALVES, - PRECISION_TENTHS, PRECISION_WHOLE) +from homeassistant.components.sensor import DOMAIN as COMP_SENSOR +from homeassistant.const import (CONF_DEVICE, CONF_MONITORED_VARIABLES, + CONF_NAME, PRECISION_HALVES, PRECISION_TENTHS, + PRECISION_WHOLE) from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -38,10 +40,12 @@ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_DEVICE): cv.string, vol.Optional(CONF_CLIMATE, default={}): CLIMATE_SCHEMA, + vol.Optional(CONF_MONITORED_VARIABLES, default=[]): vol.All( + cv.ensure_list, [cv.string]), }), }, extra=vol.ALLOW_EXTRA) -REQUIREMENTS = ['pyotgw==0.1b0'] +REQUIREMENTS = ['pyotgw==0.2b1'] _LOGGER = logging.getLogger(__name__) @@ -59,6 +63,8 @@ async def async_setup(hass, config): hass, conf[CONF_DEVICE], gateway)) hass.async_create_task(async_load_platform( hass, 'climate', DOMAIN, conf.get(CONF_CLIMATE))) + hass.async_create_task(setup_monitored_vars( + hass, conf.get(CONF_MONITORED_VARIABLES))) return True @@ -72,3 +78,85 @@ async def connect_and_subscribe(hass, device_path, gateway): _LOGGER.debug("Received report: %s", status) async_dispatcher_send(hass, SIGNAL_OPENTHERM_GW_UPDATE, status) gateway.subscribe(handle_report) + + +async def setup_monitored_vars(hass, monitored_vars): + """Set up requested sensors.""" + gw_vars = hass.data[DATA_OPENTHERM_GW][DATA_GW_VARS] + # Use dict to prepare for binary sensor support. + sensor_type_map = { + COMP_SENSOR: [ + gw_vars.DATA_CONTROL_SETPOINT, + gw_vars.DATA_MASTER_MEMBERID, + gw_vars.DATA_SLAVE_MEMBERID, + gw_vars.DATA_SLAVE_OEM_FAULT, + gw_vars.DATA_COOLING_CONTROL, + gw_vars.DATA_CONTROL_SETPOINT_2, + gw_vars.DATA_ROOM_SETPOINT_OVRD, + gw_vars.DATA_SLAVE_MAX_RELATIVE_MOD, + gw_vars.DATA_SLAVE_MAX_CAPACITY, + gw_vars.DATA_SLAVE_MIN_MOD_LEVEL, + gw_vars.DATA_ROOM_SETPOINT, + gw_vars.DATA_REL_MOD_LEVEL, + gw_vars.DATA_CH_WATER_PRESS, + gw_vars.DATA_DHW_FLOW_RATE, + gw_vars.DATA_ROOM_SETPOINT_2, + gw_vars.DATA_ROOM_TEMP, + gw_vars.DATA_CH_WATER_TEMP, + gw_vars.DATA_DHW_TEMP, + gw_vars.DATA_OUTSIDE_TEMP, + gw_vars.DATA_RETURN_WATER_TEMP, + gw_vars.DATA_SOLAR_STORAGE_TEMP, + gw_vars.DATA_SOLAR_COLL_TEMP, + gw_vars.DATA_CH_WATER_TEMP_2, + gw_vars.DATA_DHW_TEMP_2, + gw_vars.DATA_EXHAUST_TEMP, + gw_vars.DATA_SLAVE_DHW_MAX_SETP, + gw_vars.DATA_SLAVE_DHW_MIN_SETP, + gw_vars.DATA_SLAVE_CH_MAX_SETP, + gw_vars.DATA_SLAVE_CH_MIN_SETP, + gw_vars.DATA_DHW_SETPOINT, + gw_vars.DATA_MAX_CH_SETPOINT, + gw_vars.DATA_OEM_DIAG, + gw_vars.DATA_TOTAL_BURNER_STARTS, + gw_vars.DATA_CH_PUMP_STARTS, + gw_vars.DATA_DHW_PUMP_STARTS, + gw_vars.DATA_DHW_BURNER_STARTS, + gw_vars.DATA_TOTAL_BURNER_HOURS, + gw_vars.DATA_CH_PUMP_HOURS, + gw_vars.DATA_DHW_PUMP_HOURS, + gw_vars.DATA_DHW_BURNER_HOURS, + gw_vars.DATA_MASTER_OT_VERSION, + gw_vars.DATA_SLAVE_OT_VERSION, + gw_vars.DATA_MASTER_PRODUCT_TYPE, + gw_vars.DATA_MASTER_PRODUCT_VERSION, + gw_vars.DATA_SLAVE_PRODUCT_TYPE, + gw_vars.DATA_SLAVE_PRODUCT_VERSION, + gw_vars.OTGW_MODE, + gw_vars.OTGW_DHW_OVRD, + gw_vars.OTGW_ABOUT, + gw_vars.OTGW_BUILD, + gw_vars.OTGW_CLOCKMHZ, + gw_vars.OTGW_LED_A, + gw_vars.OTGW_LED_B, + gw_vars.OTGW_LED_C, + gw_vars.OTGW_LED_D, + gw_vars.OTGW_LED_E, + gw_vars.OTGW_LED_F, + gw_vars.OTGW_GPIO_A, + gw_vars.OTGW_GPIO_B, + gw_vars.OTGW_SB_TEMP, + gw_vars.OTGW_SETP_OVRD_MODE, + gw_vars.OTGW_SMART_PWR, + gw_vars.OTGW_THRM_DETECT, + gw_vars.OTGW_VREF, + ] + } + sensors = [] + for var in monitored_vars: + if var in sensor_type_map[COMP_SENSOR]: + sensors.append(var) + else: + _LOGGER.error("Monitored variable not supported: %s", var) + if sensors: + await async_load_platform(hass, COMP_SENSOR, DOMAIN, sensors) diff --git a/homeassistant/components/sensor/opentherm_gw.py b/homeassistant/components/sensor/opentherm_gw.py new file mode 100644 index 00000000000..9ae557654ce --- /dev/null +++ b/homeassistant/components/sensor/opentherm_gw.py @@ -0,0 +1,211 @@ +""" +Support for OpenTherm Gateway sensors. + +For more details about this platform, please refer to the documentation at +http://home-assistant.io/components/sensor.opentherm_gw/ +""" +import logging + +from homeassistant.components.opentherm_gw import ( + DATA_GW_VARS, DATA_OPENTHERM_GW, SIGNAL_OPENTHERM_GW_UPDATE) +from homeassistant.components.sensor import ENTITY_ID_FORMAT +from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity import Entity, async_generate_entity_id + +UNIT_BAR = 'bar' +UNIT_HOUR = 'h' +UNIT_KW = 'kW' +UNIT_L_MIN = 'L/min' +UNIT_PERCENT = '%' + +DEPENDENCIES = ['opentherm_gw'] + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): + """Set up the OpenTherm Gateway sensors.""" + if discovery_info is None: + return + gw_vars = hass.data[DATA_OPENTHERM_GW][DATA_GW_VARS] + sensor_info = { + # [device_class, unit, friendly_name] + gw_vars.DATA_CONTROL_SETPOINT: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, "Control Setpoint"], + gw_vars.DATA_MASTER_MEMBERID: [None, None, "Thermostat Member ID"], + gw_vars.DATA_SLAVE_MEMBERID: [None, None, "Boiler Member ID"], + gw_vars.DATA_SLAVE_OEM_FAULT: [None, None, "Boiler OEM Fault Code"], + gw_vars.DATA_COOLING_CONTROL: [ + None, UNIT_PERCENT, "Cooling Control Signal"], + gw_vars.DATA_CONTROL_SETPOINT_2: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, "Control Setpoint 2"], + gw_vars.DATA_ROOM_SETPOINT_OVRD: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, "Room Setpoint Override"], + gw_vars.DATA_SLAVE_MAX_RELATIVE_MOD: [ + None, UNIT_PERCENT, "Boiler Maximum Relative Modulation"], + gw_vars.DATA_SLAVE_MAX_CAPACITY: [ + None, UNIT_KW, "Boiler Maximum Capacity"], + gw_vars.DATA_SLAVE_MIN_MOD_LEVEL: [ + None, UNIT_PERCENT, "Boiler Minimum Modulation Level"], + gw_vars.DATA_ROOM_SETPOINT: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, "Room Setpoint"], + gw_vars.DATA_REL_MOD_LEVEL: [ + None, UNIT_PERCENT, "Relative Modulation Level"], + gw_vars.DATA_CH_WATER_PRESS: [ + None, UNIT_BAR, "Central Heating Water Pressure"], + gw_vars.DATA_DHW_FLOW_RATE: [None, UNIT_L_MIN, "Hot Water Flow Rate"], + gw_vars.DATA_ROOM_SETPOINT_2: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, "Room Setpoint 2"], + gw_vars.DATA_ROOM_TEMP: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, "Room Temperature"], + gw_vars.DATA_CH_WATER_TEMP: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, + "Central Heating Water Temperature"], + gw_vars.DATA_DHW_TEMP: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, "Hot Water Temperature"], + gw_vars.DATA_OUTSIDE_TEMP: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, "Outside Temperature"], + gw_vars.DATA_RETURN_WATER_TEMP: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, + "Return Water Temperature"], + gw_vars.DATA_SOLAR_STORAGE_TEMP: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, + "Solar Storage Temperature"], + gw_vars.DATA_SOLAR_COLL_TEMP: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, + "Solar Collector Temperature"], + gw_vars.DATA_CH_WATER_TEMP_2: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, + "Central Heating 2 Water Temperature"], + gw_vars.DATA_DHW_TEMP_2: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, "Hot Water 2 Temperature"], + gw_vars.DATA_EXHAUST_TEMP: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, "Exhaust Temperature"], + gw_vars.DATA_SLAVE_DHW_MAX_SETP: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, + "Hot Water Maximum Setpoint"], + gw_vars.DATA_SLAVE_DHW_MIN_SETP: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, + "Hot Water Minimum Setpoint"], + gw_vars.DATA_SLAVE_CH_MAX_SETP: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, + "Boiler Maximum Central Heating Setpoint"], + gw_vars.DATA_SLAVE_CH_MIN_SETP: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, + "Boiler Minimum Central Heating Setpoint"], + gw_vars.DATA_DHW_SETPOINT: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, "Hot Water Setpoint"], + gw_vars.DATA_MAX_CH_SETPOINT: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, + "Maximum Central Heating Setpoint"], + gw_vars.DATA_OEM_DIAG: [None, None, "OEM Diagnostic Code"], + gw_vars.DATA_TOTAL_BURNER_STARTS: [ + None, None, "Total Burner Starts"], + gw_vars.DATA_CH_PUMP_STARTS: [ + None, None, "Central Heating Pump Starts"], + gw_vars.DATA_DHW_PUMP_STARTS: [None, None, "Hot Water Pump Starts"], + gw_vars.DATA_DHW_BURNER_STARTS: [ + None, None, "Hot Water Burner Starts"], + gw_vars.DATA_TOTAL_BURNER_HOURS: [ + None, UNIT_HOUR, "Total Burner Hours"], + gw_vars.DATA_CH_PUMP_HOURS: [ + None, UNIT_HOUR, "Central Heating Pump Hours"], + gw_vars.DATA_DHW_PUMP_HOURS: [None, UNIT_HOUR, "Hot Water Pump Hours"], + gw_vars.DATA_DHW_BURNER_HOURS: [ + None, UNIT_HOUR, "Hot Water Burner Hours"], + gw_vars.DATA_MASTER_OT_VERSION: [ + None, None, "Thermostat OpenTherm Version"], + gw_vars.DATA_SLAVE_OT_VERSION: [ + None, None, "Boiler OpenTherm Version"], + gw_vars.DATA_MASTER_PRODUCT_TYPE: [ + None, None, "Thermostat Product Type"], + gw_vars.DATA_MASTER_PRODUCT_VERSION: [ + None, None, "Thermostat Product Version"], + gw_vars.DATA_SLAVE_PRODUCT_TYPE: [None, None, "Boiler Product Type"], + gw_vars.DATA_SLAVE_PRODUCT_VERSION: [ + None, None, "Boiler Product Version"], + gw_vars.OTGW_MODE: [None, None, "Gateway/Monitor Mode"], + gw_vars.OTGW_DHW_OVRD: [None, None, "Gateway Hot Water Override Mode"], + gw_vars.OTGW_ABOUT: [None, None, "Gateway Firmware Version"], + gw_vars.OTGW_BUILD: [None, None, "Gateway Firmware Build"], + gw_vars.OTGW_CLOCKMHZ: [None, None, "Gateway Clock Speed"], + gw_vars.OTGW_LED_A: [None, None, "Gateway LED A Mode"], + gw_vars.OTGW_LED_B: [None, None, "Gateway LED B Mode"], + gw_vars.OTGW_LED_C: [None, None, "Gateway LED C Mode"], + gw_vars.OTGW_LED_D: [None, None, "Gateway LED D Mode"], + gw_vars.OTGW_LED_E: [None, None, "Gateway LED E Mode"], + gw_vars.OTGW_LED_F: [None, None, "Gateway LED F Mode"], + gw_vars.OTGW_GPIO_A: [None, None, "Gateway GPIO A Mode"], + gw_vars.OTGW_GPIO_B: [None, None, "Gateway GPIO B Mode"], + gw_vars.OTGW_SB_TEMP: [ + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, + "Gateway Setback Temperature"], + gw_vars.OTGW_SETP_OVRD_MODE: [ + None, None, "Gateway Room Setpoint Override Mode"], + gw_vars.OTGW_SMART_PWR: [None, None, "Gateway Smart Power Mode"], + gw_vars.OTGW_THRM_DETECT: [None, None, "Gateway Thermostat Detection"], + gw_vars.OTGW_VREF: [None, None, "Gateway Reference Voltage Setting"], + } + sensors = [] + for var in discovery_info: + device_class = sensor_info[var][0] + unit = sensor_info[var][1] + friendly_name = sensor_info[var][2] + entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, var, hass=hass) + sensors.append( + OpenThermSensor(entity_id, var, device_class, unit, friendly_name)) + async_add_entities(sensors) + + +class OpenThermSensor(Entity): + """Representation of an OpenTherm Gateway sensor.""" + + def __init__(self, entity_id, var, device_class, unit, friendly_name): + """Initialize the sensor.""" + self.entity_id = entity_id + self._var = var + self._value = None + self._device_class = device_class + self._unit = unit + self._friendly_name = friendly_name + + async def async_added_to_hass(self): + """Subscribe to updates from the component.""" + _LOGGER.debug("Added OpenTherm Gateway sensor %s", self._friendly_name) + async_dispatcher_connect(self.hass, SIGNAL_OPENTHERM_GW_UPDATE, + self.receive_report) + + async def receive_report(self, status): + """Handle status updates from the component.""" + value = status.get(self._var) + if isinstance(value, float): + value = '{:2.1f}'.format(value) + self._value = value + self.async_schedule_update_ha_state() + + @property + def name(self): + """Return the friendly name of the sensor.""" + return self._friendly_name + + @property + def device_class(self): + """Return the device class.""" + return self._device_class + + @property + def state(self): + """Return the state of the device.""" + return self._value + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self._unit + + @property + def should_poll(self): + """Return False because entity pushes its state.""" + return False diff --git a/requirements_all.txt b/requirements_all.txt index 59e62006795..c79ad40059b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1033,7 +1033,7 @@ pyoppleio==1.0.5 pyota==2.0.5 # homeassistant.components.opentherm_gw -pyotgw==0.1b0 +pyotgw==0.2b1 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp