Add support for a wider variety of EnOcean devices (#22052)

* Implement EnOcean temperature and humidity sensors.

* Bump EnOcean version to 0.50
* Refactor components for more generic device handling
* Move radio packet data interpretation to specific devices

* Update CODEOWNERS

* Implement code review changes
This commit is contained in:
Beat 2019-04-25 00:30:46 +02:00 committed by Martin Hjelmare
parent fef1dc8c54
commit 96735e41af
10 changed files with 305 additions and 153 deletions

View file

@ -66,6 +66,7 @@ homeassistant/components/egardia/* @jeroenterheerdt
homeassistant/components/eight_sleep/* @mezz64 homeassistant/components/eight_sleep/* @mezz64
homeassistant/components/emby/* @mezz64 homeassistant/components/emby/* @mezz64
homeassistant/components/enigma2/* @fbradyirl homeassistant/components/enigma2/* @fbradyirl
homeassistant/components/enocean/* @bdurrer
homeassistant/components/ephember/* @ttroy50 homeassistant/components/ephember/* @ttroy50
homeassistant/components/epsonworkforce/* @ThaStealth homeassistant/components/epsonworkforce/* @ThaStealth
homeassistant/components/eq3btsmart/* @rytilahti homeassistant/components/eq3btsmart/* @rytilahti

View file

@ -4,13 +4,13 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.const import CONF_DEVICE from homeassistant.const import CONF_DEVICE
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DOMAIN = 'enocean' DOMAIN = 'enocean'
DATA_ENOCEAN = 'enocean'
ENOCEAN_DONGLE = None
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({ DOMAIN: vol.Schema({
@ -18,14 +18,15 @@ CONFIG_SCHEMA = vol.Schema({
}), }),
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
SIGNAL_RECEIVE_MESSAGE = 'enocean.receive_message'
SIGNAL_SEND_MESSAGE = 'enocean.send_message'
def setup(hass, config): def setup(hass, config):
"""Set up the EnOcean component.""" """Set up the EnOcean component."""
global ENOCEAN_DONGLE
serial_dev = config[DOMAIN].get(CONF_DEVICE) serial_dev = config[DOMAIN].get(CONF_DEVICE)
dongle = EnOceanDongle(hass, serial_dev)
ENOCEAN_DONGLE = EnOceanDongle(hass, serial_dev) hass.data[DATA_ENOCEAN] = dongle
return True return True
@ -39,87 +40,53 @@ class EnOceanDongle:
self.__communicator = SerialCommunicator( self.__communicator = SerialCommunicator(
port=ser, callback=self.callback) port=ser, callback=self.callback)
self.__communicator.start() self.__communicator.start()
self.__devices = [] self.hass = hass
self.hass.helpers.dispatcher.dispatcher_connect(
SIGNAL_SEND_MESSAGE, self._send_message_callback)
def register_device(self, dev): def _send_message_callback(self, command):
"""Register another device.""" """Send a command through the EnOcean dongle."""
self.__devices.append(dev)
def send_command(self, command):
"""Send a command from the EnOcean dongle."""
self.__communicator.send(command) self.__communicator.send(command)
# pylint: disable=no-self-use def callback(self, packet):
def _combine_hex(self, data):
"""Combine list of integer values to one big integer."""
output = 0x00
for i, j in enumerate(reversed(data)):
output |= (j << i * 8)
return output
def callback(self, temp):
"""Handle EnOcean device's callback. """Handle EnOcean device's callback.
This is the callback function called by python-enocan whenever there This is the callback function called by python-enocan whenever there
is an incoming packet. is an incoming packet.
""" """
from enocean.protocol.packet import RadioPacket from enocean.protocol.packet import RadioPacket
if isinstance(temp, RadioPacket): if isinstance(packet, RadioPacket):
_LOGGER.debug("Received radio packet: %s", temp) _LOGGER.debug("Received radio packet: %s", packet)
rxtype = None self.hass.helpers.dispatcher.dispatcher_send(
value = None SIGNAL_RECEIVE_MESSAGE, packet)
channel = 0
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 == 0x60:
rxtype = "switch_status"
channel = temp.data[2] & 0x1F
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_int == self._combine_hex(device.dev_id):
device.value_changed(value, temp.data[1])
if rxtype == "power" and device.stype == "powersensor":
if temp.sender_int == self._combine_hex(device.dev_id):
device.value_changed(value)
if rxtype == "power" and device.stype == "switch":
if temp.sender_int == self._combine_hex(device.dev_id):
if value > 10:
device.value_changed(1)
if rxtype == "switch_status" and device.stype == "switch" and \
channel == device.channel:
if temp.sender_int == self._combine_hex(device.dev_id):
device.value_changed(value)
if rxtype == "dimmerstatus" and device.stype == "dimmer":
if temp.sender_int == self._combine_hex(device.dev_id):
device.value_changed(value)
class EnOceanDevice(): class EnOceanDevice(Entity):
"""Parent class for all devices associated with the EnOcean component.""" """Parent class for all devices associated with the EnOcean component."""
def __init__(self): def __init__(self, dev_id, dev_name="EnOcean device"):
"""Initialize the device.""" """Initialize the device."""
ENOCEAN_DONGLE.register_device(self) self.dev_id = dev_id
self.stype = "" self.dev_name = dev_name
self.sensorid = [0x00, 0x00, 0x00, 0x00]
async def async_added_to_hass(self):
"""Register callbacks."""
self.hass.helpers.dispatcher.async_dispatcher_connect(
SIGNAL_RECEIVE_MESSAGE, self._message_received_callback)
def _message_received_callback(self, packet):
"""Handle incoming packets."""
from enocean.utils import combine_hex
if packet.sender_int == combine_hex(self.dev_id):
self.value_changed(packet)
def value_changed(self, packet):
"""Update the internal state of the device when a packet arrives."""
# pylint: disable=no-self-use # pylint: disable=no-self-use
def send_command(self, data, optional, packet_type): def send_command(self, data, optional, packet_type):
"""Send a command via the EnOcean dongle.""" """Send a command via the EnOcean dongle."""
from enocean.protocol.packet import Packet from enocean.protocol.packet import Packet
packet = Packet(packet_type, data=data, optional=optional) packet = Packet(packet_type, data=data, optional=optional)
ENOCEAN_DONGLE.send_command(packet) self.hass.helpers.dispatcher.dispatcher_send(
SIGNAL_SEND_MESSAGE, packet)

View file

@ -3,16 +3,17 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
from homeassistant.components import enocean from homeassistant.components import enocean
from homeassistant.const import ( from homeassistant.components.binary_sensor import (
CONF_NAME, CONF_ID, CONF_DEVICE_CLASS) DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.const import CONF_DEVICE_CLASS, CONF_ID, CONF_NAME
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'EnOcean binary sensor' DEFAULT_NAME = 'EnOcean binary sensor'
DEPENDENCIES = ['enocean']
EVENT_BUTTON_PRESSED = 'button_pressed'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]), vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]),
@ -24,61 +25,80 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_entities, discovery_info=None): def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Binary Sensor platform for EnOcean.""" """Set up the Binary Sensor platform for EnOcean."""
dev_id = config.get(CONF_ID) dev_id = config.get(CONF_ID)
devname = config.get(CONF_NAME) dev_name = config.get(CONF_NAME)
device_class = config.get(CONF_DEVICE_CLASS) device_class = config.get(CONF_DEVICE_CLASS)
add_entities([EnOceanBinarySensor(dev_id, devname, device_class)]) add_entities([EnOceanBinarySensor(dev_id, dev_name, device_class)])
class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice): class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice):
"""Representation of EnOcean binary sensors such as wall switches.""" """Representation of EnOcean binary sensors such as wall switches.
def __init__(self, dev_id, devname, device_class): Supported EEPs (EnOcean Equipment Profiles):
- F6-02-01 (Light and Blind Control - Application Style 2)
- F6-02-02 (Light and Blind Control - Application Style 1)
"""
def __init__(self, dev_id, dev_name, device_class):
"""Initialize the EnOcean binary sensor.""" """Initialize the EnOcean binary sensor."""
enocean.EnOceanDevice.__init__(self) super().__init__(dev_id, dev_name)
self.stype = 'listener' self._device_class = device_class
self.dev_id = dev_id
self.which = -1 self.which = -1
self.onoff = -1 self.onoff = -1
self.devname = devname
self._device_class = device_class
@property @property
def name(self): def name(self):
"""Return the default name for the binary sensor.""" """Return the default name for the binary sensor."""
return self.devname return self.dev_name
@property @property
def device_class(self): def device_class(self):
"""Return the class of this sensor.""" """Return the class of this sensor."""
return self._device_class return self._device_class
def value_changed(self, value, value2): def value_changed(self, packet):
"""Fire an event with the data that have changed. """Fire an event with the data that have changed.
This method is called when there is an incoming packet associated This method is called when there is an incoming packet associated
with this platform. with this platform.
Example packet data:
- 2nd button pressed
['0xf6', '0x10', '0x00', '0x2d', '0xcf', '0x45', '0x30']
- button released
['0xf6', '0x00', '0x00', '0x2d', '0xcf', '0x45', '0x20']
""" """
# Energy Bow
pushed = None
if packet.data[6] == 0x30:
pushed = 1
elif packet.data[6] == 0x20:
pushed = 0
self.schedule_update_ha_state() self.schedule_update_ha_state()
if value2 == 0x70:
action = packet.data[1]
if action == 0x70:
self.which = 0 self.which = 0
self.onoff = 0 self.onoff = 0
elif value2 == 0x50: elif action == 0x50:
self.which = 0 self.which = 0
self.onoff = 1 self.onoff = 1
elif value2 == 0x30: elif action == 0x30:
self.which = 1 self.which = 1
self.onoff = 0 self.onoff = 0
elif value2 == 0x10: elif action == 0x10:
self.which = 1 self.which = 1
self.onoff = 1 self.onoff = 1
elif value2 == 0x37: elif action == 0x37:
self.which = 10 self.which = 10
self.onoff = 0 self.onoff = 0
elif value2 == 0x15: elif action == 0x15:
self.which = 10 self.which = 10
self.onoff = 1 self.onoff = 1
self.hass.bus.fire('button_pressed', {'id': self.dev_id, self.hass.bus.fire(EVENT_BUTTON_PRESSED,
'pushed': value, {'id': self.dev_id,
'which': self.which, 'pushed': pushed,
'onoff': self.onoff}) 'which': self.which,
'onoff': self.onoff})

View file

@ -4,10 +4,10 @@ import math
import voluptuous as vol import voluptuous as vol
from homeassistant.components.light import (
Light, ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME, CONF_ID)
from homeassistant.components import enocean from homeassistant.components import enocean
from homeassistant.components.light import (
ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, Light)
from homeassistant.const import CONF_ID, CONF_NAME
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -28,29 +28,26 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_entities, discovery_info=None): def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the EnOcean light platform.""" """Set up the EnOcean light platform."""
sender_id = config.get(CONF_SENDER_ID) sender_id = config.get(CONF_SENDER_ID)
devname = config.get(CONF_NAME) dev_name = config.get(CONF_NAME)
dev_id = config.get(CONF_ID) dev_id = config.get(CONF_ID)
add_entities([EnOceanLight(sender_id, devname, dev_id)]) add_entities([EnOceanLight(sender_id, dev_id, dev_name)])
class EnOceanLight(enocean.EnOceanDevice, Light): class EnOceanLight(enocean.EnOceanDevice, Light):
"""Representation of an EnOcean light source.""" """Representation of an EnOcean light source."""
def __init__(self, sender_id, devname, dev_id): def __init__(self, sender_id, dev_id, dev_name):
"""Initialize the EnOcean light source.""" """Initialize the EnOcean light source."""
enocean.EnOceanDevice.__init__(self) super().__init__(dev_id, dev_name)
self._on_state = False self._on_state = False
self._brightness = 50 self._brightness = 50
self._sender_id = sender_id self._sender_id = sender_id
self.dev_id = dev_id
self._devname = devname
self.stype = 'dimmer'
@property @property
def name(self): def name(self):
"""Return the name of the device if any.""" """Return the name of the device if any."""
return self._devname return self.dev_name
@property @property
def brightness(self): def brightness(self):
@ -94,8 +91,14 @@ class EnOceanLight(enocean.EnOceanDevice, Light):
self.send_command(command, [], 0x01) self.send_command(command, [], 0x01)
self._on_state = False self._on_state = False
def value_changed(self, val): def value_changed(self, packet):
"""Update the internal state of this device.""" """Update the internal state of this device.
self._brightness = math.floor(val / 100.0 * 256.0)
self._on_state = bool(val != 0) Dimmer devices like Eltako FUD61 send telegram in different RORGs.
self.schedule_update_ha_state() We only care about the 4BS (0xA5).
"""
if packet.data[0] == 0xa5 and packet.data[1] == 0x02:
val = packet.data[2]
self._brightness = math.floor(val / 100.0 * 256.0)
self._on_state = bool(val != 0)
self.schedule_update_ha_state()

View file

@ -3,8 +3,8 @@
"name": "Enocean", "name": "Enocean",
"documentation": "https://www.home-assistant.io/components/enocean", "documentation": "https://www.home-assistant.io/components/enocean",
"requirements": [ "requirements": [
"enocean==0.40" "enocean==0.50"
], ],
"dependencies": [], "dependencies": [],
"codeowners": [] "codeowners": ["@bdurrer"]
} }

View file

@ -3,58 +3,201 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_NAME, CONF_ID, POWER_WATT)
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
from homeassistant.components import enocean from homeassistant.components import enocean
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_DEVICE_CLASS, CONF_ID, CONF_NAME, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, POWER_WATT)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_MAX_TEMP = 'max_temp'
CONF_MIN_TEMP = 'min_temp'
CONF_RANGE_FROM = 'range_from'
CONF_RANGE_TO = 'range_to'
DEFAULT_NAME = 'EnOcean sensor' DEFAULT_NAME = 'EnOcean sensor'
DEVICE_CLASS_POWER = 'powersensor'
SENSOR_TYPES = {
DEVICE_CLASS_HUMIDITY: {
'name': 'Humidity',
'unit': '%',
'icon': 'mdi:water-percent',
'class': DEVICE_CLASS_HUMIDITY,
},
DEVICE_CLASS_POWER: {
'name': 'Power',
'unit': POWER_WATT,
'icon': 'mdi:power-plug',
'class': DEVICE_CLASS_POWER,
},
DEVICE_CLASS_TEMPERATURE: {
'name': 'Temperature',
'unit': TEMP_CELSIUS,
'icon': 'mdi:thermometer',
'class': DEVICE_CLASS_TEMPERATURE,
},
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]), vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_POWER): cv.string,
vol.Optional(CONF_MAX_TEMP, default=40): vol.Coerce(int),
vol.Optional(CONF_MIN_TEMP, default=0): vol.Coerce(int),
vol.Optional(CONF_RANGE_FROM, default=255): cv.positive_int,
vol.Optional(CONF_RANGE_TO, default=0): cv.positive_int,
}) })
def setup_platform(hass, config, add_entities, discovery_info=None): def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up an EnOcean sensor device.""" """Set up an EnOcean sensor device."""
dev_id = config.get(CONF_ID) dev_id = config.get(CONF_ID)
devname = config.get(CONF_NAME) dev_name = config.get(CONF_NAME)
dev_class = config.get(CONF_DEVICE_CLASS)
add_entities([EnOceanSensor(dev_id, devname)]) if dev_class == DEVICE_CLASS_TEMPERATURE:
temp_min = config.get(CONF_MIN_TEMP)
temp_max = config.get(CONF_MAX_TEMP)
range_from = config.get(CONF_RANGE_FROM)
range_to = config.get(CONF_RANGE_TO)
add_entities([EnOceanTemperatureSensor(
dev_id, dev_name, temp_min, temp_max, range_from, range_to)])
elif dev_class == DEVICE_CLASS_HUMIDITY:
add_entities([EnOceanHumiditySensor(dev_id, dev_name)])
elif dev_class == DEVICE_CLASS_POWER:
add_entities([EnOceanPowerSensor(dev_id, dev_name)])
class EnOceanSensor(enocean.EnOceanDevice, Entity): class EnOceanSensor(enocean.EnOceanDevice):
"""Representation of an EnOcean sensor device such as a power meter.""" """Representation of an EnOcean sensor device such as a power meter."""
def __init__(self, dev_id, devname): def __init__(self, dev_id, dev_name, sensor_type):
"""Initialize the EnOcean sensor device.""" """Initialize the EnOcean sensor device."""
enocean.EnOceanDevice.__init__(self) super().__init__(dev_id, dev_name)
self.stype = "powersensor" self._sensor_type = sensor_type
self.power = None self._device_class = SENSOR_TYPES[self._sensor_type]['class']
self.dev_id = dev_id self._dev_name = '{} {}'.format(
self.which = -1 SENSOR_TYPES[self._sensor_type]['name'], dev_name)
self.onoff = -1 self._unit_of_measurement = SENSOR_TYPES[self._sensor_type]['unit']
self.devname = devname self._icon = SENSOR_TYPES[self._sensor_type]['icon']
self._state = None
@property @property
def name(self): def name(self):
"""Return the name of the device.""" """Return the name of the device."""
return 'Power %s' % self.devname return self._dev_name
def value_changed(self, value): @property
"""Update the internal state of the device.""" def icon(self):
self.power = value """Icon to use in the frontend."""
self.schedule_update_ha_state() return self._icon
@property
def device_class(self):
"""Return the device class of the sensor."""
return self._device_class
@property @property
def state(self): def state(self):
"""Return the state of the device.""" """Return the state of the device."""
return self.power return self._state
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
"""Return the unit of measurement.""" """Return the unit of measurement."""
return POWER_WATT return self._unit_of_measurement
def value_changed(self, packet):
"""Update the internal state of the sensor."""
class EnOceanPowerSensor(EnOceanSensor):
"""Representation of an EnOcean power sensor.
EEPs (EnOcean Equipment Profiles):
- A5-12-01 (Automated Meter Reading, Electricity)
"""
def __init__(self, dev_id, dev_name):
"""Initialize the EnOcean power sensor device."""
super().__init__(dev_id, dev_name, DEVICE_CLASS_POWER)
def value_changed(self, packet):
"""Update the internal state of the sensor."""
if packet.rorg != 0xA5:
return
packet.parse_eep(0x12, 0x01)
if packet.parsed['DT']['raw_value'] == 1:
# this packet reports the current value
raw_val = packet.parsed['MR']['raw_value']
divisor = packet.parsed['DIV']['raw_value']
self._state = raw_val / (10 ** divisor)
self.schedule_update_ha_state()
class EnOceanTemperatureSensor(EnOceanSensor):
"""Representation of an EnOcean temperature sensor device.
EEPs (EnOcean Equipment Profiles):
- A5-02-01 to A5-02-1B All 8 Bit Temperature Sensors of A5-02
- A5-10-01 to A5-10-14 (Room Operating Panels)
- A5-04-01 (Temp. and Humidity Sensor, Range 0°C to +40°C and 0% to 100%)
- A5-04-02 (Temp. and Humidity Sensor, Range -20°C to +60°C and 0% to 100%)
- A5-10-10 (Temp. and Humidity Sensor and Set Point)
- A5-10-12 (Temp. and Humidity Sensor, Set Point and Occupancy Control)
- 10 Bit Temp. Sensors are not supported (A5-02-20, A5-02-30)
For the following EEPs the scales must be set to "0 to 250":
- A5-04-01
- A5-04-02
- A5-10-10 to A5-10-14
"""
def __init__(self, dev_id, dev_name, scale_min, scale_max,
range_from, range_to):
"""Initialize the EnOcean temperature sensor device."""
super().__init__(dev_id, dev_name, DEVICE_CLASS_TEMPERATURE)
self._scale_min = scale_min
self._scale_max = scale_max
self.range_from = range_from
self.range_to = range_to
def value_changed(self, packet):
"""Update the internal state of the sensor."""
if packet.data[0] != 0xa5:
return
temp_scale = self._scale_max - self._scale_min
temp_range = self.range_to - self.range_from
raw_val = packet.data[3]
temperature = temp_scale / temp_range * (raw_val - self.range_from)
temperature += self._scale_min
self._state = round(temperature, 1)
self.schedule_update_ha_state()
class EnOceanHumiditySensor(EnOceanSensor):
"""Representation of an EnOcean humidity sensor device.
EEPs (EnOcean Equipment Profiles):
- A5-04-01 (Temp. and Humidity Sensor, Range 0°C to +40°C and 0% to 100%)
- A5-04-02 (Temp. and Humidity Sensor, Range -20°C to +60°C and 0% to 100%)
- A5-10-10 to A5-10-14 (Room Operating Panels)
"""
def __init__(self, dev_id, dev_name):
"""Initialize the EnOcean humidity sensor device."""
super().__init__(dev_id, dev_name, DEVICE_CLASS_HUMIDITY)
def value_changed(self, packet):
"""Update the internal state of the sensor."""
if packet.rorg != 0xA5:
return
humidity = packet.data[2] * 100 / 250
self._state = round(humidity, 1)
self.schedule_update_ha_state()

View file

@ -3,16 +3,16 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.switch import PLATFORM_SCHEMA
from homeassistant.const import (CONF_NAME, CONF_ID)
from homeassistant.components import enocean from homeassistant.components import enocean
from homeassistant.helpers.entity import ToggleEntity from homeassistant.components.switch import PLATFORM_SCHEMA
from homeassistant.const import CONF_ID, CONF_NAME
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import ToggleEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'EnOcean Switch'
CONF_CHANNEL = 'channel' CONF_CHANNEL = 'channel'
DEFAULT_NAME = 'EnOcean Switch'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]), vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]),
@ -23,26 +23,23 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_entities, discovery_info=None): def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the EnOcean switch platform.""" """Set up the EnOcean switch platform."""
dev_id = config.get(CONF_ID)
devname = config.get(CONF_NAME)
channel = config.get(CONF_CHANNEL) channel = config.get(CONF_CHANNEL)
dev_id = config.get(CONF_ID)
dev_name = config.get(CONF_NAME)
add_entities([EnOceanSwitch(dev_id, devname, channel)]) add_entities([EnOceanSwitch(dev_id, dev_name, channel)])
class EnOceanSwitch(enocean.EnOceanDevice, ToggleEntity): class EnOceanSwitch(enocean.EnOceanDevice, ToggleEntity):
"""Representation of an EnOcean switch device.""" """Representation of an EnOcean switch device."""
def __init__(self, dev_id, devname, channel): def __init__(self, dev_id, dev_name, channel):
"""Initialize the EnOcean switch device.""" """Initialize the EnOcean switch device."""
enocean.EnOceanDevice.__init__(self) super().__init__(dev_id, dev_name)
self.dev_id = dev_id
self._devname = devname
self._light = None self._light = None
self._on_state = False self._on_state = False
self._on_state2 = False self._on_state2 = False
self.channel = channel self.channel = channel
self.stype = "switch"
@property @property
def is_on(self): def is_on(self):
@ -52,7 +49,7 @@ class EnOceanSwitch(enocean.EnOceanDevice, ToggleEntity):
@property @property
def name(self): def name(self):
"""Return the device name.""" """Return the device name."""
return self._devname return self.dev_name
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Turn on the switch.""" """Turn on the switch."""
@ -74,7 +71,24 @@ class EnOceanSwitch(enocean.EnOceanDevice, ToggleEntity):
packet_type=0x01) packet_type=0x01)
self._on_state = False self._on_state = False
def value_changed(self, val): def value_changed(self, packet):
"""Update the internal state of the switch.""" """Update the internal state of the switch."""
self._on_state = val if packet.data[0] == 0xa5:
self.schedule_update_ha_state() # power meter telegram, turn on if > 10 watts
packet.parse_eep(0x12, 0x01)
if packet.parsed['DT']['raw_value'] == 1:
raw_val = packet.parsed['MR']['raw_value']
divisor = packet.parsed['DIV']['raw_value']
watts = raw_val / (10 ** divisor)
if watts > 1:
self._on_state = True
self.schedule_update_ha_state()
elif packet.data[0] == 0xd2:
# actuator status telegram
packet.parse_eep(0x01, 0x01)
if packet.parsed['CMD']['raw_value'] == 4:
channel = packet.parsed['IO']['raw_value']
output = packet.parsed['OV']['raw_value']
if channel == self.channel:
self._on_state = output > 0
self.schedule_update_ha_state()

View file

@ -383,7 +383,7 @@ elkm1-lib==0.7.13
emulated_roku==0.1.8 emulated_roku==0.1.8
# homeassistant.components.enocean # homeassistant.components.enocean
enocean==0.40 enocean==0.50
# homeassistant.components.entur_public_transport # homeassistant.components.entur_public_transport
enturclient==0.2.0 enturclient==0.2.0

View file

@ -90,6 +90,9 @@ eebrightbox==0.0.4
# homeassistant.components.emulated_roku # homeassistant.components.emulated_roku
emulated_roku==0.1.8 emulated_roku==0.1.8
# homeassistant.components.enocean
enocean==0.50
# homeassistant.components.season # homeassistant.components.season
ephem==3.7.6.0 ephem==3.7.6.0

View file

@ -58,6 +58,7 @@ TEST_REQUIREMENTS = (
'dsmr_parser', 'dsmr_parser',
'eebrightbox', 'eebrightbox',
'emulated_roku', 'emulated_roku',
'enocean',
'ephem', 'ephem',
'evohomeclient', 'evohomeclient',
'feedparser-homeassistant', 'feedparser-homeassistant',