"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;
//import Device from "../../../controller/model/device";
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
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 = __importStar(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;
    frameParserEvent = frameParser_1.frameParserEvents;
    fwVersion;
    waitress;
    joinPermitted = false;
    constructor(networkOptions, serialPortOptions, backupPath, adapterOptions) {
        super(networkOptions, serialPortOptions, backupPath, adapterOptions);
        this.hasZdoMessageOverhead = true;
        this.manufacturerID = Zcl.ManufacturerCode.DRESDEN_ELEKTRONIK_INGENIEURTECHNIK_GMBH;
        this.waitress = new utils_1.Waitress(this.waitressValidator, this.waitressTimeoutFormatter);
        const firmwareLog = [];
        if (backupPath) {
            // optional: get extra logs from the firmware (debug builds)
            const dirPath = (0, node_path_1.dirname)(backupPath);
            const configPath = `${dirPath}/deconz_options.json`;
            if ((0, node_fs_1.existsSync)(configPath)) {
                try {
                    const data = JSON.parse((0, node_fs_1.readFileSync)(configPath).toString());
                    const log = data.firmware_log || [];
                    if (Array.isArray(log)) {
                        for (const level of log) {
                            if (level === "APS" || level === "APS_L2") {
                                firmwareLog.push(level);
                            }
                        }
                    }
                }
                catch (_err) { }
            }
        }
        this.driver = new driver_1.default(serialPortOptions, networkOptions, this.getStoredBackup(), firmwareLog);
        this.driver.on("rxFrame", (frame) => (0, frameParser_1.default)(frame));
        this.openRequestsQueue = [];
        this.fwVersion = undefined;
        this.frameParserEvent.on("receivedDataPayload", (data) => this.checkReceivedDataPayload(data));
        this.frameParserEvent.on("receivedGreenPowerIndication", (data) => this.checkReceivedGreenPowerIndication(data));
        setInterval(() => {
            // Need to have a try/catch block here since promised can be rejected which don't
            // have a handler waiting for them anymore.
            try {
                this.checkWaitForDataRequestTimeouts();
            }
            catch (_err) { }
        }, 1000);
    }
    /**
     * Adapter methods
     */
    start() {
        // wait here until driver is connected and has queried all firmware parameters
        return new Promise((resolve, reject) => {
            const start = Date.now();
            const iv = setInterval(() => {
                if (this.driver.started()) {
                    clearInterval(iv);
                    if (this.driver.restoredFromBackup) {
                        resolve("restored");
                    }
                    else {
                        resolve("resumed");
                    }
                    return;
                }
                if (20000 < Date.now() - start) {
                    clearInterval(iv);
                    reject("failed to start adapter connection to firmware");
                    return;
                }
            }, 50);
        });
    }
    async stop() {
        await this.driver.close();
    }
    getCoordinatorIEEE() {
        logger_1.logger.debug("-------- z2m:getCoordinatorIEEE() ----------------", NS);
        if (this.driver.paramMacAddress === 0n) {
            return Promise.reject(new Error("Failed to query coordinator MAC address"));
        }
        return Promise.resolve(`0x${this.driver.paramMacAddress.toString(16).padStart(16, "0")}`);
    }
    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.ParamId.STK_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;
    }
    getCoordinatorVersion() {
        logger_1.logger.debug("-------- z2m:getCoordinatorVersion() ----------------", NS);
        // product: number; transportrev: number; majorrel: number; minorrel: number; maintrel: number; revision: string;
        const fw = this.driver.paramFirmwareVersion;
        if (fw === 0) {
            return Promise.reject(new Error("Failed to query coordinator firmware version"));
        }
        const fwString = `0x${fw.toString(16).padStart(8, "0")}`;
        logger_1.logger.debug(`Firmware version: ${fwString}`, NS);
        let type = "Unknown";
        const platform = (fw >> 8) & 0xff;
        if (platform === 5) {
            type = "ConBee/RaspBee";
        }
        else if (platform === 7) {
            type = "ConBee2/RaspBee2";
        }
        else if (platform === 9) {
            type = "ConBee3";
        }
        // 0x26780700 -> 2.6.78.7.00
        const major = (fw >> 28) & 0xf;
        const minor = (fw >> 24) & 0xf;
        const patch = (fw >> 16) & 0xff;
        const meta = { transportrev: 0, product: 0, majorrel: major, minorrel: minor, maintrel: patch, revision: fwString };
        this.fwVersion = { type: type, meta: meta };
        return Promise.resolve({ type: type, meta: meta });
    }
    async addInstallCode(ieeeAddress, key, hashed) {
        await this.driver.writeLinkKey(ieeeAddress, hashed ? key : ZSpec.Utils.aes128MmoHash(key));
    }
    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,
        };
        logger_1.logger.debug(`waitFor() called ${JSON.stringify(payload)}`, NS);
        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;
        let txOptions = 0;
        if (networkAddress < constants_1.NwkBroadcastAddress.BroadcastLowPowerRouters) {
            txOptions = 0x4; // enable APS ACKs for unicast addresses
        }
        const isNwkAddrRequest = clusterId === Zdo.ClusterId.NETWORK_ADDRESS_REQUEST;
        const req = {
            requestId: transactionID,
            destAddrMode: constants_1.ApsAddressMode.Nwk,
            destAddr16: 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,
            radius: constants_1.default.PARAM.txRadius.DEFAULT_RADIUS,
            timeout: 10000,
        };
        const responseClusterId = Zdo.Utils.getResponseClusterId(clusterId);
        const confirm = this.driver.enqueueApsDataRequest(req);
        let indication;
        if (!disableResponse) {
            if (responseClusterId) {
                indication = this.waitForData(isNwkAddrRequest ? ieeeAddress : networkAddress, Zdo.ZDO_PROFILE_ID, responseClusterId, transactionID, req.timeout);
            }
        }
        try {
            await confirm;
            if (indication) {
                const indicationIndex = this.openRequestsQueue.findIndex((x) => x.clusterId === responseClusterId && x.transactionSequenceNumber === transactionID);
                if (indicationIndex !== -1) {
                    this.openRequestsQueue[indicationIndex].confirmed = true;
                }
            }
        }
        catch (err) {
            // no need to wait for indication, remove waiter from queue
            if (indication) {
                const indicationIndex = this.openRequestsQueue.findIndex((x) => x.clusterId === responseClusterId && x.transactionSequenceNumber === transactionID);
                if (indicationIndex !== -1) {
                    this.openRequestsQueue.splice(indicationIndex, 1);
                }
            }
            throw new Error(`failed to send ZDO request seq: (${transactionID}) ${err}`);
        }
        if (indication) {
            try {
                const data = await indication;
                // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
                return data.zdo;
            }
            catch (error) {
                if (responseClusterId === Zdo.ClusterId.ACTIVE_ENDPOINTS_RESPONSE && networkAddress === 0) {
                    // TODO(mpi): Check following statement on older firmware versions.
                    // If it is true we can always query firmware parameters for endpoints.
                    logger_1.logger.warning("Failed to determine active endpoints of coordinator, falling back to [1]", NS);
                    // Some Conbee adapaters don't provide a response to an active endpoint request of the coordinator, always return
                    // an endpoint here. Before an active endpoint request was done to determine the endpoints, they were hardcoded:
                    // https://github.com/Koenkk/zigbee-herdsman/blob/d855b3bf85a066cb7c325fe3ef0006873c735add/src/adapter/deconz/adapter/deconzAdapter.ts#L105
                    const response = [
                        Zdo.Status.SUCCESS,
                        { endpointList: [1], nwkAddress: 0 },
                    ];
                    return response;
                }
                throw error;
            }
        }
    }
    async sendZclFrameToEndpoint(_ieeeAddr, networkAddress, endpoint, zclFrame, timeout, disableResponse, 
    // TODO(mpi): in ember driver this means keep going enqueue request to firmware (up to 3 times).
    // In our case this a little different: The firmware may reject the requests because no free APS slots are available,
    // this is the only case where "recovery" makes sense. Other cases mean the request will never succeed (network offline, invalid request, ...).
    _disableRecovery, sourceEndpoint) {
        const transactionID = this.nextTransactionID();
        const payload = zclFrame.toBuffer();
        // TODO(mpi): Enable APS ACKs for tricky devices, maintain a list of those, or keep at least a few slots free for non APS ACK requests.
        const txOptions = 0x4; // 0x00 normal, 0x04 APS ACK
        const request = {
            requestId: transactionID,
            destAddrMode: constants_1.ApsAddressMode.Nwk,
            destAddr16: networkAddress,
            destEndpoint: endpoint,
            profileId: sourceEndpoint === 242 && endpoint === 242 ? 0xa1e0 : 0x104,
            clusterId: zclFrame.cluster.ID,
            srcEndpoint: sourceEndpoint || 1,
            asduLength: payload.length,
            asduPayload: payload,
            // TODO(mpi): This must not be a global option.
            // Since z2m doesn't provide it, ideally the driver figures this out on its own.
            // deCONZ keeps an error count for each device, if devices work fine without APS ACKs don't use them.
            // But if errors occur enable them..
            //
            // ember driver enables ACKs based on 'commandResponseId' which imho doesn't make sense at all:
            //
            //      don't RETRY if no response expected
            //      if (commandResponseId === undefined)
            //         apsFrame.options &= ~EmberApsOption.RETRY;
            //
            txOptions,
            radius: constants_1.default.PARAM.txRadius.DEFAULT_RADIUS,
            // TODO(mpi): We could treat _disableRecovery = true, to retry if enqueue (valid) requests or APS-confirms fail within timeout period?
            timeout: timeout,
        };
        if (timeout < 1000) {
            throw new Error("Unexpected small timeout");
        }
        const command = zclFrame.command;
        // NOTE(mpi): 'disableResponse' is not working as expected?
        // For now use the same logic as Ember adapter since 'disableResponse === false' alone isn't correct in some cases.
        //
        // E.g. when replying to an Query Next Image Request the following parameters where passed from z2m:
        //     { command.response: undefined, zcl.disableDefaultResponse: true, z2m.disableResponse: false }
        // This resulted in waiting for a response which never arrives and a timeout error thrown.
        let needWaitResponse = false;
        if (command.response !== undefined && disableResponse === false) {
            needWaitResponse = true;
        }
        else if (!zclFrame.header.frameControl.disableDefaultResponse) {
            needWaitResponse = true;
        }
        const confirm = this.driver.enqueueApsDataRequest(request);
        logger_1.logger.debug(`ZCL request sent with transactionSequenceNumber.: ${zclFrame.header.transactionSequenceNumber}`, NS);
        logger_1.logger.debug(`command.response: ${command.response}, zcl.disableDefaultResponse: ${zclFrame.header.frameControl.disableDefaultResponse}, z2m.disableResponse: ${disableResponse}, request.timeout: ${request.timeout}`, NS);
        let indication;
        if (needWaitResponse) {
            indication = this.waitForData(networkAddress, ZSpec.HA_PROFILE_ID, zclFrame.cluster.ID, zclFrame.header.transactionSequenceNumber, request.timeout);
        }
        try {
            await confirm;
            if (indication) {
                const indicationIndex = this.openRequestsQueue.findIndex((x) => x.clusterId === zclFrame.cluster.ID && x.transactionSequenceNumber === zclFrame.header.transactionSequenceNumber);
                if (indicationIndex !== -1) {
                    this.openRequestsQueue[indicationIndex].confirmed = true;
                }
            }
        }
        catch (err) {
            // no need to wait for indication, remove waiter from queue
            if (indication) {
                const indicationIndex = this.openRequestsQueue.findIndex((x) => x.clusterId === zclFrame.cluster.ID && x.transactionSequenceNumber === zclFrame.header.transactionSequenceNumber);
                if (indicationIndex !== -1) {
                    this.openRequestsQueue.splice(indicationIndex, 1);
                }
            }
            throw new Error(`failed to send ZCL request (${zclFrame.header.transactionSequenceNumber}) ${err}`);
        }
        if (indication) {
            try {
                const data = await indication;
                // TODO(mpi): This is nuts. Need to make sure that srcAddr16 is always valid.
                let addr;
                if (data.srcAddr16 !== undefined)
                    addr = data.srcAddr16;
                else if (data.srcAddr64 !== undefined)
                    addr = `0x${data.srcAddr64}`;
                else
                    throw new Error("Unexpected waitForData() didn't contain valid address");
                const groupId = 0;
                const wasBroadCast = false;
                const response = {
                    address: addr,
                    data: data.asduPayload,
                    clusterID: zclFrame.cluster.ID,
                    header: Zcl.Header.fromBuffer(data.asduPayload),
                    endpoint: data.srcEndpoint,
                    linkquality: data.lqi,
                    groupID: groupId,
                    wasBroadcast: wasBroadCast,
                    destinationEndpoint: data.destEndpoint,
                };
                logger_1.logger.debug(`Response received transactionSequenceNumber: ${zclFrame.header.transactionSequenceNumber}`, NS);
                return response;
            }
            catch (err) {
                throw new Error(`No ZCL response received for (${zclFrame.header.transactionSequenceNumber}) ${err}`);
            }
        }
    }
    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.ApsAddressMode.Group,
            destAddr16: groupID,
            profileId: 0x104,
            clusterId: zclFrame.cluster.ID,
            srcEndpoint: 1,
            asduLength: payload.length,
            asduPayload: payload,
            txOptions: 0,
            radius: constants_1.default.PARAM.txRadius.UNLIMITED,
            timeout: constants_1.default.PARAM.APS.MAX_SEND_TIMEOUT,
        };
        logger_1.logger.debug("sendZclFrameToGroup - message send", NS);
        return await this.driver.enqueueApsDataRequest(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.ApsAddressMode.Nwk,
            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,
            timeout: constants_1.default.PARAM.APS.MAX_SEND_TIMEOUT,
        };
        logger_1.logger.debug("sendZclFrameToAll - message send", NS);
        return await this.driver.enqueueApsDataRequest(request);
    }
    async supportsBackup() {
        return await Promise.resolve(true);
    }
    /**
     * Loads currently stored backup and returns it in internal backup model.
     */
    getStoredBackup() {
        if (!(0, node_fs_1.existsSync)(this.backupPath)) {
            return undefined;
        }
        let data;
        try {
            data = JSON.parse((0, node_fs_1.readFileSync)(this.backupPath).toString());
        }
        catch (error) {
            throw new Error(`[BACKUP] Coordinator backup is corrupted. (${error.stack})`);
        }
        if (data.metadata?.format === "zigpy/open-coordinator-backup" && data.metadata?.version) {
            if (data.metadata?.version !== 1) {
                throw new Error(`[BACKUP] Unsupported open coordinator backup version (version=${data.metadata?.version}).`);
            }
            // if (data.metadata.internal.ezspVersion < BACKUP_OLDEST_SUPPORTED_EZSP_VERSION) {
            //     renameSync(this.backupPath, `${this.backupPath}.old`);
            //     logger.warning("[BACKUP] Current backup file is from an unsupported EZSP version. Renaming and ignoring.", NS);
            //     return undefined;
            // }
            return utils_1.BackupUtils.fromUnifiedBackup(data);
        }
        throw new Error("[BACKUP] Unknown backup format.");
    }
    async backup() {
        if (!this.driver.started()) {
            throw new Error("Can't create backup while driver isn't connected");
        }
        // NOTE(mpi): The U64 values are put as big endian format into the buffer, same as string (0x001234...)
        const extendedPanId = Buffer.alloc(8);
        extendedPanId.writeBigUint64BE(this.driver.paramApsUseExtPanid);
        const channelList = [this.driver.paramCurrentChannel]; // Utils.unpackChannelList(nib.channelList)
        const networkKey = this.driver.paramNwkKey;
        const coordinatorIeeeAddress = Buffer.alloc(8);
        coordinatorIeeeAddress.writeBigUint64BE(this.driver.paramMacAddress);
        /* return backup structure */
        const backup = {
            networkOptions: {
                panId: this.driver.paramNwkPanid,
                extendedPanId,
                channelList,
                networkKey,
                networkKeyDistribute: false,
            },
            logicalChannel: this.driver.paramCurrentChannel,
            networkKeyInfo: {
                sequenceNumber: 0, // TODO(mpi): on newer firmware versions we can read it
                frameCounter: this.driver.paramFrameCounter,
            },
            securityLevel: 0x05, // AES-CCM-32 (fixed)
            networkUpdateId: this.driver.paramNwkUpdateId,
            coordinatorIeeeAddress,
            devices: [], // TODO(mpi): we currently don't have this, but it will be added once install codes get a proper interface
        };
        return await Promise.resolve(backup);
    }
    getNetworkParameters() {
        // TODO(mpi): This works with 0x26780700, needs more investigation with older firmware versions.
        const panID = this.driver.paramNwkPanid;
        const extendedPanID = this.driver.paramApsUseExtPanid;
        const channel = this.driver.paramCurrentChannel;
        const nwkUpdateID = this.driver.paramNwkUpdateId;
        if (channel === 0 || extendedPanID === 0n) {
            return Promise.reject(new Error("Failed to query network parameters"));
        }
        // TODO(mpi): check this statement, this should work
        // 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 Promise.resolve({ panID, extendedPanID: `0x${extendedPanID.toString(16).padStart(16, "0")}`, channel, nwkUpdateID });
    }
    async restoreChannelInterPAN() {
        await Promise.reject(new Error("not supported"));
    }
    async sendZclFrameInterPANToIeeeAddr(_zclFrame, _ieeeAddr) {
        await Promise.reject(new Error("not supported"));
    }
    async sendZclFrameInterPANBroadcast(_zclFrame, _timeout) {
        return await Promise.reject(new Error("not supported"));
    }
    async sendZclFrameInterPANBroadcastWithResponse(_zclFrame, _timeout) {
        return await Promise.reject(new Error("not supported"));
    }
    async setChannelInterPAN(_channel) {
        await Promise.reject(new Error("not supported"));
    }
    async sendZclFrameInterPANIeeeAddr(_zclFrame, _ieeeAddr) {
        await Promise.reject(new Error("not supported"));
    }
    /**
     * Private methods
     */
    waitForData(addr, profileId, clusterId, transactionSequenceNumber, timeout) {
        return new Promise((resolve, reject) => {
            const ts = Date.now();
            if (!timeout) {
                timeout = 60000;
            }
            const confirmed = false;
            const req = { addr, profileId, clusterId, transactionSequenceNumber, resolve, reject, confirmed, 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(ind.options, 3); // options
        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);
    }
    checkWaitForDataRequestTimeouts() {
        if (this.openRequestsQueue.length === 0) {
            return;
        }
        const now = Date.now();
        const req = this.openRequestsQueue[0];
        if (req.confirmed && req.timeout < now - req.ts) {
            this.openRequestsQueue.shift();
            logger_1.logger.debug(`Timeout for request in openRequestsQueue addr: ${req.addr}, clusterId: ${req.clusterId.toString(16)}, profileId: ${req.profileId.toString(16)}, seq: ${req.transactionSequenceNumber}`, NS);
            req.reject(new Error("waiting for response TIMEOUT"));
        }
    }
    checkReceivedDataPayload(resp) {
        //let srcAddr: number | undefined;
        //let srcEUI64: string | undefined;
        let header;
        //srcAddr = resp.srcAddr16;
        // TODO(mpi): The following shouldn't be needed anymore.
        // if (resp.srcAddr16 != null) {
        //
        // } 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.debug(`Try to find network address of ${resp.srcAddr64}`, NS);
        //         // Note: Device expects addresses with a 0x prefix...
        //         srcAddr = Device.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.srcAddr16) { // temp test
        //     const dev = Device.byNetworkAddress(resp.srcAddr16);
        //
        //     if (dev) {
        //         logger.debug(`APS-DATA.indication from ${dev.ieeeAddr}, ${dev.modelID} ${dev.manufacturerID}`, NS);
        //     }
        // }
        if (resp.profileId === Zdo.ZDO_PROFILE_ID) {
            if (resp.zdo) {
                if (resp.clusterId === Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE) {
                    // if (Zdo.Buffalo.checkStatus<Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE>(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?
                    // TODO(mpi): Clarify with core devs, I don't think the adapter should do this?!
                    if (this.joinPermitted === true && Zdo.Buffalo.checkStatus(resp.zdo)) {
                        const payload = resp.zdo[1];
                        this.emit("deviceJoined", { networkAddress: payload.nwkAddress, ieeeAddr: payload.eui64 });
                    }
                }
                // TODO(mpi) it seems that the controller is only interested in NWK, IEEE and DeviceAnnounce command
                // So maybe we should filter here?
                this.emit("zdoResponse", resp.clusterId, resp.zdo);
            }
        }
        else {
            header = Zcl.Header.fromBuffer(resp.asduPayload);
            if (!header) {
                logger_1.logger.debug("Failed tp parse ZCL header of non ZDO command", NS);
            }
        }
        let i = this.openRequestsQueue.length;
        while (i--) {
            const req = this.openRequestsQueue[i];
            if (req.profileId !== resp.profileId) {
                continue;
            }
            if (req.profileId === Zdo.ZDO_PROFILE_ID) {
                if (req.clusterId !== resp.clusterId) {
                    continue;
                }
                if (req.transactionSequenceNumber !== resp.asduPayload[0]) {
                    continue; // ZDP sequence number in first byte
                }
            }
            else if (header) {
                if (header.transactionSequenceNumber !== req.transactionSequenceNumber) {
                    continue;
                }
                if (req.clusterId !== resp.clusterId) {
                    continue;
                }
            }
            else {
                continue; // We should always have a valid transactionId (ZDO and ZCL)
            }
            this.openRequestsQueue.splice(i, 1);
            req.resolve(resp);
        }
        if (resp.profileId !== Zdo.ZDO_PROFILE_ID) {
            let groupID = 0;
            let wasBroadCast = false;
            if (resp.destAddrMode === constants_1.ApsAddressMode.Group) {
                groupID = resp.destAddr16;
                wasBroadCast = true;
            }
            else if (resp.destAddrMode === constants_1.ApsAddressMode.Nwk && resp.destAddr16 >= constants_1.NwkBroadcastAddress.BroadcastLowPowerRouters) {
                wasBroadCast = true;
            }
            const payload = {
                clusterID: resp.clusterId,
                header,
                data: resp.asduPayload,
                address: resp.srcAddr16,
                endpoint: resp.srcEndpoint,
                linkquality: resp.lqi,
                groupID: groupID,
                wasBroadcast: wasBroadCast,
                destinationEndpoint: resp.destEndpoint,
            };
            this.waitress.resolve(payload);
            this.emit("zclPayload", payload);
        }
    }
    nextTransactionID() {
        return this.driver.nextTransactionID();
    }
    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