diff --git a/packages/zwave-js/src/lib/driver/Driver.ts b/packages/zwave-js/src/lib/driver/Driver.ts index 49120bd8ebd0..6f95b2b639f7 100644 --- a/packages/zwave-js/src/lib/driver/Driver.ts +++ b/packages/zwave-js/src/lib/driver/Driver.ts @@ -5381,6 +5381,9 @@ ${handlers.length} left`, this.ensureReady(); let node: ZWaveNode | undefined; + if (isNodeQuery(msg) || isCommandClassContainer(msg)) { + node = this.getNodeUnsafe(msg); + } if (options.priority == undefined) { options.priority = getDefaultPriority(msg); diff --git a/packages/zwave-js/src/lib/log/Driver.ts b/packages/zwave-js/src/lib/log/Driver.ts index f1bd38ea8a9a..2be109c6dfe6 100644 --- a/packages/zwave-js/src/lib/log/Driver.ts +++ b/packages/zwave-js/src/lib/log/Driver.ts @@ -210,11 +210,13 @@ export class DriverLogger extends ZWaveLoggerBase { }]` : ""; const command = isCommandClassContainer(trns.message) - ? ` (${trns.message.command.constructor.name})` + ? `: ${trns.message.command.constructor.name}` : ""; message += `\n· ${prefix} ${ FunctionType[trns.message.functionType] - }${command}${postfix}`; + }${command}${postfix} (P: ${ + getEnumMemberName(MessagePriority, trns.priority) + })`; } } else { message += " (empty)"; diff --git a/packages/zwave-js/src/lib/test/driver/nodeAsleepMessageOrder.test.ts b/packages/zwave-js/src/lib/test/driver/nodeAsleepMessageOrder.test.ts index 8d8308c96527..97194b4a6ea2 100644 --- a/packages/zwave-js/src/lib/test/driver/nodeAsleepMessageOrder.test.ts +++ b/packages/zwave-js/src/lib/test/driver/nodeAsleepMessageOrder.test.ts @@ -222,3 +222,102 @@ integrationTest( }, }, ); + +integrationTest( + "When a command to a sleeping node is pending, other commands are still handled", + { + // debug: true, + + provisioningDirectory: path.join( + __dirname, + "fixtures/nodeAsleepMessageOrder", + ), + + nodeCapabilities: [ + { + id: 10, + capabilities: { + commandClasses: [ + CommandClasses.Basic, + CommandClasses["Wake Up"], + ], + isListening: false, + isFrequentListening: false, + }, + }, + { + id: 17, + capabilities: { + commandClasses: [CommandClasses.Basic], + }, + }, + ], + + customSetup: async (driver, mockController, mockNodes) => { + const [mockNode10] = mockNodes; + + const doNotAnswerWhenAsleep: MockNodeBehavior = { + onControllerFrame(controller, self, frame) { + if (!mockNode10.autoAckControllerFrames) return true; + }, + }; + mockNode10.defineBehavior(doNotAnswerWhenAsleep); + }, + + testBody: async (t, driver, nodes, mockController, mockNodes) => { + const [node10, node17] = nodes; + const [mockNode10, mockNode17] = mockNodes; + + // Node 10 is sleeping + node10.markAsAsleep(); + mockNode10.autoAckControllerFrames = false; + + // Queue a command to node 10 + const commandToNode10 = node10.commandClasses.Basic.set(60); + + // Queue a command to node 17 + const commandToNode17 = node17.commandClasses.Basic.set(99); + + driver.driverLog.print("BEFORE wakeup"); + driver.driverLog.sendQueue(driver["queue"]); + + let result = await Promise.race([ + wait(500).then(() => "timeout"), + commandToNode17.then(() => "ok"), + ]); + t.is(result, "ok"); + + // The first command should not have been sent + mockNode10.assertReceivedControllerFrame( + (f) => + f.type === MockZWaveFrameType.Request + && f.payload instanceof BasicCCGet, + { + noMatch: true, + }, + ); + + driver.driverLog.print("AFTER first command"); + driver.driverLog.sendQueue(driver["queue"]); + + // Node 10 wakes up + mockNode10.autoAckControllerFrames = true; + const cc: CommandClass = new WakeUpCCWakeUpNotification( + mockNode10.host, + { + nodeId: mockController.host.ownNodeId, + }, + ); + mockNode10.sendToController(createMockZWaveRequestFrame(cc, { + ackRequested: false, + })); + + // And the first command should be sent + result = await Promise.race([ + wait(500).then(() => "timeout"), + commandToNode10.then(() => "ok"), + ]); + t.is(result, "ok"); + }, + }, +);