UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

498 lines (460 loc) 15 kB
import { CommandClass, WakeUpTime } from "@zwave-js/cc"; import { ZWaveProtocolCC, ZWaveProtocolCCAssignSUCReturnRoute, ZWaveProtocolCCNodeInformationFrame, ZWaveProtocolCCRequestNodeInformationFrame, } from "@zwave-js/cc/ZWaveProtocolCC"; import { NodeType, TransmitOptions, TransmitStatus, ZWaveDataRate, } from "@zwave-js/core"; import { MessageOrigin } from "@zwave-js/serial"; import { createMockZWaveRequestFrame, MockControllerBehavior, MockZWaveFrameType, MOCK_FRAME_ACK_TIMEOUT, } from "@zwave-js/testing"; import { wait } from "alcalzone-shared/async"; import { ApplicationCommandRequest } from "../serialapi/application/ApplicationCommandRequest"; import { ApplicationUpdateRequest, ApplicationUpdateRequestNodeInfoReceived, ApplicationUpdateRequestNodeInfoRequestFailed, } from "../serialapi/application/ApplicationUpdateRequest"; import { SerialAPIStartedRequest, SerialAPIWakeUpReason, } from "../serialapi/application/SerialAPIStartedRequest"; import { GetControllerCapabilitiesRequest, GetControllerCapabilitiesResponse, } from "../serialapi/capability/GetControllerCapabilitiesMessages"; import { GetControllerVersionRequest, GetControllerVersionResponse, } from "../serialapi/capability/GetControllerVersionMessages"; import { GetSerialApiCapabilitiesRequest, GetSerialApiCapabilitiesResponse, } from "../serialapi/capability/GetSerialApiCapabilitiesMessages"; import { GetSerialApiInitDataRequest, GetSerialApiInitDataResponse, } from "../serialapi/capability/GetSerialApiInitDataMessages"; import { GetControllerIdRequest, GetControllerIdResponse, } from "../serialapi/memory/GetControllerIdMessages"; import { SoftResetRequest } from "../serialapi/misc/SoftResetRequest"; import { AssignSUCReturnRouteRequest, AssignSUCReturnRouteRequestTransmitReport, AssignSUCReturnRouteResponse, } from "../serialapi/network-mgmt/AssignSUCReturnRouteMessages"; import { GetNodeProtocolInfoRequest, GetNodeProtocolInfoResponse, } from "../serialapi/network-mgmt/GetNodeProtocolInfoMessages"; import { GetSUCNodeIdRequest, GetSUCNodeIdResponse, } from "../serialapi/network-mgmt/GetSUCNodeIdMessages"; import { RequestNodeInfoRequest, RequestNodeInfoResponse, } from "../serialapi/network-mgmt/RequestNodeInfoMessages"; import { SendDataRequest, SendDataRequestTransmitReport, SendDataResponse, } from "../serialapi/transport/SendDataMessages"; import { MockControllerCommunicationState, MockControllerStateKeys, } from "./MockControllerState"; import { determineNIF } from "./NodeInformationFrame"; const respondToGetControllerId: MockControllerBehavior = { async onHostMessage(host, controller, msg) { if (msg instanceof GetControllerIdRequest) { const ret = new GetControllerIdResponse(host, { homeId: host.homeId, ownNodeId: host.ownNodeId, }); await controller.sendToHost(ret.serialize()); return true; } }, }; const respondToGetSerialApiCapabilities: MockControllerBehavior = { async onHostMessage(host, controller, msg) { if (msg instanceof GetSerialApiCapabilitiesRequest) { const ret = new GetSerialApiCapabilitiesResponse(host, { ...controller.capabilities, }); await controller.sendToHost(ret.serialize()); return true; } }, }; const respondToGetControllerVersion: MockControllerBehavior = { async onHostMessage(host, controller, msg) { if (msg instanceof GetControllerVersionRequest) { const ret = new GetControllerVersionResponse(host, { ...controller.capabilities, }); await controller.sendToHost(ret.serialize()); return true; } }, }; const respondToGetControllerCapabilities: MockControllerBehavior = { async onHostMessage(host, controller, msg) { if (msg instanceof GetControllerCapabilitiesRequest) { const ret = new GetControllerCapabilitiesResponse(host, { ...controller.capabilities, }); await controller.sendToHost(ret.serialize()); return true; } }, }; const respondToGetSUCNodeId: MockControllerBehavior = { async onHostMessage(host, controller, msg) { if (msg instanceof GetSUCNodeIdRequest) { const sucNodeId = controller.capabilities.isStaticUpdateController ? host.ownNodeId : controller.capabilities.sucNodeId; const ret = new GetSUCNodeIdResponse(host, { sucNodeId, }); await controller.sendToHost(ret.serialize()); return true; } }, }; const respondToGetSerialApiInitData: MockControllerBehavior = { async onHostMessage(host, controller, msg) { if (msg instanceof GetSerialApiInitDataRequest) { const nodeIds = new Set(controller.nodes.keys()); nodeIds.add(host.ownNodeId); const ret = new GetSerialApiInitDataResponse(host, { 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.sendToHost(ret.serialize()); return true; } }, }; const respondToSoftReset: MockControllerBehavior = { async onHostMessage(host, controller, msg) { if (msg instanceof SoftResetRequest) { const ret = new SerialAPIStartedRequest(host, { wakeUpReason: SerialAPIWakeUpReason.SoftwareReset, watchdogEnabled: controller.capabilities.watchdogEnabled, isListening: true, ...determineNIF(), supportsLongRange: controller.capabilities.supportsLongRange, }); await controller.sendToHost(ret.serialize()); return true; } }, }; const respondToGetNodeProtocolInfo: MockControllerBehavior = { async onHostMessage(host, controller, msg) { if (msg instanceof GetNodeProtocolInfoRequest) { if (msg.requestedNodeId === host.ownNodeId) { const ret = new GetNodeProtocolInfoResponse(host, { ...determineNIF(), nodeType: NodeType.Controller, isListening: true, isFrequentListening: false, isRouting: true, supportsSecurity: false, supportsBeaming: true, supportedDataRates: [9600, 40000, 100000], optionalFunctionality: true, protocolVersion: 3, }); await controller.sendToHost(ret.serialize()); return true; } else if (controller.nodes.has(msg.requestedNodeId)) { const nodeCaps = controller.nodes.get( msg.requestedNodeId, )!.capabilities; const ret = new GetNodeProtocolInfoResponse(host, { ...nodeCaps, }); await controller.sendToHost(ret.serialize()); return true; } } }, }; const handleSendData: MockControllerBehavior = { async onHostMessage(host, controller, msg) { if (msg instanceof SendDataRequest) { // Check if this command is legal right now const state = controller.state.get( MockControllerStateKeys.CommunicationState, ) as MockControllerCommunicationState | undefined; 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, ); // 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()!)!; // Simulate the frame being transmitted via radio const ackPromise = wait(node.capabilities.txDelay).then(() => { // Deserialize on the node after a short delay msg.command = CommandClass.from(node.host, { nodeId: controller.host.ownNodeId, data: msg.payload, origin: MessageOrigin.Host, }); // Send the data to the node const frame = createMockZWaveRequestFrame(msg.command, { ackRequested: !!(msg.transmitOptions & TransmitOptions.ACK), }); return controller.sendToNode(node, frame); }); // Notify the host that the message was sent const res = new SendDataResponse(host, { wasSent: true, }); await controller.sendToHost(res.serialize()); if (msg.callbackId !== 0) { // Put the controller into waiting state controller.state.set( MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.WaitingForNode, ); // Wait for the ACK and notify the host let ack = false; try { const ackResult = await ackPromise; ack = !!ackResult?.ack; } catch { // No response } controller.state.set( MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Idle, ); const cb = new SendDataRequestTransmitReport(host, { callbackId: msg.callbackId, transmitStatus: ack ? TransmitStatus.OK : TransmitStatus.NoAck, }); await controller.sendToHost(cb.serialize()); } else { // No callback was requested, we're done controller.state.set( MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Idle, ); } return true; } }, }; const handleRequestNodeInfo: MockControllerBehavior = { async onHostMessage(host, controller, msg) { if (msg instanceof RequestNodeInfoRequest) { // Check if this command is legal right now const state = controller.state.get( MockControllerStateKeys.CommunicationState, ) as MockControllerCommunicationState | undefined; 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( node.host, { nodeId: controller.host.ownNodeId }, ); const frame = createMockZWaveRequestFrame(command, { ackRequested: false, }); void controller.sendToNode(node, frame); const nodeInfoPromise = controller.expectNodeCC( node, MOCK_FRAME_ACK_TIMEOUT, (cc): cc is ZWaveProtocolCCNodeInformationFrame => cc instanceof ZWaveProtocolCCNodeInformationFrame, ); // Notify the host that the message was sent const res = new RequestNodeInfoResponse(host, { wasSent: true, }); await controller.sendToHost(res.serialize()); // Put the controller into waiting state controller.state.set( MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.WaitingForNode, ); // Wait for node information and notify the host let cb: ApplicationUpdateRequest; try { const nodeInfo = await nodeInfoPromise; cb = new ApplicationUpdateRequestNodeInfoReceived(host, { nodeInformation: { ...nodeInfo, nodeId: nodeInfo.nodeId as number, }, }); } catch (e) { cb = new ApplicationUpdateRequestNodeInfoRequestFailed(host); } controller.state.set( MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Idle, ); await controller.sendToHost(cb.serialize()); return true; } }, }; const handleAssignSUCReturnRoute: MockControllerBehavior = { async onHostMessage(host, controller, msg) { if (msg instanceof AssignSUCReturnRouteRequest) { // Check if this command is legal right now const state = controller.state.get( MockControllerStateKeys.CommunicationState, ) as MockControllerCommunicationState | undefined; 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()!)!; const command = new ZWaveProtocolCCAssignSUCReturnRoute(node.host, { nodeId: controller.host.ownNodeId, repeaters: [], // don't care routeIndex: 0, // don't care destinationSpeed: ZWaveDataRate["100k"], destinationWakeUp: WakeUpTime.None, }); const frame = createMockZWaveRequestFrame(command, { ackRequested: expectCallback, }); const ackPromise = controller.sendToNode(node, frame); // Notify the host that the message was sent const res = new AssignSUCReturnRouteResponse(host, { wasExecuted: true, }); await controller.sendToHost(res.serialize()); 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, ); if (expectCallback) { const cb = new AssignSUCReturnRouteRequestTransmitReport(host, { callbackId: msg.callbackId, transmitStatus: ack ? TransmitStatus.OK : TransmitStatus.NoAck, }); await controller.sendToHost(cb.serialize()); } return true; } }, }; const forwardCommandClassesToHost: MockControllerBehavior = { async onNodeFrame(host, 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(host, { 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 const data = msg.serialize(); await wait(node.capabilities.txDelay); // Then receive it await controller.sendToHost(data); return true; } }, }; /** Predefined default behaviors that are required for interacting with the driver correctly */ export function createDefaultBehaviors(): MockControllerBehavior[] { return [ respondToGetControllerId, respondToGetSerialApiCapabilities, respondToGetControllerVersion, respondToGetControllerCapabilities, respondToGetSUCNodeId, respondToGetSerialApiInitData, respondToSoftReset, respondToGetNodeProtocolInfo, handleSendData, handleRequestNodeInfo, handleAssignSUCReturnRoute, forwardCommandClassesToHost, ]; }