Koenkk / Koenkk/zigbee2mqtt

[External Converter]: TS0001 from _TZ3000_anptztic

Open
#32,609 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

external converter stale
Dominant language
TypeScript
Stars
15.7k
Forks
2k
Avg merge
18h 55m
Merged PRs (30d)
35

Description

Link

https://shopee.com.my/SMATRUL-20A-AI-Tuya-RF433-Smart-Wifi-Zigbee-Water-Heater-Boiler-Switch-Air-Conditioner-Light-EU-Wall-Electrical-App-For-Alexa-Google-Home-Smart-Life-i.49648373.43955652107

Database entry

{"id":28,"type":"Router","ieeeAddr":"0xa4c138266bc8779f","nwkAddr":37013,"manufId":4417,"manufName":"_TZ3000_anptztic","powerSource":"Mains (single phase)","modelId":"TS0001","epList":[1,242],"endpoints":{"1":{"profId":260,"epId":1,"devId":256,"inClusterList":[3,4,5,6,1794,2820,57344,57345,0],"outClusterList":[25,10],"clusters":{"57344":{"attributes":{"53251":"AAAA"}},"57345":{"attributes":{"53248":0,"53249":0,"53250":0,"53251":0,"53252":0,"53253":0,"53296":2}},"genBasic":{"attributes":{"modelId":"TS0001","manufacturerName":"_TZ3000_anptztic","powerSource":1,"zclVersion":3,"appVersion":74,"stackVersion":0,"hwVersion":1,"dateCode":""}},"haElectricalMeasurement":{"attributes":{"rmsVoltage":225,"rmsCurrent":0,"activePower":0}},"seMetering":{"attributes":{"currentSummDelivered":0}},"genOnOff":{"attributes":{"32770":2,"onOff":0,"onTime":0,"offWaitTime":0}}},"binds":[{"cluster":6,"type":"endpoint","deviceIeeeAddress":"0x00124b0038a8cede","endpointID":1},{"cluster":2820,"type":"endpoint","deviceIeeeAddress":"0x00124b0038a8cede","endpointID":1},{"cluster":1794,"type":"endpoint","deviceIeeeAddress":"0x00124b0038a8cede","endpointID":1}],"configuredReportings":[{"cluster":2820,"attrId":1285,"minRepIntval":5,"maxRepIntval":3600,"repChange":5},{"cluster":2820,"attrId":1288,"minRepIntval":5,"maxRepIntval":3600,"repChange":50},{"cluster":2820,"attrId":1291,"minRepIntval":5,"maxRepIntval":3600,"repChange":10},{"cluster":1794,"attrId":0,"minRepIntval":5,"maxRepIntval":3600,"repChange":[0,10]},{"cluster":6,"attrId":0,"minRepIntval":0,"maxRepIntval":65000,"repChange":1}],"meta":{}},"242":{"profId":41440,"epId":242,"devId":97,"inClusterList":[],"outClusterList":[33],"clusters":{},"binds":[],"configuredReportings":[],"meta":{}}},"appVersion":74,"stackVersion":0,"hwVersion":1,"dateCode":"","zclVersion":3,"interviewCompleted":true,"interviewState":"SUCCESSFUL","meta":{"configured":"0.0.0"},"lastSeen":1784512645071}

Zigbee2MQTT version

2.12.1 (unknown)

External converter
const { onOff } = require('zigbee-herdsman-converters/lib/modernExtend');
const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const e = exposes.presets;
const ea = exposes.access;
const tz = require('zigbee-herdsman-converters/converters/toZigbee');

// 1. Custom converter to intercept the electrical measurements
const fz_tuya_power_fix = {
    cluster: 'haElectricalMeasurement',
    type: ['attributeReport', 'readResponse'],
    convert: (model, msg, publish, options, meta) => {
        // Run the standard Z2M converter first
        const result = fz.electrical_measurement.convert(model, msg, publish, options, meta) || {};

        // Intercept and forcefully fix current mathematically
        if (result.current !== undefined) {
            result.current = parseFloat((result.current / 1000.0).toFixed(3));
        }
        return result;
    },
};

// 2. Custom converter to intercept the energy measurements
const fz_tuya_energy_fix = {
    cluster: 'seMetering',
    type: ['attributeReport', 'readResponse'],
    convert: (model, msg, publish, options, meta) => {
        // Run the standard Z2M converter first
        const result = fz.metering.convert(model, msg, publish, options, meta) || {};

        // Intercept and forcefully fix energy mathematically
        if (result.energy !== undefined) {
            result.energy = parseFloat((result.energy / 100.0).toFixed(3));
        }
        return result;
    },
};

// 3. Custom converter for Tuya's non-standard power-on behavior (Attribute 0x8002)
const fz_tuya_power_on_behavior = {
    cluster: 'genOnOff',
    type: ['attributeReport', 'readResponse'],
    convert: (model, msg, publish, options, meta) => {
        const attribute = msg.data.hasOwnProperty('moesStartUpOnOff') ? 'moesStartUpOnOff' :
            msg.data.hasOwnProperty(0x8002) ? 0x8002 : null;
        if (attribute !== null) {
            const lookup = { 0: 'off', 1: 'on', 2: 'previous' };
            return { power_on_behavior: lookup[msg.data[attribute]] };
        }
    }
};

const tz_tuya_power_on_behavior = {
    key: ['power_on_behavior'],
    convertSet: async (entity, key, value, meta) => {
        const lookup = { 'off': 0, 'on': 1, 'previous': 2 };
        if (lookup[value] === undefined) throw new Error(`Invalid power_on_behavior: ${value}`);
        // Tuya uses attribute 0x8002 on the genOnOff cluster (DataType: 0x30 / enum8)
        await entity.write('genOnOff', { 0x8002: { value: lookup[value], type: 0x30 } });
        return { state: { power_on_behavior: value } };
    },
    convertGet: async (entity, key, meta) => {
        await entity.read('genOnOff', [0x8002]);
    }
};

module.exports = [
    {
        fingerprint: [{ modelID: 'TS0001', manufacturerName: '_TZ3000_anptztic' }],
        model: 'TS0001_power',
        vendor: 'Tuya',
        description: '1 gang switch with power monitoring',
        extend: [
            // Pass an argument to disable the built-in, buggy Tuya read feature
            onOff({ powerOnBehavior: false }),
            // We explicitly DO NOT use electricityMeter() here to prevent the UNSUPPORTED_ATTRIBUTE crash
        ],
        // Inject our custom interceptors
        fromZigbee: [fz_tuya_power_fix, fz_tuya_energy_fix, fz_tuya_power_on_behavior],

        // Add the custom Tuya toZigbee converter for power_on_behavior so we can write to it
        toZigbee: [tz_tuya_power_on_behavior],

        // Explicitly expose these features to Home Assistant with READ-ONLY (STATE) access.
        // This prevents the "No converter available" error when the system tries to poll/set it.
        exposes: [
            e.power().withAccess(ea.STATE),
            e.current().withAccess(ea.STATE),
            e.voltage().withAccess(ea.STATE),
            e.energy().withAccess(ea.STATE),
            // Re-add power_on_behavior manually with ALL access (allows GET and SET)
            e.power_on_behavior().withAccess(ea.ALL)
        ],

        // Bind the device so it sends updates automatically
        configure: async (device, coordinatorEndpoint, logger) => {
            const endpoint = device.getEndpoint(1);

            await endpoint.bind('genOnOff', coordinatorEndpoint);
            await endpoint.bind('haElectricalMeasurement', coordinatorEndpoint);
            await endpoint.bind('seMetering', coordinatorEndpoint);

            // We safely wrap the initial read in a try/catch. 
            // If the Tuya device rejects the read, we catch the error so the configure process succeeds!
            try {
                await endpoint.read('haElectricalMeasurement', ['rmsVoltage', 'rmsCurrent', 'activePower']);
                await endpoint.read('seMetering', ['currentSummDelivered']);
                // Attempt to read the custom Tuya power on behavior state on startup
                await endpoint.read('genOnOff', [0x8002]);
            } catch (error) {
                logger.debug(`Expected Tuya initial read failure ignored: ${error}`);
            }

            // Set up automatic reporting so the device pushes updates on its own
            try {
                await endpoint.configureReporting('haElectricalMeasurement', [
                    { attribute: 'rmsVoltage', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 5 },
                    { attribute: 'rmsCurrent', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 50 },
                    { attribute: 'activePower', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 10 }
                ]);
                await endpoint.configureReporting('seMetering', [
                    // Report energy changes. [0, 10] safely handles the large number format used by Tuya.
                    { attribute: 'currentSummDelivered', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: [0, 10] }
                ]);
            } catch (error) {
                logger.debug(`Tuya reporting setup ignored: ${error}`);
            }
        },
    },
];
What does/doesn't work with the external definition?

initially, this Tuya product only show the state (on/off) and power-on-behavior entities when paired.
seller claimed it have power monitoring in the product description and ask me to use tuya gateway instead of zigbee2mqtt since that is the official apps.

i use google gemini ai to write that external converter, so that i can access the power monitoring entities include to convert the the data to correct format for its specific unit , like the Current (A) and energy(kwh). now everything seem work ok, but im not sure the external converter is correct. please do check

Notes

software_build_id: undefined
date_code: ``
endpoints:

{"1":{"clusters":{"input":["genIdentify","genGroups","genScenes","genOnOff","seMetering","haElectricalMeasurement","57344","57345","genBasic"],"output":["genOta","genTime"]}},"242":{"clusters":{"input":[],"output":["greenPower"]}}}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the provided external converter and its fz.electrical_measurement, fz.metering, modernExtend, and exposes entry points. Compare their behavior with the device's haElectricalMeasurement and seMetering endpoint data, including the reported units and power-on attribute. Done means the device's power, current, voltage, energy, and power-on behavior are correctly supported or the unsupported parts are clearly identified.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
embedded-iot
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.