A 3-gram PLA keychain took ~6 minutes longer than it should have, and the same root cause had already destroyed a nozzle assembly. _derive_chamber_target() in bambuddy’s print scheduler takes the max chamber target over every loaded AMS tray and never looks at the print’s own ams_mapping — so one PC spool parked in AMS slot 1 silently forced a 50 °C chamber and a 90 °C bed onto every PLA print. This note is the bug, the three wrong trails it sent us down, the nozzle-clog RCA it explains, the counter-intuitive fix, the material-sequencing rules bambuddy does not enforce, and a shelved Tapo P115 energy bridge. Builds directly on 2026-09-01-bambuddy-slicer-api-pipelines-mcp on telep-mainframe.
For Agents — quick facts
Bug: _derive_chamber_target(printer, targets) — /app/backend/app/services/print_scheduler.py line ~4042 — maxes over all loaded AMS trays, ignores ams_mapping
Blast radius: PLA prints got chamber_target=50 → no bed_temperature in archive metadata → bed fell back to queue_keep_warm_bed_temp = 90 °C
Cost: 57 s ramp + 300 s soak = ~6 min per print; FTP upload (the usual suspect) is 0.4 s
Fix applied: inverted the default — PC: 0 and PC-FR: 0 in preheat_filament_targets; PC-FR jobs must now pass preheat_chamber_target_override: 60
⚠️ ABS and ASA are still 45 in the map — the identical trap returns if either is loaded alongside PLA
Nozzle assembly (cold end + hot end) was replaced ~2026-09-01 — this bug is half the reason why
bambuddy has NO material-transition logic. PC → PLA is a temperature DROP and is what carbonised the hot end
Tapo P115 bridge at /opt/tapo-bridge/ is built, enabled, NOT started, and entirely UNTESTED (no plug on the network)
1. THE BUG — _derive_chamber_target ignores ams_mapping
/app/backend/app/services/print_scheduler.py, line ~4042. The docstring states the defect outright:
“Look up the chamber target for each loaded AMS tray and return the max.”
Each loaded tray. Not each tray the print actually uses. The function never consults the print’s ams_mapping.
The AMS state that triggered it
AMS 0: T0 (PLA) T1 (PC) T2 (PLA) T3 (PLA)
print ams_mapping: [0, 3] ← both PLA
The print used two PLA trays. _derive_chamber_target looked at all four, found PC’s 50 °C, and returned it.
The cascade, measured live from the scheduler log
23:20:17 archive has no bed_temperature metadata - heating the bed to 90C to drive the chamber to 50C
23:20:17 preheat starting - bed=90C chamber_target=50C (source=filament-map ...) max_wait=900s soak=300s
23:21:14 preheat target reached (bed=91.0 chamber=48.0) - entering soak
23:21:14 preheat soak - holding for 300s
23:26:14 preheat complete - proceeding to upload
23:26:15 FTP upload complete (2236048 bytes in 0.4s, 5438 KB/s)
23:26:17 PRINT START detected
57 s ramp + 300 s soak ≈ 6 minutes of dead time on a 3-gram keychain. The FTP upload everyone instinctively blames is 0.4 seconds.
The second-order damage is worse than the delay
With no bed_temperature in the archive metadataand a non-zero chamber target, the code falls back to queue_keep_warm_bed_temp = 90 °C and uses the bed as a chamber heater.
Over-adhesion — parts fused to the plate, difficult release
Elephant’s foot — first layers squashed and flared
Long-term textured-PEI wear — repeated over-temperature cycling degrades the sheet
Heat-creep — see the clog RCA in §4; this is the cold-end half of it
2. THREE WRONG TRAILS — record these, they cost real time
awaiting_plate_clear=True in the log is a RED HERRING
The scheduler emits
Queue: printer %d not available - connected=%s, state=%s, awaiting_plate_clear=%s
as a context dump, not as a diagnosis. The actual gate is if require_plate_clear and is_awaiting_plate_clear(...) at lines ~1991 and ~3320 — and require_plate_clear was false, so that gate could not possibly fire. The log line looks like an explanation and is not one.
Wrong trail 2 — state=FINISH is not a blocker. Line 3328:
idle = state.state in ("IDLE", "FINISH", "FAILED")
FINISH is explicitly idle.
Wrong trail 3 — a stale printing queue row was not it either.busy_printers is seeded from
SELECT printer_id FROM print_queue WHERE status='printing'
and there were no such rows.
The lesson, stated generally
In this scheduler a “not available” line is a context dump, not a diagnosis. The authoritative signal was the preheat log lines. Read those first.
3. THE FIX — counter-intuitive, and one option deliberately rejected
Rejected option: pull the PC spool out of the AMS
The first instinct — remove the offending spool — is wrong, and is recorded here so it is not re-proposed.
The AMS is the driest storage available (measured 41 % RH with desiccant). PC-FR is strongly hygroscopic. Removing it from the AMS worsens the moisture half of the clog while fixing the temperature half. Net negative.
Applied fix: invert the default
A per-item preheat_chamber_target_override beats the filament map:
if explicit_target is not None and explicit_target > 0: chamber_target = int(explicit_target) # per-item override WINSelif explicit_target == 0: chamber_target = 0 # explicit zero also winselse: chamber_target = self._derive_chamber_target(printer, targets)
So: set PC: 0 and PC-FR: 0 in preheat_filament_targets, and pass the override on the rare PC-FR job.
This puts the correct behaviour on the common case (~5 PLA prints to 2 PC in the observed history) with zero operator action, and moves the one manual step onto the rare and deliberate PC-FR jobs.
The exact same failure returns the moment an ABS or ASA spool is loaded alongside PLA. Either zero those too and rely on overrides, or remember to check before a PLA batch.
Sub-brand gotcha — PC-FR resolves as plain PC
The PC-FR spool reports tray_type: "PC" with tray_sub_brands: "PC FR". _derive_chamber_target reads tray_type, so it resolved to PC’s 50 °C, not PC-FR’s 60 °C — undershooting the documented target by 10 °C even when the preheat was wanted.
4. NOZZLE CLOG RCA — two independent causes, stacked
The nozzle assembly (cold end AND hot end) was replaced ~2026-09-01. Both halves failed, for different reasons.
Cold end — PLA heat-creep
Caused directly by §1: the chamber force-heated to 50 °C and the bed to 90 °C on PLA prints. The warning was already written down in 2026-09-01-bambuddy-slicer-api-pipelines-mcp:
“The printer runs the chamber at the highest requested chamber temp, so mixing PLA with a high-chamber material makes the PLA heat-creep.”
The note anticipated the failure mode for co-printing. What was not anticipated is that merely having the spool loaded was enough — no co-print required.
Hot end — PC residue carbonising
The print log shows relentless PC ↔ PLA alternation. Sep 2 alone:
Time
Material
05:47
PC
08:59 – 17:55
PLA ×5
18:22 – 20:49
PC
PC needs 260–280 °C; PLA runs ~220 °C. PC residue left in the melt zone will not melt at PLA temperature, so it bakes, carbonises and blocks.
Contributing — PC-FR cannot be dried in the AMS
PC-FR is strongly hygroscopic. Its tray metadata declares drying_temp: 80. The AMS 2 Pro maxes at 65 °C. It therefore literally cannot dry this filament — external drying is mandatory (see §6).
5. MATERIAL SEQUENCING RULES — the operational core
bambuddy has NO material-transition logic — none, at all
Grepping the scheduler for material_change|filament_change|purge|previous_filament|last_filament returns only printer-state transitions. There is no purge step, no transition guard, no awareness of what was printed before. The queue will chain any two materials back-to-back.
Transition
Verdict
Why
PLA → PC
✅ SAFE
Temperature goes up; PLA residue melts and purges at PC temps
PC → PC
⭐ BEST
No transition at all; chamber still warm from the previous 90–110 °C bed. Batch these
PC → PLA
🔴 DANGEROUS
Temperature drops 280 → 220 °C; PC residue will not melt, it carbonises. This is what caused the clog
The rule, and how to enforce it
Batch by material. Never auto-chain a temperature DROP.
Enforcement mechanism: manual_start: true is a per-item field on a queue entry. Set it on the first PLA job following any PC job — the queue halts and waits for a human, and that pause is the purge window.
6. OPERATIONAL PROCEDURE — running a PC-FR print
Checklist
Dry the filament externally at 80–100 °C. The AMS 2 Pro cannot do this (65 °C max) despite the tray metadata asking for 80.
Close the chamber, glue the plate, ventilate (VOCs).
Pass preheat_chamber_target_override: 60 when queueing. 🔴 Without it the preheat is skipped entirely — the log reads preheat skipped - archive has no bed_temperature metadata and no chamber target — and the print starts from a cold chamber. This is the deliberate cost of the §3 fix.
Brim is NOT automatic. All four pipelines share 0.20mm Standard @BBL H2S; add it explicitly.
Afterwards, purge or cold-pull at PC temps BEFORE any PLA/PETG job. See §5.
Leave the spool IN the AMS. It stays dry there (41 % RH). Do not “fix” this by unloading it.
7. THREE UPSTREAM BUGS WORTH FILING
1. _derive_chamber_target should intersect with the print’s ams_mapping instead of maxing over all loaded trays.
Repro: park a PC spool in any AMS slot, queue a pure-PLA print, watch it soak 5 minutes and heat the bed to 90 °C.
2. Pipelines cannot express preheat settings.print_queue has preheat_override and preheat_chamber_target_override; the slicer_pipelines table has neither. A pipeline run therefore cannot carry the override — so PC-FR run via its own pipeline gets NO chamber preheat at all. This is a real gap created by the §3 fix, not a theoretical one.
3. Zeroing a filament to dodge bug 1 silently disables keep-warm for it. From _apply_keep_warm’s docstring:
“Skips entirely for filaments that map to a 0 °C chamber target.”
Setting PC: 0 therefore also opts PC out of keep-warm. Accepted, but it is a hidden coupling between two unrelated features.
8. MAINTENANCE + CONFIG CHANGES MADE THIS SESSION
Clean Nozzle/Hotend counter reset. It read last_performed_at: null despite the nozzle having been replaced ~2026-09-01. The RCA from §4 was written into its history note.
Two new maintenance types created and assigned to printer 1, extending the 6 from 3. Maintenance tracker:
Cold End / Extruder Inspection — every 100 h
PC / PC-FR Dryness Check — every 14 days
Why these two Nothing in the previous 6 tasks covered the cold end — which is half of what actually failed. And nothing tracked filament dryness, the contributing cause.
Local backups ENABLED — daily 03:00, 7-day retention. Previously off entirely, with 61 prints of history plus all pipeline / spool / maintenance data unbacked.
PLA and PC-FR pipeline descriptions rewritten to carry the purge procedure (§5) and the override requirement (§6 step 3).
9. CONFIG REVIEW — state as found
Good
Every AI detector on: spaghetti, first-layer, monitor, buildplate marker, nozzle clumping, pileup, airprint, auto_recovery_step_loss
WiFi −33 dBm — excellent. Note the H2S is WiFi-only; the H2S has no Ethernet port (relevant to the cross-band isolation issue in 2026-08-26-router-reflash-mac-5ghz-dfs-and-bambu-offline — you cannot wire around it on this model)
AMS 40–41 % RH
257 MB of 2.7 TB used
Gap — filament_tangle_detect is a dead capability
filament_tangle_detect: false, and it is not settable through bambuddy. Its REST endpoint rejects the name — the valid xcam modules are:
bambuddy’s MQTT client has _set_print_optiondocumented as handling filament_tangle_detect via the print.print_option command — but nothing anywhere calls it with that name. Dead code path.
The false reading may not even be true
filament_tangle_detect is commented “tracked locally only” — so the value bambuddy reports is a local placeholder and may not reflect the printer’s actual setting. Do not trust it either way.
Nothing stops the queue starting a print onto an occupied plate. (This is also why the awaiting_plate_clear red herring in §2 could not have been the blocker.)
Calibration is a remote call — no physical presence needed — once two preconditions hold: empty plate and chamber light on.
10. TAPO P115 ENERGY BRIDGE — built, shelved, UNTESTED
Everything in this section is UNTESTED
No Tapo plug was on the network during this session. The service is enabled but NOT started. Treat every claim below as “read from source / written to spec”, not “verified in operation”.
bambuddy has no native Tapo backend — only tasmota, homeassistant, mqtt, and rest. So a shim adapts a P115 to the generic rest backend.
Item
Value
Binary
/opt/tapo-bridge/tapo-bridge
Venv
/opt/tapo-bridge/venv (python-kasa 0.10.2)
Bind
127.0.0.2:8117
Unit
tapo-bridge.service — enabled, NOT started
Env file
/etc/tapo-bridge.env, 0600 root-only, currently placeholders
bambuddy runs NetworkMode: host, so 127.0.0.2:8117 is reachable from inside it — verified.
Units, read from source rather than guessed: power in W, energy in kWh, all multipliers 1.0.
Critical P115 limitation — no lifetime energy counter exists
consumption_total returns None for every Tapo device. This is hard-coded in python-kasa’s kasa/smart/modules/energy.py — the P115 simply exposes no lifetime counter over the local API.
The bridge therefore synthesizes one by accumulating daily values into /opt/tapo-bridge/state.json. It is monotonic and restart-safe, but it starts at zero and under-counts if the bridge is down across midnight. Any figure it reports is “energy since the bridge was installed”, not “energy since the plug was made”.
python-kasa 0.10.2 API notes (both cost time to find)
Device.connect() has NO credentials= parameter. Credentials go inside DeviceConfig(...), with
rest_method must be set to GET on the bambuddy side — bambuddy defaults to POST, which the bridge does not serve.
rest_* field mapping — for when a plug is actually connected
bambuddy setting
Value
backend
rest
rest_url (base)
http://127.0.0.2:8117
rest_method
GET (not the default POST)
power field
power, W, multiplier 1.0
energy field
energy, kWh, multiplier 1.0
total field
synthesized from state.json — starts at 0, see the warning above
Before starting it: fill in real credentials in /etc/tapo-bridge.env (keep it 0600), put the plug on the LAN, then systemctl start tapo-bridge and verify against the plug’s own app reading before trusting any number.
11. IF THIS HAPPENS AGAIN — triage order
Three checks, in this order
Read the preheat log lines FIRST — not the “not available” line.preheat starting - bed=… chamber_target=… (source=…) names the decision and its source. The Queue: printer N not available - connected=…, state=…, awaiting_plate_clear=… line is a context dump and will point you at the wrong thing (§2).
Verify _derive_chamber_target’s answer against the print’s actual ams_mapping. If the derived target corresponds to a filament the print does not use, you are looking at §1. Dump the AMS tray table and the mapping side by side.
Check per-input connected state before blaming hardware. A disconnected input reads as a fault at every layer above it; confirm the link before pulling anything apart.
Corollary for delays specifically: the FTP upload is never the problem — it moved 2.2 MB in 0.4 s. Look at ramp + soak.
Related
2026-09-01-bambuddy-slicer-api-pipelines-mcp — the buildout this debugs: pipelines, maintenance tracker, Telegram, MCP. Its material cheat-sheet and its “do not co-print PLA with ABS/PC-FR” warning are the direct ancestors of §4