smart-home-engine 2.0.0 → 2.0.1

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.
@@ -1,4 +1,4 @@
1
- import{m as O}from"./monaco-langs-DKJI_o9J.js";import{t as I}from"./index-DLP7FxoS.js";/*!-----------------------------------------------------------------------------
1
+ import{m as O}from"./monaco-langs-DKJI_o9J.js";import{t as I}from"./index-CqYTIaIG.js";/*!-----------------------------------------------------------------------------
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Version: 0.52.2(404545bded1df6ffa41ea0af4e8ddb219018c6c1)
4
4
  * Released under the MIT license
@@ -175,10 +175,10 @@
175
175
  }
176
176
  })();
177
177
  </script>
178
- <script type="module" crossorigin src="/assets/index-DLP7FxoS.js"></script>
178
+ <script type="module" crossorigin src="/assets/index-CqYTIaIG.js"></script>
179
179
  <link rel="modulepreload" crossorigin href="/assets/monaco-langs-DKJI_o9J.js">
180
180
  <link rel="stylesheet" crossorigin href="/assets/monaco-langs-DyX1CsEw.css">
181
- <link rel="stylesheet" crossorigin href="/assets/index-Bvl2uECO.css">
181
+ <link rel="stylesheet" crossorigin href="/assets/index-Bu5o4KXc.css">
182
182
  </head>
183
183
  <body>
184
184
  <div id="app"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-home-engine",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Node.js based script runner for use in MQTT based Smart Home environments",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -46,6 +46,39 @@ function hex64(v) {
46
46
  }
47
47
 
48
48
  /** LQI 3 → good, 2 → fair, 1 → poor, 0 → unknown (the OHF dashboard's colours). */
49
+ /**
50
+ * A Thread partition ID as 8 hex digits: devices report it as a number (Network Diagnostics), border
51
+ * routers as hex (their mDNS TXT record) — the same partition must compare equal.
52
+ */
53
+ function partitionHex(v) {
54
+ if (typeof v === 'number' || typeof v === 'bigint' || /^\d+$/.test(String(v))) {
55
+ return Number(v).toString(16).toUpperCase().padStart(8, '0');
56
+ }
57
+ return String(v).toUpperCase().padStart(8, '0');
58
+ }
59
+
60
+ /** an IPv6 address from 16 bytes (a Uint8Array or `{ $bytes }`) in the usual short form */
61
+ function _ipv6(v) {
62
+ const buf = v?.$bytes ? Buffer.from(v.$bytes, 'hex') : ArrayBuffer.isView(v) ? Buffer.from(v.buffer, v.byteOffset, v.byteLength) : null;
63
+ if (!buf || buf.length !== 16) return null;
64
+ const groups = [];
65
+ for (let i = 0; i < 16; i += 2) groups.push(buf.readUInt16BE(i).toString(16));
66
+ // the longest run of two or more zero groups becomes "::"
67
+ let best = { at: -1, len: 0 };
68
+ for (let i = 0; i < 8; ) {
69
+ if (groups[i] !== '0') {
70
+ i++;
71
+ continue;
72
+ }
73
+ let j = i;
74
+ while (j < 8 && groups[j] === '0') j++;
75
+ if (j - i > best.len && j - i > 1) best = { at: i, len: j - i };
76
+ i = j;
77
+ }
78
+ if (best.at < 0) return groups.join(':');
79
+ return `${groups.slice(0, best.at).join(':')}::${groups.slice(best.at + best.len).join(':')}`;
80
+ }
81
+
49
82
  function lqiQuality(lqi) {
50
83
  if (lqi >= 3) return 'good';
51
84
  if (lqi === 2) return 'fair';
@@ -68,7 +101,7 @@ function rssiQuality(rssi) {
68
101
  * @param {object[]} bindings M13's binding list
69
102
  */
70
103
  function buildGraph(nodes, borderRouters = [], bindings = [], meshcop = []) {
71
- const out = { nodes: [], links: [], networks: [], meshcop: { nodes: 0, networks: [] } };
104
+ const out = { nodes: [], links: [], networks: [], hidden: { wifi: 0, other: 0 }, meshcop: { nodes: 0, networks: [] } };
72
105
  const byExt = new Map();
73
106
  const byRloc = new Map();
74
107
  const byNodeId = new Map();
@@ -87,6 +120,8 @@ function buildGraph(nodes, borderRouters = [], bindings = [], meshcop = []) {
87
120
  channel: n.thread.channel ?? null,
88
121
  partitionId: n.thread.partitionId ?? null,
89
122
  networkName: n.thread.networkName ?? null,
123
+ extPanId: n.thread.extPanId ?? null,
124
+ ipv6: n.thread.ipv6 ?? [],
90
125
  neighbours: (n.thread.neighborTable ?? []).length,
91
126
  routes: (n.thread.routeTable ?? []).length,
92
127
  readAt: n.thread.readAt ?? null,
@@ -95,23 +130,10 @@ function buildGraph(nodes, borderRouters = [], bindings = [], meshcop = []) {
95
130
  byNodeId.set(n.nodeId, g);
96
131
  if (g.extAddress) byExt.set(g.extAddress, g);
97
132
  if (g.rloc16 !== null) byRloc.set(g.rloc16, g);
98
- } else if (n.wifi) {
99
- const g = {
100
- id: `node:${n.nodeId}`,
101
- kind: 'wifi',
102
- nodeId: n.nodeId,
103
- name: n.name ?? `Node ${n.nodeId}`,
104
- online: !!n.online,
105
- rssi: n.wifi.rssi ?? null,
106
- bssid: n.wifi.bssid ?? null,
107
- quality: rssiQuality(n.wifi.rssi),
108
- };
109
- out.nodes.push(g);
110
- byNodeId.set(n.nodeId, g);
111
133
  } else {
112
- const g = { id: `node:${n.nodeId}`, kind: 'other', nodeId: n.nodeId, name: n.name ?? `Node ${n.nodeId}`, online: !!n.online };
113
- out.nodes.push(g);
114
- byNodeId.set(n.nodeId, g);
134
+ // Wi-Fi, Ethernet and devices without diagnostics (offline, bridges) are not part of a Thread
135
+ // mesh: counted for the caption, not drawn
136
+ out.hidden[n.wifi ? 'wifi' : 'other']++;
115
137
  }
116
138
  }
117
139
  // border routers: a graph node each, joined to a fabric node when the ext address matches
@@ -133,6 +155,9 @@ function buildGraph(nodes, borderRouters = [], bindings = [], meshcop = []) {
133
155
  role: br.state?.role ?? null,
134
156
  online: true,
135
157
  rloc16: br.rest?.node?.rloc16 ?? null,
158
+ partitionId: br.partitionId ?? null,
159
+ addresses: br.addresses ?? [],
160
+ hostname: br.hostname ?? null,
136
161
  };
137
162
  out.nodes.push(g);
138
163
  if (ext) byExt.set(ext, g);
@@ -158,6 +183,8 @@ function buildGraph(nodes, borderRouters = [], bindings = [], meshcop = []) {
158
183
  extAddress: ext,
159
184
  rloc16: nb.rloc16 ?? null,
160
185
  online: true,
186
+ networkName: from.networkName ?? null,
187
+ extPanId: from.extPanId ?? null,
161
188
  explanation:
162
189
  "A neighbour of a device in she's fabric that is not paired with she: a device of another controller, or a border router that announces a different address.",
163
190
  };
@@ -263,14 +290,24 @@ function buildGraph(nodes, borderRouters = [], bindings = [], meshcop = []) {
263
290
  label: `${b.sourceEndpointName ?? 'ep ' + b.sourceEndpointId} → ${b.targetEndpointName ?? 'ep ' + b.endpoint}`,
264
291
  });
265
292
  }
266
- // networks seen in the fabric
293
+ // the Thread networks: every drawn node belongs to one (by extended PAN ID, else by name), so the graph
294
+ // can group them — border routers of the same network included, whether in she's fabric or not
267
295
  const nets = new Map();
268
296
  for (const g of out.nodes) {
269
- if (g.kind !== 'thread' || !g.networkName) continue;
270
- const n = nets.get(g.networkName) ?? { networkName: g.networkName, partitions: new Set(), channel: g.channel ?? null, nodes: 0 };
271
- if (g.partitionId !== null && g.partitionId !== undefined) n.partitions.add(String(g.partitionId));
297
+ if (!g.extPanId && g.networkName) {
298
+ const byName = out.nodes.find((x) => x.extPanId && x.networkName === g.networkName);
299
+ if (byName) g.extPanId = byName.extPanId;
300
+ }
301
+ const key = g.extPanId || (g.networkName ? `name:${g.networkName}` : null);
302
+ if (!key) continue;
303
+ g.network = key;
304
+ const n = nets.get(key) ?? { key, extPanId: g.extPanId ?? null, networkName: g.networkName ?? null, partitions: new Set(), channel: null, nodes: 0, borderRouters: 0 };
305
+ if (!n.networkName && g.networkName) n.networkName = g.networkName;
306
+ if (g.partitionId !== null && g.partitionId !== undefined) n.partitions.add(partitionHex(g.partitionId));
307
+ if (n.channel === null && g.channel !== null && g.channel !== undefined) n.channel = g.channel;
272
308
  n.nodes++;
273
- nets.set(g.networkName, n);
309
+ if (g.kind === 'border-router' || g.borderRouter) n.borderRouters++;
310
+ nets.set(key, n);
274
311
  }
275
312
  out.networks = [...nets.values()].map((n) => ({ ...n, partitions: [...n.partitions] }));
276
313
  return out;
@@ -297,6 +334,8 @@ function _nodeData(node, nodeId) {
297
334
  partitionId: tnd.partitionId === undefined || tnd.partitionId === null ? null : Number(tnd.partitionId),
298
335
  leaderRouterId: tnd.leaderRouterId ?? null,
299
336
  networkName: tnd.networkName ?? null,
337
+ extPanId: hex64(tnd.extendedPanId),
338
+ ipv6: (threadIf?.iPv6Addresses ?? []).map(_ipv6).filter(Boolean),
300
339
  neighborTable: fresh?.neighborTable ?? (Array.isArray(tnd.neighborTable) ? tnd.neighborTable : []),
301
340
  routeTable: fresh?.routeTable ?? (Array.isArray(tnd.routeTable) ? tnd.routeTable : []),
302
341
  readAt: fresh?.readAt ?? null,
@@ -493,4 +532,4 @@ function stop() {
493
532
  _lastRefresh = null;
494
533
  }
495
534
 
496
- module.exports = { buildGraph, getMesh, refresh, meshcopQuery, start, stop, hex64, lqiQuality, rssiQuality, ROLES, _tables, _meshcop, _meshcopNode };
535
+ module.exports = { _ipv6, partitionHex, buildGraph, getMesh, refresh, meshcopQuery, start, stop, hex64, lqiQuality, rssiQuality, ROLES, _tables, _meshcop, _meshcopNode };