Make Parse take raw NFC UID and 1K image.

Drop zip/JSON from the root API. dump remains an adapter for the Android app format; inventory and an AMS wand both call Parse(uid, image).
This commit is contained in:
2026-08-23 15:27:11 -05:00
parent b38f7f643b
commit b67b37dc59
7 changed files with 110 additions and 169 deletions
@@ -158,10 +158,10 @@ type Spool struct {
}
func KeysA(uid []byte) ([16][6]byte, error)
func ParseImage(chipUID string, image []byte, fail []int) (Tag, error)
func ParseDump(d dump.Dump) (Tag, error)
func ParseZip(r io.Reader) ([]Tag, error)
func Parse(uid, image []byte) (Tag, error) // raw ISO UID + 1024-byte 1K image
func Merge(tags []Tag) ([]Spool, error)
The website and any AMS wand feed `Parse` with raw NFC. The `dump` package is only an adapter for the Android zip/JSON format; it is not the librarys input surface.
```
### KeysA
@@ -194,9 +194,9 @@ B4AEB23473E3
BAF630CCBFD3
```
### ParseImage
### Parse
MIFARE Classic 1K Bambu map (little-endian). Required sectors: **0, 1, 2, 4, 5, 9**. If any of those are in `fail``ErrIncomplete`.
MIFARE Classic 1K Bambu map (little-endian). Input is raw NFC only: `uid` is the ISO 14443 chip ID; `image` is 1024 bytes. Empty UID → `ErrUID`. Wrong image length → `ErrImageSize`. All-zero tray UID (block 9) → `ErrIncomplete`. No dump `sectors_fail` list — missing data is whatever is in the image (typically zeros).
| Block | Field |
|---|---|
@@ -210,18 +210,12 @@ MIFARE Classic 1K Bambu map (little-endian). Required sectors: **0, 1, 2, 4, 5,
| 12 | Produced ASCII (optional) |
| 14 | length meters `uint16` @4 (optional) |
`chipUID` argument is normalized to lowercase hex. If image block 0 bytes 03 decode to a different UID than `chipUID` (when sector 0 is OK), still trust `chipUID` from the dump metadata and keep parsing (cloned magic tags can rewrite block 0; v1 does not error).
`uid` is encoded to lowercase hex on `Tag.ChipUID`. Block 0 UID is not compared (cloned magic tags can rewrite it).
Not Bambu: if Type and Material are both empty after NUL-trim → `ErrNotBambu`.
Brand is always `BrandBambuLab` on success.
Optional sectors missing → leave those fields zero/empty, not an error.
### ParseDump / ParseZip
`ParseDump` uses `d.Image` and `d.SectorsFail` (`nil` fail list means none failed). `ParseZip` is `dump.ParseZip` then `ParseDump` each entry. A zip with one bad sticker returns error (no partial zip success in v1).
### Merge
Group by `Brand + TrayUID`. Empty `TrayUID``ErrIncomplete` (do not invent ids). Different brands never share a group.
@@ -234,13 +228,14 @@ Output spool order: sorted by TrayUID.
```go
var (
ErrInvalidDump = errors.New("invalid dump")
ErrImageSize = errors.New("image size")
ErrIncomplete = errors.New("incomplete tag")
ErrNotBambu = errors.New("not bambu")
ErrUID = errors.New("uid")
ErrConflict = errors.New("tag conflict")
)
Dump JSON/zip errors live in package `dump` (`dump.ErrInvalidDump`, `dump.ErrImageSize`).
```
Wrap with `%w`. Error strings lowercase, no punctuation.
@@ -254,13 +249,13 @@ Fixtures: copy from dump-app `docs/dumps/2026-08-23-filanfc-last-scans/` (and th
| Test | Expect |
|---|---|
| KeysA(`11223344`) | 16 keys matching Android/Python golden list |
| ParseZip of that testdata zip | 6 tags, all `BrandBambuLab` |
| Parse each testdata `.bin` with UID from the filename | 6 tags, all `BrandBambuLab` |
| Merge those tags | 3 spools; Translucent tray `2fb8e0e972084e74bf737393b28e7b12` has chips `52d60177`, `e27be276`; second Translucent `aa42f322172c48e99c0ece895346ea9f`; Basic `3cb568b7af4f41819412fe60afc5c446` |
| Colors | `#61B0FF80` and `#000000FF` |
| Diameter | `1.75` |
| Types | `PETG Translucent` ×4 tags, `PETG Basic` ×2 |
| 1023-byte image | `ErrImageSize` |
| missing block 9 in fail list | `ErrIncomplete` |
| image with block 9 all zeros | `ErrIncomplete` |
| empty UID KeysA | `ErrUID` |
| Merge two tags same tray different Type | `ErrConflict` |