zigbee-herdsman
Version:
An open source ZigBee gateway solution with node.js.
820 lines • 38.6 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.Driver = void 0;
const ezsp_1 = require("./ezsp");
const types_1 = require("./types");
const events_1 = require("events");
const struct_1 = require("./types/struct");
const utils_1 = require("./utils");
const named_1 = require("./types/named");
const multicast_1 = require("./multicast");
const utils_2 = require("../../../utils");
const es6_1 = __importDefault(require("fast-deep-equal/es6"));
const backup_1 = require("../adapter/backup");
const logger_1 = require("../../../utils/logger");
const NS = 'zh:ezsp:driv';
const IEEE_PREFIX_MFG_ID = [
{ mfgId: 0x115F, prefix: [0x04, 0xcf, 0xfc] },
{ mfgId: 0x115F, prefix: [0x54, 0xef, 0x44] },
];
const DEFAULT_MFG_ID = 0x1049;
// we make three attempts to send the request
const REQUEST_ATTEMPT_DELAYS = [500, 1000, 1500];
class Driver extends events_1.EventEmitter {
ezsp;
nwkOpt;
greenPowerGroup;
networkParams;
version;
eui64ToNodeId = new Map();
eui64ToRelays = new Map();
ieee;
multicast;
waitress;
transactionID = 1;
serialOpt;
backupMan;
constructor(serialOpt, nwkOpt, greenPowerGroup, backupPath) {
super();
this.nwkOpt = nwkOpt;
this.serialOpt = serialOpt;
this.greenPowerGroup = greenPowerGroup;
this.waitress = new utils_2.Waitress(this.waitressValidator, this.waitressTimeoutFormatter);
this.backupMan = new backup_1.EZSPAdapterBackup(this, backupPath);
}
/**
* Requested by the EZSP watchdog after too many failures, or by UART layer after port closed unexpectedly.
* Tries to stop the layers below and startup again.
* @returns
*/
async reset() {
logger_1.logger.debug(`Reset connection.`, NS);
try {
// don't emit 'close' on stop since we don't want this to bubble back up as 'disconnected' to the controller.
await this.stop(false);
}
catch (err) {
logger_1.logger.debug(`Stop error ${err.stack}`, NS);
}
try {
await (0, utils_2.Wait)(1000);
logger_1.logger.debug(`Startup again.`, NS);
await this.startup();
}
catch (err) {
logger_1.logger.debug(`Reset error ${err.stack}`, NS);
try {
// here we let emit
await this.stop();
}
catch (stopErr) {
logger_1.logger.debug(`Failed to stop after failed reset ${stopErr.stack}`, NS);
}
}
}
async onEzspReset() {
logger_1.logger.debug('onEzspReset()', NS);
await this.reset();
}
onEzspClose() {
logger_1.logger.debug('onEzspClose()', NS);
this.emit('close');
}
async stop(emitClose = true) {
logger_1.logger.debug('Stopping driver', NS);
if (this.ezsp) {
return this.ezsp.close(emitClose);
}
}
async startup() {
let result = 'resumed';
this.transactionID = 1;
this.ezsp = undefined;
this.ezsp = new ezsp_1.Ezsp();
this.ezsp.on('close', this.onEzspClose.bind(this));
try {
await this.ezsp.connect(this.serialOpt);
}
catch (error) {
logger_1.logger.debug(`EZSP could not connect: ${error.cause ?? error}`, NS);
throw error;
}
this.ezsp.on('reset', this.onEzspReset.bind(this));
await this.ezsp.version();
await this.ezsp.updateConfig();
await this.ezsp.updatePolicies();
//await this.ezsp.setValue(EzspValueId.VALUE_MAXIMUM_OUTGOING_TRANSFER_SIZE, 82);
//await this.ezsp.setValue(EzspValueId.VALUE_MAXIMUM_INCOMING_TRANSFER_SIZE, 82);
await this.ezsp.setValue(named_1.EzspValueId.VALUE_END_DEVICE_KEEP_ALIVE_SUPPORT_MODE, 3);
await this.ezsp.setValue(named_1.EzspValueId.VALUE_CCA_THRESHOLD, 0);
await this.ezsp.setSourceRouting();
//const count = await ezsp.getConfigurationValue(EzspConfigId.CONFIG_APS_UNICAST_MESSAGE_COUNT);
//logger.info("APS_UNICAST_MESSAGE_COUNT is set to %s", count, NS);
await this.addEndpoint({
inputClusters: [0x0000, 0x0003, 0x0006, 0x000A, 0x0019, 0x001A, 0x0300],
outputClusters: [0x0000, 0x0003, 0x0004, 0x0005, 0x0006, 0x0008, 0x0020,
0x0300, 0x0400, 0x0402, 0x0405, 0x0406, 0x0500, 0x0B01, 0x0B03,
0x0B04, 0x0702, 0x1000, 0xFC01, 0xFC02]
});
await this.addEndpoint({
endpoint: 242, profileId: 0xA1E0, deviceId: 0x61,
outputClusters: [0x0021]
});
// getting MFG_STRING token
//const mfgName = await ezsp.execCommand('getMfgToken', EzspMfgTokenId.MFG_STRING);
// getting MFG_BOARD_NAME token
//const boardName = await ezsp.execCommand('getMfgToken', EzspMfgTokenId.MFG_BOARD_NAME);
/* eslint-disable prefer-const */
let verInfo = await this.ezsp.getValue(named_1.EzspValueId.VALUE_VERSION_INFO);
let build, major, minor, patch, special;
[build, verInfo] = types_1.uint16_t.deserialize(types_1.uint16_t, verInfo);
[major, verInfo] = types_1.uint8_t.deserialize(types_1.uint8_t, verInfo);
[minor, verInfo] = types_1.uint8_t.deserialize(types_1.uint8_t, verInfo);
[patch, verInfo] = types_1.uint8_t.deserialize(types_1.uint8_t, verInfo);
[special, verInfo] = types_1.uint8_t.deserialize(types_1.uint8_t, verInfo);
/* eslint-enable prefer-const */
const vers = `${major}.${minor}.${patch}.${special} build ${build}`;
logger_1.logger.debug(`EmberZNet version: ${vers}`, NS);
this.version = {
product: this.ezsp.ezspV,
majorrel: `${major}`,
minorrel: `${minor}`,
maintrel: `${patch} `,
revision: vers
};
if (await this.needsToBeInitialised(this.nwkOpt)) {
// need to check the backup
const restore = await this.needsToBeRestore(this.nwkOpt);
const res = await this.ezsp.execCommand('networkState');
logger_1.logger.debug(`Network state ${res.status}`, NS);
if (res.status == named_1.EmberNetworkStatus.JOINED_NETWORK) {
logger_1.logger.info(`Leaving current network and forming new network`, NS);
const st = await this.ezsp.leaveNetwork();
if (st != types_1.EmberStatus.NETWORK_DOWN) {
logger_1.logger.error(`leaveNetwork returned unexpected status: ${st}`, NS);
}
}
if (restore) {
// restore
logger_1.logger.info("Restore network from backup", NS);
await this.formNetwork(true);
result = 'restored';
}
else {
// reset
logger_1.logger.info("Form network", NS);
await this.formNetwork(false);
result = 'reset';
}
}
const state = (await this.ezsp.execCommand('networkState')).status;
logger_1.logger.debug(`Network state ${state}`, NS);
const netParams = await this.ezsp.execCommand('getNetworkParameters');
if (netParams.status != types_1.EmberStatus.SUCCESS) {
logger_1.logger.error(`Command (getNetworkParameters) returned unexpected state: ${netParams.status}`, NS);
}
this.networkParams = netParams.parameters;
logger_1.logger.debug(`Node type: ${netParams.nodeType}, Network parameters: ${this.networkParams}`, NS);
const nwk = (await this.ezsp.execCommand('getNodeId')).nodeId;
const ieee = (await this.ezsp.execCommand('getEui64')).eui64;
this.ieee = new named_1.EmberEUI64(ieee);
logger_1.logger.debug('Network ready', NS);
this.ezsp.on('frame', this.handleFrame.bind(this));
this.handleNodeJoined(nwk, this.ieee);
logger_1.logger.debug(`EZSP nwk=${nwk}, IEEE=0x${this.ieee}`, NS);
const linkResult = await this.getKey(named_1.EmberKeyType.TRUST_CENTER_LINK_KEY);
logger_1.logger.debug(`TRUST_CENTER_LINK_KEY: ${JSON.stringify(linkResult)}`, NS);
const netResult = await this.getKey(named_1.EmberKeyType.CURRENT_NETWORK_KEY);
logger_1.logger.debug(`CURRENT_NETWORK_KEY: ${JSON.stringify(netResult)}`, NS);
await (0, utils_2.Wait)(1000);
await this.ezsp.execCommand('setManufacturerCode', { code: DEFAULT_MFG_ID });
this.multicast = new multicast_1.Multicast(this);
await this.multicast.startup([]);
await this.multicast.subscribe(this.greenPowerGroup, 242);
// await this.multicast.subscribe(1, 901);
return result;
}
async needsToBeInitialised(options) {
let valid = true;
valid = valid && (await this.ezsp.networkInit());
const netParams = await this.ezsp.execCommand('getNetworkParameters');
const networkParams = netParams.parameters;
logger_1.logger.debug(`Current Node type: ${netParams.nodeType}, Network parameters: ${networkParams}`, NS);
valid = valid && (netParams.status == types_1.EmberStatus.SUCCESS);
valid = valid && (netParams.nodeType == types_1.EmberNodeType.COORDINATOR);
valid = valid && (options.panID == networkParams.panId);
valid = valid && (options.channelList.includes(networkParams.radioChannel));
valid = valid && ((0, es6_1.default)(options.extendedPanID, networkParams.extendedPanId));
return !valid;
}
async formNetwork(restore) {
let backup;
await this.ezsp.execCommand('clearTransientLinkKeys');
let initial_security_state;
if (restore) {
backup = await this.backupMan.getStoredBackup();
initial_security_state = (0, utils_1.ember_security)(backup.networkOptions.networkKey);
initial_security_state.bitmask |= named_1.EmberInitialSecurityBitmask.NO_FRAME_COUNTER_RESET;
initial_security_state.networkKeySequenceNumber = backup.networkKeyInfo.sequenceNumber;
initial_security_state.preconfiguredKey.contents = backup.ezsp.hashed_tclk;
}
else {
await this.ezsp.execCommand('clearKeyTable');
initial_security_state = (0, utils_1.ember_security)(Buffer.from(this.nwkOpt.networkKey));
}
await this.ezsp.setInitialSecurityState(initial_security_state);
const parameters = new struct_1.EmberNetworkParameters();
parameters.radioTxPower = 5;
parameters.joinMethod = named_1.EmberJoinMethod.USE_MAC_ASSOCIATION;
parameters.nwkManagerId = 0;
parameters.nwkUpdateId = 0;
parameters.channels = 0x07FFF800; // all channels
if (restore) {
parameters.panId = backup.networkOptions.panId;
parameters.extendedPanId = backup.networkOptions.extendedPanId;
parameters.radioChannel = backup.logicalChannel;
parameters.nwkUpdateId = backup.networkUpdateId;
}
else {
parameters.radioChannel = this.nwkOpt.channelList[0];
parameters.panId = this.nwkOpt.panID;
parameters.extendedPanId = Buffer.from(this.nwkOpt.extendedPanID);
}
await this.ezsp.formNetwork(parameters);
await this.ezsp.setValue(named_1.EzspValueId.VALUE_STACK_TOKEN_WRITING, 1);
}
handleFrame(frameName, frame) {
switch (true) {
case (frameName === 'incomingMessageHandler'): {
const eui64 = this.eui64ToNodeId.get(frame.sender);
const handled = this.waitress.resolve({
address: frame.sender,
payload: frame.message,
frame: frame.apsFrame
});
if (!handled) {
this.emit('incomingMessage', {
messageType: frame.type,
apsFrame: frame.apsFrame,
lqi: frame.lastHopLqi,
rssi: frame.lastHopRssi,
sender: frame.sender,
bindingIndex: frame.bindingIndex,
addressIndex: frame.addressIndex,
message: frame.message,
senderEui64: eui64
});
}
break;
}
case (frameName === 'trustCenterJoinHandler'): {
if (frame.status === named_1.EmberDeviceUpdate.DEVICE_LEFT) {
this.handleNodeLeft(frame.newNodeId, frame.newNodeEui64);
}
else {
if (frame.policyDecision !== types_1.EmberJoinDecision.DENY_JOIN) {
this.handleNodeJoined(frame.newNodeId, frame.newNodeEui64);
}
}
break;
}
case (frameName === 'incomingRouteRecordHandler'): {
this.handleRouteRecord(frame.source, frame.longId, frame.lastHopLqi, frame.lastHopRssi, frame.relay);
break;
}
case (frameName === 'incomingRouteErrorHandler'): {
this.handleRouteError(frame.status, frame.target);
break;
}
case (frameName === 'incomingNetworkStatusHandler'): {
this.handleNetworkStatus(frame.errorCode, frame.target);
break;
}
case (frameName === 'messageSentHandler'): {
// todo
const status = frame.status;
if (status != 0) {
// send failure
}
else {
// send success
// If there was a message to the group and this group is not known,
// then we will register the coordinator in this group
// Applicable for IKEA remotes
const msgType = frame.type;
if (msgType == named_1.EmberOutgoingMessageType.OUTGOING_MULTICAST) {
const apsFrame = frame.apsFrame;
if (apsFrame.destinationEndpoint == 255) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.multicast.subscribe(apsFrame.groupId, 1);
}
}
}
break;
}
case (frameName === 'macFilterMatchMessageHandler'): {
const [rawFrame, data] = struct_1.EmberIeeeRawFrame.deserialize(struct_1.EmberIeeeRawFrame, frame.message);
logger_1.logger.debug(`macFilterMatchMessageHandler frame message: ${rawFrame}`, NS);
this.emit('incomingMessage', {
messageType: null,
apsFrame: rawFrame,
lqi: frame.lastHopLqi,
rssi: frame.lastHopRssi,
sender: null,
bindingIndex: null,
addressIndex: null,
message: data,
senderEui64: new named_1.EmberEUI64(rawFrame.sourceAddress)
});
break;
}
case (frameName === 'stackStatusHandler'): {
logger_1.logger.debug(`stackStatusHandler: ${types_1.EmberStatus.valueToName(types_1.EmberStatus, frame.status)}`, NS);
break;
}
// case (frameName === 'childJoinHandler'): {
// if (!frame.joining) {
// this.handleNodeLeft(frame.childId, frame.childEui64);
// } else {
// this.handleNodeJoined(frame.childId, frame.childEui64);
// }
// break;
// }
case (frameName == 'gpepIncomingMessageHandler'): {
this.handleGPMessage(frame);
break;
}
default:
// <=== Application frame 35 (childJoinHandler) received: 00013e9c2ebd08feff9ffd9004 +1ms
// <=== Application frame 35 (childJoinHandler) parsed: 0,1,39998,144,253,159,255,254,8,189,46,4 +1ms
// Unhandled frame childJoinHandler +2s
// <=== Application frame 98 (incomingSenderEui64Handler) received: 2ebd08feff9ffd90 +2ms
// <=== Application frame 98 (incomingSenderEui64Handler) parsed: 144,253,159,255,254,8,189,46 +1ms
// Unhandled frame incomingSenderEui64Handler
// <=== Application frame 155 (zigbeeKeyEstablishmentHandler) received: 2ebd08feff9ffd9006 +2ms
// <=== Application frame 155 (zigbeeKeyEstablishmentHandler) parsed: 144,253,159,255,254,8,189,46,6 +2ms
// Unhandled frame zigbeeKeyEstablishmentHandler
logger_1.logger.debug(`Unhandled frame ${frameName}`, NS);
}
}
handleRouteRecord(nwk, ieee, lqi, rssi, relays) {
// todo
logger_1.logger.debug(`handleRouteRecord: nwk=${nwk}, ieee=${ieee.toString()}, lqi=${lqi}, rssi=${rssi}, relays=${relays}`, NS);
this.setNode(nwk, ieee);
// if (ieee && !(ieee instanceof EmberEUI64)) {
// ieee = new EmberEUI64(ieee);
// }
// this.eui64ToRelays.set(ieee.toString(), relays);
}
handleRouteError(status, nwk) {
// todo
logger_1.logger.debug(`handleRouteError: nwk=${nwk}, status=${status}`, NS);
//this.waitress.reject({address: nwk, payload: null, frame: null}, 'Route error');
// const ieee = await this.networkIdToEUI64(nwk);
// this.eui64ToRelays.set(ieee.toString(), null);
}
handleNetworkStatus(errorCode, nwk) {
// todo
// <== Frame: e19401c4000684c5
// <== 0xc4: {
// "_cls_":"incomingNetworkStatusHandler",
// "_id_":196,
// "_isRequest_":false,
// "errorCode":6,
// "target":50564
// }
// https://docs.silabs.com/d/zigbee-stack-api/7.4.0/message#ember-incoming-network-status-handler
logger_1.logger.debug(`handleNetworkStatus: nwk=${nwk}, errorCode=${errorCode}`, NS);
}
handleNodeLeft(nwk, ieee) {
if (ieee && !(ieee instanceof named_1.EmberEUI64)) {
ieee = new named_1.EmberEUI64(ieee);
}
this.eui64ToNodeId.delete(ieee.toString());
this.emit('deviceLeft', [nwk, ieee]);
}
async resetMfgId(mfgId) {
await this.ezsp.execCommand('setManufacturerCode', { code: mfgId });
// 60 sec for waiting
await (0, utils_2.Wait)(60000);
await this.ezsp.execCommand('setManufacturerCode', { code: DEFAULT_MFG_ID });
}
handleNodeJoined(nwk, ieee) {
if (ieee && !(ieee instanceof named_1.EmberEUI64)) {
ieee = new named_1.EmberEUI64(ieee);
}
for (const rec of IEEE_PREFIX_MFG_ID) {
if ((Buffer.from(ieee.value)).indexOf(Buffer.from(rec.prefix)) == 0) {
// set ManufacturerCode
logger_1.logger.debug(`handleNodeJoined: change ManufacturerCode for ieee ${ieee} to ${rec.mfgId}`, NS);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.resetMfgId(rec.mfgId);
break;
}
}
this.eui64ToNodeId.set(ieee.toString(), nwk);
this.emit('deviceJoined', [nwk, ieee]);
}
setNode(nwk, ieee) {
if (ieee && !(ieee instanceof named_1.EmberEUI64)) {
ieee = new named_1.EmberEUI64(ieee);
}
this.eui64ToNodeId.set(ieee.toString(), nwk);
}
async request(nwk, apsFrame, data, extendedTimeout = false) {
let result = false;
for (const delay of REQUEST_ATTEMPT_DELAYS) {
try {
const seq = (apsFrame.sequence + 1) & 0xFF;
let eui64;
if (typeof nwk !== 'number') {
eui64 = nwk;
const strEui64 = eui64.toString();
let nodeId = this.eui64ToNodeId.get(strEui64);
if (nodeId === undefined) {
nodeId = (await this.ezsp.execCommand('lookupNodeIdByEui64', { eui64: eui64 })).nodeId;
if (nodeId && nodeId !== 0xFFFF) {
this.eui64ToNodeId.set(strEui64, nodeId);
}
else {
throw new Error('Unknown EUI64:' + strEui64);
}
}
nwk = nodeId;
}
else {
eui64 = await this.networkIdToEUI64(nwk);
}
if (this.ezsp.ezspV < 8) {
// const route = this.eui64ToRelays.get(eui64.toString());
// if (route) {
// const = await this.ezsp.execCommand('setSourceRoute', {eui64});
// // }
}
if (extendedTimeout) {
await this.ezsp.execCommand('setExtendedTimeout', { remoteEui64: eui64, extendedTimeout: true });
}
const sendResult = await this.ezsp.sendUnicast(named_1.EmberOutgoingMessageType.OUTGOING_DIRECT, nwk, apsFrame, seq, data);
// repeat only for these statuses
if ([types_1.EmberStatus.MAX_MESSAGE_LIMIT_REACHED, types_1.EmberStatus.NO_BUFFERS, types_1.EmberStatus.NETWORK_BUSY]
.includes(sendResult.status)) {
// need to repeat after pause
logger_1.logger.error(`Request send status ${sendResult.status}. Attempt to repeat the request`, NS);
await (0, utils_2.Wait)(delay);
}
else {
result = (sendResult.status == types_1.EmberStatus.SUCCESS);
break;
}
}
catch (e) {
logger_1.logger.debug(`Request error ${e}: ${e.stack}`, NS);
break;
}
}
return result;
}
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
async mrequest(apsFrame, data, timeout = 30000) {
try {
const seq = (apsFrame.sequence + 1) & 0xFF;
await this.ezsp.sendMulticast(apsFrame, seq, data);
return true;
}
catch (e) {
return false;
}
}
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
async rawrequest(rawFrame, data, timeout = 10000) {
try {
const msgData = Buffer.concat([struct_1.EmberRawFrame.serialize(struct_1.EmberRawFrame, rawFrame), data]);
await this.ezsp.execCommand('sendRawMessage', { message: msgData });
return true;
}
catch (e) {
logger_1.logger.debug(`Request error ${e}: ${e.stack}`, NS);
return false;
}
}
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
async ieeerawrequest(rawFrame, data, timeout = 10000) {
try {
const msgData = Buffer.concat([struct_1.EmberIeeeRawFrame.serialize(struct_1.EmberIeeeRawFrame, rawFrame), data]);
await this.ezsp.execCommand('sendRawMessage', { message: msgData });
return true;
}
catch (e) {
logger_1.logger.debug(`Request error ${e}: ${e.stack}`, NS);
return false;
}
}
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
async brequest(destination, apsFrame, data) {
try {
const seq = (apsFrame.sequence + 1) & 0xFF;
await this.ezsp.sendBroadcast(destination, apsFrame, seq, data);
return true;
}
catch (e) {
return false;
}
}
nextTransactionID() {
this.transactionID = (this.transactionID + 1) & 0xFF;
return this.transactionID;
}
makeApsFrame(clusterId, disableResponse) {
const frame = new struct_1.EmberApsFrame();
frame.clusterId = clusterId;
frame.profileId = 0;
frame.sequence = this.nextTransactionID();
frame.sourceEndpoint = 0;
frame.destinationEndpoint = 0;
frame.groupId = 0;
frame.options = (types_1.EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY ||
types_1.EmberApsOption.APS_OPTION_ENABLE_ADDRESS_DISCOVERY);
if (!disableResponse) {
frame.options ||= types_1.EmberApsOption.APS_OPTION_RETRY;
}
return frame;
}
makeEmberRawFrame() {
const frame = new struct_1.EmberRawFrame();
frame.sequence = this.nextTransactionID();
return frame;
}
makeEmberIeeeRawFrame() {
const frame = new struct_1.EmberIeeeRawFrame();
frame.sequence = this.nextTransactionID();
return frame;
}
async zdoRequest(networkAddress, requestCmd, responseCmd, params) {
const requestName = types_1.EmberZDOCmd.valueName(types_1.EmberZDOCmd, requestCmd);
const responseName = types_1.EmberZDOCmd.valueName(types_1.EmberZDOCmd, responseCmd);
logger_1.logger.debug(`ZDO ${requestName} params: ${JSON.stringify(params)}`, NS);
const frame = this.makeApsFrame(requestCmd, false);
const payload = this.makeZDOframe(requestCmd, { transId: frame.sequence, ...params });
const waiter = this.waitFor(networkAddress, responseCmd, frame.sequence);
try {
const res = await this.request(networkAddress, frame, payload);
if (!res) {
throw Error('zdoRequest>request error');
}
const response = await waiter.start().promise;
logger_1.logger.debug(`${responseName} frame: ${JSON.stringify(response.payload)}`, NS);
const result = new ezsp_1.EZSPZDOResponseFrameData(responseCmd, response.payload);
logger_1.logger.debug(`${responseName} parsed: ${JSON.stringify(result)}`, NS);
return result;
}
catch (e) {
this.waitress.remove(waiter.ID);
logger_1.logger.debug(`zdoRequest error: ${e} ${e.stack}`, NS);
throw e;
}
}
async networkIdToEUI64(nwk) {
for (const [eUI64, value] of this.eui64ToNodeId) {
if (value === nwk)
return new named_1.EmberEUI64(eUI64);
}
const value = await this.ezsp.execCommand('lookupEui64ByNodeId', { nodeId: nwk });
if (value.status === types_1.EmberStatus.SUCCESS) {
const eUI64 = new named_1.EmberEUI64(value.eui64);
this.eui64ToNodeId.set(eUI64.toString(), nwk);
return eUI64;
}
else {
throw new Error('Unrecognized nodeId:' + nwk);
}
}
async preJoining(seconds) {
if (seconds) {
const ieee = new named_1.EmberEUI64('0xFFFFFFFFFFFFFFFF');
const linkKey = new types_1.EmberKeyData();
linkKey.contents = Buffer.from("ZigBeeAlliance09");
const result = await this.addTransientLinkKey(ieee, linkKey);
if (result.status !== types_1.EmberStatus.SUCCESS) {
throw new Error(`Add Transient Link Key for '${ieee}' failed`);
}
if (this.ezsp.ezspV >= 8) {
await this.ezsp.setPolicy(named_1.EzspPolicyId.TRUST_CENTER_POLICY, named_1.EzspDecisionBitmask.ALLOW_UNSECURED_REJOINS | named_1.EzspDecisionBitmask.ALLOW_JOINS);
//| EzspDecisionBitmask.JOINS_USE_INSTALL_CODE_KEY
}
}
else {
await this.ezsp.execCommand('clearTransientLinkKeys');
}
}
async permitJoining(seconds) {
return this.ezsp.execCommand('permitJoining', { duration: seconds });
}
makeZDOframe(name, params) {
return this.ezsp.makeZDOframe(name, params);
}
async addEndpoint({ endpoint = 1, profileId = 260, deviceId = 0xBEEF, appFlags = 0, inputClusters = [], outputClusters = [] }) {
const res = await this.ezsp.execCommand('addEndpoint', {
endpoint: endpoint,
profileId: profileId,
deviceId: deviceId,
appFlags: appFlags,
inputClusterCount: inputClusters.length,
outputClusterCount: outputClusters.length,
inputClusterList: inputClusters,
outputClusterList: outputClusters,
});
logger_1.logger.debug(`Ezsp adding endpoint: ${JSON.stringify(res)}`, NS);
}
waitFor(address, clusterId, sequence, timeout = 10000) {
return this.waitress.waitFor({ address, clusterId, sequence }, timeout);
}
waitressTimeoutFormatter(matcher, timeout) {
return `${JSON.stringify(matcher)} after ${timeout}ms`;
}
waitressValidator(payload, matcher) {
return (!matcher.address || payload.address === matcher.address) &&
(!payload.frame || payload.frame.clusterId === matcher.clusterId) &&
(!payload.frame || payload.payload[0] === matcher.sequence);
}
setRadioPower(value) {
return this.ezsp.execCommand('setRadioPower', { power: value });
}
setChannel(channel) {
return this.ezsp.execCommand('setLogicalAndRadioChannel', { radioChannel: channel });
}
addTransientLinkKey(partner, transientKey) {
if (this.ezsp.ezspV < 13) {
return this.ezsp.execCommand('addTransientLinkKey', { partner, transientKey });
}
else {
return this.ezsp.execCommand('importTransientKey', { partner, transientKey, flags: 0 });
}
}
async addInstallCode(ieeeAddress, key) {
// Key need to be converted to aes hash string
const hc = new struct_1.EmberAesMmoHashContext();
hc.result = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
hc.length = 0;
const hash = await this.ezsp.execCommand('aesMmoHash', { context: hc, finalize: true, data: key });
if (hash.status == types_1.EmberStatus.SUCCESS) {
const ieee = new named_1.EmberEUI64(ieeeAddress);
const linkKey = new types_1.EmberKeyData();
linkKey.contents = hash.returnContext.result;
const result = await this.addTransientLinkKey(ieee, linkKey);
if (result.status !== types_1.EmberStatus.SUCCESS) {
throw new Error(`Add install code for '${ieeeAddress}' failed`);
}
}
else {
throw new Error(`Add install code for '${ieeeAddress}' failed`);
}
}
handleGPMessage(frame) {
// Commissioning
if (frame.gpdCommandId == 0xE0) {
let data = frame.payload.subarray(5);
/* eslint-disable */
let st, deviceId, options, extOptions, key, mic, counter;
[st, data] = types_1.uint8_t.deserialize(types_1.uint8_t, data);
[deviceId, data] = types_1.uint8_t.deserialize(types_1.uint8_t, data);
[options, data] = types_1.uint8_t.deserialize(types_1.uint8_t, data);
[extOptions, data] = types_1.uint8_t.deserialize(types_1.uint8_t, data);
[key, data] = types_1.EmberKeyData.deserialize(types_1.EmberKeyData, data);
[mic, data] = types_1.uint32_t.deserialize(types_1.uint32_t, data);
[counter, data] = types_1.uint32_t.deserialize(types_1.uint32_t, data);
/* eslint-enable */
const gpdMessage = {
messageType: frame.gpdCommandId,
apsFrame: {
profileId: 0xA1E0,
sourceEndpoint: 242,
clusterId: 0x0021,
sequence: frame.sequenceNumber,
},
lqi: frame.gpdLink,
message: {
commandID: frame.gpdCommandId,
commandFrame: {
options: options,
securityKey: Buffer.from(key.contents),
deviceID: deviceId,
outgoingCounter: counter,
},
srcID: frame.srcId,
},
sender: frame.addr,
};
this.emit('incomingMessage', gpdMessage);
}
else {
const gpdMessage = {
messageType: frame.gpdCommandId,
apsFrame: {
profileId: 0xA1E0,
sourceEndpoint: 242,
clusterId: 0x0021,
sequence: frame.sequenceNumber,
},
lqi: frame.gpdLink,
message: {
commandID: frame.gpdCommandId,
frameCounter: frame.sequenceNumber,
srcID: frame.srcId,
},
sender: frame.addr,
};
this.emit('incomingMessage', gpdMessage);
}
}
async getKey(keyType) {
if (this.ezsp.ezspV < 13) {
return this.ezsp.execCommand('getKey', { keyType });
}
else {
// Mapping EmberKeyType to SecManKeyType (ezsp13)
const SecManKeyType = {
[named_1.EmberKeyType.TRUST_CENTER_LINK_KEY]: 2,
[named_1.EmberKeyType.CURRENT_NETWORK_KEY]: 1,
};
const smc = new struct_1.EmberSecurityManagerContext();
smc.type = SecManKeyType[keyType];
smc.index = 0;
smc.derivedType = named_1.EmberDerivedKeyType.NONE;
smc.eui64 = new named_1.EmberEUI64('0x0000000000000000');
smc.multiNetworkIndex = 0;
smc.flags = 0;
smc.psaKeyAlgPermission = 0;
const keyInfo = await this.ezsp.execCommand('exportKey', { context: smc });
if (keyInfo.status !== named_1.SLStatus.SL_STATUS_OK) {
logger_1.logger.error(`exportKey(${named_1.EmberKeyType.valueToName(named_1.EmberKeyType, keyType)}) `
+ `returned unexpected SL status: ${keyInfo.status}`, NS);
}
return keyInfo;
}
}
async getNetworkKeyInfo() {
if (this.ezsp.ezspV < 13) {
throw new Error(`getNetKeyInfo(): Invalid call on EZSP < 13.`);
}
else {
const keyInfo = await this.ezsp.execCommand('getNetworkKeyInfo');
if (keyInfo.status !== named_1.SLStatus.SL_STATUS_OK) {
logger_1.logger.error(`getNetworkKeyInfo() returned unexpected SL status: ${keyInfo.status}`, NS);
}
return keyInfo;
}
}
async needsToBeRestore(options) {
// if no backup and the settings have been changed, then need to start a new network
const backup = await this.backupMan.getStoredBackup();
if (!backup)
return false;
let valid = true;
//valid = valid && (await this.ezsp.networkInit());
const netParams = await this.ezsp.execCommand('getNetworkParameters');
const networkParams = netParams.parameters;
logger_1.logger.debug(`Current Node type: ${netParams.nodeType}, Network parameters: ${networkParams}`, NS);
logger_1.logger.debug(`Backuped network parameters: ${backup.networkOptions}`, NS);
const networkKey = await this.getKey(named_1.EmberKeyType.CURRENT_NETWORK_KEY);
let netKey = null;
if (this.ezsp.ezspV < 13) {
netKey = Buffer.from(networkKey.keyStruct.key.contents);
}
else {
netKey = Buffer.from(networkKey.keyData.contents);
}
// if the settings in the backup match the chip, then need to warn to delete the backup file first
valid = valid && (networkParams.panId == backup.networkOptions.panId);
valid = valid && (networkParams.radioChannel == backup.logicalChannel);
valid = valid && (Buffer.from(networkParams.extendedPanId).equals(backup.networkOptions.extendedPanId));
valid = valid && (Buffer.from(netKey).equals(backup.networkOptions.networkKey));
if (valid) {
logger_1.logger.error(`Configuration is not consistent with adapter backup!`, NS);
logger_1.logger.error(`- PAN ID: configured=${options.panID}, adapter=${networkParams.panId}, backup=${backup.networkOptions.panId}`, NS);
logger_1.logger.error(`- Extended PAN ID: configured=${Buffer.from(options.extendedPanID).toString("hex")}, ` +
`adapter=${Buffer.from(networkParams.extendedPanId).toString("hex")}, ` +
`backup=${Buffer.from(networkParams.extendedPanId).toString("hex")}`, NS);
logger_1.logger.error(`- Channel: configured=${options.channelList}, adapter=${networkParams.radioChannel}, ` +
`backup=${backup.logicalChannel}`, NS);
logger_1.logger.error(`- Network key: configured=${Buffer.from(options.networkKey).toString("hex")}, ` +
`adapter=${Buffer.from(netKey).toString("hex")}, ` +
`backup=${backup.networkOptions.networkKey.toString("hex")}`, NS);
logger_1.logger.error(`Please update configuration to prevent further issues.`, NS);
logger_1.logger.error(`If you wish to re-commission your network, please remove coordinator backup.`, NS);
logger_1.logger.error(`Re-commissioning your network will require re-pairing of all devices!`, NS);
throw new Error("startup failed - configuration-adapter mismatch - see logs above for more information");
}
valid = true;
// if the settings in the backup match the config, then the old network is in the chip and needs to be restored
valid = valid && (options.panID == backup.networkOptions.panId);
valid = valid && (options.channelList.includes(backup.logicalChannel));
valid = valid && (Buffer.from(options.extendedPanID).equals(backup.networkOptions.extendedPanId));
valid = valid && (Buffer.from(options.networkKey).equals(backup.networkOptions.networkKey));
return valid;
}
}
exports.Driver = Driver;
//# sourceMappingURL=driver.js.map