zigbee2mqtt icon indicating copy to clipboard operation
zigbee2mqtt copied to clipboard

[New device support]: Thermostatic radiator valve _TZE200_p3dbf6qs

Open wols89 opened this issue 1 year ago β€’ 8 comments

Link

https://aliexpress.ru/item/1005005770731714.html

Database entry

{"id":5,"type":"EndDevice","ieeeAddr":"0xa4c138e49df54f23","nwkAddr":59756,"manufId":4417,"manufName":"_TZE200_p3dbf6qs","powerSource":"Battery","modelId":"TS0601","epList":[1],"endpoints":{"1":{"profId":260,"epId":1,"devId":81,"inClusterList":[4,5,61184,0],"outClusterList":[25,10],"clusters":{},"binds":[{"cluster":0,"type":"endpoint","deviceIeeeAddress":"0xe0798dfffecfba41","endpointID":1}],"configuredReportings":[],"meta":{}}},"appVersion":67,"stackVersion":0,"hwVersion":1,"dateCode":"","interviewCompleted":true,"meta":{"configured":821693351},"lastSeen":1697819079943,"defaultSendRequestWhen":"immediate"}

Comments

Hello, There is a thermostatic valve, presumably me167. I saw it on Github, they seemed to talk about its support. In fact, when connecting, z2m writes that the device is not supported. Then I installed an external converter from the author @twhittock twhittock - https://github.com/twhittock/avatto_me167. The thermostat has been identified, even the photo is correct. However, none of the functions are active. The Z2M does not see the temperature from the sensor. I also found the code for _TZE200_p3dbf6qs on Github, tried to replace β€œdataPoints: {”, in this case z2m does not start. Tnx!

no support avatto sv

External converter

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 extend = require('zigbee-herdsman-converters/lib/extend');
const e = exposes.presets;
const ea = exposes.access;
const tuya = require("zigbee-herdsman-converters/lib/tuya");

const tuyaLocal = {
  dataPoints: {
    me167Mode: 2,
    me167HeatingSetpoint: 4,
    me167LocalTemp: 5,
    me167ChildLock: 7,
    me167Heating: 3,
    me167Schedule1: 28,
    me167Schedule2: 29,
    me167Schedule3: 30,
    me167Schedule4: 31,
    me167Schedule5: 32,
    me167Schedule6: 33,
    me167Schedule7: 34,
    me167ErrorCode: 35,
    me167FrostGuard: 36,
    me167AntiScaling: 39,
    me167TempCalibration: 47,
  },
};

const fzLocal = {
  me167_thermostat: {
    cluster: 'manuSpecificTuya',
    type: ['commandDataResponse', 'commandDataReport'],
    convert: (model, msg, publish, options, meta) => {
        const result = {};

        function weeklySchedule(day, value) {
          // byte 0 - Day of Week (0~7 = Wed ~ Tue) ???
          // byte 1 - hour ???
          // byte 2 - minute ???
          // byte 3 - Temp (temp = value )
          // byte 4 - Temperature (temp = value / 10)

          const weekDays=[ 'wed', 'thu', 'fri', 'sat', 'sun','mon', 'tue'];
          // we get supplied in value only a weekday schedule, so we must add it to
          // the weekly schedule from meta.state, if it exists
          const weeklySchedule= meta.state.hasOwnProperty('weekly_schedule') ? meta.state.weekly_schedule : {};
          meta.logger.info(JSON.stringify({'received day': day, 'received values': value}));
          let daySchedule = []; // result array
          for (let i=1; i<16 && value[i]; ++i) {
            const aHour=value[i];
            ++i;
            const aMinute=value[i];
            ++i;
            const aTemp2=value[i];
            ++i;
            const aTemp=value[i];
            daySchedule=[...daySchedule, {
              temperature: Math.floor((aTemp+aTemp2*256)/10),
              hour: aHour,
              minute: aMinute,
            }];
          }
          meta.logger.info(JSON.stringify({'returned weekly schedule: ': daySchedule}));
          return {'weekly-schedule': {...weeklySchedule, [weekDays[day]]: daySchedule}};
        }


        for (const dpValue of msg.data.dpValues) {
            const value = tuya.getDataValue(dpValue);

            switch (dpValue.dp) {
            case tuyaLocal.dataPoints.me167ChildLock:
                result.child_lock = value ? 'LOCK' : 'UNLOCK';
                break;
            case tuyaLocal.dataPoints.me167HeatingSetpoint:
                result.current_heating_setpoint = value/10;
                break;
            case tuyaLocal.dataPoints.me167LocalTemp:
                result.local_temperature = value/10;
                break;
            case tuyaLocal.dataPoints.me167Heating:
                switch(value) {
                  case 0:
                    result.running_state = "heat"; // valve open
                    break;
                  case 1:
                    result.running_state = "idle"; // valve closed
                    break;
                  default:
                    meta.logger.warn('zigbee-herdsman-converters:me167_thermostat: ' +
                      `Running state ${value} is not recognized.`);
                    break;
                }
                break;
            case tuyaLocal.dataPoints.me167Mode:
                switch (value) {
                case 0: // auto
                    result.system_mode = 'auto';
                    break;
                case 1: // manu
                    result.system_mode = 'heat';
                    break;
                case 2: // off
                    result.system_mode = 'off';
                    break;
                default:
                    meta.logger.warn('zigbee-herdsman-converters:me167_thermostat: ' +
                      `Mode ${value} is not recognized.`);
                    break;
                }
                break;
            case tuyaLocal.dataPoints.me167Schedule1:
              weeklySchedule(0,value);
              break;
            case tuyaLocal.dataPoints.me167Schedule2:
              weeklySchedule(1,value);
              break;
            case tuyaLocal.dataPoints.me167Schedule3:
              weeklySchedule(2,value);
              break;
            case tuyaLocal.dataPoints.me167Schedule4:
              weeklySchedule(3,value);
              break;
            case tuyaLocal.dataPoints.me167Schedule5:
              weeklySchedule(4,value);
              break;
            case tuyaLocal.dataPoints.me167Schedule6:
              weeklySchedule(5,value);
              break;
            case tuyaLocal.dataPoints.me167Schedule7:
              weeklySchedule(6,value);
              break;
            case tuyaLocal.dataPoints.me167TempCalibration:
              if (value >= 4294967295 ){
                result.local_temperature_calibration = (value-4294967295)-1 // negative values
              }else{
                result.local_temperature_calibration = value
              }
              break;
            case tuyaLocal.dataPoints.me167ErrorCode:
                switch (value) {
                  case 0: // OK
                      result.battery_low = false;
                      meta.logger.info(`zigbee-herdsman-converters:me167_thermostat: BattOK - Error Code: ` +
                    `${JSON.stringify(dpValue)}`);
                      break;
                  case 1: // Empty Battery
                      result.battery_low = true;
                      meta.logger.info(`zigbee-herdsman-converters:me167_thermostat: BattEmtpy - Error Code: ` +
                    `${JSON.stringify(dpValue)}`);
                      break;
                  default:
                      meta.logger.warn(`zigbee-herdsman-converters:me167_thermostat: Error Code not recognized: ` +
                    `${JSON.stringify(dpValue)}`);
                      break;
                  }
                break; 
            case tuyaLocal.dataPoints.me167FrostGuard:
              result.frost_guard = value ? 'ON' : 'OFF';
              break;
            case tuyaLocal.dataPoints.me167AntiScaling:
              result.anti_scaling = value ? 'ON' : 'OFF';
              break;

            default:
                meta.logger.warn(`zigbee-herdsman-converters:me167_thermostat: NOT RECOGNIZED ` +
                  `DP #${dpValue.dp} with data ${JSON.stringify(dpValue)}`);
            }
        }
        return result;
    },
  },
};

const tzLocal = {
  me167_thermostat_current_heating_setpoint: {
      key: ['current_heating_setpoint'],
      convertSet: async (entity, key, value, meta) => {
          const temp = Math.round(value * 10);
          await tuya.sendDataPointValue(entity, tuyaLocal.dataPoints.me167HeatingSetpoint, temp);
      },
  },
  me167_thermostat_system_mode: {
      key: ['system_mode'],
      convertSet: async (entity, key, value, meta) => {
          switch (value) {
          case 'off':
              await tuya.sendDataPointEnum(entity, tuyaLocal.dataPoints.me167Mode, 2 /* off */);
              break;
          case 'heat':
              await tuya.sendDataPointEnum(entity, tuyaLocal.dataPoints.me167Mode, 1 /* manual */);
              break;
          case 'auto':
              await tuya.sendDataPointEnum(entity, tuyaLocal.dataPoints.me167Mode, 0 /* auto */);
              break;
          }
      },
  },
  me167_thermostat_child_lock: {
      key: ['child_lock'],
      convertSet: async (entity, key, value, meta) => {
          await tuya.sendDataPointBool(entity, tuyaLocal.dataPoints.me167ChildLock, value === 'LOCK');
      },
    },

  me167_thermostat_schedule: {
    key: ['weekly_schedule'],
    convertSet: async (entity, key, value, meta) => {
      const weekDays=['wed', 'thu', 'fri', 'sat', 'sun', 'mon' , 'tue'];
      // we overwirte only the received days. The other ones keep stored on the device
      const keys = Object.keys(value);
      for (const dayName of keys) { // for loop in order to delete the empty day schedules
        const output= []; // empty output byte buffer
        const dayNo=weekDays.indexOf(dayName);
        output[0]=dayNo+1;
        const schedule=value[dayName];
        schedule.forEach((el, Index) => {
          if (Index <4) {
            output[1+4*Index]=el.hour;
            output[2+4*Index]=el.minute;
            output[3+4*Index]=Math.floor((el.temperature*10)/256);
            output[4+4*Index]=(el.temperature*10)%256;
          } else {
            meta.logger.warn('more than 4 schedule points supplied for week-day '+dayName +
            ' additional schedule points will be ignored');
          }
        });
        meta.logger.info(`zigbee-herdsman-converters:me167_thermostat: Writing Schedule to ` +
                  `DP #${tuyaLocal.dataPoints.me167Schedule1+dayNo} with data ${JSON.stringify(output)}`);
        await tuya.sendDataPointRaw(entity, tuyaLocal.dataPoints.me167Schedule1+dayNo, output);
        await new Promise((r) => setTimeout(r, 2000));
        // wait 2 seconds between schedule sends in order not to overload the device
      }
    },
  },
  me167_thermostat_calibration: {
    key: ['local_temperature_calibration'],
    convertSet: async (entity, key, value, meta) => {
      if (value >= 0) value = value;
      if (value < 0) value = value+4294967295+1;
      await tuya.sendDataPointValue(entity, tuyaLocal.dataPoints.me167TempCalibration, value);
    },
  },
  me167_thermostat_anti_scaling: {
    key: ['anti_scaling'],
    convertSet: async (entity, key, value, meta) => {
      await tuya.sendDataPointBool(entity, tuyaLocal.dataPoints.me167AntiScaling, value === 'ON');
    },
  },
  me167_thermostat_frost_guard: {
    key: ['frost_guard'],
    convertSet: async (entity, key, value, meta) => {
      await tuya.sendDataPointBool(entity, tuyaLocal.dataPoints.me167FrostGuard, value === 'ON');
    },
  },
};

const definition = {
    // Since a lot of Tuya devices use the same modelID, but use different data points
    // it's usually necessary to provide a fingerprint instead of a zigbeeModel
    fingerprint: [
        {
            // The model ID from: Device with modelID 'TS0601' is not supported
            // You may need to add \u0000 at the end of the name in some cases
            modelID: 'TS0601',
            // The manufacturer name from: Device with modelID 'TS0601' is not supported.
            manufacturerName: '_TZE200_p3dbf6qs'
        },
    ],
    model: 'ME167',
    vendor: 'Avatto',
    description: 'Thermostatic radiator valve',
    fromZigbee: [
        fz.ignore_basic_report, // Add this if you are getting no converter for 'genBasic'
        //fz.tuya_data_point_dump, // This is a debug converter, it will be described in the next part
        fzLocal.me167_thermostat,
    ],
    toZigbee: [
        //tz.tuya_data_point_test, // Another debug converter
        tzLocal.me167_thermostat_child_lock,
        tzLocal.me167_thermostat_current_heating_setpoint,
        tzLocal.me167_thermostat_system_mode,
        tzLocal.me167_thermostat_schedule,
        tzLocal.me167_thermostat_calibration,
        tzLocal.me167_thermostat_anti_scaling,
        tzLocal.me167_thermostat_frost_guard,
    ],
    onEvent: tuya.onEventSetTime, // Add this if you are getting no converter for 'commandMcuSyncTime'
    configure: async (device, coordinatorEndpoint, logger) => {
        const endpoint = device.getEndpoint(1);
        await reporting.bind(endpoint, coordinatorEndpoint, ['genBasic']);
    },
    exposes: [
      e.child_lock(),
      exposes.switch().withState('anti_scaling', true).withDescription('Anti Scaling feature is ON or OFF'),
      exposes.switch().withState('frost_guard', true).withDescription('Frost Protection feature is ON or OFF'),
      exposes.climate().withSetpoint('current_heating_setpoint', 5, 35, 1)
                     .withLocalTemperature()
                     .withSystemMode(['auto','heat','off'])
                     .withRunningState(['idle', 'heat'], ea.STATE)
                     .withLocalTemperatureCalibration(-10, 10, 1, ea.STATE_SET)
    ],
};

module.exports = definition;

Supported color modes

No response

Color temperature range

No response

wols89 avatar Oct 20 '23 20:10 wols89

I'm using: ME167 (AVATTO) modelID: TS0601, manufacturerName:_TZE200_6rdj8dzm ME168 (AVATTO) modelID: TS0601, manufacturerName:_TZE200_p3dbf6qs. Both are supported. Using "system mode" auto on ME168 causes strange values of "Current heating setpoint"

Manni51 avatar Oct 22 '23 10:10 Manni51

Π― ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽ: ME167 (AVATTO) ID ΠΌΠΎΠ΄Π΅Π»ΠΈ: TS0601, имя производитСля: _TZE200_6rdj8dzm ME168 (AVATTO) ID ΠΌΠΎΠ΄Π΅Π»ΠΈ: TS0601, имя производитСля: _TZE200_p3dbf6qs. Оба ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°ΡŽΡ‚ΡΡ. ИспользованиС автоматичСского «систСмного Ρ€Π΅ΠΆΠΈΠΌΠ°Β» Π½Π° ME168 ΠΏΡ€ΠΈΠ²ΠΎΠ΄ΠΈΡ‚ ΠΊ странным значСниям Β«Π’Π΅ΠΊΡƒΡ‰Π΅ΠΉ уставки Π½Π°Π³Ρ€Π΅Π²Π°Β».

I wonder why it doesn't work for me then? The z2m versions are all the latest. Now I installed version 1.32.2-1 zigbee2mqtt. As you can see, it doesn’t work either out of the box or with the converter. Do you have any external converter for this trv?

wols89 avatar Oct 22 '23 11:10 wols89

I don't use an external converter. You might get the converter of ME167 manufacturerName:_TZE200_6rdj8dzm, change manufacturerName to _TZE200_p3dbf6qs and try this as external converter.

Manni51 avatar Oct 22 '23 15:10 Manni51

I also have the same model. I wanted to connect it to HomeAssistant via zigbee2MQTT. It got identified nicely but on HA with MQTT Integration all I see are the useless entities but the important climate once are all missing ( also checked for hidden entries)

zigbemqtt image image homeassistant image

ZZigbee2MQTT -> eclipse-misquitto -> HA (all docker) Zigbee2MQTT version [1.33.1] commit: [7e63039] Coordinator type EZSP v8 Coordinator revision 6.10.3.0 build 297 Coordinator IEEE Address 0xe0798dfffe9f25a6 Frontend version 0.6.136

anyway to fix what or check why the most important informations are not on HA?

TY

TheBelgarion avatar Oct 26 '23 14:10 TheBelgarion

I just saw that error in HA: 2023-10-28 19:14:44.536 ERROR (MainThread) [homeassistant.util.logging] Exception in async_discover when dispatching 'mqtt_discovery_new_climate_mqtt': ({'action_template': "{% set values = {None:None,'idle':'off','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json.running_state] }}", 'action_topic': 'zigbee2mqtt/TRV03', 'availability': [{'topic': 'zigbee2mqtt/bridge/state', 'value_template': '{{ value_json.state }}'}, {'topic': 'zigbee2mqtt/TRV03/availability', 'value_template': '{{ value_json.state }}'}], 'availability_mode': 'all', 'current_temperature_template': '{{ value_json.local_temperature }}', 'current_temperature_topic': 'zigbee2mqtt/TRV03', 'device': {'identifiers': ['zigbee2mqtt_0xa4c138853938d9cf'], 'manufacturer': 'AVATTO', 'model': 'Thermostatic radiator valve (ME168)', 'name': 'TRV03'}, 'json_attributes_topic': 'zigbee2mqtt/TRV03', 'max_temp': '35', 'min_temp': '5', 'mode_command_topic': 'zigbee2mqtt/TRV03/set/system_mode', 'mode_state_template': '{{ value_json.system_mode }}', 'mode_state_topic': 'zigbee2mqtt/TRV03', 'modes': ['auto', 'heat', 'off'], 'name': None, 'object_id': 'trv03', 'origin': {'name': 'Zigbee2MQTT', 'sw': '1.33.1', 'url': 'https://www.zigbee2mqtt.io'}, 'temp_step': 1, 'temperature_command_topic': 'zigbee2mqtt/TRV03/set/current_heating_setpoint', 'temperature_state_template': '{{ value_json.current_heating_setpoint }}', 'temperature_state_topic': 'zigbee2mqtt/TRV03', 'temperature_unit': 'C', 'unique_id': '0xa4c138853938d9cf_climate_zigbee2mqtt', 'platform': 'mqtt'},)

voluptuous.error.MultipleInvalid: string value is None for dictionary value @ data['name']

TheBelgarion avatar Oct 28 '23 19:10 TheBelgarion

I'm using: ME167 (AVATTO) modelID: TS0601, manufacturerName:_TZE200_6rdj8dzm ME168 (AVATTO) modelID: TS0601, manufacturerName:_TZE200_p3dbf6qs. Both are supported. Using "system mode" auto on ME168 causes strange values of "Current heating setpoint"

I am facing the same issue. Setpoint ist sporadically set to 25Β°C. Any hint?

olili avatar Oct 30 '23 06:10 olili

I updated to latest Version 2023.10 of HA and now the values are all there so climate seems to be rather new stuff

TheBelgarion avatar Oct 31 '23 19:10 TheBelgarion

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days

github-actions[bot] avatar Apr 29 '24 00:04 github-actions[bot]