Reporter on a Raspberry Pi 5 couldn't read NFC tags — gauge worked,
SPI bus and wiring fine, but PN5180 transfers didn't complete.
Manually commenting out `self._spi.no_cs = True` restored
communication. Root cause: Pi 5's RP1 southbridge SPI driver
(spi-rp1) doesn't honour the SPI_NO_CS ioctl the way the historical
Broadcom driver on Pi 4 did.
Safe to relax Pi-wide. SpoolBuddy's PN5180 NSS line is wired to
GPIO23 (manual CS in _cs_low / _cs_high — the kernel's default
auto-CS timing doesn't meet the PN5180's 5µs setup / 100µs hold
spec). The hardware CE0 line (GPIO8) is not connected to the
reader, so whether the kernel auto-toggles it is electrically
invisible. The no_cs = True call was always cosmetic on this
hardware.
Wraps the assignment in try/except OSError in both the daemon and
the diagnostic script; the daemon logs at debug level so future
Pi-5-specific triage is greppable. README updated to drop the
"spidev.no_cs = True resolves this" sentence and explain the
manual GPIO23 CS scheme carries the timing on its own.
Reshapes the embedded PrettyGCode viewer (landed in #963) into a focused
archive-preview tool, matching Bambuddy's data model instead of the
OctoPrint-style "connected-printer + library file picker" flow it shipped
with. Reached only from the Archives page 3D-preview button; URL
/gcode-viewer?archive=<id>[&plate=<N>].
Backend:
- /archives/{id}/gcode accepts ?plate=N and resolves the filename by
parsing the suffix as int, so zero-padded names like plate_01.gcode
are found when the plates endpoint reports index 1.
- /archives/{id}/plates gains top-level has_gcode: bool. Source-only
3MFs (PNG/JSON fallback path) surface the flag so the frontend can
skip the picker instead of sending the user into a dead viewer.
- printer_state_to_dict injects name + model into every WS snapshot so
consumers render proper labels on the initial tick without racing a
separate /printers fetch.
- /gcode-viewer (no trailing slash) dropped from the backend so reloads
fall through to the SPA catch-all and keep the layout shell; only
/gcode-viewer/ (trailing slash) and /gcode-viewer/<path> remain for
the iframe + static assets.
Frontend:
- PlatePickerModal shown only for multi-plate archives with sliced
gcode, grid layout with thumbnails matching the Re-print modal.
- Source-only archives show a noGcode toast instead of the empty
viewer.
- ArchivesPage navigate path swapped to /gcode-viewer?archive=<id> with
no trailing slash; GCodeViewerPage iframe forwards
window.location.search so the archive reference survives both the
initial navigate and a full-page reload.
- Viewer iframe's auth path: fetch intercept injects Bearer; a 401
redirects to / so the SPA handles login.
Viewer adapter:
- Stripped the printer selector, WebSocket subscription, library file
picker, tryAutoLoadPrintingFile, BAMBU_BED_SIZES, and updatePrinter-
Selector. The viewer no longer observes live printer state.
- Bed size derived from /archives/{id}/capabilities.build_volume
(extracted from the 3MF's printable_area/printable_height), so H2D,
H-family, and any future printer render on the correct bed without
a hardcoded map.
- loadArchiveById accepts a plate param; fetch intercept rewrites
__bambuddy_archive_<id>[_plate<N>] to /archives/<id>/gcode[?plate=N].
Nav + locale cleanup:
- Sidebar "GCode Viewer" nav entry removed (viewer is archive-scoped
now, not a destination page).
- 32 orphaned gcodeViewer locale keys deleted across all 8 locales.
- platePicker.{title, hint, plateLabel, objectCount, noGcode} keys
added in all 8 locales.
ArchivesPage: the now-unreachable ModelViewerModal render paths + its
showViewer state removed. ModelViewerModal itself stays — File Manager
still uses it for library file previews (plate picker + .3mf 3D model).
pre-commit:
- gcode_viewer/ excluded from trailing-whitespace + end-of-file-fixer
so vendored third-party JS libs don't drift away from upstream.
Incidental sweeps picked up by pre-commit and kept (unrelated but
benign):
- NotificationsPage.tsx: single trailing-whitespace line removed.
- spoolbuddy/scripts/pn5180_diag.py: dead `import gpiod` dropped —
the pn5180 driver module imported at line 27 does its own
`import gpiod` and `gpiod.Chip()` calls, so the diag script's
top-level import was never referenced.
Tests:
- 6 new cases in test_gcode_viewer.py for the backend plate / has_gcode
behaviour (plate=N resolution, zero-padded filenames, missing-plate
404, no-plate fallback, plate=0 rejection, has_gcode true/false).
- 3 new cases in test_printer_manager.py for name/model WS injection.
- PlatePickerModal.test.tsx — 6 frontend cases covering render,
plate-name composition, onSelect payload, backdrop close, and
thumbnail fallback.
The read_tag.py diagnostic script had stale PN5180 NTAG methods: TX CRC
was off (should be on), no Crypto1 clear, no IDLE→TRANSCEIVE state
reset. Multi-batch reads failed because the PN5180 enters an
unrecoverable state after an NTAG READ — requires a full GPIO hardware
reset between 4-page batches. Also rejected SAK 0x04 as unsupported,
and failed hard when reading past the end of smaller tags (MIFARE
Ultralight has 16 pages vs NTAG's 44+). Synced write methods with
daemon.
The read_tag.py diagnostic script had stale PN5180 NTAG methods: TX CRC
was off (should be on), no Crypto1 clear, no IDLE→TRANSCEIVE state
reset. Multi-batch reads failed because the PN5180 enters an
unrecoverable state after an NTAG READ — requires a full GPIO hardware
reset between 4-page batches. Also rejected SAK 0x04 as unsupported.
Synced write methods with daemon.
The read_tag.py diagnostic script had stale PN5180 NTAG methods: TX CRC
was off (should be on), no Crypto1 clear, no IDLE→TRANSCEIVE state
reset, and multi-batch reads failed because the PN5180 can't issue
consecutive NTAG READs without a full RF power cycle. Added extended-
timing reactivation (50ms gaps vs 10ms) between 4-page batches. Also
rejected SAK 0x04 as unsupported. Synced write methods with daemon.
The read_tag.py diagnostic script had stale PN5180 NTAG methods: TX CRC
was off (should be on), no Crypto1 clear, no IDLE→TRANSCEIVE state
reset, and multi-batch reads failed because subsequent READ commands
need a full state machine reset between batches. Also rejected SAK 0x04
as unsupported. Synced register setup and write methods with daemon.
The read_tag.py diagnostic script had stale PN5180 NTAG methods: TX CRC
was off (should be on), no Crypto1 clear, no IDLE→TRANSCEIVE state
reset, and the PN5180 drops the card after each READ batch requiring
reactivation between 4-page reads. Also rejected SAK 0x04 as
unsupported. Synced register setup with daemon and added per-batch
card reactivation.
The read_tag.py diagnostic script had stale PN5180 NTAG methods that
were never synced with the daemon's fixes: TX CRC was off (should be
on), no Crypto1 clear, no IDLE→TRANSCEIVE state reset, and unreliable
ACK/verification logic. Also rejected SAK 0x04 as unsupported. Synced
ntag_read_pages, ntag_write_page, and ntag_write_pages with daemon.
The read_tag.py diagnostic script only accepted SAK 0x00 for NTAG,
showing "Unsupported tag type" for chips reporting SAK 0x04 (MIFARE
Ultralight family). The daemon already handled both values — the
diagnostic was missed. Now accepts both 0x00 and 0x04.
The NAU7802 ADC returns a stale max-scale value (0x7FFFFF) on its
first conversion after power-up, polluting the moving average and
making the initial weight report wildly inaccurate. Flush the first
reading during init().
Also extract both hardware drivers out of diagnostic scripts into
proper daemon modules:
- NAU7802 scale driver: scripts/scale_diag.py -> daemon/nau7802.py
- PN5180 NFC driver: scripts/read_tag.py -> daemon/pn5180.py
The production daemon was importing driver classes from test scripts
since the original SpoolBuddy commit. Diagnostic scripts now import
from the driver modules. Removed the sys.path hack from main.py.
Write NTAG213/215/216 tags for third-party spools via the SpoolBuddy
kiosk UI. New "Write" page with three workflows: existing spool, new
spool creation, and tag replacement. Backend encodes 133-byte OpenTag3D
NDEF payloads (material, color, brand, weight, temp). Daemon writes
page-by-page via PN5180 NTAG WRITE command with read-back verification.
Write commands flow through heartbeat polling with WebSocket status
updates. Includes 39 new tests and translations for all 6 languages.