diff --git a/klippy/extras/aht20_f.so b/klippy/extras/aht20_f.so index 86954b2..d0fc730 100644 Binary files a/klippy/extras/aht20_f.so and b/klippy/extras/aht20_f.so differ diff --git a/klippy/extras/box_detect.so b/klippy/extras/box_detect.so index 62a25b3..78c0a8a 100644 Binary files a/klippy/extras/box_detect.so and b/klippy/extras/box_detect.so differ diff --git a/klippy/extras/box_extras.so b/klippy/extras/box_extras.so index 33e9df9..7ef9139 100644 Binary files a/klippy/extras/box_extras.so and b/klippy/extras/box_extras.so differ diff --git a/klippy/extras/box_heater_fan.py b/klippy/extras/box_heater_fan.py new file mode 100644 index 0000000..c2708cb --- /dev/null +++ b/klippy/extras/box_heater_fan.py @@ -0,0 +1,70 @@ +from . import fan + +PIN_MIN_TIME = 0.100 + +class BoxHeaterFan: + def __init__(self, config): + self.printer = config.get_printer() + self.printer.register_event_handler("klippy:ready", self.handle_ready) + self.printer.register_event_handler("klippy:connect", + self.handle_connect) + self.stepper_names = config.getlist("stepper", None) + self.stepper_enable = self.printer.load_object(config, 'stepper_enable') + self.printer.load_object(config, 'heaters') + self.heater_temp = config.getfloat("heater_temp", 50.0) + self.heaters = [] + self.fan = fan.Fan(config) + self.fan_speed = config.getfloat('fan_speed', default=1., + minval=0., maxval=1.) + self.idle_speed = config.getfloat( + 'idle_speed', default=self.fan_speed, minval=0., maxval=1.) + self.idle_timeout = config.getint("idle_timeout", default=30, minval=0) + self.heater_names = config.getlist("heater", ("extruder",)) + self.last_on = self.idle_timeout + self.last_speed = 0. + def handle_connect(self): + # Heater lookup + pheaters = self.printer.lookup_object('heaters') + self.heaters = [pheaters.lookup_heater(n) for n in self.heater_names] + # Stepper lookup + all_steppers = self.stepper_enable.get_steppers() + if self.stepper_names is None: + self.stepper_names = all_steppers + return + if not all(x in all_steppers for x in self.stepper_names): + raise self.printer.config_error( + "One or more of these steppers are unknown: " + "%s (valid steppers are: %s)" + % (self.stepper_names, ", ".join(all_steppers))) + def handle_ready(self): + reactor = self.printer.get_reactor() + reactor.register_timer(self.callback, reactor.monotonic()+PIN_MIN_TIME) + def get_status(self, eventtime): + return self.fan.get_status(eventtime) + def callback(self, eventtime): + speed = 0. + active = False + for name in self.stepper_names: + active |= self.stepper_enable.lookup_enable(name).is_motor_enabled() + for heater in self.heaters: + current_temp, target_temp = heater.get_temp(eventtime) + if target_temp or current_temp > self.heater_temp: + active = True + + + + if active: + self.last_on = 0 + speed = self.fan_speed + elif self.last_on < self.idle_timeout: + speed = self.idle_speed + self.last_on += 1 + if speed != self.last_speed: + self.last_speed = speed + curtime = self.printer.get_reactor().monotonic() + print_time = self.fan.get_mcu().estimated_print_time(curtime) + self.fan.set_speed(print_time + PIN_MIN_TIME, speed) + return eventtime + 1. + +def load_config_prefix(config): + return BoxHeaterFan(config) diff --git a/klippy/extras/box_stepper.so b/klippy/extras/box_stepper.so index dc55c0b..a460d58 100644 Binary files a/klippy/extras/box_stepper.so and b/klippy/extras/box_stepper.so differ diff --git a/klippy/extras/buttons_irq.py b/klippy/extras/buttons_irq.py new file mode 100644 index 0000000..6d334c0 --- /dev/null +++ b/klippy/extras/buttons_irq.py @@ -0,0 +1,94 @@ +# button_irq.py - Klipper Python module for IRQ-driven buttons +# +# Copyright (C) 2025 Menson +# +# This file may be distributed under the terms of the GNU GPLv3 license. + +import logging + +class _IRQButtonMCU: + def __init__(self, printer, mcu, debounce_us): + self.printer = printer + self.reactor = printer.get_reactor() + self.mcu = mcu + self.pin_list = [] + self.callbacks = [] + self.oid = mcu.create_oid() + self.ack_count = 0 + self.state = 0 + self.invert = 0 + self.debounce_us = debounce_us + + self.mcu.register_response(self._handle_state, "irq_button_state", self.oid) + self.mcu.register_config_callback(self._build_config) + + def setup_buttons(self, pin_params_list, callback): + mask = 0 + shift = len(self.pin_list) + for pin_params in pin_params_list: + if pin_params.get('invert', False): + self.invert |= 1 << len(self.pin_list) + mask |= 1 << len(self.pin_list) + self.pin_list.append(pin_params) + self.callbacks.append((mask, shift, callback)) + + def _build_config(self): + logging.info("_irq_button debounce_us=%d", self.debounce_us) + for pin in self.pin_list: + self.mcu.add_config_cmd( + "config_irq_button oid=%d pin=%s pull_up=%d debounce_us=%d invert=%d" % + (self.oid, pin['pin'], pin.get('pullup', 0), self.debounce_us, pin.get('invert', 0)) + ) + cmd_queue = self.mcu.alloc_command_queue() + self.ack_cmd = self.mcu.lookup_command( + "irq_button_ack oid=%c count=%c", cq=cmd_queue) + + def _handle_state(self, msg): + count = msg["count"] + states = msg["state"] + #logging.warning("IRQ button state oid=%d count=%d states=%s", + # self.oid, count, states) + for s in states: + s = int(s) ^ self.invert + if s != self.state: + changed = s ^ self.state + self.state = s + for mask, shift, callback in self.callbacks: + if changed & mask: + value = (s & mask) >> shift + self.reactor.register_async_callback( + lambda et, c=callback, v=value: c(et, v)) + self.ack_cmd.send([self.oid, count]) + + +class MCU_irq_button: + def __init__(self, config): + self.printer = config.get_printer() + self.irq_buttons = {} + + def register_buttons(self, pins, callback, debounce_us): + ppins = self.printer.lookup_object('pins') + mcu = mcu_name = None + pin_params_list = [] + + for pin in pins: + pin_params = ppins.lookup_pin(pin, can_invert=True, can_pullup=True) + if mcu is not None and pin_params['chip'] != mcu: + raise ppins.error("IRQ button pins must be on same MCU") + mcu = pin_params['chip'] + mcu_name = pin_params['chip_name'] + pin_params_list.append(pin_params) + + mcu_button = self.irq_buttons.get(mcu_name) + if (mcu_button is None + or len(mcu_button.pin_list) + len(pin_params_list) > 8): + logging.warning( + "Creating new IRQ button object for MCU '%s' with %d pins", + mcu_name, len(pin_params_list)) + mcu_button = _IRQButtonMCU(self.printer, mcu, debounce_us) + self.irq_buttons[mcu_name] = mcu_button + + mcu_button.setup_buttons(pin_params_list, callback) + +def load_config(config): + return MCU_irq_button(config) diff --git a/klippy/extras/filament_motion_sensor.py b/klippy/extras/filament_motion_sensor.py index 192915a..7207891 100644 --- a/klippy/extras/filament_motion_sensor.py +++ b/klippy/extras/filament_motion_sensor.py @@ -16,9 +16,21 @@ class EncoderSensor: self.extruder_name = config.get('extruder') self.detection_length = config.getfloat( 'detection_length', 7., above=0.) + self.use_irq = config.getboolean('use_irq', False) + logging.info(f"motion use_irq?:{self.use_irq}") + self.debounce_us = 0 # Configure pins - buttons = self.printer.load_object(config, 'buttons') - buttons.register_buttons([switch_pin], self.encoder_event) + if self.use_irq: + logging.info(f"motion use_irq1?:{self.use_irq}") + self.debounce_us = config.getint('debounce_us', 0) + irq_button = config.printer.load_object(config, "buttons_irq") + #irq_button.register_buttons([switch_pin], callback=self.encoder_event, self.debounce_us) + irq_button.register_buttons([switch_pin], callback=self.encoder_event, debounce_us = self.debounce_us) + else: + logging.info(f"motion use_irq2?:{self.use_irq}") + buttons = self.printer.load_object(config, 'buttons') + buttons.register_buttons([switch_pin], self.encoder_event) + # Get printer objects self.reactor = self.printer.get_reactor() self.runout_helper = filament_switch_sensor.RunoutHelper(config) @@ -42,12 +54,27 @@ class EncoderSensor: 'CLEAR_MOTION_DATA', self.cmd_CLEAR_MOTION_DATA ) + self.motion_name = config.get_name().split()[1] + self.gcode.register_mux_command('SET_MOTION_DETECTION', "NAME", self.motion_name, self.cmd_SET_MOTION_DETECTION) + self.gcode.register_mux_command('GET_MOTION_DETECTION', "NAME", self.motion_name, self.cmd_GET_MOTION_DETECTION) def _update_filament_runout_pos(self, eventtime=None): if eventtime is None: eventtime = self.reactor.monotonic() self.filament_runout_pos = ( self._get_extruder_pos(eventtime) + self.detection_length) + def cmd_SET_MOTION_DETECTION(self, gcmd): + d_l = gcmd.get_float('LENGTH', 0.) + if d_l <= 0: + self.gcode.respond_info("The LENGTH value is error") + else: + self.detection_length = d_l + self._update_filament_runout_pos() + self.gcode.respond_info("the detection_length has changed successsful. The will restart after next restart.") + + def cmd_GET_MOTION_DETECTION(self, gcmd): + self.gcode.respond_info("the detection_length is: " + str(self.detection_length)) + def cmd_CLEAR_MOTION_DATA(self, gcmd): self._update_filament_runout_pos() def _handle_ready(self): @@ -75,12 +102,14 @@ class EncoderSensor: extruder_pos < self.filament_runout_pos) return eventtime + CHECK_RUNOUT_TIMEOUT def encoder_event(self, eventtime, state): + #self.gcode.respond_info(f"motion state: {state} ") if self.extruder is not None: self._update_filament_runout_pos(eventtime) self.button_state = state # Check for filament insertion # Filament is always assumed to be present on an encoder event self.runout_helper.note_filament_present(True) + def load_config_prefix(config): return EncoderSensor(config) diff --git a/klippy/extras/verify_heater.py b/klippy/extras/verify_heater.py index 76592b6..afd1758 100644 --- a/klippy/extras/verify_heater.py +++ b/klippy/extras/verify_heater.py @@ -32,6 +32,7 @@ class HeaterCheck: self.last_target = self.goal_temp = self.error = 0. self.goal_systime = self.printer.get_reactor().NEVER self.check_timer = None + self.is_box_heater = config.getboolean('is_box_heter',False) def handle_connect(self): if self.printer.get_start_args().get('debugoutput') is not None: # Disable verify_heater if outputting to a debug file @@ -75,7 +76,11 @@ class HeaterCheck: self.error = 0. else: # Failure due to inability to maintain target temperature - return self.heater_fault() + if self.is_box_heater and target > 0: + self.box_heater_fault() + return eventtime + 1. + else: + return self.heater_fault() elif heater_bed.heater_bed_state == 1 and self.heater_name == "chamber": self.error = 0. self.goal_temp = temp - self.heating_gain @@ -99,6 +104,16 @@ class HeaterCheck: logging.error(msg) self.printer.invoke_shutdown(msg + HINT_THERMAL) return self.printer.get_reactor().NEVER + + def box_heater_fault(self): + msg = "Heater %s not heating at expected rate" % (self.heater_name,) + logging.error(msg) + self.printer.lookup_object('gcode').respond_info("Code:QDE_005_001; Message:"+ msg) + pheaters = self.printer.lookup_object('heaters') + heater = pheaters.lookup_heater(self.heater_name) + heater.set_temp(0) + self.last_target = 0 + return def load_config_prefix(config): return HeaterCheck(config)