Initial support for EnOcean (#2177)

* Initial support for EnOcean

Tested to work with:
 - Eltako FUD61 dimmer
 - Eltako FT55 battery-less switch
 - Permundo PSC234 (switch and power monitor)

* Rerun gen_requirements_all.py
pull/2182/head
rubund 2016-05-29 23:28:03 +02:00 committed by Paulus Schoutsen
parent 03e8627b12
commit bf940bd1f3
7 changed files with 409 additions and 0 deletions

View File

@ -75,6 +75,9 @@ omit =
homeassistant/components/zwave.py
homeassistant/components/*/zwave.py
homeassistant/components/enocean.py
homeassistant/components/*/enocean.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/binary_sensor/arest.py

View File

@ -0,0 +1,63 @@
"""
Support for EnOcean binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.enocean/
"""
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components import enocean
from homeassistant.const import CONF_NAME
DEPENDENCIES = ["enocean"]
CONF_ID = "id"
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Binary Sensor platform fo EnOcean."""
dev_id = config.get(CONF_ID, None)
devname = config.get(CONF_NAME, "EnOcean binary sensor")
add_devices([EnOceanBinarySensor(dev_id, devname)])
class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice):
"""Representation of EnOcean binary sensors such as wall switches."""
def __init__(self, dev_id, devname):
"""Initialize the EnOcean binary sensor."""
enocean.EnOceanDevice.__init__(self)
self.stype = "listener"
self.dev_id = dev_id
self.which = -1
self.onoff = -1
self.devname = devname
@property
def name(self):
"""The default name for the binary sensor."""
return self.devname
def value_changed(self, value, value2):
"""Fire an event with the data that have changed.
This method is called when there is an incoming packet associated
with this platform.
"""
self.update_ha_state()
if value2 == 0x70:
self.which = 0
self.onoff = 0
elif value2 == 0x50:
self.which = 0
self.onoff = 1
elif value2 == 0x30:
self.which = 1
self.onoff = 0
elif value2 == 0x10:
self.which = 1
self.onoff = 1
self.hass.bus.fire('button_pressed', {"id": self.dev_id,
'pushed': value,
'which': self.which,
'onoff': self.onoff})

View File

@ -0,0 +1,117 @@
"""
EnOcean Component.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/EnOcean/
"""
DOMAIN = "enocean"
REQUIREMENTS = ['enocean==0.31']
CONF_DEVICE = "device"
ENOCEAN_DONGLE = None
def setup(hass, config):
"""Setup the EnOcean component."""
global ENOCEAN_DONGLE
serial_dev = config[DOMAIN].get(CONF_DEVICE, "/dev/ttyUSB0")
ENOCEAN_DONGLE = EnOceanDongle(hass, serial_dev)
return True
class EnOceanDongle:
"""Representation of an EnOcean dongle."""
def __init__(self, hass, ser):
"""Initialize the EnOcean dongle."""
from enocean.communicators.serialcommunicator import SerialCommunicator
self.__communicator = SerialCommunicator(port=ser,
callback=self.callback)
self.__communicator.start()
self.__devices = []
def register_device(self, dev):
"""Register another device."""
self.__devices.append(dev)
def send_command(self, command):
"""Send a command from the EnOcean dongle."""
self.__communicator.send(command)
def _combine_hex(self, data): # pylint: disable=no-self-use
"""Combine list of integer values to one big integer."""
output = 0x00
for i, j in enumerate(reversed(data)):
output |= (j << i * 8)
return output
# pylint: disable=too-many-branches
def callback(self, temp):
"""Callback function for EnOcean Device.
This is the callback function called by
python-enocan whenever there is an incoming
packet.
"""
from enocean.protocol.packet import RadioPacket
if isinstance(temp, RadioPacket):
rxtype = None
value = None
if temp.data[6] == 0x30:
rxtype = "wallswitch"
value = 1
elif temp.data[6] == 0x20:
rxtype = "wallswitch"
value = 0
elif temp.data[4] == 0x0c:
rxtype = "power"
value = temp.data[3] + (temp.data[2] << 8)
elif temp.data[2] == 0x60:
rxtype = "switch_status"
if temp.data[3] == 0xe4:
value = 1
elif temp.data[3] == 0x80:
value = 0
elif temp.data[0] == 0xa5 and temp.data[1] == 0x02:
rxtype = "dimmerstatus"
value = temp.data[2]
for device in self.__devices:
if rxtype == "wallswitch" and device.stype == "listener":
if temp.sender == self._combine_hex(device.dev_id):
device.value_changed(value, temp.data[1])
if rxtype == "power" and device.stype == "powersensor":
if temp.sender == self._combine_hex(device.dev_id):
device.value_changed(value)
if rxtype == "power" and device.stype == "switch":
if temp.sender == self._combine_hex(device.dev_id):
if value > 10:
device.value_changed(1)
if rxtype == "switch_status" and device.stype == "switch":
if temp.sender == self._combine_hex(device.dev_id):
device.value_changed(value)
if rxtype == "dimmerstatus" and device.stype == "dimmer":
if temp.sender == self._combine_hex(device.dev_id):
device.value_changed(value)
# pylint: disable=too-few-public-methods
class EnOceanDevice():
"""Parent class for all devices associated with the EnOcean component."""
def __init__(self):
"""Initialize the device."""
ENOCEAN_DONGLE.register_device(self)
self.stype = ""
self.sensorid = [0x00, 0x00, 0x00, 0x00]
# pylint: disable=no-self-use
def send_command(self, data, optional, packet_type):
"""Send a command via the EnOcean dongle."""
from enocean.protocol.packet import Packet
packet = Packet(packet_type, data=data, optional=optional)
ENOCEAN_DONGLE.send_command(packet)

View File

@ -0,0 +1,92 @@
"""
Support for EnOcean light sources.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.enocean/
"""
import logging
import math
from homeassistant.components.light import Light, ATTR_BRIGHTNESS
from homeassistant.const import CONF_NAME
from homeassistant.components import enocean
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ["enocean"]
CONF_ID = "id"
CONF_SENDER_ID = "sender_id"
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the EnOcean light platform."""
sender_id = config.get(CONF_SENDER_ID, None)
devname = config.get(CONF_NAME, "Enocean actuator")
dev_id = config.get(CONF_ID, [0x00, 0x00, 0x00, 0x00])
add_devices([EnOceanLight(sender_id, devname, dev_id)])
class EnOceanLight(enocean.EnOceanDevice, Light):
"""Representation of an EnOcean light source."""
def __init__(self, sender_id, devname, dev_id):
"""Initialize the EnOcean light source."""
enocean.EnOceanDevice.__init__(self)
self._on_state = False
self._brightness = 50
self._sender_id = sender_id
self.dev_id = dev_id
self._devname = devname
self.stype = "dimmer"
@property
def name(self):
"""Return the name of the device if any."""
return self._devname
@property
def brightness(self):
"""Brightness of the light.
This method is optional. Removing it indicates to Home Assistant
that brightness is not supported for this light.
"""
return self._brightness
@property
def is_on(self):
"""If light is on."""
return self._on_state
def turn_on(self, **kwargs):
"""Turn the light source on or sets a specific dimmer value."""
brightness = kwargs.get(ATTR_BRIGHTNESS)
if brightness is not None:
self._brightness = brightness
bval = math.floor(self._brightness / 256.0 * 100.0)
if bval == 0:
bval = 1
command = [0xa5, 0x02, bval, 0x01, 0x09]
command.extend(self._sender_id)
command.extend([0x00])
self.send_command(command, [], 0x01)
self._on_state = True
def turn_off(self, **kwargs):
"""Turn the light source off."""
command = [0xa5, 0x02, 0x00, 0x01, 0x09]
command.extend(self._sender_id)
command.extend([0x00])
self.send_command(command, [], 0x01)
self._on_state = False
def value_changed(self, val):
"""Update the internal state of this device in HA."""
self._brightness = math.floor(val / 100.0 * 256.0)
self._on_state = bool(val != 0)
self.update_ha_state()

View File

@ -0,0 +1,55 @@
"""
Support for EnOcean sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.enocean/
"""
from homeassistant.const import CONF_NAME
from homeassistant.helpers.entity import Entity
from homeassistant.components import enocean
DEPENDENCIES = ["enocean"]
CONF_ID = "id"
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup an EnOcean sensor device."""
dev_id = config.get(CONF_ID, None)
devname = config.get(CONF_NAME, None)
add_devices([EnOceanSensor(dev_id, devname)])
class EnOceanSensor(enocean.EnOceanDevice, Entity):
"""Representation of an EnOcean sensor device such as a power meter."""
def __init__(self, dev_id, devname):
"""Initialize the EnOcean sensor device."""
enocean.EnOceanDevice.__init__(self)
self.stype = "powersensor"
self.power = None
self.dev_id = dev_id
self.which = -1
self.onoff = -1
self.devname = devname
@property
def name(self):
"""Return the name of the device."""
return 'Power %s' % self.devname
def value_changed(self, value):
"""Update the internal state of the device."""
self.power = value
self.update_ha_state()
@property
def state(self):
"""Return the state of the device."""
return self.power
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return "W"

View File

@ -0,0 +1,76 @@
"""
Support for EnOcean switches.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.enocean/
"""
import logging
from homeassistant.const import CONF_NAME
from homeassistant.components import enocean
from homeassistant.helpers.entity import ToggleEntity
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ["enocean"]
CONF_ID = "id"
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the EnOcean switch platform."""
dev_id = config.get(CONF_ID, None)
devname = config.get(CONF_NAME, "Enocean actuator")
add_devices([EnOceanSwitch(dev_id, devname)])
class EnOceanSwitch(enocean.EnOceanDevice, ToggleEntity):
"""Representation of an EnOcean switch device."""
def __init__(self, dev_id, devname):
"""Initialize the EnOcean switch device."""
enocean.EnOceanDevice.__init__(self)
self.dev_id = dev_id
self._devname = devname
self._light = None
self._on_state = False
self._on_state2 = False
self.stype = "switch"
@property
def is_on(self):
"""Return whether the switch is on or off."""
return self._on_state
@property
def name(self):
"""Return the device name."""
return self._devname
def turn_on(self, **kwargs):
"""Turn on the switch."""
optional = [0x03, ]
optional.extend(self.dev_id)
optional.extend([0xff, 0x00])
self.send_command(data=[0xD2, 0x01, 0x00, 0x64, 0x00,
0x00, 0x00, 0x00, 0x00], optional=optional,
packet_type=0x01)
self._on_state = True
def turn_off(self, **kwargs):
"""Turn off the switch."""
optional = [0x03, ]
optional.extend(self.dev_id)
optional.extend([0xff, 0x00])
self.send_command(data=[0xD2, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00], optional=optional,
packet_type=0x01)
self._on_state = False
def value_changed(self, val):
"""Update the internal state of the switch."""
self._on_state = val
self.update_ha_state()

View File

@ -55,6 +55,9 @@ dweepy==0.2.0
# homeassistant.components.sensor.eliqonline
eliqonline==1.0.12
# homeassistant.components.enocean
enocean==0.31
# homeassistant.components.http
eventlet==0.19.0