fix: healthcheck no longer requires curl#12
Open
koen01 wants to merge 140 commits into
Open
Conversation
- 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>
--exclude "venv/" is supposed to protect the venv from rsync --delete, but rsync still descends into the excluded directory and deletes the individual files inside it (only the empty directory shells remain), leaving venv/bin/activate missing and the pip step broken. An update script only needs to overwrite/add files — removing --delete is the correct fix. Files deleted from the repo accumulate on existing installs but that is harmless for this app. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Empty slots (present=false) have no filament so showing a material type or percentage carried over from previous state is misleading. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Track open/closed state per printer in a module-level Set (cameraOpen) that persists across render() calls. On rebuild, restore the stream state and re-set img.src so the feed continues uninterrupted. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace wrap.innerHTML="" with a reconciler that keeps live DOM nodes:
- _renderedPrinters Map: tracks each printer block + structural fingerprint
- _renderedJobsCard: tracks recent jobs card + history fingerprint
- _printerStructFingerprint: captures slot states, active slot, box metadata
(excludes temps/progress so normal polling never triggers a rebuild)
- _jobsFingerprint: captures job history timestamps
On each tick:
- If structural fingerprint unchanged → _patchPrinterBlock() updates
badges, temps, and progress bar in-place via data-live attributes
- If fingerprint changed → replaceChild() for that printer only
- Recent jobs card only rebuilt when job history actually changes
This eliminates scroll jumps, stops the camera stream from being
interrupted on every refresh, and reduces DOM work to a few text
and style updates per tick in steady state.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
During multi-color prints the firmware briefly sends selected=0 for all CFS slots while switching, which was triggering the SP fallback and calling SET_ACTIVE_SPOOL with SP's linked spool ID. - _parse_boxs_info: only call SET_ACTIVE_SPOOL when firmware explicitly selects a CFS slot (active_slot non-None); never use SP as a fallback here - moonraker_job_poll_loop: at job start, if tracking slot is SP, activate SP's linked spool — this is the only place SP activation now happens Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Full style.css rewrite with MD3 color token system (tonal surfaces, green/teal primary palette, shape scale, state layers) - Roboto + Roboto Mono fonts loaded via Google Fonts - Spool SVG hardcoded colors updated to match new green-tinted surfaces Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Left navigation drawer (hamburger + gear icon) holds Settings and Completed Jobs, moving both off the main page - Camera card now uses a fixed 16:9 placeholder so show/hide no longer causes any layout reflow in the printer block - Completed Jobs rendered into the drawer instead of the main scroll area, keeping the printer block height stable across ticks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Nav drawer now contains three destination items with Material Symbols icons: Dashboard, Completed Jobs, Settings - Each destination is a full page rendered in <main>; navigateTo() switches pages, updates active indicator, and closes the drawer - Gear icon in topbar goes directly to Settings page (no drawer) - Settings form and Completed Jobs list are no longer inline in the drawer; the drawer is purely navigation - Page fade-in animation, MD3 tonal active indicator on nav items Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Status/stats are CFS-related, so they belong in the left column alongside the box grid and active slot. Right column is now purely printer hardware: temps, progress, and camera. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- isCameraEnabled/setCameraEnabled helpers persist state in localStorage - Settings page gets a Cameras card listing all printers with a webcam URL, each with an MD3 switch toggle - Camera card respects the toggle: when disabled it shows a compact one-line hint instead of reserving the full 16:9 space - Toggling rebuilds the affected printer block on the next tick Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace dark surface tokens with light-mode palette: white/grey surfaces, dark-green primary (#006B4F), light primary container (#89F8C7). Update all hardcoded rgba color values throughout style.css and spool SVG fill colours in app.js to match the new light palette. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop --md-bg from #F5FBF5 to #B8C4BA so the page background is clearly distinct from the white/light-grey card surfaces. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MD3 Expressive relies on spacing and typography hierarchy instead of hairline separators. Remove border-bottom rules from cardHead, spoolStatRow, cfsBox, spoolStatusFilament, settingsCardHead, modalHead, and navDrawerHead. Settings items within cards retain their separators for form legibility. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace #89F8C7 / rgba(137,248,199) with #A3CEBA / rgba(163,206,186) — same hue, much lower saturation, so active chips/nav items/buttons read as green without the neon quality. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three new breakpoints plus modifications to the existing 600px one:
768px — topbar wraps to two rows, topbarRight fills second row,
container/card padding tightens, brand + logo shrink slightly.
480px — subtitle hidden, refresh label hidden, spoolman link hidden,
topbar badges compact; printerHead stacks vertically; CFS box header
narrows to 40px with vertical-rl text, spool pods shrink to 44px;
stats grid columns tighten; jobs spool rows wrap.
360px — grams column hidden in stats table to preserve layout on the
smallest Android screens.
JS: badge text shortened at <480px ("Printer: connected" → "Connected",
"Printers: 2/2 online" → "2/2", "CFS: 1 detected" → "CFS: 1").
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MD3-compliant dark colour tokens (green/teal primary) via [data-theme="dark"] on <html>, overriding all surface/primary/error/warning CSS variables - Targeted overrides for hardcoded rgba() colours (badge states, slot outlines, hover inks, modal backdrops, spoolman list selection, etc.) - Inline <script> in <head> applies saved theme before first paint (no FOUC); defaults to system prefers-color-scheme if no manual preference is stored - Appearance card in Settings page with Dark mode mdSwitch toggle - Toggle wired in _initSettingsHandlers(); preference persisted in localStorage Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The struct fingerprint only covered cfs_slots (WS data), so changes to local slot fields (spoolman_id, material, name, color) never triggered a DOM rebuild. After unlinking, tick() fetched fresh state but the stale slot pods remained, making the UI appear unresponsive to unlink actions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of closing the modal on unlink, switch it to the "not linked" state in-place and load the Spoolman dropdown so the user can immediately select a replacement spool. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Surface API errors via alert instead of silently doing nothing, which should expose why unlink fails on the spool holder modal. Also captures slot/printer IDs before awaits and awaits the dropdown fetch before tick() to avoid any race conditions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_moonraker_set_active_spool used asyncio.ensure_future which requires a running event loop in the current thread. Sync FastAPI endpoints run in an AnyIO worker thread that has no event loop, so unlinking the SP slot (which triggers CLEAR_ACTIVE_SPOOL in moonraker mode) crashed with RuntimeError: There is no current event loop in thread. Replace the async wrapper + ensure_future with a plain daemon thread. _moonraker_send_gcode is already a blocking HTTP call, so no executor indirection is needed — a thread achieves the same fire-and-forget goal while working from both async and sync call sites. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Creality shares the same RFID material-type code across all spools of the same filament type, making cfs_rfid matching unreliable when multiple spools of the same type are in use. SSH serialNum lookup is spool-unique and is the only safe auto-link method. - Remove _spoolman_autolink_by_rfid and _spoolman_set_extra (dead code) - WS handler now only uses RFID-change detection to unlink on spool swap - SSH-based _ssh_fetch_and_apply flow unchanged Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Document why RFID material codes are not used (shared per filament type), explain the SSH serialNum mechanism, add auto-unlink conditions, and clarify the full CFTag → Spoolman → auto-link workflow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each box (Box 1–4) and the Spool holder are now proper Material cards with a cardHead (title + active slot badge + env sensor chips) and the four slot pods arranged in a horizontal row as the card body — matching the physical left-to-right order of rolls in the box. - Remove .boxRow / .boxHeader sidebar layout in favour of .card structure - Active slot indicator shows "Slot A active" badge in the card header - Spool card shows "Active" badge when the holder is the active input - Env chips move from sidebar column into the card header meta row - Responsive overrides updated; dead .boxHeader CSS removed Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
print_stats.filament_used is 0 during homing, probing and bed mesh — it only rises when actual extrusion happens. Expose it as moon_filament_used_mm in the live status and gate the SP-active logic on it so the spool holder card no longer shows the active highlight until the printer is genuinely extruding filament. This also prevents purge moves at the very start of a print from being attributed to the spool holder before the CFS has selected a slot. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces direct write_text with a write-to-tmp + rename pattern. rename(2) is atomic on Linux, eliminating the window where a crash or concurrent write could produce a truncated or double-JSON file. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Docker and Docker compose deployment
The healthcheck used curl which is not installed in the python:3.12-slim base image. Using Python's built-in urllib.request instead to check the health endpoint.
|
I edited the docker-compose health check command to your new change and it works. Thank you. |
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.
Problem: The healthcheck in docker-compose.yml used curl, but the python:3.12-slim base image does not include curl. This caused the healthcheck to always report unhealthy.
Solution: Changed the healthcheck to use Python's built-in urllib.request module instead of curl.
Fixes #11