Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Support wago 750 with enocean rebase2 #58916

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
64a8cbd
Added task to read modbus states at once
toggm Jul 15, 2021
0edcd4e
Created scan groups with different intervals for different kind of en…
toggm Jul 15, 2021
e34f7f5
added entity id and unique id to modbus entities to be able to adjust…
toggm Jul 15, 2021
0f9e371
Added support for cover controlled by two coils
toggm Jul 16, 2021
8e001a9
adjusted quering of two coils state for separate opening and closing
toggm Aug 1, 2021
c953551
added max seconds to complete to reset state when opening cover fully
toggm Aug 1, 2021
008ad64
added support for stop state
toggm Aug 1, 2021
6a32ff9
integrated enocean over modbus support, added enocean implementation …
toggm Aug 17, 2021
8f5785f
added support for temp sensor reading value from a different databyte…
toggm Aug 17, 2021
3a70a2a
added support for illumination and on-off of multi value devices
toggm Aug 25, 2021
11258fc
moved onoff sensor to binary_sensor
toggm Aug 30, 2021
bc3d4ca
fixed group detection, added support for calculated positon based on …
toggm Oct 4, 2021
011f87f
added missing awaits
toggm Oct 7, 2021
03241e8
added entity_id and uniqueid to make entities modifiable in UI
toggm Oct 7, 2021
ae863e4
fixed errors after rebasing
toggm Oct 14, 2021
b0d0e68
added support for call_active flag
toggm Oct 14, 2021
3e3b1f1
fixes after rebasing
toggm Oct 16, 2021
cbf9cab
added support for querying a range of addresses in a listener to get …
toggm Oct 17, 2021
2713c62
temporary added pip install of not yet merged esp2 enocean protocol c…
toggm Oct 18, 2021
3c734ff
fixed setting enocean sensor value
toggm Oct 27, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ ENV \

WORKDIR /usr/src

# Need to install not-yet merged enocean module manually
RUN \
git clone -b dev/esp2_support https://github.com/toggm/enocean.git enocean \
&& cd enocean \
&& pip3 install .

## Setup Home Assistant
COPY . homeassistant/
RUN \
Expand Down
31 changes: 23 additions & 8 deletions homeassistant/components/enocean/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,27 @@

from homeassistant import config_entries, core
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import CONF_DEVICE
from homeassistant.const import CONF_DEVICE, CONF_TYPE
import homeassistant.helpers.config_validation as cv

from .const import DATA_ENOCEAN, DOMAIN, ENOCEAN_DONGLE
from .const import DATA_ENOCEAN, DOMAIN, ENOCEAN_DONGLE, TYPE_IMPLICIT, TYPE_SERIAL
from .dongle import EnOceanDongle

CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.Schema({vol.Required(CONF_DEVICE): cv.string})}, extra=vol.ALLOW_EXTRA
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_DEVICE): cv.string,
vol.Optional(CONF_TYPE, default=TYPE_SERIAL): vol.In(
[
TYPE_SERIAL,
TYPE_IMPLICIT,
]
),
}
)
},
extra=vol.ALLOW_EXTRA,
)


Expand Down Expand Up @@ -40,18 +53,20 @@ async def async_setup_entry(
):
"""Set up an EnOcean dongle for the given entry."""
enocean_data = hass.data.setdefault(DATA_ENOCEAN, {})
usb_dongle = EnOceanDongle(hass, config_entry.data[CONF_DEVICE])
await usb_dongle.async_setup()
enocean_data[ENOCEAN_DONGLE] = usb_dongle
if config_entry.data[CONF_TYPE] == TYPE_SERIAL:
usb_dongle = EnOceanDongle(hass, config_entry.data[CONF_DEVICE])
await usb_dongle.async_setup()
enocean_data[ENOCEAN_DONGLE] = usb_dongle

return True


async def async_unload_entry(hass, config_entry):
"""Unload ENOcean config entry."""
"""Unload EnOcean config entry."""

enocean_dongle = hass.data[DATA_ENOCEAN][ENOCEAN_DONGLE]
enocean_dongle.unload()
if enocean_dongle:
enocean_dongle.unload()
hass.data.pop(DATA_ENOCEAN)

return True
164 changes: 161 additions & 3 deletions homeassistant/components/enocean/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,23 @@
import voluptuous as vol

from homeassistant.components.binary_sensor import (
DEVICE_CLASS_MOTION,
DEVICE_CLASS_WINDOW,
DEVICE_CLASSES_SCHEMA,
ENTITY_ID_FORMAT,
PLATFORM_SCHEMA,
BinarySensorEntity,
)
from homeassistant.const import CONF_DEVICE_CLASS, CONF_ID, CONF_NAME
from homeassistant.const import (
CONF_DEVICE_CLASS,
CONF_ID,
CONF_NAME,
DEVICE_CLASS_BATTERY,
STATE_CLOSED,
STATE_OFF,
STATE_ON,
STATE_OPEN,
)
import homeassistant.helpers.config_validation as cv

from .device import EnOceanEntity
Expand All @@ -15,11 +27,42 @@
DEPENDENCIES = ["enocean"]
EVENT_BUTTON_PRESSED = "button_pressed"

CONF_INVERTED = "inverted"

SENSOR_TYPE_BATTERY = "battery"
SENSOR_TYPE_BUTTON_PRESSED = "button"
SENSOR_TYPE_MOTION = "motion"
SENSOR_TYPE_WINDOW = "window"

SENSOR_TYPES = {
SENSOR_TYPE_BATTERY: {
"name": "Battery state",
"icon": "mdi:battery",
"class": DEVICE_CLASS_BATTERY,
},
SENSOR_TYPE_BUTTON_PRESSED: {
"name": "Button pressed",
"icon": "mdi:gesture-tap-button",
"class": None,
},
SENSOR_TYPE_MOTION: {
"name": "Motion",
"icon": "mdi:motion",
"class": DEVICE_CLASS_MOTION,
},
SENSOR_TYPE_WINDOW: {
"name": "Window",
"icon": "mdi:window",
"class": DEVICE_CLASS_WINDOW,
},
}

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_INVERTED, default=0): cv.boolean,
}
)

Expand All @@ -28,9 +71,25 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Binary Sensor platform for EnOcean."""
dev_id = config.get(CONF_ID)
dev_name = config.get(CONF_NAME)
device_class = config.get(CONF_DEVICE_CLASS)

add_entities([EnOceanBinarySensor(dev_id, dev_name, device_class)])
sensor_type = config.get(CONF_DEVICE_CLASS)

if (
sensor_type == SENSOR_TYPE_BATTERY
or sensor_type == SENSOR_TYPE_BUTTON_PRESSED
or sensor_type == SENSOR_TYPE_MOTION
):
inverted = config.get(CONF_INVERTED)
add_entities(
[EnOceanOnOffSensor(dev_id, dev_name, sensor_type, inverted=inverted)]
)
elif sensor_type == SENSOR_TYPE_WINDOW:
inverted = config.get(CONF_INVERTED)
add_entities(
[EnOceanOpenClosedSensor(dev_id, dev_name, sensor_type, inverted=inverted)]
)
else:
add_entities([EnOceanBinarySensor(dev_id, dev_name, sensor_type)])


class EnOceanBinarySensor(EnOceanEntity, BinarySensorEntity):
Expand All @@ -47,6 +106,7 @@ def __init__(self, dev_id, dev_name, device_class):
self._device_class = device_class
self.which = -1
self.onoff = -1
self.entity_id = ENTITY_ID_FORMAT.format("_".join(str(e) for e in dev_id))

@property
def name(self):
Expand Down Expand Up @@ -108,3 +168,101 @@ def value_changed(self, packet):
"onoff": self.onoff,
},
)


class EnOceanOnOffSensor(EnOceanBinarySensor):
"""Representation of an EnOcean on-off sensor device, storing state in data byte 0.0, most often part of a multi-sensor device.

EEPs (EnOcean Equipment Profiles):
- D5-00-01
- A5-10-02 (slide switch of operating panel)
- A5-10-06 (slide switch of operating panel)
- A5-10-09 (slide switch of operating panel)
- A5-10-0D (slide switch of operating panel)
- A5-10-11 (slide switch of operating panel)
- A5-10-14 (slide switch of operating panel)
- A5-10-20 (user intervention on device)
- A5-10-21 (user intervention on device)
- A5-10-21 (occupied)
- A5-11-02 (occupied)
- A5-11-04 (light on)
- A5-14-08 (vibration detected)
- A5-14-0A (vibration detected)
- A5-20-10 (HVAC unit running state)
- A5-20-11 (HVAC alarm error state)
- A5-38-08 (switching command)

For the following EEPs the inverted flag has to be set to true:
- A5-08-01 (occupancy button pressed)
- A5-08-03 (occupancy button pressed)
- A5-10-01 (occupancy button pressed)
- A5-10-05 (occupancy button pressed)
- A5-10-08 (occupancy button pressed)
- A5-10-0C (occupancy button pressed)
- A5-10-0D (occupancy button pressed)
- A5-10-10 (occupancy button pressed)
- A5-10-13 (occupancy button pressed)
- A5-10-16 (occupancy button pressed)
- A5-10-17 (occupancy button pressed)
- A5-10-18 (occupancy button pressed)
- A5-10-19 (room occupancied)
- A5-10-1A (occupancy button pressed)
- A5-10-1B (occupancy button pressed)
- A5-10-1C (occupancy button pressed)
- A5-10-1D (occupancy button pressed)
- A5-10-1F (occupancy button pressed)
- A5-13-07 (battery low=0, battery ok=1)
- A5-13-08 (battery low=0, battery ok=1)
- A5-20-12 (room occupancied)
"""

def __init__(
self,
dev_id,
dev_name,
sensor_type,
state_on=STATE_ON,
state_off=STATE_OFF,
inverted: bool = False,
):
"""Initialize the EnOcean on-off sensor device."""
super().__init__(dev_id, dev_name, sensor_type)
self._state_on = state_on
self._state_off = state_off
self._inverted = inverted

def value_changed(self, packet):
"""Update the internal state of the sensor."""
stateOn = (packet.data[0] & 0x01) == 0x01

if stateOn and not self._inverted:
self._state = self._state_on
else:
self._state = self._state_off

self.schedule_update_ha_state()


class EnOceanOpenClosedSensor(EnOceanOnOffSensor):
"""Represents an EnOcean Open-Closed sensor device.

EEPs (EnOcean Equipment Profiles):
- D5-00-01

For the following EEPs the inverted flag has to be set to true:
- A5-10-0A (contact state, 1=Open)
- A5-10-08 (contact state, 1=Open)
- A5-14-01 to A5-14-04 (contact state, 1=Open)
- A5-30-02 (contact state, 1=Open)
"""

def __init__(self, dev_id, dev_name, sensor_type, inverted: bool = False):
"""Initialize EnOceanOpenClosedSensor."""
super().__init__(
dev_id,
dev_name,
sensor_type,
state_on=STATE_OPEN,
state_off=STATE_CLOSED,
inverted=inverted,
)
29 changes: 18 additions & 11 deletions homeassistant/components/enocean/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_DEVICE
from homeassistant.const import CONF_DEVICE, CONF_TYPE

from . import dongle
from .const import DOMAIN, ERROR_INVALID_DONGLE_PATH, LOGGER
from .const import DOMAIN, ERROR_INVALID_DONGLE_PATH, LOGGER, TYPE_IMPLICIT, TYPE_SERIAL


class EnOceanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
Expand All @@ -24,10 +24,6 @@ async def async_step_import(self, data=None):
"""Import a yaml configuration."""

if not await self.validate_enocean_conf(data):
LOGGER.warning(
"Cannot import yaml configuration: %s is not a valid dongle path",
data[CONF_DEVICE],
)
return self.async_abort(reason="invalid_dongle_path")

return self.create_enocean_entry(data)
Expand Down Expand Up @@ -80,11 +76,22 @@ async def async_step_manual(self, user_input=None):

async def validate_enocean_conf(self, user_input) -> bool:
"""Return True if the user_input contains a valid dongle path."""
dongle_path = user_input[CONF_DEVICE]
path_is_valid = await self.hass.async_add_executor_job(
dongle.validate_path, dongle_path
)
return path_is_valid
if user_input[CONF_TYPE] == TYPE_SERIAL:
dongle_path = user_input[CONF_DEVICE]
path_is_valid = await self.hass.async_add_executor_job(
dongle.validate_path, dongle_path
)
if not path_is_valid:
LOGGER.warning(
"Cannot import yaml configuration: %s is not a valid dongle path",
user_input[CONF_DEVICE],
)
return path_is_valid
elif user_input[CONF_TYPE] == TYPE_IMPLICIT:
# assume configuration is correct for implicit enocean dongles
return True
else:
return False

def create_enocean_entry(self, user_input):
"""Create an entry for the provided configuration."""
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/enocean/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
LOGGER = logging.getLogger(__package__)

PLATFORMS = ["light", "binary_sensor", "sensor", "switch"]

TYPE_SERIAL = "serial"
TYPE_IMPLICIT = "implicit"
6 changes: 5 additions & 1 deletion homeassistant/components/enocean/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ async def async_added_to_hass(self):
)
)

@property
def unique_id(self) -> str:
"""Return a unique ID."""
return f"enocean_{self.entity_id}"

def _message_received_callback(self, packet):
"""Handle incoming packets."""

if packet.sender_int == combine_hex(self.dev_id):
self.value_changed(packet)

Expand Down
1 change: 0 additions & 1 deletion homeassistant/components/enocean/dongle.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ def callback(self, packet):
"""

if isinstance(packet, RadioPacket):
_LOGGER.debug("Received radio packet: %s", packet)
self.hass.helpers.dispatcher.dispatcher_send(SIGNAL_RECEIVE_MESSAGE, packet)


Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/enocean/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ENTITY_ID_FORMAT,
PLATFORM_SCHEMA,
SUPPORT_BRIGHTNESS,
LightEntity,
Expand Down Expand Up @@ -46,6 +47,7 @@ def __init__(self, sender_id, dev_id, dev_name):
self._on_state = False
self._brightness = 50
self._sender_id = sender_id
self.entity_id = ENTITY_ID_FORMAT.format("_".join(str(e) for e in dev_id))

@property
def name(self):
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/enocean/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"domain": "enocean",
"name": "EnOcean",
"documentation": "https://www.home-assistant.io/integrations/enocean",
"requirements": ["enocean==0.50"],
"requirements": ["enocean==0.60.2"],
"codeowners": ["@bdurrer"],
"config_flow": true,
"iot_class": "local_push"
Expand Down
Loading