Skip to content

[External Converter]: CS-T9C-A0-BG from EZVIZ #32203

@hackade-source

Description

@hackade-source

Link

https://www.ezviz.com/product/t9c/49573

Database entry

{"id":21,"type":"EndDevice","ieeeAddr":"0xa4c138fa4d24ac42","nwkAddr":61436,"manufId":4451,"manufName":"EZVIZ","powerSource":"Battery","modelId":"CS-T9C-A0-BG","epList":[1],"endpoints":{"1":{"profId":260,"epId":1,"devId":1027,"inClusterList":[0,3,1,1280,32,1796,2821,1282,10,65024,65216],"outClusterList":[25,1796],"clusters":{"65024":{"attributes":{"1":{"type":"Buffer","data":[81,52,53,50,51,52,51,53,52,70,78,76,90,82,72]}}},"65216":{"attributes":{"61472":0,"61473":0,"61474":1,"62464":0,"62465":0,"62467":1,"62468":0}},"genBasic":{"attributes":{"appVersion":0,"hwVersion":0}},"ssIasZone":{"attributes":{"iasCieAddr":"0x00124b0039de0df7","zoneState":0}},"genPowerCfg":{"attributes":{"batteryPercentageRemaining":200}},"ssIasWd":{"attributes":{"maxDuration":5}},"haDiagnostic":{"attributes":{"lastMessageRssi":52}}},"binds":[],"configuredReportings":[],"meta":{}}},"appVersion":0,"stackVersion":2,"hwVersion":0,"dateCode":"20230708","zclVersion":3,"interviewCompleted":true,"interviewState":"SUCCESSFUL","meta":{},"lastSeen":1780603943816}

Zigbee2MQTT version

2.10.1 (unknown)

External converter

import fz from 'zigbee-herdsman-converters/converters/fromZigbee';
import tz from 'zigbee-herdsman-converters/converters/toZigbee';
import * as exposes from 'zigbee-herdsman-converters/lib/exposes';

const e = exposes.presets;
const ea = exposes.access;

const fzLocal = {
    ezviz_siren_status: {
        cluster: '65216', 
        type: ['attributeReport', 'readResponse'],
        convert: (model, msg, publish, options, meta) => {
            const result = {};
            
            if (msg.data[0xf021] !== undefined) {
                const vol = msg.data[0xf021];
                if (vol === 0) result.volume = 'mute';
                else if (vol <= 25) result.volume = 'low';
                else if (vol <= 50) result.volume = 'high';
                else result.volume = 'max';
            }
            
            if (msg.data[0xf400] !== undefined) result.motion_alarm = msg.data[0xf400] === 1 ? 'ON' : 'OFF';
            if (msg.data[0xf401] !== undefined) result.tamper_alarm = msg.data[0xf401] === 1 ? 'ON' : 'OFF';
            if (msg.data[0xf403] !== undefined) result.light_indicator = msg.data[0xf403] === 1 ? 'ON' : 'OFF';
            if (msg.data[0xf404] !== undefined) result.arm_beep = msg.data[0xf404] === 1 ? 'ON' : 'OFF';

            return Object.keys(result).length > 0 ? result : null;
        }
    }
};

const tzLocal = {
    ezviz_siren_settings: {
        key: ['volume', 'motion_alarm', 'tamper_alarm', 'light_indicator', 'arm_beep', 'alarm_duration', 'alarm'],
        
        convertSet: async (entity, key, value, meta) => {
            const payloads = {};

            if (key === 'alarm_duration') {
                await tz.warning.convertSet(entity, 'warning', {
                    mode: 'stop',
                    duration: value,
                    strobe: false
                }, meta);
                return { state: { alarm_duration: value } };
            }

            if (key === 'alarm') {
                if (value === 'ON') {
                    const duration = meta.state.alarm_duration !== undefined ? meta.state.alarm_duration : 60;
                    await tz.warning.convertSet(entity, 'warning', {
                        mode: 'burglar',
                        duration: duration,
                        strobe: false
                    }, meta);
                } else {
                    await entity.write(0xfec0, { 0xf002: { value: 0, type: 0x20 } });
                    await tz.warning.convertSet(entity, 'warning', {
                        mode: 'stop',
                        duration: 0,
                        strobe: false
                    }, meta);
                }
                return { state: { alarm: value } };
            }
            
            if (key === 'volume') {
                let vol = 0;
                if (value === 'low') vol = 25;
                if (value === 'high') vol = 50;
                if (value === 'max') vol = 100;
                payloads[0xf021] = { value: vol, type: 0x20 };
            }
            
            if (key === 'motion_alarm') payloads[0xf400] = { value: value === 'ON' ? 1 : 0, type: 0x20 };
            if (key === 'tamper_alarm') payloads[0xf401] = { value: value === 'ON' ? 1 : 0, type: 0x20 };
            if (key === 'light_indicator') payloads[0xf403] = { value: value === 'ON' ? 1 : 0, type: 0x20 };
            if (key === 'arm_beep') payloads[0xf404] = { value: value === 'ON' ? 1 : 0, type: 0x20 };

            if (Object.keys(payloads).length > 0) {
                for (const [attr, data] of Object.entries(payloads)) {
                    await entity.write(0xfec0, { [attr]: data });
                }
            }

            return { state: { [key]: value } };
        },

        convertGet: async (entity, key, meta) => {
            const attributesToRead = {
                'volume': 0xf021,
                'motion_alarm': 0xf400,
                'tamper_alarm': 0xf401,
                'light_indicator': 0xf403,
                'arm_beep': 0xf404
            };
            
            if (attributesToRead[key] !== undefined) {
                await entity.read(0xfec0, [attributesToRead[key]]);
            }
        }
    }
};

export default {
    zigbeeModel: ['CS-T9C-A0-BG'], 
    model: 'CS-T9C-A0-BG',
    vendor: 'EZVIZ',
    description: 'T9C Smart Siren',
    fromZigbee: [fzLocal.ezviz_siren_status], 
    toZigbee: [tzLocal.ezviz_siren_settings],
    exposes: [
        e.binary('alarm', ea.STATE_SET, 'ON', 'OFF').withDescription('Trigger the siren (Burglar)'),
        e.numeric('alarm_duration', ea.STATE_SET).withUnit('s').withDescription('Default alarm duration').withValueMin(5).withValueMax(180),
        e.enum('volume', ea.STATE_SET, ['mute', 'low', 'high', 'max']).withDescription('Siren volume'),
        e.binary('motion_alarm', ea.STATE_SET, 'ON', 'OFF').withDescription('Motion alarm (0xf400)'),
        e.binary('tamper_alarm', ea.STATE_SET, 'ON', 'OFF').withDescription('Tamper alarm (0xf401)'),
        e.binary('light_indicator', ea.STATE_SET, 'ON', 'OFF').withDescription('Light indicator (0xf403)'),
        e.binary('arm_beep', ea.STATE_SET, 'ON', 'OFF').withDescription('Arm/disarm beep (0xf404)')
    ],
};

What does/doesn't work with the external definition?

All features works

Notes

All device features were successfully reverse-engineered through Zigbee packet sniffing, using the original EZVIZ app and A3 gateway as a reference. Logic and payloads were manually reverse-engineered; code structure was generated with LLM assistance.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions