Skip to content

Feature: per-device/per-model option to ignore leave events #1648

@chrischris616

Description

@chrischris616

Summary

Request for a per-device or per-model option to ignore device_leave events in zigbee-herdsman, preventing removeFromDatabase() from being called for configured devices.

Problem

Tuya TS0505B (Moes ZB-TDD6-RCW-4) ceiling spots send spurious leave events when power is restored via a mains relay (wall switch). This is a firmware quirk — the devices never actually leave the Zigbee network. They continue to be physically present and communicating.

When herdsman processes the leave event, it calls removeFromDatabase(), which:

  1. Removes the device from shepherd.db
  2. Removes the device from herdsman's in-memory device list
  3. All subsequent messages from the device are silently dropped (controller.js:775: "Data is from unknown device, skipping...")
  4. Commands to the device fail because herdsman doesn't know it anymore

The device is still on the Zigbee network. If we manually restore the device entry in shepherd.db and restart, the device is immediately controllable without re-pairing (first command takes ~11s for route discovery, then normal 50ms latency).

Use case

Smart ceiling spots (37 Tuya TS0505B) installed behind HomeMatic mains relays (wall switches). Toggling the wall switch turns all spots on/off — this is the normal, intended operation. Every power cycle risks triggering phantom leave events that permanently remove devices from the database.

This is a common installation scenario for smart bulbs. The Zigbee specification's leave mechanism is intended for devices voluntarily leaving the network, not for firmware bugs during power-on.

Proposed solution

A per-device or per-model callback/option that allows the consuming application (e.g., ioBroker.zigbee adapter) to tell herdsman to ignore leave events for specific devices.

Working implementation

We have a working patch in controller.js that has been running in production since 2026-02-22 with 37 devices and zero issues:

// In Controller class:

_shouldIgnoreLeave(device) {
    try {
        const fs = require('fs');
        const path = require('path');
        const overridesPath = path.join(this.options.databasePath, 'LocalOverrides.json');
        
        if (!fs.existsSync(overridesPath)) return false;
        
        const overrides = JSON.parse(fs.readFileSync(overridesPath, 'utf8'));
        const ieeeShort = device.ieeeAddr.replace('0x', '');
        
        // Check by device ID
        const byId = overrides.by_id?.[ieeeShort];
        if (byId?.disable_leave_delete || byId?.options?.disable_leave_delete) return true;
        
        // Check by model
        const modelId = device.modelID;
        if (modelId) {
            const byModel = overrides.by_model?.[modelId];
            if (byModel?.disable_leave_delete || byModel?.options?.disable_leave_delete) return true;
        }
        
        return false;
    } catch (e) {
        return false;
    }
}

Applied in the onDeviceLeave handler (around line 541):

async onDeviceLeave(payload) {
    const device = model_1.Device.byIeeeAddr(payload.ieeeAddr);
    
    if (device && this._shouldIgnoreLeave(device)) {
        logger_1.logger.info(`Ignoring leave event for ${device.ieeeAddr} (${device.modelID}) — ignore_leave is configured`);
        return;  // Skip removeFromDatabase()
    }
    
    // ... existing leave handling ...
}

Cleaner API approach

Instead of reading config files directly, herdsman could expose a hook:

// Option 1: Callback
const controller = new Controller({
    shouldIgnoreLeave: (device) => {
        // Application decides per device
        return configuredModels.includes(device.modelID);
    }
});

// Option 2: Configuration list
const controller = new Controller({
    ignoreLeaveModels: ['TS0505B'],
    ignoreLeaveDevices: ['0xa4c138...']
});

Evidence

  • 37 Tuya TS0505B devices monitored for 5+ days
  • 30+ leave events observed from normal wall switch operation (single relay toggles)
  • Devices send commandActiveStatusReport (LQI 91-156) seconds before the leave — proving they are healthy
  • After manual shepherd.db restore: immediately controllable, no re-pairing needed
  • With our herdsman-level patch: zero device losses since Feb 22, all communication uninterrupted
  • Full debug logs and test results documented in ioBroker/ioBroker.zigbee#2748

About security

The ioBroker.zigbee adapter maintainer (@asgothian) raised concerns about "breaking Zigbee network security." We believe this is not applicable here because:

  1. This is an opt-in option, not a default behavior change
  2. It targets hardwired ceiling fixtures that cannot be physically removed
  3. The leave events are not legitimate — the devices are not voluntarily leaving
  4. The option would be configured per device or per model, not globally
  5. A device that genuinely needs to leave (e.g., factory reset for re-pairing to a different network) would still work via the normal pairing process

Environment

  • zigbee-herdsman 9.0.4
  • Coordinator: ZStack3x0, Firmware 20250321 (CC2652P)
  • 37x Tuya TS0505B (_TZ3210_b8jdosxo) / Moes ZB-TDD6-RCW-4
  • All devices are Routers (mains-powered ceiling spots)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions