diff --git a/README.md b/README.md index 6146da9..2c1b2e6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Becker cover support for Home Assistant A native Home Assistant component to control Becker RF shutters with a Becker Centronic USB stick. -It supports the Becker ***Centronic USB Stick*** with the Becker order number ***4035 200 041 0***. +It works with the Becker ***Centronic USB Stick*** with the Becker order number ***4035 200 041 0*** and ***4035 000 041 0***. It works for the Becker ***Centronic*** roller shutters, blinds and sun protection as well as for Roto roof windows with RF remotes. It is based on the work of [ole](https://github.com/ole1986) and [Nicolas Berthel](https://github.com/nicolasberthel). @@ -244,6 +244,24 @@ data: unit: 1 ``` +# Events for Remote Commands +In addition to processing remote commands to update cover states, the +integration also fires explicit events of type +`becker_remote_packet_received` for each command it receives from a remote. +Those events can be used to trigger automations when remote buttons are pressed +or for other custom purposes. + +Each event contains data about the remote unit, channel and the command +that has been received, for instance: + +```yaml +event_type: becker_remote_packet_received +data: + unit: "12345" + channel: "1" + command: "up" +``` + # Troubleshooting If you have any trouble follow these steps: - Restart Home Assistant after you have plugged in the USB stick diff --git a/const.py b/const.py index 1e6eebf..515c53b 100644 --- a/const.py +++ b/const.py @@ -20,6 +20,7 @@ DEVICE_CLASS = "shutter" RECEIVE_MESSAGE = "receive_message" +REMOTE_PACKET_EVENT = "remote_packet_received" CONF_CHANNEL = "channel" CONF_COVERS = "covers" diff --git a/cover.py b/cover.py index 37e8624..34ec8fb 100644 --- a/cover.py +++ b/cover.py @@ -28,6 +28,7 @@ async_track_template_result, ) from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import ( CLOSED_POSITION, @@ -250,8 +251,8 @@ async def async_added_to_hass(self): if self._tc.current_position() is None: self._tc.set_position(100 - CLOSED_POSITION) # Setup callback on received packets - receive = self.hass.helpers.dispatcher.async_dispatcher_connect( - f"{DOMAIN}.{RECEIVE_MESSAGE}", self._async_message_received + receive = async_dispatcher_connect( + self.hass, f"{DOMAIN}.{RECEIVE_MESSAGE}", self._async_message_received ) self.async_on_remove(receive) # Setup callback on template changes diff --git a/pybecker/becker_helper.py b/pybecker/becker_helper.py index 6ba32dc..cc38fd4 100644 --- a/pybecker/becker_helper.py +++ b/pybecker/becker_helper.py @@ -36,7 +36,7 @@ ) COMMANDS = {b'0': 'RELEASE', b'1': 'HALT', b'2': 'UP', b'4': 'DOWN', b'8': 'TRAIN'} -COMMUNICATION_TIMEOUT = 0.1 +COMMUNICATION_TIMEOUT = 0.3 _LOGGER = logging.getLogger(__name__) @@ -244,7 +244,7 @@ def run(self) -> None: self._log(packet, "Sent packet: ") # Sleep for thread switch and wait time between packets - time.sleep(0.01) + time.sleep(0.1) # Ensure all packets in queue are send before thread is stopped if self._stop_flag.is_set() and self._write_queue.empty(): break diff --git a/rf_device.py b/rf_device.py index c89464e..c31ad53 100644 --- a/rf_device.py +++ b/rf_device.py @@ -1,5 +1,6 @@ """Handling of the Becker USB device.""" +import codecs import logging import os @@ -7,7 +8,14 @@ from .pybecker.becker import Becker from .pybecker.database import FILE_PATH, SQL_DB_FILE -from .const import CONF_CHANNEL, CONF_UNIT, DOMAIN, RECEIVE_MESSAGE +from .const import ( + COMMANDS, + CONF_CHANNEL, + CONF_UNIT, + DOMAIN, + RECEIVE_MESSAGE, + REMOTE_PACKET_EVENT, +) _LOGGER = logging.getLogger(__name__) @@ -93,3 +101,15 @@ def callback(hass, packet): """Handle Becker device callback for received packets.""" _LOGGER.debug("Received packet for dispatcher") hass.helpers.dispatcher.dispatcher_send(f"{DOMAIN}.{RECEIVE_MESSAGE}", packet) + + # Also fire an explicit event that external applications can listen to + # if that is of use to them. + data = { + "unit": codecs.decode(packet.group("unit_id"), "ascii"), + "channel": codecs.decode(packet.group("channel"), "ascii"), + } + command = packet.group("command") + b"0" + command_name = [nm for nm, cmd in COMMANDS.items() if cmd == command] + if command_name: + data["command"] = command_name[0] + hass.bus.fire(f"{DOMAIN}_{REMOTE_PACKET_EVENT}", data)