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 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.534",
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,