Koenkk / zigbee2mqtt

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

[feature] Support for "advanced" commands, like "On With Timed Off" #3747

Closed Equidamoid closed 4 years ago

Equidamoid commented 4 years ago

It would be nice to be able to issue exact ZLL commands via zigbee2mqtt.

For example, I have a water valve which I really don't want to get stuck in "on" state due to a network problem/poorly timed raspberry reboot, etc., and being able to send OnWithTimedOff would ensure the device switches off in a timely manner.

MPM1107 commented 4 years ago

The code is already there, but it's only implemented per-device. I'm not sure why that is exactly, but I'm guessing that this command has to be specifically supported by each device, right? I guess we could try adding the option for a timed off to your valve manually and testing how it reacts.

Equidamoid commented 4 years ago

The feature is not device-specific, it's in the spec for OnOff cluster. I can't easily find info on whether it is considered optional though. If I were to implement it, I'd add it to all devices supporting OnOff cluster. Maybe with an option to blacklist it for devices that will be known in the future to not have it working properly.

MPM1107 commented 4 years ago

Okay, so if you wanna play around with it:

Here's a custom device that has support for timed off specifically baked in: device definition And here's the basic on_off cluster: onoff definition

What would probably have to be done is adding another key, for example value_timed or something like that and then implement the timed command like it is implemented in the example device I linked. If that's too difficult I could take a shot at it but no guarantee on speed as I'm pretty busy.

sjorge commented 4 years ago

Oh this is interesting, let me have a look tomorrow I have a generic idea to try something like this:

    on_off: {
        key: ['state'],
        convertSet: async (entity, key, value, meta) => {
            if (typeof value === 'number') {
                await entity.command('genOnOff', 'onWithTimedOff', {ctrlbits: 0, ontime: value, offwaittime: 0});
                value = 'ON';
            } else {
                await entity.command('genOnOff', value.toLowerCase(), {}, getOptions(meta.mapped));
            }

            if (value.toLowerCase() === 'toggle') {
                const currentState = meta.state[`state${meta.endpoint_name ? `_${meta.endpoint_name}` : ''}`];
                return currentState ? {state: {state: currentState === 'OFF' ? 'ON' : 'OFF'}} : {};
            } else {
                return {state: {state: value.toUpperCase()}};
            }
        },
        convertGet: async (entity, key, meta) => {
            await entity.read('genOnOff', ['onOff']);
        },
    },

Might need a bit more tweaking, but if you swap the on_off convert with this one you can send a value in seconds (I think) instead of 'ON', 'OFF', 'toggle'.

Will play with this some tomorrow, well if I don't forget.

MPM1107 commented 4 years ago

@sjorge Tried it on one of my osram smart plugs, zigbee herdsman debug log shows that the device returns response code 129 when I send a timed off. I can't find any info on what that code means exactly, but my guess is that my initial thought that this command has to be specifically supported by a device was correct. Would be interesting to see some more testing with other devices, I don't have any more that support the basic on/off cluster.

Equidamoid commented 4 years ago

@MPM1107 what kind of osram plugs do you have? I have Osram SMART+ (like https://www.coolblue.nl/product/828067/osram-smart-slimme-stekker.html?cmt=c_p%2Cr_tweakersnl ) and the feature works flawlessly with deCONZ. Also it worked well during my experiments with zigpy and custom rpc wrapper, so I'm absolutely sure they support this call. Let me quickly try it on some tradfri and hue bulbs I have...

MPM1107 commented 4 years ago

Yup, those look like the ones I have. Interesting info, I'll dig around a bit and see if I can find out why this wouldn't work.

Equidamoid commented 4 years ago

Yes, just checked it on a the Osram SMART+, Tradfri (GU10 socket, white), Hie Bloom and some round Hue "filament lamp". All three responded correctly.

MPM1107 commented 4 years ago

So you're using the deconz coordinator with zigbee2mqtt? Or what's the setup?

sjorge commented 4 years ago

My code might not be 100% working though, I wrote this on my ipad without actually having a syntax checker

Equidamoid commented 4 years ago

I have deCONZ as my coordinator now. Due to unknown causes for some devices commands fail in ~2-5% of the cases and this is annoying. I'm considering switching everything to zigbee2mqtt as an experiment (although I'm a bit terrified of the process of resetting all those lamps...). My scripts are more or less agnostic and can be adapted to zigbee2mqtt if needed, but I really need the on with timeout feature in order to do that experiment.

So the current setup is ~40 devices in deconz network and a lonely osram switch in a separate zigbee2mqtt network.

sjorge commented 4 years ago

I figured out why it is not working, on_off is a very very simple state. For bulbs we need to update light_onoff_brightness, but it's rather complex so I am not sure where to slot in the timeout.

MPM1107 commented 4 years ago

Yeah but for me it's not working on my smart plug as I said earlier, and that uses the on_off cluster. I'm just not sure why exactly it doesn't work. The sent command seems to be correct at first glance, but the plug responds with code 129 (instead of code 0 which means success).

sjorge commented 4 years ago

Ugly hack that probably needs refining:

Add this to toZigbee

    on_with_timeout: {
        key: ['on_with_timeout'],
        convertSet: async (entity, key, value, meta) => {
            const timeout = (parseInt(value) * 10);
            await entity.command('genOnOff', 'onWithTimedOff', {ctrlbits: 0, ontime: timeout, offwaittime: 0});
        },
    },

modify devices.js

You can then use 'on_with_timeout' instead of 'state' to turn on the bulb for X seconds.

Seems to work for both Innr and Trådfri (thats what I currently have paired)

mosquitto_pub -h localhost -p 1883 -t zigbee2mqtt/bedroom/bed_lamp/bulb/set/on_with_timeout -m 60

sjorge commented 4 years ago

@Koenkk is this something we want to support? It's not super hard to do, but the name 'on_with_timeout' is pretty meh. Perhaps 'timed_on' or something?

MPM1107 commented 4 years ago

Can confirm that this light "hack" works on my Osram ceiling light as well. Seems like just the plugs don't want to behave, your code is probably correct.

Koenkk commented 4 years ago

@sjorge I think it's fine to add this, but this should be added to the existing light_onoff_brightness converters to guarantee a correct state in that converter. I think the api would be something like {"state": "on_with_timed_off", "on_time": 10, "off_time": 10}. Something that also has to be checked is that the state is correct after the on time.

sjorge commented 4 years ago

I had a look at light_onoff_brightness, but it is already a rather complex best. ow do we get the on_time / off_time values? as AFAIK it only deals with the actually value inside 'state' that is passed. (so value is OFF, ON, TOGGLE) while adding some debug logging.

Koenkk commented 4 years ago

@sjorge the converter is indeed complex (but well covered with unit tests), but not adding it here will give problems later on with the state. The other properties of the message can be accessed with e.g. meta.message.on_time.

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

rindlerblabla commented 2 years ago

I am interested in this feature as well, for the same reason as the OP, to send an off command to a water pump which is connected to a wallplug at the same time as the on command is sent. Guess this was never implemented?

sjorge commented 2 years ago

https://github.com/Koenkk/zigbee-herdsman-converters/blob/c86277134c0af0bb5744977be594bb5ab1725ec8/converters/toZigbee.js#L244-L276 It's already supported, you can pass on_time or off_wait_time as additional attributes in the payload

rindlerblabla commented 2 years ago

Ah, cool! Didn't find it since it's called on_time. Obviously https://github.com/Koenkk/zigbee-herdsman-converters/pull/2326 gives some more info about it.

uncle-fed commented 6 months ago

@sjorge, @Koenkk : is there a way to cancel the active on_time timer when it is already set within the device? One way of doing it would be to turn the device off. But according to the specs mentioned here, If this attribute is set to 0x0000 or 0xffff, the device SHALL remain in its current state.. When I try to issue the command {"state":"on","on_time":10} and then, say, after 3 seconds, issue another one like {"state":"on","on_time":0}, the second command seems to be ignored and device turns off in 10 seconds anyway.