From b22c1e99fbf92fd9194411cd850fd0c3aa0acaa3 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Tue, 5 May 2026 23:45:14 +0200 Subject: [PATCH 01/42] Beginnings of AFC --- .../files/0003-drv8833-motor-control.patch | 1198 +++++++++++++++++ .../klipper/kalico_2026.02.00.inc | 1 + .../mcu-flasher/mcu-flasher_0.1.0.bb | 2 +- .../recipes-data/klipper-afc-addon/afc_0.1.bb | 44 + .../klipper-afc-addon/files/afc.cfg | 110 ++ .../klipper-afc-addon/files/canvas.cfg | 231 ++++ 6 files changed, 1585 insertions(+), 1 deletion(-) create mode 100644 meta-opencentauri/recipes-apps/klipper/files/0003-drv8833-motor-control.patch create mode 100644 meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb create mode 100644 meta-opencentauri/recipes-data/klipper-afc-addon/files/afc.cfg create mode 100644 meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg diff --git a/meta-opencentauri/recipes-apps/klipper/files/0003-drv8833-motor-control.patch b/meta-opencentauri/recipes-apps/klipper/files/0003-drv8833-motor-control.patch new file mode 100644 index 00000000..896124fc --- /dev/null +++ b/meta-opencentauri/recipes-apps/klipper/files/0003-drv8833-motor-control.patch @@ -0,0 +1,1198 @@ +diff --git a/klippy/extras/drv8833.py b/klippy/extras/drv8833.py +new file mode 100644 +index 00000000..c6271960 +--- /dev/null ++++ b/klippy/extras/drv8833.py +@@ -0,0 +1,786 @@ ++# Support for DRV8833 based CANVAS-style motor control ++# ++# This file may be distributed under the terms of the GNU GPLv3 license. ++import math ++import os ++ ++HALL_POLL_TIME = 0.001 ++CONTROL_INTERVAL = 0.010 ++MCU_REPORT_INTERVAL = 0.050 ++DEBUG_LOG_INTERVAL = 0.500 ++DEFAULT_DUTY = 50.0 ++MAX_DUTY_STEP = 4.0 ++DEFAULT_PID_KP = 1.2 ++DEFAULT_PID_KI = 0.8 ++DEFAULT_PID_KD = 0.02 ++HALL_RESOLUTION_SCALE = 1000000 ++SPEED_SCALE = 1000 ++DUTY_SCALE = 10 ++PID_PARAM_SCALE = 1000 ++AUTOTUNE_SETTLE_TIME = 0.8 ++AUTOTUNE_SAMPLE_TIME = 0.4 ++AUTOTUNE_BAND_RATIO = 0.05 ++AUTOTUNE_MIN_BAND = 1.0 ++AUTOTUNE_RELAY_RATIO = 0.15 ++AUTOTUNE_MIN_RELAY_DUTY = 4.0 ++AUTOTUNE_MAX_RELAY_DUTY = 20.0 ++AUTOTUNE_BASELINE_STEPS = 6 ++AUTOTUNE_REQUIRED_PAIRS = 3 ++AUTOTUNE_TIMEOUT = 45.0 ++ ++ ++def _clamp(value, min_value, max_value): ++ return max(min_value, min(max_value, value)) ++ ++ ++class Drv8833Interface: ++ def drv8833_set_speed(self, speed: float): ++ raise NotImplementedError() ++ ++ def drv8833_move(self, speed: float, distance: float): ++ raise NotImplementedError() ++ ++ ++class MCUDrv8833Controller: ++ def __init__(self, config, motor_fwd_params, motor_rwd_params, hall_params): ++ self.printer = config.get_printer() ++ self.reactor = self.printer.get_reactor() ++ self.mcu = motor_fwd_params["chip"] ++ self.oid = self.mcu.create_oid() ++ self.motor_fwd_pin = motor_fwd_params["pin"] ++ self.motor_rwd_pin = motor_rwd_params["pin"] ++ self.hall_pin = hall_params["pin"] ++ self.hall_pullup = hall_params["pullup"] ++ self.cycle_time = config.getfloat("motor_cycle_time", 0.002, above=0.0) ++ self.hall_resolution = config.getfloat( ++ "motor_hall_resolution", above=0.0 ++ ) ++ self.default_duty = config.getfloat( ++ "default_duty", DEFAULT_DUTY, minval=0.0, maxval=100.0 ++ ) ++ self.max_duty_step = config.getfloat( ++ "max_duty_step", MAX_DUTY_STEP, above=0.0, maxval=100.0 ++ ) ++ self.pid_kp = config.getfloat("pid_kp", DEFAULT_PID_KP, minval=0.0) ++ self.pid_ki = config.getfloat("pid_ki", DEFAULT_PID_KI, minval=0.0) ++ self.pid_kd = config.getfloat("pid_kd", DEFAULT_PID_KD, minval=0.0) ++ self.last_active = False ++ self.last_manual = False ++ self.last_count = 0 ++ self.last_speed_mm_s = 0.0 ++ self.last_duty_cycle = 0.0 ++ self.last_status_time = 0.0 ++ self.status_sequence = 0 ++ self.status_callback = lambda: None ++ self._set_cmd = None ++ self._manual_cmd = None ++ self._set_pid_cmd = None ++ self.mcu.register_response( ++ self._handle_status, "drv8833_status", self.oid ++ ) ++ self.mcu.register_config_callback(self._build_config) ++ ++ def _pid_to_mcu(self, value): ++ return int(round(value * PID_PARAM_SCALE * DUTY_SCALE)) ++ ++ def _build_config(self): ++ pwm_max = int(self.mcu.get_constant_float("PWM_MAX") + 0.5) ++ cycle_ticks = self.mcu.seconds_to_clock(self.cycle_time) ++ poll_ticks = self.mcu.seconds_to_clock(HALL_POLL_TIME) ++ control_ticks = self.mcu.seconds_to_clock(CONTROL_INTERVAL) ++ report_ticks = self.mcu.seconds_to_clock(MCU_REPORT_INTERVAL) ++ hall_resolution = int(round(self.hall_resolution * HALL_RESOLUTION_SCALE)) ++ default_duty = int(round(self.default_duty * DUTY_SCALE)) ++ max_duty_step = int(round(self.max_duty_step * DUTY_SCALE)) ++ pid_kp = self._pid_to_mcu(self.pid_kp) ++ pid_ki = self._pid_to_mcu(self.pid_ki) ++ pid_kd = self._pid_to_mcu(self.pid_kd) ++ self.mcu.add_config_cmd( ++ "config_drv8833 oid=%d motor_fwd_pin=%s motor_rwd_pin=%s " ++ "cycle_ticks=%d hall_pin=%s hall_pullup=%d poll_ticks=%d " ++ "control_ticks=%d report_ticks=%d hall_resolution=%d " ++ "pwm_max=%d default_duty=%d max_duty_step=%d" ++ % ( ++ self.oid, ++ self.motor_fwd_pin, ++ self.motor_rwd_pin, ++ cycle_ticks, ++ self.hall_pin, ++ self.hall_pullup, ++ poll_ticks, ++ control_ticks, ++ report_ticks, ++ hall_resolution, ++ pwm_max, ++ default_duty, ++ max_duty_step, ++ ) ++ ) ++ self.mcu.add_config_cmd( ++ "drv8833_set_pid oid=%d kp=%d ki=%d kd=%d" ++ % (self.oid, pid_kp, pid_ki, pid_kd), ++ is_init=True, ++ ) ++ self.mcu.add_config_cmd( ++ "drv8833_set oid=%d enable=0 direction=1 target_speed=0 stop_ticks=0" ++ % (self.oid,), ++ on_restart=True, ++ ) ++ self._set_cmd = self.mcu.lookup_command( ++ "drv8833_set oid=%c enable=%c direction=%c target_speed=%u stop_ticks=%u" ++ ) ++ self._manual_cmd = self.mcu.lookup_command( ++ "drv8833_manual oid=%c enable=%c direction=%c duty=%hu" ++ ) ++ self._set_pid_cmd = self.mcu.lookup_command( ++ "drv8833_set_pid oid=%c kp=%u ki=%u kd=%u" ++ ) ++ ++ def _handle_status(self, params): ++ self.last_active = bool(params["active"]) ++ self.last_manual = bool(params["manual"]) ++ self.last_count = params["count"] ++ self.last_speed_mm_s = params["speed"] / float(SPEED_SCALE) ++ self.last_duty_cycle = params["duty"] / float(DUTY_SCALE) ++ self.last_status_time = self.reactor.monotonic() ++ self.status_sequence += 1 ++ self.status_callback() ++ ++ def start(self, direction, target_speed): ++ self.start_move(direction, target_speed, 0) ++ ++ def start_move(self, direction, target_speed, stop_ticks): ++ set_cmd = self._set_cmd ++ if set_cmd is None: ++ raise self.printer.command_error("drv8833 controller not configured") ++ set_cmd.send( ++ [ ++ self.oid, ++ 1, ++ 1 if direction > 0 else 0, ++ int(round(target_speed * SPEED_SCALE)), ++ int(stop_ticks), ++ ] ++ ) ++ ++ def manual_start(self, direction, duty_cycle): ++ manual_cmd = self._manual_cmd ++ if manual_cmd is None: ++ raise self.printer.command_error("drv8833 controller not configured") ++ manual_cmd.send( ++ [ ++ self.oid, ++ 1, ++ 1 if direction > 0 else 0, ++ int(round(_clamp(duty_cycle, 0.0, 100.0) * DUTY_SCALE)), ++ ] ++ ) ++ ++ def set_pid(self, kp, ki, kd): ++ set_pid_cmd = self._set_pid_cmd ++ if set_pid_cmd is None: ++ raise self.printer.command_error("drv8833 controller not configured") ++ self.pid_kp = kp ++ self.pid_ki = ki ++ self.pid_kd = kd ++ set_pid_cmd.send( ++ [ ++ self.oid, ++ self._pid_to_mcu(kp), ++ self._pid_to_mcu(ki), ++ self._pid_to_mcu(kd), ++ ] ++ ) ++ ++ def stop(self): ++ set_cmd = self._set_cmd ++ if set_cmd is None: ++ raise self.printer.command_error("drv8833 controller not configured") ++ set_cmd.send([self.oid, 0, 1, 0, 0]) ++ ++ ++class Drv8833PIDAutoTune: ++ def __init__(self, lane, gcmd): ++ self.lane = lane ++ self.gcmd = gcmd ++ self.reactor = lane.reactor ++ self.controller = lane.hall_controller ++ self.target_speed = gcmd.get_float("SPEED", 40.0, above=0.0) ++ direction = gcmd.get("DIRECTION", "forwards").strip().lower() ++ if direction not in ("forwards", "forward", "backwards", "backward"): ++ raise gcmd.error("DIRECTION must be forwards or backwards") ++ self.direction = 1 if direction.startswith("for") else -1 ++ self.band = gcmd.get_float( ++ "BAND", ++ max(AUTOTUNE_MIN_BAND, self.target_speed * AUTOTUNE_BAND_RATIO), ++ above=0.0, ++ ) ++ self.settle_time = gcmd.get_float( ++ "SETTLE_TIME", AUTOTUNE_SETTLE_TIME, above=0.0 ++ ) ++ self.sample_time = gcmd.get_float( ++ "SAMPLE_TIME", AUTOTUNE_SAMPLE_TIME, above=0.0 ++ ) ++ self.timeout = gcmd.get_float("TIMEOUT", AUTOTUNE_TIMEOUT, above=0.0) ++ self.write_file = gcmd.get_int("WRITE_FILE", 0) ++ self.eventtime = self.reactor.monotonic() ++ self.data = [] ++ self.peaks = [] ++ self.switch_times = [] ++ ++ def _pause(self, duration): ++ self.eventtime = self.reactor.pause(self.eventtime + duration) ++ return self.eventtime ++ ++ def _wait_for_status(self, timeout=1.0): ++ start_seq = self.controller.status_sequence ++ deadline = self.eventtime + timeout ++ while self.controller.status_sequence == start_seq: ++ self.eventtime = self.reactor.pause( ++ min(deadline, self.eventtime + MCU_REPORT_INTERVAL) ++ ) ++ if self.eventtime >= deadline and self.controller.status_sequence == start_seq: ++ raise self.gcmd.error("Timed out waiting for drv8833 status update") ++ sample = ( ++ self.controller.last_status_time or self.eventtime, ++ self.controller.last_speed_mm_s, ++ self.controller.last_duty_cycle, ++ ) ++ self.data.append(sample + (self.target_speed,)) ++ return sample ++ ++ def _measure_speed(self, duty_cycle): ++ self.lane._start_manual(self.eventtime, self.direction, duty_cycle) ++ self._pause(self.settle_time) ++ endtime = self.eventtime + self.sample_time ++ samples = [] ++ while self.eventtime < endtime: ++ _, speed, _ = self._wait_for_status() ++ samples.append(speed) ++ if not samples: ++ samples.append(self.controller.last_speed_mm_s) ++ avg_speed = sum(samples) / float(len(samples)) ++ self.gcmd.respond_info( ++ "drv8833 %s tune sample: duty=%.2f%% speed=%.3fmm/s" ++ % (self.lane.name, duty_cycle, avg_speed) ++ ) ++ return avg_speed ++ ++ def _find_baseline_duty(self): ++ max_speed = self._measure_speed(100.0) ++ if max_speed < self.target_speed: ++ raise self.gcmd.error( ++ "Target speed %.3fmm/s is not reachable; max observed %.3fmm/s" ++ % (self.target_speed, max_speed) ++ ) ++ low = 0.0 ++ high = 100.0 ++ baseline = self.controller.default_duty ++ for _ in range(AUTOTUNE_BASELINE_STEPS): ++ baseline = 0.5 * (low + high) ++ speed = self._measure_speed(baseline) ++ if speed < self.target_speed: ++ low = baseline ++ else: ++ high = baseline ++ return 0.5 * (low + high) ++ ++ def _check_peak(self, sample_time, speed, target): ++ peak_time, peak_speed, peak_kind = self.peaks[-1] if self.peaks else (0.0, target, None) ++ if not self.peaks: ++ peak_speed = target ++ peak_time = sample_time ++ peak_kind = None ++ if speed > target: ++ if peak_kind != "high" or speed > peak_speed: ++ peak_time, peak_speed, peak_kind = sample_time, speed, "high" ++ elif speed < target: ++ if peak_kind != "low" or speed < peak_speed: ++ peak_time, peak_speed, peak_kind = sample_time, speed, "low" ++ if not self.peaks or (peak_time, peak_speed, peak_kind) != self.peaks[-1]: ++ if self.peaks and self.peaks[-1][2] == peak_kind: ++ self.peaks[-1] = (peak_time, peak_speed, peak_kind) ++ elif peak_kind is not None: ++ self.peaks.append((peak_time, peak_speed, peak_kind)) ++ ++ def _collect_relay_response(self, low_duty, high_duty): ++ target_low = self.target_speed - self.band ++ target_high = self.target_speed + self.band ++ driving_high = True ++ self.lane._start_manual(self.eventtime, self.direction, high_duty) ++ self.switch_times = [(self.eventtime, high_duty)] ++ deadline = self.eventtime + self.timeout ++ while self.eventtime < deadline: ++ sample_time, speed, _ = self._wait_for_status() ++ self._check_peak(sample_time, speed, self.target_speed) ++ if driving_high and speed >= target_high: ++ driving_high = False ++ self.controller.manual_start(self.direction, low_duty) ++ self.switch_times.append((sample_time, low_duty)) ++ elif not driving_high and speed <= target_low: ++ driving_high = True ++ self.controller.manual_start(self.direction, high_duty) ++ self.switch_times.append((sample_time, high_duty)) ++ pairs = self._get_peak_pairs() ++ if len(pairs) >= AUTOTUNE_REQUIRED_PAIRS: ++ return pairs ++ raise self.gcmd.error("drv8833 PID tune timed out before converging") ++ ++ def _get_peak_pairs(self): ++ pairs = [] ++ last_low = None ++ for sample_time, speed, kind in self.peaks: ++ if kind == "low": ++ last_low = (sample_time, speed) ++ elif kind == "high" and last_low is not None: ++ pairs.append((last_low, (sample_time, speed))) ++ last_low = None ++ return pairs ++ ++ def _calc_pid(self, relay_duty, pairs): ++ pairs = pairs[-AUTOTUNE_REQUIRED_PAIRS:] ++ high_times = [high[0] for _, high in pairs] ++ if len(high_times) < 2: ++ raise self.gcmd.error("Not enough high peaks collected for PID tuning") ++ amplitudes = [0.5 * (high[1] - low[1]) for low, high in pairs] ++ amplitude = sum(amplitudes) / float(len(amplitudes)) ++ if amplitude <= 0.0: ++ raise self.gcmd.error("Measured relay oscillation amplitude is invalid") ++ periods = [ ++ high_times[index] - high_times[index - 1] ++ for index in range(1, len(high_times)) ++ ] ++ Tu = sum(periods) / float(len(periods)) ++ Ku = 4.0 * relay_duty / (math.pi * amplitude) ++ Kp = 0.6 * Ku ++ Ki = Kp / (0.5 * Tu) ++ Kd = Kp * (0.125 * Tu) ++ return Kp, Ki, Kd, Ku, Tu, amplitude ++ ++ def write_file_data(self, filename): ++ with open(filename, "w") as out: ++ out.write("time,speed,duty,target\n") ++ out.write( ++ "\n".join( ++ "%.5f,%.5f,%.5f,%.5f" % sample for sample in self.data ++ ) ++ ) ++ ++ def run(self): ++ baseline = self._find_baseline_duty() ++ relay_duty = self.gcmd.get_float( ++ "RELAY_DUTY", ++ _clamp( ++ baseline * AUTOTUNE_RELAY_RATIO, ++ AUTOTUNE_MIN_RELAY_DUTY, ++ AUTOTUNE_MAX_RELAY_DUTY, ++ ), ++ above=0.0, ++ maxval=50.0, ++ ) ++ low_duty = _clamp(baseline - relay_duty, 0.0, 100.0) ++ high_duty = _clamp(baseline + relay_duty, 0.0, 100.0) ++ relay_duty = 0.5 * (high_duty - low_duty) ++ if high_duty - low_duty < 1.0: ++ raise self.gcmd.error("Relay duty window is too small to tune") ++ self.gcmd.respond_info( ++ "drv8833 %s tune baseline=%.2f%% relay_low=%.2f%% relay_high=%.2f%% band=%.3fmm/s" ++ % (self.lane.name, baseline, low_duty, high_duty, self.band) ++ ) ++ pairs = self._collect_relay_response(low_duty, high_duty) ++ Kp, Ki, Kd, Ku, Tu, amplitude = self._calc_pid(relay_duty, pairs) ++ if self.write_file: ++ filename = os.path.join( ++ "/tmp", "drv8833_%s_pid_tune.csv" % (self.lane.name,) ++ ) ++ self.write_file_data(filename) ++ self.gcmd.respond_info("drv8833 tune data written to %s" % (filename,)) ++ return { ++ "pid_kp": Kp, ++ "pid_ki": Ki, ++ "pid_kd": Kd, ++ "ultimate_gain": Ku, ++ "ultimate_period": Tu, ++ "amplitude": amplitude, ++ "baseline_duty": baseline, ++ "relay_duty": relay_duty, ++ } ++ ++ ++class Drv8833CommandHelper: ++ cmd_MOVE_DEBUG_help = "Run a timed DRV8833 debug move" ++ cmd_DRV_SET_SPEED_help = "Set DRV8833 speed in mm/s" ++ cmd_DRV_MOVE_help = "Run a DRV8833 move for a target distance in mm" ++ cmd_DRV8833_PID_TUNE_help = "Run relay autotune for DRV8833 PID gains" ++ cmd_SET_DRV8833_PID_help = "Update DRV8833 PID gains" ++ ++ def __init__(self, printer): ++ self.printer = printer ++ self.gcode = printer.lookup_object("gcode") ++ self.gcode.register_command( ++ "MOVE_DEBUG", self.cmd_MOVE_DEBUG, desc=self.cmd_MOVE_DEBUG_help ++ ) ++ self.gcode.register_command( ++ "DRV_SET_SPEED", ++ self.cmd_DRV_SET_SPEED, ++ desc=self.cmd_DRV_SET_SPEED_help, ++ ) ++ self.gcode.register_command( ++ "DRV_MOVE", ++ self.cmd_DRV_MOVE, ++ desc=self.cmd_DRV_MOVE_help, ++ ) ++ self.gcode.register_command( ++ "DRV_PID_TUNE", ++ self.cmd_DRV8833_PID_TUNE, ++ desc=self.cmd_DRV8833_PID_TUNE_help, ++ ) ++ self.gcode.register_command( ++ "DRV8833_PID_TUNE", ++ self.cmd_DRV8833_PID_TUNE, ++ desc=self.cmd_DRV8833_PID_TUNE_help, ++ ) ++ self.gcode.register_command( ++ "DRV_SET_PID", ++ self.cmd_SET_DRV8833_PID, ++ desc=self.cmd_SET_DRV8833_PID_help, ++ ) ++ self.gcode.register_command( ++ "SET_DRV8833_PID", ++ self.cmd_SET_DRV8833_PID, ++ desc=self.cmd_SET_DRV8833_PID_help, ++ ) ++ ++ def _lookup_lane(self, gcmd): ++ lanes = dict(self.printer.lookup_objects("drv8833")) ++ if not lanes: ++ raise gcmd.error("No [drv8833] sections configured") ++ name = gcmd.get("NAME", None) ++ if name is not None: ++ lane = lanes.get("drv8833 %s" % (name,)) ++ if lane is None: ++ lane = lanes.get(name) ++ if lane is None: ++ raise gcmd.error("Unknown drv8833 NAME '%s'" % (name,)) ++ return lane ++ if len(lanes) == 1: ++ return list(lanes.values())[0] ++ names = [section.split(" ", 1)[-1] for section in sorted(lanes)] ++ raise gcmd.error( ++ "MOVE_DEBUG requires NAME=; available: %s" ++ % (", ".join(names),) ++ ) ++ ++ def cmd_MOVE_DEBUG(self, gcmd): ++ self._lookup_lane(gcmd).cmd_MOVE_DEBUG(gcmd) ++ ++ def cmd_DRV_SET_SPEED(self, gcmd): ++ self._lookup_lane(gcmd).cmd_DRV_SET_SPEED(gcmd) ++ ++ def cmd_DRV_MOVE(self, gcmd): ++ self._lookup_lane(gcmd).cmd_DRV_MOVE(gcmd) ++ ++ def cmd_DRV8833_PID_TUNE(self, gcmd): ++ self._lookup_lane(gcmd).cmd_DRV8833_PID_TUNE(gcmd) ++ ++ def cmd_SET_DRV8833_PID(self, gcmd): ++ self._lookup_lane(gcmd).cmd_SET_DRV8833_PID(gcmd) ++ ++ ++class PrinterDrv8833(Drv8833Interface): ++ def __init__(self, config): ++ self.printer = config.get_printer() ++ self.reactor = self.printer.get_reactor() ++ self.gcode = self.printer.lookup_object("gcode") ++ self.section_name = config.get_name() ++ section_parts = self.section_name.split(None, 1) ++ self.name = ( ++ section_parts[1] if len(section_parts) > 1 else self.section_name ++ ) ++ ppins = self.printer.lookup_object("pins") ++ motor_fwd_params = ppins.lookup_pin(config.get("motor_fwd")) ++ motor_rwd_params = ppins.lookup_pin(config.get("motor_rwd")) ++ hall_params = ppins.lookup_pin(config.get("motor_hall"), can_pullup=True) ++ self.mcu = motor_fwd_params["chip"] ++ if motor_rwd_params["chip"] is not self.mcu: ++ raise config.error( ++ "drv8833 motor_fwd and motor_rwd must be on the same mcu" ++ ) ++ if hall_params["chip"] is not self.mcu: ++ raise config.error( ++ "drv8833 motor_hall, motor_fwd, and motor_rwd must be on the same mcu" ++ ) ++ self.hall_controller = MCUDrv8833Controller( ++ config, motor_fwd_params, motor_rwd_params, hall_params ++ ) ++ self.hall_controller.status_callback = self._handle_hall_status ++ self.active = False ++ self.manual_mode = False ++ self.direction = 1 ++ self.direction_name = "forwards" ++ self.target_speed = 0.0 ++ self.debug_gcmd = None ++ self.debug_next_log_time = 0.0 ++ self.debug_hall_start = 0 ++ cmd_helper = self.printer.lookup_object("drv8833_command_helper", None) ++ if cmd_helper is None: ++ cmd_helper = Drv8833CommandHelper(self.printer) ++ self.printer.add_object("drv8833_command_helper", cmd_helper) ++ self.printer.register_event_handler( ++ "gcode:request_restart", self._handle_request_restart ++ ) ++ self.printer.register_event_handler("klippy:shutdown", self._handle_stop) ++ ++ def get_status(self, eventtime): ++ return { ++ "active": self.active, ++ "manual": self.manual_mode, ++ "direction": self.direction_name, ++ "target_speed": self.target_speed, ++ "duty_cycle": self.hall_controller.last_duty_cycle, ++ "hall_speed": self.hall_controller.last_speed_mm_s, ++ "pid_kp": self.hall_controller.pid_kp, ++ "pid_ki": self.hall_controller.pid_ki, ++ "pid_kd": self.hall_controller.pid_kd, ++ } ++ ++ def _handle_request_restart(self, print_time): ++ self._stop(self.reactor.monotonic()) ++ ++ def _handle_stop(self): ++ self._stop(self.reactor.monotonic()) ++ ++ def _handle_hall_status(self): ++ self.active = self.hall_controller.last_active ++ self.manual_mode = self.hall_controller.last_manual ++ if not self.active: ++ self.target_speed = 0.0 ++ if self.debug_gcmd is None: ++ return ++ eventtime = self.reactor.monotonic() ++ if eventtime < self.debug_next_log_time: ++ return ++ self.debug_next_log_time = eventtime + DEBUG_LOG_INTERVAL ++ msg = self._format_debug_message(eventtime) ++ gcmd = self.debug_gcmd ++ self.reactor.register_async_callback( ++ lambda eventtime, m=msg, g=gcmd: g.respond_info(m) ++ ) ++ ++ def _start(self, eventtime, direction, target_speed): ++ self._start_move(eventtime, direction, target_speed, 0) ++ ++ def _start_move(self, eventtime, direction, target_speed, stop_ticks): ++ if self.active: ++ raise self.printer.command_error( ++ "drv8833 %s is already running" % (self.name,) ++ ) ++ self.active = True ++ self.manual_mode = False ++ self.direction = direction ++ self.direction_name = "forwards" if direction > 0 else "backwards" ++ self.target_speed = target_speed ++ self.hall_controller.start_move(direction, target_speed, stop_ticks) ++ ++ def _start_manual(self, eventtime, direction, duty_cycle): ++ if not self.active: ++ self.active = True ++ self.manual_mode = True ++ self.direction = direction ++ self.direction_name = "forwards" if direction > 0 else "backwards" ++ self.target_speed = 0.0 ++ self.hall_controller.manual_start(direction, duty_cycle) ++ ++ def _stop(self, eventtime): ++ if not self.active: ++ return ++ self.active = False ++ self.manual_mode = False ++ self.target_speed = 0.0 ++ self.hall_controller.stop() ++ ++ def _save_pid_gains(self, kp, ki, kd): ++ configfile = self.printer.lookup_object("configfile") ++ configfile.set(self.section_name, "pid_kp", "%.6f" % (kp,)) ++ configfile.set(self.section_name, "pid_ki", "%.6f" % (ki,)) ++ configfile.set(self.section_name, "pid_kd", "%.6f" % (kd,)) ++ ++ def set_pid_gains(self, kp, ki, kd, save_to_config=False): ++ self.hall_controller.set_pid(kp, ki, kd) ++ if save_to_config: ++ self._save_pid_gains(kp, ki, kd) ++ ++ def _get_debug_totals(self): ++ hall_clicks = self.hall_controller.last_count - self.debug_hall_start ++ return { ++ "hall_clicks": hall_clicks, ++ "hall_mm": hall_clicks * self.hall_controller.hall_resolution, ++ } ++ ++ def _wait_for_move_complete(self, eventtime): ++ while self.active: ++ eventtime = self.reactor.pause(eventtime + MCU_REPORT_INTERVAL) ++ return eventtime ++ ++ def _format_debug_message(self, eventtime): ++ totals = self._get_debug_totals() ++ return ( ++ "drv8833 %s: direction=%s target=%.3fmm/s hall_clicks=%d " ++ "hall_speed=%.3fmm/s duty=%.1f%%" ++ % ( ++ self.name, ++ self.direction_name, ++ self.target_speed, ++ totals["hall_clicks"], ++ self.hall_controller.last_speed_mm_s, ++ self.hall_controller.last_duty_cycle, ++ ) ++ ) ++ ++ def drv8833_set_speed(self, speed: float): ++ eventtime = self.reactor.monotonic() ++ if speed == 0.0: ++ self._stop(eventtime) ++ return ++ direction = 1 if speed > 0.0 else -1 ++ target_speed = abs(speed) ++ if self.active: ++ self._stop(eventtime) ++ self._start(eventtime, direction, target_speed) ++ ++ def drv8833_move(self, speed: float, distance: float): ++ eventtime = self.reactor.monotonic() ++ if distance < 0.0: ++ speed *= -1.0 ++ distance = abs(distance) ++ if speed == 0.0 or distance == 0.0: ++ self._stop(eventtime) ++ return ++ direction = 1 if speed > 0.0 else -1 ++ target_speed = abs(speed) ++ stop_ticks = max( ++ 1, ++ int(round(distance / self.hall_controller.hall_resolution)), ++ ) ++ if self.active: ++ self._stop(eventtime) ++ self._start_move(eventtime, direction, target_speed, stop_ticks) ++ self._wait_for_move_complete(eventtime) ++ ++ cmd_MOVE_DEBUG_help = ( ++ "Run the DRV8833 lane for a fixed time while logging hall counts, " ++ "hall speed, and duty cycle" ++ ) ++ cmd_DRV_SET_SPEED_help = "Set the DRV8833 lane speed in mm/s" ++ cmd_DRV_MOVE_help = "Run the DRV8833 lane for a target distance in mm" ++ ++ def cmd_MOVE_DEBUG(self, gcmd): ++ duration = gcmd.get_float("TIME", 5.0, above=0.0) ++ direction = gcmd.get("DIRECTION", "forwards").strip().lower() ++ speed = gcmd.get_float("SPEED", 40.0, minval=0.0) ++ if direction not in ("forwards", "forward", "backwards", "backward"): ++ raise gcmd.error("DIRECTION must be forwards or backwards") ++ direction_value = 1 if direction.startswith("for") else -1 ++ eventtime = self.reactor.monotonic() ++ self.debug_gcmd = gcmd ++ self.debug_next_log_time = eventtime ++ # The MCU resets hall count to zero at the start of each move. ++ self.debug_hall_start = 0 ++ self._start(eventtime, direction_value, speed) ++ endtime = eventtime + duration ++ try: ++ while eventtime < endtime: ++ eventtime = self.reactor.pause(endtime) ++ finally: ++ self.debug_gcmd = None ++ self._stop(self.reactor.monotonic()) ++ totals = self._get_debug_totals() ++ gcmd.respond_info( ++ "drv8833 %s move complete: hall_clicks=%d hall_distance=%.3fmm" ++ % ( ++ self.name, ++ totals["hall_clicks"], ++ totals["hall_mm"], ++ ) ++ ) ++ ++ def cmd_DRV_SET_SPEED(self, gcmd): ++ speed = gcmd.get_float("SPEED") ++ self.drv8833_set_speed(speed) ++ gcmd.respond_info( ++ "drv8833 %s speed set to %.3fmm/s" % (self.name, speed) ++ ) ++ ++ def cmd_DRV_MOVE(self, gcmd): ++ speed = gcmd.get_float("SPEED") ++ distance = gcmd.get_float("DISTANCE") ++ self.drv8833_move(speed, distance) ++ totals = { ++ "hall_clicks": self.hall_controller.last_count, ++ "hall_mm": self.hall_controller.last_count ++ * self.hall_controller.hall_resolution, ++ } ++ gcmd.respond_info( ++ "drv8833 %s move complete: speed=%.3fmm/s distance=%.3fmm " ++ "hall_clicks=%d hall_distance=%.3fmm" ++ % ( ++ self.name, ++ speed, ++ distance, ++ totals["hall_clicks"], ++ totals["hall_mm"], ++ ) ++ ) ++ ++ def cmd_SET_DRV8833_PID(self, gcmd): ++ kp = gcmd.get_float("KP", self.hall_controller.pid_kp, minval=0.0) ++ ki = gcmd.get_float("KI", self.hall_controller.pid_ki, minval=0.0) ++ kd = gcmd.get_float("KD", self.hall_controller.pid_kd, minval=0.0) ++ save_to_config = bool(gcmd.get_int("SAVE", 0)) ++ self.set_pid_gains(kp, ki, kd, save_to_config=save_to_config) ++ msg = ( ++ "drv8833 %s PID updated: pid_kp=%.6f pid_ki=%.6f pid_kd=%.6f" ++ % (self.name, kp, ki, kd) ++ ) ++ if save_to_config: ++ msg += ( ++ "\nThe SAVE_CONFIG command will update the printer config file " ++ "and restart the printer." ++ ) ++ gcmd.respond_info(msg) ++ ++ def cmd_DRV8833_PID_TUNE(self, gcmd): ++ if self.active: ++ raise gcmd.error("drv8833 %s is already running" % (self.name,)) ++ tuner = Drv8833PIDAutoTune(self, gcmd) ++ try: ++ result = tuner.run() ++ finally: ++ self._stop(self.reactor.monotonic()) ++ self.set_pid_gains( ++ result["pid_kp"], ++ result["pid_ki"], ++ result["pid_kd"], ++ save_to_config=True, ++ ) ++ gcmd.respond_info( ++ "drv8833 %s PID tune at %.3fmm/s: pid_kp=%.6f pid_ki=%.6f pid_kd=%.6f\n" ++ "Ku=%.6f Tu=%.6fs amplitude=%.6fmm/s baseline_duty=%.2f%% relay_duty=%.2f%%\n" ++ "The SAVE_CONFIG command will update the printer config file and restart the printer." ++ % ( ++ self.name, ++ tuner.target_speed, ++ result["pid_kp"], ++ result["pid_ki"], ++ result["pid_kd"], ++ result["ultimate_gain"], ++ result["ultimate_period"], ++ result["amplitude"], ++ result["baseline_duty"], ++ result["relay_duty"], ++ ) ++ ) ++ ++ ++def load_config_prefix(config): ++ return PrinterDrv8833(config) +\ No newline at end of file +diff --git a/src/Kconfig b/src/Kconfig +index d679cef9..00da2118 100644 +--- a/src/Kconfig ++++ b/src/Kconfig +@@ -145,6 +145,10 @@ config WANT_PULSE_COUNTER + bool + depends on HAVE_GPIO + default y ++config WANT_DRV8833 ++ bool ++ depends on HAVE_GPIO && WANT_HARD_PWM ++ default y + config WANT_ST7920 + bool + depends on HAVE_GPIO +diff --git a/src/Makefile b/src/Makefile +index 3e7754af..19421fed 100644 +--- a/src/Makefile ++++ b/src/Makefile +@@ -13,6 +13,7 @@ src-$(CONFIG_WANT_BUTTONS) += buttons.c + src-$(CONFIG_WANT_TMCUART) += tmcuart.c + src-$(CONFIG_WANT_NEOPIXEL) += neopixel.c + src-$(CONFIG_WANT_PULSE_COUNTER) += pulse_counter.c ++src-$(CONFIG_WANT_DRV8833) += drv8833.c + src-$(CONFIG_WANT_ST7920) += lcd_st7920.c + src-$(CONFIG_WANT_HD44780) += lcd_hd44780.c + src-$(CONFIG_WANT_SOFTWARE_SPI) += spi_software.c +diff --git a/src/drv8833.c b/src/drv8833.c +new file mode 100644 +index 00000000..1101992c +--- /dev/null ++++ b/src/drv8833.c +@@ -0,0 +1,371 @@ ++// MCU-side DRV8833 hall-based speed controller ++// ++// Copyright (C) 2026 ++// ++// This file may be distributed under the terms of the GNU GPLv3 license. ++ ++#include "autoconf.h" // CONFIG_CLOCK_FREQ ++#include "basecmd.h" // oid_alloc ++#include "board/gpio.h" // struct gpio_in / struct gpio_pwm ++#include "board/irq.h" // irq_disable ++#include "board/misc.h" // timer_read_time / timer_is_before ++#include "command.h" // DECL_COMMAND / sendf ++#include "sched.h" // DECL_TASK / sched_add_timer ++ ++#define DUTY_SCALE 10 ++#define DUTY_MAX (100 * DUTY_SCALE) ++#define SPEED_SMOOTH_DIV 8 ++#define SPEED_SCALE 1000 ++#define PID_PARAM_SCALE 1000 ++ ++struct drv8833 { ++ struct timer timer; ++ struct gpio_in hall_pin; ++ struct gpio_pwm motor_fwd; ++ struct gpio_pwm motor_rwd; ++ uint32_t poll_ticks; ++ uint32_t control_ticks; ++ uint32_t report_ticks; ++ uint32_t next_control_time; ++ uint32_t next_report_time; ++ uint32_t count; ++ uint32_t stop_count; ++ uint32_t last_edge_time; ++ uint32_t last_interval_ticks; ++ uint32_t hall_resolution; ++ uint32_t speed_mm_s_x1000; ++ uint32_t target_speed_mm_s_x1000; ++ int64_t integral; ++ int32_t previous_error; ++ uint32_t pid_kp; ++ uint32_t pid_ki; ++ uint32_t pid_kd; ++ uint16_t duty_x10; ++ uint16_t default_duty_x10; ++ uint16_t max_duty_step_x10; ++ uint16_t pwm_max; ++ uint8_t flags; ++ uint8_t direction; ++ uint8_t last_hall_value; ++}; ++ ++enum { ++ DF_ACTIVE = 1 << 0, ++ DF_PENDING = 1 << 1, ++ DF_MANUAL = 1 << 2, ++}; ++ ++static struct task_wake drv8833_wake; ++ ++static uint32_t ++drv8833_calc_speed(const struct drv8833 *d, uint32_t delta_count, ++ uint32_t delta_ticks) ++{ ++ if (!delta_count || !delta_ticks) ++ return 0; ++ uint64_t numer = ++ (uint64_t)delta_count * d->hall_resolution * CONFIG_CLOCK_FREQ; ++ return numer / ((uint64_t)delta_ticks * 1000); ++} ++ ++static uint32_t ++drv8833_calc_measured_speed(const struct drv8833 *d, uint32_t time) ++{ ++ if (!d->last_edge_time) ++ return 0; ++ uint32_t age_ticks = time - d->last_edge_time; ++ uint32_t measured_interval = d->last_interval_ticks ++ ? d->last_interval_ticks : age_ticks; ++ if (age_ticks > measured_interval) ++ measured_interval = age_ticks; ++ return drv8833_calc_speed(d, 1, measured_interval); ++} ++ ++static uint32_t ++drv8833_update_speed(struct drv8833 *d, uint32_t time) ++{ ++ uint32_t measured_speed = drv8833_calc_measured_speed(d, time); ++ if (!d->speed_mm_s_x1000) ++ d->speed_mm_s_x1000 = measured_speed; ++ else ++ d->speed_mm_s_x1000 += ++ ((int32_t)measured_speed - (int32_t)d->speed_mm_s_x1000) ++ / SPEED_SMOOTH_DIV; ++ return d->speed_mm_s_x1000; ++} ++ ++static int64_t ++drv8833_integral_limit(const struct drv8833 *d) ++{ ++ if (!d->pid_ki) ++ return 0x7fffffffffffffffLL; ++ return ((int64_t)DUTY_MAX * PID_PARAM_SCALE * SPEED_SCALE ++ * CONFIG_CLOCK_FREQ) / d->pid_ki; ++} ++ ++static void ++drv8833_apply_pwm(struct drv8833 *d) ++{ ++ uint32_t pwm_value = 0; ++ if ((d->flags & DF_ACTIVE) && d->duty_x10) { ++ pwm_value = ((uint32_t)d->duty_x10 * d->pwm_max + DUTY_MAX / 2) ++ / DUTY_MAX; ++ } ++ if (d->direction) { ++ gpio_pwm_write(d->motor_fwd, pwm_value); ++ gpio_pwm_write(d->motor_rwd, 0); ++ } else { ++ gpio_pwm_write(d->motor_fwd, 0); ++ gpio_pwm_write(d->motor_rwd, pwm_value); ++ } ++} ++ ++static void ++drv8833_control_update(struct drv8833 *d, uint32_t time) ++{ ++ if (!(d->flags & DF_ACTIVE) || (d->flags & DF_MANUAL)) ++ return; ++ ++ if (!d->target_speed_mm_s_x1000) ++ return; ++ ++ int32_t error = (int32_t)d->target_speed_mm_s_x1000 ++ - (int32_t)d->speed_mm_s_x1000; ++ int32_t derivative = error - d->previous_error; ++ int64_t next_integral = d->integral + (int64_t)error * d->control_ticks; ++ int64_t integral_limit = drv8833_integral_limit(d); ++ if (next_integral < -integral_limit) ++ next_integral = -integral_limit; ++ else if (next_integral > integral_limit) ++ next_integral = integral_limit; ++ ++ int32_t p_term = (int64_t)d->pid_kp * error ++ / (PID_PARAM_SCALE * SPEED_SCALE); ++ int32_t i_term = (int64_t)d->pid_ki * next_integral ++ / ((int64_t)PID_PARAM_SCALE * SPEED_SCALE ++ * CONFIG_CLOCK_FREQ); ++ int32_t d_term = 0; ++ if (d->control_ticks) { ++ d_term = (int64_t)d->pid_kd * derivative * CONFIG_CLOCK_FREQ ++ / ((int64_t)PID_PARAM_SCALE * SPEED_SCALE ++ * d->control_ticks); ++ } ++ ++ int32_t target_duty = d->default_duty_x10 + p_term + i_term + d_term; ++ if (target_duty < 0) ++ target_duty = 0; ++ else if (target_duty > DUTY_MAX) ++ target_duty = DUTY_MAX; ++ ++ if (!((target_duty == 0 && error < 0) ++ || (target_duty == DUTY_MAX && error > 0))) ++ d->integral = next_integral; ++ ++ int32_t delta_duty = target_duty - (int32_t)d->duty_x10; ++ if (delta_duty < -(int32_t)d->max_duty_step_x10) ++ delta_duty = -(int32_t)d->max_duty_step_x10; ++ else if (delta_duty > (int32_t)d->max_duty_step_x10) ++ delta_duty = d->max_duty_step_x10; ++ ++ d->duty_x10 += delta_duty; ++ d->previous_error = error; ++ drv8833_apply_pwm(d); ++} ++ ++static void ++drv8833_reset_state(struct drv8833 *d) ++{ ++ d->count = 0; ++ d->stop_count = 0; ++ d->last_edge_time = 0; ++ d->last_interval_ticks = 0; ++ d->speed_mm_s_x1000 = 0; ++ d->integral = 0; ++ d->previous_error = 0; ++} ++ ++static uint_fast8_t ++drv8833_event(struct timer *timer) ++{ ++ struct drv8833 *d = container_of(timer, struct drv8833, timer); ++ uint32_t time = d->timer.waketime; ++ ++ uint8_t hall_value = gpio_in_read(d->hall_pin); ++ if (hall_value != d->last_hall_value) { ++ d->count++; ++ if (d->last_edge_time) ++ d->last_interval_ticks = time - d->last_edge_time; ++ d->last_edge_time = time; ++ d->last_hall_value = hall_value; ++ if ((d->flags & DF_ACTIVE) && d->stop_count && d->count >= d->stop_count) { ++ d->flags &= ~(DF_ACTIVE | DF_MANUAL); ++ d->target_speed_mm_s_x1000 = 0; ++ d->stop_count = 0; ++ d->integral = 0; ++ d->previous_error = 0; ++ d->duty_x10 = 0; ++ drv8833_apply_pwm(d); ++ d->flags |= DF_PENDING; ++ sched_wake_task(&drv8833_wake); ++ } ++ } ++ ++ if (!timer_is_before(time, d->next_control_time)) { ++ d->next_control_time = time + d->control_ticks; ++ drv8833_update_speed(d, time); ++ drv8833_control_update(d, time); ++ } ++ if ((d->flags & DF_ACTIVE) && !timer_is_before(time, d->next_report_time)) { ++ d->next_report_time = time + d->report_ticks; ++ d->flags |= DF_PENDING; ++ sched_wake_task(&drv8833_wake); ++ } ++ ++ d->timer.waketime += d->poll_ticks; ++ return SF_RESCHEDULE; ++} ++ ++void ++command_config_drv8833(uint32_t *args) ++{ ++ struct drv8833 *d = oid_alloc(args[0], command_config_drv8833, ++ sizeof(*d)); ++ d->motor_fwd = gpio_pwm_setup(args[1], args[3], 0); ++ d->motor_rwd = gpio_pwm_setup(args[2], args[3], 0); ++ d->hall_pin = gpio_in_setup(args[4], args[5]); ++ d->poll_ticks = args[6]; ++ d->control_ticks = args[7]; ++ d->report_ticks = args[8]; ++ d->hall_resolution = args[9]; ++ d->pwm_max = args[10]; ++ d->default_duty_x10 = args[11]; ++ d->max_duty_step_x10 = args[12]; ++ d->pid_kp = 0; ++ d->pid_ki = 0; ++ d->pid_kd = 0; ++ d->last_hall_value = gpio_in_read(d->hall_pin); ++ d->direction = 1; ++ d->timer.func = drv8833_event; ++} ++DECL_COMMAND(command_config_drv8833, ++ "config_drv8833 oid=%c motor_fwd_pin=%u motor_rwd_pin=%u" ++ " cycle_ticks=%u hall_pin=%u hall_pullup=%c poll_ticks=%u" ++ " control_ticks=%u report_ticks=%u hall_resolution=%u" ++ " pwm_max=%hu default_duty=%hu max_duty_step=%hu"); ++ ++void ++command_drv8833_set(uint32_t *args) ++{ ++ struct drv8833 *d = oid_lookup(args[0], command_config_drv8833); ++ sched_del_timer(&d->timer); ++ d->flags &= ~DF_PENDING; ++ ++ if (!args[1]) { ++ d->flags &= ~(DF_ACTIVE | DF_MANUAL); ++ d->target_speed_mm_s_x1000 = 0; ++ drv8833_reset_state(d); ++ d->duty_x10 = 0; ++ drv8833_apply_pwm(d); ++ return; ++ } ++ ++ uint32_t now = timer_read_time(); ++ d->flags = (d->flags & ~DF_MANUAL) | DF_ACTIVE | DF_PENDING; ++ d->direction = !!args[2]; ++ d->target_speed_mm_s_x1000 = args[3]; ++ drv8833_reset_state(d); ++ d->stop_count = args[4]; ++ d->duty_x10 = d->default_duty_x10; ++ d->last_hall_value = gpio_in_read(d->hall_pin); ++ d->next_control_time = now; ++ d->next_report_time = now; ++ drv8833_apply_pwm(d); ++ d->timer.waketime = now + d->poll_ticks; ++ sched_add_timer(&d->timer); ++ sched_wake_task(&drv8833_wake); ++} ++DECL_COMMAND(command_drv8833_set, ++ "drv8833_set oid=%c enable=%c direction=%c target_speed=%u" ++ " stop_ticks=%u"); ++ ++void ++command_drv8833_manual(uint32_t *args) ++{ ++ struct drv8833 *d = oid_lookup(args[0], command_config_drv8833); ++ ++ if (args[1] && (d->flags & DF_ACTIVE) && (d->flags & DF_MANUAL)) { ++ d->direction = !!args[2]; ++ d->duty_x10 = args[3] > DUTY_MAX ? DUTY_MAX : args[3]; ++ drv8833_apply_pwm(d); ++ return; ++ } ++ ++ sched_del_timer(&d->timer); ++ d->flags &= ~DF_PENDING; ++ ++ if (!args[1]) { ++ d->flags &= ~(DF_ACTIVE | DF_MANUAL); ++ d->target_speed_mm_s_x1000 = 0; ++ drv8833_reset_state(d); ++ d->duty_x10 = 0; ++ drv8833_apply_pwm(d); ++ return; ++ } ++ ++ uint32_t now = timer_read_time(); ++ d->flags |= DF_ACTIVE | DF_PENDING | DF_MANUAL; ++ d->direction = !!args[2]; ++ d->target_speed_mm_s_x1000 = 0; ++ drv8833_reset_state(d); ++ d->duty_x10 = args[3] > DUTY_MAX ? DUTY_MAX : args[3]; ++ d->last_hall_value = gpio_in_read(d->hall_pin); ++ d->next_control_time = now; ++ d->next_report_time = now; ++ drv8833_apply_pwm(d); ++ d->timer.waketime = now + d->poll_ticks; ++ sched_add_timer(&d->timer); ++ sched_wake_task(&drv8833_wake); ++} ++DECL_COMMAND(command_drv8833_manual, ++ "drv8833_manual oid=%c enable=%c direction=%c duty=%hu"); ++ ++void ++command_drv8833_set_pid(uint32_t *args) ++{ ++ struct drv8833 *d = oid_lookup(args[0], command_config_drv8833); ++ irq_disable(); ++ d->pid_kp = args[1]; ++ d->pid_ki = args[2]; ++ d->pid_kd = args[3]; ++ d->integral = 0; ++ d->previous_error = 0; ++ irq_enable(); ++} ++DECL_COMMAND(command_drv8833_set_pid, ++ "drv8833_set_pid oid=%c kp=%u ki=%u kd=%u"); ++ ++void ++drv8833_task(void) ++{ ++ if (!sched_check_wake(&drv8833_wake)) ++ return; ++ ++ uint8_t oid; ++ struct drv8833 *d; ++ foreach_oid(oid, d, command_config_drv8833) { ++ irq_disable(); ++ uint8_t flags = d->flags; ++ uint32_t count = d->count; ++ uint32_t speed = d->speed_mm_s_x1000; ++ uint16_t duty = d->duty_x10; ++ d->flags = flags & ~DF_PENDING; ++ irq_enable(); ++ if (!(flags & DF_PENDING)) ++ continue; ++ sendf("drv8833_status oid=%c active=%c manual=%c count=%u speed=%u" ++ " duty=%hu", ++ oid, !!(flags & DF_ACTIVE), !!(flags & DF_MANUAL), ++ count, speed, duty); ++ } ++} ++DECL_TASK(drv8833_task); +\ No newline at end of file diff --git a/meta-opencentauri/recipes-apps/klipper/kalico_2026.02.00.inc b/meta-opencentauri/recipes-apps/klipper/kalico_2026.02.00.inc index eb5965a6..e0ecff03 100644 --- a/meta-opencentauri/recipes-apps/klipper/kalico_2026.02.00.inc +++ b/meta-opencentauri/recipes-apps/klipper/kalico_2026.02.00.inc @@ -12,6 +12,7 @@ SRC_URI = "git://github.com/OpenCentauri/kalico.git;protocol=https;branch=hx711s file://0001-implement-pulldown-resistor-in-thermistor-calc.patch \ file://0001-probe-home-z-includes-z-offset.patch \ file://0001-force-specific-input-shaper.patch \ + file://0003-drv8833-motor-control.patch \ " SRCREV = "3f166182f82f95584112571b4f7b2258922be60d" diff --git a/meta-opencentauri/recipes-apps/mcu-flasher/mcu-flasher_0.1.0.bb b/meta-opencentauri/recipes-apps/mcu-flasher/mcu-flasher_0.1.0.bb index 8c0d46ce..64fb7437 100644 --- a/meta-opencentauri/recipes-apps/mcu-flasher/mcu-flasher_0.1.0.bb +++ b/meta-opencentauri/recipes-apps/mcu-flasher/mcu-flasher_0.1.0.bb @@ -8,7 +8,7 @@ LIC_FILES_CHKSUM = "file://../LICENSE;md5=0a18a528575a965515cdd877f88b3c4c" SRC_URI += " \ git://github.com/OpenCentauri/OpenCentauri.git;protocol=https;nobranch=1;branch=main \ " -SRCREV = "fb829bd0a469e1c54cb9d2aa1bd98e14afd9f351" +SRCREV = "4b179ce5fa70309f24ac047b6d9e2c5c2a0e1c7c" PR = "r1" S = "${WORKDIR}/git/mcu-flasher" diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb new file mode 100644 index 00000000..a16db6da --- /dev/null +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb @@ -0,0 +1,44 @@ +HOMEPAGE = "https://github.com/suchmememanyskill/AFC-Klipper-Add-On" +LICENSE = "GPL-3.0-only" +LIC_FILES_CHKSUM = "file://LICENSE;md5=db95b6e40dc7d26d8308b6b7375637b6" +SUMMARY = "AFC-Klipper-Add-On" +DESCRIPTION = "Automated Filament Changer Software." + +FILESEXTRAPATHS:prepend := "${THISDIR}/files:" + +SRC_URI = " \ + git://github.com/suchmememanyskill/AFC-Klipper-Add-On.git;protocol=https;branch=DEV \ + file://afc.cfg \ + file://canvas.cfg \ +" + +SRCREV = "9b5291954d27fdcfb5913a79c72e15884a9935b7" + +S = "${WORKDIR}/git" + +DEPENDS = " \ + python3-native \ +" + +RDEPENDS:${PN} = " \ + klipper \ +" + +do_configure[noexec] = "1" +do_compile[noexec] = "1" + +do_install() { + # Install klippy extras + install -d ${D}${datadir}/klipper/extras + cp -r ${S}/extras/* ${D}${datadir}/klipper/extras/ + + # Install config files + install -d ${D}${sysconfdir}/klipper/config/extras-readonly + install -m 0644 ${WORKDIR}/canvas.cfg ${WORKDIR}/afc.cfg ${D}${sysconfdir}/klipper/config/extras-readonly +} + +FILES:${PN} = " \ + ${datadir}/klipper/AFC* \ + ${sysconfdir}/klipper/config/extras-readonly/afc.cfg \ + ${sysconfdir}/klipper/config/extras-readonly/canvas.cfg \ +" \ No newline at end of file diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/afc.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/afc.cfg new file mode 100644 index 00000000..a5044a72 --- /dev/null +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/afc.cfg @@ -0,0 +1,110 @@ +# Eject Loaded Lane +[gcode_macro BT_TOOL_UNLOAD] +description: Unloads the currently loaded lane +gcode: + TOOL_UNLOAD + +[gcode_macro BT_CHANGE_TOOL] +description: Switch to a new lane by ejecting the previously loaded one and then load the lane specified by LANE parameter. Lane parameter is an integer and defaults to 1. ex. `BT_CHANGE_TOOL LANE=2` +gcode: + {% set lane_num = params.LANE|default(1)|int %} + {% set stepper = printer['gcode_macro _AFC_GLOBAL_VARS'].stepper_name|string %} + + CHANGE_TOOL LANE={stepper ~ lane_num} + +[gcode_macro BT_LANE_EJECT] +description: Fully eject the filament from the lane so spool can be removed. Lane parameter is an integer and defaults to 1. ex. `BT_LANE_EJECT LANE=2` +gcode: + {% set lane_num = params.LANE|default(1)|int %} + {% set stepper = printer['gcode_macro _AFC_GLOBAL_VARS'].stepper_name|string %} + + LANE_UNLOAD LANE={stepper ~ lane_num} + +[gcode_macro BT_LANE_MOVE] +description: Move the specified lane the specified amount. Lane parameter is an integer and defaults to 1. Distance parameter is and integer and defaults to 20. Distance over 200 uses long load speeds. ex `BT_LANE_MOVE LANE=2 DISTANCE=100` +gcode: + {% set lane_num = params.LANE|default(1)|int %} + {% set dist_str = params.DISTANCE|default('20')|string %} + {% set stepper = printer['gcode_macro _AFC_GLOBAL_VARS'].stepper_name|string %} + + {% set valid_chars = '0123456789' %} + {% set is_valid = true %} + + {% if dist_str|length > 0 %} + {% if dist_str[-1] not in valid_chars %} + {% set is_valid = false %} + {% endif %} + {% else %} + {% set is_valid = false %} + {% endif %} + + {% if is_valid %} + {% set dist = dist_str|float %} + LANE_MOVE LANE={stepper ~ lane_num} DISTANCE={dist} + {% else %} + { action_respond_info("Error: Invalid DISTANCE parameter: " ~ dist_str) } + {% endif %} + + +[gcode_macro BT_RESUME] +description: Resume the print after an error, using normal resume will also call AFC_RESUME +gcode: + {% if not printer.pause_resume.is_paused %} + RESPOND MSG="Print is not paused. Resume ignored" + {% else %} + AFC_RESUME + {% endif %} + +[gcode_macro BT_PREP] +description: Run the AFC PREP sequence +gcode: + PREP + +[gcode_macro AFC_DISABLE_SKEW] +description: Disable skew correction temporarily +gcode: + {% set gVars = printer['gcode_macro _AFC_GLOBAL_VARS'] %} + {% set verbose = gVars['verbose']|int %} + {% set disable_skew = gVars['disable_skew_correction']|default(false)|lower == 'true' %} + + # Disable skew correction if defined + {% if disable_skew %} + SET_GCODE_VARIABLE MACRO=AFC_ENABLE_SKEW VARIABLE=skew_profile VALUE='"{printer.skew_correction.current_profile_name}"' + {% if verbose > 1 %} + RESPOND TYPE=command MSG='AFC: Disabling skew correction' + {% endif %} + SET_SKEW CLEAR=1 + {% if verbose > 1 %} + GET_CURRENT_SKEW + {% endif %} + {% endif %} + +[gcode_macro AFC_ENABLE_SKEW] +description: Re-enable skew correction if it was previously disabled +variable_skew_profile: "my_skew_profile" # Widely used as documented in Klipper docs. +gcode: + {% set gVars = printer['gcode_macro _AFC_GLOBAL_VARS'] %} + {% set verbose = gVars['verbose']|int %} + {% set disable_skew = gVars['disable_skew_correction']|default(false)|lower == 'true' %} + + # Restore skew correction if defined + {% if disable_skew %} + {% if verbose > 1 %} + { action_respond_info("AFC: Restoring skew correction profile " ~ skew_profile) } + {% endif %} + SKEW_PROFILE LOAD={skew_profile} + {% if verbose > 1 %} + GET_CURRENT_SKEW + {% endif %} + {% endif %} + +[AFC_prep] +enable: True +delay_time: 0.5 +disable_unload_filament_remapping: False +capture_td1_data: False + +[delayed_gcode welcome_afc] +initial_duration: 1 +gcode: + PREP \ No newline at end of file diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg new file mode 100644 index 00000000..4e8a8bbf --- /dev/null +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg @@ -0,0 +1,231 @@ +[include afc.cfg] + +[mcu canvas] +serial: /dev/serial/by-path/platform-4200000.usb-usb-0:1.4:1.0 +restart_method: command + +[drv8833 canvas_lane0] +motor_fwd: canvas:PA6 +motor_rwd: canvas:PA5 +motor_cycle_time: 0.0001 +motor_hall: canvas:PB6 +motor_hall_resolution: 0.27335 + +[drv8833 canvas_lane1] +motor_fwd: canvas:PA7 +motor_rwd: canvas:PB0 +motor_cycle_time: 0.0001 +motor_hall: canvas:PB7 +motor_hall_resolution: 0.27335 + +[drv8833 canvas_lane2] +motor_fwd: canvas:PB8 +motor_rwd: canvas:PB9 +motor_cycle_time: 0.0001 +motor_hall: canvas:PB5 +motor_hall_resolution: 0.27335 + +[drv8833 canvas_lane3] +motor_fwd: canvas:PB1 +motor_rwd: canvas:PB10 +motor_cycle_time: 0.0001 +motor_hall: canvas:PB4 +motor_hall_resolution: 0.27335 + +[board_pins canvas] +mcu: canvas +aliases: + ENABLE_9V=PC2 , ENABLE_24V=PC3, + + MOTOR0_FWD=PA6 , MOTOR0_RWD=PA5, + MOTOR1_FWD=PB0 , MOTOR1_RWD=PA7, + MOTOR2_FWD=PB8 , MOTOR2_RWD=PB9, + MOTOR3_FWD=PB10 , MOTOR3_RWD=PB1, + + HALL0=PB6 , HALL1=PB7, + HALL2=PB5 , HALL3=PB4, + + LED0_WHITE=PC9 , LED0_RED=PC8, + LED1_WHITE=PC6 , LED1_RED=PB15, + LED2_WHITE=PC10 , LED2_RED=PA15, + LED3_WHITE=PC1 , LED3_RED=PC12, + + FILAMENT0=PA8 , FILAMENT1=PC7, + FILAMENT2=PC11 , FILAMENT3=PC0, + + ODOMETER0=PA2 , ODOMETER1=PA3, + ODOMETER2=PA1 , ODOMETER3=PA0 + +[AFC_hub toolhead_4way_hub] +switch_pin: !hotend:PB2 +enable_hub_runout: False + +[AFC_extruder extruder] +pin_tool_start: !hotend:PB2 +tool_stn: 94 +tool_stn_unload: 31 +tool_sensor_after_extruder: 0 +tool_unload_speed: 15 +tool_load_speed: 8 + +[AFC_canvas CANVAS_1] +extruder: extruder +enable_9v_pin: canvas:ENABLE_9V +enable_24v_pin: canvas:ENABLE_24V +tangle_pin: !hotend:PB0 +short_moves_speed: 30 +long_moves_speed: 70 +tool_unload_lane_extra_speed: 40 # Unused +prep_distance: 120 +prep_speed: 60 +tool_load_sync_speed_offset: 2 +tool_load_lane_extra_distance: 6.5 +tool_unload_lane_extra_distance: 40 # Unused + +[AFC_canvas_lane canvas_lane0] +unit: CANVAS_1:1 +hub: toolhead_4way_hub +map: T0 +dist_hub: 1200 +prep: !canvas:FILAMENT0 +drv8833: canvas_lane0 +odometer_pin: canvas:ODOMETER0 +odometer_resolution: 3.15786 +led_white_pin: canvas:LED0_WHITE +led_red_pin: canvas:LED0_RED + +[AFC_canvas_lane canvas_lane1] +unit: CANVAS_1:2 +hub: toolhead_4way_hub +map: T1 +dist_hub: 1200 +prep: !canvas:FILAMENT1 +drv8833: canvas_lane1 +odometer_pin: canvas:ODOMETER1 +odometer_resolution: 3.15786 +led_white_pin: canvas:LED1_WHITE +led_red_pin: canvas:LED1_RED + +[AFC_canvas_lane canvas_lane2] +unit: CANVAS_1:3 +hub: toolhead_4way_hub +map: T2 +dist_hub: 1200 +prep: !canvas:FILAMENT2 +drv8833: canvas_lane2 +odometer_pin: canvas:ODOMETER2 +odometer_resolution: 3.15786 +led_white_pin: canvas:LED2_WHITE +led_red_pin: canvas:LED2_RED + +[AFC_canvas_lane canvas_lane3] +unit: CANVAS_1:4 +hub: toolhead_4way_hub +map: T3 +dist_hub: 1200 +prep: !canvas:FILAMENT3 +drv8833: canvas_lane3 +odometer_pin: canvas:ODOMETER3 +odometer_resolution: 3.15786 +led_white_pin: canvas:LED3_WHITE +led_red_pin: canvas:LED3_RED + +[gcode_macro CUT] +gcode: + {% if "xyz" not in printer.toolhead.homed_axes %} + G28 ; Home if necessary + {% endif %} + + G90 ; Absolute positioning + G0 Z14.5 F600 ; Ensure we have some Z clearance + G0 Y30 F15000 ; Get some clearance for Y + G0 X255 F8000 ; Move to cutting position + G0 Y3 F1200 ; Cut + G0 Y30 F8000 ; Retract from cutting + M400 ; Wait for movements to complete + +[gcode_macro POOP] +gcode: + {% if params.PURGE_LENGTH %} + M83 + G1 E{params.PURGE_LENGTH} F600 + {% endif %} + +# Wipe Nozzle, from new firmware printer.cfg +[gcode_macro M729] +gcode: + SAVE_GCODE_STATE NAME=m729_saved_state + + G90 ; Absolute positioning + + ; === Approach === + G1 X202 F6000 ; Move to wiper X start position + G1 Y264.5 F6000 ; Move to wiper Y position (top of wiper) + + ; === Pass 1: X sweeps at top of wiper === + G1 X165 F8000 ; Wipe left (165 vs 170 in old — 5mm wider sweep) + G1 X190 ; Wipe right + G1 X165 ; Wipe left + + ; === Full depth plunge === + G1 Y245 F6000 ; Drop to full depth (19.5mm below top — 5mm deeper than old) + + ; === Full width sweep at depth === + G1 X202 F7200 ; Full width sweep right across entire wiper + + ; === Extract === + G1 Y264.5 F1200 ; Slow lift up through wiper (clean exit) + + M400 ; Wait for all moves to complete + + RESTORE_GCODE_STATE NAME=m729_saved_state + +[AFC] +VarFile: /etc/klipper/config/AFC.var +enable_sensors_in_gui: True +ignore_spoolman_material_temps: False +load_to_hub: False +moonraker_port: 80 +debug: True +z_hop: 5 +resume_speed: 120 +resume_z_speed: 25 +led_fault: 1,0,0,0 # Fault color +led_ready: 0,0,0,1 # Ready color +led_not_ready: 1,0,0,0 # Not ready color +led_loading: 1,0,0,1 # Loading color +led_tool_loaded: 1,0,0,1 # Loaded color +led_buffer_advancing: 0,0,1,0 # Buffer advancing color +led_buffer_trailing: 0,1,0,0 # Buffer trailing color +led_buffer_disable: 0,0,0,0.25 # Buffer disable color +led_spool_illuminate: 1,1,1,0 # Loading color to illuminate spool, currently only for QuattroBox units and can be overridden in AFC_QuattroBox section +unload_on_runout: False +auto_home: True +enable_assist: False +spool_ratio: 2 +enable_hub_runout: False +enable_tool_runout: True +homing_enabled: False + +# TOOL Cutting Settings +tool_cut: True +tool_cut_cmd: CUT +tool_cut_threshold: 10000 + +# Park Settings +park: True +park_cmd: MOVE_TO_TRAY + +# Poop Settings +poop: True +poop_cmd: POOP + +# Kick Settings +kick: False + +# Wipe Settings +wipe: True +wipe_cmd: M729 + +# Form Tip Settings +form_tip: False \ No newline at end of file From dbe187706ec784bb9762a7bd75e404a564b8b07b Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Tue, 5 May 2026 23:55:48 +0200 Subject: [PATCH 02/42] Forgor to actually include AFC in the build --- meta-opencentauri/images/opencentauri-image-base.bb | 1 + 1 file changed, 1 insertion(+) diff --git a/meta-opencentauri/images/opencentauri-image-base.bb b/meta-opencentauri/images/opencentauri-image-base.bb index a2281ced..38fab979 100644 --- a/meta-opencentauri/images/opencentauri-image-base.bb +++ b/meta-opencentauri/images/opencentauri-image-base.bb @@ -45,6 +45,7 @@ CORE_IMAGE_EXTRA_INSTALL += "\ v4l-utils \ iproute2 \ chrony \ + afc \ " INITRAMFS_IMAGE = "core-image-tiny-initramfs" From da37cf2f992ccb7e79ca1db7dbdeb950f614438f Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Wed, 6 May 2026 00:15:56 +0200 Subject: [PATCH 03/42] Build CANVAS firmware, fix build issues in afc bb --- .../canvas-bootloader-upgrade_1.0.0.bb | 21 +++ ...> 0002-add-support-for-48kib-offset.patch} | 0 .../0003-add-support-for-64kib-offset.patch | 21 +++ .../recipes-apps/katapult/files/config.canvas | 79 +++++++++ .../recipes-apps/katapult/katapult_1.0.0.inc | 5 +- .../recipes-apps/klipper/files/config.canvas | 159 ++++++++++++++++++ .../kalico-firmware-canvas_2026.02.00.bb | 35 ++++ .../recipes-apps/klipper/kalico_2026.02.00.bb | 1 + .../recipes-data/klipper-afc-addon/afc_0.1.bb | 31 +++- 9 files changed, 349 insertions(+), 3 deletions(-) create mode 100644 meta-opencentauri/recipes-apps/katapult/canvas-bootloader-upgrade_1.0.0.bb rename meta-opencentauri/recipes-apps/katapult/files/{01-add-support-for-48kib-offset.patch => 0002-add-support-for-48kib-offset.patch} (100%) create mode 100644 meta-opencentauri/recipes-apps/katapult/files/0003-add-support-for-64kib-offset.patch create mode 100644 meta-opencentauri/recipes-apps/katapult/files/config.canvas create mode 100644 meta-opencentauri/recipes-apps/klipper/files/config.canvas create mode 100644 meta-opencentauri/recipes-apps/klipper/kalico-firmware-canvas_2026.02.00.bb diff --git a/meta-opencentauri/recipes-apps/katapult/canvas-bootloader-upgrade_1.0.0.bb b/meta-opencentauri/recipes-apps/katapult/canvas-bootloader-upgrade_1.0.0.bb new file mode 100644 index 00000000..fdd8d5be --- /dev/null +++ b/meta-opencentauri/recipes-apps/katapult/canvas-bootloader-upgrade_1.0.0.bb @@ -0,0 +1,21 @@ +require katapult_${PV}.inc + +SUMMARY = "Katapult Canvas Bootloader Deployer" +DESCRIPTION = "Builds the Katapult deployer binary for upgrading the canvas bootloader." + +SRC_URI += " \ + file://config.canvas \ +" + +DEPENDS += "gcc-arm-none-eabi-native" + +EXTRA_OEMAKE += "KCONFIG_CONFIG=../config.canvas" + +do_install() { + install -d ${D}/lib/firmware + install -m 0644 ${S}/out/deployer.bin ${D}/lib/firmware/katapult-deployer-canvas.bin +} + +FILES:${PN} = " \ + /lib/firmware/katapult-deployer-canvas.bin \ +" diff --git a/meta-opencentauri/recipes-apps/katapult/files/01-add-support-for-48kib-offset.patch b/meta-opencentauri/recipes-apps/katapult/files/0002-add-support-for-48kib-offset.patch similarity index 100% rename from meta-opencentauri/recipes-apps/katapult/files/01-add-support-for-48kib-offset.patch rename to meta-opencentauri/recipes-apps/katapult/files/0002-add-support-for-48kib-offset.patch diff --git a/meta-opencentauri/recipes-apps/katapult/files/0003-add-support-for-64kib-offset.patch b/meta-opencentauri/recipes-apps/katapult/files/0003-add-support-for-64kib-offset.patch new file mode 100644 index 00000000..b3304ee3 --- /dev/null +++ b/meta-opencentauri/recipes-apps/katapult/files/0003-add-support-for-64kib-offset.patch @@ -0,0 +1,21 @@ +diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig +index d45234c..e4a5f1a 100644 +--- a/src/stm32/Kconfig ++++ b/src/stm32/Kconfig +@@ -496,6 +496,8 @@ choice + prompt "Application start offset" + config STM32_APP_START_20000 + bool "128KiB offset" if MACH_STM32H7 && !MACH_STM32H750 ++ config STM32_APP_START_10000 ++ bool "64KiB offset" if MACH_STM32F4 + config STM32_APP_START_8000 + bool "32KiB offset" if MACH_STM32F2 || MACH_STM32F4 || MACH_STM32H750 + config STM32_APP_START_4000 +@@ -509,6 +511,7 @@ endchoice + config LAUNCH_APP_ADDRESS + hex + default 0x8020000 if STM32_APP_START_20000 ++ default 0x8010000 if STM32_APP_START_10000 + default 0x8008000 if STM32_APP_START_8000 + default 0x8004000 if STM32_APP_START_4000 + default 0x8002000 if STM32_APP_START_2000 diff --git a/meta-opencentauri/recipes-apps/katapult/files/config.canvas b/meta-opencentauri/recipes-apps/katapult/files/config.canvas new file mode 100644 index 00000000..b89231ca --- /dev/null +++ b/meta-opencentauri/recipes-apps/katapult/files/config.canvas @@ -0,0 +1,79 @@ +CONFIG_LOW_LEVEL_OPTIONS=y +# CONFIG_MACH_LPC176X is not set +CONFIG_MACH_STM32=y +# CONFIG_MACH_RPXXXX is not set +CONFIG_BOARD_DIRECTORY="stm32" +CONFIG_MCU="stm32f401xc" +CONFIG_CLOCK_FREQ=84000000 +CONFIG_FLASH_SIZE=0x40000 +CONFIG_FLASH_BOOT_ADDRESS=0x8000000 +CONFIG_RAM_START=0x20000000 +CONFIG_RAM_SIZE=0x10000 +CONFIG_STACK_SIZE=512 +CONFIG_FLASH_APPLICATION_ADDRESS=0x8010000 +CONFIG_FLASH_START=0x8000000 +CONFIG_LAUNCH_APP_ADDRESS=0x8010000 +CONFIG_BLOCK_SIZE=64 +CONFIG_STM32_SELECT=y +# CONFIG_MACH_STM32F103 is not set +# CONFIG_MACH_STM32F207 is not set +CONFIG_MACH_STM32F401=y +# CONFIG_MACH_STM32F405 is not set +# CONFIG_MACH_STM32F407 is not set +# CONFIG_MACH_STM32F429 is not set +# CONFIG_MACH_STM32F446 is not set +# CONFIG_MACH_STM32F031 is not set +# CONFIG_MACH_STM32F042 is not set +# CONFIG_MACH_STM32F070 is not set +# CONFIG_MACH_STM32F072 is not set +# CONFIG_MACH_STM32G0B0 is not set +# CONFIG_MACH_STM32G0B1 is not set +# CONFIG_MACH_STM32G431 is not set +# CONFIG_MACH_STM32H723 is not set +# CONFIG_MACH_STM32H743 is not set +CONFIG_MACH_STM32F4=y +CONFIG_HAVE_STM32_USBOTG=y +CONFIG_STM32_DFU_ROM_ADDRESS=0x1fff0000 +# CONFIG_STM32_FLASH_START_0000 is not set +# CONFIG_STM32_FLASH_START_8000 is not set +CONFIG_STM32_FLASH_START_C000=y +# CONFIG_STM32_FLASH_START_10000 is not set +# CONFIG_STM32_FLASH_START_4000 is not set +# CONFIG_STM32_CLOCK_REF_8M is not set +# CONFIG_STM32_CLOCK_REF_12M is not set +# CONFIG_STM32_CLOCK_REF_16M is not set +# CONFIG_STM32_CLOCK_REF_20M is not set +CONFIG_STM32_CLOCK_REF_24M=y +# CONFIG_STM32_CLOCK_REF_25M is not set +# CONFIG_STM32_CLOCK_REF_32M is not set +# CONFIG_STM32_CLOCK_REF_INTERNAL is not set +CONFIG_CLOCK_REF_FREQ=24000000 +CONFIG_STM32F0_TRIM=16 +CONFIG_STM32_USB_PA11_PA12=y +# CONFIG_STM32_SERIAL_USART1 is not set +# CONFIG_STM32_SERIAL_USART1_ALT_PB7_PB6 is not set +# CONFIG_STM32_SERIAL_USART2 is not set +# CONFIG_STM32_SERIAL_USART2_ALT_PD6_PD5 is not set +CONFIG_STM32_APP_START_C000=y +# CONFIG_STM32_APP_START_8000 is not set +# CONFIG_STM32_APP_START_4000 is not set +CONFIG_USBSERIAL=y +CONFIG_USB=y +CONFIG_USB_VENDOR_ID=0x1d50 +CONFIG_USB_DEVICE_ID=0x6177 +CONFIG_USB_SERIAL_NUMBER_CHIPID=y +CONFIG_USB_SERIAL_NUMBER="12345" + +# +# USB ids +# +# end of USB ids + +CONFIG_CANBUS_FREQUENCY=1000000 +CONFIG_INITIAL_PINS="" +CONFIG_ENABLE_DOUBLE_RESET=y +# CONFIG_ENABLE_BUTTON is not set +# CONFIG_ENABLE_LED is not set +CONFIG_BUILD_DEPLOYER=y +CONFIG_HAVE_CHIPID=y +CONFIG_KATAPULT_VERSION="b0bf421-dirty" diff --git a/meta-opencentauri/recipes-apps/katapult/katapult_1.0.0.inc b/meta-opencentauri/recipes-apps/katapult/katapult_1.0.0.inc index 1b2924ad..74e9a776 100644 --- a/meta-opencentauri/recipes-apps/katapult/katapult_1.0.0.inc +++ b/meta-opencentauri/recipes-apps/katapult/katapult_1.0.0.inc @@ -6,10 +6,11 @@ FILESEXTRAPATHS:prepend := "${THISDIR}/files:" SRC_URI = " \ git://github.com/Arksine/katapult.git;protocol=https;branch=master \ - file://01-add-support-for-48kib-offset.patch \ file://0001-Add-DEPLOYER_PAYLOAD-parameter.patch \ + file://0002-add-support-for-48kib-offset.patch \ + file://0003-add-support-for-64kib-offset.patch \ " -SRCREV = "32584cbbb66c4dc85fc87c0fa87ed508f7c2df52" +SRCREV = "ec59b9bb9ad6c2ec8d4dc6831fbc77f0b308e29e" S = "${WORKDIR}/git" diff --git a/meta-opencentauri/recipes-apps/klipper/files/config.canvas b/meta-opencentauri/recipes-apps/klipper/files/config.canvas new file mode 100644 index 00000000..88a28170 --- /dev/null +++ b/meta-opencentauri/recipes-apps/klipper/files/config.canvas @@ -0,0 +1,159 @@ +CONFIG_LOW_LEVEL_OPTIONS=y +# CONFIG_MACH_AVR is not set +# CONFIG_MACH_ATSAM is not set +# CONFIG_MACH_ATSAMD is not set +# CONFIG_MACH_LPC176X is not set +CONFIG_MACH_STM32=y +# CONFIG_MACH_HC32F460 is not set +# CONFIG_MACH_RPXXXX is not set +# CONFIG_MACH_PRU is not set +# CONFIG_MACH_AR100 is not set +# CONFIG_MACH_HIFI4 is not set +# CONFIG_MACH_LINUX is not set +# CONFIG_MACH_SIMU is not set +CONFIG_BOARD_DIRECTORY="stm32" +CONFIG_MCU="stm32f401xc" +CONFIG_CLOCK_FREQ=84000000 +CONFIG_USBSERIAL=y +CONFIG_FLASH_SIZE=0x40000 +CONFIG_FLASH_BOOT_ADDRESS=0x8000000 +CONFIG_RAM_START=0x20000000 +CONFIG_RAM_SIZE=0x10000 +CONFIG_STACK_SIZE=512 +CONFIG_FLASH_APPLICATION_ADDRESS=0x8010000 +CONFIG_STM32_SELECT=y +# CONFIG_MACH_AT32F403 is not set +# CONFIG_MACH_STM32F103 is not set +# CONFIG_MACH_STM32F207 is not set +CONFIG_MACH_STM32F401=y +# CONFIG_MACH_STM32F411 is not set +# CONFIG_MACH_STM32F405 is not set +# CONFIG_MACH_STM32F407 is not set +# CONFIG_MACH_STM32F427 is not set +# CONFIG_MACH_STM32F429 is not set +# CONFIG_MACH_STM32F446 is not set +# CONFIG_MACH_STM32F765 is not set +# CONFIG_MACH_STM32F031 is not set +# CONFIG_MACH_STM32F042 is not set +# CONFIG_MACH_STM32F070 is not set +# CONFIG_MACH_STM32F072 is not set +# CONFIG_MACH_STM32G070 is not set +# CONFIG_MACH_STM32G071 is not set +# CONFIG_MACH_STM32G0B0 is not set +# CONFIG_MACH_STM32G0B1 is not set +# CONFIG_MACH_STM32G431 is not set +# CONFIG_MACH_STM32G474 is not set +# CONFIG_MACH_STM32H723 is not set +# CONFIG_MACH_STM32H743 is not set +# CONFIG_MACH_STM32H750 is not set +# CONFIG_MACH_STM32L412 is not set +# CONFIG_MACH_N32G452 is not set +# CONFIG_MACH_N32G455 is not set +CONFIG_MACH_STM32F4=y +CONFIG_HAVE_STM32_USBOTG=y +CONFIG_STM32_DFU_ROM_ADDRESS=0x1fff0000 +# CONFIG_STM32_FLASH_START_8000 is not set +CONFIG_STM32_FLASH_START_C000=y +# CONFIG_STM32_FLASH_START_10000 is not set +# CONFIG_STM32_FLASH_START_4000 is not set +# CONFIG_STM32_FLASH_START_0000 is not set +# CONFIG_STM32_CLOCK_REF_8M is not set +# CONFIG_STM32_CLOCK_REF_12M is not set +# CONFIG_STM32_CLOCK_REF_16M is not set +# CONFIG_STM32_CLOCK_REF_20M is not set +CONFIG_STM32_CLOCK_REF_24M=y +# CONFIG_STM32_CLOCK_REF_25M is not set +# CONFIG_STM32_CLOCK_REF_INTERNAL is not set +CONFIG_CLOCK_REF_FREQ=24000000 +CONFIG_STM32F0_TRIM=16 +CONFIG_STM32_USB_PA11_PA12=y +# CONFIG_STM32_SERIAL_USART1 is not set +# CONFIG_STM32_SERIAL_USART1_ALT_PB7_PB6 is not set +# CONFIG_STM32_SERIAL_USART2 is not set +# CONFIG_STM32_SERIAL_USART2_ALT_PD6_PD5 is not set +# CONFIG_STM32_SERIAL_USART6 is not set +# CONFIG_STM32_SERIAL_USART6_ALT_PC7_PC6 is not set +CONFIG_USB=y +CONFIG_USB_VENDOR_ID=0x1d50 +CONFIG_USB_DEVICE_ID=0x614e +CONFIG_USB_SERIAL_NUMBER_CHIPID=y +CONFIG_USB_SERIAL_NUMBER="12345" +CONFIG_USB_MANUFACTURER="Klipper" + +# +# USB ids +# +# end of USB ids + +CONFIG_CAN_UUID_CUSTOM="abc1234" +CONFIG_WANT_ADC=y +CONFIG_WANT_SPI=y +CONFIG_WANT_SOFTWARE_SPI=y +CONFIG_WANT_I2C=y +CONFIG_WANT_SOFTWARE_I2C=y +CONFIG_WANT_HARD_PWM=y +CONFIG_WANT_BUTTONS=y +CONFIG_WANT_TMCUART=y +CONFIG_WANT_NEOPIXEL=y +CONFIG_WANT_PULSE_COUNTER=y +CONFIG_WANT_ST7920=y +CONFIG_WANT_HD44780=y +CONFIG_WANT_ADXL345=y +CONFIG_WANT_LIS2DW=y +CONFIG_WANT_MPU9250=y +CONFIG_WANT_ICM20948=y +CONFIG_WANT_THERMOCOUPLE=y +CONFIG_WANT_HX71X=y +CONFIG_WANT_ADS1220=y +CONFIG_WANT_LDC1612=y +# CONFIG_WANT_SENSOR_ANGLE is not set +CONFIG_WANT_GPIO_SPI=y +CONFIG_WANT_GPIO_ADC=y +CONFIG_WANT_GPIO_I2C=y +CONFIG_NEED_SENSOR_BULK=y +# CONFIG_WANT_OPTIMIZE_SIZE is not set + +# +# Feature Configuration +# + +# +# Microcontroller interfaces +# +# CONFIG_WANT_GPIO_BUTTONS is not set + +# +# ---- +# + +# +# LCD chips +# + +# +# Accelerometer chips +# + +# +# External ADC type chips +# + +# +# Compiler options +# +# end of Feature Configuration + +CONFIG_CANBUS_FREQUENCY=1000000 +CONFIG_INLINE_STEPPER_HACK=y +CONFIG_HAVE_STEPPER_OPTIMIZED_BOTH_EDGE=y +CONFIG_WANT_STEPPER_OPTIMIZED_BOTH_EDGE=y +CONFIG_INITIAL_PINS="PC8" +CONFIG_HAVE_GPIO=y +CONFIG_HAVE_GPIO_ADC=y +CONFIG_HAVE_GPIO_SPI=y +CONFIG_HAVE_GPIO_SDIO=y +CONFIG_HAVE_GPIO_I2C=y +CONFIG_HAVE_GPIO_HARD_PWM=y +CONFIG_HAVE_STRICT_TIMING=y +CONFIG_HAVE_CHIPID=y +CONFIG_HAVE_BOOTLOADER_REQUEST=y diff --git a/meta-opencentauri/recipes-apps/klipper/kalico-firmware-canvas_2026.02.00.bb b/meta-opencentauri/recipes-apps/klipper/kalico-firmware-canvas_2026.02.00.bb new file mode 100644 index 00000000..619a2281 --- /dev/null +++ b/meta-opencentauri/recipes-apps/klipper/kalico-firmware-canvas_2026.02.00.bb @@ -0,0 +1,35 @@ +require kalico_${PV}.inc +inherit update-rc.d + +SUMMARY = "Kalico 3D Printer Firmware" +DESCRIPTION = "Klipper, but Limitless" + +SRC_URI += " \ + file://config.canvas \ +" + +PR = "r3" + +DEPENDS += "gcc-arm-none-eabi-native" +RDEPENDS:${PN} = " \ + flashtool \ + canvas-bootloader-upgrade \ + mcu-flasher \ +" + +RPROVIDES:${PN} += "klipper-firmware-canvas" + +EXTRA_OEMAKE += "KCONFIG_CONFIG=../config.canvas" + +INITSCRIPT_NAME = "klipper-firmware-canvas" +INITSCRIPT_PARAMS = "defaults 94 4" + +do_install() { + install -d ${D}/lib/firmware + cp -r ${S}/out/klipper.bin ${D}/lib/firmware/klipper-canvas.bin + echo "${SRCREV}-${PR}" > ${D}/lib/firmware/klipper-canvas.bin.ver +} + +FILES:${PN} = " \ + /lib/firmware/klipper-canvas.bin \ +" diff --git a/meta-opencentauri/recipes-apps/klipper/kalico_2026.02.00.bb b/meta-opencentauri/recipes-apps/klipper/kalico_2026.02.00.bb index dc8e319a..7d11153b 100644 --- a/meta-opencentauri/recipes-apps/klipper/kalico_2026.02.00.bb +++ b/meta-opencentauri/recipes-apps/klipper/kalico_2026.02.00.bb @@ -34,6 +34,7 @@ RDEPENDS:${PN} = " \ kalico-firmware-toolhead \ kalico-firmware-bed \ check-update \ + kalico-firmware-canvas \ " RPROVIDES:${PN} += "klipper" diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb index a16db6da..8d2316fc 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb @@ -38,7 +38,36 @@ do_install() { } FILES:${PN} = " \ - ${datadir}/klipper/AFC* \ + ${datadir}/klipper/AFC_buffer.py \ + ${datadir}/klipper/AFC_stats.py \ + ${datadir}/klipper/AFC_Toolchanger.py \ + ${datadir}/klipper/AFC_respond.py \ + ${datadir}/klipper/AFC_extruder.py \ + ${datadir}/klipper/AFC_unit.py \ + ${datadir}/klipper/AFC_prep.py \ + ${datadir}/klipper/AFC_led.py \ + ${datadir}/klipper/AFC_button.py \ + ${datadir}/klipper/AFC_vivid.py \ + ${datadir}/klipper/AFC_logger.py \ + ${datadir}/klipper/AFC_spool.py \ + ${datadir}/klipper/AFC_BoxTurtle.py \ + ${datadir}/klipper/AFC_lane.py \ + ${datadir}/klipper/AFC_canvas.py \ + ${datadir}/klipper/AFC_hub.py \ + ${datadir}/klipper/AFC_canvas_lane.py \ + ${datadir}/klipper/openams_integration.py \ + ${datadir}/klipper/AFC_poop.py \ + ${datadir}/klipper/AFC_stepper.py \ + ${datadir}/klipper/AFC_QuattroBox.py \ + ${datadir}/klipper/AFC_utils.py \ + ${datadir}/klipper/AFC_HTLF.py \ + ${datadir}/klipper/AFC_form_tip.py \ + ${datadir}/klipper/AFC_assist.py \ + ${datadir}/klipper/AFC.py \ + ${datadir}/klipper/AFC_OpenAMS.py \ + ${datadir}/klipper/AFC_functions.py \ + ${datadir}/klipper/AFC_NightOwl.py \ + ${datadir}/klipper/AFC_error.py \ ${sysconfdir}/klipper/config/extras-readonly/afc.cfg \ ${sysconfdir}/klipper/config/extras-readonly/canvas.cfg \ " \ No newline at end of file From e29889d8799d5cca4c302beaa9b8d616800c2d38 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Wed, 6 May 2026 00:25:31 +0200 Subject: [PATCH 04/42] Fix katapult patch --- ...-support-for-48kib-and-64kib-offset.patch} | 9 +++++--- .../0003-add-support-for-64kib-offset.patch | 21 ------------------- .../recipes-apps/katapult/katapult_1.0.0.inc | 3 +-- 3 files changed, 7 insertions(+), 26 deletions(-) rename meta-opencentauri/recipes-apps/katapult/files/{0002-add-support-for-48kib-offset.patch => 0002-add-support-for-48kib-and-64kib-offset.patch} (84%) delete mode 100644 meta-opencentauri/recipes-apps/katapult/files/0003-add-support-for-64kib-offset.patch diff --git a/meta-opencentauri/recipes-apps/katapult/files/0002-add-support-for-48kib-offset.patch b/meta-opencentauri/recipes-apps/katapult/files/0002-add-support-for-48kib-and-64kib-offset.patch similarity index 84% rename from meta-opencentauri/recipes-apps/katapult/files/0002-add-support-for-48kib-offset.patch rename to meta-opencentauri/recipes-apps/katapult/files/0002-add-support-for-48kib-and-64kib-offset.patch index 9c92047d..0980862b 100644 --- a/meta-opencentauri/recipes-apps/katapult/files/0002-add-support-for-48kib-offset.patch +++ b/meta-opencentauri/recipes-apps/katapult/files/0002-add-support-for-48kib-and-64kib-offset.patch @@ -1,5 +1,5 @@ diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig -index d45234c..df937ef 100644 +index d45234c..4727d76 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -248,7 +248,7 @@ choice @@ -11,19 +11,22 @@ index d45234c..df937ef 100644 config STM32_FLASH_START_10000 bool "64KiB bootloader" if MACH_STM32F103 || MACH_STM32F446 || MACH_STM32F401 config STM32_FLASH_START_800 -@@ -496,6 +496,8 @@ choice +@@ -496,6 +496,10 @@ choice prompt "Application start offset" config STM32_APP_START_20000 bool "128KiB offset" if MACH_STM32H7 && !MACH_STM32H750 ++ config STM32_APP_START_10000 ++ bool "64KiB offset" if MACH_STM32F4 + config STM32_APP_START_C000 + bool "48KiB offset" if MACH_STM32F4 config STM32_APP_START_8000 bool "32KiB offset" if MACH_STM32F2 || MACH_STM32F4 || MACH_STM32H750 config STM32_APP_START_4000 -@@ -509,6 +511,7 @@ endchoice +@@ -509,6 +513,8 @@ endchoice config LAUNCH_APP_ADDRESS hex default 0x8020000 if STM32_APP_START_20000 ++ default 0x8010000 if STM32_APP_START_10000 + default 0x800C000 if STM32_APP_START_C000 default 0x8008000 if STM32_APP_START_8000 default 0x8004000 if STM32_APP_START_4000 diff --git a/meta-opencentauri/recipes-apps/katapult/files/0003-add-support-for-64kib-offset.patch b/meta-opencentauri/recipes-apps/katapult/files/0003-add-support-for-64kib-offset.patch deleted file mode 100644 index b3304ee3..00000000 --- a/meta-opencentauri/recipes-apps/katapult/files/0003-add-support-for-64kib-offset.patch +++ /dev/null @@ -1,21 +0,0 @@ -diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig -index d45234c..e4a5f1a 100644 ---- a/src/stm32/Kconfig -+++ b/src/stm32/Kconfig -@@ -496,6 +496,8 @@ choice - prompt "Application start offset" - config STM32_APP_START_20000 - bool "128KiB offset" if MACH_STM32H7 && !MACH_STM32H750 -+ config STM32_APP_START_10000 -+ bool "64KiB offset" if MACH_STM32F4 - config STM32_APP_START_8000 - bool "32KiB offset" if MACH_STM32F2 || MACH_STM32F4 || MACH_STM32H750 - config STM32_APP_START_4000 -@@ -509,6 +511,7 @@ endchoice - config LAUNCH_APP_ADDRESS - hex - default 0x8020000 if STM32_APP_START_20000 -+ default 0x8010000 if STM32_APP_START_10000 - default 0x8008000 if STM32_APP_START_8000 - default 0x8004000 if STM32_APP_START_4000 - default 0x8002000 if STM32_APP_START_2000 diff --git a/meta-opencentauri/recipes-apps/katapult/katapult_1.0.0.inc b/meta-opencentauri/recipes-apps/katapult/katapult_1.0.0.inc index 74e9a776..0220d3b8 100644 --- a/meta-opencentauri/recipes-apps/katapult/katapult_1.0.0.inc +++ b/meta-opencentauri/recipes-apps/katapult/katapult_1.0.0.inc @@ -7,8 +7,7 @@ FILESEXTRAPATHS:prepend := "${THISDIR}/files:" SRC_URI = " \ git://github.com/Arksine/katapult.git;protocol=https;branch=master \ file://0001-Add-DEPLOYER_PAYLOAD-parameter.patch \ - file://0002-add-support-for-48kib-offset.patch \ - file://0003-add-support-for-64kib-offset.patch \ + file://0002-add-support-for-48kib-and-64kib-offset.patch \ " SRCREV = "ec59b9bb9ad6c2ec8d4dc6831fbc77f0b308e29e" From dc30e5094b75bbaeaff3b06c1c9d087054a1991e Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Wed, 6 May 2026 00:31:39 +0200 Subject: [PATCH 05/42] Don't forget klipper ver --- .../recipes-apps/klipper/kalico-firmware-canvas_2026.02.00.bb | 1 + 1 file changed, 1 insertion(+) diff --git a/meta-opencentauri/recipes-apps/klipper/kalico-firmware-canvas_2026.02.00.bb b/meta-opencentauri/recipes-apps/klipper/kalico-firmware-canvas_2026.02.00.bb index 619a2281..70f64563 100644 --- a/meta-opencentauri/recipes-apps/klipper/kalico-firmware-canvas_2026.02.00.bb +++ b/meta-opencentauri/recipes-apps/klipper/kalico-firmware-canvas_2026.02.00.bb @@ -32,4 +32,5 @@ do_install() { FILES:${PN} = " \ /lib/firmware/klipper-canvas.bin \ + /lib/firmware/klipper-canvas.bin.ver \ " From c106546751c85af1f6bd2ee738c01c37562dbff1 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Wed, 6 May 2026 00:37:43 +0200 Subject: [PATCH 06/42] oops --- .../recipes-data/klipper-afc-addon/afc_0.1.bb | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb index 8d2316fc..61df055a 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb @@ -38,36 +38,36 @@ do_install() { } FILES:${PN} = " \ - ${datadir}/klipper/AFC_buffer.py \ - ${datadir}/klipper/AFC_stats.py \ - ${datadir}/klipper/AFC_Toolchanger.py \ - ${datadir}/klipper/AFC_respond.py \ - ${datadir}/klipper/AFC_extruder.py \ - ${datadir}/klipper/AFC_unit.py \ - ${datadir}/klipper/AFC_prep.py \ - ${datadir}/klipper/AFC_led.py \ - ${datadir}/klipper/AFC_button.py \ - ${datadir}/klipper/AFC_vivid.py \ - ${datadir}/klipper/AFC_logger.py \ - ${datadir}/klipper/AFC_spool.py \ - ${datadir}/klipper/AFC_BoxTurtle.py \ - ${datadir}/klipper/AFC_lane.py \ - ${datadir}/klipper/AFC_canvas.py \ - ${datadir}/klipper/AFC_hub.py \ - ${datadir}/klipper/AFC_canvas_lane.py \ - ${datadir}/klipper/openams_integration.py \ - ${datadir}/klipper/AFC_poop.py \ - ${datadir}/klipper/AFC_stepper.py \ - ${datadir}/klipper/AFC_QuattroBox.py \ - ${datadir}/klipper/AFC_utils.py \ - ${datadir}/klipper/AFC_HTLF.py \ - ${datadir}/klipper/AFC_form_tip.py \ - ${datadir}/klipper/AFC_assist.py \ - ${datadir}/klipper/AFC.py \ - ${datadir}/klipper/AFC_OpenAMS.py \ - ${datadir}/klipper/AFC_functions.py \ - ${datadir}/klipper/AFC_NightOwl.py \ - ${datadir}/klipper/AFC_error.py \ + ${datadir}/klipper/extras/AFC_buffer.py \ + ${datadir}/klipper/extras/AFC_stats.py \ + ${datadir}/klipper/extras/AFC_Toolchanger.py \ + ${datadir}/klipper/extras/AFC_respond.py \ + ${datadir}/klipper/extras/AFC_extruder.py \ + ${datadir}/klipper/extras/AFC_unit.py \ + ${datadir}/klipper/extras/AFC_prep.py \ + ${datadir}/klipper/extras/AFC_led.py \ + ${datadir}/klipper/extras/AFC_button.py \ + ${datadir}/klipper/extras/AFC_vivid.py \ + ${datadir}/klipper/extras/AFC_logger.py \ + ${datadir}/klipper/extras/AFC_spool.py \ + ${datadir}/klipper/extras/AFC_BoxTurtle.py \ + ${datadir}/klipper/extras/AFC_lane.py \ + ${datadir}/klipper/extras/AFC_canvas.py \ + ${datadir}/klipper/extras/AFC_hub.py \ + ${datadir}/klipper/extras/AFC_canvas_lane.py \ + ${datadir}/klipper/extras/openams_integration.py \ + ${datadir}/klipper/extras/AFC_poop.py \ + ${datadir}/klipper/extras/AFC_stepper.py \ + ${datadir}/klipper/extras/AFC_QuattroBox.py \ + ${datadir}/klipper/extras/AFC_utils.py \ + ${datadir}/klipper/extras/AFC_HTLF.py \ + ${datadir}/klipper/extras/AFC_form_tip.py \ + ${datadir}/klipper/extras/AFC_assist.py \ + ${datadir}/klipper/extras/AFC.py \ + ${datadir}/klipper/extras/AFC_OpenAMS.py \ + ${datadir}/klipper/extras/AFC_functions.py \ + ${datadir}/klipper/extras/AFC_NightOwl.py \ + ${datadir}/klipper/extras/AFC_error.py \ ${sysconfdir}/klipper/config/extras-readonly/afc.cfg \ ${sysconfdir}/klipper/config/extras-readonly/canvas.cfg \ " \ No newline at end of file From ef4a99d219659fa468037587ec6485bcd395d2c3 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Wed, 6 May 2026 00:46:07 +0200 Subject: [PATCH 07/42] Don't forget to delete unnesesary file --- meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb | 1 + 1 file changed, 1 insertion(+) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb index 61df055a..1b6d2053 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb @@ -31,6 +31,7 @@ do_install() { # Install klippy extras install -d ${D}${datadir}/klipper/extras cp -r ${S}/extras/* ${D}${datadir}/klipper/extras/ + rm ${D}${datadir}/klipper/extras/__init__.py # Install config files install -d ${D}${sysconfdir}/klipper/config/extras-readonly From 41cc8c1a7c72244f58016fc38bfae46b16f9df4c Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Thu, 7 May 2026 00:43:20 +0200 Subject: [PATCH 08/42] We don't have a canvas initscript yet --- .../klipper/kalico-firmware-canvas_2026.02.00.bb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/meta-opencentauri/recipes-apps/klipper/kalico-firmware-canvas_2026.02.00.bb b/meta-opencentauri/recipes-apps/klipper/kalico-firmware-canvas_2026.02.00.bb index 70f64563..da2436d3 100644 --- a/meta-opencentauri/recipes-apps/klipper/kalico-firmware-canvas_2026.02.00.bb +++ b/meta-opencentauri/recipes-apps/klipper/kalico-firmware-canvas_2026.02.00.bb @@ -1,5 +1,5 @@ require kalico_${PV}.inc -inherit update-rc.d +#inherit update-rc.d SUMMARY = "Kalico 3D Printer Firmware" DESCRIPTION = "Klipper, but Limitless" @@ -21,8 +21,8 @@ RPROVIDES:${PN} += "klipper-firmware-canvas" EXTRA_OEMAKE += "KCONFIG_CONFIG=../config.canvas" -INITSCRIPT_NAME = "klipper-firmware-canvas" -INITSCRIPT_PARAMS = "defaults 94 4" +#INITSCRIPT_NAME = "klipper-firmware-canvas" +#INITSCRIPT_PARAMS = "defaults 94 4" do_install() { install -d ${D}/lib/firmware From 88317394a20a5d7d3ab9477277b37691aa3ae056 Mon Sep 17 00:00:00 2001 From: Sims <38142618+suchmememanyskill@users.noreply.github.com> Date: Thu, 7 May 2026 20:36:11 +0200 Subject: [PATCH 09/42] oops wrong path --- .../recipes-data/klipper-afc-addon/afc_0.1.bb | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb index 1b6d2053..73699cc2 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb @@ -29,9 +29,9 @@ do_compile[noexec] = "1" do_install() { # Install klippy extras - install -d ${D}${datadir}/klipper/extras - cp -r ${S}/extras/* ${D}${datadir}/klipper/extras/ - rm ${D}${datadir}/klipper/extras/__init__.py + install -d ${D}${datadir}/klipper/klippy/extras + cp -r ${S}/extras/* ${D}${datadir}/klipper/klippy/extras/ + rm ${D}${datadir}/klipper/klippy/extras/__init__.py # Install config files install -d ${D}${sysconfdir}/klipper/config/extras-readonly @@ -39,36 +39,36 @@ do_install() { } FILES:${PN} = " \ - ${datadir}/klipper/extras/AFC_buffer.py \ - ${datadir}/klipper/extras/AFC_stats.py \ - ${datadir}/klipper/extras/AFC_Toolchanger.py \ - ${datadir}/klipper/extras/AFC_respond.py \ - ${datadir}/klipper/extras/AFC_extruder.py \ - ${datadir}/klipper/extras/AFC_unit.py \ - ${datadir}/klipper/extras/AFC_prep.py \ - ${datadir}/klipper/extras/AFC_led.py \ - ${datadir}/klipper/extras/AFC_button.py \ - ${datadir}/klipper/extras/AFC_vivid.py \ - ${datadir}/klipper/extras/AFC_logger.py \ - ${datadir}/klipper/extras/AFC_spool.py \ - ${datadir}/klipper/extras/AFC_BoxTurtle.py \ - ${datadir}/klipper/extras/AFC_lane.py \ - ${datadir}/klipper/extras/AFC_canvas.py \ - ${datadir}/klipper/extras/AFC_hub.py \ - ${datadir}/klipper/extras/AFC_canvas_lane.py \ - ${datadir}/klipper/extras/openams_integration.py \ - ${datadir}/klipper/extras/AFC_poop.py \ - ${datadir}/klipper/extras/AFC_stepper.py \ - ${datadir}/klipper/extras/AFC_QuattroBox.py \ - ${datadir}/klipper/extras/AFC_utils.py \ - ${datadir}/klipper/extras/AFC_HTLF.py \ - ${datadir}/klipper/extras/AFC_form_tip.py \ - ${datadir}/klipper/extras/AFC_assist.py \ - ${datadir}/klipper/extras/AFC.py \ - ${datadir}/klipper/extras/AFC_OpenAMS.py \ - ${datadir}/klipper/extras/AFC_functions.py \ - ${datadir}/klipper/extras/AFC_NightOwl.py \ - ${datadir}/klipper/extras/AFC_error.py \ + ${datadir}/klipper/klippy/extras/AFC_buffer.py \ + ${datadir}/klipper/klippy/extras/AFC_stats.py \ + ${datadir}/klipper/klippy/extras/AFC_Toolchanger.py \ + ${datadir}/klipper/klippy/extras/AFC_respond.py \ + ${datadir}/klipper/klippy/extras/AFC_extruder.py \ + ${datadir}/klipper/klippy/extras/AFC_unit.py \ + ${datadir}/klipper/klippy/extras/AFC_prep.py \ + ${datadir}/klipper/klippy/extras/AFC_led.py \ + ${datadir}/klipper/klippy/extras/AFC_button.py \ + ${datadir}/klipper/klippy/extras/AFC_vivid.py \ + ${datadir}/klipper/klippy/extras/AFC_logger.py \ + ${datadir}/klipper/klippy/extras/AFC_spool.py \ + ${datadir}/klipper/klippy/extras/AFC_BoxTurtle.py \ + ${datadir}/klipper/klippy/extras/AFC_lane.py \ + ${datadir}/klipper/klippy/extras/AFC_canvas.py \ + ${datadir}/klipper/klippy/extras/AFC_hub.py \ + ${datadir}/klipper/klippy/extras/AFC_canvas_lane.py \ + ${datadir}/klipper/klippy/extras/openams_integration.py \ + ${datadir}/klipper/klippy/extras/AFC_poop.py \ + ${datadir}/klipper/klippy/extras/AFC_stepper.py \ + ${datadir}/klipper/klippy/extras/AFC_QuattroBox.py \ + ${datadir}/klipper/klippy/extras/AFC_utils.py \ + ${datadir}/klipper/klippy/extras/AFC_HTLF.py \ + ${datadir}/klipper/klippy/extras/AFC_form_tip.py \ + ${datadir}/klipper/klippy/extras/AFC_assist.py \ + ${datadir}/klipper/klippy/extras/AFC.py \ + ${datadir}/klipper/klippy/extras/AFC_OpenAMS.py \ + ${datadir}/klipper/klippy/extras/AFC_functions.py \ + ${datadir}/klipper/klippy/extras/AFC_NightOwl.py \ + ${datadir}/klipper/klippy/extras/AFC_error.py \ ${sysconfdir}/klipper/config/extras-readonly/afc.cfg \ ${sysconfdir}/klipper/config/extras-readonly/canvas.cfg \ " \ No newline at end of file From ca2f048cfbdd1b7a7fb9b2497d182c62bfcbbb50 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Fri, 8 May 2026 20:01:07 +0200 Subject: [PATCH 10/42] New AFC version --- meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb | 2 +- .../recipes-data/klipper-afc-addon/files/canvas.cfg | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb index 73699cc2..816c8057 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb @@ -12,7 +12,7 @@ SRC_URI = " \ file://canvas.cfg \ " -SRCREV = "9b5291954d27fdcfb5913a79c72e15884a9935b7" +SRCREV = "85d80506470595a3657c15a520712b43aad1a533" S = "${WORKDIR}/git" diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg index 4e8a8bbf..0f65ea7f 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg @@ -182,6 +182,7 @@ gcode: [AFC] VarFile: /etc/klipper/config/AFC.var +default_material_type: PLA enable_sensors_in_gui: True ignore_spoolman_material_temps: False load_to_hub: False From a62c0d4f112faa483689b1688e6c4c8b0c79aae5 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Thu, 14 May 2026 13:01:44 +0200 Subject: [PATCH 11/42] Add patch to allow excluding sections --- .../files/0004-allow-config-exclude.patch | 20 +++++++++++++++++++ .../klipper/kalico_2026.02.00.inc | 1 + 2 files changed, 21 insertions(+) create mode 100644 meta-opencentauri/recipes-apps/klipper/files/0004-allow-config-exclude.patch diff --git a/meta-opencentauri/recipes-apps/klipper/files/0004-allow-config-exclude.patch b/meta-opencentauri/recipes-apps/klipper/files/0004-allow-config-exclude.patch new file mode 100644 index 00000000..572b9bf2 --- /dev/null +++ b/meta-opencentauri/recipes-apps/klipper/files/0004-allow-config-exclude.patch @@ -0,0 +1,20 @@ +diff --git a/klippy/configfile.py b/klippy/configfile.py +index 691f1eb8..bb3ef969 100644 +--- a/klippy/configfile.py ++++ b/klippy/configfile.py +@@ -485,6 +485,15 @@ class PrinterConfig: + self._resolve_include( + filename, include_spec, fileconfig, visited + ) ++ elif header and header.startswith("!"): ++ self._parse_config_buffer(buffer, filename, fileconfig) ++ section_name = header[1:].strip() ++ if not fileconfig.has_section(section_name): ++ raise error( ++ "Section '%s' does not exist for exclusion" ++ % (section_name,) ++ ) ++ fileconfig.remove_section(section_name) + else: + line = _INCLUDERE.sub( + lambda match: _fix_include_path(filename, match), diff --git a/meta-opencentauri/recipes-apps/klipper/kalico_2026.02.00.inc b/meta-opencentauri/recipes-apps/klipper/kalico_2026.02.00.inc index e0ecff03..050d592a 100644 --- a/meta-opencentauri/recipes-apps/klipper/kalico_2026.02.00.inc +++ b/meta-opencentauri/recipes-apps/klipper/kalico_2026.02.00.inc @@ -13,6 +13,7 @@ SRC_URI = "git://github.com/OpenCentauri/kalico.git;protocol=https;branch=hx711s file://0001-probe-home-z-includes-z-offset.patch \ file://0001-force-specific-input-shaper.patch \ file://0003-drv8833-motor-control.patch \ + file://0004-allow-config-exclude.patch \ " SRCREV = "3f166182f82f95584112571b4f7b2258922be60d" From dd5e57e894a3f0e2fc0a9e264c0a3165f66e48e0 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Thu, 14 May 2026 14:29:48 +0200 Subject: [PATCH 12/42] Bump AFC version --- meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb index 816c8057..4a25dde6 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb @@ -12,7 +12,7 @@ SRC_URI = " \ file://canvas.cfg \ " -SRCREV = "85d80506470595a3657c15a520712b43aad1a533" +SRCREV = "431e42b71e90ce2b3a3c49b973d6e64b0ae1e5fd" S = "${WORKDIR}/git" From d6ab57487774f3a0758a45b310ad3634732157aa Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Thu, 14 May 2026 16:57:26 +0200 Subject: [PATCH 13/42] Canvas CFG changes --- .../klipper-afc-addon/files/canvas.cfg | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg index 0f65ea7f..2d3afdde 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg @@ -59,11 +59,13 @@ aliases: [AFC_hub toolhead_4way_hub] switch_pin: !hotend:PB2 enable_hub_runout: False +afc_bowden_length: 6.5 +afc_unload_bowden_length: 40 [AFC_extruder extruder] pin_tool_start: !hotend:PB2 -tool_stn: 94 -tool_stn_unload: 31 +tool_stn: 43.5 +tool_stn_unload: 42 tool_sensor_after_extruder: 0 tool_unload_speed: 15 tool_load_speed: 8 @@ -73,16 +75,14 @@ extruder: extruder enable_9v_pin: canvas:ENABLE_9V enable_24v_pin: canvas:ENABLE_24V tangle_pin: !hotend:PB0 +toolhead_cover_pin: hotend:PC4 +cutter_pin: !hotend:PC5 short_moves_speed: 30 long_moves_speed: 70 -tool_unload_lane_extra_speed: 40 # Unused prep_distance: 120 prep_speed: 60 -tool_load_sync_speed_offset: 2 -tool_load_lane_extra_distance: 6.5 -tool_unload_lane_extra_distance: 40 # Unused -[AFC_canvas_lane canvas_lane0] +[AFC_canvas_lane CANVAS_1] unit: CANVAS_1:1 hub: toolhead_4way_hub map: T0 @@ -94,7 +94,7 @@ odometer_resolution: 3.15786 led_white_pin: canvas:LED0_WHITE led_red_pin: canvas:LED0_RED -[AFC_canvas_lane canvas_lane1] +[AFC_canvas_lane CANVAS_2] unit: CANVAS_1:2 hub: toolhead_4way_hub map: T1 @@ -106,7 +106,7 @@ odometer_resolution: 3.15786 led_white_pin: canvas:LED1_WHITE led_red_pin: canvas:LED1_RED -[AFC_canvas_lane canvas_lane2] +[AFC_canvas_lane CANVAS_3] unit: CANVAS_1:3 hub: toolhead_4way_hub map: T2 @@ -118,7 +118,7 @@ odometer_resolution: 3.15786 led_white_pin: canvas:LED2_WHITE led_red_pin: canvas:LED2_RED -[AFC_canvas_lane canvas_lane3] +[AFC_canvas_lane CANVAS_4] unit: CANVAS_1:4 hub: toolhead_4way_hub map: T3 @@ -131,36 +131,48 @@ led_white_pin: canvas:LED3_WHITE led_red_pin: canvas:LED3_RED [gcode_macro CUT] +variable_retract_length: 21 +variable_pushback_length: 16 gcode: + {% set self = printer["gcode_macro CUT"] %} + {% set retract_length = self.retract_length|float %} + {% set pushback_length = self.pushback_length|float %} + {% if "xyz" not in printer.toolhead.homed_axes %} G28 ; Home if necessary {% endif %} G90 ; Absolute positioning + M83 ; E relative G0 Z14.5 F600 ; Ensure we have some Z clearance + + G1 E-{retract_length} F600 ; Retract filament + G0 Y30 F15000 ; Get some clearance for Y G0 X255 F8000 ; Move to cutting position G0 Y3 F1200 ; Cut G0 Y30 F8000 ; Retract from cutting + + G1 E{pushback_length} F600 ; Push back filament + M400 ; Wait for movements to complete [gcode_macro POOP] gcode: - {% if params.PURGE_LENGTH %} - M83 - G1 E{params.PURGE_LENGTH} F600 - {% endif %} + {% set purge_length = params.PURGE_LENGTH|default(100, true)|int %} + M83 + G1 E{purge_length} F600 # Wipe Nozzle, from new firmware printer.cfg [gcode_macro M729] gcode: - SAVE_GCODE_STATE NAME=m729_saved_state - G90 ; Absolute positioning + M83 ; E relative ; === Approach === G1 X202 F6000 ; Move to wiper X start position G1 Y264.5 F6000 ; Move to wiper Y position (top of wiper) + G1 E-2 F600 ; === Pass 1: X sweeps at top of wiper === G1 X165 F8000 ; Wipe left (165 vs 170 in old — 5mm wider sweep) @@ -175,11 +187,10 @@ gcode: ; === Extract === G1 Y264.5 F1200 ; Slow lift up through wiper (clean exit) + G1 E2 F600 M400 ; Wait for all moves to complete - RESTORE_GCODE_STATE NAME=m729_saved_state - [AFC] VarFile: /etc/klipper/config/AFC.var default_material_type: PLA From 9aca024d96e0bd1d56c3909d37487c33e4b8ce3e Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Thu, 14 May 2026 23:38:30 +0200 Subject: [PATCH 14/42] Speed --- .../recipes-data/klipper-afc-addon/files/canvas.cfg | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg index 2d3afdde..5c5e7d95 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg @@ -78,7 +78,7 @@ tangle_pin: !hotend:PB0 toolhead_cover_pin: hotend:PC4 cutter_pin: !hotend:PC5 short_moves_speed: 30 -long_moves_speed: 70 +long_moves_speed: 100 prep_distance: 120 prep_speed: 60 @@ -154,8 +154,6 @@ gcode: G0 Y30 F8000 ; Retract from cutting G1 E{pushback_length} F600 ; Push back filament - - M400 ; Wait for movements to complete [gcode_macro POOP] gcode: @@ -188,8 +186,6 @@ gcode: ; === Extract === G1 Y264.5 F1200 ; Slow lift up through wiper (clean exit) G1 E2 F600 - - M400 ; Wait for all moves to complete [AFC] VarFile: /etc/klipper/config/AFC.var From 00496c492ec4c6ccbe781d881ef96f906de80876 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Fri, 15 May 2026 00:45:26 +0200 Subject: [PATCH 15/42] Bump AFC version --- meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb index 4a25dde6..0fdadaf0 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb @@ -12,7 +12,7 @@ SRC_URI = " \ file://canvas.cfg \ " -SRCREV = "431e42b71e90ce2b3a3c49b973d6e64b0ae1e5fd" +SRCREV = "ae53a76cf945bf6cf1b35b0a0c624f44cc3bfa0d" S = "${WORKDIR}/git" From 4c1318efc982b0c3293ae39a401f8c3280fc0143 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Mon, 18 May 2026 22:22:43 +0200 Subject: [PATCH 16/42] Add stock bootloader flasher, add way to auto-upgrade canvas firmware --- .../katapult/canvas-bootloader-stock_1.0.0.bb | 25 ++++++ .../files/canvas-cc1-bootloader-stock.bin | Bin 0 -> 49152 bytes .../files/klipper-firmware-canvas-init-d | 72 ++++++++++++++++++ .../kalico-firmware-canvas_2026.02.00.bb | 13 +++- .../config-manager/files/config_manager.py | 3 + .../config-manager/files/default.conf | 3 + .../update-scripts/files/restore-mcu-firmware | 11 +++ .../update-scripts/update-scripts_0.1.0.bb | 1 + 8 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 meta-opencentauri/recipes-apps/katapult/canvas-bootloader-stock_1.0.0.bb create mode 100644 meta-opencentauri/recipes-apps/katapult/files/canvas-cc1-bootloader-stock.bin create mode 100644 meta-opencentauri/recipes-apps/klipper/files/klipper-firmware-canvas-init-d diff --git a/meta-opencentauri/recipes-apps/katapult/canvas-bootloader-stock_1.0.0.bb b/meta-opencentauri/recipes-apps/katapult/canvas-bootloader-stock_1.0.0.bb new file mode 100644 index 00000000..86c35295 --- /dev/null +++ b/meta-opencentauri/recipes-apps/katapult/canvas-bootloader-stock_1.0.0.bb @@ -0,0 +1,25 @@ +require katapult_${PV}.inc + +SUMMARY = "Stock Canvas Bootloader Deployer" +DESCRIPTION = "Builds the Katapult deployer binary for reverting the canvas bootloader to stock." + +SRC_URI += " \ + file://config.canvas \ + file://canvas-cc1-bootloader-stock.bin \ +" + +DEPENDS += "gcc-arm-none-eabi-native" + +EXTRA_OEMAKE += " \ + KCONFIG_CONFIG=../config.canvas \ + DEPLOYER_PAYLOAD=../canvas-cc1-bootloader-stock.bin \ +" + +do_install() { + install -d ${D}/lib/firmware + install -m 0644 ${S}/out/deployer.bin ${D}/lib/firmware/katapult-deployer-stock-canvas.bin +} + +FILES:${PN} = " \ + /lib/firmware/katapult-deployer-stock-canvas.bin \ +" \ No newline at end of file diff --git a/meta-opencentauri/recipes-apps/katapult/files/canvas-cc1-bootloader-stock.bin b/meta-opencentauri/recipes-apps/katapult/files/canvas-cc1-bootloader-stock.bin new file mode 100644 index 0000000000000000000000000000000000000000..fddcc2494be979a628ebfaed149f7d00f650e096 GIT binary patch literal 49152 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�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 z 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 literal 0 HcmV?d00001 diff --git a/meta-opencentauri/recipes-apps/klipper/files/klipper-firmware-canvas-init-d b/meta-opencentauri/recipes-apps/klipper/files/klipper-firmware-canvas-init-d new file mode 100644 index 00000000..5d4a4a52 --- /dev/null +++ b/meta-opencentauri/recipes-apps/klipper/files/klipper-firmware-canvas-init-d @@ -0,0 +1,72 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: klipper-firmware-canvas +# Required-Start: $local_fs $remote_fs $network +# Required-Stop: $local_fs $remote_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Start OpenCentauri canvas firmware +### END INIT INFO + +FIRMWARE="/lib/firmware/klipper-canvas.bin" +FIRMWARE_VERSION="/lib/firmware/klipper-canvas.bin.ver" +KATAPULT_DEPLOYER="/lib/firmware/katapult-deployer-canvas.bin" +INSTALLED_VERSION="/etc/canvas.ver" +SERIALPORT="/dev/serial/by-path/platform-4200000.usb-usb-0:1.4:1.0" +FLASH_ENABLED="$(config-manager flash_mcu elegoo_canvas 2>/dev/null)" + +update(){ + if flashtool -d $SERIALPORT -f $FIRMWARE; then + cp $FIRMWARE_VERSION $INSTALLED_VERSION + sleep 3 + echo "Firmware flashed and version recorded." + else + echo "Firmware flash failed, version not updated." + return 1 + fi +} + +case "$1" in + start) + echo "Starting klipper-firmware-canvas..." + + if [ "$FLASH_ENABLED" != "True" ]; then + echo "Skipping canvas firmware flash because flash_mcu.elegoo_canvas is not enabled." + exit 0 + fi + + if [ -f "$INSTALLED_VERSION" ] && [ "$(cat $INSTALLED_VERSION)" = "$(cat $FIRMWARE_VERSION)" ]; then + echo "Firmware is already up to date." + exit 0 + else + echo "Firmware version mismatch or not installed, flashing firmware..." + if update; then + exit 0 + else + echo "Attempting bootloader upgrade..." + if mcu-flasher --canvas --firmware $KATAPULT_DEPLOYER --no-wait $SERIALPORT --firmware-version 6.4.9; then + sleep 3 + echo "Bootloader deployer flashed successfully, clearing installed version." + rm -f $INSTALLED_VERSION + update + else + echo "Cannot update bootloader." + exit 1 + fi + fi + fi + ;; + status) + if [ "$FLASH_ENABLED" = "True" ]; then + echo "klipper-firmware-canvas flashing is enabled" + else + echo "klipper-firmware-canvas flashing is disabled" + fi + ;; + *) + echo "Usage: $0 {start|status}" + exit 1 + ;; +esac + +exit 0 \ No newline at end of file diff --git a/meta-opencentauri/recipes-apps/klipper/kalico-firmware-canvas_2026.02.00.bb b/meta-opencentauri/recipes-apps/klipper/kalico-firmware-canvas_2026.02.00.bb index da2436d3..552daeee 100644 --- a/meta-opencentauri/recipes-apps/klipper/kalico-firmware-canvas_2026.02.00.bb +++ b/meta-opencentauri/recipes-apps/klipper/kalico-firmware-canvas_2026.02.00.bb @@ -1,11 +1,12 @@ require kalico_${PV}.inc -#inherit update-rc.d +inherit update-rc.d SUMMARY = "Kalico 3D Printer Firmware" DESCRIPTION = "Klipper, but Limitless" SRC_URI += " \ file://config.canvas \ + file://klipper-firmware-canvas-init-d \ " PR = "r3" @@ -14,6 +15,7 @@ DEPENDS += "gcc-arm-none-eabi-native" RDEPENDS:${PN} = " \ flashtool \ canvas-bootloader-upgrade \ + config-manager \ mcu-flasher \ " @@ -21,16 +23,21 @@ RPROVIDES:${PN} += "klipper-firmware-canvas" EXTRA_OEMAKE += "KCONFIG_CONFIG=../config.canvas" -#INITSCRIPT_NAME = "klipper-firmware-canvas" -#INITSCRIPT_PARAMS = "defaults 94 4" +INITSCRIPT_NAME = "klipper-firmware-canvas" +INITSCRIPT_PARAMS = "defaults 94 4" do_install() { install -d ${D}/lib/firmware cp -r ${S}/out/klipper.bin ${D}/lib/firmware/klipper-canvas.bin echo "${SRCREV}-${PR}" > ${D}/lib/firmware/klipper-canvas.bin.ver + + install -d ${D}${sysconfdir}/init.d + cp ${WORKDIR}/klipper-firmware-canvas-init-d ${D}${sysconfdir}/init.d/klipper-firmware-canvas + chmod 0755 ${D}${sysconfdir}/init.d/klipper-firmware-canvas } FILES:${PN} = " \ /lib/firmware/klipper-canvas.bin \ /lib/firmware/klipper-canvas.bin.ver \ + ${sysconfdir}/init.d/klipper-firmware-canvas \ " diff --git a/meta-opencentauri/recipes-data/config-manager/files/config_manager.py b/meta-opencentauri/recipes-data/config-manager/files/config_manager.py index 58e5c524..10122831 100644 --- a/meta-opencentauri/recipes-data/config-manager/files/config_manager.py +++ b/meta-opencentauri/recipes-data/config-manager/files/config_manager.py @@ -11,6 +11,9 @@ 'release': ['stable', 'nightly'], 'check_for_updates': ['True', 'False'], }, + 'flash_mcu': { + 'elegoo_canvas': ['True', 'False'], + }, 'klipper': { 'sync_camera_led_to_chamber_led': ['True', 'False'], 'camera_led_default_on': ['True', 'False'], diff --git a/meta-opencentauri/recipes-data/config-manager/files/default.conf b/meta-opencentauri/recipes-data/config-manager/files/default.conf index 0835ae1f..54588cf0 100644 --- a/meta-opencentauri/recipes-data/config-manager/files/default.conf +++ b/meta-opencentauri/recipes-data/config-manager/files/default.conf @@ -16,6 +16,9 @@ release = stable # Check for updates automatically on startup. Options: True, False. check_for_updates = True +[flash_mcu] +elegoo_canvas = False + [klipper] # Keep the camera LED state in sync with the chamber LED. sync_camera_led_to_chamber_led = False diff --git a/meta-opencentauri/recipes-data/update-scripts/files/restore-mcu-firmware b/meta-opencentauri/recipes-data/update-scripts/files/restore-mcu-firmware index 32126373..4c555ca5 100644 --- a/meta-opencentauri/recipes-data/update-scripts/files/restore-mcu-firmware +++ b/meta-opencentauri/recipes-data/update-scripts/files/restore-mcu-firmware @@ -2,6 +2,8 @@ set -e +FLASH_CANVAS="$(config-manager flash_mcu elegoo_canvas 2>/dev/null)" + SERIALPORT_BED="/dev/ttyS4" VERSION_BED="/etc/bed.ver" STOCK_FW_BED="/lib/firmware/katapult-deployer-stock-bed.bin" @@ -10,6 +12,10 @@ SERIALPORT_TOOLHEAD="/dev/serial/by-path/platform-4101400.usb-usb-0:1:1.0" VERSION_TOOLHEAD="/etc/toolhead.ver" STOCK_FW_TOOLHEAD="/lib/firmware/katapult-deployer-stock-toolhead.bin" +SERIALPORT_CANVAS="/dev/serial/by-path/platform-4200000.usb-usb-0:1.4:1.0" +VERSION_CANVAS="/etc/canvas.ver" +STOCK_FW_CANVAS="/lib/firmware/katapult-deployer-stock-canvas.bin" + # Flash stock bootloader service klipper stop flashtool -d $SERIALPORT_BED -b 250000 -r @@ -18,6 +24,11 @@ rm -f $VERSION_BED flashtool -d $SERIALPORT_TOOLHEAD -f $STOCK_FW_TOOLHEAD rm -f $VERSION_TOOLHEAD +if [ "$FLASH_CANVAS" = "True" ]; then + flashtool -d $SERIALPORT_CANVAS -f $STOCK_FW_CANVAS + rm -f $VERSION_CANVAS +fi + # Delete board-resource files cd /board-resource rm -f *.log* diff --git a/meta-opencentauri/recipes-data/update-scripts/update-scripts_0.1.0.bb b/meta-opencentauri/recipes-data/update-scripts/update-scripts_0.1.0.bb index 84882769..56e4b53b 100644 --- a/meta-opencentauri/recipes-data/update-scripts/update-scripts_0.1.0.bb +++ b/meta-opencentauri/recipes-data/update-scripts/update-scripts_0.1.0.bb @@ -18,6 +18,7 @@ RDEPENDS:${PN} = " \ flashtool \ toolhead-bootloader-stock \ bed-bootloader-stock \ + canvas-bootloader-stock \ config-manager \ " From 261c3bd59287facee53dbfe9364a135828be047b Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Tue, 19 May 2026 00:17:28 +0200 Subject: [PATCH 17/42] oops upload the correct configs --- .../recipes-apps/katapult/files/config.canvas | 14 ++++++++++---- .../recipes-apps/klipper/files/config.canvas | 10 +++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/meta-opencentauri/recipes-apps/katapult/files/config.canvas b/meta-opencentauri/recipes-apps/katapult/files/config.canvas index b89231ca..f73c2945 100644 --- a/meta-opencentauri/recipes-apps/katapult/files/config.canvas +++ b/meta-opencentauri/recipes-apps/katapult/files/config.canvas @@ -31,13 +31,13 @@ CONFIG_MACH_STM32F401=y # CONFIG_MACH_STM32G431 is not set # CONFIG_MACH_STM32H723 is not set # CONFIG_MACH_STM32H743 is not set +# CONFIG_MACH_STM32H750 is not set CONFIG_MACH_STM32F4=y CONFIG_HAVE_STM32_USBOTG=y CONFIG_STM32_DFU_ROM_ADDRESS=0x1fff0000 # CONFIG_STM32_FLASH_START_0000 is not set # CONFIG_STM32_FLASH_START_8000 is not set -CONFIG_STM32_FLASH_START_C000=y -# CONFIG_STM32_FLASH_START_10000 is not set +CONFIG_STM32_FLASH_START_10000=y # CONFIG_STM32_FLASH_START_4000 is not set # CONFIG_STM32_CLOCK_REF_8M is not set # CONFIG_STM32_CLOCK_REF_12M is not set @@ -54,7 +54,7 @@ CONFIG_STM32_USB_PA11_PA12=y # CONFIG_STM32_SERIAL_USART1_ALT_PB7_PB6 is not set # CONFIG_STM32_SERIAL_USART2 is not set # CONFIG_STM32_SERIAL_USART2_ALT_PD6_PD5 is not set -CONFIG_STM32_APP_START_C000=y +CONFIG_STM32_APP_START_10000=y # CONFIG_STM32_APP_START_8000 is not set # CONFIG_STM32_APP_START_4000 is not set CONFIG_USBSERIAL=y @@ -70,10 +70,16 @@ CONFIG_USB_SERIAL_NUMBER="12345" # end of USB ids CONFIG_CANBUS_FREQUENCY=1000000 +CONFIG_HAVE_OPTIMIZE_OS=y +# CONFIG_HAVE_OPTIMIZE_O2 is not set +# CONFIG_HAVE_OPTIMIZE_OZ is not set +# CONFIG_HAVE_OPTIMIZE_OG is not set +# CONFIG_HAVE_OPTIMIZE_O0 is not set +CONFIG_OPTIMIZE_FLAG="-Os" CONFIG_INITIAL_PINS="" CONFIG_ENABLE_DOUBLE_RESET=y # CONFIG_ENABLE_BUTTON is not set # CONFIG_ENABLE_LED is not set CONFIG_BUILD_DEPLOYER=y CONFIG_HAVE_CHIPID=y -CONFIG_KATAPULT_VERSION="b0bf421-dirty" +CONFIG_KATAPULT_VERSION="v0.0.1-113-gec59b9b-dirty" diff --git a/meta-opencentauri/recipes-apps/klipper/files/config.canvas b/meta-opencentauri/recipes-apps/klipper/files/config.canvas index 88a28170..fa5477af 100644 --- a/meta-opencentauri/recipes-apps/klipper/files/config.canvas +++ b/meta-opencentauri/recipes-apps/klipper/files/config.canvas @@ -53,8 +53,8 @@ CONFIG_MACH_STM32F4=y CONFIG_HAVE_STM32_USBOTG=y CONFIG_STM32_DFU_ROM_ADDRESS=0x1fff0000 # CONFIG_STM32_FLASH_START_8000 is not set -CONFIG_STM32_FLASH_START_C000=y -# CONFIG_STM32_FLASH_START_10000 is not set +# CONFIG_STM32_FLASH_START_C000 is not set +CONFIG_STM32_FLASH_START_10000=y # CONFIG_STM32_FLASH_START_4000 is not set # CONFIG_STM32_FLASH_START_0000 is not set # CONFIG_STM32_CLOCK_REF_8M is not set @@ -104,6 +104,7 @@ CONFIG_WANT_MPU9250=y CONFIG_WANT_ICM20948=y CONFIG_WANT_THERMOCOUPLE=y CONFIG_WANT_HX71X=y +CONFIG_WANT_HX711S=y CONFIG_WANT_ADS1220=y CONFIG_WANT_LDC1612=y # CONFIG_WANT_SENSOR_ANGLE is not set @@ -111,6 +112,9 @@ CONFIG_WANT_GPIO_SPI=y CONFIG_WANT_GPIO_ADC=y CONFIG_WANT_GPIO_I2C=y CONFIG_NEED_SENSOR_BULK=y +CONFIG_NEED_LOAD_CELL_PROBE=y +CONFIG_NEED_LOAD_CELL_FUSION=y +CONFIG_NEED_SOS_FILTER=y # CONFIG_WANT_OPTIMIZE_SIZE is not set # @@ -147,7 +151,7 @@ CONFIG_CANBUS_FREQUENCY=1000000 CONFIG_INLINE_STEPPER_HACK=y CONFIG_HAVE_STEPPER_OPTIMIZED_BOTH_EDGE=y CONFIG_WANT_STEPPER_OPTIMIZED_BOTH_EDGE=y -CONFIG_INITIAL_PINS="PC8" +CONFIG_INITIAL_PINS="" CONFIG_HAVE_GPIO=y CONFIG_HAVE_GPIO_ADC=y CONFIG_HAVE_GPIO_SPI=y From 5392fe8e80304c8e5347bb9b9f9d2bfb6bd16392 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sun, 24 May 2026 02:38:15 +0200 Subject: [PATCH 18/42] Improve macros for AFC --- .../recipes-data/klipper-afc-addon/afc_0.1.bb | 2 +- .../klipper-afc-addon/files/canvas.cfg | 30 +++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb index 0fdadaf0..6479af3b 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb @@ -12,7 +12,7 @@ SRC_URI = " \ file://canvas.cfg \ " -SRCREV = "ae53a76cf945bf6cf1b35b0a0c624f44cc3bfa0d" +SRCREV = "6aed17d9fb28d90c567efc3a296df24b76e9b363" S = "${WORKDIR}/git" diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg index 5c5e7d95..dff5c850 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg @@ -135,6 +135,7 @@ variable_retract_length: 21 variable_pushback_length: 16 gcode: {% set self = printer["gcode_macro CUT"] %} + {% set z_max = svar.z_maximum_lifting_distance|int %} {% set retract_length = self.retract_length|float %} {% set pushback_length = self.pushback_length|float %} @@ -142,9 +143,16 @@ gcode: G28 ; Home if necessary {% endif %} + {% if (printer.gcode_move.position.z + 4) < z_max %} + G91 + G1 Z+4 F3000 + {% else %} + G90 + G1 Z{z_max} F3000 + {% endif %} + G90 ; Absolute positioning M83 ; E relative - G0 Z14.5 F600 ; Ensure we have some Z clearance G1 E-{retract_length} F600 ; Retract filament @@ -187,6 +195,22 @@ gcode: G1 Y264.5 F1200 ; Slow lift up through wiper (clean exit) G1 E2 F600 +[gcode_macro KICK] +gcode: + G90 ; Absolute positioning + M83 ; E relative + + G1 X202 Y264.5 F6000 ; Move to tray position + G1 E-2 F600 ; Retract slightly to reduce pressure during kick + M106 S255 ; Full blast fan to solidify plastic + G4 P3000 ; Wait 3 seconds for fan to do its work + G1 X182 F8000 ; Kick left + M106 S0 ; Turn off fan + + ; Exit + G1 Y245 F6000 + G1 E2 F600 ; Push back filament after kick + [AFC] VarFile: /etc/klipper/config/AFC.var default_material_type: PLA @@ -230,10 +254,10 @@ poop_cmd: POOP # Kick Settings kick: False +kick_cmd: KICK # Wipe Settings -wipe: True -wipe_cmd: M729 +wipe: False # Form Tip Settings form_tip: False \ No newline at end of file From d3c6c21a135d7e16a86fe29da266513ef46a6ae6 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Mon, 25 May 2026 11:36:19 +0200 Subject: [PATCH 19/42] Remove toolhead cover pin --- .../recipes-data/klipper-afc-addon/files/canvas.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg index dff5c850..2785c5d1 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg @@ -75,7 +75,6 @@ extruder: extruder enable_9v_pin: canvas:ENABLE_9V enable_24v_pin: canvas:ENABLE_24V tangle_pin: !hotend:PB0 -toolhead_cover_pin: hotend:PC4 cutter_pin: !hotend:PC5 short_moves_speed: 30 long_moves_speed: 100 From 85c6bef814a99b91f7d7936c815a4503b5b28644 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Wed, 27 May 2026 17:41:24 +0200 Subject: [PATCH 20/42] oops --- .../recipes-data/klipper-afc-addon/files/canvas.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg index 2785c5d1..f497cba6 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg @@ -134,7 +134,7 @@ variable_retract_length: 21 variable_pushback_length: 16 gcode: {% set self = printer["gcode_macro CUT"] %} - {% set z_max = svar.z_maximum_lifting_distance|int %} + {% set z_max = 256 %} {% set retract_length = self.retract_length|float %} {% set pushback_length = self.pushback_length|float %} From e42059a7b2bd51336f5045f426a5f9b13b5b36ab Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sat, 27 Jun 2026 15:31:17 +0200 Subject: [PATCH 21/42] Untested: Adapt AFC branch to new macros --- .../recipes-apps/klipper/files/macros.cfg | 82 +++++++++++-- .../files/build-klipper-var-config.sh | 2 + .../config-manager/files/config_manager.py | 1 + .../config-manager/files/default.conf | 2 + .../klipper-afc-addon/files/afc.cfg | 5 + .../klipper-afc-addon/files/canvas.cfg | 109 ++++++++++-------- 6 files changed, 142 insertions(+), 59 deletions(-) diff --git a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg index 8be0281e..3e6d119b 100644 --- a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg +++ b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg @@ -17,7 +17,8 @@ gcode: {% set target_bed = params.BED|int %} {% set target_extruder = params.EXTRUDER|int %} {% set target_chamber = params.CHAMBER|int %} - {% set sensor = printer['filament_switch_sensor filament_sensor'] %} + {% set tool = params.TOOL|default(0)|int %} + {% set sensor = printer['filament_switch_sensor filament_sensor'] if 'filament_switch_sensor filament_sensor' in printer else printer["filament_switch_sensor extruder_tool_start"] %} {% set global = printer['gcode_macro _global_var'] %} {% set settings = printer['gcode_macro _COSMOS_SETTINGS']|default({}) %} {% set heatsoak = settings.heatsoak|default(1)|float %} @@ -96,11 +97,25 @@ gcode: {% endif %} {% endif %} - {% if adaptive_purge %} - SMART_PARK + {% if "AFC" in printer %} + # TODO: Improve + M104 S{target_extruder} + MOVE_TO_TRAY + M109 S{target_extruder} + + T{tool} + + {% if adaptive_purge %} + SMART_PARK + {% endif %} + {% else %} + {% if adaptive_purge %} + SMART_PARK + {% endif %} + + M109 S{target_extruder} {% endif %} - M109 S{target_extruder} {% if adaptive_purge %} LINE_PURGE {% endif %} @@ -113,10 +128,11 @@ gcode: {% set global = printer['gcode_macro _global_var'] %} {% set settings = printer['gcode_macro _COSMOS_SETTINGS']|default({}) %} {% set z_max = global.z_maximum_lifting_distance|int %} + {% set unload_after_print = settings.unload_after_print|default('false')|lower == 'true' %} {% set z_ideal = settings.z_ideal_lifting_distance|default(170)|int %} {% set toolhead_led = settings.toolhead_led|default('false')|lower == 'true' %} {% set e_mintemp = printer.configfile.settings['extruder'].min_extrude_temp %} - {% set sensor = printer['filament_switch_sensor filament_sensor'] %} + {% set sensor = printer['filament_switch_sensor filament_sensor'] if 'filament_switch_sensor filament_sensor' in printer else printer["filament_switch_sensor extruder_tool_start"] %} {% if printer.toolhead.homed_axes|lower == "xyz" %} G91 @@ -140,6 +156,14 @@ gcode: G1 Z+{(z_max - printer.gcode_move.position.z)} F3000 {% endif %} + {% if sensor.enabled and sensor.filament_detected %} # TODO: This is wrong. We should assume there's filament if the filament sensor is disabled + {% if (printer.extruder.target != 0 and (printer.extruder.temperature + 5) >= printer.extruder.target and printer.extruder.temperature >= e_mintemp) %} + {% if unload_after_print %} + UNLOAD_FILAMENT + {% endif %} + {% endif %} + {% endif %} + MOVE_TO_TRAY {% endif %} TURN_OFF_FANS @@ -168,9 +192,10 @@ rename_existing: PAUSE_BASE gcode: {% set global = printer['gcode_macro _global_var'] %} {% set state = params.STATE|default('normal') %} + {% set emergency = params.EMERGENCY|default('false')|lower == 'true' %} {% set retract_amount = global.retract_amount|float %} {% set z_lift_max = global.z_maximum_lifting_distance|int %} - {% set sensor = printer['filament_switch_sensor filament_sensor'] %} + {% set sensor = printer['filament_switch_sensor filament_sensor'] if 'filament_switch_sensor filament_sensor' in printer else printer["filament_switch_sensor extruder_tool_start"] %} {% set pos = printer.gcode_move.position %} {% if printer.pause_resume.is_paused %} @@ -199,6 +224,12 @@ gcode: G90 {% endif %} + {% if emergency %} + SET_DISPLAY_TEXT MSG="Emergency pause. Please check the printer and resume when ready." + M104 S{global.extruder_temp.probing} + RETURN + {% endif %} + {% if state == 'filament_change' %} SET_DISPLAY_TEXT MSG="Paused for filament change" UNLOAD_FILAMENT @@ -218,7 +249,7 @@ gcode: rename_existing: RESUME_BASE gcode: {% set global = printer['gcode_macro _global_var'] %} - {% set sensor = printer['filament_switch_sensor filament_sensor'] %} + {% set sensor = printer['filament_switch_sensor filament_sensor'] if 'filament_switch_sensor filament_sensor' in printer else printer["filament_switch_sensor extruder_tool_start"] %} {% set extruder_target_temp = printer['gcode_macro _global_var'].target_extruder %} # If filament sensor is enabled require filament present before resuming @@ -331,8 +362,11 @@ gcode: M400 ; Wait for movements to complete [gcode_macro KICK] +variable_end_kick_y: 250 gcode: {% set global = printer['gcode_macro _global_var'] %} + {% set self = printer["gcode_macro KICK"] %} + {% set end_kick_y = self.end_kick_y|float %} {% set retract_amount = global.retract_amount|float %} {% if "xyz" not in printer.toolhead.homed_axes %} @@ -353,7 +387,7 @@ gcode: G1 X186 F8000 ; Kick left M106 S0 ; Turn off fan - G1 Y250 F6000 ; Exit + G1 Y{end_kick_y} F6000 ; Exit M400 ; Wait for movements to complete @@ -464,16 +498,48 @@ gcode: M400 ; Wait for movements to complete [gcode_macro CUT_FILAMENT] +variable_retract_length: 21 +variable_pushback_length: 16 gcode: + {% set self = printer["gcode_macro CUT_FILAMENT"] %} + {% set global = printer["gcode_macro _global_var"] %} + {% set z_max = global.z_maximum_lifting_distance|int %} + {% set retract_length = self.retract_length|float %} + {% set pushback_length = self.pushback_length|float %} + {% if "xyz" not in printer.toolhead.homed_axes %} G28 ; Home if necessary {% endif %} + # Raise Z a little bit during a print + {% if printer.print_stats.state == "printing" %} + {% if (printer.gcode_move.position.z + 4) < z_max %} + G91 + G1 Z+4 F3000 + {% else %} + G90 + G1 Z{z_max} F3000 + {% endif %} + {% endif %} + G90 + M83 ; E relative + + {% if printer.extruder.target >= 180 and printer.extruder.can_extrude %} + G1 E-{retract_length} F300 ; Retract filament + {% endif %} + + # TODO: This can probably be faster G0 Y20 F8000 G0 X255 F8000 G0 Y4 F1200 G0 Y20 F1200 + + {% if printer.extruder.target >= 180 and printer.extruder.can_extrude %} + G1 E{pushback_length} F300 ; Push back filament + G1 E-{pushback_length} F600 ; Retract from pushback + {% endif %} + M400 [homing_override] diff --git a/meta-opencentauri/recipes-data/config-manager/files/build-klipper-var-config.sh b/meta-opencentauri/recipes-data/config-manager/files/build-klipper-var-config.sh index a980a764..2273da33 100644 --- a/meta-opencentauri/recipes-data/config-manager/files/build-klipper-var-config.sh +++ b/meta-opencentauri/recipes-data/config-manager/files/build-klipper-var-config.sh @@ -15,6 +15,7 @@ camera_led_default_on=$(get_config_value camera_led_default_on) heatsoak=$(get_config_value heatsoak) adaptive_mesh=$(get_config_value adaptive_mesh) adaptive_purge=$(get_config_value adaptive_purge) +unload_after_print=$(get_config_value unload_after_print) nozzle_z_homing=$(get_config_value nozzle_z_homing) z_ideal_lifting_distance=$(get_config_value z_ideal_lifting_distance) full_calibrate_hotend_temperature=$(get_config_value full_calibrate_hotend_temperature) @@ -34,6 +35,7 @@ variable_camera_led_default_on: ${camera_led_default_on} variable_heatsoak: ${heatsoak} variable_adaptive_mesh: ${adaptive_mesh} variable_adaptive_purge: ${adaptive_purge} +variable_unload_after_print: ${unload_after_print} variable_nozzle_z_homing: ${nozzle_z_homing} variable_z_ideal_lifting_distance: ${z_ideal_lifting_distance} variable_full_calibrate_hotend_temperature: ${full_calibrate_hotend_temperature} diff --git a/meta-opencentauri/recipes-data/config-manager/files/config_manager.py b/meta-opencentauri/recipes-data/config-manager/files/config_manager.py index 10122831..b64a6840 100644 --- a/meta-opencentauri/recipes-data/config-manager/files/config_manager.py +++ b/meta-opencentauri/recipes-data/config-manager/files/config_manager.py @@ -21,6 +21,7 @@ 'heatsoak': [str(x / 10) for x in range(100)] + [str(x) for x in range(31)], 'adaptive_mesh': ['True', 'False'], 'adaptive_purge': ['True', 'False'], + 'unload_after_print': ['True', 'False'], 'nozzle_z_homing': ['True', 'False'], 'z_ideal_lifting_distance': [str(i) for i in range(257)], 'full_calibrate_hotend_temperature': [str(i) for i in range(200, 301)], diff --git a/meta-opencentauri/recipes-data/config-manager/files/default.conf b/meta-opencentauri/recipes-data/config-manager/files/default.conf index 54588cf0..842ff819 100644 --- a/meta-opencentauri/recipes-data/config-manager/files/default.conf +++ b/meta-opencentauri/recipes-data/config-manager/files/default.conf @@ -32,6 +32,8 @@ heatsoak = 1 adaptive_mesh = False # Prime the nozzle near the print area before each print. adaptive_purge = True +# Unload filament automatically when a print finishes. +unload_after_print = False # Use the nozzle and load cell probe for Z homing. nozzle_z_homing = False # Drop the bed to this height in millimeters when a print finishes. Range: 0-256. diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/afc.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/afc.cfg index a5044a72..7d47978f 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/afc.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/afc.cfg @@ -1,3 +1,8 @@ +# ----- +# PLEASE DO NOT EDIT THIS FILE. EDIT printer.cfg INSTEAD. +# UPDATES WILL RESET THIS FILE. YOUR EDITS WILL BE LOST WHEN UPDATING. +# ----- + # Eject Loaded Lane [gcode_macro BT_TOOL_UNLOAD] description: Unloads the currently loaded lane diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg index f497cba6..df13c40a 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg @@ -1,3 +1,8 @@ +# ----- +# PLEASE DO NOT EDIT THIS FILE. EDIT printer.cfg INSTEAD. +# UPDATES WILL RESET THIS FILE. YOUR EDITS WILL BE LOST WHEN UPDATING. +# ----- + [include afc.cfg] [mcu canvas] @@ -129,55 +134,40 @@ odometer_resolution: 3.15786 led_white_pin: canvas:LED3_WHITE led_red_pin: canvas:LED3_RED -[gcode_macro CUT] -variable_retract_length: 21 -variable_pushback_length: 16 +[gcode_macro POOP] gcode: - {% set self = printer["gcode_macro CUT"] %} - {% set z_max = 256 %} - {% set retract_length = self.retract_length|float %} - {% set pushback_length = self.pushback_length|float %} + {% set purge_length = params.PURGE_LENGTH|default(100, true)|int %} - {% if "xyz" not in printer.toolhead.homed_axes %} - G28 ; Home if necessary + {% if printer.extruder.target < 180 or not printer.extruder.can_extrude %} + {action_raise_error("Extruder temperature too low for purging. Please heat the extruder to at least 180°C.")} {% endif %} - {% if (printer.gcode_move.position.z + 4) < z_max %} - G91 - G1 Z+4 F3000 - {% else %} - G90 - G1 Z{z_max} F3000 - {% endif %} + MOVE_TO_TRAY + M83 + RESPOND TYPE=command MSG="Purging {purge_length}mm of filament" + G1 E{purge_length} F300 - G90 ; Absolute positioning - M83 ; E relative - - G1 E-{retract_length} F600 ; Retract filament - - G0 Y30 F15000 ; Get some clearance for Y - G0 X255 F8000 ; Move to cutting position - G0 Y3 F1200 ; Cut - G0 Y30 F8000 ; Retract from cutting +[gcode_macro CLEAN_NOZZLE] +gcode: + {% set global = printer['gcode_macro _global_var'] %} + {% set toolhead_target = printer.extruder.target|int %} + BED_MESH_CLEAR + M104 S{global.extruder_temp.cleaning} + {% if printer.toolhead.homed_axes != "xyz" %} + G28 + {% endif %} + G90 + M83 - G1 E{pushback_length} F600 ; Push back filament + MOVE_TO_TRAY + M109 S{global.extruder_temp.cleaning} -[gcode_macro POOP] -gcode: - {% set purge_length = params.PURGE_LENGTH|default(100, true)|int %} - M83 - G1 E{purge_length} F600 + M106 S255 -# Wipe Nozzle, from new firmware printer.cfg -[gcode_macro M729] -gcode: - G90 ; Absolute positioning - M83 ; E relative - ; === Approach === G1 X202 F6000 ; Move to wiper X start position G1 Y264.5 F6000 ; Move to wiper Y position (top of wiper) - G1 E-2 F600 + #G1 E-2 F600 ; === Pass 1: X sweeps at top of wiper === G1 X165 F8000 ; Wipe left (165 vs 170 in old — 5mm wider sweep) @@ -192,23 +182,40 @@ gcode: ; === Extract === G1 Y264.5 F1200 ; Slow lift up through wiper (clean exit) - G1 E2 F600 + #G1 E2 F600 + + M104 S{toolhead_target} + M107 + M400 [gcode_macro KICK] +variable_end_kick_y: 245 + +[gcode_button toolhead_front_cover_detection] +pin: hotend:PC4 +press_gcode: + RESPOND TYPE=echo MSG="Front cover has fallen off!" + + {% if printer.print_stats.state == "printing" %} + PAUSE EMERGENCY=true + {% endif %} +release_gcode: + RESPOND TYPE=echo MSG="Front cover has been re-attached" + +[!gcode_macro LOAD_FILAMENT] + +[gcode_macro UNLOAD_FILAMENT] +gcode: + BT_TOOL_UNLOAD + +[gcode_macro M600] gcode: - G90 ; Absolute positioning - M83 ; E relative + {% set tool = params.T|int %} + {% set purge = params.L|default(0)|int %} - G1 X202 Y264.5 F6000 ; Move to tray position - G1 E-2 F600 ; Retract slightly to reduce pressure during kick - M106 S255 ; Full blast fan to solidify plastic - G4 P3000 ; Wait 3 seconds for fan to do its work - G1 X182 F8000 ; Kick left - M106 S0 ; Turn off fan + T{tool} PURGE_LENGTH={purge} - ; Exit - G1 Y245 F6000 - G1 E2 F600 ; Push back filament after kick +[!filament_switch_sensor filament_sensor] [AFC] VarFile: /etc/klipper/config/AFC.var @@ -240,7 +247,7 @@ homing_enabled: False # TOOL Cutting Settings tool_cut: True -tool_cut_cmd: CUT +tool_cut_cmd: CUT_FILAMENT tool_cut_threshold: 10000 # Park Settings From 594c533641eca34bf8b74bb83e52fa2fb5000a87 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sat, 27 Jun 2026 15:33:23 +0200 Subject: [PATCH 22/42] Add new thermistor to cfg --- .../recipes-data/klipper-afc-addon/files/canvas.cfg | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg index df13c40a..885a62df 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg @@ -266,4 +266,7 @@ kick_cmd: KICK wipe: False # Form Tip Settings -form_tip: False \ No newline at end of file +form_tip: False + +[extruder] +pulldown_resistor: 100000 \ No newline at end of file From 7ea9d39da8302a3f06a7badab283304bb39ab0b0 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sat, 27 Jun 2026 19:47:39 +0200 Subject: [PATCH 23/42] Adjust max back position for macros --- meta-opencentauri/recipes-apps/klipper/files/macros.cfg | 8 ++++---- .../recipes-data/klipper-afc-addon/files/canvas.cfg | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg index 3e6d119b..374a8159 100644 --- a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg +++ b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg @@ -318,7 +318,7 @@ gcode: {% for wipes in range(4) %} {% for i in range(8) %} {% set x = 187 - (i * 2) %} - {% set y = 265 if i % 2 == 0 else 263 %} + {% set y = 264 if i % 2 == 0 else 262 %} G1 X{x} Y{y} {% endfor %} G1 X187 @@ -331,7 +331,7 @@ gcode: {% for wipes in range(4) %} {% for i in range(8) %} {% set x = 187 - (i * 2) %} - {% set y = 263 if i % 2 == 0 else 265 %} + {% set y = 262 if i % 2 == 0 else 264 %} G1 X{x} Y{y} {% endfor %} G1 X187 @@ -350,14 +350,14 @@ gcode: G28 ; Home if necessary {% endif %} - {% if printer.gcode_move.position.x > 201.5 and printer.gcode_move.position.x < 202.5 and printer.gcode_move.position.y > 264.5 and printer.gcode_move.position.y < 265.5 %} + {% if printer.gcode_move.position.x > 201.5 and printer.gcode_move.position.x < 202.5 and printer.gcode_move.position.y > 263.5 and printer.gcode_move.position.y < 264.5 %} RETURN ; Already at the tray {% endif %} G90 ; Absolute positioning G0 X202 F12000 G0 Y250 F12000 ; Move near to the try - G0 Y265 F1200 ; Move slowly back to the tray + G0 Y264 F1200 ; Move slowly back to the tray M400 ; Wait for movements to complete diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg index 885a62df..ab49f019 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg @@ -166,7 +166,7 @@ gcode: ; === Approach === G1 X202 F6000 ; Move to wiper X start position - G1 Y264.5 F6000 ; Move to wiper Y position (top of wiper) + G1 Y264 F6000 ; Move to wiper Y position (top of wiper) #G1 E-2 F600 ; === Pass 1: X sweeps at top of wiper === @@ -181,7 +181,7 @@ gcode: G1 X202 F7200 ; Full width sweep right across entire wiper ; === Extract === - G1 Y264.5 F1200 ; Slow lift up through wiper (clean exit) + G1 Y264 F1200 ; Slow lift up through wiper (clean exit) #G1 E2 F600 M104 S{toolhead_target} From cbf8a479596eb39c5fe391eb6e882ad467f32d30 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sat, 27 Jun 2026 20:10:10 +0200 Subject: [PATCH 24/42] Fix new wiper position --- .../recipes-apps/klipper/files/macros.cfg | 2 +- .../recipes-data/klipper-afc-addon/files/canvas.cfg | 13 +++---------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg index 374a8159..ca9d60f9 100644 --- a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg +++ b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg @@ -6,7 +6,7 @@ [gcode_macro _global_var] variable_target_extruder: 0 variable_retract_amount: 2 -variable_extruder_temp:{'probing': 140, 'cleaning': 180, 'loading': 230, 'purging': 250} +variable_extruder_temp:{'probing': 140, 'cleaning': 190, 'loading': 230, 'purging': 250} variable_fan_state:{'p1': 0.0, 'p2': 0.0, 'p3': 0.0} variable_z_maximum_lifting_distance: 256 variable_pause_resume_travel_speed: 150 diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg index ab49f019..015464c7 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg @@ -165,9 +165,7 @@ gcode: M106 S255 ; === Approach === - G1 X202 F6000 ; Move to wiper X start position - G1 Y264 F6000 ; Move to wiper Y position (top of wiper) - #G1 E-2 F600 + G1 X202 Y264.5 F6000 ; Move to wiper Y position (top of wiper) ; === Pass 1: X sweeps at top of wiper === G1 X165 F8000 ; Wipe left (165 vs 170 in old — 5mm wider sweep) @@ -176,13 +174,8 @@ gcode: ; === Full depth plunge === G1 Y245 F6000 ; Drop to full depth (19.5mm below top — 5mm deeper than old) - - ; === Full width sweep at depth === - G1 X202 F7200 ; Full width sweep right across entire wiper - - ; === Extract === - G1 Y264 F1200 ; Slow lift up through wiper (clean exit) - #G1 E2 F600 + + # TODO: Testing needed. Is this enough cleaning? M104 S{toolhead_target} M107 From ca929a7d068be55f559a810ed58eaa7a02dc6d14 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sat, 27 Jun 2026 20:32:00 +0200 Subject: [PATCH 25/42] fix kick --- meta-opencentauri/recipes-apps/klipper/files/macros.cfg | 8 ++++---- .../recipes-data/klipper-afc-addon/files/canvas.cfg | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg index ca9d60f9..c16859e7 100644 --- a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg +++ b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg @@ -362,11 +362,11 @@ gcode: M400 ; Wait for movements to complete [gcode_macro KICK] -variable_end_kick_y: 250 +variable_end_kick_x: 186 gcode: {% set global = printer['gcode_macro _global_var'] %} {% set self = printer["gcode_macro KICK"] %} - {% set end_kick_y = self.end_kick_y|float %} + {% set end_kick_x = self.end_kick_x|float %} {% set retract_amount = global.retract_amount|float %} {% if "xyz" not in printer.toolhead.homed_axes %} @@ -384,10 +384,10 @@ gcode: G1 E-{retract_amount} F300 ; Retract slightly to reduce pressure during kick M106 S255 ; Full blast fan to solidify plastic G4 P3000 ; Wait 3 seconds for fan to do its work - G1 X186 F8000 ; Kick left + G1 X{self.end_kick_x} F8000 ; Kick left M106 S0 ; Turn off fan - G1 Y{end_kick_y} F6000 ; Exit + G1 Y250 F6000 ; Exit M400 ; Wait for movements to complete diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg index 015464c7..8cf15481 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg @@ -182,7 +182,7 @@ gcode: M400 [gcode_macro KICK] -variable_end_kick_y: 245 +variable_end_kick_x: 182 [gcode_button toolhead_front_cover_detection] pin: hotend:PC4 From 4324af6de505b1bcddcd758385c0868a5fd285db Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sat, 27 Jun 2026 21:08:29 +0200 Subject: [PATCH 26/42] more fixes --- .../recipes-data/klipper-afc-addon/files/canvas.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg index 8cf15481..df831585 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg @@ -136,7 +136,7 @@ led_red_pin: canvas:LED3_RED [gcode_macro POOP] gcode: - {% set purge_length = params.PURGE_LENGTH|default(100, true)|int %} + {% set purge_length = params.PURGE_LENGTH|default(100, true)|float %} {% if printer.extruder.target < 180 or not printer.extruder.can_extrude %} {action_raise_error("Extruder temperature too low for purging. Please heat the extruder to at least 180°C.")} @@ -204,7 +204,7 @@ gcode: [gcode_macro M600] gcode: {% set tool = params.T|int %} - {% set purge = params.L|default(0)|int %} + {% set purge = params.L|default(0)|float %} T{tool} PURGE_LENGTH={purge} @@ -252,7 +252,7 @@ poop: True poop_cmd: POOP # Kick Settings -kick: False +kick: True kick_cmd: KICK # Wipe Settings From 9667e09aa538ef9a3aea3c23620e34cfbc5c7adf Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sat, 27 Jun 2026 21:43:02 +0200 Subject: [PATCH 27/42] Move out out tray at start print --- meta-opencentauri/recipes-apps/klipper/files/macros.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg index c16859e7..560631c1 100644 --- a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg +++ b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg @@ -106,6 +106,8 @@ gcode: T{tool} {% if adaptive_purge %} + G90 + G1 Y250 F6000 SMART_PARK {% endif %} {% else %} From b8337f32dc116fd0871ad258cff7dfdd282074dd Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sun, 28 Jun 2026 20:11:27 +0200 Subject: [PATCH 28/42] Don't do filament check in AFC during START_PRINT (will be loaded in START_PRINT) --- meta-opencentauri/recipes-apps/klipper/files/macros.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg index 560631c1..2f796703 100644 --- a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg +++ b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg @@ -18,7 +18,7 @@ gcode: {% set target_extruder = params.EXTRUDER|int %} {% set target_chamber = params.CHAMBER|int %} {% set tool = params.TOOL|default(0)|int %} - {% set sensor = printer['filament_switch_sensor filament_sensor'] if 'filament_switch_sensor filament_sensor' in printer else printer["filament_switch_sensor extruder_tool_start"] %} + {% set sensor = printer['filament_switch_sensor filament_sensor'] if 'filament_switch_sensor filament_sensor' in printer else none %} {% set global = printer['gcode_macro _global_var'] %} {% set settings = printer['gcode_macro _COSMOS_SETTINGS']|default({}) %} {% set heatsoak = settings.heatsoak|default(1)|float %} @@ -36,7 +36,7 @@ gcode: SET_GCODE_VARIABLE MACRO=_global_var VARIABLE=target_extruder VALUE={target_extruder} - {% if sensor.enabled and not sensor.filament_detected %} + {% if sensor and sensor.enabled and not sensor.filament_detected %} SET_DISPLAY_TEXT MSG="NO FILAMENT LOADED!" CANCEL_PRINT RETURN From 3e131131da828a306a48861b74a7f3803a413753 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sun, 28 Jun 2026 22:52:17 +0200 Subject: [PATCH 29/42] Bump version of AFC --- meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb index 6479af3b..25a3295e 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb @@ -12,7 +12,7 @@ SRC_URI = " \ file://canvas.cfg \ " -SRCREV = "6aed17d9fb28d90c567efc3a296df24b76e9b363" +SRCREV = "de0c0e8c146b52e93f92018d847e57c1015e803f" S = "${WORKDIR}/git" From a19afa1c6e61a3878086671f9a4192b34a76f936 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Mon, 29 Jun 2026 22:27:53 +0200 Subject: [PATCH 30/42] Better load macro for AFC --- .../recipes-apps/klipper/files/macros.cfg | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg index 2f796703..6c8a0736 100644 --- a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg +++ b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg @@ -98,17 +98,32 @@ gcode: {% endif %} {% if "AFC" in printer %} - # TODO: Improve + {% set needed_tool = "T" ~ tool %} + + {% if needed_tool not in printer.AFC.maps %} + SET_DISPLAY_TEXT MSG="Tool {tool} is not available in AFC. Please check your tool mapping." + CANCEL_PRINT + RETURN + {% endif %} + + {% set index = printer.AFC.maps.index(needed_tool) %} + {% set load_needed = printer.AFC.lanes[index] == printer.AFC.current_load %} + M104 S{target_extruder} - MOVE_TO_TRAY - M109 S{target_extruder} - T{tool} + {% if load_needed %} + RESPOND TYPE=command MSG="Tool {tool} is not loaded. Loading..." + MOVE_TO_TRAY + M109 S{target_extruder} + T{tool} + {% else %} + RESPOND TYPE=command MSG="Tool {tool} is already loaded. Continuing..." - {% if adaptive_purge %} - G90 - G1 Y250 F6000 - SMART_PARK + {% if adaptive_purge %} + SMART_PARK + {% endif %} + + M109 S{target_extruder} {% endif %} {% else %} {% if adaptive_purge %} From 5f683d1fef76d7587b013aa27030231d6a574dd6 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Tue, 30 Jun 2026 21:55:04 +0200 Subject: [PATCH 31/42] Simplify load macro --- .../recipes-apps/klipper/files/macros.cfg | 39 ++++--------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg index 6c8a0736..7e7236ac 100644 --- a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg +++ b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg @@ -97,40 +97,15 @@ gcode: {% endif %} {% endif %} - {% if "AFC" in printer %} - {% set needed_tool = "T" ~ tool %} - - {% if needed_tool not in printer.AFC.maps %} - SET_DISPLAY_TEXT MSG="Tool {tool} is not available in AFC. Please check your tool mapping." - CANCEL_PRINT - RETURN - {% endif %} - - {% set index = printer.AFC.maps.index(needed_tool) %} - {% set load_needed = printer.AFC.lanes[index] == printer.AFC.current_load %} - - M104 S{target_extruder} - - {% if load_needed %} - RESPOND TYPE=command MSG="Tool {tool} is not loaded. Loading..." - MOVE_TO_TRAY - M109 S{target_extruder} - T{tool} - {% else %} - RESPOND TYPE=command MSG="Tool {tool} is already loaded. Continuing..." + M104 S{target_extruder} - {% if adaptive_purge %} - SMART_PARK - {% endif %} - - M109 S{target_extruder} - {% endif %} - {% else %} - {% if adaptive_purge %} - SMART_PARK - {% endif %} + {% if adaptive_purge %} + SMART_PARK + {% endif %} - M109 S{target_extruder} + M109 S{target_extruder} + {% if "AFC" in printer %} + T{tool} {% endif %} {% if adaptive_purge %} From 1dcab49a4d353d1587f568daa18904cfe951af0c Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Tue, 30 Jun 2026 23:13:21 +0200 Subject: [PATCH 32/42] Free timeout --- .../recipes-apps/klipper/files/klipper-firmware-canvas-init-d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-opencentauri/recipes-apps/klipper/files/klipper-firmware-canvas-init-d b/meta-opencentauri/recipes-apps/klipper/files/klipper-firmware-canvas-init-d index 5d4a4a52..d34aa1a7 100644 --- a/meta-opencentauri/recipes-apps/klipper/files/klipper-firmware-canvas-init-d +++ b/meta-opencentauri/recipes-apps/klipper/files/klipper-firmware-canvas-init-d @@ -44,7 +44,7 @@ case "$1" in exit 0 else echo "Attempting bootloader upgrade..." - if mcu-flasher --canvas --firmware $KATAPULT_DEPLOYER --no-wait $SERIALPORT --firmware-version 6.4.9; then + if mcu-flasher --canvas --timeout 10 --firmware $KATAPULT_DEPLOYER --no-wait $SERIALPORT --firmware-version 6.4.9; then sleep 3 echo "Bootloader deployer flashed successfully, clearing installed version." rm -f $INSTALLED_VERSION From 488ff2e67aaa70e684ee9e888de0b7fd96d12bfe Mon Sep 17 00:00:00 2001 From: Bogdan <31422391+Atomique13@users.noreply.github.com> Date: Tue, 30 Jun 2026 22:55:46 +0300 Subject: [PATCH 33/42] Add canvas MCU temperature sensor (#230) * Add canvas MCU temperature sensor * fix: increase max_extrude_only_distance to 500 for improved purging --------- Co-authored-by: Atomique13 --- meta-opencentauri/recipes-apps/klipper/files/machine.cfg | 2 +- .../recipes-data/klipper-afc-addon/files/canvas.cfg | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/meta-opencentauri/recipes-apps/klipper/files/machine.cfg b/meta-opencentauri/recipes-apps/klipper/files/machine.cfg index d0852cad..91fbbaa5 100644 --- a/meta-opencentauri/recipes-apps/klipper/files/machine.cfg +++ b/meta-opencentauri/recipes-apps/klipper/files/machine.cfg @@ -148,7 +148,7 @@ max_temp: 335 min_extrude_temp: 180 pressure_advance: 0.04 pressure_advance_smooth_time: 0.04 -max_extrude_only_distance: 150 +max_extrude_only_distance: 500 #was 150, for purging max_extrude_cross_section: 5 control: pid pid_Kp: 24.860427 diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg index df831585..f96658a1 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg @@ -262,4 +262,8 @@ wipe: False form_tip: False [extruder] -pulldown_resistor: 100000 \ No newline at end of file +pulldown_resistor: 100000 + +[temperature_sensor Mcu_canvas] +sensor_type: temperature_mcu +sensor_mcu: canvas From 702a5db9dff46074168d68d0ecbba9e799611c60 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sat, 4 Jul 2026 00:20:30 +0200 Subject: [PATCH 34/42] Flashing improvements --- .../files/klipper-firmware-canvas-init-d | 20 ++++++------------- .../files/klipper-firmware-toolhead-init-d | 12 +++++++++++ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/meta-opencentauri/recipes-apps/klipper/files/klipper-firmware-canvas-init-d b/meta-opencentauri/recipes-apps/klipper/files/klipper-firmware-canvas-init-d index d34aa1a7..f07fc390 100644 --- a/meta-opencentauri/recipes-apps/klipper/files/klipper-firmware-canvas-init-d +++ b/meta-opencentauri/recipes-apps/klipper/files/klipper-firmware-canvas-init-d @@ -39,21 +39,13 @@ case "$1" in echo "Firmware is already up to date." exit 0 else - echo "Firmware version mismatch or not installed, flashing firmware..." - if update; then - exit 0 - else - echo "Attempting bootloader upgrade..." - if mcu-flasher --canvas --timeout 10 --firmware $KATAPULT_DEPLOYER --no-wait $SERIALPORT --firmware-version 6.4.9; then - sleep 3 - echo "Bootloader deployer flashed successfully, clearing installed version." - rm -f $INSTALLED_VERSION - update - else - echo "Cannot update bootloader." - exit 1 - fi + if mcu-flasher --canvas --timeout 10 --firmware $KATAPULT_DEPLOYER --no-wait $SERIALPORT --firmware-version 6.4.9; then + sleep 3 + echo "Bootloader deployer flashed successfully, clearing installed version." + rm -f $INSTALLED_VERSION fi + + update fi ;; status) diff --git a/meta-opencentauri/recipes-apps/klipper/files/klipper-firmware-toolhead-init-d b/meta-opencentauri/recipes-apps/klipper/files/klipper-firmware-toolhead-init-d index 0e859c8a..9a650c57 100644 --- a/meta-opencentauri/recipes-apps/klipper/files/klipper-firmware-toolhead-init-d +++ b/meta-opencentauri/recipes-apps/klipper/files/klipper-firmware-toolhead-init-d @@ -35,6 +35,18 @@ case "$1" in echo $GPIO > /sys/class/gpio/unexport sleep 3 + if [ -e "$SERIALPORT" ]; then + VENDOR=$( + udevadm info -q property -n "$SERIALPORT" | + sed -n 's/^ID_VENDOR=//p' + ) + + if [ "$VENDOR" = "ShenZhenCBD" ]; then + echo "Detected Elegoo bootloader, resetting saved version..." + rm -f $INSTALLED_VERSION + fi + fi + if [ -f "$INSTALLED_VERSION" ] && [ "$(cat $INSTALLED_VERSION)" == "$(cat $FIRMWARE_VERSION)" ]; then echo "Firmware is already up to date." exit 0 From bc4acf2ffdb0ffc5f712e31a18a836638042fbc8 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sat, 4 Jul 2026 01:15:21 +0200 Subject: [PATCH 35/42] Bump AFC version --- meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb index 25a3295e..e0bfbef7 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb @@ -12,7 +12,7 @@ SRC_URI = " \ file://canvas.cfg \ " -SRCREV = "de0c0e8c146b52e93f92018d847e57c1015e803f" +SRCREV = "4379f9e50ee391436c407de32e8191162f16d24c" S = "${WORKDIR}/git" From 60fa45f8ad0bc25c4961eddad44b96cc8d7f31e4 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sat, 4 Jul 2026 11:23:42 +0200 Subject: [PATCH 36/42] Bump version of mcu flasher --- meta-opencentauri/recipes-apps/mcu-flasher/mcu-flasher_0.1.0.bb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-opencentauri/recipes-apps/mcu-flasher/mcu-flasher_0.1.0.bb b/meta-opencentauri/recipes-apps/mcu-flasher/mcu-flasher_0.1.0.bb index 64fb7437..3263d5b8 100644 --- a/meta-opencentauri/recipes-apps/mcu-flasher/mcu-flasher_0.1.0.bb +++ b/meta-opencentauri/recipes-apps/mcu-flasher/mcu-flasher_0.1.0.bb @@ -8,7 +8,7 @@ LIC_FILES_CHKSUM = "file://../LICENSE;md5=0a18a528575a965515cdd877f88b3c4c" SRC_URI += " \ git://github.com/OpenCentauri/OpenCentauri.git;protocol=https;nobranch=1;branch=main \ " -SRCREV = "4b179ce5fa70309f24ac047b6d9e2c5c2a0e1c7c" +SRCREV = "8690e3f9f84555cd0037a18f214959ae7274de7d" PR = "r1" S = "${WORKDIR}/git/mcu-flasher" From 3bf2e9c68c0d4844f2baae884b1dd43f6577338b Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sat, 4 Jul 2026 21:38:04 +0200 Subject: [PATCH 37/42] Hopefully fix pause & load stall issues --- .../klipper/files/0003-drv8833-motor-control.patch | 12 ++++++------ .../recipes-data/klipper-afc-addon/afc_0.1.bb | 2 +- .../recipes-data/klipper-afc-addon/files/afc.cfg | 1 + 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/meta-opencentauri/recipes-apps/klipper/files/0003-drv8833-motor-control.patch b/meta-opencentauri/recipes-apps/klipper/files/0003-drv8833-motor-control.patch index 896124fc..d3920196 100644 --- a/meta-opencentauri/recipes-apps/klipper/files/0003-drv8833-motor-control.patch +++ b/meta-opencentauri/recipes-apps/klipper/files/0003-drv8833-motor-control.patch @@ -1,9 +1,9 @@ diff --git a/klippy/extras/drv8833.py b/klippy/extras/drv8833.py new file mode 100644 -index 00000000..c6271960 +index 00000000..14cd066f --- /dev/null +++ b/klippy/extras/drv8833.py -@@ -0,0 +1,786 @@ +@@ -0,0 +1,787 @@ +# Support for DRV8833 based CANVAS-style motor control +# +# This file may be distributed under the terms of the GNU GPLv3 license. @@ -654,7 +654,7 @@ index 00000000..c6271960 + self._stop(eventtime) + self._start(eventtime, direction, target_speed) + -+ def drv8833_move(self, speed: float, distance: float): ++ def drv8833_move(self, speed: float, distance: float, wait_for_completion=True): + eventtime = self.reactor.monotonic() + if distance < 0.0: + speed *= -1.0 @@ -671,7 +671,8 @@ index 00000000..c6271960 + if self.active: + self._stop(eventtime) + self._start_move(eventtime, direction, target_speed, stop_ticks) -+ self._wait_for_move_complete(eventtime) ++ if wait_for_completion: ++ self._wait_for_move_complete(eventtime) + + cmd_MOVE_DEBUG_help = ( + "Run the DRV8833 lane for a fixed time while logging hall counts, " @@ -790,7 +791,6 @@ index 00000000..c6271960 + +def load_config_prefix(config): + return PrinterDrv8833(config) -\ No newline at end of file diff --git a/src/Kconfig b/src/Kconfig index d679cef9..00da2118 100644 --- a/src/Kconfig @@ -820,7 +820,7 @@ index 3e7754af..19421fed 100644 src-$(CONFIG_WANT_SOFTWARE_SPI) += spi_software.c diff --git a/src/drv8833.c b/src/drv8833.c new file mode 100644 -index 00000000..1101992c +index 00000000..cee90955 --- /dev/null +++ b/src/drv8833.c @@ -0,0 +1,371 @@ diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb index e0bfbef7..0f025114 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb @@ -12,7 +12,7 @@ SRC_URI = " \ file://canvas.cfg \ " -SRCREV = "4379f9e50ee391436c407de32e8191162f16d24c" +SRCREV = "732e9eabdba667bdbd9d6aacd4a0b9ae6e281b40" S = "${WORKDIR}/git" diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/afc.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/afc.cfg index 7d47978f..79982ac4 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/afc.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/afc.cfg @@ -108,6 +108,7 @@ enable: True delay_time: 0.5 disable_unload_filament_remapping: False capture_td1_data: False +disable_pause_resume_remapping: True [delayed_gcode welcome_afc] initial_duration: 1 From c3dd6c67967a3322c9c8113bc58d4e688787ce22 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sat, 4 Jul 2026 22:02:55 +0200 Subject: [PATCH 38/42] Don't jump during cut; bump AFC version --- .../recipes-apps/klipper/files/macros.cfg | 11 ----------- .../recipes-data/klipper-afc-addon/afc_0.1.bb | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg index 7e7236ac..f411eb16 100644 --- a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg +++ b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg @@ -503,17 +503,6 @@ gcode: G28 ; Home if necessary {% endif %} - # Raise Z a little bit during a print - {% if printer.print_stats.state == "printing" %} - {% if (printer.gcode_move.position.z + 4) < z_max %} - G91 - G1 Z+4 F3000 - {% else %} - G90 - G1 Z{z_max} F3000 - {% endif %} - {% endif %} - G90 M83 ; E relative diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb index 0f025114..2c26fe36 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb @@ -12,7 +12,7 @@ SRC_URI = " \ file://canvas.cfg \ " -SRCREV = "732e9eabdba667bdbd9d6aacd4a0b9ae6e281b40" +SRCREV = "d3c47a0a5bcfee34135e72568188c85748ffbf45" S = "${WORKDIR}/git" From 3698c43f4e6a812f696f9ddeddcc527336844b92 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sun, 19 Jul 2026 15:44:06 +0200 Subject: [PATCH 39/42] Bump AFC version --- meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb index 2c26fe36..75e45f07 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb @@ -12,7 +12,7 @@ SRC_URI = " \ file://canvas.cfg \ " -SRCREV = "d3c47a0a5bcfee34135e72568188c85748ffbf45" +SRCREV = "06055252ab1095258724cb6eda3514281852e100" S = "${WORKDIR}/git" From 2e81812603b9a4498c62464ba1d755e35b671bce Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sun, 19 Jul 2026 15:54:57 +0200 Subject: [PATCH 40/42] Push new macros --- meta-opencentauri/recipes-apps/klipper/files/macros.cfg | 5 +++-- .../recipes-data/klipper-afc-addon/files/canvas.cfg | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg index f411eb16..f00219e2 100644 --- a/meta-opencentauri/recipes-apps/klipper/files/macros.cfg +++ b/meta-opencentauri/recipes-apps/klipper/files/macros.cfg @@ -490,8 +490,8 @@ gcode: M400 ; Wait for movements to complete [gcode_macro CUT_FILAMENT] -variable_retract_length: 21 -variable_pushback_length: 16 +variable_retract_length: 10 +variable_pushback_length: 5 gcode: {% set self = printer["gcode_macro CUT_FILAMENT"] %} {% set global = printer["gcode_macro _global_var"] %} @@ -517,6 +517,7 @@ gcode: G0 Y20 F1200 {% if printer.extruder.target >= 180 and printer.extruder.can_extrude %} + MOVE_TO_TRAY G1 E{pushback_length} F300 ; Push back filament G1 E-{pushback_length} F600 ; Retract from pushback {% endif %} diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg index f96658a1..62d75a75 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg @@ -97,6 +97,7 @@ odometer_pin: canvas:ODOMETER0 odometer_resolution: 3.15786 led_white_pin: canvas:LED0_WHITE led_red_pin: canvas:LED0_RED +disengage_distance: 0.5 [AFC_canvas_lane CANVAS_2] unit: CANVAS_1:2 @@ -109,6 +110,7 @@ odometer_pin: canvas:ODOMETER1 odometer_resolution: 3.15786 led_white_pin: canvas:LED1_WHITE led_red_pin: canvas:LED1_RED +disengage_distance: 0.5 [AFC_canvas_lane CANVAS_3] unit: CANVAS_1:3 @@ -121,6 +123,7 @@ odometer_pin: canvas:ODOMETER2 odometer_resolution: 3.15786 led_white_pin: canvas:LED2_WHITE led_red_pin: canvas:LED2_RED +disengage_distance: 0.5 [AFC_canvas_lane CANVAS_4] unit: CANVAS_1:4 @@ -133,6 +136,7 @@ odometer_pin: canvas:ODOMETER3 odometer_resolution: 3.15786 led_white_pin: canvas:LED3_WHITE led_red_pin: canvas:LED3_RED +disengage_distance: 0.5 [gcode_macro POOP] gcode: @@ -145,7 +149,9 @@ gcode: MOVE_TO_TRAY M83 RESPOND TYPE=command MSG="Purging {purge_length}mm of filament" + G1 Y265 F1200 G1 E{purge_length} F300 + G1 Y264 F1200 [gcode_macro CLEAN_NOZZLE] gcode: From edb62718964d09a0ced1a033a32bfc9dc5c4121b Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:57:19 +0200 Subject: [PATCH 41/42] Also make kick go to X=181 --- .../recipes-data/klipper-afc-addon/files/canvas.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg index 62d75a75..274216d7 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg @@ -188,7 +188,7 @@ gcode: M400 [gcode_macro KICK] -variable_end_kick_x: 182 +variable_end_kick_x: 181 [gcode_button toolhead_front_cover_detection] pin: hotend:PC4 From 7351bd1a4f505c806460e7865da1483efbd0430c Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:11:34 +0200 Subject: [PATCH 42/42] Bump AFC; enable runout detection; increase afc_bowden_length --- meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb | 2 +- .../recipes-data/klipper-afc-addon/files/canvas.cfg | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb index 75e45f07..f661ffd7 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/afc_0.1.bb @@ -12,7 +12,7 @@ SRC_URI = " \ file://canvas.cfg \ " -SRCREV = "06055252ab1095258724cb6eda3514281852e100" +SRCREV = "72f597e210d4633922be4f45eacc5be1403fa747" S = "${WORKDIR}/git" diff --git a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg index 274216d7..c590aae3 100644 --- a/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg +++ b/meta-opencentauri/recipes-data/klipper-afc-addon/files/canvas.cfg @@ -64,7 +64,7 @@ aliases: [AFC_hub toolhead_4way_hub] switch_pin: !hotend:PB2 enable_hub_runout: False -afc_bowden_length: 6.5 +afc_bowden_length: 8 afc_unload_bowden_length: 40 [AFC_extruder extruder] @@ -98,6 +98,7 @@ odometer_resolution: 3.15786 led_white_pin: canvas:LED0_WHITE led_red_pin: canvas:LED0_RED disengage_distance: 0.5 +enable_hub_runout: True [AFC_canvas_lane CANVAS_2] unit: CANVAS_1:2 @@ -111,6 +112,7 @@ odometer_resolution: 3.15786 led_white_pin: canvas:LED1_WHITE led_red_pin: canvas:LED1_RED disengage_distance: 0.5 +enable_hub_runout: True [AFC_canvas_lane CANVAS_3] unit: CANVAS_1:3 @@ -124,6 +126,7 @@ odometer_resolution: 3.15786 led_white_pin: canvas:LED2_WHITE led_red_pin: canvas:LED2_RED disengage_distance: 0.5 +enable_hub_runout: True [AFC_canvas_lane CANVAS_4] unit: CANVAS_1:4 @@ -137,6 +140,7 @@ odometer_resolution: 3.15786 led_white_pin: canvas:LED3_WHITE led_red_pin: canvas:LED3_RED disengage_distance: 0.5 +enable_hub_runout: True [gcode_macro POOP] gcode: