Koenkk / zigbee2mqtt

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

[New device support]: Hue Tap Dial RDM002 9290035001 #12927

Closed xammmue closed 2 years ago

xammmue commented 2 years ago

Link

https://www.philips-hue.com/en-gb/p/hue-tap-switch/8719514440999

Database entry

{"id":45,"type":"EndDevice","ieeeAddr":"0x001788010d13248f","nwkAddr":8963,"manufId":4107,"manufName":"Signify Netherlands B.V.","powerSource":"Battery","modelId":"RDM002","epList":[1],"endpoints":{"1":{"profId":260,"epId":1,"devId":2096,"inClusterList":[0,1,3,64512,4096],"outClusterList":[25,0,3,4,6,8,5,4096],"clusters":{"genPowerCfg":{"attributes":{"61441":2667,"61442":2603,"61443":2714,"batteryPercentageRemaining":122}}},"binds":[{"cluster":6,"type":"endpoint","deviceIeeeAddress":"0x00212effff07ab97","endpointID":1},{"cluster":8,"type":"endpoint","deviceIeeeAddress":"0x00212effff07ab97","endpointID":1},{"cluster":64512,"type":"endpoint","deviceIeeeAddress":"0x00212effff07ab97","endpointID":1},{"cluster":1,"type":"endpoint","deviceIeeeAddress":"0x00212effff07ab97","endpointID":1}],"configuredReportings":[{"cluster":1,"attrId":33,"minRepIntval":3600,"maxRepIntval":62000,"repChange":0}],"meta":{}}},"appVersion":2,"stackVersion":1,"hwVersion":1,"dateCode":"20220316","swBuildId":"2.59.19","zclVersion":8,"interviewCompleted":true,"meta":{"configured":-289016272},"lastSeen":1656081001000,"defaultSendRequestWhen":"immediate"}

Comments

I already tried creating my own converter to be able to make the device usable. This made the interview process successful and the button presses seems to work as well. I copied most of this from the hue dimmer switch which is probably also the reason why the buttons are labeled on, off, up and down.

I wasn't able to make the dial work properly, which is why I am writing here. I would probably need to use the correct converter but I don't feel comfortable just picking one and copy pasting it there. Here are some logs of what happens when rotating:

Turning left "one click"

2022-06-24 16:49:45 Received Zigbee message from 'Hue Tap Dial 1', type 'commandStepWithOnOff', cluster 'genLevelCtrl', data '{"stepmode":1,"stepsize":8,"transtime":4}' from endpoint 1 with groupID null
2022-06-24 16:49:45 No converter available for '9290035001' with cluster 'genLevelCtrl' and type 'commandStepWithOnOff' and data '{"stepmode":1,"stepsize":8,"transtime":4}'
2022-06-24 16:49:45 Received Zigbee message from 'Hue Tap Dial 1', type 'commandHueNotification', cluster 'manuSpecificPhilips', data '{"button":20,"time":241,"type":1,"unknown1":3145984,"unknown2":255}' from endpoint 1 with groupID null

Turning left continuously:

2022-06-24 16:52:46 Received Zigbee message from 'Hue Tap Dial 1', type 'commandStepWithOnOff', cluster 'genLevelCtrl', data '{"stepmode":1,"stepsize":32,"transtime":4}' from endpoint 1 with groupID null
2022-06-24 16:52:46 No converter available for '9290035001' with cluster 'genLevelCtrl' and type 'commandStepWithOnOff' and data '{"stepmode":1,"stepsize":32,"transtime":4}'
2022-06-24 16:52:46 Received Zigbee message from 'Hue Tap Dial 1', type 'commandHueNotification', cluster 'manuSpecificPhilips', data '{"button":20,"time":181,"type":1,"unknown1":3145984,"unknown2":255}' from endpoint 1 with groupID null
2022-06-24 16:52:47 Received Zigbee message from 'Hue Tap Dial 1', type 'commandHueNotification', cluster 'manuSpecificPhilips', data '{"button":20,"time":134,"type":2,"unknown1":3145984,"unknown2":254}' from endpoint 1 with groupID null
2022-06-24 16:52:47 Received Zigbee message from 'Hue Tap Dial 1', type 'commandStepWithOnOff', cluster 'genLevelCtrl', data '{"stepmode":1,"stepsize":202,"transtime":5}' from endpoint 1 with groupID null
2022-06-24 16:52:47 No converter available for '9290035001' with cluster 'genLevelCtrl' and type 'commandStepWithOnOff' and data '{"stepmode":1,"stepsize":202,"transtime":5}'
2022-06-24 16:52:47 Received Zigbee message from 'Hue Tap Dial 1', type 'commandHueNotification', cluster 'manuSpecificPhilips', data '{"button":20,"time":179,"type":2,"unknown1":3145984,"unknown2":254}' from endpoint 1 with groupID null
2022-06-24 16:52:47 Received Zigbee message from 'Hue Tap Dial 1', type 'commandStepWithOnOff', cluster 'genLevelCtrl', data '{"stepmode":1,"stepsize":87,"transtime":5}' from endpoint 1 with groupID null
2022-06-24 16:52:47 No converter available for '9290035001' with cluster 'genLevelCtrl' and type 'commandStepWithOnOff' and data '{"stepmode":1,"stepsize":87,"transtime":5}'

Right "one click"

2022-06-24 16:53:05 Received Zigbee message from 'Hue Tap Dial 1', type 'commandStepWithOnOff', cluster 'genLevelCtrl', data '{"stepmode":1,"stepsize":8,"transtime":4}' from endpoint 1 with groupID null
2022-06-24 16:53:05 No converter available for '9290035001' with cluster 'genLevelCtrl' and type 'commandStepWithOnOff' and data '{"stepmode":1,"stepsize":8,"transtime":4}'
2022-06-24 16:53:05 Received Zigbee message from 'Hue Tap Dial 1', type 'commandHueNotification', cluster 'manuSpecificPhilips', data '{"button":20,"time":241,"type":1,"unknown1":3145984,"unknown2":255}' from endpoint 1 with groupID null

Right continuously:

2022-06-24 16:53:24 Received Zigbee message from 'Hue Tap Dial 1', type 'commandStepWithOnOff', cluster 'genLevelCtrl', data '{"stepmode":0,"stepsize":20,"transtime":4}' from endpoint 1 with groupID null
2022-06-24 16:53:24 No converter available for '9290035001' with cluster 'genLevelCtrl' and type 'commandStepWithOnOff' and data '{"stepmode":0,"stepsize":20,"transtime":4}'
2022-06-24 16:53:24 Received Zigbee message from 'Hue Tap Dial 1', type 'commandHueNotification', cluster 'manuSpecificPhilips', data '{"button":20,"time":45,"type":1,"unknown1":3145984,"unknown2":0}' from endpoint 1 with groupID null
2022-06-24 16:53:24 Received Zigbee message from 'Hue Tap Dial 1', type 'commandHueNotification', cluster 'manuSpecificPhilips', data '{"button":20,"time":181,"type":2,"unknown1":3145984,"unknown2":0}' from endpoint 1 with groupID null
2022-06-24 16:53:24 Received Zigbee message from 'Hue Tap Dial 1', type 'commandStepWithOnOff', cluster 'genLevelCtrl', data '{"stepmode":0,"stepsize":99,"transtime":5}' from endpoint 1 with groupID null
2022-06-24 16:53:24 No converter available for '9290035001' with cluster 'genLevelCtrl' and type 'commandStepWithOnOff' and data '{"stepmode":0,"stepsize":99,"transtime":5}'
2022-06-24 16:53:25 Received Zigbee message from 'Hue Tap Dial 1', type 'commandHueNotification', cluster 'manuSpecificPhilips', data '{"button":20,"time":227,"type":2,"unknown1":3145984,"unknown2":0}' from endpoint 1 with groupID null
2022-06-24 16:53:25 Received Zigbee message from 'Hue Tap Dial 1', type 'commandStepWithOnOff', cluster 'genLevelCtrl', data '{"stepmode":0,"stepsize":111,"transtime":5}' from endpoint 1 with groupID null
2022-06-24 16:53:25 No converter available for '9290035001' with cluster 'genLevelCtrl' and type 'commandStepWithOnOff' and data '{"stepmode":0,"stepsize":111,"transtime":5}'
2022-06-24 16:53:25 Received Zigbee message from 'Hue Tap Dial 1', type 'commandHueNotification', cluster 'manuSpecificPhilips', data '{"button":20,"time":227,"type":2,"unknown1":3145984,"unknown2":0}' from endpoint 1 with groupID null
2022-06-24 16:53:25 Received Zigbee message from 'Hue Tap Dial 1', type 'commandStepWithOnOff', cluster 'genLevelCtrl', data '{"stepmode":0,"stepsize":93,"transtime":5}' from endpoint 1 with groupID null
2022-06-24 16:53:25 No converter available for '9290035001' with cluster 'genLevelCtrl' and type 'commandStepWithOnOff' and data '{"stepmode":0,"stepsize":93,"transtime":5}'
2022-06-24 16:53:25 Received Zigbee message from 'Hue Tap Dial 1', type 'commandHueNotification', cluster 'manuSpecificPhilips', data '{"button":20,"time":106,"type":2,"unknown1":3145984,"unknown2":0}' from endpoint 1 with groupID null

External converter

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 definition = {
    zigbeeModel: ['RDM002', '9290035001'], // The model ID from: Device with modelID 'lumi.sens' is not supported.
    model: '9290035001', // Vendor model number, look on the device for a model number
    vendor: 'Signify Netherlands B.V.', // Vendor of the device (only used for documentation and startup logging)
    description: 'Hue tap dial', // Description of the device, copy from vendor site. (only used for documentation and startup logging)
    fromZigbee: [fz.ignore_command_on, fz.ignore_command_off, fz.ignore_command_step, fz.ignore_command_stop,
            fz.hue_dimmer_switch, fz.battery, fz.command_recall], // We will add this later
    toZigbee: [], // Should be empty, unless device can be controlled (e.g. lights, switches).
    exposes: [e.battery(), e.action(['on_press', 'on_hold', 'on_press_release', 'on_hold_release',
            'off_press', 'off_hold', 'off_press_release', 'off_hold_release', 'up_press', 'up_hold', 'up_press_release', 'up_hold_release',
            'down_press', 'down_hold', 'down_press_release', 'down_hold_release', 'recall_0', 'recall_1'])], // Defines what this device exposes, used for e.g. Home Assistant discovery and in the fro>
    configure: async (device, coordinatorEndpoint, logger) => {
        const endpoint = device.getEndpoint(1);
        await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'manuSpecificPhilips', 'genPowerCfg']);
        const options = {manufacturerCode: 0x100B, disableDefaultResponse: true};
        await endpoint.write('genBasic', {0x0031: {value: 0x000B, type: 0x19}}, options);
        await reporting.batteryPercentageRemaining(endpoint);
    }
};

module.exports = definition;

Supported color modes

No response

Color temperature range

No response

vandalon commented 2 years ago

Hey @xammmue, Did you make any progress on this one?

xammmue commented 2 years ago

Hi @vandalon , I didn't have much time to look further into the issue yet. I'd like to do so in case nobody has an idea and I have a bit more time to play around

What's missing right now is probably to properly map the turning action in some way to a processable state. But I couldn't find any other device having a rotary input like this one. I thought about whether it should be a message/action containing how far the ring has been turned or multiple action for slow and fast turning but didn't come to a conclusion. Second thing would probably be to have action 1 2 3 4 instead off up down etc but that would be more of a quality of life enhancement as the buttons working right now

I also hoped for some.idwas from @Koenkk but he didn't reply so far unfortunately.

Koenkk commented 2 years ago

@xammmue for the rotating part a new converter has to be created, you can use https://github.com/Koenkk/zigbee-herdsman-converters/blob/557a01445d7aa276592c844e2c84a27319d0d8da/converters/fromZigbee.js#L7051 as a starting point.

mbirkes commented 2 years ago

I'm new to this stuff here, but after digging and playing around, I have the attached external converter file working for my device. It uses a modified version of the hue_dimmer_switch converter to support the buttons 1-4 press/hold/release and the dial (button 20) step/spin up/down actions.

NOTE: my device actually has Model: 9290035003 printed on the unit (instead of 9290035001 in this issue title)

I also found it supports the genScenes binding to a group (like the hue dimmer) and existing command_recall, but I didn't really understand the point of exposing them since they coincide with the button presses. (the scenes are strange too, where instead of expected scenes/recall_0, 1, 2, or 3 you get recall_1, 0, 5, and 4)

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

const {
    precisionRound, mapNumberRange, isLegacyEnabled, toLocalISOString, numberWithinRange, hasAlreadyProcessedMessage,
    calibrateAndPrecisionRoundOptions, addActionGroup, postfixWithEndpointName, getKey,
    batteryVoltageToPercentage, getMetaValue,
} = require('zigbee-herdsman-converters/lib/utils');

const my_fz = {
    hue_tap_dial: {
        cluster: 'manuSpecificPhilips',
        type: 'commandHueNotification',
        options: [exposes.options.simulated_brightness()],
        convert: (model, msg, publish, options, meta) => {
            const buttonLookup = {1: 'button_1', 2: 'button_2', 3: 'button_3', 4: 'button_4', 20: 'dial'};
            const button = buttonLookup[msg.data['button']];
            const typeLookup = {0: 'press', 1: 'hold', 2: 'press_release', 3: 'hold_release'};
            const type = typeLookup[msg.data['type']];
            const dialTypeLookup = {1: 'step', 2: 'spin'};
            const dialType = dialTypeLookup[msg.data['type']];
            const directionLookup = {0: 'up', 255: 'down'};
            const direction = directionLookup[msg.data['unknown2']];
            const payload = {};

            if (button === 'dial'){
                payload.action = `${button}_${dialType}_${direction}`;
                // simulated brightness
                if (options.simulated_brightness) {
                    const opts = options.simulated_brightness;
                    const deltaOpts = typeof opts === 'object' && opts.hasOwnProperty('delta') ? opts.delta : 35;
                    const delta = direction === 'up' ? deltaOpts : deltaOpts * -1;
                    const brightness = globalStore.getValue(msg.endpoint, 'brightness', 255) + delta;
                    payload.brightness = numberWithinRange(brightness, 0, 255);
                    globalStore.putValue(msg.endpoint, 'brightness', payload.brightness);
                }
            }
            else
            {
                payload.action = `${button}_${type}`;
                // duration
                if (type === 'press') globalStore.putValue(msg.endpoint, 'press_start', Date.now());
                else if (type === 'hold' || type === 'hold_release') {
                    payload.action_duration = (Date.now() - globalStore.getValue(msg.endpoint, 'press_start')) / 1000;
                }
            }
            return payload;
        },
    },
};

const definition = {
    zigbeeModel: ['RDM002'], 
    model: '9290035003', 
    vendor: 'Philips', 
    description: 'Hue Tap Dial', 
    fromZigbee: [fz.ignore_command_step, my_fz.hue_tap_dial, fz.battery], 
    toZigbee: [], 
    exposes: [e.battery(), e.action(['button_1_press', 'button_1_press_release', 'button_1_hold', 'button_1_hold_release',
                                     'button_2_press', 'button_2_press_release', 'button_2_hold', 'button_2_hold_release',
                                     'button_3_press', 'button_3_press_release', 'button_3_hold', 'button_3_hold_release',
                                     'button_4_press', 'button_4_press_release', 'button_4_hold', 'button_4_hold_release',
                                     'dial_step_up', 'dial_spin_up', 'dial_step_down', 'dial_spin_down'])
    ], // Defines what this device exposes, used for e.g. Home Assistant discovery and in the frontend
    configure: async (device, coordinatorEndpoint, logger) => {
        const endpoint = device.getEndpoint(1);
        await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'manuSpecificPhilips', 'genPowerCfg']);
        const options = {manufacturerCode: 0x100B, disableDefaultResponse: true};
        await endpoint.write('genBasic', {0x0031: {value: 0x000B, type: 0x19}}, options);
        await reporting.batteryPercentageRemaining(endpoint);
    },
    ota: ota.zigbeeOTA,
};

module.exports = definition;
big-ted commented 2 years ago

@mbirkes that is working pretty well for me. I did notice that my UK white version of the dial is also 9290035001

xammmue commented 2 years ago

Also got something setup yesterday evening, but without the simulated brightness option as I am not really familiar with that

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 fzLocal = {
    hue_tap_dial: {
        cluster: 'manuSpecificPhilips',
        type: 'commandHueNotification',
        convert: (model, msg, publish, options, meta) => {
            const buttonLookup = {1: 'one', 2: 'two', 3: 'three', 4: 'four', 20: 'dial'};
            const button = buttonLookup[msg.data['button']];
            const typeLookup = {0: 'press', 1: 'hold', 2: 'press_release', 3: 'hold_release'};
            const type = typeLookup[msg.data['type']];
            const rotationDirection = {0: 'right', 255: 'left'}

            //rotation
            if ('dial' === button) {
                const direction = rotationDirection[msg.data['unknown2']]
                const time = msg.data['time'];
                let speed = 'slow'
                if(('left' === direction && 241 !== time) || ('right' === direction && 15 !== time)) {//rotate fast
                    speed = 'fast'
                }
                return {action: `rotate_${direction}_${speed}`}
            }

            return {action: `${button}_${type}`}

        },
    }
}

const definition = {
    zigbeeModel: ['RDM002', '9290035001'],
    model: '9290035001',
    vendor: 'Signify Netherlands B.V.',
    description: 'Hue tap dial',
    fromZigbee: [fz.ignore_command_step, fzLocal.hue_tap_dial, fz.battery],
    toZigbee: [],
    exposes: [e.battery(), e.action(['one_press', 'one_hold', 'one_press_release', 'one_hold_release',
            'four_press', 'four_hold', 'four_press_release', 'four_hold_release', 'two_press', 'two_hold', 'two_press_release', 'two_hold_release',
            'three_press', 'three_hold', 'three_press_release', 'three_hold_release', 'recall_0', 'recall_1'])],
    configure: async (device, coordinatorEndpoint, logger) => {
        const endpoint = device.getEndpoint(1);
        await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'manuSpecificPhilips', 'genPowerCfg']);
        const options = {manufacturerCode: 0x100B, disableDefaultResponse: true};
        await endpoint.write('genBasic', {0x0031: {value: 0x000B, type: 0x19}}, options);
        await reporting.batteryPercentageRemaining(endpoint);
    }
};

module.exports = definition;
mbirkes commented 2 years ago

Nice @xammmue . The speed is something I wish I had after I started actually using the device and HA triggers. Thanks! Beware of the recalls though, they are misleading. See my post.
The simulated brightness metadata in my post is present in the outgoing messages to describe the brightness steps, but I'm not sure how you use that in HA unless maybe by utilizing template triggers or something. I'm fairly new to the zigbee2mqtt integration and plumbing here. I also could not understand how/why the genBasic data is set and/or consumed.

Xander-V commented 2 years ago

Good to read people are already testing with this nice HUE switch! Mine seems to get correctly detected with your code snippet, but not functional yet.

Koenkk commented 2 years ago

@mbirkes is the converter you posted complete? Then I can add this for support out-of-the-box.

xammmue commented 2 years ago

@Koenkk I guess it would make sense to combine his and mine to have stimulated brightness and speed

mbirkes commented 2 years ago

I actually created another one if you guys want to check it out too. It handles additional speed variables. There is still one bug (included all of the previous ones), where if you spin VERY fast, the unknown2 variable jumps a couple of values and causes direction variable to be undefined. I haven't had time to fix this yet, but is fixable.

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

const {
    precisionRound, mapNumberRange, isLegacyEnabled, toLocalISOString, numberWithinRange, hasAlreadyProcessedMessage,
    calibrateAndPrecisionRoundOptions, addActionGroup, postfixWithEndpointName, getKey,
    batteryVoltageToPercentage, getMetaValue,
} = require('zigbee-herdsman-converters/lib/utils');

const my_fz = {
    hue_tap_dial: {
        cluster: 'manuSpecificPhilips',
        type: 'commandHueNotification',
        options: [exposes.options.simulated_brightness()],
        convert: (model, msg, publish, options, meta) => {
            const buttonLookup = {1: 'button_1', 2: 'button_2', 3: 'button_3', 4: 'button_4', 20: 'dial'};
            const button = buttonLookup[msg.data['button']];
            const typeLookup = {0: 'press', 1: 'hold', 2: 'press_release', 3: 'hold_release'};
            const type = typeLookup[msg.data['type']];
            const directionLookup = {0: 'right', 255: 'left'};
            const direction = directionLookup[msg.data['unknown2']];
            const time = msg.data['time'];
            const payload = {};

            if (button === 'dial'){
                const adjustedTime = direction === 'right' ? time : 256 - time;
                const dialType = 'rotate';                
                const speed = adjustedTime <= 15 ? 'step' : 
                        adjustedTime <= 60 ? 'slow' : 
                        adjustedTime <= 106 ? 'medium' : 'fast';
                payload.action = `${button}_${dialType}_${direction}_${speed}`;

                // simulated brightness
                if (options.simulated_brightness) {
                    const opts = options.simulated_brightness;
                    const deltaOpts = typeof opts === 'object' && opts.hasOwnProperty('delta') ? opts.delta : 35;
                    const delta = direction === 'right' ? deltaOpts : deltaOpts * -1;
                    const brightness = globalStore.getValue(msg.endpoint, 'brightness', 255) + delta;
                    payload.brightness = numberWithinRange(brightness, 0, 255);
                    globalStore.putValue(msg.endpoint, 'brightness', payload.brightness);
                }
            }
            else{
                payload.action = `${button}_${type}`;
                // duration
                if (type === 'press') globalStore.putValue(msg.endpoint, 'press_start', Date.now());
                else if (type === 'hold' || type === 'hold_release') {
                    payload.action_duration = (Date.now() - globalStore.getValue(msg.endpoint, 'press_start')) / 1000;
                }
            }
            return payload;
        },
    },
};

const definition = {
    zigbeeModel: ['RDM002'], 
    model: '9290035003', 
    vendor: 'Philips', 
    description: 'Hue Tap Dial', 
    fromZigbee: [fz.ignore_command_step, my_fz.hue_tap_dial, fz.battery], 
    toZigbee: [], 
    exposes: [e.battery(), e.action(['button_1_press', 'button_1_press_release', 'button_1_hold', 'button_1_hold_release',
                                     'button_2_press', 'button_2_press_release', 'button_2_hold', 'button_2_hold_release',
                                     'button_3_press', 'button_3_press_release', 'button_3_hold', 'button_3_hold_release',
                                     'button_4_press', 'button_4_press_release', 'button_4_hold', 'button_4_hold_release',
                                     'dial_rotate_right_step', 'dial_rotate_right_slow','dial_rotate_right_medium','dial_rotate_right_fast',
                                     'dial_rotate_left_step','dial_rotate_left_slow','dial_rotate_left_medium','dial_rotate_left_fast'])
    ], // Defines what this device exposes, used for e.g. Home Assistant discovery and in the frontend
    configure: async (device, coordinatorEndpoint, logger) => {
        const endpoint = device.getEndpoint(1);
        await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'manuSpecificPhilips', 'genPowerCfg']);
        const options = {manufacturerCode: 0x100B, disableDefaultResponse: true};
        await endpoint.write('genBasic', {0x0031: {value: 0x000B, type: 0x19}}, options);
        await reporting.batteryPercentageRemaining(endpoint);
    },
    ota: ota.zigbeeOTA,
};

module.exports = definition;
mbirkes commented 2 years ago

You could probably replace the direction line with the following. This is untested, only from memory. I remember the 'unknown2' value of 0 can become 1, 2, 3 ... etc if spun/flicked very fast (same with other direction 255, 254, 253...). Additionally, the 'time' data also changed (decreased) as the unknown2 value increased, which may give the speed value an unexpected speed with current converter code.

const direction = msg.data['unknown2'] < 10 ? 'right' : 'left'

Xander-V commented 2 years ago

@mbirkes is the converter you posted complete? Then I can add this for support out-of-the-box.

I combined the code and the remarks from @mbirkes into the following -for me- working converter.

Changed the left/right direction to 'right' for all values of unknown2 <127.

After playing around with the speed of the dial, I decided to removed the rotate_medium since it is almost impossible to repeatedly turn the dial at a certain speed that is consistently differentiated as "medium" or "fast". Most of the time it turned out to be a gamble so IMHO step/slow/medium covers it all.

Did not test with the simulated brightness part. I'll be happy to do some further testing.

Mine BTW is a black version with type number 9290035002. Maybe someone else can change this to also include a fingerprint and determine which one it actually is, but that's above my knowledge.

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

const {
    precisionRound, mapNumberRange, isLegacyEnabled, toLocalISOString, numberWithinRange, hasAlreadyProcessedMessage,
    calibrateAndPrecisionRoundOptions, addActionGroup, postfixWithEndpointName, getKey,
    batteryVoltageToPercentage, getMetaValue,
} = require('zigbee-herdsman-converters/lib/utils');

const my_fz = {
    hue_tap_dial: {
        cluster: 'manuSpecificPhilips',
        type: 'commandHueNotification',
        options: [exposes.options.simulated_brightness()],
        convert: (model, msg, publish, options, meta) => {
            const buttonLookup = {1: 'button_1', 2: 'button_2', 3: 'button_3', 4: 'button_4', 20: 'dial'};
            const button = buttonLookup[msg.data['button']];
            const typeLookup = {0: 'press', 1: 'hold', 2: 'press_release', 3: 'hold_release'};
            const type = typeLookup[msg.data['type']];
            const direction = msg.data['unknown2'] <127 ? 'right' : 'left';
            const time = msg.data['time'];
            const payload = {};

            if (button === 'dial'){
                const adjustedTime = direction === 'right' ? time : 256 - time;
                const dialType = 'rotate';                
                const speed = adjustedTime <= 25 ? 'step' : 
                        adjustedTime <= 75 ? 'slow' : 'fast';
                payload.action = `${button}_${dialType}_${direction}_${speed}`;

                // simulated brightness
                if (options.simulated_brightness) {
                    const opts = options.simulated_brightness;
                    const deltaOpts = typeof opts === 'object' && opts.hasOwnProperty('delta') ? opts.delta : 35;
                    const delta = direction === 'right' ? deltaOpts : deltaOpts * -1;
                    const brightness = globalStore.getValue(msg.endpoint, 'brightness', 255) + delta;
                    payload.brightness = numberWithinRange(brightness, 0, 255);
                    globalStore.putValue(msg.endpoint, 'brightness', payload.brightness);
                }
            }
            else{
                payload.action = `${button}_${type}`;
                // duration
                if (type === 'press') globalStore.putValue(msg.endpoint, 'press_start', Date.now());
                else if (type === 'hold' || type === 'hold_release') {
                    payload.action_duration = (Date.now() - globalStore.getValue(msg.endpoint, 'press_start')) / 1000;
                }
            }
            return payload;
        },
    },
};

const definition = {
    zigbeeModel: ['RDM002'], 
    model: '8719514440937', 
    vendor: 'Philips', 
    description: 'Hue Tap dial switch', 
    fromZigbee: [fz.ignore_command_step, my_fz.hue_tap_dial, fz.battery], 
    toZigbee: [], 
    exposes: [e.battery(), e.action(['button_1_press', 'button_1_press_release', 'button_1_hold', 'button_1_hold_release',
                                     'button_2_press', 'button_2_press_release', 'button_2_hold', 'button_2_hold_release',
                                     'button_3_press', 'button_3_press_release', 'button_3_hold', 'button_3_hold_release',
                                     'button_4_press', 'button_4_press_release', 'button_4_hold', 'button_4_hold_release',
                                     'dial_rotate_left_step', 'dial_rotate_left_slow', 'dial_rotate_left_fast',
                                     'dial_rotate_right_step', 'dial_rotate_right_slow', 'dial_rotate_right_fast'])
    ], // Defines what this device exposes, used for e.g. Home Assistant discovery and in the frontend
    configure: async (device, coordinatorEndpoint, logger) => {
        const endpoint = device.getEndpoint(1);
        await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'manuSpecificPhilips', 'genPowerCfg']);
        const options = {manufacturerCode: 0x100B, disableDefaultResponse: true};
        await endpoint.write('genBasic', {0x0031: {value: 0x000B, type: 0x19}}, options);
        await reporting.batteryPercentageRemaining(endpoint);
    },
    ota: ota.zigbeeOTA,
};

module.exports = definition;
mbirkes commented 2 years ago

Re: simulated brightness I am not familiar with how this is used or tested (on any device). I simply ported it from the dimmer switch code and adjusted according to the new tap dial local code. If anyone can shed some light on this, I would appreciate understanding this a bit more.

mbirkes commented 2 years ago

@Xander-V thanks for updating it. I'll check it out as well. Agree the 4 speeds was a little overkill

Bradfordmcmanus commented 2 years ago

@mbirkes is the converter you posted complete? Then I can add this for support out-of-the-box.

I combined the code and the remarks from @mbirkes into the following -for me- working converter.

Changed the left/right direction to 'right' for all values of unknown2 <127.

After playing around with the speed of the dial, I decided to removed the rotate_medium since it is almost impossible to repeatedly turn the dial at a certain speed that is consistently differentiated as "medium" or "fast". Most of the time it turned out to be a gamble so IMHO step/slow/medium covers it all.

Did not test with the simulated brightness part. I'll be happy to do some further testing.

Mine BTW is a black version with type number 9290035002. Maybe someone else can change this to also include a fingerprint and determine which one it actually is, but that's above my knowledge.

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

const {
    precisionRound, mapNumberRange, isLegacyEnabled, toLocalISOString, numberWithinRange, hasAlreadyProcessedMessage,
    calibrateAndPrecisionRoundOptions, addActionGroup, postfixWithEndpointName, getKey,
    batteryVoltageToPercentage, getMetaValue,
} = require('zigbee-herdsman-converters/lib/utils');

const my_fz = {
    hue_tap_dial: {
        cluster: 'manuSpecificPhilips',
        type: 'commandHueNotification',
        options: [exposes.options.simulated_brightness()],
        convert: (model, msg, publish, options, meta) => {
            const buttonLookup = {1: 'button_1', 2: 'button_2', 3: 'button_3', 4: 'button_4', 20: 'dial'};
            const button = buttonLookup[msg.data['button']];
            const typeLookup = {0: 'press', 1: 'hold', 2: 'press_release', 3: 'hold_release'};
            const type = typeLookup[msg.data['type']];
            const direction = msg.data['unknown2'] <127 ? 'right' : 'left';
            const time = msg.data['time'];
            const payload = {};

            if (button === 'dial'){
                const adjustedTime = direction === 'right' ? time : 256 - time;
                const dialType = 'rotate';                
                const speed = adjustedTime <= 25 ? 'step' : 
                      adjustedTime <= 75 ? 'slow' : 'fast';
                payload.action = `${button}_${dialType}_${direction}_${speed}`;

                // simulated brightness
                if (options.simulated_brightness) {
                    const opts = options.simulated_brightness;
                    const deltaOpts = typeof opts === 'object' && opts.hasOwnProperty('delta') ? opts.delta : 35;
                    const delta = direction === 'right' ? deltaOpts : deltaOpts * -1;
                    const brightness = globalStore.getValue(msg.endpoint, 'brightness', 255) + delta;
                    payload.brightness = numberWithinRange(brightness, 0, 255);
                    globalStore.putValue(msg.endpoint, 'brightness', payload.brightness);
                }
            }
            else{
                payload.action = `${button}_${type}`;
                // duration
                if (type === 'press') globalStore.putValue(msg.endpoint, 'press_start', Date.now());
                else if (type === 'hold' || type === 'hold_release') {
                    payload.action_duration = (Date.now() - globalStore.getValue(msg.endpoint, 'press_start')) / 1000;
                }
            }
            return payload;
        },
    },
};

const definition = {
    zigbeeModel: ['RDM002'], 
    model: '8719514440937', 
    vendor: 'Philips', 
    description: 'Hue Tap dial switch', 
    fromZigbee: [fz.ignore_command_step, my_fz.hue_tap_dial, fz.battery], 
    toZigbee: [], 
    exposes: [e.battery(), e.action(['button_1_press', 'button_1_press_release', 'button_1_hold', 'button_1_hold_release',
                                     'button_2_press', 'button_2_press_release', 'button_2_hold', 'button_2_hold_release',
                                     'button_3_press', 'button_3_press_release', 'button_3_hold', 'button_3_hold_release',
                                     'button_4_press', 'button_4_press_release', 'button_4_hold', 'button_4_hold_release',
                                     'dial_rotate_left_step', 'dial_rotate_left_slow', 'dial_rotate_left_fast',
                                     'dial_rotate_right_step', 'dial_rotate_right_slow', 'dial_rotate_right_fast'])
    ], // Defines what this device exposes, used for e.g. Home Assistant discovery and in the frontend
    configure: async (device, coordinatorEndpoint, logger) => {
        const endpoint = device.getEndpoint(1);
        await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'manuSpecificPhilips', 'genPowerCfg']);
        const options = {manufacturerCode: 0x100B, disableDefaultResponse: true};
        await endpoint.write('genBasic', {0x0031: {value: 0x000B, type: 0x19}}, options);
        await reporting.batteryPercentageRemaining(endpoint);
    },
    ota: ota.zigbeeOTA,
};

module.exports = definition;

This worked perfect for me thanks so much

Bradfordmcmanus commented 2 years ago

For the dial ? Yes I had to park battery mode hold on the reset button until it started working slow green in the front seat like between button one and 2

On Sat, Jul 23, 2022 at 5:36 AM sebastiantest @.***> wrote:

Hey, could you please explain how the pairing process works?

— Reply to this email directly, view it on GitHub https://github.com/Koenkk/zigbee2mqtt/issues/12927#issuecomment-1193095613, or unsubscribe https://github.com/notifications/unsubscribe-auth/AVGLF7WLTS5FWH6HARJWQBTVVO4JVANCNFSM5ZYDTZCA . You are receiving this because you commented.Message ID: @.***>

Xander-V commented 2 years ago

Since I think this one works fine now, @Koenkk can you please add this to the next release?

Please also add the model numbers from the posts by xammmue and mrbirkes since I think the only difference are the color and/or regional numbering by Philips. I don't know how to combine multiple model numbers in the ID, sorry.

Thanks.

Koenkk commented 2 years ago

Added! Assuming this can be closed now.

Changes will be available in the dev branch in a few hours from now. (https://www.zigbee2mqtt.io/advanced/more/switch-to-dev-branch.html)

Hessenpower01 commented 2 years ago

Maybe I didn't get it right, should it be supported now? I'm running Z2M 1.27.0-1 but when connecting a Tap Dial I get "Device not supported".

Thx for your work @Koenkk!

Xander-V commented 2 years ago

Maybe I didn't get it right, should it be supported now? I'm running Z2M 1.27.0-1 but when connecting a Tap Dial I get "Device not supported".

Thx for your work @Koenkk!

I think it still only is incorperated in the :latest-dev branch, are you using the development branch? I just checked and indeed it is not supported in the regular :latest Zigbee2MQTT version 1.27.0 (commit #a9b8808), but is in the version 1.27.0-dev (commit #281e387)

Or maybe you're device ID/type number is just a bit different from the one it is recognizing now?

@Koenkk what is needed to get this one into the :latest branch, is there something still missing or anything we can help with?

mbirkes commented 2 years ago

I think it got approved too late for the last release. Looks like they release once a month, so will have to wait a few weeks unless you use the dev branch. I just use the manual local config for now until it's supported.

From: Xander-V @.> Sent: Friday, August 12, 2022 9:03 AM To: Koenkk/zigbee2mqtt @.> Cc: Mason Birkes @.>; Mention @.> Subject: Re: [Koenkk/zigbee2mqtt] [New device support]: Hue Tap Dial RDM002 9290035001 (Issue #12927)

Maybe I didn't get it right, should it be supported now? I'm running Z2M 1.27.0-1 but when connecting a Tap Dial I get "Device not supported".

Thx for your work @Koenkkhttps://nam12.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2FKoenkk&data=05%7C01%7C%7C7d720959711044ef49c808da7c6b4caf%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C637959097552316480%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&sdata=GbnkKqICihWGve%2FUCHfMihxGl3Vt1mX3D53CiIRQdKk%3D&reserved=0!

I think it still only is incorperated in the :latest-dev branch, are you using the development branch? I just checked and indeed it is not supported in the regular :latest Zigbee2MQTT version 1.27.0 (commit #a9b8808), but is in the version 1.27.0-dev (commit #281e387)

- Reply to this email directly, view it on GitHubhttps://nam12.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2FKoenkk%2Fzigbee2mqtt%2Fissues%2F12927%23issuecomment-1213144430&data=05%7C01%7C%7C7d720959711044ef49c808da7c6b4caf%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C637959097552316480%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&sdata=RxOylIMn%2FcyL5B%2FWG0dpcYFRwqKm6TX4cC%2BwWpK4dJg%3D&reserved=0, or unsubscribehttps://nam12.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fnotifications%2Funsubscribe-auth%2FAACCTRGAIACG6GSZY4PTMRDVYZKPNANCNFSM5ZYDTZCA&data=05%7C01%7C%7C7d720959711044ef49c808da7c6b4caf%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C637959097552316480%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&sdata=Gr0cyh80YzuQobyjDcCIff0cqYTVCKZskcB1%2FYmDcn8%3D&reserved=0. You are receiving this because you were mentioned.Message ID: @.**@.>>

Yasp0 commented 2 years ago

I am using z2m edge and added the Hue tap dial to z2m. That all worked fine. But I returned the dial to the shop because of a damaged dial.

Now I connected my replacement Hue tap dial and it is not working properly. In Home Assistant when I listen to events no event is detected when pushing a button or turning the dial. There are also no usuable triggers in HA.

In z2m it says 'Device supported'. The battery level is 0, but I tested it with a new battery.

Am I doing something wrong? I tried two different tap dials, both the same result.

hjri commented 1 year ago

@Yasp0 if battery level is 0 and it doesn't blink when pressing its button you need to update its firmware. It's doable via deconz at least. Finding the firmware was a pain in the ass but i found it.

http://fds.dc1.philips.com/firmware/ZGB_100B_0121/33569561/100B-0121-02003B19-Switch-EFR32MG22-40xf.zigbee

(0x)0121 is "image type" specific to tap dial, this is the only indication that it's the right one as far as i can tell. (0x)100b is manufaturer (phillips/signify?)

hjri commented 1 year ago

Word of advice that you need to press "reset" to wake the device up, it seems to be going asleep and completely unresponsive after a while, so best course of action is to "reset" it, possibly re pair it (or discover it?), and immediately begin uploading OTAU

Yasp0 commented 1 year ago

@hjri Thanks for the reply. I am using Zigbee2mqtt, not deconz. So have to see if I can update the device.

Kind of weird not many other people are having issues.

hjri commented 1 year ago

@Yasp0 you could switch to deconz or whatever else available for your zigbee adapter (i use deconz/conbee2). Worst case scenario - you could go the "easy way" and get a hue bridge + hue app to update it.

Yasp0 commented 1 year ago

@hjri I see an OTA in Zigbee2mqtt for the dial. Am trying that right now.

Yasp0 commented 1 year ago

@hjri The update seemed to have fixed the issue! Thanks!!

Went from 2.59.19 to 2.59.25

Adesfire commented 1 year ago

Hi there! Just received my hue tap and got the same issue then Yasp0. I did the upgrade using z2m and reset/re pair the device. However, I can't see the battery level (just the % symbol on z2m which blink red. When I push one of the tap button, there is no light, I don't know if that's normal... Any help would be very much appreciated!

hjri commented 1 year ago

@Adesfire if it shows 0% battery and if button presses don't turn led on device green for a brief period that means it's running borked firmware, so probably update process failed?

Adesfire commented 1 year ago

@Adesfire if it shows 0% battery and if button presses don't turn led on device green for a brief period that means it's running borked firmware, so probably update process failed?

Hi! It doesn't show 0% but I see the % symbol blinking red, which could make me think it's like having 0% idk.

But indeed, nothing happens when I click a button on it, or if I turn the dimmer.

Yet, I already tried to reset it pushing the setup button for 10 seconds, so how could I reset or reinstall the firmware now? Fyi, after the new pairing it shows up with the last firmware version... Thank you in advance for your help!

Hessenpower01 commented 1 year ago

I upgraded my Firmware yesterday, just in Z2M. Several attempts but at the end it worked.

Clicked a button directly before starting the OTA request.

Adesfire commented 1 year ago

I upgraded my Firmware yesterday, just in Z2M. Several attempts but at the end it worked.

Clicked a button directly before starting the OTA request.

I also went from 2.59.19 to 2.59.25 yesterday, it took me about 26 minutes but it was a success according to z2m. After another reset (pushed the setup button for about 10 sec) and re-pairing, Z2M sees only the link-quality info, nothing more

I'm a bit worried about how I could reinstall the firmware, maybe now it's bricked...

Adesfire commented 1 year ago

@Adesfire if it shows 0% battery and if button presses don't turn led on device green for a brief period that means it's running borked firmware, so probably update process failed?

Tonight I removed the battery, put it back and rejoin z2m with no luck....

This is what is visible on the state tab: { "linkquality": 69, "action": null, "battery": null, "update": { "state": null }, "update_available": null }