UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

274 lines (257 loc) 8.2 kB
import { MessageRecord, protocolDataRateToString, RSSI, RssiError, rssiToString, stripUndefined, TXReport, } from "@zwave-js/core/safe"; import { AssignReturnRouteRequestTransmitReport } from "../network-mgmt/AssignReturnRouteMessages"; import { AssignSUCReturnRouteRequestTransmitReport } from "../network-mgmt/AssignSUCReturnRouteMessages"; import { DeleteReturnRouteRequestTransmitReport } from "../network-mgmt/DeleteReturnRouteMessages"; import { DeleteSUCReturnRouteRequestTransmitReport } from "../network-mgmt/DeleteSUCReturnRouteMessages"; import { SendDataBridgeRequest, SendDataBridgeRequestTransmitReport, SendDataMulticastBridgeRequest, SendDataMulticastBridgeRequestTransmitReport, } from "./SendDataBridgeMessages"; import { SendDataMulticastRequest, SendDataMulticastRequestTransmitReport, SendDataRequest, SendDataRequestTransmitReport, } from "./SendDataMessages"; export type SendDataMessage = | SendDataRequest | SendDataMulticastRequest | SendDataBridgeRequest | SendDataMulticastBridgeRequest; export type SendDataTransmitReport = | SendDataRequestTransmitReport | SendDataMulticastRequestTransmitReport | SendDataBridgeRequestTransmitReport | SendDataMulticastBridgeRequestTransmitReport; /** All message classes that are a callback with a transmit report */ export type TransmitReport = | SendDataTransmitReport | AssignReturnRouteRequestTransmitReport | AssignSUCReturnRouteRequestTransmitReport | DeleteReturnRouteRequestTransmitReport | DeleteSUCReturnRouteRequestTransmitReport; // const RSSI_RESERVED_START = 11; export function parseRSSI(payload: Buffer, offset: number = 0): RSSI { const ret = payload.readInt8(offset); // Filter out reserved values // TODO: Figure out for which controllers this is relevant // if ( // ret >= RSSI_RESERVED_START && // ret < RssiError.NoSignalDetected // ) { // ret = RssiError.NotAvailable; // } return ret; } export function tryParseRSSI( payload: Buffer, offset: number = 0, ): RSSI | undefined { if (payload.length <= offset) return; return parseRSSI(payload, offset); } function parseTXPower(payload: Buffer, offset: number = 0): number | undefined { if (payload.length <= offset) return; const ret = payload.readInt8(offset); if (ret >= -127 && ret <= 126) return ret; } /** * Parses a TX report returned by a SendData callback * @param includeACK whether ACK related fields should be parsed */ export function parseTXReport( includeACK: boolean, payload: Buffer, ): TXReport | undefined { if (payload.length < 17) return; const ret: TXReport = { txTicks: payload.readUInt16BE(0), numRepeaters: payload[2], ackRSSI: includeACK ? parseRSSI(payload, 3) : undefined, ackRepeaterRSSI: includeACK ? [ parseRSSI(payload, 4), parseRSSI(payload, 5), parseRSSI(payload, 6), parseRSSI(payload, 7), ] : undefined, ackChannelNo: includeACK ? payload[8] : undefined, txChannelNo: payload[9], routeSchemeState: payload[10], repeaterNodeIds: [payload[11], payload[12], payload[13], payload[14]], beam1000ms: !!(payload[15] & 0b0100_0000), beam250ms: !!(payload[15] & 0b0010_0000), routeSpeed: payload[15] & 0b0000_0111, routingAttempts: payload[16], // These might be missing: failedRouteLastFunctionalNodeId: payload[17], failedRouteFirstNonFunctionalNodeId: payload[18], txPower: parseTXPower(payload, 19), measuredNoiseFloor: tryParseRSSI(payload, 20), destinationAckTxPower: includeACK ? parseTXPower(payload, 21) : undefined, destinationAckMeasuredRSSI: includeACK ? tryParseRSSI(payload, 22) : undefined, destinationAckMeasuredNoiseFloor: includeACK ? tryParseRSSI(payload, 23) : undefined, }; // Remove unused repeaters from arrays const firstMissingRepeater = ret.repeaterNodeIds.indexOf(0); ret.repeaterNodeIds = ret.repeaterNodeIds.slice( 0, firstMissingRepeater, ) as any; if (ret.ackRepeaterRSSI) { ret.ackRepeaterRSSI = ret.ackRepeaterRSSI.slice( 0, firstMissingRepeater, ) as any; } return stripUndefined(ret as any) as any; } export function txReportToMessageRecord(report: TXReport): MessageRecord { const ret: MessageRecord = stripUndefined({ // This is included in the parent command's transmit status line // "TX duration": `${report.txTicks * 10} ms`, // Number of repeaters isn't interesting if it is duplicated by the node IDs // repeaters: report.numRepeaters, ...(report.repeaterNodeIds.length ? { "repeater node IDs": report.repeaterNodeIds.join(", "), } : {}), "routing attempts": report.routingAttempts, "protocol & route speed": protocolDataRateToString(report.routeSpeed), "ACK RSSI": report.ackRSSI != undefined ? rssiToString(report.ackRSSI) : undefined, ...(report.ackRepeaterRSSI?.length ? { "ACK RSSI on repeaters": report.ackRepeaterRSSI .map((rssi) => rssiToString(rssi!)) .join(", "), } : {}), "ACK channel no.": report.ackChannelNo, "TX channel no.": report.txChannelNo, // This isn't really interesting without knowing what it means // "route scheme state": report.routeSchemeState, beam: report.beam1000ms ? "1000 ms" : report.beam250ms ? "250 ms" : undefined, }); if ( report.failedRouteLastFunctionalNodeId && report.failedRouteFirstNonFunctionalNodeId ) { ret[ "route failed here" ] = `${report.failedRouteLastFunctionalNodeId} -> ${report.failedRouteFirstNonFunctionalNodeId}`; } if (report.txPower != undefined) ret["TX power"] = `${report.txPower} dBm`; if ( report.measuredNoiseFloor != undefined && report.measuredNoiseFloor !== RssiError.NotAvailable ) { ret["measured noise floor"] = rssiToString(report.measuredNoiseFloor); } if (report.destinationAckTxPower != undefined) { ret[ "ACK TX power by destination" ] = `${report.destinationAckTxPower} dBm`; } if ( report.destinationAckMeasuredRSSI != undefined && report.destinationAckMeasuredRSSI !== RssiError.NotAvailable ) { ret["measured RSSI of ACK from destination"] = rssiToString( report.destinationAckMeasuredRSSI, ); } if ( report.destinationAckMeasuredNoiseFloor != undefined && report.destinationAckMeasuredNoiseFloor !== RssiError.NotAvailable ) { ret["measured noise floor by destination"] = rssiToString( report.destinationAckMeasuredNoiseFloor, ); } return ret; } export function isSendData(msg: unknown): msg is SendDataMessage { if (!msg) return false; return ( msg instanceof SendDataRequest || msg instanceof SendDataMulticastRequest || msg instanceof SendDataBridgeRequest || msg instanceof SendDataMulticastBridgeRequest ); } export function isSendDataSinglecast( msg: unknown, ): msg is SendDataRequest | SendDataBridgeRequest { if (!msg) return false; return ( msg instanceof SendDataRequest || msg instanceof SendDataBridgeRequest ); } export function isSendDataMulticast( msg: unknown, ): msg is SendDataMulticastRequest | SendDataMulticastBridgeRequest { if (!msg) return false; return ( msg instanceof SendDataMulticastRequest || msg instanceof SendDataMulticastBridgeRequest ); } export function isSendDataTransmitReport( msg: unknown, ): msg is SendDataTransmitReport { if (!msg) return false; return ( msg instanceof SendDataRequestTransmitReport || msg instanceof SendDataMulticastRequestTransmitReport || msg instanceof SendDataBridgeRequestTransmitReport || msg instanceof SendDataMulticastBridgeRequestTransmitReport ); } export function isTransmitReport(msg: unknown): msg is TransmitReport { if (!msg) return false; return ( isSendDataTransmitReport(msg) || msg instanceof AssignReturnRouteRequestTransmitReport || msg instanceof AssignSUCReturnRouteRequestTransmitReport || msg instanceof DeleteReturnRouteRequestTransmitReport || msg instanceof DeleteSUCReturnRouteRequestTransmitReport ); } export function hasTXReport( msg: unknown, ): msg is ( | SendDataRequestTransmitReport | SendDataBridgeRequestTransmitReport ) & { txReport: TXReport } { if (!msg) return false; return ( (msg instanceof SendDataRequestTransmitReport || msg instanceof SendDataBridgeRequestTransmitReport) && !!msg.txReport ); }