diff --git a/docs/configuration.md b/docs/configuration.md index 299ea5d86..e436104a3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1460,6 +1460,49 @@ location: 1-1 port: 2 ``` +#### Arbitrary devices via Shell commands + +Control any device as long as you can come up with a way to control it with a shell command. + +The most practical way of doing it will likely be with a short Python script, although it doesn't have to be. + +```ini {title="Moonraker Config Specification"} +# moonraker.conf + +on_cmd: +# Command to be ran to switch the device on. This parameter must be provided. +off_cmd: +# Command to be ran to switch the device off. This parameter must be provided. +timeout: +# Number of seconds after which the subprocess spawned by the command will be +# forcefully terminated. It should not need to be increased in most cases. Default is 1 second. +``` + +/// Warning +These commands will not recognize the tilde (`~`) shorthand for your home directory, and as such, you must always use full paths when referring to files. + +`python3 /home//printer_data/psuctrl.py on` will work + +`python3 ~/printer_data/psuctrl.py on` will not. +/// + + +Example: + +```ini {title="Moonraker Config Example"} +# moonraker.conf + +# This assumes the presence of a Python script in the path /home/nyan/printer_data/psuctrl.py, +# that recieves an 'on' or 'off' argument and controls the power supply accordingly. +# (eg. by sending a command via serial to an Arduino, which in turn controls the +# PSU directly or via a relay) + +[power atx_psu] +type: shell_cmd +on_cmd: python3 /home/nyan/printer_data/psuctrl.py on +off_cmd: python3 /home/nyan/printer_data/psuctrl.py off +``` + #### Generic HTTP Devices Support for configurable HTTP switches. This device type may be used when diff --git a/moonraker/components/power.py b/moonraker/components/power.py index 2bf45a9a2..2b4dec10d 100644 --- a/moonraker/components/power.py +++ b/moonraker/components/power.py @@ -60,7 +60,8 @@ def __init__(self, config: ConfigHelper) -> None: "smartthings": SmartThings, "hue": HueDevice, "http": GenericHTTP, - "uhubctl": UHubCtl + "uhubctl": UHubCtl, + "shell_cmd": ShellCmd } for section in prefix_sections: @@ -1585,6 +1586,32 @@ async def _run_uhubctl(self, action: str) -> Dict[str, Any]: f"port {self.port}, " ) +class ShellCmd(PowerDevice): + def __init__(self, + config: ConfigHelper, + initial_val: Optional[int] = None + ) -> None: + super().__init__(config) + self.scmd: ShellCommand = self.server.load_component(config, "shell_command") + self.on_cmd = config.get('on_cmd') + self.off_cmd = config.get('off_cmd') + self.timeout = config.getfloat('timeout', 1.0) + + async def init_state(self) -> None: + if self.initial_state is None: + self.set_power("off") + else: + self.set_power("on" if self.initial_state else "off") + + def refresh_status(self) -> None: + pass + + def set_power(self, state) -> None: + if state == "on": + self.scmd.exec_cmd(cmd=self.on_cmd, timeout=self.timeout) + else: + self.scmd.exec_cmd(cmd=self.off_cmd, timeout=self.timeout) + self.state = state # The power component has multiple configuration sections def load_component(config: ConfigHelper) -> PrinterPower: