Zigbee2MQTT - Tuya 4-button Scene Switch (TS0044)

Hi everyone. I faced the same problem. I bought a TS004F switch and it did not work with zigbee2mqtt. So I spent a little time and wrote an external conveter for it. But I couldn’t get everything to work. Single press events work for all 4 buttons, and hold event works only for the 2 right side buttons. No other events (like double click) appear in the console in debug mode so I can’t write a converter for them.

To use my converter add the following lines to your configuration.yaml:

external_converters:
  - TS004F.js

And save the code below in the file data/TS004F.js next to the file configuration.yaml:
(UPD: the data dir is the dir where the configuration.yaml file is located and may have a different name for you)

const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const reporting = require('zigbee-herdsman-converters/lib/reporting');
const extend = require('zigbee-herdsman-converters/lib/extend');
const e = exposes.presets;
const ea = exposes.access;

const bind = async (endpoint, target, clusters) => {
    for (const cluster of clusters) {
        await endpoint.bind(cluster, target);
    }
};

/* 
  Buttons numbers:
      -------
     | 1 | 3 |
     |-------|
     | 2 | 4 |
      -------
*/

const leftButtonsConverter = {
    cluster: 'genOnOff',
    type: ['commandOn', 'commandOff', 'commandToggle'],
    convert: (model, msg, publish, options, meta) => {
        if (msg.type === 'commandOn') {
            return { action: '1_single' };
        }
        
        if (msg.type === 'commandOff') {
            return { action: '2_single' };
        }
    },
};

const rightButtonsConverter = {
    cluster: 'genLevelCtrl',
    type: ['commandStep', 'commandMove', 'commandStop'],
    convert: (model, msg, publish, options, meta) => {    
        if (msg.type === 'commandStep') {
            if (msg.data.stepmode === 0) {
                return { action: '3_single' };
            }
            else if (msg.data.stepmode === 1) {
                return { action: '4_single' };
            }
        }
        else if (msg.type === 'commandMove') {
            if (msg.data.movemode === 0) {
                return { action: '3_hold' };
            }
            else if (msg.data.movemode === 1) {
                return { action: '4_hold' };
            }
        }
        else if (msg.type === 'commandStop') {
            // No data available to determine which button was released
            return { action: 'release' };
        }
        
        // No 'double' events are fired
    },
};

const definition = {
    zigbeeModel: ['TS004F'],
    model: 'TS004F',
    vendor: 'TuYa',
    description: 'Wireless switch with 4 buttons',
    whiteLabel: [{vendor: '_TZ3000_xabckq1v', model: 'TS004F'}],

    // Exposed commands are similar to the Tuya TS0044 switch
    exposes: [
        e.battery(),
        e.action([
            '1_single', 
            '2_single', 
            '3_single', '3_hold',
            '4_single', '4_hold'
            
            /*
            '1_single', '1_double', '1_hold',
            '2_single', '2_double', '2_hold',
            '3_single', '3_double', '3_hold',
            '4_single', '4_double', '4_hold'
            */
        ])
    ],

    fingerprint: [
        {
            modelID: 'TS004F',
            manufacturerName: '_TZ3000_xabckq1v'
        },
    ],
    fromZigbee: [
        fz.battery,
        leftButtonsConverter,
        rightButtonsConverter,
    ],
    toZigbee: [
    ],
    meta: {
        configureKey: 1,
    },
    configure: async (device, coordinatorEndpoint, logger) => {
        const endpoint = device.getEndpoint(1);
        await bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl']);
        
        await bind(endpoint, coordinatorEndpoint, ['genPowerCfg']);
        const payload = [{
            attribute: 'batteryPercentageRemaining',
            minimumReportInterval: 0,
            maximumReportInterval: 3600,
            reportableChange: 1,
        }, {
            attribute: 'batteryVoltage',
            minimumReportInterval: 0,
            maximumReportInterval: 3600,
            reportableChange: 1,
        }];
        await endpoint.configureReporting('genPowerCfg', payload);
    },
};

module.exports = definition;

Then you can use the blueprint described here.

7 Likes