zigbee-herdsman
Version:
An open source ZigBee gateway solution with node.js.
1,014 lines • 341 kB
JavaScript
"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