diff --git a/CODEOWNERS b/CODEOWNERS index 60703b8cf42..dfdd9a4d396 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -184,6 +184,7 @@ homeassistant/components/nsw_fuel_station/* @nickw444 homeassistant/components/nuki/* @pschmitt homeassistant/components/ohmconnect/* @robbiet480 homeassistant/components/onboarding/* @home-assistant/core +homeassistant/components/opentherm_gw/* @mvn23 homeassistant/components/openuv/* @bachya homeassistant/components/openweathermap/* @fabaff homeassistant/components/orangepi_gpio/* @pascallj diff --git a/homeassistant/components/opentherm_gw/__init__.py b/homeassistant/components/opentherm_gw/__init__.py index 829344fb1f0..cb8f22bbc3f 100644 --- a/homeassistant/components/opentherm_gw/__init__.py +++ b/homeassistant/components/opentherm_gw/__init__.py @@ -2,357 +2,256 @@ import logging from datetime import datetime, date +import pyotgw +import pyotgw.vars as gw_vars import voluptuous as vol from homeassistant.components.binary_sensor import DOMAIN as COMP_BINARY_SENSOR +from homeassistant.components.climate import DOMAIN as COMP_CLIMATE from homeassistant.components.sensor import DOMAIN as COMP_SENSOR from homeassistant.const import ( - ATTR_DATE, ATTR_ID, ATTR_TEMPERATURE, ATTR_TIME, CONF_DEVICE, - CONF_MONITORED_VARIABLES, CONF_NAME, EVENT_HOMEASSISTANT_STOP, - PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE) + ATTR_DATE, ATTR_ID, ATTR_TEMPERATURE, ATTR_TIME, CONF_DEVICE, CONF_NAME, + EVENT_HOMEASSISTANT_STOP, PRECISION_HALVES, PRECISION_TENTHS, + PRECISION_WHOLE) from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import async_dispatcher_send import homeassistant.helpers.config_validation as cv +from .const import ( + ATTR_GW_ID, ATTR_MODE, ATTR_LEVEL, CONF_CLIMATE, CONF_FLOOR_TEMP, + CONF_PRECISION, DATA_GATEWAYS, DATA_OPENTHERM_GW, SERVICE_RESET_GATEWAY, + SERVICE_SET_CLOCK, SERVICE_SET_CONTROL_SETPOINT, SERVICE_SET_GPIO_MODE, + SERVICE_SET_LED_MODE, SERVICE_SET_MAX_MOD, SERVICE_SET_OAT, + SERVICE_SET_SB_TEMP) + + _LOGGER = logging.getLogger(__name__) DOMAIN = 'opentherm_gw' -ATTR_MODE = 'mode' -ATTR_LEVEL = 'level' - -CONF_CLIMATE = 'climate' -CONF_FLOOR_TEMP = 'floor_temperature' -CONF_PRECISION = 'precision' - -DATA_DEVICE = 'device' -DATA_GW_VARS = 'gw_vars' -DATA_LATEST_STATUS = 'latest_status' -DATA_OPENTHERM_GW = 'opentherm_gw' - -SIGNAL_OPENTHERM_GW_UPDATE = 'opentherm_gw_update' - -SERVICE_RESET_GATEWAY = 'reset_gateway' - -SERVICE_SET_CLOCK = 'set_clock' -SERVICE_SET_CLOCK_SCHEMA = vol.Schema({ - vol.Optional(ATTR_DATE, default=date.today()): cv.date, - vol.Optional(ATTR_TIME, default=datetime.now().time()): cv.time, -}) - -SERVICE_SET_CONTROL_SETPOINT = 'set_control_setpoint' -SERVICE_SET_CONTROL_SETPOINT_SCHEMA = vol.Schema({ - vol.Required(ATTR_TEMPERATURE): vol.All(vol.Coerce(float), - vol.Range(min=0, max=90)), -}) - -SERVICE_SET_GPIO_MODE = 'set_gpio_mode' -SERVICE_SET_GPIO_MODE_SCHEMA = vol.Schema(vol.Any( - vol.Schema({ - vol.Required(ATTR_ID): vol.Equal('A'), - vol.Required(ATTR_MODE): vol.All(vol.Coerce(int), - vol.Range(min=0, max=6)), - }), - vol.Schema({ - vol.Required(ATTR_ID): vol.Equal('B'), - vol.Required(ATTR_MODE): vol.All(vol.Coerce(int), - vol.Range(min=0, max=7)), - }), -)) - -SERVICE_SET_LED_MODE = 'set_led_mode' -SERVICE_SET_LED_MODE_SCHEMA = vol.Schema({ - vol.Required(ATTR_ID): vol.In('ABCDEF'), - vol.Required(ATTR_MODE): vol.In('RXTBOFHWCEMP'), -}) - -SERVICE_SET_MAX_MOD = 'set_max_modulation' -SERVICE_SET_MAX_MOD_SCHEMA = vol.Schema({ - vol.Required(ATTR_LEVEL): vol.All(vol.Coerce(int), - vol.Range(min=-1, max=100)) -}) - -SERVICE_SET_OAT = 'set_outside_temperature' -SERVICE_SET_OAT_SCHEMA = vol.Schema({ - vol.Required(ATTR_TEMPERATURE): vol.All(vol.Coerce(float), - vol.Range(min=-40, max=99)), -}) - -SERVICE_SET_SB_TEMP = 'set_setback_temperature' -SERVICE_SET_SB_TEMP_SCHEMA = vol.Schema({ - vol.Required(ATTR_TEMPERATURE): vol.All(vol.Coerce(float), - vol.Range(min=0, max=30)), -}) - CLIMATE_SCHEMA = vol.Schema({ - vol.Optional(CONF_NAME, default="OpenTherm Gateway"): cv.string, vol.Optional(CONF_PRECISION): vol.In([PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE]), vol.Optional(CONF_FLOOR_TEMP, default=False): cv.boolean, }) CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ + DOMAIN: cv.schema_with_slug_keys({ 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]), + vol.Optional(CONF_NAME): cv.string, }), }, extra=vol.ALLOW_EXTRA) async def async_setup(hass, config): """Set up the OpenTherm Gateway component.""" - import pyotgw conf = config[DOMAIN] - gateway = pyotgw.pyotgw() - monitored_vars = conf.get(CONF_MONITORED_VARIABLES) - hass.data[DATA_OPENTHERM_GW] = { - DATA_DEVICE: gateway, - DATA_GW_VARS: pyotgw.vars, - DATA_LATEST_STATUS: {} - } - hass.async_create_task(register_services(hass, gateway)) - hass.async_create_task(async_load_platform( - hass, 'climate', DOMAIN, conf.get(CONF_CLIMATE), config)) - if monitored_vars: - hass.async_create_task(setup_monitored_vars( - hass, config, monitored_vars)) - # Schedule directly on the loop to avoid blocking HA startup. - hass.loop.create_task( - connect_and_subscribe(hass, conf[CONF_DEVICE], gateway)) + hass.data[DATA_OPENTHERM_GW] = {DATA_GATEWAYS: {}} + for gw_id, cfg in conf.items(): + gateway = OpenThermGatewayDevice(hass, gw_id, cfg) + hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][gw_id] = gateway + hass.async_create_task(async_load_platform(hass, COMP_CLIMATE, DOMAIN, + gw_id, config)) + hass.async_create_task(async_load_platform(hass, COMP_BINARY_SENSOR, + DOMAIN, gw_id, config)) + hass.async_create_task(async_load_platform(hass, COMP_SENSOR, DOMAIN, + gw_id, config)) + # Schedule directly on the loop to avoid blocking HA startup. + hass.loop.create_task(gateway.connect_and_subscribe(cfg[CONF_DEVICE])) + register_services(hass) return True -async def connect_and_subscribe(hass, device_path, gateway): - """Connect to serial device and subscribe report handler.""" - await gateway.connect(hass.loop, device_path) - _LOGGER.debug("Connected to OpenTherm Gateway at %s", device_path) - - async def cleanup(event): - """Reset overrides on the gateway.""" - await gateway.set_control_setpoint(0) - await gateway.set_max_relative_mod('-') - hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, cleanup) - - async def handle_report(status): - """Handle reports from the OpenTherm Gateway.""" - _LOGGER.debug("Received report: %s", status) - hass.data[DATA_OPENTHERM_GW][DATA_LATEST_STATUS] = status - async_dispatcher_send(hass, SIGNAL_OPENTHERM_GW_UPDATE, status) - gateway.subscribe(handle_report) - - -async def register_services(hass, gateway): +def register_services(hass): """Register services for the component.""" - gw_vars = hass.data[DATA_OPENTHERM_GW][DATA_GW_VARS] + service_reset_schema = vol.Schema({ + vol.Required(ATTR_GW_ID): vol.All( + cv.string, vol.In(hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS])), + }) + service_set_clock_schema = vol.Schema({ + vol.Required(ATTR_GW_ID): vol.All( + cv.string, vol.In(hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS])), + vol.Optional(ATTR_DATE, default=date.today()): cv.date, + vol.Optional(ATTR_TIME, default=datetime.now().time()): cv.time, + }) + service_set_control_setpoint_schema = vol.Schema({ + vol.Required(ATTR_GW_ID): vol.All( + cv.string, vol.In(hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS])), + vol.Required(ATTR_TEMPERATURE): vol.All(vol.Coerce(float), + vol.Range(min=0, max=90)), + }) + service_set_gpio_mode_schema = vol.Schema(vol.Any( + vol.Schema({ + vol.Required(ATTR_GW_ID): vol.All( + cv.string, vol.In( + hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS])), + vol.Required(ATTR_ID): vol.Equal('A'), + vol.Required(ATTR_MODE): vol.All(vol.Coerce(int), + vol.Range(min=0, max=6)), + }), + vol.Schema({ + vol.Required(ATTR_GW_ID): vol.All( + cv.string, vol.In( + hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS])), + vol.Required(ATTR_ID): vol.Equal('B'), + vol.Required(ATTR_MODE): vol.All(vol.Coerce(int), + vol.Range(min=0, max=7)), + }), + )) + service_set_led_mode_schema = vol.Schema({ + vol.Required(ATTR_GW_ID): vol.All( + cv.string, vol.In(hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS])), + vol.Required(ATTR_ID): vol.In('ABCDEF'), + vol.Required(ATTR_MODE): vol.In('RXTBOFHWCEMP'), + }) + service_set_max_mod_schema = vol.Schema({ + vol.Required(ATTR_GW_ID): vol.All( + cv.string, vol.In(hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS])), + vol.Required(ATTR_LEVEL): vol.All(vol.Coerce(int), + vol.Range(min=-1, max=100)) + }) + service_set_oat_schema = vol.Schema({ + vol.Required(ATTR_GW_ID): vol.All( + cv.string, vol.In(hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS])), + vol.Required(ATTR_TEMPERATURE): vol.All(vol.Coerce(float), + vol.Range(min=-40, max=99)), + }) + service_set_sb_temp_schema = vol.Schema({ + vol.Required(ATTR_GW_ID): vol.All( + cv.string, vol.In(hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS])), + vol.Required(ATTR_TEMPERATURE): vol.All(vol.Coerce(float), + vol.Range(min=0, max=30)), + }) async def reset_gateway(call): """Reset the OpenTherm Gateway.""" + gw_dev = ( + hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][call.data[ATTR_GW_ID]]) mode_rst = gw_vars.OTGW_MODE_RESET - status = await gateway.set_mode(mode_rst) - hass.data[DATA_OPENTHERM_GW][DATA_LATEST_STATUS] = status - async_dispatcher_send(hass, SIGNAL_OPENTHERM_GW_UPDATE, status) - hass.services.async_register(DOMAIN, SERVICE_RESET_GATEWAY, reset_gateway) + status = await gw_dev.gateway.set_mode(mode_rst) + gw_dev.status = status + async_dispatcher_send(hass, gw_dev.update_signal, gw_dev.status) + hass.services.async_register(DOMAIN, SERVICE_RESET_GATEWAY, reset_gateway, + service_reset_schema) async def set_control_setpoint(call): """Set the control setpoint on the OpenTherm Gateway.""" + gw_dev = ( + hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][call.data[ATTR_GW_ID]]) gw_var = gw_vars.DATA_CONTROL_SETPOINT - value = await gateway.set_control_setpoint(call.data[ATTR_TEMPERATURE]) - status = hass.data[DATA_OPENTHERM_GW][DATA_LATEST_STATUS] - status.update({gw_var: value}) - async_dispatcher_send(hass, SIGNAL_OPENTHERM_GW_UPDATE, status) + value = await gw_dev.gateway.set_control_setpoint( + call.data[ATTR_TEMPERATURE]) + gw_dev.status.update({gw_var: value}) + async_dispatcher_send(hass, gw_dev.update_signal, gw_dev.status) hass.services.async_register(DOMAIN, SERVICE_SET_CONTROL_SETPOINT, set_control_setpoint, - SERVICE_SET_CONTROL_SETPOINT_SCHEMA) + service_set_control_setpoint_schema) async def set_device_clock(call): """Set the clock on the OpenTherm Gateway.""" + gw_dev = ( + hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][call.data[ATTR_GW_ID]]) attr_date = call.data[ATTR_DATE] attr_time = call.data[ATTR_TIME] - await gateway.set_clock(datetime.combine(attr_date, attr_time)) + await gw_dev.gateway.set_clock(datetime.combine(attr_date, attr_time)) hass.services.async_register(DOMAIN, SERVICE_SET_CLOCK, set_device_clock, - SERVICE_SET_CLOCK_SCHEMA) + service_set_clock_schema) async def set_gpio_mode(call): """Set the OpenTherm Gateway GPIO modes.""" + gw_dev = ( + hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][call.data[ATTR_GW_ID]]) gpio_id = call.data[ATTR_ID] gpio_mode = call.data[ATTR_MODE] - mode = await gateway.set_gpio_mode(gpio_id, gpio_mode) + mode = await gw_dev.gateway.set_gpio_mode(gpio_id, gpio_mode) gpio_var = getattr(gw_vars, 'OTGW_GPIO_{}'.format(gpio_id)) - status = hass.data[DATA_OPENTHERM_GW][DATA_LATEST_STATUS] - status.update({gpio_var: mode}) - async_dispatcher_send(hass, SIGNAL_OPENTHERM_GW_UPDATE, status) + gw_dev.status.update({gpio_var: mode}) + async_dispatcher_send(hass, gw_dev.update_signal, gw_dev.status) hass.services.async_register(DOMAIN, SERVICE_SET_GPIO_MODE, set_gpio_mode, - SERVICE_SET_GPIO_MODE_SCHEMA) + service_set_gpio_mode_schema) async def set_led_mode(call): """Set the OpenTherm Gateway LED modes.""" + gw_dev = ( + hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][call.data[ATTR_GW_ID]]) led_id = call.data[ATTR_ID] led_mode = call.data[ATTR_MODE] - mode = await gateway.set_led_mode(led_id, led_mode) + mode = await gw_dev.gateway.set_led_mode(led_id, led_mode) led_var = getattr(gw_vars, 'OTGW_LED_{}'.format(led_id)) - status = hass.data[DATA_OPENTHERM_GW][DATA_LATEST_STATUS] - status.update({led_var: mode}) - async_dispatcher_send(hass, SIGNAL_OPENTHERM_GW_UPDATE, status) + gw_dev.status.update({led_var: mode}) + async_dispatcher_send(hass, gw_dev.update_signal, gw_dev.status) hass.services.async_register(DOMAIN, SERVICE_SET_LED_MODE, set_led_mode, - SERVICE_SET_LED_MODE_SCHEMA) + service_set_led_mode_schema) async def set_max_mod(call): """Set the max modulation level.""" + gw_dev = ( + hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][call.data[ATTR_GW_ID]]) gw_var = gw_vars.DATA_SLAVE_MAX_RELATIVE_MOD level = call.data[ATTR_LEVEL] if level == -1: # Backend only clears setting on non-numeric values. level = '-' - value = await gateway.set_max_relative_mod(level) - status = hass.data[DATA_OPENTHERM_GW][DATA_LATEST_STATUS] - status.update({gw_var: value}) - async_dispatcher_send(hass, SIGNAL_OPENTHERM_GW_UPDATE, status) + value = await gw_dev.gateway.set_max_relative_mod(level) + gw_dev.status.update({gw_var: value}) + async_dispatcher_send(hass, gw_dev.update_signal, gw_dev.status) hass.services.async_register(DOMAIN, SERVICE_SET_MAX_MOD, set_max_mod, - SERVICE_SET_MAX_MOD_SCHEMA) + service_set_max_mod_schema) async def set_outside_temp(call): """Provide the outside temperature to the OpenTherm Gateway.""" + gw_dev = ( + hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][call.data[ATTR_GW_ID]]) gw_var = gw_vars.DATA_OUTSIDE_TEMP - value = await gateway.set_outside_temp(call.data[ATTR_TEMPERATURE]) - status = hass.data[DATA_OPENTHERM_GW][DATA_LATEST_STATUS] - status.update({gw_var: value}) - async_dispatcher_send(hass, SIGNAL_OPENTHERM_GW_UPDATE, status) + value = await gw_dev.gateway.set_outside_temp( + call.data[ATTR_TEMPERATURE]) + gw_dev.status.update({gw_var: value}) + async_dispatcher_send(hass, gw_dev.update_signal, gw_dev.status) hass.services.async_register(DOMAIN, SERVICE_SET_OAT, set_outside_temp, - SERVICE_SET_OAT_SCHEMA) + service_set_oat_schema) async def set_setback_temp(call): """Set the OpenTherm Gateway SetBack temperature.""" + gw_dev = ( + hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][call.data[ATTR_GW_ID]]) gw_var = gw_vars.OTGW_SB_TEMP - value = await gateway.set_setback_temp(call.data[ATTR_TEMPERATURE]) - status = hass.data[DATA_OPENTHERM_GW][DATA_LATEST_STATUS] - status.update({gw_var: value}) - async_dispatcher_send(hass, SIGNAL_OPENTHERM_GW_UPDATE, status) + value = await gw_dev.gateway.set_setback_temp( + call.data[ATTR_TEMPERATURE]) + gw_dev.status.update({gw_var: value}) + async_dispatcher_send(hass, gw_dev.update_signal, gw_dev.status) hass.services.async_register(DOMAIN, SERVICE_SET_SB_TEMP, set_setback_temp, - SERVICE_SET_SB_TEMP_SCHEMA) + service_set_sb_temp_schema) -async def setup_monitored_vars(hass, config, monitored_vars): - """Set up requested sensors.""" - gw_vars = hass.data[DATA_OPENTHERM_GW][DATA_GW_VARS] - sensor_type_map = { - COMP_BINARY_SENSOR: [ - gw_vars.DATA_MASTER_CH_ENABLED, - gw_vars.DATA_MASTER_DHW_ENABLED, - gw_vars.DATA_MASTER_COOLING_ENABLED, - gw_vars.DATA_MASTER_OTC_ENABLED, - gw_vars.DATA_MASTER_CH2_ENABLED, - gw_vars.DATA_SLAVE_FAULT_IND, - gw_vars.DATA_SLAVE_CH_ACTIVE, - gw_vars.DATA_SLAVE_DHW_ACTIVE, - gw_vars.DATA_SLAVE_FLAME_ON, - gw_vars.DATA_SLAVE_COOLING_ACTIVE, - gw_vars.DATA_SLAVE_CH2_ACTIVE, - gw_vars.DATA_SLAVE_DIAG_IND, - gw_vars.DATA_SLAVE_DHW_PRESENT, - gw_vars.DATA_SLAVE_CONTROL_TYPE, - gw_vars.DATA_SLAVE_COOLING_SUPPORTED, - gw_vars.DATA_SLAVE_DHW_CONFIG, - gw_vars.DATA_SLAVE_MASTER_LOW_OFF_PUMP, - gw_vars.DATA_SLAVE_CH2_PRESENT, - gw_vars.DATA_SLAVE_SERVICE_REQ, - gw_vars.DATA_SLAVE_REMOTE_RESET, - gw_vars.DATA_SLAVE_LOW_WATER_PRESS, - gw_vars.DATA_SLAVE_GAS_FAULT, - gw_vars.DATA_SLAVE_AIR_PRESS_FAULT, - gw_vars.DATA_SLAVE_WATER_OVERTEMP, - gw_vars.DATA_REMOTE_TRANSFER_DHW, - gw_vars.DATA_REMOTE_TRANSFER_MAX_CH, - gw_vars.DATA_REMOTE_RW_DHW, - gw_vars.DATA_REMOTE_RW_MAX_CH, - gw_vars.DATA_ROVRD_MAN_PRIO, - gw_vars.DATA_ROVRD_AUTO_PRIO, - gw_vars.OTGW_GPIO_A_STATE, - gw_vars.OTGW_GPIO_B_STATE, - gw_vars.OTGW_IGNORE_TRANSITIONS, - gw_vars.OTGW_OVRD_HB, - ], - 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, - ] - } - binary_sensors = [] - sensors = [] - for var in monitored_vars: - if var in sensor_type_map[COMP_SENSOR]: - sensors.append(var) - elif var in sensor_type_map[COMP_BINARY_SENSOR]: - binary_sensors.append(var) - else: - _LOGGER.error("Monitored variable not supported: %s", var) - if binary_sensors: - hass.async_create_task(async_load_platform( - hass, COMP_BINARY_SENSOR, DOMAIN, binary_sensors, config)) - if sensors: - hass.async_create_task(async_load_platform( - hass, COMP_SENSOR, DOMAIN, sensors, config)) +class OpenThermGatewayDevice(): + """OpenTherm Gateway device class.""" + + def __init__(self, hass, gw_id, config): + """Initialize the OpenTherm Gateway.""" + self.hass = hass + self.gw_id = gw_id + self.name = config.get(CONF_NAME, gw_id) + self.climate_config = config[CONF_CLIMATE] + self.status = {} + self.update_signal = '{}_{}_update'.format(DATA_OPENTHERM_GW, gw_id) + self.gateway = pyotgw.pyotgw() + + async def connect_and_subscribe(self, device_path): + """Connect to serial device and subscribe report handler.""" + await self.gateway.connect(self.hass.loop, device_path) + _LOGGER.debug("Connected to OpenTherm Gateway at %s", device_path) + + async def cleanup(event): + """Reset overrides on the gateway.""" + await self.gateway.set_control_setpoint(0) + await self.gateway.set_max_relative_mod('-') + self.hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, cleanup) + + async def handle_report(status): + """Handle reports from the OpenTherm Gateway.""" + _LOGGER.debug("Received report: %s", status) + self.status = status + async_dispatcher_send(self.hass, self.update_signal, status) + self.gateway.subscribe(handle_report) diff --git a/homeassistant/components/opentherm_gw/binary_sensor.py b/homeassistant/components/opentherm_gw/binary_sensor.py index bf342cc9813..8c70bd769d4 100644 --- a/homeassistant/components/opentherm_gw/binary_sensor.py +++ b/homeassistant/components/opentherm_gw/binary_sensor.py @@ -3,116 +3,54 @@ import logging from homeassistant.components.binary_sensor import ( ENTITY_ID_FORMAT, BinarySensorDevice) +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import async_generate_entity_id -from . import DATA_GW_VARS, DATA_OPENTHERM_GW, SIGNAL_OPENTHERM_GW_UPDATE +from .const import BINARY_SENSOR_INFO, DATA_GATEWAYS, DATA_OPENTHERM_GW + _LOGGER = logging.getLogger(__name__) -DEVICE_CLASS_COLD = 'cold' -DEVICE_CLASS_HEAT = 'heat' -DEVICE_CLASS_PROBLEM = 'problem' - async def async_setup_platform( hass, config, async_add_entities, discovery_info=None): """Set up the OpenTherm Gateway binary sensors.""" if discovery_info is None: return - gw_vars = hass.data[DATA_OPENTHERM_GW][DATA_GW_VARS] - sensor_info = { - # [device_class, friendly_name] - gw_vars.DATA_MASTER_CH_ENABLED: [ - None, "Thermostat Central Heating Enabled"], - gw_vars.DATA_MASTER_DHW_ENABLED: [ - None, "Thermostat Hot Water Enabled"], - gw_vars.DATA_MASTER_COOLING_ENABLED: [ - None, "Thermostat Cooling Enabled"], - gw_vars.DATA_MASTER_OTC_ENABLED: [ - None, "Thermostat Outside Temperature Correction Enabled"], - gw_vars.DATA_MASTER_CH2_ENABLED: [ - None, "Thermostat Central Heating 2 Enabled"], - gw_vars.DATA_SLAVE_FAULT_IND: [ - DEVICE_CLASS_PROBLEM, "Boiler Fault Indication"], - gw_vars.DATA_SLAVE_CH_ACTIVE: [ - DEVICE_CLASS_HEAT, "Boiler Central Heating Status"], - gw_vars.DATA_SLAVE_DHW_ACTIVE: [ - DEVICE_CLASS_HEAT, "Boiler Hot Water Status"], - gw_vars.DATA_SLAVE_FLAME_ON: [ - DEVICE_CLASS_HEAT, "Boiler Flame Status"], - gw_vars.DATA_SLAVE_COOLING_ACTIVE: [ - DEVICE_CLASS_COLD, "Boiler Cooling Status"], - gw_vars.DATA_SLAVE_CH2_ACTIVE: [ - DEVICE_CLASS_HEAT, "Boiler Central Heating 2 Status"], - gw_vars.DATA_SLAVE_DIAG_IND: [ - DEVICE_CLASS_PROBLEM, "Boiler Diagnostics Indication"], - gw_vars.DATA_SLAVE_DHW_PRESENT: [None, "Boiler Hot Water Present"], - gw_vars.DATA_SLAVE_CONTROL_TYPE: [None, "Boiler Control Type"], - gw_vars.DATA_SLAVE_COOLING_SUPPORTED: [None, "Boiler Cooling Support"], - gw_vars.DATA_SLAVE_DHW_CONFIG: [ - None, "Boiler Hot Water Configuration"], - gw_vars.DATA_SLAVE_MASTER_LOW_OFF_PUMP: [ - None, "Boiler Pump Commands Support"], - gw_vars.DATA_SLAVE_CH2_PRESENT: [ - None, "Boiler Central Heating 2 Present"], - gw_vars.DATA_SLAVE_SERVICE_REQ: [ - DEVICE_CLASS_PROBLEM, "Boiler Service Required"], - gw_vars.DATA_SLAVE_REMOTE_RESET: [None, "Boiler Remote Reset Support"], - gw_vars.DATA_SLAVE_LOW_WATER_PRESS: [ - DEVICE_CLASS_PROBLEM, "Boiler Low Water Pressure"], - gw_vars.DATA_SLAVE_GAS_FAULT: [ - DEVICE_CLASS_PROBLEM, "Boiler Gas Fault"], - gw_vars.DATA_SLAVE_AIR_PRESS_FAULT: [ - DEVICE_CLASS_PROBLEM, "Boiler Air Pressure Fault"], - gw_vars.DATA_SLAVE_WATER_OVERTEMP: [ - DEVICE_CLASS_PROBLEM, "Boiler Water Overtemperature"], - gw_vars.DATA_REMOTE_TRANSFER_DHW: [ - None, "Remote Hot Water Setpoint Transfer Support"], - gw_vars.DATA_REMOTE_TRANSFER_MAX_CH: [ - None, "Remote Maximum Central Heating Setpoint Write Support"], - gw_vars.DATA_REMOTE_RW_DHW: [ - None, "Remote Hot Water Setpoint Write Support"], - gw_vars.DATA_REMOTE_RW_MAX_CH: [ - None, "Remote Central Heating Setpoint Write Support"], - gw_vars.DATA_ROVRD_MAN_PRIO: [ - None, "Remote Override Manual Change Priority"], - gw_vars.DATA_ROVRD_AUTO_PRIO: [ - None, "Remote Override Program Change Priority"], - gw_vars.OTGW_GPIO_A_STATE: [None, "Gateway GPIO A State"], - gw_vars.OTGW_GPIO_B_STATE: [None, "Gateway GPIO B State"], - gw_vars.OTGW_IGNORE_TRANSITIONS: [None, "Gateway Ignore Transitions"], - gw_vars.OTGW_OVRD_HB: [None, "Gateway Override High Byte"], - } + gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][discovery_info] sensors = [] - for var in discovery_info: - device_class = sensor_info[var][0] - friendly_name = sensor_info[var][1] - entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, var, hass=hass) - sensors.append(OpenThermBinarySensor(entity_id, var, device_class, - friendly_name)) + for var, info in BINARY_SENSOR_INFO.items(): + device_class = info[0] + friendly_name_format = info[1] + sensors.append(OpenThermBinarySensor(gw_dev, var, device_class, + friendly_name_format)) async_add_entities(sensors) class OpenThermBinarySensor(BinarySensorDevice): """Represent an OpenTherm Gateway binary sensor.""" - def __init__(self, entity_id, var, device_class, friendly_name): + def __init__(self, gw_dev, var, device_class, friendly_name_format): """Initialize the binary sensor.""" - self.entity_id = entity_id + self.entity_id = async_generate_entity_id( + ENTITY_ID_FORMAT, '{}_{}'.format(var, gw_dev.gw_id), + hass=gw_dev.hass) + self._gateway = gw_dev self._var = var self._state = None self._device_class = device_class - self._friendly_name = friendly_name + self._friendly_name = friendly_name_format.format(gw_dev.name) async def async_added_to_hass(self): """Subscribe to updates from the component.""" _LOGGER.debug( "Added OpenTherm Gateway binary sensor %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): """Handle status updates from the component.""" self._state = bool(status.get(self._var)) self.async_schedule_update_ha_state() diff --git a/homeassistant/components/opentherm_gw/climate.py b/homeassistant/components/opentherm_gw/climate.py index 2dbd7f3cf79..21d9d65adfd 100644 --- a/homeassistant/components/opentherm_gw/climate.py +++ b/homeassistant/components/opentherm_gw/climate.py @@ -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 diff --git a/homeassistant/components/opentherm_gw/const.py b/homeassistant/components/opentherm_gw/const.py new file mode 100644 index 00000000000..8e07aa124aa --- /dev/null +++ b/homeassistant/components/opentherm_gw/const.py @@ -0,0 +1,215 @@ +"""Constants for the opentherm_gw integration.""" +import pyotgw.vars as gw_vars + +from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS + +ATTR_GW_ID = 'gateway_id' +ATTR_MODE = 'mode' +ATTR_LEVEL = 'level' + +CONF_CLIMATE = 'climate' +CONF_FLOOR_TEMP = 'floor_temperature' +CONF_PRECISION = 'precision' + +DATA_GATEWAYS = 'gateways' +DATA_OPENTHERM_GW = 'opentherm_gw' + +DEVICE_CLASS_COLD = 'cold' +DEVICE_CLASS_HEAT = 'heat' +DEVICE_CLASS_PROBLEM = 'problem' + +SERVICE_RESET_GATEWAY = 'reset_gateway' +SERVICE_SET_CLOCK = 'set_clock' +SERVICE_SET_CONTROL_SETPOINT = 'set_control_setpoint' +SERVICE_SET_GPIO_MODE = 'set_gpio_mode' +SERVICE_SET_LED_MODE = 'set_led_mode' +SERVICE_SET_MAX_MOD = 'set_max_modulation' +SERVICE_SET_OAT = 'set_outside_temperature' +SERVICE_SET_SB_TEMP = 'set_setback_temperature' + +UNIT_BAR = 'bar' +UNIT_HOUR = 'h' +UNIT_KW = 'kW' +UNIT_L_MIN = 'L/min' +UNIT_PERCENT = '%' + +BINARY_SENSOR_INFO = { + # [device_class, friendly_name format] + gw_vars.DATA_MASTER_CH_ENABLED: [ + None, "Thermostat Central Heating Enabled {}"], + gw_vars.DATA_MASTER_DHW_ENABLED: [None, "Thermostat Hot Water Enabled {}"], + gw_vars.DATA_MASTER_COOLING_ENABLED: [ + None, "Thermostat Cooling Enabled {}"], + gw_vars.DATA_MASTER_OTC_ENABLED: [ + None, "Thermostat Outside Temperature Correction Enabled {}"], + gw_vars.DATA_MASTER_CH2_ENABLED: [ + None, "Thermostat Central Heating 2 Enabled {}"], + gw_vars.DATA_SLAVE_FAULT_IND: [ + DEVICE_CLASS_PROBLEM, "Boiler Fault Indication {}"], + gw_vars.DATA_SLAVE_CH_ACTIVE: [ + DEVICE_CLASS_HEAT, "Boiler Central Heating Status {}"], + gw_vars.DATA_SLAVE_DHW_ACTIVE: [ + DEVICE_CLASS_HEAT, "Boiler Hot Water Status {}"], + gw_vars.DATA_SLAVE_FLAME_ON: [DEVICE_CLASS_HEAT, "Boiler Flame Status {}"], + gw_vars.DATA_SLAVE_COOLING_ACTIVE: [ + DEVICE_CLASS_COLD, "Boiler Cooling Status {}"], + gw_vars.DATA_SLAVE_CH2_ACTIVE: [ + DEVICE_CLASS_HEAT, "Boiler Central Heating 2 Status {}"], + gw_vars.DATA_SLAVE_DIAG_IND: [ + DEVICE_CLASS_PROBLEM, "Boiler Diagnostics Indication {}"], + gw_vars.DATA_SLAVE_DHW_PRESENT: [None, "Boiler Hot Water Present {}"], + gw_vars.DATA_SLAVE_CONTROL_TYPE: [None, "Boiler Control Type {}"], + gw_vars.DATA_SLAVE_COOLING_SUPPORTED: [None, "Boiler Cooling Support {}"], + gw_vars.DATA_SLAVE_DHW_CONFIG: [None, "Boiler Hot Water Configuration {}"], + gw_vars.DATA_SLAVE_MASTER_LOW_OFF_PUMP: [ + None, "Boiler Pump Commands Support {}"], + gw_vars.DATA_SLAVE_CH2_PRESENT: [ + None, "Boiler Central Heating 2 Present {}"], + gw_vars.DATA_SLAVE_SERVICE_REQ: [ + DEVICE_CLASS_PROBLEM, "Boiler Service Required {}"], + gw_vars.DATA_SLAVE_REMOTE_RESET: [None, "Boiler Remote Reset Support {}"], + gw_vars.DATA_SLAVE_LOW_WATER_PRESS: [ + DEVICE_CLASS_PROBLEM, "Boiler Low Water Pressure {}"], + gw_vars.DATA_SLAVE_GAS_FAULT: [ + DEVICE_CLASS_PROBLEM, "Boiler Gas Fault {}"], + gw_vars.DATA_SLAVE_AIR_PRESS_FAULT: [ + DEVICE_CLASS_PROBLEM, "Boiler Air Pressure Fault {}"], + gw_vars.DATA_SLAVE_WATER_OVERTEMP: [ + DEVICE_CLASS_PROBLEM, "Boiler Water Overtemperature {}"], + gw_vars.DATA_REMOTE_TRANSFER_DHW: [ + None, "Remote Hot Water Setpoint Transfer Support {}"], + gw_vars.DATA_REMOTE_TRANSFER_MAX_CH: [ + None, "Remote Maximum Central Heating Setpoint Write Support {}"], + gw_vars.DATA_REMOTE_RW_DHW: [ + None, "Remote Hot Water Setpoint Write Support {}"], + gw_vars.DATA_REMOTE_RW_MAX_CH: [ + None, "Remote Central Heating Setpoint Write Support {}"], + gw_vars.DATA_ROVRD_MAN_PRIO: [ + None, "Remote Override Manual Change Priority {}"], + gw_vars.DATA_ROVRD_AUTO_PRIO: [ + None, "Remote Override Program Change Priority {}"], + gw_vars.OTGW_GPIO_A_STATE: [None, "Gateway GPIO A State {}"], + gw_vars.OTGW_GPIO_B_STATE: [None, "Gateway GPIO B State {}"], + gw_vars.OTGW_IGNORE_TRANSITIONS: [None, "Gateway Ignore Transitions {}"], + gw_vars.OTGW_OVRD_HB: [None, "Gateway Override High Byte {}"], +} + +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 {}"], +} diff --git a/homeassistant/components/opentherm_gw/manifest.json b/homeassistant/components/opentherm_gw/manifest.json index 560e30931a3..c6097a01cc4 100644 --- a/homeassistant/components/opentherm_gw/manifest.json +++ b/homeassistant/components/opentherm_gw/manifest.json @@ -6,5 +6,7 @@ "pyotgw==0.4b4" ], "dependencies": [], - "codeowners": [] + "codeowners": [ + "@mvn23" + ] } diff --git a/homeassistant/components/opentherm_gw/sensor.py b/homeassistant/components/opentherm_gw/sensor.py index 60ccedfd451..2739f006d81 100644 --- a/homeassistant/components/opentherm_gw/sensor.py +++ b/homeassistant/components/opentherm_gw/sensor.py @@ -2,175 +2,55 @@ import logging from homeassistant.components.sensor import ENTITY_ID_FORMAT -from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity, async_generate_entity_id -from . import DATA_GW_VARS, DATA_OPENTHERM_GW, SIGNAL_OPENTHERM_GW_UPDATE +from .const import DATA_GATEWAYS, DATA_OPENTHERM_GW, SENSOR_INFO + _LOGGER = logging.getLogger(__name__) -UNIT_BAR = 'bar' -UNIT_HOUR = 'h' -UNIT_KW = 'kW' -UNIT_L_MIN = 'L/min' -UNIT_PERCENT = '%' - 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"], - } + gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][discovery_info] 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)) + for var, info in SENSOR_INFO.items(): + device_class = info[0] + unit = info[1] + friendly_name_format = info[2] + sensors.append(OpenThermSensor(gw_dev, var, device_class, unit, + friendly_name_format)) async_add_entities(sensors) class OpenThermSensor(Entity): """Representation of an OpenTherm Gateway sensor.""" - def __init__(self, entity_id, var, device_class, unit, friendly_name): + def __init__(self, gw_dev, var, device_class, unit, friendly_name_format): """Initialize the OpenTherm Gateway sensor.""" - self.entity_id = entity_id + self.entity_id = async_generate_entity_id( + ENTITY_ID_FORMAT, '{}_{}'.format(var, gw_dev.gw_id), + hass=gw_dev.hass) + self._gateway = gw_dev self._var = var self._value = None self._device_class = device_class self._unit = unit - self._friendly_name = friendly_name + self._friendly_name = friendly_name_format.format(gw_dev.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, + async_dispatcher_connect(self.hass, self._gateway.update_signal, self.receive_report) - async def receive_report(self, status): + @callback + def receive_report(self, status): """Handle status updates from the component.""" value = status.get(self._var) if isinstance(value, float): diff --git a/homeassistant/components/opentherm_gw/services.yaml b/homeassistant/components/opentherm_gw/services.yaml index df08ccaa4f9..d8fe2c7e406 100644 --- a/homeassistant/components/opentherm_gw/services.yaml +++ b/homeassistant/components/opentherm_gw/services.yaml @@ -2,10 +2,17 @@ reset_gateway: description: Reset the OpenTherm Gateway. + fields: + gateway_id: + description: The gateway_id of the OpenTherm Gateway. + example: 'opentherm_gateway' set_clock: description: Set the clock and day of the week on the connected thermostat. fields: + gateway_id: + description: The gateway_id of the OpenTherm Gateway. + example: 'opentherm_gateway' date: description: Optional date from which the day of the week will be extracted. Defaults to today. example: '2018-10-23' @@ -18,6 +25,9 @@ set_control_setpoint: Set the central heating control setpoint override on the gateway. You will only need this if you are writing your own software thermostat. fields: + gateway_id: + description: The gateway_id of the OpenTherm Gateway. + example: 'opentherm_gateway' temperature: description: > The central heating setpoint to set on the gateway. @@ -28,6 +38,9 @@ set_control_setpoint: set_gpio_mode: description: Change the function of the GPIO pins of the gateway. fields: + gateway_id: + description: The gateway_id of the OpenTherm Gateway. + example: 'opentherm_gateway' id: description: The ID of the GPIO pin. Either "A" or "B". example: 'B' @@ -40,6 +53,9 @@ set_gpio_mode: set_led_mode: description: Change the function of the LEDs of the gateway. fields: + gateway_id: + description: The gateway_id of the OpenTherm Gateway. + example: 'opentherm_gateway' id: description: The ID of the LED. Possible values are "A" through "F". example: 'C' @@ -54,6 +70,9 @@ set_max_modulation: Override the maximum relative modulation level. You will only need this if you are writing your own software thermostat. fields: + gateway_id: + description: The gateway_id of the OpenTherm Gateway. + example: 'opentherm_gateway' level: description: > The modulation level to provide to the gateway. @@ -66,6 +85,9 @@ set_outside_temperature: Provide an outside temperature to the thermostat. If your thermostat is unable to display an outside temperature and does not support OTC (Outside Temperature Correction), this has no effect. fields: + gateway_id: + description: The gateway_id of the OpenTherm Gateway. + example: 'opentherm_gateway' temperature: description: > The temperature to provide to the thermostat. @@ -76,6 +98,9 @@ set_outside_temperature: set_setback_temperature: description: Configure the setback temperature to be used with the GPIO away mode function. fields: + gateway_id: + description: The gateway_id of the OpenTherm Gateway. + example: 'opentherm_gateway' temperature: description: The setback temperature to configure on the gateway. Values between 0.0 and 30.0 are accepted. example: '16.0'