zwave-js 11.0.0 → 11.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/build/lib/controller/Controller.d.ts +86 -3
- package/build/lib/controller/Controller.d.ts.map +1 -1
- package/build/lib/controller/Controller.js +391 -164
- package/build/lib/controller/Controller.js.map +1 -1
- package/build/lib/controller/Features.js +1 -1
- package/build/lib/controller/Features.js.map +1 -1
- package/build/lib/controller/Inclusion.js +6 -6
- package/build/lib/controller/Inclusion.js.map +1 -1
- package/build/lib/controller/MockControllerBehaviors.d.ts.map +1 -1
- package/build/lib/controller/MockControllerBehaviors.js +3 -2
- package/build/lib/controller/MockControllerBehaviors.js.map +1 -1
- package/build/lib/controller/MockControllerState.js +2 -2
- package/build/lib/controller/MockControllerState.js.map +1 -1
- package/build/lib/controller/NodeInformationFrame.d.ts.map +1 -1
- package/build/lib/controller/NodeInformationFrame.js +6 -1
- package/build/lib/controller/NodeInformationFrame.js.map +1 -1
- package/build/lib/controller/ZWaveSDKVersions.js +1 -1
- package/build/lib/controller/ZWaveSDKVersions.js.map +1 -1
- package/build/lib/controller/_Types.js +1 -1
- package/build/lib/controller/_Types.js.map +1 -1
- package/build/lib/driver/Bootloader.js +1 -1
- package/build/lib/driver/Bootloader.js.map +1 -1
- package/build/lib/driver/Driver.d.ts +44 -12
- package/build/lib/driver/Driver.d.ts.map +1 -1
- package/build/lib/driver/Driver.js +495 -207
- package/build/lib/driver/Driver.js.map +1 -1
- package/build/lib/driver/MessageGenerators.d.ts.map +1 -1
- package/build/lib/driver/MessageGenerators.js +4 -11
- package/build/lib/driver/MessageGenerators.js.map +1 -1
- package/build/lib/driver/NetworkCache.d.ts +5 -0
- package/build/lib/driver/NetworkCache.d.ts.map +1 -1
- package/build/lib/driver/NetworkCache.js +5 -0
- package/build/lib/driver/NetworkCache.js.map +1 -1
- package/build/lib/driver/SerialAPICommandMachine.d.ts +17 -11
- package/build/lib/driver/SerialAPICommandMachine.d.ts.map +1 -1
- package/build/lib/driver/SerialAPICommandMachine.js +16 -6
- package/build/lib/driver/SerialAPICommandMachine.js.map +1 -1
- package/build/lib/driver/StateMachineShared.d.ts +27 -52
- package/build/lib/driver/StateMachineShared.d.ts.map +1 -1
- package/build/lib/driver/StateMachineShared.js +16 -48
- package/build/lib/driver/StateMachineShared.js.map +1 -1
- package/build/lib/node/Node.d.ts +11 -0
- package/build/lib/node/Node.d.ts.map +1 -1
- package/build/lib/node/Node.js +223 -8
- package/build/lib/node/Node.js.map +1 -1
- package/build/lib/node/NodeReadyMachine.d.ts +2 -2
- package/build/lib/node/NodeReadyMachine.d.ts.map +1 -1
- package/build/lib/node/NodeReadyMachine.js.map +1 -1
- package/build/lib/node/NodeStatusMachine.d.ts +2 -2
- package/build/lib/node/NodeStatusMachine.d.ts.map +1 -1
- package/build/lib/node/NodeStatusMachine.js.map +1 -1
- package/build/lib/serialapi/_Types.js +1 -1
- package/build/lib/serialapi/_Types.js.map +1 -1
- package/build/lib/serialapi/application/ApplicationCommandRequest.js +3 -4
- package/build/lib/serialapi/application/ApplicationCommandRequest.js.map +1 -1
- package/build/lib/serialapi/application/ApplicationUpdateRequest.js +13 -21
- package/build/lib/serialapi/application/ApplicationUpdateRequest.js.map +1 -1
- package/build/lib/serialapi/application/BridgeApplicationCommandRequest.js +2 -3
- package/build/lib/serialapi/application/BridgeApplicationCommandRequest.js.map +1 -1
- package/build/lib/serialapi/application/SerialAPIStartedRequest.js +3 -4
- package/build/lib/serialapi/application/SerialAPIStartedRequest.js.map +1 -1
- package/build/lib/serialapi/application/ShutdownMessages.js +4 -6
- package/build/lib/serialapi/application/ShutdownMessages.js.map +1 -1
- package/build/lib/serialapi/capability/GetControllerCapabilitiesMessages.js +4 -6
- package/build/lib/serialapi/capability/GetControllerCapabilitiesMessages.js.map +1 -1
- package/build/lib/serialapi/capability/GetControllerVersionMessages.js +4 -6
- package/build/lib/serialapi/capability/GetControllerVersionMessages.js.map +1 -1
- package/build/lib/serialapi/capability/GetProtocolVersionMessages.js +4 -6
- package/build/lib/serialapi/capability/GetProtocolVersionMessages.js.map +1 -1
- package/build/lib/serialapi/capability/GetSerialApiCapabilitiesMessages.js +4 -6
- package/build/lib/serialapi/capability/GetSerialApiCapabilitiesMessages.js.map +1 -1
- package/build/lib/serialapi/capability/GetSerialApiInitDataMessages.js +4 -6
- package/build/lib/serialapi/capability/GetSerialApiInitDataMessages.js.map +1 -1
- package/build/lib/serialapi/capability/HardResetRequest.js +4 -6
- package/build/lib/serialapi/capability/HardResetRequest.js.map +1 -1
- package/build/lib/serialapi/capability/SerialAPISetupMessages.js +59 -88
- package/build/lib/serialapi/capability/SerialAPISetupMessages.js.map +1 -1
- package/build/lib/serialapi/capability/SetApplicationNodeInformationRequest.js +2 -3
- package/build/lib/serialapi/capability/SetApplicationNodeInformationRequest.js.map +1 -1
- package/build/lib/serialapi/memory/GetControllerIdMessages.js +4 -6
- package/build/lib/serialapi/memory/GetControllerIdMessages.js.map +1 -1
- package/build/lib/serialapi/misc/GetBackgroundRSSIMessages.js +4 -6
- package/build/lib/serialapi/misc/GetBackgroundRSSIMessages.js.map +1 -1
- package/build/lib/serialapi/misc/SetRFReceiveModeMessages.js +4 -6
- package/build/lib/serialapi/misc/SetRFReceiveModeMessages.js.map +1 -1
- package/build/lib/serialapi/misc/SetSerialApiTimeoutsMessages.js +4 -6
- package/build/lib/serialapi/misc/SetSerialApiTimeoutsMessages.js.map +1 -1
- package/build/lib/serialapi/misc/SoftResetRequest.js +2 -3
- package/build/lib/serialapi/misc/SoftResetRequest.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/AddNodeToNetworkRequest.js +6 -8
- package/build/lib/serialapi/network-mgmt/AddNodeToNetworkRequest.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/AssignPriorityReturnRouteMessages.js +6 -9
- package/build/lib/serialapi/network-mgmt/AssignPriorityReturnRouteMessages.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages.js +6 -9
- package/build/lib/serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/AssignReturnRouteMessages.js +6 -9
- package/build/lib/serialapi/network-mgmt/AssignReturnRouteMessages.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/AssignSUCReturnRouteMessages.js +6 -9
- package/build/lib/serialapi/network-mgmt/AssignSUCReturnRouteMessages.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/DeleteReturnRouteMessages.js +6 -9
- package/build/lib/serialapi/network-mgmt/DeleteReturnRouteMessages.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.js +6 -9
- package/build/lib/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/GetNodeProtocolInfoMessages.js +4 -6
- package/build/lib/serialapi/network-mgmt/GetNodeProtocolInfoMessages.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/GetPriorityRouteMessages.js +4 -6
- package/build/lib/serialapi/network-mgmt/GetPriorityRouteMessages.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/GetRoutingInfoMessages.js +4 -6
- package/build/lib/serialapi/network-mgmt/GetRoutingInfoMessages.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/GetSUCNodeIdMessages.js +4 -6
- package/build/lib/serialapi/network-mgmt/GetSUCNodeIdMessages.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/IsFailedNodeMessages.js +4 -6
- package/build/lib/serialapi/network-mgmt/IsFailedNodeMessages.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/RemoveFailedNodeMessages.js +8 -11
- package/build/lib/serialapi/network-mgmt/RemoveFailedNodeMessages.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/RemoveNodeFromNetworkRequest.js +6 -8
- package/build/lib/serialapi/network-mgmt/RemoveNodeFromNetworkRequest.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/ReplaceFailedNodeRequest.js +8 -11
- package/build/lib/serialapi/network-mgmt/ReplaceFailedNodeRequest.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/RequestNodeInfoMessages.js +4 -6
- package/build/lib/serialapi/network-mgmt/RequestNodeInfoMessages.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/RequestNodeNeighborUpdateMessages.js +5 -7
- package/build/lib/serialapi/network-mgmt/RequestNodeNeighborUpdateMessages.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/SetPriorityRouteMessages.js +4 -6
- package/build/lib/serialapi/network-mgmt/SetPriorityRouteMessages.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/SetSUCNodeIDMessages.js +7 -10
- package/build/lib/serialapi/network-mgmt/SetSUCNodeIDMessages.js.map +1 -1
- package/build/lib/serialapi/nvm/ExtNVMReadLongBufferMessages.js +4 -6
- package/build/lib/serialapi/nvm/ExtNVMReadLongBufferMessages.js.map +1 -1
- package/build/lib/serialapi/nvm/ExtNVMReadLongByteMessages.js +4 -6
- package/build/lib/serialapi/nvm/ExtNVMReadLongByteMessages.js.map +1 -1
- package/build/lib/serialapi/nvm/ExtNVMWriteLongBufferMessages.js +4 -6
- package/build/lib/serialapi/nvm/ExtNVMWriteLongBufferMessages.js.map +1 -1
- package/build/lib/serialapi/nvm/ExtNVMWriteLongByteMessages.js +4 -6
- package/build/lib/serialapi/nvm/ExtNVMWriteLongByteMessages.js.map +1 -1
- package/build/lib/serialapi/nvm/FirmwareUpdateNVMMessages.js +29 -43
- package/build/lib/serialapi/nvm/FirmwareUpdateNVMMessages.js.map +1 -1
- package/build/lib/serialapi/nvm/GetNVMIdMessages.js +6 -8
- package/build/lib/serialapi/nvm/GetNVMIdMessages.js.map +1 -1
- package/build/lib/serialapi/nvm/NVMOperationsMessages.js +6 -8
- package/build/lib/serialapi/nvm/NVMOperationsMessages.js.map +1 -1
- package/build/lib/serialapi/transport/SendDataBridgeMessages.js +12 -18
- package/build/lib/serialapi/transport/SendDataBridgeMessages.js.map +1 -1
- package/build/lib/serialapi/transport/SendDataMessages.js +14 -21
- package/build/lib/serialapi/transport/SendDataMessages.js.map +1 -1
- package/package.json +17 -16
- package/build/lib/driver/CommandQueueMachine.d.ts +0 -60
- package/build/lib/driver/CommandQueueMachine.d.ts.map +0 -1
- package/build/lib/driver/CommandQueueMachine.js +0 -259
- package/build/lib/driver/CommandQueueMachine.js.map +0 -1
- package/build/lib/driver/SendThreadMachine.d.ts +0 -97
- package/build/lib/driver/SendThreadMachine.d.ts.map +0 -1
- package/build/lib/driver/SendThreadMachine.js +0 -286
- package/build/lib/driver/SendThreadMachine.js.map +0 -1
- package/build/lib/driver/TransactionMachine.d.ts +0 -30
- package/build/lib/driver/TransactionMachine.d.ts.map +0 -1
- package/build/lib/driver/TransactionMachine.js +0 -250
- package/build/lib/driver/TransactionMachine.js.map +0 -1
|
@@ -36,6 +36,7 @@ const serial_1 = require("@zwave-js/serial");
|
|
|
36
36
|
const shared_1 = require("@zwave-js/shared");
|
|
37
37
|
const async_1 = require("alcalzone-shared/async");
|
|
38
38
|
const deferred_promise_1 = require("alcalzone-shared/deferred-promise");
|
|
39
|
+
const sorted_list_1 = require("alcalzone-shared/sorted-list");
|
|
39
40
|
const typeguards_1 = require("alcalzone-shared/typeguards");
|
|
40
41
|
const crypto_1 = require("crypto");
|
|
41
42
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
@@ -62,7 +63,8 @@ const statistics_1 = require("../telemetry/statistics");
|
|
|
62
63
|
const Bootloader_1 = require("./Bootloader");
|
|
63
64
|
const MessageGenerators_1 = require("./MessageGenerators");
|
|
64
65
|
const NetworkCache_1 = require("./NetworkCache");
|
|
65
|
-
const
|
|
66
|
+
const SerialAPICommandMachine_1 = require("./SerialAPICommandMachine");
|
|
67
|
+
const StateMachineShared_1 = require("./StateMachineShared");
|
|
66
68
|
const ThrottlePresets_1 = require("./ThrottlePresets");
|
|
67
69
|
const Transaction_1 = require("./Transaction");
|
|
68
70
|
const TransportServiceMachine_1 = require("./TransportServiceMachine");
|
|
@@ -188,13 +190,16 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
188
190
|
constructor(port, options) {
|
|
189
191
|
super();
|
|
190
192
|
this.port = port;
|
|
193
|
+
this.queuePaused = false;
|
|
191
194
|
/** A map of handlers for all sorts of requests */
|
|
192
195
|
this.requestHandlers = new Map();
|
|
193
|
-
/** A
|
|
196
|
+
/** A list of awaited message headers */
|
|
197
|
+
this.awaitedMessageHeaders = [];
|
|
198
|
+
/** A list of awaited messages */
|
|
194
199
|
this.awaitedMessages = [];
|
|
195
|
-
/** A
|
|
200
|
+
/** A list of awaited commands */
|
|
196
201
|
this.awaitedCommands = [];
|
|
197
|
-
/** A
|
|
202
|
+
/** A list of awaited chunks from the bootloader */
|
|
198
203
|
this.awaitedBootloaderChunks = [];
|
|
199
204
|
/** A map of Node ID -> ongoing sessions */
|
|
200
205
|
this.nodeSessions = new Map();
|
|
@@ -268,9 +273,7 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
268
273
|
* Returns the next session ID for Transport Service CC
|
|
269
274
|
*/
|
|
270
275
|
this.getNextTransportServiceSessionId = (0, shared_1.createWrappingCounter)(core_1.MAX_TRANSPORT_SERVICE_SESSION_ID, true);
|
|
271
|
-
this.
|
|
272
|
-
this.saveToCacheInterval = 150;
|
|
273
|
-
this.isSavingToCache = false;
|
|
276
|
+
this.drainQueueBusy = false;
|
|
274
277
|
this.sendNodeToSleepTimers = new Map();
|
|
275
278
|
this._enteringBootloader = false;
|
|
276
279
|
this.lastBackgroundRSSITimestamp = 0;
|
|
@@ -300,131 +303,8 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
300
303
|
logContainer: this._logContainer,
|
|
301
304
|
deviceConfigPriorityDir: this._options.storage.deviceConfigPriorityDir,
|
|
302
305
|
});
|
|
303
|
-
|
|
304
|
-
const sendThreadMachine = (0, SendThreadMachine_1.createSendThreadMachine)({
|
|
305
|
-
sendData: this.writeSerial.bind(this),
|
|
306
|
-
createSendDataAbort: () => new SendDataMessages_1.SendDataAbort(this),
|
|
307
|
-
notifyUnsolicited: (msg) => {
|
|
308
|
-
void this.handleUnsolicitedMessage(msg);
|
|
309
|
-
},
|
|
310
|
-
notifyRetry: (command, lastError, message, attempts, maxAttempts, delay) => {
|
|
311
|
-
if (command === "SendData") {
|
|
312
|
-
this.controllerLog.logNode(message.getNodeId() ?? 255, `did not respond after ${attempts}/${maxAttempts} attempts. Scheduling next try in ${delay} ms.`, "warn");
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
|
-
// Translate the error into a better one
|
|
316
|
-
let errorReason;
|
|
317
|
-
switch (lastError) {
|
|
318
|
-
case "response timeout":
|
|
319
|
-
errorReason = "No response from controller";
|
|
320
|
-
this._controller?.incrementStatistics("timeoutResponse");
|
|
321
|
-
break;
|
|
322
|
-
case "callback timeout":
|
|
323
|
-
errorReason = "No callback from controller";
|
|
324
|
-
this._controller?.incrementStatistics("timeoutCallback");
|
|
325
|
-
break;
|
|
326
|
-
case "response NOK":
|
|
327
|
-
errorReason =
|
|
328
|
-
"The controller response indicated failure";
|
|
329
|
-
break;
|
|
330
|
-
case "callback NOK":
|
|
331
|
-
errorReason =
|
|
332
|
-
"The controller callback indicated failure";
|
|
333
|
-
break;
|
|
334
|
-
case "ACK timeout":
|
|
335
|
-
this._controller?.incrementStatistics("timeoutACK");
|
|
336
|
-
// fall through
|
|
337
|
-
case "CAN":
|
|
338
|
-
case "NAK":
|
|
339
|
-
default:
|
|
340
|
-
errorReason =
|
|
341
|
-
"Failed to execute controller command";
|
|
342
|
-
break;
|
|
343
|
-
}
|
|
344
|
-
this.controllerLog.print(`${errorReason} after ${attempts}/${maxAttempts} attempts. Scheduling next try in ${delay} ms.`, "warn");
|
|
345
|
-
}
|
|
346
|
-
},
|
|
347
|
-
timestamp: core_1.highResTimestamp,
|
|
348
|
-
rejectTransaction: (transaction, error) => {
|
|
349
|
-
// If a node failed to respond in time, it might be sleeping
|
|
350
|
-
if (this.isMissingNodeACK(transaction, error)) {
|
|
351
|
-
if (this.handleMissingNodeACK(transaction))
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
// If the transaction was already started, we need to throw the error into the message generator
|
|
355
|
-
// so it correctly gets ended. Otherwise just reject the result promise
|
|
356
|
-
if (transaction.parts.self) {
|
|
357
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
358
|
-
transaction.parts.self.throw(error).catch(() => { });
|
|
359
|
-
}
|
|
360
|
-
else {
|
|
361
|
-
transaction.promise.reject(error);
|
|
362
|
-
}
|
|
363
|
-
},
|
|
364
|
-
resolveTransaction: (transaction, result) => {
|
|
365
|
-
// If the transaction was already started, we need to end the message generator early by throwing
|
|
366
|
-
// the result. Otherwise just resolve the result promise
|
|
367
|
-
if (transaction.parts.self) {
|
|
368
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
369
|
-
transaction.parts.self.throw(result).catch(() => { });
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
transaction.promise.resolve(result);
|
|
373
|
-
}
|
|
374
|
-
},
|
|
375
|
-
logOutgoingMessage: (msg) => {
|
|
376
|
-
this.driverLog.logMessage(msg, {
|
|
377
|
-
direction: "outbound",
|
|
378
|
-
});
|
|
379
|
-
if (process.env.NODE_ENV !== "test") {
|
|
380
|
-
// Enrich error data in case something goes wrong
|
|
381
|
-
Sentry.addBreadcrumb({
|
|
382
|
-
category: "message",
|
|
383
|
-
timestamp: Date.now() / 1000,
|
|
384
|
-
type: "debug",
|
|
385
|
-
data: {
|
|
386
|
-
direction: "outbound",
|
|
387
|
-
msgType: msg.type,
|
|
388
|
-
functionType: msg.functionType,
|
|
389
|
-
name: msg.constructor.name,
|
|
390
|
-
nodeId: msg.getNodeId(),
|
|
391
|
-
...msg.toLogEntry(),
|
|
392
|
-
},
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
},
|
|
396
|
-
log: this.driverLog.print.bind(this.driverLog),
|
|
397
|
-
logQueue: this.driverLog.sendQueue.bind(this.driverLog),
|
|
398
|
-
}, (0, shared_1.pick)(this._options, ["timeouts", "attempts"]));
|
|
399
|
-
this.sendThread = (0, xstate_1.interpret)(sendThreadMachine);
|
|
306
|
+
this.queue = new sorted_list_1.SortedList();
|
|
400
307
|
this._sendThreadIdle = false;
|
|
401
|
-
this.sendThread.onTransition((state) => {
|
|
402
|
-
if (state.changed) {
|
|
403
|
-
this.sendThreadIdle = state.matches("idle");
|
|
404
|
-
}
|
|
405
|
-
});
|
|
406
|
-
// For debugging
|
|
407
|
-
// this.sendThread.onTransition((state) => {
|
|
408
|
-
// if (state.changed)
|
|
409
|
-
// this.driverLog.print(
|
|
410
|
-
// `send thread state: ${state.toStrings().join("->")}`,
|
|
411
|
-
// "verbose",
|
|
412
|
-
// );
|
|
413
|
-
// });
|
|
414
|
-
// this.sendThread.onEvent((evt) => {
|
|
415
|
-
// if (evt.type === "forward") {
|
|
416
|
-
// this.driverLog.print(
|
|
417
|
-
// // @ts-ignore
|
|
418
|
-
// `forwarding event: ${evt.payload.type} from ${evt.from} to ${evt.to}`,
|
|
419
|
-
// "verbose",
|
|
420
|
-
// );
|
|
421
|
-
// } else {
|
|
422
|
-
// this.driverLog.print(
|
|
423
|
-
// `send thread event: ${evt.type}`,
|
|
424
|
-
// "verbose",
|
|
425
|
-
// );
|
|
426
|
-
// }
|
|
427
|
-
// });
|
|
428
308
|
}
|
|
429
309
|
/** Whether the Send Thread is currently idle */
|
|
430
310
|
get sendThreadIdle() {
|
|
@@ -684,7 +564,6 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
684
564
|
this.driverLog.print(`version ${exports.libVersion}`, "info");
|
|
685
565
|
this.driverLog.print("", "info");
|
|
686
566
|
this.driverLog.print("starting driver...");
|
|
687
|
-
this.sendThread.start();
|
|
688
567
|
// Open the serial port
|
|
689
568
|
if (typeof this.port === "string") {
|
|
690
569
|
if (this.port.startsWith("tcp://")) {
|
|
@@ -1077,7 +956,7 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
1077
956
|
if (!node.hasSUCReturnRoute &&
|
|
1078
957
|
node.status !== _Types_1.NodeStatus.Dead) {
|
|
1079
958
|
node.hasSUCReturnRoute =
|
|
1080
|
-
await this.controller.
|
|
959
|
+
await this.controller.assignSUCReturnRoutes(node.id);
|
|
1081
960
|
}
|
|
1082
961
|
})();
|
|
1083
962
|
}
|
|
@@ -1203,18 +1082,15 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
1203
1082
|
this.controllerLog.logNode(node.id, `The node is ${oldStatus === _Types_1.NodeStatus.Unknown ? "" : "now "}awake.`);
|
|
1204
1083
|
// Make sure to handle the pending messages as quickly as possible
|
|
1205
1084
|
if (oldStatus === _Types_1.NodeStatus.Asleep) {
|
|
1206
|
-
this.
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
// Re-queue all other transactions for this node, so they get added in front of the others
|
|
1216
|
-
return { type: "requeue" };
|
|
1217
|
-
},
|
|
1085
|
+
this.reduceQueue(({ message }) => {
|
|
1086
|
+
// Ignore messages that are not for this node
|
|
1087
|
+
if (message.getNodeId() !== node.id)
|
|
1088
|
+
return { type: "keep" };
|
|
1089
|
+
// Resolve pings, so we don't need to send them (we know the node is awake)
|
|
1090
|
+
if ((0, cc_1.messageIsPing)(message))
|
|
1091
|
+
return { type: "resolve", message: undefined };
|
|
1092
|
+
// Re-queue all other transactions for this node, so they get added in front of the others
|
|
1093
|
+
return { type: "requeue" };
|
|
1218
1094
|
});
|
|
1219
1095
|
}
|
|
1220
1096
|
}
|
|
@@ -1555,12 +1431,10 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
1555
1431
|
}
|
|
1556
1432
|
/** Checks if there are any pending transactions that match the given predicate */
|
|
1557
1433
|
hasPendingTransactions(predicate) {
|
|
1558
|
-
|
|
1559
|
-
|
|
1434
|
+
if (!!this.queue.find((t) => predicate(t)))
|
|
1435
|
+
return true;
|
|
1436
|
+
if (this.currentTransaction && predicate(this.currentTransaction)) {
|
|
1560
1437
|
return true;
|
|
1561
|
-
for (const { transaction } of activeTransactions.values()) {
|
|
1562
|
-
if (predicate(transaction))
|
|
1563
|
-
return true;
|
|
1564
1438
|
}
|
|
1565
1439
|
return false;
|
|
1566
1440
|
}
|
|
@@ -1753,7 +1627,7 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
1753
1627
|
// This is a bit hacky, but what the heck...
|
|
1754
1628
|
if (!this._enteringBootloader) {
|
|
1755
1629
|
// Resume sending
|
|
1756
|
-
this.
|
|
1630
|
+
this.unpauseSendQueue();
|
|
1757
1631
|
// Soft-resetting disables any ongoing inclusion, so we need to reset
|
|
1758
1632
|
// the state that is tracked in the controller
|
|
1759
1633
|
this._controller?.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
@@ -1817,11 +1691,11 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
1817
1691
|
const pollController = async () => {
|
|
1818
1692
|
try {
|
|
1819
1693
|
// And resume sending - this requires us to unpause the send thread
|
|
1820
|
-
this.
|
|
1694
|
+
this.unpauseSendQueue();
|
|
1821
1695
|
await this.sendMessage(new GetControllerVersionMessages_1.GetControllerVersionRequest(this), {
|
|
1822
1696
|
supportCheck: false,
|
|
1823
1697
|
});
|
|
1824
|
-
this.
|
|
1698
|
+
this.pauseSendQueue();
|
|
1825
1699
|
this.controllerLog.print("Serial API responded");
|
|
1826
1700
|
return true;
|
|
1827
1701
|
}
|
|
@@ -1929,8 +1803,9 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
1929
1803
|
this._destroyPromise = (0, deferred_promise_1.createDeferredPromise)();
|
|
1930
1804
|
this.driverLog.print("destroying driver instance...");
|
|
1931
1805
|
// First stop the send thread machine and close the serial port, so nothing happens anymore
|
|
1932
|
-
if (this.
|
|
1933
|
-
this.
|
|
1806
|
+
if (this.serialAPIInterpreter?.status === xstate_1.InterpreterStatus.Running) {
|
|
1807
|
+
this.serialAPIInterpreter.stop();
|
|
1808
|
+
}
|
|
1934
1809
|
if (this.serial != undefined) {
|
|
1935
1810
|
// Avoid spewing errors if the port was in the middle of receiving something
|
|
1936
1811
|
this.serial.removeAllListeners();
|
|
@@ -1959,7 +1834,6 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
1959
1834
|
}
|
|
1960
1835
|
// Remove all timeouts
|
|
1961
1836
|
for (const timeout of [
|
|
1962
|
-
this.saveToCacheTimer,
|
|
1963
1837
|
...this.sendNodeToSleepTimers.values(),
|
|
1964
1838
|
...this.retryNodeInterviewTimeouts.values(),
|
|
1965
1839
|
...this.autoRefreshNodeValueTimers.values(),
|
|
@@ -1967,6 +1841,7 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
1967
1841
|
this.pollBackgroundRSSITimer,
|
|
1968
1842
|
...this.awaitedCommands.map((c) => c.timeout),
|
|
1969
1843
|
...this.awaitedMessages.map((m) => m.timeout),
|
|
1844
|
+
...this.awaitedMessageHeaders.map((h) => h.timeout),
|
|
1970
1845
|
...this.awaitedBootloaderChunks.map((b) => b.timeout),
|
|
1971
1846
|
]) {
|
|
1972
1847
|
if (timeout)
|
|
@@ -1992,16 +1867,25 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
1992
1867
|
switch (data) {
|
|
1993
1868
|
// single-byte messages - just forward them to the send thread
|
|
1994
1869
|
case serial_1.MessageHeaders.ACK: {
|
|
1995
|
-
this.
|
|
1870
|
+
if (this.serialAPIInterpreter?.status ===
|
|
1871
|
+
xstate_1.InterpreterStatus.Running) {
|
|
1872
|
+
this.serialAPIInterpreter.send("ACK");
|
|
1873
|
+
}
|
|
1996
1874
|
return;
|
|
1997
1875
|
}
|
|
1998
1876
|
case serial_1.MessageHeaders.NAK: {
|
|
1999
|
-
this.
|
|
1877
|
+
if (this.serialAPIInterpreter?.status ===
|
|
1878
|
+
xstate_1.InterpreterStatus.Running) {
|
|
1879
|
+
this.serialAPIInterpreter.send("NAK");
|
|
1880
|
+
}
|
|
2000
1881
|
this._controller?.incrementStatistics("NAK");
|
|
2001
1882
|
return;
|
|
2002
1883
|
}
|
|
2003
1884
|
case serial_1.MessageHeaders.CAN: {
|
|
2004
|
-
this.
|
|
1885
|
+
if (this.serialAPIInterpreter?.status ===
|
|
1886
|
+
xstate_1.InterpreterStatus.Running) {
|
|
1887
|
+
this.serialAPIInterpreter.send("CAN");
|
|
1888
|
+
}
|
|
2005
1889
|
this._controller?.incrementStatistics("CAN");
|
|
2006
1890
|
return;
|
|
2007
1891
|
}
|
|
@@ -2185,7 +2069,16 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
2185
2069
|
// We shouldn't throw just because logging a message fails
|
|
2186
2070
|
this.driverLog.print(`Logging a message failed: ${(0, shared_1.getErrorMessage)(e)}`);
|
|
2187
2071
|
}
|
|
2188
|
-
|
|
2072
|
+
// Check if this message is unsolicited by passing it to the Serial API command interpreter if possible
|
|
2073
|
+
if (this.serialAPIInterpreter?.status === xstate_1.InterpreterStatus.Running) {
|
|
2074
|
+
this.serialAPIInterpreter.send({
|
|
2075
|
+
type: "message",
|
|
2076
|
+
message: msg,
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
else {
|
|
2080
|
+
void this.handleUnsolicitedMessage(msg);
|
|
2081
|
+
}
|
|
2189
2082
|
}
|
|
2190
2083
|
}
|
|
2191
2084
|
/** Handles a decoding error and returns the desired reply to the stick */
|
|
@@ -2405,11 +2298,20 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
2405
2298
|
return false;
|
|
2406
2299
|
}
|
|
2407
2300
|
else if (node.canSleep) {
|
|
2301
|
+
if (node.status === _Types_1.NodeStatus.Asleep) {
|
|
2302
|
+
// We already moved the messages to the wakeup queue
|
|
2303
|
+
return true;
|
|
2304
|
+
}
|
|
2408
2305
|
this.controllerLog.logNode(node.id, `${messagePart1}. It is probably asleep, moving its messages to the wakeup queue.`, "warn");
|
|
2409
|
-
//
|
|
2410
|
-
//
|
|
2306
|
+
// There is no longer a reference to the current transaction. If it should be moved to the wakeup queue,
|
|
2307
|
+
// it temporarily needs to be added to the queue again.
|
|
2308
|
+
const handled = this.mayMoveToWakeupQueue(transaction);
|
|
2309
|
+
if (handled) {
|
|
2310
|
+
this.queue.add(transaction);
|
|
2311
|
+
}
|
|
2312
|
+
// Mark the node as asleep. This will move the messages to the wakeup queue
|
|
2411
2313
|
node.markAsAsleep();
|
|
2412
|
-
return
|
|
2314
|
+
return handled;
|
|
2413
2315
|
}
|
|
2414
2316
|
else {
|
|
2415
2317
|
const errorMsg = `${messagePart1}, it is presumed dead`;
|
|
@@ -2692,6 +2594,10 @@ ${handlers.length} left`);
|
|
|
2692
2594
|
this.controllerLog.logNode(cc.nodeId, `is unknown - discarding received command...`, "warn");
|
|
2693
2595
|
return true;
|
|
2694
2596
|
}
|
|
2597
|
+
// CRC16, Transport Service belong outside of Security encapsulation
|
|
2598
|
+
if (cc instanceof cc_1.CRC16CC || cc instanceof cc_1.TransportServiceCC) {
|
|
2599
|
+
return false;
|
|
2600
|
+
}
|
|
2695
2601
|
if (cc.constructor.name.endsWith("Get") &&
|
|
2696
2602
|
(cc.frameType === "multicast" || cc.frameType === "broadcast")) {
|
|
2697
2603
|
this.controllerLog.logNode(cc.nodeId, `received GET-type command via ${cc.frameType} - discarding...`, "warn");
|
|
@@ -2797,23 +2703,18 @@ ${handlers.length} left`);
|
|
|
2797
2703
|
}
|
|
2798
2704
|
// It could also be that this is the node's response for a CC that we sent, but where the ACK is delayed
|
|
2799
2705
|
if ((0, cc_1.isCommandClassContainer)(msg)) {
|
|
2800
|
-
const
|
|
2801
|
-
|
|
2802
|
-
.
|
|
2803
|
-
.
|
|
2804
|
-
|
|
2805
|
-
return (sentMsg.expectsNodeUpdate() &&
|
|
2806
|
-
sentMsg.isExpectedNodeUpdate(msg));
|
|
2807
|
-
});
|
|
2808
|
-
if (msgExpectingUpdate) {
|
|
2809
|
-
// Found a message that is still in progress but expects this message in response.
|
|
2706
|
+
const currentMessage = this.currentTransaction?.getCurrentMessage();
|
|
2707
|
+
if (currentMessage &&
|
|
2708
|
+
currentMessage.expectsNodeUpdate() &&
|
|
2709
|
+
currentMessage.isExpectedNodeUpdate(msg)) {
|
|
2710
|
+
// The message we're currently sending is still in progress but expects this message in response.
|
|
2810
2711
|
// Remember the message there.
|
|
2811
2712
|
this.controllerLog.logNode(msg.getNodeId(), {
|
|
2812
2713
|
message: `received expected response prematurely, remembering it...`,
|
|
2813
2714
|
level: "verbose",
|
|
2814
2715
|
direction: "inbound",
|
|
2815
2716
|
});
|
|
2816
|
-
|
|
2717
|
+
currentMessage.prematureNodeUpdate = msg;
|
|
2817
2718
|
return;
|
|
2818
2719
|
}
|
|
2819
2720
|
}
|
|
@@ -3114,6 +3015,252 @@ ${handlers.length} left`);
|
|
|
3114
3015
|
this._controller?.incrementStatistics("messagesTX");
|
|
3115
3016
|
}
|
|
3116
3017
|
}
|
|
3018
|
+
mayStartNextTransaction() {
|
|
3019
|
+
// We may not send anything if the send thread is paused
|
|
3020
|
+
if (this.queuePaused)
|
|
3021
|
+
return false;
|
|
3022
|
+
const nextTransaction = this.queue.peekStart();
|
|
3023
|
+
// We can't send anything if the queue is empty
|
|
3024
|
+
if (!nextTransaction)
|
|
3025
|
+
return false;
|
|
3026
|
+
const message = nextTransaction.message;
|
|
3027
|
+
const targetNode = message.getNodeUnsafe(this);
|
|
3028
|
+
// The transaction queue is sorted automatically. If the first message is for a sleeping node, all messages in the queue are.
|
|
3029
|
+
// There are a few exceptions:
|
|
3030
|
+
// 1. Pings may be used to determine whether a node is really asleep.
|
|
3031
|
+
// 2. Responses to nonce requests must be sent independent of the node status, because some sleeping nodes may try to send us encrypted messages.
|
|
3032
|
+
// If we don't send them, they block the send queue
|
|
3033
|
+
// 3. Nodes that can sleep but do not support wakeup: https://github.com/zwave-js/node-zwave-js/discussions/1537
|
|
3034
|
+
// We need to try and send messages to them even if they are asleep, because we might never hear from them
|
|
3035
|
+
// // While the queue is busy, we may not start any transaction, except nonce responses to the node we're currently communicating with
|
|
3036
|
+
// if (meta.state.matches("busy")) {
|
|
3037
|
+
// if (nextTransaction.priority === MessagePriority.Nonce) {
|
|
3038
|
+
// for (const active of ctx.activeTransactions.values()) {
|
|
3039
|
+
// if (
|
|
3040
|
+
// active.transaction.message.getNodeId() ===
|
|
3041
|
+
// nextTransaction.message.getNodeId()
|
|
3042
|
+
// ) {
|
|
3043
|
+
// return true;
|
|
3044
|
+
// }
|
|
3045
|
+
// }
|
|
3046
|
+
// }
|
|
3047
|
+
// return false;
|
|
3048
|
+
// }
|
|
3049
|
+
// Replies to nonce requests and Supervision Get requests must always be allowed
|
|
3050
|
+
if (nextTransaction.priority === core_1.MessagePriority.Nonce ||
|
|
3051
|
+
nextTransaction.priority === core_1.MessagePriority.Supervision) {
|
|
3052
|
+
return true;
|
|
3053
|
+
}
|
|
3054
|
+
// Same for pings
|
|
3055
|
+
if ((0, cc_1.messageIsPing)(message))
|
|
3056
|
+
return true;
|
|
3057
|
+
// Or messages to the controller
|
|
3058
|
+
if (!targetNode)
|
|
3059
|
+
return true;
|
|
3060
|
+
return (targetNode.status !== _Types_1.NodeStatus.Asleep ||
|
|
3061
|
+
(!targetNode.supportsCC(core_1.CommandClasses["Wake Up"]) &&
|
|
3062
|
+
targetNode.interviewStage >= _Types_1.InterviewStage.NodeInfo));
|
|
3063
|
+
}
|
|
3064
|
+
async drainQueue() {
|
|
3065
|
+
// Don't execute more than once at a time
|
|
3066
|
+
if (this.drainQueueBusy)
|
|
3067
|
+
return;
|
|
3068
|
+
this.drainQueueBusy = true;
|
|
3069
|
+
try {
|
|
3070
|
+
while (this.mayStartNextTransaction()) {
|
|
3071
|
+
const transaction = (this.currentTransaction =
|
|
3072
|
+
this.queue.shift());
|
|
3073
|
+
// We have something to send, so not idle
|
|
3074
|
+
this.sendThreadIdle = false;
|
|
3075
|
+
let error;
|
|
3076
|
+
try {
|
|
3077
|
+
await this.drainTransactionGenerator(transaction);
|
|
3078
|
+
}
|
|
3079
|
+
catch (e) {
|
|
3080
|
+
error = e;
|
|
3081
|
+
}
|
|
3082
|
+
finally {
|
|
3083
|
+
this.currentTransaction = undefined;
|
|
3084
|
+
}
|
|
3085
|
+
// Handle errors after clearing the current transaction.
|
|
3086
|
+
// Otherwise, it will get considered the active transaction and cause an unnecessary SendDataAbort
|
|
3087
|
+
if (error) {
|
|
3088
|
+
this.rejectTransaction(transaction, error);
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
finally {
|
|
3093
|
+
this.drainQueueBusy = false;
|
|
3094
|
+
this.sendThreadIdle = true;
|
|
3095
|
+
}
|
|
3096
|
+
// Avoid a deadlock when a transaction was added after the advanceQueue call,
|
|
3097
|
+
// but before setting the busy flag back to false
|
|
3098
|
+
if (this.mayStartNextTransaction())
|
|
3099
|
+
this.triggerQueue();
|
|
3100
|
+
}
|
|
3101
|
+
/** Steps through the message generator of a transaction. Throws an error if the transaction should fail. */
|
|
3102
|
+
async drainTransactionGenerator(transaction) {
|
|
3103
|
+
transaction.parts.start();
|
|
3104
|
+
// .self is now guaranteed to be defined
|
|
3105
|
+
let prevResult;
|
|
3106
|
+
// Step through the transaction as long as it gives us a next message
|
|
3107
|
+
while (!(await transaction.parts.self.next(prevResult)).done) {
|
|
3108
|
+
// The .current property of the current transactions's message generator
|
|
3109
|
+
// now contains the next message to send
|
|
3110
|
+
const msg = transaction.getCurrentMessage();
|
|
3111
|
+
// TODO: refactor this nested loop or make it part of executeSerialAPICommand
|
|
3112
|
+
attemptMessage: for (let attemptNumber = 1;; attemptNumber++) {
|
|
3113
|
+
try {
|
|
3114
|
+
prevResult = await this.executeSerialAPICommand(msg, transaction.stack);
|
|
3115
|
+
if ((0, SendDataShared_1.isTransmitReport)(prevResult) && !prevResult.isOK()) {
|
|
3116
|
+
// The node did not acknowledge the command. Convert this into an
|
|
3117
|
+
// error so it can be handled.
|
|
3118
|
+
// First throw into the generator, so it can be reset
|
|
3119
|
+
transaction.parts.self.throw(prevResult).catch(shared_1.noop);
|
|
3120
|
+
throw new core_1.ZWaveError("The node did not acknowledge the command", core_1.ZWaveErrorCodes.Controller_CallbackNOK, prevResult, transaction.stack);
|
|
3121
|
+
}
|
|
3122
|
+
// We got a result - it will be passed to the next iteration
|
|
3123
|
+
break attemptMessage;
|
|
3124
|
+
}
|
|
3125
|
+
catch (e) {
|
|
3126
|
+
let delay = 0;
|
|
3127
|
+
let zwError;
|
|
3128
|
+
if (!(0, core_1.isZWaveError)(e)) {
|
|
3129
|
+
zwError = (0, StateMachineShared_1.createMessageDroppedUnexpectedError)(e);
|
|
3130
|
+
}
|
|
3131
|
+
else {
|
|
3132
|
+
if (e.code === core_1.ZWaveErrorCodes.Controller_CommandAborted) {
|
|
3133
|
+
// This transaction was aborted by the driver due to a controller timeout.
|
|
3134
|
+
// Rejections, re-queuing etc. have been handled, so just drop it silently and
|
|
3135
|
+
// continue with the next message
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
else if ((0, SendDataShared_1.isSendData)(msg) &&
|
|
3139
|
+
e.code === core_1.ZWaveErrorCodes.Controller_Timeout &&
|
|
3140
|
+
e.context === "callback") {
|
|
3141
|
+
// If the callback to SendData times out, we need to issue a SendDataAbort
|
|
3142
|
+
await this.abortSendData();
|
|
3143
|
+
// Wait a short amount of time so everything can settle
|
|
3144
|
+
delay = 50;
|
|
3145
|
+
}
|
|
3146
|
+
if (this.mayRetrySerialAPICommand(msg, attemptNumber, e.code)) {
|
|
3147
|
+
// Retry the command
|
|
3148
|
+
if (delay)
|
|
3149
|
+
await (0, async_1.wait)(delay, true);
|
|
3150
|
+
continue attemptMessage;
|
|
3151
|
+
}
|
|
3152
|
+
zwError = e;
|
|
3153
|
+
}
|
|
3154
|
+
// Sending the command failed, reject the transaction
|
|
3155
|
+
throw zwError;
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
// This transaction is finished, try the next one
|
|
3160
|
+
}
|
|
3161
|
+
triggerQueue() {
|
|
3162
|
+
process.nextTick(() => this.drainQueue());
|
|
3163
|
+
}
|
|
3164
|
+
mayRetrySerialAPICommand(msg, attemptNumber, errorCode) {
|
|
3165
|
+
if (!(0, SendDataShared_1.isSendData)(msg))
|
|
3166
|
+
return false;
|
|
3167
|
+
if (msg instanceof SendDataMessages_1.SendDataMulticastRequest ||
|
|
3168
|
+
msg instanceof SendDataBridgeMessages_1.SendDataMulticastBridgeRequest) {
|
|
3169
|
+
// Don't try to resend multicast messages if they were already transmitted.
|
|
3170
|
+
// One or more nodes might have already reacted
|
|
3171
|
+
if (errorCode === core_1.ZWaveErrorCodes.Controller_CallbackNOK) {
|
|
3172
|
+
return false;
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
return attemptNumber < msg.maxSendAttempts;
|
|
3176
|
+
}
|
|
3177
|
+
/**
|
|
3178
|
+
* Executes a Serial API command and returns or throws the result.
|
|
3179
|
+
* @param transaction The transaction which contains the message to be executed
|
|
3180
|
+
*/
|
|
3181
|
+
async executeSerialAPICommand(msg, transactionSource) {
|
|
3182
|
+
const machine = (0, SerialAPICommandMachine_1.createSerialAPICommandMachine)(msg, {
|
|
3183
|
+
sendData: this.writeSerial.bind(this),
|
|
3184
|
+
notifyUnsolicited: (msg) => {
|
|
3185
|
+
void this.handleUnsolicitedMessage(msg);
|
|
3186
|
+
},
|
|
3187
|
+
notifyRetry: (lastError, message, attempts, maxAttempts, delay) => {
|
|
3188
|
+
// Translate the error into a better one
|
|
3189
|
+
let errorReason;
|
|
3190
|
+
switch (lastError) {
|
|
3191
|
+
case "response timeout":
|
|
3192
|
+
errorReason = "No response from controller";
|
|
3193
|
+
this._controller?.incrementStatistics("timeoutResponse");
|
|
3194
|
+
break;
|
|
3195
|
+
case "callback timeout":
|
|
3196
|
+
errorReason = "No callback from controller";
|
|
3197
|
+
this._controller?.incrementStatistics("timeoutCallback");
|
|
3198
|
+
break;
|
|
3199
|
+
case "response NOK":
|
|
3200
|
+
errorReason =
|
|
3201
|
+
"The controller response indicated failure";
|
|
3202
|
+
break;
|
|
3203
|
+
case "callback NOK":
|
|
3204
|
+
errorReason =
|
|
3205
|
+
"The controller callback indicated failure";
|
|
3206
|
+
break;
|
|
3207
|
+
case "ACK timeout":
|
|
3208
|
+
this._controller?.incrementStatistics("timeoutACK");
|
|
3209
|
+
// fall through
|
|
3210
|
+
case "CAN":
|
|
3211
|
+
case "NAK":
|
|
3212
|
+
default:
|
|
3213
|
+
errorReason =
|
|
3214
|
+
"Failed to execute controller command";
|
|
3215
|
+
break;
|
|
3216
|
+
}
|
|
3217
|
+
this.controllerLog.print(`${errorReason} after ${attempts}/${maxAttempts} attempts. Scheduling next try in ${delay} ms.`, "warn");
|
|
3218
|
+
},
|
|
3219
|
+
timestamp: core_1.highResTimestamp,
|
|
3220
|
+
logOutgoingMessage: (msg) => {
|
|
3221
|
+
this.driverLog.logMessage(msg, {
|
|
3222
|
+
direction: "outbound",
|
|
3223
|
+
});
|
|
3224
|
+
if (process.env.NODE_ENV !== "test") {
|
|
3225
|
+
// Enrich error data in case something goes wrong
|
|
3226
|
+
Sentry.addBreadcrumb({
|
|
3227
|
+
category: "message",
|
|
3228
|
+
timestamp: Date.now() / 1000,
|
|
3229
|
+
type: "debug",
|
|
3230
|
+
data: {
|
|
3231
|
+
direction: "outbound",
|
|
3232
|
+
msgType: msg.type,
|
|
3233
|
+
functionType: msg.functionType,
|
|
3234
|
+
name: msg.constructor.name,
|
|
3235
|
+
nodeId: msg.getNodeId(),
|
|
3236
|
+
...msg.toLogEntry(),
|
|
3237
|
+
},
|
|
3238
|
+
});
|
|
3239
|
+
}
|
|
3240
|
+
},
|
|
3241
|
+
}, (0, shared_1.pick)(this._options, ["timeouts", "attempts"]));
|
|
3242
|
+
const result = (0, deferred_promise_1.createDeferredPromise)();
|
|
3243
|
+
this.serialAPIInterpreter = (0, xstate_1.interpret)(machine).onDone((evt) => {
|
|
3244
|
+
this.serialAPIInterpreter?.stop();
|
|
3245
|
+
this.serialAPIInterpreter = undefined;
|
|
3246
|
+
const cmdResult = evt.data;
|
|
3247
|
+
if (cmdResult.type === "success") {
|
|
3248
|
+
result.resolve(cmdResult.result);
|
|
3249
|
+
}
|
|
3250
|
+
else if (cmdResult.reason === "callback NOK" &&
|
|
3251
|
+
((0, SendDataShared_1.isSendData)(msg) || (0, SendDataShared_1.isTransmitReport)(cmdResult.result))) {
|
|
3252
|
+
// For messages that were sent to a node, a NOK callback still contains useful info we need to evaluate
|
|
3253
|
+
// ... so we treat it as a result
|
|
3254
|
+
result.resolve(cmdResult.result);
|
|
3255
|
+
}
|
|
3256
|
+
else {
|
|
3257
|
+
// Convert to a Z-Wave error
|
|
3258
|
+
result.reject((0, StateMachineShared_1.serialAPICommandErrorToZWaveError)(cmdResult.reason, msg, cmdResult.result, transactionSource));
|
|
3259
|
+
}
|
|
3260
|
+
});
|
|
3261
|
+
this.serialAPIInterpreter.start();
|
|
3262
|
+
return result;
|
|
3263
|
+
}
|
|
3117
3264
|
/**
|
|
3118
3265
|
* Sends a message to the Z-Wave stick.
|
|
3119
3266
|
* @param msg The message to send
|
|
@@ -3193,22 +3340,20 @@ ${handlers.length} left`);
|
|
|
3193
3340
|
transaction.requestWakeUpOnDemand = !!options.requestWakeUpOnDemand;
|
|
3194
3341
|
transaction.tag = options.tag;
|
|
3195
3342
|
// And queue it
|
|
3196
|
-
this.
|
|
3343
|
+
this.queue.add(transaction);
|
|
3344
|
+
this.triggerQueue();
|
|
3197
3345
|
// If the transaction should expire, start the timeout
|
|
3198
3346
|
let expirationTimeout;
|
|
3199
3347
|
if (options.expire) {
|
|
3200
3348
|
expirationTimeout = setTimeout(() => {
|
|
3201
|
-
this.
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
};
|
|
3210
|
-
return { type: "keep" };
|
|
3211
|
-
},
|
|
3349
|
+
this.reduceQueue((t, _source) => {
|
|
3350
|
+
if (t === transaction)
|
|
3351
|
+
return {
|
|
3352
|
+
type: "reject",
|
|
3353
|
+
message: `The message has expired`,
|
|
3354
|
+
code: core_1.ZWaveErrorCodes.Controller_MessageExpired,
|
|
3355
|
+
};
|
|
3356
|
+
return { type: "keep" };
|
|
3212
3357
|
});
|
|
3213
3358
|
}, options.expire).unref();
|
|
3214
3359
|
}
|
|
@@ -3420,6 +3565,27 @@ ${handlers.length} left`);
|
|
|
3420
3565
|
// @ts-expect-error TS doesn't know we've narrowed the return type to match
|
|
3421
3566
|
return this.sendCommandInternal(command, options);
|
|
3422
3567
|
}
|
|
3568
|
+
async abortSendData(abortInterpreter = false) {
|
|
3569
|
+
try {
|
|
3570
|
+
const abort = new SendDataMessages_1.SendDataAbort(this);
|
|
3571
|
+
await this.writeSerial(abort.serialize());
|
|
3572
|
+
this.driverLog.logMessage(abort, {
|
|
3573
|
+
direction: "outbound",
|
|
3574
|
+
});
|
|
3575
|
+
// We're bypassing the serial API machine, so we need to wait for the ACK ourselves
|
|
3576
|
+
// This could also cause a NAK or CAN, but we don't really care
|
|
3577
|
+
await this.waitForMessageHeader(() => true, 500).catch(shared_1.noop);
|
|
3578
|
+
// Abort the currently active command machine only if the controller has timed out.
|
|
3579
|
+
// SendData commands we abort early MUST result in the normal callback.
|
|
3580
|
+
if (abortInterpreter &&
|
|
3581
|
+
this.serialAPIInterpreter?.status === xstate_1.InterpreterStatus.Running) {
|
|
3582
|
+
this.serialAPIInterpreter.send("abort");
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
catch {
|
|
3586
|
+
// ignore
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3423
3589
|
/**
|
|
3424
3590
|
* Sends a low-level message like ACK, NAK or CAN immediately
|
|
3425
3591
|
* @param header The low-level message to send
|
|
@@ -3432,6 +3598,40 @@ ${handlers.length} left`);
|
|
|
3432
3598
|
async writeSerial(data) {
|
|
3433
3599
|
return this.serial?.writeAsync(data);
|
|
3434
3600
|
}
|
|
3601
|
+
/**
|
|
3602
|
+
* Waits until a matching message header is received or a timeout has elapsed. Returns the received message.
|
|
3603
|
+
*
|
|
3604
|
+
* @param timeout The number of milliseconds to wait. If the timeout elapses, the returned promise will be rejected
|
|
3605
|
+
* @param predicate A predicate function to test all incoming message headers.
|
|
3606
|
+
*/
|
|
3607
|
+
waitForMessageHeader(predicate, timeout) {
|
|
3608
|
+
return new Promise((resolve, reject) => {
|
|
3609
|
+
const promise = (0, deferred_promise_1.createDeferredPromise)();
|
|
3610
|
+
const entry = {
|
|
3611
|
+
predicate,
|
|
3612
|
+
handler: (msg) => promise.resolve(msg),
|
|
3613
|
+
timeout: undefined,
|
|
3614
|
+
};
|
|
3615
|
+
this.awaitedMessageHeaders.push(entry);
|
|
3616
|
+
const removeEntry = () => {
|
|
3617
|
+
if (entry.timeout)
|
|
3618
|
+
clearTimeout(entry.timeout);
|
|
3619
|
+
const index = this.awaitedMessageHeaders.indexOf(entry);
|
|
3620
|
+
if (index !== -1)
|
|
3621
|
+
this.awaitedMessageHeaders.splice(index, 1);
|
|
3622
|
+
};
|
|
3623
|
+
// When the timeout elapses, remove the wait entry and reject the returned Promise
|
|
3624
|
+
entry.timeout = setTimeout(() => {
|
|
3625
|
+
removeEntry();
|
|
3626
|
+
reject(new core_1.ZWaveError(`Received no matching serial frame within the provided timeout!`, core_1.ZWaveErrorCodes.Controller_Timeout));
|
|
3627
|
+
}, timeout);
|
|
3628
|
+
// When the promise is resolved, remove the wait entry and resolve the returned Promise
|
|
3629
|
+
void promise.then((cc) => {
|
|
3630
|
+
removeEntry();
|
|
3631
|
+
resolve(cc);
|
|
3632
|
+
});
|
|
3633
|
+
});
|
|
3634
|
+
}
|
|
3435
3635
|
/**
|
|
3436
3636
|
* Waits until an unsolicited serial message is received or a timeout has elapsed. Returns the received message.
|
|
3437
3637
|
*
|
|
@@ -3524,6 +3724,31 @@ ${handlers.length} left`);
|
|
|
3524
3724
|
unregister: removeEntry,
|
|
3525
3725
|
};
|
|
3526
3726
|
}
|
|
3727
|
+
rejectTransaction(transaction, error) {
|
|
3728
|
+
// If a node failed to respond in time, it might be sleeping
|
|
3729
|
+
if (this.isMissingNodeACK(transaction, error)) {
|
|
3730
|
+
if (this.handleMissingNodeACK(transaction))
|
|
3731
|
+
return;
|
|
3732
|
+
}
|
|
3733
|
+
// If the transaction was already started, we need to throw the error into the message generator
|
|
3734
|
+
// so it correctly gets ended. Otherwise just reject the result promise
|
|
3735
|
+
if (transaction.parts.self) {
|
|
3736
|
+
transaction.parts.self.throw(error).catch(shared_1.noop);
|
|
3737
|
+
}
|
|
3738
|
+
else {
|
|
3739
|
+
transaction.promise.reject(error);
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
resolveTransaction(transaction, result) {
|
|
3743
|
+
// If the transaction was already started, we need to end the message generator early by throwing
|
|
3744
|
+
// the result. Otherwise just resolve the result promise
|
|
3745
|
+
if (transaction.parts.self) {
|
|
3746
|
+
transaction.parts.self.throw(result).catch(shared_1.noop);
|
|
3747
|
+
}
|
|
3748
|
+
else {
|
|
3749
|
+
transaction.promise.resolve(result);
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3527
3752
|
/** Checks if a message is allowed to go into the wakeup queue */
|
|
3528
3753
|
mayMoveToWakeupQueue(transaction) {
|
|
3529
3754
|
const msg = transaction.message;
|
|
@@ -3556,7 +3781,7 @@ ${handlers.length} left`);
|
|
|
3556
3781
|
...requeue,
|
|
3557
3782
|
tag: "interview",
|
|
3558
3783
|
};
|
|
3559
|
-
|
|
3784
|
+
this.reduceQueue((transaction, _source) => {
|
|
3560
3785
|
const msg = transaction.message;
|
|
3561
3786
|
if (msg.getNodeId() !== nodeId)
|
|
3562
3787
|
return { type: "keep" };
|
|
@@ -3567,16 +3792,14 @@ ${handlers.length} left`);
|
|
|
3567
3792
|
? requeueAndTagAsInterview
|
|
3568
3793
|
: requeue
|
|
3569
3794
|
: reject;
|
|
3570
|
-
};
|
|
3571
|
-
// Apply the reducer to the send thread
|
|
3572
|
-
this.sendThread.send({ type: "reduce", reducer });
|
|
3795
|
+
});
|
|
3573
3796
|
}
|
|
3574
3797
|
/**
|
|
3575
3798
|
* @internal
|
|
3576
3799
|
* Rejects all pending transactions that match a predicate and removes them from the send queue
|
|
3577
3800
|
*/
|
|
3578
3801
|
rejectTransactions(predicate, errorMsg = `The message has been removed from the queue`, errorCode = core_1.ZWaveErrorCodes.Controller_MessageDropped) {
|
|
3579
|
-
|
|
3802
|
+
this.reduceQueue((transaction, _source) => {
|
|
3580
3803
|
if (predicate(transaction)) {
|
|
3581
3804
|
return {
|
|
3582
3805
|
type: "reject",
|
|
@@ -3587,9 +3810,7 @@ ${handlers.length} left`);
|
|
|
3587
3810
|
else {
|
|
3588
3811
|
return { type: "keep" };
|
|
3589
3812
|
}
|
|
3590
|
-
};
|
|
3591
|
-
// Apply the reducer to the send thread
|
|
3592
|
-
this.sendThread.send({ type: "reduce", reducer });
|
|
3813
|
+
});
|
|
3593
3814
|
}
|
|
3594
3815
|
/**
|
|
3595
3816
|
* @internal
|
|
@@ -3599,22 +3820,89 @@ ${handlers.length} left`);
|
|
|
3599
3820
|
this.rejectTransactions((t) => t.message.getNodeId() === nodeId, errorMsg, errorCode);
|
|
3600
3821
|
}
|
|
3601
3822
|
/**
|
|
3602
|
-
*
|
|
3603
|
-
* Pauses the send thread, avoiding commands to be sent to the controller
|
|
3823
|
+
* Pauses the send queue, avoiding commands to be sent to the controller
|
|
3604
3824
|
*/
|
|
3605
|
-
|
|
3606
|
-
this.
|
|
3825
|
+
pauseSendQueue() {
|
|
3826
|
+
this.queuePaused = true;
|
|
3607
3827
|
}
|
|
3608
3828
|
/**
|
|
3609
|
-
*
|
|
3610
|
-
* Unpauses the send thread, allowing commands to be sent to the controller again
|
|
3829
|
+
* Unpauses the send queue, allowing commands to be sent to the controller again
|
|
3611
3830
|
*/
|
|
3612
|
-
|
|
3613
|
-
this.
|
|
3831
|
+
unpauseSendQueue() {
|
|
3832
|
+
this.queuePaused = false;
|
|
3833
|
+
this.triggerQueue();
|
|
3834
|
+
}
|
|
3835
|
+
reduceQueue(reducer) {
|
|
3836
|
+
const dropQueued = [];
|
|
3837
|
+
let stopActive;
|
|
3838
|
+
const requeue = [];
|
|
3839
|
+
const reduceTransaction = (transaction, source) => {
|
|
3840
|
+
const reducerResult = reducer(transaction, source);
|
|
3841
|
+
switch (reducerResult.type) {
|
|
3842
|
+
case "drop":
|
|
3843
|
+
if (source === "queue") {
|
|
3844
|
+
dropQueued.push(transaction);
|
|
3845
|
+
}
|
|
3846
|
+
else {
|
|
3847
|
+
stopActive = transaction;
|
|
3848
|
+
}
|
|
3849
|
+
break;
|
|
3850
|
+
case "requeue":
|
|
3851
|
+
if (reducerResult.priority != undefined) {
|
|
3852
|
+
transaction.priority = reducerResult.priority;
|
|
3853
|
+
}
|
|
3854
|
+
if (reducerResult.tag != undefined) {
|
|
3855
|
+
transaction.tag = reducerResult.tag;
|
|
3856
|
+
}
|
|
3857
|
+
if (source === "active")
|
|
3858
|
+
stopActive = transaction;
|
|
3859
|
+
requeue.push(transaction);
|
|
3860
|
+
break;
|
|
3861
|
+
case "resolve":
|
|
3862
|
+
this.resolveTransaction(transaction, reducerResult.message);
|
|
3863
|
+
if (source === "queue") {
|
|
3864
|
+
dropQueued.push(transaction);
|
|
3865
|
+
}
|
|
3866
|
+
else {
|
|
3867
|
+
stopActive = transaction;
|
|
3868
|
+
}
|
|
3869
|
+
break;
|
|
3870
|
+
case "reject":
|
|
3871
|
+
this.rejectTransaction(transaction, new core_1.ZWaveError(reducerResult.message, reducerResult.code, undefined, transaction.stack));
|
|
3872
|
+
if (source === "queue") {
|
|
3873
|
+
dropQueued.push(transaction);
|
|
3874
|
+
}
|
|
3875
|
+
else {
|
|
3876
|
+
stopActive = transaction;
|
|
3877
|
+
}
|
|
3878
|
+
break;
|
|
3879
|
+
}
|
|
3880
|
+
};
|
|
3881
|
+
for (const transaction of this.queue) {
|
|
3882
|
+
reduceTransaction(transaction, "queue");
|
|
3883
|
+
}
|
|
3884
|
+
if (this.currentTransaction) {
|
|
3885
|
+
reduceTransaction(this.currentTransaction, "active");
|
|
3886
|
+
}
|
|
3887
|
+
// Now we know what to do with the transactions
|
|
3888
|
+
this.queue.remove(...dropQueued, ...requeue);
|
|
3889
|
+
this.queue.add(...requeue.map((t) => t.clone()));
|
|
3890
|
+
// Abort ongoing SendData messages that should be dropped
|
|
3891
|
+
if ((0, SendDataShared_1.isSendData)(stopActive?.message)) {
|
|
3892
|
+
void this.abortSendData();
|
|
3893
|
+
}
|
|
3894
|
+
// Continue sending
|
|
3895
|
+
this.triggerQueue();
|
|
3614
3896
|
}
|
|
3615
|
-
/**
|
|
3616
|
-
|
|
3617
|
-
|
|
3897
|
+
/** @internal */
|
|
3898
|
+
resolvePendingPings(nodeId) {
|
|
3899
|
+
// When a previously sleeping node sends a NIF after a ping was sent to it, but not acknowledged yet,
|
|
3900
|
+
// the node is awake, but the ping would fail. Resolve pending pings, so communication can continue.
|
|
3901
|
+
const msg = this.currentTransaction?.parts.current;
|
|
3902
|
+
if (!!msg && (0, cc_1.messageIsPing)(msg) && msg.getNodeId() === nodeId) {
|
|
3903
|
+
// The pending transaction is a ping. Short-circuit its message generator by throwing something that's not an error
|
|
3904
|
+
this.currentTransaction?.parts.self.throw(undefined).catch(shared_1.noop);
|
|
3905
|
+
}
|
|
3618
3906
|
}
|
|
3619
3907
|
/**
|
|
3620
3908
|
* @internal
|
|
@@ -3830,7 +4118,7 @@ ${handlers.length} left`);
|
|
|
3830
4118
|
// Avoid re-transmissions etc. communicating with the bootloader
|
|
3831
4119
|
this.rejectTransactions((_t) => true, "The controller is entering bootloader mode.");
|
|
3832
4120
|
await this.trySoftReset();
|
|
3833
|
-
this.
|
|
4121
|
+
this.pauseSendQueue();
|
|
3834
4122
|
// Again, just to be very sure
|
|
3835
4123
|
this.rejectTransactions((_t) => true, "The controller is entering bootloader mode.");
|
|
3836
4124
|
// It would be nicer to not hardcode the command here, but since we're switching stream parsers
|
|
@@ -3880,7 +4168,7 @@ ${handlers.length} left`);
|
|
|
3880
4168
|
});
|
|
3881
4169
|
}
|
|
3882
4170
|
else {
|
|
3883
|
-
this.
|
|
4171
|
+
this.unpauseSendQueue();
|
|
3884
4172
|
await this.ensureSerialAPI();
|
|
3885
4173
|
}
|
|
3886
4174
|
}
|