Created
February 7, 2024 03:09
-
-
Save waterlou/8ba3767a339b48a9d75741f5e664894e to your computer and use it in GitHub Desktop.
Aqara_z1.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const exposes = require('zigbee-herdsman-converters/lib/exposes'); | |
const fz = require('zigbee-herdsman-converters/converters/fromZigbee'); | |
const legacy = require('zigbee-herdsman-converters/lib/legacy'); | |
const tz = require('zigbee-herdsman-converters/converters/toZigbee'); | |
const constants = require('zigbee-herdsman-converters/lib/constants'); | |
const reporting = require('zigbee-herdsman-converters/lib/reporting'); | |
const extend = require('zigbee-herdsman-converters/lib/extend'); | |
const utils = require('zigbee-herdsman-converters/lib/utils'); | |
const globalStore = require('zigbee-herdsman-converters/lib/store'); | |
const lumi = require('zigbee-herdsman-converters/lib/lumi'); | |
const { lumiZigbeeOTA } = lumi.modernExtend; | |
//. PLEASE READ: These are present from device/xiaomi.ts, only needed for external converter | |
const herdsman = require('zigbee-herdsman'); | |
const e = exposes.presets; | |
const ea = exposes.access; | |
const manufacturerOptions = { | |
xiaomi: { manufacturerCode: herdsman.Zcl.ManufacturerCode.LUMI_UNITED_TECH, disableDefaultResponse: true }, | |
}; | |
const { hasAlreadyProcessedMessage } = require('zigbee-herdsman-converters/lib/utils'); | |
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 payload = { | |
[0xfff0]: { | |
value: [0xaa, 0x10, 0x05, 0x41, 0x47, 0x01, 0x01, 0x10, 0x01], | |
type: 0x41, | |
} | |
}; | |
await device.getEndpoint(1).write('genBasic', payload, { manufacturerCode }); | |
}; | |
const fzLocal = { | |
lumi_multistate_action: { | |
cluster: 'genMultistateInput', | |
type: ['attributeReport'], | |
convert: (model, msg, publish, options, meta) => { | |
if (hasAlreadyProcessedMessage(msg, model)) 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', 'WXKG22LM'].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', 'QBKG28LM', 'QBKG31LM', 'ZNQBKG25LM'].includes(model.model)) { | |
buttonLookup = { 41: 'left', 42: 'right', 51: 'both' }; | |
} | |
if (['QBKG25LM', 'QBKG26LM', 'QBKG29LM', 'QBKG32LM', 'QBKG34LM', 'ZNQBKG31LM', 'ZNQBKG26LM'].includes(model.model)) { | |
buttonLookup = { | |
41: 'left', 42: 'center', 43: 'right', | |
51: 'left_center', 52: 'left_right', 53: 'center_right', | |
61: 'all', | |
}; | |
} | |
if (['ZNQBKG39LM'].includes(model.model)) { | |
buttonLookup = { 1: 'top', 2: 'bottom' }; | |
} | |
if (['ZNQBKG40LM'].includes(model.model)) { | |
buttonLookup = { 1: 'top', 2: 'middle', 3: 'bottom' }; | |
} | |
if (['ZNQBKG41LM'].includes(model.model)) { | |
buttonLookup = { 1: 'top', 2: 'middle', 3: 'bottom', 4: 'wireless' }; | |
} | |
if (['WS-USC02', 'WS-USC04'].includes(model.model)) { | |
buttonLookup = { 41: 'top', 42: 'bottom', 51: 'both' }; | |
} | |
const action = actionLookup[msg.data['presentValue']]; | |
if (buttonLookup) { | |
const button = buttonLookup[msg.endpoint.ID]; | |
if (button) { | |
return { action: `${action}_${button}` }; | |
} | |
} else { | |
return { action }; | |
} | |
}, | |
} | |
}; | |
const tzLocal = { | |
lumi_switch_lock_relay_opple: { | |
key: ['lock_relay'], | |
convertSet: async (entity, key, value, meta) => { | |
await entity.write('manuSpecificLumi', { 0x0285: { value: (value ? 1 : 0), type: 0x20 } }, | |
manufacturerOptions.lumi); | |
return { state: { lock_relay: value } }; | |
}, | |
convertGet: async (entity, key, meta) => { | |
await entity.read('manuSpecificLumi', [0x0285], manufacturerOptions.lumi); | |
}, | |
}, | |
lumi_switch_click_mode: { | |
key: ['click_mode'], | |
convertSet: async (entity, key, value, meta) => { | |
await entity.write('manuSpecificLumi', | |
{ 0x0286: { value: utils.getFromLookup(value, { 'fast': 0x1, 'multi': 0x02 }), type: 0x20 } }, manufacturerOptions.lumi); | |
return { state: { click_mode: value } }; | |
}, | |
convertGet: async (entity, key, meta) => { | |
await entity.read('manuSpecificLumi', [0x0286], manufacturerOptions.lumi); | |
}, | |
}, | |
lumi_switch_power_outage_memory: { | |
key: ['power_outage_memory'], | |
convertSet: async (entity, key, value, meta) => { | |
if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); | |
if (['SP-EUC01', 'ZNCZ04LM', 'ZNCZ15LM', 'QBCZ14LM', 'QBCZ15LM', 'SSM-U01', 'SSM-U02', 'DLKZMK11LM', 'DLKZMK12LM', | |
'WS-EUK01', 'WS-EUK02', 'WS-EUK03', 'WS-EUK04', 'QBKG19LM', 'QBKG18LM', 'QBKG20LM', 'QBKG25LM', 'QBKG26LM', 'QBKG28LM', 'QBKG29LM', | |
'QBKG30LM', 'QBKG31LM', 'QBKG32LM', 'QBKG34LM', 'QBKG38LM', 'QBKG39LM', 'QBKG40LM', 'QBKG41LM', 'ZNDDMK11LM', 'ZNLDP13LM', | |
'ZNQBKG31LM', 'WS-USC02', 'WS-USC03', 'WS-USC04', 'ZNQBKG24LM', 'ZNQBKG25LM', 'JWDL001A', 'SSWQD02LM', 'SSWQD03LM', | |
'XDD11LM', 'XDD12LM', 'XDD13LM', 'ZNLDP12LM', 'ZNLDP13LM', 'ZNXDD01LM', 'WS-USC01', | |
].includes(meta.mapped.model)) { | |
await entity.write('manuSpecificLumi', { 0x0201: { value: value ? 1 : 0, type: 0x10 } }, manufacturerOptions.lumi); | |
} 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.lumi); | |
await entity.write('genBasic', { 0xFFF0: { value: payload[1], type: 0x41 } }, manufacturerOptions.lumi); | |
} else if (['ZNCZ11LM', 'ZNCZ12LM'].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.lumi); | |
} else if (['ZNQBKG38LM', 'ZNQBKG39LM', 'ZNQBKG40LM', 'ZNQBKG41LM'].includes(meta.mapped.model)) { | |
// Support existing syntax of a nested object just for the state field. Though it's quite silly IMO. | |
const targetValue = utils.isObject(value) && value.hasOwnProperty('state') ? value.state : value; | |
const lookupState = { on: 0x01, electric_appliances_on: 0x00, electric_appliances_off: 0x02, inverted: 0x03 }; | |
await entity.write('manuSpecificLumi', { 0x0517: { value: utils.getFromLookup(targetValue, lookupState), type: 0x20 } }, manufacturerOptions.lumi); | |
} else { | |
throw new Error('Not supported'); | |
} | |
return { state: { power_outage_memory: value } }; | |
}, | |
convertGet: async (entity, key, meta) => { | |
if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); | |
if (['SP-EUC01', 'ZNCZ04LM', 'ZNCZ15LM', 'QBCZ14LM', 'QBCZ15LM', 'SSM-U01', 'SSM-U02', 'DLKZMK11LM', 'DLKZMK12LM', | |
'WS-EUK01', 'WS-EUK02', 'WS-EUK03', 'WS-EUK04', 'QBKG19LM', 'QBKG18LM', 'QBKG20LM', 'QBKG25LM', 'QBKG26LM', 'QBKG28LM', 'QBKG29LM', | |
'QBKG30LM', 'QBKG31LM', 'QBKG32LM', 'QBKG34LM', 'QBKG38LM', 'QBKG39LM', 'QBKG40LM', 'QBKG41LM', 'ZNDDMK11LM', 'ZNLDP13LM', | |
'ZNQBKG31LM', 'WS-USC02', 'WS-USC03', 'WS-USC04', 'ZNQBKG24LM', 'ZNQBKG25LM', 'JWDL001A', 'SSWQD02LM', 'SSWQD03LM', | |
'XDD11LM', 'XDD12LM', 'XDD13LM', 'ZNLDP12LM', 'ZNLDP13LM', 'ZNXDD01LM', 'WS-USC01', | |
].includes(meta.mapped.model)) { | |
await entity.read('manuSpecificLumi', [0x0201]); | |
} else if (['ZNCZ02LM', 'QBCZ11LM', 'ZNCZ11LM', 'ZNCZ12LM'].includes(meta.mapped.model)) { | |
await entity.read('manuSpecificLumi', [0xFFF0]); | |
} else if (['ZNQBKG38LM', 'ZNQBKG39LM', 'ZNQBKG40LM', 'ZNQBKG41LM'].includes(meta.mapped.model)) { | |
await entity.read('manuSpecificLumi', [0x0517]); | |
} else { | |
throw new Error('Not supported'); | |
} | |
}, | |
}, | |
lumi_led_disabled_night: { | |
key: ['led_disabled_night'], | |
convertSet: async (entity, key, value, meta) => { | |
if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); | |
if (['ZNCZ04LM', 'ZNCZ12LM', 'ZNCZ15LM', 'QBCZ14LM', 'QBCZ15LM', 'QBKG19LM', 'QBKG18LM', 'QBKG20LM', 'QBKG25LM', 'QBKG26LM', | |
'QBKG28LM', 'QBKG29LM', 'QBKG30LM', 'QBKG31LM', 'QBKG32LM', 'QBKG34LM', 'DLKZMK11LM', 'SSM-U01', 'WS-EUK01', 'WS-EUK02', | |
'WS-EUK03', 'WS-EUK04', 'SP-EUC01', 'ZNQBKG24LM', 'ZNQBKG25LM', | |
'ZNQBKG38LM', 'ZNQBKG39LM', 'ZNQBKG40LM', 'ZNQBKG41LM'].includes(meta.mapped.model)) { | |
await entity.write('manuSpecificLumi', {0x0203: {value: value ? 1 : 0, type: 0x10}}, manufacturerOptions.lumi); | |
} 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.lumi); | |
} else { | |
throw new Error('Not supported'); | |
} | |
return {state: {led_disabled_night: value}}; | |
}, | |
convertGet: async (entity, key, meta) => { | |
if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`); | |
if (['ZNCZ04LM', 'ZNCZ12LM', 'ZNCZ15LM', 'QBCZ15LM', 'QBCZ14LM', 'QBKG19LM', 'QBKG18LM', 'QBKG20LM', 'QBKG25LM', 'QBKG26LM', | |
'QBKG28LM', 'QBKG29LM', 'QBKG30LM', 'QBKG31LM', 'QBKG32LM', 'QBKG34LM', 'DLKZMK11LM', 'SSM-U01', 'WS-EUK01', 'WS-EUK02', | |
'WS-EUK03', 'WS-EUK04', 'SP-EUC01', 'ZNQBKG24LM', 'ZNQBKG25LM', | |
'ZNQBKG38LM', 'ZNQBKG39LM', 'ZNQBKG40LM', 'ZNQBKG41LM'].includes(meta.mapped.model)) { | |
await entity.read('manuSpecificLumi', [0x0203], manufacturerOptions.lumi); | |
} else { | |
throw new Error('Not supported'); | |
} | |
}, | |
}, | |
}; | |
const definitions = [ | |
{ | |
zigbeeModel: ['lumi.switch.acn048'], | |
model: 'ZNQBKG38LM', | |
vendor: 'Aqara', | |
description: '(External) Smart wall switch Z1 (single rocker)', | |
fromZigbee: [fz.on_off, fzLocal.lumi_multistate_action, lumi.fromZigbee.lumi_specific, fz.lumi_power], | |
toZigbee: [tz.on_off, tz.lumi_switch_operation_mode_opple, tzLocal.lumi_switch_power_outage_memory, | |
tzLocal.lumi_led_disabled_night, tzLocal.lumi_switch_lock_relay_opple], | |
meta: { multiEndpoint: true }, | |
endpoint: (device) => { | |
return { 'button': 1 }; | |
}, | |
exposes: [ | |
e.power(), e.voltage(), e.device_temperature(), | |
e.switch().withEndpoint('button'), | |
e.enum('power_outage_memory', ea.ALL, ['on', 'electric_appliances_on', 'electric_appliances_off', 'inverted']) | |
.withDescription('Power Outage Memory').withEndpoint('button'), | |
e.led_disabled_night(), | |
e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) | |
.withDescription('Decoupled mode').withEndpoint('button'), | |
e.binary('lock_relay', ea.ALL, true, false) | |
.withDescription('Lock relay mode').withEndpoint('button'), | |
e.action(['single']), | |
], | |
onEvent: preventReset, | |
configure: async (device, coordinatorEndpoint, logger) => { | |
await device.getEndpoint(1).write('manuSpecificLumi', { 'mode': 1 }, { manufacturerCode: manufacturerCode, disableResponse: true }); | |
}, | |
extend: [lumiZigbeeOTA()], | |
}, | |
{ | |
zigbeeModel: ['lumi.switch.acn049'], | |
model: 'ZNQBKG39LM', | |
vendor: 'Aqara', | |
description: '(External) Aqara smart wall switch Z1 (double rocker)', | |
fromZigbee: [fz.on_off, fzLocal.lumi_multistate_action, lumi.fromZigbee.lumi_specific, fz.lumi_power], | |
toZigbee: [tz.on_off, tz.lumi_switch_operation_mode_opple, tzLocal.lumi_switch_power_outage_memory, | |
tzLocal.lumi_led_disabled_night, tzLocal.lumi_switch_lock_relay_opple], | |
meta: { multiEndpoint: true }, | |
endpoint: (device) => { | |
return { 'top': 1, 'bottom': 2 }; | |
}, | |
exposes: [ | |
e.power(), e.voltage(), e.device_temperature(), | |
e.switch().withEndpoint('top'), e.switch().withEndpoint('bottom'), | |
e.enum('power_outage_memory', ea.ALL, ['on', 'electric_appliances_on', 'electric_appliances_off', 'inverted']) | |
.withDescription('Power Outage Memory'), | |
e.led_disabled_night(), | |
e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) | |
.withDescription('Decoupled mode for top button').withEndpoint('top'), | |
e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) | |
.withDescription('Decoupled mode for bottom button').withEndpoint('bottom'), | |
e.binary('lock_relay', ea.ALL, true, false) | |
.withDescription('Lock relay mode for top button').withEndpoint('top'), | |
e.binary('lock_relay', ea.ALL, true, false) | |
.withDescription('Lock relay mode for bottom button').withEndpoint('bottom'), | |
e.action(['single_top', 'single_bottom']), | |
], | |
onEvent: preventReset, | |
configure: async (device, coordinatorEndpoint, logger) => { | |
await device.getEndpoint(1).write('manuSpecificLumi', { 'mode': 1 }, { manufacturerCode: 0x115f, disableResponse: true }); | |
}, | |
extend: [lumiZigbeeOTA()], | |
}, | |
{ | |
zigbeeModel: ['lumi.switch.acn054'], | |
model: 'ZNQBKG40LM', | |
vendor: 'Aqara', | |
description: '(External) Smart wall switch Z1 (triple rocker)', | |
fromZigbee: [fz.on_off, fzLocal.lumi_multistate_action, lumi.fromZigbee.lumi_specific, fz.lumi_power], | |
toZigbee: [tz.on_off, tz.lumi_switch_operation_mode_opple, tzLocal.lumi_switch_power_outage_memory, | |
tzLocal.lumi_led_disabled_night, tzLocal.lumi_switch_lock_relay_opple], | |
meta: { multiEndpoint: true }, | |
endpoint: (device) => { | |
return { 'top': 1, 'middle': 2, 'bottom': 3 }; | |
}, | |
exposes: [ | |
e.power(), e.voltage(), e.device_temperature(), | |
e.switch().withEndpoint('top'), e.switch().withEndpoint('middle'), e.switch().withEndpoint('bottom'), | |
e.enum('power_outage_memory', ea.ALL, ['on', 'electric_appliances_on', 'electric_appliances_off', 'inverted']) | |
.withDescription('Power Outage Memory'), | |
e.led_disabled_night(), | |
e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) | |
.withDescription('Decoupled mode for top button').withEndpoint('top'), | |
e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) | |
.withDescription('Decoupled mode for middle button').withEndpoint('middle'), | |
e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) | |
.withDescription('Decoupled mode for bottom button').withEndpoint('bottom'), | |
e.binary('lock_relay', ea.ALL, true, false) | |
.withDescription('Lock relay mode for top button').withEndpoint('top'), | |
e.binary('lock_relay', ea.ALL, true, false) | |
.withDescription('Lock relay mode for middle button').withEndpoint('middle'), | |
e.binary('lock_relay', ea.ALL, true, false) | |
.withDescription('Lock relay mode for bottom button').withEndpoint('bottom'), | |
e.action(['single_top', 'single_middle', 'single_bottom']), | |
], | |
onEvent: preventReset, | |
configure: async (device, coordinatorEndpoint, logger) => { | |
await device.getEndpoint(1).write('manuSpecificLumi', { 'mode': 1 }, { manufacturerCode: manufacturerCode, disableResponse: true }); | |
}, | |
extend: [lumiZigbeeOTA()], | |
}, | |
{ | |
zigbeeModel: ['lumi.switch.acn055'], | |
model: 'ZNQBKG41LM', | |
vendor: 'Aqara', | |
description: '(External) Smart wall switch Z1 (quadruple rocker)', | |
fromZigbee: [fz.on_off, fzLocal.lumi_multistate_action, lumi.fromZigbee.lumi_specific, fz.lumi_power], | |
toZigbee: [tz.on_off, tz.lumi_switch_operation_mode_opple, tzLocal.lumi_switch_power_outage_memory, | |
tzLocal.lumi_led_disabled_night, tzLocal.lumi_switch_lock_relay_opple, tzLocal.lumi_switch_click_mode], | |
meta: { multiEndpoint: true }, | |
endpoint: (device) => { | |
return { 'top': 1, 'middle': 2, 'bottom': 3, 'wireless': 4 }; | |
}, | |
exposes: [ | |
e.power(), e.voltage(), e.device_temperature(), | |
e.switch().withEndpoint('top'), e.switch().withEndpoint('middle'), e.switch().withEndpoint('bottom'), | |
e.enum('power_outage_memory', ea.ALL, ['on', 'electric_appliances_on', 'electric_appliances_off', 'inverted']) | |
.withDescription('Power Outage Memory'), | |
e.led_disabled_night(), | |
e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) | |
.withDescription('Decoupled mode for top button').withEndpoint('top'), | |
e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) | |
.withDescription('Decoupled mode for middle button').withEndpoint('middle'), | |
e.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']) | |
.withDescription('Decoupled mode for bottom button').withEndpoint('bottom'), | |
e.binary('lock_relay', ea.ALL, true, false) | |
.withDescription('Lock relay mode for top button').withEndpoint('top'), | |
e.binary('lock_relay', ea.ALL, true, false) | |
.withDescription('Lock relay mode for middle button').withEndpoint('middle'), | |
e.binary('lock_relay', ea.ALL, true, false) | |
.withDescription('Lock relay mode for bottom button').withEndpoint('bottom'), | |
e.enum('click_mode', ea.ALL, ['fast', 'multi']) | |
.withDescription('Click mode(Wireless button only), fast: only supports single click which will be send immediately after clicking.' + | |
'multi: supports more events like double and hold'), | |
e.action(['single_top', 'single_middle', 'single_bottom', | |
'hold_wireless', 'single_wireless', 'double_wireless', 'release_wireless']), | |
], | |
onEvent: preventReset, | |
configure: async (device, coordinatorEndpoint, logger) => { | |
await device.getEndpoint(1).write('manuSpecificLumi', { 'mode': 1 }, { manufacturerCode: manufacturerCode, disableResponse: true }); | |
}, | |
extend: [lumiZigbeeOTA()], | |
}, | |
]; | |
module.exports = definitions; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment