/* istanbul ignore file */ /* eslint-disable */ const MIN_BUFFER_SIZE = 3; const littleEndian = true; import PARAM from './constants'; import { busyQueue, apsBusyQueue } from './driver'; import { Request, ReceivedDataResponse, DataStateResponse, Command, ParamMac, ParamPanId, ParamNwkAddr, ParamExtPanId, ParamChannel, ParamChannelMask, ParamPermitJoin, ParamNetworkKey, gpDataInd } from './constants'; import * as Events from '../../events'; import {ZclFrame} from '../../../zcl'; import Debug from 'debug'; const debug = Debug('zigbee-herdsman:deconz:frameParser'); interface lastReceivedGpInd { srcId: number; commandId: number; frameCounter: number; } var lastReceivedGpInd = {srcId: 0, commandId: 0, frameCounter: 0}; var events = require('events'); var frameParserEvents = new events.EventEmitter(); module.exports.frameParserEvents = frameParserEvents; function parseReadParameterResponse(view: DataView) : Command { const parameterId = view.getUint8(7); switch (parameterId) { case PARAM.PARAM.Network.MAC: const mac: ParamMac = view.getBigUint64(8, littleEndian).toString(16); let result = mac; while (result.length < 16) { result = "0" + result; } result = "0x" + result; debug(`MAC: ${result}`); return result; case PARAM.PARAM.Network.PAN_ID: const panId: ParamPanId = view.getUint16(8, littleEndian); debug('PANID: ' + panId.toString(16)); return panId; case PARAM.PARAM.Network.NWK_ADDRESS: const nwkAddr: ParamNwkAddr = view.getUint16(8, littleEndian); debug('NWKADDR: ' + nwkAddr.toString(16)); return nwkAddr; case PARAM.PARAM.Network.EXT_PAN_ID: const extPanId: ParamExtPanId = view.getBigUint64(8, littleEndian).toString(16); let res = extPanId; while (res.length < 16) { res = "0" + res; } res = "0x" + res; debug(`EXT_PANID: ${res}`); return res; case PARAM.PARAM.Network.APS_EXT_PAN_ID: const apsExtPanId: ParamExtPanId = view.getBigUint64(8, littleEndian).toString(16); let resAEPID = apsExtPanId; while (resAEPID.length < 16) { resAEPID = "0" + resAEPID; } resAEPID = "0x" + resAEPID; debug(`APS_EXT_PANID: ${resAEPID}`); return resAEPID; case PARAM.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; } debug('NETWORK_KEY: hidden'); return "0x"+res1+res2; case PARAM.PARAM.Network.CHANNEL: const channel: ParamChannel = view.getUint8(8); debug('CHANNEL: ' + channel); return channel; case PARAM.PARAM.Network.CHANNEL_MASK: const chMask: ParamChannelMask = view.getUint32(8, littleEndian); debug('CHANNELMASK: ' + chMask.toString(16)); return chMask; case PARAM.PARAM.Network.PERMIT_JOIN: const permitJoin: ParamPermitJoin = view.getUint8(8); debug('PERMIT_JOIN: ' + permitJoin); return permitJoin; case PARAM.PARAM.Network.WATCHDOG_TTL: const ttl: ParamPermitJoin = view.getUint32(8); debug('WATCHDOG_TTL: ' + ttl); return ttl; default: //throw new Error(`unknown parameter id ${parameterId}`); debug(`unknown parameter id ${parameterId}`); return null; } } function parseReadFirmwareResponse(view : DataView) : number[] { const fw = [view.getUint8(5), view.getUint8(6), view.getUint8(7), view.getUint8(8)]; debug("read firmware version response - version: " + fw); return fw; } function parseDeviceStateResponse(view : DataView) : number { const flag = view.getUint8(5); debug("device state: " + flag.toString(2)); frameParserEvents.emit('receivedDataNotification', flag); return flag; } function parseChangeNetworkStateResponse(view : DataView) : number { const status = view.getUint8(2); const state = view.getUint8(5); debug("change network state - status: " + status + " new state: " + state); return state; } function parseQuerySendDataStateResponse(view : DataView) : object { const response: DataStateResponse = {}; let buf2, buf3; response.commandId = view.getUint8(0); response.seqNr = view.getUint8(1); response.status = view.getUint8(2); if (response.status != 0) { debug("DATA_CONFIRM RESPONSE - seqNr.: " + response.seqNr + " status: " + response.status); return null; } response.frameLength = 7; response.payloadLength = view.getUint16(5, littleEndian); response.deviceState = view.getUint8(7); response.requestId = view.getUint8(8); response.destAddrMode = view.getUint8(9); let destAddr = ""; if (response.destAddrMode === 0x03) { let res = view.getBigUint64(10, littleEndian).toString(16); while (res.length < 16) { res = "0" + res; } response.destAddr64 = res; buf2 = view.buffer.slice(18, view.buffer.byteLength); destAddr = response.destAddr64; } else { response.destAddr16 = view.getUint16(10, littleEndian); buf2 = view.buffer.slice(12, view.buffer.byteLength); destAddr = response.destAddr16.toString(16); } if (response.destAddrMode === 0x02 || response.destAddrMode === 0x03) { response.destEndpoint = view.getUint8(view.byteLength - 7); } response.srcEndpoint = view.getUint8(view.byteLength - 6); response.confirmStatus = view.getInt8(view.byteLength - 5); let newStatus = response.deviceState.toString(2); for (let l = 0; l <= (8 - newStatus.length); l++) { newStatus = "0" + newStatus; } // resolve send data request promise const i = apsBusyQueue.findIndex((r: Request) => (r.request && r.request.requestId === response.requestId)); if (i < 0) { return; } let string = ""; for (let u = 0; u < apsBusyQueue.length; u++) { const re: Request = apsBusyQueue[u]; string += re.seqNumber; string += ","; } const req: Request = apsBusyQueue[i]; // TODO timeout (at driver.ts) if (response.confirmStatus !== 0) { // reject if status is not SUCCESS //debug("REJECT APS_REQUEST - request id: " + response.requestId + " confirm status: " + response.confirmStatus); req.reject(response.confirmStatus); } else { //debug("RESOLVE APS_REQUEST - request id: " + response.requestId + " confirm status: " + response.confirmStatus); req.resolve(response.confirmStatus); } //remove from busyqueue apsBusyQueue.splice(i, 1); debug("DATA_CONFIRM RESPONSE - destAddr: 0x" + destAddr + " request id: " + response.requestId + " confirm status: " + response.confirmStatus); frameParserEvents.emit('receivedDataNotification', response.deviceState); /* const zclPayload: ZclDataPayload = { frame: ZclFrame; endpoint: number; linkquality: number; groupID: number; } */ return response; } function parseReadReceivedDataResponse(view : DataView) : object { // min 28 bytelength const response: ReceivedDataResponse = {}; let buf2, buf3; response.commandId = view.getUint8(0); response.seqNr = view.getUint8(1); response.status = view.getUint8(2); if (response.status != 0) { debug("DATA_INDICATION RESPONSE - seqNr.: " + response.seqNr + " status: " + response.status); return null; } response.frameLength = view.getUint16(3, littleEndian); response.payloadLength = view.getUint16(5, littleEndian); response.deviceState = view.getUint8(7); response.destAddrMode = view.getUint8(8); let destAddr = ""; if (response.destAddrMode === 0x03) { let res = view.getBigUint64(9, littleEndian).toString(16); while (res.length < 16) { res = "0" + res; } response.destAddr64 = res; buf2 = view.buffer.slice(17, view.buffer.byteLength); destAddr = response.destAddr64; } else { response.destAddr16 = view.getUint16(9, littleEndian); buf2 = view.buffer.slice(11, view.buffer.byteLength); destAddr = response.destAddr16.toString(16); } view = new DataView(buf2); response.destEndpoint = view.getUint8(0); response.srcAddrMode = view.getUint8(1); let srcAddr = ""; if (response.srcAddrMode === 0x02 || response.srcAddrMode === 0x04) { response.srcAddr16 = view.getUint16(2, littleEndian) buf3 = view.buffer.slice(4, view.buffer.byteLength); srcAddr = response.srcAddr16.toString(16); } if (response.srcAddrMode === 0x03 || response.srcAddrMode === 0x04) { let res = view.getBigUint64(2, littleEndian).toString(16); while (res.length < 16) { res = "0" + res; } response.srcAddr64 = res; buf3 = view.buffer.slice(10, view.buffer.byteLength); srcAddr = response.srcAddr64; } view = new DataView(buf3); response.srcEndpoint = view.getUint8(0); response.profileId = view.getUint16(1, littleEndian); response.clusterId = view.getUint16(3, littleEndian); response.asduLength = view.getUint16(5, littleEndian); let payload = []; let i = 0; for (let u = 7; u < (response.asduLength + 7); u++) { payload[i] = view.getUint8(u); i++; } response.asduPayload = payload; response.lqi = view.getUint8(view.byteLength - 6); response.rssi = view.getInt8(view.byteLength - 1); let newStatus = response.deviceState.toString(2); for (let l = 0; l <= (8 - newStatus.length); l++) { newStatus = "0" + newStatus; } debug("DATA_INDICATION RESPONSE - seqNr. " + response.seqNr + " srcAddr: 0x" + srcAddr + " destAddr: 0x" + destAddr + " profile id: 0x" + response.profileId.toString(16) + " cluster id: 0x" + response.clusterId.toString(16)); debug("response payload: " + payload); frameParserEvents.emit('receivedDataPayload', response); frameParserEvents.emit('receivedDataNotification', response.deviceState); return response; } function parseEnqueueSendDataResponse(view : DataView) : number { const status = view.getUint8(2); const requestId = view.getUint8(8); const deviceState = view.getUint8(7); debug("DATA_REQUEST RESPONSE - request id: " + requestId + " status: " + status); frameParserEvents.emit('receivedDataNotification', deviceState); return deviceState; } function parseWriteParameterResponse(view : DataView) : number { const parameterId = view.getUint8(7); debug(`write parameter response - parameter id: ${parameterId} - status: ${view.getUint8(2)}`); return parameterId; } function parseReceivedDataNotification(view : DataView) : number { const deviceState = view.getUint8(5); debug("DEVICE_STATE changed: " + deviceState.toString(2)); frameParserEvents.emit('receivedDataNotification', deviceState); return deviceState; } function parseGreenPowerDataIndication(view : DataView) : object { const ind: gpDataInd = {}; ind.seqNr = view.getUint8(1); console.log(view); if (view.byteLength < 30) { debug("GP data notification"); ind.id = 0x00; // 0 = notification, 4 = commissioning ind.rspId = 0x01; // 1 = pairing, 2 = commissioning ind.options = 0; view.getUint16(7, littleEndian); // frame ctrl field(7) ext.fcf(8) ind.srcId = view.getUint32(9, littleEndian); ind.frameCounter = view.getUint32(13, littleEndian); ind.commandId = view.getUint8(17); ind.commandFrameSize = view.byteLength - 18 - 6; // cut 18 from begin and 4 (sec mic) and 2 from end (cfc) let payload = []; let i = 0; for (let u = 18; u < (ind.commandFrameSize + 18); u++) { payload[i] = view.getUint8(u); i++; } ind.commandFrame = payload; } else { debug("GP commissioning notification"); ind.id = 0x04; // 0 = notification, 4 = commissioning ind.rspId = 0x01; // 1 = pairing, 2 = commissioning ind.options = view.getUint16(14, littleEndian); // opt(14) ext.opt(15) ind.srcId = view.getUint32(8, littleEndian); ind.frameCounter = view.getUint32(36, littleEndian); ind.commandId = view.getUint8(12); ind.commandFrameSize = view.byteLength - 13 - 2; // cut 13 from begin and 2 from end (cfc) let payload = []; let i = 0; for (let u = 13; u < (ind.commandFrameSize + 13); u++) { payload[i] = view.getUint8(u); i++; } ind.commandFrame = payload; } if (!(lastReceivedGpInd.srcId === ind.srcId && lastReceivedGpInd.commandId === ind.commandId && lastReceivedGpInd.frameCounter === ind.frameCounter)) { lastReceivedGpInd.srcId = ind.srcId; lastReceivedGpInd.commandId = ind.commandId; lastReceivedGpInd.frameCounter = ind.frameCounter; //debug(`GP_DATA_INDICATION - src id: ${ind.srcId} cmd id: ${ind.commandId} frameCounter: ${ind.frameCounter}`); debug(`GP_DATA_INDICATION - src id: 0x${ind.srcId.toString(16)} cmd id: 0x${ind.commandId.toString(16)} frameCounter: 0x${ind.frameCounter.toString(16)}`); frameParserEvents.emit('receivedGreenPowerIndication', ind); } return ind; } function parseMacPollCommand(view : DataView) : number { //debug("Received command MAC_POLL"); return 28; } function parseBeaconRequest(view : DataView) : number { debug("Received Beacon Request"); return 31; } function parseUnknownCommand(view : DataView) : number { const id = view.getUint8(0); debug(`received unknown command - id ${id}`); return id; } function getParserForCommandId(id: Number) : Function { switch (id) { case PARAM.PARAM.FrameType.ReadParameter: return parseReadParameterResponse; case PARAM.PARAM.FrameType.WriteParameter: return parseWriteParameterResponse; case PARAM.PARAM.FrameType.ReadFirmwareVersion: return parseReadFirmwareResponse; case PARAM.PARAM.FrameType.ReadDeviceState: return parseDeviceStateResponse; case PARAM.PARAM.APS.DATA_INDICATION: return parseReadReceivedDataResponse; case PARAM.PARAM.APS.DATA_REQUEST: return parseEnqueueSendDataResponse; case PARAM.PARAM.APS.DATA_CONFIRM: return parseQuerySendDataStateResponse; case PARAM.PARAM.FrameType.DeviceStateChanged: return parseReceivedDataNotification; case PARAM.PARAM.NetworkState.CHANGE_NETWORK_STATE: return parseChangeNetworkStateResponse; case PARAM.PARAM.FrameType.GreenPowerDataInd: return parseGreenPowerDataIndication; case 28: return parseMacPollCommand; case 31: return parseBeaconRequest; default: return parseUnknownCommand; //throw new Error(`unknown command id ${id}`); } } async function processFrame(frame: Uint8Array) : Promise { const [seqNumber, status, command, commandId] = await parseFrame(frame); //debug(`process frame with seq: ${seqNumber} status: ${status}`); let queue = busyQueue; if (commandId === PARAM.PARAM.APS.DATA_INDICATION || commandId === PARAM.PARAM.APS.DATA_REQUEST || commandId === PARAM.PARAM.APS.DATA_CONFIRM ) { queue = apsBusyQueue; } const i = queue.findIndex((r: Request) => r.seqNumber === seqNumber); if (i < 0) { return; } const req: Request = queue[i]; if (commandId === PARAM.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; //} } if (status !== 0) { // reject if status is not SUCCESS //debug("REJECT REQUEST"); req.reject({status}); } else { //debug("RESOLVE REQUEST"); req.resolve(command); } //remove from busyqueue queue.splice(i, 1); } function parseFrame(frame: Uint8Array) : [number, number, Command, number] { if (frame.length < MIN_BUFFER_SIZE) { throw new Error("received frame size to small"); } 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); // todo check framelength, payloadlength < x const parser = getParserForCommandId(commandId); return [seqNumber, status, parser(view), commandId]; } export default processFrame;