tigertag 1.0.6 → 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 +190 -3
- package/LICENSE +202 -0
- package/README.md +181 -47
- package/bin/tigertag.js +74 -23
- package/database/id_aspect.json +5 -0
- package/database/id_brand.json +996 -1
- package/database/id_catalog.json.gz +0 -0
- package/database/id_catalog.meta.json +9 -0
- package/database/id_material.json +2325 -1
- package/database/id_type.json +18 -1
- package/database/id_version.json +30 -1
- package/database/last_update.json +1 -1
- package/package.json +12 -3
- package/src/catalog.js +285 -0
- package/src/datadir.js +65 -0
- package/src/db.js +337 -148
- package/src/index.js +32 -6
- package/src/signature.js +26 -13
- package/src/tag.js +227 -15
- package/LICENSE.md +0 -21
package/src/signature.js
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
// Copyright (C) 2025 TigerTag
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
5
4
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
5
|
+
// TigerTag SDK
|
|
6
|
+
// Copyright (c) 2025-2026 TigerTag Corp.
|
|
7
|
+
//
|
|
8
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
+
// you may not use this file except in compliance with the License.
|
|
10
|
+
// You may obtain a copy of the License at
|
|
11
|
+
//
|
|
12
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
+
//
|
|
14
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
15
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
+
// See the License for the specific language governing permissions and
|
|
18
|
+
// limitations under the License.
|
|
19
|
+
//
|
|
20
|
+
// Implementing the TigerTag protocol requires no licence and no payment.
|
|
21
|
+
// https://github.com/TigerTag-Project/TigerTag-RFID-Guide/blob/main/LICENSING.md
|
|
9
22
|
|
|
10
23
|
/**
|
|
11
24
|
* ECDSA-P256 signature verification for TigerTag chips.
|
|
@@ -52,7 +65,7 @@ class SignatureResult {
|
|
|
52
65
|
}
|
|
53
66
|
|
|
54
67
|
toString() {
|
|
55
|
-
const base = SignatureResult.
|
|
68
|
+
const base = SignatureResult._LABELS[this.status] || `? ${this.status}`;
|
|
56
69
|
return this.detail ? `${base} ${this.detail}` : base;
|
|
57
70
|
}
|
|
58
71
|
|
|
@@ -72,13 +85,13 @@ SignatureResult.NO_CRYPTO = 'no_crypto';
|
|
|
72
85
|
SignatureResult.NO_KEY = 'no_key';
|
|
73
86
|
SignatureResult.NO_UID = 'no_uid';
|
|
74
87
|
|
|
75
|
-
SignatureResult.
|
|
76
|
-
valid: '
|
|
77
|
-
invalid: '
|
|
78
|
-
unsigned: '
|
|
79
|
-
no_crypto: '
|
|
80
|
-
no_key: '
|
|
81
|
-
no_uid: '
|
|
88
|
+
SignatureResult._LABELS = {
|
|
89
|
+
valid: 'VALID',
|
|
90
|
+
invalid: 'INVALID',
|
|
91
|
+
unsigned: 'NOT SIGNED',
|
|
92
|
+
no_crypto: 'NO CRYPTO — crypto not available',
|
|
93
|
+
no_key: 'NO PUBLIC KEY — public key not found in id_version.json',
|
|
94
|
+
no_uid: 'NO UID — UID unavailable, provide a full 180-byte chip dump',
|
|
82
95
|
};
|
|
83
96
|
|
|
84
97
|
/**
|
package/src/tag.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
// Copyright (C) 2025 TigerTag
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
5
4
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
5
|
+
// TigerTag SDK
|
|
6
|
+
// Copyright (c) 2025-2026 TigerTag Corp.
|
|
7
|
+
//
|
|
8
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
+
// you may not use this file except in compliance with the License.
|
|
10
|
+
// You may obtain a copy of the License at
|
|
11
|
+
//
|
|
12
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
+
//
|
|
14
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
15
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
+
// See the License for the specific language governing permissions and
|
|
18
|
+
// limitations under the License.
|
|
19
|
+
//
|
|
20
|
+
// Implementing the TigerTag protocol requires no licence and no payment.
|
|
21
|
+
// https://github.com/TigerTag-Project/TigerTag-RFID-Guide/blob/main/LICENSING.md
|
|
9
22
|
|
|
10
23
|
/**
|
|
11
24
|
* TigerTag class — binary parsing, serialization, CRUD, and cloud sync.
|
|
12
25
|
*/
|
|
13
26
|
|
|
14
|
-
const { TigerTagDB, syncDatabases
|
|
27
|
+
const { TigerTagDB, syncDatabases } = require('./db');
|
|
15
28
|
const { SignatureResult, verifySignature } = require('./signature');
|
|
16
29
|
|
|
17
30
|
// ── Protocol constants ──────────────────────────────────────────────────────
|
|
@@ -48,6 +61,7 @@ const _PATCHABLE_FIELDS = new Set([
|
|
|
48
61
|
'measure', 'idUnit', 'measureAvailable',
|
|
49
62
|
'nozzleTempMin', 'nozzleTempMax', 'dryTemp', 'dryTime', 'bedTempMin', 'bedTempMax',
|
|
50
63
|
'timestamp', 'customMessage', 'tdRaw',
|
|
64
|
+
'tagInfo', 'tagCount', 'tagIndex',
|
|
51
65
|
]);
|
|
52
66
|
|
|
53
67
|
// ── ApiDiff ─────────────────────────────────────────────────────────────────
|
|
@@ -142,6 +156,11 @@ class TigerTag {
|
|
|
142
156
|
// HueForge
|
|
143
157
|
this.tdRaw = fields.tdRaw || 0;
|
|
144
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
|
+
|
|
145
164
|
// Signature (optional)
|
|
146
165
|
this.signatureR = fields.signatureR instanceof Buffer ? fields.signatureR : Buffer.alloc(32);
|
|
147
166
|
this.signatureS = fields.signatureS instanceof Buffer ? fields.signatureS : Buffer.alloc(32);
|
|
@@ -178,6 +197,21 @@ class TigerTag {
|
|
|
178
197
|
/** HueForge TD as float. 0.0 = undefined, valid range 0.1–100.0. */
|
|
179
198
|
get tdValue() { return this.tdRaw / 10.0; }
|
|
180
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
|
+
|
|
181
215
|
/** Manufacturing timestamp as UTC Date. */
|
|
182
216
|
get manufacturingDate() {
|
|
183
217
|
return new Date(_TIGERTAG_EPOCH_MS + this.timestamp * 1000);
|
|
@@ -366,6 +400,7 @@ class TigerTag {
|
|
|
366
400
|
color2R: u8(36),
|
|
367
401
|
color2G: u8(37),
|
|
368
402
|
color2B: u8(38),
|
|
403
|
+
tagInfo: u8(39),
|
|
369
404
|
color3R: u8(40),
|
|
370
405
|
color3G: u8(41),
|
|
371
406
|
color3B: u8(42),
|
|
@@ -418,6 +453,8 @@ class TigerTag {
|
|
|
418
453
|
* @param {number} [options.timestamp] - Seconds since 2000-01-01 UTC. Defaults to now.
|
|
419
454
|
* @param {string} [options.customMessage='']
|
|
420
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).
|
|
421
458
|
* @param {TigerTagDB} [options.db]
|
|
422
459
|
* @returns {TigerTag}
|
|
423
460
|
*/
|
|
@@ -444,6 +481,8 @@ class TigerTag {
|
|
|
444
481
|
timestamp = null,
|
|
445
482
|
customMessage = '',
|
|
446
483
|
tdRaw = 0,
|
|
484
|
+
tagCount = 0,
|
|
485
|
+
tagIndex = 0,
|
|
447
486
|
measureAvailable = null,
|
|
448
487
|
db = null,
|
|
449
488
|
} = {}) {
|
|
@@ -492,6 +531,7 @@ class TigerTag {
|
|
|
492
531
|
timestamp,
|
|
493
532
|
customMessage,
|
|
494
533
|
tdRaw,
|
|
534
|
+
tagInfo: TigerTag._packTagInfo(tagCount, tagIndex),
|
|
495
535
|
uid,
|
|
496
536
|
_db: db,
|
|
497
537
|
});
|
|
@@ -521,6 +561,7 @@ class TigerTag {
|
|
|
521
561
|
timestamp: ts,
|
|
522
562
|
customMessage: '',
|
|
523
563
|
tdRaw: 0,
|
|
564
|
+
tagInfo: 0,
|
|
524
565
|
uid,
|
|
525
566
|
});
|
|
526
567
|
}
|
|
@@ -577,7 +618,7 @@ class TigerTag {
|
|
|
577
618
|
buf[o++] = this.color2R & 0xFF;
|
|
578
619
|
buf[o++] = this.color2G & 0xFF;
|
|
579
620
|
buf[o++] = this.color2B & 0xFF;
|
|
580
|
-
buf[o++] =
|
|
621
|
+
buf[o++] = this.tagInfo & 0xFF;
|
|
581
622
|
|
|
582
623
|
buf[o++] = this.color3R & 0xFF;
|
|
583
624
|
buf[o++] = this.color3G & 0xFF;
|
|
@@ -618,8 +659,14 @@ class TigerTag {
|
|
|
618
659
|
* Protected fields (idTigertag, idProduct, uid, signatureR, signatureS)
|
|
619
660
|
* cannot be modified — they are covered by the ECDSA signature.
|
|
620
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
|
+
*
|
|
621
667
|
* @param {object} kwargs - Field names (camelCase) and their new values.
|
|
622
668
|
* @returns {TigerTag}
|
|
669
|
+
* @throws {RangeError} When tagCount / tagIndex is not an integer in 0–15.
|
|
623
670
|
*/
|
|
624
671
|
patch(kwargs) {
|
|
625
672
|
const protected_ = Object.keys(kwargs).filter((k) => _PROTECTED_FIELDS.has(k));
|
|
@@ -636,7 +683,38 @@ class TigerTag {
|
|
|
636
683
|
+ `Valid patchable fields: ${[..._PATCHABLE_FIELDS].sort().join(', ')}`,
|
|
637
684
|
);
|
|
638
685
|
}
|
|
639
|
-
|
|
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;
|
|
640
718
|
}
|
|
641
719
|
|
|
642
720
|
// ── Validation ──────────────────────────────────────────────────────────
|
|
@@ -662,6 +740,25 @@ class TigerTag {
|
|
|
662
740
|
`TD HueForge out of range: ${this.tdRaw} (valid: 10–1000 or 0=undefined)`,
|
|
663
741
|
);
|
|
664
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
|
+
}
|
|
665
762
|
if (this.measure > 0 && this.measureAvailable > this.measure) {
|
|
666
763
|
warnings.push(
|
|
667
764
|
`measure_available (${this.measureAvailable}) > initial measure (${this.measure})`,
|
|
@@ -936,16 +1033,24 @@ class TigerTag {
|
|
|
936
1033
|
}
|
|
937
1034
|
|
|
938
1035
|
/**
|
|
939
|
-
*
|
|
1036
|
+
* Check for new reference tables now and reload this tag's database.
|
|
940
1037
|
*
|
|
941
|
-
*
|
|
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.
|
|
942
1042
|
* @param {boolean} [force=false] - Re-download all files even if up to date.
|
|
943
1043
|
* @returns {Promise<string[]>} List of filenames that were downloaded/updated.
|
|
944
1044
|
*/
|
|
945
1045
|
async syncDb(dbPath = null, force = false) {
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
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;
|
|
949
1054
|
return updated;
|
|
950
1055
|
}
|
|
951
1056
|
|
|
@@ -1073,6 +1178,7 @@ class TigerTag {
|
|
|
1073
1178
|
color_g3: numColors >= 3 ? this.color3G : 0,
|
|
1074
1179
|
color_b3: numColors >= 3 ? this.color3B : 0,
|
|
1075
1180
|
td_raw: this.tdRaw,
|
|
1181
|
+
tag_info: this.tagInfo,
|
|
1076
1182
|
message: this.customMessage,
|
|
1077
1183
|
measure_available: this.measureAvailable,
|
|
1078
1184
|
...TigerTag._baseUnitFields(this.measure, this.measureAvailable, this.idUnit),
|
|
@@ -1126,6 +1232,7 @@ class TigerTag {
|
|
|
1126
1232
|
bed_max: 'bedTempMax',
|
|
1127
1233
|
timestamp: 'timestamp',
|
|
1128
1234
|
td_raw: 'tdRaw',
|
|
1235
|
+
tag_info: 'tagInfo',
|
|
1129
1236
|
message: 'customMessage',
|
|
1130
1237
|
};
|
|
1131
1238
|
const kwargs = {};
|
|
@@ -1180,6 +1287,8 @@ class TigerTag {
|
|
|
1180
1287
|
timestamp: raw.timestamp ?? null,
|
|
1181
1288
|
customMessage: raw.message ?? '',
|
|
1182
1289
|
tdRaw: raw.td_raw ?? 0,
|
|
1290
|
+
tagCount: (raw.tag_info ?? 0) & 0x0F,
|
|
1291
|
+
tagIndex: (raw.tag_info ?? 0) >> 4,
|
|
1183
1292
|
db,
|
|
1184
1293
|
});
|
|
1185
1294
|
}
|
|
@@ -1230,6 +1339,101 @@ class TigerTag {
|
|
|
1230
1339
|
});
|
|
1231
1340
|
}
|
|
1232
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
|
+
|
|
1233
1437
|
/**
|
|
1234
1438
|
* Apply a surgical patch using snake_case keys (toRawDict format).
|
|
1235
1439
|
* Only the supplied keys are changed — all other fields are preserved.
|
|
@@ -1263,7 +1467,7 @@ class TigerTag {
|
|
|
1263
1467
|
return {
|
|
1264
1468
|
sdk: 'tigertag-sdk-js',
|
|
1265
1469
|
sdk_mode: 'offline',
|
|
1266
|
-
protocol: 'TigerTag Open Source v2.
|
|
1470
|
+
protocol: 'TigerTag Open Source v2.2',
|
|
1267
1471
|
chip: 'NTAG213/215/216',
|
|
1268
1472
|
uid: this.uidHex,
|
|
1269
1473
|
version: {
|
|
@@ -1356,6 +1560,8 @@ class TigerTag {
|
|
|
1356
1560
|
},
|
|
1357
1561
|
manufacturing_date: this.manufacturingDate.toISOString(),
|
|
1358
1562
|
twin_tag_pairing_id: this.timestamp,
|
|
1563
|
+
tag_count: this.tagCount || null,
|
|
1564
|
+
tag_index: this.tagIndex || null,
|
|
1359
1565
|
custom_message: this.customMessage,
|
|
1360
1566
|
authentication: {
|
|
1361
1567
|
signed: this.isSigned,
|
|
@@ -1452,6 +1658,11 @@ class TigerTag {
|
|
|
1452
1658
|
}
|
|
1453
1659
|
|
|
1454
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
|
+
}
|
|
1455
1666
|
|
|
1456
1667
|
const dateStr = this.manufacturingDate.toISOString().slice(0, 10);
|
|
1457
1668
|
parts.push(`Manufactured: ${dateStr}.`);
|
|
@@ -1496,7 +1707,7 @@ class TigerTag {
|
|
|
1496
1707
|
const ul = TigerTagDB.label(_db.unit(this.idUnit));
|
|
1497
1708
|
const sig = sigResult
|
|
1498
1709
|
? String(sigResult)
|
|
1499
|
-
: (this.isSigned ? 'signed
|
|
1710
|
+
: (this.isSigned ? 'signed (not verified)' : 'not signed');
|
|
1500
1711
|
|
|
1501
1712
|
const recNote = (kMin, kMax, suffix = '°C') =>
|
|
1502
1713
|
rec[kMin] != null ? ` (DB: ${rec[kMin]}–${rec[kMax]}${suffix})` : '';
|
|
@@ -1541,6 +1752,7 @@ class TigerTag {
|
|
|
1541
1752
|
+ `├─ Traceability ────────────────────────────────────────\n`
|
|
1542
1753
|
+ `│ Manufactured ${this.manufacturingDate.toISOString().replace('T', ' ').slice(0, 16)} UTC\n`
|
|
1543
1754
|
+ `│ Twin tag ID ${this.timestamp}\n`
|
|
1755
|
+
+ `│ Tag ${this.tagIndex || '?'} of ${this.tagCount || '?'}${this.tagInfo === 0 ? ' (unknown)' : ''}\n`
|
|
1544
1756
|
+ `│ Message ${JSON.stringify(this.customMessage)}\n`
|
|
1545
1757
|
+ `├─ Signature ───────────────────────────────────────────\n`
|
|
1546
1758
|
+ `│ ECDSA ${sig}\n`
|
package/LICENSE.md
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# GNU General Public License v3.0
|
|
2
|
-
|
|
3
|
-
TigerTag JavaScript SDK
|
|
4
|
-
Copyright (C) 2025 TigerTag
|
|
5
|
-
|
|
6
|
-
This program is free software: you can redistribute it and/or modify
|
|
7
|
-
it under the terms of the GNU General Public License as published by
|
|
8
|
-
the Free Software Foundation, either version 3 of the License, or
|
|
9
|
-
(at your option) any later version.
|
|
10
|
-
|
|
11
|
-
This program is distributed in the hope that it will be useful,
|
|
12
|
-
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
-
GNU General Public License for more details.
|
|
15
|
-
|
|
16
|
-
You should have received a copy of the GNU General Public License
|
|
17
|
-
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
For commercial OEM licensing inquiries, contact: licensing@tigertag.io
|