mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-22 03:49:18 +02:00
fix(usage-tracker): split mid-print AMS-Backup spool switch correctly (#1771)
Reporter forcefully started a print needing ~260 g with 180 g on the
first spool and a backup spool in the AMS. Printer correctly consumed
spool 1, AMS Backup switched, spool 2 finished the print. Bambuddy
attributed all 260 g to spool 2 -- spool 1 untouched in inventory.
Two stacking bugs produced the exact "all to second spool" symptom for
prints without per-layer 3MF gcode data:
1. bambu_mqtt.py:2135 wrote state.total_layers = int(data["total_layer_num"])
unconditionally. P1S firmware pushes total_layer_num=0 at print end
(same reset pattern other models do for layer_num / progress). The
unconditional write clobbered the slicer's actual total to 0 before
the usage tracker read it.
2. usage_tracker.py:1129-1137 linear-fallback dumped EVERYTHING onto the
last segment when total_layers was 0:
if total_layers > 0:
segment_grams = total_weight * (seg_end_layer - seg_start_layer) / total_layers
else:
segment_grams = 0.0 # <- entire print weight ends up on last segment
Path 2 (AMS remain% delta) couldn't recover because (a) the emptied
spool reported remain=-1 and (b) Bug-A had already added the second
spool's key to handled_trays, suppressing the Path 2 lookup.
Fix:
- bambu_mqtt.py: only overwrite state.total_layers when the incoming
value is positive (mirror of the existing _last_valid_layer_num
pattern at line 2127). Explicit reset on new print start at
_handle_print_start so the previous print's total can't bleed in.
- usage_tracker.py: cascade the linear-fallback denominator -
state.total_layers, then last_layer_num (already threaded in for
the last_progress fallback), then equal-split as a bounded fence.
Equal-split is still wrong but never dumps the whole print on the
last segment, which was strictly worse.
This commit is contained in:
@@ -2133,7 +2133,14 @@ class BambuMQTTClient:
|
||||
if new_layer > old_layer and self.on_layer_change:
|
||||
self.on_layer_change(new_layer)
|
||||
if "total_layer_num" in data:
|
||||
self.state.total_layers = int(data["total_layer_num"])
|
||||
# Some firmware (P1S observed) resets `total_layer_num` to 0 at
|
||||
# print end — same shape as the `layer_num` reset guarded above.
|
||||
# Preserve the last known good value so the usage-tracker split
|
||||
# path (#1771) has a denominator that survives the reset frame.
|
||||
# Explicit reset to 0 happens on print start (`_handle_print_start`).
|
||||
new_total = int(data["total_layer_num"])
|
||||
if new_total > 0:
|
||||
self.state.total_layers = new_total
|
||||
|
||||
# Fan speeds (MQTT sends as string "0"-"15" representing speed levels, or percentage)
|
||||
# Convert to 0-100 percentage for display
|
||||
@@ -3120,6 +3127,12 @@ class BambuMQTTClient:
|
||||
self.state.hms_errors = []
|
||||
# Reset layer tracking for new print (needed for layer-based timelapse)
|
||||
self.state.layer_num = 0
|
||||
# Reset total_layers so the previous print's value can't bleed into
|
||||
# this print's usage-tracker split before the new push_status arrives
|
||||
# with the slicer's total (#1771 follow-on to the preservation guard
|
||||
# above at line ~2135 — the guard now ignores firmware-reset 0s, so
|
||||
# the explicit reset has to happen here instead).
|
||||
self.state.total_layers = 0
|
||||
# Reset completion tracking for new print
|
||||
self._was_running = True
|
||||
self._completion_triggered = False
|
||||
|
||||
@@ -1127,14 +1127,27 @@ async def _track_from_3mf(
|
||||
mm_at_end = get_cumulative_usage_at_layer(split_layer_usage, seg_end_layer).get(filament_id, 0)
|
||||
segment_grams = mm_to_grams(mm_at_end - mm_at_start, diameter, density)
|
||||
else:
|
||||
# No per-layer data: linear fallback by layer ratio
|
||||
# No per-layer data: linear fallback by layer ratio (#1771).
|
||||
# Cascade denominators because firmware on some models (P1S
|
||||
# observed) resets `total_layer_num` to 0 at print end —
|
||||
# `last_layer_num` is the print's last-valid layer captured
|
||||
# mid-print and survives that reset (same shape as the
|
||||
# `last_progress` fallback at line 1040). Equal-split is the
|
||||
# last-resort fence: still wrong, but bounded — never dumps
|
||||
# the entire print onto the last segment, which was the
|
||||
# original #1771 symptom for the reporter (P1S, AMS Backup
|
||||
# fed from spool 1 then spool 2, all 260 g credited to
|
||||
# spool 2 even though spool 1 had given up its 180 g).
|
||||
seg_end_layer = tray_changes[seg_idx + 1][1]
|
||||
total_layers = state.total_layers if state else 0
|
||||
if total_layers > 0:
|
||||
segment_grams = total_weight * (seg_end_layer - seg_start_layer) / total_layers
|
||||
denom = (state.total_layers if state else 0) or last_layer_num
|
||||
if denom > 0:
|
||||
segment_grams = total_weight * (seg_end_layer - seg_start_layer) / denom
|
||||
else:
|
||||
# Can't compute ratio — assign all to last segment
|
||||
segment_grams = 0.0
|
||||
# No layer information available from any source —
|
||||
# spread evenly across segments. The last segment will
|
||||
# get the rounding remainder via the `is_last` branch
|
||||
# above on its own iteration.
|
||||
segment_grams = total_weight / len(tray_changes)
|
||||
|
||||
sum_previous += segment_grams
|
||||
if segment_grams <= 0:
|
||||
|
||||
Reference in New Issue
Block a user