#!/usr/bin/env python """ An alarm can be executed when an error condition occurs Author: Elias Bakken Redeem is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Redeem is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Redeem. If not, see <http://www.gnu.org/licenses/>. """ import logging import time from multiprocessing import JoinableQueue from six import PY2, iteritems from threading import Thread if PY2: import Queue as queue else: import queue class Alarm: THERMISTOR_ERROR = 0 # Thermistor error. HEATER_TOO_COLD = 1 # Temperature has fallen below the limit HEATER_TOO_HOT = 2 # Temperature has gone too high HEATER_RISING_FAST = 3 # Temperture is rising too rapidly HEATER_FALLING_FAST = 4 # Temperature is falling too rapidly FILAMENT_JAM = 5 # If filament sensor is installed WATCHDOG_ERROR = 6 # Can this be detected? PHYSICAL_ENDSTOP_HIT = 7 # During print. SOFT_ENDSTOP_HIT = 8 IMPOSSIBLE_MOVE_ATTEMPTED = 9 STEPPER_FAULT = 10 # Error on a stepper ALARM_TEST = 11 # Testsignal, used during start-up printer = None executor = None def __init__(self, alarm_type, message, short_message=None): self.type = alarm_type self.message = message if not short_message: self.short_message = message else: self.short_message = short_message if Alarm.executor: Alarm.executor.queue.put(self) else: logging.error("Enable to enqueue alarm!") def execute(self): """ Execute the alarm """ if self.type == Alarm.THERMISTOR_ERROR: self.stop_print() self.inform_listeners() Alarm.action_command("pause") Alarm.action_command("alarm_thermistor_error", self.message) elif self.type == Alarm.HEATER_TOO_COLD: self.stop_print() self.inform_listeners() Alarm.action_command("pause") Alarm.action_command("alarm_heater_too_cold", self.message) elif self.type == Alarm.HEATER_TOO_HOT: self.stop_print() self.inform_listeners() Alarm.action_command("pause") Alarm.action_command("alarm_heater_too_hot", self.message) elif self.type == Alarm.HEATER_RISING_FAST: self.stop_print() self.inform_listeners() Alarm.action_command("pause") Alarm.action_command("alarm_heater_rising_fast", self.message) elif self.type == Alarm.HEATER_FALLING_FAST: self.disable_heaters() self.inform_listeners() Alarm.action_command("pause") Alarm.action_command("alarm_heater_falling_fast", self.message) elif self.type == Alarm.STEPPER_FAULT: self.inform_listeners() Alarm.action_command("pause") Alarm.action_command("alarm_stepper_fault", self.message) elif self.type == Alarm.FILAMENT_JAM: Alarm.action_command("pause") Alarm.action_command("alarm_filament_jam", self.message) elif self.type == Alarm.PHYSICAL_ENDSTOP_HIT: self.stop_print() Alarm.action_command("pause") self.inform_listeners() Alarm.action_command("alarm_endstop_hit", self.message) elif self.type == Alarm.SOFT_ENDSTOP_HIT: self.stop_print() Alarm.action_command("pause") self.inform_listeners() Alarm.action_command("alarm_endstop_hit", self.message) elif self.type == Alarm.IMPOSSIBLE_MOVE_ATTEMPTED: self.stop_print() Alarm.action_command("pause") self.inform_listeners() Alarm.action_command("alarm_endstop_hit", self.message) elif self.type == Alarm.ALARM_TEST: logging.info("Alarm: Operational") Alarm.action_command("alarm_operational", self.message) else: logging.warning("An Alarm of unknown type was sounded!") # These are the different actions that can be # done once an alarm is sounded. def stop_print(self): """ Stop the print """ logging.warning("Stopping print") self.printer.path_planner.emergency_interrupt() self.disable_heaters() def disable_heaters(self): logging.warning("Disabling heaters") for _, heater in iteritems(self.printer.heaters): heater.extruder_error = True def inform_listeners(self): """ Inform all listeners (comm channels) of the occured error """ logging.error("Alarm: " + self.message) if Alarm.printer and hasattr(Alarm.printer, "comms"): for name, comm in iteritems(Alarm.printer.comms): if name == "toggle": comm.send_message(self.short_message) else: comm.send_message("Alarm: " + self.message) @staticmethod def action_command(command, message=""): if Alarm.printer and hasattr(Alarm.printer, "comms"): if "octoprint" in Alarm.printer.comms: comm = Alarm.printer.comms["octoprint"] # Send action command to listeners if message: comm.send_message("// action:{}@{}".format(command, message)) else: comm.send_message("// action:{}".format(command)) def make_sound(self): """ If a speaker is connected, sound it """ pass def send_email(self): """ Send an e-mail to a predefined address """ pass def send_sms(self): pass def record_position(self): """ Save last completed segment to file """ pass class AlarmExecutor: def __init__(self): self.queue = JoinableQueue(10) self.running = False self.t = Thread(target=self._run, name="AlarmExecutor") def _run(self): while self.running: try: alarm = self.queue.get(block=True, timeout=1) alarm.execute() logging.debug("Alarm executed") self.queue.task_done() except queue.Empty: continue def start(self): logging.debug("Starting alarm executor") self.running = True self.t.start() def stop(self): if self.running: logging.debug("Stopping alarm executor") self.running = False self.t.join() else: msg = "Attempted to stop alarm executor when it is not running" logging.debug(msg) if __name__ == '__main__': logformat = '%(asctime)s %(name)-12s %(levelname)-8s %(message)s' logging.basicConfig(level=logging.DEBUG, format=logformat, datefmt='%m-%d %H:%M') class FooPrinter: pass p = FooPrinter() alarm_executor = AlarmExecutor() Alarm.printer = p Alarm.executor = alarm_executor alarm = Alarm(Alarm.ALARM_TEST, {}, "Test") time.sleep(1) alarm_executor.stop()