zigbee2mqtt icon indicating copy to clipboard operation
zigbee2mqtt copied to clipboard

[New device support]: TOMZN TOB9Z-VAP Smart circuit breaker

Open lordlightman opened this issue 1 year ago • 7 comments

Link

https://www.aliexpress.com/item/1005006177598520.html

Database entry

{"id":18,"type":"Router","ieeeAddr":"top_secret","nwkAddr":"top_secret","manufId":4098,"manufName":"_TZ3000_303avxxt","powerSource":"Mains (single phase)","modelId":"TS011F","epList":[1,242],"endpoints":{"1":{"profId":260,"epId":1,"devId":266,"inClusterList":[0,3,4,5,6,1794,2820,1026,57344,57345],"outClusterList":[25,10],"clusters":{"genBasic":{"attributes":{"65503":"\u0000\u0000\u0000\u0000\u0005\u001b\t�-\u0013\u001c\t�-\u0012!\t�-\u0018","65506":31,"65508":0,"65534":0,"modelId":"TS011F","manufacturerName":"_TZ3000_303avxxt","stackVersion":0,"dateCode":"","zclVersion":3,"appVersion":69,"powerSource":1}},"genOnOff":{"attributes":{"32768":0,"onOff":1,"onTime":0,"offWaitTime":0,"tuyaBacklightMode":1,"moesStartUpOnOff":2,"tuyaBacklightSwitch":1}},"manuSpecificTuya_3":{"attributes":{"53248":0,"53249":0,"53250":0,"53251":0,"53252":0,"53253":0,"powerOnBehavior":2,"switchType":0}},"haElectricalMeasurement":{"attributes":{"acCurrentDivisor":1000,"acCurrentMultiplier":1,"rmsVoltage":229,"rmsCurrent":0,"activePower":0}},"seMetering":{"attributes":{"divisor":100,"multiplier":1,"currentSummDelivered":[0,0]}},"manuSpecificBosch":{"attributes":{"53251":"AAAA"}},"msTemperatureMeasurement":{"attributes":{"measuredValue":0}}},"binds":[{"cluster":1026,"type":"endpoint","deviceIeeeAddress":"top_secret","endpointID":1},{"cluster":6,"type":"endpoint","deviceIeeeAddress":"top_secret","endpointID":1},{"cluster":2820,"type":"endpoint","deviceIeeeAddress":"top_secret","endpointID":1},{"cluster":1794,"type":"endpoint","deviceIeeeAddress":"top_secret","endpointID":1}],"configuredReportings":[{"cluster":2820,"attrId":1285,"minRepIntval":5,"maxRepIntval":3600,"repChange":1,"manufacturerCode":null},{"cluster":2820,"attrId":1288,"minRepIntval":5,"maxRepIntval":3600,"repChange":10,"manufacturerCode":null},{"cluster":2820,"attrId":1291,"minRepIntval":5,"maxRepIntval":3600,"repChange":1,"manufacturerCode":null},{"cluster":1794,"attrId":0,"minRepIntval":5,"maxRepIntval":3600,"repChange":[1,1],"manufacturerCode":null}],"meta":{}},"242":{"profId":41440,"epId":242,"devId":97,"inClusterList":[],"outClusterList":[33],"clusters":{},"binds":[],"configuredReportings":[],"meta":{}}},"appVersion":69,"stackVersion":0,"hwVersion":1,"dateCode":"","zclVersion":3,"interviewCompleted":true,"meta":{"configured":332242049},"lastSeen":1715873912168}

Comments

Hello. Please, add support for TOMZN TOB9Z-VAP Smart circuit breaker.

I was able to put together an external converter for this device, see external definition section.

Observations

  • unlike similar models from EARU EAKCB-T-M-Z and Tongou TO-Q-SY2-163JZT, this circuit breaker does not measure temperature, nor does it provide temp breaker and threshold (also no power breaker and threshold)
  • energy reading is reset to 0 after the circuit breaker is removed from Z2M and then added again

Here is an image of the device from the official product page:

TOMZN TOB9Z-VAP Smart circuit breaker

External definition

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

const fzLocal = {
    TS011F_electrical_measurement: {
        ...fz.electrical_measurement,
        convert: async (model, msg, publish, options, meta) => {
            const result = await fz.electrical_measurement.convert(model, msg, publish, options, meta) ?? {};
            const lookup = {power: 'activePower', current: 'rmsCurrent', voltage: 'rmsVoltage'};

            // Wait 5 seconds before reporting a 0 value as this could be an invalid measurement.
            // https://github.com/Koenkk/zigbee2mqtt/issues/16709#issuecomment-1509599046
            if (result) {
                for (const key of ['power', 'current', 'voltage']) {
                    if (key in result) {
                        const value = result[key];
                        clearTimeout(globalStore.getValue(msg.endpoint, key));
                        if (value === 0) {
                            const configuredReporting = msg.endpoint.configuredReportings.find((c) =>
                                c.cluster.name === 'haElectricalMeasurement' && c.attribute.name === lookup[key]);
                            const time = ((configuredReporting ? configuredReporting.minimumReportInterval : 5) * 2) + 1;
                            globalStore.putValue(msg.endpoint, key, setTimeout(() => {
                                const payload = {[key]: value};
                                // Device takes a lot of time to report power 0 in some cases. When current == 0 we can assume power == 0
                                // https://github.com/Koenkk/zigbee2mqtt/discussions/19680#discussioncomment-7868445
                                if (key === 'current') {
                                    payload.power = 0;
                                }
                                publish(payload);
                            }, time * 1000));
                            delete result[key];
                        }
                    }
                }
            }

            // Device takes a lot of time to report power 0 in some cases. When the state is OFF we can assume power == 0
            // https://github.com/Koenkk/zigbee2mqtt/discussions/19680#discussioncomment-7868445
            if (meta.state.state === 'OFF') {
                result.power = 0;
            }

            return result;
        }
    },
    TS011F_threshold: {
        cluster: 'manuSpecificTuya_3',
        type: 'raw',
        convert: (model, msg, publish, options, meta) => {
            const splitToAttributes = (value) => {
                const result = {};
                const len = value.length;
                let i = 0;
                while (i < len) {
                    const key = value.readUInt8(i);
                    result[key] = [value.readUInt8(i+1), value.readUInt16BE(i+2)];
                    i += 4;
                }
                return result;
            };
            const lookup = {0: 'OFF', 1: 'ON'};
            const command = msg.data[2];
            const data = msg.data.slice(3);
            if (command == 0xE7) {
                const value = splitToAttributes(data);
                return {
                    'over_current_threshold': value[0x01][1],
                    'over_current_breaker': lookup[value[0x01][0]],
                    'over_voltage_threshold': value[0x03][1],
                    'over_voltage_breaker': lookup[value[0x03][0]],
                    'under_voltage_threshold': value[0x04][1],
                    'under_voltage_breaker': lookup[value[0x04][0]],
                };
            }
        }
    }
};

const tzLocal = {
    TS011F_threshold: {
        key: [
            'over_current_threshold', 'over_current_breaker', 'over_voltage_threshold', 'over_voltage_breaker',
            'under_voltage_threshold', 'under_voltage_breaker',
        ],
        convertSet: async (entity, key, value, meta) => {
            const onOffLookup = {'on': 1, 'off': 0};
            switch (key) {
            case 'over_current_threshold': {
                const state = meta.state['over_current_breaker'];
                const buf = Buffer.from([1, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'over_current_threshold')]);
                await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf});
                break;
            }
            case 'over_current_breaker': {
                const threshold = meta.state['over_current_threshold'];
                const number = utils.toNumber(threshold, 'over_current_threshold');
                const buf = Buffer.from([1, utils.getFromLookup(value, onOffLookup), 0, number]);
                await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf});
                break;
            }
            case 'over_voltage_threshold': {
                const state = meta.state['over_voltage_breaker'];
                const buf = Buffer.from([3, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'over_voltage_breaker')]);
                await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf});
                break;
            }
            case 'over_voltage_breaker': {
                const threshold = meta.state['over_voltage_threshold'];
                const number = utils.toNumber(threshold, 'over_voltage_threshold');
                const buf = Buffer.from([3, utils.getFromLookup(value, onOffLookup), 0, number]);
                await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf});
                break;
            }
            case 'under_voltage_threshold': {
                const state = meta.state['under_voltage_breaker'];
                const buf = Buffer.from([4, utils.getFromLookup(state, onOffLookup), 0, utils.toNumber(value, 'under_voltage_threshold')]);
                await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf});
                break;
            }
            case 'under_voltage_breaker': {
                const threshold = meta.state['under_voltage_threshold'];
                const number = utils.toNumber(threshold, 'under_voltage_breaker');
                const buf = Buffer.from([4, utils.getFromLookup(value, onOffLookup), 0, number]);
                await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf});
                break;
            }
            default: // Unknown key
                logger.warning(`Unhandled key ${key}`, NS);
            }
        }
    }
};

const definition = {
    fingerprint: tuya.fingerprint('TS011F', ['_TZ3000_303avxxt']),
    model: 'TS011F_with_threshold',
    description: 'Din rail switch with power monitoring and threshold settings',
    vendor: 'TuYa',
    ota: ota.zigbeeOTA,
    extend: [tuya.modernExtend.tuyaOnOff({
        electricalMeasurements: true, electricalMeasurementsFzConverter: fzLocal.TS011F_electrical_measurement,
        powerOutageMemory: true, indicatorMode: true,
    })],
    fromZigbee: [fz.temperature, fzLocal.TS011F_threshold],
    toZigbee: [tzLocal.TS011F_threshold],
    exposes: [
        e.numeric('over_current_threshold', ea.STATE_SET).withValueMin(1).withValueMax(64).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(265).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'),
    ],
    configure: async (device, coordinatorEndpoint) => {
        await tuya.configureMagicPacket(device, coordinatorEndpoint);
        const endpoint = device.getEndpoint(1);
        endpoint.command('genBasic', 'tuyaSetup', {});
        await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement', 'seMetering']);
        await reporting.rmsVoltage(endpoint, {change: 1});
        await reporting.rmsCurrent(endpoint, {change: 10});
        await reporting.activePower(endpoint, {change: 1});
        await reporting.currentSummDelivered(endpoint);
        endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {acCurrentDivisor: 1000, acCurrentMultiplier: 1});
        endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 100, multiplier: 1});
        device.save();
    },
    whiteLabel: [
        tuya.whitelabel('TOMZN', 'TOB9Z-VAP', 'Smart circuit breaker', ['_TZ3000_303avxxt']),
    ],
};

module.exports = definition;

lordlightman avatar May 16 '24 16:05 lordlightman

hey, @lordlightman I've bought the same device and encountered the same issue.

I'm thinking about writing zha quirk and add it to my hass, but I'm new to this. Are there any way I could adapt your code above to python zha quirk file?

ogvalt avatar May 16 '24 18:05 ogvalt

Hello @ogvalt. Sorry, I am not a developer, I barely scraped this converter together following various tutorials on how to write an external converter for Z2M.

lordlightman avatar May 16 '24 19:05 lordlightman

@lordlightman what you scraped together is quite impressive!

ogvalt avatar May 16 '24 19:05 ogvalt

Thanks @ogvalt, I just took bits and pieces from Tuya converter for similar device EARU EAKCB-T-M-Z and after some trial and error managed to make my external converter work.

lordlightman avatar May 16 '24 20:05 lordlightman

@lordlightman it seems I also successfully found a solution and everything working as I expected

ogvalt avatar May 17 '24 09:05 ogvalt

could you make a pull request to add out of the box support for this device?

Koenkk avatar May 17 '24 20:05 Koenkk

Hello @Koenkk. Sorry, I'm not a developer, I do not know how to integrate my converter into the file with all other Tuya converters.

lordlightman avatar May 19 '24 10:05 lordlightman

@lordlightman just to let you know I added this converter (using GUI) with latest Z2M (1.39.0-1) but converter is not starting firing an error

[2024-07-02 00:25:01] info: 	z2m: Logging to console, file (filename: log.log)
[2024-07-02 00:25:01] error: 	z2m: Failed to load external converter file blabla

[2024-07-02 00:25:01] error: 	z2m: Probably there is a syntax error in the file or the external converter is not compatible with the current Zigbee2MQTT version

anyway, not a big deal but could be great to see this device integrated to Z2M :-)

BTW, not related I also added this device to Tuya Smart Home (with Tuya ZigBee HUB) and I don't have any option (in Tuya App) to set threshold for over voltage or over current, very strange because it is the same device of the picture. So if someone knows how to set these option with native app, let me know please.

hallard avatar Jul 01 '24 22:07 hallard

I also have no luck using over voltage/current protection via zha. it seems that device do not expose corresponding endpoints.

so I would say it's kinda misleading marketing from their side, because i specifically chose this device over cheaper one

ogvalt avatar Jul 02 '24 05:07 ogvalt

@ogvalt did you install the external decoder in ZHA? also did you try with official tuya app just to be sure?

Anyway I bought them also for this feature and I'm unable to have the menu below with official app

image

hallard avatar Jul 02 '24 07:07 hallard

@hallard I haven't tried and I'm unable to, because my setup is custom. Also no external decoder.

My observations are based on observations from home assistant logs. I found zha device descriptor that matched with endpoints that device was broadcasting. Maybe they yet not exposed this features on firmware level?

ogvalt avatar Jul 02 '24 11:07 ogvalt

@ogvalt got it, I think to have it on HA you need the custom decoder of this issue (if your device can handle it of course), may explain why you don't have it, worth trying maybe :-)

@Koenkk, you confirm this change is not merged due to lack of PR?

hallard avatar Jul 02 '24 11:07 hallard

you confirm this change is not merged due to lack of PR?

yes

Koenkk avatar Jul 02 '24 19:07 Koenkk

Hello @hallard, sorry to hear that you got an error. I've updated to Zigbee2MQTT version 1.39.0 this week and did not get any errors in the logs - the converter loads and works fine.

lordlightman avatar Jul 09 '24 09:07 lordlightman

I sent back devices even with official Tuya app I don't have these settings, I suspect marketing issue 🤣😂

hallard avatar Jul 09 '24 09:07 hallard

@hallard, unfortunately, I cannot provide any help with the Tuya app since I'm only using Z2M. You can contact TOMZN support on AliExpress to inquire about the thresholds in the app. Also, are you certain that your model is TOB9Z-VAP? There are also 2 other models - TOB9Z-M with metering and TOB9Z-63T which is just a switch. If you purchased this device on AliExpress, you may open a dispute and get your money back if they shipped you the wrong device or it lacks the functions mentioned on the product page. Perhaps, the device is just defective or the manufacturer put a wrong label on it. Not related to this device, but I've purchased a number of dumb voltage protection devices from TOMZN, and sometimes their settings and functions were a bit different even though the model number was the same.

lordlightman avatar Jul 09 '24 09:07 lordlightman

Sorry it was just for information no need for support ;) Yep it's the correct model and I already asked tomzn on Ali they are taking me for a noob and says it works (ah ah for sure no options on my side) so I asked for a refund May be device in enclosure do not match name on enclosure

hallard avatar Jul 09 '24 09:07 hallard

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

github-actions[bot] avatar Dec 21 '24 00:12 github-actions[bot]

I apologize for the off-topic question, but does the relay have a time delay function? When starting, should it wait for a set time before supplying power to the load?

EMAKASS avatar Feb 07 '25 11:02 EMAKASS