Skip to content

Commit

Permalink
Merge branch 'kevin_2_merge'
Browse files Browse the repository at this point in the history
  • Loading branch information
Zeanon committed Nov 29, 2024
2 parents ced92d2 + 8a3c562 commit fe48dad
Show file tree
Hide file tree
Showing 166 changed files with 3,873 additions and 1,465 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ See the [Danger Features document](https://dangerklipper.io/Danger_Features.html

- [core: non-critical-mcus](https://github.com/DangerKlippers/danger-klipper/pull/339)

- [gcode_macros: !python templates](https://github.com/DangerKlippers/danger-klipper/pull/360)

- [core: action_log](https://github.com/DangerKlippers/danger-klipper/pull/367)

- [danger_options: configurable homing constants](https://github.com/DangerKlippers/danger-klipper/pull/378)
Expand Down
90 changes: 82 additions & 8 deletions docs/Command_Templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,17 @@ speed (via the `F` parameter) on the first `G1` command.

## Template expansion

The gcode_macro `gcode:` config section is evaluated using the Jinja2
template language. One can evaluate expressions at run-time by
wrapping them in `{ }` characters or use conditional statements
wrapped in `{% %}`. See the
The gcode_macro `gcode:` config section is evaluated using either the Jinja2
template language or Python.

### Jinja2

One can evaluate expressions at run-time by wrapping them in `{ }` characters
or use conditional statements wrapped in `{% %}`. See the
[Jinja2 documentation](http://jinja.pocoo.org/docs/2.10/templates/)
for further information on the syntax.

An example of a complex macro:
An example of a complex Jinja2 macro:
```
[gcode_macro clean_nozzle]
gcode:
Expand All @@ -101,7 +104,7 @@ gcode:
RESTORE_GCODE_STATE NAME=clean_nozzle_state
```

### Macro parameters
#### Jinja2: Macro parameters

It is often useful to inspect parameters passed to the macro when
it is called. These parameters are available via the `params`
Expand All @@ -128,7 +131,7 @@ gcode:
M140 S{bed_temp}
```

### The "rawparams" variable
#### Jinja2: The "rawparams" variable

The full unparsed parameters for the running macro can be access via the
`rawparams` pseudo-variable.
Expand All @@ -138,7 +141,7 @@ Note that this will include any comments that were part of the original command.
See the [sample-macros.cfg](../config/sample-macros.cfg) file for an example
showing how to override the `M117` command using `rawparams`.

### The "printer" Variable
#### Jinja2 The "printer" variable

It is possible to inspect (and alter) the current state of the printer
via the `printer` pseudo-variable. For example:
Expand Down Expand Up @@ -178,6 +181,77 @@ gcode:
M117 Temp:{sensor.temperature} Humidity:{sensor.humidity}
```

### Python

Templates can also be written in Python code. The template will automatically
be interpreted as Python if lines are prefixed with `!`.
Note: You can't mix Python and Jinja2.

An example of a complex Python macro:
```
[gcode_macro clean_nozzle]
gcode:
!wipe_count = 8
!emit("G90")
!emit("G0 Z15 F300")
!for wipe in range(wipe_count):
! for coordinate in [(275, 4), (235, 4)]:
! emit(f"G0 X{coordinate[0]} Y{coordinate[1] + 0.25 * wipe} Z9.7 F12000")
```

#### Python: Rawparams

```
[gcode_macro G4]
rename_existing: G4.1
gcode:
!if rawparams and "S" in rawparams:
! s = int(rawparams.split("S")[1])
! respond_info(f"Sleeping for {s} seconds")
! emit(f"G4.1 P{s * 1000}")
!else:
! p = int(rawparams.split("P")[1])
! respond_info(f"Sleeping for {p/1000} seconds")
! emit(f"G4.1 {rawparams}")
```

#### Python: Variables

```
[gcode_macro POKELOOP]
variable_count: 10
variable_speed: 3
gcode:
!for i in range(own_vars.count):
! emit(f"BEACON_POKE SPEED={own_vars.speed} TOP=5 BOTTOM=-0.3")
```

#### Python: Printer objects

```
[gcode_macro EXTRUDER_TEMP]
gcode:
!ACTUAL_TEMP = printer["extruder"]["temperature"]
!TARGET_TEMP = printer["extruder"]["target"]
!
!respond_info("Extruder Target: %.1fC, Actual: %.1fC" % (TARGET_TEMP, ACTUAL_TEMP))
```

#### Python: Helpers

- emit
- wait_while
- wait_until
- wait_moves
- blocking
- sleep
- set_gcode_variable
- emergency_stop / action_emergency_stop
- respond_info / action_respond_info
- raise_error / action_raise_error
- call_remote_method / action_call_remote_method
- math

## Actions

There are some commands available that can alter the state of the
Expand Down
6 changes: 6 additions & 0 deletions docs/Config_Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ All dates in this document are approximate.

## Changes

20241125: The `off_below` parameter in fans config section is
deprecated. It will be removed in the near future. Use
[`min_power`](./Config_Reference.md#fans)
instead. The `printer[fan object].speed` status will be replaced by
`printer[fan object].value` and `printer[fan object].power`.

20240430: The `adc_ignore_limits` parameter in the `[danger_options]`
config section has been renamed to `temp_ignore_limits` and it now
covers all possible temperature sensors.
Expand Down
3 changes: 3 additions & 0 deletions docs/Config_Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -3326,6 +3326,9 @@ pin:
# input. In such a case, the PWM pin can be used normally, and e.g. a
# ground-switched FET(standard fan pin) can be used to control power to
# the fan.
#off_below:
# These option is deprecated and should no longer be specified.
# Use `min_power` instead.
```

### [heated_fan]
Expand Down
1 change: 1 addition & 0 deletions docs/Danger_Features.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
- The jinja `do` extension has been enabled. You can now call functions in your macros without resorting to dirty hacks: `{% do array.append(5) %}`
- The python [`math`](https://docs.python.org/3/library/math.html) library is available to macros. `{math.sin(math.pi * variable)}` and more!
- New [`RELOAD_GCODE_MACROS`](./G-Codes.md#reload_gcode_macros) G-Code command to reload `[gcode_macro]` templates without requiring a restart.
- G-Code Macros can be written in Python. Read more [here](./Command_Templates.md)

## [Plugins](./Plugins.md)

Expand Down
5 changes: 4 additions & 1 deletion docs/Status_Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,12 @@ The following information is available in
[heater_fan some_name](Config_Reference.md#heater_fan) and
[controller_fan some_name](Config_Reference.md#controller_fan)
objects:
- `speed`: The fan speed as a float between 0.0 and 1.0.
- `value`: The fan speed value as a float between 0.0 and 1.0.
- `power`: The fan power as a float between 0|`min_power` and 1.0|`max_power`.
- `rpm`: The measured fan speed in rotations per minute if the fan has
a tachometer_pin defined.
deprecated objects (for UI compatibility only):
- `speed`: The fan speed as a float between 0.0 and `max_power`.

## filament_switch_sensor

Expand Down
23 changes: 17 additions & 6 deletions klippy/clocksync.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,30 @@ def _handle_clock(self, params):
self.clock_est[2],
)
# Filter out samples that are extreme outliers
exp_clock = (sent_time - self.time_avg) * self.clock_est[2] + self.clock_avg
exp_clock = (sent_time - self.time_avg) * self.clock_est[
2
] + self.clock_avg
clock_diff2 = (clock - exp_clock) ** 2
if (
clock_diff2 > 25.0 * self.prediction_variance
and clock_diff2 > (0.000500 * self.mcu_freq) ** 2
):
if clock > exp_clock and sent_time < self.last_prediction_time + 10.0:
if (
clock > exp_clock
and sent_time < self.last_prediction_time + 10.0
):
logging.debug(
"Ignoring clock sample %.3f:" " freq=%d diff=%d stddev=%.3f",
"Ignoring clock sample %.3f:"
" freq=%d diff=%d stddev=%.3f",
sent_time,
self.clock_est[2],
clock - exp_clock,
math.sqrt(self.prediction_variance),
)
return
logging.info(
"Resetting prediction variance %.3f:" " freq=%d diff=%d stddev=%.3f",
"Resetting prediction variance %.3f:"
" freq=%d diff=%d stddev=%.3f",
sent_time,
self.clock_est[2],
clock - exp_clock,
Expand Down Expand Up @@ -234,15 +241,19 @@ def connect_file(self, serial, pace=False):
# clock frequency conversions
def print_time_to_clock(self, print_time):
if self.clock_adj[1] == 1.0:
logging.warning("Clock not yet synchronized, clock is untrustworthy")
logging.warning(
"Clock not yet synchronized, clock is untrustworthy"
)
for line in traceback.format_stack():
logging.warning(line.strip())
adjusted_offset, adjusted_freq = self.clock_adj
return int((print_time - adjusted_offset) * adjusted_freq)

def clock_to_print_time(self, clock):
if self.clock_adj[1] == 1.0:
logging.warning("Clock not yet synchronized, print time is untrustworthy")
logging.warning(
"Clock not yet synchronized, print time is untrustworthy"
)
for line in traceback.format_stack():
logging.warning(line.strip())
adjusted_offset, adjusted_freq = self.clock_adj
Expand Down
52 changes: 38 additions & 14 deletions klippy/configfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,17 @@ def _get_wrapper(
self.access_tracking[acc_id] = default
return default
raise error(
"Option '%s' in section '%s' must be specified" % (option, self.section)
"Option '%s' in section '%s' must be specified"
% (option, self.section)
)
try:
v = parser(self.section, option)
except self.error as e:
raise
except:
raise error(
"Unable to parse option '%s' in section '%s'" % (option, self.section)
"Unable to parse option '%s' in section '%s'"
% (option, self.section)
)
if note_valid:
self.access_tracking[(self.section.lower(), option.lower())] = v
Expand Down Expand Up @@ -171,9 +173,13 @@ def lparser(value, pos):
def fcparser(section, option):
return lparser(self.fileconfig.get(section, option), len(seps) - 1)

return self._get_wrapper(fcparser, option, default, note_valid=note_valid)
return self._get_wrapper(
fcparser, option, default, note_valid=note_valid
)

def getlist(self, option, default=sentinel, sep=",", count=None, note_valid=True):
def getlist(
self, option, default=sentinel, sep=",", count=None, note_valid=True
):
return self.getlists(
option,
default,
Expand Down Expand Up @@ -224,7 +230,9 @@ def get_prefix_sections(self, prefix):

def get_prefix_options(self, prefix):
return [
o for o in self.fileconfig.options(self.section) if o.startswith(prefix)
o
for o in self.fileconfig.options(self.section)
if o.startswith(prefix)
]

def deprecate(self, option, value=None):
Expand Down Expand Up @@ -298,7 +306,8 @@ def _find_autosave_data(self, data):
# Check for errors and strip line prefixes
if "\n#*# " in regular_data:
logging.warning(
"Can't read autosave from config file" " - autosave state corrupted"
"Can't read autosave from config file"
" - autosave state corrupted"
)
return data, ""
out = [""]
Expand Down Expand Up @@ -353,7 +362,9 @@ def _parse_config_buffer(self, buffer, filename, fileconfig):
else:
fileconfig.readfp(sbuffer, filename)

def _resolve_include(self, source_filename, include_spec, fileconfig, visited):
def _resolve_include(
self, source_filename, include_spec, fileconfig, visited
):
dirname = os.path.dirname(source_filename)
include_spec = include_spec.strip()
include_glob = os.path.join(dirname, include_spec)
Expand All @@ -367,7 +378,9 @@ def _resolve_include(self, source_filename, include_spec, fileconfig, visited):
include_filenames.sort()
for include_filename in include_filenames:
include_data = self._read_config_file(include_filename)
self._parse_config(include_data, include_filename, fileconfig, visited)
self._parse_config(
include_data, include_filename, fileconfig, visited
)
return include_filenames

def _parse_config(self, data, filename, fileconfig, visited):
Expand All @@ -390,7 +403,9 @@ def _parse_config(self, data, filename, fileconfig, visited):
if header and header.startswith("include "):
self._parse_config_buffer(buffer, filename, fileconfig)
include_spec = header[8:].strip()
self._resolve_include(filename, include_spec, fileconfig, visited)
self._resolve_include(
filename, include_spec, fileconfig, visited
)
else:
buffer.append(line)
self._parse_config_buffer(buffer, filename, fileconfig)
Expand All @@ -412,7 +427,9 @@ def _build_config_string(self, config):
return sfile.getvalue().strip()

def read_config(self, filename):
return self._build_config_wrapper(self._read_config_file(filename), filename)
return self._build_config_wrapper(
self._read_config_file(filename), filename
)

def read_main_config(self):
filename = self.printer.get_start_args()["config_file"]
Expand All @@ -439,7 +456,8 @@ def check_unused_options(self, config, error_on_unused):
if section not in valid_sections and section not in objects:
if error_on_unused:
raise error(
"Section '%s' is not a valid config section" % (section,)
"Section '%s' is not a valid config section"
% (section,)
)
else:
self.unused_sections.append(section)
Expand Down Expand Up @@ -557,7 +575,9 @@ def _disallow_include_conflicts(self, regular_data, cfgname, gcode):
if config.fileconfig.has_option(section, option):
# They conflict only if they are not the same value
included_value = config.fileconfig.get(section, option)
autosave_value = self.autosave.fileconfig.get(section, option)
autosave_value = self.autosave.fileconfig.get(
section, option
)
if included_value != autosave_value:
msg = (
"SAVE_CONFIG section '%s' option '%s' value '%s' conflicts "
Expand Down Expand Up @@ -586,7 +606,9 @@ def _write_backup(self, cfgpath, cfgdata, gcode, write_backups):
backup_path = backupdir + "/" + cfgname + datestr
if cfgpath.endswith(".cfg"):
backup_path = backupdir + "/" + cfgname[:-4] + datestr + ".cfg"
logging.info("SAVE_CONFIG to '%s' (backup in '%s')", cfgpath, backup_path)
logging.info(
"SAVE_CONFIG to '%s' (backup in '%s')", cfgpath, backup_path
)
try:
if write_backups:
# Read the current config into the backup before making changes to
Expand Down Expand Up @@ -632,7 +654,9 @@ def _save_includes(self, cfgpath, data, visitedpaths, gcode, write_backups):
if not include_filenames and not glob.has_magic(include_glob):
# Empty set is OK if wildcard but not for direct file
# reference
raise error("Include file '%s' does not exist" % (include_glob,))
raise error(
"Include file '%s' does not exist" % (include_glob,)
)
include_filenames.sort()
# Read the include files and check them against autosave data.
# If autosave data overwites anything we'll update the file
Expand Down
Loading

0 comments on commit fe48dad

Please sign in to comment.