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
pull/20001/head
pbalogh77 2019-01-12 00:29:54 +01:00 committed by Paulus Schoutsen
parent d820efc4e3
commit 7dac7b9e5e
8 changed files with 78 additions and 58 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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]

View File

@ -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):