diff --git a/.coveragerc b/.coveragerc index 433a43bea10..26fae1ba3f9 100644 --- a/.coveragerc +++ b/.coveragerc @@ -26,6 +26,9 @@ omit = homeassistant/components/zwave.py homeassistant/components/*/zwave.py + homeassistant/components/rfxtrx.py + homeassistant/components/*/rfxtrx.py + homeassistant/components/ifttt.py homeassistant/components/browser.py homeassistant/components/camera/* @@ -45,6 +48,7 @@ omit = homeassistant/components/keyboard.py homeassistant/components/light/hue.py homeassistant/components/light/limitlessled.py + homeassistant/components/light/blinksticklight.py homeassistant/components/media_player/cast.py homeassistant/components/media_player/denon.py homeassistant/components/media_player/itunes.py @@ -72,7 +76,6 @@ omit = homeassistant/components/sensor/mysensors.py homeassistant/components/sensor/openweathermap.py homeassistant/components/sensor/rest.py - homeassistant/components/sensor/rfxtrx.py homeassistant/components/sensor/rpi_gpio.py homeassistant/components/sensor/sabnzbd.py homeassistant/components/sensor/swiss_public_transport.py diff --git a/config/configuration.yaml.example b/config/configuration.yaml.example index ae9959a193d..fae945b05e4 100644 --- a/config/configuration.yaml.example +++ b/config/configuration.yaml.example @@ -134,6 +134,9 @@ automation: service: light.turn_off entity_id: group.all_lights +# Sensors need to be added into the configuration.yaml as sensor:, sensor 2:, sensor 3:, etc. +# Each sensor label should be unique or your sensors might not load correctly. + sensor: platform: systemmonitor resources: diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index c8d99bc3ea2..6989009b2d5 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit c8d99bc3ea21cdd7bfb39e7700f92ed09f4b9efd +Subproject commit 6989009b2d59e39fd39b3025ff5899877f618bd3 diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 8d09910093b..c1b1579b4b5 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -246,6 +246,7 @@ def setup(hass, config): rgb_color = dat.get(ATTR_RGB_COLOR) if len(rgb_color) == 3: + params[ATTR_RGB_COLOR] = [int(val) for val in rgb_color] params[ATTR_XY_COLOR] = \ color_util.color_RGB_to_xy(int(rgb_color[0]), int(rgb_color[1]), diff --git a/homeassistant/components/light/blinksticklight.py b/homeassistant/components/light/blinksticklight.py new file mode 100644 index 00000000000..364b5f030c6 --- /dev/null +++ b/homeassistant/components/light/blinksticklight.py @@ -0,0 +1,72 @@ +""" +homeassistant.components.light.blinksticklight +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for Blinkstick lights. +""" + +from blinkstick import blinkstick +import logging + +_LOGGER = logging.getLogger(__name__) + +from homeassistant.components.light import (Light, ATTR_RGB_COLOR) + +REQUIREMENTS = ["blinkstick==1.1.7"] +DEPENDENCIES = [] + +# pylint: disable=unused-argument + + +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Add device specified by serial number """ + stick = blinkstick.find_by_serial(config['serial']) + + add_devices_callback([BlinkStickLight(stick, config['name'])]) + + +class BlinkStickLight(Light): + """ Represents a BlinkStick light """ + + def __init__(self, stick, name): + """ Initialise """ + self._stick = stick + self._name = name + self._serial = stick.get_serial() + self._rgb_color = stick.get_color() + + @property + def should_poll(self): + return True + + @property + def name(self): + return self._name + + @property + def rgb_color(self): + """ Read back the color of the light """ + return self._rgb_color + + @property + def is_on(self): + """ Check whether any of the LEDs colors are non-zero """ + return sum(self._rgb_color) > 0 + + def update(self): + """ Read back the device state """ + self._rgb_color = self._stick.get_color() + + def turn_on(self, **kwargs): + """ Turn the device on. """ + if ATTR_RGB_COLOR in kwargs: + self._rgb_color = kwargs[ATTR_RGB_COLOR] + else: + self._rgb_color = [255, 255, 255] + + self._stick.set_color(red=self._rgb_color[0], + green=self._rgb_color[1], + blue=self._rgb_color[2]) + + def turn_off(self, **kwargs): + """ Turn the device off """ + self._stick.turn_off() diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/light/rfxtrx.py new file mode 100644 index 00000000000..f76d9f7ed5b --- /dev/null +++ b/homeassistant/components/light/rfxtrx.py @@ -0,0 +1,129 @@ +""" +homeassistant.components.light.rfxtrx +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for Rfxtrx lights. + +Configuration: + +To use Rfxtrx lights you will need to add the following to your +configuration.yaml file. + +light: + platform: rfxtrx + + devices: + ac09c4f1: Bedroom Light + ac09c4f2: Kitchen Light + ac09c4f3: Bathroom Light + +*Optional* + + # Automatic add new light + automatic_add: True + +""" +import logging +import homeassistant.components.rfxtrx as rfxtrx +import RFXtrx as rfxtrxmod + +from homeassistant.components.light import Light +from homeassistant.util import slugify + +DEPENDENCIES = ['rfxtrx'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Setup the RFXtrx platform. """ + # Add light from config file + lights = [] + devices = config.get('devices', None) + if devices: + for entity_id, entity_info in devices.items(): + if entity_id not in rfxtrx.RFX_DEVICES: + _LOGGER.info("Add %s rfxtrx.light", entity_info['name']) + rfxobject = rfxtrx.get_rfx_object(entity_info['packetid']) + new_light = RfxtrxLight(entity_info['name'], rfxobject, False) + rfxtrx.RFX_DEVICES[entity_id] = new_light + lights.append(new_light) + + add_devices_callback(lights) + + def light_update(event): + """ Callback for sensor updates from the RFXtrx gateway. """ + if not isinstance(event.device, rfxtrxmod.LightingDevice): + return + + # Add entity if not exist and the automatic_add is True + entity_id = slugify(event.device.id_string.lower()) + if entity_id not in rfxtrx.RFX_DEVICES: + automatic_add = config.get('automatic_add', False) + if not automatic_add: + return + + _LOGGER.info( + "Automatic add %s rfxtrx.light (Class: %s Sub: %s)", + entity_id, + event.device.__class__.__name__, + event.device.subtype + ) + pkt_id = "".join("{0:02x}".format(x) for x in event.data) + entity_name = "%s : %s" % (entity_id, pkt_id) + new_light = RfxtrxLight(entity_name, event, False) + rfxtrx.RFX_DEVICES[entity_id] = new_light + add_devices_callback([new_light]) + + # Check if entity exists or previously added automatically + if entity_id in rfxtrx.RFX_DEVICES: + if event.values['Command'] == 'On'\ + or event.values['Command'] == 'Off': + if event.values['Command'] == 'On': + rfxtrx.RFX_DEVICES[entity_id].turn_on() + else: + rfxtrx.RFX_DEVICES[entity_id].turn_off() + + # Subscribe to main rfxtrx events + if light_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS: + rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(light_update) + + +class RfxtrxLight(Light): + """ Provides a demo switch. """ + def __init__(self, name, event, state): + self._name = name + self._event = event + self._state = state + + @property + def should_poll(self): + """ No polling needed for a demo light. """ + return False + + @property + def name(self): + """ Returns the name of the device if any. """ + return self._name + + @property + def is_on(self): + """ True if device is on. """ + return self._state + + def turn_on(self, **kwargs): + """ Turn the device on. """ + + if hasattr(self, '_event') and self._event: + self._event.device.send_on(rfxtrx.RFXOBJECT.transport) + + self._state = True + self.update_ha_state() + + def turn_off(self, **kwargs): + """ Turn the device off. """ + + if hasattr(self, '_event') and self._event: + self._event.device.send_off(rfxtrx.RFXOBJECT.transport) + + self._state = False + self.update_ha_state() diff --git a/homeassistant/components/rfxtrx.py b/homeassistant/components/rfxtrx.py new file mode 100644 index 00000000000..79378b85e78 --- /dev/null +++ b/homeassistant/components/rfxtrx.py @@ -0,0 +1,99 @@ +""" +homeassistant.components.rfxtrx +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Connects Home Assistant to a RFXtrx device. + +Configuration: + +To use Rfxtrx device you will need to add the following to your +configuration.yaml file. + +rfxtrx: + device: /dev/serial/by-id/usb-RFXCOM_RFXtrx433_A1YVC1P0-if00-port0 + +*Optional* + + debug: True + +""" +import logging +from homeassistant.util import slugify + +DEPENDENCIES = [] +REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/0.2.zip' + + '#RFXtrx==0.2'] + +DOMAIN = "rfxtrx" +CONF_DEVICE = 'device' +CONF_DEBUG = 'debug' +RECEIVED_EVT_SUBSCRIBERS = [] +RFX_DEVICES = {} +_LOGGER = logging.getLogger(__name__) +RFXOBJECT = None + + +def setup(hass, config): + """ Setup the Rfxtrx component. """ + + # Declare the Handle event + def handle_receive(event): + """ Callback all subscribers for RFXtrx gateway. """ + + # Log RFXCOM event + entity_id = slugify(event.device.id_string.lower()) + packet_id = "".join("{0:02x}".format(x) for x in event.data) + entity_name = "%s : %s" % (entity_id, packet_id) + _LOGGER.info("Receive RFXCOM event from %s => %s", + event.device, entity_name) + + # Callback to HA registered components + for subscriber in RECEIVED_EVT_SUBSCRIBERS: + subscriber(event) + + # Try to load the RFXtrx module + try: + import RFXtrx as rfxtrxmod + except ImportError: + _LOGGER.exception("Failed to import rfxtrx") + return False + + # Init the rfxtrx module + global RFXOBJECT + + if CONF_DEVICE not in config[DOMAIN]: + _LOGGER.exception( + "can found device parameter in %s YAML configuration section", + DOMAIN + ) + return False + + device = config[DOMAIN][CONF_DEVICE] + debug = config[DOMAIN].get(CONF_DEBUG, False) + + RFXOBJECT = rfxtrxmod.Core(device, handle_receive, debug=debug) + + return True + + +def get_rfx_object(packetid): + """ return the RFXObject with the packetid""" + try: + import RFXtrx as rfxtrxmod + except ImportError: + _LOGGER.exception("Failed to import rfxtrx") + return False + + binarypacket = bytearray.fromhex(packetid) + + pkt = rfxtrxmod.lowlevel.parse(binarypacket) + if pkt is not None: + if isinstance(pkt, rfxtrxmod.lowlevel.SensorPacket): + obj = rfxtrxmod.SensorEvent(pkt) + elif isinstance(pkt, rfxtrxmod.lowlevel.Status): + obj = rfxtrxmod.StatusEvent(pkt) + else: + obj = rfxtrxmod.ControlEvent(pkt) + + return obj + + return None diff --git a/homeassistant/components/sensor/rfxtrx.py b/homeassistant/components/sensor/rfxtrx.py index 4cb8a939d5e..b09d9d4a09c 100644 --- a/homeassistant/components/sensor/rfxtrx.py +++ b/homeassistant/components/sensor/rfxtrx.py @@ -24,9 +24,11 @@ from collections import OrderedDict from homeassistant.const import (TEMP_CELCIUS) from homeassistant.helpers.entity import Entity +import homeassistant.components.rfxtrx as rfxtrx +from RFXtrx import SensorEvent +from homeassistant.util import slugify -REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/' + - 'ec7a1aaddf8270db6e5da1c13d58c1547effd7cf.zip#RFXtrx==0.15'] +DEPENDENCIES = ['rfxtrx'] DATA_TYPES = OrderedDict([ ('Temperature', TEMP_CELCIUS), @@ -34,32 +36,30 @@ DATA_TYPES = OrderedDict([ ('Barometer', ''), ('Wind direction', ''), ('Rain rate', '')]) +_LOGGER = logging.getLogger(__name__) -def setup_platform(hass, config, add_devices, discovery_info=None): +def setup_platform(hass, config, add_devices_callback, discovery_info=None): """ Setup the RFXtrx platform. """ - logger = logging.getLogger(__name__) - - sensors = {} # keep track of sensors added to HA def sensor_update(event): """ Callback for sensor updates from the RFXtrx gateway. """ - if event.device.id_string in sensors: - sensors[event.device.id_string].event = event - else: - logger.info("adding new sensor: %s", event.device.type_string) - new_sensor = RfxtrxSensor(event) - sensors[event.device.id_string] = new_sensor - add_devices([new_sensor]) - try: - import RFXtrx as rfxtrx - except ImportError: - logger.exception( - "Failed to import rfxtrx") - return False + if isinstance(event.device, SensorEvent): + entity_id = slugify(event.device.id_string.lower()) - device = config.get("device", "") - rfxtrx.Core(device, sensor_update) + # Add entity if not exist and the automatic_add is True + if entity_id not in rfxtrx.RFX_DEVICES: + automatic_add = config.get('automatic_add', True) + if automatic_add: + _LOGGER.info("Automatic add %s rfxtrx.light", entity_id) + new_sensor = RfxtrxSensor(event) + rfxtrx.RFX_DEVICES[entity_id] = new_sensor + add_devices_callback([new_sensor]) + else: + rfxtrx.RFX_DEVICES[entity_id].event = event + + if sensor_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS: + rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(sensor_update) class RfxtrxSensor(Entity): diff --git a/homeassistant/components/switch/rfxtrx.py b/homeassistant/components/switch/rfxtrx.py new file mode 100644 index 00000000000..98963beb769 --- /dev/null +++ b/homeassistant/components/switch/rfxtrx.py @@ -0,0 +1,128 @@ +""" +homeassistant.components.switch.rfxtrx +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for Rfxtrx switch. + +Configuration: + +To use Rfxtrx switchs you will need to add the following to your +configuration.yaml file. + +switch: + platform: rfxtrx + + devices: + ac09c4f1: Bedroom Door + ac09c4f2: Kitchen Door + ac09c4f3: Bathroom Door + +*Optional* + + # Automatic add new switch + automatic_add: True + +""" +import logging +import homeassistant.components.rfxtrx as rfxtrx +from RFXtrx import LightingDevice + +from homeassistant.components.switch import SwitchDevice +from homeassistant.util import slugify + +DEPENDENCIES = ['rfxtrx'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Setup the RFXtrx platform. """ + + # Add switch from config file + switchs = [] + devices = config.get('devices') + if devices: + for entity_id, entity_info in devices.items(): + if entity_id not in rfxtrx.RFX_DEVICES: + _LOGGER.info("Add %s rfxtrx.switch", entity_info['name']) + rfxobject = rfxtrx.get_rfx_object(entity_info['packetid']) + newswitch = RfxtrxSwitch(entity_info['name'], rfxobject, False) + rfxtrx.RFX_DEVICES[entity_id] = newswitch + switchs.append(newswitch) + + add_devices_callback(switchs) + + def switch_update(event): + """ Callback for sensor updates from the RFXtrx gateway. """ + if isinstance(event.device, LightingDevice): + return + + # Add entity if not exist and the automatic_add is True + entity_id = slugify(event.device.id_string.lower()) + if entity_id not in rfxtrx.RFX_DEVICES: + automatic_add = config.get('automatic_add', False) + if not automatic_add: + return + + _LOGGER.info( + "Automatic add %s rfxtrx.switch (Class: %s Sub: %s)", + entity_id, + event.device.__class__.__name__, + event.device.subtype + ) + pkt_id = "".join("{0:02x}".format(x) for x in event.data) + entity_name = "%s : %s" % (entity_id, pkt_id) + new_switch = RfxtrxSwitch(entity_name, event, False) + rfxtrx.RFX_DEVICES[entity_id] = new_switch + add_devices_callback([new_switch]) + + # Check if entity exists or previously added automatically + if entity_id in rfxtrx.RFX_DEVICES: + if event.values['Command'] == 'On'\ + or event.values['Command'] == 'Off': + if event.values['Command'] == 'On': + rfxtrx.RFX_DEVICES[entity_id].turn_on() + else: + rfxtrx.RFX_DEVICES[entity_id].turn_off() + + # Subscribe to main rfxtrx events + if switch_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS: + rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(switch_update) + + +class RfxtrxSwitch(SwitchDevice): + """ Provides a demo switch. """ + def __init__(self, name, event, state): + self._name = name + self._event = event + self._state = state + + @property + def should_poll(self): + """ No polling needed for a demo switch. """ + return False + + @property + def name(self): + """ Returns the name of the device if any. """ + return self._name + + @property + def is_on(self): + """ True if device is on. """ + return self._state + + def turn_on(self, **kwargs): + """ Turn the device on. """ + if self._event: + self._event.device.send_on(rfxtrx.RFXOBJECT.transport) + + self._state = True + self.update_ha_state() + + def turn_off(self, **kwargs): + """ Turn the device off. """ + if self._event: + self._event.device.send_off(rfxtrx.RFXOBJECT.transport) + + self._state = False + self.update_ha_state() diff --git a/requirements_all.txt b/requirements_all.txt index 7e3a3acc458..e78effedfb8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -141,3 +141,5 @@ https://github.com/adrienbrault/python-plexapi/archive/df2d0847e801d6d5cda920326 # python-pysnmp (device_tracker.snmp) pysnmp==4.2.5 +# Blinkstick +blinkstick==1.1.7 diff --git a/tests/components/test_light.py b/tests/components/test_light.py index 515b79b6fc0..156ba51e59a 100644 --- a/tests/components/test_light.py +++ b/tests/components/test_light.py @@ -152,9 +152,13 @@ class TestLight(unittest.TestCase): data) method, data = dev2.last_call('turn_on') - self.assertEqual( - {light.ATTR_XY_COLOR: color_util.color_RGB_to_xy(255, 255, 255)}, - data) + self.assertEquals( + data[light.ATTR_XY_COLOR], + color_util.color_RGB_to_xy(255, 255, 255)) + + self.assertEquals( + data[light.ATTR_RGB_COLOR], + [255, 255, 255]) method, data = dev3.last_call('turn_on') self.assertEqual({light.ATTR_XY_COLOR: [.4, .6]}, data)