Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New device support]: Sunricher SR-ZG2833PAC-C4 #23194

Open
matteohexagon opened this issue Jun 28, 2024 · 0 comments
Open

[New device support]: Sunricher SR-ZG2833PAC-C4 #23194

matteohexagon opened this issue Jun 28, 2024 · 0 comments
Labels
new device support New device support request

Comments

@matteohexagon
Copy link

matteohexagon commented Jun 28, 2024

Link

https://www.sunricher.com/zigbee-series/zigbee-push-button-coupler.html
https://www.sunricher.com/media/resources/manual/SR-ZG2833PAC-C4%20Instruction.pdf

Database entry

{ "id": 13, "type": "Router", "ieeeAddr": "0xf0d1b81e2400038b", "nwkAddr": 57721, "manufId": 4489, "manufName": "LEDVANCE", "powerSource": "Mains (single phase)", "modelId": "VIVARES_PBC4_01", "epList": [ 1, 2, 3, 4, 5, 6, 232, 242 ], "endpoints": { "1": { "profId": 260, "epId": 1, "devId": 1, "inClusterList": [ 0, 3, 4096 ], "outClusterList": [ 3, 4, 5, 6, 8, 4096 ], "clusters": { "genBasic": { "attributes": { "modelId": "VIVARES_PBC4_01", "manufacturerName": "LEDVANCE", "powerSource": 1, "zclVersion": 3, "appVersion": 0, "stackVersion": 0, "hwVersion": 0, "dateCode": "NULL", "swBuildId": "10102503", "appProfileVersion": 255 } } }, "binds": [ { "cluster": 0, "groupID": 2, "type": "group" }, { "cluster": 8, "groupID": 2, "type": "group" }, { "cluster": 4, "groupID": 2, "type": "group" }, { "cluster": 6, "groupID": 2, "type": "group" } ], "configuredReportings": [], "meta": {} }, "2": { "profId": 260, "epId": 2, "devId": 1, "inClusterList": [ 0, 3, 4096 ], "outClusterList": [ 3, 4, 5, 6, 8, 4096 ], "clusters": { "genBasic": { "attributes": { "appVersion": 0, "stackVersion": 0, "hwVersion": 0, "manufacturerName": "LEDVANCE", "modelId": "VIVARES_PBC4_01", "dateCode": "NULL", "physicalEnv": 0, "zclVersion": 3 } } }, "binds": [ { "cluster": 6, "type": "endpoint", "deviceIeeeAddress": "0x00124b00258cbc3a", "endpointID": 1 }, { "cluster": 8, "type": "endpoint", "deviceIeeeAddress": "0x00124b00258cbc3a", "endpointID": 1 }, { "cluster": 0, "groupID": 4, "type": "group" }, { "cluster": 6, "groupID": 4, "type": "group" }, { "cluster": 8, "groupID": 4, "type": "group" } ], "configuredReportings": [], "meta": {} }, "3": { "profId": 260, "epId": 3, "devId": 1, "inClusterList": [ 0, 3, 4096 ], "outClusterList": [ 3, 4, 5, 6, 8, 4096 ], "clusters": { "genBasic": { "attributes": { "appVersion": 0, "stackVersion": 0, "hwVersion": 0 } }, "genIdentify": { "attributes": { "identifyTime": 0 } } }, "binds": [ { "cluster": 6, "type": "endpoint", "deviceIeeeAddress": "0x00124b00258cbc3a", "endpointID": 1 }, { "cluster": 8, "type": "endpoint", "deviceIeeeAddress": "0x00124b00258cbc3a", "endpointID": 1 } ], "configuredReportings": [], "meta": {} }, "4": { "profId": 260, "epId": 4, "devId": 1, "inClusterList": [ 0, 3, 4096 ], "outClusterList": [ 3, 4, 5, 6, 8, 4096 ], "clusters": { "genBasic": { "attributes": { "appVersion": 0, "stackVersion": 0, "hwVersion": 0 } } }, "binds": [ { "cluster": 6, "type": "endpoint", "deviceIeeeAddress": "0x00124b00258cbc3a", "endpointID": 1 }, { "cluster": 8, "type": "endpoint", "deviceIeeeAddress": "0x00124b00258cbc3a", "endpointID": 1 } ], "configuredReportings": [], "meta": {} }, "5": { "profId": 260, "epId": 5, "devId": 515, "inClusterList": [ 0, 3, 4096 ], "outClusterList": [ 3, 4, 5, 258, 4096 ], "clusters": { "genBasic": { "attributes": { "appVersion": 0, "stackVersion": 0, "hwVersion": 0 } } }, "binds": [ { "cluster": 258, "type": "endpoint", "deviceIeeeAddress": "0x00124b00258cbc3a", "endpointID": 1 } ], "configuredReportings": [], "meta": {} }, "6": { "profId": 260, "epId": 6, "devId": 515, "inClusterList": [ 0, 3, 4096 ], "outClusterList": [ 3, 4, 5, 258, 4096 ], "clusters": { "genBasic": { "attributes": { "appVersion": 0, "stackVersion": 0, "hwVersion": 0 } } }, "binds": [ { "cluster": 258, "type": "endpoint", "deviceIeeeAddress": "0x00124b00258cbc3a", "endpointID": 1 } ], "configuredReportings": [], "meta": {} }, "232": { "profId": 260, "epId": 232, "devId": 1287, "inClusterList": [ 2821, 64512 ], "outClusterList": [ 25 ], "clusters": { "haDiagnostic": { "attributes": {} } }, "binds": [], "configuredReportings": [], "meta": {} }, "242": { "profId": 41440, "epId": 242, "devId": 102, "inClusterList": [ 33 ], "outClusterList": [ 33 ], "clusters": {}, "binds": [], "configuredReportings": [], "meta": {} } }, "appVersion": 0, "stackVersion": 0, "hwVersion": 0, "dateCode": "NULL", "swBuildId": "10102503", "zclVersion": 3, "interviewCompleted": true, "meta": { "configured": 332242049 }, "lastSeen": 1719574880485 }

Comments

Unfortunately they discontinued the product last week...
and all the device there now are different than this one..

this device is a copy of the ubisys c4 module in almost everything (in their page it was even listed as Ubisys compatible)

Ubisys C4 zigbee2mqtt

I try to follow the instruction for supporting the new device, i got the device working and it control the group but the exposes tab remain empty.

I tried to copy the device in the ubisys.ts file here
github ubisys c4 file
can someone help me? what am i doing wrong?

SR-ZG2833PAC-C4 Instruction.pdf

ubisys-c4-technical-reference.pdf

External definition

import * as legacy from '../lib/legacy';
import * as exposes from '../lib/exposes';
import { Fz ,Tz } from '../lib/types';
import * as ota from '../lib/ota';

const {deviceEndpoints, identify, commandsOnOff, commandsLevelCtrl, commandsWindowCovering} = require('zigbee-herdsman-converters/lib/modernExtend');

const e = exposes.presets;

const ledvance = {
    fz: {
        configure_device_setup: {
            cluster: 'manuSpecificUbisysDeviceSetup',
            type: ['attributeReport', 'readResponse'],
            convert: (model, msg, publish, options, meta) => {
                const result = (meta.state.hasOwnProperty('configure_device_setup') ? meta.state.configure_device_setup : {}) as KeyValue;
                if (msg.data['inputConfigurations'] != null) {
                    result['input_configurations'] = msg.data['inputConfigurations'];
                }
                if (msg.data['inputActions'] != null) {
                    result['input_actions'] = msg.data['inputActions'].map(function(el: KeyValue) {
                        return Object.values(el);
                    });
                }
                return {configure_device_setup: result};
            },
        } satisfies Fz.Converter,
    },
    tz: {
        configure_device_setup: {
            key: ['configure_device_setup'],
            convertSet: async (entity, key, value: KeyValueAny, meta) => {
                const devMgmtEp = meta.device.getEndpoint(232);
                const cluster = Zcl.Utils.getCluster('manuSpecificUbisysDeviceSetup', null, meta.device.customClusters);
                const attributeInputConfigurations = cluster.getAttribute('inputConfigurations');
                const attributeInputActions = cluster.getAttribute('inputActions');

                // ubisys switched to writeStructure a while ago, change log only goes back to 1.9.x
                // and it happened before that but to be safe we will only use writeStrucutre on 1.9.0 and up
                let useWriteStruct = false;
                if (meta.device.softwareBuildID != undefined) {
                    useWriteStruct = semver.gte(meta.device.softwareBuildID, '1.9.0', true);
                }
                if (useWriteStruct) {
                    logger.debug(`ubisys: using writeStructure for '${meta.options.friendly_name}'.`, NS);
                }

                if (value.hasOwnProperty('input_configurations')) {
                    // example: [0, 0, 0, 0]
                    if (useWriteStruct) {
                        await devMgmtEp.writeStructured(
                            'manuSpecificUbisysDeviceSetup',
                            [{
                                attrId: attributeInputConfigurations.ID,
                                selector: {},
                                dataType: Zcl.DataType.ARRAY,
                                elementData: {
                                    elementType: Zcl.DataType.DATA8,
                                    elements: value.input_configurations,
                                },
                            }],
                            manufacturerOptions.ubisysNull,
                        );
                    } else {
                        await devMgmtEp.write(
                            'manuSpecificUbisysDeviceSetup',
                            {[attributeInputConfigurations.name]: {elementType: Zcl.DataType.DATA8, elements: value.input_configurations}},
                            manufacturerOptions.ubisysNull,
                        );
                    }
                }

                if (value.hasOwnProperty('input_actions')) {
                    // example (default for C4): [[0,13,1,6,0,2], [1,13,2,6,0,2], [2,13,3,6,0,2], [3,13,4,6,0,2]]
                    if (useWriteStruct) {
                        await devMgmtEp.writeStructured(
                            'manuSpecificUbisysDeviceSetup',
                            [{
                                attrId: attributeInputActions.ID,
                                selector: {},
                                dataType: Zcl.DataType.ARRAY,
                                elementData: {
                                    elementType: Zcl.DataType.OCTET_STR,
                                    elements: value.input_actions,
                                },
                            }],
                            manufacturerOptions.ubisysNull,
                        );
                    } else {
                        await devMgmtEp.write(
                            'manuSpecificUbisysDeviceSetup',
                            {[attributeInputActions.name]: {elementType: Zcl.DataType.OCTET_STR, elements: value.input_actions}},
                            manufacturerOptions.ubisysNull,
                        );
                    }
                }

                if (value.hasOwnProperty('input_action_templates')) {
                    const templateTypes = {
                        // source: "ZigBee Device Physical Input Configurations Integrator’s Guide"
                        // (can be obtained directly from ubisys upon request)
                        'toggle': {
                            getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [
                                [input, 0x0D, endpoint, 0x06, 0x00, 0x02],
                            ],
                        },
                        'toggle_switch': {
                            getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [
                                [input, 0x0D, endpoint, 0x06, 0x00, 0x02],
                                [input, 0x03, endpoint, 0x06, 0x00, 0x02],
                            ],
                        },
                        'on_off_switch': {
                            getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [
                                [input, 0x0D, endpoint, 0x06, 0x00, 0x01],
                                [input, 0x03, endpoint, 0x06, 0x00, 0x00],
                            ],
                        },
                        'on': {
                            getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [
                                [input, 0x0D, endpoint, 0x06, 0x00, 0x01],
                            ],
                        },
                        'off': {
                            getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [
                                [input, 0x0D, endpoint, 0x06, 0x00, 0x00],
                            ],
                        },
                        'dimmer_single': {
                            getInputActions: (input: unknown, endpoint: Zh.Endpoint, template: KeyValue) => {
                                const moveUpCmd = template.no_onoff || template.no_onoff_up ? 0x01 : 0x05;
                                const moveDownCmd = template.no_onoff || template.no_onoff_down ? 0x01 : 0x05;
                                const moveRate = template.rate || 50;
                                return [
                                    [input, 0x07, endpoint, 0x06, 0x00, 0x02],
                                    [input, 0x86, endpoint, 0x08, 0x00, moveUpCmd, 0x00, moveRate],
                                    [input, 0xC6, endpoint, 0x08, 0x00, moveDownCmd, 0x01, moveRate],
                                    [input, 0x0B, endpoint, 0x08, 0x00, 0x03],
                                ];
                            },
                        },
                        'dimmer_double': {
                            doubleInputs: true,
                            getInputActions: (inputs: unknown[], endpoint: Zh.Endpoint, template: KeyValue) => {
                                const moveUpCmd = template.no_onoff || template.no_onoff_up ? 0x01 : 0x05;
                                const moveDownCmd = template.no_onoff || template.no_onoff_down ? 0x01 : 0x05;
                                const moveRate = template.rate || 50;
                                return [
                                    [inputs[0], 0x07, endpoint, 0x06, 0x00, 0x01],
                                    [inputs[0], 0x06, endpoint, 0x08, 0x00, moveUpCmd, 0x00, moveRate],
                                    [inputs[0], 0x0B, endpoint, 0x08, 0x00, 0x03],
                                    [inputs[1], 0x07, endpoint, 0x06, 0x00, 0x00],
                                    [inputs[1], 0x06, endpoint, 0x08, 0x00, moveDownCmd, 0x01, moveRate],
                                    [inputs[1], 0x0B, endpoint, 0x08, 0x00, 0x03],
                                ];
                            },
                        },
                        'cover': {
                            cover: true,
                            doubleInputs: true,
                            getInputActions: (inputs: unknown[], endpoint: Zh.Endpoint) => [
                                [inputs[0], 0x0D, endpoint, 0x02, 0x01, 0x00],
                                [inputs[0], 0x07, endpoint, 0x02, 0x01, 0x02],
                                [inputs[1], 0x0D, endpoint, 0x02, 0x01, 0x01],
                                [inputs[1], 0x07, endpoint, 0x02, 0x01, 0x02],
                            ],
                        },
                        'cover_switch': {
                            cover: true,
                            doubleInputs: true,
                            getInputActions: (inputs: unknown[], endpoint: Zh.Endpoint) => [
                                [inputs[0], 0x0D, endpoint, 0x02, 0x01, 0x00],
                                [inputs[0], 0x03, endpoint, 0x02, 0x01, 0x02],
                                [inputs[1], 0x0D, endpoint, 0x02, 0x01, 0x01],
                                [inputs[1], 0x03, endpoint, 0x02, 0x01, 0x02],
                            ],
                        },
                        'cover_up': {
                            cover: true,
                            getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [
                                [input, 0x0D, endpoint, 0x02, 0x01, 0x00],
                            ],
                        },
                        'cover_down': {
                            cover: true,
                            getInputActions: (input: unknown, endpoint: Zh.Endpoint) => [
                                [input, 0x0D, endpoint, 0x02, 0x01, 0x01],
                            ],
                        },
                        'scene': {
                            scene: true,
                            getInputActions: (input: unknown, endpoint: Zh.Endpoint, groupId: number, sceneId: number) => [
                                [input, 0x07, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
                            ],
                            getInputActions2: (input: unknown, endpoint: Zh.Endpoint, groupId: number, sceneId: number) => [
                                [input, 0x06, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
                            ],
                        },
                        'scene_switch': {
                            scene: true,
                            getInputActions: (input: unknown, endpoint: Zh.Endpoint, groupId: number, sceneId: number) => [
                                [input, 0x0D, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
                            ],
                            getInputActions2: (input: unknown, endpoint: Zh.Endpoint, groupId: number, sceneId: number) => [
                                [input, 0x03, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
                            ],
                        },
                    };

                    // first input
                    let input = 0;
                    // first client endpoint - depends on actual device
                    if (Array.isArray(meta.mapped)) throw new Error(`Not supported for groups`);
                    let endpoint = {'S1': 2, 'S2': 3, 'D1': 2, 'J1': 2, 'C4': 1}[meta.mapped.model];
                    // default group id
                    let groupId = 0;

                    const templates = Array.isArray(value.input_action_templates) ? value.input_action_templates :
                        [value.input_action_templates];
                    let resultingInputActions: unknown[] = [];
                    for (const template of templates) {
                        // @ts-expect-error
                        const templateType = templateTypes[template.type];
                        if (!templateType) {
                            throw new Error(`input_action_templates: Template type '${template.type}' is not valid ` +
                                `(valid types: ${Object.keys(templateTypes)})`);
                        }

                        if (template.hasOwnProperty('input')) {
                            input = template.input;
                        }
                        if (template.hasOwnProperty('endpoint')) {
                            endpoint = template.endpoint;
                        }
                        // C4 cover endpoints only start at 5
                        if (templateType.cover && meta.mapped.model === 'C4' && endpoint < 5) {
                            endpoint += 4;
                        }

                        let inputActions;
                        if (!templateType.doubleInputs) {
                            if (!templateType.scene) {
                                // single input, no scene(s)
                                inputActions = templateType.getInputActions(input, endpoint, template);
                            } else {
                                // scene(s) (always single input)
                                if (!template.hasOwnProperty('scene_id')) {
                                    throw new Error(`input_action_templates: Need an attribute 'scene_id' for '${template.type}'`);
                                }
                                if (template.hasOwnProperty('group_id')) {
                                    groupId = template.group_id;
                                }
                                inputActions = templateType.getInputActions(input, endpoint, groupId, template.scene_id);

                                if (template.hasOwnProperty('scene_id_2')) {
                                    if (template.hasOwnProperty('group_id_2')) {
                                        groupId = template.group_id_2;
                                    }
                                    inputActions = inputActions.concat(templateType.getInputActions2(input, endpoint, groupId,
                                        template.scene_id_2));
                                }
                            }
                        } else {
                            // double inputs
                            input = template.hasOwnProperty('inputs') ? template.inputs : [input, input + 1];
                            inputActions = templateType.getInputActions(input, endpoint, template);
                        }
                        resultingInputActions = resultingInputActions.concat(inputActions);

                        logger.warning(`ubisys: Using input(s) ${input} and endpoint ${endpoint} for '${template.type}'.`, NS);
                        // input might by now be an array (in case of double inputs)
                        input = (Array.isArray(input) ? Math.max(...input) : input) + 1;
                        endpoint += 1;
                    }

                    logger.debug(`ubisys: input_actions to be sent to '${meta.options.friendly_name}': ` +
                        JSON.stringify(resultingInputActions), NS);
                    if (useWriteStruct) {
                        await devMgmtEp.writeStructured(
                            'manuSpecificUbisysDeviceSetup',
                            [{
                                attrId: attributeInputActions.ID,
                                selector: {},
                                dataType: Zcl.DataType.ARRAY,
                                elementData: {
                                    elementType: Zcl.DataType.OCTET_STR,
                                    elements: resultingInputActions,
                                },
                            }],
                            manufacturerOptions.ubisysNull,
                        );
                    } else {
                        await devMgmtEp.write(
                            'manuSpecificUbisysDeviceSetup',
                            {[attributeInputActions.name]: {elementType: Zcl.DataType.OCTET_STR, elements: resultingInputActions}},
                            manufacturerOptions.ubisysNull,
                        );
                    }
                }

                // re-read effective settings and dump them to the log
                await ubisys.tz.configure_device_setup.convertGet(entity, key, meta);
            },

            convertGet: async (entity, key, meta) => {
                const devMgmtEp = meta.device.getEndpoint(232);
                await devMgmtEp.read('manuSpecificUbisysDeviceSetup', ['inputConfigurations'],
                    manufacturerOptions.ubisysNull);
                await devMgmtEp.read('manuSpecificUbisysDeviceSetup', ['inputActions'],
                    manufacturerOptions.ubisysNull);
            },
        } satisfies Tz.Converter,
    },
};


const definition = {
    zigbeeModel: ['VIVARES_PBC4_01'],
    model: 'VIVARES_PBC4_01',
    vendor: 'LEDVANCE',
    description: 'Control unit C4 from Sunricher (Discontinued) Compatible/clone of Ubisys C4',
    fromZigbee: [legacy.fz.ubisys_c4_scenes, legacy.fz.ubisys_c4_onoff, legacy.fz.ubisys_c4_level, legacy.fz.ubisys_c4_cover,
        ledvance.fz.configure_device_setup],
    toZigbee: [ledvance.tz.configure_device_setup],
    exposes: [
        e.action([
            'toggle_s1', 'toggle_s2', 'toggle_s3', 'toggle_s4', 'on_s1', 'on_s2', 'on_s3', 'on_s4',
            'off_s1', 'off_s2', 'off_s3', 'off_s4', 'recall_*_s1', 'recal_*_s2', 'recall_*_s3', 'recal_*_s4',
            'brightness_move_up_s1', 'brightness_move_up_s2', 'brightness_move_up_s3', 'brightness_move_up_s4',
            'brightness_move_down_s1', 'brightness_move_down_s2', 'brightness_move_down_s3', 'brightness_move_down_s4',
            'brightness_stop_s1', 'brightness_stop_s2', 'brightness_stop_s3', 'brightness_stop_s4',
            'cover_open_s5', 'cover_close_s5', 'cover_stop_s5',
            'cover_open_s6', 'cover_close_s6', 'cover_stop_s6',
        ]),
    ],
    extend: [
        deviceEndpoints(
            {"endpoints":{"1":1,"2":2,"3":3,"4":4,"5":5,"6":6,"232":232}}),
             identify(), 
             commandsOnOff({"endpointNames":["1","2","3","4"]}), 
             commandsLevelCtrl({"endpointNames":["1","2","3","4"]}), 
             commandsWindowCovering({"endpointNames":["5","6"]})
    ],
    configure: async (device, coordinatorEndpoint) => {
        for (const ep of [1, 2, 3, 4]) {
            await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ['genScenes', 'genOnOff', 'genLevelCtrl']);
        }
        for (const ep of [5, 6]) {
            await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ['genScenes', 'closuresWindowCovering']);
        }
    },
    meta: {"multiEndpoint":true},
    endpoint: (device) => {
        return {'s1': 1, 's2': 2, 's3': 3, 's4': 4, 's5': 5, 's6': 6};
    },
    ota: ota.ledvance,


};


module.exports = definition;
@matteohexagon matteohexagon added the new device support New device support request label Jun 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new device support New device support request
Projects
None yet
Development

No branches or pull requests

1 participant