myio-js-library 0.1.534 → 0.1.535
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/README.md +1 -0
- package/dist/index.cjs +255 -1
- package/dist/index.d.cts +115 -9
- package/dist/index.d.ts +115 -9
- package/dist/index.js +248 -1
- package/dist/myio-js-library.umd.js +248 -1
- package/dist/myio-js-library.umd.min.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,6 +67,7 @@ devices e OS integrada ao ecossistema).
|
|
|
67
67
|
- 🏷️ **Classification** — energy entity classification utilities.
|
|
68
68
|
- 🔍 **Data access** — nested object value retrieval with datakey paths.
|
|
69
69
|
- 🔌 **Device status** — comprehensive status calculation and management with `calculateDeviceStatus`.
|
|
70
|
+
- 🏭 **Device product code** — RFC-0230 factory serial (`B1.B2.B3.B4`) ⇄ canonical name (`PREFIX YYMMDD-NNNN`), lossless encode/decode/validate with `encodeDeviceProductCode`/`decodeDeviceProductCode`/`deviceProductCodeToName`.
|
|
70
71
|
- 🎯 **Goals Panel** — consumption goals setup with annual/monthly targets, versioning, and ThingsBoard integration.
|
|
71
72
|
- ⚡ **Dual module support** — ESM and CJS.
|
|
72
73
|
- 🌍 **Browser-ready** — UMD global + CDN link.
|
package/dist/index.cjs
CHANGED
|
@@ -492,6 +492,7 @@ __export(src_exports, {
|
|
|
492
492
|
csvEscapeAnnotation: () => csvEscape,
|
|
493
493
|
daysInMonthYear: () => daysInMonthYear,
|
|
494
494
|
decimalStringToCents: () => decimalStringToCents,
|
|
495
|
+
decodeDeviceProductCode: () => decodeDeviceProductCode,
|
|
495
496
|
decodePayload: () => decodePayload,
|
|
496
497
|
decodePayloadBase64Xor: () => decodePayloadBase64Xor,
|
|
497
498
|
deleteFreshdeskTicket: () => deleteTicket,
|
|
@@ -506,10 +507,13 @@ __export(src_exports, {
|
|
|
506
507
|
deviceCategoryToPanelTerm: () => deviceCategoryToPanelTerm,
|
|
507
508
|
deviceIconLabels: () => deviceIconLabels,
|
|
508
509
|
deviceIcons: () => deviceIcons,
|
|
510
|
+
deviceNameToDeviceProductCode: () => deviceNameToDeviceProductCode,
|
|
511
|
+
deviceProductCodeToName: () => deviceProductCodeToName,
|
|
509
512
|
deviceStatusIcons: () => deviceStatusIcons,
|
|
510
513
|
deviceTypeToken: () => deviceTypeToken,
|
|
511
514
|
doSchedulesOverlap: () => doSchedulesOverlap,
|
|
512
515
|
downloadAnnotationsTextFile: () => downloadTextFile,
|
|
516
|
+
encodeDeviceProductCode: () => encodeDeviceProductCode,
|
|
513
517
|
escapeAnnotationHtml: () => escapeHtml16,
|
|
514
518
|
evaluateDeviceStatus: () => evaluateDeviceStatus,
|
|
515
519
|
expandBandHours: () => expandBandHours,
|
|
@@ -553,6 +557,7 @@ __export(src_exports, {
|
|
|
553
557
|
formatDateToYMD: () => formatDateToYMD,
|
|
554
558
|
formatDateWithTimezoneOffset: () => formatDateWithTimezoneOffset,
|
|
555
559
|
formatDeltaPct: () => formatDeltaPct,
|
|
560
|
+
formatDeviceProductCode: () => formatDeviceProductCode,
|
|
556
561
|
formatDuration: () => formatDuration,
|
|
557
562
|
formatEnergy: () => formatEnergy,
|
|
558
563
|
formatHours: () => formatHours,
|
|
@@ -847,6 +852,8 @@ __export(src_exports, {
|
|
|
847
852
|
updateDeviceGridStats: () => updateStats,
|
|
848
853
|
updateFreshdeskTicket: () => updateTicket,
|
|
849
854
|
upsertAlarmAnnotation: () => upsertAlarmAnnotation,
|
|
855
|
+
validateDeviceProductCode: () => validateDeviceProductCode,
|
|
856
|
+
validateDeviceProductName: () => validateDeviceProductName,
|
|
850
857
|
validateProfile: () => validateProfile,
|
|
851
858
|
validateTree: () => validateTree,
|
|
852
859
|
version: () => version,
|
|
@@ -861,7 +868,7 @@ module.exports = __toCommonJS(src_exports);
|
|
|
861
868
|
// package.json
|
|
862
869
|
var package_default = {
|
|
863
870
|
name: "myio-js-library",
|
|
864
|
-
version: "0.1.
|
|
871
|
+
version: "0.1.535",
|
|
865
872
|
description: "A clean, standalone JS SDK for MYIO projects",
|
|
866
873
|
license: "MIT",
|
|
867
874
|
repository: "github:gh-myio/myio-js-library",
|
|
@@ -159576,6 +159583,246 @@ function getHeaderAnnotationsPanel() {
|
|
|
159576
159583
|
return _singleton;
|
|
159577
159584
|
}
|
|
159578
159585
|
|
|
159586
|
+
// src/utils/devices/device-product-code/errors.ts
|
|
159587
|
+
var DeviceProductCodeError = class extends Error {
|
|
159588
|
+
reason;
|
|
159589
|
+
constructor(reason, message) {
|
|
159590
|
+
super(message);
|
|
159591
|
+
this.name = "DeviceProductCodeError";
|
|
159592
|
+
this.reason = reason;
|
|
159593
|
+
}
|
|
159594
|
+
};
|
|
159595
|
+
|
|
159596
|
+
// src/utils/devices/device-product-code/codecs/v2.ts
|
|
159597
|
+
var YEAR_BASE = 2026;
|
|
159598
|
+
var YEAR_MAX = 2041;
|
|
159599
|
+
function checkFields(fields) {
|
|
159600
|
+
if (fields.year < YEAR_BASE || fields.year > YEAR_MAX) return "year-out-of-range";
|
|
159601
|
+
if (fields.month < 1 || fields.month > 12) return "month-out-of-range";
|
|
159602
|
+
if (fields.day < 1 || fields.day > 31) return "day-out-of-range";
|
|
159603
|
+
if (fields.seq3 < 0 || fields.seq3 > 7) return "seq3-out-of-range";
|
|
159604
|
+
if (fields.seq < 1 || fields.seq > 254) return "seq-out-of-range";
|
|
159605
|
+
if (fields.productType < 0 || fields.productType > 255) return "product-type-out-of-range";
|
|
159606
|
+
return null;
|
|
159607
|
+
}
|
|
159608
|
+
function encodeV2(fields) {
|
|
159609
|
+
const reason = checkFields(fields);
|
|
159610
|
+
if (reason) {
|
|
159611
|
+
throw new DeviceProductCodeError(reason, `encodeDeviceProductCode: ${reason} (${JSON.stringify(fields)})`);
|
|
159612
|
+
}
|
|
159613
|
+
const yearOffset = fields.year - YEAR_BASE;
|
|
159614
|
+
const b1 = yearOffset << 4 | fields.month;
|
|
159615
|
+
const b2 = fields.seq3 << 5 | fields.day;
|
|
159616
|
+
const b3 = fields.seq;
|
|
159617
|
+
const b4 = fields.productType;
|
|
159618
|
+
return `${b1}.${b2}.${b3}.${b4}`;
|
|
159619
|
+
}
|
|
159620
|
+
function parseBytes(code) {
|
|
159621
|
+
const parts = code.split(".");
|
|
159622
|
+
if (parts.length !== 4) return null;
|
|
159623
|
+
const bytes = [];
|
|
159624
|
+
for (const part of parts) {
|
|
159625
|
+
if (!/^\d{1,3}$/.test(part)) return null;
|
|
159626
|
+
const n = Number(part);
|
|
159627
|
+
if (n < 0 || n > 255) return null;
|
|
159628
|
+
bytes.push(n);
|
|
159629
|
+
}
|
|
159630
|
+
return bytes;
|
|
159631
|
+
}
|
|
159632
|
+
function decodeV2(code) {
|
|
159633
|
+
const bytes = parseBytes(code);
|
|
159634
|
+
if (!bytes) {
|
|
159635
|
+
throw new DeviceProductCodeError("invalid-code-shape", `decodeDeviceProductCode: not 4 dotted decimal bytes 0-255 ("${code}")`);
|
|
159636
|
+
}
|
|
159637
|
+
const [b1, b2, b3, b4] = bytes;
|
|
159638
|
+
const yearOffset = b1 >> 4 & 15;
|
|
159639
|
+
const month = b1 & 15;
|
|
159640
|
+
const seq3 = b2 >> 5 & 7;
|
|
159641
|
+
const day = b2 & 31;
|
|
159642
|
+
const seq = b3;
|
|
159643
|
+
const productType = b4;
|
|
159644
|
+
const fields = { year: YEAR_BASE + yearOffset, month, day, seq3, seq, productType };
|
|
159645
|
+
const reason = checkFields(fields);
|
|
159646
|
+
if (reason) {
|
|
159647
|
+
throw new DeviceProductCodeError(reason, `decodeDeviceProductCode: ${reason} ("${code}" -> ${JSON.stringify(fields)})`);
|
|
159648
|
+
}
|
|
159649
|
+
return fields;
|
|
159650
|
+
}
|
|
159651
|
+
function validateV2(code) {
|
|
159652
|
+
try {
|
|
159653
|
+
decodeV2(code);
|
|
159654
|
+
return { valid: true };
|
|
159655
|
+
} catch (e) {
|
|
159656
|
+
if (e instanceof DeviceProductCodeError) {
|
|
159657
|
+
return { valid: false, reason: e.reason, message: e.message };
|
|
159658
|
+
}
|
|
159659
|
+
throw e;
|
|
159660
|
+
}
|
|
159661
|
+
}
|
|
159662
|
+
var V2Codec = {
|
|
159663
|
+
version: "v2",
|
|
159664
|
+
encode: encodeV2,
|
|
159665
|
+
decode: decodeV2,
|
|
159666
|
+
validate: validateV2
|
|
159667
|
+
};
|
|
159668
|
+
|
|
159669
|
+
// src/utils/devices/device-product-code/codecs/registry.ts
|
|
159670
|
+
var CODEC_REGISTRY = {
|
|
159671
|
+
v2: V2Codec
|
|
159672
|
+
};
|
|
159673
|
+
var DEFAULT_VERSION = "v2";
|
|
159674
|
+
function resolveCodec(version2) {
|
|
159675
|
+
const key = version2 ?? DEFAULT_VERSION;
|
|
159676
|
+
const codec = CODEC_REGISTRY[key];
|
|
159677
|
+
if (!codec) {
|
|
159678
|
+
throw new DeviceProductCodeError("unsupported-codec-version", `resolveCodec: unsupported version "${key}"`);
|
|
159679
|
+
}
|
|
159680
|
+
return codec;
|
|
159681
|
+
}
|
|
159682
|
+
|
|
159683
|
+
// src/utils/devices/device-product-code/registry/productTypeRegistry.ts
|
|
159684
|
+
var ENTRIES = [
|
|
159685
|
+
// DEVICE-NAME-SPEC.md §3a. `12` was originally documented as `switch`;
|
|
159686
|
+
// GCDR reconciled it to the hydrometer device type. The decodable name
|
|
159687
|
+
// prefix is HIDR — `switch` survives only as the byte's legacy/internal
|
|
159688
|
+
// label (GCDR's own generator UI shows it as "12 · switch/HIDR").
|
|
159689
|
+
{ byte: 12, prefix: "HIDR", status: "ratified", legacyLabel: "switch" },
|
|
159690
|
+
{ byte: 14, prefix: "REM", status: "ratified" },
|
|
159691
|
+
{ byte: 15, prefix: "3F", status: "ratified" },
|
|
159692
|
+
// Proposed, not ratified — DEVICE-PRODUCT-CODE-NUMBERING.md.
|
|
159693
|
+
{ byte: 16, prefix: "TEMP", status: "draft" },
|
|
159694
|
+
{ byte: 17, prefix: "TANK", status: "draft" },
|
|
159695
|
+
// Ratified 2026-08-25 (GCDR PR #39, DEVICE-NAME-SPEC.md §3a). Type-byte
|
|
159696
|
+
// entry only — the BOX device *profile*'s own fields/parsing stay out of
|
|
159697
|
+
// scope (RFC-0230 Non-goals). Registered here so a code with B4=18
|
|
159698
|
+
// decodes as BOX instead of falling through to the unknown-type
|
|
159699
|
+
// (`T{B4}`) fallback.
|
|
159700
|
+
{ byte: 18, prefix: "BOX", status: "ratified" }
|
|
159701
|
+
];
|
|
159702
|
+
var BY_BYTE = new Map(ENTRIES.map((e) => [e.byte, e]));
|
|
159703
|
+
var BY_PREFIX = new Map(ENTRIES.map((e) => [e.prefix, e]));
|
|
159704
|
+
function getProductTypeEntryByByte(byte) {
|
|
159705
|
+
return BY_BYTE.get(byte);
|
|
159706
|
+
}
|
|
159707
|
+
function getProductTypeEntryByPrefix(prefix) {
|
|
159708
|
+
return BY_PREFIX.get(prefix);
|
|
159709
|
+
}
|
|
159710
|
+
|
|
159711
|
+
// src/utils/devices/device-product-code/registry/functionalKeywordRegistry.ts
|
|
159712
|
+
var FUNCTIONAL_KEYWORDS = [
|
|
159713
|
+
"COMPRESSOR",
|
|
159714
|
+
"VENTILADOR",
|
|
159715
|
+
"ESCADA_ROLANTE",
|
|
159716
|
+
"ELEVADOR",
|
|
159717
|
+
"MOTOR",
|
|
159718
|
+
"RELOGIO",
|
|
159719
|
+
"ENTRADA",
|
|
159720
|
+
"3F_MEDIDOR",
|
|
159721
|
+
"HIDROMETRO",
|
|
159722
|
+
"CAIXA_DAGUA",
|
|
159723
|
+
"TANK",
|
|
159724
|
+
"SELETOR_AUTO_MANUAL",
|
|
159725
|
+
"TERMOSTATO",
|
|
159726
|
+
"SOLENOIDE",
|
|
159727
|
+
"GLOBAL_AUTOMACAO",
|
|
159728
|
+
"CONTROLE REMOTO"
|
|
159729
|
+
];
|
|
159730
|
+
function isFunctionalKeywordPrefix(prefix) {
|
|
159731
|
+
return FUNCTIONAL_KEYWORDS.includes(prefix.toUpperCase());
|
|
159732
|
+
}
|
|
159733
|
+
|
|
159734
|
+
// src/utils/devices/device-product-code/name.ts
|
|
159735
|
+
var NAME_RE = /^([A-Z0-9]{2,12}) (\d{2})(\d{2})(\d{2})-(\d{4})$/;
|
|
159736
|
+
var UNKNOWN_PREFIX_RE = /^T(\d{1,3})$/;
|
|
159737
|
+
function prefixForProductType(productType) {
|
|
159738
|
+
const entry = getProductTypeEntryByByte(productType);
|
|
159739
|
+
if (entry) return entry.prefix;
|
|
159740
|
+
return `T${productType}`;
|
|
159741
|
+
}
|
|
159742
|
+
function pad24(n) {
|
|
159743
|
+
return String(n).padStart(2, "0");
|
|
159744
|
+
}
|
|
159745
|
+
function pad4(n) {
|
|
159746
|
+
return String(n).padStart(4, "0");
|
|
159747
|
+
}
|
|
159748
|
+
function deviceProductCodeToName(value) {
|
|
159749
|
+
const prefix = prefixForProductType(value.productType);
|
|
159750
|
+
const yy = pad24(value.year % 100);
|
|
159751
|
+
const mm = pad24(value.month);
|
|
159752
|
+
const dd = pad24(value.day);
|
|
159753
|
+
const unit = value.seq3 * 254 + value.seq;
|
|
159754
|
+
return `${prefix} ${yy}${mm}${dd}-${pad4(unit)}`;
|
|
159755
|
+
}
|
|
159756
|
+
function resolveProductType(prefix) {
|
|
159757
|
+
const entry = getProductTypeEntryByPrefix(prefix);
|
|
159758
|
+
if (entry) return entry.byte;
|
|
159759
|
+
if (isFunctionalKeywordPrefix(prefix)) {
|
|
159760
|
+
throw new DeviceProductCodeError(
|
|
159761
|
+
"non-invertible-prefix",
|
|
159762
|
+
`deviceNameToDeviceProductCode: "${prefix}" is a functional keyword, not a product-type prefix \u2014 the product-type byte cannot be derived from it alone`
|
|
159763
|
+
);
|
|
159764
|
+
}
|
|
159765
|
+
const fallbackMatch = UNKNOWN_PREFIX_RE.exec(prefix);
|
|
159766
|
+
if (fallbackMatch) {
|
|
159767
|
+
const byte = Number(fallbackMatch[1]);
|
|
159768
|
+
if (byte >= 0 && byte <= 255) return byte;
|
|
159769
|
+
}
|
|
159770
|
+
throw new DeviceProductCodeError("unknown-prefix", `deviceNameToDeviceProductCode: unrecognized prefix "${prefix}"`);
|
|
159771
|
+
}
|
|
159772
|
+
function deviceNameToDeviceProductCode(name) {
|
|
159773
|
+
const match = NAME_RE.exec(name);
|
|
159774
|
+
if (!match) {
|
|
159775
|
+
throw new DeviceProductCodeError("invalid-name-shape", `deviceNameToDeviceProductCode: does not match PREFIX YYMMDD-NNNN ("${name}")`);
|
|
159776
|
+
}
|
|
159777
|
+
const [, prefix, yy, mm, dd, nnnn] = match;
|
|
159778
|
+
const productType = resolveProductType(prefix);
|
|
159779
|
+
const unit = Number(nnnn);
|
|
159780
|
+
if (unit < 1 || unit > 2032) {
|
|
159781
|
+
throw new DeviceProductCodeError("unit-out-of-range", `deviceNameToDeviceProductCode: NNNN out of 0001-2032 ("${name}")`);
|
|
159782
|
+
}
|
|
159783
|
+
const seq3 = Math.floor((unit - 1) / 254);
|
|
159784
|
+
const seq = unit - seq3 * 254;
|
|
159785
|
+
const fields = {
|
|
159786
|
+
year: 2e3 + Number(yy),
|
|
159787
|
+
month: Number(mm),
|
|
159788
|
+
day: Number(dd),
|
|
159789
|
+
seq3,
|
|
159790
|
+
seq,
|
|
159791
|
+
productType
|
|
159792
|
+
};
|
|
159793
|
+
const reason = checkFields(fields);
|
|
159794
|
+
if (reason) {
|
|
159795
|
+
throw new DeviceProductCodeError(reason, `deviceNameToDeviceProductCode: ${reason} ("${name}" -> ${JSON.stringify(fields)})`);
|
|
159796
|
+
}
|
|
159797
|
+
return fields;
|
|
159798
|
+
}
|
|
159799
|
+
function validateDeviceProductName(name) {
|
|
159800
|
+
try {
|
|
159801
|
+
deviceNameToDeviceProductCode(name);
|
|
159802
|
+
return { valid: true };
|
|
159803
|
+
} catch (e) {
|
|
159804
|
+
if (e instanceof DeviceProductCodeError) {
|
|
159805
|
+
return { valid: false, reason: e.reason, message: e.message };
|
|
159806
|
+
}
|
|
159807
|
+
throw e;
|
|
159808
|
+
}
|
|
159809
|
+
}
|
|
159810
|
+
|
|
159811
|
+
// src/utils/devices/device-product-code/index.ts
|
|
159812
|
+
function encodeDeviceProductCode(fields) {
|
|
159813
|
+
const codec = resolveCodec();
|
|
159814
|
+
return codec.decode(codec.encode(fields));
|
|
159815
|
+
}
|
|
159816
|
+
function decodeDeviceProductCode(code) {
|
|
159817
|
+
return resolveCodec().decode(code);
|
|
159818
|
+
}
|
|
159819
|
+
function formatDeviceProductCode(value) {
|
|
159820
|
+
return resolveCodec().encode(value);
|
|
159821
|
+
}
|
|
159822
|
+
function validateDeviceProductCode(code) {
|
|
159823
|
+
return resolveCodec().validate(code);
|
|
159824
|
+
}
|
|
159825
|
+
|
|
159579
159826
|
// src/index.ts
|
|
159580
159827
|
var version = package_default.version || "0.0.0";
|
|
159581
159828
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -160043,6 +160290,7 @@ var version = package_default.version || "0.0.0";
|
|
|
160043
160290
|
csvEscapeAnnotation,
|
|
160044
160291
|
daysInMonthYear,
|
|
160045
160292
|
decimalStringToCents,
|
|
160293
|
+
decodeDeviceProductCode,
|
|
160046
160294
|
decodePayload,
|
|
160047
160295
|
decodePayloadBase64Xor,
|
|
160048
160296
|
deleteFreshdeskTicket,
|
|
@@ -160057,10 +160305,13 @@ var version = package_default.version || "0.0.0";
|
|
|
160057
160305
|
deviceCategoryToPanelTerm,
|
|
160058
160306
|
deviceIconLabels,
|
|
160059
160307
|
deviceIcons,
|
|
160308
|
+
deviceNameToDeviceProductCode,
|
|
160309
|
+
deviceProductCodeToName,
|
|
160060
160310
|
deviceStatusIcons,
|
|
160061
160311
|
deviceTypeToken,
|
|
160062
160312
|
doSchedulesOverlap,
|
|
160063
160313
|
downloadAnnotationsTextFile,
|
|
160314
|
+
encodeDeviceProductCode,
|
|
160064
160315
|
escapeAnnotationHtml,
|
|
160065
160316
|
evaluateDeviceStatus,
|
|
160066
160317
|
expandBandHours,
|
|
@@ -160104,6 +160355,7 @@ var version = package_default.version || "0.0.0";
|
|
|
160104
160355
|
formatDateToYMD,
|
|
160105
160356
|
formatDateWithTimezoneOffset,
|
|
160106
160357
|
formatDeltaPct,
|
|
160358
|
+
formatDeviceProductCode,
|
|
160107
160359
|
formatDuration,
|
|
160108
160360
|
formatEnergy,
|
|
160109
160361
|
formatHours,
|
|
@@ -160398,6 +160650,8 @@ var version = package_default.version || "0.0.0";
|
|
|
160398
160650
|
updateDeviceGridStats,
|
|
160399
160651
|
updateFreshdeskTicket,
|
|
160400
160652
|
upsertAlarmAnnotation,
|
|
160653
|
+
validateDeviceProductCode,
|
|
160654
|
+
validateDeviceProductName,
|
|
160401
160655
|
validateProfile,
|
|
160402
160656
|
validateTree,
|
|
160403
160657
|
version,
|