zigbee-herdsman
Version:
An open source ZigBee gateway solution with node.js.
979 lines • 364 kB
JavaScript
"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`