Skip to content

Add support for Nova Digital NFZB-2#12658

Open
trzenaro wants to merge 1 commit into
Koenkk:masterfrom
trzenaro:agent/add-nova-digital-nfzb-2
Open

Add support for Nova Digital NFZB-2#12658
trzenaro wants to merge 1 commit into
Koenkk:masterfrom
trzenaro:agent/add-nova-digital-nfzb-2

Conversation

@trzenaro

@trzenaro trzenaro commented Jul 10, 2026

Copy link
Copy Markdown

Summary

  • add a dedicated Nova Digital NFZB-2 definition for TS0002 / _TZ3210_5ksufhqi
  • expose both switch endpoints together with power outage memory, backlight mode, indicator mode, countdown, and inching controls
  • configure genOnOff binding and reporting for both endpoints so physical switch actions update their Zigbee2MQTT state

Motivation

The device was previously matched by the generic Tuya TS0002 definition, but physical on/off actions were not reported. A device-specific definition is required to configure on/off reporting while preserving the Tuya features supported by this model.

Validation

  • tested the equivalent external converter on a physical Nova Digital NFZB-2
  • verified physical on/off reporting for both endpoints
  • verified the exposed Tuya controls, including power outage memory and inching
  • pnpm run build
  • pnpm run check
  • pnpm test (650 tests passed)

Link to picture pull request: Koenkk/zigbee2mqtt.io#5316

@trzenaro trzenaro marked this pull request as ready for review July 10, 2026 18:00

export const definitions: DefinitionWithExtend[] = [
{
fingerprint: tuya.fingerprint("TS0002", ["_TZ3210_5ksufhqi"]),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add this to the already existing TS0002 definition in tuya.ts

@trzenaro trzenaro Jul 13, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review @Koenkk . I initially created a separate definition because this device behaves differently from the other devices handled by the generic TS0002 definition:

  • Physical on/off changes are only reported after configuring onOff reporting for both endpoints.
  • switchType and powerOnBehavior2 return UNSUPPORTED_ATTRIBUTE.
  • The device supports the legacy powerOutageMemory implementation instead.

I considered adding the following mutually exclusive conditions to the existing tuyaOnOff extend:

powerOnBehavior2: (manufacturerName) => manufacturerName !== "_TZ3210_5ksufhqi",
powerOutageMemory: (manufacturerName) => manufacturerName === "_TZ3210_5ksufhqi",

However, tuyaOnOff currently handles these options through an if / else if branch. Since the predicate function itself is truthy, the powerOutageMemory branch is always selected while building the definition, and the powerOnBehavior2 converters are not registered for the existing models.

This is why I originally used a separate definition: to avoid adding several manufacturer-specific conditions to the generic definition and, more importantly, to avoid changing the behavior of existing TS0002 devices.

I have prepared the following integrated implementation for the existing TS0002 definition. It conditionally disables the unsupported options, exposes power_outage_memory only for _TZ3210_5ksufhqi, restricts the legacy converter to the power_outage_memory key, and configures on/off reporting only for this manufacturer:

{
    // TS0002 2 gang switch module with all available features. This is the default for TS0002 devices.
    model: "TS0002",
    zigbeeModel: ["TS0002"],
    vendor: "Tuya",
    description: "2-Gang switch with backlight, countdown and inching",
    fromZigbee: [tuya.fz.power_outage_memory],
    toZigbee: [{...tuya.tz.power_on_behavior_1, key: ["power_outage_memory"]}],
    exposes: (device) => (device.manufacturerName === "_TZ3210_5ksufhqi" ? [te.powerOutageMemory()] : []),
    extend: [
        tuya.modernExtend.tuyaBase(),
        tuya.modernExtend.tuyaOnOff({
            switchType: (m) => m !== "_TZ3210_5ksufhqi",
            powerOnBehavior2: (m) => m !== "_TZ3210_5ksufhqi",
            backlightModeOffOn: (m) => m !== "_TZ3000_criiahcg",
            indicatorMode: true,
            onOffCountdown: true,
            inchingSwitch: true,
            endpoints: ["l1", "l2"],
        }),
        tuya.clusters.addTuyaCommonPrivateCluster(),
    ],
    endpoint: (device) => {
        return {l1: 1, l2: 2};
    },
    meta: {multiEndpoint: true},
    configure: async (device, coordinatorEndpoint) => {
        await tuya.configureMagicPacket(device, coordinatorEndpoint);
        await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ["genOnOff"]);
        await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ["genOnOff"]);
        if (device.manufacturerName === "_TZ3210_5ksufhqi") {
            await reporting.onOff(device.getEndpoint(1));
            await reporting.onOff(device.getEndpoint(2));
        }
    },
    whiteLabel: [
        tuya.whitelabel("Zemismart", "TB26-2", "2 Gang switch with backlight, countdown, inching", ["_TZ3000_ywubfuvt"]),
        {vendor: "Zemismart", model: "ZM-CSW002-D_switch"},
        {vendor: "Lonsonho", model: "X702"},
        {vendor: "AVATTO", model: "ZTS02"},
        tuya.whitelabel("PSMART", "T462", "2 Gang switch with backlight, countdown, inching", ["_TZ3000_wnzoyohq"]),
        tuya.whitelabel("Nova Digital", "FZB-2", "2-Gang switch with backlight, countdown and inching", ["_TZ3000_5ksufhqi"]),
        tuya.whitelabel("Nova Digital", "NFZB-2", "2-Gang switch with backlight, countdown and inching", ["_TZ3210_5ksufhqi"]),
        tuya.whitelabel("iHseno", "_TZ3000_zxrfobzw", "2-gang touch switch", ["_TZ3000_zxrfobzw"]),
        tuya.whitelabel("Moes", "ZM4LT2", "2-gang switch module", ["_TZ3000_criiahcg"]),
        tuya.whitelabel("Tuya", "ZG-2002-RF", "Three mode Zigbee Switch", [
            "_TZ3000_lugaswf8",
            "_TZ3000_nuenzetq",
            "_TZ3000_ruldv5dt",
            "_TZ3210_nuenzetq",
        ]),
    ],
}

What approach would you prefer here: keeping the dedicated definition, or integrating these exceptions into the existing TS0002 definition? Could you also please check whether the integrated implementation above is correct? I am not very familiar with adding model-specific behavior to shared definitions without affecting the existing models.

@trzenaro trzenaro requested a review from Koenkk July 13, 2026 12:26
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

Successfully merging this pull request may close these issues.

2 participants