You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In any case, i was told on the discord server that given the fact there's multiple custom converters, it would be better to just put the external converter here and have someone more experienced with the codebase actually integrate it in herdsman.
I was able to make almost all of the features work fine in z2m, there were a couple (anti freezing config, ThermostatProgrammingOperationMode) that i wasn't able to integrate, but they're really specific and i don't think they're essential to be controlled via z2m.
following is the external converter i used (tested on the home assistant z2m addon, v.1.42.0-2)
const{}=require('zigbee-herdsman-converters/lib/modernExtend');constfz=require('zigbee-herdsman-converters/converters/fromZigbee');consttz=require('zigbee-herdsman-converters/converters/toZigbee');constexposes=require('zigbee-herdsman-converters/lib/exposes');constreporting=require('zigbee-herdsman-converters/lib/reporting');constota=require('zigbee-herdsman-converters/lib/ota');constutils=require('zigbee-herdsman-converters/lib/utils');constglobalStore=require('zigbee-herdsman-converters/lib/store');constlogger=require('zigbee-herdsman-converters/lib/logger')constconstants=require('zigbee-herdsman-converters/lib/constants')constzh=require('zigbee-herdsman')conste=exposes.presets;constea=exposes.access;asyncfunctionsyncTime(endpoint){try{consttime=Math.round((newDate().getTime()-constants.OneJanuary2000)/1000+newDate().getTimezoneOffset()*-1*60);constvalues={time: time};awaitendpoint.write('genTime',values);}catch(e){/* Do nothing*/logger.logger.warning(e)}}constfzLocal={thermostat: {cluster: 'hvacThermostat',type: ['attributeReport','readResponse'],convert: (model,msg,publish,options,meta)=>{constresult={};if(msg.data.minSetpointDeadBand!==undefined){result[utils.postfixWithEndpointName('min_setpoint_deadband',msg,model,meta)]=utils.precisionRound(msg.data['minSetpointDeadBand'],2)/10;}returnresult;},},};consttzLocal={min_setpoint_deadband: {key: ['min_setpoint_deadband'],convertGet: async(entity,key,meta)=>{awaitentity.read('hvacThermostat',['minSetpointDeadBand']);},convertSet: async(entity,key,value,meta)=>{letnewValue=value;awaitentity.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)=>{letnewValue=value;constlookup={room: 0,set: 1,floor: 2};constpayload={0x1008: {value: utils.getFromLookup(value,lookup),type: zh.Zcl.DataType.ENUM8}};awaitentity.write('hvacThermostat',payload,{manufacturerCode: 0x1224});return{state: {temperature_display: value}};},convertGet: async(entity,key,meta)=>{awaitentity.read('hvacThermostat',[0x1008],{manufacturerCode: 0x1224});},},sensor:{key:['sensor'],convertSet: async(entity,key,value,meta)=>{constlookup={room: 1,floor: 2};constpayload={0x1003: {value: utils.getFromLookup(value,lookup),type: zh.Zcl.DataType.ENUM8}};awaitentity.write('hvacThermostat',payload,{manufacturerCode: 0x1224});return{state: {sensor: value}};},convertGet: async(entity,key,meta)=>{awaitentity.read('hvacThermostat',[0x1003],{manufacturerCode: 0x1224});},},lcd_brightness:{key:['lcd_brightness'],convertSet: async(entity,key,value,meta)=>{constlookup={low: 1,mid: 2,high: 3};constpayload={0x1000: {value: utils.getFromLookup(value,lookup),type: zh.Zcl.DataType.ENUM8}};awaitentity.write('hvacThermostat',payload,{manufacturerCode: 0x1224});return{state: {lcd_brightness: value}};},convertGet: async(entity,key,meta)=>{awaitentity.read('hvacThermostat',[0x1000],{manufacturerCode: 0x1224});},}};constdefinition={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');}elseif(!globalStore.hasValue(device,'time')){constendpoint=device.getEndpoint(1);consthours24=1000*60*60*24;// Device does not ask for the time with binding, therefore we write the time every 24 hoursconstinterval=setInterval(async()=>awaitsyncTime(endpoint),hours24);globalStore.putValue(device,'time',interval);}},configure: async(device,coordinatorEndpoint)=>{constendpoint=device.getEndpoint(1);constbinds=['genBasic','genIdentify','hvacThermostat','seMetering','genTime','hvacUserInterfaceCfg',];awaitreporting.bind(endpoint,coordinatorEndpoint,binds);// standard ZCL attributesawaitreporting.thermostatTemperature(endpoint);awaitreporting.thermostatOccupiedHeatingSetpoint(endpoint);awaitreporting.thermostatUnoccupiedHeatingSetpoint(endpoint);try{awaitreporting.thermostatKeypadLockMode(endpoint);}catch{// Fails for some// https://github.com/Koenkk/zigbee2mqtt/issues/15025logger.debug(`Failed to setup keypadLockout reporting`,NS);}// Custom attributesconstoptions={manufacturerCode: 0x1224};// OperateDisplayLcdBrightnesssawaitendpoint.configureReporting('hvacThermostat',[{attribute: {ID: 0x1000,type: 0x30},minimumReportInterval: 0,maximumReportInterval: constants.repInterval.HOUR,reportableChange: null,},],options,);// ButtonVibrationLevelawaitendpoint.configureReporting('hvacThermostat',[{attribute: {ID: 0x1001,type: 0x30},minimumReportInterval: 0,maximumReportInterval: constants.repInterval.HOUR,reportableChange: null,},],options,);// FloorSensorTypeawaitendpoint.configureReporting('hvacThermostat',[{attribute: {ID: 0x1002,type: 0x30},minimumReportInterval: 0,maximumReportInterval: constants.repInterval.HOUR,reportableChange: null,},],options,);// ControlTypeawaitendpoint.configureReporting('hvacThermostat',[{attribute: {ID: 0x1003,type: 0x30},minimumReportInterval: 0,maximumReportInterval: constants.repInterval.HOUR,reportableChange: null,},],options,);// PowerUpStatusawaitendpoint.configureReporting('hvacThermostat',[{attribute: {ID: 0x1004,type: 0x30},minimumReportInterval: 0,maximumReportInterval: constants.repInterval.HOUR,reportableChange: null,},],options,);// FloorSensorCalibrationawaitendpoint.configureReporting('hvacThermostat',[{attribute: {ID: 0x1005,type: 0x28},minimumReportInterval: 0,maximumReportInterval: constants.repInterval.HOUR,reportableChange: 0,},],options,);// TemperatureDisplayawaitendpoint.configureReporting('hvacThermostat',[{attribute: {ID: 0x1008,type: 0x30},minimumReportInterval: 0,maximumReportInterval: constants.repInterval.HOUR,reportableChange: null,},],options,);// Away Mode Setawaitendpoint.configureReporting('hvacThermostat',[{attribute: {ID: 0x2002,type: 0x30},minimumReportInterval: 0,maximumReportInterval: constants.repInterval.HOUR,reportableChange: null,},],);// Control Sequence Of Operationawaitendpoint.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 configureawaitsyncTime(endpoint);// Trigger initial readawaitendpoint.read('hvacThermostat',['systemMode','runningState','occupiedHeatingSetpoint']);awaitendpoint.read('hvacThermostat',[0x2002,0x001b]);awaitendpoint.read('hvacThermostat',[0x1000,0x1001,0x1002,0x1003],options);awaitendpoint.read('hvacThermostat',[0x1004,0x1005],options);awaitendpoint.read('hvacThermostat',[0x1008],options);},};module.exports=definition;
The text was updated successfully, but these errors were encountered:
I wrote an external definition for this thermostat https://www.sunricher.com/zigbee-thermostat-heating-cooling-controller-sr-zg9095b-0-10v.html, the device is pretty similar to the already supported SR-ZG9092A but can control both heating and cooling, and as i found out has a quite different zigbee spec, mostly different enums.
In any case, i was told on the discord server that given the fact there's multiple custom converters, it would be better to just put the external converter here and have someone more experienced with the codebase actually integrate it in herdsman.
The entire zigbee spec of the device is available in the manual https://www.sunricher.com/media/resources/manual/SR-ZG9095B-0-10V%20instruction.pdf.
I was able to make almost all of the features work fine in z2m, there were a couple (anti freezing config, ThermostatProgrammingOperationMode) that i wasn't able to integrate, but they're really specific and i don't think they're essential to be controlled via z2m.
following is the external converter i used (tested on the home assistant z2m addon, v.1.42.0-2)
The text was updated successfully, but these errors were encountered: