diff --git a/homeassistant/components/cover/fibaro.py b/homeassistant/components/cover/fibaro.py new file mode 100644 index 00000000000..dc82087f802 --- /dev/null +++ b/homeassistant/components/cover/fibaro.py @@ -0,0 +1,92 @@ +""" +Support for Fibaro cover - curtains, rollershutters etc. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/cover.fibaro/ +""" +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) + +DEPENDENCIES = ['fibaro'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the Fibaro covers.""" + if discovery_info is None: + return + + add_entities( + [FibaroCover(device, hass.data[FIBARO_CONTROLLER]) for + device in hass.data[FIBARO_DEVICES]['cover']], True) + + +class FibaroCover(FibaroDevice, CoverDevice): + """Representation a Fibaro Cover.""" + + def __init__(self, fibaro_device, controller): + """Initialize the Vera device.""" + super().__init__(fibaro_device, controller) + self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id) + + @staticmethod + def bound(position): + """Normalize the position.""" + if position is None: + return None + position = int(position) + if position <= 5: + return 0 + if position >= 95: + return 100 + return position + + @property + def current_cover_position(self): + """Return current position of cover. 0 is closed, 100 is open.""" + return self.bound(self.level) + + @property + def current_cover_tilt_position(self): + """Return the current tilt position for venetian blinds.""" + return self.bound(self.level2) + + def set_cover_position(self, **kwargs): + """Move the cover to a specific position.""" + self.set_level(kwargs.get(ATTR_POSITION)) + + def set_cover_tilt_position(self, **kwargs): + """Move the cover to a specific position.""" + self.set_level2(kwargs.get(ATTR_TILT_POSITION)) + + @property + def is_closed(self): + """Return if the cover is closed.""" + if self.current_cover_position is None: + return None + return self.current_cover_position == 0 + + def open_cover(self, **kwargs): + """Open the cover.""" + self.action("open") + + def close_cover(self, **kwargs): + """Close the cover.""" + self.action("close") + + def open_cover_tilt(self, **kwargs): + """Open the cover tilt.""" + self.set_level2(100) + + def close_cover_tilt(self, **kwargs): + """Close the cover.""" + self.set_level2(0) + + def stop_cover(self, **kwargs): + """Stop the cover.""" + self.action("stop") diff --git a/homeassistant/components/fibaro.py b/homeassistant/components/fibaro.py index 9a9e5b12851..c9dd19b4bc8 100644 --- a/homeassistant/components/fibaro.py +++ b/homeassistant/components/fibaro.py @@ -23,14 +23,11 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = 'fibaro' FIBARO_DEVICES = 'fibaro_devices' FIBARO_CONTROLLER = 'fibaro_controller' -FIBARO_ID_FORMAT = '{}_{}_{}' ATTR_CURRENT_POWER_W = "current_power_w" ATTR_CURRENT_ENERGY_KWH = "current_energy_kwh" CONF_PLUGINS = "plugins" -FIBARO_COMPONENTS = [ - 'binary_sensor', -] +FIBARO_COMPONENTS = ['binary_sensor', 'cover', 'light', 'sensor', 'switch'] FIBARO_TYPEMAP = { 'com.fibaro.multilevelSensor': "sensor", @@ -174,7 +171,7 @@ class FibaroController(): else: room_name = self._room_map[device.roomID].name device.friendly_name = room_name + ' ' + device.name - device.ha_id = FIBARO_ID_FORMAT.format( + device.ha_id = '{}_{}_{}'.format( slugify(room_name), slugify(device.name), device.id) self._device_map[device.id] = device self.fibaro_devices = defaultdict(list) @@ -232,13 +229,15 @@ class FibaroDevice(Entity): """Update the state.""" self.schedule_update_ha_state(True) - def get_level(self): + @property + def level(self): """Get the level of Fibaro device.""" if 'value' in self.fibaro_device.properties: return self.fibaro_device.properties.value return None - def get_level2(self): + @property + def level2(self): """Get the tilt level of Fibaro device.""" if 'value2' in self.fibaro_device.properties: return self.fibaro_device.properties.value2 @@ -258,7 +257,21 @@ class FibaroDevice(Entity): if 'brightness' in self.fibaro_device.properties: self.fibaro_device.properties.brightness = level - def set_color(self, red, green, blue, white): + def set_level2(self, level): + """Set the level2 of Fibaro device.""" + self.action("setValue2", level) + if 'value2' in self.fibaro_device.properties: + self.fibaro_device.properties.value2 = level + + def call_turn_on(self): + """Turn on the Fibaro device.""" + self.action("turnOn") + + def call_turn_off(self): + """Turn off the Fibaro device.""" + self.action("turnOff") + + def call_set_color(self, red, green, blue, white): """Set the color of Fibaro device.""" color_str = "{},{},{},{}".format(int(red), int(green), int(blue), int(white)) diff --git a/homeassistant/components/light/fibaro.py b/homeassistant/components/light/fibaro.py new file mode 100644 index 00000000000..cfc28e12218 --- /dev/null +++ b/homeassistant/components/light/fibaro.py @@ -0,0 +1,165 @@ +""" +Support for Fibaro lights. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/light.fibaro/ +""" + +import logging +import threading + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_WHITE_VALUE, ENTITY_ID_FORMAT, + SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_WHITE_VALUE, Light) +import homeassistant.util.color as color_util +from homeassistant.components.fibaro import ( + FIBARO_CONTROLLER, FIBARO_DEVICES, FibaroDevice) + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['fibaro'] + + +def scaleto255(value): + """Scale the input value from 0-100 to 0-255.""" + # Fibaro has a funny way of storing brightness either 0-100 or 0-99 + # depending on device type (e.g. dimmer vs led) + if value > 98: + value = 100 + return max(0, min(255, ((value * 256.0) / 100.0))) + + +def scaleto100(value): + """Scale the input value from 0-255 to 0-100.""" + # Make sure a low but non-zero value is not rounded down to zero + if 0 < value < 3: + return 1 + return max(0, min(100, ((value * 100.4) / 255.0))) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Perform the setup for Fibaro controller devices.""" + if discovery_info is None: + return + + add_entities( + [FibaroLight(device, hass.data[FIBARO_CONTROLLER]) + 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): + """Initialize the light.""" + self._supported_flags = 0 + self._last_brightness = 0 + self._color = (0, 0) + self._brightness = None + self._white = 0 + + self._update_lock = threading.RLock() + if 'levelChange' in fibaro_device.interfaces: + self._supported_flags |= SUPPORT_BRIGHTNESS + if 'color' in fibaro_device.properties: + self._supported_flags |= SUPPORT_COLOR + if 'setW' in fibaro_device.actions: + self._supported_flags |= SUPPORT_WHITE_VALUE + super().__init__(fibaro_device, controller) + self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id) + + @property + def brightness(self): + """Return the brightness of the light.""" + return scaleto255(self._brightness) + + @property + def hs_color(self): + """Return the color of the light.""" + return self._color + + @property + def white_value(self): + """Return the white value of this light between 0..255.""" + return self._white + + @property + def supported_features(self): + """Flag supported features.""" + return self._supported_flags + + def turn_on(self, **kwargs): + """Turn the light on.""" + with self._update_lock: + if self._supported_flags & SUPPORT_BRIGHTNESS: + target_brightness = kwargs.get(ATTR_BRIGHTNESS) + + # No brightness specified, so we either restore it to + # last brightness or switch it on at maximum level + if target_brightness is None: + if self._brightness == 0: + if self._last_brightness: + self._brightness = self._last_brightness + else: + self._brightness = 100 + else: + # We set it to the target brightness and turn it on + self._brightness = scaleto100(target_brightness) + + if self._supported_flags & SUPPORT_COLOR: + # Update based on parameters + self._white = kwargs.get(ATTR_WHITE_VALUE, self._white) + self._color = kwargs.get(ATTR_HS_COLOR, self._color) + rgb = color_util.color_hs_to_RGB(*self._color) + self.call_set_color( + int(rgb[0] * self._brightness / 99.0 + 0.5), + int(rgb[1] * self._brightness / 99.0 + 0.5), + int(rgb[2] * self._brightness / 99.0 + 0.5), + int(self._white * self._brightness / 99.0 + + 0.5)) + if self.state == 'off': + self.set_level(int(self._brightness)) + return + + if self._supported_flags & SUPPORT_BRIGHTNESS: + self.set_level(int(self._brightness)) + return + + # The simplest case is left for last. No dimming, just switch on + self.call_turn_on() + + def turn_off(self, **kwargs): + """Turn the light off.""" + # Let's save the last brightness level before we switch it off + with self._update_lock: + if (self._supported_flags & SUPPORT_BRIGHTNESS) and \ + self._brightness and self._brightness > 0: + self._last_brightness = self._brightness + self._brightness = 0 + self.call_turn_off() + + @property + def is_on(self): + """Return true if device is on.""" + return self.current_binary_state + + def update(self): + """Call to update state.""" + # Brightness handling + with self._update_lock: + if self._supported_flags & SUPPORT_BRIGHTNESS: + self._brightness = float(self.fibaro_device.properties.value) + # Color handling + if self._supported_flags & SUPPORT_COLOR: + # Fibaro communicates the color as an 'R, G, B, W' string + rgbw_s = self.fibaro_device.properties.color + if rgbw_s == '0,0,0,0' and\ + 'lastColorSet' in self.fibaro_device.properties: + rgbw_s = self.fibaro_device.properties.lastColorSet + rgbw_list = [int(i) for i in rgbw_s.split(",")][:4] + if rgbw_list[0] or rgbw_list[1] or rgbw_list[2]: + self._color = color_util.color_RGB_to_hs(*rgbw_list[:3]) + if (self._supported_flags & SUPPORT_WHITE_VALUE) and \ + self.brightness != 0: + self._white = min(255, max(0, rgbw_list[3]*100.0 / + self._brightness)) diff --git a/homeassistant/components/sensor/fibaro.py b/homeassistant/components/sensor/fibaro.py new file mode 100644 index 00000000000..e5ed5638c5b --- /dev/null +++ b/homeassistant/components/sensor/fibaro.py @@ -0,0 +1,99 @@ +""" +Support for Fibaro sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.fibaro/ +""" +import logging + +from homeassistant.const import ( + DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_ILLUMINANCE, TEMP_CELSIUS, TEMP_FAHRENHEIT) +from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import ENTITY_ID_FORMAT +from homeassistant.components.fibaro import ( + FIBARO_CONTROLLER, FIBARO_DEVICES, FibaroDevice) + +SENSOR_TYPES = { + 'com.fibaro.temperatureSensor': + ['Temperature', None, None, DEVICE_CLASS_TEMPERATURE], + 'com.fibaro.smokeSensor': + ['Smoke', 'ppm', 'mdi:fire', None], + 'CO2': + ['CO2', 'ppm', 'mdi:cloud', None], + 'com.fibaro.humiditySensor': + ['Humidity', '%', None, DEVICE_CLASS_HUMIDITY], + 'com.fibaro.lightSensor': + ['Light', 'lx', None, DEVICE_CLASS_ILLUMINANCE] +} + +DEPENDENCIES = ['fibaro'] +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the Fibaro controller devices.""" + if discovery_info is None: + return + + add_entities( + [FibaroSensor(device, hass.data[FIBARO_CONTROLLER]) + for device in hass.data[FIBARO_DEVICES]['sensor']], True) + + +class FibaroSensor(FibaroDevice, Entity): + """Representation of a Fibaro Sensor.""" + + def __init__(self, fibaro_device, controller): + """Initialize the sensor.""" + self.current_value = None + self.last_changed_time = None + super().__init__(fibaro_device, controller) + 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] + self._icon = SENSOR_TYPES[fibaro_device.type][2] + self._device_class = SENSOR_TYPES[fibaro_device.type][3] + else: + self._unit = None + self._icon = None + self._device_class = None + try: + if not self._unit: + if self.fibaro_device.properties.unit == 'lux': + self._unit = 'lx' + elif self.fibaro_device.properties.unit == 'C': + self._unit = TEMP_CELSIUS + elif self.fibaro_device.properties.unit == 'F': + self._unit = TEMP_FAHRENHEIT + else: + self._unit = self.fibaro_device.properties.unit + except (KeyError, ValueError): + pass + + @property + def state(self): + """Return the state of the sensor.""" + return self.current_value + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return self._unit + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return self._icon + + @property + def device_class(self): + """Return the device class of the sensor.""" + return self._device_class + + def update(self): + """Update the state.""" + try: + self.current_value = float(self.fibaro_device.properties.value) + except (KeyError, ValueError): + pass diff --git a/homeassistant/components/switch/fibaro.py b/homeassistant/components/switch/fibaro.py new file mode 100644 index 00000000000..d3e96646a45 --- /dev/null +++ b/homeassistant/components/switch/fibaro.py @@ -0,0 +1,68 @@ +""" +Support for Fibaro switches. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.fibaro/ +""" +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) + +DEPENDENCIES = ['fibaro'] +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the Fibaro switches.""" + if discovery_info is None: + return + + add_entities( + [FibaroSwitch(device, hass.data[FIBARO_CONTROLLER]) for + device in hass.data[FIBARO_DEVICES]['switch']], True) + + +class FibaroSwitch(FibaroDevice, SwitchDevice): + """Representation of a Fibaro Switch.""" + + def __init__(self, fibaro_device, controller): + """Initialize the Fibaro device.""" + self._state = False + super().__init__(fibaro_device, controller) + self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id) + + def turn_on(self, **kwargs): + """Turn device on.""" + self.call_turn_on() + self._state = True + + def turn_off(self, **kwargs): + """Turn device off.""" + self.call_turn_off() + self._state = False + + @property + def current_power_w(self): + """Return the current power usage in W.""" + if 'power' in self.fibaro_device.interfaces: + return convert(self.fibaro_device.properties.power, float, 0.0) + return None + + @property + def today_energy_kwh(self): + """Return the today total energy usage in kWh.""" + if 'energy' in self.fibaro_device.interfaces: + return convert(self.fibaro_device.properties.energy, float, 0.0) + return None + + @property + def is_on(self): + """Return true if device is on.""" + return self._state + + def update(self): + """Update device state.""" + self._state = self.current_binary_state