zwave-js
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
833 lines • 40.7 kB
JavaScript
// oxlint-disable typescript/no-misused-spread
import { CommandClass, WakeUpTime } from "@zwave-js/cc";
import { ZWaveProtocolCC, ZWaveProtocolCCAssignSUCReturnRoute, ZWaveProtocolCCNodeInformationFrame, ZWaveProtocolCCRequestNodeInformationFrame, } from "@zwave-js/cc/ZWaveProtocolCC";
import { NodeType, TransmitOptions, TransmitStatus, ZWaveDataRate, ZWaveErrorCodes, isZWaveError, } from "@zwave-js/core";
import { AddNodeStatus, AddNodeToNetworkRequest, AddNodeToNetworkRequestStatusReport, AddNodeType, ApplicationCommandRequest, ApplicationUpdateRequestNodeInfoReceived, ApplicationUpdateRequestNodeInfoRequestFailed, AssignSUCReturnRouteRequest, AssignSUCReturnRouteRequestTransmitReport, AssignSUCReturnRouteResponse, DeleteSUCReturnRouteRequest, DeleteSUCReturnRouteRequestTransmitReport, DeleteSUCReturnRouteResponse, GetControllerCapabilitiesRequest, GetControllerCapabilitiesResponse, GetControllerIdRequest, GetControllerIdResponse, GetControllerVersionRequest, GetControllerVersionResponse, GetNodeProtocolInfoRequest, GetNodeProtocolInfoResponse, GetSUCNodeIdRequest, GetSUCNodeIdResponse, GetSerialApiCapabilitiesRequest, GetSerialApiCapabilitiesResponse, GetSerialApiInitDataRequest, GetSerialApiInitDataResponse, RemoveNodeFromNetworkRequest, RemoveNodeFromNetworkRequestStatusReport, RemoveNodeStatus, RemoveNodeType, RequestNodeInfoRequest, RequestNodeInfoResponse, SendDataBridgeRequest, SendDataBridgeRequestTransmitReport, SendDataBridgeResponse, SendDataMulticastBridgeRequest, SendDataMulticastBridgeRequestTransmitReport, SendDataMulticastBridgeResponse, SendDataMulticastRequest, SendDataMulticastRequestTransmitReport, SendDataMulticastResponse, SendDataRequest, SendDataRequestTransmitReport, SendDataResponse, SerialAPIStartedRequest, SerialAPIWakeUpReason, SoftResetRequest, } from "@zwave-js/serial/serialapi";
import { MOCK_FRAME_ACK_TIMEOUT, MockNode, MockZWaveFrameType, createMockZWaveRequestFrame, } from "@zwave-js/testing";
import { wait } from "alcalzone-shared/async";
import { createDefaultMockNodeBehaviors } from "../../Testing.js";
import { MockControllerCommunicationState, MockControllerInclusionState, MockControllerStateKeys, } from "./MockControllerState.js";
import { determineNIF } from "./NodeInformationFrame.js";
function createLazySendDataPayload(controller, node, msg) {
return async () => {
try {
const cmd = await CommandClass.parse(msg.serializedCC, {
sourceNodeId: controller.ownNodeId,
__internalIsMockNode: true,
...node.encodingContext,
...node.securityManagers,
// The frame type is always singlecast because the controller sends it to the node
frameType: "singlecast",
});
// Store the command because assertReceivedHostMessage needs it
// @ts-expect-error
msg.command = cmd;
return cmd;
}
catch (e) {
if (isZWaveError(e)) {
if (e.code === ZWaveErrorCodes.CC_NotImplemented) {
// The whole CC is not implemented yet. If this happens in tests, it is because we sent a raw CC.
try {
const cmd = new CommandClass({
nodeId: controller.ownNodeId,
ccId: msg.payload[0],
ccCommand: msg.payload[1],
payload: msg.payload.subarray(2),
});
// Store the command because assertReceivedHostMessage needs it
// @ts-expect-error
msg.command = cmd;
return cmd;
}
catch (e) {
console.error(e.message);
throw e;
}
}
else if (e.code === ZWaveErrorCodes.Deserialization_NotImplemented) {
// We want to know when we're using a command in tests that cannot be decoded yet
console.error(e.message);
throw e;
}
}
console.error(e);
throw e;
}
};
}
const respondToGetControllerId = {
async onHostMessage(controller, msg) {
if (msg instanceof GetControllerIdRequest) {
const ret = new GetControllerIdResponse({
homeId: controller.homeId,
ownNodeId: controller.ownNodeId,
});
await controller.sendMessageToHost(ret);
return true;
}
},
};
const respondToGetSerialApiCapabilities = {
async onHostMessage(controller, msg) {
if (msg instanceof GetSerialApiCapabilitiesRequest) {
const ret = new GetSerialApiCapabilitiesResponse({
...controller.capabilities,
});
await controller.sendMessageToHost(ret);
return true;
}
},
};
const respondToGetControllerVersion = {
async onHostMessage(controller, msg) {
if (msg instanceof GetControllerVersionRequest) {
const ret = new GetControllerVersionResponse({
...controller.capabilities,
});
await controller.sendMessageToHost(ret);
return true;
}
},
};
const respondToGetControllerCapabilities = {
async onHostMessage(controller, msg) {
if (msg instanceof GetControllerCapabilitiesRequest) {
const ret = new GetControllerCapabilitiesResponse({
...controller.capabilities,
});
await controller.sendMessageToHost(ret);
return true;
}
},
};
const respondToGetSUCNodeId = {
async onHostMessage(controller, msg) {
if (msg instanceof GetSUCNodeIdRequest) {
const sucNodeId = controller.capabilities.isStaticUpdateController
? controller.ownNodeId
: controller.capabilities.sucNodeId;
const ret = new GetSUCNodeIdResponse({
sucNodeId,
});
await controller.sendMessageToHost(ret);
return true;
}
},
};
const respondToGetSerialApiInitData = {
async onHostMessage(controller, msg) {
if (msg instanceof GetSerialApiInitDataRequest) {
const nodeIds = new Set(controller.nodes.keys());
nodeIds.add(controller.ownNodeId);
const ret = new GetSerialApiInitDataResponse({
zwaveApiVersion: controller.capabilities.zwaveApiVersion,
isPrimary: !controller.capabilities.isSecondary,
nodeType: NodeType.Controller,
supportsTimers: controller.capabilities.supportsTimers,
isSIS: controller.capabilities.isSISPresent
&& controller.capabilities.isStaticUpdateController,
nodeIds: [...nodeIds],
zwaveChipType: controller.capabilities.zwaveChipType,
});
await controller.sendMessageToHost(ret);
return true;
}
},
};
const respondToSoftReset = {
onHostMessage(controller, msg) {
if (msg instanceof SoftResetRequest) {
const ret = new SerialAPIStartedRequest({
wakeUpReason: SerialAPIWakeUpReason.SoftwareReset,
watchdogEnabled: controller.capabilities.watchdogEnabled,
isListening: true,
...determineNIF(),
supportsLongRange: controller.capabilities.supportsLongRange,
});
setImmediate(async () => {
await controller.sendMessageToHost(ret);
});
return true;
}
},
};
const respondToGetNodeProtocolInfo = {
async onHostMessage(controller, msg) {
if (msg instanceof GetNodeProtocolInfoRequest) {
if (msg.requestedNodeId === controller.ownNodeId) {
const ret = new GetNodeProtocolInfoResponse({
...determineNIF(),
nodeType: NodeType.Controller,
isListening: true,
isFrequentListening: false,
isRouting: true,
supportsSecurity: false,
supportsBeaming: true,
supportedDataRates: [9600, 40000, 100000],
optionalFunctionality: true,
protocolVersion: 3,
});
await controller.sendMessageToHost(ret);
return true;
}
else if (controller.nodes.has(msg.requestedNodeId)) {
const nodeCaps = controller.nodes.get(msg.requestedNodeId).capabilities;
const ret = new GetNodeProtocolInfoResponse({
...nodeCaps,
});
await controller.sendMessageToHost(ret);
return true;
}
}
},
};
const handleSendData = {
async onHostMessage(controller, msg) {
if (msg instanceof SendDataRequest) {
// Check if this command is legal right now
const state = controller.state.get(MockControllerStateKeys.CommunicationState);
if (state != undefined
&& state !== MockControllerCommunicationState.Idle) {
throw new Error("Received SendDataRequest while not idle");
}
// Put the controller into sending state
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Sending);
// Notify the host that the message was sent
const res = new SendDataResponse({
wasSent: true,
});
await controller.sendMessageToHost(res);
// We deferred parsing of the CC because it requires the node's host to do so.
// Now we can do that. Also set the CC node ID to the controller's own node ID,
// so CC knows it came from the controller's node ID.
const node = controller.nodes.get(msg.getNodeId());
// Create a lazy frame, so it can be deserialized on the node after a short delay to simulate radio transmission
const lazyPayload = createLazySendDataPayload(controller, node, msg);
const ackRequested = !!(msg.transmitOptions & TransmitOptions.ACK);
const lazyFrame = createMockZWaveRequestFrame(lazyPayload, {
ackRequested,
});
const ackPromise = controller.sendToNode(node, lazyFrame);
if (msg.callbackId !== 0) {
// Put the controller into waiting state
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.WaitingForNode);
// If an ACK is requested, wait for the ACK and notify the host
let ack = true;
if (ackRequested) {
try {
const ackResult = await ackPromise;
ack = !!ackResult?.ack;
}
catch (e) {
// We want to know when we're using a command in tests that cannot be decoded yet
if (isZWaveError(e)
&& e.code
=== ZWaveErrorCodes
.Deserialization_NotImplemented) {
console.error(e.message);
throw e;
}
// Treat all other errors as no response
ack = false;
}
}
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Idle);
const cb = new SendDataRequestTransmitReport({
callbackId: msg.callbackId,
transmitStatus: ack
? TransmitStatus.OK
: TransmitStatus.NoAck,
});
await controller.sendMessageToHost(cb);
}
else {
// No callback was requested, we're done
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Idle);
}
return true;
}
},
};
const handleSendDataMulticast = {
async onHostMessage(controller, msg) {
if (msg instanceof SendDataMulticastRequest) {
// Check if this command is legal right now
const state = controller.state.get(MockControllerStateKeys.CommunicationState);
if (state != undefined
&& state !== MockControllerCommunicationState.Idle) {
throw new Error("Received SendDataMulticastRequest while not idle");
}
// Put the controller into sending state
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Sending);
// Notify the host that the message was sent
const res = new SendDataMulticastResponse({
wasSent: true,
});
await controller.sendMessageToHost(res);
// We deferred parsing of the CC because it requires the node's host to do so.
// Now we can do that. Also set the CC node ID to the controller's own node ID,
// so CC knows it came from the controller's node ID.
const nodeIds = msg.nodeIds;
const ackRequested = !!(msg.transmitOptions & TransmitOptions.ACK);
const ackPromises = nodeIds.map((nodeId) => {
const node = controller.nodes.get(nodeId);
// Create a lazy frame, so it can be deserialized on the node after a short delay to simulate radio transmission
const lazyPayload = createLazySendDataPayload(controller, node, msg);
const lazyFrame = createMockZWaveRequestFrame(lazyPayload, {
ackRequested,
});
const ackPromise = controller.sendToNode(node, lazyFrame);
return ackPromise;
});
if (msg.callbackId !== 0) {
// Put the controller into waiting state
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.WaitingForNode);
// If an ACK is requested, wait for the ACKs and notify the host
let ack = true;
if (ackRequested) {
try {
const ackResults = await Promise.all(ackPromises);
ack = ackResults.every((result) => !!result?.ack);
}
catch (e) {
// We want to know when we're using a command in tests that cannot be decoded yet
if (isZWaveError(e)
&& e.code
=== ZWaveErrorCodes
.Deserialization_NotImplemented) {
console.error(e.message);
throw e;
}
// Treat all other errors as no response
ack = false;
}
}
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Idle);
const cb = new SendDataMulticastRequestTransmitReport({
callbackId: msg.callbackId,
transmitStatus: ack
? TransmitStatus.OK
: TransmitStatus.NoAck,
});
await controller.sendMessageToHost(cb);
}
else {
// No callback was requested, we're done
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Idle);
}
return true;
}
},
};
const handleSendDataBridge = {
async onHostMessage(controller, msg) {
if (msg instanceof SendDataBridgeRequest) {
// Check if this command is legal right now
const state = controller.state.get(MockControllerStateKeys.CommunicationState);
if (state != undefined
&& state !== MockControllerCommunicationState.Idle) {
throw new Error("Received SendDataBridgeRequest while not idle");
}
// Put the controller into sending state
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Sending);
// Notify the host that the message was sent
const res = new SendDataBridgeResponse({
wasSent: true,
});
await controller.sendMessageToHost(res);
// We deferred parsing of the CC because it requires the node's host to do so.
// Now we can do that. Also set the CC node ID to the controller's own node ID,
// so CC knows it came from the controller's node ID.
const node = controller.nodes.get(msg.getNodeId());
// Create a lazy frame, so it can be deserialized on the node after a short delay to simulate radio transmission
const lazyPayload = createLazySendDataPayload(controller, node, msg);
const ackRequested = !!(msg.transmitOptions & TransmitOptions.ACK);
const lazyFrame = createMockZWaveRequestFrame(lazyPayload, {
ackRequested,
});
const ackPromise = controller.sendToNode(node, lazyFrame);
if (msg.callbackId !== 0) {
// Put the controller into waiting state
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.WaitingForNode);
// If an ACK is requested, wait for the ACK and notify the host
let ack = true;
if (ackRequested) {
try {
const ackResult = await ackPromise;
ack = !!ackResult?.ack;
}
catch (e) {
// We want to know when we're using a command in tests that cannot be decoded yet
if (isZWaveError(e)
&& e.code
=== ZWaveErrorCodes
.Deserialization_NotImplemented) {
console.error(e.message);
throw e;
}
// Treat all other errors as no response
ack = false;
}
}
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Idle);
const cb = new SendDataBridgeRequestTransmitReport({
callbackId: msg.callbackId,
transmitStatus: ack
? TransmitStatus.OK
: TransmitStatus.NoAck,
});
await controller.sendMessageToHost(cb);
}
else {
// No callback was requested, we're done
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Idle);
}
return true;
}
},
};
const handleSendDataMulticastBridge = {
async onHostMessage(controller, msg) {
if (msg instanceof SendDataMulticastBridgeRequest) {
// Check if this command is legal right now
const state = controller.state.get(MockControllerStateKeys.CommunicationState);
if (state != undefined
&& state !== MockControllerCommunicationState.Idle) {
throw new Error("Received SendDataMulticastBridgeRequest while not idle");
}
// Put the controller into sending state
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Sending);
// Notify the host that the message was sent
const res = new SendDataMulticastBridgeResponse({
wasSent: true,
});
await controller.sendMessageToHost(res);
// We deferred parsing of the CC because it requires the node's host to do so.
// Now we can do that. Also set the CC node ID to the controller's own node ID,
// so CC knows it came from the controller's node ID.
const nodeIds = msg.nodeIds;
const ackRequested = !!(msg.transmitOptions & TransmitOptions.ACK);
const ackPromises = nodeIds.map((nodeId) => {
const node = controller.nodes.get(nodeId);
// Create a lazy frame, so it can be deserialized on the node after a short delay to simulate radio transmission
const lazyPayload = createLazySendDataPayload(controller, node, msg);
const lazyFrame = createMockZWaveRequestFrame(lazyPayload, {
ackRequested,
});
const ackPromise = controller.sendToNode(node, lazyFrame);
return ackPromise;
});
if (msg.callbackId !== 0) {
// Put the controller into waiting state
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.WaitingForNode);
// If an ACK is requested, wait for the ACKs and notify the host
let ack = true;
if (ackRequested) {
try {
const ackResults = await Promise.all(ackPromises);
ack = ackResults.every((result) => !!result?.ack);
}
catch (e) {
// We want to know when we're using a command in tests that cannot be decoded yet
if (isZWaveError(e)
&& e.code
=== ZWaveErrorCodes
.Deserialization_NotImplemented) {
console.error(e.message);
throw e;
}
// Treat all other errors as no response
ack = false;
}
}
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Idle);
const cb = new SendDataMulticastBridgeRequestTransmitReport({
callbackId: msg.callbackId,
transmitStatus: ack
? TransmitStatus.OK
: TransmitStatus.NoAck,
});
await controller.sendMessageToHost(cb);
}
else {
// No callback was requested, we're done
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Idle);
}
return true;
}
},
};
const handleRequestNodeInfo = {
async onHostMessage(controller, msg) {
if (msg instanceof RequestNodeInfoRequest) {
// Check if this command is legal right now
const state = controller.state.get(MockControllerStateKeys.CommunicationState);
if (state != undefined
&& state !== MockControllerCommunicationState.Idle) {
throw new Error("Received RequestNodeInfoRequest while not idle");
}
// Put the controller into sending state
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Sending);
// Send the data to the node
const node = controller.nodes.get(msg.getNodeId());
const command = new ZWaveProtocolCCRequestNodeInformationFrame({
nodeId: controller.ownNodeId,
});
const frame = createMockZWaveRequestFrame(command, {
ackRequested: false,
});
void controller.sendToNode(node, frame);
const nodeInfoPromise = controller.expectNodeCC(node, (cc) => cc instanceof ZWaveProtocolCCNodeInformationFrame, {
timeout: MOCK_FRAME_ACK_TIMEOUT,
// Prevent forwarding the NIF to the host. Otherwise it will mess with
// the state tracking in the MockController.
preventDefault: true,
});
// Notify the host that the message was sent
const res = new RequestNodeInfoResponse({
wasSent: true,
});
await controller.sendMessageToHost(res);
// Put the controller into waiting state
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.WaitingForNode);
// Wait for node information and notify the host
let cb;
try {
const nodeInfo = await nodeInfoPromise;
cb = new ApplicationUpdateRequestNodeInfoReceived({
nodeInformation: {
...nodeInfo,
nodeId: nodeInfo.nodeId,
},
});
}
catch {
cb = new ApplicationUpdateRequestNodeInfoRequestFailed();
}
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Idle);
await controller.sendMessageToHost(cb);
return true;
}
},
};
async function transmitSUCReturnRoute(controller, node, callbackId, options) {
const expectCallback = callbackId !== 0;
// Send the command to the node
const command = new ZWaveProtocolCCAssignSUCReturnRoute({
nodeId: node.id,
destinationNodeId: controller.ownNodeId,
routeIndex: 0, // don't care
...options,
});
const frame = createMockZWaveRequestFrame(command, {
ackRequested: expectCallback,
});
const ackPromise = controller.sendToNode(node, frame);
let ack = false;
if (expectCallback) {
// Put the controller into waiting state
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.WaitingForNode);
// Wait for the ACK and notify the host
try {
const ackResult = await ackPromise;
ack = !!ackResult?.ack;
}
catch {
// No response
}
}
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Idle);
return ack;
}
const handleDeleteSUCReturnRoute = {
async onHostMessage(controller, msg) {
if (msg instanceof DeleteSUCReturnRouteRequest) {
// Check if this command is legal right now
const state = controller.state.get(MockControllerStateKeys.CommunicationState);
if (state != undefined
&& state !== MockControllerCommunicationState.Idle) {
throw new Error("Received DeleteSUCReturnRouteRequest while not idle");
}
// Put the controller into sending state
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Sending);
const expectCallback = msg.callbackId !== 0;
// Send the command to the node
const node = controller.nodes.get(msg.getNodeId());
// Respond with success
const response = new DeleteSUCReturnRouteResponse({
wasExecuted: true,
});
await controller.sendMessageToHost(response);
const ack = await transmitSUCReturnRoute(controller, node, msg.callbackId ?? 0,
// Set the empty route
{
destinationSpeed: ZWaveDataRate["9k6"],
destinationWakeUp: WakeUpTime.None,
repeaters: [],
});
if (expectCallback) {
const transmitReport = new DeleteSUCReturnRouteRequestTransmitReport({
callbackId: msg.callbackId,
transmitStatus: ack
? TransmitStatus.OK
: TransmitStatus.NoAck,
});
await controller.sendMessageToHost(transmitReport);
}
return true;
}
},
};
const handleAssignSUCReturnRoute = {
async onHostMessage(controller, msg) {
if (msg instanceof AssignSUCReturnRouteRequest) {
// Check if this command is legal right now
const state = controller.state.get(MockControllerStateKeys.CommunicationState);
if (state != undefined
&& state !== MockControllerCommunicationState.Idle) {
throw new Error("Received AssignSUCReturnRouteRequest while not idle");
}
// Put the controller into sending state
controller.state.set(MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Sending);
const expectCallback = msg.callbackId !== 0;
// Send the command to the node
const node = controller.nodes.get(msg.getNodeId());
// Notify the host that the message was sent
const res = new AssignSUCReturnRouteResponse({
wasExecuted: true,
});
await controller.sendMessageToHost(res);
// Assign uses 100kbps speed and empty repeaters (supports beaming)
const ack = await transmitSUCReturnRoute(controller, node, msg.callbackId ?? 0,
// Don't care about the actual settings here
{
destinationSpeed: ZWaveDataRate["100k"],
destinationWakeUp: WakeUpTime.None,
repeaters: [],
});
if (expectCallback) {
const cb = new AssignSUCReturnRouteRequestTransmitReport({
callbackId: msg.callbackId,
transmitStatus: ack
? TransmitStatus.OK
: TransmitStatus.NoAck,
});
await controller.sendMessageToHost(cb);
}
return true;
}
},
};
const handleAddNode = {
async onHostMessage(controller, msg) {
if (msg instanceof AddNodeToNetworkRequest) {
// Check if this command is legal right now
const state = controller.state.get(MockControllerStateKeys.InclusionState);
const expectCallback = msg.callbackId !== 0;
let cb;
if (state === MockControllerInclusionState.AddingNode) {
// While adding, only accept stop commands
if (msg.addNodeType === AddNodeType.Stop) {
controller.state.set(MockControllerStateKeys.InclusionState, MockControllerInclusionState.Idle);
// If there's a node pending inclusion, send Done status with node ID
const pendingNode = controller.nodePendingInclusion;
if (pendingNode) {
cb = new AddNodeToNetworkRequestStatusReport({
callbackId: msg.callbackId,
status: AddNodeStatus.Done,
nodeId: pendingNode.id,
});
// Clear the pending node
controller.nodePendingInclusion = undefined;
}
else {
cb = new AddNodeToNetworkRequestStatusReport({
callbackId: msg.callbackId,
status: AddNodeStatus.Failed,
});
}
}
else {
cb = new AddNodeToNetworkRequestStatusReport({
callbackId: msg.callbackId,
status: AddNodeStatus.Failed,
});
}
}
else if (state === MockControllerInclusionState.RemovingNode) {
// Cannot start adding nodes while removing one
cb = new AddNodeToNetworkRequestStatusReport({
callbackId: msg.callbackId,
status: AddNodeStatus.Failed,
});
}
else {
// Idle
// Set the controller into "adding node" state
controller.state.set(MockControllerStateKeys.InclusionState, MockControllerInclusionState.AddingNode);
cb = new AddNodeToNetworkRequestStatusReport({
callbackId: msg.callbackId,
status: AddNodeStatus.Ready,
});
// If there's a node pending inclusion, simulate the inclusion sequence
// after responding to the add request
if (controller.nodePendingInclusion) {
const { setup: testSpecificSetup, ...nodeOptions } = controller.nodePendingInclusion;
const node = await MockNode.create({
controller,
...nodeOptions,
});
// Apply default behaviors that are required for interacting with the driver correctly
node.defineBehavior(...createDefaultMockNodeBehaviors());
// Allow the tests to set up additional behavior before inclusion happens
testSpecificSetup?.(node);
const supportedCCs = [...node.implementedCCs]
.filter(([, info]) => info.isSupported && info.version > 0)
.map(([cc]) => cc);
const nodeInfo = {
nodeId: node.id,
basicDeviceClass: node.capabilities.basicDeviceClass,
genericDeviceClass: node.capabilities.genericDeviceClass,
specificDeviceClass: node.capabilities.specificDeviceClass,
supportedCCs,
};
void (async () => {
// Wait a bit, then send NodeFound
await wait(10);
const nodeFoundMsg = new AddNodeToNetworkRequestStatusReport({
callbackId: msg.callbackId,
status: AddNodeStatus.NodeFound,
});
await controller.sendMessageToHost(nodeFoundMsg);
// Wait a bit, then send AddingSlave with node info
await wait(10);
const addingSlaveMsg = new AddNodeToNetworkRequestStatusReport({
callbackId: msg.callbackId,
status: AddNodeStatus.AddingSlave,
nodeInfo,
});
await controller.sendMessageToHost(addingSlaveMsg);
// Wait a bit, add the node to the controller's list, then send ProtocolDone
await wait(10);
controller.addNode(node);
const protocolDoneMsg = new AddNodeToNetworkRequestStatusReport({
callbackId: msg.callbackId,
status: AddNodeStatus.ProtocolDone,
});
await controller.sendMessageToHost(protocolDoneMsg);
})();
}
}
if (expectCallback && cb) {
await controller.sendMessageToHost(cb);
}
return true;
}
},
};
const handleRemoveNode = {
async onHostMessage(controller, msg) {
if (msg instanceof RemoveNodeFromNetworkRequest) {
// Check if this command is legal right now
const state = controller.state.get(MockControllerStateKeys.InclusionState);
const expectCallback = msg.callbackId !== 0;
let cb;
if (state === MockControllerInclusionState.RemovingNode) {
// While removing, only accept stop commands
if (msg.removeNodeType === RemoveNodeType.Stop) {
controller.state.set(MockControllerStateKeys.InclusionState, MockControllerInclusionState.Idle);
cb = new RemoveNodeFromNetworkRequestStatusReport({
callbackId: msg.callbackId,
status: RemoveNodeStatus.Failed,
});
}
else {
cb = new RemoveNodeFromNetworkRequestStatusReport({
callbackId: msg.callbackId,
status: RemoveNodeStatus.Failed,
});
}
}
else if (state === MockControllerInclusionState.AddingNode) {
// Cannot start removing nodes while adding one
cb = new RemoveNodeFromNetworkRequestStatusReport({
callbackId: msg.callbackId,
status: RemoveNodeStatus.Failed,
});
}
else {
// Idle
// Set the controller into "removing node" state
// For now we don't actually do anything in that state
controller.state.set(MockControllerStateKeys.InclusionState, MockControllerInclusionState.RemovingNode);
cb = new RemoveNodeFromNetworkRequestStatusReport({
callbackId: msg.callbackId,
status: RemoveNodeStatus.Ready,
});
}
if (expectCallback && cb) {
await controller.sendMessageToHost(cb);
}
return true;
}
},
};
const forwardCommandClassesToHost = {
async onNodeFrame(controller, node, frame) {
if (frame.type === MockZWaveFrameType.Request
&& frame.payload instanceof CommandClass
&& !(frame.payload instanceof ZWaveProtocolCC)) {
// This is a CC that is meant for the host application
const msg = new ApplicationCommandRequest({
command: frame.payload,
});
// Nodes send commands TO the controller, so we need to fix the node ID before forwarding
msg.getNodeId = () => node.id;
// Simulate a serialized frame being transmitted via radio before receiving it
await controller.sendMessageToHost(msg, node);
return true;
}
},
};
const forwardUnsolicitedNIF = {
async onNodeFrame(controller, node, frame) {
if (frame.type === MockZWaveFrameType.Request
&& frame.payload instanceof ZWaveProtocolCCNodeInformationFrame) {
const updateRequest = new ApplicationUpdateRequestNodeInfoReceived({
nodeInformation: {
...frame.payload,
nodeId: frame.payload.nodeId,
},
});
// Simulate a serialized frame being transmitted via radio before receiving it
await controller.sendMessageToHost(updateRequest, node);
return true;
}
},
};
/** Predefined default behaviors that are required for interacting with the driver correctly */
export function createDefaultBehaviors() {
return [
respondToGetControllerId,
respondToGetSerialApiCapabilities,
respondToGetControllerVersion,
respondToGetControllerCapabilities,
respondToGetSUCNodeId,
respondToGetSerialApiInitData,
respondToSoftReset,
respondToGetNodeProtocolInfo,
handleSendData,
handleSendDataMulticast,
handleSendDataBridge,
handleSendDataMulticastBridge,
handleRequestNodeInfo,
handleDeleteSUCReturnRoute,
handleAssignSUCReturnRoute,
handleAddNode,
handleRemoveNode,
forwardCommandClassesToHost,
forwardUnsolicitedNIF,
];
}
//# sourceMappingURL=MockControllerBehaviors.js.map