Skip to content
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

Template for new device TS0601_TZE200_wbhaespm #7181

Open
clumsy-stefan opened this issue Mar 8, 2024 · 1 comment
Open

Template for new device TS0601_TZE200_wbhaespm #7181

clumsy-stefan opened this issue Mar 8, 2024 · 1 comment

Comments

@clumsy-stefan
Copy link

clumsy-stefan commented Mar 8, 2024

Below a template which is working with the above mentioned 3 phase circuit breaker with integrated leakage protection (from Ali). I also have the original documentation from the manufacturer with all the DP's and communication described (in chinese and machine translated in english).

Unfortunately my coding knowledge is limited so I only could get all values readable but not write the configurations, except the switch itself, which can be controlled with this template. Also discussed here.

Probably one of the developers will want to look through it and include it in the standard build. Let me know if I can be of further help!

Thanks, regards.

EDIT: updated version with some more DP's and all settings included... (work in progress)

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 tuya = require('zigbee-herdsman-converters/lib/tuya');
const e = exposes.presets;
const ea = exposes.access;
const extend = require('zigbee-herdsman-converters/lib/extend');
const legacy = require('zigbee-herdsman-converters/lib/legacy');
const utils = require('zigbee-herdsman-converters/lib/utils');
const { Buffer } = require('node:buffer');

const fzLocal = {
        TZE200_wbhaespm: {
        cluster: 'manuSpecificTuya',
        type: ['commandDataResponse', 'commandDataReport'],
        convert: (model, msg, publish, options, meta) => {
                const splitToAttributes = (limit) => {
                const result = {};
                const len = limit.length;
                let i = 0;
                while (i < len) {
                        const key = limit.readUInt8(i);
                        result[key] = [limit.readUInt8(i + 1), limit.readUInt16BE(i + 2)];
                        i += 4;
                }
                return result;
                };
                const lookup = { 0: 'OFF', 1: 'ON' };

                for (const dpValue of msg.data.dpValues) {
                        const value = legacy.getDataValue(dpValue);
                        const dp = dpValue.dp;
//                      meta.logger.info(`DP: ${dp} -- VALUE = ${value}`);
                        switch (dp) {
                                case 16: return {
                                        state: value ? 'ON' : 'OFF',
                                        trip: 'clear',
                                }
                                case 11: return {
                                        prepay: value ? 'ON' : 'OFF',
                                }
                                case 12: {
//                                      meta.logger.info(`DP: ${dp} -- VALUE = ${value}`);
                                        return {
//                                              clear_energy: value ? 'reset' : 'clear',
                                                clear_energy: 'clear',
                                        }
                                }
                                case 102: {
                                        meta.logger.info(`DP: ${dp} -- VALUE = ${value}`);
                                }
                                case 17: {
                                        const limit = splitToAttributes(value);
                                        meta.logger.info(`DEBUG DP #${dp} with data ${JSON.stringify(msg.data)}`);
                                        return {
                                                        'power_threshold': limit[0x03][1],
                                                        'power_breaker': lookup[limit[0x03][0]],
                                                        'leakage_threshold': limit[0x04][1],
                                                        'leakage_breaker': lookup[limit[0x04][0]],
                                                        'temperature_threshold': limit[0x05][1],
                                                        'temperature_breaker': lookup[limit[0x05][0]],
                                        };
                                }
                                case 18: {
                                        const limit = splitToAttributes(value);
                                        meta.logger.info(`DEBUG DP #${dp} with data ${JSON.stringify(msg.data)}`);
                                        return {
                                                        'over_current_threshold': limit[0x01][1],
                                                        'over_current_breaker': lookup[limit[0x01][0]],
                                                        'over_voltage_threshold': limit[0x03][1],
                                                        'over_voltage_breaker': lookup[limit[0x03][0]],
                                                        'under_voltage_threshold': limit[0x04][1],
                                                        'under_voltage_breaker': lookup[limit[0x04][0]],
                                                        'imbalance_power_threshold': limit[0x08][1],
                                                        'imbalance_power_breaker': lookup[limit[0x08][0]],
                                        };
                                }
                        }
                }
          },
         },
};

const tzLocal = {
    TZE200_wbhaespm: {
        key: ['trip', 'clear_energy', 'prepay', 'serial', 'power_threshold', 'power_breaker', 'leakage_threshold', 'leakage_breaker', 'temperature_threshold', 'temperature_breaker',
        'over_current_threshold', 'over_current_breaker', 'over_voltage_threshold', 'over_voltage_breaker', 'under_voltage_threshold', 'under_voltage_breaker', 'imbalance_power_threshold', 'imbalance_power_breaker'],
        convertSet: async (entity, key, value, meta) => {
                const onOffLookup = { 'ON': 1, 'OFF': 0 };
//              meta.logger.info(`Key: ${key} -- VALUE = ${value}`);
                switch (key) {
                case 'trip': {
                        if (value === 'trip') {
                                await tuya.sendDataPointBool(entity, 21, true);
                        };
                        return { state: { trip: 'clear' } };
                }
                case 'clear_energy': {
                        if (value === 'reset') {
                                await tuya.sendDataPointBool(entity, 12, true);
                        };
                        return { state: { clear_energy: 'clear' } };
                }
                case 'prepay': {
                        await tuya.sendDataPointBool(entity, 11, value === 'ON');
                        return { state: { prepay: value } };
                }
                case 'serial': {
                        let data = [];
                        data = data.concat(legacy.convertStringToHexArray(value));
                        await tuya.sendDataPointStringBuffer(entity, 19, data);
                        return { state: { serial: value } };
                }
                case 'power_threshold': {
                        var state = [];
                        oldstate = meta.state['alarm_set_1'];
                        for (var item in oldstate) {
                                state.push(oldstate[item]);
                        }
//                      meta.logger.info(`DEBUG ${JSON.stringify(meta.state)}`);
                        state[0] = 3;
                        state[2] = (value & 0xff00) >> 8;
                        state[3] = value & 0xff;
                        meta.logger.info(`DEBUG2 ${JSON.stringify(state)} length: ` + state.length);
                        await tuya.sendDataPointRaw(entity, 17, state);
                        break;
                }
                case 'power_breaker': {
                        var state = [];
                        oldstate = meta.state['alarm_set_1'];
                        for (var item in oldstate) {
                                state.push(oldstate[item]);
                        }
//                      meta.logger.info(`DEBUG ${JSON.stringify(meta.state)}`);
                        state[0] = 3;
                        state[1] = utils.getFromLookup(value, onOffLookup);
                        meta.logger.info(`DEBUG2 ${JSON.stringify(state)} length: ` + state.length);
                        await tuya.sendDataPointRaw(entity, 17, state);
                        break;
                }
                case 'leakage_threshold': {
                        var state = [];
                        oldstate = meta.state['alarm_set_1'];
                        for (var item in oldstate) {
                                state.push(oldstate[item]);
                        }
                        state[4] = 4;
                        state[6] = (value & 0xff00) >> 8;
                        state[7] = value & 0xff;
                        await tuya.sendDataPointRaw(entity, 17, state);
                        break;
                }
                case 'leakage_breaker': {
                        var state = [];
                        oldstate = meta.state['alarm_set_1'];
                        for (var item in oldstate) {
                                state.push(oldstate[item]);
                        }
                        state[4] = 4;
                        state[5] = utils.getFromLookup(value, onOffLookup);
                        await tuya.sendDataPointRaw(entity, 17, state);
                        break;
                }
                case 'temperature_threshold': {
                        var state = [];
                        oldstate = meta.state['alarm_set_1'];
                        for (var item in oldstate) {
                                state.push(oldstate[item]);
                        }
                        state[8] = 5;
                        state[10] = (value & 0xff00) >> 8;
                        state[11] = value & 0xff;
                        await tuya.sendDataPointRaw(entity, 17, state);
                        break;
                }
                case 'temperature_breaker': {
                        var state = [];
                        oldstate = meta.state['alarm_set_1'];
                        for (var item in oldstate) {
                                state.push(oldstate[item]);
                        }
                        state[8] = 5;
                        state[9] = utils.getFromLookup(value, onOffLookup);
                        await tuya.sendDataPointRaw(entity, 17, state);
                        break;
                }
                case 'over_current_threshold': {
                        var state = [];
                        oldstate = meta.state['alarm_set_2'];
                        for (var item in oldstate) {
                                state.push(oldstate[item]);
                        }
                        state[0] = 1;
                        state[2] = (value & 0xff00) >> 8;
                        state[3] = value & 0xff;
                        await tuya.sendDataPointRaw(entity, 18, state);
                        break;
                }
                case 'over_current_breaker': {
                        var state = [];
                        oldstate = meta.state['alarm_set_2'];
                        for (var item in oldstate) {
                                state.push(oldstate[item]);
                        }
                        state[0] = 1;
                        state[1] = utils.getFromLookup(state, onOffLookup);
                        await tuya.sendDataPointRaw(entity, 18, state);
                        break;
                }
                case 'over_voltage_threshold': {
                        var state = [];
                        oldstate = meta.state['alarm_set_2'];
                        for (var item in oldstate) {
                                state.push(oldstate[item]);
                        }
                        state[4] = 3;
                        state[6] = (value & 0xff00) >> 8;
                        state[7] = value & 0xff;
                        await tuya.sendDataPointRaw(entity, 18, state);
                        break;
                }
                case 'over_voltage_breaker': {
                        var state = [];
                        oldstate = meta.state['alarm_set_2'];
                        for (var item in oldstate) {
                                state.push(oldstate[item]);
                        }
                        state[4] = 3;
                        state[5] = utils.getFromLookup(state, onOffLookup);
                        await tuya.sendDataPointRaw(entity, 18, state);
                        break;
                }
                case 'under_voltage_threshold': {
                        var state = [];
                        oldstate = meta.state['alarm_set_2'];
                        for (var item in oldstate) {
                                state.push(oldstate[item]);
                        }
                        state[8] = 4;
                        state[10] = (value & 0xff00) >> 8;
                        state[11] = value & 0xff;
                        await tuya.sendDataPointRaw(entity, 18, state);
                        break;
                }
                case 'under_voltage_breaker': {
                        var state = [];
                        oldstate = meta.state['alarm_set_2'];
                        for (var item in oldstate) {
                                state.push(oldstate[item]);
                        }
                        state[8] = 4;
                        state[9] = utils.getFromLookup(state, onOffLookup);
                        await tuya.sendDataPointRaw(entity, 18, state);
                        break;
                }
                case 'imbalance_power_threshold': {
                        var state = [];
                        oldstate = meta.state['alarm_set_2'];
                        for (var item in oldstate) {
                                state.push(oldstate[item]);
                        }
                        state[12] = 8;
                        state[14] = (value & 0xff00) >> 8;
                        state[15] = value & 0xff;
                        await tuya.sendDataPointRaw(entity, 18, state);
                        break;
                }
                case 'imbalance_power_breaker': {
                        var state = [];
                        oldstate = meta.state['alarm_set_2'];
                        for (var item in oldstate) {
                                state.push(oldstate[item]);
                        }
                        state[12] = 8;
                        state[13] = utils.getFromLookup(state, onOffLookup);
                        await tuya.sendDataPointRaw(entity, 18, state);
                        break;
                }
                default: // Unknown key
                        meta.logger.warn(`Unhandled key ${key}`);
                }
        },
    },
};


const definition = {
        fingerprint: tuya.fingerprint('TS0601', ['_TZE200_wbhaespm']),
        model: 'TS0601',
        vendor: 'TuYa',
        description: '3 Phase breaker with leakage protection and power meter',
        extend: [],
        fromZigbee: [tuya.fz.datapoints, fzLocal.TZE200_wbhaespm],
        toZigbee: [tuya.tz.datapoints, tzLocal.TZE200_wbhaespm],
        configure: tuya.configureMagicPacket,
        exposes: [  e.switch().setAccess('state', ea.STATE_SET),
                        e.binary('prepay', ea.STATE_SET, 'ON', 'OFF').withDescription('Prepayment switch state'),
                tuya.exposes.voltageWithPhase('a'), tuya.exposes.voltageWithPhase('b'), tuya.exposes.voltageWithPhase('c'),
                tuya.exposes.powerWithPhase('a'), tuya.exposes.powerWithPhase('b'), tuya.exposes.powerWithPhase('c'),
                tuya.exposes.currentWithPhase('a'), tuya.exposes.currentWithPhase('b'), tuya.exposes.currentWithPhase('c'),
                // Change the description according to the specifications of the device
                e.energy().withDescription('Total forward active energy'), e.temperature(),
                e.binary('trip', ea.STATE_SET, 'trip', 'clear').withDescription('Trip'),
                e.binary('clear_energy', ea.STATE_SET, 'reset', 'clear').withDescription('Reset Energy Counter'),
                e.enum('fault', ea.STATE, ['clear', 'short_circuit_alarm', 'surge_alarm', 'overload_alarm',
                        'leakagecurr_alarm', 'temp_dif_fault', 'fire_alarm',
                        'high_power_alarm', 'self_test_alarm', 'ov_cr',
                        'unbalance_alarm', 'ov_vol', 'undervoltage_alarm',
                        'miss_phase_alarm', 'outage_alarm', 'magnetism_alarm',
                        'credit_alarm', 'no_balance_alarm']).withDescription('Fault status of the device (clear = nothing)'),
                e.numeric('serial', ea.STATE).withDescription('Serial'),
                e.numeric('leakage_current', ea.STATE).withDescription('Leakage current'),
                e.numeric('temperature_threshold', ea.STATE_SET).withValueMin(-40).withValueMax(200).withValueStep(1).withUnit('*C')
                        .withDescription('High temperature threshold'),
                e.binary('temperature_breaker', ea.STATE_SET, 'ON', 'OFF')
                        .withDescription('High temperature breaker'),
                e.numeric('power_threshold', ea.STATE_SET).withValueMin(1).withValueMax(30).withValueStep(1).withUnit('kW')
                        .withDescription('High power threshold'),
                e.binary('power_breaker', ea.STATE_SET, 'ON', 'OFF')
                        .withDescription('High power breaker'),
                e.numeric('leakage_threshold', ea.STATE_SET).withValueMin(1).withValueMax(1000).withValueStep(1).withUnit('mA')
                        .withDescription('Leakage-current threshold'),
                e.binary('leakage_breaker', ea.STATE_SET, 'ON', 'OFF')
                        .withDescription('Leakage-current breaker'),
                e.numeric('over_current_threshold', ea.STATE_SET).withValueMin(1).withValueMax(80).withValueStep(1).withUnit('A')
                        .withDescription('Over-current threshold'),
                e.binary('over_current_breaker', ea.STATE_SET, 'ON', 'OFF')
                        .withDescription('Over-current breaker'),
                e.numeric('over_voltage_threshold', ea.STATE_SET).withValueMin(220).withValueMax(280).withValueStep(1).withUnit('V')
                        .withDescription('Over-voltage threshold'),
                e.binary('over_voltage_breaker', ea.STATE_SET, 'ON', 'OFF')
                        .withDescription('Over-voltage breaker'),
                e.numeric('under_voltage_threshold', ea.STATE_SET).withValueMin(76).withValueMax(240).withValueStep(1).withUnit('V')
                        .withDescription('Under-voltage threshold'),
                e.binary('under_voltage_breaker', ea.STATE_SET, 'ON', 'OFF')
                        .withDescription('Under-voltage breaker'),
                e.numeric('imbalance_power_threshold', ea.STATE_SET).withValueMin(1).withValueMax(30).withValueStep(1).withUnit('kW')
                        .withDescription('Imbalance-power threshold'),
                e.binary('imbalance_power_breaker', ea.STATE_SET, 'ON', 'OFF')
                        .withDescription('Imbalance-power breaker'),
                ],
        meta: {
                tuyaDatapoints: [
                [1, 'energy', tuya.valueConverter.divideBy100],
                [6, null, tuya.valueConverter.phaseVariant2WithPhase('a')],
                [7, null, tuya.valueConverter.phaseVariant2WithPhase('b')],
                [8, null, tuya.valueConverter.phaseVariant2WithPhase('c')],
                [9, 'fault', tuya.valueConverterBasic.lookup({ 'clear': 0, 'short_circuit_alarm': 1, 'surge_alarm': 2, 'overload_alarm': 4,
                                                'leakagecurr_alarm': 8, 'temp_dif_fault': 16, 'fire_alarm': 32,
                                                'high_power_alarm': 64, 'self_test_alarm': 128, 'ov_cr': 256,
                                                'unbalance_alarm': 512, 'ov_vol': 1024, 'undervoltage_alarm': 2048,
                                                'miss_phase_alarm': 4096, 'outage_alarm': 8192, 'magnetism_alarm': 16384,
                                                'credit_alarm': 32768, 'no_balance_alarm': 65536 })],
                [11, 'prepay', tuya.valueConverter.onOff],
                [12, 'clear_energy', tuya.valueConverterBasic.lookup({ 'reset': true, 'clear': false })],
                [13, 'balance', tuya.valueConverter.raw],
                [14, 'charge', tuya.valueConverter.raw],
                [15, 'leakage_current', tuya.valueConverter.raw],
                [16, 'state', tuya.valueConverter.onOff],
                [17, 'alarm_set_1', tuya.valueConverter.raw],
                [18, 'alarm_set_2', tuya.valueConverter.raw],
                [19, 'serial', tuya.valueConverter.raw],
                [21, 'trip', tuya.valueConverterBasic.lookup({ 'trip': true, 'clear': false })],
                [101, 'countdown', tuya.valueConverter.raw],
                [102, 'temperature', tuya.valueConverter.divideBy10],
                ],
        },
};

module.exports = definition;
/*

{"1":"Total forward energy",
"6":"Phase A",
"7":"Phase B",
"8":"Phase C",
"9":"Fault",
"11":"Switch prepayment",
"12":"Clear energy",
"13":"Balance energy",
"14":"charge energy",
"15":"Leakage current",
"16":"Switch",
"17":"Alarm set1",
"18":"Alarm set2",
"19":"Breaker id",
"21":"Leakagecurr test",
"101":"Countdown",
"102":"Temp Current"}

"fault": [
"short_circuit_alarm",
"surge_alarm",
"overload_alarm",
"leakagecurr_alarm",
"temp_dif_fault",
"fire_alarm",
"high_power_alarm",
"self_test_alarm",
"ov_cr",
"unbalance_alarm",
"ov_vol",
"undervoltage_alarm",
"miss_phase_alarm",
"outage_alarm",
"magnetism_alarm",
"credit_alarm",
"no_balance_alarm"
]

"alarm_set_1": [
"3:overload_power_limit:kW",
"trip_on_off",
"limit_byte_high",
"limit_byte_low",
"4:leakage_current_limit:mA",
"trip_on_off",
"limit_byte_high",
"limit_byte_low",
"5:over_temperature_limit:C",
"trip_on_off",
"limit_byte_high",
"limit_byte_low"
]
"alarm_set_2": [
"1:overload_current_linit:A",
"trip_on_off",
"limit_byte_high",
"limit_byte_low",
"3:over_voltage_limit:V",
"trip_on_off",
"limit_byte_high",
"limit_byte_low",
"4:under_voltage_limit:V",
"trip_on_off",
"limit_byte_high",
"limit_byte_low",
"8:unbalance_power_limit:kW",
"trip_on_off",
"limit_byte_high",
"limit_byte_low"
]
Note to me:
Overload: 29kW
Leakage: 30mA
High Temp: 80C
Over Current: 63A
Over Voltage: 275V
Under Voltage: 160V
Balance: 10kW

*/
@clumsy-stefan
Copy link
Author

I'm struggling to understand the structure of the converters. eg. what needs to go in lib/* , converters/* and devices/* (especially the fzLocal and tzLocal functions). Also if I should extend the */tuya.js definitions oder make a complete new one (all in device/xyz.js or split in lib/xyz.js and device/xyz.js)...

So I'm currently not able to make a PR out of this...

Really sorry, but I'm not an actual developper, but I'm happy to support wherever I can..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant