I am currently working on a project, where we want to control the print head of a Voron 2.4 using a gamepad. So basically, we translate the deflection of the joystick into gcode and send around 100 gcode commands per second to /tmp/printer.
First, we have noticed a significant delay of around 0.4s between sending the gcode commands and actual execution.
A second problem that appeared was “batching” of gcode commands. A video of this can be found here. If I deflect the joystick only by a tiny bit and move it back into “neutral” (where no gcode commands are sent), the first movement is seen after around 0.4 second. However, if I make a large continuous movement wit the joystick, the first movement is only seen long after the 0.4s. So apparently Klipper seems to wait somehow to batch these gcode commands.
We have tried fiddling with the _process_data function in GCodeIO and with the create_pty in util.py, but with limited success.
Any idea’s and suggestions for problem 1 & 2 are appreciated.
For what it is worth, what you’ve described is the intended behavior of Klipper. Klipper is designed to read batches of movement commands (as one would find in a typical “3d print g-code file”) and generate the appropriate movement and acceleration to produce the desired “printed object”. Klipper is not designed to support dynamic low latency user control of motors.
Just to give you all an update as I have noticed there is some interest in this topic:
The solution is to integrate your logic as a module into Klipper. To do that, we created a module that receives an instance of toolhead (we had to programm a callback in toolhead.py here). Movement commands are now sent via
toolhead.move()
and are executed in near real-time.
However, the issue with the look-ahead mechanism is still unsolved, but we are really confident that we can solve it.
@koconnor what constants can we change to reduce look-ahead to a minimum? I think we don’t really need a look-ahead, since the gamepad already generates movement commands with sufficient correlation, so rapid acceleration is not a problem here.
@lhelmig Did you get any further regarding this topic ? I’m looking for a way to have near real-time control at times but let klipper run its kinematics otherwise
It depends on what you mean by “near real-time”. Dealing with lookahead seems to be possible without any real modifications…model your controls to map the time of a “button press” to the time the tool moves. You would likely want to set your accelerations as high as you can get and keep the tool speeds reasonably low. In addition it would be necessary to buffer some movement to keep the tool from stopping, this would add a bit of latency.
Lookahead is only on part of the equation. By design Klipper schedules stepper events in the future on connected MCUs. This is necessary to synchronize events across multiple MCUs and deal with the fact that the host is running on a GPOS. Kevin can correct me if I’m wrong, but I believe the best latency you can hope for (sans lookahead and buffering) will still be above 100ms.
In my opinion, it is not a question about the queue size or timing but the arichtecture of the solution. I’m also looking for a similar solution and played around with some parameter and It seems not possibel to empty a queue partially, which where is not a deacceleration at the end.
Have you had any further developments with your project @lhelmig ? Or anyone else with real-time/ near real time user control implementation?
I’m attempting to develop something very similar myself. It’s not ideal, but as a proof of concept work around I wrote some code that takes my full movement commands and automatically segments them into small gcode movements that sit in a buffer until it’s their time to be sent. This allows for interruptions and changes in movement with minimal delay.
For a simplified example. X100 would be segmented into 10 separate x10 commands. They are sent at a frequency determined by the distance to travel and the input speed. When another command is prompted, say an interruption to pause the motors, or slow the speed down, the buffer cue is emptied and repopulated with updated segmented commands. This works in my tests, but to prevent noticeable changes between segments it requires acceleration to be set so high that it’s essentially off. Again, not ideal.
Would love a more robust integrated approach. It sounds like @lhelmig was getting close!
Alternatively: is there an alternative to Klipper that would do this natively?