diff --git a/.ai/standards.md b/.ai/standards.md index a3eca7b..6bdcbdb 100644 --- a/.ai/standards.md +++ b/.ai/standards.md @@ -53,6 +53,57 @@ The agent exposes raw block/page access via API. Creality, Anycubic, Qidi, etc. - **Read:** `GET /readers/0/card` → response has `"dataType":"openprinttag"` with full parsed JSON - **Write:** `POST /readers/0/card` with `"dataType":"openprinttag"` and JSON material fields +**ICode SLIX2 — physical format** + +- Expected CC bytes: `E1 40 27 01` (MLEN=39 blocks × 8 bytes = 312 usable bytes) +- NDEF TLV: Type `0x03`, followed by CBOR payload, terminated by `0xFE` +- AuxSection: ISO 15693 auxiliary memory — contains `consumedWeight` (key 0) and workgroup (key 1) +- Reader required: **ACR1552U only** (ISO 15693 support) + +**CBOR key reference — complete table** + +| Key | Go field (Input) | JSON field (Response) | Description | +|-----|------------------|-----------------------|-------------| +| 0 | InstanceUUID | instanceUuid | UUIDv5(UID + materialUuid) — auto-generated | +| 1 | PackageUUID | packageUuid | Package UUID | +| 2 | MaterialUUID | materialUuid | Material UUID | +| 3 | BrandUUID | brandUuid | Brand UUID | +| 4 | GTIN | gtin | EAN-13 / GTIN product code | +| 5 | BrandSpecificInstID | brandSpecificInstanceId | Brand's spool instance reference | +| 6 | BrandSpecificPkgID | brandSpecificPackageId | Brand's package/product reference | +| 8 | MaterialClass | materialClass | 0=FFF, 1=SLA | +| 9 | MaterialType | materialType | Material type enum | +| 10 | MaterialName | materialName | Material name / color | +| 11 | BrandName | brandName | Brand name | +| 16 | NominalWeight | nominalWeight | Nominal weight (g) | +| 17 | ActualWeight | actualWeight | Actual measured weight (g) | +| 18 | SpoolWeight | spoolWeight | Empty spool weight (g) | +| 19 | PrimaryColor | primaryColor | RGBA hex color | +| 29 | Density | density | Density (g/cm³) | +| 30 | FilamentDiameter | filamentDiameter | Diameter (mm) | +| 34 | MinPrintTemp | minPrintTemp | Min nozzle temp (°C) | +| 35 | MaxPrintTemp | maxPrintTemp | Max nozzle temp (°C) | +| 36 | PreheatTemp | preheatTemp | Preheat temp (°C) | +| 37 | MinBedTemp | minBedTemp | Min bed temp (°C) | +| 38 | MaxBedTemp | maxBedTemp | Max bed temp (°C) | +| 39 | MinChamberTemp | minChamberTemp | Min chamber temp (°C) | +| 41 | ChamberTemp | chamberTemp | Max chamber temp (°C) | +| 42 | ContainerWidth | containerWidth | Spool width (mm) | +| 43 | ContainerOuterDiam | containerOuterDiam | Outer diameter (mm) | +| 44 | ContainerInnerDiam | containerInnerDiam | Inner diameter (mm) | +| 45 | ContainerHoleDiam | containerHoleDiam | Hub hole diameter (mm) | +| 53 | NominalFullLength | nominalFullLength | Nominal full length (mm) | +| 54 | ActualFullLength | actualFullLength | Actual full length (mm) | +| — | ManufacturedDate | manufacturedDate | Unix timestamp | + +**Critical rule — Input AND Response must be updated together** + +When adding a CBOR field, update **both structs** in `openprinttag.go`: +1. `Input` struct + `ToOpenPrintTag()` in `codec.go` → **write** path +2. `Response` struct + `ToResponse()` in `openprinttag.go` → **read** path + +Forgetting step 2: the field is encoded on the tag but missing from the `GET /card` JSON response. + **Write example:** ```bash curl -X POST http://127.0.0.1:32145/v1/readers/0/card \ @@ -67,12 +118,16 @@ curl -X POST http://127.0.0.1:32145/v1/readers/0/card \ "materialType": 0, "primaryColor": "#1A1A1A", "minPrintTemp": 215, - "maxPrintTemp": 230 + "maxPrintTemp": 230, + "minBedTemp": 60, + "maxBedTemp": 60, + "preheatTemp": 170, + "gtin": 8594178550112 } }' ``` -**materialType values:** 0=PLA, 1=ABS, 2=PETG, 3=TPU, 4=ASA, 5=PA (Nylon), 6=PC, 7=Carbon fiber composite, 8=HIPS, 9=PVA, 10=Other +**materialType values:** 0=PLA, 1=PETG, 2=TPU, 3=ABS, 4=ASA, 5=PC, 6=PCTG, 7=PP, 8=PA, 9=PA11, 10=PA12, 11=PA66, 12=CPE, 13=TPE, 14=HIPS, 15=PHA, 16=PET, 17=PEI, 255=Unknown --- diff --git a/CLAUDE.md b/CLAUDE.md index fc12a5e..351e904 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -118,3 +118,42 @@ Full API reference: [`.ai/api.md`](.ai/api.md) | `internal/config/config.go` | Environment variable config | | `.goreleaser.yaml` | Linux release packaging config | | `.github/workflows/build.yml` | Full release CI pipeline | + + +## Fork gienne/nfc-agent — Specific Changes + +This repo is a fork of `SimplyPrint/nfc-agent`, adapted for the **bobines** app (3D filament spool management on Raspberry Pi). Changes are concentrated in `internal/openprinttag/`. + +### Key fork commits + +| Commit | Description | +|--------|-------------| +| `5912c1c` | fix: NDEF read — skip `0xFE` inside CBOR payload (false-positive terminator) | +| `521fa5b` | fix: ICode SLIX2 CC bytes `E1 40 27 01` (MLEN=39) + actualWeight/spoolWeight/preheatTemp/lengths | +| `becaad8` | feat: Input struct — brandSpecificInstId/PkgId + container dimensions (keys 5,6,42-45) | +| `6ea01f7` | feat: Input struct — GTIN uint64 (key 4) + codec.go mapping | +| `0180716` | feat: Response struct — GTIN, preheat/chamber temps, container dims, nominalFullLength | + +### Modified files + +- `internal/openprinttag/openprinttag.go` — `Input`, `Response`, `ToOpenPrintTag()`, `ToResponse()` structs +- `internal/openprinttag/codec.go` — CBOR encode/decode; Input → OpenPrintTag.Main mapping + +### Rule: update Input AND Response together + +When adding a CBOR field: +1. Add to `Input` struct + add mapping in `ToOpenPrintTag()` (codec.go) → **write path** +2. Add to `Response` struct + add mapping in `ToResponse()` (openprinttag.go) → **read path** + +Missing step 2 means the field is written to the tag but absent from `GET /card` JSON. + +### Integration with bobines app + +The PHP app (`/var/www/bobines-next/`) communicates with this service via `modules/filament/nfc-proxy.php` (HTTP proxy → port 32145). JSON fields returned by `GET /v1/readers/0/card` are used directly by PHP. + +When adding fields written to the tag, update in parallel: +- `nfc-proxy.php` (build the JSON payload sent to `POST /card`) +- `nfc-tag-write.php` (its own catRes SQL query + preview table) +- `add-form.php`, `edit-form.php`, `duplicate-form.php` + their handlers (if new MPI column) +- `trash-handler.php` (explicit column list in restore SELECT/INSERT) +- `nfc-erase.php` + `scripts/nfc-read.sh` (display of read data) diff --git a/README.md b/README.md index fa40daa..2bd0a29 100644 --- a/README.md +++ b/README.md @@ -674,21 +674,79 @@ curl -X POST http://127.0.0.1:32145/v1/readers/0/card \ ### OpenPrintTag Fields +**Identification** + | Field | Type | Description | |-------|------|-------------| | `materialName` | string | Material display name (required) | | `brandName` | string | Brand/manufacturer name (required) | | `materialClass` | int | 0 = FFF (filament), 1 = SLA (resin) | -| `materialType` | int | Material type (0=PLA, 1=ABS, 2=PETG, etc.) | -| `nominalWeight` | float | Nominal weight in grams (required) | +| `materialType` | int | Material type — see values below (required) | | `primaryColor` | string | Hex color code (#RRGGBB or #RRGGBBAA) | +| `gtin` | uint64 | EAN-13 / GTIN product code | +| `brandSpecificInstanceId` | string | Brand's own spool instance reference | +| `brandSpecificPackageId` | string | Brand's own package/product reference | + +**Weight & Length** + +| Field | Type | Description | +|-------|------|-------------| +| `nominalWeight` | float | Nominal weight in grams (required) | +| `actualWeight` | float | Actual netto weight measured (g) | +| `spoolWeight` | float | Empty spool/bobbin weight (g) | +| `nominalFullLength` | float | Nominal full length (mm) | +| `actualFullLength` | float | Actual full length measured (mm) | + +**Print Parameters** + +| Field | Type | Description | +|-------|------|-------------| | `filamentDiameter` | float | Diameter in mm (default: 1.75) | | `density` | float | Material density in g/cm³ | -| `minPrintTemp` | int | Minimum print temperature °C | -| `maxPrintTemp` | int | Maximum print temperature °C | +| `minPrintTemp` | int | Minimum nozzle temperature °C | +| `maxPrintTemp` | int | Maximum nozzle temperature °C | +| `minBedTemp` | int | Minimum bed temperature °C | +| `maxBedTemp` | int | Maximum bed temperature °C | +| `preheatTemp` | int | Preheat temperature °C | +| `minChamberTemp` | int | Minimum chamber temperature °C | +| `chamberTemp` | int | Maximum chamber temperature °C | + +**Container Dimensions** + +| Field | Type | Description | +|-------|------|-------------| +| `containerOuterDiam` | float | Spool outer diameter (mm) | +| `containerInnerDiam` | float | Spool inner diameter (mm) | +| `containerHoleDiam` | float | Spool hub hole diameter (mm) | +| `containerWidth` | float | Spool width (mm) | + +**Timestamps** + +| Field | Type | Description | +|-------|------|-------------| | `manufacturedDate` | int | Unix timestamp | | `expirationDate` | int | Unix timestamp | +**Read-only (auto-generated on write, returned on read)** + +| Field | Type | Description | +|-------|------|-------------| +| `instanceUuid` | string | UUIDv5 derived from UID + materialUuid | +| `packageUuid` | string | UUID of the material package | +| `materialUuid` | string | UUID of the material | +| `brandUuid` | string | UUID of the brand | +| `remainingWeight` | float | nominalWeight − consumedWeight (computed) | +| `filamentLength` | float | Remaining length (mm, computed from AuxSection consumedWeight) | + +**AuxSection** (ICode SLIX2 only — written to the tag's auxiliary memory) + +| Key | Field | Description | +|-----|-------|-------------| +| 0 | `consumedWeight` | Weight consumed so far (g) | +| 1 | workgroup | OpenPrintTag workgroup identifier | + +**materialType values:** 0=PLA, 1=PETG, 2=TPU, 3=ABS, 4=ASA, 5=PC, 6=PCTG, 7=PP, 8=PA, 9=PA11, 10=PA12, 11=PA66, 12=CPE, 13=TPE, 14=HIPS, 15=PHA, 16=PET, 17=PEI, 18=PBT, 19=PVB, 20=PVA, 21=PEKK, 22=PEEK, 255=Unknown + See the [OpenPrintTag specification](https://openprinttag.org) for the complete field reference. ## Building from Source diff --git a/internal/core/card.go b/internal/core/card.go index 227bf45..228f892 100644 --- a/internal/core/card.go +++ b/internal/core/card.go @@ -895,6 +895,17 @@ func WriteDataWithURL(readerName string, data []byte, dataType string, url strin if err := writeMifareClassic(card, ndefMessage); err != nil { return fmt.Errorf("failed to write NDEF message: %w", err) } + } else if cardInfo.Protocol == "NFC-V" { + // ISO 15693 (ICode SLI/SLIX/SLIX2): CC at block 0, NDEF TLV at block 1. + // Do NOT pad to card.Size — writing past block 79 causes errors on ICode SLIX2. + // E1=NDEF magic, 40=v1.0 read/write, 27=MLEN 39 (40×8=320 bytes), 01=MBREAD + cc := []byte{0xE1, 0x40, 0x27, 0x01} + if err := writeNTAGPages(card, 0, cc); err != nil { + return fmt.Errorf("failed to write NDEF message: %w", err) + } + if err := writeNTAGPages(card, 1, ndefMessage); err != nil { + return fmt.Errorf("failed to write NDEF message: %w", err) + } } else { // NTAG and other cards use page-based writes. // Zero-fill remaining user pages after the NDEF data so leftover @@ -1581,13 +1592,6 @@ func readNDEFData(card *scard.Card, cardInfo *Card) { allData = append(allData, blockData...) - // Check for NDEF terminator - for _, b := range blockData { - if b == 0xFE { - goto done - } - } - // Check if we have complete NDEF message if len(allData) > 2 && allData[0] == 0x03 { var ndefLength, ndefStart int @@ -1598,8 +1602,19 @@ func readNDEFData(card *scard.Card, cardInfo *Card) { ndefLength = int(allData[1]) ndefStart = 2 } - if ndefStart > 0 && len(allData) >= ndefStart+ndefLength+1 { - break + if ndefStart > 0 { + if len(allData) >= ndefStart+ndefLength+1 { + break + } + // Skip 0xFE check inside NDEF payload — binary payloads (CBOR) may contain 0xFE as data + continue + } + } + + // Check for NDEF terminator (only when NDEF header not yet parsed) + for _, b := range blockData { + if b == 0xFE { + goto done } } } @@ -1624,13 +1639,6 @@ func readNDEFData(card *scard.Card, cardInfo *Card) { allData = append(allData, pageData...) - // Check for NDEF terminator - for _, b := range pageData { - if b == 0xFE { - goto done - } - } - // Check if we have complete NDEF message if len(allData) > 2 && allData[0] == 0x03 { var ndefLength, ndefStart int @@ -1641,8 +1649,19 @@ func readNDEFData(card *scard.Card, cardInfo *Card) { ndefLength = int(allData[1]) ndefStart = 2 } - if ndefStart > 0 && len(allData) >= ndefStart+ndefLength+1 { - break + if ndefStart > 0 { + if len(allData) >= ndefStart+ndefLength+1 { + break + } + // Skip 0xFE check inside NDEF payload — binary payloads (CBOR) may contain 0xFE as data + continue + } + } + + // Check for NDEF terminator (only when NDEF header not yet parsed) + for _, b := range pageData { + if b == 0xFE { + goto done } } } @@ -2223,12 +2242,9 @@ func WriteMultipleRecords(readerName string, records []NDEFRecord) error { if isISO15693 { // ISO 15693 (Type 5) tags: CC at block 0, NDEF at block 1 - // CC format: E1 [version/access] [size/8] [features] - // - 0xE1: Magic number - // - 0x40: Version 1.0 (4), read/write access (0) - // - Size: Available memory / 8 (we'll use 0x40 = 512 bytes, conservative) - // - 0x00: No special features - cc := []byte{0xE1, 0x40, 0x40, 0x00} + // CC format: E1 [version/access] [MLEN] [features] + // E1=NDEF magic, 40=v1.0 read/write, 27=MLEN 39 ((39+1)×8=320 bytes for ICode SLIX2), 01=MBREAD + cc := []byte{0xE1, 0x40, 0x27, 0x01} // Write CC at block 0 if err := writeNTAGPages(card, 0, cc); err != nil { diff --git a/internal/openprinttag/codec.go b/internal/openprinttag/codec.go index fc31add..f7f6cf6 100644 --- a/internal/openprinttag/codec.go +++ b/internal/openprinttag/codec.go @@ -489,8 +489,24 @@ func (i *Input) ToOpenPrintTag() (*OpenPrintTag, error) { opt.Main.NominalNettoFullWeight = i.NominalWeight opt.Main.FilamentDiameter = i.FilamentDiameter opt.Main.Density = i.Density + opt.Main.GTIN = i.GTIN + opt.Main.BrandSpecificInstanceID = i.BrandSpecificInstID + opt.Main.BrandSpecificPackageID = i.BrandSpecificPkgID + opt.Main.ActualNettoFullWeight = i.ActualWeight + opt.Main.EmptyContainerWeight = i.SpoolWeight opt.Main.MinPrintTemp = i.MinPrintTemp opt.Main.MaxPrintTemp = i.MaxPrintTemp + opt.Main.PreheatTemp = i.PreheatTemp + opt.Main.MinBedTemp = i.MinBedTemp + opt.Main.MaxBedTemp = i.MaxBedTemp + opt.Main.MinChamberTemp = i.MinChamberTemp + opt.Main.ChamberTemp = i.ChamberTemp + opt.Main.ContainerWidth = i.ContainerWidth + opt.Main.ContainerOuterDiameter = i.ContainerOuterDiam + opt.Main.ContainerInnerDiameter = i.ContainerInnerDiam + opt.Main.ContainerHoleDiameter = i.ContainerHoleDiam + opt.Main.NominalFullLength = i.NominalFullLength + opt.Main.ActualFullLength = i.ActualFullLength opt.Main.ManufacturedDate = i.ManufacturedDate opt.Main.ExpirationDate = i.ExpirationDate diff --git a/internal/openprinttag/openprinttag.go b/internal/openprinttag/openprinttag.go index ecdcb55..267424c 100644 --- a/internal/openprinttag/openprinttag.go +++ b/internal/openprinttag/openprinttag.go @@ -25,26 +25,27 @@ const ( type MaterialType uint8 const ( - // FFF material types + // FFF material types — valeurs conformes spec OpenPrintTag (material_type_enum.yaml) MaterialTypePLA MaterialType = 0 - MaterialTypeABS MaterialType = 1 - MaterialTypePETG MaterialType = 2 - MaterialTypeASA MaterialType = 3 - MaterialTypePC MaterialType = 4 - MaterialTypeNylon MaterialType = 5 - MaterialTypeTPU MaterialType = 6 - MaterialTypePVA MaterialType = 7 - MaterialTypeHIPS MaterialType = 8 - MaterialTypePP MaterialType = 9 - MaterialTypePEI MaterialType = 10 - MaterialTypePEEK MaterialType = 11 - MaterialTypePA MaterialType = 12 - MaterialTypePACF MaterialType = 13 - MaterialTypePAGF MaterialType = 14 - MaterialTypePLACF MaterialType = 15 - MaterialTypePLAGF MaterialType = 16 - MaterialTypePETGCF MaterialType = 17 - MaterialTypePETGGF MaterialType = 18 + MaterialTypePETG MaterialType = 1 + MaterialTypeTPU MaterialType = 2 + MaterialTypeABS MaterialType = 3 + MaterialTypeASA MaterialType = 4 + MaterialTypePC MaterialType = 5 + MaterialTypePCTG MaterialType = 6 + MaterialTypePP MaterialType = 7 + MaterialTypePA MaterialType = 8 // PA6 + MaterialTypeHIPS MaterialType = 14 + MaterialTypePEI MaterialType = 17 + MaterialTypePVA MaterialType = 20 + MaterialTypePEEK MaterialType = 22 + MaterialTypeNylon MaterialType = 56 // générique, hors spec de base + MaterialTypePACF MaterialType = 100 // composite, valeur propriétaire + MaterialTypePAGF MaterialType = 101 + MaterialTypePLACF MaterialType = 102 + MaterialTypePLAGF MaterialType = 103 + MaterialTypePETGCF MaterialType = 104 + MaterialTypePETGGF MaterialType = 105 MaterialTypeOther MaterialType = 255 ) @@ -173,6 +174,11 @@ type Response struct { MaterialUUID string `json:"materialUuid,omitempty"` BrandUUID string `json:"brandUuid,omitempty"` + // Brand-specific identifiers (MainSection keys 5-7) + BrandSpecificInstanceID string `json:"brandSpecificInstanceId,omitempty"` + BrandSpecificPackageID string `json:"brandSpecificPackageId,omitempty"` + BrandSpecificMaterialID string `json:"brandSpecificMaterialId,omitempty"` + // Weight information NominalWeight float32 `json:"nominalWeight,omitempty"` ConsumedWeight float32 `json:"consumedWeight,omitempty"` @@ -189,10 +195,25 @@ type Response struct { SpoolWeight float32 `json:"spoolWeight,omitempty"` // empty container weight // Temperature settings - MinPrintTemp uint16 `json:"minPrintTemp,omitempty"` - MaxPrintTemp uint16 `json:"maxPrintTemp,omitempty"` - MinBedTemp uint16 `json:"minBedTemp,omitempty"` - MaxBedTemp uint16 `json:"maxBedTemp,omitempty"` + MinPrintTemp uint16 `json:"minPrintTemp,omitempty"` + MaxPrintTemp uint16 `json:"maxPrintTemp,omitempty"` + MinBedTemp uint16 `json:"minBedTemp,omitempty"` + MaxBedTemp uint16 `json:"maxBedTemp,omitempty"` + PreheatTemp uint16 `json:"preheatTemp,omitempty"` // key 36 + MinChamberTemp uint16 `json:"minChamberTemp,omitempty"` // key 39 + ChamberTemp uint16 `json:"chamberTemp,omitempty"` // key 41 + + // GTIN + GTIN uint64 `json:"gtin,omitempty"` // key 4 + + // Length + NominalFullLength uint32 `json:"nominalFullLength,omitempty"` // key 53, mm + + // Container dimensions (mm) + ContainerWidth uint16 `json:"containerWidth,omitempty"` // key 42 + ContainerOuterDiam uint16 `json:"containerOuterDiam,omitempty"` // key 43 + ContainerInnerDiam uint16 `json:"containerInnerDiam,omitempty"` // key 44 + ContainerHoleDiam uint16 `json:"containerHoleDiam,omitempty"` // key 45 // Dates ManufacturedDate uint32 `json:"manufacturedDate,omitempty"` @@ -212,19 +233,35 @@ type Input struct { NominalWeight float32 `json:"nominalWeight"` // Optional fields - InstanceUUID string `json:"instanceUuid,omitempty"` - PackageUUID string `json:"packageUuid,omitempty"` - MaterialUUID string `json:"materialUuid,omitempty"` - BrandUUID string `json:"brandUuid,omitempty"` + InstanceUUID string `json:"instanceUuid,omitempty"` + PackageUUID string `json:"packageUuid,omitempty"` + MaterialUUID string `json:"materialUuid,omitempty"` + BrandUUID string `json:"brandUuid,omitempty"` + GTIN uint64 `json:"gtin,omitempty"` // key 4 + BrandSpecificInstID string `json:"brandSpecificInstId,omitempty"` // key 5: brand_specific_id de l'instance + BrandSpecificPkgID string `json:"brandSpecificPkgId,omitempty"` // key 6: brand_specific_id du package catalogue FilamentDiameter float32 `json:"filamentDiameter,omitempty"` PrimaryColor string `json:"primaryColor,omitempty"` // hex #RRGGBB or #RRGGBBAA Density float32 `json:"density,omitempty"` MinPrintTemp uint16 `json:"minPrintTemp,omitempty"` MaxPrintTemp uint16 `json:"maxPrintTemp,omitempty"` - ConsumedWeight float32 `json:"consumedWeight,omitempty"` - Workgroup string `json:"workgroup,omitempty"` - ManufacturedDate uint32 `json:"manufacturedDate,omitempty"` - ExpirationDate uint32 `json:"expirationDate,omitempty"` + MinBedTemp uint16 `json:"minBedTemp,omitempty"` + MaxBedTemp uint16 `json:"maxBedTemp,omitempty"` + ConsumedWeight float32 `json:"consumedWeight,omitempty"` + ActualWeight float32 `json:"actualWeight,omitempty"` // key 17: actual netto full weight + SpoolWeight float32 `json:"spoolWeight,omitempty"` // key 18: empty container weight + PreheatTemp uint16 `json:"preheatTemp,omitempty"` // key 36 + MinChamberTemp uint16 `json:"minChamberTemp,omitempty"` // key 39 + ChamberTemp uint16 `json:"chamberTemp,omitempty"` // key 41 + ContainerWidth uint16 `json:"containerWidth,omitempty"` // key 42: mm + ContainerOuterDiam uint16 `json:"containerOuterDiam,omitempty"` // key 43: mm + ContainerInnerDiam uint16 `json:"containerInnerDiam,omitempty"` // key 44: mm + ContainerHoleDiam uint16 `json:"containerHoleDiam,omitempty"` // key 45: mm + NominalFullLength uint32 `json:"nominalFullLength,omitempty"` // key 53: mm + ActualFullLength uint32 `json:"actualFullLength,omitempty"` // key 54: mm + Workgroup string `json:"workgroup,omitempty"` + ManufacturedDate uint32 `json:"manufacturedDate,omitempty"` + ExpirationDate uint32 `json:"expirationDate,omitempty"` } // ToResponse converts internal OpenPrintTag to API response @@ -241,13 +278,22 @@ func (o *OpenPrintTag) ToResponse() *Response { FilamentDiameter: o.Main.FilamentDiameter, FilamentLength: o.Main.ActualFullLength, Density: o.Main.Density, - MinPrintTemp: o.Main.MinPrintTemp, - MaxPrintTemp: o.Main.MaxPrintTemp, - MinBedTemp: o.Main.MinBedTemp, - MaxBedTemp: o.Main.MaxBedTemp, - ManufacturedDate: o.Main.ManufacturedDate, - ExpirationDate: o.Main.ExpirationDate, - Workgroup: o.Aux.Workgroup, + MinPrintTemp: o.Main.MinPrintTemp, + MaxPrintTemp: o.Main.MaxPrintTemp, + MinBedTemp: o.Main.MinBedTemp, + MaxBedTemp: o.Main.MaxBedTemp, + PreheatTemp: o.Main.PreheatTemp, + MinChamberTemp: o.Main.MinChamberTemp, + ChamberTemp: o.Main.ChamberTemp, + GTIN: o.Main.GTIN, + NominalFullLength: o.Main.NominalFullLength, + ContainerWidth: o.Main.ContainerWidth, + ContainerOuterDiam: o.Main.ContainerOuterDiameter, + ContainerInnerDiam: o.Main.ContainerInnerDiameter, + ContainerHoleDiam: o.Main.ContainerHoleDiameter, + ManufacturedDate: o.Main.ManufacturedDate, + ExpirationDate: o.Main.ExpirationDate, + Workgroup: o.Aux.Workgroup, } // Calculate remaining weight @@ -272,6 +318,11 @@ func (o *OpenPrintTag) ToResponse() *Response { resp.BrandUUID = formatUUID(o.Main.BrandUUID) } + // Brand-specific identifiers + resp.BrandSpecificInstanceID = o.Main.BrandSpecificInstanceID + resp.BrandSpecificPackageID = o.Main.BrandSpecificPackageID + resp.BrandSpecificMaterialID = o.Main.BrandSpecificMaterialID + // Convert color to hex string if len(o.Main.PrimaryColor) >= 3 { resp.PrimaryColor = colorToHex(o.Main.PrimaryColor) @@ -296,18 +347,19 @@ func materialClassToString(mc MaterialClass) string { func materialTypeToString(mt MaterialType) string { names := map[MaterialType]string{ MaterialTypePLA: "PLA", - MaterialTypeABS: "ABS", MaterialTypePETG: "PETG", + MaterialTypeTPU: "TPU", + MaterialTypeABS: "ABS", MaterialTypeASA: "ASA", MaterialTypePC: "PC", - MaterialTypeNylon: "Nylon", - MaterialTypeTPU: "TPU", - MaterialTypePVA: "PVA", - MaterialTypeHIPS: "HIPS", + MaterialTypePCTG: "PCTG", MaterialTypePP: "PP", + MaterialTypePA: "PA", + MaterialTypeHIPS: "HIPS", MaterialTypePEI: "PEI", + MaterialTypePVA: "PVA", MaterialTypePEEK: "PEEK", - MaterialTypePA: "PA", + MaterialTypeNylon: "Nylon", MaterialTypePACF: "PA-CF", MaterialTypePAGF: "PA-GF", MaterialTypePLACF: "PLA-CF",