UNPKG

zwave-js

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

732 lines (731 loc) 32.7 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; 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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var Zniffer_exports = {}; __export(Zniffer_exports, { Zniffer: () => Zniffer }); module.exports = __toCommonJS(Zniffer_exports); var import_cc = require("@zwave-js/cc"); var import_config = require("@zwave-js/config"); var import_core = require("@zwave-js/core"); var import_serial = require("@zwave-js/serial"); var import_shared = require("@zwave-js/shared"); var import_deferred_promise = require("alcalzone-shared/deferred-promise"); var import_Zniffer = require("../log/Zniffer.js"); var import_MPDU = require("./MPDU.js"); var import_ZLFEntry = require("./ZLFEntry.js"); (0, import_cc.registerCCs)(); const logo = ` \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2550\u2550\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u255D \u2588\u2588\u2554\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588 \u2588\u2588\u2551 \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D `.trim(); function is700PlusSeries(chipType) { if (typeof chipType !== "string") { return chipType.type >= 7; } const chipTypeNumeric = (0, import_core.getChipTypeAndVersion)(chipType); if (chipTypeNumeric) { return chipTypeNumeric.type >= 7; } return false; } __name(is700PlusSeries, "is700PlusSeries"); function tryConvertRSSI(rssi, chipType) { if (is700PlusSeries(chipType)) { return rssi * 4 - 256; } else { return rssi * 1.5 - 153.5; } } __name(tryConvertRSSI, "tryConvertRSSI"); class Zniffer extends import_shared.TypedEventTarget { static { __name(this, "Zniffer"); } port; constructor(port, options = {}) { super(); this.port = port; if (typeof port !== "string" && !(0, import_serial.isZWaveSerialPortImplementation)(port) && !(0, import_serial.isZWaveSerialBindingFactory)(port)) { throw new import_core.ZWaveError(`The port must be a string or a valid custom serial port implementation!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } this._options = options; this._active = false; this.parsingContext = { getHighestSecurityClass(_nodeId) { return import_core.SecurityClass.S2_AccessControl; }, hasSecurityClass(_nodeId, _securityClass) { return true; }, setSecurityClass(_nodeId, _securityClass, _granted) { }, getDeviceConfig(_nodeId) { return new import_config.DeviceConfig( "unknown.json", false, "UNKNOWN_MANUFACTURER", 0, "UNKNOWN_PRODUCT", "UNKNOWN_DESCRIPTION", [], { min: "0.0", max: "255.255" }, true, void 0, void 0, void 0, void 0, void 0, // ...down here: { disableStrictEntryControlDataValidation: true, disableStrictMeasurementValidation: true } ); } }; } _options; /** * The host bindings used to access file system etc. */ // This is set during `init()` and should not be accessed before bindings; serialFactory; /** The serial port instance */ serial; parsingContext; _destroyPromise; get wasDestroyed() { return !!this._destroyPromise; } _chipType; _currentFrequency; /** The currently configured frequency */ get currentFrequency() { return this._currentFrequency; } _supportedFrequencies = /* @__PURE__ */ new Map(); /** A map of supported frequency identifiers and their names */ get supportedFrequencies() { return this._supportedFrequencies; } _lrRegions = /* @__PURE__ */ new Set(); /** A list regions that are Long Range capable */ get lrRegions() { return this._lrRegions; } _currentLRChannelConfig; /** The currently configured Long Range channel configuration */ get currentLRChannelConfig() { return this._currentLRChannelConfig; } _supportedLRChannelConfigs = /* @__PURE__ */ new Map(); /** A map of supported Long Range channel configurations and their names */ get supportedLRChannelConfigs() { return this._supportedLRChannelConfigs; } // This is set during `start()` and should not be accessed before _logContainer; // This is set during `start()` and should not be accessed before znifferLog; /** The security managers for each node */ securityManagers = /* @__PURE__ */ new Map(); /** A list of awaited messages */ awaitedMessages = []; _active; /** Whether the Zniffer instance is currently capturing */ get active() { return this._active; } _capturedFrames = []; /** A list of raw captured frames that can be saved to a .zlf file later */ get capturedFrames() { return this._capturedFrames.filter((f) => f.parsedFrame !== void 0).map((f) => ({ timestamp: f.timestamp, frameData: f.frameData, parsedFrame: f.parsedFrame })); } async init() { if (this.wasDestroyed) { throw new import_core.ZWaveError("The Zniffer was destroyed. Create a new instance and initialize that one.", import_core.ZWaveErrorCodes.Driver_Destroyed); } this.bindings = { fs: this._options.host?.fs ?? (await import("#default_bindings/fs")).fs, serial: this._options.host?.serial ?? (await import("#default_bindings/serial")).serial, log: this._options.host?.log ?? (await import("#default_bindings/log")).log }; this._logContainer = this.bindings.log(this._options.logConfig); this.znifferLog = new import_Zniffer.ZnifferLogger(this, this._logContainer); let binding; if (typeof this.port === "string") { if (typeof this.bindings.serial.createFactoryByPath === "function") { this.znifferLog.print(`opening serial port ${this.port}`); binding = await this.bindings.serial.createFactoryByPath(this.port); } else { throw new import_core.ZWaveError("This platform does not support creating a serial connection by path", import_core.ZWaveErrorCodes.Driver_Failed); } } else if ((0, import_serial.isZWaveSerialPortImplementation)(this.port)) { this.znifferLog.print("opening serial port using the provided custom implementation"); this.znifferLog.print("This is deprecated! Switch to the factory pattern instead.", "warn"); binding = (0, import_serial.wrapLegacySerialBinding)(this.port); } else { this.znifferLog.print("opening serial port using the provided custom factory"); binding = this.port; } this.serialFactory = new import_serial.ZnifferSerialStreamFactory(binding, this._logContainer); this.serial = await this.serialFactory.createStream(); void this.handleSerialData(this.serial); this.znifferLog.print(logo, "info"); await this.stop(); const versionInfo = await this.getVersion(); this._chipType = versionInfo.chipType; this.znifferLog.print(`received Zniffer info: Chip type: ${typeof versionInfo.chipType === "string" ? versionInfo.chipType : `unknown (${(0, import_shared.num2hex)(versionInfo.chipType.type)}, ${(0, import_shared.num2hex)(versionInfo.chipType.version)})`} Zniffer version: ${versionInfo.majorVersion}.${versionInfo.minorVersion}`, "info"); await this.setBaudrate(0); const freqs = await this.getFrequencies(); this._currentFrequency = freqs.currentFrequency; if (is700PlusSeries(this._chipType)) { for (const freq of freqs.supportedFrequencies) { this._supportedFrequencies.set(freq, (0, import_shared.getEnumMemberName)(import_core.ZnifferRegion, freq)); } const unknownRegions = freqs.supportedFrequencies.filter((f) => !(0, import_shared.isEnumMember)(import_core.ZnifferRegion, f)); for (const freq of unknownRegions) { const freqInfo = await this.getFrequencyInfo(freq); this._supportedFrequencies.set(freq, freqInfo.frequencyName); } } else if ( // Version 2.55+ supports querying the frequency names (0, import_core.sdkVersionGte)(`${versionInfo.majorVersion}.${versionInfo.minorVersion}`, "2.55") ) { for (const freq of freqs.supportedFrequencies) { const freqInfo = await this.getFrequencyInfo(freq); this._supportedFrequencies.set(freq, freqInfo.frequencyName); } } else { for (const freq of freqs.supportedFrequencies) { this._supportedFrequencies.set(freq, (0, import_shared.getEnumMemberName)(import_core.ZnifferRegionLegacy, freq)); } } this.znifferLog.print(`received frequency info: current frequency: ${this._supportedFrequencies.get(freqs.currentFrequency) ?? `unknown (${(0, import_shared.num2hex)(freqs.currentFrequency)})`} supported frequencies: ${[...this._supportedFrequencies].map(([region, name]) => ` \xB7 ${region.toString().padStart(2, " ")}: ${name}`).join("")}`, "info"); if (typeof this._options.defaultFrequency === "number" && freqs.currentFrequency !== this._options.defaultFrequency && this._supportedFrequencies.has(this._options.defaultFrequency)) { await this.setFrequency(this._options.defaultFrequency); } if ((0, import_core.sdkVersionGte)(`${versionInfo.majorVersion}.${versionInfo.minorVersion}`, "10.22")) { for (const region of await this.getLRRegions()) { this._lrRegions.add(region); } const channels = await this.getLRChannelConfigs(); this._currentLRChannelConfig = channels.currentConfig; for (const channel of channels.supportedConfigs) { this._supportedLRChannelConfigs.set(channel, (0, import_shared.getEnumMemberName)(import_core.ZnifferLRChannelConfig, channel)); } const unknownConfigs = channels.supportedConfigs.filter((f) => !(0, import_shared.isEnumMember)(import_core.ZnifferLRChannelConfig, f)); for (const channel of unknownConfigs) { const channelInfo = await this.getLRChannelConfigInfo(channel); this._supportedLRChannelConfigs.set(channel, channelInfo.configName); } this.znifferLog.print(`received LR channel info: current channel: ${this._supportedLRChannelConfigs.get(this._currentLRChannelConfig) ?? `unknown (${(0, import_shared.num2hex)(this._currentLRChannelConfig)})`} supported channels: ${[...this._supportedLRChannelConfigs].map(([channel, name]) => ` \xB7 ${channel.toString()}: ${name}`).join("")}`, "info"); if (this._lrRegions.has(this._currentFrequency) && typeof this._options.defaultLRChannelConfig === "number" && this._currentLRChannelConfig !== this._options.defaultLRChannelConfig && this._supportedLRChannelConfigs.has(this._options.defaultLRChannelConfig)) { await this.setLRChannelConfig(this._options.defaultLRChannelConfig); } } this.emit("ready"); } async handleSerialData(serial) { try { for await (const frame of serial.readable) { setImmediate(() => { if (frame.type === import_serial.ZnifferSerialFrameType.SerialAPI) { void this.serialport_onData(frame.data); } else { } }); } } catch (e) { if ((0, import_shared.isAbortError)(e)) { return; } else if ((0, import_core.isZWaveError)(e) && e.code === import_core.ZWaveErrorCodes.Driver_Failed) { this.emit("error", e); return this.destroy(); } throw e; } } /** * Is called when the serial port has received a Zniffer frame */ async serialport_onData(data) { let msg; let bytesRead; try { ({ msg, bytesRead } = import_serial.ZnifferMessage.parse(data)); } catch (e) { console.error(e); return; } if (bytesRead < data.length) { this.znifferLog.print(`Possible data loss: read only ${bytesRead} of ${data.length} bytes!`, "warn"); } if (msg.type === import_serial.ZnifferMessageType.Command) { this.handleResponse(msg); } else { const dataMsg = msg; const capture = { timestamp: /* @__PURE__ */ new Date(), rawData: data, frameData: dataMsg.payload }; this._capturedFrames.push(capture); if (this._options.maxCapturedFrames != void 0 && this._capturedFrames.length > this._options.maxCapturedFrames) { this._capturedFrames.shift(); } await this.handleDataMessage(dataMsg, capture); } } /** * Is called when a Request-type message was received */ handleResponse(msg) { for (const entry of this.awaitedMessages) { if (entry.predicate(msg)) { entry.handler(msg); return; } } } /** * Is called when a Request-type message was received */ async handleDataMessage(msg, capture) { try { const frame = await this.parseFrame(msg); capture.parsedFrame = frame.external; if (frame.internal instanceof import_MPDU.ZWaveBeamStart || frame.internal instanceof import_MPDU.LongRangeBeamStart || frame.internal instanceof import_MPDU.BeamStop) { this.znifferLog.beam(frame.internal); this.emit("frame", frame.external, capture.frameData); return; } if (frame.internal === void 0) { this.znifferLog.crcError(msg); this.emit("corrupted frame", frame.external, capture.frameData); return; } if (frame.internal instanceof import_MPDU.ZWaveMPDU || frame.internal instanceof import_MPDU.LongRangeMPDU) { this.znifferLog.mpdu(frame.internal, frame.cc); this.emit("frame", frame.external, capture.frameData); return; } } catch (e) { console.error(e); } } /** * Waits until a certain serial message is received or a timeout has elapsed. Returns the received message. * @param timeout The number of milliseconds to wait. If the timeout elapses, the returned promise will be rejected * @param predicate A predicate function to test all incoming messages. */ waitForMessage(predicate, timeout) { return new Promise((resolve, reject) => { const promise = (0, import_deferred_promise.createDeferredPromise)(); const entry = { predicate, handler: /* @__PURE__ */ __name((msg) => promise.resolve(msg), "handler"), timeout: void 0 }; this.awaitedMessages.push(entry); const removeEntry = /* @__PURE__ */ __name(() => { if (entry.timeout) clearTimeout(entry.timeout); const index = this.awaitedMessages.indexOf(entry); if (index !== -1) this.awaitedMessages.splice(index, 1); }, "removeEntry"); entry.timeout = setTimeout(() => { removeEntry(); reject(new import_core.ZWaveError(`Received no matching message within the provided timeout!`, import_core.ZWaveErrorCodes.Controller_Timeout)); }, timeout); void promise.then((cc) => { removeEntry(); resolve(cc); }); }); } async getVersion() { const req = new import_serial.ZnifferGetVersionRequest(); await this.serial?.writeAsync(req.serialize()); const res = await this.waitForMessage((msg) => msg instanceof import_serial.ZnifferGetVersionResponse, 1e3); return (0, import_shared.pick)(res, ["chipType", "majorVersion", "minorVersion"]); } async getFrequencies() { const req = new import_serial.ZnifferGetFrequenciesRequest(); await this.serial?.writeAsync(req.serialize()); const res = await this.waitForMessage((msg) => msg instanceof import_serial.ZnifferGetFrequenciesResponse, 1e3); return (0, import_shared.pick)(res, [ "currentFrequency", "supportedFrequencies" ]); } async setFrequency(frequency) { const req = new import_serial.ZnifferSetFrequencyRequest({ frequency }); await this.serial?.writeAsync(req.serialize()); await this.waitForMessage((msg) => msg instanceof import_serial.ZnifferSetFrequencyResponse, 1e3); this._currentFrequency = frequency; } async getFrequencyInfo(frequency) { const req = new import_serial.ZnifferGetFrequencyInfoRequest({ frequency }); await this.serial?.writeAsync(req.serialize()); const res = await this.waitForMessage((msg) => msg instanceof import_serial.ZnifferGetFrequencyInfoResponse && msg.frequency === frequency, 1e3); return (0, import_shared.pick)(res, ["numChannels", "frequencyName"]); } async getLRRegions() { const req = new import_serial.ZnifferGetLRRegionsRequest(); await this.serial?.writeAsync(req.serialize()); const res = await this.waitForMessage((msg) => msg instanceof import_serial.ZnifferGetLRRegionsResponse, 1e3); return res.regions; } async getLRChannelConfigs() { const req = new import_serial.ZnifferGetLRChannelConfigsRequest(); await this.serial?.writeAsync(req.serialize()); const res = await this.waitForMessage((msg) => msg instanceof import_serial.ZnifferGetLRChannelConfigsResponse, 1e3); return (0, import_shared.pick)(res, [ "currentConfig", "supportedConfigs" ]); } async setLRChannelConfig(channelConfig) { if (this._currentFrequency == void 0 || !this._lrRegions.has(this._currentFrequency)) { throw new import_core.ZWaveError(`The LR channel configuration can only be set for LR regions!`, import_core.ZWaveErrorCodes.Controller_NotSupported); } const req = new import_serial.ZnifferSetLRChannelConfigRequest({ channelConfig }); await this.serial?.writeAsync(req.serialize()); await this.waitForMessage((msg) => msg instanceof import_serial.ZnifferSetLRChannelConfigResponse, 1e3); this._currentLRChannelConfig = channelConfig; } async getLRChannelConfigInfo(channelConfig) { const req = new import_serial.ZnifferGetLRChannelConfigInfoRequest({ channelConfig }); await this.serial?.writeAsync(req.serialize()); const res = await this.waitForMessage((msg) => msg instanceof import_serial.ZnifferGetLRChannelConfigInfoResponse && msg.channelConfig === channelConfig, 1e3); return (0, import_shared.pick)(res, ["numChannels", "configName"]); } /** Starts the capture and discards all previously captured frames */ async start() { if (this.wasDestroyed) { throw new import_core.ZWaveError("The Zniffer is not ready or has been destroyed", import_core.ZWaveErrorCodes.Driver_NotReady); } if (this._active) return; this._capturedFrames = []; this._active = true; const req = new import_serial.ZnifferStartRequest(); await this.serial?.writeAsync(req.serialize()); await this.waitForMessage((msg) => msg instanceof import_serial.ZnifferStartResponse, 1e3); } async stop() { if (!this._active) return; this._active = false; if (!this.serial) return; const req = new import_serial.ZnifferStopRequest(); await this.serial?.writeAsync(req.serialize()); await this.waitForMessage((msg) => msg instanceof import_serial.ZnifferStopResponse, 1e3); } async setBaudrate(baudrate) { const req = new import_serial.ZnifferSetBaudRateRequest({ baudrate }); await this.serial?.writeAsync(req.serialize()); await this.waitForMessage((msg) => msg instanceof import_serial.ZnifferSetBaudRateResponse, 1e3); } async getSecurityManagers(sourceNodeId) { if (this.securityManagers.has(sourceNodeId)) { return this.securityManagers.get(sourceNodeId); } const S0Key = this._options.securityKeys?.S0_Legacy; let securityManager; if (S0Key) { securityManager = new import_core.SecurityManager({ networkKey: S0Key, // FIXME: Track nonces separately for each destination node ownNodeId: sourceNodeId, nonceTimeout: Number.POSITIVE_INFINITY }); } let securityManager2; if (this._options.securityKeys && Object.keys(this._options.securityKeys).some((key) => key.startsWith("S2_") && key in import_core.SecurityClass && (0, import_core.securityClassIsS2)(import_core.SecurityClass[key]))) { securityManager2 = await import_core.SecurityManager2.create(); securityManager2.isDuplicateSinglecast = () => false; for (const secClass of [ "S2_Unauthenticated", "S2_Authenticated", "S2_AccessControl", "S0_Legacy" ]) { const key = this._options.securityKeys[secClass]; if (key) { await securityManager2.setKey(import_core.SecurityClass[secClass], key); } } } let securityManagerLR; if (this._options.securityKeysLongRange?.S2_AccessControl || this._options.securityKeysLongRange?.S2_Authenticated) { securityManagerLR = await import_core.SecurityManager2.create(); securityManagerLR.isDuplicateSinglecast = () => false; if (this._options.securityKeysLongRange?.S2_AccessControl) { await securityManagerLR.setKey(import_core.SecurityClass.S2_AccessControl, this._options.securityKeysLongRange.S2_AccessControl); } if (this._options.securityKeysLongRange?.S2_Authenticated) { await securityManagerLR.setKey(import_core.SecurityClass.S2_Authenticated, this._options.securityKeysLongRange.S2_Authenticated); } } const ret = { securityManager, securityManager2, securityManagerLR }; this.securityManagers.set(sourceNodeId, ret); return ret; } /** Clears the list of captured frames */ clearCapturedFrames() { this._capturedFrames = []; } /** * Get the captured frames in the official Zniffer application format. * @param frameFilter Optional predicate function to filter the frames included in the capture */ getCaptureAsZLFBuffer(frameFilter) { const header = new import_shared.Bytes(2048).fill(0); header[0] = 104; header.writeUInt16BE(8978, 2046); let filteredFrames = this._capturedFrames; if (frameFilter) { filteredFrames = filteredFrames.filter((f) => ( // Always include Zniffer-protocol frames f.parsedFrame == void 0 || frameFilter({ frameData: f.frameData, parsedFrame: f.parsedFrame, timestamp: f.timestamp }) )); } return import_shared.Bytes.concat([ header, ...filteredFrames.map(import_ZLFEntry.captureToZLFEntry) ]); } /** * Saves the captured frames in a `.zlf` file that can be read by the official Zniffer application. * @param frameFilter Optional predicate function to filter the frames included in the capture */ async saveCaptureToFile(filePath, frameFilter) { await this.bindings.fs.writeFile(filePath, this.getCaptureAsZLFBuffer(frameFilter)); } /** * Terminates the Zniffer instance and closes the underlying serial connection. * Must be called under any circumstances. */ async destroy() { if (this._destroyPromise) return this._destroyPromise; this._destroyPromise = (0, import_deferred_promise.createDeferredPromise)(); this.znifferLog.print("Destroying Zniffer instance..."); if (this._active) { await this.stop().catch(import_shared.noop); } if (this.serial != void 0) { if (this.serial.isOpen) await this.serial.close(); this.serial = void 0; } this.znifferLog.print("Zniffer instance destroyed"); this._logContainer.destroy(); this._destroyPromise.resolve(); } /** * Loads captured frames from a `.zlf` file that was written by the official Zniffer application or Z-Wave JS. */ async loadCaptureFromFile(filePath) { const buffer = await this.bindings.fs.readFile(filePath); await this.loadCaptureFromBuffer(buffer); } /** * Load captured frames from a buffer */ async loadCaptureFromBuffer(buffer) { let { bytesRead: offset } = (0, import_ZLFEntry.parseZLFHeader)(buffer); this.clearCapturedFrames(); let accumulator; while (offset < buffer.length) { const { bytesRead, complete, accumulator: newAccumulator, entries } = (0, import_ZLFEntry.parseZLFEntry)(buffer, offset, accumulator); if (bytesRead <= 0) break; offset += bytesRead; accumulator = complete ? void 0 : newAccumulator; let index = 0; for (const entry of entries) { if (entry.kind !== import_ZLFEntry.ZLFEntryKind.Zniffer) continue; try { if (entry.type === import_serial.ZnifferMessageType.Data) { entry.capture.parsedFrame = // FIXME: Figure out which values the Zniffer application actually stores // as RSSI. Both the attempted conversion and the raw values seem wrong. (await this.parseFrame(entry.msg, false)).external; this._capturedFrames.push(entry.capture); } } catch (e) { console.warn(`Failed to parse entry #${index} at offset ${(0, import_shared.num2hex)(offset - bytesRead)}:`, (0, import_shared.getErrorMessage)(e, true)); } index++; } } } async parseFrame(msg, convertRSSI = this._options.convertRSSI ?? false) { let convertedRSSI; if (convertRSSI && this._chipType) { convertedRSSI = tryConvertRSSI(msg.rssiRaw, this._chipType); } if (msg.frameType === import_serial.ZnifferFrameType.BeamStart || msg.frameType === import_serial.ZnifferFrameType.BeamStop) { const beam = (0, import_MPDU.parseBeamFrame)(msg); beam.frameInfo.rssi = convertedRSSI; return { internal: beam, external: (0, import_MPDU.beamToFrame)(beam) }; } if (!msg.checksumOK) { return { internal: void 0, external: (0, import_MPDU.znifferDataMessageToCorruptedFrame)(msg) }; } const mpdu = (0, import_MPDU.parseMPDU)(msg); mpdu.frameInfo.rssi = convertedRSSI; let destSecurityManager; let destSecurityManager2; let destSecurityManagerLR; let destNodeId = 255; let cc; if (mpdu.payload.length > 0 && mpdu.headerType !== import_core.MPDUHeaderType.Acknowledgement) { if ("destinationNodeId" in mpdu) { destNodeId = mpdu.destinationNodeId; ({ securityManager: destSecurityManager, securityManager2: destSecurityManager2, securityManagerLR: destSecurityManagerLR } = await this.getSecurityManagers(mpdu.destinationNodeId)); } const frameType = mpdu.headerType === import_core.MPDUHeaderType.Multicast ? "multicast" : destNodeId === import_core.NODE_ID_BROADCAST || destNodeId === import_core.NODE_ID_BROADCAST_LR ? "broadcast" : "singlecast"; try { cc = await import_cc.CommandClass.parse(mpdu.payload, { homeId: mpdu.homeId, ownNodeId: destNodeId, sourceNodeId: mpdu.sourceNodeId, frameType, securityManager: destSecurityManager, securityManager2: destSecurityManager2, securityManagerLR: destSecurityManagerLR, ...this.parsingContext }); } catch (e) { console.error(e.stack); } } if (cc?.ccId === import_core.CommandClasses["Security 2"]) { const securityManagers = await this.getSecurityManagers(mpdu.sourceNodeId); const isLR = (0, import_core.isLongRangeNodeId)(mpdu.sourceNodeId) || (0, import_core.isLongRangeNodeId)(destNodeId); const senderSecurityManager = isLR ? securityManagers.securityManagerLR : securityManagers.securityManager2; const destSecurityManager3 = isLR ? destSecurityManagerLR : destSecurityManager2; if (senderSecurityManager && destSecurityManager3) { if (cc instanceof import_cc.Security2CCNonceGet) { senderSecurityManager.deleteNonce(destNodeId); destSecurityManager3.deleteNonce(mpdu.sourceNodeId); } else if (cc instanceof import_cc.Security2CCNonceReport && cc.SOS) { senderSecurityManager.setSPANState(destNodeId, { type: import_core.SPANState.LocalEI, receiverEI: cc.receiverEI }); destSecurityManager3.storeRemoteEI(mpdu.sourceNodeId, cc.receiverEI); } else if (cc instanceof import_cc.Security2CCMessageEncapsulation) { const senderEI = cc.getSenderEI(); if (senderEI) { const receiverSPANState = destSecurityManager3.getSPANState(mpdu.sourceNodeId); if (receiverSPANState.type === import_core.SPANState.SPAN) { senderSecurityManager.setSPANState(destNodeId, receiverSPANState); } } } } } else if (cc?.ccId === import_core.CommandClasses.Security && cc instanceof import_cc.SecurityCCNonceReport) { const senderSecurityManager = (await this.getSecurityManagers(mpdu.sourceNodeId)).securityManager; const destSecurityManager3 = (await this.getSecurityManagers(destNodeId)).securityManager; if (senderSecurityManager && destSecurityManager3) { senderSecurityManager.setNonce({ issuer: mpdu.sourceNodeId, nonceId: senderSecurityManager.getNonceId(cc.nonce) }, { nonce: cc.nonce, receiver: destNodeId }, { free: true }); destSecurityManager3.setNonce({ issuer: mpdu.sourceNodeId, nonceId: senderSecurityManager.getNonceId(cc.nonce) }, { nonce: cc.nonce, receiver: destNodeId }, { free: true }); } } return { internal: mpdu, cc, external: (0, import_MPDU.mpduToFrame)(mpdu, cc) }; } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Zniffer }); //# sourceMappingURL=Zniffer.js.map