The User Problem
Currently a klipper printer lacks Gnosis - the self awareness of how the user is currently using the printer. Other printers generally know and remember:
- If filament is loaded or not
- The type of filament loaded
- The filaments temperature and bed temperature
- The diameter of the nozzle installed
- The removable build sheet installed
- The build sheets “squish” - additional z-offset required for a good first layer
All of these things are needed to implement functionality common in a modern printer:
- Basic print safety checks:
- Filament is installed, the filament matched the print file filament type
- Print file nozzle size matches the installed nozzle
- Build sheet installed matches the print file
- Pre-heating / heat soaking the extruder and heater bed
- Automating filament loading and unloading
- Automating babystepping when build sheets are swapped
- Automating safe purge lines for different nozzle widths
For the sake of needing a term I’ll calling these things ‘User Settings’. Klipper currently lacks a place for klippy modules to store user settings that are not true Config. User settings differ from Config in 3 ways:
- A printer restart is not required to make the change, users don’t expect a workflow interruption.
- The user expects the printer to remember the setting between restarts
- Users don’t expect to have to confirm or save the setting for it to be remembered
Currently all front ends for klipper get around this limitation by asking the user to supply this information repeatedly every time it is required (e.g. filament temperatures). Or they simply omit basic functionality (build sheets, nozzles, print safety checks).
Over the past years I’ve implemented all of these ideas in macros and extensions (filaments, build sheets, nozzles). They all store their state in the save_variables
file. This works, but only if save_variables
is configured and set up to be stored in a separate file. Programming against save_variables
has downsides. If the user doesn’t set things up as required is causes a lot of confusion. This is not a good solution for extensions.
Proposal
TLDR: Extend the config
object with a user_settings
property that implements the ConfigWrapper
interface. Automatically save user settings in a separate file when user_settings.set(key, value)
is used.
File Storage
The user settings file will be stored in a file named <config-name>.user_settings.cfg
in the same location as the printers config file. e.g. if the printers config file is:
/usr/pi/printer-data/printer.cfg
The settings file will be:
/usr/pi/printer-data/printer.user_settings.cfg
If the file does not exist it is automatically created when something is saved. Whenever user settings are updated the file is silently written.
Programming Interface
User settings are accessible to a klippy module via the config object:
class MyModule:
def __init__(self, config)
user_settings = config.user_settings
The user settings
interface matches the existing ConfigWrapper
interface, this makes retrieving data from user_settings
use the same patterns as retrieving data from config. This is already familiar to klipper developers:
user_settings.getint()
user_settings.getfloat()
# etc.
Default Values & Read Fall-through
User settings have fall-through on read behavior. If the setting is not available in the user settings file, it will be looked up in the config file in the same section as the module. This allows for authoring config to specify default values that override the default in the get*
method calls. Here is a real world example: the most popular nozzle size is 0.4
, so we might default the nozzle size in code to this value:
nozzle_diameter = user_settings.getfloat('installed_nozzle_diameter', default=0.4)
If you wanted to create a clay/chocolate printer with a default nozzle diameter of 2.0mm, you can specify that in the printer.cfg
. This means printer designers never have to write to the settings file to change default printer behavior.
Saving Settings
Settings are saved via a set
method on the settings object:
user_settings.set('key', python_string_value)
When this happens the user settings file is written immediately.
Macro Access
The user setting’s current value is exposed via the printer model object. This means the storage value and the exposed model don’t have to match exactly if that is not desirable (storage format is an abstraction for the module). The values can be placed wherever is most appropriate for the object model.
If a macro author wants to read the settings directly, they can do this via the configfile.user_settings
.
FAQ
user_settings
is too long!
I agree . I’m not married to the name, please propose a better one.
config_file
currently exposes a settings
field so that name was already taken. Maybe props
?
Why not store settings in save_variables
?
save_variables
is accessible to macros meaning the data is vulnerable to being overwritten by macro authors.save_variables
lacks a namespacing scheme to stop multiple modules from overwriting each others settings. Config solves this problem.- Its optional, meaning access can fail
- If its stored in the default location, inside printer config, it requires user action to save settings.
Why not use a structured database like SQLite?
- It’s not clear there is a benefit based on the small number of things we want to actually store.
- Moonraker already has a such a structured data store. For large amounts of data maybe that’s a better place to do it?
- If your idea has specific storage requirements you can implement your own storage ala Spoolman.
- We can re-use what we already have in
config_file.py
to implement this quickly. - Module and extensions authors wont have to learn a new API