[External Converter]: TS0001 from _TZ3000_anptztic
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 15.7k
- Forks
- 2k
- Avg merge
- 18h 55m
- Merged PRs (30d)
- 35
Description
Link
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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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