Support for multiple printers and the direct spool input#2
Open
davidkinnes wants to merge 95 commits intojkef80:mainfrom
Open
Support for multiple printers and the direct spool input#2davidkinnes wants to merge 95 commits intojkef80:mainfrom
davidkinnes wants to merge 95 commits intojkef80:mainfrom
Conversation
davidkinnes
commented
Mar 12, 2026
Introduce lightweight internationalization without external dependencies. The UI auto-detects the browser language (falling back to English), and a DE/EN toggle in the header lets users switch manually. The choice is persisted in localStorage. - New static/i18n.js with translation dictionaries and t() helper - Static HTML elements use data-i18n attributes, dynamic JS strings use t() - Language switcher styled to match existing badge/pill aesthetic - /api/ui/help accepts ?lang=en for English help text Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bridge between Spoolman spool manager and CFS slot tracking: - Link Spoolman spools to CFS slots via modal dropdown (sorted by material/color match) - Import remaining_weight on link, sync consumption back on print finalize and manual allocation - Sync measured weight on "Übernehmen", auto-unlink on roll change - All Spoolman calls are fire-and-forget — local tracking is never blocked - Set spoolman_url in config.json to enable; hidden when unconfigured Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Drop slot_history, spool_ref_*, spool_epoch_consumed_g_total, remaining_g, last_accounted_* from schema and state - Remove _hist_push, _hist_upsert_by_src, _inc_slot_epoch_consumed, _apply_job_usage, _slot_consumed_g_epoch helpers - Poll loop finalization now only calls _spoolman_report_usage per slot - Remove POST /api/spool/reset, /apply_usage, /ui/slot/reset, /ui/spool/set_remaining endpoints - Add idempotency guard to /api/moonraker/allocate (prevents double Spoolman sync); stored value drops alloc_g - Add GET /api/ui/spoolman/spool_detail?slot= proxy endpoint (graceful error, never HTTP 502) - Replace renderHistory() with fetchAndRenderSpoolmanStatus() in UI; right-side panel now shows live Spoolman spool status for active slot - Remove Istgewicht/Übernehmen form from spool modal; roll-change increments epoch and auto-unlinks Spoolman spool - Update i18n keys: remove history/spool-stats keys, add spoolman status keys (DE + EN) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add websockets>=12.0 dependency - Remove all Moonraker polling code (~490 lines): _moonraker_fetch_history, _moonraker_build_url, _moonraker_list_objects, _extract_cfs_slot_data, _walk, moonraker_poll_loop - Add _printer_ws_url, _normalize_ws_color, _parse_ws_cfs_data, _ws_connect_and_run, printer_ws_loop (exponential backoff 2→60s) - Config: printer_url replaces moonraker_url; backward compat migrates hostname from old moonraker_url automatically - Spoolman usage reported incrementally via usedMaterialLength deltas (m → mm → g via mm_to_g); roll change clears ws_slot_length_m baseline - Remove moonraker_history, moonraker_allocations, cfs_raw, job_track_*, current_job* from AppState; add ws_slot_length_m - Remove allocation + job set/update endpoints - Frontend: remove renderMoonHistory, captureUiState/restoreUiState, buildSlotIds, jobKeyFromMoon; add spoolPct percent badge on slot tiles - i18n: remove moon.* and assign.* keys; add slot.percent Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The printer pushes an unsolicited JSON status message immediately on connect. This caused _ws_connect_and_run to receive the status dump instead of the heartbeat "ok" reply, crashing the loop. Fix: drain all messages (1.5s timeout loop) after connecting, then send the heartbeat. Also downgrade unexpected heartbeat replies from errors to warnings so a timing quirk doesn't drop the connection. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
WS fix: disable websockets keepalive pings (ping_interval=None,
ping_timeout=None) — the printer doesn't respond to WebSocket ping
frames, causing the library to drop the connection after ~20s.
Slot chip display:
- Line 2 (.slotSub): shows "{manufacturer} {name}" when CFS/Spoolman
data is available; falls back to "MATERIAL · #COLOR" otherwise
- Line 3 (.slotDetail): shows material type + "SP #{id}" when a
Spoolman spool is linked
- Add .slotDetail and .spoolPct CSS rules
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On manual spool link: write the slot's CFS RFID to the Spoolman spool's extra.cfs_rfid field via PATCH. On each WS snapshot: if an RFID-tagged spool (state==2) appears on an unlinked slot and the RFID is new since last seen, search Spoolman for a spool with matching extra.cfs_rfid and auto-link it. On roll change: clear the RFID cache for the slot so re-inserting any spool (even the same one) triggers auto-link again. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
If the CFS RFID changes on a slot that already has a Spoolman link, treat it as an implicit spool swap: clear the old spoolman_id and ws_slot_length_m baseline, then try to auto-link to the new spool via extra.cfs_rfid — same as if the slot had been unlinked first. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously cfs_active_slot was only written when selected==1 was found, so an old value (e.g. 2A from a previous session) would persist indefinitely. Always write the value — None when the printer is idle — so the Active section stays blank when nothing is printing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Spoolman requires extra field values to be double-encoded — the value
itself must be a JSON string. Sending "06001" caused a 400 Bad Request;
the correct form is json.dumps("06001") → "\"06001\"".
Also decode the stored value (json.loads) when comparing RFID during
auto-link lookup so the match works correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The printer never sets selected==1 on any slot (all 0 in WS data), so cfs_active_slot is always null. The JS was falling back to state.active_slot which defaulted to "2A" from the Pydantic schema, permanently showing "Box 2 · Slot A" as active. - JS: remove || state.active_slot fallbacks in render() and fetchAndRenderSpoolmanStatus() — cfs_active_slot is the only source - Schema: AppState.active_slot default changed from "2A" to None - Migration: clear existing "2A" values from state.json on load Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… cases Optional[Literal[...]] = None can behave unexpectedly across Pydantic versions. active_slot is now driven by WS only and not used by the frontend, so the Literal constraint adds no value. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drain loop: replaced per-message 1.5s timeout with a 2-second total deadline. The printer sends status messages continuously, so the old loop never timed out and the WS was stuck in drain forever, never reaching printer_connected = True. State protection: load_state() now sets _state_load_failed when it falls back to default_state(). save_state() checks this flag and refuses to write, preventing a failed load from wiping real spool data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace WS-delta Spoolman reporting with Moonraker-driven end-of-job attribution: snapshot ws_slot_length_m at job start, then at job complete proportionally split filament_used across linked slots using WS deltas, and report to Spoolman via _spoolman_report_usage() - Add _moonraker_base_url(), _moon_report_job_usage(), moonraker_job_poll_loop() to main.py; launch loop in _startup() - Remove WS Spoolman delta block from _parse_ws_cfs_data() (ws_slot_length_m still updated for attribution tracking) - Delete static/i18n.js; hardcode English throughout app.js and index.html - Remove language switcher buttons and .langSwitch/.langBtn CSS Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace native <select> with a custom scrollable list so each spool entry can display a colored swatch next to its label. Color comes from filament.color_hex already returned by /api/ui/spoolman/spools. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace flat list cards with a horizontal row per box: narrow box header on the left (number + temp/humidity), then 4 slot pods showing a colored SVG spool disk, material name, percent, and edit/active icon. Empty slots render as a dark disk with a diagonal slash. Active slot gets a green border and eye icon. All existing click/modal functionality preserved. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
WS fix: replace drain+request/response pattern with a continuous read loop. Old code drained 2s of real CFS data, then only read ONE message per request cycle — causing "ok" heartbeat acks to land as the "response" and parse-fail, while the actual boxsInfo reply went unread. New loop: - Minimal drain (5 msgs × 0.15s) so real data is not lost - Heartbeat handshake scans up to 10 messages for the "ok" ack - Continuous recv() loop processes every incoming message in order - "ok" and "heart_beat" strings handled inline, not mistaken for JSON - Re-requests boxsInfo every 5s; re-requests on 6s recv timeout Branding: rename to CFSync, add Creality triangle logo (logo.png), update page title and header. FastAPI title updated to match. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add printer_name and printer_firmware fields to AppState - Add _parse_ws_printer_info(): tries common Creality WS key names (machineName, softVersion, etc.) and persists any found values; also logs all newly-seen top-level message keys once per session so the exact field names can be identified from logs - Call _parse_ws_printer_info() for every parsed JSON WS message - Header subtitle (#printerSubtitle) updated dynamically by render() to show "PrinterName · FirmwareVersion"; empty until WS delivers data Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RFID slots (state=2) already report a real sensor percent from the WS. Manual slots (state=1) always report 100% from the WS (no sensor), which is misleading. Fix: - state=2: use WS percent as-is (unchanged) - state=1 + Spoolman link: calculate remaining/nominal*100 from Spoolman filament.weight; falls back to remaining/(remaining+used) if nominal weight is not set; stored in _spoolman_manual_pct cache - state=1 without link: show no percent (None) - state=0 (empty): show no percent _refresh_manual_slot_pcts() is an async task triggered after each boxsInfo parse; per-slot TTL of 60s prevents hammering Spoolman. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CFSync now generates a bookmarklet (footer "Fluidd panel" link) that, when clicked in Fluidd, replaces the Runout Sensors card with a live CFS slot grid pulled from CFSync's /api/ui/state API. - static/fluidd-panel.js: self-contained IIFE; finds Vuetify card by title text (Runout Sensors / Filament Sensor), replaces content with colour-coded slot grid; falls back to floating panel after 15 s; polls every 3 s; handles both Vuetify v2 and v3 DOM structures - static/app.js: initFluiddBookmarklet() generates javascript: href with CFSYNC_URL embedded from window.location.origin; copy button fallback via navigator.clipboard - static/index.html: footer bookmarklet link + copy button - static/style.css: footer link / copy button styles Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two fixes for the 'no WS slot deltas to attribute' failure: 1. Re-snapshot on next tick if WS was empty at job start When the service starts while a print is already running, the Moonraker poll may take the ws_slot_length_m snapshot before the WebSocket loop has received any boxsInfo data. Now we set _moon_snapshot_pending=True in that case and retry the snapshot on each subsequent 5 s tick until WS data arrives. 2. Active slot fallback when no WS deltas at completion If deltas are still zero at job end (snapshot was empty or WS never updated usedMaterialLength), fall back to attributing the full filament_used to the active CFS slot (cfs_active_slot / active_slot) if it has a Spoolman link. This covers the common single-spool restart-mid-print scenario. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Fluidd panel bookmarklet fetches from CFSync (different host/port) so the browser blocks it without an Access-Control-Allow-Origin header. Allow all origins since CFSync is a local-network-only service. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
rsync --delete was wiping the venv/ directory because it was not in the exclude list. Then pip ran against the system Python which is PEP 668 externally-managed and rejects installs without a venv. - Add --exclude "venv/" to rsync so the existing venv is preserved - Recreate venv with python3 -m venv if it is missing (safety net) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The WS boxsInfo payload doesn't include serialNum — it's only in the printer's material_box_info.json file. Replace paramiko (which failed with "EOF during negotiation" due to legacy KEX algorithm mismatch) with sshpass + system ssh binary, which handles legacy algorithms automatically. Also keep the WS serialNum path as a fast-path in case a future firmware includes it in the WS payload. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts: # main.py # static/app.js # static/fluidd-panel.js # static/index.html
…rsion in frontend
…vements - Introduced `/api/ui/jobs/reallocate_spool` endpoint to handle spool reallocation for completed jobs. - Updated job history card to display detailed spool usage, including colors and link buttons. - Added `Relink`/`Link` functionality for job spools in the frontend. - Improved visual styling for displayed spools and job history. - Bumped cache-busting version for updated frontend assets.
- Improved `_normalize_color_hex` to handle various printer color formats. - Replaced `_normalize_ws_color` with `_normalize_color_hex` for consistency. - Expanded spool metadata with normalized color, material, name, and manufacturer. - Cached spool colors from Spoolman for improved frontend accuracy. - Updated frontend to utilize `s.color` fallback for color display. - Bumped cache-busting version for updated `app.js`.
…caching for performance
- Introduced a new "Relink Job Spool" modal to streamline spool reallocation for completed jobs. - Refactored spool list rendering into reusable `renderSpoolmanList` for improved maintainability. - Added modal initialization logic and keyboard shortcuts for closing modal. - Updated job history cards to open the new modal for spool linking. - Bumped frontend cache version to ensure updated assets are loaded.
- Added `fmtDuration` function to calculate and format print duration. - Updated job history cards to show print time alongside start and end timestamps. - Bumped cache-busting version for updated `app.js`.
…` determination - Added `_resolve_tracking_slot` function to centralize slot resolution logic. - Updated filament tracking to use `_resolve_tracking_slot` for improved accuracy. - Ensured proper handling of CFS-active and fallback slots.
- Bumped cache-busting version for `app.js` to ensure clients load updated assets. - Enhanced spool label text to display material type, if available.
- Implemented rolling 24-hour temperature and humidity history sampling for CFS boxes. - Added frontend modal for environmental data visualization with interactive charts. - Enhanced UI with buttons to display historical temperature and humidity data per box. - Updated backend schema and handling logic to support environmental data storage. - Bumped cache-busting version for `app.js` to ensure updated assets are loaded.
- Introduced multi-range buttons (24h, 7d, 30d) in the environmental modal for selecting dataset views. - Enhanced environmental data retention logic with bucket compaction to manage long-term history efficiently. - Updated frontend to display range-specific aggregated data and interactive charts. - Improved backend sampling logic to reduce memory usage for historical data. - Updated styles and interactions for range buttons in the modal.
- Added `_write_json_atomic` function to prevent file truncation on interruptions. - Updated `save_state_all` and `state.json` creation to use atomic writes. - Improved error handling with recovery and logging for state load/save operations.
- Added `_moon_sync_missing_history_jobs` to recover jobs completed while offline. - Updated job history syncing to include filament, metadata, and linkage statuses. - Enhanced frontend to display recovered jobs with flags (e.g., "Recovered while offline").
- Introduced material and vendor filters for spool selection UI. - Added reusable `renderSpoolmanPicker` for dropdown rendering with filters. - Enhanced backend to provide preferred material/vendor information for slots. - Updated styles and interactions for filter dropdowns. - Bumped cache-busting version for `app.js` to ensure updated assets are loaded.
…ement - Migrated job history storage from JSON to SQLite for improved scalability and query performance. - Backfilled legacy job history into the database during startup if necessary. - Replaced ad-hoc job history handling with `_jobdb` functions for insertion, fetching, and updating. - Updated frontend to use database-backed job history with unlimited retention. - Bumped cache-busting version for `app.js` to ensure updated assets are loaded.
- Introduced new `/jobs` page to display job history with filtering and pagination. -
- Introduced `/jobs` page to display job history with advanced filtering (e.g., printer, material, date range). - Added pagination controls for navigating through job records. - Implemented spool usage display with material, color, and usage details. - Integrated frontend with API-backed job history retrieval. - Updated styles and scripts for the new page.
- Added `connectedCfsBoxesForState` utility to determine connected CFS boxes for each printer. - Enhanced CFS badge display to list connected boxes and corresponding printers. - Refactored CFS connectivity logic for clarity and accuracy.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.