UNPKG

zigbee-herdsman

Version:

An open source ZigBee gateway solution with node.js.

1,014 lines 341 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Ezsp = exports.EzspEvents = void 0; /* istanbul ignore file */ const events_1 = __importDefault(require("events")); const cluster_1 = __importDefault(require("../../../zcl/definition/cluster")); const math_1 = require("../utils/math"); const enums_1 = require("../enums"); const zdo_1 = require("../zdo"); const consts_1 = require("./consts"); const enums_2 = require("./enums"); const ash_1 = require("../uart/ash"); const buffalo_1 = require("./buffalo"); const consts_2 = require("../consts"); const endpoints_1 = require("../adapter/endpoints"); const logger_1 = require("../../../utils/logger"); 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; /* eslint-disable max-len */ var EzspEvents; (function (EzspEvents) { //-- App logic EzspEvents["ncpNeedsResetAndInit"] = "ncpNeedsResetAndInit"; //-- ezspIncomingMessageHandler /** params => status: EmberZdoStatus, sender: EmberNodeId, apsFrame: EmberApsFrame, payload: { cluster-dependent @see zdo.ts } */ EzspEvents["ZDO_RESPONSE"] = "ZDO_RESPONSE"; /** params => type: EmberIncomingMessageType, apsFrame: EmberApsFrame, lastHopLqi: number, sender: EmberNodeId, messageContents: Buffer */ EzspEvents["INCOMING_MESSAGE"] = "INCOMING_MESSAGE"; /** params => sourcePanId: EmberPanId, sourceAddress: EmberEUI64, groupId: number | null, lastHopLqi: number, messageContents: Buffer */ EzspEvents["TOUCHLINK_MESSAGE"] = "TOUCHLINK_MESSAGE"; /** params => sender: EmberNodeId, apsFrame: EmberApsFrame, payload: EndDeviceAnnouncePayload */ EzspEvents["END_DEVICE_ANNOUNCE"] = "END_DEVICE_ANNOUNCE"; //-- ezspStackStatusHandler /** params => status: EmberStatus */ EzspEvents["STACK_STATUS"] = "STACK_STATUS"; //-- ezspTrustCenterJoinHandler /** params => newNodeId: EmberNodeId, newNodeEui64: EmberEUI64, status: EmberDeviceUpdate, policyDecision: EmberJoinDecision, parentOfNewNodeId: EmberNodeId */ EzspEvents["TRUST_CENTER_JOIN"] = "TRUST_CENTER_JOIN"; //-- ezspMessageSentHandler /** params => type: EmberOutgoingMessageType, indexOrDestination: number, apsFrame: EmberApsFrame, messageTag: number */ // MESSAGE_SENT_SUCCESS = 'MESSAGE_SENT_SUCCESS', /** params => type: EmberOutgoingMessageType, indexOrDestination: number, apsFrame: EmberApsFrame, messageTag: number */ EzspEvents["MESSAGE_SENT_DELIVERY_FAILED"] = "MESSAGE_SENT_DELIVERY_FAILED"; //-- ezspGpepIncomingMessageHandler /** params => sequenceNumber: number, commandIdentifier: number, sourceId: number, frameCounter: number, gpdCommandId: number, gpdCommandPayload: Buffer, gpdLink: number */ EzspEvents["GREENPOWER_MESSAGE"] = "GREENPOWER_MESSAGE"; })(EzspEvents || (exports.EzspEvents = EzspEvents = {})); /* eslint-enable max-len */ /** * 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`...). * * @event 'ncpNeedsResetAndInit(EzspStatus)' An error was detected that requires resetting the NCP. */ class Ezsp extends events_1.default { tickInterval; 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; initialVersionSent; /** True if a command is in the process of being sent. */ sendingCommand; /** EZSP frame sequence number. Used in EZSP_SEQUENCE_INDEX byte. */ frameSequence; /** Sequence used for EZSP send() tagging. static uint8_t */ sendSequence; /** If if a command is currently waiting for a response. Used to manage async CBs vs command responses */ waitingForResponse; /** Awaiting response resolve/timer struct. If waitingForResponse is not true, this should not be used. */ responseWaiter; /** Counter for Queue Full errors */ counterErrQueueFull; /** Handle used to tick for possible received callbacks */ tickHandle; constructor(tickInterval, options) { super(); this.tickInterval = tickInterval || 5; this.frameContents = Buffer.alloc(consts_1.EZSP_MAX_FRAME_LENGTH); this.buffalo = new buffalo_1.EzspBuffalo(this.frameContents); this.ash = new ash_1.UartAsh(options); this.ash.on(ash_1.AshEvents.fatalError, this.onAshFatalError.bind(this)); this.ash.on(ash_1.AshEvents.frame, this.onAshFrame.bind(this)); } /** * 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 frame in storage (sent or received). */ get frameToString() { const id = this.buffalo.getFrameId(); return `[FRAME: ID=${id}:"${enums_2.EzspFrameID[id]}" Seq=${this.frameContents[consts_1.EZSP_SEQUENCE_INDEX]} Len=${this.frameLength}]`; } initVariables() { if (this.waitingForResponse) { clearTimeout(this.responseWaiter.timer); } clearTimeout(this.tickHandle); this.frameContents.fill(0); this.frameLength = 0; this.buffalo.setPosition(0); this.initialVersionSent = false; this.sendingCommand = false; this.frameSequence = -1; // start at 0 this.sendSequence = 0; // start at 1 this.waitingForResponse = false; this.responseWaiter = null; this.counterErrQueueFull = 0; this.tickHandle = null; } async start() { logger_1.logger.info(`======== EZSP starting ========`, NS); this.initVariables(); let status; 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); this.tick(); 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.initVariables(); logger_1.logger.info(`======== EZSP stopped ========`, NS); } /** * Check if connected. * If not, attempt to restore the connection. * * @returns */ checkConnection() { return this.ash.connected; } onAshFatalError(status) { this.emit(EzspEvents.ncpNeedsResetAndInit, status); } onAshFrame() { // let tick handle if not waiting for response (CBs) if (this.waitingForResponse) { const status = this.responseReceived(); if (status !== enums_1.EzspStatus.NO_RX_DATA) { // we've got a non-CB frame, must be it! clearTimeout(this.responseWaiter.timer); this.responseWaiter.resolve(status); } } } /** * 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: ${this.frameToString}.`; if (status === enums_1.EzspStatus.ERROR_QUEUE_FULL) { this.counterErrQueueFull += 1; logger_1.logger.error(`NCP Queue full (counter: ${this.counterErrQueueFull}). ${lastFrameStr}`, NS); } else if (status === enums_1.EzspStatus.ERROR_OVERFLOW) { logger_1.logger.error(`The NCP 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 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)) { this.emit(EzspEvents.ncpNeedsResetAndInit, status); } } /** * The Host application must call this function periodically to allow the EZSP layer to handle asynchronous events. */ tick() { // don't process any callbacks while sending a command and waiting for its response // nothing in the rx queue, nothing to receive if (!this.sendingCommand && !this.ash.rxQueue.empty) { if (this.responseReceived() === enums_1.EzspStatus.SUCCESS) { this.callbackDispatch(); } } this.tickHandle = setTimeout(this.tick.bind(this), this.tickInterval); } nextFrameSequence() { return (this.frameSequence = ((++this.frameSequence) & 0xFF)); } startCommand(command) { if (this.sendingCommand) { logger_1.logger.error(`[SEND COMMAND] Cannot send second one before processing response from first one.`, NS); throw new Error(enums_1.EzspStatus[enums_1.EzspStatus.ERROR_INVALID_CALL]); } this.sendingCommand = true; // Send initial EZSP_VERSION command with old packet format for old Hosts/NCPs if (command === enums_2.EzspFrameID.VERSION && !this.initialVersionSent) { this.buffalo.setPosition(consts_1.EZSP_PARAMETERS_INDEX); this.buffalo.setCommandByte(consts_1.EZSP_FRAME_ID_INDEX, (0, math_1.lowByte)(command)); } else { // convert to extended frame format this.buffalo.setPosition(consts_1.EZSP_EXTENDED_PARAMETERS_INDEX); this.buffalo.setCommandByte(consts_1.EZSP_EXTENDED_FRAME_ID_LB_INDEX, (0, math_1.lowByte)(command)); this.buffalo.setCommandByte(consts_1.EZSP_EXTENDED_FRAME_ID_HB_INDEX, (0, math_1.highByte)(command)); } } /** * 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_ACK_TIMEOUT (XXX: for now) * * 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() { if (!this.checkConnection()) { logger_1.logger.debug(`[SEND COMMAND] NOT CONNECTED`, NS); return enums_1.EzspStatus.NOT_CONNECTED; } this.buffalo.setCommandByte(consts_1.EZSP_SEQUENCE_INDEX, this.nextFrameSequence()); // we always set the network index in the ezsp frame control. this.buffalo.setCommandByte(consts_1.EZSP_EXTENDED_FRAME_CONTROL_LB_INDEX, (consts_1.EZSP_FRAME_CONTROL_COMMAND | (DEFAULT_SLEEP_MODE & consts_1.EZSP_FRAME_CONTROL_SLEEP_MODE_MASK) | ((DEFAULT_NETWORK_INDEX << consts_1.EZSP_FRAME_CONTROL_NETWORK_INDEX_OFFSET) & consts_1.EZSP_FRAME_CONTROL_NETWORK_INDEX_MASK))); // Send initial EZSP_VERSION command with old packet format for old Hosts/NCPs if (!this.initialVersionSent && (this.buffalo.getCommandByte(consts_1.EZSP_FRAME_ID_INDEX) === enums_2.EzspFrameID.VERSION)) { this.initialVersionSent = true; } else { this.buffalo.setCommandByte(consts_1.EZSP_EXTENDED_FRAME_CONTROL_HB_INDEX, consts_1.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 = this.buffalo.getPosition(); if (length > consts_1.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; } this.frameLength = length; let status; logger_1.logger.debug(`===> ${this.frameToString}`, NS); try { status = await (new Promise((resolve, reject) => { const sendStatus = (this.ash.send(this.frameLength, this.frameContents)); if (sendStatus !== enums_1.EzspStatus.SUCCESS) { reject(new Error(enums_1.EzspStatus[sendStatus])); } const error = new Error(); Error.captureStackTrace(error); this.waitingForResponse = true; this.responseWaiter = { timer: setTimeout(() => { this.waitingForResponse = false; error.message = `timed out after ${this.ash.responseTimeout}ms`; reject(error); }, this.ash.responseTimeout), resolve, }; })); if (status !== enums_1.EzspStatus.SUCCESS) { throw new Error(enums_1.EzspStatus[status]); } } catch (err) { logger_1.logger.debug(`=x=> ${this.frameToString} Error: ${err}`, NS); this.ezspErrorHandler(status); } this.sendingCommand = false; return status; } /** * Checks whether a new EZSP response frame has been received. * If any, the response payload is stored in frameContents/frameLength. * Any other return value means that an error has been detected by the serial protocol layer. * @returns NO_RX_DATA if no new response has been received. * @returns SUCCESS if a new response has been received. */ checkResponseReceived() { // trigger housekeeping in ASH layer this.ash.sendExec(); let status = enums_1.EzspStatus.NO_RX_DATA; let dropBuffer = null; let buffer = this.ash.rxQueue.getPrecedingEntry(null); while (buffer != null) { // While we are waiting for a response to a command, we use the asynch callback flag to ignore asynchronous callbacks. // This allows our caller to assume that no callbacks will appear between sending a command and receiving its response. if (this.waitingForResponse && (buffer.data[consts_1.EZSP_FRAME_CONTROL_INDEX] & consts_1.EZSP_FRAME_CONTROL_ASYNCH_CB)) { logger_1.logger.debug(`Skipping async callback while waiting for response to command.`, NS); if (this.ash.rxFree.length === 0) { dropBuffer = buffer; } buffer = this.ash.rxQueue.getPrecedingEntry(buffer); } else { this.ash.rxQueue.removeEntry(buffer); buffer.data.copy(this.frameContents, 0, 0, buffer.len); // take only what len tells us is actual content this.frameLength = buffer.len; logger_1.logger.debug(`<=== ${this.frameToString}`, NS); this.ash.rxFree.freeBuffer(buffer); buffer = null; status = enums_1.EzspStatus.SUCCESS; this.waitingForResponse = false; } } if (dropBuffer != null) { this.ash.rxQueue.removeEntry(dropBuffer); this.ash.rxFree.freeBuffer(dropBuffer); logger_1.logger.debug(`ERROR Host receive queue full. Dropping received callback: ${dropBuffer.data.toString('hex')}`, NS); this.ezspErrorHandler(enums_1.EzspStatus.ERROR_QUEUE_FULL); } return status; } /** * Check if a response was received and sets the stage for parsing if valid (indexes buffalo to params index). * @returns */ responseReceived() { let status; status = this.checkResponseReceived(); if (status === enums_1.EzspStatus.NO_RX_DATA) { return status; } let frameControl, frameId, parametersIndex; // eslint-disable-next-line prefer-const [status, frameControl, frameId, parametersIndex] = this.buffalo.getResponseMetadata(); if (status === enums_1.EzspStatus.SUCCESS) { if (frameId === enums_2.EzspFrameID.INVALID_COMMAND) { status = this.buffalo.getResponseByte(parametersIndex); } if ((frameControl & consts_1.EZSP_FRAME_CONTROL_DIRECTION_MASK) !== consts_1.EZSP_FRAME_CONTROL_RESPONSE) { status = enums_1.EzspStatus.ERROR_WRONG_DIRECTION; } if ((frameControl & consts_1.EZSP_FRAME_CONTROL_TRUNCATED_MASK) === consts_1.EZSP_FRAME_CONTROL_TRUNCATED) { status = enums_1.EzspStatus.ERROR_TRUNCATED; } if ((frameControl & consts_1.EZSP_FRAME_CONTROL_OVERFLOW_MASK) === consts_1.EZSP_FRAME_CONTROL_OVERFLOW) { status = enums_1.EzspStatus.ERROR_OVERFLOW; } if ((frameControl & consts_1.EZSP_FRAME_CONTROL_PENDING_CB_MASK) === consts_1.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) { logger_1.logger.debug(`[RESPONSE RECEIVED] ERROR ${enums_1.EzspStatus[status]}`, NS); this.ezspErrorHandler(status); } this.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) { return enums_1.EzspStatus.SUCCESS; } return status; } /** * Dispatches callback frames handlers. */ callbackDispatch() { switch (this.buffalo.getExtFrameId()) { case enums_2.EzspFrameID.NO_CALLBACKS: { this.ezspNoCallbacks(); break; } case enums_2.EzspFrameID.STACK_TOKEN_CHANGED_HANDLER: { const tokenAddress = this.buffalo.readUInt16(); this.ezspStackTokenChangedHandler(tokenAddress); break; } case enums_2.EzspFrameID.TIMER_HANDLER: { const timerId = this.buffalo.readUInt8(); this.ezspTimerHandler(timerId); break; } case enums_2.EzspFrameID.COUNTER_ROLLOVER_HANDLER: { const type = this.buffalo.readUInt8(); this.ezspCounterRolloverHandler(type); break; } case enums_2.EzspFrameID.CUSTOM_FRAME_HANDLER: { const payloadLength = this.buffalo.readUInt8(); const payload = this.buffalo.readListUInt8({ length: payloadLength }); this.ezspCustomFrameHandler(payloadLength, payload); break; } case enums_2.EzspFrameID.STACK_STATUS_HANDLER: { const status = this.buffalo.readUInt8(); this.ezspStackStatusHandler(status); break; } case enums_2.EzspFrameID.ENERGY_SCAN_RESULT_HANDLER: { const channel = this.buffalo.readUInt8(); const maxRssiValue = this.buffalo.readUInt8(); this.ezspEnergyScanResultHandler(channel, maxRssiValue); break; } case enums_2.EzspFrameID.NETWORK_FOUND_HANDLER: { const networkFound = this.buffalo.readEmberZigbeeNetwork(); const lastHopLqi = this.buffalo.readUInt8(); const lastHopRssi = this.buffalo.readUInt8(); this.ezspNetworkFoundHandler(networkFound, lastHopLqi, lastHopRssi); break; } case enums_2.EzspFrameID.SCAN_COMPLETE_HANDLER: { const channel = this.buffalo.readUInt8(); const status = this.buffalo.readUInt8(); this.ezspScanCompleteHandler(channel, status); break; } case enums_2.EzspFrameID.UNUSED_PAN_ID_FOUND_HANDLER: { const panId = this.buffalo.readUInt16(); const channel = this.buffalo.readUInt8(); this.ezspUnusedPanIdFoundHandler(panId, channel); break; } case enums_2.EzspFrameID.CHILD_JOIN_HANDLER: { const index = this.buffalo.readUInt8(); const joining = this.buffalo.readUInt8() === 1 ? true : false; const childId = this.buffalo.readUInt16(); const childEui64 = this.buffalo.readIeeeAddr(); const childType = this.buffalo.readUInt8(); this.ezspChildJoinHandler(index, joining, childId, childEui64, childType); break; } case enums_2.EzspFrameID.DUTY_CYCLE_HANDLER: { const channelPage = this.buffalo.readUInt8(); const channel = this.buffalo.readUInt8(); const state = this.buffalo.readUInt8(); const totalDevices = this.buffalo.readUInt8(); const arrayOfDeviceDutyCycles = this.buffalo.readEmberPerDeviceDutyCycle(); this.ezspDutyCycleHandler(channelPage, channel, state, totalDevices, arrayOfDeviceDutyCycles); break; } case enums_2.EzspFrameID.REMOTE_SET_BINDING_HANDLER: { const entry = this.buffalo.readEmberBindingTableEntry(); const index = this.buffalo.readUInt8(); const policyDecision = this.buffalo.readUInt8(); this.ezspRemoteSetBindingHandler(entry, index, policyDecision); break; } case enums_2.EzspFrameID.REMOTE_DELETE_BINDING_HANDLER: { const index = this.buffalo.readUInt8(); const policyDecision = this.buffalo.readUInt8(); this.ezspRemoteDeleteBindingHandler(index, policyDecision); break; } case enums_2.EzspFrameID.MESSAGE_SENT_HANDLER: { const type = this.buffalo.readUInt8(); const indexOrDestination = this.buffalo.readUInt16(); const apsFrame = this.buffalo.readEmberApsFrame(); const messageTag = this.buffalo.readUInt8(); const status = this.buffalo.readUInt8(); const messageContents = this.buffalo.readPayload(); this.ezspMessageSentHandler(type, indexOrDestination, apsFrame, messageTag, status, messageContents); break; } case enums_2.EzspFrameID.POLL_COMPLETE_HANDLER: { const status = this.buffalo.readUInt8(); this.ezspPollCompleteHandler(status); break; } case enums_2.EzspFrameID.POLL_HANDLER: { const childId = this.buffalo.readUInt16(); const transmitExpected = this.buffalo.readUInt8() === 1 ? true : false; this.ezspPollHandler(childId, transmitExpected); break; } case enums_2.EzspFrameID.INCOMING_SENDER_EUI64_HANDLER: { const senderEui64 = this.buffalo.readIeeeAddr(); this.ezspIncomingSenderEui64Handler(senderEui64); break; } case enums_2.EzspFrameID.INCOMING_MESSAGE_HANDLER: { const type = this.buffalo.readUInt8(); const apsFrame = this.buffalo.readEmberApsFrame(); const lastHopLqi = this.buffalo.readUInt8(); const lastHopRssi = this.buffalo.readUInt8(); const sender = this.buffalo.readUInt16(); const bindingIndex = this.buffalo.readUInt8(); const addressIndex = this.buffalo.readUInt8(); const messageContents = this.buffalo.readPayload(); this.ezspIncomingMessageHandler(type, apsFrame, lastHopLqi, lastHopRssi, sender, bindingIndex, addressIndex, messageContents); break; } case enums_2.EzspFrameID.INCOMING_MANY_TO_ONE_ROUTE_REQUEST_HANDLER: { const source = this.buffalo.readUInt16(); const longId = this.buffalo.readIeeeAddr(); const cost = this.buffalo.readUInt8(); this.ezspIncomingManyToOneRouteRequestHandler(source, longId, cost); break; } case enums_2.EzspFrameID.INCOMING_ROUTE_ERROR_HANDLER: { const status = this.buffalo.readUInt8(); const target = this.buffalo.readUInt16(); this.ezspIncomingRouteErrorHandler(status, target); break; } case enums_2.EzspFrameID.INCOMING_NETWORK_STATUS_HANDLER: { const errorCode = this.buffalo.readUInt8(); const target = this.buffalo.readUInt16(); this.ezspIncomingNetworkStatusHandler(errorCode, target); break; } case enums_2.EzspFrameID.INCOMING_ROUTE_RECORD_HANDLER: { const source = this.buffalo.readUInt16(); const sourceEui = this.buffalo.readIeeeAddr(); const lastHopLqi = this.buffalo.readUInt8(); const lastHopRssi = this.buffalo.readUInt8(); const relayCount = this.buffalo.readUInt8(); const relayList = this.buffalo.readListUInt16({ length: relayCount }); //this.buffalo.readListUInt8({length: (relayCount * 2)}); this.ezspIncomingRouteRecordHandler(source, sourceEui, lastHopLqi, lastHopRssi, relayCount, relayList); break; } case enums_2.EzspFrameID.ID_CONFLICT_HANDLER: { const id = this.buffalo.readUInt16(); this.ezspIdConflictHandler(id); break; } case enums_2.EzspFrameID.MAC_PASSTHROUGH_MESSAGE_HANDLER: { const messageType = this.buffalo.readUInt8(); const lastHopLqi = this.buffalo.readUInt8(); const lastHopRssi = this.buffalo.readUInt8(); const messageContents = this.buffalo.readPayload(); this.ezspMacPassthroughMessageHandler(messageType, lastHopLqi, lastHopRssi, messageContents); break; } case enums_2.EzspFrameID.MAC_FILTER_MATCH_MESSAGE_HANDLER: { const filterIndexMatch = this.buffalo.readUInt8(); const legacyPassthroughType = this.buffalo.readUInt8(); const lastHopLqi = this.buffalo.readUInt8(); const lastHopRssi = this.buffalo.readUInt8(); const messageContents = this.buffalo.readPayload(); this.ezspMacFilterMatchMessageHandler(filterIndexMatch, legacyPassthroughType, lastHopLqi, lastHopRssi, messageContents); break; } case enums_2.EzspFrameID.RAW_TRANSMIT_COMPLETE_HANDLER: { const status = this.buffalo.readUInt8(); this.ezspRawTransmitCompleteHandler(status); break; } case enums_2.EzspFrameID.SWITCH_NETWORK_KEY_HANDLER: { const sequenceNumber = this.buffalo.readUInt8(); this.ezspSwitchNetworkKeyHandler(sequenceNumber); break; } case enums_2.EzspFrameID.ZIGBEE_KEY_ESTABLISHMENT_HANDLER: { const partner = this.buffalo.readIeeeAddr(); const status = this.buffalo.readUInt8(); this.ezspZigbeeKeyEstablishmentHandler(partner, status); break; } case enums_2.EzspFrameID.TRUST_CENTER_JOIN_HANDLER: { const newNodeId = this.buffalo.readUInt16(); const newNodeEui64 = this.buffalo.readIeeeAddr(); const status = this.buffalo.readUInt8(); const policyDecision = this.buffalo.readUInt8(); const parentOfNewNodeId = this.buffalo.readUInt16(); this.ezspTrustCenterJoinHandler(newNodeId, newNodeEui64, status, policyDecision, parentOfNewNodeId); break; } case enums_2.EzspFrameID.GENERATE_CBKE_KEYS_HANDLER: { const status = this.buffalo.readUInt8(); const ephemeralPublicKey = this.buffalo.readEmberPublicKeyData(); this.ezspGenerateCbkeKeysHandler(status, ephemeralPublicKey); break; } case enums_2.EzspFrameID.CALCULATE_SMACS_HANDLER: { const status = this.buffalo.readUInt8(); const initiatorSmac = this.buffalo.readEmberSmacData(); const responderSmac = this.buffalo.readEmberSmacData(); this.ezspCalculateSmacsHandler(status, initiatorSmac, responderSmac); break; } case enums_2.EzspFrameID.GENERATE_CBKE_KEYS_HANDLER283K1: { const status = this.buffalo.readUInt8(); const ephemeralPublicKey = this.buffalo.readEmberPublicKey283k1Data(); this.ezspGenerateCbkeKeysHandler283k1(status, ephemeralPublicKey); break; } case enums_2.EzspFrameID.CALCULATE_SMACS_HANDLER283K1: { const status = this.buffalo.readUInt8(); const initiatorSmac = this.buffalo.readEmberSmacData(); const responderSmac = this.buffalo.readEmberSmacData(); this.ezspCalculateSmacsHandler283k1(status, initiatorSmac, responderSmac); break; } case enums_2.EzspFrameID.DSA_SIGN_HANDLER: { const status = this.buffalo.readUInt8(); const messageContents = this.buffalo.readPayload(); this.ezspDsaSignHandler(status, messageContents); break; } case enums_2.EzspFrameID.DSA_VERIFY_HANDLER: { const status = this.buffalo.readUInt8(); this.ezspDsaVerifyHandler(status); break; } case enums_2.EzspFrameID.MFGLIB_RX_HANDLER: { const linkQuality = this.buffalo.readUInt8(); const rssi = this.buffalo.readUInt8(); const packetLength = this.buffalo.readUInt8(); const packetContents = this.buffalo.readListUInt8({ length: packetLength }); this.ezspMfglibRxHandler(linkQuality, rssi, packetLength, packetContents); break; } case enums_2.EzspFrameID.INCOMING_BOOTLOAD_MESSAGE_HANDLER: { const longId = this.buffalo.readIeeeAddr(); const lastHopLqi = this.buffalo.readUInt8(); const lastHopRssi = this.buffalo.readUInt8(); const messageContents = this.buffalo.readPayload(); this.ezspIncomingBootloadMessageHandler(longId, lastHopLqi, lastHopRssi, messageContents); break; } case enums_2.EzspFrameID.BOOTLOAD_TRANSMIT_COMPLETE_HANDLER: { const status = this.buffalo.readUInt8(); const messageContents = this.buffalo.readPayload(); this.ezspBootloadTransmitCompleteHandler(status, messageContents); break; } case enums_2.EzspFrameID.ZLL_NETWORK_FOUND_HANDLER: { const networkInfo = this.buffalo.readEmberZllNetwork(); const isDeviceInfoNull = this.buffalo.readUInt8() === 1 ? true : false; const deviceInfo = this.buffalo.readEmberZllDeviceInfoRecord(); const lastHopLqi = this.buffalo.readUInt8(); const lastHopRssi = this.buffalo.readUInt8(); this.ezspZllNetworkFoundHandler(networkInfo, isDeviceInfoNull, deviceInfo, lastHopLqi, lastHopRssi); break; } case enums_2.EzspFrameID.ZLL_SCAN_COMPLETE_HANDLER: { const status = this.buffalo.readUInt8(); this.ezspZllScanCompleteHandler(status); break; } case enums_2.EzspFrameID.ZLL_ADDRESS_ASSIGNMENT_HANDLER: { const addressInfo = this.buffalo.readEmberZllAddressAssignment(); const lastHopLqi = this.buffalo.readUInt8(); const lastHopRssi = this.buffalo.readUInt8(); this.ezspZllAddressAssignmentHandler(addressInfo, lastHopLqi, lastHopRssi); break; } case enums_2.EzspFrameID.ZLL_TOUCH_LINK_TARGET_HANDLER: { const networkInfo = this.buffalo.readEmberZllNetwork(); this.ezspZllTouchLinkTargetHandler(networkInfo); break; } case enums_2.EzspFrameID.D_GP_SENT_HANDLER: { const status = this.buffalo.readUInt8(); const gpepHandle = this.buffalo.readUInt8(); this.ezspDGpSentHandler(status, gpepHandle); break; } case enums_2.EzspFrameID.GPEP_INCOMING_MESSAGE_HANDLER: { const status = this.buffalo.readUInt8(); const gpdLink = this.buffalo.readUInt8(); const sequenceNumber = this.buffalo.readUInt8(); const addr = this.buffalo.readEmberGpAddress(); const gpdfSecurityLevel = this.buffalo.readUInt8(); const gpdfSecurityKeyType = this.buffalo.readUInt8(); const autoCommissioning = this.buffalo.readUInt8() === 1 ? true : false; const bidirectionalInfo = this.buffalo.readUInt8(); const gpdSecurityFrameCounter = this.buffalo.readUInt32(); const gpdCommandId = this.buffalo.readUInt8(); const mic = this.buffalo.readUInt32(); const proxyTableIndex = this.buffalo.readUInt8(); const gpdCommandPayload = this.buffalo.readPayload(); this.ezspGpepIncomingMessageHandler(status, gpdLink, sequenceNumber, addr, gpdfSecurityLevel, gpdfSecurityKeyType, autoCommissioning, bidirectionalInfo, gpdSecurityFrameCounter, gpdCommandId, mic, proxyTableIndex, gpdCommandPayload); break; } default: this.ezspErrorHandler(enums_1.EzspStatus.ERROR_INVALID_FRAME_ID); } } /** * * @returns uint8_t */ nextSendSequence() { return (this.sendSequence = ((++this.sendSequence) & MESSAGE_TAG_MASK)); } /** * 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 EmberNodeId 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. * @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.BAD_ARGUMENT if type not supported. * @returns apsSequence as returned by ezspSend${x} command * @returns messageTag Tag used for ezspSend${x} command */ async send(type, indexOrDestination, apsFrame, message, alias, sequence) { let status = enums_1.EmberStatus.BAD_ARGUMENT; let apsSequence; const messageTag = this.nextSendSequence(); 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: { [status, apsSequence] = (await this.ezspSendMulticast(apsFrame, ZA_MAX_HOPS /* hops */, ZA_MAX_HOPS /* nonmember radius */, messageTag, message)); break; } case enums_1.EmberOutgoingMessageType.MULTICAST_WITH_ALIAS: { [status, apsSequence] = (await this.ezspSendMulticastWithAlias(apsFrame, apsFrame.radius /*radius*/, apsFrame.radius /*nonmember radius*/, alias, sequence, messageTag, message)); break; } case enums_1.EmberOutgoingMessageType.BROADCAST: { [status, apsSequence] = (await this.ezspSendBroadcast(indexOrDestination, apsFrame, ZA_MAX_HOPS /*radius*/, messageTag, message)); break; } case enums_1.EmberOutgoingMessageType.BROADCAST_WITH_ALIAS: { [status, apsSequence] = (await this.ezspProxyBroadcast(alias, indexOrDestination, sequence, apsFrame, apsFrame.radius, messageTag, message)); break; } default: 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.EmberStatus[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 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 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`. * @param endpoint uint8_t * @returns EzspStatus * @returns flags */ async ezspGetEndpointFlags(endpoint) { const [status, outValLen, outVal] = (await this.ezspGetExtendedValue(enums_2.EzspExtendedValueId.ENDPOINT_FLAGS, endpoint, 2)); if (outValLen < 2) { throw enums_1.EzspStatus.ERROR_INVALID_VALUE; } const returnFlags = (0, math_1.highLowToInt)(outVal[1], outVal[0]); return [status, returnFlags]; } /** * Wrapper for `ezspGetExtendedValue`. * @param EmberNodeId * @param destination * @returns EzspStatus * @returns overhead uint8_t */ async ezspGetSourceRouteOverhead(destination) { const [status, outValLen, outVal] = (await this.ezspGetExtendedValue(enums_2.EzspExtendedValueId.GET_SOURCE_ROUTE_OVERHEAD, destination, 1)); if (outValLen < 1) { throw enums_1.EzspStatus.ERROR_INVALID_VALUE; } return [status, outVal[0]]; } /** * Wrapper for `ezspGetExtendedValue`. * @returns EzspStatus * @returns reason * @returns nodeId EmberNodeId* */ async ezspGetLastLeaveReason() { const [status, outValLen, outVal] = (await this.ezspGetExtendedValue(enums_2.EzspExtendedValueId.LAST_LEAVE_REASON, 0, 3)); if (outValLen < 3) { throw enums_1.EzspStatus.ERROR_INVALID_VALUE; } return [status, outVal[0], (0, math_1.highLowToInt)(outVal[2], outVal[1])]; } /** * Wrapper for `ezspGetValue`. * @returns EzspStatus * @returns reason */ async ezspGetLastRejoinReason() { const [status, outValLen, outVal] = (await this.ezspGetValue(enums_2.EzspValueId.LAST_REJOIN_REASON, 1)); if (outValLen < 1) { throw enums_1.EzspStatus.ERROR_INVALID_VALUE; } return [status, outVal[0]]; } /** * Wrapper for `ezspSetValue`. * @param mask * @returns */ async ezspSetExtendedSecurityBitmask(mask) { return this.ezspSetValue(enums_2.EzspValueId.EXTENDED_SECURITY_BITMASK, 2, [(0, math_1.lowByte)(mask), (0, math_1.highByte)(mask)]); } /** * Wrapper for `ezspGetValue`. * @returns */ async ezspGetExtendedSecurityBitmask() { const [status, outValLen, outVal] = (await this.ezspGetValue(enums_2.EzspValueId.EXTENDED_SECURITY_BITMASK, 2)); if (outValLen < 2) { throw enums_1.EzspStatus.ERROR_INVALID_VALUE; } return [status, (0, math_1.highLowToInt)(outVal[1], outVal[0])]; } /** * Wrapper for `ezspSetValue`. * @returns */ async ezspStartWritingStackTokens() { return this.ezspSetValue(enums_2.EzspValueId.STACK_TOKEN_WRITING, 1, [1]); } /** * Wrapper for `ezspSetValue`. * @returns */ async ezspStopWritingStackTokens() { return this.ezspSetValue(enums_2.EzspValueId.STACK_TOKEN_WRITING, 1, [0]); } //-----------------------------------------------------------------------------// //---------------------------- START EZSP COMMANDS ----------------------------// //-----------------------------------------------------------------------------// //----------------------------------------------------------------------------- // Configuration Frames //----------------------------------------------------------------------------- /** * The command allows the Host to specify the desired EZSP version and must be * sent before any other command. The response provides information about the * firmware running on the NCP. * * @param desiredProtocolVersion uint8_t The EZSP version the Host wishes to use. * To successfully set the version and allow other commands, this must be same as EZSP_PROTOCOL_VERSION. * @return * - uint8_t The EZSP version the NCP is using. * - uint8_t * The type of stack running on the NCP (2). * - uint16_t * The version number of the stack. */ async ezspVersion(desiredProtocolVersion) { this.startCommand(enums_2.EzspFrameID.VERSION); this.buffalo.writeUInt8(desiredProtocolVersion); const sendStatus = await this.sendCommand(); if (sendStatus !== enums_1.EzspStatus.SUCCESS) { throw new Error(enums_1.EzspStatus[sendStatus]); } const protocolVersion = this.buffalo.readUInt8(); const stackType = this.buffalo.readUInt8(); const stackVersion = this.buffalo.readUInt16(); return [protocolVersion, stackType, stackVersion]; } /** * Reads a configuration value from the NCP. * * @param configId Identifies which configuration value to read. * @returns * - EzspStatus.SUCCESS if the value was read successfully, * - EzspStatus.ERROR_INVALID_ID if the NCP does not recognize configId. * - uint16_t * The configuration value. */ async ezspGetConfigurationValue(configId) { this.startCommand(enums_2.EzspFrameID.GET_CONFIGURATION_VALUE); this.buffalo.writeUInt8(configId); const sendStatus = await this.sendCommand(); if (sendStatus !== enums_1.EzspStatus.SUCCESS) { throw new Error(enums_1.EzspStatus[sendStatus]); } const status = this.buffalo.readUInt8(); const value = this.buffalo.readUInt16(); return [status, value]; } /** * Writes a configuration value to the NCP. Configuration values can be modified * by the Host after the NCP has reset. Once the status of the stack changes to * EMBER_NETWORK_UP, configuration values can no longer be modified and this * command will respond with EzspStatus.ERROR_INVALID_CALL. * * @param configId Identifies which configuration value to change. * @param value uint16_t The new configuration value. * @returns EzspStatus * - EzspStatus.SUCCESS if the configuration value was changed, * - EzspStatus.ERROR_OUT_OF_MEMORY if the new value exceeded the available memory, * - EzspStatus.ERROR_INVALID_VALUE if the new value was out of bounds, * - EzspStatus.ERROR_INVALID_ID if the NCP does not recognize configId, * - EzspStatus.ERROR_INVALID_CALL if configuration values can no longer be modified. */ async ezspSetConfigurationValue(configId, value) { this.startCommand(enums_2.EzspFrameID.SET_CONFIGURATION_VALUE); this.buffalo.writeUInt8(configId); this.buffalo.writeUInt16(value); const sendStatus = await this.sendCommand(); if (sendStatus !== enums_1.EzspStatus.SUCCESS) { throw new Error(enums_1.EzspStatus[sendStatus]); } const status = this.buffalo.readUInt8(); return status; } /** * Read attribute data on NCP endpoints. * @param endpoint uint8_t Endpoint * @param cluster uint16_t Cluster. * @param attributeId uint16_t Attribute ID. * @param mask uint8_t Mask. * @param manufacturerCode uint16_t Manufacturer code. * @returns * - An EmberStatus value indicating success or the reason for failure. * - uint8_t * Attribute data type. * - uint8_t