Created
February 29, 2020 14:28
-
-
Save Koenkk/10f5febae43f9f5151979279cb60f6ef to your computer and use it in GitHub Desktop.
Improve LQI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | |
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | |
return new (P || (P = Promise))(function (resolve, reject) { | |
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | |
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | |
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | |
step((generator = generator.apply(thisArg, _arguments || [])).next()); | |
}); | |
}; | |
var __importStar = (this && this.__importStar) || function (mod) { | |
if (mod && mod.__esModule) return mod; | |
var result = {}; | |
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; | |
result["default"] = mod; | |
return result; | |
}; | |
var __importDefault = (this && this.__importDefault) || function (mod) { | |
return (mod && mod.__esModule) ? mod : { "default": mod }; | |
}; | |
Object.defineProperty(exports, "__esModule", { value: true }); | |
const tstype_1 = require("./tstype"); | |
const Events = __importStar(require("../../events")); | |
const adapter_1 = __importDefault(require("../../adapter")); | |
const znp_1 = require("../znp"); | |
const startZnp_1 = __importDefault(require("./startZnp")); | |
const unpi_1 = require("../unpi"); | |
const zcl_1 = require("../../../zcl"); | |
const utils_1 = require("../../../utils"); | |
const Constants = __importStar(require("../constants")); | |
const debug_1 = __importDefault(require("debug")); | |
const backup_1 = require("./backup"); | |
const debug = debug_1.default("zigbee-herdsman:adapter:zStack:adapter"); | |
const Subsystem = unpi_1.Constants.Subsystem; | |
const Type = unpi_1.Constants.Type; | |
const DataConfirmTimeout = 3000; | |
const DataConfirmErrorCodeLookup = { | |
183: 'APS no ack', | |
205: 'No network route', | |
225: 'MAC channel access failure', | |
233: 'MAC no ack', | |
240: 'MAC transaction expired', | |
}; | |
; | |
class DataConfirmError extends Error { | |
constructor(code) { | |
const message = `Data request failed with error: '${DataConfirmErrorCodeLookup[code]}' (${code})`; | |
super(message); | |
this.code = code; | |
} | |
} | |
class ZStackAdapter extends adapter_1.default { | |
constructor(networkOptions, serialPortOptions, backupPath) { | |
super(networkOptions, serialPortOptions, backupPath); | |
this.znp = new znp_1.Znp(this.serialPortOptions.path, this.serialPortOptions.baudRate, this.serialPortOptions.rtscts); | |
this.transactionID = 0; | |
this.closing = false; | |
this.waitress = new utils_1.Waitress(this.waitressValidator, this.waitressTimeoutFormatter); | |
this.znp.on('received', this.onZnpRecieved.bind(this)); | |
this.znp.on('close', this.onZnpClose.bind(this)); | |
} | |
/** | |
* Adapter methods | |
*/ | |
start() { | |
return __awaiter(this, void 0, void 0, function* () { | |
yield this.znp.open(); | |
try { | |
yield this.znp.request(Subsystem.SYS, 'ping', { capabilities: 1 }); | |
} | |
catch (e) { | |
throw new Error(`Failed to connect to the adapter (${e})`); | |
} | |
// Old firmware did not support version, assume it's Z-Stack 1.2 for now. | |
try { | |
this.version = (yield this.znp.request(Subsystem.SYS, 'version', {})).payload; | |
} | |
catch (e) { | |
debug(`Failed to get zStack version, assuming 1.2`); | |
this.version = { "transportrev": 2, "product": 0, "majorrel": 2, "minorrel": 0, "maintrel": 0, "revision": "" }; | |
} | |
this.queue = new utils_1.Queue(this.version.product === tstype_1.ZnpVersion.zStack3x0 ? 16 : 2); | |
debug(`Detected znp version '${tstype_1.ZnpVersion[this.version.product]}' (${JSON.stringify(this.version)})`); | |
return startZnp_1.default(this.znp, this.version.product, this.networkOptions, this.backupPath); | |
}); | |
} | |
stop() { | |
return __awaiter(this, void 0, void 0, function* () { | |
this.closing = true; | |
yield this.znp.close(); | |
}); | |
} | |
static isValidPath(path) { | |
return __awaiter(this, void 0, void 0, function* () { | |
return znp_1.Znp.isValidPath(path); | |
}); | |
} | |
static autoDetectPath() { | |
return __awaiter(this, void 0, void 0, function* () { | |
return znp_1.Znp.autoDetectPath(); | |
}); | |
} | |
getCoordinator() { | |
return __awaiter(this, void 0, void 0, function* () { | |
return this.queue.execute(() => __awaiter(this, void 0, void 0, function* () { | |
const activeEpRsp = this.znp.waitFor(unpi_1.Constants.Type.AREQ, Subsystem.ZDO, 'activeEpRsp'); | |
yield this.znp.request(Subsystem.ZDO, 'activeEpReq', { dstaddr: 0, nwkaddrofinterest: 0 }); | |
const activeEp = yield activeEpRsp.promise; | |
const deviceInfo = yield this.znp.request(Subsystem.UTIL, 'getDeviceInfo', {}); | |
const endpoints = []; | |
for (const endpoint of activeEp.payload.activeeplist) { | |
const simpleDescRsp = this.znp.waitFor(unpi_1.Constants.Type.AREQ, Subsystem.ZDO, 'simpleDescRsp', { endpoint }); | |
this.znp.request(Subsystem.ZDO, 'simpleDescReq', { dstaddr: 0, nwkaddrofinterest: 0, endpoint }); | |
const simpleDesc = yield simpleDescRsp.promise; | |
endpoints.push({ | |
ID: simpleDesc.payload.endpoint, | |
profileID: simpleDesc.payload.profileid, | |
deviceID: simpleDesc.payload.deviceid, | |
inputClusters: simpleDesc.payload.inclusterlist, | |
outputClusters: simpleDesc.payload.outclusterlist, | |
}); | |
} | |
return { | |
networkAddress: 0, | |
manufacturerID: 0, | |
ieeeAddr: deviceInfo.payload.ieeeaddr, | |
endpoints, | |
}; | |
})); | |
}); | |
} | |
permitJoin(seconds, networkAddress) { | |
return __awaiter(this, void 0, void 0, function* () { | |
const addrmode = networkAddress === null ? 0x0F : 0x02; | |
const dstaddr = networkAddress || 0xFFFC; | |
yield this.queue.execute(() => __awaiter(this, void 0, void 0, function* () { | |
const payload = { addrmode, dstaddr, duration: seconds, tcsignificance: 0 }; | |
yield this.znp.request(Subsystem.ZDO, 'mgmtPermitJoinReq', payload); | |
})); | |
}); | |
} | |
getCoordinatorVersion() { | |
return __awaiter(this, void 0, void 0, function* () { | |
return { type: tstype_1.ZnpVersion[this.version.product], meta: this.version }; | |
}); | |
} | |
reset(type) { | |
return __awaiter(this, void 0, void 0, function* () { | |
if (type === 'soft') { | |
yield this.znp.request(Subsystem.SYS, 'resetReq', { type: Constants.SYS.resetType.SOFT }); | |
} | |
else { | |
yield this.znp.request(Subsystem.SYS, 'resetReq', { type: Constants.SYS.resetType.HARD }); | |
} | |
}); | |
} | |
supportsLED() { | |
return __awaiter(this, void 0, void 0, function* () { | |
return this.version.product !== tstype_1.ZnpVersion.zStack3x0; | |
}); | |
} | |
setLED(enabled) { | |
return __awaiter(this, void 0, void 0, function* () { | |
yield this.znp.request(Subsystem.UTIL, 'ledControl', { ledid: 3, mode: enabled ? 1 : 0 }); | |
}); | |
} | |
supportsSourceRouting() { | |
if ((this.version.product === tstype_1.ZnpVersion.zStack12 && this.version.revision === '20190619') || | |
(this.version.product === tstype_1.ZnpVersion.zStack30x && this.version.revision === '20200211') || | |
(this.version.product === tstype_1.ZnpVersion.zStack3x0)) { | |
return true; | |
} | |
else { | |
return false; | |
} | |
} | |
discoverRoute(networkAddress) { | |
return __awaiter(this, void 0, void 0, function* () { | |
// 8 = No multicast; Extended destination: True; Many-to-One discovery: With Source Routing | |
// 16 = No multicast; Extended destination: True; Many-to-One discovery: Without Source Routing | |
const options = this.supportsSourceRouting() ? 8 : 16; | |
const payload = { dstAddr: networkAddress, options, radius: Constants.AF.DEFAULT_RADIUS }; | |
yield this.znp.request(Subsystem.ZDO, 'extRouteDisc', payload); | |
// Wait 3 seconds to give some time for the responses. | |
yield utils_1.Wait(3000); | |
}); | |
} | |
nodeDescriptor(networkAddress) { | |
return __awaiter(this, void 0, void 0, function* () { | |
return this.queue.execute(() => __awaiter(this, void 0, void 0, function* () { | |
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'nodeDescRsp', { nwkaddr: networkAddress }); | |
const payload = { dstaddr: networkAddress, nwkaddrofinterest: networkAddress }; | |
this.znp.request(Subsystem.ZDO, 'nodeDescReq', payload); | |
const descriptor = yield response.promise; | |
let type = 'Unknown'; | |
const logicalType = descriptor.payload.logicaltype_cmplxdescavai_userdescavai & 0x07; | |
for (const [key, value] of Object.entries(Constants.ZDO.deviceLogicalType)) { | |
if (value === logicalType) { | |
if (key === 'COORDINATOR') | |
type = 'Coordinator'; | |
else if (key === 'ROUTER') | |
type = 'Router'; | |
else if (key === 'ENDDEVICE') | |
type = 'EndDevice'; | |
break; | |
} | |
} | |
return { manufacturerCode: descriptor.payload.manufacturercode, type }; | |
}), networkAddress); | |
}); | |
} | |
activeEndpoints(networkAddress) { | |
return __awaiter(this, void 0, void 0, function* () { | |
return this.queue.execute(() => __awaiter(this, void 0, void 0, function* () { | |
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'activeEpRsp', { nwkaddr: networkAddress }); | |
const payload = { dstaddr: networkAddress, nwkaddrofinterest: networkAddress }; | |
this.znp.request(Subsystem.ZDO, 'activeEpReq', payload); | |
const activeEp = yield response.promise; | |
return { endpoints: activeEp.payload.activeeplist }; | |
}), networkAddress); | |
}); | |
} | |
simpleDescriptor(networkAddress, endpointID) { | |
return __awaiter(this, void 0, void 0, function* () { | |
return this.queue.execute(() => __awaiter(this, void 0, void 0, function* () { | |
const responsePayload = { nwkaddr: networkAddress, endpoint: endpointID }; | |
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'simpleDescRsp', responsePayload); | |
const payload = { dstaddr: networkAddress, nwkaddrofinterest: networkAddress, endpoint: endpointID }; | |
this.znp.request(Subsystem.ZDO, 'simpleDescReq', payload); | |
const descriptor = yield response.promise; | |
return { | |
profileID: descriptor.payload.profileid, | |
endpointID: descriptor.payload.endpoint, | |
deviceID: descriptor.payload.deviceid, | |
inputClusters: descriptor.payload.inclusterlist, | |
outputClusters: descriptor.payload.outclusterlist, | |
}; | |
}), networkAddress); | |
}); | |
} | |
sendZclFrameToEndpoint(networkAddress, endpoint, zclFrame, timeout) { | |
return __awaiter(this, void 0, void 0, function* () { | |
return this.queue.execute(() => __awaiter(this, void 0, void 0, function* () { | |
return this.sendZclFrameToEndpointInternal(networkAddress, endpoint, zclFrame, timeout, true); | |
}), networkAddress); | |
}); | |
} | |
sendZclFrameToEndpointInternal(networkAddress, endpoint, zclFrame, timeout, firstAttempt) { | |
return __awaiter(this, void 0, void 0, function* () { | |
let response = null; | |
const command = zclFrame.getCommand(); | |
if (command.hasOwnProperty('response')) { | |
response = this.waitFor(networkAddress, endpoint, zclFrame.Header.frameControl.frameType, zcl_1.Direction.SERVER_TO_CLIENT, zclFrame.Header.transactionSequenceNumber, zclFrame.Cluster.ID, command.response, timeout); | |
} | |
else if (!zclFrame.Header.frameControl.disableDefaultResponse) { | |
response = this.waitFor(networkAddress, endpoint, zcl_1.FrameType.GLOBAL, zcl_1.Direction.SERVER_TO_CLIENT, zclFrame.Header.transactionSequenceNumber, zclFrame.Cluster.ID, zcl_1.Foundation.defaultRsp.ID, timeout); | |
} | |
try { | |
yield this.dataRequest(networkAddress, endpoint, 1, zclFrame.Cluster.ID, Constants.AF.DEFAULT_RADIUS, zclFrame.toBuffer(), 5); | |
} | |
catch (error) { | |
if (response) { | |
response.cancel(); | |
} | |
throw error; | |
} | |
if (response !== null) { | |
try { | |
const result = yield response.promise; | |
return result; | |
} | |
catch (error) { | |
if (firstAttempt) { | |
// Timeout could happen because of invalid route, rediscover and retry. | |
yield this.discoverRoute(networkAddress); | |
return this.sendZclFrameToEndpointInternal(networkAddress, endpoint, zclFrame, timeout, false); | |
} | |
else { | |
throw error; | |
} | |
} | |
} | |
else { | |
return null; | |
} | |
}); | |
} | |
sendZclFrameToGroup(groupID, zclFrame) { | |
return __awaiter(this, void 0, void 0, function* () { | |
return this.queue.execute(() => __awaiter(this, void 0, void 0, function* () { | |
yield this.dataRequestExtended(Constants.COMMON.addressMode.ADDR_GROUP, groupID, 0xFF, 0, 1, zclFrame.Cluster.ID, Constants.AF.DEFAULT_RADIUS, zclFrame.toBuffer(), DataConfirmTimeout, true); | |
/** | |
* As a group command is not confirmed and thus immidiately returns | |
* (contrary to network address requests) we will give the | |
* command some time to 'settle' in the network. | |
*/ | |
yield utils_1.Wait(200); | |
})); | |
}); | |
} | |
lqi(networkAddress) { | |
return __awaiter(this, void 0, void 0, function* () { | |
return this.queue.execute(() => __awaiter(this, void 0, void 0, function* () { | |
// First make sure we have a valid route | |
yield this.discoverRoute(networkAddress); | |
const neighbors = []; | |
// eslint-disable-next-line | |
const request = (startIndex) => __awaiter(this, void 0, void 0, function* () { | |
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'mgmtLqiRsp', { srcaddr: networkAddress }); | |
this.znp.request(Subsystem.ZDO, 'mgmtLqiReq', { dstaddr: networkAddress, startindex: startIndex }); | |
const result = yield response.promise; | |
if (result.payload.status !== 0) { | |
throw new Error(`LQI for '${networkAddress}' failed`); | |
} | |
return result; | |
}); | |
// eslint-disable-next-line | |
const add = (list) => { | |
for (const entry of list) { | |
neighbors.push({ | |
linkquality: entry.lqi, | |
networkAddress: entry.nwkAddr, | |
ieeeAddr: entry.extAddr, | |
relationship: entry.relationship, | |
depth: entry.depth, | |
}); | |
} | |
}; | |
let response = yield request(0); | |
add(response.payload.neighborlqilist); | |
const size = response.payload.neighbortableentries; | |
let nextStartIndex = response.payload.neighborlqilist.length; | |
while (neighbors.length < size) { | |
response = yield request(nextStartIndex); | |
add(response.payload.neighborlqilist); | |
nextStartIndex += response.payload.neighborlqilist.length; | |
} | |
return { neighbors }; | |
}), networkAddress); | |
}); | |
} | |
routingTable(networkAddress) { | |
return __awaiter(this, void 0, void 0, function* () { | |
return this.queue.execute(() => __awaiter(this, void 0, void 0, function* () { | |
const table = []; | |
// eslint-disable-next-line | |
const request = (startIndex) => __awaiter(this, void 0, void 0, function* () { | |
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'mgmtRtgRsp', { srcaddr: networkAddress }); | |
this.znp.request(Subsystem.ZDO, 'mgmtRtgReq', { dstaddr: networkAddress, startindex: startIndex }); | |
const result = yield response.promise; | |
if (result.payload.status !== 0) { | |
throw new Error(`Routing table for '${networkAddress}' failed`); | |
} | |
return result; | |
}); | |
// eslint-disable-next-line | |
const add = (list) => { | |
for (const entry of list) { | |
table.push({ | |
destinationAddress: entry.destNwkAddr, | |
status: entry.routeStatus, | |
nextHop: entry.nextHopNwkAddr, | |
}); | |
} | |
}; | |
let response = yield request(0); | |
add(response.payload.routingtablelist); | |
const size = response.payload.routingtableentries; | |
let nextStartIndex = response.payload.routingtablelist.length; | |
while (table.length < size) { | |
response = yield request(nextStartIndex); | |
add(response.payload.routingtablelist); | |
nextStartIndex += response.payload.routingtablelist.length; | |
} | |
return { table }; | |
}), networkAddress); | |
}); | |
} | |
bind(destinationNetworkAddress, sourceIeeeAddress, sourceEndpoint, clusterID, destinationAddressOrGroup, type, destinationEndpoint) { | |
return __awaiter(this, void 0, void 0, function* () { | |
return this.queue.execute(() => __awaiter(this, void 0, void 0, function* () { | |
const responsePayload = { srcaddr: destinationNetworkAddress }; | |
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'bindRsp', responsePayload); | |
const payload = { | |
dstaddr: destinationNetworkAddress, | |
srcaddr: sourceIeeeAddress, | |
srcendpoint: sourceEndpoint, | |
clusterid: clusterID, | |
dstaddrmode: type === 'group' ? | |
Constants.COMMON.addressMode.ADDR_GROUP : Constants.COMMON.addressMode.ADDR_64BIT, | |
dstaddress: this.toAddressString(destinationAddressOrGroup), | |
dstendpoint: type === 'group' ? 0xFF : destinationEndpoint, | |
}; | |
this.znp.request(Subsystem.ZDO, 'bindReq', payload); | |
yield response.promise; | |
}), destinationNetworkAddress); | |
}); | |
} | |
unbind(destinationNetworkAddress, sourceIeeeAddress, sourceEndpoint, clusterID, destinationAddressOrGroup, type, destinationEndpoint) { | |
return __awaiter(this, void 0, void 0, function* () { | |
return this.queue.execute(() => __awaiter(this, void 0, void 0, function* () { | |
const response = this.znp.waitFor(Type.AREQ, Subsystem.ZDO, 'unbindRsp', { srcaddr: destinationNetworkAddress }); | |
const payload = { | |
dstaddr: destinationNetworkAddress, | |
srcaddr: sourceIeeeAddress, | |
srcendpoint: sourceEndpoint, | |
clusterid: clusterID, | |
dstaddrmode: type === 'group' ? | |
Constants.COMMON.addressMode.ADDR_GROUP : Constants.COMMON.addressMode.ADDR_64BIT, | |
dstaddress: this.toAddressString(destinationAddressOrGroup), | |
dstendpoint: type === 'group' ? 0xFF : destinationEndpoint, | |
}; | |
this.znp.request(Subsystem.ZDO, 'unbindReq', payload); | |
yield response.promise; | |
}), destinationNetworkAddress); | |
}); | |
} | |
removeDevice(networkAddress, ieeeAddr) { | |
return this.queue.execute(() => __awaiter(this, void 0, void 0, function* () { | |
const response = this.znp.waitFor(unpi_1.Constants.Type.AREQ, Subsystem.ZDO, 'mgmtLeaveRsp', { srcaddr: networkAddress }); | |
const payload = { | |
dstaddr: networkAddress, | |
deviceaddress: ieeeAddr, | |
removechildrenRejoin: 0, | |
}; | |
this.znp.request(Subsystem.ZDO, 'mgmtLeaveReq', payload); | |
yield response.promise; | |
}), networkAddress); | |
} | |
/** | |
* Event handlers | |
*/ | |
onZnpClose() { | |
if (!this.closing) { | |
this.emit(Events.Events.disconnected); | |
} | |
} | |
onZnpRecieved(object) { | |
if (object.type !== unpi_1.Constants.Type.AREQ) { | |
return; | |
} | |
if (object.subsystem === Subsystem.ZDO) { | |
if (object.command === 'tcDeviceInd') { | |
const payload = { | |
networkAddress: object.payload.nwkaddr, | |
ieeeAddr: object.payload.extaddr, | |
}; | |
this.emit(Events.Events.deviceJoined, payload); | |
} | |
else if (object.command === 'endDeviceAnnceInd') { | |
const payload = { | |
networkAddress: object.payload.nwkaddr, | |
ieeeAddr: object.payload.ieeeaddr, | |
}; | |
this.emit(Events.Events.deviceAnnounce, payload); | |
} | |
else { | |
/* istanbul ignore else */ | |
if (object.command === 'leaveInd') { | |
const payload = { | |
networkAddress: object.payload.srcaddr, | |
ieeeAddr: object.payload.extaddr, | |
}; | |
this.emit(Events.Events.deviceLeave, payload); | |
} | |
} | |
} | |
else { | |
/* istanbul ignore else */ | |
if (object.subsystem === Subsystem.AF) { | |
/* istanbul ignore else */ | |
if (object.command === 'incomingMsg' || object.command === 'incomingMsgExt') { | |
try { | |
const payload = { | |
frame: zcl_1.ZclFrame.fromBuffer(object.payload.clusterid, object.payload.data), | |
address: object.payload.srcaddr, | |
endpoint: object.payload.srcendpoint, | |
linkquality: object.payload.linkquality, | |
groupID: object.payload.groupid, | |
}; | |
this.waitress.resolve(payload); | |
this.emit(Events.Events.zclData, payload); | |
} | |
catch (error) { | |
const payload = { | |
clusterID: object.payload.clusterid, | |
data: object.payload.data, | |
address: object.payload.srcaddr, | |
endpoint: object.payload.srcendpoint, | |
linkquality: object.payload.linkquality, | |
groupID: object.payload.groupid, | |
}; | |
this.emit(Events.Events.rawData, payload); | |
} | |
} | |
} | |
} | |
} | |
getNetworkParameters() { | |
return __awaiter(this, void 0, void 0, function* () { | |
const result = yield this.znp.request(Subsystem.ZDO, 'extNwkInfo', {}); | |
return { | |
panID: result.payload.panid, extendedPanID: result.payload.extendedpanid, | |
channel: result.payload.channel | |
}; | |
}); | |
} | |
supportsBackup() { | |
return __awaiter(this, void 0, void 0, function* () { | |
return this.version.product !== tstype_1.ZnpVersion.zStack12; | |
}); | |
} | |
backup() { | |
return __awaiter(this, void 0, void 0, function* () { | |
return backup_1.Backup(this.znp); | |
}); | |
} | |
setChannelInterPAN(channel) { | |
return __awaiter(this, void 0, void 0, function* () { | |
return this.queue.execute(() => __awaiter(this, void 0, void 0, function* () { | |
yield this.znp.request(Subsystem.AF, 'interPanCtl', { cmd: 1, data: [channel] }); | |
// Make sure that endpoint 12 is registered to proxy the InterPAN messages. | |
yield this.znp.request(Subsystem.AF, 'interPanCtl', { cmd: 2, data: [12] }); | |
})); | |
}); | |
} | |
sendZclFrameInterPANToIeeeAddr(zclFrame, ieeeAddr) { | |
return __awaiter(this, void 0, void 0, function* () { | |
return this.queue.execute(() => __awaiter(this, void 0, void 0, function* () { | |
yield this.dataRequestExtended(Constants.COMMON.addressMode.ADDR_64BIT, ieeeAddr, 0xFE, 0xFFFF, 12, zclFrame.Cluster.ID, 30, zclFrame.toBuffer(), 10000, false); | |
})); | |
}); | |
} | |
sendZclFrameInterPANBroadcast(zclFrame, timeout) { | |
return __awaiter(this, void 0, void 0, function* () { | |
return this.queue.execute(() => __awaiter(this, void 0, void 0, function* () { | |
const command = zclFrame.getCommand(); | |
if (!command.hasOwnProperty('response')) { | |
throw new Error(`Command '${command.name}' has no response, cannot wait for response`); | |
} | |
const response = this.waitFor(null, 0xFE, zclFrame.Header.frameControl.frameType, zcl_1.Direction.SERVER_TO_CLIENT, null, zclFrame.Cluster.ID, command.response, timeout); | |
try { | |
yield this.dataRequestExtended(Constants.COMMON.addressMode.ADDR_16BIT, 0xFFFF, 0xFE, 0xFFFF, 12, zclFrame.Cluster.ID, 30, zclFrame.toBuffer(), 10000, false); | |
} | |
catch (error) { | |
response.cancel(); | |
throw error; | |
} | |
return response.promise; | |
})); | |
}); | |
} | |
restoreChannelInterPAN() { | |
return __awaiter(this, void 0, void 0, function* () { | |
return this.queue.execute(() => __awaiter(this, void 0, void 0, function* () { | |
yield this.znp.request(Subsystem.AF, 'interPanCtl', { cmd: 0, data: [] }); | |
// Give adapter some time to restore, otherwise stuff crashes | |
yield utils_1.Wait(1000); | |
})); | |
}); | |
} | |
setTransmitPower(value) { | |
return __awaiter(this, void 0, void 0, function* () { | |
return this.queue.execute(() => __awaiter(this, void 0, void 0, function* () { | |
yield this.znp.request(Subsystem.SYS, 'stackTune', { operation: 0, value }); | |
})); | |
}); | |
} | |
waitFor(networkAddress, endpoint, frameType, direction, transactionSequenceNumber, clusterID, commandIdentifier, timeout) { | |
const payload = { | |
address: networkAddress, endpoint, clusterID, commandIdentifier, frameType, direction, | |
transactionSequenceNumber, | |
}; | |
const waiter = this.waitress.waitFor(payload, timeout); | |
const cancel = () => this.waitress.remove(waiter.ID); | |
return { promise: waiter.promise, cancel }; | |
} | |
/** | |
* Private methods | |
*/ | |
dataRequest(destinationAddress, destinationEndpoint, sourceEndpoint, clusterID, radius, data, attemptsLeft) { | |
return __awaiter(this, void 0, void 0, function* () { | |
const transactionID = this.nextTransactionID(); | |
const response = this.znp.waitFor(Type.AREQ, Subsystem.AF, 'dataConfirm', { transid: transactionID }, DataConfirmTimeout); | |
try { | |
yield this.znp.request(Subsystem.AF, 'dataRequest', { | |
dstaddr: destinationAddress, | |
destendpoint: destinationEndpoint, | |
srcendpoint: sourceEndpoint, | |
clusterid: clusterID, | |
transid: transactionID, | |
options: 0, | |
radius: radius, | |
len: data.length, | |
data: data, | |
}); | |
} | |
catch (error) { | |
this.znp.removeWaitFor(response.ID); | |
throw error; | |
} | |
const dataConfirm = yield response.promise; | |
if (dataConfirm.payload.status !== 0) { | |
if ([225, 240].includes(dataConfirm.payload.status) && attemptsLeft > 0) { | |
/** | |
* 225: When many commands at once are executed we can end up in a MAC channel access failure | |
* error. This is because there is too much traffic on the network. | |
* Retry this command once after a cooling down period. | |
* 240: Mac layer is sleeping, try a few more times | |
*/ | |
yield utils_1.Wait(2000); | |
return this.dataRequest(destinationAddress, destinationEndpoint, sourceEndpoint, clusterID, radius, data, attemptsLeft - 1); | |
} | |
else if ([205, 233].includes(dataConfirm.payload.status) && attemptsLeft > 0) { | |
// 205: no network route => rediscover route | |
// 233: route may be corrupted | |
yield this.discoverRoute(destinationAddress); | |
return this.dataRequest(destinationAddress, destinationEndpoint, sourceEndpoint, clusterID, radius, data, 0); | |
} | |
else { | |
throw new DataConfirmError(dataConfirm.payload.status); | |
} | |
} | |
return dataConfirm; | |
}); | |
} | |
; | |
dataRequestExtended(addressMode, destinationAddressOrGroupID, destinationEndpoint, panID, sourceEndpoint, clusterID, radius, data, timeout, confirmation, attemptsLeft = 5) { | |
return __awaiter(this, void 0, void 0, function* () { | |
const transactionID = this.nextTransactionID(); | |
const response = confirmation ? | |
this.znp.waitFor(Type.AREQ, Subsystem.AF, 'dataConfirm', { transid: transactionID }, timeout) : null; | |
try { | |
yield this.znp.request(Subsystem.AF, 'dataRequestExt', { | |
dstaddrmode: addressMode, | |
dstaddr: this.toAddressString(destinationAddressOrGroupID), | |
destendpoint: destinationEndpoint, | |
dstpanid: panID, | |
srcendpoint: sourceEndpoint, | |
clusterid: clusterID, | |
transid: transactionID, | |
options: 0, | |
radius, | |
len: data.length, | |
data: data, | |
}); | |
} | |
catch (error) { | |
if (confirmation) { | |
this.znp.removeWaitFor(response.ID); | |
} | |
throw error; | |
} | |
if (confirmation) { | |
const dataConfirm = yield response.promise; | |
if (dataConfirm.payload.status !== 0) { | |
if (dataConfirm.payload.status === 225 && attemptsLeft > 0) { | |
/** | |
* 225: When many commands at once are executed we can end up in a MAC channel access failure | |
* error. This is because there is too much traffic on the network. | |
* Retry this command once after a cooling down period. | |
*/ | |
yield utils_1.Wait(2000); | |
return this.dataRequestExtended(addressMode, destinationAddressOrGroupID, destinationEndpoint, panID, sourceEndpoint, clusterID, radius, data, timeout, confirmation, attemptsLeft - 1); | |
} | |
else { | |
throw new DataConfirmError(dataConfirm.payload.status); | |
} | |
} | |
return dataConfirm; | |
} | |
}); | |
} | |
; | |
nextTransactionID() { | |
this.transactionID++; | |
if (this.transactionID > 255) { | |
this.transactionID = 1; | |
} | |
return this.transactionID; | |
} | |
toAddressString(address) { | |
if (typeof address === 'number') { | |
let addressString = address.toString(16); | |
for (let i = addressString.length; i < 16; i++) { | |
addressString = '0' + addressString; | |
} | |
return `0x${addressString}`; | |
} | |
else { | |
return address.toString(); | |
} | |
} | |
waitressTimeoutFormatter(matcher, timeout) { | |
return `Timeout - ${matcher.address} - ${matcher.endpoint}` + | |
` - ${matcher.transactionSequenceNumber} - ${matcher.clusterID}` + | |
` - ${matcher.commandIdentifier} after ${timeout}ms`; | |
} | |
waitressValidator(payload, matcher) { | |
const transactionSequenceNumber = payload.frame.Header.transactionSequenceNumber; | |
return (!matcher.address || payload.address === matcher.address) && | |
payload.endpoint === matcher.endpoint && | |
(!matcher.transactionSequenceNumber || transactionSequenceNumber === matcher.transactionSequenceNumber) && | |
payload.frame.Cluster.ID === matcher.clusterID && | |
matcher.frameType === payload.frame.Header.frameControl.frameType && | |
matcher.commandIdentifier === payload.frame.Header.commandIdentifier && | |
matcher.direction === payload.frame.Header.frameControl.direction; | |
} | |
} | |
exports.default = ZStackAdapter; | |
//# sourceMappingURL=zStackAdapter.js.map |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment