UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

352 lines (326 loc) 9.17 kB
import { getImplementedVersion } from "@zwave-js/cc"; import { ConfigManager } from "@zwave-js/config"; import { CommandClasses, FLiRS, InterviewStage, IZWaveEndpoint, IZWaveNode, Maybe, MessagePriority, NodeStatus, SecurityClass, securityClassOrder, unknownBoolean, } from "@zwave-js/core"; import type { ZWaveHost } from "@zwave-js/host"; import { expectedResponse, FunctionType, Message, MessageType, messageTypes, priority, } from "@zwave-js/serial"; import type { Driver } from "../driver/Driver"; import type { ZWaveNode } from "../node/Node"; import * as nodeUtils from "../node/utils"; import { SendDataRequest } from "../serialapi/transport/SendDataMessages"; const MockRequestMessageWithExpectation_FunctionType = 0xfa as unknown as FunctionType; const MockRequestMessageWithoutExpectation_FunctionType = 0xfb as unknown as FunctionType; const MockResponseMessage_FunctionType = 0xff as unknown as FunctionType; @messageTypes( MessageType.Request, MockRequestMessageWithExpectation_FunctionType, ) @expectedResponse(MockResponseMessage_FunctionType) @priority(MessagePriority.Normal) export class MockRequestMessageWithExpectation extends Message {} @messageTypes( MessageType.Request, MockRequestMessageWithoutExpectation_FunctionType, ) @priority(MessagePriority.Normal) export class MockRequestMessageWithoutExpectation extends Message {} @messageTypes(MessageType.Response, MockResponseMessage_FunctionType) export class MockResponseMessage extends Message {} export const mockDriverDummyCallbackId = 0xfe; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function createEmptyMockDriver() { const ret = { sendMessage: jest.fn().mockImplementation(() => Promise.resolve()), sendCommand: jest.fn(), getSafeCCVersionForNode: jest .fn() .mockImplementation( ( ccId: CommandClasses, nodeId: number, endpointIndex: number = 0, ) => { if ( ret.controller?.nodes instanceof Map && ret.controller.nodes.has(nodeId) ) { const node: ZWaveNode = ret.controller.nodes.get(nodeId); const ccVersion = node .getEndpoint(endpointIndex)! .getCCVersion(ccId); if (ccVersion > 0) return ccVersion; } // default to the implemented version return getImplementedVersion(ccId); }, ), isCCSecure: jest.fn().mockImplementation(() => false), getNextCallbackId: jest .fn() .mockImplementation(() => mockDriverDummyCallbackId), controller: { nodes: new Map(), ownNodeId: 1, }, get nodes() { return ret.controller.nodes; }, get ownNodeId() { return ret.controller.ownNodeId; }, valueDB: new Map(), getValueDB: (nodeId: number) => { return ret.controller.nodes.get(nodeId).valueDB; }, metadataDB: new Map(), networkCache: new Map(), cacheGet: jest.fn().mockImplementation( <T>( cacheKey: string, options?: { reviver?: (value: any) => T; }, ): T | undefined => { let _ret = ret.networkCache.get(cacheKey); if ( _ret !== undefined && typeof options?.reviver === "function" ) { try { _ret = options.reviver(_ret); } catch { // ignore, invalid entry } } return _ret; }, ), cacheSet: jest.fn().mockImplementation( <T>( cacheKey: string, value: T | undefined, options?: { serializer?: (value: T) => any; }, ): void => { if ( value !== undefined && typeof options?.serializer === "function" ) { value = options.serializer(value); } if (value === undefined) { ret.networkCache.delete(cacheKey); } else { ret.networkCache.set(cacheKey, value); } }, ), options: { timeouts: { ack: 1000, byte: 150, response: 1600, report: 1600, nonce: 5000, sendDataCallback: 65000, }, attempts: { sendData: 3, controller: 3, }, }, driverLog: new Proxy( {}, { get() { return () => { /* intentionally empty */ }; }, }, ), controllerLog: new Proxy( {}, { get() { return () => { /* intentionally empty */ }; }, }, ), configManager: new ConfigManager(), }; ret.sendCommand.mockImplementation(async (command, options) => { const msg = new SendDataRequest(ret as unknown as Driver, { command, }); const resp = await ret.sendMessage(msg, options); return resp?.command; }); return ret; } export interface CreateTestNodeOptions { id: number; isListening?: boolean | undefined; isFrequentListening?: FLiRS | undefined; status?: NodeStatus; interviewStage?: InterviewStage; isSecure?: Maybe<boolean>; numEndpoints?: number; supportsCC?: (cc: CommandClasses) => boolean; controlsCC?: (cc: CommandClasses) => boolean; isCCSecure?: (cc: CommandClasses) => boolean; getCCVersion?: (cc: CommandClasses) => number; } export interface TestNode extends IZWaveNode { setEndpoint(endpoint: CreateTestEndpointOptions): void; } export function createTestNode( host: ZWaveHost, options: CreateTestNodeOptions, ): TestNode { const endpointCache = new Map<number, IZWaveEndpoint>(); const securityClasses = new Map<SecurityClass, boolean>(); const ret: TestNode = { id: options.id, ...createTestEndpoint(host, { nodeId: options.id, index: 0, supportsCC: options.supportsCC, controlsCC: options.controlsCC, isCCSecure: options.isCCSecure, getCCVersion: options.getCCVersion, }), isListening: options.isListening ?? true, isFrequentListening: options.isFrequentListening ?? false, get canSleep() { if (ret.isListening == undefined) return undefined; if (ret.isFrequentListening == undefined) return undefined; return !ret.isListening && !ret.isFrequentListening; }, status: options.status ?? (options.isListening ? NodeStatus.Alive : NodeStatus.Asleep), interviewStage: options.interviewStage ?? InterviewStage.Complete, setEndpoint: (endpoint) => { endpointCache.set( endpoint.index, createTestEndpoint(host, { nodeId: options.id, index: endpoint.index, supportsCC: endpoint.supportsCC ?? options.supportsCC, controlsCC: endpoint.controlsCC ?? options.controlsCC, isCCSecure: endpoint.isCCSecure ?? options.isCCSecure, getCCVersion: endpoint.getCCVersion ?? options.getCCVersion, }), ); }, getEndpoint: ((index: number) => { // When the endpoint count is known, return undefined for non-existent endpoints if ( options.numEndpoints != undefined && index > options.numEndpoints ) { return undefined; } if (!endpointCache.has(index)) { ret.setEndpoint( createTestEndpoint(host, { nodeId: options.id, index, supportsCC: options.supportsCC, controlsCC: options.controlsCC, isCCSecure: options.isCCSecure, getCCVersion: options.getCCVersion, }), ); } return endpointCache.get(index); }) as IZWaveNode["getEndpoint"], // These are copied from Node.ts getHighestSecurityClass(): SecurityClass | undefined { if (securityClasses.size === 0) return undefined; let missingSome = false; for (const secClass of securityClassOrder) { if (securityClasses.get(secClass) === true) return secClass; if (!securityClasses.has(secClass)) { missingSome = true; } } // If we don't have the info for every security class, we don't know the highest one yet return missingSome ? undefined : SecurityClass.None; }, hasSecurityClass(securityClass: SecurityClass): Maybe<boolean> { return securityClasses.get(securityClass) ?? unknownBoolean; }, setSecurityClass(securityClass: SecurityClass, granted: boolean): void { securityClasses.set(securityClass, granted); }, get isSecure(): Maybe<boolean> { const securityClass = ret.getHighestSecurityClass(); if (securityClass == undefined) return unknownBoolean; if (securityClass === SecurityClass.None) return false; return true; }, }; endpointCache.set(0, ret); // If the number of endpoints are given, use them as the individual endpoint count if (options.numEndpoints != undefined) { nodeUtils.setIndividualEndpointCount(host, ret, options.numEndpoints); nodeUtils.setAggregatedEndpointCount(host, ret, 0); nodeUtils.setMultiChannelInterviewComplete(host, ret, true); } return ret; } export interface CreateTestEndpointOptions { nodeId: number; index: number; supportsCC?: (cc: CommandClasses) => boolean; controlsCC?: (cc: CommandClasses) => boolean; isCCSecure?: (cc: CommandClasses) => boolean; getCCVersion?: (cc: CommandClasses) => number; } export function createTestEndpoint( host: ZWaveHost, options: CreateTestEndpointOptions, ): IZWaveEndpoint { const ret: IZWaveEndpoint = { nodeId: options.nodeId, index: options.index, supportsCC: options.supportsCC ?? (() => true), controlsCC: options.controlsCC ?? (() => false), isCCSecure: options.isCCSecure ?? (() => false), getCCVersion: options.getCCVersion ?? ((cc) => host.getSafeCCVersionForNode( cc, options.nodeId, options.index, )), }; return ret; }