Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
6243da2
Add German/English language toggle (i18n)
koen01 Feb 27, 2026
7e15d26
Add optional Spoolman integration for spool inventory sync
koen01 Feb 27, 2026
0db34b8
Remove local history tracking — Spoolman-first refactor
koen01 Feb 28, 2026
ee92c85
Update repository URL in install script
koen01 Feb 28, 2026
098663c
Replace Moonraker HTTP poll with native WebSocket (ws://printer:9999)
koen01 Feb 28, 2026
3d0431e
Fix WS connect: drain initial status dump before heartbeat handshake
koen01 Feb 28, 2026
b3ab94f
Fix WS ping timeout; show brand/name/material/Spoolman on slot chips
koen01 Feb 28, 2026
51da7ca
Add RFID-based Spoolman auto-link via extra.cfs_rfid
koen01 Feb 28, 2026
7193387
Auto-unlink + re-link on RFID change for already-linked slots
koen01 Feb 28, 2026
a577197
Clear stale cfs_active_slot when no spool is selected
koen01 Feb 28, 2026
ab4bb6c
Fix Spoolman extra field format: values must be JSON-encoded strings
koen01 Feb 28, 2026
e17ddf2
Fix stale active_slot: remove JS fallback, clear bad schema default
koen01 Feb 28, 2026
84e286c
Use Optional[str] for active_slot to avoid Pydantic Literal type edge…
koen01 Mar 1, 2026
1ec3120
Fix WS drain loop stuck + protect state from being wiped on load failure
koen01 Mar 1, 2026
4df92dc
Add Moonraker job-end usage reporting; remove i18n
koen01 Mar 1, 2026
a0d699b
Show color swatch in Spoolman spool link list
koen01 Mar 1, 2026
cebd531
Redesign slot grid to Creality-style spool pod layout
koen01 Mar 1, 2026
8ea7f24
Fix WS polling loop + rebrand to CFSync
koen01 Mar 1, 2026
c2e36bf
Show printer name + firmware from WS in header subtitle
koen01 Mar 1, 2026
cfea2e6
Calculate Spoolman percent for manual (non-RFID) CFS slots
koen01 Mar 1, 2026
289b105
Add README and screenshot for CFSync
koen01 Mar 1, 2026
63158f2
Fix repo URL in update.sh to match install.sh
koen01 Mar 1, 2026
9dedad7
README: document Spoolman cfs_rfid extra field and auto-link setup
koen01 Mar 1, 2026
2ff75bc
README: broaden supported printers to all K1 series with CFS
koen01 Mar 1, 2026
d211302
Add Fluidd runout sensor panel replacement via bookmarklet
koen01 Mar 1, 2026
85a4e9e
Fix Spoolman not updated when service starts mid-print
koen01 Mar 1, 2026
c5e7d20
Add CORS middleware to allow Fluidd panel cross-origin requests
koen01 Mar 1, 2026
bd9717a
Add Spoolman link modal screenshot to README
koen01 Mar 1, 2026
955739d
Add credits section to README
koen01 Mar 1, 2026
83425a2
Fix update.sh deleting venv and failing on Debian 13
koen01 Mar 1, 2026
f3db1c9
Fix update.sh: drop --delete from rsync to preserve venv
koen01 Mar 1, 2026
84153bf
Hide material and percent on empty CFS slots
koen01 Mar 1, 2026
8ba5c45
Fix RFID auto-link writing wrong cfs_rfid to non-RFID spools
koen01 Mar 1, 2026
f076d09
Replace slot pod edit icon with Spoolman link indicator dot
koen01 Mar 1, 2026
031d2d1
Add Spoolman link to status card header
koen01 Mar 1, 2026
87afa61
Add workflow section to README with SpoolID app reference
koen01 Mar 1, 2026
c524de3
Simplify install.sh: remove unused prompts, fix config keys
koen01 Mar 2, 2026
1ad11ae
Add ws_dump.py: passive WebSocket stream dumper for protocol analysis
koen01 Mar 2, 2026
fd0c46c
Add SSH-based serialNum auto-link and fix spool swap unlink
koen01 Mar 2, 2026
079849b
Rename repo references from Filament-Management to CFSync
koen01 Mar 2, 2026
0aa960f
Update README install/update URLs to CFSync repo
koen01 Mar 2, 2026
b5e3aad
Replace SpoolID with CFTag in workflow section
koen01 Mar 2, 2026
365ca6c
Rewrite RFID auto-linking section to reflect current dual-mechanism b…
koen01 Mar 2, 2026
55c0997
Fix install/update curl commands to use correct branch URL
koen01 Mar 2, 2026
c40d385
Replace WS-snapshot attribution with real-time per-slot delta tracking
koen01 Mar 3, 2026
7a81305
Show spool fill level in icon based on percent remaining
koen01 Mar 3, 2026
457d675
Replace Spoolman Status panel with CFS Stats lifetime wear tracking
koen01 Mar 3, 2026
75843c0
Render CFS Stats boxes based on active slots from state
koen01 Mar 4, 2026
b3742aa
Use hostname and modelVersion from WS payload for page title and heading
koen01 Mar 4, 2026
89f6962
Fix printer name overlapping CFSync title
koen01 Mar 6, 2026
9a59b89
Parse initial WS burst for printer identity (hostname/modelVersion)
koen01 Mar 6, 2026
6849038
Replace logo with Creality logo
koen01 Mar 6, 2026
e432f23
Fix logo alignment - use contain instead of clipping
koen01 Mar 6, 2026
086bd92
Add CFS favicon
koen01 Mar 6, 2026
da14582
Swap logo to CFS unit image
koen01 Mar 6, 2026
21f3bd1
Show filament colors in Fluidd bookmarklet panel
koen01 Mar 7, 2026
507b2e4
Add Tampermonkey userscript generator for permanent Fluidd panel
koen01 Mar 7, 2026
026683e
Remove roll change option from filament modals
koen01 Mar 7, 2026
71ae7fa
Use Spoolman-based % calculation for RFID spools (same as manual)
koen01 Mar 9, 2026
a9cb0f5
Update CLAUDE.md to reflect current codebase state
koen01 Mar 10, 2026
dc58911
Adjust to allow multiple printers to be tracked to a single spoolman
davidkinnes Mar 11, 2026
ce29465
Adjust to allow multiple printers to be tracked to a single spoolman
davidkinnes Mar 11, 2026
d608b5e
Remove stale printer_url tip from footer
koen01 Mar 10, 2026
25d17ac
Fix SSH EOF during negotiation by enabling legacy KEX algorithms
koen01 Mar 11, 2026
92a17bb
Replace SSH-based serialNum linking with direct WS extraction
koen01 Mar 11, 2026
89710b7
Restore SSH serialNum fetch using sshpass instead of paramiko
koen01 Mar 11, 2026
9305806
Add support for direct printer spool input and related UI adjustments
davidkinnes Mar 12, 2026
f2c4aa3
Handle empty slot detection and updates across backend and frontend
davidkinnes Mar 12, 2026
bce9be0
Merge branch 'pr-1' into spoolman
davidkinnes Mar 12, 2026
e980e90
Handle empty spool detection improvements and update cache-busting ve…
davidkinnes Mar 12, 2026
3da6a4e
Handle empty spool signatures and missing CFS slots as empty
davidkinnes Mar 12, 2026
bd3ba30
Filter phantom CFS boxes and infer boxes only from live signals
davidkinnes Mar 12, 2026
a10e977
Add spool to status panel and rename status labels
davidkinnes Mar 12, 2026
c4b417d
Simplify CFS badge text for consistency
davidkinnes Mar 12, 2026
acae56e
Reset Spoolman link when filament metadata changes on loaded slot
davidkinnes Mar 12, 2026
f5d4d3f
Align spool status row with CFS distance and grams metrics
davidkinnes Mar 12, 2026
6aef68c
Add recent jobs history card with start/end/printer/spool/usage
davidkinnes Mar 12, 2026
45ae9e5
Add spool reallocation API and frontend integration with visual impro…
davidkinnes Mar 13, 2026
7702858
Normalize color handling for spools and update frontend cache version
davidkinnes Mar 13, 2026
c01426e
Hydrate job history with spool colors from Spoolman metadata and add …
davidkinnes Mar 13, 2026
d44c080
Add history relink modal and enhance spool reallocation workflow
davidkinnes Mar 13, 2026
c3b03cc
Display print duration on job history cards and update cache version
davidkinnes Mar 13, 2026
d1571e6
Resolve slot tracking with new fallback logic and refactor `curr_slot…
davidkinnes Mar 16, 2026
dd05c2a
Update cache version and include spool material in label display
davidkinnes Mar 18, 2026
31e5f4f
Add environmental tracking for CFS boxes and update cache version
davidkinnes Mar 20, 2026
0e3330f
Update README.md with new features, screenshots, and usage details
davidkinnes Mar 20, 2026
c34033f
Update README.md with new features, screenshots, and usage details
davidkinnes Mar 20, 2026
05d1abc
Add multi-range view and retention for environmental data in CFS boxes
davidkinnes Mar 24, 2026
5961701
Ensure atomic writes for JSON state files and improve error handling
davidkinnes Apr 25, 2026
9e70889
Backfill missed jobs from Moonraker history during offline periods
davidkinnes Apr 25, 2026
207df1d
Add spool filtering and improve material/vendor handling in dropdowns
davidkinnes Apr 25, 2026
3be2086
Introduce SQLite-based job history database and improve history manag…
davidkinnes Apr 29, 2026
489eb05
Add jobs page, API enhancements, and job history filtering
davidkinnes Apr 29, 2026
a50cdb4
Add jobs page with filtering, pagination, and spool usage display
davidkinnes Apr 29, 2026
54a060e
Improve CFS badge details with connected box counts per printer
davidkinnes Apr 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

CFSync is a local web application for tracking 3D printer filament/spool usage, built for Creality K2 Plus CFS (4x4 slot grid) and Klipper/Moonraker-based printers. It runs as a FastAPI backend with a vanilla JavaScript SPA frontend.

## Development Commands

```bash
# Setup
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

# Run development server (with hot-reload)
uvicorn main:app --reload --host 0.0.0.0 --port 8000

# Health check
curl http://localhost:8000/api/health
```

There are no automated tests, linting tools, or CI/CD pipelines configured.

## Architecture

**Backend:** Single-file FastAPI app (`main.py`, ~1500 lines) with Pydantic models in `models/schemas.py`. Data is persisted as JSON files in `data/` (state.json, config.json, profiles.json) — no database.

**Frontend:** Vanilla JS SPA in `static/` (index.html, app.js, app.css, style.css). No build step, no framework — pure DOM manipulation. `fluidd-panel.js` is a standalone script injected into the Fluidd UI via bookmarklet or Tampermonkey userscript (generated from the settings page).

**Moonraker integration:** Optional async background polling loop that queries the printer's Moonraker API for print job status, filament usage, and CFS slot info. Includes Creality K2 Plus-specific object parsing (box.T1-T4, filament_rack). Printer identity (`printer_name`, `printer_firmware`) is parsed from the Moonraker WebSocket.

## Key Patterns

- **Pydantic v1/v2 compatibility:** Helper functions `_model_dump()`, `_model_validate()`, `_req_dump()` abstract over version differences. Always use these instead of calling `.dict()` or `.model_dump()` directly.
- **State migration:** `_migrate_state_dict()` handles legacy field names (e.g., `color` → `color_hex`, `vendor` → `manufacturer`) and older state.json formats.
- **Two API tiers:** `/api/*` returns raw JSON; `/api/ui/*` wraps responses in `{"result": {...}}` for the frontend.
- **Slot IDs:** Literal type `SlotId` = `"1A"` through `"4D"` (4 boxes × 4 colors, 16 total).
- **Spool epochs:** Incrementing `spool_epoch` counter tracks spool changes per slot, enabling per-spool history filtering.
- **History conventions:** `_hist_push()` prepends (newest-first); `_hist_upsert_by_src()` updates existing entries by source marker during live prints.
- **Internal functions** are prefixed with `_` (e.g., `_http_get_json`, `_hist_push`).
- **Filament calculation:** grams = density × π × (diameter/2)² × length, with material-specific density from profiles.json.

## Spoolman Integration (Optional)

Set `spoolman_url` in `data/config.json` to enable. This app acts as the only bridge between Spoolman and the printer (Moonraker's Spoolman plugin is not used). Spools are linked manually via the slot modal dropdown or auto-linked by RFID tag (`_spoolman_autolink_by_rfid()`). On link, `remaining_weight` is imported from Spoolman. Consumption is synced back via `PUT /api/v1/spool/{id}/use` (fire-and-forget) when prints finalize or manual allocations are made. Roll changes auto-unlink the Spoolman spool. All Spoolman calls are best-effort (`_spoolman_*` helpers) and never block local tracking.

**Spoolman API endpoints:** `GET /api/ui/spoolman/spools`, `POST /api/ui/spoolman/link`, `POST /api/ui/spoolman/unlink`, `GET /api/ui/spoolman/spool_detail`.

**Percentage calculation:** For RFID-linked spools, remaining % is calculated the same way as manual spools — using Spoolman's `remaining_weight` divided by the spool's initial weight.

## Production Deployment

Installs to `/opt/filament-management/` as a systemd service. See `install.sh`, `update.sh`, `uninstall.sh`, and `filament-management.service.example`.
137 changes: 130 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,137 @@
# Filament-Management
# CFSync

Local filament / spool tracking for Creality CFS & Klipper via Moonraker.
A local web dashboard for managing filament spools on **Creality K1 series printers with CFS** (Colour Filament System), including the K1, K1C, K1 Max, K1 SE, and K2 Plus. CFSync connects directly to one or more printers over WebSocket, reads live spool data from all CFS slots, and optionally syncs consumption back to [Spoolman](https://github.com/Donkie/Spoolman).

Track filament usage per slot, handle color changes during prints and keep everything fully local.
![CFSync screenshot](docs/screenshot.png)

No cloud. No external services.
## Features

---
- Multi-printer support — monitor and manage multiple configured printers in one UI
- Live CFS slot view — filament colour, material, and fill level per slot
- RFID spool percent from printer sensor; calculated percent for non-RFID spools via Spoolman
- Spoolman integration — link spools, track remaining weight, auto-report usage at job end
- Moonraker job tracking — attributes `filament_used` proportionally across active slots at print completion
- Clickable CFS box temperature/humidity values with historical graphs
- Recent jobs panel with per-slot filament usage breakdown
- Reallocate historical job usage to a different Spoolman spool (link/relink)
- Printer name and firmware version shown in header (read from WebSocket)
- Dark UI, no build step, runs as a systemd service

## 🚀 Installation (One-Liner)
## Screenshots

### Dashboard

![CFSync dashboard](docs/screenshot.png)

### Recent jobs

![Recent jobs](docs/screenshot-past-jobs.png)

### Temperature history graph

![Temperature history graph](docs/screenshot-temperature-graph.png)

### Humidity history graph

![Humidity history graph](docs/screenshot-humidity-graph.png)

### Reallocate filament usage

![Reallocate filament usage](docs/screenshot-reallocate-filament-usage.png)

## Requirements

- Linux host on the same network as the printer (e.g. a Pi or the printer's companion board)
- Creality K1 series printer with CFS (K1, K1C, K1 Max, K1 SE, K2 Plus)
- Python 3.10+
- Optional: [Spoolman](https://github.com/Donkie/Spoolman) for spool tracking

## Install

```bash
curl -fsSL https://raw.githubusercontent.com/jkef80/Filament-Management/main/install.sh | sudo bash
curl -fsSL https://raw.githubusercontent.com/koen01/CFSync/refs/heads/spoolman/install.sh | sudo bash
```

The installer will prompt for:

| Prompt | Example |
|---|---|
| UI port | `8005` |
| Printer IPs | `192.168.1.144, 192.168.1.145` |
| Spoolman URL *(optional)* | `http://192.168.1.10:7912` |

After install, open `http://<host-ip>:<port>` in your browser.

## Configuration

Settings are stored in `data/config.json`:

```json
{
"printers": [
{ "id": "Creality k2 Pro", "address": "192.168.1.144" },
{ "id": "Creality Hi", "address": "192.168.1.145" }
],
"filament_diameter_mm": 1.75,
"spoolman_url": "http://192.168.1.10:7912"
}
```

Alternative (IDs default to IP address):

```json
{
"printer_urls": ["192.168.1.144", "192.168.1.145"],
"filament_diameter_mm": 1.75,
"spoolman_url": "http://192.168.1.10:7912"
}
```

## Spoolman — RFID auto-linking

When an RFID-tagged spool is inserted into a CFS slot, CFSync automatically links it to the correct Spoolman spool — no manual selection needed. There are two mechanisms, used together:

**1. Serial number via SSH (primary — works instantly with CFTag-tagged spools)**

CFTag writes the Spoolman spool ID directly onto the RFID chip as its serial number. When CFSync detects a new RFID spool, it SSHes into the printer and reads the spool data file to extract the serial number. If it matches a Spoolman spool ID, the slot is linked immediately — no prior setup or manual linking required.

**2. RFID code via Spoolman extra field (fallback)**

When you manually link a spool via the CFSync slot modal, CFSync stores the slot's RFID code in a `cfs_rfid` extra field on that spool in Spoolman. Next time the same tag is detected in any slot, CFSync looks it up and auto-links. This requires the extra field to be pre-created in Spoolman:

1. Open Spoolman → **Settings** → **Extra fields**
2. Add a new field: **Name** `cfs_rfid`, **Field type** Text
3. Save

![Spoolman link modal](docs/spoolman-link.png)

> **Note:** RFID tags are only present on spools with a Creality RFID chip. Spools without RFID can still be linked manually.

## Workflow — adding a new spool with RFID

The recommended flow uses **[CFTag](https://github.com/koen01/cftag)** (Android, NFC required) — a companion app built for this ecosystem. CFTag handles the entire tagging process in one session: it creates the spool in Spoolman, then guides you through writing both RFID tags on the spool back-to-back without re-entering any data.

1. **Open CFTag** → fill in filament details → tap **Create in Spoolman**. CFTag creates the spool entry and immediately prompts you to write the first tag. Hold your phone to the tag, then flip the spool and write the second tag when prompted — done in one flow.
2. **Load the spool** into a CFS slot.
3. **CFSync auto-links** the slot to the Spoolman spool the moment it detects the RFID tag — no manual action needed.

From this point on, inserting that spool into any CFS slot will auto-link it instantly. Filament consumption is reported back to Spoolman after each print.

> Spools without a Creality RFID chip skip step 1 and must be linked manually each time they are loaded.

## Update

```bash
curl -fsSL https://raw.githubusercontent.com/koen01/CFSync/refs/heads/spoolman/update.sh | sudo bash
```

## Logs

```bash
sudo journalctl -u filament-management -f
```

## Credits

- [jkef80/Filament-Management](https://github.com/jkef80/Filament-Management) — original Moonraker-based filament management that this project evolved from
- [DaviBe92/k2-websocket-re](https://github.com/DaviBe92/k2-websocket-re) — reverse-engineered Creality K2 WebSocket protocol documentation that made the CFS integration possible
Binary file added docs/screenshot-humidity-graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshot-past-jobs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshot-reallocate-filament-usage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshot-temperature-graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/spoolman-link.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 13 additions & 12 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set -euo pipefail

APP_DIR="/opt/filament-management"
SERVICE_NAME="filament-management"
REPO_URL="https://github.com/jkef80/Filament-Management.git"
REPO_URL="https://github.com/davidkinnes/CFSync.git"

if [[ ${EUID} -ne 0 ]]; then
echo "Please run with sudo"
Expand All @@ -28,17 +28,19 @@ ask() {
echo "${var:-$default}"
}

echo "=== Filament Management Installer ==="
echo "=== CFSync Installer ==="

UI_PORT=$(ask "UI Port" "8005")
MOON_HOST=$(ask "Moonraker Host/IP" "192.168.178.148")
MOON_PORT=$(ask "Moonraker Port" "7125")
POLL=$(ask "Poll interval (sec)" "5")
PRINTER_IPS=$(ask "Printer IPs (comma-separated)" "192.168.1.144")
DIAM=$(ask "Filament diameter (mm)" "1.75")
AUTOSYNC=$(ask "CFS Autosync? (y/N)" "N")
SPOOLMAN_URL=$(ask "Spoolman URL (optional, e.g. http://host:7912)" "")

AUTOSYNC_BOOL=false
if [[ "$AUTOSYNC" =~ ^[Yy]$ ]]; then AUTOSYNC_BOOL=true; fi
PRINTER_JSON=$(echo "$PRINTER_IPS" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | awk 'NF' | awk '{printf "\"%s\",", $0}' | sed 's/,$//')
if [ -n "$PRINTER_JSON" ]; then
PRINTER_JSON="[$PRINTER_JSON]"
else
PRINTER_JSON="[]"
fi

echo "Installing to $APP_DIR"

Expand Down Expand Up @@ -73,18 +75,17 @@ mkdir -p "$APP_DIR/data"

cat > "$APP_DIR/data/config.json" <<CFG
{
"moonraker_url": "http://${MOON_HOST}:${MOON_PORT}",
"poll_interval_sec": ${POLL},
"printer_urls": ${PRINTER_JSON},
"filament_diameter_mm": ${DIAM},
"cfs_autosync": ${AUTOSYNC_BOOL}
"spoolman_url": "${SPOOLMAN_URL}"
}
CFG

chown -R "$REAL_USER":"$REAL_USER" "$APP_DIR/data"

cat > "/etc/systemd/system/${SERVICE_NAME}.service" <<SVC
[Unit]
Description=Filament Management
Description=CFSync
After=network-online.target
Wants=network-online.target

Expand Down
Loading