zigbee-herdsman-converters
Version:
Collection of device converters to be used with zigbee-herdsman
937 lines (936 loc) • 66.4 kB
JavaScript
"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",