Koenkk / zigbee2mqtt

Zigbee 🐝 to MQTT bridge 🌉, get rid of your proprietary Zigbee bridges 🔨
https://www.zigbee2mqtt.io
GNU General Public License v3.0
11.78k stars 1.64k forks source link

[New device support]: lumi.switch.n3acn1 "Aqara smart wall switch H1 Pro (with neutral, triple rocker)" #15447

Closed Deuteronomy93 closed 1 year ago

Deuteronomy93 commented 1 year ago

Link

https://www.aqara.com/cn/d27_overview

Database entry

{"id":87,"type":"Router","ieeeAddr":"0x54ef441000644d0b","nwkAddr":8591,"manufId":4447,"manufName":"LUMI","powerSource":"Mains (single phase)","modelId":"lumi.switch.n3acn1","epList":[1,2,3,21,41,42,43,51,52,61,242],"endpoints":{"1":{"profId":260,"epId":1,"devId":256,"inClusterList":[0,2,3,4,5,6,9,1794,2820],"outClusterList":[10,25],"clusters":{"genBasic":{"attributes":{"modelId":"lumi.switch.n3acn1"}},"aqaraOpple":{"attributes":{"15":{"type":"Buffer","data":[1,10,0,0,0,0,16,16,51,0,0,0,2,10,1,240,3,0,16,240,21,0,0,0]},"223":{"type":"Buffer","data":[22,33,0,0,23,33,0,0,12,33,0,0,24,33,0,0,30,33,0,0,31,33,26,0,32,33,248,0,34,33,98,0,35,33,0,0,36,32,0,37,35,65,110,81,5,254,35,128,0,4,0]},"229":{"type":"Buffer","data":[8,0,8,0,0,4,96,170,232,37,52,90,186,37,56,197,158,37,66,149,25,37,3,129,212,37,114,103,97,37,98,227,95,37,96]},"238":5427,"247":{"type":"Buffer","data":[100,16,1,101,16,1,102,16,1,3,40,37,152,57,200,50,254,64,149,57,34,98,195,59,150,57,254,72,14,69,151,57,77,118,56,66,5,33,1,0,154,32,0,9,33,0,10,13,35,51,21,0,0,14,35,0,0,0,0,10,33,0,0,12,32,1,17,35,1,0,0,0]},"512":0,"670":0}},"genOnOff":{"attributes":{"245":117440544,"onOff":1}}},"binds":[],"configuredReportings":[],"meta":{}},"2":{"profId":260,"epId":2,"devId":256,"inClusterList":[0,3,4,5,6],"outClusterList":[],"clusters":{"genBasic":{"attributes":{}},"genOnOff":{"attributes":{"245":117440545,"onOff":1}},"aqaraOpple":{"attributes":{"512":0}}},"binds":[],"configuredReportings":[],"meta":{}},"3":{"profId":260,"epId":3,"devId":256,"inClusterList":[0,3,4,5,6,18,64704],"outClusterList":[],"clusters":{"genBasic":{"attributes":{}},"genOnOff":{"attributes":{"245":52530944,"onOff":1}},"aqaraOpple":{"attributes":{"512":0}}},"binds":[],"configuredReportings":[],"meta":{}},"21":{"epId":21,"inClusterList":[],"outClusterList":[],"clusters":{"genAnalogInput":{"attributes":{"presentValue":7.989096164703369}}},"binds":[],"configuredReportings":[],"meta":{}},"41":{"epId":41,"inClusterList":[],"outClusterList":[],"clusters":{"genMultistateInput":{"attributes":{"presentValue":1}}},"binds":[],"configuredReportings":[],"meta":{}},"42":{"epId":42,"inClusterList":[],"outClusterList":[],"clusters":{"genMultistateInput":{"attributes":{"presentValue":1}}},"binds":[],"configuredReportings":[],"meta":{}},"43":{"epId":43,"inClusterList":[],"outClusterList":[],"clusters":{"genMultistateInput":{"attributes":{"presentValue":1}}},"binds":[],"configuredReportings":[],"meta":{}},"51":{"epId":51,"inClusterList":[],"outClusterList":[],"clusters":{},"binds":[],"configuredReportings":[],"meta":{}},"52":{"epId":52,"inClusterList":[],"outClusterList":[],"clusters":{},"binds":[],"configuredReportings":[],"meta":{}},"61":{"epId":61,"inClusterList":[],"outClusterList":[],"clusters":{},"binds":[],"configuredReportings":[],"meta":{}},"242":{"profId":41440,"epId":242,"devId":97,"inClusterList":[],"outClusterList":[33],"clusters":{},"binds":[],"configuredReportings":[],"meta":{}}},"appVersion":51,"stackVersion":2,"hwVersion":1,"dateCode":"02-17-2022","zclVersion":3,"interviewCompleted":true,"meta":{"configured":1511975526},"lastSeen":1670521448223,"defaultSendRequestWhen":"immediate"}

Comments

Hi

I've copied the full converter file from here: https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/devices/xiaomi.js

The device I'm adding has effectively already been added but as the 2 button version: lumi.switch.n2acn1

I've edited the entry to replace the model numbers, include a center button, change "both" presses to "all" etc. The library of zigbee herdsman converters have been added to my Z2M folder.

When using this converter and restarting Z2M, it hangs with the below info in the log. These exposes came from the 2 button's approved converter: [01:50:40] INFO: Preparing to start... [01:50:40] INFO: Socat not enabled [01:50:40] INFO: Zigbee Herdsman debug logging enabled [01:50:40] INFO: Starting Zigbee2MQTT... /app/dist/util/externally-loaded.js:756 exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) ^ SyntaxError: Unexpected token '.' at new Script (node:vm:100:7) at createScript (node:vm:265:10) at Object.runInNewContext (node:vm:306:10) at loadModuleFromText (/app/lib/util/utils.ts:148:8) at loadModuleFromFile (/app/lib/util/utils.ts:155:12) at Object.getExternalConvertersDefinitions (/app/lib/util/utils.ts:165:25) at getExternalConvertersDefinitions.next () at new ExternalConverters (/app/lib/extension/externalConverters.ts:12:20) at new Controller (/app/lib/controller.ts:84:58) at start (/app/index.js:106:18)

When using a different converter (which can't differentiate button presses properly) I've found that rather than referencing endpoints 1, 2 and 3, for left, center and right, it uses 41 for left, 42 for center, 43 for right (log info showing this below). The clusters in Z2M show info for endpoints 1, 2 and 3, but not for the ones in the 40s.

0x54ef441000644d0b -Endpoint 1 --Output clusters ---genTime ---genOta --Input clusters ---genBasic ---genDeviceTempCfg ---genIdentify ---genGroups ---genScenes ---genOnOff ---genAlarms ---seMetering ---haElectricalMeasurement -Endpoint 2 --Input clusters ---genBasic ---genIdentify ---genGroups ---genScenes ---genOnOff -Endpoint 3 --Input clusters ---genBasic ---genIdentify ---genGroups ---genScenes ---genOnOff ---genMultistateInput ---aqaraOpple -Endpoint 21 -Endpoint 41 -Endpoint 42 -Endpoint 43 -Endpoint 51 -Endpoint 52 -Endpoint 61 -Endpoint 242 --Output clusters ---greenPower

Here are my button presses:-

Single left:

Debug 2022-12-09 02:11:27Received Zigbee message from 'Office Xiong: 3 button switch', type 'attributeReport', cluster 'genMultistateInput', data '{"presentValue":1}' from endpoint 41 with groupID 0 Info 2022-12-09 02:11:27MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch', payload '{"action":"single","consumption":0.01,"current":0.0463,"device":{"applicationVersion":51,"dateCode":"02-17-2022","friendlyName":"Office Xiong: 3 button switch","hardwareVersion":1,"ieeeAddr":"0x54ef441000644d0b","manufacturerID":4447,"manufacturerName":"LUMI","model":"QBKG32LM","networkAddress":8591,"powerSource":"Mains (single phase)","stackVersion":2,"type":"Router","zclVersion":3},"device_temperature":38,"energy":0.01,"flip_indicator_light":null,"led_disabled_night":null,"linkquality":112,"operation_mode_center":"decoupled","operation_mode_left":"decoupled","operation_mode_right":"decoupled","power":7.9,"power_outage_count":0,"power_outage_memory":null,"state":"ON","state_center":"ON","state_left":"ON","state_right":"ON","temperature":null,"voltage":227.4}' Info 2022-12-09 02:11:27MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch', payload '{"action":"","consumption":0.01,"current":0.0463,"device":{"applicationVersion":51,"dateCode":"02-17-2022","friendlyName":"Office Xiong: 3 button switch","hardwareVersion":1,"ieeeAddr":"0x54ef441000644d0b","manufacturerID":4447,"manufacturerName":"LUMI","model":"QBKG32LM","networkAddress":8591,"powerSource":"Mains (single phase)","stackVersion":2,"type":"Router","zclVersion":3},"device_temperature":38,"energy":0.01,"flip_indicator_light":null,"led_disabled_night":null,"linkquality":112,"operation_mode_center":"decoupled","operation_mode_left":"decoupled","operation_mode_right":"decoupled","power":7.9,"power_outage_count":0,"power_outage_memory":null,"state":"ON","state_center":"ON","state_left":"ON","state_right":"ON","temperature":null,"voltage":227.4}' Info 2022-12-09 02:11:27MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch/action', payload 'single' Debug 2022-12-09 02:11:34Received Zigbee message from 'Office Xiong: 3 button switch', type 'attributeReport', cluster 'aqaraOpple', data '{"229":{"data":[9,0,9,0,0,4,99,90,186,37,49,197,158,37,71,170,232,37,51,129,212,37,119,124,1,37,95,103,97,37,108,227,95,37,93,25,201,37,109],"type":"Buffer"}}' from endpoint 1 with groupID 0

Single Center:

Debug 2022-12-09 02:11:58Received Zigbee message from 'Office Xiong: 3 button switch', type 'attributeReport', cluster 'genMultistateInput', data '{"presentValue":1}' from endpoint 42 with groupID 0 Info 2022-12-09 02:11:58MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch', payload '{"action":"single","consumption":0.01,"current":0.0463,"device":{"applicationVersion":51,"dateCode":"02-17-2022","friendlyName":"Office Xiong: 3 button switch","hardwareVersion":1,"ieeeAddr":"0x54ef441000644d0b","manufacturerID":4447,"manufacturerName":"LUMI","model":"QBKG32LM","networkAddress":8591,"powerSource":"Mains (single phase)","stackVersion":2,"type":"Router","zclVersion":3},"device_temperature":38,"energy":0.01,"flip_indicator_light":null,"led_disabled_night":null,"linkquality":105,"operation_mode_center":"decoupled","operation_mode_left":"decoupled","operation_mode_right":"decoupled","power":7.9,"power_outage_count":0,"power_outage_memory":null,"state":"ON","state_center":"ON","state_left":"ON","state_right":"ON","temperature":null,"voltage":227.4}' Info 2022-12-09 02:11:58MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch', payload '{"action":"","consumption":0.01,"current":0.0463,"device":{"applicationVersion":51,"dateCode":"02-17-2022","friendlyName":"Office Xiong: 3 button switch","hardwareVersion":1,"ieeeAddr":"0x54ef441000644d0b","manufacturerID":4447,"manufacturerName":"LUMI","model":"QBKG32LM","networkAddress":8591,"powerSource":"Mains (single phase)","stackVersion":2,"type":"Router","zclVersion":3},"device_temperature":38,"energy":0.01,"flip_indicator_light":null,"led_disabled_night":null,"linkquality":105,"operation_mode_center":"decoupled","operation_mode_left":"decoupled","operation_mode_right":"decoupled","power":7.9,"power_outage_count":0,"power_outage_memory":null,"state":"ON","state_center":"ON","state_left":"ON","state_right":"ON","temperature":null,"voltage":227.4}' Info 2022-12-09 02:11:58MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch/action', payload 'single'

Single right:

Debug 2022-12-09 02:12:10Received Zigbee message from 'Office Xiong: 3 button switch', type 'attributeReport', cluster 'genMultistateInput', data '{"presentValue":1}' from endpoint 43 with groupID 0 Info 2022-12-09 02:12:10MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch', payload '{"action":"single","consumption":0.01,"current":0.0463,"device":{"applicationVersion":51,"dateCode":"02-17-2022","friendlyName":"Office Xiong: 3 button switch","hardwareVersion":1,"ieeeAddr":"0x54ef441000644d0b","manufacturerID":4447,"manufacturerName":"LUMI","model":"QBKG32LM","networkAddress":8591,"powerSource":"Mains (single phase)","stackVersion":2,"type":"Router","zclVersion":3},"device_temperature":38,"energy":0.01,"flip_indicator_light":null,"led_disabled_night":null,"linkquality":102,"operation_mode_center":"decoupled","operation_mode_left":"decoupled","operation_mode_right":"decoupled","power":7.9,"power_outage_count":0,"power_outage_memory":null,"state":"ON","state_center":"ON","state_left":"ON","state_right":"ON","temperature":null,"voltage":227.4}' Info 2022-12-09 02:12:10MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch', payload '{"action":"","consumption":0.01,"current":0.0463,"device":{"applicationVersion":51,"dateCode":"02-17-2022","friendlyName":"Office Xiong: 3 button switch","hardwareVersion":1,"ieeeAddr":"0x54ef441000644d0b","manufacturerID":4447,"manufacturerName":"LUMI","model":"QBKG32LM","networkAddress":8591,"powerSource":"Mains (single phase)","stackVersion":2,"type":"Router","zclVersion":3},"device_temperature":38,"energy":0.01,"flip_indicator_light":null,"led_disabled_night":null,"linkquality":102,"operation_mode_center":"decoupled","operation_mode_left":"decoupled","operation_mode_right":"decoupled","power":7.9,"power_outage_count":0,"power_outage_memory":null,"state":"ON","state_center":"ON","state_left":"ON","state_right":"ON","temperature":null,"voltage":227.4}' Info 2022-12-09 02:12:10MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch/action', payload 'single'

Double left:

Debug 2022-12-09 02:12:29Received Zigbee message from 'Office Xiong: 3 button switch', type 'attributeReport', cluster 'genMultistateInput', data '{"presentValue":2}' from endpoint 41 with groupID 0 Info 2022-12-09 02:12:29MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch', payload '{"action":"double","consumption":0.01,"current":0.0463,"device":{"applicationVersion":51,"dateCode":"02-17-2022","friendlyName":"Office Xiong: 3 button switch","hardwareVersion":1,"ieeeAddr":"0x54ef441000644d0b","manufacturerID":4447,"manufacturerName":"LUMI","model":"QBKG32LM","networkAddress":8591,"powerSource":"Mains (single phase)","stackVersion":2,"type":"Router","zclVersion":3},"device_temperature":38,"energy":0.01,"flip_indicator_light":null,"led_disabled_night":null,"linkquality":105,"operation_mode_center":"decoupled","operation_mode_left":"decoupled","operation_mode_right":"decoupled","power":7.9,"power_outage_count":0,"power_outage_memory":null,"state":"ON","state_center":"ON","state_left":"ON","state_right":"ON","temperature":null,"voltage":227.4}' Info 2022-12-09 02:12:29MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch', payload '{"action":"","consumption":0.01,"current":0.0463,"device":{"applicationVersion":51,"dateCode":"02-17-2022","friendlyName":"Office Xiong: 3 button switch","hardwareVersion":1,"ieeeAddr":"0x54ef441000644d0b","manufacturerID":4447,"manufacturerName":"LUMI","model":"QBKG32LM","networkAddress":8591,"powerSource":"Mains (single phase)","stackVersion":2,"type":"Router","zclVersion":3},"device_temperature":38,"energy":0.01,"flip_indicator_light":null,"led_disabled_night":null,"linkquality":105,"operation_mode_center":"decoupled","operation_mode_left":"decoupled","operation_mode_right":"decoupled","power":7.9,"power_outage_count":0,"power_outage_memory":null,"state":"ON","state_center":"ON","state_left":"ON","state_right":"ON","temperature":null,"voltage":227.4}' Info 2022-12-09 02:12:29MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch/action', payload 'double'

Double center:

Debug 2022-12-09 02:12:46Received Zigbee message from 'Office Xiong: 3 button switch', type 'attributeReport', cluster 'genMultistateInput', data '{"presentValue":2}' from endpoint 42 with groupID 0 Info 2022-12-09 02:12:46MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch', payload '{"action":"double","consumption":0.01,"current":0.0463,"device":{"applicationVersion":51,"dateCode":"02-17-2022","friendlyName":"Office Xiong: 3 button switch","hardwareVersion":1,"ieeeAddr":"0x54ef441000644d0b","manufacturerID":4447,"manufacturerName":"LUMI","model":"QBKG32LM","networkAddress":8591,"powerSource":"Mains (single phase)","stackVersion":2,"type":"Router","zclVersion":3},"device_temperature":38,"energy":0.01,"flip_indicator_light":null,"led_disabled_night":null,"linkquality":105,"operation_mode_center":"decoupled","operation_mode_left":"decoupled","operation_mode_right":"decoupled","power":7.9,"power_outage_count":0,"power_outage_memory":null,"state":"ON","state_center":"ON","state_left":"ON","state_right":"ON","temperature":null,"voltage":227.4}' Info 2022-12-09 02:12:46MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch', payload '{"action":"","consumption":0.01,"current":0.0463,"device":{"applicationVersion":51,"dateCode":"02-17-2022","friendlyName":"Office Xiong: 3 button switch","hardwareVersion":1,"ieeeAddr":"0x54ef441000644d0b","manufacturerID":4447,"manufacturerName":"LUMI","model":"QBKG32LM","networkAddress":8591,"powerSource":"Mains (single phase)","stackVersion":2,"type":"Router","zclVersion":3},"device_temperature":38,"energy":0.01,"flip_indicator_light":null,"led_disabled_night":null,"linkquality":105,"operation_mode_center":"decoupled","operation_mode_left":"decoupled","operation_mode_right":"decoupled","power":7.9,"power_outage_count":0,"power_outage_memory":null,"state":"ON","state_center":"ON","state_left":"ON","state_right":"ON","temperature":null,"voltage":227.4}' Info 2022-12-09 02:12:46MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch/action', payload 'double'

Double right:

Debug 2022-12-09 02:12:54Received Zigbee message from 'Office Xiong: 3 button switch', type 'attributeReport', cluster 'genMultistateInput', data '{"presentValue":2}' from endpoint 43 with groupID 0 Info 2022-12-09 02:12:54MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch', payload '{"action":"double","consumption":0.01,"current":0.0463,"device":{"applicationVersion":51,"dateCode":"02-17-2022","friendlyName":"Office Xiong: 3 button switch","hardwareVersion":1,"ieeeAddr":"0x54ef441000644d0b","manufacturerID":4447,"manufacturerName":"LUMI","model":"QBKG32LM","networkAddress":8591,"powerSource":"Mains (single phase)","stackVersion":2,"type":"Router","zclVersion":3},"device_temperature":38,"energy":0.01,"flip_indicator_light":null,"led_disabled_night":null,"linkquality":105,"operation_mode_center":"decoupled","operation_mode_left":"decoupled","operation_mode_right":"decoupled","power":7.9,"power_outage_count":0,"power_outage_memory":null,"state":"ON","state_center":"ON","state_left":"ON","state_right":"ON","temperature":null,"voltage":227.4}' Info 2022-12-09 02:12:54MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch', payload '{"action":"","consumption":0.01,"current":0.0463,"device":{"applicationVersion":51,"dateCode":"02-17-2022","friendlyName":"Office Xiong: 3 button switch","hardwareVersion":1,"ieeeAddr":"0x54ef441000644d0b","manufacturerID":4447,"manufacturerName":"LUMI","model":"QBKG32LM","networkAddress":8591,"powerSource":"Mains (single phase)","stackVersion":2,"type":"Router","zclVersion":3},"device_temperature":38,"energy":0.01,"flip_indicator_light":null,"led_disabled_night":null,"linkquality":105,"operation_mode_center":"decoupled","operation_mode_left":"decoupled","operation_mode_right":"decoupled","power":7.9,"power_outage_count":0,"power_outage_memory":null,"state":"ON","state_center":"ON","state_left":"ON","state_right":"ON","temperature":null,"voltage":227.4}' Info 2022-12-09 02:12:54MQTT publish: topic 'zigbee2mqtt/Office Xiong: 3 button switch/action', payload 'double'

All in all, I'm not sure how to proceed. Ideally I can recycle the 2 button's converter but this isn't allowing me to start Z2M. With the button presses also not matching/clusters info being different, I'm not sure how best to recyucle the 2 button version's converter.

Any help would be truly appreciated!

Edit: apparently too many characters, editing down the converter

External converter

const exposes = require('zigbee-herdsman-converters/converters/exposes');
const fz = {...require('zigbee-herdsman-converters/converters/fromZigbee'), legacy: require('zigbee-herdsman-converters/converters/legacy').fromZigbee};
const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const ota = require('zigbee-herdsman-converters/converters/ota');
const constants = require('zigbee-herdsman-converters/converters/constants');
const reporting = require('zigbee-herdsman-converters/converters/reporting');
const extend = require('zigbee-herdsman-converters/converters/extend');
const e = exposes.presets;
const ea = exposes.access;
const globalStore = require('zigbee-herdsman-converters/converters/store');
const xiaomi = require('zigbee-herdsman-converters/converters/xiaomi');
const utils = require('zigbee-herdsman-converters/converters/utils');

const xiaomiExtend = {
    light_onoff_brightness_colortemp: (options={disableColorTempStartup: true}) => ({
        ...extend.light_onoff_brightness_colortemp(options),
        fromZigbee: extend.light_onoff_brightness_colortemp(options).fromZigbee.concat([
            fz.xiaomi_bulb_interval, fz.ignore_occupancy_report, fz.ignore_humidity_report,
            fz.ignore_pressure_report, fz.ignore_temperature_report,
        ]),
    }),
};

const preventReset = async (type, data, device) => {
    if (
        // options.allow_reset ||
        type !== 'message' ||
        data.type !== 'attributeReport' ||
        data.cluster !== 'genBasic' ||
        !data.data[0xfff0] ||
        // eg: [0xaa, 0x10, 0x05, 0x41, 0x87, 0x01, 0x01, 0x10, 0x00]
        !data.data[0xFFF0].slice(0, 5).equals(Buffer.from([0xaa, 0x10, 0x05, 0x41, 0x87]))
    ) {
        return;
    }
    const options = {manufacturerCode: 0x115f};
    const payload = {[0xfff0]: {
        value: [0xaa, 0x10, 0x05, 0x41, 0x47, 0x01, 0x01, 0x10, 0x01],
        type: 0x41,
    }};
    await device.getEndpoint(1).write('genBasic', payload, options);
};

const daysLookup = {
    0x7f: 'everyday',
    0x1f: 'workdays',
    0x60: 'weekend',
    0x01: 'mon',
    0x02: 'tue',
    0x04: 'wed',
    0x08: 'thu',
    0x10: 'fri',
    0x20: 'sat',
    0x40: 'sun',
    0x55: 'mon-wed-fri-sun',
    0x2a: 'tue-thu-sat',
};

const fzLocal = {
    aqara_trv: {
        cluster: 'aqaraOpple',
        type: ['attributeReport', 'readResponse'],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            Object.entries(msg.data).forEach(([key, value]) => {
                switch (parseInt(key)) {
                case 0x0271:
                    result['system_mode'] = {1: 'heat', 0: 'off'}[value];
                    break;
                case 0x0272:
                    result['preset'] = {2: 'away', 1: 'auto', 0: 'manual'}[value];
                    break;
                case 0x0273:
                    result['window_detection'] = {1: 'ON', 0: 'OFF'}[value];
                    break;
                case 0x0274:
                    result['valve_detection'] = {1: 'ON', 0: 'OFF'}[value];
                    break;
                case 0x0277:
                    result['child_lock'] = {1: 'LOCK', 0: 'UNLOCK'}[value];
                    break;
                case 0x0279:
                    result['away_preset_temperature'] = (value / 100).toFixed(1);
                    break;
                case 0x027b:
                    result['calibrated'] = {1: true, 0: false}[value];
                    break;
                case 0x027e:
                    result['sensor'] = {1: 'external', 0: 'internal'}[value];
                    break;
                case 0x040a:
                    result['battery'] = value;
                    break;
                case 0x027a:
                    result['window_open'] = {1: true, 0: false}[value];
                    break;
                case 0x0275:
                    result['valve_alarm'] = {1: true, 0: false}[value];
                    break;
                case 0xfff2:
                case 0x00ff: // 4e:27:49:bb:24:b6:30:dd:74:de:53:76:89:44:c4:81
                case 0x00f7: // 03:28:1f:05:21:01:00:0a:21:00:00:0d:23:19:08:00:00:11:23...
                case 0x0276: // 04:3e:01:e0:00:00:09:60:04:38:00:00:06:a4:05:64:00:00:08:98:81:e0:00:00:08:98
                case 0x027c: // 0x00
                case 0x027d: // 0x00
                case 0x0280: // 0x00/0x01
                    meta.logger.debug(`zigbee-herdsman-converters:aqara_trv: Unhandled key ${key} = ${value}`);
                    break;
                default:
                    meta.logger.warn(`zigbee-herdsman-converters:aqara_trv: Unknown key ${key} = ${value}`);
                }
            });
            return result;
        },
    },
    aqara_feeder: {
        cluster: 'aqaraOpple',
        type: ['attributeReport', 'readResponse'],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            Object.entries(msg.data).forEach(([key, value]) => {
                switch (parseInt(key)) {
                case 0xfff1: {
                    const attr = value.slice(3, 7);
                    const len = value.slice(7, 8).readUInt8();
                    const val = value.slice(8, 8 + len);
                    switch (attr.readInt32BE()) {
                    case 0x04150055: // feeding
                        result['feed'] = '';
                        break;
                    case 0x041502bc: { // feeding report
                        const report = val.toString();
                        result['feeding_source'] = {0: 'schedule', 1: 'manual', 2: 'remote'}[parseInt(report.slice(0, 2))];
                        result['feeding_size'] = parseInt(report.slice(3, 4));
                        break;
                    }
                    case 0x0d680055: // portions per day
                        result['portions_per_day'] = val.readUInt16BE();
                        break;
                    case 0x0d690055: // weight per day
                        result['weight_per_day'] = val.readUInt32BE();
                        break;
                    case 0x0d0b0055: // error ?
                        result['error'] = {1: true, 0: false}[val.readUInt8()];
                        break;
                    case 0x080008c8: { // schedule string
                        const schlist = val.toString().split(',');
                        const schedule = [];
                        schlist.forEach((str) => { // 7f13000100
                            const feedtime = Buffer.from(str, 'hex');
                            schedule.push({
                                'days': daysLookup[feedtime[0]],
                                'hour': feedtime[1],
                                'minute': feedtime[2],
                                'size': feedtime[3],
                            });
                        });
                        result['schedule'] = schedule;
                        break;
                    }
                    case 0x04170055: // indicator
                        result['led_indicator'] = {1: 'ON', 0: 'OFF'}[val.readUInt8()];
                        break;
                    case 0x04160055: // child lock
                        result['child_lock'] = {1: 'LOCK', 0: 'UNLOCK'}[val.readUInt8()];
                        break;
                    case 0x04180055: // mode
                        result['mode'] = {1: 'schedule', 0: 'manual'}[val.readUInt8()];
                        break;
                    case 0x0e5c0055: // serving size
                        result['serving_size'] = val.readUInt8();
                        break;
                    case 0x0e5f0055: // portion weight
                        result['portion_weight'] = val.readUInt8();
                        break;
                    case 0x080007d1: // ? 64
                    case 0x0d090055: // ? 00
                        meta.logger.warn(`zigbee-herdsman-converters:aqara_feeder: Unhandled attribute ${attr} = ${val}`);
                        break;
                    default:
                        meta.logger.warn(`zigbee-herdsman-converters:aqara_feeder: Unknown attribute ${attr} = ${val}`);
                    }
                    break;
                }
                case 0x00ff: // 80:13:58:91:24:33:20:24:58:53:44:07:05:97:75:17
                case 0x0007: // 00:00:00:00:1d:b5:a6:ed
                case 0x00f7: // 05:21:14:00:0d:23:21:25:00:00:09:21:00:01
                    meta.logger.debug(`zigbee-herdsman-converters:aqara_feeder: Unhandled key ${key} = ${value}`);
                    break;
                default:
                    meta.logger.warn(`zigbee-herdsman-converters:aqara_feeder: Unknown key ${key} = ${value}`);
                }
            });
            return result;
        },
    },
};

const tzLocal = {
    aqara_trv: {
        key: ['system_mode', 'preset', 'window_detection', 'valve_detection', 'child_lock', 'away_preset_temperature',
            'calibrate', 'sensor', 'sensor_temp', 'identify'],
        convertSet: async (entity, key, value, meta) => {
            const aqaraHeader = (counter, params, action) => {
                const header = [0xaa, 0x71, params.length + 3, 0x44, counter];
                const integrity = 512 - header.reduce((sum, elem) => sum + elem, 0);
                return [...header, integrity, action, 0x41, params.length];
            };
            const sensor = Buffer.from('00158d00019d1b98', 'hex');

            switch (key) {
            case 'system_mode':
                await entity.write('aqaraOpple', {0x0271: {value: {'off': 0, 'heat': 1}[value], type: 0x20}},
                    {manufacturerCode: 0x115f});
                break;
            case 'preset':
                await entity.write('aqaraOpple', {0x0272: {value: {'manual': 0, 'auto': 1, 'away': 2}[value], type: 0x20}},
                    {manufacturerCode: 0x115f});
                break;
            case 'window_detection':
                await entity.write('aqaraOpple', {0x0273: {value: {'OFF': 0, 'ON': 1}[value], type: 0x20}},
                    {manufacturerCode: 0x115f});
                break;
            case 'valve_detection':
                await entity.write('aqaraOpple', {0x0274: {value: {'OFF': 0, 'ON': 1}[value], type: 0x20}},
                    {manufacturerCode: 0x115f});
                break;
            case 'child_lock':
                await entity.write('aqaraOpple', {0x0277: {value: {'UNLOCK': 0, 'LOCK': 1}[value], type: 0x20}},
                    {manufacturerCode: 0x115f});
                break;
            case 'away_preset_temperature':
                await entity.write('aqaraOpple', {0x0279: {value: Math.round(value * 100), type: 0x23}}, {manufacturerCode: 0x115f});
                break;
            case 'sensor': {
                const device = Buffer.from(entity.deviceIeeeAddress.substring(2), 'hex');
                const timestamp = Buffer.alloc(4);
                timestamp.writeUint32BE(Date.now()/1000);

                if (value === 'external') {
                    const params1 = [
                        ...timestamp,
                        0x3d, 0x04,
                        ...device,
                        ...sensor,
                        0x00, 0x01, 0x00, 0x55,
                        0x13, 0x0a, 0x02, 0x00, 0x00, 0x64, 0x04, 0xce, 0xc2, 0xb6, 0xc8,
                        0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3d,
                        0x64,
                        0x65,
                    ];
                    const params2 = [
                        ...timestamp,
                        0x3d, 0x05,
                        ...device,
                        ...sensor,
                        0x08, 0x00, 0x07, 0xfd,
                        0x16, 0x0a, 0x02, 0x0a, 0xc9, 0xe8, 0xb1, 0xb8, 0xd4, 0xda, 0xcf, 0xdf, 0xc0, 0xeb,
                        0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3d,
                        0x04,
                        0x65,
                    ];

                    const val1 = [...(aqaraHeader(0x12, params1, 0x02)), ...params1];
                    const val2 = [...(aqaraHeader(0x13, params2, 0x02)), ...params2];

                    await entity.write('aqaraOpple', {0xfff2: {value: val1, type: 0x41}}, {manufacturerCode: 0x115f});
                    await entity.write('aqaraOpple', {0xfff2: {value: val2, type: 0x41}}, {manufacturerCode: 0x115f});
                } else if (value === 'internal') {
                    const params1 = [
                        ...timestamp,
                        0x3d, 0x05,
                        ...device,
                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                    ];
                    const params2 = [
                        ...timestamp,
                        0x3d, 0x04,
                        ...device,
                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                    ];

                    const val1 = [...(aqaraHeader(0x12, params1, 0x04)), ...params1];
                    const val2 = [...(aqaraHeader(0x13, params2, 0x04)), ...params2];

                    await entity.write('aqaraOpple', {0xfff2: {value: val1, type: 0x41}}, {manufacturerCode: 0x115f});
                    await entity.write('aqaraOpple', {0xfff2: {value: val2, type: 0x41}}, {manufacturerCode: 0x115f});

                    await entity.read('hvacThermostat', ['localTemp']);
                }
                break;
            }
            case 'sensor_temp':
                if (meta.state['sensor'] === 'external') {
                    const temperatureBuf = Buffer.alloc(4);
                    temperatureBuf.writeFloatBE(Math.round(value * 100));

                    const params = [...sensor, 0x00, 0x01, 0x00, 0x55, ...temperatureBuf];
                    const data = [...(aqaraHeader(0x12, params, 0x05)), ...params];

                    await entity.write('aqaraOpple', {0xfff2: {value: data, type: 0x41}}, {manufacturerCode: 0x115f});
                }
                break;
            case 'calibrate':
                await entity.write('aqaraOpple', {0x0270: {value: 1, type: 0x20}}, {manufacturerCode: 0x115F});
                break;
            case 'identify':
                await entity.command('genIdentify', 'identify', {identifytime: 5}, {});
                break;
            default: // Unknown key
                meta.logger.warn(`zigbee-herdsman-converters:aqara_trv: Unhandled key ${key}`);
            }
        },
        convertGet: async (entity, key, meta) => {
            const dict = {'system_mode': 0x0271, 'preset': 0x0272, 'window_detection': 0x0273, 'valve_detection': 0x0274,
                'child_lock': 0x0277, 'away_preset_temperature': 0x0279, 'calibrated': 0x027b, 'sensor': 0x027e};

            if (dict.hasOwnProperty(key)) {
                await entity.read('aqaraOpple', [dict[key]], {manufacturerCode: 0x115F});
            }
        },
    },
    VOCKQJK11LM_display_unit: {
        key: ['display_unit'],
        convertSet: async (entity, key, value, meta) => {
            await entity.write('aqaraOpple',
                {0x0114: {value: xiaomi.VOCKQJK11LMDisplayUnit[value], type: 0x20}}, {manufacturerCode: 0x115F});
            return {state: {display_unit: value}};
        },
        convertGet: async (entity, key, meta) => {
            await entity.read('aqaraOpple', [0x0114], {manufacturerCode: 0x115F, disableDefaultResponse: true});
        },
    },
    aqara_feeder: {
        key: ['feed', 'schedule', 'led_indicator', 'child_lock', 'mode', 'serving_size', 'portion_weight'],
        convertSet: async (entity, key, value, meta) => {
            const sendAttr = async (attrCode, value, length) => {
                entity.sendSeq = ((entity.sendSeq || 0)+1) % 256;
                const val = Buffer.from([0x00, 0x02, entity.sendSeq, 0, 0, 0, 0, 0]);
                entity.sendSeq += 1;
                val.writeInt32BE(attrCode, 3);
                val.writeUInt8(length, 7);
                let v = Buffer.alloc(length);
                switch (length) {
                case 1:
                    v.writeUInt8(value);
                    break;
                case 2:
                    v.writeUInt16BE(value);
                    break;
                case 4:
                    v.writeUInt32BE(value);
                    break;
                default:
                    v = value;
                }
                await entity.write('aqaraOpple', {0xfff1: {value: Buffer.concat([val, v]), type: 0x41}},
                    {manufacturerCode: 0x115f});
            };
            switch (key) {
            case 'feed':
                sendAttr(0x04150055, 1, 1);
                break;
            case 'schedule': {
                const schedule = [];
                value.forEach((item) => {
                    const schedItem = Buffer.from([
                        utils.getKey(daysLookup, item.days, 0x7f),
                        item.hour,
                        item.minute,
                        item.size,
                        0,
                    ]);
                    schedule.push(schedItem.toString('hex'));
                });
                const val = Buffer.concat([Buffer.from(schedule.join(',')), Buffer.from([0])]);
                sendAttr(0x080008c8, val, val.length);
                break;
            }
            case 'led_indicator':
                sendAttr(0x04170055, {'OFF': 0, 'ON': 1}[value], 1);
                break;
            case 'child_lock':
                sendAttr(0x04160055, {'UNLOCK': 0, 'LOCK': 1}[value], 1);
                break;
            case 'mode':
                sendAttr(0x04180055, {'manual': 0, 'schedule': 1}[value], 1);
                break;
            case 'serving_size':
                sendAttr(0x0e5c0055, value, 4);
                break;
            case 'portion_weight':
                sendAttr(0x0e5f0055, value, 4);
                break;
            default: // Unknown key
                meta.logger.warn(`zigbee-herdsman-converters:aqara_feeder: Unhandled key ${key}`);
            }
            return {state: {[key]: value}};
        },
    },
};

module.exports = [
    {
        zigbeeModel: ['lumi.flood.acn001'],
        model: 'SJCGQ13LM',
        vendor: 'Xiaomi',
        description: 'Aqara E1 water leak sensor',
        fromZigbee: [fz.ias_water_leak_alarm_1, fz.aqara_opple, fz.battery],
        toZigbee: [],
        exposes: [e.water_leak(), e.battery(), e.battery_low(), e.battery_voltage(), e.device_temperature(), e.power_outage_count(false)],
        meta: {battery: {voltageToPercentage: '3V_2850_3000'}},
        configure: async (device, coordinatorEndpoint, logger) => {
            const endpoint = device.getEndpoint(1);
            await endpoint.read('genPowerCfg', ['batteryVoltage']);
        },
        ota: ota.zigbeeOTA,
    },
    {
        zigbeeModel: ['lumi.magnet.acn001'],
        model: 'MCCGQ14LM',
        vendor: 'Xiaomi',
        description: 'Aqara E1 door & window contact sensor',
        fromZigbee: [fz.ias_contact_alarm_1, fz.aqara_opple, fz.battery],
        toZigbee: [],
        meta: {battery: {voltageToPercentage: '3V_2850_3000'}},
        exposes: [e.contact(), e.battery(), e.battery_low(), e.battery_voltage()],
        configure: async (device, coordinatorEndpoint, logger) => {
            const endpoint = device.getEndpoint(1);
            await endpoint.read('genPowerCfg', ['batteryVoltage']);
        },
        // OTA request: "fieldControl":0, "manufacturerCode":4447, "imageType":10635
        ota: ota.zigbeeOTA,
    },
    {
        zigbeeModel: ['lumi.magnet.ac01'],
        model: 'MCCGQ13LM',
        vendor: 'Xiaomi',
        description: 'Aqara P1 door & window contact sensor',
        fromZigbee: [fz. xiaomi_contact, fz.ias_contact_alarm_1, fz.aqara_opple],
        toZigbee: [],
        meta: {battery: {voltageToPercentage: '3V_2850_3000'}},
        exposes: [e.contact(), e.battery(), e.battery_voltage()],
    },
    {
        zigbeeModel: ['lumi.dimmer.rcbac1'],
        model: 'ZNDDMK11LM',
        vendor: 'Xiaomi',
        description: 'Aqara smart lightstrip driver',
        fromZigbee: extend.light_onoff_brightness_colortemp_color().fromZigbee.concat([
            fz.xiaomi_power, fz.aqara_opple]),
        toZigbee: extend.light_onoff_brightness_colortemp_color().toZigbee.concat([
            tz.xiaomi_dimmer_mode, tz.xiaomi_switch_power_outage_memory]),
        meta: {multiEndpoint: true},
        endpoint: (device) => {
            return {l1: 1, l2: 2};
        },
        exposes: [e.power(), e.energy(), e.voltage(), e.device_temperature(), e.power_outage_memory(),
            // When in rgbw mode, only one of color and colortemp will be valid, and l2 will be invalid
            // Do not control l2 in rgbw mode
            e.light_brightness_colortemp_colorxy([153, 370]).removeFeature('color_temp_startup').withEndpoint('l1'),
            e.light_brightness_colortemp([153, 370]).removeFeature('color_temp_startup').withEndpoint('l2'),
            exposes.enum('dimmer_mode', ea.ALL, ['rgbw', 'dual_ct'])
                .withDescription('Switch between rgbw mode or dual color temperature mode')],
        ota: ota.zigbeeOTA,
    },
    {
        zigbeeModel: ['lumi.light.aqcn02'],
        model: 'ZNLDP12LM',
        vendor: 'Xiaomi',
        description: 'Aqara smart LED bulb',
        toZigbee: xiaomiExtend.light_onoff_brightness_colortemp({colorTempRange: [153, 370]}).toZigbee.concat([
            tz.xiaomi_light_power_outage_memory]),
        fromZigbee: xiaomiExtend.light_onoff_brightness_colortemp({colorTempRange: [153, 370]}).fromZigbee,
        // power_on_behavior 'toggle' does not seem to be supported
        exposes: xiaomiExtend.light_onoff_brightness_colortemp({colorTempRange: [153, 370]}).exposes.concat([
            e.power_outage_memory().withAccess(ea.STATE_SET)]),
        ota: ota.zigbeeOTA,
    },
    {
        zigbeeModel: ['lumi.light.acn003'],
        model: 'ZNXDD01LM',
        vendor: 'Xiaomi',
        description: 'Aqara ceiling light L1-350',
        extend: xiaomiExtend.light_onoff_brightness_colortemp({disableEffect: true, colorTempRange: [153, 370]}),
        ota: ota.zigbeeOTA,
    },
    {
        zigbeeModel: ['lumi.light.cwac02', 'lumi.light.acn014'],
        model: 'ZNLDP13LM',
        vendor: 'Xiaomi',
        description: 'Aqara T1 smart LED bulb',
        toZigbee: xiaomiExtend.light_onoff_brightness_colortemp({disableEffect: true, disablePowerOnBehavior: true}).toZigbee.concat([
            tz.xiaomi_switch_power_outage_memory,
        ]),
        fromZigbee: xiaomiExtend.light_onoff_brightness_colortemp({disableEffect: true, disablePowerOnBehavior: true}).fromZigbee.concat([
            fz.aqara_opple,
        ]),
        exposes: xiaomiExtend.light_onoff_brightness_colortemp({
            disableEffect: true,
            disablePowerOnBehavior: true,
            colorTempRange: [153, 370],
        }).exposes.concat([
            e.power_outage_memory(),
            e.device_temperature(),
            e.power_outage_count(),
        ]),
        ota: ota.zigbeeOTA,
    },
    {
        zigbeeModel: ['lumi.light.cwopcn01'],
        model: 'XDD11LM',
        vendor: 'Xiaomi',
        description: 'Aqara Opple MX960',
        meta: {turnsOffAtBrightness1: true},
        extend: xiaomiExtend.light_onoff_brightness_colortemp({disableEffect: true, disableColorTempStartup: true,
            colorTempRange: [175, 370]}),
        ota: ota.zigbeeOTA,
    },
    {
        zigbeeModel: ['lumi.light.cwopcn02'],
        model: 'XDD12LM',
        vendor: 'Xiaomi',
        description: 'Aqara Opple MX650',
        meta: {turnsOffAtBrightness1: true},
        extend: xiaomiExtend.light_onoff_brightness_colortemp({disableEffect: true, disableColorTempStartup: true,
            colorTempRange: [175, 370]}),
        ota: ota.zigbeeOTA,
    },
    {
        zigbeeModel: ['lumi.light.cwopcn03'],
        model: 'XDD13LM',
        vendor: 'Xiaomi',
        description: 'Aqara Opple MX480',
        meta: {turnsOffAtBrightness1: true},
        extend: xiaomiExtend.light_onoff_brightness_colortemp({disableEffect: true, disableColorTempStartup: true,
            colorTempRange: [175, 370]}),
        ota: ota.zigbeeOTA,
    },
    {
        zigbeeModel: ['lumi.light.cwjwcn01'],
        model: 'JWSP001A',
        vendor: 'Xiaomi',
        description: 'Jiawen LED Driver & Dimmer',
        extend: xiaomiExtend.light_onoff_brightness_colortemp({disableEffect: true, disableColorTempStartup: true,
            colorTempRange: [153, 370]}),
    },
    {
        zigbeeModel: ['lumi.light.cwjwcn02'],
        model: 'JWDL001A',
        vendor: 'Xiaomi',
        description: 'Aqara embedded spot led light',
        extend: xiaomiExtend.light_onoff_brightness_colortemp({colorTempRange: [153, 370]}),
    },
    {
        zigbeeModel: ['lumi.sensor_switch'],
        model: 'WXKG01LM',
        vendor: 'Xiaomi',
        whiteLabel: [{vendor: 'Xiaomi', model: 'YTC4040GL'}, {vendor: 'Xiaomi', model: 'YTC4006CN'},
            {vendor: 'Xiaomi', model: 'YTC4017CN'}, {vendor: 'Xiaomi', model: 'ZHTZ02LM'}],
        description: 'MiJia wireless switch',
        meta: {battery: {voltageToPercentage: '3V_2850_3000'}},
        fromZigbee: [fz.xiaomi_basic, fz.xiaomi_WXKG01LM_action, fz.legacy.WXKG01LM_click],
        exposes: [e.battery(), e.action(['single', 'double', 'triple', 'quadruple', 'hold', 'release', 'many']), e.battery_voltage(),
            e.power_outage_count(false)],
        toZigbee: [],
    },
    {
        zigbeeModel: ['lumi.sensor_switch.aq2', 'lumi.remote.b1acn01'],
        model: 'WXKG11LM',
        vendor: 'Xiaomi',
        description: 'Aqara wireless switch',
        meta: {battery: {voltageToPercentage: '3V_2850_3000'}},
        exposes: [e.battery(), e.battery_voltage(), e.action(['single', 'double', 'triple', 'quadruple', 'hold', 'release']),
            e.device_temperature(), e.power_outage_count()],
        fromZigbee: [fz.xiaomi_multistate_action, fz.xiaomi_WXKG11LM_action, fz.xiaomi_basic,
            fz.legacy.WXKG11LM_click, fz.legacy.xiaomi_action_click_multistate],
        toZigbee: [],
    },
    {
        zigbeeModel: ['lumi.sensor_switch.aq3', 'lumi.sensor_swit'],
        model: 'WXKG12LM',
        vendor: 'Xiaomi',
        description: 'Aqara wireless switch (with gyroscope)',
        meta: {battery: {voltageToPercentage: '3V_2850_3000'}},
        exposes: [e.battery(), e.action(['single', 'double', 'hold', 'release', 'shake']), e.battery_voltage()],
        fromZigbee: [fz.xiaomi_basic, fz.xiaomi_multistate_action, fz.legacy.WXKG12LM_action_click_multistate],
        toZigbee: [],
    },
    {
        zigbeeModel: ['lumi.sensor_86sw1'],
        model: 'WXKG03LM_rev1',
        vendor: 'Xiaomi',
        description: 'Aqara single key wireless wall switch (2016 model)',
        meta: {battery: {voltageToPercentage: '3V_2850_3000'}},
        exposes: [e.battery(), e.action(['single']), e.battery_voltage()],
        fromZigbee: [fz.xiaomi_on_off_action, fz.xiaomi_basic, fz.legacy.WXKG03LM_click],
        toZigbee: [],
        onEvent: preventReset,
    },
    {
        zigbeeModel: ['lumi.remote.b186acn01'],
        model: 'WXKG03LM_rev2',
        vendor: 'Xiaomi',
        description: 'Aqara single key wireless wall switch (2018 model)',
        meta: {battery: {voltageToPercentage: '3V_2850_3000'}},
        exposes: [e.battery(), e.action(['single', 'double', 'hold']), e.battery_voltage()],
        fromZigbee: [fz.xiaomi_on_off_action, fz.xiaomi_multistate_action, fz.xiaomi_basic,
            fz.legacy.WXKG03LM_click, fz.legacy.xiaomi_action_click_multistate],
        toZigbee: [],
        onEvent: preventReset,
    },
    {
        zigbeeModel: ['lumi.remote.b186acn02'],
        model: 'WXKG06LM',
        vendor: 'Xiaomi',
        description: 'Aqara D1 single key wireless wall switch',
        fromZigbee: [fz.xiaomi_basic, fz.xiaomi_on_off_action, fz.xiaomi_multistate_action],
        toZigbee: [],
        exposes: [e.battery(),
            e.action(['single', 'double', 'hold']),
            e.battery_voltage()],
        onEvent: preventReset,
        meta: {battery: {voltageToPercentage: '3V_2850_3000'}},
        configure: async (device, coordinatorEndpoint, logger) => {
            try {
                const endpoint = device.endpoints[1];
                await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genPowerCfg']);
            } catch (error) {
                // fails for some but device works as expected: https://github.com/Koenkk/zigbee2mqtt/issues/9136
            }
        },
    },
    {
        zigbeeModel: ['lumi.sensor_86sw2', 'lumi.sensor_86sw2.es1'],
        model: 'WXKG02LM_rev1',
        vendor: 'Xiaomi',
        description: 'Aqara double key wireless wall switch (2016 model)',
        meta: {battery: {voltageToPercentage: '3V_2850_3000'}},
        exposes: [e.battery(), e.action(['single_left', 'single_right', 'single_both']), e.battery_voltage(), e.power_outage_count(false)],
        fromZigbee: [fz.xiaomi_on_off_action, fz.xiaomi_basic, fz.legacy.WXKG02LM_click],
        toZigbee: [],
        onEvent: preventReset,
    },
    {
        zigbeeModel: ['lumi.remote.b286acn01'],
        model: 'WXKG02LM_rev2',
        vendor: 'Xiaomi',
        description: 'Aqara double key wireless wall switch (2018 model)',
        meta: {battery: {voltageToPercentage: '3V_2850_3000'}},
        exposes: [e.battery(), e.action(['single_left', 'single_right', 'single_both', 'double_left', 'double_right', 'double_both',
            'hold_left', 'hold_right', 'hold_both']), e.battery_voltage()],
        fromZigbee: [fz.xiaomi_on_off_action, fz.xiaomi_multistate_action, fz.xiaomi_basic,
            fz.legacy.WXKG02LM_click, fz.legacy.WXKG02LM_click_multistate],
        toZigbee: [],
        onEvent: preventReset,
    },
    {
        zigbeeModel: ['lumi.switch.b1laus01'],
        model: 'WS-USC01',
        vendor: 'Xiaomi',
        description: 'Aqara smart wall switch (no neutral, single rocker)',
        fromZigbee: [fz.on_off, fz.xiaomi_multistate_action, fz.aqara_opple],
        toZigbee: [tz.on_off, tz.xiaomi_switch_operation_mode_opple,
            tz.xiaomi_flip_indicator_light, tz.aqara_switch_mode_switch],
        exposes: [e.switch(), e.action(['single', 'double']), e.flip_indicator_light(),
            exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']).withDescription('Decoupled mode'),
            exposes.enum('mode_switch', ea.ALL, ['anti_flicker_mode', 'quick_mode'])
                .withDescription('Anti flicker mode can be used to solve blinking issues of some lights.' +
                    'Quick mode makes the device respond faster.')],
        onEvent: preventReset,
        configure: async (device, coordinatorEndpoint, logger) => {
            const endpoint1 = device.getEndpoint(1);
            // set "event" mode
            await endpoint1.write('aqaraOpple', {'mode': 1}, {manufacturerCode: 0x115f, disableResponse: true});
        },
        ota: ota.zigbeeOTA,
    },
    {
        zigbeeModel: ['lumi.switch.b2laus01'],
        model: 'WS-USC02',
        vendor: 'Xiaomi',
        description: 'Aqara smart wall switch (no neutral, double rocker)',
        extend: extend.switch(),
        exposes: [e.switch().withEndpoint('top'), e.switch().withEndpoint('bottom')],
        meta: {multiEndpoint: true},
        endpoint: (device) => {
            return {'top': 1, 'bottom': 2};
        },
        configure: async (device, coordinatorEndpoint, logger) => {
            const endpoint1 = device.getEndpoint(1);
            await reporting.bind(endpoint1, coordinatorEndpoint, ['genOnOff']);
            await reporting.onOff(endpoint1);
            const endpoint2 = device.getEndpoint(2);
            await reporting.bind(endpoint2, coordinatorEndpoint, ['genOnOff']);
            await reporting.onOff(endpoint2);
        },
        ota: ota.zigbeeOTA,
    },
    {
        zigbeeModel: ['lumi.switch.b1naus01'],
        model: 'WS-USC03',
        vendor: 'Xiaomi',
        description: 'Aqara smart wall switch (neutral, single rocker)',
        extend: extend.switch(),
        configure: async (device, coordinatorEndpoint, logger) => {
            const endpoint = device.getEndpoint(1);
            await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
            await reporting.onOff(endpoint);
        },
        ota: ota.zigbeeOTA,
    },
    {
        zigbeeModel: ['lumi.switch.b2naus01'],
        model: 'WS-USC04',
        vendor: 'Xiaomi',
        description: 'Aqara smart wall switch (neutral, double rocker)',
        extend: extend.switch(),
        exposes: [e.switch().withEndpoint('top'), e.switch().withEndpoint('bottom')],
        meta: {multiEndpoint: true},
        endpoint: (device) => {
            return {'top': 1, 'bottom': 2};
        },
        configure: async (device, coordinatorEndpoint, logger) => {
            const endpoint1 = device.getEndpoint(1);
            await reporting.bind(endpoint1, coordinatorEndpoint, ['genOnOff']);
            await reporting.onOff(endpoint1);
            const endpoint2 = device.getEndpoint(2);
            await reporting.bind(endpoint2, coordinatorEndpoint, ['genOnOff']);
            await reporting.onOff(endpoint2);
        },
        ota: ota.zigbeeOTA,
    },
    {
        zigbeeModel: ['lumi.switch.n3acn1'],
        model: 'QBKG32LM',
        vendor: 'Xiaomi',
        description: 'Aqara smart wall switch H1 Pro (with neutral, triple rocker)',
        meta: {multiEndpoint: true},
        endpoint: (device) => {
            return {'left': 1, 'center': 2, 'right': 3};
        },
        fromZigbee: [fz.on_off, fz.xiaomi_power, fz.aqara_opple, fz.xiaomi_multistate_action],
        toZigbee: [tz.on_off, tz.xiaomi_switch_operation_mode_opple, tz.xiaomi_switch_power_outage_memory,
            tz.xiaomi_led_disabled_night, tz.xiaomi_flip_indicator_light],
        exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('center'), e.switch().withEndpoint('right'), e.power(), e.energy(), e.voltage(),
            e.device_temperature(), e.power_outage_memory(), e.led_disabled_night(), e.flip_indicator_light(),
            e.action([
                'single_left', 'single_center', 'single_right', 'single_all',
                'double_left', 'double_center', 'double_right', 'double_all']),
            exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled'])
                .withDescription('Decoupled mode for left button').withEndpoint('left'),
            exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled'])
                .withDescription('Decoupled mode for center button').withEndpoint('center')],
            exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled'])
                .withDescription('Decoupled mode for right button').withEndpoint('right')],
        onEvent: preventReset,
        ota: ota.zigbeeOTA,
    },
];

Supported color modes

No response

Color temperature range

No response

Deuteronomy93 commented 1 year ago

After removing the converter, restarting Z2M to get the network up again, and then adding the converter and restarting, Z2M works. Not sure what the issue was but no edits were made to the converter.

I have played around with the converter more and come to these theories:

The current converter I'm using doesn't allow the center button to be turned on/off or decoupled (I'll look at this later as I had a previous converter where it worked).

Please can someone review and let me know how I can achieve the above?

Many thanks!

    {
        zigbeeModel: ['lumi.switch.n3acn1'],
        model: 'QBKG32LM',
        vendor: 'Xiaomi',
        description: 'Aqara smart wall switch H1 Pro (with neutral, three rocker)',
        meta: {multiEndpoint: true},
        endpoint: (device) => {
            return {'left': 1, 'centre': 2, 'right': 3};
            return {'left': 41, 'center': 42, 'right': 43}; 
        },
        fromZigbee: [fz.on_off, fz.xiaomi_power, fz.aqara_opple, fz.xiaomi_multistate_action],
        toZigbee: [tz.on_off, tz.xiaomi_switch_operation_mode_opple, tz.xiaomi_switch_power_outage_memory,
            tz.xiaomi_led_disabled_night, tz.xiaomi_flip_indicator_light],
        exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('center'), e.switch().withEndpoint('right'), e.power(), e.energy(), e.voltage(),
            e.device_temperature(), e.power_outage_memory(), e.led_disabled_night(), e.flip_indicator_light(),
            e.action([
                'single_left', 'single_center', 'single_right', 'single_all',
                'double_left', 'double_center', 'double_right', 'double_all']),
            exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled'])
                .withDescription('Decoupled mode for left button').withEndpoint('left'),
            exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled'])
                .withDescription('Decoupled mode for center button').withEndpoint('center'),
            exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled'])
                .withDescription('Decoupled mode for right button').withEndpoint('right')],
        onEvent: preventReset,
        ota: ota.zigbeeOTA,
    },
CarlosGomez-dev commented 1 year ago

Try this:


const h1_pro_actions = {
  cluster: 'genMultistateInput',
  type: ['attributeReport', 'readResponse'],
  convert: (model, msg, publish, options, meta) => {
    if ([1, 2].includes(msg.data.presentValue)) {
      const mapping = { 41: 'left', 42: 'center', 43: 'right' };
      const times = { 1: 'single', 2: 'double' };
      const button = mapping[msg.endpoint.ID];
      return { action: `${times[msg.data.presentValue]}_${button}` };
    }
  },
}

const definition = {
        zigbeeModel: ['lumi.switch.n3acn1'],
        model: 'QBKG32LM',
        vendor: 'Xiaomi',
        description: 'Aqara smart wall switch H1 Pro (with neutral, three rocker)',
        meta: {multiEndpoint: true},
        endpoint: (device) => {
            return {'left': 1, 'centre': 2, 'right': 3};
        },
        fromZigbee: [fz.on_off, fz.xiaomi_power, fz.aqara_opple, fz.xiaomi_multistate_action, h1_pro_actions],
        toZigbee: [tz.on_off, tz.xiaomi_switch_operation_mode_opple, tz.xiaomi_switch_power_outage_memory,
            tz.xiaomi_led_disabled_night, tz.xiaomi_flip_indicator_light],
        exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('center'), e.switch().withEndpoint('right'), e.power(), e.energy(), e.voltage(),
            e.device_temperature(), e.power_outage_memory(), e.led_disabled_night(), e.flip_indicator_light(),
            e.action([
                'single_left', 'single_center', 'single_right', 'single_all',
                'double_left', 'double_center', 'double_right', 'double_all']),
            exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled'])
                .withDescription('Decoupled mode for left button').withEndpoint('left'),
            exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled'])
                .withDescription('Decoupled mode for center button').withEndpoint('center'),
            exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled'])
                .withDescription('Decoupled mode for right button').withEndpoint('right')],
        onEvent: preventReset,
        ota: ota.zigbeeOTA,
    }

I removed the second return in the endpoint key (the first return ensures the second return never runs), and added the h1_pro_actions object, hopefully that's enough to report the actions.

I don't see a debug log for "all" button press, if you find the endpoint just add it to the h1_pro_actions mapping.

Deuteronomy93 commented 1 year ago

@CarlosGomez-dev

Many thanks for your feedback! I'm trying to use the converter however it's causing Z2M to hang when restarting.

Firstly I get the below, this string has always caused issues in my previous converters:

[19:27:09] INFO: Preparing to start...
[19:27:09] INFO: Socat not enabled
[19:27:09] INFO: Zigbee Herdsman debug logging enabled
[19:27:09] INFO: Starting Zigbee2MQTT...
/app/dist/util/externally-loaded.js:45
        onEvent: preventReset,
                 ^
ReferenceError: preventReset is not defined
    at /app/dist/util/externally-loaded.js:45:18
    at Script.runInContext (node:vm:141:12)
    at Script.runInNewContext (node:vm:146:17)
    at Object.runInNewContext (node:vm:306:38)
    at loadModuleFromText (/app/lib/util/utils.ts:148:8)
    at loadModuleFromFile (/app/lib/util/utils.ts:155:12)
    at Object.getExternalConvertersDefinitions (/app/lib/util/utils.ts:165:25)
    at getExternalConvertersDefinitions.next (<anonymous>)
    at new ExternalConverters (/app/lib/extension/externalConverters.ts:12:20)
    at new Controller (/app/lib/controller.ts:84:58)

If I comment out "onEvent: preventReset," I get the following when trying to start Z2M:

[19:30:27] INFO: Preparing to start...
[19:30:27] INFO: Socat not enabled
[19:30:27] INFO: Zigbee Herdsman debug logging enabled
[19:30:27] INFO: Starting Zigbee2MQTT...
/app/node_modules/zigbee-herdsman-converters/index.js:78
    definition.toZigbee.push(tz.scene_store, tz.scene_recall, tz.scene_add, tz.scene_remove, tz.scene_remove_all, tz.read, tz.write,
                        ^
TypeError: Cannot read properties of undefined (reading 'push')
    at Object.addDefinition [as addDeviceDefinition] (/app/node_modules/zigbee-herdsman-converters/index.js:78:25)
    at new ExternalConverters (/app/lib/extension/externalConverters.ts:15:17)
    at new Controller (/app/lib/controller.ts:84:58)
    at start (/app/index.js:106:18)

I've downloaded all of the converters from https://github.com/Koenkk/zigbee-herdsman-converters)/lib/ (as well as to/from zigbee) and edited the paths accordingly. It looks like it's referencing files I cannot find in Github?

Any thoughts on how to proceed?

Many thanks again!

CarlosGomez-dev commented 1 year ago

Are you using an external converter as described in https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html? You shouldn't need to download all converters, you should have one file with your external converter.

According to the error you're missing the preventReset function which is which is in the xiaomi devices file, just copy/paste this function to wherever your converter is:

const preventReset = async (type, data, device) => {
    if (
        // options.allow_reset ||
        type !== 'message' ||
        data.type !== 'attributeReport' ||
        data.cluster !== 'genBasic' ||
        !data.data[0xfff0] ||
        // eg: [0xaa, 0x10, 0x05, 0x41, 0x87, 0x01, 0x01, 0x10, 0x00]
        !data.data[0xFFF0].slice(0, 5).equals(Buffer.from([0xaa, 0x10, 0x05, 0x41, 0x87]))
    ) {
        return;
    }
    const options = {manufacturerCode: 0x115f};
    const payload = {[0xfff0]: {
        value: [0xaa, 0x10, 0x05, 0x41, 0x47, 0x01, 0x01, 0x10, 0x01],
        type: 0x41,
    }};
    await device.getEndpoint(1).write('genBasic', payload, options);
};

Not sure if that'll fix the 2nd error, if it doesn't I would suggest setting up an external converter following the z2m guide.

github-actions[bot] commented 1 year ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days

nvp760 commented 1 year ago

Hi.. got any progress on this device? Still unsupported by Z2M.

karrui commented 9 months ago

This just started happening to me after I unpaired and re-paired the device recently. The decoupled mode no longer does anything, and the power/energy values are no longer being communicated:

image image

anyone else seeing this and know how to fix it?

seems similar to https://github.com/Koenkk/zigbee2mqtt/issues/17305. I'm already using SkyConnect with the latest firmware, and it WAS working before I tried to re-pair it (due to a stuck firmware OTA update that was not actually available). Could it be due to a failed firmware update?

karrui commented 9 months ago

Seems to have been fixed with the following external converter retrieved from https://github.com/Koenkk/zigbee2mqtt/issues/12029

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 herdsman = require('zigbee-herdsman');
const {
    precisionRound, mapNumberRange, isLegacyEnabled, toLocalISOString, numberWithinRange, hasAlreadyProcessedMessage,
    calibrateAndPrecisionRoundOptions, addActionGroup, postfixWithEndpointName, getKey,
    batteryVoltageToPercentage, getMetaValue,
} = require('zigbee-herdsman-converters/lib/utils');
const manufacturerOptions = {
    xiaomi: {manufacturerCode: herdsman.Zcl.ManufacturerCode.LUMI_UNITED_TECH, disableDefaultResponse: true},
};
const tzLocal = {
    xiaomi_switch_power_outage_memory: {
        key: ['power_outage_memory'],
        convertSet: async (entity, key, value, meta) => {
//add 'QBKG32LM' to line 2123 in toZigbee.js
            if (['SP-EUC01', 'ZNCZ04LM', 'ZNCZ15LM', 'QBCZ14LM', 'QBCZ15LM', 'SSM-U01', 'SSM-U02', 'DLKZMK11LM', 'DLKZMK12LM',
                'WS-EUK01', 'WS-EUK02', 'WS-EUK03', 'WS-EUK04', 'QBKG19LM', 'QBKG20LM', 'QBKG25LM', 'QBKG26LM',
                'QBKG31LM', 'QBKG32LM', 'QBKG34LM', 'QBKG38LM', 'QBKG39LM', 'QBKG40LM', 'QBKG41LM', 'ZNDDMK11LM'].includes(meta.mapped.model)) {
                await entity.write('aqaraOpple', {0x0201: {value: value ? 1 : 0, type: 0x10}}, manufacturerOptions.xiaomi);
            } else if (['ZNCZ02LM', 'QBCZ11LM', 'LLKZMK11LM'].includes(meta.mapped.model)) {
                const payload = value ?
                    [[0xaa, 0x80, 0x05, 0xd1, 0x47, 0x07, 0x01, 0x10, 0x01], [0xaa, 0x80, 0x03, 0xd3, 0x07, 0x08, 0x01]] :
                    [[0xaa, 0x80, 0x05, 0xd1, 0x47, 0x09, 0x01, 0x10, 0x00], [0xaa, 0x80, 0x03, 0xd3, 0x07, 0x0a, 0x01]];

                await entity.write('genBasic', {0xFFF0: {value: payload[0], type: 0x41}}, manufacturerOptions.xiaomi);
                await entity.write('genBasic', {0xFFF0: {value: payload[1], type: 0x41}}, manufacturerOptions.xiaomi);
            } else if (['ZNCZ11LM'].includes(meta.mapped.model)) {
                const payload = value ?
                    [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x00, 0x01, 0x10, 0x01] :
                    [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x01, 0x01, 0x10, 0x00];

                await entity.write('genBasic', {0xFFF0: {value: payload, type: 0x41}}, manufacturerOptions.xiaomi);
            } else {
                throw new Error('Not supported');
            }
            return {state: {power_outage_memory: value}};
        },
        convertGet: async (entity, key, meta) => {
//add 'QBKG32LM' to line 2146 in toZigbee.js
            if (['SP-EUC01', 'ZNCZ04LM', 'ZNCZ15LM', 'QBCZ14LM', 'QBCZ15LM', 'SSM-U01', 'SSM-U02', 'DLKZMK11LM', 'DLKZMK12LM',
                'WS-EUK01', 'WS-EUK02', 'WS-EUK03', 'WS-EUK04', 'QBKG19LM', 'QBKG20LM', 'QBKG25LM', 'QBKG26LM',
                'QBKG31LM', 'QBKG32LM', 'QBKG34LM', 'QBKG38LM', 'QBKG39LM', 'QBKG40LM', 'QBKG41LM', 'ZNDDMK11LM'].includes(meta.mapped.model)) {
                await entity.read('aqaraOpple', [0x0201]);
            } else if (['ZNCZ02LM', 'QBCZ11LM', 'ZNCZ11LM'].includes(meta.mapped.model)) {
                await entity.read('aqaraOpple', [0xFFF0]);
            } else {
                throw new Error('Not supported');
            }
        },
    },
    xiaomi_led_disabled_night: {
      key: ['led_disabled_night'],
//add 'QBKG32LM' to line 2256 in toZigbee.js
      convertSet: async (entity, key, value, meta) => {
          if (['ZNCZ04LM', 'ZNCZ15LM', 'QBCZ14LM', 'QBCZ15LM', 'QBKG19LM', 'QBKG20LM', 'QBKG25LM', 'QBKG26LM',
              'QBKG31LM', 'QBKG32LM', 'QBKG34LM', 'DLKZMK11LM', 'SSM-U01'].includes(meta.mapped.model)) {
              await entity.write('aqaraOpple', {0x0203: {value: value ? 1 : 0, type: 0x10}}, manufacturerOptions.xiaomi);
          } else if (['ZNCZ11LM'].includes(meta.mapped.model)) {
              const payload = value ?
                  [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x00, 0x03, 0x10, 0x00] :
                  [0xaa, 0x80, 0x05, 0xd1, 0x47, 0x01, 0x03, 0x10, 0x01];

              await entity.write('genBasic', {0xFFF0: {value: payload, type: 0x41}}, manufacturerOptions.xiaomi);
          } else {
              throw new Error('Not supported');
          }
          return {state: {led_disabled_night: value}};
      },
//add 'QBKG32LM' to line 2270 in toZigbee.js
      convertGet: async (entity, key, meta) => {
          if (['ZNCZ04LM', 'ZNCZ15LM', 'QBCZ15LM', 'QBCZ14LM', 'QBKG19LM', 'QBKG20LM', 'QBKG25LM', 'QBKG26LM',
              'QBKG31LM', 'QBKG32LM', 'QBKG34LM', 'DLKZMK11LM', 'SSM-U01'].includes(meta.mapped.model)) {
              await entity.read('aqaraOpple', [0x0203], manufacturerOptions.xiaomi);
          } else {
              throw new Error('Not supported');
          }
      },
    },
 }; 

const fzLocal = { 
    xiaomi_multistate_action: {
        cluster: 'genMultistateInput',
        type: ['attributeReport'],
        convert: (model, msg, publish, options, meta) => {
            if (hasAlreadyProcessedMessage(msg)) return;
            let actionLookup = {0: 'hold', 1: 'single', 2: 'double', 3: 'triple', 255: 'release'};
            if (model.model === 'WXKG12LM') {
                actionLookup = {...actionLookup, 16: 'hold', 17: 'release', 18: 'shake'};
            }

            let buttonLookup = null;
            if (['WXKG02LM_rev2', 'WXKG07LM', 'WXKG15LM', 'WXKG17LM',
                'WRS-R02'].includes(model.model)) buttonLookup = {1: 'left', 2: 'right', 3: 'both'};
            if (['QBKG12LM', 'QBKG24LM'].includes(model.model)) buttonLookup = {5: 'left', 6: 'right', 7: 'both'};
            if (['QBKG39LM', 'QBKG41LM', 'WS-EUK02', 'WS-EUK04', 'QBKG20LM', 'QBKG31LM'].includes(model.model)) {
                buttonLookup = {41: 'left', 42: 'right', 51: 'both'};
            }
//add 'QBKG32LM' to line 5418 in fromZigbee.js
            if (['QBKG25LM', 'QBKG26LM', 'QBKG34LM', 'QBKG32LM'].includes(model.model)) {
                buttonLookup = {
                    41: 'left', 42: 'center', 43: 'right',
                    51: 'left_center', 52: 'left_right', 53: 'center_right',
                    61: 'all',
                };
            }

            const action = actionLookup[msg.data['presentValue']];
            if (buttonLookup) {
                const button = buttonLookup[msg.endpoint.ID];
                if (button) {
                    return {action: `${action}_${button}`};
                }
            } else {
                return {action};
            }
        },
    },
 };  
//add the below to xiaomi.js
const definition = {
    zigbeeModel: ['lumi.switch.n3acn1'],
    model: 'QBKG32LM',
    vendor: 'Xiaomi',
    description: 'Aqara smart wall switch H1 Pro (with neutral, triple rocker)',
    meta: {multiEndpoint: true},
    endpoint: (device) => {
        return {'left': 1, 'center': 2, 'right': 3};
    },
    fromZigbee: [fz.on_off, fz.xiaomi_power, fz.aqara_opple, fz.xiaomi_multistate_action],
    toZigbee: [tz.on_off, tz.xiaomi_switch_operation_mode_opple, tz.xiaomi_switch_power_outage_memory,
        tz.xiaomi_led_disabled_night, tz.xiaomi_flip_indicator_light],
    exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('center'), e.switch().withEndpoint('right'), e.power(), e.energy(), e.voltage(),
        e.device_temperature(), e.power_outage_memory(), e.led_disabled_night(), e.flip_indicator_light(),
        e.action([
            'single_left', 'single_center', 'single_right', 'single_all',
            'double_left', 'double_center', 'double_right', 'double_all']),
        exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled'])
            .withDescription('Decoupled mode for left button').withEndpoint('left'),
        exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled'])
            .withDescription('Decoupled mode for center button').withEndpoint('center'),
        exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled'])
            .withDescription('Decoupled mode for right button').withEndpoint('right')],
}

module.exports = definition;