From c589287e0b759805e2ddfbbd77bce426428d9799 Mon Sep 17 00:00:00 2001 From: jordanruthe <31575189+jordanruthe@users.noreply.github.com> Date: Mon, 27 Jul 2020 14:08:48 -0400 Subject: [PATCH] power: printer power on/off control plugin Sign-off-by: Jordan Ruthe --- docs/installation.md | 34 +++++++- moonraker/plugins/power.py | 167 +++++++++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 moonraker/plugins/power.py diff --git a/docs/installation.md b/docs/installation.md index 819a632..683b2e8 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -174,8 +174,9 @@ batch subscription updates together. ## Plugin Configuration The core plugins are configured via the primary configuration above. Optional -plugins each need their own configuration. Currently the only optional plugin -available is the `paneldue` plugin, which can be configured as follows: +plugins each need their own configuration as outlined below. + +### PanelDue Plugin ``` [moonraker_plugin paneldue] @@ -206,4 +207,31 @@ gcode: { printer.moonraker.action_call_remote_method( "paneldue_beep", frequency=FREQUENCY|int, duration=DURATION|float) } -``` \ No newline at end of file +``` + +### Power Control Plugin +``` +[moonraker_plugin power] +devices: printer, led +# A comma separated list of devices you wish to control. Do not use spaces in +# the device's name here +#{dev}_name: Friendly Name +# This is the friendly name for the device. {dev} must be swapped for the name +# of the device used under devices, as an example: +# printer_name: My Printer +{dev}_pin: 23 +# This option is required. +# The GPIO Pin number you wish to control +#{dev}_active_low: False +# If you have a device that needs a low or 0 signal to be turned on, set this +# option to True. +``` + +Define the devices you wish to control under _devices_ with a comma separated +list. For device specific configrations, swap {dev} for the name of the device +that you listed under devices. + +Each device can have a Friendly Name, pin, and activehigh set. Pin is the only +required option. For devices that should be active when the signal is 0 or low, +set {dev}_activehigh to False, otherwise don't put the option in the +configuration. diff --git a/moonraker/plugins/power.py b/moonraker/plugins/power.py new file mode 100644 index 0000000..d8d9700 --- /dev/null +++ b/moonraker/plugins/power.py @@ -0,0 +1,167 @@ +# Raspberry Pi Power Control +# +# Copyright (C) 2020 Jordan Ruthe +# +# This file may be distributed under the terms of the GNU GPLv3 license. + +import logging +import os +import time + +class PrinterPower: + def __init__(self, server): + self.server = server + self.server.register_endpoint( + "/printer/power/devices", "power_devices", ['GET'], + self._handle_list_devices) + self.server.register_endpoint( + "/printer/power/status", "power_status", ['GET'], + self._handle_power_request) + self.server.register_endpoint( + "/printer/power/on", "power_on", ['POST'], + self._handle_power_request) + self.server.register_endpoint( + "/printer/power/off", "power_off", ['POST'], + self._handle_power_request) + + self.current_dev = None + self.devices = {} + + async def _handle_list_devices(self, path, method, args): + output = {"devices": []} + for dev in self.devices: + output['devices'].append({ + "name": self.devices[dev]["name"], + "id": dev + }) + return output + + async def _handle_power_request(self, path, method, args): + if len(args) == 0: + if path == "/printer/power/status": + args = self.devices + else: + return "no_devices" + + result = {} + for dev in args: + if dev not in self.devices: + result[dev] = "device_not_found" + continue + + GPIO.verify_pin(self.devices[dev]["pin"], self.devices[dev]["active_low"]) + if path == "/printer/power/on": + GPIO.set_pin_value(self.devices[dev]["pin"], 1) + elif path == "/printer/power/off": + GPIO.set_pin_value(self.devices[dev]["pin"], 0) + elif path != "/printer/power/status": + raise self.server.error("Unsupported power request") + + self.devices[dev]["status"] = GPIO.is_pin_on(self.devices[dev]["pin"]) + + result[dev] = self.devices[dev]["status"] + return result + + def load_config(self, config): + if "devices" not in config.keys(): + return + + devices = config["devices"].split(',') + logging.info("Power plugin loading devices: " + str(devices)) + + for dev in devices: + dev = dev.strip() + if dev + "_pin" not in config.keys(): + logging.info("Power plugin: ERR " + dev + " does not have a pin defined") + continue + + self.devices[dev] = { + "name": config[dev + "_name"] if dev + "_name" in config.keys() else dev, + "pin": int(config[dev + "_pin"]), + "active_low": 1 if dev+"_active_low" in config.keys() and config[dev+"_active_low"] == "True" else 0, + "status": None + } + + try: + logging.debug("Attempting to configure pin GPIO" + str(self.devices[dev]["pin"])) + GPIO.setup_pin(self.devices[dev]["pin"], self.devices[dev]["active_low"]) + self.devices[dev]["status"] = GPIO.is_pin_on(self.devices[dev]["pin"]) + except: + logging.info("Power plugin: ERR Problem configuring the output pin for device " + dev + ". Removing device") + self.devices.pop(dev, None) + continue + +class GPIO: + gpio_root = "/sys/class/gpio" + + @staticmethod + def _set_gpio_option(gpio, option, value): + GPIO._write( + os.path.join(GPIO.gpio_root, "gpio"+str(gpio), option), + value + ) + + def _get_gpio_option(pin, option): + return GPIO._read( + os.path.join(GPIO.gpio_root, "gpio"+str(pin), option) + ) + + @staticmethod + def _write(file, data): + with open(file, 'w') as f: + f.write(str(data)) + f.flush() + + @staticmethod + def _read(file): + with open(file, 'r') as f: + f.seek(0) + return f.read().strip() + + @staticmethod + def verify_pin(pin, active_low=1): + gpiopath = os.path.join(GPIO.gpio_root, "gpio"+str(pin)) + if not os.path.exists(gpiopath): + logging.info("Re-intializing GPIO"+str(pin)) + GPIO.setup_pin(pin, active_low) + return + + if GPIO._get_gpio_option(pin, "active_low").strip() != str(active_low): + GPIO._set_gpio_option(pin, "active_low", active_low) + + if GPIO._get_gpio_option(pin, "direction").strip() != "out": + GPIO._set_gpio_option(pin, "direction", "out") + + @staticmethod + def setup_pin(pin, active_low=1): + pin = int(pin) + active_low = 1 if active_low == 1 else 0 + + gpiopath = os.path.join(GPIO.gpio_root, "gpio"+str(pin)) + if not os.path.exists(gpiopath): + GPIO._write( + os.path.join(GPIO.gpio_root, "export"), + pin) + logging.info("Waiting for GPIO"+str(pin)+" to initialize") + while os.stat(os.path.join(GPIO.gpio_root, "gpio"+str(pin),"active_low")).st_gid == 0: + time.sleep(.1) + + if GPIO._get_gpio_option(pin, "active_low").strip() != str(active_low): + GPIO._set_gpio_option(pin, "active_low", active_low) + + if GPIO._get_gpio_option(pin, "direction").strip() != "out": + GPIO._set_gpio_option(pin, "direction", "out") + + + @staticmethod + def is_pin_on(pin): + return "on" if int(GPIO._get_gpio_option(pin, "value")) else "off" + + @staticmethod + def set_pin_value(pin, active): + value = 1 if (active == 1) else 0 + GPIO._set_gpio_option(pin, "value", value) + + +def load_plugin(server): + return PrinterPower(server)