diff --git a/README.md b/README.md index f7f5ef3..3c6bb57 100644 --- a/README.md +++ b/README.md @@ -200,8 +200,5 @@ You just need to add the following section: - control - gcodeviewer - terminal - - plugin_enclosure
-
-
-     
-
+      - plugin_enclosure
+
\ No newline at end of file diff --git a/octoprint_enclosure/__init__.py b/octoprint_enclosure/__init__.py index 2632b5e..11cd2c0 100644 --- a/octoprint_enclosure/__init__.py +++ b/octoprint_enclosure/__init__.py @@ -122,11 +122,22 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, self.print_complete = False def get_settings_version(self): - return 5 + return 6 - def on_settings_migrate(self, target, current): + def on_settings_migrate(self, target, current=None): self._logger.warn( "######### current settings version %s target settings version %s #########", current, target) + + if current >= 4 and target == 6: + self._logger.warn( + "######### migrating settings to v6 #########") + old_outputs = self._settings.get(["rpi_outputs"]) + for rpi_output in old_outputs: + if 'shutdown_on_failed' not in rpi_output: + rpi_output['shutdown_on_failed'] = False + if 'shell_script' not in rpi_output: + rpi_output['shell_script'] = "" + if current == 4 and target == 5: self._logger.warn( "######### migrating settings from v4 to v5 #########") @@ -172,7 +183,6 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, val = GPIO.input(pin) if not rpi_output['active_low'] else ( not GPIO.input(pin)) index = self.to_int(rpi_output['index_id']) - # result.append(dict(index_id=rpi_output['index_id'], value=val)) gpio_status.append(dict(index_id=index, status=val)) return flask.Response(json.dumps(gpio_status), mimetype='application/json') @@ -186,6 +196,14 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, self.write_gpio(self.to_int(rpi_output['gpio_pin']), val) return flask.jsonify(success=True) + @octoprint.plugin.BlueprintPlugin.route("/sendShellCommand", methods=["GET"]) + def send_send_shell_command(self): + output_index = self.to_int(flask.request.values["index_id"]) + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int( + r_out['index_id']) == output_index].pop() + self.send_gcode_command(rpi_output['shell_script']) + return flask.jsonify(success=True) + @octoprint.plugin.BlueprintPlugin.route("/setAutoStartUp", methods=["GET"]) def set_auto_startup(self): index = flask.request.values["index_id"] @@ -273,6 +291,19 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, return flask.jsonify(success=True) + def send_shell_command(self, command): + try: + stdout = (Popen(command, shell=True, stdout=PIPE).stdout).read() + + response = stdout or "Command executed with no return value." + + self._plugin_manager.send_plugin_message( + self._identifier, dict(is_msg=True, msg=response, msg_type="success")) + except Exception as ex: + self.log_error(ex) + self._plugin_manager.send_plugin_message( + self._identifier, dict(is_msg=True, msg="Could not execute shell script", msg_type="error")) + def send_neopixel_command(self, led_pin, led_count, led_brightness, red, green, blue, address, neopixel_dirrect, index_id, queue_id=None): """Send neopixel command @@ -839,7 +870,7 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, tempstr + " as pin numbers. Please update GPIO accordingly!" self._logger.info(warn_msg) self._plugin_manager.send_plugin_message( - self._identifier, dict(isMsg=True, msg=warn_msg)) + self._identifier, dict(is_msg=True, msg=warn_msg, msg_type="error")) GPIO.setwarnings(False) except Exception as ex: self.log_error(ex) @@ -1032,33 +1063,37 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, if self._settings.get(["debug"]) is True: self._logger.info( "GPIO event triggered on channel %s", channel) - for rpi_input in [r_inp for r_inp in self.rpi_inputs if self.to_int(r_inp['gpio_pin']) == self.to_int(channel)]: - gpio_pin = self.to_int(rpi_input['gpio_pin']) - controlled_io = self.to_int(rpi_input['controlled_io']) - if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): - rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int( - r_out['index_id']) == controlled_io].pop() - if rpi_output['output_type'] == 'regular': - if rpi_input['controlled_io_set_value'] == 'toggle': - val = GPIO.LOW if GPIO.input(self.to_int( - rpi_output['gpio_pin'])) == GPIO.HIGH else GPIO.HIGH - else: - val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH - self.write_gpio(self.to_int( - rpi_output['gpio_pin']), val) - for notification in self.notifications: - if notification['gpioAction']: - msg = "GPIO control action caused by input " + str(rpi_input['label']) + ". Setting GPIO" + str( - rpi_input['controlled_io']) + " to: " + str(rpi_input['controlled_io_set_value']) - self.send_notification(msg) - if rpi_output['output_type'] == 'gcode_output': - self.send_gcode_command(rpi_output['gcode']) - for notification in self.notifications: - if notification['gpioAction']: - msg = "GPIO control action caused by input " + \ - str(rpi_input['label']) + \ - ". Sending GCODE command" - self.send_notification(msg) + rpi_input = [r_inp for r_inp in self.rpi_inputs if self.to_int( + r_inp['gpio_pin']) == self.to_int(channel)].pop() + gpio_pin = self.to_int(rpi_input['gpio_pin']) + controlled_io = self.to_int(rpi_input['controlled_io']) + if (rpi_input['edge'] == 'fall') ^ GPIO.input(gpio_pin): + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int( + r_out['index_id']) == controlled_io].pop() + if rpi_output['output_type'] == 'regular': + if rpi_input['controlled_io_set_value'] == 'toggle': + val = GPIO.LOW if GPIO.input(self.to_int( + rpi_output['gpio_pin'])) == GPIO.HIGH else GPIO.HIGH + else: + val = GPIO.LOW if rpi_input['controlled_io_set_value'] == 'low' else GPIO.HIGH + self.write_gpio(self.to_int( + rpi_output['gpio_pin']), val) + for notification in self.notifications: + if notification['gpioAction']: + msg = "GPIO control action caused by input " + str(rpi_input['label']) + ". Setting GPIO" + str( + rpi_input['controlled_io']) + " to: " + str(rpi_input['controlled_io_set_value']) + self.send_notification(msg) + if rpi_output['output_type'] == 'gcode_output': + self.send_gcode_command(rpi_output['gcode']) + for notification in self.notifications: + if notification['gpioAction']: + msg = "GPIO control action caused by input " + \ + str(rpi_input['label']) + \ + ". Sending GCODE command" + self.send_notification(msg) + if rpi_output['output_type'] == 'shell_output': + command = rpi_output['shell_script'] + self.shell_command(command) except Exception as ex: self.log_error(ex) pass @@ -1088,11 +1123,20 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, self._printer.cancel_print() elif rpi_input['printer_action'] == 'toggle': self._logger.info("Printer action toggle.") + if self._printer.is_operational(): + self._printer.toggle_pause_print() + else: + self._printer.connect() + elif rpi_input['printer_action'] == 'start': + self._logger.info("Printer action start.") + self._printer.start_print() + elif rpi_input['printer_action'] == 'toggle_job': + self._logger.info("Printer action toggle_job.") if self._printer.is_operational(): if self._printer.is_printing(): - self._printer.pause_print() - elif self._printer.is_paused(): - self._printer.resume_print() + self._printer.cancel_print() + elif self._printer.is_ready(): + self._printer.start_print() else: self._printer.connect() elif rpi_input['printer_action'] == 'stop_temp_hum_control': @@ -1238,8 +1282,6 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, delay_seconds = self.to_float(shutdown_time) self.schedule_auto_shutdown_outputs( rpi_output, delay_seconds) - if rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = 0 self.run_tasks() self.update_ui() @@ -1292,6 +1334,10 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, if (rpi_output['output_type'] == 'neopixel_indirect' or rpi_output['output_type'] == 'neopixel_direct'): self.add_neopixel_output_to_queue( rpi_output, shutdown_delay_seconds, 0, 0, 0, sufix) + if rpi_output['output_type'] == 'temp_hum_control': + value = 0 + self.add_temperature_output_temperature_queue( + delay_seconds, rpi_output, value, sufix) if self._settings.get(["debug"]) is True: self._logger.info("Events scheduled to run %s", self.event_queue) @@ -1334,7 +1380,9 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, self.add_neopixel_output_to_queue( rpi_output, delay_seconds, red, green, blue, sufix) if rpi_output['output_type'] == 'temp_hum_control': - rpi_output['temp_ctr_set_value'] = rpi_output['temp_ctr_default_value'] + value = rpi_output['temp_ctr_default_value'] + self.add_temperature_output_temperature_queue( + delay_seconds, rpi_output, value, sufix) if self._settings.get(["debug"]) is True: self._logger.info("Events scheduled to run %s", self.event_queue) @@ -1423,6 +1471,43 @@ class EnclosurePlugin(octoprint.plugin.StartupPlugin, self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + def add_temperature_output_temperature_queue(self, delay_seconds, rpi_output, value, sufix): + queue_id = '{0!s}_{1!s}'.format(rpi_output['index_id'], sufix) + if self._settings.get(["debug"]) is True: + self._logger.info( + "Scheduling temperature control id %s on %s delay_seconds", queue_id, delay_seconds) + + thread = threading.Timer(delay_seconds, + self.write_temperature_to_output, + args=[self.to_int(rpi_output['index_id']), value, queue_id]) + + self.event_queue.append(dict(queue_id=queue_id, thread=thread)) + + def write_temperature_to_output(self, rpi_output_index, value, queue_id=None): + try: + rpi_output = [r_out for r_out in self.rpi_outputs if self.to_int( + r_out['index_id']) == rpi_output_index].pop() + + if rpi_output['output_type'] == 'temp_hum_control': + rpi_output['temp_ctr_set_value'] = value + + if self._settings.get(["debug"]) is True: + if queue_id is not None: + self._logger.info("Runing scheduled queue id %s", queue_id) + self._logger.info( + "Setting temperature to output index: %s value %s", rpi_output['index_id'], value) + + self.update_ui() + if queue_id is not None: + self.stop_queue_item(queue_id) + + except Exception as ex: + template = "An exception of type {0} occurred on {1} when writing on pin {2}. Arguments:\n{3!r}" + message = template.format( + type(ex).__name__, inspect.currentframe().f_code.co_name, gpio, ex.args) + self._logger.warn(message) + pass + def get_startup_delay_from_output(self, rpi_output): start_up_time = rpi_output['startup_time'] if self.is_hour(start_up_time): diff --git a/octoprint_enclosure/static/js/enclosure.js b/octoprint_enclosure/static/js/enclosure.js index da6cf56..ffc07a3 100644 --- a/octoprint_enclosure/static/js/enclosure.js +++ b/octoprint_enclosure/static/js/enclosure.js @@ -21,7 +21,7 @@ $(function () { self.settings_possible_outputs = ko.pureComputed(function () { return ko.utils.arrayFilter(self.settingsViewModel.settings.plugins.enclosure.rpi_outputs(), function (item) { - return ((item.output_type() === "regular" && !item.toggle_timer()) || item.output_type() === "gcode_output"); + return ((item.output_type() === "regular" && !item.toggle_timer()) || item.output_type() === "gcode_output" || item.output_type() === "shell_output"); }); }); @@ -236,11 +236,11 @@ $(function () { }) } - if (data.isMsg) { + if (data.is_msg) { new PNotify({ title: "Enclosure", text: data.msg, - type: "error" + type: data.msg_type }); } }; @@ -362,6 +362,7 @@ $(function () { index_id: ko.observable(nextIndex), label: ko.observable("Ouput " + nextIndex), output_type: ko.observable("regular"), + shell_script: ko.observable(""), gpio_pin: ko.observable(0), gpio_status: ko.observable(false), hide_btn_ui: ko.observable(false), @@ -510,6 +511,19 @@ $(function () { }); }; + self.handleShellOutput = function (item, form) { + var request = { + "index_id": item.index_id() + }; + + $.ajax({ + type: "GET", + dataType: "json", + data: request, + url: self.buildPluginUrl("/sendShellCommand") + }); + }; + self.switchAutoStartUp = function (item) { var request = { diff --git a/octoprint_enclosure/templates/enclosure_navbar.jinja2 b/octoprint_enclosure/templates/enclosure_navbar.jinja2 index 64a6b42..143bcaa 100644 --- a/octoprint_enclosure/templates/enclosure_navbar.jinja2 +++ b/octoprint_enclosure/templates/enclosure_navbar.jinja2 @@ -5,6 +5,7 @@ diff --git a/octoprint_enclosure/templates/enclosure_settings.jinja2 b/octoprint_enclosure/templates/enclosure_settings.jinja2 index 6810866..38c0ee4 100644 --- a/octoprint_enclosure/templates/enclosure_settings.jinja2 +++ b/octoprint_enclosure/templates/enclosure_settings.jinja2 @@ -53,6 +53,12 @@ +
+ +
@@ -70,6 +76,15 @@ Id used for API control
+ +
+ +
+ + Shell script to be executed +
+
+
@@ -88,45 +103,45 @@
- -
-
- - Link PWM ouput to temperature. PWM output will be interpolated between the point from duty A, temperature A -> duty B, temperature B. - This output will automatomatically start when a print starts and will default to the default duty cycle when print is complete. - -
+
+
+ + Link PWM ouput to temperature. PWM output will be interpolated between the point from duty A, temperature A -> duty + B, temperature B. This output will automatomatically start when a print starts and will default to the default + duty cycle when print is complete. +
- +
+ -
- -
- -
+
+ +
+
-
- -
- -
+
+
+ +
+
-
- -
- -
+
+
+ +
+
-
- -
- -
+
+
+ +
+
+
@@ -221,15 +236,16 @@ Time delay in secconds to turn on GPIO when print starts OR exact time that the event should happen, note that events will only be scheduled after a print starts, time should be formated as HH:MM on a 24 hours format, don't forget to check timezone of your raspberry pi. - Attention - Hour schedule does not work with Link PWM to Temperature option + Attention + Hour schedule does not work with + Link PWM to Temperature option
- +
- +
+ +

+ + Shell Script +

+
+ +
+ +

@@ -292,4 +302,4 @@

- + \ No newline at end of file