zigbee-herdsman-converters
Version:
Collection of device converters to be used with zigbee-herdsman
1,076 lines • 150 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.boschThermostatExtend = exports.boschSmartPlugExtend = exports.boschSmokeAlarmExtend = exports.boschWaterAlarmExtend = exports.boschBsenExtend = exports.boschDoorWindowContactExtend = exports.boschBsirExtend = exports.boschBmctExtend = exports.boschGeneralSensorDeviceExtend = exports.boschGeneralEnergyDeviceExtend = exports.boschGeneralExtend = exports.manufacturerOptions = void 0;
const zigbee_herdsman_1 = require("zigbee-herdsman");
const fz = __importStar(require("../converters/fromZigbee"));
const tz = __importStar(require("../converters/toZigbee"));
const exposes = __importStar(require("../lib/exposes"));
const m = __importStar(require("../lib/modernExtend"));
const constants_1 = require("./constants");
const logger_1 = require("./logger");
const reporting_1 = require("./reporting");
const globalStore = __importStar(require("./store"));
const utils = __importStar(require("./utils"));
const utils_1 = require("./utils");
const e = exposes.presets;
const ea = exposes.access;
const NS = "zhc:bosch";
exports.manufacturerOptions = {
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
sendPolicy: "immediate",
};
//region Generally used Bosch functionality
exports.boschGeneralExtend = {
/** Some devices now use a different name for some custom clusters than
* originally used. This can lead to issues like those described in
* https://github.com/Koenkk/zigbee2mqtt/issues/28806. To prevent that
* we have to make sure that all attributes of the renamed cluster are
* available when using "getEndpoint().getClusterAttributeValue()". */
handleRenamedCustomCluster: (oldClusterName, newClusterName) => {
const onEvent = [
async (event) => {
if (event.type !== "start") {
return;
}
const device = event.data.device;
const renameAlreadyApplied = device.meta.renamedClusters?.includes(oldClusterName);
if (!renameAlreadyApplied) {
logger_1.logger.debug(`Try to apply cluster rename from ${oldClusterName} to ${newClusterName} for device ${device.ieeeAddr}. Current meta state: ${JSON.stringify(device.meta)}`, NS);
const newClusterDefinition = device.customClusters[newClusterName];
const endpointsWithNewCluster = device.endpoints.filter((endpoint) => endpoint.clusters[newClusterName] !== undefined);
for (const endpointToRead in endpointsWithNewCluster) {
const endpoint = endpointsWithNewCluster[endpointToRead];
logger_1.logger.debug(`Attempt to read all attributes for cluster ${newClusterName} from endpoint ${endpoint.ID}`, NS);
for (const attributeToRead in newClusterDefinition.attributes) {
const attributeValueExistsInDatabase = endpoint.getClusterAttributeValue(newClusterName, newClusterDefinition.attributes[attributeToRead].ID) !== undefined;
if (!attributeValueExistsInDatabase) {
logger_1.logger.debug(`Attempt to read undefined attribute ${attributeToRead} in cluster ${newClusterName} from endpoint ${endpoint.ID}`, NS);
try {
await endpoint.read(newClusterDefinition.ID, [newClusterDefinition.attributes[attributeToRead].ID]);
}
catch (exception) {
logger_1.logger.debug(`Error during read attempt for attribute ${attributeToRead}. Probably unsupported attribute on this device or endpoint. Skipping... ${exception}`, NS);
}
}
else {
logger_1.logger.debug(`Value for attribute ${attributeToRead} in cluster ${newClusterName} from endpoint ${endpoint.ID} already in database. Skipping...`, NS);
}
}
}
logger_1.logger.debug(`All attributes are read, now calling deviceExposeChanged() for device ${device.ieeeAddr}`, NS);
event.data.deviceExposesChanged();
device.meta.renamedClusters =
device.meta.renamedClusters === undefined ? [oldClusterName] : [...device.meta.renamedClusters, oldClusterName];
logger_1.logger.debug(`Cluster rename from ${oldClusterName} to ${newClusterName} for device ${device.ieeeAddr} successfully applied. New meta state: ${JSON.stringify(device.meta)}`, NS);
}
},
];
return {
onEvent,
isModernExtend: true,
};
},
/** Some Bosch devices ask the coordinator for their ZCL version
* during deviceAnnouncement. Without answer, these devices regularly
* re-join the network. To avoid that, we have to make sure that a readRequest
* for the zclVersion is always being answered. The answered zclVersion is
* taken from the Bosch Smart Home Controller II.
*
* Exception: BTH-RM and BTH-RM230Z ask the coordinator at regular
* intervals for their zclVersion (maybe availability check like Z2M does?)
* and *not* during interview! To avoid code-duplication, we handle that
* case here as well. */
handleZclVersionReadRequest: () => {
const onEvent = [
(event) => {
if (event.type === "start") {
event.data.device.customReadResponse = (frame, endpoint) => {
if (frame.isCluster("genBasic") && frame.payload.some((i) => i.attrId === 0x0000)) {
// XXX: we're replying to specific attribute, which could be incorrect (not based on the request attrIds)
endpoint.readResponse("genBasic", frame.header.transactionSequenceNumber, { zclVersion: 1 }).catch((e) => {
logger_1.logger.warning(`Custom zclVersion response failed for '${event.data.device.ieeeAddr}': ${e}`, NS);
});
return true;
}
return false;
};
}
},
];
return {
onEvent,
isModernExtend: true,
};
},
batteryWithPercentageAndLowStatus: (args) => m.battery({
percentage: true,
percentageReportingConfig: false,
lowStatus: true,
lowStatusReportingConfig: { min: "MIN", max: "MAX", change: null },
...args,
}),
};
exports.boschGeneralEnergyDeviceExtend = {
customMeteringCluster: () => m.deviceAddCustomCluster("seMetering", {
name: "seMetering",
ID: zigbee_herdsman_1.Zcl.Clusters.seMetering.ID,
attributes: {},
commands: {
resetEnergyMeters: {
name: "resetEnergyMeters",
ID: 0x80,
parameters: [],
},
},
commandsResponse: {},
}),
resetEnergyMeters: () => {
const exposes = [
e
.enum("reset_energy_meters", ea.SET, ["reset"])
.withDescription("Triggers the reset of all energy meters on the device to 0 kWh")
.withCategory("config"),
];
const toZigbee = [
{
key: ["reset_energy_meters"],
convertSet: async (entity, key, value, meta) => {
await entity.command("seMetering", "resetEnergyMeters", {}, exports.manufacturerOptions);
},
},
];
return {
exposes,
toZigbee,
isModernExtend: true,
};
},
autoOff: (args) => {
const { endpoint } = args ?? {};
const offOnLookup = {
OFF: 0x00,
ON: 0x01,
};
const expose = (device, options) => {
if (utils.isDummyDevice(device)) {
return [];
}
if (device.modelID === "RBSH-MMR-ZB-EU") {
const pulsedModeEnabled = device.getEndpoint(1).getClusterAttributeValue("boschEnergyDevice", "pulseLength") !== 0;
if (pulsedModeEnabled) {
return [];
}
}
const autoOffEnabledExpose = e
.binary("auto_off_enabled", ea.ALL, utils.getFromLookupByValue(0x01, offOnLookup), utils.getFromLookupByValue(0x00, offOnLookup))
.withLabel("Enable auto-off")
.withDescription("Enable/disable the automatic turn-off feature")
.withCategory("config");
const autoOffTimeExpose = e
.numeric("auto_off_time", ea.ALL)
.withLabel("Auto-off time")
.withDescription("Turn off the output after the specified amount of time. Only in action when the automatic turn-off is enabled.")
.withUnit("min")
.withValueMin(0)
.withValueMax(720)
.withValueStep(0.5)
.withCategory("config");
if (endpoint !== undefined) {
autoOffEnabledExpose.withEndpoint(endpoint.toString());
autoOffTimeExpose.withEndpoint(endpoint.toString());
}
return [autoOffEnabledExpose, autoOffTimeExpose];
};
const fromZigbee = [
{
cluster: "boschEnergyDevice",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
const data = msg.data;
if (data.autoOffEnabled !== undefined) {
const property = utils.postfixWithEndpointName("auto_off_enabled", msg, model, meta);
result[property] = utils.getFromLookupByValue(data.autoOffEnabled, offOnLookup);
}
if (data.autoOffTime !== undefined) {
const property = utils.postfixWithEndpointName("auto_off_time", msg, model, meta);
result[property] = data.autoOffTime / 60;
}
return result;
},
},
];
const toZigbee = [
{
key: ["auto_off_enabled", "auto_off_time"],
convertSet: async (entity, key, value, meta) => {
if (key === "auto_off_enabled") {
const selectedState = utils.getFromLookup(value, offOnLookup);
await entity.write("boschEnergyDevice", {
autoOffEnabled: utils.toNumber(selectedState),
});
return { state: { auto_off_enabled: value } };
}
if (key === "auto_off_time") {
await entity.write("boschEnergyDevice", {
autoOffTime: (0, utils_1.toNumber)(value) * 60,
});
return { state: { auto_off_time: value } };
}
},
convertGet: async (entity, key, meta) => {
if (key === "auto_off_enabled") {
await entity.read("boschEnergyDevice", ["autoOffEnabled"]);
}
if (key === "auto_off_time") {
await entity.read("boschEnergyDevice", ["autoOffTime"]);
}
},
},
];
const configure = [
m.setupConfigureForBinding("boschEnergyDevice", "input", endpoint ? [endpoint.toString()] : null),
m.setupConfigureForReading("boschEnergyDevice", ["autoOffEnabled", "autoOffTime"], endpoint ? [endpoint.toString()] : null),
];
return {
exposes: [expose],
fromZigbee,
toZigbee,
configure,
isModernExtend: true,
};
},
};
//endregion
//region Generally used Bosch functionality on sensor devices
exports.boschGeneralSensorDeviceExtend = {
testMode: (args) => {
const { testModeDescription, sensitivityLevelToUse, variableTimeoutSupported = false, defaultTimeout = 0, zoneStatusBit = 8 } = args;
const testModeLookup = {
ON: true,
OFF: false,
};
const enableTestMode = async (endpoint, sensitivityLevelToUse, timeoutInSeconds) => {
await endpoint.command("ssIasZone", "initTestMode", {
testModeDuration: timeoutInSeconds,
currentZoneSensitivityLevel: sensitivityLevelToUse,
});
};
const disableTestMode = async (endpoint) => {
await endpoint.command("ssIasZone", "initNormalOpMode", {});
};
const exposes = [
e
.binary("test_mode", ea.ALL, utils.getFromLookupByValue(true, testModeLookup), utils.getFromLookupByValue(false, testModeLookup))
.withDescription(testModeDescription)
.withCategory("config"),
];
if (variableTimeoutSupported) {
exposes.push(e
.numeric("test_mode_timeout", ea.ALL)
.withDescription(`Determines how long the test mode should be activated. The default length is ${defaultTimeout} seconds.`)
.withValueMin(1)
.withValueMax(255)
.withUnit("seconds")
.withCategory("config"));
}
const fromZigbee = [
{
cluster: "ssIasZone",
type: ["commandStatusChangeNotification", "attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const zoneStatus = "zonestatus" in msg.data ? msg.data.zonestatus : msg.data.zoneStatus;
if (zoneStatus === undefined) {
return;
}
const result = {};
const testModeEnabled = (zoneStatus & (1 << zoneStatusBit)) > 0;
result.test_mode = utils.getFromLookupByValue(testModeEnabled, testModeLookup);
return result;
},
},
];
const toZigbee = [
{
key: ["test_mode", "test_mode_timeout"],
convertSet: async (entity, key, value, meta) => {
if (key === "test_mode") {
if (value === utils.getFromLookupByValue(true, testModeLookup)) {
let timeoutInSeconds;
const currentTimeout = meta.state.test_mode_timeout;
if (currentTimeout == null) {
timeoutInSeconds = defaultTimeout;
meta.publish({ test_mode_timeout: timeoutInSeconds });
}
else {
timeoutInSeconds = utils.toNumber(currentTimeout);
}
await enableTestMode(entity, sensitivityLevelToUse, timeoutInSeconds);
}
else {
await disableTestMode(entity);
}
}
if (key === "test_mode_timeout") {
return { state: { test_mode_timeout: value } };
}
},
convertGet: async (entity, key, meta) => {
if (key === "test_mode") {
await entity.read("ssIasZone", ["zoneStatus"]);
}
if (key === "test_mode_timeout" && meta.state.test_mode_timeout == null) {
meta.publish({ test_mode_timeout: defaultTimeout });
}
},
},
];
const configure = [m.setupConfigureForBinding("ssIasZone", "input"), m.setupConfigureForReading("ssIasZone", ["zoneStatus"])];
return {
exposes,
fromZigbee,
toZigbee,
configure,
isModernExtend: true,
};
},
};
exports.boschBmctExtend = {
switchMode: (args) => {
const { endpoint, deviceModeLookup, switchModeLookup, switchTypeLookup } = args;
const expose = (device, options) => {
if (utils.isDummyDevice(device)) {
return [];
}
const switchTypeKey = device.getEndpoint(1).getClusterAttributeValue("boschEnergyDevice", "switchType") ?? 0x00;
const selectedSwitchType = utils.getFromLookupByValue(switchTypeKey, switchTypeLookup);
if (selectedSwitchType === "none") {
return [];
}
let supportedSwitchModes = Object.keys(switchModeLookup);
if (device.modelID === "RBSH-MMS-ZB-EU") {
const deviceModeKey = device.getEndpoint(1).getClusterAttributeValue("boschEnergyDevice", "deviceMode");
const deviceMode = utils.getFromLookupByValue(deviceModeKey, deviceModeLookup);
if (deviceMode === "light") {
if (selectedSwitchType.includes("rocker_switch")) {
supportedSwitchModes = supportedSwitchModes.filter((switchMode) => switchMode === "coupled" || switchMode === "decoupled");
}
}
if (deviceMode === "shutter") {
if (selectedSwitchType.includes("button")) {
supportedSwitchModes = supportedSwitchModes.filter((switchMode) => switchMode === "coupled" || switchMode === "only_long_press_decoupled");
}
else if (selectedSwitchType.includes("rocker_switch")) {
supportedSwitchModes = supportedSwitchModes.filter((switchMode) => switchMode === "coupled");
}
}
}
const switchModeExpose = e
.enum("switch_mode", ea.ALL, supportedSwitchModes)
.withDescription("Decouple the switch from the corresponding output to use it for other purposes. Please keep in mind that the available options may depend on the used switch type.")
.withCategory("config");
if (endpoint !== undefined) {
switchModeExpose.withEndpoint(endpoint.toString());
}
return [switchModeExpose];
};
const fromZigbee = [
{
cluster: "boschEnergyDevice",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
const data = msg.data;
if (data.switchMode !== undefined) {
const property = utils.postfixWithEndpointName("switch_mode", msg, model, meta);
result[property] = utils.getFromLookupByValue(data.switchMode, switchModeLookup);
}
return result;
},
},
];
const toZigbee = [
{
key: ["switch_mode"],
convertSet: async (entity, key, value, meta) => {
if (key === "switch_mode") {
const index = utils.getFromLookup(value, switchModeLookup);
await entity.write("boschEnergyDevice", { switchMode: index });
return { state: { switch_mode: value } };
}
},
convertGet: async (entity, key, meta) => {
if (key === "switch_mode") {
await entity.read("boschEnergyDevice", ["switchMode"]);
}
},
},
];
const configure = [
async (device, coordinatorEndpoint, definition) => {
const desiredEndpoint = device.getEndpoint(endpoint ?? 1);
await desiredEndpoint.read("boschEnergyDevice", ["switchMode"]);
},
];
return {
exposes: [expose],
fromZigbee,
toZigbee,
configure,
isModernExtend: true,
};
},
childLock: (args) => {
const { endpoint } = args ?? {};
const childLockLookup = {
UNLOCKED: 0x00,
LOCKED: 0x01,
};
const expose = (device, options) => {
if (utils.isDummyDevice(device)) {
return [];
}
const currentSwitchType = device.getEndpoint(1).getClusterAttributeValue("boschEnergyDevice", "switchType") ?? 0x00;
if (currentSwitchType === 0) {
return [];
}
const childLockExpose = e
.binary("child_lock", ea.ALL, utils.getFromLookupByValue(0x01, childLockLookup), utils.getFromLookupByValue(0x00, childLockLookup))
.withDescription("Enables/disables physical input on the switch")
.withCategory("config");
if (endpoint !== undefined) {
childLockExpose.withEndpoint(endpoint.toString());
}
return [childLockExpose];
};
const fromZigbee = [
{
cluster: "boschEnergyDevice",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
const data = msg.data;
if (data.childLock !== undefined) {
const property = utils.postfixWithEndpointName("child_lock", msg, model, meta);
result[property] = data.childLock;
}
return result;
},
},
];
const toZigbee = [
{
key: ["child_lock"],
convertSet: async (entity, key, value, meta) => {
if (key === "child_lock") {
const selectedMode = utils.getFromLookup(value, childLockLookup);
await entity.write("boschEnergyDevice", { childLock: selectedMode });
return { state: { child_lock: value } };
}
},
convertGet: async (entity, key, meta) => {
if (key === "child_lock") {
await entity.read("boschEnergyDevice", ["childLock"]);
}
},
},
];
const configure = [
async (device, coordinatorEndpoint, definition) => {
const desiredEndpoint = device.getEndpoint(endpoint ?? 1);
await desiredEndpoint.read("boschEnergyDevice", ["childLock"]);
},
];
return {
exposes: [expose],
fromZigbee,
toZigbee,
configure,
isModernExtend: true,
};
},
actuatorType: () => m.enumLookup({
name: "actuator_type",
cluster: "boschEnergyDevice",
attribute: "actuatorType",
description: "Select the appropriate actuator type so that the connected device can be controlled correctly.",
lookup: {
normally_closed: 0x00,
normally_open: 0x01,
},
entityCategory: "config",
}),
dimmerType: () => m.enumLookup({
name: "dimmer_type",
cluster: "boschEnergyDevice",
attribute: "dimmerType",
description: "Select the appropriate dimmer type for your lamps. Make sure that you are only using dimmable lamps.",
lookup: {
leading_edge_phase_cut: 0x00,
trailing_edge_phase_cut: 0x01,
},
entityCategory: "config",
}),
brightnessRange: () => {
const expose = (device, options) => {
if (utils.isDummyDevice(device)) {
return [];
}
const minimumBrightnessExpose = e
.numeric("minimum_brightness", ea.ALL)
.withLabel("Raise minimum brightness")
.withDescription("This raises the minimum brightness level of the connected light")
.withValueMin(0)
.withValueMax(255)
.withCategory("config");
const maximumBrightnessExpose = e
.numeric("maximum_brightness", ea.ALL)
.withLabel("Lower maximum brightness")
.withDescription("This lowers the maximum brightness level of the connected light")
.withValueMin(0)
.withValueMax(255)
.withCategory("config");
return [minimumBrightnessExpose, maximumBrightnessExpose];
};
const fromZigbee = [
{
cluster: "boschEnergyDevice",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
const data = msg.data;
if (data.minimumBrightness !== undefined) {
result.minimum_brightness = data.minimumBrightness;
}
if (data.maximumBrightness !== undefined) {
result.maximum_brightness = data.maximumBrightness;
}
return result;
},
},
];
const toZigbee = [
{
key: ["minimum_brightness", "maximum_brightness"],
convertSet: async (entity, key, value, meta) => {
if (key === "minimum_brightness") {
const newMinimumBrightness = (0, utils_1.toNumber)(value);
const currentState = await entity.read("boschEnergyDevice", ["maximumBrightness"]);
if (newMinimumBrightness >= currentState.maximumBrightness) {
throw new Error("The minimum brightness must be lower than the maximum brightness!");
}
await entity.write("boschEnergyDevice", {
minimumBrightness: newMinimumBrightness,
});
return { state: { minimum_brightness: value } };
}
if (key === "maximum_brightness") {
const newMaximumBrightness = (0, utils_1.toNumber)(value);
const currentState = await entity.read("boschEnergyDevice", ["minimumBrightness"]);
if (newMaximumBrightness <= currentState.minimumBrightness) {
throw new Error("The maximum brightness must be higher than the minimum brightness!");
}
await entity.write("boschEnergyDevice", {
maximumBrightness: newMaximumBrightness,
});
return { state: { maximum_brightness: value } };
}
},
convertGet: async (entity, key, meta) => {
if (key === "minimum_brightness") {
await entity.read("boschEnergyDevice", ["minimumBrightness"]);
}
if (key === "maximum_brightness") {
await entity.read("boschEnergyDevice", ["maximumBrightness"]);
}
},
},
];
const configure = [
async (device, coordinatorEndpoint, definition) => {
const endpoint = device.getEndpoint(1);
await endpoint.read("boschEnergyDevice", ["minimumBrightness", "maximumBrightness"]);
},
];
return {
exposes: [expose],
fromZigbee,
toZigbee,
configure,
isModernExtend: true,
};
},
rzDeviceModes: (args) => {
const { deviceModesLookup } = args;
const expose = (device, options) => {
if (utils.isDummyDevice(device)) {
return [];
}
const deviceModeExpose = e
.enum("device_mode", ea.SET, Object.keys(deviceModesLookup))
.withLabel("Device mode")
.withDescription("Set the desired mode of the relay")
.withCategory("config");
return [deviceModeExpose];
};
const toZigbee = [
{
key: ["device_mode"],
convertSet: async (entity, key, value, meta) => {
if (key === "device_mode") {
const deviceModeChanged = meta.state.device_mode !== value;
if (deviceModeChanged) {
const newPulseLength = value === utils.getFromLookupByValue(0x00, deviceModesLookup) ? 0 : 10;
const endpoint = meta.device.getEndpoint(1);
if (newPulseLength > 0) {
await endpoint.write("boschEnergyDevice", {
switchType: 0x05,
switchMode: 0x00,
childLock: 0x00,
autoOffEnabled: 0x00,
autoOffTime: 0,
});
await endpoint.read("boschEnergyDevice", [
"switchType",
"switchMode",
"childLock",
"autoOffEnabled",
"autoOffTime",
]);
}
else {
await endpoint.write("boschEnergyDevice", {
switchType: 0x00,
switchMode: 0x00,
childLock: 0x00,
});
await endpoint.read("boschEnergyDevice", [
"switchType",
"switchMode",
"childLock",
]);
}
await endpoint.write("boschEnergyDevice", {
pulseLength: newPulseLength,
});
await endpoint.read("boschEnergyDevice", ["pulseLength"]);
}
return { state: { device_mode: value } };
}
},
},
];
return {
exposes: [expose],
toZigbee,
isModernExtend: true,
};
},
pulseLength: (args) => {
const { updateDeviceMode, deviceModesLookup } = args;
const expose = (device, options) => {
if (utils.isDummyDevice(device)) {
return [];
}
if (device.getEndpoint(1).getClusterAttributeValue("boschEnergyDevice", "pulseLength") === 0) {
return [];
}
const pulseLengthExpose = e
.numeric("pulse_length", ea.ALL)
.withLabel("Pulse length")
.withDescription("Set the desired pulse length for the relay in seconds.")
.withUnit("s")
.withValueStep(0.1)
.withValueMin(0.5)
.withValueMax(20)
.withCategory("config");
return [pulseLengthExpose];
};
const fromZigbee = [
{
cluster: "boschEnergyDevice",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
const data = msg.data;
if (data.pulseLength !== undefined) {
const currentPulseLength = data.pulseLength / 10;
const oldPulseLength = meta.device.meta.pulseLength ?? 0;
const pulsedModeActivated = currentPulseLength > 0 && oldPulseLength === 0;
const pulsedModeDeactivated = currentPulseLength === 0 && oldPulseLength > 0;
if (pulsedModeActivated || pulsedModeDeactivated) {
meta.device.meta.pulseLength = currentPulseLength;
meta.deviceExposesChanged();
}
if (updateDeviceMode) {
result.device_mode =
currentPulseLength === 0
? utils.getFromLookupByValue(0x00, deviceModesLookup)
: utils.getFromLookupByValue(0x01, deviceModesLookup);
}
result.pulse_length = currentPulseLength;
}
return result;
},
},
];
const toZigbee = [
{
key: ["pulse_length"],
convertSet: async (entity, key, value, meta) => {
if (key === "pulse_length") {
const selectedPulseLength = (0, utils_1.toNumber)(value) * 10;
await entity.write("boschEnergyDevice", {
pulseLength: selectedPulseLength,
});
return { state: { pulse_length: value } };
}
},
convertGet: async (entity, key, meta) => {
if (key === "pulse_length") {
await entity.read("boschEnergyDevice", ["pulseLength"]);
}
},
},
];
const configure = [
async (device, coordinatorEndpoint, definition) => {
const endpoint = device.getEndpoint(1);
await endpoint.read("boschEnergyDevice", ["pulseLength"]);
},
];
return {
exposes: [expose],
fromZigbee,
toZigbee,
configure,
isModernExtend: true,
};
},
switchType: (args) => {
const { switchTypeLookup } = args;
const expose = (device, options) => {
if (utils.isDummyDevice(device)) {
return [];
}
let supportedSwitchTypes = Object.keys(switchTypeLookup);
if (device.modelID === "RBSH-MMR-ZB-EU") {
const pulseModeActive = device.getEndpoint(1).getClusterAttributeValue("boschEnergyDevice", "pulseLength") > 0;
if (pulseModeActive) {
supportedSwitchTypes = Object.keys(switchTypeLookup).filter((switchType) => switchType !== utils.getFromLookup("rocker_switch", switchTypeLookup));
}
}
const switchTypeExpose = e
.enum("switch_type", ea.ALL, supportedSwitchTypes)
.withLabel("Connected switch type")
.withDescription("Select which switch type is connected to the module. Please keep in mind that the available options may depend on the selected device mode.")
.withCategory("config");
return [switchTypeExpose];
};
const fromZigbee = [
{
cluster: "boschEnergyDevice",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
const data = msg.data;
if (data.switchType !== undefined) {
const switchType = data.switchType;
result.switch_type = utils.getFromLookupByValue(switchType, switchTypeLookup);
if (switchType !== meta.device.meta.switchType) {
meta.device.meta.switchType = switchType;
meta.deviceExposesChanged();
}
}
return result;
},
},
];
const toZigbee = [
{
key: ["switch_type"],
convertSet: async (entity, key, value, meta) => {
if (key === "switch_type") {
const selectedSwitchType = utils.getFromLookup(value, switchTypeLookup);
if (meta.device.meta.switchType !== selectedSwitchType) {
const endpoints = meta.device.endpoints.filter((e) => e.supportsInputCluster("boschEnergyDevice"));
for (const endpoint of endpoints) {
await endpoint.write("boschEnergyDevice", {
switchMode: 0x00,
childLock: 0x00,
});
await endpoint.read("boschEnergyDevice", ["switchMode", "childLock"]);
}
}
await entity.write("boschEnergyDevice", {
switchType: selectedSwitchType,
});
await entity.read("boschEnergyDevice", ["switchType"]);
return { state: { switch_type: value } };
}
},
convertGet: async (entity, key, meta) => {
if (key === "switch_type") {
await entity.read("boschEnergyDevice", ["switchType"]);
}
},
},
];
const configure = [
async (device, coordinatorEndpoint, definition) => {
const endpoint = device.getEndpoint(1);
await endpoint.read("boschEnergyDevice", ["switchType"]);
},
];
return {
exposes: [expose],
fromZigbee,
toZigbee,
configure,
isModernExtend: true,
};
},
reportSwitchAction: (args) => {
const { switchTypeLookup, hasDualSwitchInputs } = args;
const expose = (device, options) => {
const exposeList = [];
if (utils.isDummyDevice(device)) {
return exposeList;
}
const switchTypeKey = device.getEndpoint(1).getClusterAttributeValue("boschEnergyDevice", "switchType") ?? 0x00;
const selectedSwitchType = utils.getFromLookupByValue(switchTypeKey, switchTypeLookup);
let supportedActionTypes;
if (selectedSwitchType.includes("button")) {
if (hasDualSwitchInputs) {
supportedActionTypes = [
"press_released_left",
"press_released_right",
"hold_left",
"hold_right",
"hold_released_left",
"hold_released_right",
];
}
else {
supportedActionTypes = ["press_released", "hold", "hold_released"];
}
exposeList.push(e.action(supportedActionTypes), e.action_duration());
}
else if (selectedSwitchType.includes("rocker_switch")) {
if (hasDualSwitchInputs) {
supportedActionTypes = ["opened_left", "opened_right", "closed_left", "closed_right"];
}
else {
supportedActionTypes = ["opened", "closed"];
}
exposeList.push(e.action(supportedActionTypes));
}
return exposeList;
};
const fromZigbee = [
{
cluster: "boschEnergyDevice",
type: ["raw"],
convert: (model, msg, publish, options, meta) => {
const command = msg.data[4];
if (command !== 0x03 && command !== 0x04) {
return;
}
let state;
const status = msg.data[5];
const duration = msg.data[6] / 10;
switch (status) {
case 0:
state = "press_released";
break;
case 1:
state = duration !== 0 ? "hold" : "hold_released";
break;
case 2:
state = "closed";
break;
case 3:
state = "opened";
break;
}
let action;
if (hasDualSwitchInputs) {
const triggeredSide = command === 0x03 ? "left" : "right";
action = `${state}_${triggeredSide}`;
}
else {
action = state;
}
return { action: action, action_duration: duration };
},
},
];
return {
exposes: [expose],
fromZigbee,
isModernExtend: true,
};
},
slzExtends: () => {
const stateDeviceMode = {
light: 0x04,
shutter: 0x01,
disabled: 0x00,
};
const stateMotor = {
stopped: 0x00,
opening: 0x01,
closing: 0x02,
unknownOne: 0x03,
unknownTwo: 0x04,
};
const stateSwitchType = {
button: 0x01,
button_key_change: 0x02,
rocker_switch: 0x03,
rocker_switch_key_change: 0x04,
none: 0x00,
};
const stateSwitchMode = {
coupled: 0x00,
decoupled: 0x01,
only_short_press_decoupled: 0x02,
only_long_press_decoupled: 0x03,
};
const stateOffOn = {
OFF: 0x00,
ON: 0x01,
};
const fromZigbee = [
fz.on_off_force_multiendpoint,
fz.power_on_behavior,
fz.cover_position_tilt,
{
cluster: "boschEnergyDevice",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
const data = msg.data;
if (data.deviceMode !== undefined) {
result.device_mode = Object.keys(stateDeviceMode).find((key) => stateDeviceMode[key] === msg.data.deviceMode);
const deviceMode = msg.data.deviceMode;
if (deviceMode !== meta.device.meta.deviceMode) {
meta.device.meta.deviceMode = deviceMode;
meta.deviceExposesChanged();
}
}
if (data.switchType !== undefined) {
const switchType = msg.data.switchType;
result.switch_type = Object.keys(stateSwitchType).find((key) => stateSwitchType[key] === switchType);
if (switchType !== meta.device.meta.switchType) {
meta.device.meta.switchType = switchType;
meta.deviceExposesChanged();
}
}
if (data.switchMode !== undefined) {
const property = utils.postfixWithEndpointName("switch_mode", msg, model, meta);
result[property] = Object.keys(stateSwitchMode).find((key) => stateSwitchMode[key] === msg.data.switchMode);
}
if (data.calibrationOpeningTime !== undefined) {
result.calibration_opening_time = msg.data.calibrationOpeningTime / 10;
}
if (data.calibrationClosingTime !== undefined) {
result.calibration_closing_time = msg.data.calibrationClosingTime / 10;
}
if (data.calibrationButtonHoldTime !== undefined) {
result.calibration_button_hold_time = msg.data.calibrationButtonHoldTime / 10;
}
if (data.calibrationMotorStartDelay !== undefined) {
result.calibration_motor_start_delay = msg.data.calibrationMotorStartDelay / 10;
}
if (data.childLock !== undefined) {
const property = utils.postfixWithEndpointName("child_lock", msg, model, meta);
result[property] = msg.data.childLock === 1 ? "ON" : "OFF";
}
if (data.motorState !== undefined) {
result.motor_state = Object.keys(stateMotor).find((key) => stateMotor[key] === msg.data.motorState);
}
if (data.autoOffEnabled !== undefined) {
const property = utils.postfixWithEndpointName("auto_off_enabled", msg, model, meta);
result[property] = msg.data.autoOffEnabled === 1 ? "ON" : "OFF";
}
if (data.autoOffTime !== undefined) {
const property = utils.postfixWithEndpointName("auto_off_time", msg, model, meta);
result[property] = msg.data.autoOffTime / 60;
}
return result;
},
},
];
const toZigbee = [
tz.power_on_behavior,
tz.cover_position_tilt,
{
key: [
"device_mode",
"switch_type",
"switch_mode",
"child_lock",
"state",
"on_time",
"off_wait_time",
"auto_off_enabled",
"auto_off_time",
],
convertSet: async (entity, key, value, meta) => {
if (key === "state") {
if ("ID" in entity && entity.ID === 1) {
await tz.cover_state.convertSet(entity, key, value, meta);
}
else {
await tz.on_off.convertSet(entity, key, value, meta);
}
}
if (key === "on_time" || key === "on_wait_time") {
if ("ID" in entity && entity.ID !== 1) {
await tz.on_off.convertSet(entity, key, value, meta);
}
}
if (key === "device_mode") {
const index = utils.getFromLookup(value, stateDeviceMode);
await entity.write("boschEnergyDevice", { deviceMode: index });
await entity.read("boschEnergyDevice", ["deviceMode"]);
return { state: { device_mode: value } };
}
if (key === "switch_type") {
const applyDefaultForSwitchModeAndChildLock = async (endpoint) => {
const switchModeDefault = utils.getFromLookup("coupled", stateSwitchMode);
const childLockDefault =