UNPKG

zigbee-herdsman

Version:

An open source ZigBee gateway solution with node.js.

979 lines 364 kB
"use strict"; /* v8 ignore start */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Ezsp = void 0; const node_events_1 = __importDefault(require("node:events")); const utils_1 = require("../../../utils"); const logger_1 = require("../../../utils/logger"); const ZSpec = __importStar(require("../../../zspec")); const Zcl = __importStar(require("../../../zspec/zcl")); const cluster_1 = require("../../../zspec/zcl/definition/cluster"); const Zdo = __importStar(require("../../../zspec/zdo")); const endpoints_1 = require("../adapter/endpoints"); const consts_1 = require("../consts"); const enums_1 = require("../enums"); const ezspError_1 = require("../ezspError"); const ash_1 = require("../uart/ash"); const initters_1 = require("../utils/initters"); const math_1 = require("../utils/math"); const buffalo_1 = require("./buffalo"); const consts_2 = require("./consts"); const enums_2 = require("./enums"); const NS = "zh:ember:ezsp"; /** no multi-network atm, so just use const */ const DEFAULT_NETWORK_INDEX = endpoints_1.FIXED_ENDPOINTS[0].networkIndex; /** other values not supported atm */ const DEFAULT_SLEEP_MODE = enums_2.EzspSleepMode.IDLE; /** Maximum number of times we attempt to reset the NCP and start the ASH protocol. */ const MAX_INIT_ATTEMPTS = 5; /** * This is the max hops that the network can support - used to determine the max source route overhead * and broadcast radius if we havent defined MAX_HOPS then define based on profile ID */ // #ifdef HAS_SECURITY_PROFILE_SE // export const ZA_MAX_HOPS = 6; // #else const ZA_MAX_HOPS = 12; // #endif /** * The mask applied to generated message tags used by the framework when sending messages via EZSP. * Customers who call ezspSend functions directly must use message tags outside this mask. */ const MESSAGE_TAG_MASK = 0x7f; /** * Host EZSP layer. * * Provides functions that allow the Host application to send every EZSP command to the NCP. * * Commands to send to the serial>ASH layers all are named `ezsp${CommandName}`. * They do nothing but build the command, send it and return the value(s). * Callers are expected to handle errors appropriately. * - They will throw `EzspStatus` if `sendCommand` fails or the returned value(s) by NCP are invalid (wrong length, etc). * - Most will return `EmberStatus` given by NCP (some `EzspStatus`, some `SLStatus`...). */ class Ezsp extends node_events_1.default { version; ash; buffalo; /** The contents of the current EZSP frame. CAREFUL using this guy, it's pre-allocated. */ frameContents; /** The total Length of the incoming frame */ frameLength; callbackBuffalo; /** The contents of the current EZSP frame. CAREFUL using this guy, it's pre-allocated. */ callbackFrameContents; /** The total Length of the incoming frame */ callbackFrameLength; initialVersionSent; /** EZSP frame sequence number. Used in EZSP_SEQUENCE_INDEX byte. */ frameSequence; /** Sequence used for EZSP send() tagging. static uint8_t */ sendSequence; queue; /** Awaiting response resolve/timer struct. undefined if not waiting for response. */ responseWaiter; /** Counter for Queue Full errors */ counterErrQueueFull; constructor(options) { super(); this.frameContents = Buffer.alloc(consts_2.EZSP_MAX_FRAME_LENGTH); this.buffalo = new buffalo_1.EzspBuffalo(this.frameContents, 0); this.callbackFrameContents = Buffer.alloc(consts_2.EZSP_MAX_FRAME_LENGTH); this.callbackBuffalo = new buffalo_1.EzspBuffalo(this.callbackFrameContents, 0); this.queue = new utils_1.Queue(1); this.ash = new ash_1.UartAsh(options); this.version = 0; this.frameLength = 0; this.callbackFrameLength = 0; this.initialVersionSent = false; this.frameSequence = -1; // start at 0 this.sendSequence = 0; // start at 1 this.counterErrQueueFull = 0; } /** * Returns the number of EZSP responses that have been received by the serial * protocol and are ready to be collected by the EZSP layer via * responseReceived(). */ get pendingResponseCount() { return this.ash.rxQueue.length; } /** * Create a string representation of the last received frame in storage. */ get frameToString() { const id = this.buffalo.getFrameId(); return `[FRAME: ID=${id}:"${enums_2.EzspFrameID[id]}" Seq=${this.frameContents[consts_2.EZSP_SEQUENCE_INDEX]} Len=${this.frameLength}]`; } /** * Create a string representation of the last received callback frame in storage. */ get callbackFrameToString() { const id = this.callbackBuffalo.getFrameId(); return `[CBFRAME: ID=${id}:"${enums_2.EzspFrameID[id]}" Seq=${this.callbackFrameContents[consts_2.EZSP_SEQUENCE_INDEX]} Len=${this.callbackFrameLength}]`; } async start() { logger_1.logger.info("======== EZSP starting ========", NS); let status = enums_1.EzspStatus.HOST_FATAL_ERROR; for (let i = 0; i < MAX_INIT_ATTEMPTS; i++) { status = await this.ash.resetNcp(); // fail early if we couldn't even get the port set up if (status !== enums_1.EzspStatus.SUCCESS) { return status; } status = await this.ash.start(); if (status === enums_1.EzspStatus.SUCCESS) { logger_1.logger.info("======== EZSP started ========", NS); // registered after reset sequence this.ash.on("frame", this.onAshFrame.bind(this)); this.ash.on("fatalError", this.onAshFatalError.bind(this)); return status; } } return status; } /** * Cleanly close down the serial protocol (UART). * After this function has been called, init() must be called to resume communication with the NCP. */ async stop() { await this.ash.stop(); this.ash.removeAllListeners(); logger_1.logger.info("======== EZSP stopped ========", NS); } /** * Must be called immediately after `ezspVersion` to sync the Host protocol version. * @param version Version for the Host to use. */ setProtocolVersion(version) { this.version = version; } /** * Check if connected. * If not, attempt to restore the connection. * * @returns */ checkConnection() { return this.ash.connected; } /** * Triggered by @see 'FATAL_ERROR' */ onAshFatalError(status) { this.emit("ncpNeedsResetAndInit", status); } /** * Triggered by @see 'FRAME' */ onAshFrame() { const buffer = this.ash.rxQueue.getPrecedingEntry(); if (buffer == null) { // something is seriously wrong logger_1.logger.error("Found no buffer in queue but ASH layer sent signal that one was available.", NS); return; } this.ash.rxQueue.removeEntry(buffer); // logger.debug(`<<<< ${buffer.data.subarray(0, buffer.len).toString('hex')}`, NS); if (buffer.data[consts_2.EZSP_FRAME_CONTROL_INDEX] & consts_2.EZSP_FRAME_CONTROL_ASYNCH_CB) { // take only what len tells us is actual content buffer.data.copy(this.callbackFrameContents, 0, 0, buffer.len); this.callbackFrameLength = buffer.len; logger_1.logger.debug(`<=== ${this.callbackFrameToString}`, NS); this.ash.rxFree.freeBuffer(buffer); const status = this.validateReceivedFrame(this.callbackBuffalo); if (status === enums_1.EzspStatus.SUCCESS) { this.callbackDispatch(); } else { logger_1.logger.debug(`<=x= ${this.callbackFrameToString} Invalid, status=${enums_1.EzspStatus[status]}.`, NS); } } else { // take only what len tells us is actual content buffer.data.copy(this.frameContents, 0, 0, buffer.len); this.frameLength = buffer.len; logger_1.logger.debug(`<=== ${this.frameToString}`, NS); this.ash.rxFree.freeBuffer(buffer); // always if (this.responseWaiter !== undefined) { const status = this.validateReceivedFrame(this.buffalo); clearTimeout(this.responseWaiter.timer); this.responseWaiter.resolve(status); this.responseWaiter = undefined; // done, gc } else { logger_1.logger.debug("Received response while not expecting one. Ignoring.", NS); } } } /** * Event from the EZSP layer indicating that the transaction with the NCP could not be completed due to a * serial protocol error or that the response received from the NCP reported an error. * The status parameter provides more information about the error. * * @param status */ ezspErrorHandler(status) { const lastFrameStr = `Last Frame: ${this.frameToString}.`; if (status === enums_1.EzspStatus.ERROR_QUEUE_FULL) { this.counterErrQueueFull += 1; logger_1.logger.error(`Adapter queue full (counter: ${this.counterErrQueueFull}). ${lastFrameStr}`, NS); } else if (status === enums_1.EzspStatus.ERROR_OVERFLOW) { logger_1.logger.error(`The adapter has run out of buffers, causing general malfunction. Remediate network congestion, if present. ${lastFrameStr}`, NS); } else { logger_1.logger.error(`ERROR Transaction failure; status=${enums_1.EzspStatus[status]}. ${lastFrameStr}`, NS); } // Do not reset if improper frame control direction flag (XXX: certain devices appear to trigger this somehow, without reason?) // Do not reset if this is a decryption failure, as we ignored the packet // Do not reset for a callback overflow or error queue, as we don't want the device to reboot under stress; // resetting under these conditions does not solve the problem as the problem is external to the NCP. // Throttling the additional traffic and staggering things might make it better instead. // For all other errors, we reset the NCP if (status !== enums_1.EzspStatus.ERROR_SECURITY_PARAMETERS_INVALID && status !== enums_1.EzspStatus.ERROR_OVERFLOW && status !== enums_1.EzspStatus.ERROR_QUEUE_FULL && status !== enums_1.EzspStatus.ERROR_WRONG_DIRECTION) { this.emit("ncpNeedsResetAndInit", status); } } nextFrameSequence() { this.frameSequence = ++this.frameSequence & 0xff; return this.frameSequence; } startCommand(command) { const sendBuffalo = new buffalo_1.EzspBuffalo(Buffer.alloc(consts_2.EZSP_MAX_FRAME_LENGTH)); // Send initial EZSP_VERSION command with old packet format for old Hosts/NCPs if (command === enums_2.EzspFrameID.VERSION && !this.initialVersionSent) { sendBuffalo.setPosition(consts_2.EZSP_PARAMETERS_INDEX); sendBuffalo.setCommandByte(consts_2.EZSP_FRAME_ID_INDEX, (0, math_1.lowByte)(command)); } else { // convert to extended frame format sendBuffalo.setPosition(consts_2.EZSP_EXTENDED_PARAMETERS_INDEX); sendBuffalo.setCommandByte(consts_2.EZSP_EXTENDED_FRAME_ID_LB_INDEX, (0, math_1.lowByte)(command)); sendBuffalo.setCommandByte(consts_2.EZSP_EXTENDED_FRAME_ID_HB_INDEX, (0, math_1.highByte)(command)); } return sendBuffalo; } /** * Sends the current EZSP command frame. Returns EZSP_SUCCESS if the command was sent successfully. * Any other return value means that an error has been detected by the serial protocol layer. * * if ezsp.sendCommand fails early, this will be: * - EzspStatus.ERROR_INVALID_CALL * - EzspStatus.NOT_CONNECTED * - EzspStatus.ERROR_COMMAND_TOO_LONG * * if ezsp.sendCommand fails, this will be whatever ash.send returns: * - EzspStatus.SUCCESS * - EzspStatus.NO_TX_SPACE * - EzspStatus.DATA_FRAME_TOO_SHORT * - EzspStatus.DATA_FRAME_TOO_LONG * - EzspStatus.NOT_CONNECTED * * if ezsp.sendCommand times out, this will be EzspStatus.ASH_ERROR_TIMEOUTS * * if ezsp.sendCommand resolves, this will be whatever ezsp.responseReceived returns: * - EzspStatus.NO_RX_DATA (should not happen if command was sent (since we subscribe to frame event to trigger function)) * - status from EzspFrameID.INVALID_COMMAND status byte * - EzspStatus.ERROR_UNSUPPORTED_CONTROL * - EzspStatus.ERROR_WRONG_DIRECTION * - EzspStatus.ERROR_TRUNCATED * - EzspStatus.SUCCESS */ async sendCommand(sendBuffalo) { if (!this.checkConnection()) { logger_1.logger.debug("[SEND COMMAND] NOT CONNECTED", NS); return enums_1.EzspStatus.NOT_CONNECTED; } const sequence = this.nextFrameSequence(); sendBuffalo.setCommandByte(consts_2.EZSP_SEQUENCE_INDEX, sequence); // we always set the network index in the ezsp frame control. sendBuffalo.setCommandByte(consts_2.EZSP_EXTENDED_FRAME_CONTROL_LB_INDEX, consts_2.EZSP_FRAME_CONTROL_COMMAND | (DEFAULT_SLEEP_MODE & consts_2.EZSP_FRAME_CONTROL_SLEEP_MODE_MASK) | ((DEFAULT_NETWORK_INDEX << consts_2.EZSP_FRAME_CONTROL_NETWORK_INDEX_OFFSET) & consts_2.EZSP_FRAME_CONTROL_NETWORK_INDEX_MASK)); // Send initial EZSP_VERSION command with old packet format for old Hosts/NCPs if (!this.initialVersionSent && sendBuffalo.getCommandByte(consts_2.EZSP_FRAME_ID_INDEX) === enums_2.EzspFrameID.VERSION) { this.initialVersionSent = true; } else { sendBuffalo.setCommandByte(consts_2.EZSP_EXTENDED_FRAME_CONTROL_HB_INDEX, consts_2.EZSP_EXTENDED_FRAME_FORMAT_VERSION); } // might have tried to write more than allocated EZSP_MAX_FRAME_LENGTH for frameContents // use write index to detect broken frames cases (inc'ed every time a byte is supposed to have been written) // since index is always inc'ed on setCommandByte, this should always end at 202 max const length = sendBuffalo.getPosition(); if (length > consts_2.EZSP_MAX_FRAME_LENGTH) { // this.ezspErrorHandler(EzspStatus.ERROR_COMMAND_TOO_LONG);// XXX: this forces a NCP reset?? return enums_1.EzspStatus.ERROR_COMMAND_TOO_LONG; } return await this.queue.execute(async () => { let status = enums_1.EzspStatus.ASH_ERROR_TIMEOUTS; // will be overwritten below as necessary const frameId = sendBuffalo.getFrameId(); const frameString = `[FRAME: ID=${frameId}:"${enums_2.EzspFrameID[frameId]}" Seq=${sequence} Len=${length}]`; logger_1.logger.debug(`===> ${frameString}`, NS); try { status = await new Promise((resolve, reject) => { const sendStatus = this.ash.send(length, sendBuffalo.getBuffer()); if (sendStatus !== enums_1.EzspStatus.SUCCESS) { reject(new ezspError_1.EzspError(sendStatus)); return; } this.responseWaiter = { timer: setTimeout(() => reject(new ezspError_1.EzspError(enums_1.EzspStatus.ASH_ERROR_TIMEOUTS)), this.ash.responseTimeout), resolve, }; }); if (status !== enums_1.EzspStatus.SUCCESS) { throw new ezspError_1.EzspError(status); } } catch (error) { this.responseWaiter = undefined; logger_1.logger.debug(`=x=> ${frameString} ${error}`, NS); if (error instanceof ezspError_1.EzspError) { status = error.code; this.ezspErrorHandler(status); } } return status; }); } /** * Sets the stage for parsing if valid (indexes buffalo to params index). * @returns */ validateReceivedFrame(buffalo) { const [retStatus, frameControl, frameId, parametersIndex] = buffalo.getResponseMetadata(); let status = retStatus; if (frameId === enums_2.EzspFrameID.INVALID_COMMAND) { status = buffalo.getResponseByte(parametersIndex); } if ((frameControl & consts_2.EZSP_FRAME_CONTROL_DIRECTION_MASK) !== consts_2.EZSP_FRAME_CONTROL_RESPONSE) { status = enums_1.EzspStatus.ERROR_WRONG_DIRECTION; } if ((frameControl & consts_2.EZSP_FRAME_CONTROL_TRUNCATED_MASK) === consts_2.EZSP_FRAME_CONTROL_TRUNCATED) { status = enums_1.EzspStatus.ERROR_TRUNCATED; } if ((frameControl & consts_2.EZSP_FRAME_CONTROL_OVERFLOW_MASK) === consts_2.EZSP_FRAME_CONTROL_OVERFLOW) { status = enums_1.EzspStatus.ERROR_OVERFLOW; } if ((frameControl & consts_2.EZSP_FRAME_CONTROL_PENDING_CB_MASK) === consts_2.EZSP_FRAME_CONTROL_PENDING_CB) { this.ash.ncpHasCallbacks = true; } else { this.ash.ncpHasCallbacks = false; } // Set the callback network //this.callbackNetworkIndex = (frameControl & EZSP_FRAME_CONTROL_NETWORK_INDEX_MASK) >> EZSP_FRAME_CONTROL_NETWORK_INDEX_OFFSET; if (status !== enums_1.EzspStatus.SUCCESS) { this.ezspErrorHandler(status); } buffalo.setPosition(parametersIndex); // An overflow does not indicate a comms failure; // The system can still communicate but buffers are running critically low. // This is almost always due to network congestion and goes away when the network becomes quieter. if (status === enums_1.EzspStatus.ERROR_OVERFLOW) { status = enums_1.EzspStatus.SUCCESS; } return status; } /** * Dispatches callback frames handlers. */ callbackDispatch() { switch (this.callbackBuffalo.getExtFrameId()) { case enums_2.EzspFrameID.NO_CALLBACKS: { this.ezspNoCallbacks(); break; } case enums_2.EzspFrameID.STACK_TOKEN_CHANGED_HANDLER: { const tokenAddress = this.callbackBuffalo.readUInt16(); this.ezspStackTokenChangedHandler(tokenAddress); break; } case enums_2.EzspFrameID.TIMER_HANDLER: { const timerId = this.callbackBuffalo.readUInt8(); this.ezspTimerHandler(timerId); break; } case enums_2.EzspFrameID.COUNTER_ROLLOVER_HANDLER: { const type = this.callbackBuffalo.readUInt8(); this.ezspCounterRolloverHandler(type); break; } case enums_2.EzspFrameID.CUSTOM_FRAME_HANDLER: { const payload = this.callbackBuffalo.readPayload(); this.ezspCustomFrameHandler(payload); break; } case enums_2.EzspFrameID.STACK_STATUS_HANDLER: { const status = this.callbackBuffalo.readStatus(this.version); this.ezspStackStatusHandler(status); break; } case enums_2.EzspFrameID.ENERGY_SCAN_RESULT_HANDLER: { const channel = this.callbackBuffalo.readUInt8(); const maxRssiValue = this.callbackBuffalo.readInt8(); this.ezspEnergyScanResultHandler(channel, maxRssiValue); break; } case enums_2.EzspFrameID.NETWORK_FOUND_HANDLER: { const networkFound = this.callbackBuffalo.readEmberZigbeeNetwork(); const lastHopLqi = this.callbackBuffalo.readUInt8(); const lastHopRssi = this.callbackBuffalo.readInt8(); this.ezspNetworkFoundHandler(networkFound, lastHopLqi, lastHopRssi); break; } case enums_2.EzspFrameID.SCAN_COMPLETE_HANDLER: { const channel = this.callbackBuffalo.readUInt8(); const status = this.callbackBuffalo.readStatus(this.version); this.ezspScanCompleteHandler(channel, status); break; } case enums_2.EzspFrameID.UNUSED_PAN_ID_FOUND_HANDLER: { const panId = this.callbackBuffalo.readUInt16(); const channel = this.callbackBuffalo.readUInt8(); this.ezspUnusedPanIdFoundHandler(panId, channel); break; } case enums_2.EzspFrameID.CHILD_JOIN_HANDLER: { const index = this.callbackBuffalo.readUInt8(); const joining = this.callbackBuffalo.readUInt8() !== 0; const childId = this.callbackBuffalo.readUInt16(); const childEui64 = this.callbackBuffalo.readIeeeAddr(); const childType = this.callbackBuffalo.readUInt8(); this.ezspChildJoinHandler(index, joining, childId, childEui64, childType); break; } case enums_2.EzspFrameID.DUTY_CYCLE_HANDLER: { const channelPage = this.callbackBuffalo.readUInt8(); const channel = this.callbackBuffalo.readUInt8(); const state = this.callbackBuffalo.readUInt8(); const totalDevices = this.callbackBuffalo.readUInt8(); const arrayOfDeviceDutyCycles = this.callbackBuffalo.readEmberPerDeviceDutyCycle(); this.ezspDutyCycleHandler(channelPage, channel, state, totalDevices, arrayOfDeviceDutyCycles); break; } case enums_2.EzspFrameID.REMOTE_SET_BINDING_HANDLER: { const entry = this.callbackBuffalo.readEmberBindingTableEntry(); const index = this.callbackBuffalo.readUInt8(); const policyDecision = this.callbackBuffalo.readStatus(this.version); this.ezspRemoteSetBindingHandler(entry, index, policyDecision); break; } case enums_2.EzspFrameID.REMOTE_DELETE_BINDING_HANDLER: { const index = this.callbackBuffalo.readUInt8(); const policyDecision = this.callbackBuffalo.readStatus(this.version); this.ezspRemoteDeleteBindingHandler(index, policyDecision); break; } case enums_2.EzspFrameID.MESSAGE_SENT_HANDLER: { if (this.version < 0x0e) { const type = this.callbackBuffalo.readUInt8(); const indexOrDestination = this.callbackBuffalo.readUInt16(); const apsFrame = this.callbackBuffalo.readEmberApsFrame(); const messageTag = this.callbackBuffalo.readUInt8(); const status = this.callbackBuffalo.readStatus(this.version); // EzspPolicyId.MESSAGE_CONTENTS_IN_CALLBACK_POLICY set to messageTag only, so skip parsing entirely // const messageContents = this.callbackBuffalo.readPayload(); this.ezspMessageSentHandler(status, type, indexOrDestination, apsFrame, messageTag); } else { const status = this.callbackBuffalo.readUInt32(); const type = this.callbackBuffalo.readUInt8(); const indexOrDestination = this.callbackBuffalo.readUInt16(); const apsFrame = this.callbackBuffalo.readEmberApsFrame(); const messageTag = this.callbackBuffalo.readUInt16(); // EzspPolicyId.MESSAGE_CONTENTS_IN_CALLBACK_POLICY set to messageTag only, so skip parsing entirely // const messageContents = this.callbackBuffalo.readPayload(); this.ezspMessageSentHandler(status, type, indexOrDestination, apsFrame, messageTag); } break; } case enums_2.EzspFrameID.POLL_COMPLETE_HANDLER: { const status = this.callbackBuffalo.readStatus(this.version); this.ezspPollCompleteHandler(status); break; } case enums_2.EzspFrameID.POLL_HANDLER: { const childId = this.callbackBuffalo.readUInt16(); const transmitExpected = this.callbackBuffalo.readUInt8() !== 0; this.ezspPollHandler(childId, transmitExpected); break; } case enums_2.EzspFrameID.INCOMING_MESSAGE_HANDLER: { if (this.version < 0x0e) { const type = this.callbackBuffalo.readUInt8(); const apsFrame = this.callbackBuffalo.readEmberApsFrame(); const lastHopLqi = this.callbackBuffalo.readUInt8(); const lastHopRssi = this.callbackBuffalo.readInt8(); const senderShortId = this.callbackBuffalo.readUInt16(); const bindingIndex = this.callbackBuffalo.readUInt8(); const addressIndex = this.callbackBuffalo.readUInt8(); const packetInfo = { senderShortId, senderLongId: ZSpec.BLANK_EUI64, bindingIndex, addressIndex, lastHopLqi, lastHopRssi, lastHopTimestamp: 0, }; const messageContents = this.callbackBuffalo.readPayload(); this.ezspIncomingMessageHandler(type, apsFrame, packetInfo, messageContents); } else { const type = this.callbackBuffalo.readUInt8(); const apsFrame = this.callbackBuffalo.readEmberApsFrame(); const packetInfo = this.callbackBuffalo.readEmberRxPacketInfo(); const messageContents = this.callbackBuffalo.readPayload(); this.ezspIncomingMessageHandler(type, apsFrame, packetInfo, messageContents); } break; } case enums_2.EzspFrameID.INCOMING_MANY_TO_ONE_ROUTE_REQUEST_HANDLER: { const source = this.callbackBuffalo.readUInt16(); const longId = this.callbackBuffalo.readIeeeAddr(); const cost = this.callbackBuffalo.readUInt8(); this.ezspIncomingManyToOneRouteRequestHandler(source, longId, cost); break; } case enums_2.EzspFrameID.INCOMING_ROUTE_ERROR_HANDLER: { const status = this.callbackBuffalo.readStatus(this.version); const target = this.callbackBuffalo.readUInt16(); this.ezspIncomingRouteErrorHandler(status, target); break; } case enums_2.EzspFrameID.INCOMING_NETWORK_STATUS_HANDLER: { const errorCode = this.callbackBuffalo.readUInt8(); const target = this.callbackBuffalo.readUInt16(); this.ezspIncomingNetworkStatusHandler(errorCode, target); break; } case enums_2.EzspFrameID.INCOMING_ROUTE_RECORD_HANDLER: { const source = this.callbackBuffalo.readUInt16(); const sourceEui = this.callbackBuffalo.readIeeeAddr(); const lastHopLqi = this.callbackBuffalo.readUInt8(); const lastHopRssi = this.callbackBuffalo.readInt8(); const relayCount = this.callbackBuffalo.readUInt8(); const relayList = this.callbackBuffalo.readListUInt16(relayCount); //this.callbackBuffalo.readListUInt8(relayCount * 2); this.ezspIncomingRouteRecordHandler(source, sourceEui, lastHopLqi, lastHopRssi, relayCount, relayList); break; } case enums_2.EzspFrameID.ID_CONFLICT_HANDLER: { const id = this.callbackBuffalo.readUInt16(); this.ezspIdConflictHandler(id); break; } case enums_2.EzspFrameID.MAC_PASSTHROUGH_MESSAGE_HANDLER: { if (this.version < 0x0e) { const messageType = this.callbackBuffalo.readUInt8(); const lastHopLqi = this.callbackBuffalo.readUInt8(); const lastHopRssi = this.callbackBuffalo.readInt8(); const packetInfo = { senderShortId: ZSpec.NULL_NODE_ID, senderLongId: ZSpec.BLANK_EUI64, bindingIndex: ZSpec.NULL_BINDING, addressIndex: 0xff, lastHopLqi, lastHopRssi, lastHopTimestamp: 0, }; const messageContents = this.callbackBuffalo.readPayload(); this.ezspMacPassthroughMessageHandler(messageType, packetInfo, messageContents); } else { const messageType = this.callbackBuffalo.readUInt8(); const packetInfo = this.callbackBuffalo.readEmberRxPacketInfo(); const messageContents = this.callbackBuffalo.readPayload(); this.ezspMacPassthroughMessageHandler(messageType, packetInfo, messageContents); } break; } case enums_2.EzspFrameID.MAC_FILTER_MATCH_MESSAGE_HANDLER: { if (this.version < 0x0e) { const filterIndexMatch = this.callbackBuffalo.readUInt8(); const legacyPassthroughType = this.callbackBuffalo.readUInt8(); const lastHopLqi = this.callbackBuffalo.readUInt8(); const lastHopRssi = this.callbackBuffalo.readInt8(); const packetInfo = { senderShortId: ZSpec.NULL_NODE_ID, senderLongId: ZSpec.BLANK_EUI64, bindingIndex: ZSpec.NULL_BINDING, addressIndex: 0xff, lastHopLqi, lastHopRssi, lastHopTimestamp: 0, }; const messageContents = this.callbackBuffalo.readPayload(); this.ezspMacFilterMatchMessageHandler(filterIndexMatch, legacyPassthroughType, packetInfo, messageContents); } else { const filterIndexMatch = this.callbackBuffalo.readUInt8(); const legacyPassthroughType = this.callbackBuffalo.readUInt8(); const packetInfo = this.callbackBuffalo.readEmberRxPacketInfo(); const messageContents = this.callbackBuffalo.readPayload(); this.ezspMacFilterMatchMessageHandler(filterIndexMatch, legacyPassthroughType, packetInfo, messageContents); } break; } case enums_2.EzspFrameID.RAW_TRANSMIT_COMPLETE_HANDLER: { if (this.version < 0x0e) { const status = this.callbackBuffalo.readStatus(this.version); this.ezspRawTransmitCompleteHandler(Buffer.alloc(0), status); } else { const messageContents = this.callbackBuffalo.readPayload(); const status = this.callbackBuffalo.readUInt32(); this.ezspRawTransmitCompleteHandler(messageContents, status); } break; } case enums_2.EzspFrameID.SWITCH_NETWORK_KEY_HANDLER: { const sequenceNumber = this.callbackBuffalo.readUInt8(); this.ezspSwitchNetworkKeyHandler(sequenceNumber); break; } case enums_2.EzspFrameID.ZIGBEE_KEY_ESTABLISHMENT_HANDLER: { const partner = this.callbackBuffalo.readIeeeAddr(); const status = this.callbackBuffalo.readUInt8(); this.ezspZigbeeKeyEstablishmentHandler(partner, status); break; } case enums_2.EzspFrameID.TRUST_CENTER_JOIN_HANDLER: { const newNodeId = this.callbackBuffalo.readUInt16(); const newNodeEui64 = this.callbackBuffalo.readIeeeAddr(); const status = this.callbackBuffalo.readUInt8(); const policyDecision = this.callbackBuffalo.readUInt8(); const parentOfNewNodeId = this.callbackBuffalo.readUInt16(); this.ezspTrustCenterJoinHandler(newNodeId, newNodeEui64, status, policyDecision, parentOfNewNodeId); break; } case enums_2.EzspFrameID.GENERATE_CBKE_KEYS_HANDLER: { const status = this.callbackBuffalo.readStatus(this.version); const ephemeralPublicKey = this.callbackBuffalo.readEmberPublicKeyData(); this.ezspGenerateCbkeKeysHandler(status, ephemeralPublicKey); break; } case enums_2.EzspFrameID.CALCULATE_SMACS_HANDLER: { const status = this.callbackBuffalo.readStatus(this.version); const initiatorSmac = this.callbackBuffalo.readEmberSmacData(); const responderSmac = this.callbackBuffalo.readEmberSmacData(); this.ezspCalculateSmacsHandler(status, initiatorSmac, responderSmac); break; } case enums_2.EzspFrameID.GENERATE_CBKE_KEYS_HANDLER283K1: { const status = this.callbackBuffalo.readStatus(this.version); const ephemeralPublicKey = this.callbackBuffalo.readEmberPublicKey283k1Data(); this.ezspGenerateCbkeKeysHandler283k1(status, ephemeralPublicKey); break; } case enums_2.EzspFrameID.CALCULATE_SMACS_HANDLER283K1: { const status = this.callbackBuffalo.readStatus(this.version); const initiatorSmac = this.callbackBuffalo.readEmberSmacData(); const responderSmac = this.callbackBuffalo.readEmberSmacData(); this.ezspCalculateSmacsHandler283k1(status, initiatorSmac, responderSmac); break; } case enums_2.EzspFrameID.DSA_SIGN_HANDLER: { const status = this.callbackBuffalo.readStatus(this.version); const messageContents = this.callbackBuffalo.readPayload(); this.ezspDsaSignHandler(status, messageContents); break; } case enums_2.EzspFrameID.DSA_VERIFY_HANDLER: { const status = this.callbackBuffalo.readStatus(this.version); this.ezspDsaVerifyHandler(status); break; } case enums_2.EzspFrameID.MFGLIB_RX_HANDLER: { const linkQuality = this.callbackBuffalo.readUInt8(); const rssi = this.callbackBuffalo.readInt8(); const packetContents = this.callbackBuffalo.readPayload(); this.ezspMfglibRxHandler(linkQuality, rssi, packetContents); break; } case enums_2.EzspFrameID.INCOMING_BOOTLOAD_MESSAGE_HANDLER: { if (this.version < 0x0e) { const longId = this.callbackBuffalo.readIeeeAddr(); const lastHopLqi = this.callbackBuffalo.readUInt8(); const lastHopRssi = this.callbackBuffalo.readInt8(); const packetInfo = { senderShortId: ZSpec.NULL_NODE_ID, senderLongId: ZSpec.BLANK_EUI64, bindingIndex: ZSpec.NULL_BINDING, addressIndex: 0xff, lastHopLqi, lastHopRssi, lastHopTimestamp: 0, }; const messageContents = this.callbackBuffalo.readPayload(); this.ezspIncomingBootloadMessageHandler(longId, packetInfo, messageContents); } else { const longId = this.callbackBuffalo.readIeeeAddr(); const packetInfo = this.callbackBuffalo.readEmberRxPacketInfo(); const messageContents = this.callbackBuffalo.readPayload(); this.ezspIncomingBootloadMessageHandler(longId, packetInfo, messageContents); } break; } case enums_2.EzspFrameID.BOOTLOAD_TRANSMIT_COMPLETE_HANDLER: { const status = this.callbackBuffalo.readStatus(this.version); const messageContents = this.callbackBuffalo.readPayload(); this.ezspBootloadTransmitCompleteHandler(status, messageContents); break; } case enums_2.EzspFrameID.INCOMING_MFG_TEST_MESSAGE_HANDLER: { const messageType = this.callbackBuffalo.readUInt8(); const messageContents = this.callbackBuffalo.readPayload(); this.ezspIncomingMfgTestMessageHandler(messageType, messageContents); break; } case enums_2.EzspFrameID.ZLL_NETWORK_FOUND_HANDLER: { if (this.version < 0x0e) { const networkInfo = this.callbackBuffalo.readEmberZllNetwork(); const isDeviceInfoNull = this.callbackBuffalo.readUInt8() !== 0; const deviceInfo = this.callbackBuffalo.readEmberZllDeviceInfoRecord(); const lastHopLqi = this.callbackBuffalo.readUInt8(); const lastHopRssi = this.callbackBuffalo.readInt8(); const packetInfo = { senderShortId: ZSpec.NULL_NODE_ID, senderLongId: ZSpec.BLANK_EUI64, bindingIndex: ZSpec.NULL_BINDING, addressIndex: 0xff, lastHopLqi, lastHopRssi, lastHopTimestamp: 0, }; this.ezspZllNetworkFoundHandler(networkInfo, isDeviceInfoNull, deviceInfo, packetInfo); } else { const networkInfo = this.callbackBuffalo.readEmberZllNetwork(); const isDeviceInfoNull = this.callbackBuffalo.readUInt8() !== 0; const deviceInfo = this.callbackBuffalo.readEmberZllDeviceInfoRecord(); const packetInfo = this.callbackBuffalo.readEmberRxPacketInfo(); this.ezspZllNetworkFoundHandler(networkInfo, isDeviceInfoNull, deviceInfo, packetInfo); } break; } case enums_2.EzspFrameID.ZLL_SCAN_COMPLETE_HANDLER: { const status = this.callbackBuffalo.readStatus(this.version); this.ezspZllScanCompleteHandler(status); break; } case enums_2.EzspFrameID.ZLL_ADDRESS_ASSIGNMENT_HANDLER: { if (this.version < 0x0e) { const addressInfo = this.callbackBuffalo.readEmberZllAddressAssignment(); const lastHopLqi = this.callbackBuffalo.readUInt8(); const lastHopRssi = this.callbackBuffalo.readInt8(); const packetInfo = { senderShortId: ZSpec.NULL_NODE_ID, senderLongId: ZSpec.BLANK_EUI64, bindingIndex: ZSpec.NULL_BINDING, addressIndex: 0xff, lastHopLqi, lastHopRssi, lastHopTimestamp: 0, }; this.ezspZllAddressAssignmentHandler(addressInfo, packetInfo); } else { const addressInfo = this.callbackBuffalo.readEmberZllAddressAssignment(); const packetInfo = this.callbackBuffalo.readEmberRxPacketInfo(); this.ezspZllAddressAssignmentHandler(addressInfo, packetInfo); } break; } case enums_2.EzspFrameID.ZLL_TOUCH_LINK_TARGET_HANDLER: { const networkInfo = this.callbackBuffalo.readEmberZllNetwork(); this.ezspZllTouchLinkTargetHandler(networkInfo); break; } case enums_2.EzspFrameID.D_GP_SENT_HANDLER: { const status = this.callbackBuffalo.readStatus(this.version); const gpepHandle = this.callbackBuffalo.readUInt8(); this.ezspDGpSentHandler(status, gpepHandle); break; } case enums_2.EzspFrameID.GPEP_INCOMING_MESSAGE_HANDLER: { const gpStatus = this.callbackBuffalo.readUInt8(); const gpdLink = this.callbackBuffalo.readUInt8(); const sequenceNumber = this.callbackBuffalo.readUInt8(); const addr = this.callbackBuffalo.readEmberGpAddress(); const gpdfSecurityLevel = this.callbackBuffalo.readUInt8(); const gpdfSecurityKeyType = this.callbackBuffalo.readUInt8(); const autoCommissioning = this.callbackBuffalo.readUInt8() !== 0; const bidirectionalInfo = this.callbackBuffalo.readUInt8(); const gpdSecurityFrameCounter = this.callbackBuffalo.readUInt32(); const gpdCommandId = this.callbackBuffalo.readUInt8(); const mic = this.callbackBuffalo.readUInt32(); const proxyTableIndex = this.callbackBuffalo.readUInt8(); const gpdCommandPayload = this.callbackBuffalo.readPayload(); let packetInfo; if (this.version < 0x10) { packetInfo = { senderShortId: ZSpec.NULL_NODE_ID, senderLongId: ZSpec.BLANK_EUI64, bindingIndex: ZSpec.NULL_BINDING, addressIndex: 0xff, lastHopLqi: gpdLink, lastHopRssi: 0, lastHopTimestamp: 0, }; } else { packetInfo = this.callbackBuffalo.readEmberRxPacketInfo(); } this.ezspGpepIncomingMessageHandler(gpStatus, gpdLink, sequenceNumber, addr, gpdfSecurityLevel, gpdfSecurityKeyType, autoCommissioning, bidirectionalInfo, gpdSecurityFrameCounter, gpdCommandId, mic, proxyTableIndex, gpdCommandPayload, packetInfo); break; } default: logger_1.logger.debug(`<=x= Ignored unused/unknown ${this.callbackFrameToString}`, NS); } } /** * * @returns uint8_t */ nextSendSequence() { this.sendSequence = ++this.sendSequence & MESSAGE_TAG_MASK; return this.sendSequence; } /** * Calls ezspSend${x} based on type and takes care of tagging message. * * Alias types expect `alias` & `sequence` params, along with `apsFrame.radius`. * * @param type Specifies the outgoing message type. * @param indexOrDestination uint16_t Depending on the type of addressing used, this is either the NodeId of the destination, * an index into the address table, or an index into the binding table. * Unused for multicast types. * This must be one of the three ZigBee broadcast addresses for broadcast. * @param apsFrame [IN/OUT] EmberApsFrame * The APS frame which is to be added to the message. * Sequence set in OUT as returned by ezspSend${x} command * @param message uint8_t * Content of the message. * @param alias The alias source address * @param sequence uint8_t The alias sequence number * @returns Result of the ezspSend${x} call or EmberStatus.INVALID_PARAMETER if type not supported. * @returns messageTag Tag used for ezspSend${x} command */ async send(type, indexOrDestination, apsFrame, message, alias, sequence) { let status = enums_1.SLStatus.INVALID_PARAMETER; let apsSequence; const messageTag = this.nextSendSequence(); let nwkRadius = ZA_MAX_HOPS; let nwkAlias = ZSpec.NULL_NODE_ID; switch (type) { case enums_1.EmberOutgoingMessageType.VIA_BINDING: case enums_1.EmberOutgoingMessageType.VIA_ADDRESS_TABLE: case enums_1.EmberOutgoingMessageType.DIRECT: { [status, apsSequence] = await this.ezspSendUnicast(type, indexOrDestination, apsFrame, messageTag, message); break; } case enums_1.EmberOutgoingMessageType.MULTICAST: case enums_1.EmberOutgoingMessageType.MULTICAST_WITH_ALIAS: { if (type === enums_1.EmberOutgoingMessageType.MULTICAST_WITH_ALIAS || (apsFrame.sourceEndpoint === ZSpec.GP_ENDPOINT && apsFrame.destinationEndpoint === ZSpec.GP_ENDPOINT && apsFrame.options & enums_1.EmberApsOption.USE_ALIAS_SEQUENCE_NUMBER)) { nwkRadius = apsFrame.radius ?? nwkRadius; nwkAlias = alias; } [status, apsSequence] = await this.ezspSendMulticast(apsFrame, nwkRadius, 0, // broadcast addr nwkAlias, sequence, messageTag, message); break; } case enums_1.EmberOutgoingMessageType.BROADCAST: case enums_1.EmberOutgoingMessageType.BROADCAST_WITH_ALIAS: { if (type === enums_1.EmberOutgoingMessageType.BROADCAST_WITH_ALIAS || (apsFrame.sourceEndpoint === ZSpec.GP_ENDPOINT && apsFrame.destinationEndpoint === ZSpec.GP_ENDPOINT && apsFrame.options & enums_1.EmberApsOption.USE_ALIAS_SEQUENCE_NUMBER)) { nwkRadius = apsFrame.radius ?? nwkRadius; nwkAlias = alias; } [status, apsSequence] = await this.ezspSendBroadcast(nwkAlias, indexOrDestination, sequence, apsFrame, nwkRadius, messageTag, message); break; } } apsFrame.sequence = apsSequence; // NOTE: match `~~~>` from adapter since this is just a wrapper for it logger_1.logger.debug(`~~~> [SENT type=${enums_1.EmberOutgoingMessageType[type]} apsSequence=${apsSequence} messageTag=${messageTag} status=${enums_1.SLStatus[status]}]`, NS); return [status, messageTag]; } /** * Retrieving the new version info. * Wrapper for `ezspGetValue`. * @returns Send status * @returns EmberVersion*, null if status not SUCCESS. */ async ezspGetVersionStruct() { const [status, outValueLength, outValue] = await this.ezspGetValue(enums_2.EzspValueId.VERSION_INFO, 7); // sizeof(EmberVersion) if (outValueLength !== 7) { throw new ezspError_1.EzspError(enums_1.EzspStatus.ERROR_INVALID_VALUE); } return [ status, { build: outValue[0] + (outValue[1] << 8), major: outValue[2], minor: outValue[3], patch: outValue[4], special: outValue[5], type: outValue[6], }, ]; } /** * Function for manipulating the endpoints flags on the NCP. * Wrapper for `ezspGetExtendedValue` * @param endpoint uint8_t * @param flags EzspEndpointFlags * @returns EzspStatus */ async ezspSetEndpointFlags(endpoint, flags) { return await this.ezspSetValue(enums_2.EzspValueId.ENDPOINT_FLAGS, 3, [endpoint, (0, math_1.lowByte)(flags), (0, math_1.highByte)(flags)]); } /** * Function for manipulating the endpoints flags on the NCP. * Wrapper for `ezspGetExtendedValue`