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

Fix various logic for ZBT-CCTSwitch-D0001 + add new device Ecosmart-ZBT-A19-CCT-Bulb #826

Merged
merged 686 commits into from
Dec 23, 2019

Conversation

qosmio
Copy link
Contributor

@qosmio qosmio commented Dec 19, 2019

UPDATE: Updated PR to now also include hold/release for color_temp. No. 5 below

This is an attempt to enhance PR #822. Currently, it only maps generic actions to the buttons (i.e. 'button_1','button_2', etc). It should align with the actions/clicks corresponding to what's listed on the remote (power on/off, brightness up/down/hold/release, color_temp up/down, recall command sequence). This will also provide easier integration into various automation platforms (HA/Mozilla)

The following is the new behavior:
1.) button_1 -> click: power_(on|off)
2.) button_2 -> action: brightness_(up|down) (brightness value is also provided)
3.) button_2 (hold/release) -> brightness_(up|down)_(hold|release) (NEW FEATURE)
4.) button_3 -> click: color_temp_(up|down) (color temp value is also provided)
5.) button_3 (hold/release) -> color_temp_(up|down)_(hold|release) (NEW FEATURE)
6.) button_4 -> action: action_recall (since this is a two part command brightness/color, it will combine into one payload with brightness and color_temp)

Power On/Off

{"brightness":254,"color_temp":153,"click":"power_off"}
{"brightness":254,"color_temp":153,"click":"power_on"}

Brightness Up/Down (brightness increments/decrements in both direction rather than cycling from the beginning)

{"brightness":191,"color_temp":153,"click":"brightness_down","transition_time":0.3}
{"brightness":127,"color_temp":153,"click":"brightness_down","transition_time":0.3}
{"brightness":64,"color_temp":153,"click":"brightness_down","transition_time":0.3}
{"brightness":13,"color_temp":153,"click":"brightness_down","transition_time":0.3}
-- Then starts to increment
{"brightness":64,"color_temp":153,"click":"brightness_up","transition_time":0.3}
{"brightness":127,"color_temp":153,"click":"brightness_up","transition_time":0.3}
{"brightness":191,"color_temp":153,"click":"brightness_up","transition_time":0.3}
{"brightness":254,"color_temp":153,"click":"brightness_up","transition_time":0.3}
{"brightness":191,"color_temp":153,"click":"brightness_down","transition_time":0.3}

Brightness hold/release

{"brightness":191,"color_temp":153,"action":"brightness_down_hold"}
{"brightness":191,"color_temp":153,"action":"brightness_down_release"}
{"brightness":191,"color_temp":153,"action":"brightness_down_hold"}
{"brightness":191,"color_temp":153,"action":"brightness_down_release"}

Color Temp Up/Down (same logic as brightness up/down)

{"brightness":191,"color_temp":181,"transition_time":0.3,"click":"color_temp_down"}
{"brightness":191,"color_temp":222,"transition_time":0.3,"click":"color_temp_up"}
{"brightness":191,"color_temp":285,"transition_time":0.3,"click":"color_temp_up"}
{"brightness":191,"color_temp":370,"transition_time":0.3,"click":"color_temp_up"}
{"brightness":191,"color_temp":285,"transition_time":0.3,"click":"color_temp_down"}
{"brightness":191,"color_temp":222,"transition_time":0.3,"click":"color_temp_down"}
{"brightness":191,"color_temp":181,"transition_time":0.3,"click":"color_temp_down"}
{"brightness":191,"color_temp":153,"transition_time":0.3,"click":"color_temp_down"}
{"brightness":191,"color_temp":181,"transition_time":0.3,"click":"color_temp_up"}

Recalling from 3 brightness+color_temp settings (Remote stores 3)

{"brightness":13,"color_temp":222,"transition_time":0.3,"action":"action_recall"}
{"brightness":143,"color_temp":222,"transition_time":0.3,"action":"action_recall"}
{"brightness":254,"color_temp":153,"transition_time":0.3,"action":"action_recall"}
{"brightness":13,"color_temp":222,"transition_time":0.3,"action":"action_recall"}

NOTE: There were some cosmetic changes as well to functions to make it more readable, and corrected the manufacturer name. This remote is made by Leedarson vs. Ecosmart (Home Depot). It's just bundled in the same box.

Koenkk and others added 30 commits March 15, 2019 19:40
* Update devices.js

* Update devices.js

* update
* added Bitron Wall Thermostat

* added Bitron fromZigbee convertes

bitron_battery (should be usefull for all bitron battery powered devices)
bitron_thermostat_att_report
bitron_thermostat_dev_change

* Update fromZigbee.js

* Update devices.js

* Update devices.js

* Update devices.js

* Update fromZigbee.js

* Update toZigbee.js

i changed the calculation of the input values so that you can also set half degrees.

before: 
21.3 -> 21.0
21.4 -> 21.0
21.5 -> 22.0

now:
21.3 -> 21.5
21.4 -> 21.5
21.5 -> 21.5

* Update fromZigbee.js

* added temperature_display_mode + running_state

* Update devices.js

* Trailing spaces

* Update fromZigbee.js

* Update fromZigbee.js

* Update fromZigbee.js

* Update devices.js

* Update devices.js
* GLEDOPTO GL-B-008Z

* HGZB-20-DE

https://www.amazon.de/Intelligente-Steckdose-kompatibel-SmartThings-Steuerung/dp/B07GYG5GRP

* Update devices.js

* Update devices.js

* Update devices.js

* FB56-ZCW11HG1.2

https://uploads.tapatalk-cdn.com/20190106/dcf0f59343233342d004319d05c04bc0.jpg

* FB56-ZCW11HG1.2

* FB56-ZCW11HG1.2

i miss this device

* rebase

* Update devices.js
* Update device.js to include Nue HGZB-01A

Device is a mains inline zigbee light controller with brightness.
Amazon link:
https://www.amazon.com/gp/product/B07FBD96DG/ref=oh_aui_detailpage_o01_s00?ie=UTF8&psc=1

Added and tested device per: https://koenkk.github.io/zigbee2mqtt/how_tos/how_to_support_new_devices.html

* remove extranious tab that fails check

* Fixed the rest of the tabs - still figuring this out... sorry.

* adding space back...

* Update devices.js
* Add device support for iCasa Zigbee 3.0 Dimmer

* Update devices.js
* Add Iris 3326-L motion and temperature sensor

* Use scoped zcl-id

* Only package needed files

* refactor: Use a Map for findByZigbeeModel

* Add `extend` attribute

The attribute shallow-merges the model object,
with the explicitly defined properties taking precidence
Converted all uses of `generic` to `extend` format.
* added Hue Power-on Converter

tested with GU10.
needs Firmware 1.46

hue_power_on_behavior
// default / enable / disable / configure hue_power_on_behavior, 
// default = 1
// on - lamps on after power loss with configured brightness, color temperature, color
// off - lamps off after power loss
// recover - lamps on after power loss with last state 

hue_power_on_brightness
// brightness when hue_power_on_behavior = on, same settings as brightness converter
// default = 255

hue_power_on_color_temperature
// color temperature when hue_power_on_behavior = on, same settings as colortemp converter
// default = 366

to-do:
hue_power_on_color (must be added by someone with appropriate bulb)

* Update toZigbee.js

* Hue specific settings

generic hue settings with power-on converter

* Update toZigbee.js

* fixed 'null' values

as discussed here: Koenkk/zigbee2mqtt#848

* Update fromZigbee.js

* Update fromZigbee.js

* Update toZigbee.js

* Update devices.js

* Update devices.js
* fixed 'null' values in thermostat converters.

as discussed here: Koenkk/zigbee2mqtt#848

* Update fromZigbee.js

* Update fromZigbee.js

* Update fromZigbee.js

* Update fromZigbee.js

* Update fromZigbee.js

* Update fromZigbee.js
Qosmio added 9 commits March 16, 2019 19:21
Also updates the brightness hold/release logic.
Fixes the following error
FAIL  test/index.test.js
  index.js
    ✓ Find device by model ID (6ms)
    ✓ Find device by model ID with strange characters 1 (1ms)
    ✓ Find device by model ID with strange characters 2
    ✓ Find device by model ID with strange characters 3 (1ms)
    ✓ Find device by model ID without strange characters (1ms)
    ✓ Find device by model ID null (1ms)
    ✕ Verify devices.js definitions (132ms)

  ● index.js › Verify devices.js definitions

    TypeError: Cannot convert undefined or null to object
        at Function.keys (<anonymous>)

      63 |                 const converter = device.fromZigbee[converterKey];
      64 |
    > 65 |                 const keys = Object.keys(converter);
         |                                     ^
      66 |                 verifyKeys(['cluster', 'type', 'convert'], keys, converterKey);
      67 |
      68 |                 if (4 != converter.convert.length) {

      at keys (test/index.test.js:65:37)
          at Array.forEach (<anonymous>)
      at forEach (test/index.test.js:62:44)
          at Array.forEach (<anonymous>)
      at Object.forEach (test/index.test.js:47:17)
@verjus
Copy link
Contributor

verjus commented Dec 20, 2019

This is great. Amazingly, I've been working on a very similar approach. However, it doesn't try to merge the actions of button 3 and 4. The reason being that this was how the remote was designed (button 4 is a memory recall that can be programmed to hold 3 states) and allows for greater generality. This is of interest to me since I use Node Red as my main platform, giving me full control over the messages.

Here is the alternative implementation:

Button 1 (Pressed Twice):

zigbee2mqtt/EcoSmart_1 {"brightness":127,"linkquality":68,"state":"OFF","color_temp":222,"button":"On/Off"}
zigbee2mqtt/EcoSmart_1 {"brightness":127,"linkquality":70,"state":"ON","color_temp":222,"button":"On/Off"}

Button 2 (Pressed 8 times)

zigbee2mqtt/EcoSmart_1 {"brightness":191,"linkquality":68,"state":"ON","color_temp":222,"button":"DIM","transition_time":3,"action":"DIM_UP"}
zigbee2mqtt/EcoSmart_1 {"brightness":127,"linkquality":76,"state":"ON","color_temp":222,"button":"DIM","transition_time":3,"action":"DIM_DOWN"}
zigbee2mqtt/EcoSmart_1 {"brightness":64,"linkquality":76,"state":"ON","color_temp":222,"button":"DIM","transition_time":3,"action":"DIM_DOWN"}
zigbee2mqtt/EcoSmart_1 {"brightness":13,"linkquality":76,"state":"ON","color_temp":222,"button":"DIM","transition_time":3,"action":"DIM_DOWN"}
zigbee2mqtt/EcoSmart_1 {"brightness":64,"linkquality":76,"state":"ON","color_temp":222,"button":"DIM","transition_time":3,"action":"DIM_UP"}
zigbee2mqtt/EcoSmart_1 {"brightness":127,"linkquality":73,"state":"ON","color_temp":222,"button":"DIM","transition_time":3,"action":"DIM_UP"}
zigbee2mqtt/EcoSmart_1 {"brightness":191,"linkquality":81,"state":"ON","color_temp":222,"button":"DIM","transition_time":3,"action":"DIM_UP"}
zigbee2mqtt/EcoSmart_1 {"brightness":254,"linkquality":78,"state":"ON","color_temp":222,"button":"DIM","transition_time":3,"action":"DIM_UP"}

Button 3 (Pressed 7 times)

zigbee2mqtt/EcoSmart_1 {"brightness":254,"linkquality":78,"state":"ON","color_temp":285,"button":"CCT","transition_time":3,"action":"CT_UP"}
zigbee2mqtt/EcoSmart_1 {"brightness":254,"linkquality":86,"state":"ON","color_temp":370,"button":"CCT","transition_time":3,"action":"CT_UP"}
zigbee2mqtt/EcoSmart_1 {"brightness":254,"linkquality":89,"state":"ON","color_temp":285,"button":"CCT","transition_time":3,"action":"CT_DOWN"}
zigbee2mqtt/EcoSmart_1 {"brightness":254,"linkquality":86,"state":"ON","color_temp":222,"button":"CCT","transition_time":3,"action":"CT_DOWN"}
zigbee2mqtt/EcoSmart_1 {"brightness":254,"linkquality":92,"state":"ON","color_temp":181,"button":"CCT","transition_time":3,"action":"CT_DOWN"}
zigbee2mqtt/EcoSmart_1 {"brightness":254,"linkquality":89,"state":"ON","color_temp":153,"button":"CCT","transition_time":3,"action":"CT_DOWN"}
zigbee2mqtt/EcoSmart_1 {"brightness":254,"linkquality":92,"state":"ON","color_temp":181,"button":"CCT","transition_time":3,"action":"CT_UP"}

Button 4 (Pressed Once)

zigbee2mqtt/EcoSmart_1 {"brightness":254,"linkquality":94,"state":"ON","button":"MEMORY","transition_time":0,"action":"MEM_RECALL"}
zigbee2mqtt/EcoSmart_1 {"brightness":254,"linkquality":94,"state":"ON","color_temp":153,"button":"VIRTUAL","transition_time":3,"action":"MEM_RECALL"}

Note that Button 4 generates TWO messages. This is how the remote was designed. However, one message may be attributed to a physical button (button: MEMORY) and the other to a virtual button (button: VIRTUAL)

In my opinion, this provides the most general use case for this remote. The underlying functionality of the remote is preserved, including state and memory recall. In addition, it is possible to identify the physical buttons begin pushed by looking for buttons ON/OFF, DIM, CCT and MEMORY and ignoring messages associated with button VIRTUAL.

I've also come up with an elegant way of differentiating between button 3 and button 4 messages that does not rely on sequence number and is much simpler:

   // Button 1
   ZBT_CCTSwitch_D0001_cmdOnOff: {
       cluster: 'genOnOff',
       type: ['commandOn', 'commandOff'],
       convert: (model, msg, publish, options) => {
         let cmd = null;
         if ( msg.type === 'commandOn' ) {
             cmd = 'ON';
         } else if ( msg.type === 'commandOff' ) {
             cmd = 'OFF';
         }
         return {state: cmd, button: 'On/Off'}
       },
   },

       // Button 2
   ZBT_CCTSwitch_D0001_moveToLevel: {
       cluster: 'genLevelCtrl',
       type: ['commandMoveToLevel'],
       convert: (model, msg, publish, options) => {
           const deviceID = msg.device.ieeeAddr;
           const level = msg.data.level;

           if (!store[deviceID]) {
               store[deviceID] = {last_colortemp: 0, last_level: level};
           }
           let action = null;
           if (level > store[deviceID].last_level) {
               action = "DIM_UP";
           } else if (level < store[deviceID].last_level) {
               action = "DIM_DOWN";
           }

           store[deviceID].last_level = level;

           return {
                   button: 'DIM',
                   brightness: msg.data.level,
                   transition_time: msg.data.transtime,
                   action: action,
           }
       },
   },    
      // Button 3
   ZBT_CCTSwitch_D0001_moveToColorTemp: {
       cluster: 'lightingColorCtrl',
       type: ['commandMoveToColorTemp'],
       convert: (model, msg, publish, options) => {
           const deviceID = msg.device.ieeeAddr;
           const colortemp = msg.data.colortemp;
           const last_colortemp = store[deviceID].last_colortemp;

           if (!store[deviceID]) {
               store[deviceID] = {last_colortemp: colortemp, last_level: 0};
           }
           let button = 'CCT';
           let action = null;
           if (last_colortemp == null) {
               // This is coming from button 4
               action = 'MEM_RECALL';
               button = 'VIRTUAL';
           } else if (colortemp > last_colortemp) {
               action = "CT_UP";
           } else if (colortemp < last_colortemp) {
               action = "CT_DOWN";
           }
               // action= action + '-' + colortemp + '-' + store[deviceID].last_colortemp + '-' + deviceID;

           store[deviceID].last_colortemp = colortemp;

           return {
                   button: button,
                   color_temp: msg.data.colortemp,
                   transition_time: msg.data.transtime,
                   action: action,
           }
       },
   },

   // Button 4
   ZBT_CCTSwitch_D0001_moveToColorTempWithOnOff: {
       cluster: 'genLevelCtrl',
       type: ['commandMoveToLevelWithOnOff'],
       convert: (model, msg, publish, options) => {
           const deviceID = msg.device.ieeeAddr;
           const colortemp = msg.data.colortemp;

           if (!store[deviceID]) {
               store[deviceID] = {last_colortemp: null, last_level: 0};
           }
           // Button 4 was pressed
           store[deviceID].last_colortemp = null;

           return {
                   button: 'MEMORY',
                   color_temp: msg.data.colortemp,
                   transition_time: msg.data.transtime,
                   action: 'MEM_RECALL',
           }
       },
   },

Being late to the party, I didn't create a pull request. If there is interest in this implementation and it is accepted in the main code by @Koenkk, I would be happy to create a PR (after further cleaning up the code)

@qosmio
Copy link
Contributor Author

qosmio commented Dec 20, 2019

Thanks for the feedback! I was actually thinking of an approach similar to this as well. I wanted to try and find middle ground in trying to align with current implementation I noticed in the convertors. The one for the Philips dimmer switch is pretty extensive...

I like the 'button' and 'action' approach actually. Makes more sense. Going with that logic would it make more sense to say: button: brightness,action: brightness_up vs. button: brightness,action: up?

Since we're implying the first half of the 'action' from the button, would it be redundant? Perhaps I'm making this more complicated than it is already. My approach was to try and expose as much of the remote's actions as possible to home assistant and then work out my automation for the key presses there.

As for button 4, I'm inclined to want to merge the two command sequences mostly for a smoother transition effect. I tested paring the bulbs directly and recalling the memory commands and there's a noticeable delay in moving to the light level and then switching to color_temp. I found the approach to combine in Z2M easier than having to create a convoluted template in HA.

Anyhow, not sure any of this matters as it's not guaranteed my PR will even get merged. Waiting feedback from @Koenkk 👍

Many automations in HA are set up to split '_' based
on `attribute`, `direction`, `action`. Trying to keep
the splits consistent.
@verjus
Copy link
Contributor

verjus commented Dec 20, 2019

I agree that having both a "Button" and an "Action" message has some redundancy to it, but it allows for two separate use cases. The first being to ignore state information and only use the information of which button was pressed, making it a generic remote. I think that's what the current converter aims to to.

Just like you, and I am amazed by how our thinking is aligned, I found this too restrictive, so I looked for a way to also pass along the state information. Basically, translate the zigbee info to mqtt as closely as the remote generates it.

I agree that generating two messages, even though the remote does that by design, is problematic and that combining the two messages into one would be ideal.

@verjus
Copy link
Contributor

verjus commented Dec 21, 2019

I continued working on this and here is my latest suggested implementation. It consistently produce a action [ON, OFF, DIM_UP, DIM_DOWN, CT_UP, CT_DOWN, SCENE] and a button name [ON/OFF, DIM, CCT, MEMORY]. In addition, it includes STATES (brightness, color_temp, state, transition_time). As discussed, it only generates 1 message when button 4 is pressed (level/brightness is combined with color_temp in button 3 in from the second message). It doesn't rely on seq number and the only assumption it makes is that the messages are always in the same order (seems reasonable).

Note that when button 4 is pressed, the state is forced to ON since this is a scene setting brightness and color temp.

Button 1

{"linkquality":76,"color_temp":3700,"brightness":64,"state":"ON","action":"ON","button":"ON/OFF"}
{"linkquality":94,"color_temp":3700,"brightness":64,"state":"OFF","action":"OFF","button":"ON/OFF"}
{"linkquality":86,"color_temp":3700,"brightness":64,"state":"ON","action":"ON","button":"ON/OFF"}

Button 2

{"linkquality":97,"color_temp":3700,"brightness":13,"state":"ON","transition_time":3,"button":"DIM","action":"DIM_DOWN"}
{"linkquality":99,"color_temp":3700,"brightness":64,"state":"ON","transition_time":3,"button":"DIM","action":"DIM_UP"}
{"linkquality":102,"color_temp":3700,"brightness":127,"state":"ON","transition_time":3,"button":"DIM","action":"DIM_UP"}
{"linkquality":102,"color_temp":3700,"brightness":191,"state":"ON","transition_time":3,"button":"DIM","action":"DIM_UP"}
{"linkquality":105,"color_temp":3700,"brightness":254,"state":"ON","transition_time":3,"button":"DIM","action":"DIM_UP"}
{"linkquality":102,"color_temp":3700,"brightness":191,"state":"ON","transition_time":3,"button":"DIM","action":"DIM_DOWN"}
{"linkquality":105,"color_temp":3700,"brightness":127,"state":"ON","transition_time":3,"button":"DIM","action":"DIM_DOWN"}
{"linkquality":102,"color_temp":3700,"brightness":64,"state":"ON","transition_time":3,"button":"DIM","action":"DIM_DOWN"}
{"linkquality":102,"color_temp":3700,"brightness":13,"state":"ON","transition_time":3,"button":"DIM","action":"DIM_DOWN"}

Button 3

{"linkquality":99,"color_temp":2220,"brightness":13,"state":"ON","transition_time":3,"button":"CCT","action":"CT_DOWN"}
{"linkquality":99,"color_temp":1810,"brightness":13,"state":"ON","transition_time":3,"button":"CCT","action":"CT_DOWN"}
{"linkquality":99,"color_temp":1530,"brightness":13,"state":"ON","transition_time":3,"button":"CCT","action":"CT_DOWN"}
{"linkquality":99,"color_temp":1810,"brightness":13,"state":"ON","transition_time":3,"button":"CCT","action":"CT_UP"}
{"linkquality":99,"color_temp":2220,"brightness":13,"state":"ON","transition_time":3,"button":"CCT","action":"CT_UP"}
{"linkquality":97,"color_temp":2850,"brightness":13,"state":"ON","transition_time":3,"button":"CCT","action":"CT_UP"}
{"linkquality":94,"color_temp":3700,"brightness":13,"state":"ON","transition_time":3,"button":"CCT","action":"CT_UP"}
{"linkquality":94,"color_temp":2850,"brightness":13,"state":"ON","transition_time":3,"button":"CCT","action":"CT_DOWN"}

Button 4

This button recalls 3 stored states of [brightness, color_temp], aka a SCENE

{"linkquality":113,"color_temp":1530,"brightness":254,"state":"ON","transition_time":3,"button":"MEMORY","action":"SCENE"}
{"linkquality":110,"color_temp":2220,"brightness":127,"state":"ON","transition_time":3,"button":"MEMORY","action":"SCENE"}
{"linkquality":113,"color_temp":3700,"brightness":13,"state":"ON","transition_time":3,"button":"MEMORY","action":"SCENE"}

The implementation is now very tidy:


    // Button 1
    ZBT_CCTSwitch_D0001_cmdOnOff: {
        cluster: 'genOnOff',
        type: ['commandOn', 'commandOff'],
        convert: (model, msg, publish, options) => {
	  let cmd = null;
	  if ( msg.type === 'commandOn' ) {
              cmd = 'ON';
	  } else if ( msg.type === 'commandOff' ) {
	      cmd = 'OFF';
	  }
	  return {state: cmd, action: cmd, button: 'ON/OFF'}

        },
    },

    // Button 2
    ZBT_CCTSwitch_D0001_moveToLevel: {
        cluster: 'genLevelCtrl',
        type: ['commandMoveToLevel'],
        convert: (model, msg, publish, options) => {
	    const deviceID = msg.device.ieeeAddr;
	    const level = msg.data.level;

	    if (!store[deviceID]) {
		store[deviceID] = {lastColortemp: null, lastLevel: null, thisLevel: null};
	    }
	    let action = null;
	    if (level > store[deviceID].lastLevel) {
		action = "DIM_UP";
	    } else if (level < store[deviceID].lastLevel) {
		action = "DIM_DOWN";
	    }

	    store[deviceID].lastLevel = level;

	    return {
		    brightness: level,
		    transition_time: msg.data.transtime,
		    button: 'DIM',
		    action: action,
	    }
        },
    },    
    
    // Button 3
    ZBT_CCTSwitch_D0001_moveToColorTemp: {
        cluster: 'lightingColorCtrl',
        type: ['commandMoveToColorTemp'],
        convert: (model, msg, publish, options) => {
	    const deviceID = msg.device.ieeeAddr;
	    const colortemp = msg.data.colortemp;


	    if (!store[deviceID]) {
		store[deviceID] = {lastColortemp: null, lastLevel: null, thisLevel: null};
	    }

	    let payload = null;

	    if (store[deviceID].thisLevel != null) {
		// Button 4 was pressed

		payload= {
		    color_temp: msg.data.colortemp * 10,
		    brightness: store[deviceID].thisLevel,
		    transition_time: msg.data.transtime,
		    state: 'ON',
		    button: 'MEMORY',
		    action: 'SCENE',
		}

 		store[deviceID].thisLevel = null;

	    } else {
		// Button 3 was pressed

		let action = null;
		if (colortemp > store[deviceID].lastColortemp) {
		    action = "CT_UP";
		} else if (colortemp < store[deviceID].lastColortemp) {
		    action = "CT_DOWN";
		}
		
		store[deviceID].lastColortemp = colortemp;

		payload= {
		    color_temp: msg.data.colortemp * 10,
		    transition_time: msg.data.transtime,
		    button: 'CCT',
		    action: action,
		}
	    }
	    return payload;
	},
    },

    // Button 4
    ZBT_CCTSwitch_D0001_moveToColorTempWithOnOff: {
        cluster: 'genLevelCtrl',
        type: ['commandMoveToLevelWithOnOff'],
        convert: (model, msg, publish, options) => {
	    const deviceID = msg.device.ieeeAddr;

	    if (!store[deviceID]) {
		store[deviceID] = {lastColortemp: null, lastLevel: null, thisLevel: null};
	    }
	    
            // Store level for button 3
	    store[deviceID].thisLevel = msg.data.level;

	    return null;
        },
    },


@qosmio
Copy link
Contributor Author

qosmio commented Dec 21, 2019

Looks great! I cleaned up my PR a little more to align with the 'button' and 'action' approach from your suggestion. I didn't mess with the actual values being sent for brightness/color_temp. Trying to leave that as close to the original values as possible.

I was debating with calling the recall button "scenes" as well, but that'd be a misnomer as scenes are handled different in the Zigbee spec, and didn't want to confuse the two. Even though it's technically a "scene".

I've also implemented the hold and release functions for brightness/color_temp.

Button 1 (power on/off)

{"button":"power","action":"on"}
{"button":"power","action":"off"}

Button 2 (brightness up/down hold/release)

up/down

{"brightness":127,"transition":0.3,"button":"brightness","action":"brightness_up"}
{"brightness":191,"transition":0.3,"button":"brightness","action":"brightness_up"}
{"brightness":254,"transition":0.3,"button":"brightness","action":"brightness_up"}

{"brightness":191,"transition":0.3,"button":"brightness","action":"brightness_down"}
{"brightness":127,"transition":0.3,"button":"brightness","action":"brightness_down"}
{"brightness":64,"transition":0.3,"button":"brightness","action":"brightness_down"}

hold/release

{"button":"brightness","action":"brightness_down_hold"}
{"button":"brightness","action":"brightness_down_release","duration":780}
{"button":"brightness","action":"brightness_up_hold"}
{"button":"brightness","action":"brightness_up_release","duration":1782}

Button 3 (colortemp up/down hold/release)

up/down

{"colortemp":222,"transition":0.3,"button":"colortemp","action":"colortemp_up"}
{"colortemp":254,"transition":0.3,"button":"colortemp","action":"colortemp_up"}
{"colortemp":285,"transition":0.3,"button":"colortemp","action":"colortemp_up"}
{"colortemp":370,"transition":0.3,"button":"colortemp","action":"colortemp_up"}

{"colortemp":285,"transition":0.3,"button":"colortemp","action":"colortemp_down"}
{"colortemp":254,"transition":0.3,"button":"colortemp","action":"colortemp_down"}
{"colortemp":22,"transition":0.3,"button":"colortemp","action":"colortemp_down"}

hold/release

{"button":"colortemp","action":"colortemp_down_hold"}
{"button":"colortemp","action":"colortemp_down_release","duration":780}
{"button":"colortemp","action":"colortemp_up_hold"}
{"button":"colortemp","action":"colortemp_up_release","duration":1782}

Button 4 (memory recall)

{"color_temp":222,"transition":0.3,"button":"memory","action":"recall","brightness":127}
{"color_temp":153,"transition":0.3,"button":"memory","action":"recall","brightness":254}
{"color_temp":370,"transition":0.3,"button":"memory","action":"recall","brightness":191}

@verjus
Copy link
Contributor

verjus commented Dec 21, 2019

Sounds good!

Note that I've also added the state to on/off in the messages. It's redundant for button 1, but it allows for the on/off state to be kept in the other messages, since action gets over-written. Also, in the Scene/Recall mode, that state is forced to ON, which is what you would expect it to do.

Also, I noticed that you divided the transition_time by 10. Why is that? I looked at the other converter and none do this.

I did modify one of the input, which the the color temperature to make it to Kelvin but just noticed that I should have multiplied color_temp by 17.6 (instead of 10), because the bulb ranges from 2700K to 6500K and the color_temp ranges form 153 to 370, which means Temp in Kelvin = color_temp * 17.6. Not sure about the value of sending Kelvins instead of current 8 bit value.

BTW, do you know if the remote sends out battery state? I didn't see any messages related to it and find that surprising because most battery devices do.

Maybe I'll submit an alternative pull request and let @Koenkk decide which one he wants to go with (I think that handling the messages separately as done above makes the code more tidy and readable).

@qosmio
Copy link
Contributor Author

qosmio commented Dec 21, 2019

Note that I've also added the state to on/off in the messages. It's redundant for button 1, but it allows for the on/off state to be kept in the other messages, since action gets over-written.

I left the state portion out since z2m sends out the state for any commands that require it (i.e. brightness, color_temp, color, etc.).

Also, I noticed that you divided the transition_time by 10. Why is that? I looked at the other converter and none do this.

So, for most of my code I was actually using a sniffer to see the actual codes being sent. The remote sends out a transition time, but it sends it as "0.3" seconds in Wireshark. I converted it back to the original. Perhaps there's a reason @Koenkk is setting it to 3 seconds? I'm not sure. But I tried to keep that value as is. As I'd like my actions to happen instantly and again, close to what the remote is sending.

BTW, do you know if the remote sends out battery state? I didn't see any messages related to it and find that surprising because most battery devices do.

My initial PR had a generic battery state, however upon inspection of the traffic from the remote I didn't see any mention of battery. Which is really odd for battery devices. I'll keep checking the traffic to see if it just reports it much later.

@qosmio
Copy link
Contributor Author

qosmio commented Dec 21, 2019

Just adding to this. I know you said you're currently using Node Red for your automation, but figured others using Home Assistant might want to use this in their setup.

Overview

This setup assumes naming your remotes the same as your devices/groups but with the topic "remote" preceding it. It also assumes the topic being published to is zigbee2mqtt/

Example 1: (control single device)
device: zigbee2mqtt/bulb/bedroom_lamp_bulb_1
remote: zigbee2mqtt/remote/bedroom_lamp_bulb_1

Example 2: (control a group)
group: zigbee2mqtt/group/kitchen_track
remote: zigbee2mqtt/remote/group/kitchen_track

I name my device/groups with their respective category as preceding topics as it helps me identify it better in Home Assistant for various trigger/automation.

- id: 'cct_switch_d0001'
  alias: Zigbee Remote
  trigger:
  - platform: mqtt
    topic: zigbee2mqtt/remote/#
  condition:
  - condition: and
    conditions:
    - condition: template
      value_template: '{{ trigger.payload_json[''action''] | length > 1 }}'
    - condition: template
      value_template: '{{ ''hold'' not in trigger.payload_json[''action''] and ''release''
        not in trigger.payload_json[''action''] }}'
  action:
  - data_template:
      payload_template: |
        {% set remote_payload = {} %}
        {% if 'action' in trigger.payload -%}
          {% if trigger.payload_json.click == 'power' -%}
            {% set remote_payload = {"state": "toggle" } %}
          {% else %}
            {% set remote_payload = {"brightness": trigger.payload_json.brightness,"color_temp": trigger.payload_json.color_temp} %}
          {%- endif %}
          {{ remote_payload | tojson }}
        {%- else -%}
          {{ trigger.payload }}
        {%- endif %}
      topic: '{{ "zigbee2mqtt/" + trigger.topic.split(''/'')[2:] | join(''/'') + "/set" }}'
    service: mqtt.publish
  initial_state: true

NOTE: This will perform all functions of the remote EXCEPT the "hold/release" actions. That requires a little more work and likely be an App Daemon or scripted.

@qosmio
Copy link
Contributor Author

qosmio commented Dec 21, 2019

Last update. Reverting the button behavior to click as the home assistant extension in Z2M does not have mappings for buttons.

@Koenkk
Copy link
Owner

Koenkk commented Dec 23, 2019

Thanks!

@qosmio
Copy link
Contributor Author

qosmio commented Dec 23, 2019

Thanks!

Thanks for merging this in! Currently working on getting the brightness/color_temp hold+release working for the bulbs. Will push out a PR for that next.

@mbafford
Copy link
Contributor

I wish github had notified me of this whole conversation - I was just about to rework my PR along the same general lines as this one. I contributed #822 , but it was definitely a quick and dirty first pass, and I wasn't happy with it. It was bothering me that data from the remote was being thrown away. I'm happy to see you all followed-through and cleaned it up and added the functionality to please both mindsets (i.e. receiving actual commands vs. the button numbers) around using this remote.

As for battery, SmartThings and Hubitat both can receive battery notices with some of the generic Zigbee handlers. I attempted to write a native Hubitat handler before understanding that group messaging wasn't supported by Hubitat - and I did get it reporting battery, but only after I sent binding requests (probably to the standard power cluster 0x0001, since one of the generic handlers on Hubitat also received battery messages).

xmow49 pushed a commit to xmow49/zigbee-herdsman-converters that referenced this pull request Jun 14, 2024
* Cleanup: variable rename

* Cleanup: move requestQueue to separate file, rename variables

* Adapt data types in tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet