cc-znp
Version:
The interface for a host to communicate with TI CC253X Zigbee Network Processor(ZNP) over a serial port.
472 lines (471 loc) • 13.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const zmeta = require("./zmeta");
const Unpi = require("unpi");
const Concentrate = Unpi.Concentrate;
const DChunks = Unpi.DChunks;
const ru = DChunks().Rule();
/**
* 1. Provides command framer (SREQ)
* 2. Provides parser (SRSP, AREQ)
*
* args is optional, and can be an array or a value-object if given
*/
class ZpiObject {
constructor(subsys, cmd, args) {
const subsystem = zmeta.Subsys.get(subsys);
let command;
let reqParams;
if (!subsystem) {
throw new Error('Unrecognized subsystem');
}
this.subsys = subsystem.key;
command = zmeta[this.subsys].get(cmd);
if (!command) {
throw new Error('Unrecognized command');
}
this.cmd = command.key;
this.cmdId = command.value;
this.type = zmeta.getType(this.subsys, this.cmd);
if (!this.type) {
throw new Error('Unrecognized type');
}
// if args is given, this is for REQ transmission
// otherwise, maybe just for parsing RSP packet
if (args) {
// [ { name, type }, ... ]
reqParams = zmeta.getReqParams(this.subsys, this.cmd);
}
if (reqParams) {
if (Array.isArray(args)) {
// arg: { name, type } -> { name, type, value }
reqParams.forEach((arg, idx) => {
arg.value = args[idx];
});
}
else if (typeof args === 'object') {
reqParams.forEach((arg, idx) => {
if (!args.hasOwnProperty(arg.name)) {
throw new Error('The argument object has incorrect properties');
}
else {
arg.value = args[arg.name];
}
});
}
// [ { name, type, value }, ... ]
this.args = reqParams;
}
}
parse(type, bufLen, zBuf, callback) {
let err;
let rspParams;
let parser;
let parseTimeout;
// SRSP
if (type === 'SRSP' || type === 3) {
rspParams = zmeta.getRspParams(this.subsys, this.cmd);
// AREQ
}
else if (type === 'AREQ' || type === 2) {
rspParams = zmeta.getReqParams(this.subsys, this.cmd);
}
// [ { name, type }, ... ]
if (!rspParams)
return callback(new Error('Response parameter definitions not found.'));
let chunkRules;
chunkRules = rspParams.map(arg => {
let rule = ru[arg.type];
if (!rule)
throw new Error(`Parsing rule for ${arg.type} is not found.`);
return rule(arg.name, bufLen);
});
if (!err) {
if (chunkRules.length === 0) {
callback(null, {});
return;
}
parser = DChunks()
.join(chunkRules)
.compile();
parseTimeout = setTimeout(() => {
if (parser.listenerCount('parsed')) {
parser.emit('parsed', '__timeout__');
}
parseTimeout = null;
}, 3000);
parser.once('parsed', result => {
if (parseTimeout) {
clearTimeout(parseTimeout);
parseTimeout = null;
}
parser = null;
if (result === '__timeout__') {
callback(new Error('parse timeout'));
}
else {
callback(null, result);
}
});
}
// error occurs, no parser created
if (!parser) {
callback(err);
}
else {
parser.end(zBuf);
}
}
frame() {
// no args, cannot build frame
if (!Array.isArray(this.args)) {
return null;
}
let dataBuf = Concentrate();
// arg: { name, type, value }
this.args.forEach((arg, idx) => {
const type = arg.type;
const val = arg.value;
let msb;
let lsb;
let tempBuf;
let idxbuf;
switch (type) {
case 'uint8':
case 'uint16':
case 'uint32':
dataBuf = dataBuf[type](val);
break;
case 'buffer':
case 'dynbuffer':
dataBuf = dataBuf.buffer(Buffer.from(val));
break;
case 'longaddr':
// string '0x00124b00019c2ee9'
msb = parseInt(val.slice(2, 10), 16);
lsb = parseInt(val.slice(10), 16);
dataBuf = dataBuf.uint32le(lsb).uint32le(msb);
break;
case 'listbuffer':
// [ 0x0001, 0x0002, 0x0003, ... ]
tempBuf = Buffer.alloc(val.length * 2);
for (idxbuf = 0; idxbuf < val.length; idxbuf += 1) {
tempBuf.writeUInt16LE(val[idxbuf], idxbuf * 2);
}
dataBuf = dataBuf.buffer(tempBuf);
break;
default:
throw new Error('Unknown Data Type');
}
});
return dataBuf.result();
}
}
/*
Add Parsing Rules to DChunks
*/
const rules = [
'buffer8',
'buffer16',
'buffer18',
'buffer32',
'buffer42',
'buffer100',
'_preLenUint8',
'_preLenUint16',
];
rules.forEach(function (ruName) {
ru.clause(ruName, function (name) {
let needTap = true;
let bufLen;
if (ruName === '_preLenUint8') {
this.uint8(name);
}
else if (ruName === '_preLenUint16') {
this.uint16(name);
}
else {
needTap = false;
bufLen = parseInt(ruName.slice(6));
this.buffer(name, bufLen);
}
if (needTap) {
this.tap(function () {
this.buffer('preLenData', this.vars[name]);
});
}
});
});
ru.clause('longaddr', function (name) {
this.buffer(name, 8).tap(function () {
const addrBuf = this.vars[name];
this.vars[name] = addrBuf2Str(addrBuf);
});
});
ru.clause('uint8ZdoInd', function (name, bufLen) {
if (bufLen === 3) {
this.uint16('nwkaddr').uint8(name);
}
else if (bufLen === 1) {
this.uint8(name);
}
});
ru.clause('devlistbuffer', function (name, bufLen) {
this.buffer(name, bufLen - 13).tap(function () {
this.vars[name] = bufToArray(this.vars[name], 'uint16');
});
});
ru.clause('nwklistbuffer', function (name, bufLen) {
this.buffer(name, bufLen - 6).tap(function () {
const buf = this.vars[name];
const list = [];
let listcount;
let getList;
let start = 0;
let end;
let len;
let i;
if (name === 'networklist') {
listcount = this.vars.networklistcount;
end = len = 12;
getList = networkList;
}
else if (name === 'neighborlqilist') {
listcount = this.vars.neighborlqilistcount;
end = len = 22;
getList = neighborLqiList;
}
else if (name === 'routingtablelist') {
listcount = this.vars.routingtablelistcount;
end = len = 5;
getList = routingTableList;
}
else {
listcount = this.vars.bindingtablelistcount;
this.vars[name] = bindTableList(buf, listcount);
return;
}
for (i = 0; i < listcount; i += 1) {
list.push(getList(buf.slice(start, end)));
start = start + len;
end = end + len;
}
this.vars[name] = list;
});
});
ru.clause('zdomsgcb', function (name, bufLen) {
this.buffer(name, bufLen - 9);
});
ru.clause('preLenList', function (name) {
this.uint8(name).tap(function () {
this.buffer('preLenData', 2 * this.vars[name]);
});
});
ru.clause('preLenBeaconlist', function (name) {
this.uint8(name).tap(function () {
this.buffer('preLenData', 21 * this.vars[name]).tap(function () {
const buf = this.vars.preLenData;
const list = [];
let len = 21;
let start = 0;
let end = 21;
let i;
for (i = 0; i < this.vars[name]; i += 1) {
list.push(beaconList(buf.slice(start, end)));
start = start + len;
end = end + len;
}
this.vars.preLenData = list;
});
});
});
ru.clause('dynbuffer', function (name) {
this.tap(function () {
this.vars[name] = this.vars.preLenData;
delete this.vars.preLenData;
});
});
function networkList(buf) {
let i = 0;
const neightborPanId = buf.readUInt16LE(i);
i += 2 + 6;
const logicalChannel = buf.readUInt8(i);
i += 1;
const stackProfile = buf.readUInt8(i) & 0x0f;
const zigbeeVersion = (buf.readUInt8(i) & 0xf0) >> 4;
i += 1;
const beaconOrder = buf.readUInt8(i) & 0x0f;
const superFrameOrder = (buf.readUInt8(i) & 0xf0) >> 4;
i += 1;
const permitJoin = buf.readUInt8(i);
i += 1;
return {
neightborPanId,
logicalChannel,
stackProfile,
zigbeeVersion,
beaconOrder,
superFrameOrder,
permitJoin,
};
}
function neighborLqiList(buf) {
let i = 0;
const extPandId = addrBuf2Str(buf.slice(0, 8));
i += 8;
const extAddr = addrBuf2Str(buf.slice(8, 16));
i += 8;
const nwkAddr = buf.readUInt16LE(i);
i += 2;
const deviceType = buf.readUInt8(i) & 0x03;
const rxOnWhenIdle = (buf.readUInt8(i) & 0x0c) >> 2;
const relationship = (buf.readUInt8(i) & 0x70) >> 4;
i += 1;
const permitJoin = buf.readUInt8(i) & 0x03;
i += 1;
const depth = buf.readUInt8(i);
i += 1;
const lqi = buf.readUInt8(i);
i += 1;
return {
extPandId,
extAddr,
nwkAddr,
deviceType,
rxOnWhenIdle,
relationship,
permitJoin,
depth,
lqi,
};
}
function routingTableList(buf) {
let i = 0;
const destNwkAddr = buf.readUInt16LE(i);
i += 2;
const routeStatus = buf.readUInt8(i) & 0x07;
i += 1;
const nextHopNwkAddr = buf.readUInt16LE(i);
i += 2;
return { destNwkAddr, routeStatus, nextHopNwkAddr };
}
function bindTableList(buf, listcount) {
function getList(buf) {
let thisItemLen = 21;
let i = 0;
const srcAddr = addrBuf2Str(buf.slice(0, 8));
i += 8;
const srcEp = buf.readUInt8(i);
i += 1;
const clusterId = buf.readUInt16LE(i);
i += 2;
const dstAddrMode = buf.readUInt8(i);
i += 1;
const dstAddr = addrBuf2Str(buf.slice(12, 20));
i += 8;
// 'Addr64Bit'
if (dstAddrMode === 3) {
const dstEp = buf.readUInt8(i);
i += 1;
return {
item: { srcAddr, srcEp, clusterId, dstAddrMode, dstAddr, dstEp },
thisItemLen,
};
}
thisItemLen = thisItemLen - 1;
return {
item: { srcAddr, srcEp, clusterId, dstAddrMode, dstAddr },
thisItemLen,
};
}
const list = [];
const len = 21;
let start = 0;
let end = len;
for (let i = 0; i < listcount; i += 1) {
const itemObj = getList(buf.slice(start, end));
list.push(itemObj.item);
start = start + itemObj.thisItemLen;
// for the last item, we don't know the length of bytes
// so, assign 'end' by the buf length to avoid memory leak.
// for each item, take 21 bytes from buf to parse
if (i === listcount - 2) {
end = buf.length;
}
else {
end = start + len;
}
}
return list;
}
function beaconList(buf) {
let i = 0;
const srcAddr = buf.readUInt16LE(i);
i += 2;
const padId = buf.readUInt16LE(i);
i += 2;
const logicalChannel = buf.readUInt8(i);
i += 1;
const permitJoin = buf.readUInt8(i);
i += 1;
const routerCapacity = buf.readUInt8(i);
i += 1;
const deviceCapacity = buf.readUInt8(i);
i += 1;
const protocolVersion = buf.readUInt8(i);
i += 1;
const stackProfile = buf.readUInt8(i);
i += 1;
const lqi = buf.readUInt8(i);
i += 1;
const depth = buf.readUInt8(i);
i += 1;
const updateId = buf.readUInt8(i);
i += 1;
const extPandId = addrBuf2Str(buf.slice(13));
i += 8;
return {
srcAddr,
padId,
logicalChannel,
permitJoin,
routerCapacity,
deviceCapacity,
protocolVersion,
stackProfile,
lqi,
depth,
updateId,
extPandId,
};
}
function addrBuf2Str(buf) {
const bufLen = buf.length;
let val;
let strChunk = '0x';
for (let i = 0; i < bufLen; i += 1) {
val = buf.readUInt8(bufLen - i - 1);
if (val <= 15) {
strChunk += `0${val.toString(16)}`;
}
else {
strChunk += val.toString(16);
}
}
return strChunk;
}
function bufToArray(buf, nip) {
const nipArr = [];
if (nip === 'uint8') {
for (let i = 0; i < buf.length; i += 1) {
nipArr.push(buf.readUInt8(i));
}
}
else if (nip === 'uint16') {
for (let i = 0; i < buf.length; i += 2) {
nipArr.push(buf.readUInt16LE(i));
}
}
return nipArr;
}
module.exports = ZpiObject;