smart-home-engine 1.20.1 → 1.20.2
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/dist/web/assets/index-C31bVgVn.js +234 -0
- package/dist/web/assets/{tsMode-BNcOSQVq.js → tsMode-wYM5jYsY.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/src/index.js +22 -0
- package/src/lib/redis.js +7 -0
- package/src/lib/state-store.js +16 -0
- package/src/matter/controller.js +54 -1
- package/src/web/mqtt-api.js +8 -1
- package/dist/web/assets/index-DxR31sxM.js +0 -234
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{m as O}from"./monaco-langs-Dt8a9eeo.js";import{t as I}from"./index-
|
|
1
|
+
import{m as O}from"./monaco-langs-Dt8a9eeo.js";import{t as I}from"./index-C31bVgVn.js";/*!-----------------------------------------------------------------------------
|
|
2
2
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
3
|
* Version: 0.52.2(404545bded1df6ffa41ea0af4e8ddb219018c6c1)
|
|
4
4
|
* Released under the MIT license
|
package/dist/web/index.html
CHANGED
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
}
|
|
173
173
|
})();
|
|
174
174
|
</script>
|
|
175
|
-
<script type="module" crossorigin src="/assets/index-
|
|
175
|
+
<script type="module" crossorigin src="/assets/index-C31bVgVn.js"></script>
|
|
176
176
|
<link rel="modulepreload" crossorigin href="/assets/monaco-langs-Dt8a9eeo.js">
|
|
177
177
|
<link rel="stylesheet" crossorigin href="/assets/monaco-langs-DyX1CsEw.css">
|
|
178
178
|
<link rel="stylesheet" crossorigin href="/assets/index-CjRDTePT.css">
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -498,6 +498,28 @@ if (config.url) {
|
|
|
498
498
|
|
|
499
499
|
if (shedb.handleMqttMessage(topic, payload)) return;
|
|
500
500
|
|
|
501
|
+
// An empty payload deletes the topic from the state store (mqtt-smarthome
|
|
502
|
+
// convention; also how a retained message is cleared per MQTT spec). The
|
|
503
|
+
// retain flag cannot be used to detect this: MQTT 3.1.1 brokers clear it
|
|
504
|
+
// when forwarding to established subscriptions, so a retained-clear
|
|
505
|
+
// arrives here as a plain empty message. Previously an empty payload was
|
|
506
|
+
// stored as { val: NaN }, so the MQTT tab kept showing cleared topics.
|
|
507
|
+
// Subscription callbacks are still dispatched (empty publishes are also
|
|
508
|
+
// used as plain triggers) with val '' — only the stored state is dropped.
|
|
509
|
+
if (payload.length === 0) {
|
|
510
|
+
const emptyOld = store.getObject('mqtt::' + topic) || {};
|
|
511
|
+
if (store.delete('mqtt::' + topic)) {
|
|
512
|
+
log.debug('empty payload, removed topic from state store:', topic);
|
|
513
|
+
}
|
|
514
|
+
const delArr = topic.split('/');
|
|
515
|
+
if (delArr[0] === config.variablePrefix && delArr[1] === 'status') {
|
|
516
|
+
store.delete('var::' + delArr.slice(2).join('/'));
|
|
517
|
+
}
|
|
518
|
+
const ts = new Date().getTime();
|
|
519
|
+
stateChange(topic, { val: '', ts, lc: ts }, emptyOld, msg);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
|
|
501
523
|
const state = require('./lib/parse-payload')(payload);
|
|
502
524
|
|
|
503
525
|
const topicArr = topic.split('/');
|
package/src/lib/redis.js
CHANGED
|
@@ -63,6 +63,13 @@ async function init({ url, store, log }) {
|
|
|
63
63
|
});
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
+
// Write-through: StateStore deletions (e.g. cleared retained messages) → hdel
|
|
67
|
+
store.on('delete', (key) => {
|
|
68
|
+
client.hdel('she:state', key).catch((err) => {
|
|
69
|
+
log.error('redis: hdel failed:', err.message);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
66
73
|
log.info('redis: connected', url);
|
|
67
74
|
}
|
|
68
75
|
|
package/src/lib/state-store.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Events:
|
|
12
12
|
* 'change' (key, val, obj, prevObj)
|
|
13
|
+
* 'delete' (key, prevObj)
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
const { EventEmitter } = require('events');
|
|
@@ -47,6 +48,21 @@ class StateStore extends EventEmitter {
|
|
|
47
48
|
this.emit('change', key, obj.val, obj, prev);
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Remove a key from the store (e.g. when a retained MQTT message is
|
|
53
|
+
* deleted via an empty retained publish). Emits 'delete' (key, prevObj)
|
|
54
|
+
* when the key existed; a no-op otherwise.
|
|
55
|
+
* @param {string} key
|
|
56
|
+
* @returns {boolean} true when the key existed and was removed
|
|
57
|
+
*/
|
|
58
|
+
delete(key) {
|
|
59
|
+
const prev = this._map.get(key);
|
|
60
|
+
if (prev === undefined) return false;
|
|
61
|
+
this._map.delete(key);
|
|
62
|
+
this.emit('delete', key, prev);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
50
66
|
/**
|
|
51
67
|
* @param {string} key
|
|
52
68
|
* @returns {*} the val of the stored state, or undefined
|
package/src/matter/controller.js
CHANGED
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
* All nodeIds are exposed as decimal strings (BigInt serialization boundary).
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
const { Environment, ServerNode, ControllerBehavior } = require('@matter/main');
|
|
12
|
+
const { Environment, ServerNode, ControllerBehavior, Logger, LogLevel, LogFormat } = require('@matter/main');
|
|
13
|
+
|
|
14
|
+
// Upper bound for a commissioning attempt — matter.js has internal retries that
|
|
15
|
+
// can keep an HTTP request (and the pairing wizard) hanging for a very long time.
|
|
16
|
+
const COMMISSION_TIMEOUT_MS = 120000;
|
|
13
17
|
|
|
14
18
|
/** @type {import('@matter/main').ServerNode | null} */
|
|
15
19
|
let _server = null;
|
|
@@ -105,6 +109,17 @@ async function init(storagePath, log, broadcastFn) {
|
|
|
105
109
|
if (_server) throw new Error('Matter controller already started');
|
|
106
110
|
_log = log;
|
|
107
111
|
|
|
112
|
+
// Route matter.js's own diagnostics into the daemon logger so commissioning
|
|
113
|
+
// problems (discovery, PASE failures, retries) are visible in the Logs tab —
|
|
114
|
+
// by default matter.js writes straight to stdout, bypassing she's logging.
|
|
115
|
+
Logger.format = LogFormat.PLAIN;
|
|
116
|
+
Logger.level = LogLevel.INFO;
|
|
117
|
+
Logger.destinations.default.write = (text, message) => {
|
|
118
|
+
if (message.level >= LogLevel.ERROR) _log.error('matter.js:', text);
|
|
119
|
+
else if (message.level === LogLevel.WARN) _log.warn('matter.js:', text);
|
|
120
|
+
else _log.debug('matter.js:', text);
|
|
121
|
+
};
|
|
122
|
+
|
|
108
123
|
// Configure storage before anything else touches StorageService
|
|
109
124
|
Environment.default.vars.set('storage.path', storagePath);
|
|
110
125
|
|
|
@@ -240,6 +255,44 @@ function listPaired() {
|
|
|
240
255
|
*/
|
|
241
256
|
async function commission(options) {
|
|
242
257
|
if (!_server) throw new Error('Matter controller not started');
|
|
258
|
+
|
|
259
|
+
// Accept QR-code payloads ("MT:...") — matter.js's pairingCode option only
|
|
260
|
+
// decodes 11/21-digit manual pairing codes, so decode QR payloads here.
|
|
261
|
+
if (typeof options.pairingCode === 'string' && options.pairingCode.trim().toUpperCase().startsWith('MT:')) {
|
|
262
|
+
const { QrPairingCodeCodec } = require('@matter/types');
|
|
263
|
+
const [decoded] = QrPairingCodeCodec.decode(options.pairingCode.trim());
|
|
264
|
+
const { pairingCode: _pc, ...rest } = options;
|
|
265
|
+
options = { ...rest, passcode: decoded.passcode, discriminator: decoded.discriminator };
|
|
266
|
+
_log.debug('matter: decoded QR pairing code, discriminator', decoded.discriminator);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const label = options.pairingCode !== undefined ? 'pairing code' : `passcode${options.discriminator !== undefined ? ' + discriminator ' + options.discriminator : ''}`;
|
|
270
|
+
_log.info(`matter: commissioning start (${label}${options.discoveryAddress ? ', direct address ' + options.discoveryAddress : ''})`);
|
|
271
|
+
let timer;
|
|
272
|
+
try {
|
|
273
|
+
const nodeId = await Promise.race([
|
|
274
|
+
_commissionInner(options),
|
|
275
|
+
new Promise((_resolve, reject) => {
|
|
276
|
+
timer = setTimeout(
|
|
277
|
+
() =>
|
|
278
|
+
reject(
|
|
279
|
+
new Error(`commissioning timed out after ${COMMISSION_TIMEOUT_MS / 1000} s — check that the pairing window is still open and the device is reachable`),
|
|
280
|
+
),
|
|
281
|
+
COMMISSION_TIMEOUT_MS,
|
|
282
|
+
);
|
|
283
|
+
}),
|
|
284
|
+
]);
|
|
285
|
+
_log.info('matter: commissioning succeeded, nodeId', nodeId);
|
|
286
|
+
return nodeId;
|
|
287
|
+
} catch (err) {
|
|
288
|
+
_log.error('matter: commissioning failed:', err.message);
|
|
289
|
+
throw err;
|
|
290
|
+
} finally {
|
|
291
|
+
clearTimeout(timer);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function _commissionInner(options) {
|
|
243
296
|
const { discoveryAddress, ...commissionOpts } = options;
|
|
244
297
|
let clientNode;
|
|
245
298
|
if (discoveryAddress) {
|
package/src/web/mqtt-api.js
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
* (or null when no broker is configured).
|
|
13
13
|
*
|
|
14
14
|
* State changes for mqtt:: keys are automatically broadcast to WebSocket clients
|
|
15
|
-
* as { type: 'mqtt', topic, val, ts }
|
|
15
|
+
* as { type: 'mqtt', topic, val, ts }; deletions (retained message cleared via
|
|
16
|
+
* an empty retained publish) as { type: 'mqtt', topic, deleted: true, ts }.
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
const express = require('express');
|
|
@@ -43,6 +44,12 @@ function init(store, getMqttClient) {
|
|
|
43
44
|
broadcastBrokerLog(topic.slice('$SYS/broker/log/'.length), String(obj.val), obj.ts);
|
|
44
45
|
}
|
|
45
46
|
});
|
|
47
|
+
|
|
48
|
+
// Forward deletions (retained message cleared) so clients drop the topic
|
|
49
|
+
store.on('delete', (key) => {
|
|
50
|
+
if (!key.startsWith('mqtt::')) return;
|
|
51
|
+
broadcast({ type: 'mqtt', topic: key.slice(6), deleted: true, ts: Date.now() });
|
|
52
|
+
});
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
// GET /she/mqtt/state — sorted snapshot of all retained MQTT topic values
|