bambuddy/spoolbuddy
maziggy 5bbfeefa65 fix(backup): diagnose an unwritable backup path instead of quoting errno 30 (#2544)
Nightly backups to a mounted NAS share ran from May and then stopped, failing
with [Errno 30] Read-only file system. The reporter checked folder permissions
-- correctly: the mount is gid=backup,dir_mode=0775, the service user is in that
group, and his own shell writes to the share fine.

Errno 30 is EROFS. A permission problem is errno 13. EROFS means the filesystem
refused the write, and it refused because we told it to: our systemd unit ships
ProtectSystem=strict, which mounts everything read-only inside the service's
mount namespace and carves back out only ReadWritePaths=<install> <data> <logs>.
A NAS share is not one of those three. Reads are unaffected -- which is why the
UI happily listed his existing backups from the share while being unable to
write a new one -- and his shell is outside the namespace entirely, so every
check he could think to run said the directory was fine.

Both installers write the unit file wholesale, so a ReadWritePaths line added by
hand disappeared on the next install, taking the backups with it. They now back
the old unit up (.bak-<timestamp>) and carry the operator's extra writable paths
forward, reporting which ones they kept. The unit template documents the
carve-out.

The output directory is probed with a real write when it is saved and when the
backup card loads, so an unwritable path is caught there rather than at 03:00
for a week. On failure the card names the cause and hands over the fix with the
operator's path already in it (systemctl edit bambuddy -> ReadWritePaths=...),
and a failed run reports the same diagnosis rather than the raw OSError. EROFS
outside systemd, permission-denied, out-of-space, not-a-directory and missing are
told apart, in all 11 locales.

Docker: a backup path that is not bind-mounted is writable -- the write lands in
the container's ephemeral layer and is lost on the next compose up. The probe
compares the directory's device against the container root and warns, with the
compose snippet that mounts it properly.
2026-07-12 08:44:53 +02:00
..
daemon fix(spoolbuddy): tolerate SPI_NO_CS rejection on Pi 5 (#1424) 2026-05-19 12:33:33 +02:00
install fix(backup): diagnose an unwritable backup path instead of quoting errno 30 (#2544) 2026-07-12 08:44:53 +02:00
scripts fix(spoolbuddy): tolerate SPI_NO_CS rejection on Pi 5 (#1424) 2026-05-19 12:33:33 +02:00
tests fix(spoolbuddy): apply screen-blank timeout changes live without kiosk restart 2026-05-05 13:50:08 +02:00
README.md fix(spoolbuddy): tolerate SPI_NO_CS rejection on Pi 5 (#1424) 2026-05-19 12:33:33 +02:00

SpoolBuddy Hardware Setup

PN5180 NFC Reader (SPI)

Wiring

PN5180 Pin Raspberry Pi Pin GPIO Wire Color
3V3 Pin 1 Red
5V Pin 2 Red
GND Pin 20 Black
SCK Pin 23 GPIO11 Yellow
MISO Pin 21 GPIO9 Blue
MOSI Pin 19 GPIO10 Green
NSS (CS) Pin 16 GPIO23 Orange
BUSY Pin 22 GPIO25 White
RST Pin 18 GPIO24 Brown

Power: The PN5180 board has two power pins. 3V3 powers the IC itself, 5V powers the antenna booster and extends read range. Both should be connected. Do NOT connect 5V to the 3V3 pin — it will destroy the reader.

NSS: We use GPIO23 for manual chip-select instead of the default SPI CE0 (GPIO8) because the kernel SPI driver's automatic CS timing does not meet the PN5180's requirements (5µs setup, 100µs hold). The reader's NSS line is wired to GPIO23 only, so whether the kernel auto-toggles CE0 is electrically invisible to the PN5180. Pi 4 and Pi 5 are both supported — the code asks the driver to disable CE0 toggling but tolerates Pi 5's RP1 driver rejecting that request (#1424).

Setup Steps

1. Enable SPI and I2C

After a fresh Raspberry Pi OS install, SPI and I2C are disabled by default.

sudo raspi-config
# Navigate to: Interface Options -> SPI -> Enable
# Navigate to: Interface Options -> I2C -> Enable
sudo reboot

Verify after reboot:

ls /dev/spidev0.*
# Should show: /dev/spidev0.0  /dev/spidev0.1

ls /dev/i2c-*
# Should include: /dev/i2c-1

2. Configure /boot/firmware/config.txt

Add the following lines under the [all] section:

# SpoolBuddy: I2C bus 1 for NAU7802 scale (GPIO2/GPIO3)
dtparam=i2c_arm=on

# SpoolBuddy: Disable SPI auto CS (manual CS on GPIO23 for PN5180)
dtoverlay=spi0-0cs
  • i2c_arm=on enables I2C bus 1 (GPIO2/GPIO3). The NAU7802 is wired to bus 1. manual CS on GPIO23 because the driver's CS timing doesn't meet the PN5180's

Then reboot:

sudo reboot

Verify after reboot:

ls /dev/i2c-1
# Should exist

sudo i2cdetect -y 1
# Should show 0x2A (NAU7802)

3. Install system packages

sudo apt install python3-spidev python3-libgpiod gpiod libgpiod3 i2c-tools
  • python3-spidev / libgpiod3 — system libraries for SPI and GPIO access
  • gpiod — command-line GPIO tools (useful for debugging)
pip install spidev gpiod smbus2
  • spidev — Python SPI bindings (PN5180 NFC reader)
  • gpiod — Python GPIO bindings via libgpiod (works on both RPi 4 and RPi 5)

Wago connectors or breadboard jumpers are unreliable for SPI — the PN5180 is very sensitive to signal integrity issues (loose connections cause RF field flickering, phantom errors, and intermittent communication failures). Solder all wires directly for reliable operation.

6. Verify hardware communication

Run the diagnostic script to confirm the PN5180 is responding:

sudo python3 spoolbuddy/pn5180_diag.py

Expected output includes product version (e.g. v4.0), firmware version, register dump, and "Diagnostics complete" at the end.

7. Test tag reading

sudo python3 spoolbuddy/read_tag.py

Place a tag on the reader. Supported tag types:

Tag Type SAK Use Case
MIFARE Classic 1K 0x08 Bambu Lab filament tags
MIFARE Classic 4K 0x18 Bambu Lab filament tags
NTAG (213/215/216) 0x00 / 0x04 SpoolEase / OpenPrintTag

Troubleshooting

Symptom Cause Fix
All zeros from SPI reads SPI not enabled Run raspi-config and enable SPI, then reboot
GENERAL_ERROR on SEND_DATA Automatic CS timing too fast Use manual CS on GPIO23 with spi0-0cs overlay
BUSY timeout Wiring issue or RST not connected Check RST and BUSY pin connections
RF field flickering on/off Loose power wires Solder all connections
No tag found but tag is present Wrong protocol or missing setTransceiveMode() Ensure ISO 14443A config (0x00, 0x80) and setTransceiveMode() before every SEND_DATA
Auth failed for block N Wrong key derivation Verify HKDF uses context "RFID-A\0" (7 bytes including null terminator)
EBUSY when requesting GPIO8 Kernel SPI driver owns CE0 Use GPIO23 for NSS instead

Technical Notes

  • SPI speed: 500 kHz (higher speeds cause communication errors)
  • SPI mode: 0 (CPOL=0, CPHA=0)

Wiring

NAU7802 Pin Raspberry Pi Pin GPIO Wire Color
VCC Pin 1 Red
SDA Pin 3 GPIO 2 Yellow
SCL Pin 5 GPIO 3 White
GND Pin 30 Black

I2C Bus: Uses I2C bus 1 (GPIO2/GPIO3), enabled via dtparam=i2c_arm=on in config.txt.

Verify

sudo i2cdetect -y 1
# Should show 0x2A

sudo python3 spoolbuddy/scale_diag.py

The diagnostic reads 10 samples at 10 SPS and shows raw ADC values, average, and spread. Typical idle readings are around ~500k with a spread under 20k.