"use strict";
/* v8 ignore start */
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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __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.DeconzAdapter = void 0;
const device_1 = __importDefault(require("../../../controller/model/device"));
const utils_1 = require("../../../utils");
const logger_1 = require("../../../utils/logger");
const ZSpec = __importStar(require("../../../zspec"));
const Zcl = __importStar(require("../../../zspec/zcl"));
const Zdo = __importStar(require("../../../zspec/zdo"));
const adapter_1 = __importDefault(require("../../adapter"));
const constants_1 = __importDefault(require("../driver/constants"));
const driver_1 = __importDefault(require("../driver/driver"));
const frameParser_1 = __importStar(require("../driver/frameParser"));
const NS = 'zh:deconz';
class DeconzAdapter extends adapter_1.default {
    driver;
    openRequestsQueue;
    transactionID;
    frameParserEvent = frameParser_1.frameParserEvents;
    fwVersion;
    waitress;
    TX_OPTIONS = 0x00; // No APS ACKS
    joinPermitted = false;
    constructor(networkOptions, serialPortOptions, backupPath, adapterOptions) {
        super(networkOptions, serialPortOptions, backupPath, adapterOptions);
        this.hasZdoMessageOverhead = true;
        this.manufacturerID = Zcl.ManufacturerCode.DRESDEN_ELEKTRONIK_INGENIEURTECHNIK_GMBH;
        // const concurrent = this.adapterOptions && this.adapterOptions.concurrent ? this.adapterOptions.concurrent : 2;
        // TODO: https://github.com/Koenkk/zigbee2mqtt/issues/4884#issuecomment-728903121
        const delay = this.adapterOptions && typeof this.adapterOptions.delay === 'number' ? this.adapterOptions.delay : 0;
        this.waitress = new utils_1.Waitress(this.waitressValidator, this.waitressTimeoutFormatter);
        this.driver = new driver_1.default(serialPortOptions.path);
        this.driver.setDelay(delay);
        if (delay >= 200) {
            this.TX_OPTIONS = 0x04; // activate APS ACKS
        }
        this.driver.on('rxFrame', (frame) => {
            (0, frameParser_1.default)(frame);
        });
        this.transactionID = 0;
        this.openRequestsQueue = [];
        this.fwVersion = undefined;
        this.frameParserEvent.on('receivedDataPayload', (data) => {
            this.checkReceivedDataPayload(data);
        });
        this.frameParserEvent.on('receivedGreenPowerIndication', (data) => {
            this.checkReceivedGreenPowerIndication(data);
        });
        setInterval(() => {
            this.checkReceivedDataPayload(null);
        }, 1000);
    }
    /**
     * Adapter methods
     */
    async start() {
        const baudrate = this.serialPortOptions.baudRate || 38400;
        await this.driver.open(baudrate);
        let changed = false;
        const panid = await this.driver.readParameterRequest(constants_1.default.PARAM.Network.PAN_ID);
        const expanid = await this.driver.readParameterRequest(constants_1.default.PARAM.Network.APS_EXT_PAN_ID);
        const channel = await this.driver.readParameterRequest(constants_1.default.PARAM.Network.CHANNEL);
        const networkKey = await this.driver.readParameterRequest(constants_1.default.PARAM.Network.NETWORK_KEY);
        // check current channel against configuration.yaml
        if (this.networkOptions.channelList[0] !== channel) {
            logger_1.logger.debug('Channel in configuration.yaml (' +
                this.networkOptions.channelList[0] +
                ') differs from current channel (' +
                channel +
                '). Changing channel.', NS);
            let setChannelMask = 0;
            switch (this.networkOptions.channelList[0]) {
                case 11:
                    setChannelMask = 0x800;
                    break;
                case 12:
                    setChannelMask = 0x1000;
                    break;
                case 13:
                    setChannelMask = 0x2000;
                    break;
                case 14:
                    setChannelMask = 0x4000;
                    break;
                case 15:
                    setChannelMask = 0x8000;
                    break;
                case 16:
                    setChannelMask = 0x10000;
                    break;
                case 17:
                    setChannelMask = 0x20000;
                    break;
                case 18:
                    setChannelMask = 0x40000;
                    break;
                case 19:
                    setChannelMask = 0x80000;
                    break;
                case 20:
                    setChannelMask = 0x100000;
                    break;
                case 21:
                    setChannelMask = 0x200000;
                    break;
                case 22:
                    setChannelMask = 0x400000;
                    break;
                case 23:
                    setChannelMask = 0x800000;
                    break;
                case 24:
                    setChannelMask = 0x1000000;
                    break;
                case 25:
                    setChannelMask = 0x2000000;
                    break;
                case 26:
                    setChannelMask = 0x4000000;
                    break;
                default:
                    break;
            }
            try {
                await this.driver.writeParameterRequest(constants_1.default.PARAM.Network.CHANNEL_MASK, setChannelMask);
                await (0, utils_1.wait)(500);
                changed = true;
            }
            catch (error) {
                logger_1.logger.debug('Could not set channel: ' + error, NS);
            }
        }
        // check current panid against configuration.yaml
        if (this.networkOptions.panID !== panid) {
            logger_1.logger.debug('panid in configuration.yaml (' + this.networkOptions.panID + ') differs from current panid (' + panid + '). Changing panid.', NS);
            try {
                await this.driver.writeParameterRequest(constants_1.default.PARAM.Network.PAN_ID, this.networkOptions.panID);
                await (0, utils_1.wait)(500);
                changed = true;
            }
            catch (error) {
                logger_1.logger.debug('Could not set panid: ' + error, NS);
            }
        }
        // check current extended_panid against configuration.yaml
        if (this.driver.generalArrayToString(this.networkOptions.extendedPanID, 8) !== expanid) {
            logger_1.logger.debug('extended panid in configuration.yaml (' +
                this.driver.macAddrArrayToString(this.networkOptions.extendedPanID) +
                ') differs from current extended panid (' +
                expanid +
                '). Changing extended panid.', NS);
            try {
                await this.driver.writeParameterRequest(constants_1.default.PARAM.Network.APS_EXT_PAN_ID, this.networkOptions.extendedPanID);
                await (0, utils_1.wait)(500);
                changed = true;
            }
            catch (error) {
                logger_1.logger.debug('Could not set extended panid: ' + error, NS);
            }
        }
        // check current network key against configuration.yaml
        if (this.driver.generalArrayToString(this.networkOptions.networkKey, 16) !== networkKey) {
            logger_1.logger.debug('network key in configuration.yaml (hidden) differs from current network key (' + networkKey + '). Changing network key.', NS);
            try {
                await this.driver.writeParameterRequest(constants_1.default.PARAM.Network.NETWORK_KEY, this.networkOptions.networkKey);
                await (0, utils_1.wait)(500);
                changed = true;
            }
            catch (error) {
                logger_1.logger.debug('Could not set network key: ' + error, NS);
            }
        }
        if (changed) {
            await this.driver.changeNetworkStateRequest(constants_1.default.PARAM.Network.NET_OFFLINE);
            await (0, utils_1.wait)(2000);
            await this.driver.changeNetworkStateRequest(constants_1.default.PARAM.Network.NET_CONNECTED);
            await (0, utils_1.wait)(2000);
        }
        // write endpoints
        //[ sd1   ep    proId       devId       vers  #inCl iCl1        iCl2        iCl3        iCl4        iCl5        #outC oCl1        oCl2        oCl3        oCl4      ]
        const sd = [
            0x00, 0x01, 0x04, 0x01, 0x05, 0x00, 0x01, 0x05, 0x00, 0x00, 0x00, 0x06, 0x0a, 0x00, 0x19, 0x00, 0x01, 0x05, 0x04, 0x01, 0x00, 0x20, 0x00,
            0x00, 0x05, 0x02, 0x05,
        ];
        const sd1 = sd.reverse();
        await this.driver.writeParameterRequest(constants_1.default.PARAM.STK.Endpoint, sd1);
        return 'resumed';
    }
    async stop() {
        await this.driver.close();
    }
    async getCoordinatorIEEE() {
        return (await this.driver.readParameterRequest(constants_1.default.PARAM.Network.MAC));
    }
    async permitJoin(seconds, networkAddress) {
        const clusterId = Zdo.ClusterId.PERMIT_JOINING_REQUEST;
        if (networkAddress) {
            // `authentication`: TC significance always 1 (zb specs)
            const zdoPayload = Zdo.Buffalo.buildRequest(this.hasZdoMessageOverhead, clusterId, seconds, 1, []);
            const result = await this.sendZdo(ZSpec.BLANK_EUI64, networkAddress, clusterId, zdoPayload, false);
            if (!Zdo.Buffalo.checkStatus(result)) {
                // TODO: will disappear once moved upstream
                throw new Zdo.StatusError(result[0]);
            }
        }
        else {
            await this.driver.writeParameterRequest(constants_1.default.PARAM.Network.PERMIT_JOIN, seconds);
            logger_1.logger.debug(`Permit joining on coordinator for ${seconds} sec.`, NS);
            // broadcast permit joining ZDO
            if (networkAddress === undefined) {
                // `authentication`: TC significance always 1 (zb specs)
                const zdoPayload = Zdo.Buffalo.buildRequest(this.hasZdoMessageOverhead, clusterId, seconds, 1, []);
                await this.sendZdo(ZSpec.BLANK_EUI64, ZSpec.BroadcastAddress.DEFAULT, clusterId, zdoPayload, true);
            }
        }
        this.joinPermitted = seconds !== 0;
    }
    async getCoordinatorVersion() {
        // product: number; transportrev: number; majorrel: number; minorrel: number; maintrel: number; revision: string;
        if (this.fwVersion != undefined) {
            return this.fwVersion;
        }
        else {
            try {
                const fw = await this.driver.readFirmwareVersionRequest();
                const buf = Buffer.from(fw);
                const fwString = '0x' + buf.readUInt32LE(0).toString(16);
                let type = '';
                if (fw[1] === 5) {
                    type = 'ConBee/RaspBee';
                }
                else if (fw[1] === 7) {
                    type = 'ConBee2/RaspBee2';
                }
                else {
                    type = 'ConBee3';
                }
                const meta = { transportrev: 0, product: 0, majorrel: fw[3], minorrel: fw[2], maintrel: 0, revision: fwString };
                this.fwVersion = { type: type, meta: meta };
                return { type: type, meta: meta };
            }
            catch (error) {
                throw new Error('Get coordinator version Error: ' + error);
            }
        }
    }
    async addInstallCode(ieeeAddress, key, hashed) {
        await this.driver.writeLinkKey(ieeeAddress, hashed ? key : ZSpec.Utils.aes128MmoHash(key));
    }
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    async reset(type) {
        return await Promise.reject(new Error('Reset is not supported'));
    }
    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.start().promise, cancel };
    }
    async sendZdo(ieeeAddress, networkAddress, clusterId, payload, disableResponse) {
        const transactionID = this.nextTransactionID();
        payload[0] = transactionID;
        const isNwkAddrRequest = clusterId === Zdo.ClusterId.NETWORK_ADDRESS_REQUEST;
        const req = {
            requestId: transactionID,
            destAddrMode: isNwkAddrRequest ? constants_1.default.PARAM.addressMode.IEEE_ADDR : constants_1.default.PARAM.addressMode.NWK_ADDR,
            destAddr16: isNwkAddrRequest ? undefined : networkAddress,
            destAddr64: isNwkAddrRequest ? ieeeAddress : undefined,
            destEndpoint: Zdo.ZDO_ENDPOINT,
            profileId: Zdo.ZDO_PROFILE_ID,
            clusterId,
            srcEndpoint: Zdo.ZDO_ENDPOINT,
            asduLength: payload.length,
            asduPayload: payload,
            txOptions: 0,
            radius: constants_1.default.PARAM.txRadius.DEFAULT_RADIUS,
            timeout: 30,
        };
        this.driver
            .enqueueSendDataRequest(req)
            .then(() => { })
            .catch(() => { });
        if (!disableResponse) {
            const responseClusterId = Zdo.Utils.getResponseClusterId(clusterId);
            if (responseClusterId) {
                const response = await this.waitForData(isNwkAddrRequest ? ieeeAddress : networkAddress, Zdo.ZDO_PROFILE_ID, responseClusterId);
                return response.zdo;
            }
        }
    }
    async sendZclFrameToEndpoint(ieeeAddr, networkAddress, endpoint, zclFrame, timeout, disableResponse, disableRecovery, sourceEndpoint) {
        const transactionID = this.nextTransactionID();
        const payload = zclFrame.toBuffer();
        const request = {
            requestId: transactionID,
            destAddrMode: constants_1.default.PARAM.addressMode.NWK_ADDR,
            destAddr16: networkAddress,
            destEndpoint: endpoint,
            profileId: sourceEndpoint === 242 && endpoint === 242 ? 0xa1e0 : 0x104,
            clusterId: zclFrame.cluster.ID,
            srcEndpoint: sourceEndpoint || 1,
            asduLength: payload.length,
            asduPayload: payload,
            txOptions: this.TX_OPTIONS, // 0x00 normal, 0x04 APS ACK
            radius: constants_1.default.PARAM.txRadius.DEFAULT_RADIUS,
            timeout: timeout,
        };
        const command = zclFrame.command;
        this.driver
            .enqueueSendDataRequest(request)
            .then(() => {
            logger_1.logger.debug(`sendZclFrameToEndpoint - message send with transSeq Nr.: ${zclFrame.header.transactionSequenceNumber}`, NS);
            logger_1.logger.debug((command.response !== undefined) +
                ', ' +
                zclFrame.header.frameControl.disableDefaultResponse +
                ', ' +
                disableResponse +
                ', ' +
                request.timeout, NS);
            if (command.response == undefined || zclFrame.header.frameControl.disableDefaultResponse || !disableResponse) {
                logger_1.logger.debug(`resolve request (${zclFrame.header.transactionSequenceNumber})`, NS);
                return Promise.resolve();
            }
        })
            .catch((error) => {
            logger_1.logger.debug(`sendZclFrameToEndpoint ERROR (${zclFrame.header.transactionSequenceNumber})`, NS);
            logger_1.logger.debug(error, NS);
            //return Promise.reject(new Error("sendZclFrameToEndpoint ERROR " + error));
        });
        try {
            let data = null;
            if ((command.response != undefined && !disableResponse) || !zclFrame.header.frameControl.disableDefaultResponse) {
                data = await this.waitForData(networkAddress, ZSpec.HA_PROFILE_ID, zclFrame.cluster.ID, zclFrame.header.transactionSequenceNumber, request.timeout);
            }
            if (data !== null) {
                const response = {
                    address: data.srcAddr16 ?? `0x${data.srcAddr64}`,
                    data: data.asduPayload,
                    clusterID: zclFrame.cluster.ID,
                    header: Zcl.Header.fromBuffer(data.asduPayload),
                    endpoint: data.srcEndpoint,
                    linkquality: data.lqi,
                    groupID: data.srcAddrMode === 0x01 ? data.srcAddr16 : 0,
                    wasBroadcast: data.srcAddrMode === 0x01 || data.srcAddrMode === 0xf,
                    destinationEndpoint: data.destEndpoint,
                };
                logger_1.logger.debug(`response received (${zclFrame.header.transactionSequenceNumber})`, NS);
                return response;
            }
            else {
                logger_1.logger.debug(`no response expected (${zclFrame.header.transactionSequenceNumber})`, NS);
            }
        }
        catch (error) {
            throw new Error(`no response received (${zclFrame.header.transactionSequenceNumber}) ${error}`);
        }
    }
    async sendZclFrameToGroup(groupID, zclFrame) {
        const transactionID = this.nextTransactionID();
        const payload = zclFrame.toBuffer();
        logger_1.logger.debug(`zclFrame to group - ${groupID}`, NS);
        const request = {
            requestId: transactionID,
            destAddrMode: constants_1.default.PARAM.addressMode.GROUP_ADDR,
            destAddr16: groupID,
            profileId: 0x104,
            clusterId: zclFrame.cluster.ID,
            srcEndpoint: 1,
            asduLength: payload.length,
            asduPayload: payload,
            txOptions: 0,
            radius: constants_1.default.PARAM.txRadius.UNLIMITED,
        };
        logger_1.logger.debug(`sendZclFrameToGroup - message send`, NS);
        return await this.driver.enqueueSendDataRequest(request);
    }
    async sendZclFrameToAll(endpoint, zclFrame, sourceEndpoint, destination) {
        const transactionID = this.nextTransactionID();
        const payload = zclFrame.toBuffer();
        logger_1.logger.debug(`zclFrame to all - ${endpoint}`, NS);
        const request = {
            requestId: transactionID,
            destAddrMode: constants_1.default.PARAM.addressMode.NWK_ADDR,
            destAddr16: destination,
            destEndpoint: endpoint,
            profileId: sourceEndpoint === 242 && endpoint === 242 ? 0xa1e0 : 0x104,
            clusterId: zclFrame.cluster.ID,
            srcEndpoint: sourceEndpoint,
            asduLength: payload.length,
            asduPayload: payload,
            txOptions: 0,
            radius: constants_1.default.PARAM.txRadius.UNLIMITED,
        };
        logger_1.logger.debug(`sendZclFrameToAll - message send`, NS);
        return await this.driver.enqueueSendDataRequest(request);
    }
    async supportsBackup() {
        return false;
    }
    async backup() {
        throw new Error('This adapter does not support backup');
    }
    async getNetworkParameters() {
        try {
            const panid = await this.driver.readParameterRequest(constants_1.default.PARAM.Network.PAN_ID);
            const expanid = await this.driver.readParameterRequest(constants_1.default.PARAM.Network.APS_EXT_PAN_ID);
            const channel = await this.driver.readParameterRequest(constants_1.default.PARAM.Network.CHANNEL);
            // For some reason, reading NWK_UPDATE_ID always returns `null` (tested with `0x26780700` on Conbee II)
            // 0x24 was taken from https://github.com/zigpy/zigpy-deconz/blob/70910bc6a63e607332b4f12754ba470651eb878c/zigpy_deconz/api.py#L152
            // const nwkUpdateId = await this.driver.readParameterRequest(0x24 /*PARAM.PARAM.Network.NWK_UPDATE_ID*/);
            return {
                panID: panid,
                extendedPanID: expanid, // read as `0x...`
                channel: channel,
                nwkUpdateID: 0,
            };
        }
        catch (error) {
            const msg = 'get network parameters Error:' + error;
            logger_1.logger.debug(msg, NS);
            return await Promise.reject(new Error(msg));
        }
    }
    async restoreChannelInterPAN() {
        throw new Error('not supported');
    }
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    async sendZclFrameInterPANToIeeeAddr(zclFrame, ieeeAddr) {
        throw new Error('not supported');
    }
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    async sendZclFrameInterPANBroadcast(zclFrame, timeout) {
        throw new Error('not supported');
    }
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    async sendZclFrameInterPANBroadcastWithResponse(zclFrame, timeout) {
        throw new Error('not supported');
    }
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    async setChannelInterPAN(channel) {
        throw new Error('not supported');
    }
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    async sendZclFrameInterPANIeeeAddr(zclFrame, ieeeAddr) {
        throw new Error('not supported');
    }
    /**
     * Private methods
     */
    waitForData(addr, profileId, clusterId, transactionSequenceNumber, timeout) {
        return new Promise((resolve, reject) => {
            const ts = Date.now();
            // const commandId = PARAM.PARAM.APS.DATA_INDICATION;
            const req = { addr, profileId, clusterId, transactionSequenceNumber, resolve, reject, ts, timeout };
            this.openRequestsQueue.push(req);
        });
    }
    checkReceivedGreenPowerIndication(ind) {
        const gpdHeader = Buffer.alloc(15); // applicationId === IEEE_ADDRESS ? 20 : 15
        gpdHeader.writeUInt8(0b00000001, 0); // frameControl: FrameType.SPECIFIC + Direction.CLIENT_TO_SERVER + disableDefaultResponse=false
        gpdHeader.writeUInt8(ind.seqNr, 1);
        gpdHeader.writeUInt8(ind.id, 2); // commandIdentifier
        gpdHeader.writeUInt16LE(0, 3); // options, only srcID present
        gpdHeader.writeUInt32LE(ind.srcId, 5);
        // omitted: gpdIEEEAddr (ieeeAddr)
        // omitted: gpdEndpoint (uint8)
        gpdHeader.writeUInt32LE(ind.frameCounter, 9);
        gpdHeader.writeUInt8(ind.commandId, 13);
        gpdHeader.writeUInt8(ind.commandFrameSize, 14);
        const payBuf = Buffer.concat([gpdHeader, ind.commandFrame]);
        const payload = {
            header: Zcl.Header.fromBuffer(payBuf),
            data: payBuf,
            clusterID: Zcl.Clusters.greenPower.ID,
            address: ind.srcId & 0xffff,
            endpoint: ZSpec.GP_ENDPOINT,
            linkquality: 0xff, // bogus
            groupID: ZSpec.GP_GROUP_ID,
            wasBroadcast: true, // Take the codepath that doesn't require `gppNwkAddr` as its not present in the payload
            destinationEndpoint: ZSpec.GP_ENDPOINT,
        };
        this.waitress.resolve(payload);
        this.emit('zclPayload', payload);
    }
    checkReceivedDataPayload(resp) {
        let srcAddr;
        let srcEUI64;
        let header;
        if (resp) {
            if (resp.srcAddr16 != null) {
                srcAddr = resp.srcAddr16;
            }
            else {
                // For some devices srcAddr64 is reported by ConBee 3, even if the frame contains both
                // srcAddr16 and srcAddr64. This happens even if the request was sent to a short address.
                // At least some parts, e.g. the while loop below, only work with srcAddr16 (i.e. the network
                // address) being set. So we try to look up the network address in the list of know devices.
                if (resp.srcAddr64 != null) {
                    logger_1.logger.debug(`Try to find network address of ${resp.srcAddr64}`, NS);
                    // Note: Device expects addresses with a 0x prefix...
                    srcAddr = device_1.default.byIeeeAddr('0x' + resp.srcAddr64, false)?.networkAddress;
                    // apperantly some functions furhter up in the protocol stack expect this to be set.
                    // so let's make sure they get the network address
                    // Note: srcAddr16 can be undefined after this and this is intended behavior
                    // there are zigbee frames which do not contain a 16 bit address, e.g. during joining.
                    // So any code that relies on srcAddr16 must handle this in some way.
                    resp.srcAddr16 = srcAddr;
                }
            }
            if (resp.profileId === Zdo.ZDO_PROFILE_ID) {
                if (resp.clusterId === Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE) {
                    if (Zdo.Buffalo.checkStatus(resp.zdo)) {
                        srcEUI64 = resp.zdo[1].eui64;
                    }
                }
                else if (resp.clusterId === Zdo.ClusterId.END_DEVICE_ANNOUNCE) {
                    // XXX: using same response for announce (handled by controller) or joined depending on permit join status?
                    if (this.joinPermitted === true && Zdo.Buffalo.checkStatus(resp.zdo)) {
                        const payload = resp.zdo[1];
                        this.emit('deviceJoined', { networkAddress: payload.nwkAddress, ieeeAddr: payload.eui64 });
                    }
                }
                this.emit('zdoResponse', resp.clusterId, resp.zdo);
            }
            else {
                header = Zcl.Header.fromBuffer(resp.asduPayload);
            }
        }
        let i = this.openRequestsQueue.length;
        while (i--) {
            const req = this.openRequestsQueue[i];
            if (resp &&
                (req.addr === undefined ||
                    (typeof req.addr === 'number' ? srcAddr !== undefined && req.addr === srcAddr : srcEUI64 && req.addr === srcEUI64)) &&
                req.clusterId === resp.clusterId &&
                req.profileId === resp.profileId &&
                (header === undefined ||
                    req.transactionSequenceNumber === undefined ||
                    req.transactionSequenceNumber === header.transactionSequenceNumber)) {
                this.openRequestsQueue.splice(i, 1);
                req.resolve(resp);
            }
            const now = Date.now();
            // Default timeout: 60 seconds.
            // Comparison is negated to prevent orphans when invalid timeout is entered (resulting in NaN).
            if (!(now - req.ts <= (req.timeout ?? 60000))) {
                //logger.debug("Timeout for request in openRequestsQueue addr: " + req.addr.toString(16) + " clusterId: " + req.clusterId.toString(16) + " profileId: " + req.profileId.toString(16), NS);
                //remove from busyQueue
                this.openRequestsQueue.splice(i, 1);
                req.reject(new Error('waiting for response TIMEOUT'));
            }
        }
        if (resp && resp.profileId != Zdo.ZDO_PROFILE_ID) {
            const payload = {
                clusterID: resp.clusterId,
                header,
                data: resp.asduPayload,
                address: resp.srcAddrMode === 0x03 ? `0x${resp.srcAddr64}` : resp.srcAddr16,
                endpoint: resp.srcEndpoint,
                linkquality: resp.lqi,
                groupID: resp.destAddrMode === 0x01 ? resp.destAddr16 : 0,
                wasBroadcast: resp.destAddrMode === 0x01 || resp.destAddrMode === 0xf,
                destinationEndpoint: resp.destEndpoint,
            };
            this.waitress.resolve(payload);
            this.emit('zclPayload', payload);
        }
    }
    nextTransactionID() {
        this.transactionID++;
        if (this.transactionID > 255) {
            this.transactionID = 1;
        }
        return this.transactionID;
    }
    waitressTimeoutFormatter(matcher, timeout) {
        return (`Timeout - ${matcher.address} - ${matcher.endpoint}` +
            ` - ${matcher.transactionSequenceNumber} - ${matcher.clusterID}` +
            ` - ${matcher.commandIdentifier} after ${timeout}ms`);
    }
    waitressValidator(payload, matcher) {
        return Boolean(payload.header &&
            (!matcher.address || payload.address === matcher.address) &&
            payload.endpoint === matcher.endpoint &&
            (!matcher.transactionSequenceNumber || payload.header.transactionSequenceNumber === matcher.transactionSequenceNumber) &&
            payload.clusterID === matcher.clusterID &&
            matcher.frameType === payload.header.frameControl.frameType &&
            matcher.commandIdentifier === payload.header.commandIdentifier &&
            matcher.direction === payload.header.frameControl.direction);
    }
}
exports.DeconzAdapter = DeconzAdapter;
//# sourceMappingURL=deconzAdapter.js.map