From dba14aadb6dec7b9fe788966e3291bed173147a6 Mon Sep 17 00:00:00 2001 From: th33xitus <> Date: Wed, 26 Aug 2020 08:18:59 +0200 Subject: [PATCH] new function: install shell command extension by Arksine --- docs/Shell Command Extension.md | 46 ++++++++++++++++++ resources/shell_command.py | 83 +++++++++++++++++++++++++++++++++ scripts/functions.sh | 73 +++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+) create mode 100644 docs/Shell Command Extension.md create mode 100755 resources/shell_command.py diff --git a/docs/Shell Command Extension.md b/docs/Shell Command Extension.md new file mode 100644 index 0000000..b3d7bbd --- /dev/null +++ b/docs/Shell Command Extension.md @@ -0,0 +1,46 @@ +# Shell Command Extension + +### Creator of this extension is [Arksine](https://github.com/Arksine). + +This is a brief explanation of how to use the shell command extension for Klipper, which you can install with KIAUH. + +After installing the extension you can execute linux commands or even scripts from within Klipper with custom commands defined in your printer.cfg. + +#### How to configure a shell command: + +```shell +# Runs a linux command or script from within klipper. Note that sudo commands +# that require password authentication are disallowed. All executable scripts +# should include a shebang. +# [shell_command my_shell_cmd] +#command: +# The linux shell command/script to be executed. This parameter must be +# provided +#timeout: 2. +# The timeout in seconds until the command is forcably terminated. Default +# is 2 seconds. +#verbose: True +# If enabled, the command's output will be forwarded to the terminal. Its +# recommended to set this to false for commands that my run in quick +# succession. Default is True. +``` + +Once you have set up a shell command with the given parameters from above in your printer.cfg you can run the command as follows: +`RUN_SHELL_COMMAND CMD=name` + +Example: + +``` +[shell_command hello_world] +command: echo hello world +timeout: 2. +verbose: True +``` + +Execute with: +`RUN_SHELL_COMMAND CMD=hello_world` + +## Warning + +This extension may have a high potential for abuse if not used carefully! Also, depending on the command you execute, high system loads may occur and can cause system instabilities. +Use this extension at your own risk and only if you know what you are doing! diff --git a/resources/shell_command.py b/resources/shell_command.py new file mode 100755 index 0000000..4138172 --- /dev/null +++ b/resources/shell_command.py @@ -0,0 +1,83 @@ +# Run a shell command via gcode +# +# Copyright (C) 2019 Eric Callahan +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import os +import shlex +import subprocess +import logging + +class ShellCommand: + def __init__(self, config): + self.name = config.get_name().split()[-1] + self.printer = config.get_printer() + self.gcode = self.printer.lookup_object('gcode') + cmd = config.get('command') + cmd = os.path.expanduser(cmd) + self.command = shlex.split(cmd) + self.timeout = config.getfloat('timeout', 2., above=0.) + self.verbose = config.getboolean('verbose', True) + self.proc_fd = None + self.partial_output = "" + self.gcode.register_mux_command( + "RUN_SHELL_COMMAND", "CMD", self.name, + self.cmd_RUN_SHELL_COMMAND, + desc=self.cmd_RUN_SHELL_COMMAND_help) + + def _process_output(self, eventime): + if self.proc_fd is None: + return + try: + data = os.read(self.proc_fd, 4096) + except Exception: + pass + data = self.partial_output + data + if '\n' not in data: + self.partial_output = data + return + elif data[-1] != '\n': + split = data.rfind('\n') + 1 + self.partial_output = data[split:] + data = data[:split] + self.gcode.respond_info(data) + + cmd_RUN_SHELL_COMMAND_help = "Run a linux shell command" + def cmd_RUN_SHELL_COMMAND(self, params): + reactor = self.printer.get_reactor() + try: + proc = subprocess.Popen( + self.command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + except Exception: + logging.exception( + "shell_command: Command {%s} failed" % (self.name)) + raise self.gcode.error("Error running command {%s}" % (self.name)) + if self.verbose: + self.proc_fd = proc.stdout.fileno() + self.gcode.respond_info("Running Command {%s}...:" % (self.name)) + hdl = reactor.register_fd(self.proc_fd, self._process_output) + eventtime = reactor.monotonic() + endtime = eventtime + self.timeout + complete = False + while eventtime < endtime: + eventtime = reactor.pause(eventtime + .05) + if proc.poll() is not None: + complete = True + break + if not complete: + proc.terminate() + if self.verbose: + if self.partial_output: + self.gcode.respond_info(self.partial_output) + self.partial_output = "" + if complete: + msg = "Command {%s} finished\n" % (self.name) + else: + msg = "Command {%s} timed out" % (self.name) + self.gcode.respond_info(msg) + reactor.unregister_fd(hdl) + self.proc_fd = None + + +def load_config_prefix(config): + return ShellCommand(config) diff --git a/scripts/functions.sh b/scripts/functions.sh index 713e332..f21f922 100755 --- a/scripts/functions.sh +++ b/scripts/functions.sh @@ -331,3 +331,76 @@ remove_branding(){ esac done } + +install_extension_shell_command(){ + echo + top_border + echo -e "| You are about to install the shell command extension. |" + echo -e "| Please make sure to read the instructions before you |" + echo -e "| continue and remember that there are potential risks! |" + bottom_border + while true; do + read -p "${cyan}###### Do you want to continue? (Y/n):${default} " yn + case "$yn" in + Y|y|Yes|yes|"") + if [ -d $KLIPPER_DIR/klippy/extras ] && [ ! -f $KLIPPER_DIR/klippy/extras/shell_command.py ] ; then + status_msg "Installing shell command extension ..." + stop_klipper + cp ${HOME}/kiauh/resources/shell_command.py $KLIPPER_DIR/klippy/extras + status_msg "Creating example macro ..." + create_shell_command_example + ok_msg "Example macro created!" + ok_msg "Shell command extension installed!" + restart_klipper + else + if [ ! -d $KLIPPER_DIR/klippy/extras ]; then + ERROR_MSG="Folder ~/klipper/klippy/extras not found!" + fi + if [ -f $KLIPPER_DIR/klippy/extras/shell_command.py ]; then + ERROR_MSG="Extension already installed!" + fi + fi + break;; + N|n|No|no) + break;; + esac + done +} + +create_shell_command_example(){ + unset SC_ENTRY + unset write_entries + #check for a SAVE_CONFIG entry + SC="#*# <---------------------- SAVE_CONFIG ---------------------->" + if [[ $(grep "$SC" ${HOME}/printer.cfg) ]]; then + SC_LINE=$(grep -n "$SC" $PRINTER_CFG | cut -d ":" -f1) + PRE_SC_LINE=$(expr $SC_LINE - 1) + SC_ENTRY="true" + else + SC_ENTRY="false" + fi + #example shell command + write_entries+=("[shell_command hello_world]\ncommand: echo hello world\ntimeout: 2.\nverbose: True") + #example macro + write_entries+=("[gcode_macro HELLO_WORLD]\ngcode:\n RUN_SHELL_COMMAND CMD=hello_world") + if [ "${#write_entries[@]}" != "0" ]; then + write_entries+=("\\\n############################\n##### CREATED BY KIAUH #####\n############################") + write_entries=("############################\n" "${write_entries[@]}") + fi + #execute writing + status_msg "Writing to printer.cfg ..." + if [ "$SC_ENTRY" = "true" ]; then + PRE_SC_LINE="$(expr $SC_LINE - 1)a" + for entry in "${write_entries[@]}" + do + sed -i "$PRE_SC_LINE $entry" $PRINTER_CFG + done + fi + if [ "$SC_ENTRY" = "false" ]; then + LINE_COUNT="$(wc -l < $PRINTER_CFG)a" + for entry in "${write_entries[@]}" + do + sed -i "$LINE_COUNT $entry" $PRINTER_CFG + done + fi +} \ No newline at end of file