UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

1,341 lines 67.7 kB
"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 () {
    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.definitions = void 0;
const node_assert_1 = __importDefault(require("node:assert"));
const semver_1 = require("semver");
const zigbee_herdsman_1 = require("zigbee-herdsman");
const fz = __importStar(require("../converters/fromZigbee"));
const tz = __importStar(require("../converters/toZigbee"));
const constants = __importStar(require("../lib/constants"));
const exposes = __importStar(require("../lib/exposes"));
const logger_1 = require("../lib/logger");
const m = __importStar(require("../lib/modernExtend"));
const reporting = __importStar(require("../lib/reporting"));
const ubisys_1 = require("../lib/ubisys");
const utils = __importStar(require("../lib/utils"));
const NS = "zhc:ubisys";
const e = exposes.presets;
const ea = exposes.access;
const manufacturerOptions = {
    /*
     * Ubisys doesn't accept a manufacturerCode on some commands
     * This bug has been reported, but it has not been fixed:
     * https://github.com/Koenkk/zigbee-herdsman/issues/52
     */
    ubisys: { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.UBISYS_TECHNOLOGIES_GMBH },
    // @ts-expect-error ignore
    ubisysNull: { manufacturerCode: null },
};
// Maximum payload of a single structured write. The adapter reports the limit it can send, but it
// is not exposed to converters, and the usable APS payload shrinks further with source routing.
// Use the largest payload the previous per-template chunking already produced (`dimmer_single`,
// four records) so that no request is larger than one that already worked.
const MAX_STRUCTURED_WRITE_BYTES = 56;
const ubisys = {
    fz: {
        dimmer_setup: {
            cluster: "manuSpecificUbisysDimmerSetup",
            type: ["attributeReport", "readResponse"],
            convert: (model, msg, publish, options, meta) => {
                if (msg.data.capabilities !== undefined) {
                    const capabilities = msg.data.capabilities;
                    const forwardPhaseControl = capabilities & 1;
                    const reversePhaseControl = (capabilities & 2) >>> 1;
                    const reactanceDiscriminator = (capabilities & 0x20) >>> 5;
                    const configurableCurve = (capabilities & 0x40) >>> 6;
                    const overloadDetection = (capabilities & 0x80) >>> 7;
                    return {
                        capabilities_forward_phase_control: !!forwardPhaseControl,
                        capabilities_reverse_phase_control: !!reversePhaseControl,
                        capabilities_reactance_discriminator: !!reactanceDiscriminator,
                        capabilities_configurable_curve: !!configurableCurve,
                        capabilities_overload_detection: !!overloadDetection,
                    };
                }
                if (msg.data.status !== undefined) {
                    const status = msg.data.status;
                    const forwardPhaseControl = status & 1;
                    const reversePhaseControl = (status & 2) >>> 1;
                    const overload = (status & 8) >>> 3;
                    const capacitiveLoad = (status & 0x40) >>> 6;
                    const inductiveLoad = (status & 0x80) >>> 7;
                    return {
                        status_forward_phase_control: !!forwardPhaseControl,
                        status_reverse_phase_control: !!reversePhaseControl,
                        status_overload: !!overload,
                        status_capacitive_load: !!capacitiveLoad,
                        status_inductive_load: !!inductiveLoad,
                    };
                }
                if (msg.data.mode !== undefined) {
                    const mode = msg.data.mode;
                    const phaseControl = mode & 3;
                    const phaseControlValues = { 0: "automatic", 1: "forward", 2: "reverse" };
                    return {
                        mode_phase_control: utils.getFromLookup(phaseControl, phaseControlValues),
                    };
                }
            },
        },
        // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
        dimmer_setup_genLevelCtrl: {
            cluster: "genLevelCtrl",
            type: ["attributeReport", "readResponse"],
            convert: (model, msg, publish, options, meta) => {
                if (msg.data.ubisysMinimumOnLevel !== undefined) {
                    return { minimum_on_level: msg.data.ubisysMinimumOnLevel };
                }
            },
        },
        configure_device_setup: {
            cluster: "manuSpecificUbisysDeviceSetup",
            type: ["attributeReport", "readResponse"],
            convert: (model, msg, publish, options, meta) => {
                const result = (meta.state.configure_device_setup !== undefined ? meta.state.configure_device_setup : {});
                if (msg.data.inputConfigurations != null) {
                    result.input_configurations = msg.data.inputConfigurations;
                }
                if (msg.data.inputActions != null) {
                    result.input_actions = msg.data.inputActions.map((el) => Object.values(el));
                }
                return { configure_device_setup: result };
            },
        },
        operational_status: {
            cluster: "closuresWindowCovering",
            type: ["attributeReport", "readResponse"],
            convert: (model, msg, publish, options, meta) => {
                if (msg.data.operationalStatus !== undefined) {
                    const status = msg.data.operationalStatus;
                    const operationalStatus = status & 3;
                    const operationalStatusValues = { 0: "stopped", 1: "opening", 2: "closing" };
                    return {
                        motor_state: utils.getFromLookup(operationalStatus, operationalStatusValues),
                    };
                }
            },
        },
    },
    tz: {
        configure_j1: {
            key: ["configure_j1"],
            convertSet: async (entity, key, value, meta) => {
                const log = (message) => {
                    logger_1.logger.warning(`ubisys: ${message}`, NS);
                };
                const waitUntilStopped = async () => {
                    let operationalStatus = 0;
                    do {
                        await utils.sleep(2000);
                        const response = await entity.read("closuresWindowCovering", [
                            "operationalStatus",
                        ]);
                        operationalStatus = response.operationalStatus;
                    } while (operationalStatus !== 0);
                    await utils.sleep(2000);
                };
                const writeAttrFromJson = async (attr, jsonAttr, converterFunc, delaySecondsAfter) => {
                    if (!jsonAttr)
                        jsonAttr = attr;
                    if (jsonAttr.startsWith("ubisys")) {
                        jsonAttr = jsonAttr.substring(6, 7).toLowerCase() + jsonAttr.substring(7);
                    }
                    if (value[jsonAttr] !== undefined) {
                        let attrValue = value[jsonAttr];
                        if (converterFunc) {
                            attrValue = converterFunc(attrValue);
                        }
                        const attributes = { [attr]: attrValue };
                        await entity.write("closuresWindowCovering", attributes, manufacturerOptions.ubisys);
                        if (delaySecondsAfter) {
                            await utils.sleep(delaySecondsAfter * 1000);
                        }
                    }
                };
                const stepsPerSecond = value.steps_per_second || 50;
                const hasCalibrate = value.calibrate != null;
                // cancel any running calibration
                let mode = (await entity.read("closuresWindowCovering", ["windowCoveringMode"])).windowCoveringMode;
                const modeCalibrationBitMask = 0x02;
                if (mode & modeCalibrationBitMask) {
                    await entity.write("closuresWindowCovering", {
                        windowCoveringMode: mode & ~modeCalibrationBitMask,
                    });
                    await utils.sleep(2000);
                }
                // delay a bit if reconfiguring basic configuration attributes
                await writeAttrFromJson("ubisysWindowCoveringType", undefined, undefined, 2);
                await writeAttrFromJson("ubisysConfigStatus", undefined, undefined, 2);
                // @ts-expect-error ignore
                if (await writeAttrFromJson("windowCoveringMode", undefined, undefined, 2)) {
                    mode = value.windowCoveringMode;
                }
                if (hasCalibrate) {
                    log("Cover calibration starting...");
                    // first of all, move to top position to not confuse calibration later
                    log("  Moving cover to top position to get a good starting point...");
                    await entity.command("closuresWindowCovering", "upOpen", {});
                    await waitUntilStopped();
                    log("  Settings some attributes...");
                    // reset attributes
                    await entity.write("closuresWindowCovering", {
                        ubisysInstalledOpenLimitLiftCm: 0,
                        ubisysInstalledClosedLimitLiftCm: 240,
                        ubisysInstalledOpenLimitTiltDdegree: 0,
                        ubisysInstalledClosedLimitTiltDdegree: 900,
                        ubisysLiftToTiltTransitionSteps: 0xffff,
                        ubisysTotalSteps: 0xffff,
                        ubisysLiftToTiltTransitionSteps2: 0xffff,
                        ubisysTotalSteps2: 0xffff,
                    }, manufacturerOptions.ubisys);
                    // enable calibration mode
                    await utils.sleep(2000);
                    await entity.write("closuresWindowCovering", {
                        windowCoveringMode: mode | modeCalibrationBitMask,
                    });
                    await utils.sleep(2000);
                    // move down a bit and back up to detect upper limit
                    log("  Moving cover down a bit...");
                    await entity.command("closuresWindowCovering", "downClose", {});
                    await utils.sleep(5000);
                    await entity.command("closuresWindowCovering", "stop", {});
                    await utils.sleep(2000);
                    log("  Moving up again to detect upper limit...");
                    await entity.command("closuresWindowCovering", "upOpen", {});
                    await waitUntilStopped();
                    log("  Moving down to count steps from open to closed...");
                    await entity.command("closuresWindowCovering", "downClose", {});
                    await waitUntilStopped();
                    log("  Moving up to count steps from closed to open...");
                    await entity.command("closuresWindowCovering", "upOpen", {});
                    await waitUntilStopped();
                }
                // now write any attribute values present in JSON
                await writeAttrFromJson("ubisysInstalledOpenLimitLiftCm");
                await writeAttrFromJson("ubisysInstalledClosedLimitLiftCm");
                await writeAttrFromJson("ubisysInstalledOpenLimitTiltDdegree");
                await writeAttrFromJson("ubisysInstalledClosedLimitTiltDdegree");
                await writeAttrFromJson("ubisysTurnaroundGuardTime");
                await writeAttrFromJson("ubisysLiftToTiltTransitionSteps");
                await writeAttrFromJson("ubisysTotalSteps");
                await writeAttrFromJson("ubisysLiftToTiltTransitionSteps2");
                await writeAttrFromJson("ubisysTotalSteps2");
                await writeAttrFromJson("ubisysAdditionalSteps");
                await writeAttrFromJson("ubisysInactivePowerThreshold");
                await writeAttrFromJson("ubisysStartupSteps");
                // some convenience functions to not have to calculate
                await writeAttrFromJson("ubisysTotalSteps", "open_to_closed_s", (s) => s * stepsPerSecond);
                await writeAttrFromJson("ubisysTotalSteps2", "closed_to_open_s", (s) => s * stepsPerSecond);
                await writeAttrFromJson("ubisysLiftToTiltTransitionSteps", "lift_to_tilt_transition_ms", (s) => (s * stepsPerSecond) / 1000);
                await writeAttrFromJson("ubisysLiftToTiltTransitionSteps2", "lift_to_tilt_transition_ms", (s) => (s * stepsPerSecond) / 1000);
                if (hasCalibrate) {
                    log("  Finalizing calibration...");
                    // disable calibration mode again
                    await utils.sleep(2000);
                    await entity.write("closuresWindowCovering", {
                        windowCoveringMode: mode & ~modeCalibrationBitMask,
                    });
                    await utils.sleep(2000);
                    // re-read and dump all relevant attributes
                    log("  Done - will now read back the results.");
                    await ubisys.tz.configure_j1.convertGet(entity, key, meta);
                }
            },
            convertGet: async (entity, key, meta) => {
                const log = (json) => {
                    logger_1.logger.warning(`ubisys: Cover configuration read: ${JSON.stringify(json)}`, NS);
                };
                log(await entity.read("closuresWindowCovering", [
                    "windowCoveringType",
                    "physicalClosedLimitLiftCm",
                    "physicalClosedLimitTiltDdegree",
                    "installedOpenLimitLiftCm",
                    "installedClosedLimitLiftCm",
                    "installedOpenLimitTiltDdegree",
                    "installedClosedLimitTiltDdegree",
                ]));
                log(await entity.read("closuresWindowCovering", [
                    "configStatus",
                    "windowCoveringMode",
                    "currentPositionLiftPercentage",
                    "currentPositionLiftCm",
                    "currentPositionTiltPercentage",
                    "currentPositionTiltDdegree",
                    "operationalStatus",
                ]));
                log(await entity.read("closuresWindowCovering", [
                    "ubisysTurnaroundGuardTime",
                    "ubisysLiftToTiltTransitionSteps",
                    "ubisysTotalSteps",
                    "ubisysLiftToTiltTransitionSteps2",
                    "ubisysTotalSteps2",
                    "ubisysAdditionalSteps",
                    "ubisysInactivePowerThreshold",
                    "ubisysStartupSteps",
                ], manufacturerOptions.ubisys));
            },
        },
        dimmer_setup: {
            key: [
                "capabilities_forward_phase_control",
                "capabilities_reverse_phase_control",
                "capabilities_reactance_discriminator",
                "capabilities_configurable_curve",
                "capabilities_overload_detection",
                "status_forward_phase_control",
                "status_reverse_phase_control",
                "status_overload",
                "status_capacitive_load",
                "status_inductive_load",
                "mode_phase_control",
            ],
            convertSet: async (entity, key, value, meta) => {
                if (key === "mode_phase_control") {
                    utils.assertString(value, "mode_phase_control");
                    const phaseControl = value.toLowerCase();
                    const phaseControlValues = { automatic: 0, forward: 1, reverse: 2 };
                    utils.validateValue(phaseControl, Object.keys(phaseControlValues));
                    await entity.write("manuSpecificUbisysDimmerSetup", { mode: utils.getFromLookup(phaseControl, phaseControlValues) }, manufacturerOptions.ubisysNull);
                }
                await ubisys.tz.dimmer_setup.convertGet(entity, key, meta);
            },
            convertGet: async (entity, key, meta) => {
                await entity.read("manuSpecificUbisysDimmerSetup", ["capabilities"], manufacturerOptions.ubisysNull);
                await entity.read("manuSpecificUbisysDimmerSetup", ["status"], manufacturerOptions.ubisysNull);
                await entity.read("manuSpecificUbisysDimmerSetup", ["mode"], manufacturerOptions.ubisysNull);
            },
        },
        // biome-ignore lint/style/useNamingConvention: ignored using `--suppress`
        dimmer_setup_genLevelCtrl: {
            key: ["minimum_on_level"],
            convertSet: async (entity, key, value, meta) => {
                if (key === "minimum_on_level") {
                    await entity.write("genLevelCtrl", { ubisysMinimumOnLevel: value }, manufacturerOptions.ubisys);
                }
                await ubisys.tz.dimmer_setup_genLevelCtrl.convertGet(entity, key, meta);
            },
            convertGet: async (entity, key, meta) => {
                await entity.read("genLevelCtrl", ["ubisysMinimumOnLevel"], manufacturerOptions.ubisys);
            },
        },
        configure_device_setup: {
            key: ["configure_device_setup"],
            convertSet: async (entity, key, value, meta) => {
                const devMgmtEp = meta.device.getEndpoint(232);
                const customCluster = meta.device.customClusters["manuSpecificUbisysDeviceSetup"];
                (0, node_assert_1.default)(customCluster);
                const attributeInputConfigurations = customCluster.attributes.inputConfigurations;
                const attributeInputActions = customCluster.attributes.inputActions;
                (0, node_assert_1.default)(attributeInputConfigurations && attributeInputActions);
                // ubisys switched to writeStructure a while ago, change log only goes back to 1.9.x
                // and it happened before that but to be safe we will only use writeStrucutre on 1.9.0 and up
                if (!meta.device.softwareBuildID ||
                    !(0, semver_1.valid)(meta.device.softwareBuildID, true) ||
                    !(0, semver_1.gte)(meta.device.softwareBuildID, "1.9.0", true)) {
                    logger_1.logger.warning(`ubisys: update firmware of '${meta.options.friendly_name}' before writing configure_device_setup!`, NS);
                    return;
                }
                if (value.input_configurations != null) {
                    // example: [0, 0, 0, 0]
                    await devMgmtEp.writeStructured("manuSpecificUbisysDeviceSetup", [
                        {
                            attrId: attributeInputConfigurations.ID,
                            selector: {},
                            dataType: zigbee_herdsman_1.Zcl.DataType.ARRAY,
                            elementData: {
                                elementType: zigbee_herdsman_1.Zcl.DataType.DATA8,
                                elements: value.input_configurations,
                            },
                        },
                    ], manufacturerOptions.ubisysNull);
                }
                if (value.input_actions != null) {
                    // example (default for C4): [[0,13,1,6,0,2], [1,13,2,6,0,2], [2,13,3,6,0,2], [3,13,4,6,0,2]]
                    await devMgmtEp.writeStructured("manuSpecificUbisysDeviceSetup", [
                        {
                            attrId: attributeInputActions.ID,
                            selector: {},
                            dataType: zigbee_herdsman_1.Zcl.DataType.ARRAY,
                            elementData: {
                                elementType: zigbee_herdsman_1.Zcl.DataType.OCTET_STR,
                                elements: value.input_actions,
                            },
                        },
                    ], manufacturerOptions.ubisysNull);
                }
                if (value.input_action_templates != null) {
                    const templateTypes = {
                        // source: "Zigbee Device Physical Input Configurations Integrator’s Guide"
                        // (can be obtained directly from ubisys upon request)
                        toggle: {
                            getInputActions: (input, endpoint) => [[input, 0x0d, endpoint, 0x06, 0x00, 0x02]],
                        },
                        toggle_switch: {
                            getInputActions: (input, endpoint) => [
                                [input, 0x0d, endpoint, 0x06, 0x00, 0x02],
                                [input, 0x03, endpoint, 0x06, 0x00, 0x02],
                            ],
                        },
                        on_off_switch: {
                            getInputActions: (input, endpoint) => [
                                [input, 0x0d, endpoint, 0x06, 0x00, 0x01],
                                [input, 0x03, endpoint, 0x06, 0x00, 0x00],
                            ],
                        },
                        on: {
                            getInputActions: (input, endpoint) => [[input, 0x0d, endpoint, 0x06, 0x00, 0x01]],
                        },
                        off: {
                            getInputActions: (input, endpoint) => [[input, 0x0d, endpoint, 0x06, 0x00, 0x00]],
                        },
                        dimmer_single: {
                            getInputActions: (input, endpoint, template) => {
                                const moveUpCmd = template.no_onoff || template.no_onoff_up ? 0x01 : 0x05;
                                const moveDownCmd = template.no_onoff || template.no_onoff_down ? 0x01 : 0x05;
                                const moveRate = template.rate || 50;
                                return [
                                    [input, 0x07, endpoint, 0x06, 0x00, 0x02],
                                    [input, 0x86, endpoint, 0x08, 0x00, moveUpCmd, 0x00, moveRate],
                                    [input, 0xc6, endpoint, 0x08, 0x00, moveDownCmd, 0x01, moveRate],
                                    [input, 0x0b, endpoint, 0x08, 0x00, 0x03],
                                ];
                            },
                        },
                        dimmer_double: {
                            doubleInputs: true,
                            getInputActions: (inputs, endpoint, template) => {
                                const moveUpCmd = template.no_onoff || template.no_onoff_up ? 0x01 : 0x05;
                                const moveDownCmd = template.no_onoff || template.no_onoff_down ? 0x01 : 0x05;
                                const moveRate = template.rate || 50;
                                return [
                                    [inputs[0], 0x07, endpoint, 0x06, 0x00, 0x01],
                                    [inputs[0], 0x06, endpoint, 0x08, 0x00, moveUpCmd, 0x00, moveRate],
                                    [inputs[0], 0x0b, endpoint, 0x08, 0x00, 0x03],
                                    [inputs[1], 0x07, endpoint, 0x06, 0x00, 0x00],
                                    [inputs[1], 0x06, endpoint, 0x08, 0x00, moveDownCmd, 0x01, moveRate],
                                    [inputs[1], 0x0b, endpoint, 0x08, 0x00, 0x03],
                                ];
                            },
                        },
                        cover: {
                            cover: true,
                            doubleInputs: true,
                            getInputActions: (inputs, endpoint) => [
                                [inputs[0], 0x0d, endpoint, 0x02, 0x01, 0x00],
                                [inputs[0], 0x07, endpoint, 0x02, 0x01, 0x02],
                                [inputs[1], 0x0d, endpoint, 0x02, 0x01, 0x01],
                                [inputs[1], 0x07, endpoint, 0x02, 0x01, 0x02],
                            ],
                        },
                        cover_switch: {
                            cover: true,
                            doubleInputs: true,
                            getInputActions: (inputs, endpoint) => [
                                [inputs[0], 0x0d, endpoint, 0x02, 0x01, 0x00],
                                [inputs[0], 0x03, endpoint, 0x02, 0x01, 0x02],
                                [inputs[1], 0x0d, endpoint, 0x02, 0x01, 0x01],
                                [inputs[1], 0x03, endpoint, 0x02, 0x01, 0x02],
                            ],
                        },
                        cover_up: {
                            cover: true,
                            getInputActions: (input, endpoint) => [[input, 0x0d, endpoint, 0x02, 0x01, 0x00]],
                        },
                        cover_down: {
                            cover: true,
                            getInputActions: (input, endpoint) => [[input, 0x0d, endpoint, 0x02, 0x01, 0x01]],
                        },
                        scene: {
                            scene: true,
                            getInputActions: (input, endpoint, groupId, sceneId) => [
                                [input, 0x07, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
                            ],
                            getInputActions2: (input, endpoint, groupId, sceneId) => [
                                [input, 0x06, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
                            ],
                        },
                        scene_switch: {
                            scene: true,
                            getInputActions: (input, endpoint, groupId, sceneId) => [
                                [input, 0x0d, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
                            ],
                            getInputActions2: (input, endpoint, groupId, sceneId) => [
                                [input, 0x03, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
                            ],
                        },
                    };
                    // first input
                    let input = 0;
                    if (Array.isArray(meta.mapped)) {
                        // first client endpoint - depends on actual device
                        throw new Error("Not supported for groups");
                    }
                    let endpoint = { S1: 2, S2: 3, D1: 2, J1: 2, C4: 1 }[meta.mapped.model];
                    // default group id
                    let groupId = 0;
                    const templates = Array.isArray(value.input_action_templates) ? value.input_action_templates : [value.input_action_templates];
                    const resultingInputActions = [];
                    for (const template of templates) {
                        const templateType = templateTypes[template.type];
                        if (!templateType) {
                            throw new Error(`input_action_templates: Template type '${template.type}' is not valid ` +
                                `(valid types: ${Object.keys(templateTypes)})`);
                        }
                        if (template.input !== undefined) {
                            input = template.input;
                        }
                        if (template.endpoint !== undefined) {
                            endpoint = template.endpoint;
                        }
                        // C4 cover endpoints only start at 5
                        if ("cover" in templateType && meta.mapped.model === "C4" && endpoint < 5) {
                            endpoint += 4;
                        }
                        let inputActions = [];
                        if ("doubleInputs" in templateType) {
                            // double inputs
                            const inputArr = template.inputs !== undefined ? template.inputs : [input, input + 1];
                            inputActions = templateType.getInputActions(inputArr, endpoint, template);
                            if (Array.isArray(inputArr)) {
                                input = Math.max(...inputArr);
                            }
                        }
                        else {
                            if ("scene" in templateType) {
                                // scene(s) (always single input)
                                if (template.scene_id === undefined) {
                                    throw new Error(`input_action_templates: Need an attribute 'scene_id' for '${template.type}'`);
                                }
                                if (template.group_id !== undefined) {
                                    groupId = template.group_id;
                                }
                                inputActions = templateType.getInputActions(input, endpoint, groupId, template.scene_id);
                                if (template.scene_id_2 !== undefined) {
                                    if (template.group_id_2 !== undefined) {
                                        groupId = template.group_id_2;
                                    }
                                    inputActions = inputActions.concat(templateType.getInputActions2(input, endpoint, groupId, template.scene_id_2));
                                }
                            }
                            else {
                                // single input, no scene(s)
                                inputActions = templateType.getInputActions(input, endpoint, template);
                            }
                        }
                        resultingInputActions.push(inputActions);
                        logger_1.logger.warning(`ubisys: Using input ${input} and endpoint ${endpoint} for '${template.type}'.`, NS);
                        input += 1;
                        endpoint += 1;
                    }
                    // XXX: input_action_templates is not validated
                    //      it could potentially write values that don't fit OCTET_STR (e.g. value at x index is > 0xff)
                    const flatInputActions = resultingInputActions.flat();
                    // write length of octet str array at index 0
                    await devMgmtEp.writeStructured("manuSpecificUbisysDeviceSetup", [
                        {
                            attrId: attributeInputActions.ID,
                            selector: { indicatorType: zigbee_herdsman_1.Zcl.StructuredIndicatorType.Whole, indexes: [0] },
                            dataType: zigbee_herdsman_1.Zcl.DataType.UINT16,
                            elementData: flatInputActions.length,
                        },
                    ], manufacturerOptions.ubisysNull);
                    // Write in chunks to prevent frame overflow. Chunking per template is not
                    // enough: a single `dimmer_double` produces six records of 82 bytes total,
                    // which exceeds the APS payload limit once the ZCL header is added and
                    // fails with MESSAGE_TOO_LONG before the request is sent.
                    let chunk = [];
                    let chunkBytes = 0;
                    let index = 1;
                    const writeChunk = async () => {
                        if (chunk.length === 0)
                            return;
                        await devMgmtEp.writeStructured("manuSpecificUbisysDeviceSetup", chunk, manufacturerOptions.ubisysNull);
                        chunk = [];
                        chunkBytes = 0;
                    };
                    for (const inputAction of flatInputActions) {
                        // attrId (2) + selector indicator (1) + one index (2) + data type (1) + octet string length (1)
                        const recordBytes = 7 + inputAction.length;
                        if (chunkBytes + recordBytes > MAX_STRUCTURED_WRITE_BYTES) {
                            await writeChunk();
                        }
                        chunk.push({
                            attrId: attributeInputActions.ID,
                            selector: { indicatorType: zigbee_herdsman_1.Zcl.StructuredIndicatorType.Whole, indexes: [index++] },
                            dataType: zigbee_herdsman_1.Zcl.DataType.OCTET_STR,
                            elementData: Buffer.from(inputAction),
                        });
                        chunkBytes += recordBytes;
                    }
                    await writeChunk();
                }
            },
        },
    },
};
exports.definitions = [
    {
        zigbeeModel: ["S1 (5501)"],
        model: "S1",
        vendor: "Ubisys",
        description: "Power switch S1",
        fromZigbee: [ubisys.fz.configure_device_setup],
        toZigbee: [ubisys.tz.configure_device_setup],
        endpoint: (device) => {
            return { l1: 1, s1: 2 };
        },
        extend: [
            m.identify({ endpointNames: ["l1"] }),
            m.onOff({ powerOnBehavior: true }),
            m.electricityMeter({ cluster: "metering", configureReporting: false }),
            m.commandsOnOff({ endpointNames: ["2"] }),
            m.commandsLevelCtrl({ endpointNames: ["2"] }),
            m.commandsColorCtrl({ endpointNames: ["2"] }),
            ubisys_1.ubisysModernExtend.pollCurrentSummDelivered(3),
            ubisys_1.ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup(),
        ],
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(3);
            await reporting.bind(endpoint, coordinatorEndpoint, ["seMetering"]);
            await reporting.readMeteringMultiplierDivisor(endpoint);
            await reporting.instantaneousDemand(endpoint);
        },
        onEvent: (event) => {
            /*
             * As per technical doc page 18 section 7.3.4
             * https://www.ubisys.de/wp-content/uploads/ubisys-s1-technical-reference.pdf
             *
             * This cluster uses the binding table for managing command targets.
             * When factory fresh, this cluster is bound to endpoint #1 to
             * enable local control.
             *
             * We use addBinding to 'record' this default binding.
             */
            if (event.type === "deviceInterview") {
                const ep1 = event.data.device.getEndpoint(1);
                const ep2 = event.data.device.getEndpoint(2);
                ep2.addBinding("genOnOff", ep1);
            }
        },
        ota: true,
    },
    {
        zigbeeModel: ["S1-R (5601)"],
        model: "S1-R",
        vendor: "Ubisys",
        description: "Power switch S1-R",
        fromZigbee: [ubisys.fz.configure_device_setup],
        toZigbee: [ubisys.tz.configure_device_setup],
        endpoint: (device) => {
            return { l1: 1, s1: 2, s2: 3 };
        },
        extend: [
            m.identify({ endpointNames: ["l1"] }),
            m.onOff({ powerOnBehavior: true }),
            m.electricityMeter({ cluster: "metering", configureReporting: false }),
            m.commandsOnOff({ endpointNames: ["2", "3"] }),
            m.commandsLevelCtrl({ endpointNames: ["2", "3"] }),
            m.commandsColorCtrl({ endpointNames: ["2", "3"] }),
            ubisys_1.ubisysModernExtend.pollCurrentSummDelivered((device) => (device.hardwareVersion < 16 ? 4 : 1)),
            ubisys_1.ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup(),
        ],
        configure: async (device, coordinatorEndpoint) => {
            // Series 2 has metering on endpoint 1, older devices on endpoint 4
            // hardwareVersion is 16
            const endpoint = device.getEndpoint(device.hardwareVersion < 16 ? 4 : 1);
            await reporting.bind(endpoint, coordinatorEndpoint, ["seMetering"]);
            await reporting.readMeteringMultiplierDivisor(endpoint);
            await reporting.instantaneousDemand(endpoint);
        },
        onEvent: (event) => {
            /*
             * As per technical doc page 18 section 7.3.4
             * https://www.ubisys.de/wp-content/uploads/ubisys-s1-technical-reference.pdf
             *
             * This cluster uses the binding table for managing command targets.
             * When factory fresh, this cluster is bound to endpoint #1 to
             * enable local control.
             *
             * We use addBinding to 'record' this default binding.
             */
            if (event.type === "deviceInterview") {
                const ep1 = event.data.device.getEndpoint(1);
                const ep2 = event.data.device.getEndpoint(2);
                ep2.addBinding("genOnOff", ep1);
            }
        },
        ota: true,
    },
    {
        zigbeeModel: ["S2 (5502)", "S2-R (5602)"],
        model: "S2",
        vendor: "Ubisys",
        description: "Power switch S2",
        exposes: [
            e.switch().withEndpoint("l1"),
            e.switch().withEndpoint("l2"),
            e.action([
                "toggle_s1",
                "toggle_s2",
                "on_s1",
                "on_s2",
                "off_s1",
                "off_s2",
                "recall_*_s1",
                "recal_*_s2",
                "brightness_move_up_s1",
                "brightness_move_up_s2",
                "brightness_move_down_s1",
                "brightness_move_down_s2",
                "brightness_stop_s1",
                "brightness_stop_s2",
            ]),
            e.power_on_behavior().withEndpoint("l1"),
            e.power_on_behavior().withEndpoint("l2"),
            e.power().withAccess(ea.STATE_GET),
            e.energy().withAccess(ea.STATE_GET),
        ],
        fromZigbee: [
            fz.on_off,
            fz.metering,
            fz.command_toggle,
            fz.command_on,
            fz.command_off,
            fz.command_recall,
            fz.command_move,
            fz.command_stop,
            fz.power_on_behavior,
            ubisys.fz.configure_device_setup,
        ],
        toZigbee: [tz.on_off, tz.metering_power, ubisys.tz.configure_device_setup, tz.power_on_behavior, tz.currentsummdelivered],
        endpoint: (device) => {
            return { l1: 1, l2: 2, s1: 3, s2: 4 };
        },
        meta: { multiEndpoint: true, multiEndpointSkip: ["power", "energy"] },
        extend: [
            ubisys_1.ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup(),
            ubisys_1.ubisysModernExtend.pollCurrentSummDelivered(5),
            m.identify({ endpointNames: ["l1", "l2"] }),
        ],
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(5);
            await reporting.bind(endpoint, coordinatorEndpoint, ["seMetering"]);
            await reporting.readMeteringMultiplierDivisor(endpoint);
            await reporting.instantaneousDemand(endpoint);
        },
        onEvent: (event) => {
            /*
             * As per technical doc page 20 section 7.4.4 and
             *                      page 22 section 7.5.4
             * https://www.ubisys.de/wp-content/uploads/ubisys-s2-technical-reference.pdf
             *
             * This cluster uses the binding table for managing command targets.
             * When factory fresh, this cluster is bound to endpoint #1 to
             * enable local control.
             *
             * This cluster uses the binding table for managing command targets.
             * When factory fresh, this cluster is bound to endpoint #2 to
             * enable local control
             *
             * We use addBinding to 'record' this default binding.
             */
            if (event.type === "deviceInterview") {
                const ep1 = event.data.device.getEndpoint(1);
                const ep2 = event.data.device.getEndpoint(2);
                const ep3 = event.data.device.getEndpoint(3);
                const ep4 = event.data.device.getEndpoint(4);
                ep3.addBinding("genOnOff", ep1);
                ep4.addBinding("genOnOff", ep2);
            }
        },
        ota: true,
    },
    {
        zigbeeModel: ["D1 (5503)", "D1-R (5603)"],
        model: "D1",
        vendor: "Ubisys",
        description: "Universal dimmer D1",
        fromZigbee: [
            fz.on_off,
            fz.brightness,
            fz.metering,
            fz.command_toggle,
            fz.command_on,
            fz.command_off,
            fz.command_recall,
            fz.command_move,
            fz.command_stop,
            fz.lighting_ballast_configuration,
            fz.level_config,
            ubisys.fz.dimmer_setup,
            ubisys.fz.dimmer_setup_genLevelCtrl,
            ubisys.fz.configure_device_setup,
        ],
        toZigbee: [
            tz.light_onoff_brightness,
            tz.ballast_config,
            tz.level_config,
            ubisys.tz.dimmer_setup,
            ubisys.tz.dimmer_setup_genLevelCtrl,
            ubisys.tz.configure_device_setup,
            tz.ignore_transition,
            tz.light_brightness_move,
            tz.light_brightness_step,
            tz.metering_power,
            tz.currentsummdelivered,
        ],
        exposes: [
            e.action([
                "toggle_s1",
                "toggle_s2",
                "on_s1",
                "on_s2",
                "off_s1",
                "off_s2",
                "recall_*_s1",
                "recal_*_s2",
                "brightness_move_up_s1",
                "brightness_move_up_s2",
                "brightness_move_down_s1",
                "brightness_move_down_s2",
                "brightness_stop_s1",
                "brightness_stop_s2",
            ]),
            e.light_brightness(),
            e
                .composite("level_config", "level_config", ea.ALL)
                .withFeature(e
                .numeric("on_off_transition_time", ea.ALL)
                .withDescription("Specifies the amount of time, in units of 0.1 seconds, which will be used during a transition to " +
                "either the on or off state, when an on/off/toggle command of the on/off cluster is used to turn the light on or off"))
                .withFeature(e
                .numeric("on_level", ea.ALL)
                .withValueMin(1)
                .withValueMax(254)
                .withPreset("previous", 255, "Use previous value")
                .withDescription("Specifies the level that shall be applied, when an on/toggle command causes the light to turn on."))
                .withFeature(e
                .binary("execute_if_off", ea.ALL, true, false)
                .withDescription("Defines if you can send a brightness change without to turn on the light"))
                .withFeature(e
                .numeric("current_level_startup", ea.ALL)
                .withValueMin(1)
                .withValueMax(254)
                .withPreset("previous", 255, "Use previous value")
                .withDescription("Specifies the initial level to be applied after the device is supplied with power")),
            e.power().withAccess(ea.STATE_GET),
            e.energy().withAccess(ea.STATE_GET),
            e
                .numeric("ballast_minimum_level", ea.ALL)
                .withValueMin(1)
                .withValueMax(254)
                .withDescription("Specifies the minimum light output of the ballast"),
            e
                .numeric("ballast_maximum_level", ea.ALL)
                .withValueMin(1)
                .withValueMax(254)
                .withDescription("Specifies the maximum light output of the ballast"),
            e
                .numeric("minimum_on_level", ea.ALL)
                .withValueMin(0)
                .withValueMax(255)
                .withDescription("Specifies the minimum level that shall be applied, when an on/toggle command causes the " +
                "light to turn on. When this attribute is set to the invalid value (255) this feature is disabled " +
                "and standard rules apply: The light will either return to the previously active level (before it " +
                "was turned off) if the OnLevel attribute is set to the invalid value (255/previous); or to the specified " +
                "value of the OnLevel attribute if this value is in the range 0…254. Otherwise, if the " +
                "MinimumOnLevel is in the range 0…254, the light will be set to the the previously " +
                "active level (before it was turned off), or the value specified here, whichever is the larger " +
                "value. For example, if the previous level was 30 and the MinimumOnLevel was 40 then " +
                "the light would turn on and move to level 40. Conversely, if the previous level was 50, " +
                "and the MinimumOnLevel was 40, then the light would turn on and move to level 50."),
            e.binary("capabilities_forward_phase_control", ea.ALL, true, false).withDescription("The dimmer supports AC forward phase control."),
            e.binary("capabilities_reverse_phase_control", ea.ALL, true, false).withDescription("The dimmer supports AC reverse phase control."),
            e
                .binary("capabilities_reactance_discriminator", ea.ALL, true, false)
                .withDescription("The dimmer is capable of measuring the reactanceto distinguish inductive and capacitive loads."),
            e
                .binary("capabilities_configurable_curve", ea.ALL, true, false)
                .withDescription("The dimmer is capable of replacing the built-in, default dimming curve."),
            e
                .binary("capabilities_overload_detection", ea.ALL, true, false)
                .withDescription("The dimmer is capable of detecting an output overload and shutting the output off."),
            e
                .binary("status_forward_phase_control", ea.ALL, true, false)
                .withDescription("The dimmer is currently operating in AC forward phase control mode."),
            e
                .binary("status_reverse_phase_control", ea.ALL, true, false)
                .withDescription("The dimmer is currently operating in AC reverse phase control mode."),
            e
                .binary("status_overload", ea.ALL, true, false)
                .withDescription("The output is currently turned off, because the dimmer has detected an overload."),
            e
                .binary("status_capacitive_load", ea.ALL, true, false)
                .withDescription("The dimmer's reactance discriminator had detected a capacitive load."),
            e
                .binary("status_inductive_load", ea.ALL, true, false)
                .withDescription("The dimmer's reactance discriminator had detected an inductive load."),
            e.enum("mode_phase_control", ea.ALL, ["automatic", "forward", "reverse"]).withDescription("Configures the dimming technique."),
        ],
        extend: [
            ubisys_1.ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup(),
            ubisys_1.ubisysModernExtend.addCustomClusterManuSpecificUbisysDimmerSetup(),
            ubisys_1.ubisysModernExtend.addCustomClusterGenLevelCtrl(),
            ubisys_1.ubisysModernExtend.pollCurrentSummDelivered(4),
            m.identify({ endpointNames: ["default"] }),
        ],
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(4);
            await reporting.bind(endpoint, coordinatorEndpoint, ["seMetering"]);
            await reporting.readMeteringMultiplierDivisor(endpoint);
            await reporting.instantaneousDemand(endpoint);
        },
        meta: { multiEndpoint: true, multiEndpointSkip: ["state", "brightness", "power", "energy"] },
        endpoint: (device) => {
            return { default: 1, s1: 2, s2: 3 };
        },
        onEvent: (event) => {
            /*
             * As per technical doc page 23 section 7.3.4, 7.3.5
             * https://www.ubisys.de/wp-content/uploads/ubisys-d1-technical-reference.pdf
             *
             * We use addBinding to 'record' this default binding.
             */
            if (event.type === "deviceInterview") {
                const ep1 = event.data.device.getEndpoint(1);
                const ep2 = event.data.device.getEndpoint(2);
                ep2.addBinding("genOnOff", ep1);
                ep2.addBinding("genLevelCtrl", ep1);
            }
        },
        ota: true,
    },
    {
        zigbeeModel: ["J1 (5502)", "J1-R (5602)"],
        model: "J1",
        vendor: "Ubisys",
        description: "Shutter control J1",
        fromZigbee: [fz.cover_position_tilt, fz.metering, ubisys.fz.configure_device_setup, ubisys.fz.operational_status],
        toZigbee: [
            tz.cover_state,
            tz.cover_position_tilt,
            tz.metering_power,
            ubisys.tz.configure_j1,
            ubisys.tz.configure_device_setup,
            tz.currentsummdelivered,
        ],
        exposes: (device, options) => {
            const coverExpose = e.cover();
            const coverType = !utils.isDummyDevice(device)
                ? device.getEndpoint(1).getClusterAttributeValue("closuresWindowCovering", "windowCoveringType")
                : undefined;
            switch (coverType // cf. Ubisys J1 Technical Reference Manual, chapter 7.2.5.1 Calibration
            ) {
                case 0: // Roller Shade, Lift only
                case 1: // Roller Shade two motors, Lift only
                case 2: // Roller Shade exterior, Lift only
                case 3: // Roller Shade two motors exterior, Lift only
                case 4: // Drapery, Lift only
                case 5: // Awning, Lift only
                case 9: // Projector Screen, Lift only
                    coverExpose.withPosition();
                    break;
                case 6: // Shutter, Tilt only
                case 7: // Tilt Blind, Tilt only
                    coverExpose.withTilt();
                    break;
                default:
                    coverExpose.withPosition().withTilt();
                    break;
            }
            return [coverExpose, e.power().withAccess(ea.STATE_GET), e.energy().withAccess(ea.STATE_GET)];
        },
        extend: [
            ubisys_1.ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup(),
            ubisys_1.ubisysModernExtend.addCustomClusterClosuresWindowCovering(),
            ubisys_1.ubisysModernExtend.pollCurrentSummDelivered(3),
            ubisys_1.ubisysModernExtend.operationalStatus(),
            m.identify(),
        ],
        configure: async (device, coordinatorEndpoint) => {
            const endpoint1 = device.getEndpoint(1);
            const endpoint3 = device.getEndpoint(3);
            await reporting.bind(endpoint3, coordinatorEndpoint, ["seMetering"]);
            await reporting.readMeteringMultiplierDivisor(endpoint3);
            await reporting.instantaneousDemand(endpoint3);
            await reporting.bind(endpoint1, coordinatorEndpoint, ["closuresWindowCovering"]);
            await reporting.currentPositionLiftPercentage(endpoint1);
            const payload = reporting.payload("operationalStatus", 0, 65000, 0);
            await endpoint1.configureReporting("closuresWindowCovering", payload);
        },
        onEvent: (event) => {
            /*
             * As per technical doc page 21 section 7.3.4
             * https://www.ubisys.de/wp-content/uploads/ubisys-j1-technical-reference.pdf
             *
             * We use addBinding to 'record' this default binding.
             */
            if (event.type === "deviceInterview") {
                const ep1 = event.data.device.getEndpoint(1);
                const ep2 = event.data.device.getEndpoint(2);
                ep2.addBinding("closuresWindowCovering", ep1);
            }
        },
        ota: true,
    },
    {
        zigbeeModel: ["C4 (5504)"],
        model: "C4",
        vendor: "Ubisys",
        description: "Control unit C4",
        fromZigbee: [
            fz.command_toggle,
            fz.command_on,
            fz.command_off,
            fz.command_recall,
            fz.command_move,
            fz.command_stop,
            fz.command_cover_open,
            fz.command_cover_close,
            fz.command_cover_stop,
            ubisys.fz.configure_device_setup,
        ],
        toZigbee: [ubisys.tz.configure_device_setup],
        exposes: [
            e.action([
                "toggle_s1",
                "toggle_s2",
                "toggle_s3",
                "toggle_s4",
                "on_s1",
                "on_s2",
                "on_s3",
                "on_s4",
                "off_s1",
                "off_s2",
                "off_s3",
                "off_s4",
                "recall_*_s1",
                "recal_*_s2",
                "recall_*_s3",
                "recal_*_s4",
                "brightness_move_up_s1",
                "brightness_move_up_s2",
                "brightness_move_up_s3",
                "brightness_move_up_s4",
                "brightness_move_down_s1",
                "brightness_move_down_s2",
                "brightness_move_down_s3",
                "brightness_move_down_s4",
                "brightness_stop_s1",
                "brightness_stop_s2",
                "brightness_stop_s3",
                "brightness_stop_s4",
                "cover_open_s5",
                "cover_close_s5",
                "cover_stop_s5",
                "cover_open_s6",
                "cover_close_s6",
                "cover_stop_s6",
            ]),
        ],
        extend: [ubisys_1.ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup()],
        configure: async (device, coordinatorEndpoint) => {
            for (const ep of [1, 2, 3, 4]) {
                await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genScenes", "genOnOff", "genLevelCtrl"]);
            }
            for (const ep of [5, 6]) {
                await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ["genScenes", "closuresWindowCovering"]);
            }
        },
        meta: { multiEndpoint: true },
        endpoint: (device) => {
            return { s1: 1, s2: 2, s3: 3, s4: 4, s5: 5, s6: 6 };
        },
        ota: true,
    },
    {
        zigbeeModel: ["H1"],
        model: "H1",
        vendor: "Ubisys",
        description: "Heating regulator",
        meta: { thermostat: { dontMapPIHeatingDemand: true } },
        fromZigbee: [fz.thermostat, fz.thermostat_weekly_schedule],
        toZigbee: [
            tz.thermostat_occupied_heating_setpoint,
            tz.thermostat_unoccupied_heating_setpoint,
            tz.thermostat_local_temperature,
            tz.thermostat_system_mode,
            tz.thermostat_weekly_schedule,
            tz.thermostat_clear_weekly_schedule,
            tz.thermostat_running_mode,
            tz.thermostat_pi_heating_demand,
        ],
        exposes: [
            e
                .climate()
                .withSystemMode(["off", "heat"], ea.ALL)
                .withRunningMode(["off", "heat"])
                .withSetpoint("occupied_heating_setpoint", 7, 30, 0.5)
                .withSetpoint("unoccupied_heating_setpoint", 7, 30, 0.5)
                .withLocalTemperature()
                .withPiHeatingDemand(ea.STATE_GET)
                .withWeeklySchedule(["heat"]),
        ],
        extend: [
            ubisys_1.ubisysModernExtend.addCustomClusterHvacThermostat(),
            ubisys_1.ubisysModernExtend.openWindowState(),
            ubisys_1.ubisysModernExtend.vacationMode(),
            ubisys_1.ubisysModernExtend.localTemperatureOffset(),
            ubisys_1.ubisysModernExtend.occupiedHeatingSetpointDefault(),
            ubisys_1.ubisysModernExtend.remoteTemperatureDuration(),
            ubisys_1.ubisysModernExtend.openWindowDetect(),
            ubisys_1.ubisysModernExtend.openWindowTimeout(),
            ubisys_1.ubisysModernExtend.openWindowDetectionPeriod(),
            ubisys_1.ubisysModernExtend.openWindowSensitivity(),
            m.battery({
                percentage: true,
                voltage: true,
                voltageReporting: true,
                percentageReporting: true,
            }),
        ],
        configure: async (device, coordinatorEndpoint) => {
            const endpoint = device.getEndpoint(1);
            const binds = ["genBasic", "genPowerCfg", "genTime", "hvacThermostat"];
            await reporting.bind(endpoint, coordinatorEndpoint, binds);
            // reporting
            // NOTE: temperature is 0.5 deg steps
            // NOTE: unoccupied_heating_setpoint cannot be set via the device itself
            //       so we do not need to setup reporting for this, as reporting slots
            //       seem to be limited.
            await reporting.thermostatSystemMode(endpoint);
            await reporting.thermostatRunningMode(endpoint);
            await reporting.thermostatTemperature(endpoint, { min: 0, max: constants.repInterval.HOUR, change: 50 });
            await reporting.thermostatOccupiedHeatingSetpoint(endpoint, { min: 0, max: constants.repInterval.HOUR, change: 50 });
            await reporting.thermostatPIHeatingDemand(endpoint, { min: 15, max: constants.repInterval.HOUR, change: 1 });
            // read attributes
            // NOTE: configuring reporting on hvacThermostat seems to trigger an immediate
            //       report, so the values are available after configure has run.
            //       this does not seem to be the case for genPowerCfg, so we read
            //       the battery percentage
            await endpoint.read("genPowerCfg", ["batteryPercentageRemaining"]);
            // write attributes
            // NOTE: device checks in every 1h once the device has entered deepsleep
            //       this might be a bit long if you want to set the temperature remotely
            //       update this to every 15 minutes. (value is in 1/4th of a second)
            await endpoint.write("genPollCtrl", { checkinInterval: 4 * 60 * 15 });
        },
        ota: true,
    },
    {
        zigbeeModel: ["H10"],
        model: "H10",
        vendor: "Ubisys",
        description: "Heatingcontrol 10-Way",
        meta: { thermostat: { dontMapPIHeatingDemand: true }, multiEndpoint: true },
        fromZigbee: [fz.on_off, fz.thermostat, fz.thermostat_weekly_schedule],
        toZigbee: [
            tz.on_off,
            tz.thermostat_occupied_heating_setpoint,
            tz.thermostat_unoccupied_heating_setpoint,
            tz.thermostat_occupied_cooling_setpoint,
            tz.thermostat_unoccupied_cooling_setpoint,
            tz.thermostat_local_temperature,
            tz.thermostat_system_mode,
            tz.thermostat_weekly_schedule,
            tz.thermostat_clear_weekly_schedule,
            tz.thermostat_running_mode,
            tz.thermostat_pi_heating_demand,
        ],
        endpoint: (device) => {
            return {
                th1: 1,
                th2: 2,
                th3: 3,
                th4: 4,
                th5: 5,
                th6: 6,
                th7: 7,
                th8: 8,
                th9: 9,
                th10: 10,
                l1: 11,
                l2: 12,
                l3: 13,
                l4: 14,
                l5: 15,
                l6: 16,
                l7: 17,
                l8: 18,
                l9: 19,
                l10: 20,
                default: 21,
            };
        },
        exposes: [
            e.switch().withEndpoint("l1"),
            e.switch().withEndpoint("l2"),
            e.switch().withEndpoint("l3"),
            e.switch().withEndpoint("l4"),
            e.switch().withEndpoint("l5"),
            e.switch().withEndpoint("l6"),
            e.switch().withEndpoint("l7"),
            e.switch().withEndpoint("l8"),
            e.switch().withEndpoint("l9"),
            e.switch().withEndpoint("l10"),
            e
                .climate()
                .withEndpoint("th1")
                .withSystemMode(["off", "heat", "cool"], ea.ALL)
                .withRunningMode(["off", "heat", "cool"])
                .withSetpoint("occupied_heating_setpoint", 7, 30, 0.5)
                .withLocalTemperature()
                .withPiHeatingDemand(ea.STATE_GET),
            e
                .climate()
                .withEndpoint("th2")
                .withSystemMode(["off", "heat", "cool"], ea.ALL)
                .withRunningMode(["off", "heat", "cool"])
                .withSetpoint("occupied_heating_setpoint", 7, 30, 0.5)
                .withLocalTemperature()
                .withPiHeatingDemand(ea.STATE_GET),
            e
                .climate()
                .withEndpoint("th3")
                .withSystemMode(["off", "heat", "cool"], ea.ALL)
                .withRunningMode(["off", "heat", "cool"])
                .withSetpoint("occupied_heating_setpoint", 7, 30, 0.5)
                .withLocalTemperature()
                .withPiHeatingDemand(ea.STATE_GET),
            e
                .climate()
                .withEndpoint("th4")
                .withSystemMode(["off", "heat", "cool"], ea.ALL)
                .withRunningMode(["off", "heat", "cool"])
                .withSetpoint("occupied_heating_setpoint", 7, 30, 0.5)
                .withLocalTemperature()
                .withPiHeatingDemand(ea.STATE_GET),
            e
                .climate()
                .withEndpoint("th5")
                .withSystemMode(["off", "heat", "cool"], ea.ALL)
                .withRunningMode(["off", "heat", "cool"])
                .withSetpoint("occupied_heating_setpoint", 7, 30, 0.5)
                .withLocalTemperature()
                .withPiHeatingDemand(ea.STATE_GET),
            e
                .climate()
                .withEndpoint("th6")
                .withSystemMode(["off", "heat", "cool"], ea.ALL)
                .withRunningMode(["off", "heat", "cool"])
                .withSetpoint("occupied_heating_setpoint", 7, 30, 0.5)
                .withLocalTemperature()
                .withPiHeatingDemand(ea.STATE_GET),
            e
                .climate()
                .withEndpoint("th7")
                .withSystemMode(["off", "heat", "cool"], ea.ALL)
                .withRunningMode(["off", "heat", "cool"])
                .withSetpoint("occupied_heating_setpoint", 7, 30, 0.5)
                .withLocalTemperature()
                .withPiHeatingDemand(ea.STATE_GET),
            e
                .climate()
                .withEndpoint("th8")
                .withSystemMode(["off", "heat", "cool"], ea.ALL)
                .withRunningMode(["off", "heat", "cool"])
                .withSetpoint("occupied_heating_setpoint", 7, 30, 0.5)
                .withLocalTemperature()
                .withPiHeatingDemand(ea.STATE_GET),
            e
                .climate()
                .withEndpoint("th9")
                .withSystemMode(["off", "heat", "cool"], ea.ALL)
                .withRunningMode(["off", "heat", "cool"])
                .withSetpoint("occupied_heating_setpoint", 7, 30, 0.5)
                .withLocalTemperature()
                .withPiHeatingDemand(ea.STATE_GET),
            e
                .climate()
                .withEndpoint("th10")
                .withSystemMode(["off", "heat", "cool"], ea.ALL)
                .withRunningMode(["off", "heat", "cool"])
                .withSetpoint("occupied_heating_setpoint", 7, 30, 0.5)
                .withLocalTemperature()
                .withPiHeatingDemand(ea.STATE_GET),
        ],
        extend: [ubisys_1.ubisysModernExtend.addCustomClusterHvacThermostat(), ubisys_1.ubisysModernExtend.addCustomClusterGenLevelCtrl()],
        configure: async (device, coordinatorEndpoint) => {
            // setup ep 1-10 as thermostats
            // NOTE: doesn't look like fz/tz.thermostat and friends support
            //        multiEndpoint
            const thermostatBinds = ["hvacThermostat"];
            for (let ep = 1; ep <= 10; ep++) {
                const endpoint = device.getEndpoint(ep);
                await reporting.bind(endpoint, coordinatorEndpoint, thermostatBinds);
                await reporting.thermostatSystemMode(endpoint);
                await reporting.thermostatTemperature(endpoint, { min: 0, max: constants.repInterval.HOUR, change: 50 });
                await reporting.thermostatOccupiedHeatingSetpoint(endpoint, { min: 0, max: constants.repInterval.HOUR, change: 50 });
                await reporting.thermostatOccupiedCoolingSetpoint(endpoint, { min: 0, max: constants.repInterval.HOUR, change: 50 });
                await reporting.thermostatPIHeatingDemand(endpoint, { min: 15, max: constants.repInterval.HOUR, change: 1 });
                await reporting.thermostatPICoolingDemand(endpoint, { min: 15, max: constants.repInterval.HOUR, change: 1 });
            }
            // setup ep 11-20 as on/off switches
            const heaterCoolerBinds = ["genOnOff"];
            for (let ep = 11; ep <= 20; ep++) {
                const endpoint = device.getEndpoint(ep);
                await reporting.bind(endpoint, coordinatorEndpoint, heaterCoolerBinds);
                await reporting.onOff(endpoint);
            }
        },
        ota: true,
    },
    {
        zigbeeModel: ["R0 (5501)"],
        model: "R0",
        vendor: "Ubisys",
        description: "Zigbee Router",
        fromZigbee: [fz.linkquality_from_basic],
        toZigbee: [],
        exposes: [],
        ota: true,
    },
];
//# sourceMappingURL=ubisys.js.map