Skip to content

SMaBiT AV2010/32 does support cool when configured via ctrlSeqOfOper #5452

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

Merged
merged 10 commits into from
Feb 8, 2023
Merged
13 changes: 12 additions & 1 deletion converters/toZigbee.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ const converters = {
await entity.read('genPowerCfg', ['batteryPercentageRemaining']);
},
},
battery_voltage: {
key: ['battery', 'voltage'],
convertGet: async (entity, key, meta) => {
await entity.read('genPowerCfg', ['batteryVoltage']);
},
},
power_on_behavior: {
key: ['power_on_behavior'],
convertSet: async (entity, key, value, meta) => {
Expand Down Expand Up @@ -1260,7 +1266,12 @@ const converters = {
if (val === undefined) {
val = utils.getKey(constants.thermostatControlSequenceOfOperations, value, value, Number);
}
await entity.write('hvacThermostat', {ctrlSeqeOfOper: val});
const attributes = {ctrlSeqeOfOper: val};
await entity.write('hvacThermostat', attributes);
// NOTE: update the cluster attribute we store as this is used by
// SMaBiT AV2010/32's dynamic expose function.
entity.saveClusterAttributeKeyValue('hvacThermostat', attributes);
return {readAfterWriteTime: 250, state: {control_sequence_of_operation: value}};
},
convertGet: async (entity, key, meta) => {
await entity.read('hvacThermostat', ['ctrlSeqeOfOper']);
Expand Down
47 changes: 39 additions & 8 deletions devices/bitron.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const tz = require('../converters/toZigbee');
const reporting = require('../lib/reporting');
const extend = require('../lib/extend');
const e = exposes.presets;
const ea = exposes.access;

module.exports = [
{
Expand Down Expand Up @@ -190,26 +191,56 @@ module.exports = [
vendor: 'SMaBiT (Bitron Video)',
description: 'Wireless wall thermostat with relay',
fromZigbee: [fz.legacy.thermostat_att_report, fz.battery, fz.hvac_user_interface],
toZigbee: [tz.thermostat_control_sequence_of_operation, tz.thermostat_occupied_heating_setpoint,
tz.thermostat_occupied_cooling_setpoint, tz.thermostat_local_temperature_calibration, tz.thermostat_local_temperature,
tz.thermostat_running_state, tz.thermostat_temperature_display_mode, tz.thermostat_keypad_lockout, tz.thermostat_system_mode],
exposes: [e.battery(), exposes.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 0.5).withLocalTemperature()
.withSystemMode(['off', 'heat']).withRunningState(['idle', 'heat'])
.withLocalTemperatureCalibration(), e.keypad_lockout()],
meta: {battery: {voltageToPercentage: '3V_2500_3200'}},
toZigbee: [
tz.thermostat_control_sequence_of_operation, tz.thermostat_occupied_heating_setpoint,
tz.thermostat_occupied_cooling_setpoint, tz.thermostat_local_temperature_calibration,
tz.thermostat_local_temperature, tz.thermostat_running_state, tz.thermostat_temperature_display_mode,
tz.thermostat_keypad_lockout, tz.thermostat_system_mode, tz.battery_voltage,
],
exposes: (device, options) => {
const dynExposes = [];
let ctrlSeqeOfOper = device ? device.getEndpoint(1).getClusterAttributeValue('hvacThermostat', 'ctrlSeqeOfOper') : null;
const modes = [];

// NOTE: ctrlSeqeOfOper defaults to 2 for this device (according to the manual)
if (ctrlSeqeOfOper == null) ctrlSeqeOfOper = 2;

// NOTE: set cool and/or heat support based on ctrlSeqeOfOper (see lib/constants -> thermostatControlSequenceOfOperations)
// WARN: a restart of zigbee2mqtt is required after changing ctrlSeqeOfOper for expose data to be re-calculated
if (ctrlSeqeOfOper >= 2) {
modes.push('heat');
}
if (ctrlSeqeOfOper < 2 || ctrlSeqeOfOper > 3) {
modes.push('cool');
}

dynExposes.push(exposes.climate()
.withSetpoint('occupied_heating_setpoint', 7, 30, 0.5)
.withLocalTemperature()
.withSystemMode(['off'].concat(modes))
.withRunningState(['idle'].concat(modes))
.withLocalTemperatureCalibration()
.withControlSequenceOfOperation(['heating_only', 'cooling_only'], ea.ALL));
dynExposes.push(e.keypad_lockout());
dynExposes.push(e.battery().withAccess(ea.STATE_GET));
dynExposes.push(e.battery_low());
dynExposes.push(e.linkquality());
return dynExposes;
},
meta: {battery: {voltageToPercentage: '3V_2500'}},
configure: async (device, coordinatorEndpoint, logger) => {
const endpoint = device.getEndpoint(1);
const binds = [
'genBasic', 'genPowerCfg', 'genIdentify', 'genPollCtrl', 'hvacThermostat', 'hvacUserInterfaceCfg',
];
await reporting.bind(endpoint, coordinatorEndpoint, binds);
await reporting.thermostatTemperature(endpoint);
await reporting.thermostatTemperatureCalibration(endpoint);
await reporting.thermostatOccupiedHeatingSetpoint(endpoint);
await reporting.thermostatOccupiedCoolingSetpoint(endpoint);
await reporting.thermostatRunningState(endpoint);
await reporting.batteryAlarmState(endpoint);
await reporting.batteryVoltage(endpoint);
await endpoint.read('hvacThermostat', ['ctrlSeqeOfOper', 'localTemperatureCalibration']);
},
},
{
Expand Down
8 changes: 8 additions & 0 deletions lib/exposes.js
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,14 @@ class Climate extends Base {
return this;
}

withControlSequenceOfOperation(modes, access=a.STATE) {
assert(!this.endpoint, 'Cannot add feature after adding endpoint');
const allowed = ['cooling_only', 'cooling_with_reheat', 'heating_only', 'heating_with_reheat', 'cooling_and_heating_4-pipes', 'cooling_and_heating_4-pipes_with_reheat'];
modes.forEach((m) => assert(allowed.includes(m)));
this.features.push(new Enum('control_sequence_of_operation', access, modes).withDescription('Operating environment of the thermostat'));
return this;
}

withAcLouverPosition(positions, access=a.ALL) {
assert(!this.endpoint, 'Cannot add feature after adding endpoint');
const allowed = ['fully_open', 'fully_closed', 'half_open', 'quarter_open', 'three_quarters_open'];
Expand Down