b38f7f643b
Parse filanfc-dump/v1 zip/JSON, Bambu 1K images, and merge stickers by tray UID. dump and mifare packages stay importable for a later common library.
157 lines
3.7 KiB
Go
157 lines
3.7 KiB
Go
package bambunfc
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/binary"
|
||
"encoding/hex"
|
||
"fmt"
|
||
"io"
|
||
"math"
|
||
"strings"
|
||
|
||
"git.s1d3sw1ped.com/s1d3sw1ped/bambu-nfc/dump"
|
||
"git.s1d3sw1ped.com/s1d3sw1ped/bambu-nfc/mifare"
|
||
)
|
||
|
||
const BrandBambuLab = "Bambu Lab"
|
||
|
||
// requiredSectors must not appear in SectorsFail (blocks 0–2, 4–6, 8–9).
|
||
var requiredSectors = []int{0, 1, 2}
|
||
|
||
// Tag is one Bambu sticker after parsing the 1K image.
|
||
type Tag struct {
|
||
Brand string
|
||
ChipUID string
|
||
TrayUID string
|
||
Type string
|
||
Material string
|
||
MaterialID string
|
||
VariantID string
|
||
Color Color
|
||
WeightG int
|
||
DiameterMM float32
|
||
LengthM int
|
||
MinHotendC int
|
||
MaxHotendC int
|
||
BedC int
|
||
DryC int
|
||
DryHours int
|
||
Produced string
|
||
SpoolWidthRaw uint16
|
||
}
|
||
|
||
func failedSet(fail []int) map[int]bool {
|
||
s := make(map[int]bool, len(fail))
|
||
for _, n := range fail {
|
||
s[n] = true
|
||
}
|
||
return s
|
||
}
|
||
|
||
func block(image []byte, n int) []byte {
|
||
off := n * mifare.BlockSize
|
||
return image[off : off+mifare.BlockSize]
|
||
}
|
||
|
||
func cstr(b []byte) string {
|
||
if i := bytes.IndexByte(b, 0); i >= 0 {
|
||
b = b[:i]
|
||
}
|
||
return string(b)
|
||
}
|
||
|
||
func normalizeUID(uid string) (string, error) {
|
||
uid = strings.ToLower(strings.TrimSpace(uid))
|
||
if uid == "" || len(uid)%2 != 0 {
|
||
return "", fmt.Errorf("%w: %q", ErrUID, uid)
|
||
}
|
||
if _, err := hex.DecodeString(uid); err != nil {
|
||
return "", fmt.Errorf("%w: %v", ErrUID, err)
|
||
}
|
||
return uid, nil
|
||
}
|
||
|
||
// ParseImage parses a Bambu MIFARE Classic 1K image.
|
||
// fail is the dump's sectors_fail list (sector indexes 0–15).
|
||
func ParseImage(chipUID string, image []byte, fail []int) (Tag, error) {
|
||
var zero Tag
|
||
uid, err := normalizeUID(chipUID)
|
||
if err != nil {
|
||
return zero, err
|
||
}
|
||
if len(image) != mifare.Size1K {
|
||
return zero, fmt.Errorf("%w: want %d got %d", ErrImageSize, mifare.Size1K, len(image))
|
||
}
|
||
failed := failedSet(fail)
|
||
for _, s := range requiredSectors {
|
||
if failed[s] {
|
||
return zero, fmt.Errorf("%w: sector %d", ErrIncomplete, s)
|
||
}
|
||
}
|
||
|
||
b1 := block(image, 1)
|
||
b2 := block(image, 2)
|
||
b4 := block(image, 4)
|
||
b5 := block(image, 5)
|
||
b6 := block(image, 6)
|
||
b9 := block(image, 9)
|
||
|
||
material := cstr(b2)
|
||
typ := cstr(b4)
|
||
if material == "" && typ == "" {
|
||
return zero, ErrNotBambu
|
||
}
|
||
|
||
tag := Tag{
|
||
Brand: BrandBambuLab,
|
||
ChipUID: uid,
|
||
VariantID: cstr(b1[0:8]),
|
||
MaterialID: cstr(b1[8:16]),
|
||
Material: material,
|
||
Type: typ,
|
||
Color: Color{
|
||
R: b5[0], G: b5[1], B: b5[2], A: b5[3],
|
||
},
|
||
WeightG: int(binary.LittleEndian.Uint16(b5[4:6])),
|
||
DiameterMM: math.Float32frombits(binary.LittleEndian.Uint32(b5[8:12])),
|
||
TrayUID: hex.EncodeToString(b9),
|
||
DryC: int(binary.LittleEndian.Uint16(b6[0:2])),
|
||
DryHours: int(binary.LittleEndian.Uint16(b6[2:4])),
|
||
BedC: int(binary.LittleEndian.Uint16(b6[6:8])),
|
||
MaxHotendC: int(binary.LittleEndian.Uint16(b6[8:10])),
|
||
MinHotendC: int(binary.LittleEndian.Uint16(b6[10:12])),
|
||
}
|
||
if !failed[2] {
|
||
b10 := block(image, 10)
|
||
tag.SpoolWidthRaw = binary.LittleEndian.Uint16(b10[4:6])
|
||
}
|
||
if !failed[3] {
|
||
tag.Produced = cstr(block(image, 12))
|
||
b14 := block(image, 14)
|
||
tag.LengthM = int(binary.LittleEndian.Uint16(b14[4:6]))
|
||
}
|
||
return tag, nil
|
||
}
|
||
|
||
// ParseDump parses one dump JSON record as a Bambu tag.
|
||
func ParseDump(d dump.Dump) (Tag, error) {
|
||
return ParseImage(d.ChipUID, d.Image, d.SectorsFail)
|
||
}
|
||
|
||
// ParseZip reads a filanfc-dump zip and parses every sticker as Bambu.
|
||
func ParseZip(r io.Reader) ([]Tag, error) {
|
||
dumps, err := dump.ParseZip(r)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("%w: %v", ErrInvalidDump, err)
|
||
}
|
||
tags := make([]Tag, 0, len(dumps))
|
||
for _, d := range dumps {
|
||
tag, err := ParseDump(d)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("chip %s: %w", d.ChipUID, err)
|
||
}
|
||
tags = append(tags, tag)
|
||
}
|
||
return tags, nil
|
||
}
|