From 5208fc38edc7e62958bfc51de7918351b888b408 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 11 Mar 2018 00:22:44 -0500 Subject: [PATCH] verify_heater: Add initial support for verifying heaters and sensors Add runtime checks to heaters and temperature sensors to check for possible hardware faults. Signed-off-by: Kevin O'Connor --- config/example-extras.cfg | 22 +++++++++++ docs/Todo.md | 4 -- klippy/extras/verify_heater.py | 67 ++++++++++++++++++++++++++++++++++ klippy/heater.py | 2 + 4 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 klippy/extras/verify_heater.py diff --git a/config/example-extras.cfg b/config/example-extras.cfg index 33b67178..fa48e780 100644 --- a/config/example-extras.cfg +++ b/config/example-extras.cfg @@ -122,6 +122,28 @@ # See the example.cfg for the definition of the above parameters. +# Heater and temperature sensor verification. Heater verification is +# automatically enabled for each heater that is configured on the +# printer. Use verify_heater sections to change the default settings. +#[verify_heater heater_config_name] +#heating_gain: 2 +# The minimum temperature (in Celsius) that the heater must increase +# by when approaching a new target temperature. The default is 2. +#check_gain_time: +# The amount of time (in seconds) that the heating_gain must be met +# in before an error is raised. The default is 20 seconds for +# extruders and 60 seconds for heater_bed. +#hysteresis: 4 +# The difference between the target temperature and the current +# temperature for the heater to be considered within range of the +# target temperature. The default is 4. +#check_time: 10 +# The amount of time (in seconds) a heater that has reached the +# target temperature (as defined by the hysteresis field) may fall +# outside the target temperature range before an error is +# raised. The default is 10. + + # Multi-stepper axes. On a cartesian style printer, the stepper # controlling a given axis may have additional config blocks defining # steppers that should be stepped in concert with the primary diff --git a/docs/Todo.md b/docs/Todo.md index adf4c8c1..2c9be3c5 100644 --- a/docs/Todo.md +++ b/docs/Todo.md @@ -44,10 +44,6 @@ Safety features endstop detection is a good idea because of spurious signals caused by electrical noise.) -* Support validating that heaters are heating at expected rates. This - can be useful to detect a sensor failure (eg, thermistor short) that - could otherwise cause the PID to command excessive heating. - Testing features ================ diff --git a/klippy/extras/verify_heater.py b/klippy/extras/verify_heater.py new file mode 100644 index 00000000..f7b750eb --- /dev/null +++ b/klippy/extras/verify_heater.py @@ -0,0 +1,67 @@ +# Heater/sensor verification code +# +# Copyright (C) 2018 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import logging +import extruder + +class HeaterCheck: + def __init__(self, config): + self.printer = config.get_printer() + self.heater_name = config.get_name().split()[1] + self.heater = None + self.hysteresis = config.getfloat('hysteresis', 4., above=0.) + self.check_time = config.getfloat('check_time', 10., minval=1.) + self.heating_gain = config.getfloat('heating_gain', 2., above=0.) + default_gain_time = 20. + if self.heater_name == 'heater_bed': + default_gain_time = 60. + self.check_gain_time = config.getfloat( + 'check_gain_time', default_gain_time, minval=1.) + self.met_target = False + self.last_target = self.goal_temp = 0. + self.fault_systime = self.printer.get_reactor().NEVER + def printer_state(self, state): + if state == 'connect': + self.heater = extruder.get_printer_heater( + self.printer, self.heater_name) + logging.info("Starting heater checks for %s", self.heater_name) + reactor = self.printer.get_reactor() + reactor.register_timer(self.check_event, reactor.NOW) + def check_event(self, eventtime): + temp, target = self.heater.get_temp(eventtime) + if temp >= target - self.hysteresis: + # Temperature near target - reset checks + if not self.met_target: + logging.info("Heater %s within range of %.3f", + self.heater_name, target) + self.met_target = True + self.fault_systime = eventtime + self.check_time + elif self.met_target: + if target != self.last_target: + # Target changed - reset checks + logging.info("Heater %s approaching new target of %.3f", + self.heater_name, target) + self.met_target = False + self.goal_temp = temp + self.heating_gain + self.fault_systime = eventtime + self.check_gain_time + elif eventtime >= self.fault_systime: + # Failure due to inability to maintain target temperature + return self.heater_fault() + elif temp >= self.goal_temp: + # Temperature approaching target - reset checks + self.goal_temp = temp + self.heating_gain + self.fault_systime = eventtime + self.check_gain_time + elif eventtime >= self.fault_systime: + # Failure due to inability to approach target temperature + return self.heater_fault() + self.last_target = target + return eventtime + 1. + def heater_fault(self): + logging.error("Heater %s not heating at expected rate", self.heater_name) + self.printer.invoke_shutdown("Heater %s failsafe" % (self.heater_name,)) + return self.printer.get_reactor().NEVER + +def load_config_prefix(config): + return HeaterCheck(config) diff --git a/klippy/heater.py b/klippy/heater.py index d3315a52..0e854db7 100644 --- a/klippy/heater.py +++ b/klippy/heater.py @@ -141,6 +141,8 @@ class PrinterHeater: # pwm caching self.next_pwm_time = 0. self.last_pwm_value = 0. + # Load verify_heater module + printer.try_load_module(config, "verify_heater %s" % (self.name,)) def set_pwm(self, read_time, value): if self.target_temp <= 0.: value = 0.