Koenkk / zigbee2mqtt

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

[New device support]: Immax Neo Smart Keypad 07505L #20273

Open nobges opened 9 months ago

nobges commented 9 months ago

What happened?

Hello,

I bought a Immax 07505L alarm keypad and I am trying to get it working. No issue adding the device to the network and getting it identified, however no actions are recognized when I press its buttons. I looked it up and found those posts: https://github.com/Koenkk/zigbee2mqtt/issues/17800 https://github.com/zigpy/zha-device-handlers/issues/1852 https://github.com/Koenkk/zigbee2mqtt/issues/11728

Which all use an external converter with varying levels of success. I tried this one (including the modifications suggested in the thread). But while the converter is properly linked to the device, it's still not working.

This is what logs indicate: Exception while calling fromZigbee converter: Cannot read properties of undefined (reading '0')} debug 2023-12-17 21:53:12 TypeError: Cannot read properties of undefined (reading '0') at Object.convert (/app/data/extension/externally-loaded.js:36:32) at Receive.onDeviceMessage (/app/lib/extension/receive.ts:150:51) at EventEmitter.emit (node:events:525:35) at EventBus.emitDeviceMessage (/app/lib/eventBus.ts:102:22) at Controller.<anonymous> (/app/lib/zigbee.ts:108:27) at Controller.emit (node:events:513:28) at Controller.selfAndDeviceEmit (/app/node_modules/zigbee-herdsman/src/controller/controller.ts:527:14) at Controller.onZclOrRawData (/app/node_modules/zigbee-herdsman/src/controller/controller.ts:738:18) at ZStackAdapter.<anonymous> (/app/node_modules/zigbee-herdsman/src/controller/controller.ts:144:70) at ZStackAdapter.emit (node:events:513:28)

"Object.convert (/app/data/extension/externally-loaded.js:36:32" refers to this line in the converter : "const dp = msg.data.dpValues[0].dp;"

But from there I don't know how to troubleshoot it. Any idea?

Thanks a lot!

What did you expect to happen?

No response

How to reproduce it (minimal and precise)

No response

Zigbee2MQTT version

1.34.0

Adapter firmware version

20221226

Adapter

SONOFF ZigBee 3.0 USB Dongle Plus TI CC2652P

Debug log

No response

besiktas97 commented 9 months ago

I am strugling with the same issue and didn't also tried the pr's without succes.

panostaja commented 7 months ago

By default, the keyboard only sends action messages after the correct pin+action_button is pressed. Default user pin = 1234, so pressing 1234(lock) should result as an action. Refer here for keypad internal commands to change its behavior: https://manuals.plus/immax/07505l-neo-smart-keypad-manual#axzz8R2Z5dWOe

martin1997nm commented 3 months ago

Updated Post! Modification: added User id for datapoint 112 Manual: https://manuals.plus/immax/07505l-neo-smart-keypad-manual#axzz8R2Z5dWOe

I had the same issue(1.35), but diving deeper into the debug log, i found out that not only the manufacturer code was not right, but the tuya datapoint were wrong too. There for i satrted thinking that the device got an updated FW in a newer production. image Zigbee Manufacturer _TZE200_moycceze So i made my custom converter for this device. image

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 ota = require('zigbee-herdsman-converters/lib/ota');
const tuya = require('zigbee-herdsman-converters/lib/tuya');
const e = exposes.presets;
const ea = exposes.access;

const toZigbeeConverters = {
    arm_delay_time: {
        key: ['arm_delay_time'],
        convertSet: async (entity, key, value, meta) => {
            const dp = 103; 
            await tuya.sendDataPointValue(entity, dp, value);
        },
        convertGet: async (entity, key, meta) => {
            const dp = 103; 
            await tuya.sendDataPointValue(entity, dp, 0);
        },
    },
    beep_sound_enabled: {
        key: ['beep_sound_enabled'],
        convertSet: async (entity, key, value, meta) => {
            const dp = 104; 
            await tuya.sendDataPointBool(entity, dp, value === 'ON');
        },
        convertGet: async (entity, key, meta) => {
            const dp = 104; 
            await tuya.sendDataPointValue(entity, dp, 0);
        },
    },
    quick_home_enabled: {
        key: ['quick_home_enabled'],
        convertSet: async (entity, key, value, meta) => {
            const dp = 105; 
            await tuya.sendDataPointBool(entity, dp, value === 'ON');
        },
        convertGet: async (entity, key, meta) => {
            const dp = 105; 
            await tuya.sendDataPointValue(entity, dp, 0);
        },
    },
    quick_disarm_enabled: {
        key: ['quick_disarm_enabled'],
        convertSet: async (entity, key, value, meta) => {
            const dp = 106; 
            await tuya.sendDataPointBool(entity, dp, value === 'ON');
        },
        convertGet: async (entity, key, meta) => {
            const dp = 106; 
            await tuya.sendDataPointValue(entity, dp, 0);
        },
    },
    quick_arm_enabled: {
        key: ['quick_arm_enabled'],
        convertSet: async (entity, key, value, meta) => {
            const dp = 107; 
            await tuya.sendDataPointBool(entity, dp, value === 'ON');
        },
        convertGet: async (entity, key, meta) => {
            const dp = 107; 
            await tuya.sendDataPointValue(entity, dp, 0);
        },
    },
    arm_delay_beep_sound: {
        key: ['arm_delay_beep_sound'],
        convertSet: async (entity, key, value, meta) => {
            const dp = 111; 
            await tuya.sendDataPointBool(entity, dp, value === 'ON');
        },
        convertGet: async (entity, key, meta) => {
            const dp = 111; 
            await tuya.sendDataPointValue(entity, dp, 0);
        },
    },
    admin_code: {
        key: ['admin_code'],
        convertSet: async (entity, key, value, meta) => {
            const dp = 108; 
            await tuya.sendDataPointValue(entity, dp, value);
        },
        convertGet: async (entity, key, meta) => {
            const dp = 108; 
            await tuya.sendDataPointValue(entity, dp, 0);
        },
    },
    last_added_user_code: {
        key: ['last_added_user_code'],
        convertSet: async (entity, key, value, meta) => {
            const dp = 109; 
            await tuya.sendDataPointValue(entity, dp, value);
        },
        convertGet: async (entity, key, meta) => {
            const dp = 109; 
            await tuya.sendDataPointValue(entity, dp, 0);
        },
    },
    user_id: {
        key: ['user_id'],
        convertSet: async (entity, key, value, meta) => {
            const dp = 112; 
            await tuya.sendDataPointValue(entity, dp, value);
        },
        convertGet: async (entity, key, meta) => {
            const dp = 112; 
            await tuya.sendDataPointValue(entity, dp, 0);
        },
    },
};

const fromZigbeeConverters = {
    tamper: {
        cluster: 'manuSpecificTuya',
        type: 'commandDataReport',
        convert: (model, msg, publish, options, meta) => {
            const dpValue = msg.data.dpValues.find(dpValue => dpValue.dp === 24);
            if (dpValue) {
                const value = dpValue.data[0]; 
                const tamper = value === 1;
                return { tamper: tamper };
            }
        },
    },
    arm_delay_time: {
        cluster: 'manuSpecificTuya',
        type: ['commandDataReport', 'commandDataResponse', 'commandSetDataResponse'],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = msg.data.dpValues.find(dpValue => dpValue.dp === 103);
            if (dpValue && dpValue.data && Array.isArray(dpValue.data) && dpValue.data.length > 0) {
                const value = dpValue.data[0];
                const clampedValue = Math.min(Math.max(value, 0), 180);
                return { arm_delay_time: clampedValue };
            }
        },
    },
    beep_sound_enabled: {
        cluster: 'manuSpecificTuya',
        type: ['commandDataReport', 'commandDataResponse', 'commandSetDataResponse'],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = msg.data.dpValues.find(dpValue => dpValue.dp === 104);
            if (dpValue) {
                return { beep_sound_enabled: dpValue.data[0] === 1 ? 'ON' : 'OFF' };
            }
        },
    },
    quick_home_enabled: {
        cluster: 'manuSpecificTuya',
        type: ['commandDataReport', 'commandDataResponse', 'commandSetDataResponse'],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = msg.data.dpValues.find(dpValue => dpValue.dp === 105);
            if (dpValue) {
                return { quick_home_enabled: dpValue.data[0] === 1 ? 'ON' : 'OFF' };
            }
        },
    },
    quick_disarm_enabled: {
        cluster: 'manuSpecificTuya',
        type: ['commandDataReport', 'commandDataResponse', 'commandSetDataResponse'],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = msg.data.dpValues.find(dpValue => dpValue.dp === 106);
            if (dpValue) {
                return { quick_disarm_enabled: dpValue.data[0] === 1 ? 'ON' : 'OFF' };
            }
        },
    },
    quick_arm_enabled: {
        cluster: 'manuSpecificTuya',
        type: ['commandDataReport', 'commandDataResponse', 'commandSetDataResponse'],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = msg.data.dpValues.find(dpValue => dpValue.dp === 107);
            if (dpValue) {
                return { quick_arm_enabled: dpValue.data[0] === 1 ? 'ON' : 'OFF' };
            }
        },
    },
    arm_delay_beep_sound: {
        cluster: 'manuSpecificTuya',
        type: ['commandDataReport', 'commandDataResponse', 'commandSetDataResponse'],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = msg.data.dpValues.find(dpValue => dpValue.dp === 111);
            if (dpValue) {
                return { arm_delay_beep_sound: dpValue.data[0] === 1 ? 'ON' : 'OFF' };
            }
        },
    },
    admin_code: {
        cluster: 'manuSpecificTuya',
        type: ['commandDataReport', 'commandDataResponse', 'commandSetDataResponse'],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = msg.data.dpValues.find(dpValue => dpValue.dp === 108);
            if (dpValue) {
                return { admin_code: dpValue.data.toString() };
            }
        },
    },
    last_added_user_code: {
        cluster: 'manuSpecificTuya',
        type: ['commandDataReport', 'commandDataResponse', 'commandSetDataResponse'],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = msg.data.dpValues.find(dpValue => dpValue.dp === 109);
            if (dpValue) {
                return { last_added_user_code: dpValue.data.toString() };
            }
        },
    },
    user_id: {
        cluster: 'manuSpecificTuya',
        type: ['commandDataReport', 'commandDataResponse', 'commandSetDataResponse'],
        convert: (model, msg, publish, options, meta) => {
            const dpValue = msg.data.dpValues.find(dpValue => dpValue.dp === 112);
            if (dpValue) {
                const value = dpValue.data[0];
                const clampedValue = Math.min(Math.max(value, 0), 9);
                return { user_id: clampedValue.toString() };
            }
        },
    },
};

const definition = {
    fingerprint: tuya.fingerprint('TS0601', ['_TZE200_moycceze']),
    model: '07505L',
    vendor: 'Immax',
    description: 'Neo smart keypad',
    fromZigbee: [
        tuya.fz.datapoints,
        fromZigbeeConverters.tamper,
        fromZigbeeConverters.arm_delay_time,
        fromZigbeeConverters.beep_sound_enabled,
        fromZigbeeConverters.quick_home_enabled,
        fromZigbeeConverters.quick_disarm_enabled,
        fromZigbeeConverters.quick_arm_enabled,
        fromZigbeeConverters.arm_delay_beep_sound,
        fromZigbeeConverters.admin_code,
        fromZigbeeConverters.last_added_user_code,
        fromZigbeeConverters.user_id,
    ],
    toZigbee: [
        toZigbeeConverters.arm_delay_time,
        toZigbeeConverters.beep_sound_enabled,
        toZigbeeConverters.quick_home_enabled,
        toZigbeeConverters.quick_disarm_enabled,
        toZigbeeConverters.quick_arm_enabled,
        toZigbeeConverters.arm_delay_beep_sound,
        toZigbeeConverters.admin_code,
        toZigbeeConverters.last_added_user_code,
        toZigbeeConverters.user_id,
    ],
    exposes: [
        e.action(['disarm', 'arm_home', 'arm_away', 'sos']),
        e.battery(),
        e.tamper(),
        exposes.text('admin_code', ea.STATE_SET)
            .withDescription('Admin code')
            .withAccess(ea.STATE),
        exposes.text('last_added_user_code', ea.STATE_SET)
            .withDescription('Last Added User code')
            .withAccess(ea.STATE),
        exposes.numeric('arm_delay_time', ea.STATE_SET)
            .withValueMin(0).withValueMax(180)
            .withDescription('Arm Delay Time'),
        exposes.binary('beep_sound_enabled', ea.STATE_SET, 'ON', 'OFF')
            .withDescription('Beep Sound Enabled'),
        exposes.binary('quick_home_enabled', ea.STATE_SET, 'ON', 'OFF')
            .withDescription('Quick Home Enabled'),
        exposes.binary('quick_disarm_enabled', ea.STATE_SET, 'ON', 'OFF')
            .withDescription('Quick Disarm Enabled'),
        exposes.binary('quick_arm_enabled', ea.STATE_SET, 'ON', 'OFF')
            .withDescription('Quick Arm Enabled'),
        exposes.binary('arm_delay_beep_sound', ea.STATE_SET, 'ON', 'OFF')
            .withDescription('Arm Delay Beep Sound'),
        exposes.text('user_id', ea.STATE)
            .withDescription('User ID'),
    ],
    meta: {
        tuyaDatapoints: [
            [3, 'battery', tuya.valueConverter.raw],
            [24, 'tamper', tuya.valueConverter.raw],
            [26, 'action', tuya.valueConverter.static('disarm')],
            [27, 'action', tuya.valueConverter.static('arm_away')],
            [28, 'action', tuya.valueConverter.static('arm_home')],
            [29, 'action', tuya.valueConverter.static('sos')],
            [108, 'admin_code', tuya.valueConverter.raw],
            [109, 'last_added_user_code', tuya.valueConverter.raw],
            [103, 'arm_delay_time', tuya.valueConverter.raw],
            [104, 'beep_sound_enabled', tuya.valueConverter.trueFalse],
            [105, 'quick_home_enabled', tuya.valueConverter.trueFalse],
            [106, 'quick_disarm_enabled', tuya.valueConverter.trueFalse],
            [107, 'quick_arm_enabled', tuya.valueConverter.trueFalse],
            [111, 'arm_delay_beep_sound', tuya.valueConverter.trueFalse],
            [112, 'user_id', tuya.valueConverter.raw],
        ],
    },
};

module.exports = definition;

Have fun whit it!

NikolamViser commented 3 months ago

@martin1997nm Thank you for the code. I have managed to get it working finally. Just one thing, with the latest Z2M version I had to remove the line: const extend = require( 'zigbee-herdsman-converters/lib/extend'); Converter wouldn't load with it.

Koenkk commented 3 months ago

Could you make a PR to add out of the box support for this device?

TechsDomadoo commented 1 month ago

Hello @martin1997nm

Thank you for the code. Please could you make a PR as this keypad is still not supported directly by Z2M ?

NikolamViser commented 1 month ago

Hi @martin1997nm,

Do you maybe know if there is a possibility for this keyboard to be given a mqtt command to beep for countdown. For example I can enable countdown when arming, but it would be good to have it beep when it is triggered and waiting for disarm code. I don't use it with their proprietary hub so I don't know if it has this option at all, and the user manual is very short.

Regards,

Nikola

martin1997nm commented 1 month ago

Hi! It does not have that funcionality in the firmware!

xmatr1x69 commented 3 weeks ago

Hi @martin1997nm in the action for the keypad when arming/disarming/any action there is no info "action": "", I`ve got "user_id": "0". Any idea what I can do?

logs:

[2024-09-06 15:36:34] info:     z2m:mqtt: MQTT publish: topic 'zigbee2mqtt/Alarm keypad', payload '{"action":"arm_away", . . . .}'

[2024-09-06 15:36:34] info:     z2m:mqtt: MQTT publish: topic 'zigbee2mqtt/Alarm keypad', payload '{"action":"", . . . .}'

[2024-09-06 15:36:34] info:     z2m:mqtt: MQTT publish: topic 'zigbee2mqtt/Alarm keypad/action', payload 'arm_away'

on arming there are 3 messages, 1 of them is empty. In HA I can see cahnging it for arm_way in the logs, but instantly it is back to "" (empty)