Skip to content

concept(applications): SpoolEase integration — NFC hardware ↔ Spoolman sync#11

Draft
akira69 wants to merge 1 commit intobase/upstream-masterfrom
concept/spoolease-integration
Draft

concept(applications): SpoolEase integration — NFC hardware ↔ Spoolman sync#11
akira69 wants to merge 1 commit intobase/upstream-masterfrom
concept/spoolease-integration

Conversation

@akira69
Copy link
Copy Markdown
Owner

@akira69 akira69 commented Mar 19, 2026

Summary

This is a concept PR — no implementation code yet. It documents the full problem space, landscape of existing solutions, justification for building within Spoolman, and a concrete implementation plan for a Hardware Scale + NFC Sync Application within the Applications Framework (PR #9).

The primary integration target is SpoolEase, but the design is hardware-agnostic — the same Application framework can host adapters for SpoolEase, FilaMan (original ESP32), and future scale/NFC devices.


1. The Problem

Spoolman is excellent at tracking filament inventory but has no mechanism for automatically receiving weight updates from physical hardware. Every user with a scale today must manually weigh spools and type numbers into the UI. This is friction that directly determines whether a user bothers keeping their inventory accurate at all.

The ecosystem has produced several hardware projects that solve the physical measurement problem (scale + NFC tag) but each one deals with the Spoolman integration differently:

  • Some call Spoolman's REST API directly from embedded C++ (fragile, no auth)
  • Some require Spoolman to run in SPOOLMAN_DEBUG_MODE=TRUE to disable CORS (a workaround, not a solution)
  • Some have abandoned Spoolman integration entirely and built parallel inventory systems
  • None have a stable, documented, officially-supported integration path from Spoolman's side

This PR proposes solving that from the Spoolman side: a Device Sync Application that gives hardware devices a well-designed, authenticated, first-party sync endpoint.


2. The Ecosystem — What's Already Out There

2.1 SpoolEase (yanshay/SpoolEase, 451 ⭐)

ESP32 Console + Scale. NFC read/write. Bambu AMS via MQTT. Local web server. Open source (Apache 2.0 + Commons Clause).

Integration status: SpoolEase Issue #43 ("Add integration with spoolman") was opened June 2025 and closed wontfix October 2025. yanshay's objections were all valid from SpoolEase's perspective:

"I actually received quite a few requests to support spoolman, but that IMO contradicts [simplicity] above from what I've seen."
"Maintaining [integrations] would slow me down considerably introducing new features."

None of those objections apply when Spoolman implements the integration — Spoolman owns the code, and SpoolEase doesn't need to change a line.

How this PR addresses yanshay's issue Donkie#43 concerns directly:

yanshay's concern How this PR addresses it
"Spoolman requires Docker/DNS — too complex for my non-technical users" Integration lives in Spoolman, not SpoolEase. yanshay's users are not affected.
"Maintaining integrations would slow me down" Spoolman owner maintains the Spoolman side. yanshay maintains nothing.
"Integrations break when I can't control the other party" Spoolman's REST API has versioned stable endpoints. Spoolman controls the contract.
"Would require developing code inside SpoolMan itself" ✅ Yes — that's exactly what this PR does, from the Spoolman side.

A future action: comment on SpoolEase issue Donkie#43 once P2 is working, referencing the implementation as a full resolution.

2.2 SpoolEase API — Encryption Analysis

SpoolEase's web API encrypts payloads using AES-256-GCM (main API) and AES-256-CTR with HMAC-SHA256 (captive portal API). This is NOT security through obscurity — it's real cryptography — but it is fully open-source and implementable in Python.

The yanshay/esp-hal-app framework (MIT licensed) contains complete encrypt() / ctr_encrypt() / derive_key() implementations. Python equivalents using the cryptography library are a straightforward port of ~50 lines.

There is also a fixed key mechanism:

  • /api/fixed-key-config allows the user to set a persistent key on the device (stored across reboots)
  • This fixed key functions as a static API secret — user sets it once in the SpoolEase UI, shares it with Spoolman
  • Spoolman stores it, uses it to encrypt outbound requests and decrypt responses

There is also precedent for plaintext access:

  • /insecure/screenshot?key= endpoint already ships in SpoolEase — yanshay already considered programmatic access via plain URL key auth
  • This makes a /insecure/spools?key= feature request to yanshay a natural extension of existing design thinking

Q1 resolution: The encryption is not a deal-breaker. Implementation path:

  1. Primary: Implement AES-256-CTR encrypt/decrypt in Python using cryptography library + the fixed key as the API secret
  2. Better long-term: Open a feature request to yanshay for GET /insecure/spools?key= since the /insecure/ pattern already exists in his codebase

2.3 FilaMan Original (ManuelW77/Filaman, 169 ⭐, ARCHIVED)

DIY ESP32 firmware (HX711 load cell + PN532 NFC + OLED). Directly called Spoolman REST API from the device. No longer developed.

What FilaMan proved: The sm_id-on-tag → PUT /api/v1/spool/{id}/ with remaining_weight loop works at production quality. This is exactly the loop this integration Application formalizes.

ManuelW77 commented in Spoolman PR #605 about CORS, and Stothed775 echoed it: "would be great to use filaman without debug mode." PR Donkie#605 (merged Jun 2025) added SPOOLMAN_CORS_ORIGIN — FilaMan was the direct driver.

2.4 FilaMan-System (Fire-Devils/filaman-system, 23 ⭐, Very actively developed)

A standalone inventory management system — FastAPI + Astro frontend + Docker. A Spoolman competitor, not a companion.

What FilaMan-System proves: the problem is real enough that ManuelW77 built an entire alternative inventory system around it. Their spoolman-import endpoint shows users migrate from Spoolman — validating that Spoolman users are the natural audience.

How this PR changes the FilaMan-System story: A Device Sync Application in Spoolman removes the primary reason users would migrate away, and makes the FilaMan-System-ESP32 hardware usable with Spoolman directly (same Device Token pattern).


3. Why Implement Within Spoolman (Not as External Tool)

Argument Detail
Users are already here Spoolman users have the database, the inventory, the NFC tags linked to spool IDs. External tools must either replicate or import this data.
REST API is stable and versioned Integrations from Spoolman's side are against a stable contract Spoolman controls.
No second app to install Spoolman instance becomes the sync hub. No parallel inventory.
Applications Framework isolates the code Per PR #9, Applications are independent modules. This feature ships without touching core Spoolman code.
Hardware devices already call Spoolman's API FilaMan proved it works. The only missing pieces are: proper device auth, CORS exemption for registered devices, and a weight-update endpoint.
CORS/debug mode workaround eliminated Device Token auth with a dedicated endpoint eliminates SPOOLMAN_DEBUG_MODE=TRUE.

4. Implementation Plan — Spoolman-Side Device Sync Application

4.1 Best Reference Codebases

Primary reference: Original FilaMan (ManuelW77/Filaman, MIT)

  • Weight update logic: spool_id from NFC tag → remaining_weight = measured_weight - empty_spool_weight
  • Weight stabilization filter (moving average, 3+ stable readings before update)
  • NFC tag JSON format: {"sm_id": "123", "color": "#...", "type": "PLA", "brand": "..."}

Secondary reference: FilaMan-System Device API (Fire-Devils/filaman-system, MIT)

  • Device registration: 6-digit device code → persistent token
  • POST /api/v1/devices/scale/weight with tag_uuid + measured_weight_gremaining_weight_g
  • Authorization: Device header pattern — embeddable, no session complexity
  • Heartbeat / online state tracking pattern

Tertiary reference: SpoolEase encryption framework (yanshay/esp-hal-app, MIT)

  • derive_key() / ctr_encrypt() / ctr_decrypt() — direct starting point for Python port
  • ScaleConfigDTO.key — static key pattern for the external scale, reusable as API secret model

4.2 Tare Weight Source — Three-Level Fallback

remaining_weight = measured_weight − tare_weight. Spoolman's data model provides three levels of tare:

Level Field Scope
1 (most specific) Spool.spool_weight Individual spool override
2 Filament.spool_weight Filament-type default
3 (vendor default) Vendor.empty_spool_weight Vendor catch-all

Resolution: Walk levels 1→3. If all three are None, reject the weight update and return an error requiring the user to fill in a spool weight before linking the spool to a device. No silent failures or guessing.

Q3 resolution: Tare weight is fully covered by existing Spoolman data model. The weight hardware (SpoolEase scale) provides only the raw measurement — Spoolman owns the tare calculation using its own vendor/filament/spool data. This is the correct data ownership boundary.

4.3 Device API — New Endpoints

POST /api/v1/application/device-sync/devices/register
     Body: { "device_code": "ABC123" }
     Response: { "token": "dev.1.xxx" }
     Auth: Device code (one-time)

POST /api/v1/application/device-sync/devices/heartbeat
     Body: { "ip_address": "192.168.1.100", "device_type": "spoolease|filaman|generic" }
     Auth: Device token

POST /api/v1/application/device-sync/weight
     Body: {
       "tag_id": "04:AB:12:...",     // NFC tag UID — primary lookup
       "spool_id": 42,                // Fallback if no tag_id
       "measured_weight_g": 847.3    // Total weight including empty spool
     }
     Response: {
       "remaining_weight_g": 612.3,  // Calculated: measured - tare
       "spool_id": 42,
       "filament_name": "Bambu PLA Matte White",
       "tare_source": "filament"     // Shows which level was used
     }
     Auth: Device token

GET  /api/v1/application/device-sync/devices
     Response: list of registered devices + online status + last_seen
     Auth: User session (admin UI)

Device Token scope: Tokens are Application-scoped — they may only call Device Sync endpoints. A compromised device token cannot read or modify spool data beyond what the weight endpoint expresses. Q2 resolved: per-Application tokens.

4.4 Weight History

Every successful weight update is logged:

weight_sync_log (Application-level table)
  id
  spool_id
  device_id         // Which registered device reported the measurement
  measured_weight_g // Raw measurement from device
  tare_weight_g     // Tare used (from spool/filament/vendor)
  remaining_weight_g
  timestamp

Policy: last-write-wins on remaining_weight for the actual Spoolman spool record. The full measurement history is preserved in weight_sync_log for auditing and future "usage graph" features.

Q5 resolved: Timestamped log with device_id, retained indefinitely in the Application DB.

4.5 NFC Tag Link Storage

Option A — Extra Field (no schema changes, independent of PR Donkie#880):

  • nfc_tag_uid extra field key stored on the Spoolman spool record
  • Device Sync Application queries extra = {"nfc_tag_uid": tag_id} to resolve tag → spool

Option B — Sibling Application sharing with PR Donkie#880:

Q4 resolution: Use Option A for P2 (no dependency on PR Donkie#880 merge). PR Donkie#880 becomes a sibling NFC Application — the two Applications will share a common NFC lookup utility once PR Donkie#880 lands. The Common Fields Library (PR #10) will provide the nfc_tag_uid extra field template.


5. Data Ownership Boundary Design

(a) Linking — NFC Tag UID as Shared Key

Three workflows to establish the link:

  1. Smart selection list: Fetch device's spool list → present → user confirms match → nfc_tag_uid extra field saved to spool
  2. NFC-assisted (Web NFC API, Chrome Android): Tap tag → read UID → find or create link
  3. Auto-match on import: Match on (brand + material + color) → flag for confirmation

(b) Field Locking

Once linked, hardware-managed fields become read-only markers in the Spoolman UI:

Field Owner after linking
remaining_weight Hardware device (measured)
color_hex, material, brand Hardware device (NFC-encoded)
first_used, last_used Spoolman
location, comment Spoolman

Override: "Unlink / Take ownership" action unlocks all fields.

(c) Tare Weight Requirement Enforcement

Before a spool can be linked to a device for weight sync, Spoolman validates that a tare weight is available (checking the three-level fallback). The UI prompts the user to fill in spool_weight or filament.spool_weight if none is found. This is a one-time setup step, not ongoing friction.

(d) Sync Methods

  1. Push (preferred): Device calls POST /weight after each weighing — immediate, low overhead
  2. AES-256-CTR pull (SpoolEase fallback): Spoolman polls SpoolEase using fixed-key-encrypted requests — uses Python cryptography library port of SpoolEase's ctr_encrypt/ctr_decrypt
  3. SSDP-triggered pull: Spoolman detects SpoolEase Console via SSDP → immediate weight check
  4. Future: Request yanshay add GET /insecure/spools?key= (precedent: /insecure/screenshot already exists)

6. Target Hardware / Integration Roadmap

Device Status Integration Path Notes
SpoolEase Console+Scale Primary target Push via Device API OR encrypted AES-256-CTR poll Fixed key = API secret; /insecure/ precedent in codebase
FilaMan-System-ESP32 Secondary target FilaMan-System device push → Device API (same endpoint pattern) Aligns with FilaMan-System's Device Token design
FilaMan Original (archived) Reference only Pattern: NFC uid → sm_id lookup → PUT weight Proven approach
Generic HTTP scale Future Any device that can POST to /weight with Device Token No specific hardware required
Klipper-NFC-Daemon (PR Donkie#880) Sibling Application Calls NFC Application endpoints, not Device Sync Different flow: NFC identification, not weight
OctoPrint-Spoolman plugin Already exists No change needed

7. How This Changes the Community Conversations

SpoolEase issue Donkie#43 (wontfix)

Once P2 delivers a working weight endpoint: comment on yanshay/SpoolEase#43 showing that Spoolman provides a Device Token endpoint SpoolEase can call with a single HTTPS POST, plus a Python implementation of the AES-256-CTR layer for the pull direction. No changes needed on SpoolEase's side.

Spoolman Discussion Donkie#629 (FilaMan companion device)

ManuelW77 introduced FilaMan here in Feb 2025. With FilaMan now archived and FilaMan-System having pivoted away, Discussion Donkie#629 has no resolution. This PR gives the Spoolman-native answer.

Spoolman PR Donkie#605 (CORS origin variable)

The Device Token auth pattern makes the CORS workaround unnecessary for registered devices. A registered device's calls are pre-authorized — no SPOOLMAN_DEBUG_MODE=TRUE or manual CORS configuration.

FilaMan-System spoolman-import endpoint

FilaMan-System users can keep Spoolman as home inventory and use FilaMan-System-ESP32 hardware via the same Device Token pattern, reducing migration pressure.

Q6 resolved: All outreach deferred until P2 delivers a working implementation. Then comment on the three locations above in one coordinated update.


8. Implementation Phases

Phase Scope Key Dependencies
P1 — Device Registration Device code → token flow, heartbeat, device list UI PR #9 (Applications Framework)
P2 — Weight Sync (push) POST /weight endpoint, tare three-level fallback, Extra Field tag link, weight_sync_log P1 complete
P3 — Field Locking UI markers for hardware-managed fields, override mechanism P2 complete
P4 — Pull Sync (SpoolEase) Python AES-256-CTR encrypt/decrypt, fixed key config, SSDP discovery P2 complete
P5 — Smart Sync SSDP auto-detect, MQTT event trigger, webhook receiver P4 complete
P6 — FilaMan-System adapter Accept FilaMan-System Device Token format, map FilaMan API to Spoolman schema P2 complete

Note for PR #9 — Navigation Hierarchy

Application configuration in PR #9 (Applications Framework) should include a per-Application toggle controlling where the Application surfaces in the sidebar navigation:

  • Top-level sidebar entry (same hierarchy as Spools, Filaments, Vendors) — for high-traffic Applications like Hardware Sync that users access frequently
  • Under the Applications tab — the default; appropriate for rarely-accessed or administrative Applications

This toggle should be set by the Application author at registration time, with an optional instance-admin override. This allows Hardware Sync, once stable, to graduate to top-level visibility without a structural refactor.


9. Open Questions — Status

# Question Status
Q1 SpoolEase API encryption Resolved — AES-256-CTR, open-source, Python implementable. Fixed key = API secret. /insecure/ endpoint precedent for cleaner future path.
Q2 Device Token scope Resolved — Per-Application tokens only
Q3 Tare weight source Resolved — Three-level fallback: Spool.spool_weightFilament.spool_weightVendor.empty_spool_weight. Reject (not silently fail) if all null.
Q4 NFC tag link storage Resolvednfc_tag_uid Extra Field for P2; PR Donkie#880 becomes sibling Application sharing lookup utility
Q5 Multiple devices / history Resolved — Last-write-wins on spool record; weight_sync_log table (device_id + timestamp + measured + tare + remaining)
Q6 FilaMan-System outreach Resolved — Defer until P2 works; then comment on Donkie#629, Donkie#605, SpoolEase#43

10. Relationship to Other PRs

PR Relationship
#9 (Applications Framework) Required infrastructure — Device Sync Application lives inside the framework
#10 (Common Fields Library) Provides nfc_tag_uid, spoolease_id, spoolease_console_url, spoolease_fixed_key extra field templates
Donkie#880 (NFC TigerTag/OpenPrintTag) Sibling Application — device sync shares NFC tag lookup utility. Covers NFC identification; Device Sync covers weight sync.
Donkie#605 (CORS origin) Obsoleted for registered devices — Device Token auth removes the CORS workaround requirement
yanshay/SpoolEase#43 Upstream closed wontfix — integration is entirely Spoolman-side, no changes needed to SpoolEase

References

Resource URL
SpoolEase project https://github.com/yanshay/SpoolEase (451 ⭐)
SpoolEase framework (encryption source) https://github.com/yanshay/esp-hal-app/blob/0.6.1/esp-hal-app-framework/src/framework_web_app.rs
SpoolEase issue Donkie#43 (wontfix) yanshay/SpoolEase#43
FilaMan original (archived) https://github.com/ManuelW77/Filaman (169 ⭐)
FilaMan-System (actively developed) https://github.com/Fire-Devils/filaman-system (23 ⭐)
FilaMan-System Device API docs https://github.com/Fire-Devils/filaman-system/blob/main/API-DEVICES.md
FilaMan in Spoolman Discussions Donkie#629
FilaMan CORS issue in Spoolman Donkie#605
Applications Framework #9
Common Fields Library #10

This branch holds the concept PR for integrating SpoolEase (NFC hardware
Console + Scale) with Spoolman's Application framework.

See PR description for full design spec including:
- SpoolEase API surface analysis
- Data ownership boundary design (4-point spec)
- Sync strategy options and open questions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant