UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

937 lines (936 loc) 66.4 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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.definitions = void 0; 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")); //import * as legacy from '../lib/legacy'; 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 }, }; const ubisysPollCurrentSummDelivered = (type, data, device, endpointId, options) => { const endpoint = device.getEndpoint(endpointId); const poll = async () => { await endpoint.read("seMetering", ["currentSummDelivered"]); }; utils.onEventPoll(type, data, device, options, "measurement", 60, poll); }; 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 }; }, }, }, tz: { configure_j1: { key: ["configure_j1"], convertSet: async (entity, key, value, meta) => { const log = (message) => { logger_1.logger.warning(`ubisys: ${message}`, NS); }; const sleepSeconds = async (s) => { return await new Promise((resolve) => setTimeout(resolve, s * 1000)); }; const waitUntilStopped = async () => { let operationalStatus = 0; do { await sleepSeconds(2); const response = await entity.read("closuresWindowCovering", ["operationalStatus"]); // @ts-expect-error ignore operationalStatus = response.operationalStatus; } while (operationalStatus !== 0); await sleepSeconds(2); }; const writeAttrFromJson = async (attr, jsonAttr, converterFunc, delaySecondsAfter) => { // biome-ignore lint/style/noParameterAssign: ignored using `--suppress` if (!jsonAttr) jsonAttr = attr; if (jsonAttr.startsWith("ubisys")) { // biome-ignore lint/style/noParameterAssign: ignored using `--suppress` jsonAttr = jsonAttr.substring(6, 1).toLowerCase + jsonAttr.substring(7); } if (value[jsonAttr] !== undefined) { let attrValue = value[jsonAttr]; if (converterFunc) { attrValue = converterFunc(attrValue); } const attributes = {}; attributes[attr] = attrValue; await entity.write("closuresWindowCovering", attributes, manufacturerOptions.ubisys); if (delaySecondsAfter) { await sleepSeconds(delaySecondsAfter); } } }; const stepsPerSecond = value.steps_per_second || 50; const hasCalibrate = value.calibrate != null; // cancel any running calibration // @ts-expect-error ignore let mode = (await entity.read("closuresWindowCovering", ["windowCoveringMode"])).windowCoveringMode; const modeCalibrationBitMask = 0x02; if (mode & modeCalibrationBitMask) { await entity.write("closuresWindowCovering", { windowCoveringMode: mode & ~modeCalibrationBitMask }); await sleepSeconds(2); } // delay a bit if reconfiguring basic configuration attributes await writeAttrFromJson("windowCoveringType", undefined, undefined, 2); await writeAttrFromJson("configStatus", 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", { installedOpenLimitLiftCm: 0, installedClosedLimitLiftCm: 240, installedOpenLimitTiltDdegree: 0, installedClosedLimitTiltDdegree: 900, ubisysLiftToTiltTransitionSteps: 0xffff, ubisysTotalSteps: 0xffff, ubisysLiftToTiltTransitionSteps2: 0xffff, ubisysTotalSteps2: 0xffff, }, manufacturerOptions.ubisys); // enable calibration mode await sleepSeconds(2); await entity.write("closuresWindowCovering", { windowCoveringMode: mode | modeCalibrationBitMask }); await sleepSeconds(2); // move down a bit and back up to detect upper limit log(" Moving cover down a bit..."); await entity.command("closuresWindowCovering", "downClose", {}); await sleepSeconds(5); await entity.command("closuresWindowCovering", "stop", {}); await sleepSeconds(2); 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("installedOpenLimitLiftCm"); await writeAttrFromJson("installedClosedLimitLiftCm"); await writeAttrFromJson("installedOpenLimitTiltDdegree"); await writeAttrFromJson("installedClosedLimitTiltDdegree"); 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 sleepSeconds(2); await entity.write("closuresWindowCovering", { windowCoveringMode: mode & ~modeCalibrationBitMask }); await sleepSeconds(2); // 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 cluster = zigbee_herdsman_1.Zcl.Utils.getCluster("manuSpecificUbisysDeviceSetup", null, meta.device.customClusters); const attributeInputConfigurations = cluster.getAttribute("inputConfigurations"); const attributeInputActions = cluster.getAttribute("inputActions"); // 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 let useWriteStruct = false; if (meta.device.softwareBuildID && (0, semver_1.valid)(meta.device.softwareBuildID, true)) { useWriteStruct = (0, semver_1.gte)(meta.device.softwareBuildID, "1.9.0", true); } if (useWriteStruct) { logger_1.logger.debug(`ubisys: using writeStructure for '${meta.options.friendly_name}'.`, NS); } if (value.input_configurations != null) { // example: [0, 0, 0, 0] if (useWriteStruct) { 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); } else { await devMgmtEp.write("manuSpecificUbisysDeviceSetup", { [attributeInputConfigurations.name]: { 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]] if (useWriteStruct) { 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); } else { await devMgmtEp.write("manuSpecificUbisysDeviceSetup", { [attributeInputActions.name]: { 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; // first client endpoint - depends on actual device if (Array.isArray(meta.mapped)) 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]; let resultingInputActions = []; for (const template of templates) { // @ts-expect-error ignore 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 (templateType.cover && meta.mapped.model === "C4" && endpoint < 5) { endpoint += 4; } // biome-ignore lint/suspicious/noImplicitAnyLet: ignored using `--suppress` let inputActions; if (!templateType.doubleInputs) { if (!templateType.scene) { // single input, no scene(s) inputActions = templateType.getInputActions(input, endpoint, template); } else { // 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 { // double inputs input = template.inputs !== undefined ? template.inputs : [input, input + 1]; inputActions = templateType.getInputActions(input, endpoint, template); } resultingInputActions = resultingInputActions.concat(inputActions); logger_1.logger.warning(`ubisys: Using input(s) ${input} and endpoint ${endpoint} for '${template.type}'.`, NS); // input might by now be an array (in case of double inputs) input = (Array.isArray(input) ? Math.max(...input) : input) + 1; endpoint += 1; } logger_1.logger.debug(`ubisys: input_actions to be sent to '${meta.options.friendly_name}': ${JSON.stringify(resultingInputActions)}`, NS); if (useWriteStruct) { 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: resultingInputActions, }, }, ], manufacturerOptions.ubisysNull); } else { await devMgmtEp.write("manuSpecificUbisysDeviceSetup", { [attributeInputActions.name]: { elementType: zigbee_herdsman_1.Zcl.DataType.OCTET_STR, elements: resultingInputActions } }, manufacturerOptions.ubisysNull); } } // re-read effective settings and dump them to the log await ubisys.tz.configure_device_setup.convertGet(entity, key, meta); }, convertGet: async (entity, key, meta) => { const devMgmtEp = meta.device.getEndpoint(232); await devMgmtEp.read("manuSpecificUbisysDeviceSetup", ["inputConfigurations"], manufacturerOptions.ubisysNull); await devMgmtEp.read("manuSpecificUbisysDeviceSetup", ["inputActions"], manufacturerOptions.ubisysNull); }, }, }, }; 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 }; }, options: [exposes.options.measurement_poll_interval()], extend: [ // NOTE: identify is supported but no visual indicator so omitted here 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.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: async (type, data, device, settings) => { /* * 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 (type === "deviceInterview") { const ep1 = device.getEndpoint(1); const ep2 = device.getEndpoint(2); ep2.addBinding("genOnOff", ep1); } else { await ubisysPollCurrentSummDelivered(type, data, device, 3, settings); } }, 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 }; }, options: [exposes.options.measurement_poll_interval()], extend: [ m.identify(), 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.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: async (type, data, device, settings) => { /* * 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 (type === "deviceInterview") { const ep1 = device.getEndpoint(1); const ep2 = device.getEndpoint(2); ep2.addBinding("genOnOff", ep1); } else { await ubisysPollCurrentSummDelivered(type, data, device, device.hardwareVersion < 16 ? 4 : 1, settings); } }, 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"] }, options: [exposes.options.measurement_poll_interval()], extend: [ubisys_1.ubisysModernExtend.addCustomClusterManuSpecificUbisysDeviceSetup()], configure: async (device, coordinatorEndpoint) => { const endpoint = device.getEndpoint(5); await reporting.bind(endpoint, coordinatorEndpoint, ["seMetering"]); await reporting.readMeteringMultiplierDivisor(endpoint); await reporting.instantaneousDemand(endpoint); }, onEvent: async (type, data, device, settings) => { /* * 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 (type === "deviceInterview") { const ep1 = device.getEndpoint(1); const ep2 = device.getEndpoint(2); const ep3 = device.getEndpoint(3); const ep4 = device.getEndpoint(4); ep3.addBinding("genOnOff", ep1); ep4.addBinding("genOnOff", ep2); } else { await ubisysPollCurrentSummDelivered(type, data, device, 5, settings); } }, 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(), ], 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"] }, options: [exposes.options.measurement_poll_interval()], endpoint: (device) => { return { default: 1, s1: 2, s2: 3 }; }, onEvent: async (type, data, device, settings) => { /* * 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 (type === "deviceInterview") { const ep1 = device.getEndpoint(1); const ep2 = device.getEndpoint(2); ep2.addBinding("genOnOff", ep1); ep2.addBinding("genLevelCtrl", ep1); } else { await ubisysPollCurrentSummDelivered(type, data, device, 4, settings); } }, ota: true, }, { zigbeeModel: ["J1 (5502)", "J1-R (5602)"], model: "J1", vendor: "Ubisys",