"Maximum recursion" crashes due to `gcode_button`

Basic Information:

Printer Model: Voron2.4
MCU / Printerboard: Octopus v1.1
klippy.log klippy (6).log (2.8 MB)

Describe your issue:

In my config I have the following [gcode_button] sections defined:

[gcode_macro DISPLAY_ON]
gcode:
    SET_LCD

[gcode_macro DISPLAY_OFF]
gcode:
    SET_LCD COLOR=0,0,0

[duplicate_pin_override]
pins: EXP1_2, EXP2_5

[gcode_button display_click_button]
pin: ^!EXP1_2
press_gcode:
    DISPLAY_ON
release_gcode:
    {% if not printer.menu.running %}
        UPDATE_DELAYED_GCODE ID=TURN_OFF_DISPLAY DURATION=30
    {% endif %}

[gcode_button display_enc_button]
pin: ^EXP2_5
press_gcode:
    DISPLAY_ON
release_gcode:
    {% if not printer.menu.running %}
        UPDATE_DELAYED_GCODE ID=TURN_OFF_DISPLAY DURATION=30
    {% endif %}

The EXP1_2 and EXP2_5 are aliases for PE7 and PB2 pins, which are the encoder and pushbutton on my display. What I want to get is the ability to turn on the display when the encoder or button are used and then turn it off30 seconds after.

All of that works except when there is a print running. If a print is running and I use the display, eventually Klipper crashes with a “Maximum recursion” error. My guess is that the issue will happen any time the display is used while there is other GCode being executed.

Is there anything bad with the way I’ve setup the buttons or could this be a bug in Klipper?

Some more data: I tried removing one of the gcode_button sections (“display_enc_button”) but that did not fix the issue. So, either one will cause the crash.

I’ve done some more debugging and I am starting to wonder if Python logging and greenlets can be mixed?

It seems that all of the crashes that I’ve caught are somewhere in the logging code. Greenlet documentation suggests that normal tracing does not work with greenlets because stacks get switched around. I also see that the logging code is trying to get the current frame. Wonder if that code is getting mixed up causing the recursion.

I would appreciate it if someone could attempt to reproduce this just so I know if this is unique to me or not.

You may have a look on your printer.cfg:

[gcode_macro SET_LCD]
description = Set display colors
gcode = 
	{% set user = printer["gcode_macro USER_VARIABLES"] %}
	{% set color_screen = params.COLOR|default(user.hw.default_color|join(","))|string %}
	{% set color_values = color_screen.split(",") %}
	{% set color = { "red" : color_values[0]|int / 255,
	"green" : color_values[1]|int / 255,
	"blue" : color_values[2]|int / 255} %}
	SET_LED LED=lcd_display RED={color.red} GREEN={color.green} BLUE={color.blue}

SET_LCD is calling itself (lastline), therefore the maximum recursive


BTW: A quite long config. Over 3000 lines. Did you wrote them all yourself?

The last line is SET_LED vs the macro name SET_LCD. Those are not the same.

Not all of it. Probably about half of that is taken from other places.

Oops, Mixed it up. Sorry.

I do not know for sure but actually I would be surprised if the “special” pins from the encoder can simply be overidden with secondary functions. I would guess that was never intended and thus is leading to the issue.

I tested with the following gcode_button config:

[gcode_button display_click_button]
pin: ^!EXP1_2
press_gcode:

[gcode_button display_enc_button]
pin: ^EXP2_5
press_gcode:

and the crashes still happen.

The crashes can be reproduced with the click pin, as well. It just takes longer since the number and frequency of events is much lower.

Well, it is the same logic. As I said, I doubt that it was intended or considered to be used like this.