shishu94 / Klipperotchy

Companion tools for klipper
GNU General Public License v3.0
7 stars 3 forks source link

Commands to slow down and speed up printer during splicing aren't running in time #3

Open 3dprintpittsburgh opened 7 months ago

3dprintpittsburgh commented 7 months ago

We bought a Palette 3 Pro in order to provide automated spool swapping for a large format 3D printer we're working on. This printer uses 1.75 filament, and we normally run it with a 1.2mm nozzle and .8mm layer height at around 40mm/s speed. This translates to around 38.5mm3/s flow rate from the extruder. On first layers, we print slower, at around 20mm3/s or so. I've gotten it hooked up to our machine and started trying to run a connected mode print after following the setup instructions here and making a serial bridge from two USB to serial converters. Everything works fine and the print starts as expected. All the initial splices happen and work as expected, however I find that when the printer actually starts printing, that it seems to outrun the splice rate of the machine.

That said, I did set up the setting to slow down the printer when splices happen, however it doesn't seem to immediately trigger when the splice starts. Rather, the splice seems to start, and then 10-15 seconds later the printer actually slows down. In a lot of instances, this isn't fast enough, and all remaining slack in the buffer zone is pulled out, resulting in the filament breaking and the print failing. Then, 30-40 seconds after the splice is already done and the palette is back up to full speed, the printer speeds back up. It's as if the slow down and speed up are both delayed. I'm guessing this has something to do with the way commands are buffered or sent over USB in connected mode, or maybe something to do with my connection to the machine, but all commands do seem to be running properly, it's just that there's a big delay in the slow down for splicing. Do you have any ideas for me? I feel like I'm so close to being able to use this machine for automated run out splicing for our big machine.

shishu94 commented 7 months ago

Hello,

Thank you for reaching out. Very glad to see that the bridge works as intended.

Now for your problem. A few things. One that you guessed correctly. The slow down being delayed is indeed inherent to how klipper buffers the gcode. Depending on the machine this would be more or less pronounced. But 10s is a bit excessive. Do you mind sharing details about your setup (more like your klipper version, and maybe printer.cfg, to understand the specifics)? The bridge doesn't do anything special about it but I could introduce some logic to prioritize the slow down for splice feature, so it goes directly to the front of the queue. Can't promise any ETA, as I don't have much free time to work on this, but will do my best. Another way to solve this would be to catch via a wire when the palette cuts filament and trigger a slow down on klipper for X seconds.

Second, and the actual reason for your problem, this device has been developed to print at relatively moderate speed (around 100mm/s with .4 nozzle). That gives us a flow rate of 10mm3/s. Your are way to far from this. I also experienced the same problem on my mercury while printing with .6 at 200mm/s. I was almost always drying the buffer. I created an additional buffer that goes after the out port of the P3 which gives me roughly 80cm of additional filament, but this is still in progress as it kinda messes up with the ping/pongs of the Palette.

Sorry if I don't have a clear solution for you immediately, but this is something that I work only on my rare free time and shared it more as proof of concept to show that it works with klipper too. Let me know if you want to test the change I mentioned above. I can probably create the code quickly but will lack time for testing, and I don't want to push something that I don't know if it works. Maybe you can help on that side.

Le mer. 10 avr. 2024, 22:45, 3dprintpittsburgh @.***> a écrit :

We bought a Palette 3 Pro in order to provide automated spool swapping for a large format 3D printer we're working on. This printer uses 1.75 filament, and we normally run it with a 1.2mm nozzle and .8mm layer height at around 40mm/s speed. This translates to around 38.5mm3/s flow rate from the extruder. On first layers, we print slower, at around 20mm3/s or so. I've gotten it hooked up to our machine and started trying to run a connected mode print after following the setup instructions here and making a serial bridge from two USB to serial converters. Everything works fine and the print starts as expected. All the initial splices happen and work as expected, however I find that when the printer actually starts printing, that it seems to outrun the splice rate of the machine.

That said, I did set up the setting to slow down the printer when splices happen, however it doesn't seem to immediately trigger when the splice starts. Rather, the splice seems to start, and then 10-15 seconds later the printer actually slows down. In a lot of instances, this isn't fast enough, and all remaining slack in the buffer zone is pulled out, resulting in the filament breaking and the print failing. Then, 30-40 seconds after the splice is already done and the palette is back up to full speed, the printer speeds back up. It's as if the slow down and speed up are both delayed. I'm guessing this has something to do with the way commands are buffered or sent over USB in connected mode, or maybe something to do with my connection to the machine, but all commands do seem to be running properly, it's just that there's a big delay in the slow down for splicing. Do you have any ideas for me? I feel like I'm so close to being able to use this machine for automated run out splicing for our big machine.

— Reply to this email directly, view it on GitHub https://github.com/shishu94/Klipperotchy/issues/3, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGTZRJRM3B6SYNG26RN63CTY4WQGXAVCNFSM6AAAAABGBDWVNGVHI2DSMVQWIX3LMV43ASLTON2WKOZSGIZTMMZZHA2DGMY . You are receiving this because you are subscribed to this thread.Message ID: @.***>

3dprintpittsburgh commented 7 months ago

Thank you so much for the quick reply! I'd love to test this out for you, and have pretty deep experience both with Klipper and software engineering in general, so I can definitely run through things and be a tester for this!

The build in question is a custom large format 800x800x900 running a BigTreeTech Manta M8P V2 MCU and BTT CB1 on board for Klipper. Everything installed and configured through KIAUH. Multiple USB webcams and KlipperScreen running, but CPU usage seems alright. Klipper version v0.12.0-118-g18de421c

If you do manage to put in some code to prioritize the slow down commands, I would be infinitely grateful and I do think it would help my situation. I'd be happy to test in any way I can. If email is best for you for testing, you can reach me at support@3dprintpgh.com

Printer.cfg below:

[include serial_moonraker_bridge.cfg]
[include shell_command.cfg]
# This file contains common pin mappings for the BIGTREETECH Manta M8P V2.0
# To use this config, the firmware should be compiled for the
# STM32H723 with a "128KiB bootloader" "25 MHz crystal"
# and "USB (on PA11/PA12)", "CAN bus (on PD0/PD1)" or Serial (on USART1 PA10/PA9).

# See docs/Config_Reference.md for a description of parameters.

[virtual_sdcard]
path: /home/biqu/printer_data/gcodes

[pause_resume]

[display_status]

[respond]

[force_move]
enable_force_move: True 

[exclude_object]

[output_pin BEEPER_pin]
pin: PF0
pwm: False

[multi_pin heater_cartridges]
pins: PA0, PA1

[multi_pin part_cooling_fans]
pins: PF9, PF6

# Motor1
[stepper_x]
step_pin: PE6
dir_pin: !PE5
enable_pin: !PC14
microsteps: 16
rotation_distance: 80
endstop_pin: tmc5160_stepper_x:virtual_endstop
position_endstop: 0
position_max: 785
homing_speed: 65
homing_retract_dist: 0

## Motor1
[tmc5160 stepper_x]
cs_pin: PC13
spi_software_mosi_pin: PG6
spi_software_miso_pin: PG7
spi_software_sclk_pin: PG8
diag1_pin: !PF4
driver_SGT: 2
run_current: 1.500
stealthchop_threshold: 0

# Motor2
[stepper_y]
step_pin: PE2
dir_pin: !PE1
enable_pin: !PE4
microsteps: 16
# microsteps: 64
rotation_distance: 80
# rotation_distance: 2
# endstop_pin: ^PF3
endstop_pin: tmc5160_stepper_y:virtual_endstop
position_endstop: -45
position_max: 785
position_min:-45
homing_speed: 65
homing_retract_dist: 0

## Motor2
[tmc5160 stepper_y]
cs_pin: PE3
spi_software_mosi_pin: PG6
spi_software_miso_pin: PG7
spi_software_sclk_pin: PG8
diag1_pin: !PF3
driver_SGT: 1
run_current: 2.75
stealthchop_threshold: 999999
sense_resistor: 0.022

# Motor3
[stepper_z]
step_pin: PB8
dir_pin: PB7
enable_pin: !PE0
microsteps: 16
rotation_distance: 2
endstop_pin: probe:z_virtual_endstop
position_max: 890
position_min: -5.0
homing_speed: 6 
second_homing_speed: 3
homing_retract_dist: 3

## Motor3
[tmc5160 stepper_z]
cs_pin: PB9
spi_software_mosi_pin: PG6
spi_software_miso_pin: PG7
spi_software_sclk_pin: PG8
#diag1_pin: PF2
run_current: 2.75
stealthchop_threshold: 0
sense_resistor: 0.022

[safe_z_home]
home_xy_position: 400, 400 # Change coordinates to the center of your print bed
speed: 120
z_hop: 5                # Move up 10mm
z_hop_speed: 7

[temperature_sensor hotend1]
sensor_type: ATC Semitec 104NT-4-R025H42G
sensor_pin: PB0

[temperature_sensor hotend2]
sensor_type: ATC Semitec 104NT-4-R025H42G
sensor_pin: PC5

# Motor7
[extruder]
step_pin: PB4
dir_pin: PB3
enable_pin: !PB6
microsteps: 16
# rotation_distance: 55
# full_steps_per_rotation: 200
# gear_ratio: 44:14, 37:17 
rotation_distance: 7.9395582 
nozzle_diameter: 1.2
filament_diameter: 1.75
heater_pin: multi_pin:heater_cartridges # HE0
# sensor_pin: PB0 # T0
# sensor_type: ATC Semitec 104NT-4-R025H42G
sensor_type: temperature_combined
sensor_list: temperature_sensor hotend1, temperature_sensor hotend2
combination_method: mean
maximum_deviation: 200

#control: pid
#pid_Kp: 22.2
#pid_Ki: 1.08
#pid_Kd: 114
min_temp: -270
max_temp: 310

# Motor5
[tmc5160 extruder]
cs_pin: PB5
spi_software_mosi_pin: PG6
spi_software_miso_pin: PG7
spi_software_sclk_pin: PG8
run_current: 0.950
stealthchop_threshold: 0

# Fan1
[heater_fan hotend_cooling_fan]
pin: PF7
heater: extruder
heater_temp: 50.0

[temperature_sensor bed1]
sensor_type: Generic 3950
sensor_pin: PB1

[temperature_sensor bed2]
sensor_type: Generic 3950
sensor_pin: PC4

[heater_bed]
heater_pin: PF5
min_temp: 0
max_temp: 300
max_power: 0.5
pwm_cycle_time: 0.0166
sensor_type: temperature_combined
sensor_list: temperature_sensor bed1, temperature_sensor bed2
combination_method: mean
maximum_deviation: 10

#[fan_generic soc-fan]
#pin: host:gpio79  #CB1
#pin: host:gpio26  #CM4

# Fan0
[fan]
pin: multi_pin:part_cooling_fans

## Fan2
#[heater_fan fan2]
#pin: PF6

## Fan3
#[heater_fan fan3]
#pin: PF8

## Fan4
#[heater_fan fan4]
#pin: PA4

## Fan5
#[heater_fan fan5]
#pin: PA6
#tachometer_pin: PC2

## Fan6
#[heater_fan fan6]
#pin: PA2
#tachometer_pin: PC1

[mcu]
serial: /dev/serial/by-id/usb-Klipper_stm32h723xx_240028000E51313236343430-if00

[printer]
kinematics: cartesian
max_velocity: 250
max_accel: 1000
max_z_velocity: 12
max_z_accel: 100

[board_pins]
aliases:
    # EXP1 header
    EXP1_1=PE7, EXP1_2=PG1,
    EXP1_3=PG0, EXP1_4=PF15,
    EXP1_5=PF14, EXP1_6=PF13,    # Slot in the socket on this side
    EXP1_7=PF12, EXP1_8=PF11,
    EXP1_9=<GND>, EXP1_10=<5V>,

    # EXP2 header
    EXP2_1=PE13, EXP2_2=PE12,
    EXP2_3=PE15, EXP2_4=PE11,
    EXP2_5=PE10, EXP2_6=PE14,      # Slot in the socket on this side
    EXP2_7=PE8, EXP2_8=<RST>,
    EXP2_9=<GND>, EXP2_10=<NC>

[filament_switch_sensor switch_sensor]
switch_pin: ^PC0 # as shown in Figure 2, switch sensor IO is PA0
pause_on_runout: True
runout_gcode:
  NOTIFY_FILAMENT_CHANGE
# runout_gcode:
#   M300 S440 P500;
#   g4 p500;
#   M300 S440 P500;
#   g4 p500;
#   M300 S440 P500;
#   g4 p500;
#   M300 S440 P500;
#   g4 p500;
#   M300 S440 P500;
#   g4 p500;
#   M300 S440 P500;
#   g4 p500;
#   M300 S440 P500;
#   g4 p500;
#   M300 S440 P500;
#   g4 p500;
#   M300 S440 P500;
#   g4 p500;
#   M300 S440 P500;

[filament_motion_sensor encoder_sensor]
switch_pin: PF10 # as shown in Figure 2, motion sensor IO is PC2
detection_length: 21 # accuracy of motion sensor 2.88mm
extruder: extruder
pause_on_runout: False
# runout_gcode:
#   M300 S440 P200;
#   g4 p200;
#   M300 S440 P200;
#   g4 p200;
#   M300 S440 P200;
#   g4 p200;
#   M300 S440 P200;
#   g4 p200;
#   M300 S440 P200;
#   g4 p200;
#   M300 S440 P200;
#   g4 p200;
#   M300 S440 P200;
#   g4 p200;
#   M300 S440 P200;
#   g4 p200;
#   M300 S440 P200;
#   g4 p200;
#   M300 S440 P200;

[bltouch]
# sensor_pin: PD13
sensor_pin: PA10
# control_pin: PD12
control_pin: PA9
x_offset: 42
y_offset: 0
#z_offset: 0

[bed_mesh]
speed: 250
horizontal_move_z: 5
mesh_pps: 3, 3
mesh_min: 64, 30     # Probe coordinates
mesh_max: 765, 730
probe_count: 4, 4
algorithm: bicubic

[gcode_button kill_switch]
pin: ^!PC15
press_gcode: M112

[gcode_macro draw_box]
variable_repetitions: 1
variable_speed: 3000
gcode:
    {% set repetitions = params.REPETITIONS|int %}
    {% set speed = params.SPEED|default(variable_speed)|int %}
    SAVE_GCODE_STATE NAME=draw_box_state
    G90 ; Use absolute positioning
    {% for i in range(repetitions) %}
        G1 X300 Y300 F{ speed } ; Move to the starting corner of the cube at a safe Z height
        G1 X300 Y500 ; Draw the first line of the cube
        G1 X500 Y300 ; Draw the second line
        G1 X500 Y500 ; Draw the third line
        G1 X300 Y300 ; Close the cube
    {% endfor %}
    RESTORE_GCODE_STATE NAME=draw_box_state

[gcode_macro wiggle_x]
variable_repetitions: 1
gcode:
    {% set repetitions = params.REPETITIONS|int %}
    SAVE_GCODE_STATE NAME=wiggle_x_state
    G90 ; Use absolute positioning
    {% for i in range(repetitions) %}
      FORCE_MOVE STEPPER=stepper_x DISTANCE=100 VELOCITY=200 ACCEL=1000
      FORCE_MOVE STEPPER=stepper_x DISTANCE=-100 VELOCITY=200 ACCEL=1000
    {% endfor %}
    RESTORE_GCODE_STATE NAME=wiggle_x_state

[gcode_macro PAUSE]
description: Pause the actual running print
rename_existing: PAUSE_BASE
# change this if you need more or less extrusion
variable_extrude: 1.0
gcode:
  ##### read E from pause macro #####
  {% set E = printer["gcode_macro PAUSE"].extrude|float %}
  ##### set park positon for x and y #####
  # default is your max posion from your printer.cfg
  {% set x_park = printer.toolhead.axis_minimum.x|float + 25.0 %}
  {% set y_park = printer.toolhead.axis_minimum.y|float + 25.0 %}
  ##### calculate save lift position #####
  {% set max_z = printer.toolhead.axis_maximum.z|float %}
  {% set act_z = printer.toolhead.position.z|float %}
  {% if act_z < (max_z - 25.0) %}
      {% set z_safe = 25.0 %}
  {% else %}
      {% set z_safe = max_z - act_z %}
  {% endif %}
  ##### end of definitions #####
  PAUSE_BASE
  G91
  {% if printer.extruder.can_extrude|lower == 'true' %}
    G1 E-{E} F2100
  {% else %}
    {action_respond_info("Extruder not hot enough")}
  {% endif %}
  {% if "xyz" in printer.toolhead.homed_axes %}
    G1 Z{z_safe} F900
    G90
    G1 X{x_park} Y{y_park} F6000
  {% else %}
    {action_respond_info("Printer not homed")}
  {% endif %} 

[gcode_macro RESUME]
description: Resume the actual running print
rename_existing: RESUME_BASE
gcode:
  ##### read E from pause macro #####
  {% set E = printer["gcode_macro PAUSE"].extrude|float %}
  #### get VELOCITY parameter if specified ####
  {% if 'VELOCITY' in params|upper %}
    {% set get_params = ('VELOCITY=' + params.VELOCITY)  %}
  {%else %}
    {% set get_params = "" %}
  {% endif %}
  ##### end of definitions #####
  {% if printer.extruder.can_extrude|lower == 'true' %}
    G91
    G1 E{E} F2100
  {% else %}
    {action_respond_info("Extruder not hot enough")}
  {% endif %}  
  RESUME_BASE {get_params}

[gcode_macro CANCEL_PRINT]
description: Cancel the actual running print
rename_existing: CANCEL_PRINT_BASE
gcode:
  TURN_OFF_HEATERS
  CANCEL_PRINT_BASE

[gcode_macro NOTIFY_FILAMENT_CHANGE]
gcode:
  {action_call_remote_method("notify",
      name="telegram",
      message="Filament change needed!")}

[idle_timeout]
gcode:
  {% if printer.pause_resume.is_paused %}
    M118 Idle timeout while paused, turning off hotend
    SET_HEATER_TEMPERATURE HEATER=extruder TARGET=0
  {% else %}
    M118 Idle timeout
    TURN_OFF_HEATERS
    M84
  {% endif %}
timeout: 1800

[include timelapse.cfg]

[include moonraker_obico_macros.cfg]

#*# <---------------------- SAVE_CONFIG ---------------------->
#*# DO NOT EDIT THIS BLOCK OR BELOW. The contents are auto-generated.
#*#
#*# [heater_bed]
#*# control = pid
#*# pid_kp = 32.714
#*# pid_ki = 0.920
#*# pid_kd = 290.745
#*#
#*# [bed_mesh default]
#*# version = 1
#*# points =
#*#       0.663125, 0.118750, -0.032500, 0.359375
#*#       0.162500, -0.038125, -0.045000, 0.240625
#*#       0.124375, 0.091875, 0.226250, 0.541250
#*#       0.416250, 0.201875, 0.456875, 0.966875
#*# x_count = 4
#*# y_count = 4
#*# mesh_x_pps = 3
#*# mesh_y_pps = 3
#*# algo = bicubic
#*# tension = 0.2
#*# min_x = 64.0
#*# max_x = 764.98
#*# min_y = 30.0
#*# max_y = 730.0
#*#
#*# [extruder]
#*# control = pid
#*# pid_kp = 23.150
#*# pid_ki = 0.841
#*# pid_kd = 159.375
#*#
#*# [bltouch]
#*# z_offset = 1.500
3dprintpittsburgh commented 7 months ago

So in an attempt to debug this more, I ran the serial moonraker bridge python script directly from shell with the following command:

/usr/bin/python3 /home/biqu/Klipperotchy/tools/serial_moonraker_bridge/serial_moonraker_bridge.py --baudrate=115200 --serial='/dev/ttyUSB0' --moonraker='localhost:7125' --log=INFO

I was able to connect up and using --log=INFO allowed me to see the raw output from both machines back and forth. Using this, I'm able to see the data flow. What I was able to observe is that the Palette 3 seems to flood Klipper with X number of commands and then seems to wait for them to process before sending more downstream. When a splice happens, the Palette doesn't immediately send the m220 command to slow down. Rather, it seems to process out a few more g1 commands and then it sends the m220. This would imply that the palette is simply expecting the printer to be able to handle too many g1 commands before expending the buffer. The only way I can think of to help this would be to cache all the commands coming from the Palette into a buffer in the serial bridge and then manually sort and reorganize them as required. Otherwise, I'd imagine this would require modification to the actual source code of the palette.

My mind says local buffer in the bridge, but I'm really not sure what would be the best way to handle this as I'm pretty unfamiliar with the inner workings of the Palette's connected mode operation. Do you have any ideas around this?

shishu94 commented 7 months ago

I didn't manage to test anything today. But did look for more info into moonraker API and prioritizing command into klipper is not a thing, meaning that I wouldn't be able to send higher priority commands in the queue without modifying klipper.

Now your observations are quite helpful. The flood of commands is the normal behavior when running over serial, basically the printer accepts stuff into its buffer while it can and acks the query with a "ok" over serial, when the buffer is full (the receiver stops acking), the sender stops for a while and starts resending later hoping the receiver is able to receive again. That is how octoprint with marlin or even klipper works. Or lightburn with lasers. Nonetheless, I actually experimented with introducing a buffer in the bridge code, I have the code if you really want to try it. The problem that I had was that it kinda messes up with pings and pongs in connected mode. And if the buffer is too large it fails the print because it might end up accepting pings from the palette at a faster rate than the rate at what they are physically performed (M400 command).

I'm thinking of ways to run the job in klipper directly and kinda sync the palette. This would allow to introduce the M220 at a more precise moment, but I'm far from having it, and it would need a bit more work to start a job.

For your specific case, I can think of writing a klipper module associated with a IO pin connected to the filament cutter on the palette (that would require some hardware tweaking and you might not want to do that)

Now that actually makes me wonder if i shouldn't just create a klipper module that does what the bridge does, by doing so I would have much more freedom on what I can do to klipper.

Can you elaborate a bit more on your exact use case ? Do you do multi filament prints on the palette or just use the filament swap to rollover spools ? Depending on the use case I might be able to think about alternatives.

Le jeu. 11 avr. 2024 à 21:06, 3dprintpittsburgh @.***> a écrit :

So in an attempt to debug this more, I ran the serial moonraker bridge python script directly from shell with the following command:

/usr/bin/python3 /home/biqu/Klipperotchy/tools/serial_moonraker_bridge/serial_moonraker_bridge.py --baudrate=115200 --serial='/dev/ttyUSB0' --moonraker='localhost:7125' --log=INFO

I was able to connect up and using --log=INFO allowed me to see the raw output from both machines back and forth. Using this, I'm able to see the data flow. What I was able to observe is that the Palette 3 seems to flood Klipper with X number of commands and then seems to wait for them to process before sending more downstream. When a splice happens, the Palette doesn't immediately send the m220 command to slow down. Rather, it seems to process out a few more g1 commands and then it sends the m220. This would imply that the palette is simply expecting the printer to be able to handle too many g1 commands before expending the buffer. The only way I can think of to help this would be to cache all the commands coming from the Palette into a buffer in the serial bridge and then manually sort and reorganize them as required. Otherwise, I'd imagine this would require modification to the actual source code of the palette.

My mind says local buffer in the bridge, but I'm really not sure what would be the best way to handle this as I'm pretty unfamiliar with the inner workings of the Palette's connected mode operation. Do you have any ideas around this?

— Reply to this email directly, view it on GitHub https://github.com/shishu94/Klipperotchy/issues/3#issuecomment-2050336719, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGTZRJXRCC3POIQ563QYWS3Y43NMRAVCNFSM6AAAAABGBDWVNGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANJQGMZTMNZRHE . You are receiving this because you commented.Message ID: @.***>

3dprintpittsburgh commented 7 months ago

Nonetheless, I actually experimented with introducing a buffer in the bridge code, I have the code if you really want to try it. The problem that I had was that it kinda messes up with pings and pongs in connected mode. And if the buffer is too large it fails the print because it might end up accepting pings from the palette at a faster rate than the rate at what they are physically performed (M400 command).

So regarding this, I'd be happy to test your bridge code. Also, reading through your ACK explanation, would it be possible to perhaps store the incoming commands from the palette into an array/dict, which we can then use to send the commands only X at a time to Klipper (IE only pass through a max of five or ten gcode lines at a time). Then, the serial bridge listens for the ack from Klipper, and when that happens, it pops off the first item in the array, and forwards an ack to the palette. This way, we're intercepting that flow and controlling the rate at which the printer receives commands by more or less simulating acks. Or is this where you're saying that the pings and pongs mess up? I'd imagine that if we didn't relay all the acks straight back to the pallete and instead sent them at a slower rate from the serial bridge, that this could slow down the rate at which the palette is sending new commands because it still thinks it needs to wait for a cleared queue.

Then, if we have an array with say 50 commands in it from the palette, and a new one drops in that's m220, we can restructure our array to move the m220 up to the top, and then send that ack to the palette. I guess I can see how that could mess up ping/pong though because the palette is expecting that ack to be for a g1, not an m220, and if the filament isn't moving when the ack comes in, that could create the ping/pong gap. Is this what you're meaning here?

For your specific case, I can think of writing a klipper module associated with a IO pin connected to the filament cutter on the palette (that would require some hardware tweaking and you might not want to do that)

I'm perfectly comfortable tapping an IO pin, I would just need a bit of guidance on which wire to tap in to in order to get that signal back to the board, and how I should then configure that in klipper. I'd imagine it would just work like a switch with a [gcode_button] section in printer.cfg, but I still am unsure of which wire, if I would need ground too, and where I should look to drop them on the board. Basic IO? ADC? Mind giving me a bit of insight on which wire to snip?

My exact use case is primarily to do automated run out joining. Multi color would be nice, but the biggest concern and the reason I bought the palette in the first place was so that I could manage automated run out swaps. Any good ideas on how I could get that up and running quickly?