Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b926d5d
zram: make swap initialization reliable on low-memory printers
pdscomp Jul 18, 2026
764e174
overlayfs: refresh image-owned init scripts on update
pdscomp Jul 18, 2026
4831b0a
ustreamer: reduce camera streamer memory use
pdscomp Jul 18, 2026
78ad740
overlayfs: escape shell expansions in preinit template
pdscomp Jul 18, 2026
b5d80fc
kernel: reduce runtime memory footprint on Centauri
pdscomp Jul 18, 2026
4edb6be
python: reduce Klipper and Moonraker memory overhead
pdscomp Jul 18, 2026
c25ea7b
kalico: bound input-shaper calibration memory
pdscomp Jul 18, 2026
c851dcb
rusty-shaper: add low-RAM input-shaper calibration
pdscomp Jul 18, 2026
51cda0e
calibration: skip stages with saved results
pdscomp Jul 18, 2026
6f900c9
kalico: persist input-shaper settings and stream raw accelerometer wr…
pdscomp Jul 19, 2026
b348004
calibration: detect only saved extruder PID results
pdscomp Jul 19, 2026
4b0ba66
kalico: update HX711 reliability revision
pdscomp Jul 23, 2026
b1ffdff
kalico: retry transient invalid HX711 probe samples
pdscomp Jul 23, 2026
1cd5e28
kalico: retry single HX711 acquisition error during probing
pdscomp Jul 23, 2026
f237e50
kalico: bump to 13952564 (HX711 timing-miss reclassification)
pdscomp Jul 23, 2026
2922c5e
kalico: bump to 426ac30b (drift-safety vs trigger-confirm race fix)
pdscomp Jul 23, 2026
87e8b6c
kalico: bump to a4ceadd3 (first-crossing trigger default)
pdscomp Jul 23, 2026
939bdbf
kalico: bump to 87f1c4f7 (per-channel spike rejection)
pdscomp Jul 23, 2026
72c4ab1
macros: relax LOADCELL_Z_HOME cleaning-pass samples_tolerance to 0.1
pdscomp Jul 23, 2026
caf0bbf
kalico: bump to c368f84e (bulk overflow tolerance)
pdscomp Jul 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions meta-opencentauri/images/files/overlayfs-etc-preinit.sh.in
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,37 @@ then
mkdir -p $UPPER_DIR
mkdir -p $WORK_DIR

# Init scripts supplied by the image are release-owned rather than printer
# configuration. Before /etc becomes an overlay, compare upper entries
# with the new rootfs's /etc/init.d and discard only stale overrides of
# scripts that the image also supplies. This keeps user-created scripts
# (for example ustreamer.new) intact while allowing every shipped service
# to receive revised launch defaults on the first boot after an A/B update.
if [ -d "$UPPER_DIR/init.d" ]; then
for upper_script in "$UPPER_DIR"/init.d/*; do
[ -f "$upper_script" ] || continue
script_name=${{upper_script##*/}}
[ -f "/etc/init.d/$script_name" ] || continue
rm -f "$upper_script"
done

# A deleted image script may be represented by an overlayfs whiteout.
# Remove whiteouts only when they hide an image-owned init script.
for whiteout in "$UPPER_DIR"/init.d/.wh.*; do
[ -e "$whiteout" ] || continue
whiteout_name=${{whiteout##*/}}
case "$whiteout_name" in
.wh..wh..opq)
rm -f "$whiteout"
;;
.wh.*)
script_name=${{whiteout_name#.wh.}}
[ -f "/etc/init.d/$script_name" ] && rm -f "$whiteout"
;;
esac
done
fi

if {OVERLAYFS_ETC_EXPOSE_LOWER}; then
mkdir -p $LOWER_DIR

Expand Down
1 change: 1 addition & 0 deletions meta-opencentauri/images/opencentauri-image-base.bb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ CORE_IMAGE_EXTRA_INSTALL += "\
rtw88 \
aic8800 \
kalico \
rusty-shaper \
moonraker \
mainsail \
fluidd \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
From 0e90d3d558a119af3239519e105778c8a5bd3d16 Mon Sep 17 00:00:00 2001
From: James Turton <james.turton@gmx.com>
Date: Sat, 27 Jun 2026 10:50:56 +0200
Subject: [PATCH] shaper_calibrate: bound Welch PSD memory use

The previous Welch implementation materialised and transformed every
overlapping window at once. On the printer that makes its largest temporary
arrays proportional to the complete accelerometer capture.

Accumulate the FFT energy in fixed 64-window batches instead. This preserves
the original PSD calculation while bounding the window and FFT intermediates.
Also pack the retained samples as float32 and keep timestamps relative to the
first sample, avoiding precision loss from large absolute timestamps.

This intentionally does not change accelerometer capture or raw CSV writing:
it reduces the memory used while Kalico processes calibration data.

Upstream-Status: Pending [OpenCentauri memory-constrained targets]

---
klippy/extras/shaper_calibrate.py | 49 ++++++++++++++++++++++--------
1 file changed, 35 insertions(+), 14 deletions(-)

diff --git a/klippy/extras/shaper_calibrate.py b/klippy/extras/shaper_calibrate.py
index 230e2f58..457d3632 100644
--- a/klippy/extras/shaper_calibrate.py
+++ b/klippy/extras/shaper_calibrate.py
@@ -147,25 +147,31 @@ class ShaperCalibrate:
window = np.kaiser(nfft, 6.0)
# Compensation for windowing loss
scale = 1.0 / (window**2).sum()
+ window = window[:, None]

# Split into overlapping windows of size nfft
overlap = nfft // 2
x = self._split_into_windows(x, nfft, overlap)
+ n_windows = x.shape[-1]
+
+ # Accumulate the response over windows in small batches to keep
+ # peak memory usage low
+ psd = np.zeros(nfft // 2 + 1)
+ batch = 64
+ for start in range(0, n_windows, batch):
+ win = x[:, start : start + batch]
+ # First detrend, then apply windowing function
+ win = window * (win - win.mean(axis=0))
+ # Calculate frequency response for each window using FFT
+ result = np.fft.rfft(win, n=nfft, axis=0)
+ psd += (result.real**2 + result.imag**2).sum(axis=-1)

- # First detrend, then apply windowing function
- x = window[:, None] * (x - np.mean(x, axis=0))
-
- # Calculate frequency response for each window using FFT
- result = np.fft.rfft(x, n=nfft, axis=0)
- result = np.conjugate(result) * result
- result *= scale / fs
+ # Welch's algorithm: average response over windows
+ psd *= scale / (fs * n_windows)
# For one-sided FFT output the response must be doubled, except
# the last point for unpaired Nyquist frequency (assuming even nfft)
# and the 'DC' term (0 Hz)
- result[1:-1, :] *= 2.0
-
- # Welch's algorithm: average response over windows
- psd = result.real.mean(axis=-1)
+ psd[1:-1] *= 2.0

# Calculate the frequency bins
freqs = np.fft.rfftfreq(nfft, 1.0 / fs)
@@ -177,14 +183,19 @@ class ShaperCalibrate:
return None
if isinstance(raw_values, np.ndarray):
data = raw_values
+ N = data.shape[0]
+ T = float(data[-1, 0]) - float(data[0, 0])
else:
samples = raw_values.get_samples()
if not samples:
return None
- data = np.array(samples)
+ # Determine the sampling interval before converting timestamps
+ # to a compact single-precision representation.
+ N = len(samples)
+ T = samples[-1][0] - samples[0][0]
+ data = np.asarray(samples, dtype=np.float32)
+ del samples

- N = data.shape[0]
- T = data[-1, 0] - data[0, 0]
SAMPLING_FREQ = N / T
# Round up to the nearest power of 2 for faster FFT
M = 1 << int(SAMPLING_FREQ * WINDOW_T_SEC - 1).bit_length()
@@ -199,6 +210,16 @@ class ShaperCalibrate:
return CalibrationData(fx, px + py + pz, px, py, pz)

def process_accelerometer_data(self, data):
+ np = self.numpy
+ if not isinstance(data, np.ndarray):
+ samples = data.get_samples()
+ if samples:
+ # Keep timestamps relative to the first sample before packing
+ # into float32, preserving the sampling interval precision.
+ data = np.asarray(samples, dtype=np.float32)
+ data[0, 0] = 0.0
+ data[-1, 0] = samples[-1][0] - samples[0][0]
+ del samples
calibration_data = self.background_process_exec(
self.calc_freq_response, (data,)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
From 0d1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a Mon Sep 17 00:00:00 2001
From: Paul Swenson <paul@swenson.co>
Date: Sat, 18 Jul 2026 19:50:00 +0000
Subject: [PATCH] input_shaper: persist SET_INPUT_SHAPER values for SAVE_CONFIG

Klipper's SET_INPUT_SHAPER updates the live shaper parameters but never
marks the config file as dirty, so a later SAVE_CONFIG does not write the
updated shaper_type_*/shaper_freq_* values. This breaks Rusty Shaper and any
other flow that expects the user to hit SAVE_CONFIG after setting shapers via
G-code.

Call configfile.set() for each axis when the command actually changes
parameters, mirroring the behaviour of shaper_calibrate.save_params().

Upstream-Status: Pending

---
klippy/extras/input_shaper.py | 8 ++++++++
1 file changed, 8 insertions(+)

diff --git a/klippy/extras/input_shaper.py b/klippy/extras/input_shaper.py
index 1600d034..179632df 100644
--- a/klippy/extras/input_shaper.py
+++ b/klippy/extras/input_shaper.py
@@ -212,6 +212,14 @@ class InputShaper:
for shaper in self.shapers:
shaper.update(gcmd)
self._update_input_shaping()
+ configfile = self.printer.lookup_object('configfile')
+ for shaper in self.shapers:
+ status = shaper.params.get_status()
+ axis = shaper.axis
+ configfile.set("input_shaper", "shaper_type_" + axis,
+ status["shaper_type"])
+ configfile.set("input_shaper", "shaper_freq_" + axis,
+ status["shaper_freq"])
for shaper in self.shapers:
shaper.report(gcmd)
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
From be88bd00a33ae93a813c21f48aed7a3f167dc35e Mon Sep 17 00:00:00 2001
From: Paul Swenson <pdscomp@gmail.com>
Date: Sat, 18 Jul 2026 16:07:49 -0400
Subject: [PATCH] adxl345: stream raw accelerometer writes to avoid holding samples in RAM

write_to_file() previously flattened all captured batches into a Python list
of Accel_Measurement tuples before writing. For long resonance captures this
doubles the peak memory: the batches already in msgs plus the new samples list.

Stream the same batches directly to the CSV instead. The msgs are already
filtered to the requested time window by handle_batch, so iterate them and
skip samples outside request_start_time/request_end_time exactly as before.
No new allocations for the full capture.

Upstream-Status: Pending

---

diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py
index 7909e04a..478647b6 100644
--- a/klippy/extras/adxl345.py
+++ b/klippy/extras/adxl345.py
@@ -109,18 +109,18 @@ class AccelQueryHelper:
def write_to_file(self, filename):
def write_impl():
try:
- # Try to re-nice writing process
+ # ponytail: keep re-nice; IO-bound writer should not steal reactor time
os.nice(20)
- except:
+ except Exception:
pass
- f = open(filename, "w")
- f.write("#time,accel_x,accel_y,accel_z\n")
- samples = self.samples or self.get_samples()
- for t, accel_x, accel_y, accel_z in samples:
- f.write(
- "%.6f,%.6f,%.6f,%.6f\n" % (t, accel_x, accel_y, accel_z)
- )
- f.close()
+ with open(filename, "w") as f:
+ f.write("#time,accel_x,accel_y,accel_z\n")
+ # ponytail: stream from msgs; avoid flattening whole capture into a list
+ for msg in self.msgs:
+ for t, x, y, z in msg["data"]:
+ if t < self.request_start_time or t > self.request_end_time:
+ continue
+ f.write("%.6f,%.6f,%.6f,%.6f\n" % (t, x, y, z))

write_proc = multiprocessing.Process(target=write_impl)
write_proc.daemon = True
Loading