mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-04 05:01:37 +02:00
6e6c0e06449775565ea7725a264b08806fd232b3
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9ee162d51a |
feat(printers): expose P2S/X2D accessory fans (left aux + exhaust)
The P2S/X2D have two fans bambuddy didn't fully handle. On the P2S both are add-on kits; on the X2D they ship from the factory. 1. Left auxiliary part cooling fan — not shown or controllable. It is reported ONLY as device.airduct part id 10 (raw id 160 >> 4; FAN_REMOTE_COOLING_1 in Bambu Studio's DevFan::ParseV3_0) and is never mirrored into a flat big_fanX_speed field, which is why it was invisible. This is the gap identified in #2576, where the single 'Auxiliary' fan (big_fan1 / M106 P2) only reaches the right-hand aux fan. 2. Chamber exhaust fan — shown on every P2S labelled 'Chamber Fan'. On P2S/X2D Bambu's firmware/UI (and Bambu Studio's FAN_CHAMBER_0_IDX) call it 'Exhaust', and it is a kit on the P2S rather than built in. Both are now detected from device.airduct.parts, which lists only the fans that physically exist, so each tile appears only when the hardware is present. - bambu_mqtt: parse airduct part 10 -> left_aux_fan_speed (None when absent) and part 3 presence -> exhaust_fan_present; set_fan_speed() accepts index 10 plus a set_left_aux_fan() helper - schema / status route / printer_manager broadcast / mqtt_relay expose both fields - POST /printers/{id}/fan-speed accepts fan=aux2 -> M106 P10, the command Bambu's official P2S machine profiles use - frontend: 'Left Auxiliary Fan' tile shown when reported; big_fan2 tile labelled 'Exhaust' and presence-gated on P2S/X2D, unchanged 'Chamber Fan' elsewhere - i18n: leftAuxiliary + exhaust for all 12 locales Verified fan -> field map on a live P2S (fw 01.02.00.00), stable across cooling and heating airduct modes: Part cooling -> cooling_fan_speed / airduct id 1 (built in) Aux -> big_fan1_speed / airduct id 2 (built in) Exhaust -> big_fan2_speed / airduct id 3 (kit) Left aux -> airduct id 10 only (kit; forced off in heating by mode config) Tests: airduct id-10 parsing (raw 160 -> id 10, not literal 160), id-3 presence, base-P2S absence, diff-push survival, clamping, malformed entries, M106 P10 emission, invalid-index rejection; fan-speed API aux2->10 mapping; frontend tile presence and labelling per model/kit. |
||
|
|
8fd1f884dc |
feat(mqtt): publish the plate-clear gate and add a notification for it (#2525)
When a print reaches a terminal state Bambuddy holds the queue until
someone confirms the build plate is clear. That gate was visible only in
the Web UI: the printer's own MQTT push reports nothing beyond RUNNING,
PAUSE, FAILED, FINISH and IDLE, so an external automation could not tell
"finished" from "finished and still waiting for a human".
The per-printer status topic now carries an awaiting_plate_clear field,
and every transition is additionally published on a new retained topic,
bambuddy/printers/{serial}/plate_clear. Retained, and published from the
flag itself rather than from printer telemetry: a subscriber learns the
state of every printer the moment it connects, and the state stays
correct after Auto Off powers a printer down - telemetry stops there,
which would otherwise leave the status topic frozen at false.
Publishing is edge-triggered. The queue clears the gate on every
dispatch whether or not it was up, and no subscriber should see a
"plate cleared" for a plate that was never dirty. Persistence and the
WebSocket broadcast stay unconditional; they are idempotent and predate
this.
A matching Plate Clear Required notification event was added, off by
default on every provider because it fires after every print at the
same moment as the print-complete alert. Only the rising edge notifies.
Acknowledging still goes through POST /printers/{id}/clear-plate.
Two tests in test_printer_manager_status_broadcast.py asserted
_schedule_async.call_count == 2 for the setter. The new emission makes
it three on a transition, so they now assert that the persist and
broadcast coroutines are actually scheduled - which is the contract
Translated in all locales; wiki updated. Covered by backend and
frontend tests.
|
||
|
|
b1cb26f6ee |
feat(ams-backup): add status badge + toggle, fix prefer-lowest (#1766)
Two tightly-coupled deliverables in one drop -- a new AMS Filament Backup status/control surface, and the #1766 fix that depends on it. Added -- AMS Filament Backup status + control - Parse bit 18 of top-level print.cfg into PrinterState.ams_filament_backup on every push_status. Verified against OrcaSlicer source (DeviceManager.cpp:4961) and a live H2D ON/OFF capture. Tri-state (None = A1 family / pre-cfg push) preserves today's behaviour. - Hold-timer guard (3 s) prevents stale frames from flickering the badge back to the printer's old cfg after a user-initiated toggle. - POST /printers/{id}/ams-backup toggle, set_ams_filament_backup() client method calling _set_print_option("auto_switch_filament", enabled). - GET /printers/{id}/inventory-remain endpoint exposes the same map the dispatcher uses (internal and Spoolman modes both work uniformly). - Small icon badge in the printer card's "Filaments" section header (placement reads as printer-wide because the cfg bit is printer-wide, not per-AMS). Click to toggle, success toast. - 5 i18n keys x 11 locales for the badge UI. Fixed -- #1766: prefer_lowest didn't pick lowest, ignored backup state - Backend gate in _compute_ams_mapping_for_printer: coerce prefer_lowest to False when status.ams_filament_backup is False; log the skip. - New effectivePreferLowest(setting, backup) helper applied at every frontend sort entry point: single-printer PrintModal, multi-printer hook per-printer, PrinterSelector InlineMappingEditor, FilamentMapping standalone editor (the last had NO preferLowest awareness at all before this change). - New preferLowestSortKey(f, inventoryByTrayId) mirrors backend's two-tier key exactly, including the banding tie-break (regular AMS < AMS-HT < external) so the client-side pre-compute matches the dispatch-time pick. An earlier draft used a flat `amsId * 4 + trayId` priority which gave external slots (ams_id = -1) a NEGATIVE priority -- caught in code review before commit. - Settings -> Filament -> "Prefer lowest remaining filament" gets an explanatory note about the printer-side AMS Backup dependency, with i18n key in all 11 locales. |
||
|
|
b444921021 |
test: drop racy printer_state_to_dict patch; assert structural shape
The patch on printer_state_to_dict raced against the broadcast coroutine under pytest-xdist's parallel workers. Mostly won locally, lost occasionally on CI — surfaced first as AttributeError on .kprofiles, then (after _fake_state was hardened) as a dict-content mismatch between the patched return value and the real 36-key dict. Fix: stop patching printer_state_to_dict; let it run for real against the complete _fake_state stub. Assertions now check the broadcast fired with the right printer_id and a dict containing awaiting_plate_clear, not the exact dict shape — that decouples the test from printer_state_to_dict's evolving body. |
||
|
|
c36bfa24d4 |
test: harden _fake_state against printer_state_to_dict mock-leakage
The two TestBroadcastStatusChange / TestEndToEndUnderRunningLoop tests patch printer_state_to_dict to return a fixed dict, but on parallel xdist runners (CI's pytest -n 30) the patch occasionally didn't catch the call and the real function ran against the 4-field SimpleNamespace fake — first attr access (.kprofiles) AttributeError'd, swallowed by the try/except in _broadcast_status_change, send_status never awaited, the assertion failed. Filled _fake_state with every attribute the real printer_state_to_dict reads (iterables empty, scalars None, stg_cur=0 for the int comparison in get_derived_status_name). Test now passes whether or not the patch lands. |
||
|
|
57af8a1c19 |
feat(projects): URL field + cover photo on project cards (#1155)
Two new project fields: a free-text URL rendered as a one-click
external-link button beside the project name on every card (opens in a
new tab, click is e.stopPropagation()-guarded so it doesn't enter the
project), and a cover photo that replaces the status-icon box with a
square thumbnail.
URL is plumbed through ProjectCreate/Update/Response/ListResponse,
including from-template + create-template flows so it inherits between
a project and its template. Cover photo is not inherited because the
file would be shared on disk between source and copy.
Schema validator rejects anything other than http:// or https://
prefixes -- <a href> rendering would otherwise execute javascript:
/ data: / file: URLs even with React's default escaping. PATCH uses
model_fields_set for the URL field so users can clear it by sending
{"url": null}.
Cover image storage: Project.cover_image_filename references a file
Cover image storage: Project.cover_image_filename references a file
inside the existing archives/projects/{id}/attachments/ dir, but it's
tracked separately from the attachments JSON list so swap/delete on
the cover doesn't perturb the user's other attachments. Three routes
(POST/GET/DELETE /projects/{id}/cover-image) accept only .jpg/.jpeg/
.png/.gif/.webp (no SVG -- SVG can carry script payloads), replace in
place (prior file deleted before the new one lands so repeat uploads
can't accumulate orphans), and self-heal when a DB reference points at
a vanished disk file by clearing the column and 404'ing.
GET cover-image is gated by RequireCameraStreamTokenIfAuthEnabled
(accepts ?token=... query string) -- not the bearer-token gate -- so
<img src> requests work in both auth-on and auth-off configurations.
The frontend wraps getProjectCoverImageUrl with withStreamToken(),
matching the existing pattern from getArchiveThumbnail.
Permissions: PROJECTS_UPDATE for upload/delete/PATCH, PROJECTS_READ
gate is implicit via the stream-token credential. Migration: 2
idempotent ALTER TABLE projects ADD COLUMN. Localised across all 8
UI languages.
|
||
|
|
096bdd92a8 |
fix(#1128): broadcast printer_status when awaiting_plate_clear flips
awaiting_plate_clear is a Bambuddy-side flag, not a printer-side one,
so toggling it does not produce an MQTT push from the printer. Commit
|