From c3f1b290f8667f771f2d58a3f012e87853c4e85c Mon Sep 17 00:00:00 2001 From: danijoo Date: Fri, 1 Oct 2021 07:45:15 +0100 Subject: [PATCH] power: add RF transmitter support Signed-off: Daniel Bauer --- docs/configuration.md | 14 ++++++--- moonraker/components/power.py | 53 +++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index d809ee1..b9f6c5c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -190,7 +190,7 @@ gcode: ``` ## `[power]` -Enables device power control. Currently GPIO (relays), TPLink Smartplug, +Enables device power control. Currently GPIO (relays), RF transmitter, TPLink Smartplug, and Tasmota (via http) devices, HomeAssistant switch are supported. ```ini @@ -198,7 +198,7 @@ and Tasmota (via http) devices, HomeAssistant switch are supported. [power device_name] type: gpio -# The type of device. Can be either gpio, tplink_smartplug, tasmota +# The type of device. Can be either gpio, rf, tplink_smartplug, tasmota # shelly, homeseer, homeassistant, or loxonev1. # This parameter must be provided. off_when_shutdown: False @@ -217,7 +217,7 @@ restart_delay: 1. # If "restart_klipper_when_powered" is set, this option specifies the amount # of time (in seconds) to delay the restart. Default is 1 second. pin: gpiochip0/gpio26 -# The pin to use for GPIO devices. The chip is optional, if left out +# The pin to use for GPIO and RF devices. The chip is optional, if left out # then the module will default to gpiochip0. If one wishes to invert # the signal, a "!" may be prefixed to the pin. Valid examples: # gpiochip0/gpio26 @@ -297,6 +297,12 @@ output_id: # The output_id is the name of a programmed output, virtual input or virtual # output in the loxone config his output_id (name) may only be used once in # the loxone config +on_code: +off_code: +# The above options are used for "rf" devices. The +# codes should be valid binary codes that are send via the RF transmitter. +# For example: 1011. + ``` Below are some potential examples: ```ini @@ -559,4 +565,4 @@ default_qos: 0 api_qos: # The QOS level to use for the API topics. If not provided, the # value specified by "default_qos" will be used. -``` \ No newline at end of file +``` diff --git a/moonraker/components/power.py b/moonraker/components/power.py index 580afe0..f9dee07 100644 --- a/moonraker/components/power.py +++ b/moonraker/components/power.py @@ -12,6 +12,7 @@ import json import struct import socket import asyncio +import time from tornado.iostream import IOStream from tornado.httpclient import AsyncHTTPClient from tornado.escape import json_decode @@ -68,7 +69,8 @@ class PrinterPower: "shelly": Shelly, "homeseer": HomeSeer, "homeassistant": HomeAssistant, - "loxonev1": Loxonev1 + "loxonev1": Loxonev1, + "rf": RFDevice } try: for section in prefix_sections: @@ -79,7 +81,7 @@ class PrinterPower: if dev_class is None: raise config.error(f"Unsupported Device Type: {dev_type}") dev = dev_class(cfg) - if isinstance(dev, GpioDevice): + if isinstance(dev, GpioDevice) or isinstance(dev, RFDevice): if not HAS_GPIOD: continue dev.configure_line(cfg, self.chip_factory) @@ -470,6 +472,53 @@ class GpioDevice(PowerDevice): def close(self) -> None: self.line.release() +class RFDevice(GpioDevice): + + # Protocol definition + # [1, 3] means HIGH is set for 1x pulse_len and LOW for 3x pulse_len + ZERO_BIT = [1, 3] # zero bit + ONE_BIT = [3, 1] # one bit + SYNC_BIT = [1, 31] # sync between + PULSE_LEN = 0.00035 # length of a single pulse + RETRIES = 10 # send the code this many times + + def __init__(self, config: ConfigHelper): + super().__init__(config) + self.on = config.get("on_code").zfill(24) + self.off = config.get("off_code").zfill(24) + + def initialize(self) -> None: + self.set_power("on" if self.initial_state else "off") + + def _transmit_digit(self, waveform) -> None: + self.line.set_value(1) + time.sleep(waveform[0]*RFDevice.PULSE_LEN) + self.line.set_value(0) + time.sleep(waveform[1]*RFDevice.PULSE_LEN) + + def _transmit_code(self, code) -> None: + for _ in range(RFDevice.RETRIES): + for i in code: + if i == "1": + self._transmit_digit(RFDevice.ONE_BIT) + elif i == "0": + self._transmit_digit(RFDevice.ZERO_BIT) + self._transmit_digit(RFDevice.SYNC_BIT) + + def set_power(self, state) -> None: + try: + if state == "on": + code = self.on + else: + code = self.off + self._transmit_code(code) + except Exception: + self.state = "error" + msg = f"Error Toggling Device Power: {self.name}" + logging.exception(msg) + raise self.server.error(msg) from None + self.state = state + # This implementation based off the work tplink_smartplug # script by Lubomir Stroetmann available at: