From ae70cf8c4acd99da5bd94b573d680377197dfd15 Mon Sep 17 00:00:00 2001 From: pdscomp Date: Wed, 25 Mar 2026 10:39:36 -0400 Subject: [PATCH 1/3] clocksync: expose transmit_extra as a tunable danger_option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TRANSMIT_EXTRA constant in clocksync.py controls how far ahead of the estimated MCU clock the host pre-schedules commands. When the host is under CPU or scheduling pressure (e.g. competing processes, high swap, real-time preemption latency) the gap between when klippy calculates a clock value and when the MCU actually receives the command can exceed this window. The MCU then sees the scheduled waketime land in the past and fires the hard shutdown: MCU 'hotend' shutdown: Timer too close This commit: 1. Changes the default TRANSMIT_EXTRA from 0.001 s (1 ms) to 0.002 s (2 ms), giving the serialqueue twice as much headroom to absorb host scheduling jitter. 2. Exposes the value as `transmit_extra` in [danger_options] so operators can tune it without patching Python. Tuning guidance for end users ------------------------------ Add or adjust in your printer.cfg: [danger_options] transmit_extra: 0.002 # default; increase if you see 'Timer too close' Recommended values by scenario: - Quiet dedicated host (e.g. CB1/CM4 running only Klipper): 0.001 - Default / lightly loaded host: 0.002 - Shared host with other services or high CPU load: 0.003-0.004 - CAN-bus multi-MCU setups with added bus latency: 0.003-0.005 Keeping the value below ~0.010 s is advisable; larger values push commands further into the future relative to the MCU, which can cause a small increase in scheduling latency for move commands but has no impact on step timing accuracy or print quality at normal values. The MCU-side 'Timer too close' guard in src/sched.c is intentionally not modified — it is a hard safety check and should not be weakened. --- klippy/clocksync.py | 16 +++++++++++++--- klippy/extras/danger_options.py | 27 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/klippy/clocksync.py b/klippy/clocksync.py index 9dbd82de4d..81e2ab2d7e 100644 --- a/klippy/clocksync.py +++ b/klippy/clocksync.py @@ -9,7 +9,11 @@ RTT_AGE = 0.000010 / (60.0 * 60.0) DECAY = 1.0 / 30.0 -TRANSMIT_EXTRA = 0.001 +# Default forward-scheduling headroom added to the clock estimate sent +# to the serialqueue. Can be overridden via [danger_options] +# transmit_extra. Increase this (e.g. to 0.003-0.005) if you +# experience 'Timer too close' shutdowns on a heavily loaded host. +TRANSMIT_EXTRA = 0.002 class ClockSync: @@ -30,6 +34,11 @@ def __init__(self, reactor): self.clock_avg = self.clock_covariance = 0.0 self.prediction_variance = 0.0 self.last_prediction_time = 0.0 + self.transmit_extra = TRANSMIT_EXTRA + + def set_transmit_extra(self, value): + """Override the transmit_extra headroom (called from danger_options).""" + self.transmit_extra = value def disconnect(self): self.reactor.update_timer(self.get_clock_timer, self.reactor.NEVER) @@ -145,7 +154,7 @@ def _handle_clock(self, params): pred_stddev = math.sqrt(self.prediction_variance) self.serial.set_clock_est( new_freq, - self.time_avg + TRANSMIT_EXTRA, + self.time_avg + self.transmit_extra, int(self.clock_avg - 3.0 * pred_stddev), clock, ) @@ -192,7 +201,7 @@ def dump_debug(self): "clocksync state: mcu_freq=%d last_clock=%d" " clock_est=(%.3f %d %.3f) min_half_rtt=%.6f min_rtt_time=%.3f" " time_avg=%.3f(%.3f) clock_avg=%.3f(%.3f)" - " pred_variance=%.3f" + " pred_variance=%.3f transmit_extra=%.6f" % ( self.mcu_freq, self.last_clock, @@ -206,6 +215,7 @@ def dump_debug(self): self.clock_avg, self.clock_covariance, self.prediction_variance, + self.transmit_extra, ) ) diff --git a/klippy/extras/danger_options.py b/klippy/extras/danger_options.py index 554f2978fb..467d2e5635 100644 --- a/klippy/extras/danger_options.py +++ b/klippy/extras/danger_options.py @@ -1,3 +1,6 @@ +from ..clocksync import TRANSMIT_EXTRA + + class DangerOptions: def __init__(self, config): self.minimal_logging = config.getboolean("minimal_logging", False) @@ -38,6 +41,13 @@ def __init__(self, config): self.homing_elapsed_distance_tolerance = config.getfloat( "homing_elapsed_distance_tolerance", 0.5, minval=0.0 ) + # transmit_extra: forward-scheduling headroom (seconds) added to the + # MCU clock estimate passed to the serialqueue. Increasing this helps + # prevent 'Timer too close' MCU shutdowns on hosts that experience CPU + # or scheduling jitter. See docs/Danger_Options.md for guidance. + self.transmit_extra = config.getfloat( + "transmit_extra", TRANSMIT_EXTRA, minval=0.0, maxval=0.010 + ) temp_ignore_limits = False if config.getboolean("temp_ignore_limits", None) is None: @@ -78,4 +88,21 @@ def get_danger_options(): def load_config(config): global DANGER_OPTIONS DANGER_OPTIONS = DangerOptions(config) + # Apply transmit_extra to all ClockSync instances already registered + printer = config.get_printer() + reactor = printer.get_reactor() + # Wire transmit_extra into MCU clocksync objects at connect time + printer.register_event_handler( + "klippy:mcu_identify", + lambda: _apply_transmit_extra(printer, DANGER_OPTIONS.transmit_extra), + ) return DANGER_OPTIONS + + +def _apply_transmit_extra(printer, value): + """Push transmit_extra into all MCU clocksync objects.""" + for name, obj in printer.objects.items(): + if name == "mcu" or name.startswith("mcu "): + clocksync = getattr(obj, "_clocksync", None) + if clocksync is not None and hasattr(clocksync, "set_transmit_extra"): + clocksync.set_transmit_extra(value) From 07e85378ccf546324fb24222cd233235f04149e3 Mon Sep 17 00:00:00 2001 From: pdscomp Date: Wed, 25 Mar 2026 10:44:11 -0400 Subject: [PATCH 2/3] Update klippy/extras/danger_options.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- klippy/extras/danger_options.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/klippy/extras/danger_options.py b/klippy/extras/danger_options.py index 467d2e5635..6e649e2ecc 100644 --- a/klippy/extras/danger_options.py +++ b/klippy/extras/danger_options.py @@ -44,7 +44,8 @@ def __init__(self, config): # transmit_extra: forward-scheduling headroom (seconds) added to the # MCU clock estimate passed to the serialqueue. Increasing this helps # prevent 'Timer too close' MCU shutdowns on hosts that experience CPU - # or scheduling jitter. See docs/Danger_Options.md for guidance. + # or scheduling jitter. See docs/Config_Reference.md ([danger_options]) + # for guidance. self.transmit_extra = config.getfloat( "transmit_extra", TRANSMIT_EXTRA, minval=0.0, maxval=0.010 ) From 997a43299421d80a15f10dbf28b1ade07a8727b7 Mon Sep 17 00:00:00 2001 From: pdscomp Date: Wed, 25 Mar 2026 10:44:26 -0400 Subject: [PATCH 3/3] Update klippy/extras/danger_options.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- klippy/extras/danger_options.py | 1 - 1 file changed, 1 deletion(-) diff --git a/klippy/extras/danger_options.py b/klippy/extras/danger_options.py index 6e649e2ecc..f2f2f54c75 100644 --- a/klippy/extras/danger_options.py +++ b/klippy/extras/danger_options.py @@ -91,7 +91,6 @@ def load_config(config): DANGER_OPTIONS = DangerOptions(config) # Apply transmit_extra to all ClockSync instances already registered printer = config.get_printer() - reactor = printer.get_reactor() # Wire transmit_extra into MCU clocksync objects at connect time printer.register_event_handler( "klippy:mcu_identify",