I’ve been looking at Klipper’s existing Jinja2 templating support for macros and by the looks of it, we have the basic bare bones of Jinja2, plus the printer object and the actions_* commands.
I would like to introduce some extras, like a now() function to have the current date & time, some regular expression filters, …, some utility filters and functions that might come in handy for more “advanced” macro templating (kind like Home Assistant has)
I thought best to discuss this before doing any code… Is this something that would interest people?
Right starters, I’m thinking on filters that would allow to make better conditional commands!
For example, one could have a buzzer sound only if the current time is between 10am and 10pm!
I currently have an open PR that would allow to have a rawparams on the custom command templates; that together with regular expression filters could allow for more fine-tuned custom commands!
This is just a simple example but given the possibility I do believe the community might come with some interesting things!
I would love improvements in this area! My macros are now around 3K lines, and sometimes convoluted to get around missing functionality. Some things I’ve been thinking about for Klipper/Jinja…
Better reusability of macros/code. Adding ‘include’ support for Jinja2 so Jinja macros/functions can be reused would be fantastic. Even nicer would be for Jinja to share the namespace across Klipper macros so you could avoid having to do the ‘include’ on every macro that uses it (such as one include at the top of each Klipper macro file, or even just one at the start of printer.cfg)
I’d love both time/date support. It would be nice to be able to get a string (various formats) that can be used for messages (such as RESPOND or M117) or in names (such as mesh profiles). Also a number, such as milliseconds (or seconds) since epoch, so relative time compare can be done (how long did this print take? how long to do a bed mesh?). And of course simple access to hours, minutes, day, month etc as numbers.
Expanding on date/time, an alarm/timer feature [such as ‘[alarm_gcode]’ or ‘[timer_gcode]’) would be very useful (more of a Klipper plug-in than Jinja, but…) Basically the ability to specify gcode to execute at a particular time event (date/time, frequency like hourly, time of day, etc). Similar to [delayed_gcode] except it would run based on a specific time measurement. Also similar to delayed_gcode, it would allow macros to enable/disable or change the functionality at runtime. Use cases include turning off LED lights at a particular time, auto-pausing a print at night and starting up in morning, and starting a pre-heat of the bed/enclosure in the morning so it’s ready when you get to work.
Another feature I’d love to see is the ability to write content to a file. My primary use cases are general logging of macros (outside of the klipper log, which gets filled with other stuff), but also I’m doing a bunch of thermal analysis tests and currently I output CSV data to the console via action_respond_info but that then needs hand processing afterwards to get rid of the leading date/time and the ‘//’ comment characters before I can import the data into a spreadsheet for analysis.
I tried … but currently gcode_shell_command doesn’t allow sending parameters to the shell script (so no way to send my data to the script). I do see why file access might not be a common need, or perhaps one better suited for task-specific extras python implementation.
FWIW, the printer object can be used for some time related functionality. While cputime will not tell you the present time it can be used to determine time deltas, e.g., present - previous. I believe that Cputime provides the elapsed time since the MCU was powered ON in whole Minutes.decimal fraction format.
CPU time indication of how much processing time that the process has used since the process has started, not the elapsed time since the MCU was powered ON!
I would also like to have an absolute time to be able to switch printer off (via Tasmota) when it is the weekend or the printer has stopped at the middle of the night.
For that I need somehow to process the current day and time
People would try all kinds of crazy timing things in their gcode and stall their printer and come to the forum and ask why Klipper isn’t working right.
Then they’d ask for non-blocking waits in the G-code, but how do you handle that in a G-code queue? Now you’re asking for multi-threaded Gcode.
As the person before you wanted, how would you even run a Gcode to determine if it’s the weekend and turn off your printer? That’s not really what GCodes are for, that’s what a home automation plug is for and Moonraker already works with that for that very thing.
People can probably already do all manner of stupid things in macros, but if they feel competent enough to change the code, they should understand what they are doing and accept the consequences.
I also agree that using G-code to turn the printer off is not sensible. Using time tests so it doesn’t sound a beeper at 3am possibly is.
The python code I run actually has significant changes in specific areas which I’ve merged with almost every update of the past year - I check for updates a couple of times a week. I haven’t yet decided if they’re likely to be useful enough to others to warrant a PR.
Having programmed for over 40 years and previously submitted PRs to Marlin and BTT’s touchscreen code, I probably do understand what I’m doing and if I encountered something I couldn’t fix myself and needed to ask for support would first revert to a standard setup to check I hadn’t done something stupid!
Aw man, that seemed so simple!
I have developer experience, although this was a little out of my usual area of expertise. Nonetheless, I thought I could pull it off. It didn’t work (sigh).
Here’s what I did-- perhaps someone can tell me what I missed:
1) Use ssh to transfer /usr/share/klipper/klippy/extras/gcode_macro.py to my computer.
2) Edited it using my favorite editor, added:
import datetime
after import jinja2, line 8.
Then added:
'now': datetime.now,
after action_call_remote_method: self._action_call_remote_method,
in the def create_template_context section of class PrinterGCodeMacro, line 112.
3) Saved the file. (Duh)
4) Compiled it. Opened a command window, and navigated to the folder where I have the gcode_macro.py file, and entered:
python -m compileall .
it created a __pycache__ subfolder containing my newly compiled file; gcode_macro.pyc
5) In ssh, navigated to the extras folder:
cd /usr/share/klipper/klippy/extras
6) Renamed the original file for safe keeping:
mv gcode_macro.pyc gcode_macro.pyc.original.bak
7) Transfered the new version of gcode_macro.pyc back to the printer.
8) Add the following test code to START_PRINT
{% set hour=now().hour %}
M118 The Hour is {%hour%}
9) Restarted Klipper.
10) Ran a test print.
11) Got error:
!! {"code":"key165", "msg": "Error evaluating 'gcode_macro START_PRINT:gcode': jinja2.exceptions.UndefinedError: 'now' is undefined
I recently (ab)used G-code Macros to hack together MMU load/unload commands.
It’s important to remember that the entirety of the Jinja2 template is evaluated immediately when the macro is called.
A simple 'Do something, then check the results macro has to look like:
[gcode_macro _UNLOAD_JAM_CHECK]
gcode:
...
{% if sync_feedback_detected %}
M118 Filament Jammed!
{% endif %}
[gcode_macro UNLOAD_FILAMENT]
description: Unload the fillament all the way to the 3MS unit.
gcode:
...
FORCE_MOVE STEPPER=... DISTANCE=...VELOCITY=...
_UNLOAD_JAM_CHECK
I can see you’re not used to Python and are over-thinking it. Let me help you a bit.
Python should be treated as closer to an interpreted language than a compiled one. Yes, there’s a JIT. However, pyc files are just cached bytecode. Which is why the pycache folder is named what it is. You can safely delete it and the code will still run. It’s a horrible analogy, but treat it like you would a “.sh” file.
In practice:
Ignore compileall.
Just delete the pycache folder(s)
make a copy of the original “py” file. (or just use git)
Overwrite it with your modified file.
Re-Start Klipper.
That’s it. Just edit the file and re-start.
Here’s a few other tips.
Python 2 is EOL, but some older systems still have it, and call it python. If that’s the case for you, use the python3 command.
If you are going to run Python from the command line, be aware it’s common to have multiple Python interpreters. That’s actually the recommended Klipper install method.