tigertag 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +174 -3
- package/README.md +169 -42
- package/bin/tigertag.js +56 -18
- package/database/id_aspect.json +5 -0
- package/database/id_brand.json +119 -10
- package/database/id_catalog.json.gz +0 -0
- package/database/id_catalog.meta.json +9 -0
- package/database/id_material.json +489 -489
- package/database/id_type.json +18 -1
- package/database/id_version.json +30 -1
- package/database/last_update.json +1 -1
- package/package.json +11 -2
- package/src/catalog.js +285 -0
- package/src/datadir.js +65 -0
- package/src/db.js +319 -143
- package/src/index.js +14 -1
- package/src/signature.js +8 -8
- package/src/tag.js +209 -10
package/src/tag.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* TigerTag class — binary parsing, serialization, CRUD, and cloud sync.
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
const { TigerTagDB, syncDatabases
|
|
27
|
+
const { TigerTagDB, syncDatabases } = require('./db');
|
|
28
28
|
const { SignatureResult, verifySignature } = require('./signature');
|
|
29
29
|
|
|
30
30
|
// ── Protocol constants ──────────────────────────────────────────────────────
|
|
@@ -61,6 +61,7 @@ const _PATCHABLE_FIELDS = new Set([
|
|
|
61
61
|
'measure', 'idUnit', 'measureAvailable',
|
|
62
62
|
'nozzleTempMin', 'nozzleTempMax', 'dryTemp', 'dryTime', 'bedTempMin', 'bedTempMax',
|
|
63
63
|
'timestamp', 'customMessage', 'tdRaw',
|
|
64
|
+
'tagInfo', 'tagCount', 'tagIndex',
|
|
64
65
|
]);
|
|
65
66
|
|
|
66
67
|
// ── ApiDiff ─────────────────────────────────────────────────────────────────
|
|
@@ -155,6 +156,11 @@ class TigerTag {
|
|
|
155
156
|
// HueForge
|
|
156
157
|
this.tdRaw = fields.tdRaw || 0;
|
|
157
158
|
|
|
159
|
+
// Tag index / tag count (protocol v2.2) — u8 at payload offset +39.
|
|
160
|
+
// High nibble = which tag this one is, low nibble = tags on the item (0 = unknown),
|
|
161
|
+
// so the hex reads "index/count": 0x12 = tag 1 of 2.
|
|
162
|
+
this.tagInfo = fields.tagInfo || 0;
|
|
163
|
+
|
|
158
164
|
// Signature (optional)
|
|
159
165
|
this.signatureR = fields.signatureR instanceof Buffer ? fields.signatureR : Buffer.alloc(32);
|
|
160
166
|
this.signatureS = fields.signatureS instanceof Buffer ? fields.signatureS : Buffer.alloc(32);
|
|
@@ -191,6 +197,21 @@ class TigerTag {
|
|
|
191
197
|
/** HueForge TD as float. 0.0 = undefined, valid range 0.1–100.0. */
|
|
192
198
|
get tdValue() { return this.tdRaw / 10.0; }
|
|
193
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Number of TigerTags on the tagged item — a filament spool, a resin bottle… as given
|
|
202
|
+
* by idType (low nibble of tagInfo).
|
|
203
|
+
* 0 = unknown, 1 = single tag, 2 = twin tag, … up to 15.
|
|
204
|
+
* @returns {number}
|
|
205
|
+
*/
|
|
206
|
+
get tagCount() { return this.tagInfo & 0x0F; }
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Which of those tags this one is, from 1 (high nibble of tagInfo).
|
|
210
|
+
* 0 = unknown.
|
|
211
|
+
* @returns {number}
|
|
212
|
+
*/
|
|
213
|
+
get tagIndex() { return this.tagInfo >> 4; }
|
|
214
|
+
|
|
194
215
|
/** Manufacturing timestamp as UTC Date. */
|
|
195
216
|
get manufacturingDate() {
|
|
196
217
|
return new Date(_TIGERTAG_EPOCH_MS + this.timestamp * 1000);
|
|
@@ -379,6 +400,7 @@ class TigerTag {
|
|
|
379
400
|
color2R: u8(36),
|
|
380
401
|
color2G: u8(37),
|
|
381
402
|
color2B: u8(38),
|
|
403
|
+
tagInfo: u8(39),
|
|
382
404
|
color3R: u8(40),
|
|
383
405
|
color3G: u8(41),
|
|
384
406
|
color3B: u8(42),
|
|
@@ -431,6 +453,8 @@ class TigerTag {
|
|
|
431
453
|
* @param {number} [options.timestamp] - Seconds since 2000-01-01 UTC. Defaults to now.
|
|
432
454
|
* @param {string} [options.customMessage='']
|
|
433
455
|
* @param {number} [options.tdRaw=0]
|
|
456
|
+
* @param {number} [options.tagCount=0] - TigerTags on the item (0 = unknown, 0–15).
|
|
457
|
+
* @param {number} [options.tagIndex=0] - Which tag this one is, from 1 (0 = unknown, 0–15).
|
|
434
458
|
* @param {TigerTagDB} [options.db]
|
|
435
459
|
* @returns {TigerTag}
|
|
436
460
|
*/
|
|
@@ -457,6 +481,8 @@ class TigerTag {
|
|
|
457
481
|
timestamp = null,
|
|
458
482
|
customMessage = '',
|
|
459
483
|
tdRaw = 0,
|
|
484
|
+
tagCount = 0,
|
|
485
|
+
tagIndex = 0,
|
|
460
486
|
measureAvailable = null,
|
|
461
487
|
db = null,
|
|
462
488
|
} = {}) {
|
|
@@ -505,6 +531,7 @@ class TigerTag {
|
|
|
505
531
|
timestamp,
|
|
506
532
|
customMessage,
|
|
507
533
|
tdRaw,
|
|
534
|
+
tagInfo: TigerTag._packTagInfo(tagCount, tagIndex),
|
|
508
535
|
uid,
|
|
509
536
|
_db: db,
|
|
510
537
|
});
|
|
@@ -534,6 +561,7 @@ class TigerTag {
|
|
|
534
561
|
timestamp: ts,
|
|
535
562
|
customMessage: '',
|
|
536
563
|
tdRaw: 0,
|
|
564
|
+
tagInfo: 0,
|
|
537
565
|
uid,
|
|
538
566
|
});
|
|
539
567
|
}
|
|
@@ -590,7 +618,7 @@ class TigerTag {
|
|
|
590
618
|
buf[o++] = this.color2R & 0xFF;
|
|
591
619
|
buf[o++] = this.color2G & 0xFF;
|
|
592
620
|
buf[o++] = this.color2B & 0xFF;
|
|
593
|
-
buf[o++] =
|
|
621
|
+
buf[o++] = this.tagInfo & 0xFF;
|
|
594
622
|
|
|
595
623
|
buf[o++] = this.color3R & 0xFF;
|
|
596
624
|
buf[o++] = this.color3G & 0xFF;
|
|
@@ -631,8 +659,14 @@ class TigerTag {
|
|
|
631
659
|
* Protected fields (idTigertag, idProduct, uid, signatureR, signatureS)
|
|
632
660
|
* cannot be modified — they are covered by the ECDSA signature.
|
|
633
661
|
*
|
|
662
|
+
* tagCount / tagIndex are accepted as shortcuts and re-encoded into
|
|
663
|
+
* tagInfo; the nibble that is not supplied keeps its current value.
|
|
664
|
+
* tagInfo is not covered by the signature, so patching it never
|
|
665
|
+
* invalidates a signed tag.
|
|
666
|
+
*
|
|
634
667
|
* @param {object} kwargs - Field names (camelCase) and their new values.
|
|
635
668
|
* @returns {TigerTag}
|
|
669
|
+
* @throws {RangeError} When tagCount / tagIndex is not an integer in 0–15.
|
|
636
670
|
*/
|
|
637
671
|
patch(kwargs) {
|
|
638
672
|
const protected_ = Object.keys(kwargs).filter((k) => _PROTECTED_FIELDS.has(k));
|
|
@@ -649,7 +683,38 @@ class TigerTag {
|
|
|
649
683
|
+ `Valid patchable fields: ${[..._PATCHABLE_FIELDS].sort().join(', ')}`,
|
|
650
684
|
);
|
|
651
685
|
}
|
|
652
|
-
|
|
686
|
+
const updates = Object.assign({}, kwargs);
|
|
687
|
+
if ('tagCount' in updates || 'tagIndex' in updates) {
|
|
688
|
+
const base = 'tagInfo' in updates ? (updates.tagInfo & 0xFF) : this.tagInfo;
|
|
689
|
+
const count = 'tagCount' in updates ? updates.tagCount : base & 0x0F;
|
|
690
|
+
const index = 'tagIndex' in updates ? updates.tagIndex : base >> 4;
|
|
691
|
+
updates.tagInfo = TigerTag._packTagInfo(count, index);
|
|
692
|
+
delete updates.tagCount;
|
|
693
|
+
delete updates.tagIndex;
|
|
694
|
+
}
|
|
695
|
+
return new TigerTag(Object.assign({}, this, updates));
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Pack a tag count and tag index into the tagInfo byte:
|
|
700
|
+
* (tagIndex << 4) | tagCount — the hex reads "index/count" (0x12 = tag 1 of 2).
|
|
701
|
+
*
|
|
702
|
+
* @param {number} tagCount - TigerTags on the item (0 = unknown, 0–15).
|
|
703
|
+
* @param {number} tagIndex - Which tag this one is, from 1 (0 = unknown, 0–15).
|
|
704
|
+
* @returns {number} u8 tagInfo value.
|
|
705
|
+
* @throws {RangeError} When either value is not an integer in 0–15
|
|
706
|
+
* (it would not fit in its 4-bit nibble).
|
|
707
|
+
* @private
|
|
708
|
+
*/
|
|
709
|
+
static _packTagInfo(tagCount, tagIndex) {
|
|
710
|
+
const count = tagCount || 0;
|
|
711
|
+
const index = tagIndex || 0;
|
|
712
|
+
for (const [name, v] of [['tagCount', count], ['tagIndex', index]]) {
|
|
713
|
+
if (!Number.isInteger(v) || v < 0 || v > 15) {
|
|
714
|
+
throw new RangeError(`${name} must be an integer in 0–15, got ${v}`);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return (index << 4) | count;
|
|
653
718
|
}
|
|
654
719
|
|
|
655
720
|
// ── Validation ──────────────────────────────────────────────────────────
|
|
@@ -675,6 +740,25 @@ class TigerTag {
|
|
|
675
740
|
`TD HueForge out of range: ${this.tdRaw} (valid: 10–1000 or 0=undefined)`,
|
|
676
741
|
);
|
|
677
742
|
}
|
|
743
|
+
const tagCount = this.tagCount;
|
|
744
|
+
const tagIndex = this.tagIndex;
|
|
745
|
+
if (!Number.isInteger(this.tagInfo) || this.tagInfo < 0 || this.tagInfo > 0xFF
|
|
746
|
+
|| tagCount > 15 || tagIndex > 15) {
|
|
747
|
+
warnings.push(
|
|
748
|
+
`Tag index/count out of range: tag_info=${this.tagInfo} `
|
|
749
|
+
+ `(count and index must each be 0–15, tag_info must be 0x00–0xFF)`,
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
if (tagCount > 0 && tagIndex > tagCount) {
|
|
753
|
+
warnings.push(
|
|
754
|
+
`Tag index (${tagIndex}) > tag count (${tagCount}) (valid index: 1–count or 0=unknown)`,
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
if (tagCount === 1 && tagIndex > 1) {
|
|
758
|
+
warnings.push(
|
|
759
|
+
`Single-tag item (count=1) but tag index is ${tagIndex} (expected 1 or 0=unknown)`,
|
|
760
|
+
);
|
|
761
|
+
}
|
|
678
762
|
if (this.measure > 0 && this.measureAvailable > this.measure) {
|
|
679
763
|
warnings.push(
|
|
680
764
|
`measure_available (${this.measureAvailable}) > initial measure (${this.measure})`,
|
|
@@ -949,16 +1033,24 @@ class TigerTag {
|
|
|
949
1033
|
}
|
|
950
1034
|
|
|
951
1035
|
/**
|
|
952
|
-
*
|
|
1036
|
+
* Check for new reference tables now and reload this tag's database.
|
|
953
1037
|
*
|
|
954
|
-
*
|
|
1038
|
+
* Without dbPath: updates the data dir (see TigerTagDB.update()). With dbPath:
|
|
1039
|
+
* downloads into that custom folder, which is then used exclusively.
|
|
1040
|
+
*
|
|
1041
|
+
* @param {string} [dbPath] - Custom target folder. Default: the data dir.
|
|
955
1042
|
* @param {boolean} [force=false] - Re-download all files even if up to date.
|
|
956
1043
|
* @returns {Promise<string[]>} List of filenames that were downloaded/updated.
|
|
957
1044
|
*/
|
|
958
1045
|
async syncDb(dbPath = null, force = false) {
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1046
|
+
if (dbPath) {
|
|
1047
|
+
const updated = await syncDatabases(dbPath, { force, verbose: true });
|
|
1048
|
+
this._db = new TigerTagDB({ dbPath });
|
|
1049
|
+
return updated;
|
|
1050
|
+
}
|
|
1051
|
+
const db = new TigerTagDB({ autoUpdate: false });
|
|
1052
|
+
const updated = await db.update({ force });
|
|
1053
|
+
this._db = db;
|
|
962
1054
|
return updated;
|
|
963
1055
|
}
|
|
964
1056
|
|
|
@@ -1086,6 +1178,7 @@ class TigerTag {
|
|
|
1086
1178
|
color_g3: numColors >= 3 ? this.color3G : 0,
|
|
1087
1179
|
color_b3: numColors >= 3 ? this.color3B : 0,
|
|
1088
1180
|
td_raw: this.tdRaw,
|
|
1181
|
+
tag_info: this.tagInfo,
|
|
1089
1182
|
message: this.customMessage,
|
|
1090
1183
|
measure_available: this.measureAvailable,
|
|
1091
1184
|
...TigerTag._baseUnitFields(this.measure, this.measureAvailable, this.idUnit),
|
|
@@ -1139,6 +1232,7 @@ class TigerTag {
|
|
|
1139
1232
|
bed_max: 'bedTempMax',
|
|
1140
1233
|
timestamp: 'timestamp',
|
|
1141
1234
|
td_raw: 'tdRaw',
|
|
1235
|
+
tag_info: 'tagInfo',
|
|
1142
1236
|
message: 'customMessage',
|
|
1143
1237
|
};
|
|
1144
1238
|
const kwargs = {};
|
|
@@ -1193,6 +1287,8 @@ class TigerTag {
|
|
|
1193
1287
|
timestamp: raw.timestamp ?? null,
|
|
1194
1288
|
customMessage: raw.message ?? '',
|
|
1195
1289
|
tdRaw: raw.td_raw ?? 0,
|
|
1290
|
+
tagCount: (raw.tag_info ?? 0) & 0x0F,
|
|
1291
|
+
tagIndex: (raw.tag_info ?? 0) >> 4,
|
|
1196
1292
|
db,
|
|
1197
1293
|
});
|
|
1198
1294
|
}
|
|
@@ -1243,6 +1339,101 @@ class TigerTag {
|
|
|
1243
1339
|
});
|
|
1244
1340
|
}
|
|
1245
1341
|
|
|
1342
|
+
/**
|
|
1343
|
+
* Build a ready-to-burn TigerTag+ from one entry of the official catalogue
|
|
1344
|
+
* (id_catalog.json). Pure and synchronous — see fromCatalog() to look the
|
|
1345
|
+
* product up by ID.
|
|
1346
|
+
*
|
|
1347
|
+
* RFID_Data mapping (same as the cloud document, see fromCloudDoc()):
|
|
1348
|
+
* data1 = idDiameter, data2/data3 = nozzle min/max, data4/data5 = dry temp/time,
|
|
1349
|
+
* data6/data7 = bed min/max; id_aspect2 null → 0 (none); missing values → 0.
|
|
1350
|
+
* Colours 2 and 3 come from RFID_Data color_r2…b3 when present, otherwise from
|
|
1351
|
+
* color_info.colors[1] / [2] ("#RRGGBBAA").
|
|
1352
|
+
*
|
|
1353
|
+
* @param {object} entry - Catalogue entry ({ id, RFID_Data, title, brand, color_info, … }).
|
|
1354
|
+
* @param {object} [options]
|
|
1355
|
+
* @param {Buffer} [options.uid] - 7-byte chip UID.
|
|
1356
|
+
* @param {number} [options.tagCount=0] - TigerTags on the item (twin tag: 2).
|
|
1357
|
+
* @param {number} [options.tagIndex=0] - Which tag this one is, from 1.
|
|
1358
|
+
* @param {number} [options.timestamp] - Seconds since 2000-01-01 UTC (default: now). Use the
|
|
1359
|
+
* same value on every tag of a twin tag.
|
|
1360
|
+
* @param {TigerTagDB} [options.db]
|
|
1361
|
+
* @returns {TigerTag} A TigerTag+ (idTigertag = ID_TIGERTAG_PLUS, idProduct = entry.id).
|
|
1362
|
+
* @throws {Error} When the entry has no RFID_Data (it cannot be written to a tag).
|
|
1363
|
+
*/
|
|
1364
|
+
static fromCatalogEntry(entry, {
|
|
1365
|
+
uid = null, tagCount = 0, tagIndex = 0, timestamp = null, db = null,
|
|
1366
|
+
} = {}) {
|
|
1367
|
+
const r = entry && entry.RFID_Data;
|
|
1368
|
+
if (!r) {
|
|
1369
|
+
throw new Error(
|
|
1370
|
+
`Catalogue product ${entry && entry.id} ("${(entry && entry.title) || '?'}") has no RFID_Data `
|
|
1371
|
+
+ '— it cannot be written to a tag.',
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
const n = (v) => (v == null ? 0 : Number(v));
|
|
1375
|
+
const hex = (h) => {
|
|
1376
|
+
const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i.exec(h || '');
|
|
1377
|
+
return m ? [parseInt(m[1], 16), parseInt(m[2], 16), parseInt(m[3], 16)] : [0, 0, 0];
|
|
1378
|
+
};
|
|
1379
|
+
const colors = (entry.color_info && entry.color_info.colors) || [];
|
|
1380
|
+
const c2 = r.color_r2 != null ? [n(r.color_r2), n(r.color_g2), n(r.color_b2)] : colors[1] ? hex(colors[1]) : [0, 0, 0];
|
|
1381
|
+
const c3 = r.color_r3 != null ? [n(r.color_r3), n(r.color_g3), n(r.color_b3)] : colors[2] ? hex(colors[2]) : [0, 0, 0];
|
|
1382
|
+
return TigerTag.create({
|
|
1383
|
+
productId: Number(entry.id),
|
|
1384
|
+
uid,
|
|
1385
|
+
idMaterial: n(r.id_material),
|
|
1386
|
+
idAspect1: n(r.id_aspect1),
|
|
1387
|
+
idAspect2: n(r.id_aspect2),
|
|
1388
|
+
idType: n(r.id_type),
|
|
1389
|
+
idDiameter: n(r.data1),
|
|
1390
|
+
idBrand: n(r.id_brand),
|
|
1391
|
+
color1R: n(r.color_r),
|
|
1392
|
+
color1G: n(r.color_g),
|
|
1393
|
+
color1B: n(r.color_b),
|
|
1394
|
+
color1A: r.color_a == null ? 255 : n(r.color_a),
|
|
1395
|
+
color2R: c2[0], color2G: c2[1], color2B: c2[2],
|
|
1396
|
+
color3R: c3[0], color3G: c3[1], color3B: c3[2],
|
|
1397
|
+
measure: n(r.measure),
|
|
1398
|
+
idUnit: n(r.id_unit),
|
|
1399
|
+
nozzleTempMin: n(r.data2),
|
|
1400
|
+
nozzleTempMax: n(r.data3),
|
|
1401
|
+
dryTemp: n(r.data4),
|
|
1402
|
+
dryTime: n(r.data5),
|
|
1403
|
+
bedTempMin: n(r.data6),
|
|
1404
|
+
bedTempMax: n(r.data7),
|
|
1405
|
+
timestamp,
|
|
1406
|
+
tagCount,
|
|
1407
|
+
tagIndex,
|
|
1408
|
+
db,
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
/**
|
|
1413
|
+
* Build a ready-to-burn TigerTag+ from a product ID of the official catalogue.
|
|
1414
|
+
* The catalogue is downloaded on first use and cached on disk (see loadCatalog()).
|
|
1415
|
+
* Use catalogEntry(productId) for the display metadata (title, brand, sku,
|
|
1416
|
+
* barcode, img_src).
|
|
1417
|
+
*
|
|
1418
|
+
* @param {number} productId - TigerTag+ product ID.
|
|
1419
|
+
* @param {object} [options] - fromCatalogEntry() options, plus:
|
|
1420
|
+
* @param {Map<number, object>} [options.catalog] - Already loaded catalogue (otherwise loaded with
|
|
1421
|
+
* the loadCatalog() options url / cacheDir / maxAge / force).
|
|
1422
|
+
* @returns {Promise<TigerTag>}
|
|
1423
|
+
* @throws {Error} When the ID is not in the catalogue, the product has no RFID_Data, or the
|
|
1424
|
+
* catalogue cannot be loaded (offline with no cache).
|
|
1425
|
+
*/
|
|
1426
|
+
static async fromCatalog(productId, options = {}) {
|
|
1427
|
+
const { loadCatalog } = require('./catalog');
|
|
1428
|
+
const { catalog: given, uid, tagCount, tagIndex, timestamp, db, ...loadOpts } = options;
|
|
1429
|
+
const catalog = given || await loadCatalog(loadOpts);
|
|
1430
|
+
const entry = catalog.get(Number(productId));
|
|
1431
|
+
if (!entry) {
|
|
1432
|
+
throw new Error(`Product ID ${productId} is not in the TigerTag catalogue (${catalog.size} products).`);
|
|
1433
|
+
}
|
|
1434
|
+
return TigerTag.fromCatalogEntry(entry, { uid, tagCount, tagIndex, timestamp, db });
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1246
1437
|
/**
|
|
1247
1438
|
* Apply a surgical patch using snake_case keys (toRawDict format).
|
|
1248
1439
|
* Only the supplied keys are changed — all other fields are preserved.
|
|
@@ -1276,7 +1467,7 @@ class TigerTag {
|
|
|
1276
1467
|
return {
|
|
1277
1468
|
sdk: 'tigertag-sdk-js',
|
|
1278
1469
|
sdk_mode: 'offline',
|
|
1279
|
-
protocol: 'TigerTag Open Source v2.
|
|
1470
|
+
protocol: 'TigerTag Open Source v2.2',
|
|
1280
1471
|
chip: 'NTAG213/215/216',
|
|
1281
1472
|
uid: this.uidHex,
|
|
1282
1473
|
version: {
|
|
@@ -1369,6 +1560,8 @@ class TigerTag {
|
|
|
1369
1560
|
},
|
|
1370
1561
|
manufacturing_date: this.manufacturingDate.toISOString(),
|
|
1371
1562
|
twin_tag_pairing_id: this.timestamp,
|
|
1563
|
+
tag_count: this.tagCount || null,
|
|
1564
|
+
tag_index: this.tagIndex || null,
|
|
1372
1565
|
custom_message: this.customMessage,
|
|
1373
1566
|
authentication: {
|
|
1374
1567
|
signed: this.isSigned,
|
|
@@ -1465,6 +1658,11 @@ class TigerTag {
|
|
|
1465
1658
|
}
|
|
1466
1659
|
|
|
1467
1660
|
if (this.tdRaw !== 0) parts.push(`HueForge TD: ${this.tdValue.toFixed(1)}.`);
|
|
1661
|
+
if (this.tagInfo !== 0) {
|
|
1662
|
+
// What the item is comes from idType (Filament, Resin, …); unknown → "item"
|
|
1663
|
+
const typeLabel = ((_db.type(this.idType) || {}).label || '').trim().toLowerCase();
|
|
1664
|
+
parts.push(`Tag ${this.tagIndex || '?'} of ${this.tagCount || '?'} on this ${typeLabel || 'item'}.`);
|
|
1665
|
+
}
|
|
1468
1666
|
|
|
1469
1667
|
const dateStr = this.manufacturingDate.toISOString().slice(0, 10);
|
|
1470
1668
|
parts.push(`Manufactured: ${dateStr}.`);
|
|
@@ -1509,7 +1707,7 @@ class TigerTag {
|
|
|
1509
1707
|
const ul = TigerTagDB.label(_db.unit(this.idUnit));
|
|
1510
1708
|
const sig = sigResult
|
|
1511
1709
|
? String(sigResult)
|
|
1512
|
-
: (this.isSigned ? 'signed
|
|
1710
|
+
: (this.isSigned ? 'signed (not verified)' : 'not signed');
|
|
1513
1711
|
|
|
1514
1712
|
const recNote = (kMin, kMax, suffix = '°C') =>
|
|
1515
1713
|
rec[kMin] != null ? ` (DB: ${rec[kMin]}–${rec[kMax]}${suffix})` : '';
|
|
@@ -1554,6 +1752,7 @@ class TigerTag {
|
|
|
1554
1752
|
+ `├─ Traceability ────────────────────────────────────────\n`
|
|
1555
1753
|
+ `│ Manufactured ${this.manufacturingDate.toISOString().replace('T', ' ').slice(0, 16)} UTC\n`
|
|
1556
1754
|
+ `│ Twin tag ID ${this.timestamp}\n`
|
|
1755
|
+
+ `│ Tag ${this.tagIndex || '?'} of ${this.tagCount || '?'}${this.tagInfo === 0 ? ' (unknown)' : ''}\n`
|
|
1557
1756
|
+ `│ Message ${JSON.stringify(this.customMessage)}\n`
|
|
1558
1757
|
+ `├─ Signature ───────────────────────────────────────────\n`
|
|
1559
1758
|
+ `│ ECDSA ${sig}\n`
|