From 7dac7b9e5e83809d2a42e5d148d088935a8ceb16 Mon Sep 17 00:00:00 2001 From: pbalogh77 Date: Sat, 12 Jan 2019 00:29:54 +0100 Subject: [PATCH] Support for multiple Fibaro gateways (#19705) * Preparing for transition to config flow Added multiple gateway support Reworked parameter flow to platforms to enable multiple controllers Breaking change to config, now a list of gateways is expected instead of a single config * Updated coveragerc Added new location of fibaro component * Fixes based on code review and extended logging Addressed issues raised by code review Added extended debug logging to get better reports from users if the device type mapping is not perfect * Changhes based on code review Changes to how configuration is read and schemas Fix to device type mapping logic * simplified reading config * oops oops * grr grr * change based on code review * changes based on code review changes based on code review --- .coveragerc | 2 +- .../components/binary_sensor/fibaro.py | 8 +- homeassistant/components/cover/fibaro.py | 8 +- .../{fibaro.py => fibaro/__init__.py} | 90 +++++++++++-------- homeassistant/components/light/fibaro.py | 8 +- homeassistant/components/scene/fibaro.py | 4 +- homeassistant/components/sensor/fibaro.py | 8 +- homeassistant/components/switch/fibaro.py | 8 +- 8 files changed, 78 insertions(+), 58 deletions(-) rename homeassistant/components/{fibaro.py => fibaro/__init__.py} (85%) diff --git a/.coveragerc b/.coveragerc index 3107af9140a..f062501bd35 100644 --- a/.coveragerc +++ b/.coveragerc @@ -127,7 +127,7 @@ omit = homeassistant/components/eufy.py homeassistant/components/*/eufy.py - homeassistant/components/fibaro.py + homeassistant/components/fibaro/__init__.py homeassistant/components/*/fibaro.py homeassistant/components/gc100.py diff --git a/homeassistant/components/binary_sensor/fibaro.py b/homeassistant/components/binary_sensor/fibaro.py index 8af2bde10ad..1934580c58e 100644 --- a/homeassistant/components/binary_sensor/fibaro.py +++ b/homeassistant/components/binary_sensor/fibaro.py @@ -9,7 +9,7 @@ import logging from homeassistant.components.binary_sensor import ( BinarySensorDevice, ENTITY_ID_FORMAT) from homeassistant.components.fibaro import ( - FIBARO_CONTROLLER, FIBARO_DEVICES, FibaroDevice) + FIBARO_DEVICES, FibaroDevice) from homeassistant.const import (CONF_DEVICE_CLASS, CONF_ICON) DEPENDENCIES = ['fibaro'] @@ -33,17 +33,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return add_entities( - [FibaroBinarySensor(device, hass.data[FIBARO_CONTROLLER]) + [FibaroBinarySensor(device) for device in hass.data[FIBARO_DEVICES]['binary_sensor']], True) class FibaroBinarySensor(FibaroDevice, BinarySensorDevice): """Representation of a Fibaro Binary Sensor.""" - def __init__(self, fibaro_device, controller): + def __init__(self, fibaro_device): """Initialize the binary_sensor.""" self._state = None - super().__init__(fibaro_device, controller) + super().__init__(fibaro_device) self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id) stype = None devconf = fibaro_device.device_config diff --git a/homeassistant/components/cover/fibaro.py b/homeassistant/components/cover/fibaro.py index dc82087f802..d47dbb20315 100644 --- a/homeassistant/components/cover/fibaro.py +++ b/homeassistant/components/cover/fibaro.py @@ -9,7 +9,7 @@ import logging from homeassistant.components.cover import ( CoverDevice, ENTITY_ID_FORMAT, ATTR_POSITION, ATTR_TILT_POSITION) from homeassistant.components.fibaro import ( - FIBARO_CONTROLLER, FIBARO_DEVICES, FibaroDevice) + FIBARO_DEVICES, FibaroDevice) DEPENDENCIES = ['fibaro'] @@ -22,16 +22,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return add_entities( - [FibaroCover(device, hass.data[FIBARO_CONTROLLER]) for + [FibaroCover(device) for device in hass.data[FIBARO_DEVICES]['cover']], True) class FibaroCover(FibaroDevice, CoverDevice): """Representation a Fibaro Cover.""" - def __init__(self, fibaro_device, controller): + def __init__(self, fibaro_device): """Initialize the Vera device.""" - super().__init__(fibaro_device, controller) + super().__init__(fibaro_device) self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id) @staticmethod diff --git a/homeassistant/components/fibaro.py b/homeassistant/components/fibaro/__init__.py similarity index 85% rename from homeassistant/components/fibaro.py rename to homeassistant/components/fibaro/__init__.py index d506f6c471d..715cc036265 100644 --- a/homeassistant/components/fibaro.py +++ b/homeassistant/components/fibaro/__init__.py @@ -11,8 +11,8 @@ from typing import Optional import voluptuous as vol from homeassistant.const import ( - ATTR_ARMED, ATTR_BATTERY_LEVEL, CONF_DEVICE_CLASS, - CONF_EXCLUDE, CONF_ICON, CONF_PASSWORD, CONF_URL, CONF_USERNAME, + ATTR_ARMED, ATTR_BATTERY_LEVEL, CONF_DEVICE_CLASS, CONF_EXCLUDE, + CONF_ICON, CONF_PASSWORD, CONF_URL, CONF_USERNAME, CONF_WHITE_VALUE, EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv @@ -24,10 +24,11 @@ REQUIREMENTS = ['fiblary3==0.1.7'] _LOGGER = logging.getLogger(__name__) DOMAIN = 'fibaro' FIBARO_DEVICES = 'fibaro_devices' -FIBARO_CONTROLLER = 'fibaro_controller' +FIBARO_CONTROLLERS = 'fibaro_controllers' ATTR_CURRENT_POWER_W = "current_power_w" ATTR_CURRENT_ENERGY_KWH = "current_energy_kwh" CONF_PLUGINS = "plugins" +CONF_GATEWAYS = 'gateways' CONF_DIMMING = "dimming" CONF_COLOR = "color" CONF_RESET_COLOR = "reset_color" @@ -65,15 +66,20 @@ DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema({ FIBARO_ID_LIST_SCHEMA = vol.Schema([cv.string]) +GATEWAY_CONFIG = vol.Schema({ + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_URL): cv.url, + vol.Optional(CONF_PLUGINS, default=False): cv.boolean, + vol.Optional(CONF_EXCLUDE, default=[]): FIBARO_ID_LIST_SCHEMA, + vol.Optional(CONF_DEVICE_CONFIG, default={}): + vol.Schema({cv.string: DEVICE_CONFIG_SCHEMA_ENTRY}) +}, extra=vol.ALLOW_EXTRA) + CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_URL): cv.url, - vol.Optional(CONF_PLUGINS, default=False): cv.boolean, - vol.Optional(CONF_EXCLUDE, default=[]): FIBARO_ID_LIST_SCHEMA, - vol.Optional(CONF_DEVICE_CONFIG, default={}): - vol.Schema({cv.string: DEVICE_CONFIG_SCHEMA_ENTRY}) + vol.Required(CONF_GATEWAYS): + vol.All(cv.ensure_list, [GATEWAY_CONFIG]) }) }, extra=vol.ALLOW_EXTRA) @@ -81,20 +87,23 @@ CONFIG_SCHEMA = vol.Schema({ class FibaroController(): """Initiate Fibaro Controller Class.""" - def __init__(self, username, password, url, import_plugins, config): + def __init__(self, config): """Initialize the Fibaro controller.""" from fiblary3.client.v4.client import Client as FibaroClient - self._client = FibaroClient(url, username, password) + + self._client = FibaroClient(config[CONF_URL], + config[CONF_USERNAME], + config[CONF_PASSWORD]) self._scene_map = None # Whether to import devices from plugins - self._import_plugins = import_plugins + self._import_plugins = config[CONF_PLUGINS] self._device_config = config[CONF_DEVICE_CONFIG] self._room_map = None # Mapping roomId to room object self._device_map = None # Mapping deviceId to device object self.fibaro_devices = None # List of devices by type self._callbacks = {} # Update value callbacks by deviceId self._state_handler = None # Fiblary's StateHandler object - self._excluded_devices = config.get(CONF_EXCLUDE, []) + self._excluded_devices = config[CONF_EXCLUDE] self.hub_serial = None # Unique serial number of the hub def connect(self): @@ -167,12 +176,11 @@ class FibaroController(): def _map_device_to_type(device): """Map device to HA device type.""" # Use our lookup table to identify device type + device_type = None if 'type' in device: device_type = FIBARO_TYPEMAP.get(device.type) - elif 'baseType' in device: + if device_type is None and 'baseType' in device: device_type = FIBARO_TYPEMAP.get(device.baseType) - else: - device_type = None # We can also identify device type by its capabilities if device_type is None: @@ -200,6 +208,7 @@ class FibaroController(): for device in scenes: if not device.visible: continue + device.fibaro_controller = self if device.roomID == 0: room_name = 'Unknown' else: @@ -220,6 +229,7 @@ class FibaroController(): self.fibaro_devices = defaultdict(list) for device in devices: try: + device.fibaro_controller = self if device.roomID == 0: room_name = 'Unknown' else: @@ -242,33 +252,43 @@ class FibaroController(): self.hub_serial, device.id) self._device_map[device.id] = device self.fibaro_devices[device.mapped_type].append(device) - else: - _LOGGER.debug("%s (%s, %s) not used", - device.ha_id, device.type, - device.baseType) + _LOGGER.debug("%s (%s, %s) -> %s. Prop: %s Actions: %s", + device.ha_id, device.type, + device.baseType, device.mapped_type, + str(device.properties), str(device.actions)) except (KeyError, ValueError): pass -def setup(hass, config): +def setup(hass, base_config): """Set up the Fibaro Component.""" - hass.data[FIBARO_CONTROLLER] = controller = \ - FibaroController(config[DOMAIN][CONF_USERNAME], - config[DOMAIN][CONF_PASSWORD], - config[DOMAIN][CONF_URL], - config[DOMAIN][CONF_PLUGINS], - config[DOMAIN]) + gateways = base_config[DOMAIN][CONF_GATEWAYS] + hass.data[FIBARO_CONTROLLERS] = {} def stop_fibaro(event): """Stop Fibaro Thread.""" _LOGGER.info("Shutting down Fibaro connection") - hass.data[FIBARO_CONTROLLER].disable_state_handler() + for controller in hass.data[FIBARO_CONTROLLERS].values(): + controller.disable_state_handler() - if controller.connect(): - hass.data[FIBARO_DEVICES] = controller.fibaro_devices + hass.data[FIBARO_DEVICES] = {} + for component in FIBARO_COMPONENTS: + hass.data[FIBARO_DEVICES][component] = [] + + for gateway in gateways: + controller = FibaroController(gateway) + if controller.connect(): + hass.data[FIBARO_CONTROLLERS][controller.hub_serial] = controller + for component in FIBARO_COMPONENTS: + hass.data[FIBARO_DEVICES][component].extend( + controller.fibaro_devices[component]) + + if hass.data[FIBARO_CONTROLLERS]: for component in FIBARO_COMPONENTS: - discovery.load_platform(hass, component, DOMAIN, {}, config) - controller.enable_state_handler() + discovery.load_platform(hass, component, DOMAIN, {}, + base_config) + for controller in hass.data[FIBARO_CONTROLLERS].values(): + controller.enable_state_handler() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_fibaro) return True @@ -278,10 +298,10 @@ def setup(hass, config): class FibaroDevice(Entity): """Representation of a Fibaro device entity.""" - def __init__(self, fibaro_device, controller): + def __init__(self, fibaro_device): """Initialize the device.""" self.fibaro_device = fibaro_device - self.controller = controller + self.controller = fibaro_device.fibaro_controller self._name = fibaro_device.friendly_name self.ha_id = fibaro_device.ha_id diff --git a/homeassistant/components/light/fibaro.py b/homeassistant/components/light/fibaro.py index 6f2842524a2..9b3b3850f39 100644 --- a/homeassistant/components/light/fibaro.py +++ b/homeassistant/components/light/fibaro.py @@ -12,7 +12,7 @@ from functools import partial from homeassistant.const import ( CONF_WHITE_VALUE) from homeassistant.components.fibaro import ( - FIBARO_CONTROLLER, FIBARO_DEVICES, FibaroDevice, + FIBARO_DEVICES, FibaroDevice, CONF_DIMMING, CONF_COLOR, CONF_RESET_COLOR) from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_WHITE_VALUE, ENTITY_ID_FORMAT, @@ -50,14 +50,14 @@ async def async_setup_platform(hass, return async_add_entities( - [FibaroLight(device, hass.data[FIBARO_CONTROLLER]) + [FibaroLight(device) for device in hass.data[FIBARO_DEVICES]['light']], True) class FibaroLight(FibaroDevice, Light): """Representation of a Fibaro Light, including dimmable.""" - def __init__(self, fibaro_device, controller): + def __init__(self, fibaro_device): """Initialize the light.""" self._brightness = None self._color = (0, 0) @@ -81,7 +81,7 @@ class FibaroLight(FibaroDevice, Light): if devconf.get(CONF_WHITE_VALUE, supports_white_v): self._supported_flags |= SUPPORT_WHITE_VALUE - super().__init__(fibaro_device, controller) + super().__init__(fibaro_device) self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id) @property diff --git a/homeassistant/components/scene/fibaro.py b/homeassistant/components/scene/fibaro.py index 7a36900f884..a0bd4e7ff40 100644 --- a/homeassistant/components/scene/fibaro.py +++ b/homeassistant/components/scene/fibaro.py @@ -9,7 +9,7 @@ import logging from homeassistant.components.scene import ( Scene) from homeassistant.components.fibaro import ( - FIBARO_CONTROLLER, FIBARO_DEVICES, FibaroDevice) + FIBARO_DEVICES, FibaroDevice) DEPENDENCIES = ['fibaro'] @@ -23,7 +23,7 @@ async def async_setup_platform(hass, config, async_add_entities, return async_add_entities( - [FibaroScene(scene, hass.data[FIBARO_CONTROLLER]) + [FibaroScene(scene) for scene in hass.data[FIBARO_DEVICES]['scene']], True) diff --git a/homeassistant/components/sensor/fibaro.py b/homeassistant/components/sensor/fibaro.py index e5ed5638c5b..e437ef8710d 100644 --- a/homeassistant/components/sensor/fibaro.py +++ b/homeassistant/components/sensor/fibaro.py @@ -12,7 +12,7 @@ from homeassistant.const import ( from homeassistant.helpers.entity import Entity from homeassistant.components.sensor import ENTITY_ID_FORMAT from homeassistant.components.fibaro import ( - FIBARO_CONTROLLER, FIBARO_DEVICES, FibaroDevice) + FIBARO_DEVICES, FibaroDevice) SENSOR_TYPES = { 'com.fibaro.temperatureSensor': @@ -37,18 +37,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return add_entities( - [FibaroSensor(device, hass.data[FIBARO_CONTROLLER]) + [FibaroSensor(device) for device in hass.data[FIBARO_DEVICES]['sensor']], True) class FibaroSensor(FibaroDevice, Entity): """Representation of a Fibaro Sensor.""" - def __init__(self, fibaro_device, controller): + def __init__(self, fibaro_device): """Initialize the sensor.""" self.current_value = None self.last_changed_time = None - super().__init__(fibaro_device, controller) + super().__init__(fibaro_device) self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id) if fibaro_device.type in SENSOR_TYPES: self._unit = SENSOR_TYPES[fibaro_device.type][1] diff --git a/homeassistant/components/switch/fibaro.py b/homeassistant/components/switch/fibaro.py index d3e96646a45..8b59aabec72 100644 --- a/homeassistant/components/switch/fibaro.py +++ b/homeassistant/components/switch/fibaro.py @@ -9,7 +9,7 @@ import logging from homeassistant.util import convert from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice from homeassistant.components.fibaro import ( - FIBARO_CONTROLLER, FIBARO_DEVICES, FibaroDevice) + FIBARO_DEVICES, FibaroDevice) DEPENDENCIES = ['fibaro'] _LOGGER = logging.getLogger(__name__) @@ -21,17 +21,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return add_entities( - [FibaroSwitch(device, hass.data[FIBARO_CONTROLLER]) for + [FibaroSwitch(device) for device in hass.data[FIBARO_DEVICES]['switch']], True) class FibaroSwitch(FibaroDevice, SwitchDevice): """Representation of a Fibaro Switch.""" - def __init__(self, fibaro_device, controller): + def __init__(self, fibaro_device): """Initialize the Fibaro device.""" self._state = False - super().__init__(fibaro_device, controller) + super().__init__(fibaro_device) self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id) def turn_on(self, **kwargs):