From 109c0504da7e1205fd41b76006eeb3d875a5d50b Mon Sep 17 00:00:00 2001 From: Zeanon Date: Tue, 5 Nov 2024 00:34:59 +0100 Subject: [PATCH 1/9] Implemented disable heaters for adxl --- README.md | 2 ++ docs/Config_Reference.md | 4 ++++ klippy/extras/adxl345.py | 38 ++++++++++++++++++++++++++++++++++++++ klippy/extras/heaters.py | 13 +++++++++++++ 4 files changed, 57 insertions(+) diff --git a/README.md b/README.md index 4da15d639..3a50737c3 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,8 @@ See the [Danger Features document](https://dangerklipper.io/Danger_Features.html - [danger_options: configurable homing constants](https://github.com/DangerKlippers/danger-klipper/pull/378) +- [adxl: disable heaters when connected](https://github.com/DangerKlippers/danger-klipper/pull/421) + If you're feeling adventurous, take a peek at the extra features in the bleeding-edge-v2 branch [feature documentation](docs/Bleeding_Edge.md) and [feature configuration reference](docs/Config_Reference_Bleeding_Edge.md): diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index ba5c06a42..343a64f6c 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -2018,6 +2018,10 @@ cs_pin: # not recommended to change this rate from the default 3200, and # rates below 800 will considerably affect the quality of resonance # measurements. +#disable_heaters: +# A list of heaters to disable when this adxl is connected. +# Useful for Nozzle-ADXL to automatically disable the hotend so you don't +# fry them. ``` ### [lis2dw] diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py index f88c8345b..e7e62ddb9 100644 --- a/klippy/extras/adxl345.py +++ b/klippy/extras/adxl345.py @@ -125,15 +125,53 @@ def write_impl(): class AccelCommandHelper: def __init__(self, config, chip): self.printer = config.get_printer() + self.reactor = self.printer.get_reactor() self.chip = chip self.bg_client = None name_parts = config.get_name().split() self.base_name = name_parts[0] self.name = name_parts[-1] self.register_commands(self.name) + self.disabled_heaters = [] + if hasattr(config, "getlist"): + self.disabled_heaters = config.getlist( + "disable_heaters", self.disabled_heaters + ) if len(name_parts) == 1: if self.name == "adxl345" or not config.has_section("adxl345"): self.register_commands(None) + self.printer.register_event_handler("klippy:ready", self._handle_ready) + + def read_accelerometer(self): + toolhead = self.printer.lookup_object("toolhead") + aclient = self.chip.start_internal_client() + toolhead.dwell(1.0) + aclient.finish_measurements() + values = aclient.get_samples() + if not values: + raise Exception("No accelerometer measurements found") + + def _handle_ready(self): + self.reactor.register_callback(self._init_accel, self.reactor.NOW) + + def _init_accel(self, eventtime=None): + try: + self.read_accelerometer() + connected = True + except Exception as e: + connected = False + for heater_name in self.disabled_heaters: + heater_object = self.printer.lookup_object(heater_name) + if not hasattr(heater_object, "heater"): + raise self.printer.config_error( + "'%s' is not a valid heater." % (heater_name,) + ) + heater = heater_object.heater + if not hasattr(heater, "set_enabled"): + raise self.printer.config_error( + "'%s' is not a valid heater." % (heater_name,) + ) + heater.set_enabled(not connected) def register_commands(self, name): # Register commands diff --git a/klippy/extras/heaters.py b/klippy/extras/heaters.py index b5e76efa0..b0bab25d0 100644 --- a/klippy/extras/heaters.py +++ b/klippy/extras/heaters.py @@ -61,6 +61,7 @@ def __init__(self, config, sensor): self.printer.get_start_args().get("debugoutput") is not None ) self.can_extrude = self.min_extrude_temp <= 0.0 or is_fileoutput + self.enabled = True self.max_power = config.getfloat( "max_power", 1.0, above=0.0, maxval=1.0 ) @@ -130,6 +131,12 @@ def __init__(self, config, sensor): "klippy:shutdown", self._handle_shutdown ) + def notify_disabled(self): + raise self.printer.command_error( + "Heater [%s] is disabled due to an " + "accelerometer being connected." % self.short_name + ) + def lookup_control(self, profile, load_clean=False): algos = collections.OrderedDict( { @@ -261,6 +268,9 @@ def get_status(self, eventtime): ret["control_stats"] = control_stats return ret + def set_enabled(self, enabled): + self.enabled = enabled + def is_adc_faulty(self): if self.last_temp > self.max_temp or self.last_temp < self.min_temp: return True @@ -1102,6 +1112,9 @@ def check(eventtime): self.printer.wait_while(check) def set_temperature(self, heater, temp, wait=False): + if not heater.enabled: + heater.notify_disabled() + return toolhead = self.printer.lookup_object("toolhead") toolhead.register_lookahead_callback((lambda pt: None)) heater.set_temp(temp) From 06b09e0e60860ecf5d51e837613e90b8415dcc19 Mon Sep 17 00:00:00 2001 From: Zeanon Date: Tue, 5 Nov 2024 00:35:43 +0100 Subject: [PATCH 2/9] Fixed doc --- docs/Config_Reference.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 343a64f6c..db5d2adcd 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -2018,7 +2018,7 @@ cs_pin: # not recommended to change this rate from the default 3200, and # rates below 800 will considerably affect the quality of resonance # measurements. -#disable_heaters: +#disabled_heaters: # A list of heaters to disable when this adxl is connected. # Useful for Nozzle-ADXL to automatically disable the hotend so you don't # fry them. @@ -2043,6 +2043,8 @@ cs_pin: # above parameters. #axes_map: x, y, z # See the "adxl345" section for information on this parameter. +#disabled_heaters: +# See the "adxl345" section for information on this parameter. ``` ### [mpu9250] @@ -2064,6 +2066,8 @@ accelerometers (one may define any number of sections with an # above parameters. The default "i2c_speed" is 400000. #axes_map: x, y, z # See the "adxl345" section for information on this parameter. +#disabled_heaters: +# See the "adxl345" section for information on this parameter. ``` ### [resonance_tester] From 5b1a9955d75a0ddf65a2f7f53e76277bd404086f Mon Sep 17 00:00:00 2001 From: Zeanon Date: Tue, 5 Nov 2024 00:37:45 +0100 Subject: [PATCH 3/9] formatting --- klippy/extras/heaters.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/klippy/extras/heaters.py b/klippy/extras/heaters.py index b0bab25d0..9cc2f9218 100644 --- a/klippy/extras/heaters.py +++ b/klippy/extras/heaters.py @@ -133,9 +133,9 @@ def __init__(self, config, sensor): def notify_disabled(self): raise self.printer.command_error( - "Heater [%s] is disabled due to an " - "accelerometer being connected." % self.short_name - ) + "Heater [%s] is disabled due to an " + "accelerometer being connected." % self.short_name + ) def lookup_control(self, profile, load_clean=False): algos = collections.OrderedDict( From 22b2e0b151f5cd8bcfdace6ad86c5bd97e71d0be Mon Sep 17 00:00:00 2001 From: Zeanon Date: Tue, 5 Nov 2024 00:39:18 +0100 Subject: [PATCH 4/9] added test case --- docs/Config_Reference.md | 6 +++--- test/klippy/input_shaper.cfg | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index db5d2adcd..b6d84e81c 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -2018,7 +2018,7 @@ cs_pin: # not recommended to change this rate from the default 3200, and # rates below 800 will considerably affect the quality of resonance # measurements. -#disabled_heaters: +#disable_heaters: # A list of heaters to disable when this adxl is connected. # Useful for Nozzle-ADXL to automatically disable the hotend so you don't # fry them. @@ -2043,7 +2043,7 @@ cs_pin: # above parameters. #axes_map: x, y, z # See the "adxl345" section for information on this parameter. -#disabled_heaters: +#disable_heaters: # See the "adxl345" section for information on this parameter. ``` @@ -2066,7 +2066,7 @@ accelerometers (one may define any number of sections with an # above parameters. The default "i2c_speed" is 400000. #axes_map: x, y, z # See the "adxl345" section for information on this parameter. -#disabled_heaters: +#disable_heaters: # See the "adxl345" section for information on this parameter. ``` diff --git a/test/klippy/input_shaper.cfg b/test/klippy/input_shaper.cfg index bca94eb77..3f597fd68 100644 --- a/test/klippy/input_shaper.cfg +++ b/test/klippy/input_shaper.cfg @@ -76,6 +76,7 @@ shaper_freq_x: 39.3 [adxl345] cs_pin: PK7 axes_map: -x,-y,z +disable_heaters: extruder [mpu9250 my_mpu] From 34d6fd37a7777fdcd60fade29133abfbcac9b843 Mon Sep 17 00:00:00 2001 From: Zeanon Date: Tue, 5 Nov 2024 00:40:02 +0100 Subject: [PATCH 5/9] fixed readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a50737c3..839047880 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ See the [Danger Features document](https://dangerklipper.io/Danger_Features.html - [danger_options: configurable homing constants](https://github.com/DangerKlippers/danger-klipper/pull/378) -- [adxl: disable heaters when connected](https://github.com/DangerKlippers/danger-klipper/pull/421) +- [adxl: disable heaters when connected](https://github.com/DangerKlippers/danger-klipper/pull/425) If you're feeling adventurous, take a peek at the extra features in the bleeding-edge-v2 branch [feature documentation](docs/Bleeding_Edge.md) and [feature configuration reference](docs/Config_Reference_Bleeding_Edge.md): From 1a184668a5fe9ebc576183c6c7cde6ee9e5aa49a Mon Sep 17 00:00:00 2001 From: Zeanon Date: Mon, 25 Nov 2024 22:20:44 +0100 Subject: [PATCH 6/9] modified to now be a heater feature --- docs/Config_Reference.md | 14 ++-- klippy/extras/adxl345.py | 38 --------- klippy/extras/heaters.py | 147 +++++++++++++---------------------- test/klippy/input_shaper.cfg | 2 +- 4 files changed, 61 insertions(+), 140 deletions(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index b6d84e81c..9d08960b9 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -1091,7 +1091,9 @@ per_move_pressure_advance: False # If true, uses pressure advance constant from trapq when processing moves # This causes changes to pressure advance be taken into account immediately, # for all moves in the current queue, rather than ~250ms later once the queue gets flushed - +#disable_if_connected: +# List of mcus that should disable the heater if connected, usefull for nozzle adxls +# as to not accidentally fry them due to heating the hotend. ``` ### [heater_bed] @@ -1107,6 +1109,7 @@ sensor_pin: control: min_temp: max_temp: +disable_if_connected: # See the "extruder" section for a description of the above parameters. ``` @@ -2018,10 +2021,6 @@ cs_pin: # not recommended to change this rate from the default 3200, and # rates below 800 will considerably affect the quality of resonance # measurements. -#disable_heaters: -# A list of heaters to disable when this adxl is connected. -# Useful for Nozzle-ADXL to automatically disable the hotend so you don't -# fry them. ``` ### [lis2dw] @@ -2043,8 +2042,6 @@ cs_pin: # above parameters. #axes_map: x, y, z # See the "adxl345" section for information on this parameter. -#disable_heaters: -# See the "adxl345" section for information on this parameter. ``` ### [mpu9250] @@ -2066,8 +2063,6 @@ accelerometers (one may define any number of sections with an # above parameters. The default "i2c_speed" is 400000. #axes_map: x, y, z # See the "adxl345" section for information on this parameter. -#disable_heaters: -# See the "adxl345" section for information on this parameter. ``` ### [resonance_tester] @@ -2928,6 +2923,7 @@ target temperature. #pwm_cycle_time: #min_temp: #max_temp: +#disable_if_connected: # See the "extruder" section for the definition of the above # parameters. ``` diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py index e7e62ddb9..f88c8345b 100644 --- a/klippy/extras/adxl345.py +++ b/klippy/extras/adxl345.py @@ -125,53 +125,15 @@ def write_impl(): class AccelCommandHelper: def __init__(self, config, chip): self.printer = config.get_printer() - self.reactor = self.printer.get_reactor() self.chip = chip self.bg_client = None name_parts = config.get_name().split() self.base_name = name_parts[0] self.name = name_parts[-1] self.register_commands(self.name) - self.disabled_heaters = [] - if hasattr(config, "getlist"): - self.disabled_heaters = config.getlist( - "disable_heaters", self.disabled_heaters - ) if len(name_parts) == 1: if self.name == "adxl345" or not config.has_section("adxl345"): self.register_commands(None) - self.printer.register_event_handler("klippy:ready", self._handle_ready) - - def read_accelerometer(self): - toolhead = self.printer.lookup_object("toolhead") - aclient = self.chip.start_internal_client() - toolhead.dwell(1.0) - aclient.finish_measurements() - values = aclient.get_samples() - if not values: - raise Exception("No accelerometer measurements found") - - def _handle_ready(self): - self.reactor.register_callback(self._init_accel, self.reactor.NOW) - - def _init_accel(self, eventtime=None): - try: - self.read_accelerometer() - connected = True - except Exception as e: - connected = False - for heater_name in self.disabled_heaters: - heater_object = self.printer.lookup_object(heater_name) - if not hasattr(heater_object, "heater"): - raise self.printer.config_error( - "'%s' is not a valid heater." % (heater_name,) - ) - heater = heater_object.heater - if not hasattr(heater, "set_enabled"): - raise self.printer.config_error( - "'%s' is not a valid heater." % (heater_name,) - ) - heater.set_enabled(not connected) def register_commands(self, name): # Register commands diff --git a/klippy/extras/heaters.py b/klippy/extras/heaters.py index 9cc2f9218..c20a2e29b 100644 --- a/klippy/extras/heaters.py +++ b/klippy/extras/heaters.py @@ -57,14 +57,14 @@ def __init__(self, config, sensor): minval=self.min_temp, maxval=self.max_temp, ) - is_fileoutput = ( - self.printer.get_start_args().get("debugoutput") is not None - ) + is_fileoutput = self.printer.get_start_args().get("debugoutput") is not None self.can_extrude = self.min_extrude_temp <= 0.0 or is_fileoutput - self.enabled = True - self.max_power = config.getfloat( - "max_power", 1.0, above=0.0, maxval=1.0 - ) + self.disable_if_connected = [] + if hasattr(config, "getlist"): + self.disable_if_connected = config.getlist( + "disable_if_connected", self.disable_if_connected + ) + self.max_power = config.getfloat("max_power", 1.0, above=0.0, maxval=1.0) self.config_smooth_time = config.getfloat("smooth_time", 1.0, above=0.0) self.smooth_time = self.config_smooth_time self.inv_smooth_time = 1.0 / self.smooth_time @@ -95,9 +95,7 @@ def __init__(self, config, sensor): self.printer.load_object(config, "pid_calibrate") self.gcode = self.printer.lookup_object("gcode") self.pmgr = self.ProfileManager(self) - self.control = self.lookup_control( - self.pmgr.init_default_profile(), True - ) + self.control = self.lookup_control(self.pmgr.init_default_profile(), True) self.gcode.register_mux_command( "SET_HEATER_TEMPERATURE", "HEATER", @@ -127,14 +125,12 @@ def __init__(self, config, sensor): desc=self.cmd_SET_HEATER_PID_help, ) - self.printer.register_event_handler( - "klippy:shutdown", self._handle_shutdown - ) + self.printer.register_event_handler("klippy:shutdown", self._handle_shutdown) - def notify_disabled(self): + def notify_disabled(self, mcu_name): raise self.printer.command_error( - "Heater [%s] is disabled due to an " - "accelerometer being connected." % self.short_name + "Heater [%s] is disabled due to [%s] being connected." + % (self.short_name, mcu_name) ) def lookup_control(self, profile, load_clean=False): @@ -207,9 +203,7 @@ def set_temp(self, degrees): self.target_temp = degrees def get_temp(self, eventtime): - print_time = ( - self.mcu_pwm.get_mcu().estimated_print_time(eventtime) - 5.0 - ) + print_time = self.mcu_pwm.get_mcu().estimated_print_time(eventtime) - 5.0 with self.lock: if self.last_temp_time < print_time: return 0.0, self.target_temp @@ -268,9 +262,6 @@ def get_status(self, eventtime): ret["control_stats"] = control_stats return ret - def set_enabled(self, enabled): - self.enabled = enabled - def is_adc_faulty(self): if self.last_temp > self.max_temp or self.last_temp < self.min_temp: return True @@ -321,9 +312,7 @@ def __init__(self, outer_instance): "pid_profile %s" % self.outer_instance.short_name ) for profile in stored_profs: - self._init_profile( - profile, profile.get_name().split(" ", 2)[-1] - ) + self._init_profile(profile, profile.get_name().split(" ", 2)[-1]) def _init_profile(self, config_section, name): version = config_section.getint("pid_version", 1) @@ -337,9 +326,7 @@ def _init_profile(self, config_section, name): self.incompatible_profiles.append(name) return None temp_profile = {} - control = self._check_value_config( - "control", config_section, str, False - ) + control = self._check_value_config("control", config_section, str, False) if control == "watermark": temp_profile["max_delta"] = config_section.getfloat( "max_delta", 2.0, above=0.0 @@ -375,10 +362,8 @@ def _init_profile(self, config_section, name): temp_profile["filament_density"] = config_section.getfloat( "filament_density", above=0.0, default=1.2 ) - temp_profile["filament_heat_capacity"] = ( - config_section.getfloat( - "filament_heat_capacity", above=0.0, default=1.8 - ) + temp_profile["filament_heat_capacity"] = config_section.getfloat( + "filament_heat_capacity", above=0.0, default=1.8 ) temp_profile["maximum_retract"] = config_section.getfloat( "maximum_retract", above=0.0, default=2.0 @@ -402,9 +387,7 @@ def _init_profile(self, config_section, name): filament_temp_src = (FILAMENT_TEMP_SRC_FIXED, value) temp_profile["filament_temp_src"] = filament_temp_src - ambient_sensor_name = config_section.get( - "ambient_temp_sensor", None - ) + ambient_sensor_name = config_section.get("ambient_temp_sensor", None) ambient_sensor = None if ambient_sensor_name is not None: ambient_sensor = config_section.get_printer().load_object( @@ -413,10 +396,8 @@ def _init_profile(self, config_section, name): None, ) if ambient_sensor is None: - ambient_sensor = ( - config_section.get_printer().lookup_object( - ambient_sensor_name, None - ) + ambient_sensor = config_section.get_printer().lookup_object( + ambient_sensor_name, None ) if ambient_sensor is None: raise config_section.error( @@ -449,8 +430,8 @@ def _init_profile(self, config_section, name): fan = fan_obj.fan temp_profile["cooling_fan"] = fan - temp_profile["fan_ambient_transfer"] = ( - config_section.getfloatlist("fan_ambient_transfer", []) + temp_profile["fan_ambient_transfer"] = config_section.getfloatlist( + "fan_ambient_transfer", [] ) elif control == "pid" or control == "pid_v": for key, (type, placeholder) in PID_PROFILE_OPTIONS.items(): @@ -497,10 +478,7 @@ def _compute_section_name(self, profile_name): self.outer_instance.short_name if profile_name == "default" else ( - "pid_profile " - + self.outer_instance.short_name - + " " - + profile_name + "pid_profile " + self.outer_instance.short_name + " " + profile_name ) ) @@ -515,13 +493,9 @@ def _check_value_gcmd( maxval=None, ): if type is int: - value = gcmd.get_int( - name, default, minval=minval, maxval=maxval - ) + value = gcmd.get_int(name, default, minval=minval, maxval=maxval) elif type is float: - value = gcmd.get_float( - name, default, minval=minval, maxval=maxval - ) + value = gcmd.get_float(name, default, minval=minval, maxval=maxval) else: value = gcmd.get(name, default) if not can_be_none and value is None: @@ -549,9 +523,7 @@ def set_values(self, profile_name, gcmd, verbose): kp = self._check_value_gcmd("KP", None, gcmd, float, False) ki = self._check_value_gcmd("KI", None, gcmd, float, False) kd = self._check_value_gcmd("KD", None, gcmd, float, False) - smooth_time = self._check_value_gcmd( - "SMOOTH_TIME", None, gcmd, float, True - ) + smooth_time = self._check_value_gcmd("SMOOTH_TIME", None, gcmd, float, True) keep_target = self._check_value_gcmd( "KEEP_TARGET", 0, gcmd, int, True, minval=0, maxval=1 ) @@ -567,9 +539,7 @@ def set_values(self, profile_name, gcmd, verbose): "pid_ki": ki, "pid_kd": kd, } - temp_control = self.outer_instance.lookup_control( - temp_profile, load_clean - ) + temp_control = self.outer_instance.lookup_control(temp_profile, load_clean) self.outer_instance.set_control(temp_control, keep_target) msg = ( "PID Parameters:\n" @@ -607,8 +577,7 @@ def get_values(self, profile_name, gcmd, verbose): "Control: %s\n" "Smooth Time: %.3f\n" "pid_Kp=%.3f pid_Ki=%.3f pid_Kd=%.3f\n" - "name: %s" - % (target, tolerance, control, smooth_time, kp, ki, kd, name) + "name: %s" % (target, tolerance, control, smooth_time, kp, ki, kd, name) ) def save_profile(self, profile_name=None, gcmd=None, verbose=True): @@ -637,15 +606,12 @@ def save_profile(self, profile_name=None, gcmd=None, verbose=True): ) def load_profile(self, profile_name, gcmd, verbose): - verbose = self._check_value_gcmd( - "VERBOSE", "low", gcmd, "lower", True - ) + verbose = self._check_value_gcmd("VERBOSE", "low", gcmd, "lower", True) load_clean = self._check_value_gcmd( "LOAD_CLEAN", 0, gcmd, int, True, minval=0, maxval=1 ) if ( - profile_name - == self.outer_instance.get_control().get_profile()["name"] + profile_name == self.outer_instance.get_control().get_profile()["name"] and not load_clean ): if verbose == "high" or verbose == "low": @@ -696,20 +662,22 @@ def load_profile(self, profile_name, gcmd, verbose): if profile["smooth_time"] is None else profile["smooth_time"] ) - msg = "Target: %.2f\n" "Tolerance: %.4f\n" "Control: %s\n" % ( - profile["pid_target"], - profile["pid_tolerance"], - profile["control"], + msg = ( + "Target: %.2f\n" + "Tolerance: %.4f\n" + "Control: %s\n" + % ( + profile["pid_target"], + profile["pid_tolerance"], + profile["control"], + ) ) if smooth_time is not None: msg += "Smooth Time: %.3f\n" % smooth_time - msg += ( - "PID Parameters: pid_Kp=%.3f pid_Ki=%.3f pid_Kd=%.3f\n" - % ( - profile["pid_kp"], - profile["pid_ki"], - profile["pid_kd"], - ) + msg += "PID Parameters: pid_Kp=%.3f pid_Ki=%.3f pid_Kd=%.3f\n" % ( + profile["pid_kp"], + profile["pid_ki"], + profile["pid_kd"], ) self.outer_instance.gcode.respond_info(msg) @@ -836,8 +804,7 @@ def temperature_update(self, read_time, temp, target_temp): temp_deriv = temp_diff / time_diff else: temp_deriv = ( - self.prev_temp_deriv * (self.min_deriv_time - time_diff) - + temp_diff + self.prev_temp_deriv * (self.min_deriv_time - time_diff) + temp_diff ) / self.min_deriv_time # Calculate accumulated temperature "error" temp_err = target_temp - temp @@ -896,9 +863,7 @@ def __init__(self, profile, heater, load_clean=False): self.temps = ( ([AMBIENT_TEMP] * 3) if load_clean - else ( - [self.heater.get_temp(self.heater.reactor.monotonic())[0]] * 3 - ) + else ([self.heater.get_temp(self.heater.reactor.monotonic())[0]] * 3) ) self.times = [0.0] * 3 # temperature reading times self.d1 = 0.0 # previous smoothed 1st derivative @@ -947,9 +912,7 @@ def temperature_update(self, read_time, temp, target_temp): def check_busy(self, eventtime, smoothed_temp, target_temp): temp_diff = target_temp - smoothed_temp - return ( - abs(temp_diff) > PID_SETTLE_DELTA or abs(self.d1) > PID_SETTLE_SLOPE - ) + return abs(temp_diff) > PID_SETTLE_DELTA or abs(self.d1) > PID_SETTLE_SLOPE def update_smooth_time(self): self.smooth_time = self.heater.get_smooth_time() # smoothing window @@ -1030,9 +993,7 @@ def lookup_heater(self, heater_name): if " " in heater_name: heater_name = heater_name.split(" ", 1)[1] if heater_name not in self.heaters: - raise self.printer.config_error( - "Unknown heater '%s'" % (heater_name,) - ) + raise self.printer.config_error("Unknown heater '%s'" % (heater_name,)) return self.heaters[heater_name] def setup_sensor(self, config): @@ -1112,9 +1073,13 @@ def check(eventtime): self.printer.wait_while(check) def set_temperature(self, heater, temp, wait=False): - if not heater.enabled: - heater.notify_disabled() - return + for mcu_name in heater.disable_if_connected: + if not mcu_name.startswith("mcu"): + mcu_name = "mcu " + mcu_name + mcu_object = self.printer.lookup_object(mcu_name, None) + if not mcu_object.non_critical_disconnected: + heater.notify_disabled(mcu_name) + return toolhead = self.printer.lookup_object("toolhead") toolhead.register_lookahead_callback((lambda pt: None)) heater.set_temp(temp) @@ -1131,9 +1096,7 @@ def cmd_TEMPERATURE_WAIT(self, gcmd): max_temp = gcmd.get_float("MAXIMUM", float("inf"), above=min_temp) error_on_cancel = gcmd.get("ALLOW_CANCEL", None) is None if min_temp == float("-inf") and max_temp == float("inf"): - raise gcmd.error( - "Error on 'TEMPERATURE_WAIT': missing MINIMUM or MAXIMUM." - ) + raise gcmd.error("Error on 'TEMPERATURE_WAIT': missing MINIMUM or MAXIMUM.") if self.printer.get_start_args().get("debugoutput") is not None: return if sensor_name in self.heaters: diff --git a/test/klippy/input_shaper.cfg b/test/klippy/input_shaper.cfg index 3f597fd68..ba407d10a 100644 --- a/test/klippy/input_shaper.cfg +++ b/test/klippy/input_shaper.cfg @@ -48,6 +48,7 @@ pid_Ki: 1.08 pid_Kd: 114 min_temp: 0 max_temp: 210 +disable_if_connected: mcu [heater_bed] heater_pin: PH5 @@ -76,7 +77,6 @@ shaper_freq_x: 39.3 [adxl345] cs_pin: PK7 axes_map: -x,-y,z -disable_heaters: extruder [mpu9250 my_mpu] From 3c4d97c7c8ee919184caca170cedb0e41360bb48 Mon Sep 17 00:00:00 2001 From: Zeanon Date: Mon, 25 Nov 2024 22:39:37 +0100 Subject: [PATCH 7/9] added none check --- klippy/extras/heaters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/klippy/extras/heaters.py b/klippy/extras/heaters.py index c20a2e29b..079a2dc6e 100644 --- a/klippy/extras/heaters.py +++ b/klippy/extras/heaters.py @@ -1077,7 +1077,7 @@ def set_temperature(self, heater, temp, wait=False): if not mcu_name.startswith("mcu"): mcu_name = "mcu " + mcu_name mcu_object = self.printer.lookup_object(mcu_name, None) - if not mcu_object.non_critical_disconnected: + if mcu_object is not None and not mcu_object.non_critical_disconnected: heater.notify_disabled(mcu_name) return toolhead = self.printer.lookup_object("toolhead") From 2551506e8adf08669c164b3115cec1d5c3932d75 Mon Sep 17 00:00:00 2001 From: Zeanon Date: Mon, 25 Nov 2024 23:13:37 +0100 Subject: [PATCH 8/9] fixed ruff --- klippy/extras/heaters.py | 127 ++++++++++++++++++++++++++------------- 1 file changed, 86 insertions(+), 41 deletions(-) diff --git a/klippy/extras/heaters.py b/klippy/extras/heaters.py index 079a2dc6e..cc978c3e9 100644 --- a/klippy/extras/heaters.py +++ b/klippy/extras/heaters.py @@ -57,14 +57,18 @@ def __init__(self, config, sensor): minval=self.min_temp, maxval=self.max_temp, ) - is_fileoutput = self.printer.get_start_args().get("debugoutput") is not None + is_fileoutput = ( + self.printer.get_start_args().get("debugoutput") is not None + ) self.can_extrude = self.min_extrude_temp <= 0.0 or is_fileoutput self.disable_if_connected = [] if hasattr(config, "getlist"): self.disable_if_connected = config.getlist( "disable_if_connected", self.disable_if_connected ) - self.max_power = config.getfloat("max_power", 1.0, above=0.0, maxval=1.0) + self.max_power = config.getfloat( + "max_power", 1.0, above=0.0, maxval=1.0 + ) self.config_smooth_time = config.getfloat("smooth_time", 1.0, above=0.0) self.smooth_time = self.config_smooth_time self.inv_smooth_time = 1.0 / self.smooth_time @@ -95,7 +99,9 @@ def __init__(self, config, sensor): self.printer.load_object(config, "pid_calibrate") self.gcode = self.printer.lookup_object("gcode") self.pmgr = self.ProfileManager(self) - self.control = self.lookup_control(self.pmgr.init_default_profile(), True) + self.control = self.lookup_control( + self.pmgr.init_default_profile(), True + ) self.gcode.register_mux_command( "SET_HEATER_TEMPERATURE", "HEATER", @@ -125,7 +131,9 @@ def __init__(self, config, sensor): desc=self.cmd_SET_HEATER_PID_help, ) - self.printer.register_event_handler("klippy:shutdown", self._handle_shutdown) + self.printer.register_event_handler( + "klippy:shutdown", self._handle_shutdown + ) def notify_disabled(self, mcu_name): raise self.printer.command_error( @@ -203,7 +211,9 @@ def set_temp(self, degrees): self.target_temp = degrees def get_temp(self, eventtime): - print_time = self.mcu_pwm.get_mcu().estimated_print_time(eventtime) - 5.0 + print_time = ( + self.mcu_pwm.get_mcu().estimated_print_time(eventtime) - 5.0 + ) with self.lock: if self.last_temp_time < print_time: return 0.0, self.target_temp @@ -312,7 +322,9 @@ def __init__(self, outer_instance): "pid_profile %s" % self.outer_instance.short_name ) for profile in stored_profs: - self._init_profile(profile, profile.get_name().split(" ", 2)[-1]) + self._init_profile( + profile, profile.get_name().split(" ", 2)[-1] + ) def _init_profile(self, config_section, name): version = config_section.getint("pid_version", 1) @@ -326,7 +338,9 @@ def _init_profile(self, config_section, name): self.incompatible_profiles.append(name) return None temp_profile = {} - control = self._check_value_config("control", config_section, str, False) + control = self._check_value_config( + "control", config_section, str, False + ) if control == "watermark": temp_profile["max_delta"] = config_section.getfloat( "max_delta", 2.0, above=0.0 @@ -362,8 +376,10 @@ def _init_profile(self, config_section, name): temp_profile["filament_density"] = config_section.getfloat( "filament_density", above=0.0, default=1.2 ) - temp_profile["filament_heat_capacity"] = config_section.getfloat( - "filament_heat_capacity", above=0.0, default=1.8 + temp_profile["filament_heat_capacity"] = ( + config_section.getfloat( + "filament_heat_capacity", above=0.0, default=1.8 + ) ) temp_profile["maximum_retract"] = config_section.getfloat( "maximum_retract", above=0.0, default=2.0 @@ -387,7 +403,9 @@ def _init_profile(self, config_section, name): filament_temp_src = (FILAMENT_TEMP_SRC_FIXED, value) temp_profile["filament_temp_src"] = filament_temp_src - ambient_sensor_name = config_section.get("ambient_temp_sensor", None) + ambient_sensor_name = config_section.get( + "ambient_temp_sensor", None + ) ambient_sensor = None if ambient_sensor_name is not None: ambient_sensor = config_section.get_printer().load_object( @@ -396,8 +414,10 @@ def _init_profile(self, config_section, name): None, ) if ambient_sensor is None: - ambient_sensor = config_section.get_printer().lookup_object( - ambient_sensor_name, None + ambient_sensor = ( + config_section.get_printer().lookup_object( + ambient_sensor_name, None + ) ) if ambient_sensor is None: raise config_section.error( @@ -430,8 +450,8 @@ def _init_profile(self, config_section, name): fan = fan_obj.fan temp_profile["cooling_fan"] = fan - temp_profile["fan_ambient_transfer"] = config_section.getfloatlist( - "fan_ambient_transfer", [] + temp_profile["fan_ambient_transfer"] = ( + config_section.getfloatlist("fan_ambient_transfer", []) ) elif control == "pid" or control == "pid_v": for key, (type, placeholder) in PID_PROFILE_OPTIONS.items(): @@ -478,7 +498,10 @@ def _compute_section_name(self, profile_name): self.outer_instance.short_name if profile_name == "default" else ( - "pid_profile " + self.outer_instance.short_name + " " + profile_name + "pid_profile " + + self.outer_instance.short_name + + " " + + profile_name ) ) @@ -493,9 +516,13 @@ def _check_value_gcmd( maxval=None, ): if type is int: - value = gcmd.get_int(name, default, minval=minval, maxval=maxval) + value = gcmd.get_int( + name, default, minval=minval, maxval=maxval + ) elif type is float: - value = gcmd.get_float(name, default, minval=minval, maxval=maxval) + value = gcmd.get_float( + name, default, minval=minval, maxval=maxval + ) else: value = gcmd.get(name, default) if not can_be_none and value is None: @@ -523,7 +550,9 @@ def set_values(self, profile_name, gcmd, verbose): kp = self._check_value_gcmd("KP", None, gcmd, float, False) ki = self._check_value_gcmd("KI", None, gcmd, float, False) kd = self._check_value_gcmd("KD", None, gcmd, float, False) - smooth_time = self._check_value_gcmd("SMOOTH_TIME", None, gcmd, float, True) + smooth_time = self._check_value_gcmd( + "SMOOTH_TIME", None, gcmd, float, True + ) keep_target = self._check_value_gcmd( "KEEP_TARGET", 0, gcmd, int, True, minval=0, maxval=1 ) @@ -539,7 +568,9 @@ def set_values(self, profile_name, gcmd, verbose): "pid_ki": ki, "pid_kd": kd, } - temp_control = self.outer_instance.lookup_control(temp_profile, load_clean) + temp_control = self.outer_instance.lookup_control( + temp_profile, load_clean + ) self.outer_instance.set_control(temp_control, keep_target) msg = ( "PID Parameters:\n" @@ -577,7 +608,8 @@ def get_values(self, profile_name, gcmd, verbose): "Control: %s\n" "Smooth Time: %.3f\n" "pid_Kp=%.3f pid_Ki=%.3f pid_Kd=%.3f\n" - "name: %s" % (target, tolerance, control, smooth_time, kp, ki, kd, name) + "name: %s" + % (target, tolerance, control, smooth_time, kp, ki, kd, name) ) def save_profile(self, profile_name=None, gcmd=None, verbose=True): @@ -606,12 +638,15 @@ def save_profile(self, profile_name=None, gcmd=None, verbose=True): ) def load_profile(self, profile_name, gcmd, verbose): - verbose = self._check_value_gcmd("VERBOSE", "low", gcmd, "lower", True) + verbose = self._check_value_gcmd( + "VERBOSE", "low", gcmd, "lower", True + ) load_clean = self._check_value_gcmd( "LOAD_CLEAN", 0, gcmd, int, True, minval=0, maxval=1 ) if ( - profile_name == self.outer_instance.get_control().get_profile()["name"] + profile_name + == self.outer_instance.get_control().get_profile()["name"] and not load_clean ): if verbose == "high" or verbose == "low": @@ -662,22 +697,20 @@ def load_profile(self, profile_name, gcmd, verbose): if profile["smooth_time"] is None else profile["smooth_time"] ) - msg = ( - "Target: %.2f\n" - "Tolerance: %.4f\n" - "Control: %s\n" - % ( - profile["pid_target"], - profile["pid_tolerance"], - profile["control"], - ) + msg = "Target: %.2f\n" "Tolerance: %.4f\n" "Control: %s\n" % ( + profile["pid_target"], + profile["pid_tolerance"], + profile["control"], ) if smooth_time is not None: msg += "Smooth Time: %.3f\n" % smooth_time - msg += "PID Parameters: pid_Kp=%.3f pid_Ki=%.3f pid_Kd=%.3f\n" % ( - profile["pid_kp"], - profile["pid_ki"], - profile["pid_kd"], + msg += ( + "PID Parameters: pid_Kp=%.3f pid_Ki=%.3f pid_Kd=%.3f\n" + % ( + profile["pid_kp"], + profile["pid_ki"], + profile["pid_kd"], + ) ) self.outer_instance.gcode.respond_info(msg) @@ -804,7 +837,8 @@ def temperature_update(self, read_time, temp, target_temp): temp_deriv = temp_diff / time_diff else: temp_deriv = ( - self.prev_temp_deriv * (self.min_deriv_time - time_diff) + temp_diff + self.prev_temp_deriv * (self.min_deriv_time - time_diff) + + temp_diff ) / self.min_deriv_time # Calculate accumulated temperature "error" temp_err = target_temp - temp @@ -863,7 +897,9 @@ def __init__(self, profile, heater, load_clean=False): self.temps = ( ([AMBIENT_TEMP] * 3) if load_clean - else ([self.heater.get_temp(self.heater.reactor.monotonic())[0]] * 3) + else ( + [self.heater.get_temp(self.heater.reactor.monotonic())[0]] * 3 + ) ) self.times = [0.0] * 3 # temperature reading times self.d1 = 0.0 # previous smoothed 1st derivative @@ -912,7 +948,9 @@ def temperature_update(self, read_time, temp, target_temp): def check_busy(self, eventtime, smoothed_temp, target_temp): temp_diff = target_temp - smoothed_temp - return abs(temp_diff) > PID_SETTLE_DELTA or abs(self.d1) > PID_SETTLE_SLOPE + return ( + abs(temp_diff) > PID_SETTLE_DELTA or abs(self.d1) > PID_SETTLE_SLOPE + ) def update_smooth_time(self): self.smooth_time = self.heater.get_smooth_time() # smoothing window @@ -993,7 +1031,9 @@ def lookup_heater(self, heater_name): if " " in heater_name: heater_name = heater_name.split(" ", 1)[1] if heater_name not in self.heaters: - raise self.printer.config_error("Unknown heater '%s'" % (heater_name,)) + raise self.printer.config_error( + "Unknown heater '%s'" % (heater_name,) + ) return self.heaters[heater_name] def setup_sensor(self, config): @@ -1077,7 +1117,10 @@ def set_temperature(self, heater, temp, wait=False): if not mcu_name.startswith("mcu"): mcu_name = "mcu " + mcu_name mcu_object = self.printer.lookup_object(mcu_name, None) - if mcu_object is not None and not mcu_object.non_critical_disconnected: + if ( + mcu_object is not None + and not mcu_object.non_critical_disconnected + ): heater.notify_disabled(mcu_name) return toolhead = self.printer.lookup_object("toolhead") @@ -1096,7 +1139,9 @@ def cmd_TEMPERATURE_WAIT(self, gcmd): max_temp = gcmd.get_float("MAXIMUM", float("inf"), above=min_temp) error_on_cancel = gcmd.get("ALLOW_CANCEL", None) is None if min_temp == float("-inf") and max_temp == float("inf"): - raise gcmd.error("Error on 'TEMPERATURE_WAIT': missing MINIMUM or MAXIMUM.") + raise gcmd.error( + "Error on 'TEMPERATURE_WAIT': missing MINIMUM or MAXIMUM." + ) if self.printer.get_start_args().get("debugoutput") is not None: return if sensor_name in self.heaters: From d5f0da8fc6d0892a8193ff6f2add12f0ab10beb3 Mon Sep 17 00:00:00 2001 From: Zeanon Date: Mon, 23 Dec 2024 19:06:33 +0100 Subject: [PATCH 9/9] fixed docs, readme and hassattr --- README.md | 2 +- docs/Config_Reference.md | 2 +- klippy/extras/heaters.py | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 839047880..feabf913b 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ See the [Danger Features document](https://dangerklipper.io/Danger_Features.html - [danger_options: configurable homing constants](https://github.com/DangerKlippers/danger-klipper/pull/378) -- [adxl: disable heaters when connected](https://github.com/DangerKlippers/danger-klipper/pull/425) +- [heater: disable if specified mcu is connected](https://github.com/DangerKlippers/danger-klipper/pull/425) If you're feeling adventurous, take a peek at the extra features in the bleeding-edge-v2 branch [feature documentation](docs/Bleeding_Edge.md) and [feature configuration reference](docs/Config_Reference_Bleeding_Edge.md): diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 9d08960b9..21ef49d07 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -1092,7 +1092,7 @@ per_move_pressure_advance: False # This causes changes to pressure advance be taken into account immediately, # for all moves in the current queue, rather than ~250ms later once the queue gets flushed #disable_if_connected: -# List of mcus that should disable the heater if connected, usefull for nozzle adxls +# List of mcus that should disable the heater if connected, useful for nozzle adxls # as to not accidentally fry them due to heating the hotend. ``` diff --git a/klippy/extras/heaters.py b/klippy/extras/heaters.py index cc978c3e9..59fa6e649 100644 --- a/klippy/extras/heaters.py +++ b/klippy/extras/heaters.py @@ -62,10 +62,9 @@ def __init__(self, config, sensor): ) self.can_extrude = self.min_extrude_temp <= 0.0 or is_fileoutput self.disable_if_connected = [] - if hasattr(config, "getlist"): - self.disable_if_connected = config.getlist( - "disable_if_connected", self.disable_if_connected - ) + self.disable_if_connected = config.getlist( + "disable_if_connected", self.disable_if_connected + ) self.max_power = config.getfloat( "max_power", 1.0, above=0.0, maxval=1.0 )