zwave-js
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
383 lines (382 loc) • 15.2 kB
JavaScript
;
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