Koenkk / zigbee2mqtt

Zigbee 🐝 to MQTT bridge 🌉, get rid of your proprietary Zigbee bridges 🔨
https://www.zigbee2mqtt.io
GNU General Public License v3.0
12.07k stars 1.68k forks source link

[New device support]: Nedis ZBHTR20WT #21202

Open nilsmahlstaedt opened 9 months ago

nilsmahlstaedt commented 9 months ago

Link

https://nedis.de/de-de/product/haushalt-und-wohnen/klima/heizung/550786152/smartlife-heizkorpersteuerung-zigbee-30-batteriebetrieben-led-android-ios

Database entry

{ "id": 2, "type": "EndDevice", "ieeeAddr": "0xa4c138a9adee5729", "nwkAddr": 51184, "manufId": 4417, "manufName": "_TZE200_ne4pikwm", "powerSource": "Battery", "modelId": "TS0601", "epList": [ 1 ], "endpoints": { "1": { "profId": 260, "epId": 1, "devId": 81, "inClusterList": [ 4, 5, 61184, 0 ], "outClusterList": [ 25, 10 ], "clusters": { "genBasic": { "attributes": { "65503": "\u0000\u0000\u0000\u0000\u0011\u0004\u0000\u0000\u0000f~9Q-\u0012", "65506": 55, "65508": 0, "65534": 0, "stackVersion": 0, "dateCode": "", "manufacturerName": "_TZE200_ne4pikwm", "zclVersion": 3, "appVersion": 67, "modelId": "TS0601", "powerSource": 3 } } }, "binds": [], "configuredReportings": [], "meta": {} } }, "appVersion": 67, "stackVersion": 0, "hwVersion": 1, "dateCode": "", "zclVersion": 3, "interviewCompleted": true, "meta": { "configured": -708457359 }, }

Comments

Hi, today i spend the day trying to get a Nedis ZBHTR20WT Smart Thermostatic Radiator Value working.

As i don't have access to a tuya gateway, i cannot perform the recommended steps (see supporting new tuya devices) and had to rely on guesswork and testing with implementations of other tuya converters for comparable (maybe whitelabel) products.

This is as far as i got with the adapted external definition (see below). image

I need help from someone who maybe has a working setup to get extended functions like Battery State, Anti-Scaling, Window Open Sensor going.

Testing State:

If someone is currently tinkering with these, I hope writing my current progress down will help you completing this integration. My current feeling is, that the Nedis TRV is just a whitelabel of one of the below products. Sadly they seem to have scambled the datapoint id's. Once these are known, you can probably copy and paste all the converters from existing implementations.

Comparable Products used as sources and inspiration for testing:

PS: I had some Datapoints I could not make heads or tails from. Log Lines from Debug log are in Definition as comments.

External defintion

const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const legacy = require('zigbee-herdsman-converters/lib/legacy');
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 definition = {
        fingerprint: tuya.fingerprint('TS0601', [
            '_TZE200_ne4pikwm', /* modell: 'ZBHTR20WT', vendor: 'Nedis' */
        ]),
        model: 'ZBTHR20WT',
        vendor: 'Nedis',
        description: '(TESTING) Thermostat radiator valve',
        fromZigbee: [tuya.fz.datapoints],
        toZigbee: [tuya.tz.datapoints],
        onEvent: tuya.onEventSetLocalTime,
        configure: tuya.configureMagicPacket,
        exposes: [
            e.battery_low(), 
            e.child_lock(), 
            e.open_window(), 
            //e.open_window_temperature().withValueMin(5).withValueMax(30),
            e.climate()
                .withLocalTemperatureCalibration(-5, 5, 0.1, ea.STATE_SET)
                .withLocalTemperature(ea.STATE)
                .withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET),
            tuya.exposes.frostProtection('When Anti-Freezing function is activated, the temperature in the house is kept '+
                    'at 8 °C, the device display "AF".press the pair button to cancel.'),
            // what about anti scale?
            e.binary('online', ea.STATE_SET, 'ON', 'OFF').withDescription('The current data request from the device.'),
            tuya.exposes.errorStatus(),
        ],
        meta: {
            tuyaDatapoints: [
                [2, 'preset', tuya.valueConverter.tv02Preset()],
                    //Datapoint 3 not defined for '_TZE200_ne4pikwm' with value 1
                [8, 'open_window', tuya.valueConverter.onOff],
                [10, 'frost_protection', tuya.valueConverter.TV02FrostProtection],
                //[24, 'local_temperature', tuya.valueConverter.divideBy10],
                [27, 'local_temperature_calibration', tuya.valueConverter.localTempCalibration1],
                //[35, 'battery_low', tuya.valueConverter.trueFalse0],
                [40, 'child_lock', tuya.valueConverter.lockUnlock],
                //[45, 'error_status', tuya.valueConverter.raw],
                [101, 'schedule_mode', tuya.valueConverter.onOff],
                [102, 'local_temperature', tuya.valueConverter.divideBy10],
                [103, 'current_heating_setpoint', tuya.valueConverter.divideBy10],    
                [107, 'system_mode', tuya.valueConverter.TV02SystemMode],
                [107, 'heating_stop', tuya.valueConverter.TV02SystemMode],
                    //Datapoint 110 not defined for '_TZE200_ne4pikwm' with value 
                    //"dpValues":[{"data":{"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,15,0,0,0,0,0],"type":"Buffer"},"datatype":0,"dp":110}],"seq":3328}
                [115, 'online', tuya.valueConverter.onOffNotStrict],
                    //Datapoint 119 not defined for '_TZE200_ne4pikwm' with value
                    //{"dpValues":[{"data":{"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,2,0,0,0,0,0],"type":"Buffer"},"datatype":0,"dp":119}],"seq":4352}

                // Datapoint 132 not defined for _ with value 8 {"dpValues":[{"data":{"data":[1,24,2,3,18,56,0,6],"type":"Buffer"},"datatype":0,"dp":132}],"seq":7680}
            ],
        },
    } 

module.exports = definition;
a42wg commented 7 months ago

Hi I have used your recipe and it works fine (there are missing a ; after the last curly bracket in blue print above). I was primarily missing the battery level, so I hooked my device up to the Tuya platform to extract the data points.

Here is a compiled list of all DP-codes from the Tuya developer page. Not all data points have entry's in my log, it have only been running for about 24 hours. The data types in the table are somehow inconsistent, the types are extracted from the data point description, but for instance DP 105 Battery level alarm are listed as a bitmap type, but the value in the log is "0".

Most of the historical data seems to share the same data point in the FW, but the names is mostly in chinese and are translated using google, so that may also introduce some uncertainty.

br Anders

ZBTHR20WT Codes.pdf

fridtjof commented 6 months ago

Also interested in this. When I joined a thermostat with this code, it somehow enabled the schedule feature. I can see that supposedly z2m can expose a schedule on/off to Home Assistant, but when I tried adding it (through e.schedule()), it did not end up working because tuya's onOff converter deals in "ON"/"OFF", whereas e.schedule is a Binary with values true/false.

In the meantime, is there another way to manually send a value to a tuya datapoint (to turn the schedule off)? Whatever schedule's built into the device as a default is pretty annoying and I'd rather have it function as a "dumb" thermostat for now...

inFLOmatic commented 1 month ago

A bit of issue necromancy on my side but I happend to buy he nedis thermostat a few days ago. I made the schedule switch work by assigning schedule_mode to the data point 108 [108, 'schedule_mode', tuya.valueConverter.onOff], and exposing it with e.binary('schedule_mode', ea.STATE_SET, 'ON', 'OFF').withDescription('Should the device be on the heating schedule')

Using the schedule is still not possible and the thermostat is clearly on a wrong time setting (the default schedule did things in the middle of the night). But thats no issue on my side as I'm using Home Assistant to do those things anyway