"use strict";
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 });
const entity_1 = __importDefault(require("./entity"));
const Zcl = __importStar(require("../../zcl"));
const zclTransactionSequenceNumber_1 = __importDefault(require("../helpers/zclTransactionSequenceNumber"));
const ZclFrameConverter = __importStar(require("../helpers/zclFrameConverter"));
const request_1 = __importDefault(require("../helpers/request"));
const requestQueue_1 = __importDefault(require("../helpers/requestQueue"));
const group_1 = __importDefault(require("./group"));
const device_1 = __importDefault(require("./device"));
const debug_1 = __importDefault(require("debug"));
const assert_1 = __importDefault(require("assert"));
const debug = {
    info: (0, debug_1.default)('zigbee-herdsman:controller:endpoint'),
    error: (0, debug_1.default)('zigbee-herdsman:controller:endpoint'),
};
class Endpoint extends entity_1.default {
    deviceID;
    inputClusters;
    outputClusters;
    profileID;
    ID;
    clusters;
    deviceIeeeAddress;
    deviceNetworkAddress;
    _binds;
    _configuredReportings;
    meta;
    pendingRequests;
    // Getters/setters
    get binds() {
        return this._binds.map((entry) => {
            let target = null;
            if (entry.type === 'endpoint') {
                const device = device_1.default.byIeeeAddr(entry.deviceIeeeAddress);
                if (device) {
                    target = device.getEndpoint(entry.endpointID);
                }
            }
            else {
                target = group_1.default.byGroupID(entry.groupID);
            }
            if (target) {
                return { target, cluster: Zcl.Utils.getCluster(entry.cluster, this.getDevice().manufacturerID) };
            }
            else {
                return undefined;
            }
        }).filter(b => b !== undefined);
    }
    get configuredReportings() {
        return this._configuredReportings.map((entry) => {
            const cluster = Zcl.Utils.getCluster(entry.cluster, entry.manufacturerCode);
            let attribute;
            if (cluster.hasAttribute(entry.attrId)) {
                attribute = cluster.getAttribute(entry.attrId);
            }
            else {
                attribute = {
                    ID: entry.attrId,
                    name: undefined,
                    type: undefined,
                    manufacturerCode: undefined
                };
            }
            return {
                cluster, attribute,
                minimumReportInterval: entry.minRepIntval,
                maximumReportInterval: entry.maxRepIntval,
                reportableChange: entry.repChange,
            };
        });
    }
    constructor(ID, profileID, deviceID, inputClusters, outputClusters, deviceNetworkAddress, deviceIeeeAddress, clusters, binds, configuredReportings, meta) {
        super();
        this.ID = ID;
        this.profileID = profileID;
        this.deviceID = deviceID;
        this.inputClusters = inputClusters;
        this.outputClusters = outputClusters;
        this.deviceNetworkAddress = deviceNetworkAddress;
        this.deviceIeeeAddress = deviceIeeeAddress;
        this.clusters = clusters;
        this._binds = binds;
        this._configuredReportings = configuredReportings;
        this.meta = meta;
        this.pendingRequests = new requestQueue_1.default(this);
    }
    /**
     * Get device of this endpoint
     */
    getDevice() {
        return device_1.default.byIeeeAddr(this.deviceIeeeAddress);
    }
    /**
     * @param {number|string} clusterKey
     * @returns {boolean}
     */
    supportsInputCluster(clusterKey) {
        const cluster = Zcl.Utils.getCluster(clusterKey);
        return this.inputClusters.includes(cluster.ID);
    }
    /**
     * @param {number|string} clusterKey
     * @returns {boolean}
     */
    supportsOutputCluster(clusterKey) {
        const cluster = Zcl.Utils.getCluster(clusterKey);
        return this.outputClusters.includes(cluster.ID);
    }
    /**
     * @returns {Zcl.TsType.Cluster[]}
     */
    getInputClusters() {
        return this.clusterNumbersToClusters(this.inputClusters);
    }
    /**
     * @returns {Zcl.TsType.Cluster[]}
     */
    getOutputClusters() {
        return this.clusterNumbersToClusters(this.outputClusters);
    }
    clusterNumbersToClusters(clusterNumbers) {
        return clusterNumbers.map((c) => Zcl.Utils.getCluster(c, this.getDevice().manufacturerID));
    }
    /*
     * CRUD
     */
    static fromDatabaseRecord(record, deviceNetworkAddress, deviceIeeeAddress) {
        // Migrate attrs to attributes
        for (const entry of Object.values(record.clusters).filter((e) => e.hasOwnProperty('attrs'))) {
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            entry.attributes = entry.attrs;
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            delete entry.attrs;
        }
        return new Endpoint(record.epId, record.profId, record.devId, record.inClusterList, record.outClusterList, deviceNetworkAddress, deviceIeeeAddress, record.clusters, record.binds || [], record.configuredReportings || [], record.meta || {});
    }
    toDatabaseRecord() {
        return {
            profId: this.profileID, epId: this.ID, devId: this.deviceID,
            inClusterList: this.inputClusters, outClusterList: this.outputClusters, clusters: this.clusters,
            binds: this._binds, configuredReportings: this._configuredReportings, meta: this.meta,
        };
    }
    static create(ID, profileID, deviceID, inputClusters, outputClusters, deviceNetworkAddress, deviceIeeeAddress) {
        return new Endpoint(ID, profileID, deviceID, inputClusters, outputClusters, deviceNetworkAddress, deviceIeeeAddress, {}, [], [], {});
    }
    saveClusterAttributeKeyValue(clusterKey, list) {
        const cluster = Zcl.Utils.getCluster(clusterKey);
        if (!this.clusters[cluster.name])
            this.clusters[cluster.name] = { attributes: {} };
        for (const [attribute, value] of Object.entries(list)) {
            this.clusters[cluster.name].attributes[attribute] = value;
        }
    }
    getClusterAttributeValue(clusterKey, attributeKey) {
        const cluster = Zcl.Utils.getCluster(clusterKey);
        const attribute = cluster.getAttribute(attributeKey);
        if (this.clusters[cluster.name] && this.clusters[cluster.name].attributes) {
            return this.clusters[cluster.name].attributes[attribute.name];
        }
        return null;
    }
    hasPendingRequests() {
        return this.pendingRequests.size > 0;
    }
    async sendPendingRequests(fastPolling) {
        return this.pendingRequests.send(fastPolling);
    }
    async sendRequest(frame, options, func = (d) => {
        return entity_1.default.adapter.sendZclFrameToEndpoint(this.deviceIeeeAddress, this.deviceNetworkAddress, this.ID, d, options.timeout, options.disableResponse, options.disableRecovery, options.srcEndpoint);
    }) {
        const logPrefix = `Request Queue (${this.deviceIeeeAddress}/${this.ID}): `;
        const request = new request_1.default(func, frame, this.getDevice().pendingRequestTimeout, options.sendPolicy);
        if (request.sendPolicy !== 'bulk') {
            // Check if such a request is already in the queue and remove the old one(s) if necessary
            this.pendingRequests.filter(request);
        }
        // send without queueing if sendPolicy is 'immediate' or if the device has no timeout set
        if (request.sendPolicy === 'immediate'
            || !this.getDevice().pendingRequestTimeout) {
            if (this.getDevice().pendingRequestTimeout > 0) {
                debug.info(logPrefix + `send ${frame.getCommand().name} request immediately ` +
                    `(sendPolicy=${options.sendPolicy})`);
            }
            return request.send();
        }
        // If this is a bulk message, we queue directly.
        if (request.sendPolicy === 'bulk') {
            debug.info(logPrefix + `queue request (${this.pendingRequests.size})))`);
            return this.pendingRequests.queue(request);
        }
        try {
            debug.info(logPrefix + `send request`);
            return await request.send();
        }
        catch (error) {
            // If we got a failed transaction, the device is likely sleeping.
            // Queue for transmission later.
            debug.info(logPrefix + `queue request (transaction failed)`);
            return this.pendingRequests.queue(request);
        }
    }
    /*
     * Zigbee functions
     */
    checkStatus(payload) {
        const codes = Array.isArray(payload) ? payload.map((i) => i.status) : [payload.statusCode];
        const invalid = codes.find((c) => c !== Zcl.Status.SUCCESS);
        if (invalid)
            throw new Zcl.ZclStatusError(invalid);
    }
    async report(clusterKey, attributes, options) {
        const cluster = Zcl.Utils.getCluster(clusterKey);
        const payload = [];
        for (const [nameOrID, value] of Object.entries(attributes)) {
            if (cluster.hasAttribute(nameOrID)) {
                const attribute = cluster.getAttribute(nameOrID);
                payload.push({ attrId: attribute.ID, attrData: value, dataType: attribute.type });
            }
            else if (!isNaN(Number(nameOrID))) {
                payload.push({ attrId: Number(nameOrID), attrData: value.value, dataType: value.type });
            }
            else {
                throw new Error(`Unknown attribute '${nameOrID}', specify either an existing attribute or a number`);
            }
        }
        await this.zclCommand(clusterKey, "report", payload, options, attributes);
    }
    async write(clusterKey, attributes, options) {
        const cluster = Zcl.Utils.getCluster(clusterKey);
        options = this.getOptionsWithDefaults(options, true, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
        options.manufacturerCode = this.ensureManufacturerCodeIsUniqueAndGet(cluster, Object.keys(attributes), options.manufacturerCode, 'write');
        const payload = [];
        for (const [nameOrID, value] of Object.entries(attributes)) {
            if (cluster.hasAttribute(nameOrID)) {
                const attribute = cluster.getAttribute(nameOrID);
                payload.push({ attrId: attribute.ID, attrData: value, dataType: attribute.type });
            }
            else if (!isNaN(Number(nameOrID))) {
                payload.push({ attrId: Number(nameOrID), attrData: value.value, dataType: value.type });
            }
            else {
                throw new Error(`Unknown attribute '${nameOrID}', specify either an existing attribute or a number`);
            }
        }
        await this.zclCommand(clusterKey, options.writeUndiv ? "writeUndiv" : "write", payload, options, attributes, true);
    }
    async writeResponse(clusterKey, transactionSequenceNumber, attributes, options) {
        (0, assert_1.default)(!options || !options.hasOwnProperty('transactionSequenceNumber'), 'Use parameter');
        const cluster = Zcl.Utils.getCluster(clusterKey);
        const payload = [];
        for (const [nameOrID, value] of Object.entries(attributes)) {
            if (value.hasOwnProperty('status')) {
                if (cluster.hasAttribute(nameOrID)) {
                    const attribute = cluster.getAttribute(nameOrID);
                    payload.push({ attrId: attribute.ID, status: value.status });
                }
                else if (!isNaN(Number(nameOrID))) {
                    payload.push({ attrId: Number(nameOrID), status: value.status });
                }
                else {
                    throw new Error(`Unknown attribute '${nameOrID}', specify either an existing attribute or a number`);
                }
            }
            else {
                throw new Error(`Missing attribute 'status'`);
            }
        }
        await this.zclCommand(clusterKey, 'writeRsp', payload, { direction: Zcl.Direction.SERVER_TO_CLIENT, ...options, transactionSequenceNumber }, attributes);
    }
    async read(clusterKey, attributes, options) {
        const cluster = Zcl.Utils.getCluster(clusterKey);
        options = this.getOptionsWithDefaults(options, true, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
        options.manufacturerCode = this.ensureManufacturerCodeIsUniqueAndGet(cluster, attributes, options.manufacturerCode, 'read');
        const payload = [];
        for (const attribute of attributes) {
            payload.push({ attrId: typeof attribute === 'number' ? attribute : cluster.getAttribute(attribute).ID });
        }
        const result = await this.zclCommand(clusterKey, 'read', payload, options, attributes, true);
        if (result) {
            return ZclFrameConverter.attributeKeyValue(result.frame, this.getDevice().manufacturerID);
        }
        else {
            return null;
        }
    }
    async readResponse(clusterKey, transactionSequenceNumber, attributes, options) {
        (0, assert_1.default)(!options || !options.hasOwnProperty('transactionSequenceNumber'), 'Use parameter');
        const cluster = Zcl.Utils.getCluster(clusterKey);
        const payload = [];
        for (const [nameOrID, value] of Object.entries(attributes)) {
            if (cluster.hasAttribute(nameOrID)) {
                const attribute = cluster.getAttribute(nameOrID);
                payload.push({ attrId: attribute.ID, attrData: value, dataType: attribute.type, status: 0 });
            }
            else if (!isNaN(Number(nameOrID))) {
                payload.push({ attrId: Number(nameOrID), attrData: value.value, dataType: value.type, status: 0 });
            }
            else {
                throw new Error(`Unknown attribute '${nameOrID}', specify either an existing attribute or a number`);
            }
        }
        await this.zclCommand(clusterKey, 'readRsp', payload, { direction: Zcl.Direction.SERVER_TO_CLIENT, ...options, transactionSequenceNumber }, attributes);
    }
    addBinding(clusterKey, target) {
        const cluster = Zcl.Utils.getCluster(clusterKey);
        if (typeof target === 'number') {
            target = group_1.default.byGroupID(target) || group_1.default.create(target);
        }
        if (!this.binds.find((b) => b.cluster.ID === cluster.ID && b.target === target)) {
            if (target instanceof group_1.default) {
                this._binds.push({ cluster: cluster.ID, groupID: target.groupID, type: 'group' });
            }
            else {
                this._binds.push({
                    cluster: cluster.ID, type: 'endpoint', deviceIeeeAddress: target.deviceIeeeAddress,
                    endpointID: target.ID
                });
            }
            this.save();
        }
    }
    async bind(clusterKey, target) {
        const cluster = Zcl.Utils.getCluster(clusterKey);
        const type = target instanceof Endpoint ? 'endpoint' : 'group';
        if (typeof target === 'number') {
            target = group_1.default.byGroupID(target) || group_1.default.create(target);
        }
        const destinationAddress = target instanceof Endpoint ? target.deviceIeeeAddress : target.groupID;
        const log = `Bind ${this.deviceIeeeAddress}/${this.ID} ${cluster.name} from ` +
            `'${target instanceof Endpoint ? `${destinationAddress}/${target.ID}` : destinationAddress}'`;
        debug.info(log);
        try {
            await entity_1.default.adapter.bind(this.deviceNetworkAddress, this.deviceIeeeAddress, this.ID, cluster.ID, destinationAddress, type, target instanceof Endpoint ? target.ID : null);
            this.addBinding(clusterKey, target);
        }
        catch (error) {
            error.message = `${log} failed (${error.message})`;
            debug.error(error.message);
            throw error;
        }
    }
    save() {
        this.getDevice().save();
    }
    async unbind(clusterKey, target) {
        const cluster = Zcl.Utils.getCluster(clusterKey);
        const type = target instanceof Endpoint ? 'endpoint' : 'group';
        const destinationAddress = target instanceof Endpoint ? target.deviceIeeeAddress : (target instanceof group_1.default ? target.groupID : target);
        const log = `Unbind ${this.deviceIeeeAddress}/${this.ID} ${cluster.name} from ` +
            `'${target instanceof Endpoint ? `${destinationAddress}/${target.ID}` : destinationAddress}'`;
        debug.info(log);
        try {
            await entity_1.default.adapter.unbind(this.deviceNetworkAddress, this.deviceIeeeAddress, this.ID, cluster.ID, destinationAddress, type, target instanceof Endpoint ? target.ID : null);
            if (typeof target === 'number' && group_1.default.byGroupID(target)) {
                target = group_1.default.byGroupID(target);
            }
            const index = this.binds.findIndex((b) => b.cluster.ID === cluster.ID && b.target === target);
            if (index !== -1) {
                this._binds.splice(index, 1);
                this.save();
            }
        }
        catch (error) {
            error.message = `${log} failed (${error.message})`;
            debug.error(error.message);
            throw error;
        }
    }
    async defaultResponse(commandID, status, clusterID, transactionSequenceNumber, options) {
        (0, assert_1.default)(!options || !options.hasOwnProperty('transactionSequenceNumber'), 'Use parameter');
        const payload = { cmdId: commandID, statusCode: status };
        await this.zclCommand(clusterID, 'defaultRsp', payload, { direction: Zcl.Direction.SERVER_TO_CLIENT, ...options, transactionSequenceNumber });
    }
    async configureReporting(clusterKey, items, options) {
        const cluster = Zcl.Utils.getCluster(clusterKey);
        options = this.getOptionsWithDefaults(options, true, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
        options.manufacturerCode = this.ensureManufacturerCodeIsUniqueAndGet(cluster, items, options.manufacturerCode, 'configureReporting');
        const payload = items.map((item) => {
            let dataType, attrId;
            if (typeof item.attribute === 'object') {
                dataType = item.attribute.type;
                attrId = item.attribute.ID;
            }
            else {
                /* istanbul ignore else */
                if (cluster.hasAttribute(item.attribute)) {
                    const attribute = cluster.getAttribute(item.attribute);
                    dataType = attribute.type;
                    attrId = attribute.ID;
                }
            }
            return {
                direction: Zcl.Direction.CLIENT_TO_SERVER,
                attrId, dataType,
                minRepIntval: item.minimumReportInterval,
                maxRepIntval: item.maximumReportInterval,
                repChange: item.reportableChange,
            };
        });
        await this.zclCommand(clusterKey, 'configReport', payload, options, items, true);
        for (const e of payload) {
            this._configuredReportings = this._configuredReportings.filter((c) => !(c.attrId === e.attrId && c.cluster === cluster.ID &&
                (!('manufacturerCode' in c) || c.manufacturerCode === options.manufacturerCode)));
        }
        for (const entry of payload) {
            if (entry.maxRepIntval !== 0xFFFF) {
                this._configuredReportings.push({
                    cluster: cluster.ID, attrId: entry.attrId, minRepIntval: entry.minRepIntval,
                    maxRepIntval: entry.maxRepIntval, repChange: entry.repChange,
                    manufacturerCode: options.manufacturerCode,
                });
            }
        }
        this.save();
    }
    async writeStructured(clusterKey, payload, options) {
        await this.zclCommand(clusterKey, 'writeStructured', payload, options);
        // TODO: support `writeStructuredResponse`
    }
    async command(clusterKey, commandKey, payload, options) {
        const result = await this.zclCommand(clusterKey, commandKey, payload, options, null, false, Zcl.FrameType.SPECIFIC);
        if (result) {
            return result.frame.Payload;
        }
    }
    async commandResponse(clusterKey, commandKey, payload, options, transactionSequenceNumber) {
        (0, assert_1.default)(!options || !options.hasOwnProperty('transactionSequenceNumber'), 'Use parameter');
        const cluster = Zcl.Utils.getCluster(clusterKey);
        const command = cluster.getCommandResponse(commandKey);
        transactionSequenceNumber = transactionSequenceNumber || zclTransactionSequenceNumber_1.default.next();
        options = this.getOptionsWithDefaults(options, true, Zcl.Direction.SERVER_TO_CLIENT, cluster.manufacturerCode);
        const frame = Zcl.ZclFrame.create(Zcl.FrameType.SPECIFIC, options.direction, options.disableDefaultResponse, options.manufacturerCode, transactionSequenceNumber, command.name, cluster.name, payload, options.reservedBits);
        const log = `CommandResponse ${this.deviceIeeeAddress}/${this.ID} ` +
            `${cluster.name}.${command.name}(${JSON.stringify(payload)}, ${JSON.stringify(options)})`;
        debug.info(log);
        try {
            await this.sendRequest(frame, options, async (f) => {
                // Broadcast Green Power responses
                if (this.ID === 242) {
                    await entity_1.default.adapter.sendZclFrameToAll(242, f, 242);
                }
                else {
                    await entity_1.default.adapter.sendZclFrameToEndpoint(this.deviceIeeeAddress, this.deviceNetworkAddress, this.ID, f, options.timeout, options.disableResponse, options.disableRecovery, options.srcEndpoint);
                }
            });
        }
        catch (error) {
            error.message = `${log} failed (${error.message})`;
            debug.error(error.message);
            throw error;
        }
    }
    waitForCommand(clusterKey, commandKey, transactionSequenceNumber, timeout) {
        const cluster = Zcl.Utils.getCluster(clusterKey);
        const command = cluster.getCommand(commandKey);
        const waiter = entity_1.default.adapter.waitFor(this.deviceNetworkAddress, this.ID, Zcl.FrameType.SPECIFIC, Zcl.Direction.CLIENT_TO_SERVER, transactionSequenceNumber, cluster.ID, command.ID, timeout);
        const promise = new Promise((resolve, reject) => {
            waiter.promise.then((payload) => resolve({ header: payload.frame.Header, payload: payload.frame.Payload }), (error) => reject(error));
        });
        return { promise, cancel: waiter.cancel };
    }
    getOptionsWithDefaults(options, disableDefaultResponse, direction, manufacturerCode) {
        const providedOptions = options || {};
        return {
            timeout: 10000,
            disableResponse: false,
            disableRecovery: false,
            disableDefaultResponse,
            direction,
            srcEndpoint: null,
            reservedBits: 0,
            manufacturerCode: manufacturerCode ? manufacturerCode : null,
            transactionSequenceNumber: null,
            writeUndiv: false,
            ...providedOptions
        };
    }
    ensureManufacturerCodeIsUniqueAndGet(cluster, attributes, fallbackManufacturerCode, caller) {
        const manufacturerCodes = new Set(attributes.map((nameOrID) => {
            let attributeID;
            if (typeof nameOrID == 'object') {
                // ConfigureReportingItem
                if (typeof nameOrID.attribute !== 'object') {
                    attributeID = nameOrID.attribute;
                }
                else {
                    return fallbackManufacturerCode;
                }
            }
            else {
                // string || number
                attributeID = nameOrID;
            }
            // we fall back to caller|cluster provided manufacturerCode
            if (cluster.hasAttribute(attributeID)) {
                const attribute = cluster.getAttribute(attributeID);
                return (attribute.manufacturerCode === undefined) ?
                    fallbackManufacturerCode :
                    attribute.manufacturerCode;
            }
            else {
                // unknown attribute, we should not fail on this here
                return fallbackManufacturerCode;
            }
        }));
        if (manufacturerCodes.size == 1) {
            return manufacturerCodes.values().next().value;
        }
        else {
            throw new Error(`Cannot have attributes with different manufacturerCode in single '${caller}' call`);
        }
    }
    async addToGroup(group) {
        await this.command('genGroups', 'add', { groupid: group.groupID, groupname: '' });
        group.addMember(this);
    }
    /**
     * Remove endpoint from a group, accepts both a Group and number as parameter.
     * The number parameter type should only be used when removing from a group which is not known
     * to zigbee-herdsman.
     */
    async removeFromGroup(group) {
        await this.command('genGroups', 'remove', { groupid: group instanceof group_1.default ? group.groupID : group });
        if (group instanceof group_1.default) {
            group.removeMember(this);
        }
    }
    async removeFromAllGroups() {
        await this.command('genGroups', 'removeAll', {}, { disableDefaultResponse: true });
        this.removeFromAllGroupsDatabase();
    }
    removeFromAllGroupsDatabase() {
        for (const group of group_1.default.all()) {
            if (group.hasMember(this)) {
                group.removeMember(this);
            }
        }
    }
    async zclCommand(clusterKey, commandKey, payload, options, logPayload, checkStatus = false, frameType = Zcl.FrameType.GLOBAL) {
        const cluster = Zcl.Utils.getCluster(clusterKey);
        const command = (frameType == Zcl.FrameType.GLOBAL) ? Zcl.Utils.getGlobalCommand(commandKey) : cluster.getCommand(commandKey);
        const hasResponse = (frameType == Zcl.FrameType.GLOBAL) ? true : command.hasOwnProperty('response');
        options = this.getOptionsWithDefaults(options, hasResponse, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
        const frame = Zcl.ZclFrame.create(frameType, options.direction, options.disableDefaultResponse, options.manufacturerCode, options.transactionSequenceNumber ?? zclTransactionSequenceNumber_1.default.next(), command.name, cluster.name, payload, options.reservedBits);
        const log = `ZCL command ${this.deviceIeeeAddress}/${this.ID} ` +
            `${cluster.name}.${command.name}(${JSON.stringify((logPayload) ? logPayload : payload)}, ${JSON.stringify(options)})`;
        debug.info(log);
        try {
            const result = await this.sendRequest(frame, options);
            if (result && checkStatus && !options.disableResponse) {
                this.checkStatus(result.frame.Payload);
            }
            return result;
        }
        catch (error) {
            error.message = `${log} failed (${error.message})`;
            debug.error(error.message);
            throw error;
        }
    }
}
exports.default = Endpoint;
//# sourceMappingURL=endpoint.js.map