Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add custom cluster for SONOFF TRVZB #7432

Merged
merged 4 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 45 additions & 20 deletions src/devices/sonoff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -805,24 +805,24 @@ const definitions: Definition[] = [
extend: [
binary({
name: 'child_lock',
cluster: 0xFC11,
attribute: {ID: 0x0000, type: 0x10},
cluster: 'sonoffTrvzbCluster',
attribute: 'childLock',
description: 'Enables/disables physical input on the device',
valueOn: ['LOCK', 0x01],
valueOff: ['UNLOCK', 0x00],
}),
binary({
name: 'open_window',
cluster: 0xFC11,
attribute: {ID: 0x6000, type: 0x10},
cluster: 'sonoffTrvzbCluster',
attribute: 'openWindow',
description: 'Automatically turns off the radiator when local temperature drops by more than 1.5°C in 4.5 minutes.',
valueOn: ['ON', 0x01],
valueOff: ['OFF', 0x00],
}),
numeric({
name: 'frost_protection_temperature',
cluster: 0xFC11,
attribute: {ID: 0x6002, type: 0x29},
cluster: 'sonoffTrvzbCluster',
attribute: 'frostProtectionTemperature',
description: 'Minimum temperature at which to automatically turn on the radiator, ' +
'if system mode is off, to prevent pipes freezing.',
valueMin: 4.0,
Expand All @@ -833,46 +833,46 @@ const definitions: Definition[] = [
}),
numeric({
name: 'idle_steps',
cluster: 0xFC11,
attribute: {ID: 0x6003, type: 0x21},
cluster: 'sonoffTrvzbCluster',
attribute: 'idleSteps',
description: 'Number of steps used for calibration (no-load steps)',
access: 'STATE_GET',
}),
numeric({
name: 'closing_steps',
cluster: 0xFC11,
attribute: {ID: 0x6004, type: 0x21},
cluster: 'sonoffTrvzbCluster',
attribute: 'closingSteps',
description: 'Number of steps it takes to close the valve',
access: 'STATE_GET',
}),
numeric({
name: 'valve_opening_limit_voltage',
cluster: 0xFC11,
attribute: {ID: 0x6005, type: 0x21},
cluster: 'sonoffTrvzbCluster',
attribute: 'valveOpeningLimitVoltage',
description: 'Valve opening limit voltage',
unit: 'mV',
access: 'STATE_GET',
}),
numeric({
name: 'valve_closing_limit_voltage',
cluster: 0xFC11,
attribute: {ID: 0x6006, type: 0x21},
cluster: 'sonoffTrvzbCluster',
attribute: 'valveClosingLimitVoltage',
description: 'Valve closing limit voltage',
unit: 'mV',
access: 'STATE_GET',
}),
numeric({
name: 'valve_motor_running_voltage',
cluster: 0xFC11,
attribute: {ID: 0x6007, type: 0x21},
cluster: 'sonoffTrvzbCluster',
attribute: 'valveMotorRunningVoltage',
description: 'Valve motor running voltage',
unit: 'mV',
access: 'STATE_GET',
}),
numeric({
name: 'valve_opening_degree',
cluster: 0xFC11,
attribute: {ID: 0x600B, type: 0x20},
cluster: 'sonoffTrvzbCluster',
attribute: 'valveOpeningDegree',
description: 'Valve open position (percentage) control. ' +
'If the opening degree is set to 100%, the valve is fully open when it is opened. ' +
'If the opening degree is set to 0%, the valve is fully closed when it is opened, ' +
Expand All @@ -885,8 +885,8 @@ const definitions: Definition[] = [
}),
numeric({
name: 'valve_closing_degree',
cluster: 0xFC11,
attribute: {ID: 0x600C, type: 0x20},
cluster: 'sonoffTrvzbCluster',
attribute: 'valveClosingDegree',
description: 'Valve closed position (percentage) control. ' +
'If the closing degree is set to 100%, the valve is fully closed when it is closed. ' +
'If the closing degree is set to 0%, the valve is fully opened when it is closed, ' +
Expand All @@ -910,6 +910,31 @@ const definitions: Definition[] = [
await endpoint.read('hvacThermostat', ['localTemperatureCalibration']);
await endpoint.read(0xFC11, [0x0000, 0x6000, 0x6002, 0x6003, 0x6004, 0x6005, 0x6006, 0x6007]);
},
onEvent: async (type, data, device, settings, state) => {
const name = 'sonoffTrvzbCluster';
if (!device.customClusters[name]) {
device.addCustomCluster(name, {
ID: 0xfc11,
manufacturerCode: 0x1286,
attributes: {
childLock: {ID: 0x0000, type: Zcl.DataType.boolean},
tamper: {ID: 0x2000, type: Zcl.DataType.uint8},
illumination: {ID: 0x2001, type: Zcl.DataType.uint8},
openWindow: {ID: 0x6000, type: Zcl.DataType.boolean},
frostProtectionTemperature: {ID: 0x6002, type: Zcl.DataType.int16},
idleSteps: {ID: 0x6003, type: Zcl.DataType.uint16},
closingSteps: {ID: 0x6004, type: Zcl.DataType.uint16},
valveOpeningLimitVoltage: {ID: 0x6005, type: Zcl.DataType.uint16},
valveClosingLimitVoltage: {ID: 0x6006, type: Zcl.DataType.uint16},
valveMotorRunningVoltage: {ID: 0x6007, type: Zcl.DataType.uint16},
valveOpeningDegree: {ID: 0x600B, type: Zcl.DataType.uint8},
valveClosingDegree: {ID: 0x600C, type: Zcl.DataType.uint8},
},
commands: {},
commandsResponse: {},
});
}
},
},
{
zigbeeModel: ['S60ZBTPF'],
Expand Down
20 changes: 12 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import assert from 'assert';
import * as ota from './lib/ota';
import allDefinitions from './devices';
import * as utils from './lib/utils';
import {Definition, Fingerprint, Zh, OnEventData, OnEventType, Configure, Expose, Tz, OtaUpdateAvailableResult, KeyValue} from './lib/types';
import {Definition, Fingerprint, Zh, OnEventData, OnEventType, Configure, Expose, Tz, OtaUpdateAvailableResult, KeyValue, OnEvent} from './lib/types';
import {generateDefinition} from './lib/generateDefinition';
import {Zcl} from 'zigbee-herdsman';
import * as logger from './lib/logger';
Expand Down Expand Up @@ -101,7 +101,7 @@ function processExtensions(definition: Definition): Definition {
assert.fail(`'${definition.model}' has legacy extend which is not supported anymore`);
}
// Modern extend, merges properties, e.g. when both extend and definition has toZigbee, toZigbee will be combined
let {extend, toZigbee, fromZigbee, exposes, meta, endpoint, configure: definitionConfigure, onEvent, ota, ...definitionWithoutExtend} = definition;
let {extend, toZigbee, fromZigbee, exposes, meta, endpoint, configure: definitionConfigure, onEvent: definitionOnEvent, ota, ...definitionWithoutExtend} = definition;
if (typeof exposes === 'function') {
assert.fail(`'${definition.model}' has function exposes which is not allowed`);
}
Expand All @@ -111,6 +111,7 @@ function processExtensions(definition: Definition): Definition {
fromZigbee = [...fromZigbee ?? []];

const configures: Configure[] = definitionConfigure ? [definitionConfigure] : [];
const onEvents: OnEvent[] = definitionOnEvent ? [definitionOnEvent] : [];

for (const ext of extend) {
if (!ext.isModernExtend) {
Expand All @@ -121,6 +122,7 @@ function processExtensions(definition: Definition): Definition {
if (ext.exposes) exposes.push(...ext.exposes);
if (ext.meta) meta = {...ext.meta, ...meta};
if (ext.configure) configures.push(ext.configure);
if (ext.onEvent) onEvents.push(ext.onEvent);
if (ext.ota) {
if (ota && ext.ota !== ota) {
assert.fail(`'${definition.model}' has multiple 'ota', this is not allowed`);
Expand All @@ -133,12 +135,6 @@ function processExtensions(definition: Definition): Definition {
}
endpoint = ext.endpoint;
}
if (ext.onEvent) {
if (onEvent) {
assert.fail(`'${definition.model}' has multiple 'onEvent', this is not allowed`);
}
onEvent = ext.onEvent;
}
}

// Filtering out action exposes to combine them one
Expand All @@ -165,6 +161,14 @@ function processExtensions(definition: Definition): Definition {
}
}
}
let onEvent: OnEvent = null;
if (onEvents.length !== 0) {
onEvent = async (type, data, device, settings, state) => {
for (const func of onEvents) {
await func(type, data, device, settings, state);
}
}
}
definition = {toZigbee, fromZigbee, exposes, meta, configure, endpoint, onEvent, ota, ...definitionWithoutExtend};
}

Expand Down
Loading