Skip to content

Commit e0629a3

Browse files
committed
feat: add fromCloudDoc, fromRawDict, patchFromRawDict — cloud→chip write pipeline
1 parent ec18e3a commit e0629a3

5 files changed

Lines changed: 229 additions & 3 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,21 @@
33
All notable changes to this project will be documented in this file.
44
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
55

6+
## [1.0.2] — 2026-05-21
7+
8+
### Added
9+
- `TigerTag.fromCloudDoc(doc, db?)` — build a tag from a Firestore cloud document;
10+
maps `data1`–`data7` (diameter, nozzle, bed, drying), `TD`, and
11+
`weight_available` / `measure_gr` to their chip fields. Primary entry point
12+
for the cloud → chip write pipeline.
13+
- `TigerTag.fromRawDict(raw, db?)` — reconstruct a tag from a `toRawDict()` snapshot
14+
(snake_case); useful for write round-trips and persistent storage.
15+
- `tag.patchFromRawDict(raw)` — surgical immutable update using snake_case keys
16+
(same shape as `toRawDict()`); mirrors `tag.patch()` for callers that store or
17+
receive snake_case dicts.
18+
- `TigerTag._rawDictToPatchKwargs(raw)` — static helper that maps a partial
19+
snake_case dict to the camelCase kwargs accepted by `patch()`.
20+
621
## [1.0.1] — 2026-05-20
722

823
### Added

‎README.md‎

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ for reading — all data lives on the chip.
198198
| `TigerTag.fromPages(uid, payload)` | 7-byte UID + 80 or 144 bytes | **NFC SDK integration (recommended)** |
199199
| `TigerTag.fromDump(data)` | 80 / 144 / 180 bytes | Binary dumps, ACR122U raw read |
200200
| `TigerTag.fromFile(path)` | path to `.bin` file | Testing, offline batch processing |
201+
| `TigerTag.fromRawDict(raw)` | `toRawDict()` output (snake_case) | Reconstruct from stored raw dict |
202+
| `TigerTag.fromCloudDoc(doc)` | Firestore cloud document | **Write pipeline: cloud → chip** |
201203

202204
**`fromPages`** is the primary constructor for production use. NFC SDKs always provide the
203205
UID as a separate property — pass it directly for full signature verification.
@@ -243,7 +245,10 @@ tag.verify(db) // → SignatureResult
243245
TigerTag.create(fields) // → TigerTag build from scratch
244246
TigerTag.asInit(uid) // → TigerTag blank Init tag
245247
TigerTag.erase() // → Buffer(80) zero bytes — write to chip to wipe
246-
tag.patch(fields) // → TigerTag surgical field update
248+
TigerTag.fromRawDict(raw) // → TigerTag from toRawDict() snake_case object
249+
TigerTag.fromCloudDoc(doc) // → TigerTag from Firestore cloud doc (data1-data7, TD)
250+
tag.patch(fields) // → TigerTag surgical camelCase field update
251+
tag.patchFromRawDict(raw) // → TigerTag surgical snake_case field update
247252

248253
// Cloud (TigerTag+ only — uses built-in fetch, Node.js 18+)
249254
tag.rawApi(timeout) // → Promise<object|null> fetch live product data
@@ -305,6 +310,40 @@ console.log(`${applied.length} field(s) updated from cloud`);
305310
**Protected fields** — `patch()` throws if you try to modify:
306311
`idTigertag`, `idProduct`, `uid`, `signatureR`, `signatureS`.
307312

313+
### Cloud document → chip write pipeline
314+
315+
`fromCloudDoc()` maps the Firestore document format to chip fields.
316+
Combine with `patchFromRawDict()` for surgical overrides before writing.
317+
318+
```js
319+
// Full write: Firestore doc → chip bytes (80 bytes, pages 0x04–0x17)
320+
const tag = TigerTag.fromCloudDoc(firestoreDoc);
321+
const bytes = tag.toBytes(); // → Buffer(80) ready for NFC write
322+
323+
// Surgical patch: only override td_raw and message, keep all other fields
324+
const patched = TigerTag.fromCloudDoc(firestoreDoc)
325+
.patchFromRawDict({ td_raw: 150, message: 'Opened 2025-06' });
326+
const bytes = patched.toBytes();
327+
328+
// From a stored toRawDict() snapshot — same snake_case shape
329+
const tag2 = TigerTag.fromRawDict(storedRawDict);
330+
const patched2 = tag2.patchFromRawDict({ measure_available: 650 });
331+
```
332+
333+
**Firestore field mapping** used by `fromCloudDoc()`:
334+
335+
| Firestore field | Chip field |
336+
|-----------------|------------|
337+
| `data1` | `idDiameter` |
338+
| `data2` | `nozzleTempMin` |
339+
| `data3` | `nozzleTempMax` |
340+
| `data4` | `dryTemp` |
341+
| `data5` | `dryTime` |
342+
| `data6` | `bedTempMin` |
343+
| `data7` | `bedTempMax` |
344+
| `TD` | `tdRaw` |
345+
| `weight_available` / `measure_gr` | `measureAvailable` |
346+
308347
### ApiDiff
309348

310349
`ApiDiff` is a plain object `{ field, chipValue, apiValue }`:

‎llms.txt‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,13 +181,26 @@ TigerTag.asInit(uid?)
181181
TigerTag.erase()
182182
Returns Buffer(80) of zeros — wipes a chip back to blank NDEF.
183183

184+
TigerTag.fromRawDict(raw, db?)
185+
raw: toRawDict()-style snake_case object (partial or full).
186+
Reconstructs a TigerTag from a stored raw dict — e.g. for a write round-trip.
187+
Missing keys fall back to safe zero/default values.
188+
189+
TigerTag.fromCloudDoc(doc, db?)
190+
doc: Firestore cloud document object.
191+
Maps cloud fields to chip fields: data1=idDiameter, data2=nozzleTempMin,
192+
data3=nozzleTempMax, data4=dryTemp, data5=dryTime, data6=bedTempMin,
193+
data7=bedTempMax, TD=tdRaw, weight_available/measure_gr=measureAvailable.
194+
Primary entry point for the cloud→chip write pipeline.
195+
184196

185197
## INSTANCE METHODS
186198

187199
tag.toBytes(includeSignature=false) → Buffer(80 or 144)
188200
tag.toDict(db?) → resolved object (IDs → labels + metadata)
189201
tag.toRawDict() → raw protocol fields
190-
tag.patch({ ...fields }) → new TigerTag (immutable update)
202+
tag.patch({ ...fields }) → new TigerTag (immutable update, camelCase keys)
203+
tag.patchFromRawDict(raw) → new TigerTag (immutable update, snake_case keys from toRawDict())
191204
tag.validate() → string[] warnings
192205
tag.verify(db?) → SignatureResult
193206
tag.pretty(db?, sigResult?) → human-readable string

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "tigertag",
3-
"version": "1.0.1",
3+
"version": "1.0.2",
44
"description": "JavaScript SDK for TigerTag RFID material identification",
55
"main": "src/index.js",
66
"bin": {

‎src/tag.js‎

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,165 @@ class TigerTag {
946946
};
947947
}
948948

949+
// ── Write helpers ────────────────────────────────────────────────────────
950+
951+
/**
952+
* Map a snake_case raw-dict partial object to the camelCase kwargs accepted
953+
* by patch(). Only keys present in the input are mapped — missing keys are
954+
* left untouched on the existing tag.
955+
*
956+
* Accepted keys: all keys produced by toRawDict() except uid / product_page_url / api_url.
957+
*
958+
* @param {object} raw - Partial or full toRawDict()-style object.
959+
* @returns {object} - camelCase kwargs ready for patch().
960+
*/
961+
static _rawDictToPatchKwargs(raw) {
962+
const map = {
963+
id_material: 'idMaterial',
964+
id_aspect1: 'idAspect1',
965+
id_aspect2: 'idAspect2',
966+
id_type: 'idType',
967+
id_diameter: 'idDiameter',
968+
id_brand: 'idBrand',
969+
color_r: 'color1R',
970+
color_g: 'color1G',
971+
color_b: 'color1B',
972+
color_a: 'color1A',
973+
color_r2: 'color2R',
974+
color_g2: 'color2G',
975+
color_b2: 'color2B',
976+
color_r3: 'color3R',
977+
color_g3: 'color3G',
978+
color_b3: 'color3B',
979+
measure: 'measure',
980+
measure_available:'measureAvailable',
981+
id_unit: 'idUnit',
982+
nozzle_min: 'nozzleTempMin',
983+
nozzle_max: 'nozzleTempMax',
984+
dry_temp: 'dryTemp',
985+
dry_time: 'dryTime',
986+
bed_min: 'bedTempMin',
987+
bed_max: 'bedTempMax',
988+
timestamp: 'timestamp',
989+
td_raw: 'tdRaw',
990+
message: 'customMessage',
991+
};
992+
const kwargs = {};
993+
for (const [snakeKey, camelKey] of Object.entries(map)) {
994+
if (Object.prototype.hasOwnProperty.call(raw, snakeKey)) {
995+
kwargs[camelKey] = raw[snakeKey];
996+
}
997+
}
998+
return kwargs;
999+
}
1000+
1001+
/**
1002+
* Build a new TigerTag from a toRawDict()-style object (snake_case).
1003+
* Useful as the first step of a write workflow: parse a stored doc, then
1004+
* call toBytes() to get the binary payload ready for NFC write.
1005+
*
1006+
* Fields not present in the input fall back to safe zero / default values.
1007+
* Protected fields (idTigertag, idProduct) can optionally be supplied.
1008+
*
1009+
* @param {object} raw - toRawDict()-style object.
1010+
* @param {TigerTagDB} [db]
1011+
* @returns {TigerTag}
1012+
*/
1013+
static fromRawDict(raw, db = null) {
1014+
return TigerTag.create({
1015+
productId: raw.id_product ?? MAKER_PRODUCT_ID,
1016+
idMaterial: raw.id_material ?? 0,
1017+
idAspect1: raw.id_aspect1 ?? 0,
1018+
idAspect2: raw.id_aspect2 ?? 0,
1019+
idType: raw.id_type ?? 0,
1020+
idDiameter: raw.id_diameter ?? 0,
1021+
idBrand: raw.id_brand ?? 0,
1022+
color1R: raw.color_r ?? 0,
1023+
color1G: raw.color_g ?? 0,
1024+
color1B: raw.color_b ?? 0,
1025+
color1A: raw.color_a ?? 255,
1026+
color2R: raw.color_r2 ?? 0,
1027+
color2G: raw.color_g2 ?? 0,
1028+
color2B: raw.color_b2 ?? 0,
1029+
color3R: raw.color_r3 ?? 0,
1030+
color3G: raw.color_g3 ?? 0,
1031+
color3B: raw.color_b3 ?? 0,
1032+
measure: raw.measure ?? 0,
1033+
measureAvailable: raw.measure_available ?? raw.measure ?? 0,
1034+
idUnit: raw.id_unit ?? 0,
1035+
nozzleTempMin: raw.nozzle_min ?? 0,
1036+
nozzleTempMax: raw.nozzle_max ?? 0,
1037+
dryTemp: raw.dry_temp ?? 0,
1038+
dryTime: raw.dry_time ?? 0,
1039+
bedTempMin: raw.bed_min ?? 0,
1040+
bedTempMax: raw.bed_max ?? 0,
1041+
timestamp: raw.timestamp ?? null,
1042+
customMessage: raw.message ?? '',
1043+
tdRaw: raw.td_raw ?? 0,
1044+
db,
1045+
});
1046+
}
1047+
1048+
/**
1049+
* Build a new TigerTag from a Firestore cloud document.
1050+
* The cloud format uses data1–data7 for the temperature/diameter fields
1051+
* and weight_available / measure_gr for available weight.
1052+
*
1053+
* @param {object} doc - Firestore document data object.
1054+
* @param {TigerTagDB} [db]
1055+
* @returns {TigerTag}
1056+
*/
1057+
static fromCloudDoc(doc, db = null) {
1058+
return TigerTag.create({
1059+
productId: doc.id_product ?? MAKER_PRODUCT_ID,
1060+
idMaterial: doc.id_material ?? 0,
1061+
idAspect1: doc.id_aspect1 ?? 0,
1062+
idAspect2: doc.id_aspect2 ?? 0,
1063+
idType: doc.id_type ?? 0,
1064+
idDiameter: doc.data1 ?? 0, // data1 = id_diameter
1065+
idBrand: doc.id_brand ?? 0,
1066+
color1R: doc.color_r ?? 0,
1067+
color1G: doc.color_g ?? 0,
1068+
color1B: doc.color_b ?? 0,
1069+
color1A: doc.color_a ?? 255,
1070+
color2R: doc.color_r2 ?? 0,
1071+
color2G: doc.color_g2 ?? 0,
1072+
color2B: doc.color_b2 ?? 0,
1073+
color3R: doc.color_r3 ?? 0,
1074+
color3G: doc.color_g3 ?? 0,
1075+
color3B: doc.color_b3 ?? 0,
1076+
measure: doc.measure ?? 0,
1077+
measureAvailable: doc.weight_available ?? doc.measure_gr ?? doc.measure ?? 0,
1078+
idUnit: doc.id_unit ?? 0,
1079+
nozzleTempMin: doc.data2 ?? 0, // data2 = nozzle_min
1080+
nozzleTempMax: doc.data3 ?? 0, // data3 = nozzle_max
1081+
dryTemp: doc.data4 ?? 0, // data4 = dry_temp
1082+
dryTime: doc.data5 ?? 0, // data5 = dry_time
1083+
bedTempMin: doc.data6 ?? 0, // data6 = bed_min
1084+
bedTempMax: doc.data7 ?? 0, // data7 = bed_max
1085+
timestamp: doc.timestamp ?? null,
1086+
customMessage: doc.message ?? '',
1087+
tdRaw: doc.TD ?? 0, // TD = td_raw
1088+
db,
1089+
});
1090+
}
1091+
1092+
/**
1093+
* Apply a surgical patch using snake_case keys (toRawDict format).
1094+
* Only the supplied keys are changed — all other fields are preserved.
1095+
*
1096+
* Examples:
1097+
* tag.patchFromRawDict({ td_raw: 150 })
1098+
* tag.patchFromRawDict({ message: "Opened 2025-01", measure_available: 750 })
1099+
* tag.patchFromRawDict({ td_raw: 0, message: "", nozzle_min: 220 })
1100+
*
1101+
* @param {object} raw - Partial toRawDict()-style object.
1102+
* @returns {TigerTag} New patched instance (immutable).
1103+
*/
1104+
patchFromRawDict(raw) {
1105+
return this.patch(TigerTag._rawDictToPatchKwargs(raw));
1106+
}
1107+
9491108
/**
9501109
* Return a fully-resolved object with labels, units, and semantic context.
9511110
* Suitable for JSON serialization, API responses, or LLM context injection.

0 commit comments

Comments
 (0)