power: initial support for Klipper Devices

Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
This commit is contained in:
Pedro Lamas 2022-01-29 12:53:30 +00:00 committed by GitHub
parent 7c8c0e715f
commit 287982acdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 147 additions and 13 deletions

View File

@ -332,8 +332,9 @@ The following configuration options are available for all power device types:
[power device_name]
type:
# The type of device. Can be either gpio, rf, tplink_smartplug, tasmota
# shelly, homeseer, homeassistant, loxonev1, or mqtt.
# The type of device. Can be either gpio, klipper_device, rf,
# tplink_smartplug, tasmota, shelly, homeseer, homeassistant, loxonev1,
# or mqtt.
# This parameter must be provided.
off_when_shutdown: False
# If set to True the device will be powered off when Klipper enters
@ -407,16 +408,12 @@ initial_state: off
timer:
# A time (in seconds) after which the device will power off after being.
# switched on. This effectively turns the device into a momentary switch.
# This option is available for gpio, tplink_smartplug, shelly, and tasmota
# devices. The timer may be a floating point value for gpio types, it should
# be an integer for all other types. The default is no timer is set.
# This option is available for gpio, klipper_device, tplink_smartplug,
# shelly, and tasmota devices. The timer may be a floating point value
# for gpio types, it should be an integer for all other types. The
# default is no timer is set.
```
!!! Note
Moonraker can only be used to toggle host device GPIOs (ie: GPIOs on your
PC or SBC). Moonraker cannot control GPIOs on an MCU, Klipper should be
used for this purpose.
Examples:
```ini
@ -443,6 +440,42 @@ pin: gpiochip0/gpio17
initial_state: on
```
#### Klipper Device Configuration
The following options are available for `klipper_device` device types:
```ini
# moonraker.conf
object_name: output_pin my_pin
# The Klipper object_name (as defined in your Klipper config). Valid examples:
# output_pin my_pin
# This parameter must be provided for "klipper_device" type devices.
# Currently, only `output_pin` Klipper devices are supported.
timer:
# A time (in seconds) after which the device will power off after being.
# switched on. This effectively turns the device into a momentary switch.
# This option is available for gpio, klipper_device, tplink_smartplug,
# shelly, and tasmota devices. The timer may be a floating point value
# for gpio types, it should be an integer for all other types. The
# default is no timer is set.
```
!!! Note
These devices cannot be used to toggle Klipper's power supply as they
require Klipper to actually be running.
Examples:
```ini
# moonraker.conf
# Control a relay providing power to the printer
[power my_pin]
type: klipper_device
object_name: output_pin my_pin
```
#### RF Device Configuration
The following options are available for gpio controlled `rf` device types:
@ -466,9 +499,10 @@ initial_state: off
timer:
# A time (in seconds) after which the device will power off after being.
# switched on. This effectively turns the device into a momentary switch.
# This option is available for gpio, tplink_smartplug, shelly, and tasmota
# devices. The timer may be a floating point value for gpio types, it should
# be an integer for all other types. The default is no timer is set.
# This option is available for gpio, klipper_device, tplink_smartplug,
# shelly, and tasmota devices. The timer may be a floating point value
# for gpio types, it should be an integer for all other types. The
# default is no timer is set.
on_code:
off_code:
# Valid binary codes that are sent via the RF transmitter.

View File

@ -44,6 +44,7 @@ class PrinterPower:
logging.info(f"Power component loading devices: {prefix_sections}")
dev_types = {
"gpio": GpioDevice,
"klipper_device": KlipperDevice,
"tplink_smartplug": TPLinkSmartPlug,
"tasmota": Tasmota,
"shelly": Shelly,
@ -514,6 +515,105 @@ class GpioDevice(PowerDevice):
self.timer_handle.cancel()
self.timer_handle = None
class KlipperDevice(PowerDevice):
def __init__(self,
config: ConfigHelper,
initial_val: Optional[int] = None
) -> None:
if config.getboolean('off_when_shutdown', None) is not None:
raise config.error(
"Option 'off_when_shutdown' in section "
f"[{config.get_name()}] is unsupported for 'klipper_device'")
if config.getboolean('klipper_restart', None) is not None:
raise config.error(
"Option 'klipper_restart' in section "
f"[{config.get_name()}] is unsupported for 'klipper_device'")
super().__init__(config)
self.off_when_shutdown = False
self.klipper_restart = False
self.timer: Optional[float] = config.getfloat('timer', None)
if self.timer is not None and self.timer < 0.000001:
raise config.error(
f"Option 'timer' in section [{config.get_name()}] must "
"be above 0.0")
self.timer_handle: Optional[asyncio.TimerHandle] = None
self.object_name = config.get('object_name', '')
if not self.object_name.startswith("output_pin "):
raise config.error(
"Currently only Klipper 'output_pin' objects supported for "
f"'object_name' in section [{config.get_name()}]")
self.server.register_event_handler(
"server:status_update", self._status_update)
self.server.register_event_handler(
"server:klippy_ready", self._handle_ready)
self.server.register_event_handler(
"server:klippy_disconnect", self._handle_disconnect)
def _status_update(self, data: Dict[str, Any]) -> None:
self._set_state_from_data(data)
async def _handle_ready(self) -> None:
kapis: APIComp = self.server.lookup_component('klippy_apis')
sub: Dict[str, Optional[List[str]]] = {self.object_name: None}
try:
data = await kapis.subscribe_objects(sub)
self._set_state_from_data(data)
except self.server.error as e:
logging.info(f"Error subscribing to {self.object_name}")
async def _handle_disconnect(self) -> None:
self._set_state("init")
def process_klippy_shutdown(self) -> None:
self._set_state("init")
def refresh_status(self) -> None:
pass
async def set_power(self, state) -> None:
if self.timer_handle is not None:
self.timer_handle.cancel()
self.timer_handle = None
try:
kapis: APIComp = self.server.lookup_component('klippy_apis')
object_name = self.object_name[len("output_pin "):]
object_name_value = "1" if state == "on" else "0"
await kapis.run_gcode(
f"SET_PIN PIN={object_name} VALUE={object_name_value}")
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
self._check_timer()
def _set_state_from_data(self, data) -> None:
if self.object_name not in data:
return
is_on = data.get(self.object_name, {}).get('value', 0.0) > 0.0
state = "on" if is_on else "off"
self._set_state(state)
def _set_state(self, state) -> None:
last_state = self.state
self.state = state
if last_state != state:
self.notify_power_changed()
def _check_timer(self):
if self.state == "on" and self.timer is not None:
event_loop = self.server.get_event_loop()
power: PrinterPower = self.server.lookup_component("power")
self.timer_handle = event_loop.delay_callback(
self.timer, power.set_device_power, self.name, "off")
def close(self) -> None:
if self.timer_handle is not None:
self.timer_handle.cancel()
self.timer_handle = None
class RFDevice(GpioDevice):
# Protocol definition