UNPKG

zigbee-herdsman

Version:

An open source ZigBee gateway solution with node.js.

520 lines 21.3 kB
"use strict"; /* istanbul ignore file */ 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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __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.frameParserEvents = void 0; const stream_1 = require("stream"); const logger_1 = require("../../../utils/logger"); const Zdo = __importStar(require("../../../zspec/zdo")); const constants_1 = __importDefault(require("./constants")); const driver_1 = require("./driver"); const NS = 'zh:deconz:frameparser'; const MIN_BUFFER_SIZE = 3; const littleEndian = true; const lastReceivedGpInd = { srcId: 0, commandId: 0, frameCounter: 0 }; exports.frameParserEvents = new stream_1.EventEmitter(); function parseReadParameterResponse(view) { const parameterId = view.getUint8(7); switch (parameterId) { case constants_1.default.PARAM.Network.MAC: { const mac = view.getBigUint64(8, littleEndian).toString(16); let result = mac; while (result.length < 16) { result = '0' + result; } result = '0x' + result; logger_1.logger.debug(`MAC: ${result}`, NS); return result; } case constants_1.default.PARAM.Network.PAN_ID: { const panId = view.getUint16(8, littleEndian); logger_1.logger.debug('PANID: ' + panId.toString(16), NS); return panId; } case constants_1.default.PARAM.Network.NWK_ADDRESS: { const nwkAddr = view.getUint16(8, littleEndian); logger_1.logger.debug('NWKADDR: ' + nwkAddr.toString(16), NS); return nwkAddr; } case constants_1.default.PARAM.Network.EXT_PAN_ID: { const extPanId = view.getBigUint64(8, littleEndian).toString(16); let res = extPanId; while (res.length < 16) { res = '0' + res; } res = '0x' + res; logger_1.logger.debug(`EXT_PANID: ${res}`, NS); return res; } case constants_1.default.PARAM.Network.APS_EXT_PAN_ID: { const apsExtPanId = view.getBigUint64(8, littleEndian).toString(16); let resAEPID = apsExtPanId; while (resAEPID.length < 16) { resAEPID = '0' + resAEPID; } resAEPID = '0x' + resAEPID; logger_1.logger.debug(`APS_EXT_PANID: ${resAEPID}`, NS); return resAEPID; } case constants_1.default.PARAM.Network.NETWORK_KEY: { const networkKey1 = view.getBigUint64(9).toString(16); let res1 = networkKey1; while (res1.length < 16) { res1 = '0' + res1; } const networkKey2 = view.getBigUint64(17).toString(16); let res2 = networkKey2; while (res2.length < 16) { res2 = '0' + res2; } logger_1.logger.debug('NETWORK_KEY: hidden', NS); return '0x' + res1 + res2; } case constants_1.default.PARAM.Network.CHANNEL: { const channel = view.getUint8(8); logger_1.logger.debug('CHANNEL: ' + channel, NS); return channel; } case constants_1.default.PARAM.Network.CHANNEL_MASK: { const chMask = view.getUint32(8, littleEndian); logger_1.logger.debug('CHANNELMASK: ' + chMask.toString(16), NS); return chMask; } case constants_1.default.PARAM.Network.PERMIT_JOIN: { const permitJoin = view.getUint8(8); logger_1.logger.debug('PERMIT_JOIN: ' + permitJoin, NS); return permitJoin; } case constants_1.default.PARAM.Network.WATCHDOG_TTL: { const ttl = view.getUint32(8); logger_1.logger.debug('WATCHDOG_TTL: ' + ttl, NS); return ttl; } default: //throw new Error(`unknown parameter id ${parameterId}`); logger_1.logger.debug(`unknown parameter id ${parameterId}`, NS); return null; } } function parseReadFirmwareResponse(view) { const fw = [view.getUint8(5), view.getUint8(6), view.getUint8(7), view.getUint8(8)]; logger_1.logger.debug('read firmware version response - version: ' + fw, NS); return fw; } function parseDeviceStateResponse(view) { const flag = view.getUint8(5); logger_1.logger.debug('device state: ' + flag.toString(2), NS); exports.frameParserEvents.emit('receivedDataNotification', flag); return flag; } function parseChangeNetworkStateResponse(view) { const status = view.getUint8(2); const state = view.getUint8(5); logger_1.logger.debug('change network state - status: ' + status + ' new state: ' + state, NS); return state; } function parseQuerySendDataStateResponse(view) { try { const commandId = view.getUint8(0); const seqNr = view.getUint8(1); const status = view.getUint8(2); if (status !== 0) { if (status !== 5) { logger_1.logger.debug('DATA_CONFIRM RESPONSE - seqNr.: ' + seqNr + ' status: ' + status, NS); } return null; } const frameLength = 7; const payloadLength = view.getUint16(5, littleEndian); const deviceState = view.getUint8(7); const requestId = view.getUint8(8); const destAddrMode = view.getUint8(9); let destAddr64; let destAddr16; let destEndpoint; let destAddr = ''; if (destAddrMode === constants_1.default.PARAM.addressMode.IEEE_ADDR) { let res = view.getBigUint64(10, littleEndian).toString(16); while (res.length < 16) { res = '0' + res; } destAddr64 = res; // const buf2 = view.buffer.slice(18, view.buffer.byteLength); destAddr = destAddr64; } else { destAddr16 = view.getUint16(10, littleEndian); // const buf2 = view.buffer.slice(12, view.buffer.byteLength); destAddr = destAddr16.toString(16); } if (destAddrMode === constants_1.default.PARAM.addressMode.NWK_ADDR || destAddrMode === constants_1.default.PARAM.addressMode.IEEE_ADDR) { destEndpoint = view.getUint8(view.byteLength - 7); } const srcEndpoint = view.getUint8(view.byteLength - 6); const confirmStatus = view.getInt8(view.byteLength - 5); let newStatus = deviceState.toString(2); for (let l = 0; l <= 8 - newStatus.length; l++) { newStatus = '0' + newStatus; } // resolve send data request promise const i = driver_1.apsBusyQueue.findIndex((r) => r.request && r.request.requestId === requestId); if (i < 0) { return null; } clearTimeout(driver_1.enableRtsTimeout); (0, driver_1.enableRTS)(); // enable ReadyToSend because confirm received const req = driver_1.apsBusyQueue[i]; // TODO timeout (at driver.ts) if (confirmStatus !== 0) { // reject if status is not SUCCESS //logger.debug("REJECT APS_REQUEST - request id: " + requestId + " confirm status: " + confirmStatus, NS); req.reject(new Error(`confirmStatus=${confirmStatus}`)); } else { //logger.debug("RESOLVE APS_REQUEST - request id: " + requestId + " confirm status: " + confirmStatus, NS); req.resolve(confirmStatus); } //remove from busyqueue driver_1.apsBusyQueue.splice(i, 1); logger_1.logger.debug('DATA_CONFIRM RESPONSE - destAddr: 0x' + destAddr + ' request id: ' + requestId + ' confirm status: ' + confirmStatus, NS); exports.frameParserEvents.emit('receivedDataNotification', deviceState); return { commandId, seqNr, status, frameLength, payloadLength, deviceState, requestId, destAddrMode, destAddr16, destAddr64, destEndpoint, srcEndpoint, confirmStatus, }; } catch (error) { logger_1.logger.debug('DATA_CONFIRM RESPONSE - ' + error, NS); return null; } } function parseReadReceivedDataResponse(view) { // min 28 bytelength try { let buf2, buf3; const commandId = view.getUint8(0); const seqNr = view.getUint8(1); const status = view.getUint8(2); if (status != 0) { if (status !== 5) { logger_1.logger.debug('DATA_INDICATION RESPONSE - seqNr.: ' + seqNr + ' status: ' + status, NS); } return null; } const frameLength = view.getUint16(3, littleEndian); const payloadLength = view.getUint16(5, littleEndian); const deviceState = view.getUint8(7); const destAddrMode = view.getUint8(8); let destAddr64; let destAddr16; let destAddr = ''; if (destAddrMode === constants_1.default.PARAM.addressMode.IEEE_ADDR) { let res = view.getBigUint64(9, littleEndian).toString(16); while (res.length < 16) { res = '0' + res; } destAddr64 = res; buf2 = view.buffer.slice(17, view.buffer.byteLength); destAddr = destAddr64; } else { destAddr16 = view.getUint16(9, littleEndian); buf2 = view.buffer.slice(11, view.buffer.byteLength); destAddr = destAddr16.toString(16); } view = new DataView(buf2); const destEndpoint = view.getUint8(0); const srcAddrMode = view.getUint8(1); let srcAddr64; let srcAddr16; let srcAddr = ''; if (srcAddrMode === constants_1.default.PARAM.addressMode.NWK_ADDR || srcAddrMode === 0x04) { srcAddr16 = view.getUint16(2, littleEndian); buf3 = view.buffer.slice(4, view.buffer.byteLength); srcAddr = srcAddr16.toString(16); } if (srcAddrMode === constants_1.default.PARAM.addressMode.IEEE_ADDR || srcAddrMode === 0x04) { let res = view.getBigUint64(2, littleEndian).toString(16); while (res.length < 16) { res = '0' + res; } srcAddr64 = res; buf3 = view.buffer.slice(10, view.buffer.byteLength); srcAddr = srcAddr64; } view = new DataView(buf3); // XXX: not validated? const srcEndpoint = view.getUint8(0); const profileId = view.getUint16(1, littleEndian); const clusterId = view.getUint16(3, littleEndian); const asduLength = view.getUint16(5, littleEndian); const asduPayload = Buffer.from(view.buffer.slice(7, asduLength + 7)); const lqi = view.getUint8(view.byteLength - 8); const rssi = view.getInt8(view.byteLength - 3); let newStatus = deviceState.toString(2); for (let l = 0; l <= 8 - newStatus.length; l++) { newStatus = '0' + newStatus; } logger_1.logger.debug('DATA_INDICATION RESPONSE - seqNr. ' + seqNr + ' srcAddr: 0x' + srcAddr + ' destAddr: 0x' + destAddr + ' profile id: 0x' + profileId.toString(16) + ' cluster id: 0x' + clusterId.toString(16) + ' lqi: ' + lqi, NS); logger_1.logger.debug('response payload: ' + asduPayload.toString('hex'), NS); exports.frameParserEvents.emit('receivedDataNotification', deviceState); const response = { commandId, seqNr, status, frameLength, payloadLength, deviceState, destAddrMode, destAddr16, destAddr64, destEndpoint, srcAddrMode, srcAddr16, srcAddr64, srcEndpoint, profileId, clusterId, asduLength, asduPayload, lqi, rssi, zdo: profileId === Zdo.ZDO_PROFILE_ID ? Zdo.Buffalo.readResponse(true, clusterId, asduPayload) : undefined, }; exports.frameParserEvents.emit('receivedDataPayload', response); return response; } catch (error) { logger_1.logger.debug('DATA_INDICATION RESPONSE - ' + error, NS); return null; } } function parseEnqueueSendDataResponse(view) { try { const status = view.getUint8(2); const requestId = view.getUint8(8); const deviceState = view.getUint8(7); logger_1.logger.debug('DATA_REQUEST RESPONSE - request id: ' + requestId + ' status: ' + status, NS); exports.frameParserEvents.emit('receivedDataNotification', deviceState); return deviceState; } catch (error) { logger_1.logger.debug('parseEnqueueSendDataResponse - ' + error, NS); return null; } } function parseWriteParameterResponse(view) { try { const parameterId = view.getUint8(7); logger_1.logger.debug(`write parameter response - parameter id: ${parameterId} - status: ${view.getUint8(2)}`, NS); return parameterId; } catch (error) { logger_1.logger.debug('parseWriteParameterResponse - ' + error, NS); return null; } } function parseReceivedDataNotification(view) { try { const deviceState = view.getUint8(5); logger_1.logger.debug('DEVICE_STATE changed: ' + deviceState.toString(2), NS); exports.frameParserEvents.emit('receivedDataNotification', deviceState); return deviceState; } catch (error) { logger_1.logger.debug('parseReceivedDataNotification - ' + error, NS); return null; } } function parseGreenPowerDataIndication(view) { try { let id, rspId, options, srcId, frameCounter, commandId, commandFrameSize, commandFrame; const seqNr = view.getUint8(1); if (view.byteLength < 30) { logger_1.logger.debug('GP data notification', NS); id = 0x00; // 0 = notification, 4 = commissioning rspId = 0x01; // 1 = pairing, 2 = commissioning options = 0; view.getUint16(7, littleEndian); // frame ctrl field(7) ext.fcf(8) srcId = view.getUint32(9, littleEndian); frameCounter = view.getUint32(13, littleEndian); commandId = view.getUint8(17); commandFrameSize = view.byteLength - 18 - 6; // cut 18 from begin and 4 (sec mic) and 2 from end (cfc) commandFrame = Buffer.from(view.buffer.slice(18, commandFrameSize + 18)); } else { logger_1.logger.debug('GP commissioning notification', NS); id = 0x04; // 0 = notification, 4 = commissioning rspId = 0x01; // 1 = pairing, 2 = commissioning options = view.getUint16(14, littleEndian); // opt(14) ext.opt(15) srcId = view.getUint32(8, littleEndian); frameCounter = view.getUint32(36, littleEndian); commandId = view.getUint8(12); commandFrameSize = view.byteLength - 13 - 2; // cut 13 from begin and 2 from end (cfc) commandFrame = Buffer.from(view.buffer.slice(13, commandFrameSize + 13)); } const ind = { rspId, seqNr, id, options, srcId, frameCounter, commandId, commandFrameSize, commandFrame, }; if (!(lastReceivedGpInd.srcId === srcId && lastReceivedGpInd.commandId === commandId && lastReceivedGpInd.frameCounter === frameCounter)) { lastReceivedGpInd.srcId = srcId; lastReceivedGpInd.commandId = commandId; lastReceivedGpInd.frameCounter = frameCounter; //logger.debug(`GP_DATA_INDICATION - src id: ${srcId} cmd id: ${commandId} frameCounter: ${frameCounter}`, NS); logger_1.logger.debug(`GP_DATA_INDICATION - src id: 0x${srcId.toString(16)} cmd id: 0x${commandId.toString(16)} frameCounter: 0x${frameCounter.toString(16)}`, NS); exports.frameParserEvents.emit('receivedGreenPowerIndication', ind); } return ind; } catch (error) { logger_1.logger.debug('GREEN_POWER INDICATION - ' + error, NS); return null; } } // eslint-disable-next-line @typescript-eslint/no-unused-vars function parseMacPollCommand(view) { //logger.debug("Received command MAC_POLL", NS); return 28; } // eslint-disable-next-line @typescript-eslint/no-unused-vars function parseBeaconRequest(view) { logger_1.logger.debug('Received Beacon Request', NS); return 31; } function parseUnknownCommand(view) { const id = view.getUint8(0); logger_1.logger.debug(`received unknown command - id ${id}`, NS); return id; } function getParserForCommandId(id) { switch (id) { case constants_1.default.PARAM.FrameType.ReadParameter: return parseReadParameterResponse; case constants_1.default.PARAM.FrameType.WriteParameter: return parseWriteParameterResponse; case constants_1.default.PARAM.FrameType.ReadFirmwareVersion: return parseReadFirmwareResponse; case constants_1.default.PARAM.FrameType.ReadDeviceState: return parseDeviceStateResponse; case constants_1.default.PARAM.APS.DATA_INDICATION: return parseReadReceivedDataResponse; case constants_1.default.PARAM.APS.DATA_REQUEST: return parseEnqueueSendDataResponse; case constants_1.default.PARAM.APS.DATA_CONFIRM: return parseQuerySendDataStateResponse; case constants_1.default.PARAM.FrameType.DeviceStateChanged: return parseReceivedDataNotification; case constants_1.default.PARAM.NetworkState.CHANGE_NETWORK_STATE: return parseChangeNetworkStateResponse; case constants_1.default.PARAM.FrameType.GreenPowerDataInd: return parseGreenPowerDataIndication; case 28: return parseMacPollCommand; case 31: return parseBeaconRequest; default: return parseUnknownCommand; //throw new Error(`unknown command id ${id}`); } } function processFrame(frame) { const [seqNumber, status, command, commandId] = parseFrame(frame); //logger.debug(`process frame with seq: ${seqNumber} status: ${status}`, NS); let queue = driver_1.busyQueue; if (commandId === constants_1.default.PARAM.APS.DATA_INDICATION || commandId === constants_1.default.PARAM.APS.DATA_REQUEST || commandId === constants_1.default.PARAM.APS.DATA_CONFIRM) { queue = driver_1.apsBusyQueue; } const i = queue.findIndex((r) => r.seqNumber === seqNumber); if (i < 0) { return; } const req = queue[i]; if (commandId === constants_1.default.PARAM.APS.DATA_REQUEST) { // if confirm is true resolve request only when data confirm arrives // TODO only return if a confirm was requested. if no confirm needed: go ahead //if (req.confirm === true) { return; //} } //remove from busyqueue queue.splice(i, 1); if (status !== 0) { // reject if status is not SUCCESS //logger.debug("REJECT REQUEST", NS); req.reject(new Error(`status=${status}`)); } else { //logger.debug("RESOLVE REQUEST", NS); req.resolve(command); } } function parseFrame(frame) { if (frame.length < MIN_BUFFER_SIZE) { logger_1.logger.debug('received frame size to small - discard frame', NS); return [null, null, null, null]; } const view = new DataView(frame.buffer); const commandId = view.getUint8(0); const seqNumber = view.getUint8(1); const status = view.getUint8(2); //const frameLength = view.getUint16(3, littleEndian); //const payloadLength = view.getUint16(5, littleEndian); const parser = getParserForCommandId(commandId); return [seqNumber, status, parser(view), commandId]; } exports.default = processFrame; //# sourceMappingURL=frameParser.js.map