UNPKG

zwave-js

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

383 lines (382 loc) 15.2 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var mockServer_exports = {}; __export(mockServer_exports, { MockServer: () => MockServer, createMockNodeOptionsFromDump: () => createMockNodeOptionsFromDump }); module.exports = __toCommonJS(mockServer_exports); var import_ciao = require("@homebridge/ciao"); var import_cc = require("@zwave-js/cc"); var import_core = require("@zwave-js/core"); var import_serial = require("@zwave-js/serial"); var import_mock = require("@zwave-js/serial/mock"); var import_shared = require("@zwave-js/shared"); var import_testing = require("@zwave-js/testing"); var import_deferred_promise = require("alcalzone-shared/deferred-promise"); var import_node_net = require("node:net"); var import_Testing = require("./Testing.js"); var import_Utils = require("./Utils.js"); class MockServer { static { __name(this, "MockServer"); } options; constructor(options = {}) { this.options = options; } serialport; binding; server; responder; service; mockController; mockNodes; async start() { const { serial, port: mockPort } = await (0, import_mock.createAndOpenMockedZWaveSerialPort)(); this.serialport = serial; this.binding = mockPort; console.log("Mock serial port opened"); ({ mockController: this.mockController, mockNodes: this.mockNodes } = await prepareMocks(mockPort, serial, this.options.config?.controller, this.options.config?.nodes)); if (typeof this.options.config?.onInit === "function") { this.options.config.onInit(this.mockController, this.mockNodes); } const faucet = new import_serial.Faucet(serial.readable); this.server = (0, import_node_net.createServer)((socket) => { if (!this.serialport) { console.error("Serial port not initialized"); socket.destroy(); return; } console.log("Client connected"); const writable = new WritableStream({ write: /* @__PURE__ */ __name((chunk) => { if (chunk.type !== import_serial.ZWaveSerialFrameType.SerialAPI) return; if (typeof chunk.data === "number") { socket.write(Uint8Array.from([chunk.data])); } else { socket.write(chunk.data); } }, "write") }); faucet.connect(writable); socket.on("close", () => { faucet.disconnect(); void writable.close(); console.log("Client disconnected"); }); socket.on("data", async (chunk) => { await this.serialport?.writeAsync(chunk).catch((e) => { console.error(`Error writing to serialport`, e); }); }); }); const port = this.options.port ?? 5555; this.responder = (0, import_ciao.getResponder)(); this.service = this.responder.createService({ name: "zwave-mock-server", type: "zwave", protocol: "tcp", port, txt: { manufacturer: "Z-Wave JS", model: "Mock Server" } }); this.server.maxConnections = 1; const promise = (0, import_deferred_promise.createDeferredPromise)(); this.server.on("error", (err) => { if (err.code === "EADDRINUSE") { promise.reject(err); } }); this.server.listen({ host: this.options.interface, port }, async () => { const address = this.server.address(); console.log(`Server listening on tcp://${address.address}:${address.port}`); promise.resolve(); try { await this.service.advertise(); console.log(`Enabled mDNS service discovery.`); } catch (e) { console.error(`Failed to enable mDNS service discovery: ${(0, import_shared.getErrorMessage)(e)}`); } }); } async stop() { console.log("Shutting down mock server..."); await this.service?.end(); await this.service?.destroy(); await this.responder?.shutdown(); this.mockController?.destroy(); this.server?.close(); await this.serialport?.close(); this.binding?.destroy(); console.log("Mock server shut down"); } } async function prepareMocks(mockPort, serial, controller = {}, nodes = []) { const mockController = await import_testing.MockController.create({ homeId: 2119630849, ownNodeId: 1, ...controller, mockPort, serial }); mockController.defineBehavior(...(0, import_Testing.createDefaultMockControllerBehaviors)()); if (controller.behaviors) { mockController.defineBehavior(...controller.behaviors); } const mockNodes = []; for (const node of nodes) { const mockNode = await import_testing.MockNode.create({ ...node, controller: mockController }); mockController.addNode(mockNode); mockNodes.push(mockNode); mockNode.defineBehavior(...(0, import_Testing.createDefaultMockNodeBehaviors)()); if (node.behaviors) { mockNode.defineBehavior(...node.behaviors); } } return { mockController, mockNodes }; } __name(prepareMocks, "prepareMocks"); function createMockNodeOptionsFromDump(dump) { const ret = { id: dump.id }; ret.capabilities = (0, import_testing.getDefaultMockNodeCapabilities)(); if (typeof dump.isListening === "boolean") { ret.capabilities.isListening = dump.isListening; } if (dump.isFrequentListening !== "unknown") { ret.capabilities.isFrequentListening = dump.isFrequentListening; } if (typeof dump.isRouting === "boolean") { ret.capabilities.isRouting = dump.isRouting; } if (typeof dump.supportsBeaming === "boolean") { ret.capabilities.supportsBeaming = dump.supportsBeaming; } if (typeof dump.supportsSecurity === "boolean") { ret.capabilities.supportsSecurity = dump.supportsSecurity; } if (typeof dump.supportedDataRates === "boolean") { ret.capabilities.supportedDataRates = dump.supportedDataRates; } if (import_Utils.ProtocolVersion[dump.protocol] !== void 0) { ret.capabilities.protocolVersion = import_Utils.ProtocolVersion[dump.protocol]; } if (dump.deviceClass !== "unknown") { ret.capabilities.basicDeviceClass = dump.deviceClass.basic.key; ret.capabilities.genericDeviceClass = dump.deviceClass.generic.key; ret.capabilities.specificDeviceClass = dump.deviceClass.specific.key; } ret.capabilities.firmwareVersion = dump.fingerprint.firmwareVersion; ret.capabilities.manufacturerId = parseInt(dump.fingerprint.manufacturerId, 16); ret.capabilities.productType = parseInt(dump.fingerprint.productType, 16); ret.capabilities.productId = parseInt(dump.fingerprint.productId, 16); for (const [ccName, ccDump] of Object.entries(dump.commandClasses)) { const ccId = import_core.CommandClasses[ccName]; if (ccId == void 0) continue; if (ccId === import_core.CommandClasses.Security || ccId === import_core.CommandClasses["Security 2"]) { continue; } if (ccId === import_core.CommandClasses.Supervision) { continue; } if (ccId === import_core.CommandClasses["Transport Service"]) { continue; } ret.capabilities.commandClasses ??= []; ret.capabilities.commandClasses.push(createCCCapabilitiesFromDump(ccId, ccDump)); } if (dump.endpoints) { for (const [indexStr, endpointDump] of Object.entries(dump.endpoints)) { const epCaps = (0, import_testing.getDefaultMockEndpointCapabilities)( // @ts-expect-error We are initializing the device classes above ret.capabilities ); let epCCs; if (endpointDump.deviceClass !== "unknown") { epCaps.genericDeviceClass = endpointDump.deviceClass.generic.key; epCaps.specificDeviceClass = endpointDump.deviceClass.specific.key; } for (const [ccName, ccDump] of Object.entries(endpointDump.commandClasses)) { const ccId = import_core.CommandClasses[ccName]; if (ccId == void 0) continue; if (ccId === import_core.CommandClasses.Security || ccId === import_core.CommandClasses["Security 2"]) { continue; } epCCs ??= []; epCCs.push(createCCCapabilitiesFromDump(ccId, ccDump)); } ret.capabilities.endpoints ??= []; ret.capabilities.endpoints.push({ ...epCaps, commandClasses: epCCs }); } } return ret; } __name(createMockNodeOptionsFromDump, "createMockNodeOptionsFromDump"); function createCCCapabilitiesFromDump(ccId, dump) { const ret = { ccId, isSupported: dump.isSupported, isControlled: dump.isControlled, secure: dump.secure, version: dump.version }; if (ccId === import_core.CommandClasses.Configuration) { Object.assign(ret, createConfigurationCCCapabilitiesFromDump(dump)); } else if (ccId === import_core.CommandClasses.Notification) { Object.assign(ret, createNotificationCCCapabilitiesFromDump(dump)); } else if (ccId === import_core.CommandClasses["Binary Switch"]) { Object.assign(ret, createBinarySwitchCCCapabilitiesFromDump(dump)); } else if (ccId === import_core.CommandClasses["Multilevel Switch"]) { Object.assign(ret, createMultilevelSwitchCCCapabilitiesFromDump(dump)); } else if (ccId === import_core.CommandClasses["Sound Switch"]) { Object.assign(ret, createSoundSwitchCCCapabilitiesFromDump(dump)); } else if (ccId === import_core.CommandClasses["Node Naming and Location"]) { Object.assign(ret, createNodeNamingAndLocationCCCapabilitiesFromDump(dump)); } return ret; } __name(createCCCapabilitiesFromDump, "createCCCapabilitiesFromDump"); function createConfigurationCCCapabilitiesFromDump(dump) { const ret = { bulkSupport: false, parameters: [] }; for (const val of dump.values) { if (typeof val.property !== "number") continue; if (val.propertyKey != void 0) continue; if (!val.metadata) continue; const meta = val.metadata; ret.parameters.push({ "#": val.property, valueSize: meta.valueSize ?? 1, name: meta.label, info: meta.description, format: meta.format, minValue: meta.min, maxValue: meta.max, defaultValue: meta.default, readonly: !meta.writeable }); } return ret; } __name(createConfigurationCCCapabilitiesFromDump, "createConfigurationCCCapabilitiesFromDump"); function createNotificationCCCapabilitiesFromDump(dump) { const supportsV1Alarm = findDumpedValue(dump, import_core.CommandClasses.Notification, import_cc.NotificationCCValues.supportsV1Alarm.id, false); const ret = { supportsV1Alarm, notificationTypesAndEvents: {} }; const supportedNotificationTypes = findDumpedValue(dump, import_core.CommandClasses.Notification, import_cc.NotificationCCValues.supportedNotificationTypes.id, []); for (const type of supportedNotificationTypes) { const supportedEvents = findDumpedValue(dump, import_core.CommandClasses.Notification, import_cc.NotificationCCValues.supportedNotificationEvents(type).id, []); ret.notificationTypesAndEvents[type] = supportedEvents; } return ret; } __name(createNotificationCCCapabilitiesFromDump, "createNotificationCCCapabilitiesFromDump"); function createBinarySwitchCCCapabilitiesFromDump(dump) { const defaultValue = findDumpedValue(dump, import_core.CommandClasses["Binary Switch"], import_cc.BinarySwitchCCValues.currentValue.id, void 0); return { defaultValue }; } __name(createBinarySwitchCCCapabilitiesFromDump, "createBinarySwitchCCCapabilitiesFromDump"); function createMultilevelSwitchCCCapabilitiesFromDump(dump) { const defaultValue = findDumpedValue(dump, import_core.CommandClasses["Multilevel Switch"], import_cc.MultilevelSwitchCCValues.currentValue.id, void 0); const switchType = findDumpedValue(dump, import_core.CommandClasses["Multilevel Switch"], import_cc.MultilevelSwitchCCValues.switchType.id, import_cc.SwitchType["Down/Up"]); return { defaultValue, primarySwitchType: switchType }; } __name(createMultilevelSwitchCCCapabilitiesFromDump, "createMultilevelSwitchCCCapabilitiesFromDump"); function createSoundSwitchCCCapabilitiesFromDump(dump) { const defaultToneId = findDumpedValue(dump, import_core.CommandClasses["Sound Switch"], import_cc.SoundSwitchCCValues.defaultToneId.id, 1); const defaultVolume = findDumpedValue(dump, import_core.CommandClasses["Sound Switch"], import_cc.SoundSwitchCCValues.defaultVolume.id, 50); const ret = { defaultToneId, defaultVolume, tones: [] }; const tonesMetadata = findDumpedMetadata(dump, import_core.CommandClasses["Sound Switch"], import_cc.SoundSwitchCCValues.toneId.id); if (tonesMetadata?.states) { for (const [toneIdStr, nameAndDuration] of Object.entries(tonesMetadata.states)) { const toneId = parseInt(toneIdStr); if (Number.isNaN(toneId) || toneId < 1 || toneId > 254) continue; const durationIndex = nameAndDuration.lastIndexOf("("); if (durationIndex === -1) continue; const name = nameAndDuration.slice(0, durationIndex).trim(); const duration = parseInt(nameAndDuration.slice(durationIndex + 1, -1), 10); if (Number.isNaN(duration)) continue; ret.tones.push({ name, duration }); } } return ret; } __name(createSoundSwitchCCCapabilitiesFromDump, "createSoundSwitchCCCapabilitiesFromDump"); function createNodeNamingAndLocationCCCapabilitiesFromDump(dump) { const name = findDumpedValue(dump, import_core.CommandClasses["Node Naming and Location"], import_cc.NodeNamingAndLocationCCValues.name.id, void 0); const location = findDumpedValue(dump, import_core.CommandClasses["Node Naming and Location"], import_cc.NodeNamingAndLocationCCValues.location.id, void 0); return { name, location }; } __name(createNodeNamingAndLocationCCCapabilitiesFromDump, "createNodeNamingAndLocationCCCapabilitiesFromDump"); function findDumpedValue(dump, commandClass, valueId, defaultValue) { return dump.values.find((id) => id.property === valueId.property && id.propertyKey === valueId.propertyKey)?.value ?? defaultValue; } __name(findDumpedValue, "findDumpedValue"); function findDumpedMetadata(dump, commandClass, valueId) { return dump.values.find((id) => id.property === valueId.property && id.propertyKey === valueId.propertyKey)?.metadata; } __name(findDumpedMetadata, "findDumpedMetadata"); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { MockServer, createMockNodeOptionsFromDump }); //# sourceMappingURL=mockServer.js.map