UNPKG

zwave-js

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

927 lines 32.4 kB
import { MPDUHeaderType, NODE_ID_BROADCAST, NODE_ID_BROADCAST_LR, Protocols, ZWaveError, ZWaveErrorCodes, ZnifferRegion, parseNodeBitMask, rssiToString, validatePayload, znifferProtocolDataRateToString, } from "@zwave-js/core"; import { ZnifferFrameType, } from "@zwave-js/serial"; import { parseRSSI } from "@zwave-js/serial/serialapi"; import { Bytes, buffer2hex, pick, staticExtends, } from "@zwave-js/shared"; import { ExplorerFrameCommand, LongRangeFrameType, ZWaveFrameType, } from "./_Types.js"; function getChannelConfiguration(region) { switch (region) { case ZnifferRegion.Japan: case ZnifferRegion.Korea: return "3"; case ZnifferRegion["USA (Long Range)"]: case ZnifferRegion["USA (Long Range, backup)"]: case ZnifferRegion["USA (Long Range, end device)"]: return "4"; default: return "1/2"; } } function longRangeBeamPowerToDBm(power) { return [ -6, -2, 2, 6, 10, 13, 16, 19, 21, 23, 25, 26, 27, 28, 29, 30, ][power]; } function formatNodeId(nodeId) { return nodeId.toString().padStart(3, "0"); } function formatRoute(source, repeaters, destination, direction, currentHop, failedHop) { return [ direction === "outbound" ? formatNodeId(source) : formatNodeId(destination), ...repeaters.map(formatNodeId), direction === "outbound" ? formatNodeId(destination) : formatNodeId(source), ].map((id, i) => { if (i === 0) return id; if (i - 1 === failedHop) return " × " + id; if (i - 1 === currentHop) { return (direction === "outbound" ? " » " : " « ") + id; } return (direction === "outbound" ? " › " : " ‹ ") + id; }) .join(""); } export function parseMPDU(frame) { switch (frame.channel) { case 0: case 1: case 2: return ZWaveMPDU.from(frame); case 3: case 4: return LongRangeMPDU.from(frame); default: validatePayload.fail(`Unsupported channel ${frame.channel}. MPDU payload: ${buffer2hex(frame.payload)}`); } } export class LongRangeMPDU { constructor(options) { const data = options.data; this.frameInfo = options.frameInfo; if (options.frameInfo.channel !== 3 // LR Channel A && options.frameInfo.channel !== 4 // LR Channel B ) { validatePayload.fail(`Unsupported channel ${options.frameInfo.channel} for LongRangeMPDU`); } this.homeId = data.readUInt32BE(0); const nodeIds = data.readUIntBE(4, 3); this.sourceNodeId = nodeIds >>> 12; this.destinationNodeId = nodeIds & 0xfff; // skip length byte const frameControl = data[8]; this.ackRequested = !!(frameControl & 0b1000_0000); const hasExtendedHeader = !!(frameControl & 0b0100_0000); this.headerType = frameControl & 0b0000_0111; this.sequenceNumber = data[9]; this.noiseFloor = parseRSSI(data, 10); this.txPower = data.readInt8(11); let offset = 12; if (hasExtendedHeader) { const extensionControl = data[offset++]; const extensionLength = extensionControl & 0b111; // const discardUnknown = extensionControl & 0b0000_1000; // const extensionType = (extensionControl & 0b0111_0000) >>> 4; // TODO: Parse extension (once there is a definition) offset += extensionLength; } const Constructor = this.headerType === MPDUHeaderType.Acknowledgement ? AckLongRangeMPDU : this.headerType === MPDUHeaderType.Singlecast ? SinglecastLongRangeMPDU : undefined; if (!Constructor) { validatePayload.fail(`Unsupported Long Range MPDU header type ${this.headerType}`); } else if (new.target !== Constructor && !staticExtends(new.target, Constructor)) { return new Constructor(options); } this.payload = data.subarray(offset); } frameInfo; homeId; sourceNodeId; destinationNodeId; ackRequested; headerType; sequenceNumber; noiseFloor; txPower; payload; static from(msg) { return new LongRangeMPDU({ data: msg.payload, frameInfo: pick(msg, [ "channel", "frameType", "region", "protocolDataRate", "rssiRaw", ]), }); } toLogEntry() { const tags = [ formatRoute(this.sourceNodeId, [], this.destinationNodeId, // Singlecast frames do not contain a bit for this, we consider them all "outbound" "outbound", 0), ]; if (this.headerType === MPDUHeaderType.Acknowledgement) { tags.unshift("ACK"); } const message = { "sequence no.": this.sequenceNumber, channel: this.frameInfo.channel, "protocol/data rate": znifferProtocolDataRateToString(this.frameInfo.protocolDataRate), "TX power": `${this.txPower} dBm`, RSSI: this.frameInfo.rssi != undefined ? rssiToString(this.frameInfo.rssi) : this.frameInfo.rssiRaw.toString(), "noise floor": rssiToString(this.noiseFloor), }; if (this.headerType !== MPDUHeaderType.Acknowledgement) { message["ack requested"] = this.ackRequested; } if (this.payload.length > 0) { message.payload = buffer2hex(this.payload); } return { tags, message, }; } } export class SinglecastLongRangeMPDU extends LongRangeMPDU { toLogEntry() { const { tags, message: original } = super.toLogEntry(); const message = { ...original, payload: buffer2hex(this.payload), }; return { tags, message, }; } } export class AckLongRangeMPDU extends LongRangeMPDU { constructor(options) { super(options); this.incomingRSSI = parseRSSI(this.payload, 0); this.payload = this.payload.subarray(1); } incomingRSSI; toLogEntry() { const { tags, message: original } = super.toLogEntry(); const message = { ...original, "incoming RSSI": rssiToString(this.incomingRSSI), }; if (this.payload.length > 0) { message.payload = buffer2hex(this.payload); } return { tags, message, }; } } export class ZWaveMPDU { constructor(options) { const data = options.data; this.frameInfo = options.frameInfo; let destinationOffset = 8; const frameControl = data.subarray(5, 7); switch (options.frameInfo.channel) { case 0: case 1: { this.routed = !!(frameControl[0] & 0b1000_0000); this.ackRequested = !!(frameControl[0] & 0b0100_0000); this.lowPower = !!(frameControl[0] & 0b0010_0000); this.speedModified = !!(frameControl[0] & 0b0001_0000); this.headerType = frameControl[0] & 0b0000_1111; this.beamingInfo = frameControl[1] & 0b0110_0000; this.sequenceNumber = frameControl[1] & 0b0000_1111; break; } case 2: { this.routed = false; this.ackRequested = !!(frameControl[0] & 0b1000_0000); this.lowPower = !!(frameControl[0] & 0b0100_0000); this.speedModified = false; this.headerType = frameControl[0] & 0b0000_1111; this.beamingInfo = frameControl[1] & 0b0111_0000; this.sequenceNumber = data[destinationOffset]; destinationOffset++; break; } case 3: case 4: { validatePayload.fail(`Channel ${options.frameInfo.channel} (ZWLR) must be parsed as a LongRangeMPDU!`); } default: { validatePayload.fail(`Unsupported channel ${options.frameInfo.channel}. MPDU payload: ${buffer2hex(data)}`); } } const Constructor = this.headerType === MPDUHeaderType.Acknowledgement ? AckZWaveMPDU : (this.headerType === MPDUHeaderType.Routed || (this.headerType === MPDUHeaderType.Singlecast && this.routed)) ? RoutedZWaveMPDU : this.headerType === MPDUHeaderType.Singlecast ? SinglecastZWaveMPDU : this.headerType === MPDUHeaderType.Multicast ? MulticastZWaveMPDU : this.headerType === MPDUHeaderType.Explorer ? ExplorerZWaveMPDU : undefined; if (!Constructor) { validatePayload.fail(`Unsupported MPDU header type ${this.headerType}`); } else if (new.target !== Constructor && !staticExtends(new.target, Constructor)) { return new Constructor(options); } // FIXME: Parse Beams this.homeId = data.readUInt32BE(0); this.sourceNodeId = data[4]; // byte 7 is another length byte // FIXME: This should consider the multicast control byte const destinationLength = this.headerType === MPDUHeaderType.Multicast ? 30 : 1; this.destinationBuffer = data.subarray(destinationOffset, destinationOffset + destinationLength); this.payload = data.subarray(destinationOffset + destinationLength); } frameInfo; homeId; sourceNodeId; routed; ackRequested; lowPower; speedModified; headerType; beamingInfo; sequenceNumber; destinationBuffer; payload; static from(msg) { return new ZWaveMPDU({ data: msg.payload, frameInfo: pick(msg, [ "channel", "frameType", "region", "protocolDataRate", "rssiRaw", ]), }); } toLogEntry() { const tags = [formatNodeId(this.sourceNodeId)]; const message = { "sequence no.": this.sequenceNumber, channel: this.frameInfo.channel, "protocol/data rate": znifferProtocolDataRateToString(this.frameInfo.protocolDataRate) + (this.speedModified ? " (reduced)" : ""), RSSI: this.frameInfo.rssi != undefined ? rssiToString(this.frameInfo.rssi) : this.frameInfo.rssiRaw.toString(), }; return { tags, message, }; } } export class SinglecastZWaveMPDU extends ZWaveMPDU { constructor(options) { super(options); this.destinationNodeId = this.destinationBuffer[0]; } destinationNodeId; toLogEntry() { const { tags, message: original } = super.toLogEntry(); tags[0] = formatRoute(this.sourceNodeId, [], this.destinationNodeId, // Singlecast frames do not contain a bit for this, we consider them all "outbound" "outbound", 0); const message = { ...original, "ack requested": this.ackRequested, payload: buffer2hex(this.payload), }; return { tags, message, }; } } export class AckZWaveMPDU extends ZWaveMPDU { constructor(options) { super(options); this.destinationNodeId = this.destinationBuffer[0]; } destinationNodeId; toLogEntry() { const { tags, message } = super.toLogEntry(); tags[0] = formatRoute(this.sourceNodeId, [], this.destinationNodeId, // ACK frames do not contain a bit for this, we consider them all "inbound" "inbound", 0); tags.unshift("ACK"); return { tags, message, }; } } export class RoutedZWaveMPDU extends ZWaveMPDU { constructor(options) { super(options); const channelConfig = getChannelConfiguration(this.frameInfo.region); this.direction = (this.payload[0] & 0b1) ? "inbound" : "outbound"; this.routedAck = !!(this.payload[0] & 0b10); this.routedError = !!(this.payload[0] & 0b100); const hasExtendedHeader = !!(this.payload[0] & 0b1000); if (this.routedError) { this.failedHop = this.payload[0] >>> 4; } else if (channelConfig === "1/2") { // @ts-expect-error speedModified is readonly this.speedModified = !!(this.payload[0] & 0b10000); } this.hop = this.payload[1] & 0b1111; // The hop field in the MPDU indicates which repeater should handle the frame next. // This means that for an inbound frame between repeater 0 and 1, the value is one // less (0) than for an outbound frame (1). This also means that the field overflows // to 0x0f when the frame returns to the source node. // // We normalize this, so hop = 0 always means the frame is transmitted between the source node and repeater 0. if (this.direction === "inbound") { this.hop = (this.hop + 1) % 16; } const numRepeaters = this.payload[1] >>> 4; this.repeaters = [...this.payload.subarray(2, 2 + numRepeaters)]; let offset = 2 + numRepeaters; if (channelConfig === "3") { this.destinationWakeup = this.payload[offset++] === 0x02; } if (hasExtendedHeader) { const headerPreamble = this.payload[offset++]; const headerLength = headerPreamble >>> 4; const headerType = headerPreamble & 0b1111; const header = this.payload.subarray(offset, offset + headerLength); offset += headerLength; if (headerType === 0x00) { this.destinationWakeupType = header[0] & 0b0100_0000 ? "1000ms" : header[0] & 0b0010_0000 ? "250ms" : undefined; } else if (headerType === 0x01) { const repeaterRSSI = []; for (let i = 0; i < numRepeaters; i++) { repeaterRSSI.push(parseRSSI(header, i)); } this.repeaterRSSI = repeaterRSSI; } } this.payload = this.payload.subarray(offset); this.destinationNodeId = this.destinationBuffer[0]; } destinationNodeId; direction; routedAck; routedError; failedHop; hop; repeaters; destinationWakeup; destinationWakeupType; repeaterRSSI; toLogEntry() { const { tags, message: original } = super.toLogEntry(); tags[0] = formatRoute(this.sourceNodeId, this.repeaters, this.destinationNodeId, this.direction, this.hop, this.failedHop); const message = { ...original, "ack requested": this.ackRequested, payload: buffer2hex(this.payload), }; return { tags, message, }; } } export class MulticastZWaveMPDU extends ZWaveMPDU { constructor(options) { super(options); const control = this.destinationBuffer[0]; // 3 bits offset, 5 bits mask length, but this MUST be set to 29 validatePayload.withReason("Invalid multicast control byte")(control === 29); this.destinationNodeIds = parseNodeBitMask(this.destinationBuffer.subarray(1)); } destinationNodeIds; toLogEntry() { const { tags, message: original } = super.toLogEntry(); tags.push("MULTICAST"); const message = { destinations: this.destinationNodeIds.join(", "), ...original, payload: buffer2hex(this.payload), }; return { tags, message, }; } } export class ExplorerZWaveMPDU extends ZWaveMPDU { constructor(options) { super(options); this.version = this.payload[0] >>> 5; this.command = this.payload[0] & 0b0001_1111; this.stop = !!(this.payload[1] & 0b100); this.direction = this.payload[1] & 0b010 ? "inbound" : "outbound"; this.sourceRouted = !!(this.payload[1] & 0b001); this.randomTXInterval = this.payload[2]; this.ttl = this.payload[3] >>> 4; const numRepeaters = this.payload[3] & 0b1111; this.repeaters = [...this.payload.subarray(4, 4 + numRepeaters)]; // Make sure the correct constructor gets called const Constructor = this.command === ExplorerFrameCommand.Normal ? NormalExplorerZWaveMPDU : this.command === ExplorerFrameCommand.InclusionRequest ? InclusionRequestExplorerZWaveMPDU : this.command === ExplorerFrameCommand.SearchResult ? SearchResultExplorerZWaveMPDU : undefined; if (!Constructor) { validatePayload.fail(`Unsupported Explorer MPDU command ${this.command}`); } else if (new.target !== Constructor && !staticExtends(new.target, Constructor)) { return new Constructor(options); } this.destinationNodeId = this.destinationBuffer[0]; this.payload = this.payload.subarray(8); } destinationNodeId; version; command; stop; sourceRouted; direction; randomTXInterval; ttl; repeaters; } export class NormalExplorerZWaveMPDU extends ExplorerZWaveMPDU { toLogEntry() { const { tags, message: original } = super.toLogEntry(); tags[0] = formatRoute(this.sourceNodeId, this.repeaters, this.destinationNodeId, // Explorer frames do not contain a bit for the direction, we consider them all "outbound" "outbound", 4 - this.ttl); tags.unshift("EXPLORER"); const message = { ...original, "ack requested": this.ackRequested, payload: buffer2hex(this.payload), }; return { tags, message, }; } } export class InclusionRequestExplorerZWaveMPDU extends ExplorerZWaveMPDU { constructor(options) { super(options); this.networkHomeId = this.payload.readUInt32BE(0); this.payload = this.payload.subarray(4); } /** The home ID of the repeating node */ networkHomeId; toLogEntry() { const { tags, message: original } = super.toLogEntry(); tags[0] = formatRoute(this.sourceNodeId, this.repeaters, this.destinationNodeId, // Explorer frames do not contain a bit for the direction, we consider them all "outbound" "outbound", 4 - this.ttl); tags.unshift("INCL REQUEST"); const message = { ...original, "network home ID": this.networkHomeId.toString(16).padStart(8, "0"), payload: buffer2hex(this.payload), }; return { tags, message, }; } } export class SearchResultExplorerZWaveMPDU extends ExplorerZWaveMPDU { constructor(options) { super(options); this.searchingNodeId = this.payload[0]; this.frameHandle = this.payload[1]; this.resultTTL = this.payload[2] >>> 4; const numRepeaters = this.payload[2] & 0b1111; this.resultRepeaters = [ ...this.payload.subarray(3, 3 + numRepeaters), ]; // This frame contains no payload this.payload = new Bytes(); } /** The node ID that sent the explorer frame that's being answered here */ searchingNodeId; /** The sequence number of the original explorer frame */ frameHandle; resultTTL; resultRepeaters; toLogEntry() { const { tags, message: original } = super.toLogEntry(); tags[0] = formatRoute(this.sourceNodeId, this.repeaters, this.destinationNodeId, // Explorer frames do not contain a bit for the direction, we consider their responses "inbound" "inbound", 4 - this.ttl); tags.unshift("EXPLORER RESULT"); const message = { ...original, "frame handle": this.frameHandle, "result TTL": this.resultTTL, "result repeaters": this.resultRepeaters.join(", "), }; return { tags, message, }; } } export function parseBeamFrame(frame) { if (frame.frameType === ZnifferFrameType.BeamStop) { return new BeamStop({ data: frame.payload, frameInfo: frame, }); } const channelConfig = getChannelConfiguration(frame.region); switch (channelConfig) { case "1/2": case "3": { return new ZWaveBeamStart({ data: frame.payload, frameInfo: frame, }); } case "4": { return new LongRangeBeamStart({ data: frame.payload, frameInfo: frame, }); } default: validatePayload.fail( // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Unsupported channel configuration ${channelConfig}. MPDU payload: ${buffer2hex(frame.payload)}`); } } export class ZWaveBeamStart { constructor(options) { const data = options.data; this.frameInfo = options.frameInfo; switch (options.frameInfo.channel) { case 0: case 1: case 2: // OK break; case 3: case 4: { validatePayload.fail(`Channel ${options.frameInfo.channel} (ZWLR) must be parsed as a LongRangeMPDU!`); } default: { validatePayload.fail(`Unsupported channel ${options.frameInfo.channel}. MPDU payload: ${buffer2hex(data)}`); } } this.destinationNodeId = data[1]; if (data[2] === 0x01) { this.homeIdHash = data[3]; } } frameInfo; homeIdHash; destinationNodeId; toLogEntry() { const tags = [ `BEAM » ${formatNodeId(this.destinationNodeId)}`, ]; const message = { channel: this.frameInfo.channel, "protocol/data rate": znifferProtocolDataRateToString(this.frameInfo.protocolDataRate), RSSI: this.frameInfo.rssi != undefined ? rssiToString(this.frameInfo.rssi) : this.frameInfo.rssiRaw.toString(), }; return { tags, message, }; } } export class LongRangeBeamStart { constructor(options) { const data = options.data; this.frameInfo = options.frameInfo; switch (options.frameInfo.channel) { case 0: case 1: case 2: validatePayload.fail(`Channel ${options.frameInfo.channel} (Mesh) must be parsed as a ZWaveMPDU!`); case 3: case 4: { // OK break; } default: { validatePayload.fail(`Unsupported channel ${options.frameInfo.channel}. MPDU payload: ${buffer2hex(data)}`); } } const txPower = data[1] >>> 4; this.txPower = longRangeBeamPowerToDBm(txPower); this.destinationNodeId = data.readUInt16BE(1) & 0x0fff; this.homeIdHash = data[3]; } frameInfo; homeIdHash; destinationNodeId; txPower; toLogEntry() { const tags = [ `BEAM » ${formatNodeId(this.destinationNodeId)}`, ]; const message = { channel: this.frameInfo.channel, "protocol/data rate": znifferProtocolDataRateToString(this.frameInfo.protocolDataRate), "TX power": `${this.txPower} dBm`, RSSI: this.frameInfo.rssi != undefined ? rssiToString(this.frameInfo.rssi) : this.frameInfo.rssiRaw.toString(), }; return { tags, message, }; } } export class BeamStop { constructor(options) { this.frameInfo = options.frameInfo; } frameInfo; toLogEntry() { const tags = [ "BEAM STOP", ]; const message = { channel: this.frameInfo.channel, }; return { tags, message, }; } } export function mpduToFrame(mpdu, payloadCC) { if (mpdu instanceof ZWaveMPDU) { return mpduToZWaveFrame(mpdu, payloadCC); } else if (mpdu instanceof LongRangeMPDU) { return mpduToLongRangeFrame(mpdu, payloadCC); } throw new ZWaveError(`mpduToFrame not supported for ${mpdu.constructor.name}`, ZWaveErrorCodes.Argument_Invalid); } export function mpduToZWaveFrame(mpdu, payloadCC) { const retBase = { protocol: Protocols.ZWave, channel: mpdu.frameInfo.channel, region: mpdu.frameInfo.region, rssiRaw: mpdu.frameInfo.rssiRaw, rssi: mpdu.frameInfo.rssi, protocolDataRate: mpdu.frameInfo.protocolDataRate, speedModified: mpdu.speedModified, sequenceNumber: mpdu.sequenceNumber, homeId: mpdu.homeId, sourceNodeId: mpdu.sourceNodeId, }; if (mpdu instanceof SinglecastZWaveMPDU) { const ret = { ...retBase, ackRequested: mpdu.ackRequested, payload: payloadCC ?? mpdu.payload, }; if (mpdu.destinationNodeId === NODE_ID_BROADCAST) { return { type: ZWaveFrameType.Broadcast, destinationNodeId: mpdu.destinationNodeId, ...ret, }; } else { return { type: ZWaveFrameType.Singlecast, destinationNodeId: mpdu.destinationNodeId, ...ret, }; } } else if (mpdu instanceof AckZWaveMPDU) { return { type: ZWaveFrameType.AckDirect, ...retBase, destinationNodeId: mpdu.destinationNodeId, }; } else if (mpdu instanceof MulticastZWaveMPDU) { return { type: ZWaveFrameType.Multicast, ...retBase, destinationNodeIds: [...mpdu.destinationNodeIds], payload: payloadCC ?? mpdu.payload, }; } else if (mpdu instanceof RoutedZWaveMPDU) { return { type: ZWaveFrameType.Singlecast, ...retBase, destinationNodeId: mpdu.destinationNodeId, ackRequested: mpdu.ackRequested, payload: payloadCC ?? mpdu.payload, direction: mpdu.direction, hop: mpdu.hop, repeaters: [...mpdu.repeaters], repeaterRSSI: mpdu.repeaterRSSI && [...mpdu.repeaterRSSI], routedAck: mpdu.routedAck, routedError: mpdu.routedError, failedHop: mpdu.failedHop, }; } else if (mpdu instanceof ExplorerZWaveMPDU) { const explorerBase = { ...retBase, destinationNodeId: mpdu.destinationNodeId, ackRequested: mpdu.ackRequested, direction: mpdu.direction, repeaters: [...mpdu.repeaters], ttl: mpdu.ttl, }; if (mpdu instanceof NormalExplorerZWaveMPDU) { return { type: ZWaveFrameType.ExplorerNormal, payload: payloadCC ?? mpdu.payload, ...explorerBase, }; } else if (mpdu instanceof SearchResultExplorerZWaveMPDU) { return { type: ZWaveFrameType.ExplorerSearchResult, ...explorerBase, searchingNodeId: mpdu.searchingNodeId, frameHandle: mpdu.frameHandle, resultTTL: mpdu.resultTTL, resultRepeaters: [...mpdu.resultRepeaters], }; } else if (mpdu instanceof InclusionRequestExplorerZWaveMPDU) { return { type: ZWaveFrameType.ExplorerInclusionRequest, payload: payloadCC ?? mpdu.payload, ...explorerBase, networkHomeId: mpdu.networkHomeId, }; } } throw new ZWaveError(`mpduToZWaveFrame not supported for ${mpdu.constructor.name}`, ZWaveErrorCodes.Argument_Invalid); } export function mpduToLongRangeFrame(mpdu, payloadCC) { const retBase = { protocol: Protocols.ZWaveLongRange, channel: mpdu.frameInfo.channel, region: mpdu.frameInfo.region, protocolDataRate: mpdu.frameInfo.protocolDataRate, rssiRaw: mpdu.frameInfo.rssiRaw, rssi: mpdu.frameInfo.rssi, noiseFloor: mpdu.noiseFloor, txPower: mpdu.txPower, sequenceNumber: mpdu.sequenceNumber, homeId: mpdu.homeId, sourceNodeId: mpdu.sourceNodeId, destinationNodeId: mpdu.destinationNodeId, }; if (mpdu instanceof SinglecastLongRangeMPDU) { const ret = { ...retBase, ackRequested: mpdu.ackRequested, payload: payloadCC ?? mpdu.payload, }; if (mpdu.destinationNodeId === NODE_ID_BROADCAST_LR) { return { type: LongRangeFrameType.Broadcast, ...ret, destinationNodeId: mpdu.destinationNodeId, // Make TS happy }; } else { return { type: LongRangeFrameType.Singlecast, ...ret, }; } } else if (mpdu instanceof AckLongRangeMPDU) { return { type: LongRangeFrameType.Ack, ...retBase, incomingRSSI: mpdu.incomingRSSI, payload: mpdu.payload, }; } throw new ZWaveError(`mpduToLongRangeFrame not supported for ${mpdu.constructor.name}`, ZWaveErrorCodes.Argument_Invalid); } export function beamToFrame(beam) { const retBase = { channel: beam.frameInfo.channel, region: beam.frameInfo.region, rssiRaw: beam.frameInfo.rssiRaw, rssi: beam.frameInfo.rssi, protocolDataRate: beam.frameInfo.protocolDataRate, }; if (beam instanceof ZWaveBeamStart) { return { protocol: Protocols.ZWave, type: ZWaveFrameType.BeamStart, ...retBase, destinationNodeId: beam.destinationNodeId, homeIdHash: beam.homeIdHash, }; } else if (beam instanceof LongRangeBeamStart) { return { protocol: Protocols.ZWaveLongRange, type: LongRangeFrameType.BeamStart, ...retBase, destinationNodeId: beam.destinationNodeId, homeIdHash: beam.homeIdHash, txPower: beam.txPower, }; } else { // Beam Stop - contains only the channel, the other fields are garbage const isLR = beam.frameInfo.channel === 4; if (isLR) { return { protocol: Protocols.ZWaveLongRange, type: LongRangeFrameType.BeamStop, channel: beam.frameInfo.channel, }; } else { return { protocol: Protocols.ZWave, type: ZWaveFrameType.BeamStop, channel: beam.frameInfo.channel, }; } } } export function znifferDataMessageToCorruptedFrame(msg, rssi) { if (msg.checksumOK) { throw new ZWaveError(`znifferDataMessageToCorruptedFrame expects the checksum to be incorrect`, ZWaveErrorCodes.Argument_Invalid); } return { channel: msg.channel, region: msg.region, rssiRaw: msg.rssiRaw, rssi, protocolDataRate: msg.protocolDataRate, payload: msg.payload, }; } //# sourceMappingURL=MPDU.js.map