Skip to content

[External Converter] NovaDigital TS0601 8-gang switch (_TZE204_72bewjky, DP 1–6,101,102)Β #30464

@joemore

Description

@joemore

Link

No official product page available. Device sold as "NovaDigital 8-gang Zigbee wall switch" (Brazil).

Database entry

{ "description": "8-gang wall switch (DP 1–6, 101, 102)", "exposes": [ { "description": "Light 1 (DP1)", "endpoint": "l1", "features": [ { "access": 7, "description": "On/off state of the switch", "endpoint": "l1", "label": "State", "name": "state", "property": "state_l1", "type": "binary", "value_off": "OFF", "value_on": "ON", "value_toggle": "TOGGLE" } ], "type": "switch" }, { "description": "Light 2 (DP2)", "endpoint": "l2", "features": [ { "access": 7, "description": "On/off state of the switch", "endpoint": "l2", "label": "State", "name": "state", "property": "state_l2", "type": "binary", "value_off": "OFF", "value_on": "ON", "value_toggle": "TOGGLE" } ], "type": "switch" }, { "description": "Light 3 (DP3)", "endpoint": "l3", "features": [ { "access": 7, "description": "On/off state of the switch", "endpoint": "l3", "label": "State", "name": "state", "property": "state_l3", "type": "binary", "value_off": "OFF", "value_on": "ON", "value_toggle": "TOGGLE" } ], "type": "switch" }, { "description": "Light 4 (DP4)", "endpoint": "l4", "features": [ { "access": 7, "description": "On/off state of the switch", "endpoint": "l4", "label": "State", "name": "state", "property": "state_l4", "type": "binary", "value_off": "OFF", "value_on": "ON", "value_toggle": "TOGGLE" } ], "type": "switch" }, { "description": "Light 5 (DP5)", "endpoint": "l5", "features": [ { "access": 7, "description": "On/off state of the switch", "endpoint": "l5", "label": "State", "name": "state", "property": "state_l5", "type": "binary", "value_off": "OFF", "value_on": "ON", "value_toggle": "TOGGLE" } ], "type": "switch" }, { "description": "Light 6 (DP6)", "endpoint": "l6", "features": [ { "access": 7, "description": "On/off state of the switch", "endpoint": "l6", "label": "State", "name": "state", "property": "state_l6", "type": "binary", "value_off": "OFF", "value_on": "ON", "value_toggle": "TOGGLE" } ], "type": "switch" }, { "description": "Light 7 (DP101)", "endpoint": "l7", "features": [ { "access": 7, "description": "On/off state of the switch", "endpoint": "l7", "label": "State", "name": "state", "property": "state_l7", "type": "binary", "value_off": "OFF", "value_on": "ON", "value_toggle": "TOGGLE" } ], "type": "switch" }, { "description": "Light 8 (DP102)", "endpoint": "l8", "features": [ { "access": 7, "description": "On/off state of the switch", "endpoint": "l8", "label": "State", "name": "state", "property": "state_l8", "type": "binary", "value_off": "OFF", "value_on": "ON", "value_toggle": "TOGGLE" } ], "type": "switch" }, { "access": 1, "category": "diagnostic", "description": "Link quality (signal strength)", "label": "Linkquality", "name": "linkquality", "property": "linkquality", "type": "numeric", "unit": "lqi", "value_max": 255, "value_min": 0 } ], "model": "TS0601_novadigital_8gang_dp1_6_101_102", "options": [], "source": "external", "supports_ota": false, "vendor": "NovaDigital" }

Zigbee2MQTT version

2.7.2

External converter

/**
 * NovaDigital 8-gang wall switch (Tuya TS0601)
 *
 * Fingerprint:
 * - modelID: TS0601
 * - manufacturerName: _TZE204_72bewjky
 *
 * DP layout (datatype=1 booleans):
 * - l1..l6  -> DP 1..6
 * - l7      -> DP 101
 * - l8      -> DP 102
 *
 * Notes:
 * - Some datapoints may arrive as datatype=2 "sync/noise" packets; these are ignored.
 * - This converter supports 2-way feedback (physical -> HA/Z2M and UI -> relay).
 */

// Toggle verbose logs (default OFF for upstream submission)
const DEBUG = false;

const exposes = require('zigbee-herdsman-converters/lib/exposes');
const tuya = require('zigbee-herdsman-converters/lib/tuya');
const e = exposes.presets;

// Endpoint -> DP mapping (your verified physical order)
const dpMap = {
    l1: 1,
    l2: 2,
    l3: 3,
    l4: 4,
    l5: 5,
    l6: 6,
    l7: 101,
    l8: 102,
};

// DP -> endpoint lookup for fast mapping
const dpToEndpoint = Object.fromEntries(
    Object.entries(dpMap).map(([ep, dp]) => [String(dp), ep]),
);

function log(...args) {
    if (DEBUG) console.log(...args);
}

// Extract first byte from: [1], Buffer, Uint8Array, {type:'Buffer', data:[1]}, etc.
function extractFirstByte(raw) {
    if (raw == null) return null;

    // raw might be: [1]
    if (Array.isArray(raw)) return raw[0];

    // raw might be: Buffer / Uint8Array (indexable)
    if (typeof raw[0] === 'number') return raw[0];

    // raw might be: {type:'Buffer', data:[1]}
    if (raw && raw.data != null) {
        const d = raw.data;
        if (Array.isArray(d)) return d[0];
        if (d && typeof d[0] === 'number') return d[0];

        // sometimes nested again: {data:{data:[1]}}
        if (d && d.data != null) {
            const dd = d.data;
            if (Array.isArray(dd)) return dd[0];
            if (dd && typeof dd[0] === 'number') return dd[0];
        }
    }

    return null;
}

const fzLocal = {
    novadigital_8gang: {
        cluster: 'manuSpecificTuya',
        type: ['commandDataReport', 'commandDataResponse'],
        convert: (model, msg) => {
            try {
                const dpValues = msg?.data?.dpValues;
                if (!Array.isArray(dpValues) || dpValues.length === 0) return {};

                log(`\n✨ [8-GANG SWITCH] Incoming Tuya Report πŸ“₯`);
                log(`β†’ Raw dpValues: ${JSON.stringify(dpValues)}\n`);

                const stateUpdate = {};

                for (const dpValue of dpValues) {
                    const dp = dpValue.dp;
                    const datatype = dpValue.datatype;

                    // Only care about boolean relay reports
                    if (datatype !== 1) {
                        if (DEBUG) {
                            log(
                                `πŸ” DP Report β†’ πŸ†” DP=${dp} | πŸ“¦ datatype=${datatype} | (ignored non-bool)`,
                            );
                        }
                        continue;
                    }

                    const ep = dpToEndpoint[String(dp)];
                    if (!ep) {
                        log(`πŸ” DP Report β†’ πŸ†” DP=${dp} | πŸ“¦ datatype=${datatype} | (unknown DP)`);
                        continue;
                    }

                    const val = extractFirstByte(dpValue.data);

                    log(
                        `πŸ” DP Report β†’ πŸ†” DP=${dp} | πŸ“¦ datatype=${datatype} | πŸ’‘ val=${val} | 🎯 ep=${ep}`,
                    );

                    if (val === null) continue;

                    const newState = val === 1 ? 'ON' : 'OFF';
                    stateUpdate[`state_${ep}`] = newState;

                    log(`    ➜ βœ… Matched ${ep.toUpperCase()} (DP ${dp}) β†’ ${newState}`);
                }

                return stateUpdate;
            } catch (err) {
                console.log('πŸ’₯ Error in novadigital_8gang convert:', err);
                return {};
            }
        },
    },
};

const tzLocal = {
    novadigital_8gang: {
        key: ['state'],
        convertSet: async (entity, key, value, meta) => {
            const ep = meta?.endpoint_name;
            if (!ep || !dpMap[ep]) return;

            const upperVal = String(value).toUpperCase();
            const dp = dpMap[ep];
            const boolVal = upperVal === 'ON';

            await tuya.sendDataPointBool(entity, dp, boolVal);
            return { [`state_${ep}`]: upperVal };
        },
    },
};

module.exports = {
    fingerprint: [{ modelID: 'TS0601', manufacturerName: '_TZE204_72bewjky' }],
    model: 'TS0601_novadigital_8gang_dp1_6_101_102',
    vendor: 'NovaDigital',
    description: '8-gang wall switch (DP 1–6, 101, 102)',
    fromZigbee: [fzLocal.novadigital_8gang],
    toZigbee: [tzLocal.novadigital_8gang],
    exposes: [
        e.switch().withEndpoint('l1').withDescription('Light 1 (DP1)'),
        e.switch().withEndpoint('l2').withDescription('Light 2 (DP2)'),
        e.switch().withEndpoint('l3').withDescription('Light 3 (DP3)'),
        e.switch().withEndpoint('l4').withDescription('Light 4 (DP4)'),
        e.switch().withEndpoint('l5').withDescription('Light 5 (DP5)'),
        e.switch().withEndpoint('l6').withDescription('Light 6 (DP6)'),
        e.switch().withEndpoint('l7').withDescription('Light 7 (DP101)'),
        e.switch().withEndpoint('l8').withDescription('Light 8 (DP102)'),
    ],
    meta: { multiEndpoint: true },
    endpoint: () => ({
        l1: 1, l2: 1, l3: 1, l4: 1,
        l5: 1, l6: 1, l7: 1, l8: 1,
    }),
};

What does/doesn't work with the external definition?

βœ… What works

  • All 8 physical buttons are exposed as independent switches (l1–l8)
  • Full 2-way state feedback:
    • Physical button presses update state in Zigbee2MQTT and Home Assistant
    • UI toggles correctly control the physical relays
  • Device acts as a Zigbee router
  • Stable operation after reboot
  • Works with multiEndpoint=true

⚠️ Notes / quirks

  • Device uses a non-standard Tuya DP layout:
    • l1–l6 β†’ DP 1–6
    • l7 β†’ DP 101
    • l8 β†’ DP 102
  • Device also sends datatype=2 "noise/sync" datapoints (e.g. DP 7, 12) which must be ignored
  • Using incorrect DP values (e.g. DP13) causes unintended behavior (multiple relays toggling)

❌ What does not apply

  • No scene-only or action-only buttons on this variant
  • All 8 buttons are true relays

Notes

Additional observations:

  • Physical button layout does not strictly match DP numbering order
  • Correct DP mapping was determined by observing raw Tuya datapoint reports
  • Multiple devices tested with identical behavior
  • Likely sold under different brand names but same _TZE204_72bewjky fingerprint

Happy to submit a PR once validated by maintainers.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions