Skip to content

Instantly share code, notes, and snippets.

@ProtoxiDe22
Created January 2, 2025 21:18
Show Gist options
  • Select an option

  • Save ProtoxiDe22/c34f3551aba2bcaaf0d8fa1c88e6d4f9 to your computer and use it in GitHub Desktop.

Select an option

Save ProtoxiDe22/c34f3551aba2bcaaf0d8fa1c88e6d4f9 to your computer and use it in GitHub Desktop.
zigbee2mqtt external definition for sunricher zigbe 0-10v thermostat
const {} = require('zigbee-herdsman-converters/lib/modernExtend');
const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const reporting = require('zigbee-herdsman-converters/lib/reporting');
const ota = require('zigbee-herdsman-converters/lib/ota');
const utils = require('zigbee-herdsman-converters/lib/utils');
const globalStore = require('zigbee-herdsman-converters/lib/store');
const logger = require('zigbee-herdsman-converters/lib/logger')
const constants = require('zigbee-herdsman-converters/lib/constants')
const zh = require('zigbee-herdsman')
const e = exposes.presets;
const ea = exposes.access;
async function syncTime(endpoint) {
try {
const time = Math.round((new Date().getTime() - constants.OneJanuary2000) / 1000 + new Date().getTimezoneOffset() * -1 * 60);
const values = {time: time};
await endpoint.write('genTime', values);
} catch (e){
/* Do nothing*/
logger.logger.warning(e)
}
}
const fzLocal = {
thermostat: {
cluster: 'hvacThermostat',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.minSetpointDeadBand !== undefined) {
result[
utils.postfixWithEndpointName('min_setpoint_deadband', msg, model, meta)
] = utils.precisionRound(msg.data['minSetpointDeadBand'], 2) / 10;
}
return result;
},
},
};
const tzLocal = {
min_setpoint_deadband: {
key: ['min_setpoint_deadband'],
convertGet: async (entity, key, meta) => {
await entity.read('hvacThermostat', ['minSetpointDeadBand']);
},
convertSet: async (entity, key, value, meta) => {
let newValue = value;
await entity.write('hvacThermostat', {
minSetpointDeadBand: Math.round(Number(value) * 10),
});
return { state: { min_setpoint_deadband: value } };
},
},
temperature_display: {
key:['temperature_display'],
convertSet: async (entity, key, value, meta) => {
let newValue = value;
const lookup = {room: 0, set: 1, floor: 2};
const payload = {0x1008: {value: utils.getFromLookup(value, lookup), type: zh.Zcl.DataType.ENUM8}};
await entity.write('hvacThermostat', payload, {manufacturerCode: 0x1224});
return { state: { temperature_display: value } };
},
convertGet: async (entity, key, meta) => {
await entity.read('hvacThermostat', [0x1008], {manufacturerCode: 0x1224});
},
},
sensor:{
key:['sensor'],
convertSet: async (entity, key, value, meta) => {
const lookup = {room: 1, floor: 2};
const payload = {0x1003: {value: utils.getFromLookup(value, lookup), type: zh.Zcl.DataType.ENUM8}};
await entity.write('hvacThermostat', payload, {manufacturerCode: 0x1224});
return { state: { sensor: value } };
},
convertGet: async (entity, key, meta) => {
await entity.read('hvacThermostat', [0x1003], {manufacturerCode: 0x1224});
},
},
lcd_brightness:{
key:['lcd_brightness'],
convertSet: async (entity, key, value, meta) => {
const lookup = {low: 1, mid: 2, high: 3};
const payload = {0x1000: {value: utils.getFromLookup(value, lookup), type: zh.Zcl.DataType.ENUM8}};
await entity.write('hvacThermostat', payload, {manufacturerCode: 0x1224});
return { state: { lcd_brightness: value } };
},
convertGet: async (entity, key, meta) => {
await entity.read('hvacThermostat', [0x1000], {manufacturerCode: 0x1224});
},
}
};
const definition = {
zigbeeModel: ['ZG9095B'],
model: 'SR-ZG9095B',
vendor: 'Sunricher',
description: 'Touch thermostat',
fromZigbee: [fz.thermostat, fz.namron_thermostat, fz.metering, fz.electrical_measurement, fz.namron_hvac_user_interface, fzLocal.thermostat],
toZigbee: [
tzLocal.temperature_display,
tzLocal.sensor,
tzLocal.lcd_brightness,
tz.thermostat_occupied_heating_setpoint,
tz.thermostat_unoccupied_heating_setpoint,
tz.thermostat_occupied_cooling_setpoint,
tz.thermostat_unoccupied_cooling_setpoint,
tz.thermostat_local_temperature_calibration,
tz.thermostat_local_temperature,
tz.thermostat_outdoor_temperature,
tz.thermostat_system_mode,
tz.thermostat_control_sequence_of_operation,
tz.thermostat_running_state,
tz.namron_thermostat,
tz.namron_thermostat_child_lock,
tz.fan_mode,
tzLocal.min_setpoint_deadband
],
exposes: [
e.numeric('outdoor_temperature', ea.STATE_GET).withUnit('°C').withDescription('Current temperature measured from the floor sensor'),
e
.climate()
.withSetpoint('occupied_heating_setpoint', 5, 32, 0.1)
.withSetpoint('unoccupied_heating_setpoint', 5, 32, 0.1)
.withSetpoint('occupied_cooling_setpoint', 5, 32, 0.1)
.withSetpoint('unoccupied_cooling_setpoint', 5, 32, 0.1)
.withLocalTemperature()
.withLocalTemperatureCalibration(-2.5, 2.5, 0.1)
.withSystemMode(['off', 'auto', 'cool', 'heat', 'fan_only'])
.withRunningState(['idle', 'heat', 'cool', 'fan_only'])
.withFanMode(['off', 'low', 'medium', 'high', 'auto'])
.withControlSequenceOfOperation(['cooling_only', 'heating_only', 'cooling_and_heating_4-pipes']),
e.binary('away_mode', ea.ALL, 'ON', 'OFF').withDescription('Enable/disable away mode'),
e.binary('child_lock', ea.ALL, 'UNLOCK', 'LOCK').withDescription('Enables/disables physical input on the device'),
e.enum('lcd_brightness', ea.ALL, ['low', 'mid', 'high']).withDescription('OLED brightness when operating the buttons. Default: Medium.'),
e.enum('button_vibration_level', ea.ALL, ['off', 'low', 'high']).withDescription('Key beep volume and vibration level. Default: Low.'),
e
.enum('floor_sensor_type', ea.ALL, ['10k', '15k', '50k', '100k', '12k'])
.withDescription('Type of the external floor sensor. Default: NTC 10K/25.'),
e.enum('sensor', ea.ALL, ['room', 'floor']).withDescription('The sensor used for heat control. Default: Room Sensor.'),
e.enum('powerup_status', ea.ALL, ['default', 'last_status']).withDescription('The mode after a power reset. Default: Previous Mode.'),
e
.numeric('floor_sensor_calibration', ea.ALL)
.withUnit('°C')
.withValueMin(-2.5)
.withValueMax(2.5)
.withValueStep(0.1)
.withDescription('The tempearatue calibration for the external floor sensor, between -3 and 3 in 0.1°C. Default: 0.'),
e.enum('temperature_display', ea.ALL, ['room', 'set', 'floor']).withDescription('The temperature on the display. Default: Room Temperature.'),
e
.numeric('min_setpoint_deadband', ea.ALL)
.withUnit('°C')
.withValueMin(1)
.withValueMax(1.5)
.withValueStep(0.1)
.withDescription('This parameter refers to the minimum difference between cooling and heating temperatures. between 1 and 1.5 in 0.1 °C Default: 1 °C. The hysteresis used by this device = MinSetpointDeadBand /2'),
],
onEvent: async (type, data, device, options) => {
if (type === 'stop') {
clearInterval(globalStore.getValue(device, 'time'));
globalStore.clearValue(device, 'time');
} else if (!globalStore.hasValue(device, 'time')) {
const endpoint = device.getEndpoint(1);
const hours24 = 1000 * 60 * 60 * 24;
// Device does not ask for the time with binding, therefore we write the time every 24 hours
const interval = setInterval(async () => await syncTime(endpoint), hours24);
globalStore.putValue(device, 'time', interval);
}
},
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
const binds = [
'genBasic',
'genIdentify',
'hvacThermostat',
'seMetering',
'genTime',
'hvacUserInterfaceCfg',
];
await reporting.bind(endpoint, coordinatorEndpoint, binds);
// standard ZCL attributes
await reporting.thermostatTemperature(endpoint);
await reporting.thermostatOccupiedHeatingSetpoint(endpoint);
await reporting.thermostatUnoccupiedHeatingSetpoint(endpoint);
try {
await reporting.thermostatKeypadLockMode(endpoint);
} catch {
// Fails for some
// https://github.com/Koenkk/zigbee2mqtt/issues/15025
logger.debug(`Failed to setup keypadLockout reporting`, NS);
}
// Custom attributes
const options = {manufacturerCode: 0x1224};
// OperateDisplayLcdBrightnesss
await endpoint.configureReporting(
'hvacThermostat',
[
{
attribute: {ID: 0x1000, type: 0x30},
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
],
options,
);
// ButtonVibrationLevel
await endpoint.configureReporting(
'hvacThermostat',
[
{
attribute: {ID: 0x1001, type: 0x30},
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
],
options,
);
// FloorSensorType
await endpoint.configureReporting(
'hvacThermostat',
[
{
attribute: {ID: 0x1002, type: 0x30},
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
],
options,
);
// ControlType
await endpoint.configureReporting(
'hvacThermostat',
[
{
attribute: {ID: 0x1003, type: 0x30},
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
],
options,
);
// PowerUpStatus
await endpoint.configureReporting(
'hvacThermostat',
[
{
attribute: {ID: 0x1004, type: 0x30},
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
],
options,
);
// FloorSensorCalibration
await endpoint.configureReporting(
'hvacThermostat',
[
{
attribute: {ID: 0x1005, type: 0x28},
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: 0,
},
],
options,
);
// AntiFreezingModeConfig
await endpoint.configureReporting(
'hvacThermostat',
[
{
attribute: {ID: 0x1006, type: 0x20},
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: 0,
},
],
options,
);
// TemperatureDisplay
await endpoint.configureReporting(
'hvacThermostat',
[
{
attribute: {ID: 0x1008, type: 0x30},
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
],
options,
);
// Away Mode Set
await endpoint.configureReporting(
'hvacThermostat',
[
{
attribute: {ID: 0x2002, type: 0x30},
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
},
],
);
// Control Sequence Of Operation
await endpoint.configureReporting(
'hvacThermostat',
[
{
attribute: {ID: 0x001b, type: 0x30},
minimumReportInterval: 0,
maximumReportInterval: constants.repInterval.HOUR,
reportableChange: null,
}
]
)
// Device does not asks for the time with binding, we need to write time during configure
await syncTime(endpoint);
// Trigger initial read
await endpoint.read('hvacThermostat', ['systemMode', 'runningState', 'occupiedHeatingSetpoint']);
await endpoint.read('hvacThermostat', [0x2002, 0x001b]);
await endpoint.read('hvacThermostat', [0x1000, 0x1001, 0x1002, 0x1003], options);
await endpoint.read('hvacThermostat', [0x1004, 0x1005, 0x1006], options);
await endpoint.read('hvacThermostat', [0x1008], options);
},
};
module.exports = definition;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment