Turning buttons into dimmers – Xiaomi MiHome Switch in NodeRED

Click to A, Double Click to B, and much more

A button can be more than just a click. It could be at least three things, as shown in this Xiaomi MiHome Switch overview. I have even more freedom in interpreting button behaviour thanks to Xiaomi MiHome Switch in NodeRED. After all, if I’m going to spend the kindly donated funds onto something – I will squeeze out every possible functionality out of it.

Smart dimming – Xiaomi MiHome Switch in NodeRED

There are a couple of ways I can approach this problem. The simplest of options would be to cycle through the presets with one of the click functions. I want to preserve the clicks for other options.  Clicks are far too precious for that. In this article, I will show you how to map Xiaomi MiHome Switch in NodeRED and use all available actions. To showcase this I will use my trusty Yeelight Smart bulbs which are just plain awesome.

Support NotEnoughTech
Buy Yeelight RGB LED bulb 

I will showcase how to add the following options to the Xiaomi Aqara Switch in NodeRED:

  • click – toggle lights (retain previous values)
  • double click – cycle through temperature presets (retaining other values)
  • press & hold – smart dimmer (changing brightness up/down)

bonus:

  • press & hold – select different actions based on the length of a press

Because of the Xiaomi MiHome Switch has to be linked to Xiaomi MiHome Gateway, you will need as well. I will assume you have read that article and you know how the Xiaomi gadgets work with NodeREd already. If not – you may want to brush up on that knowledge.

Click… ON, Click… OFF

The toggle is the most basic behaviour you could imagine. To my surprise, the toggle behaviour is not available to IKEA Trådfri devices connected to MiHome. I’m sure the support will come soon, but for now, you have to map 2 actions to turn the lights on and off. Yeelight posses no such issues, and I had implemented a toggle like that previously in my Yeelight & Amazon Dash write up.

The xiaomi switch node in node red comes with a couple of options. If you want to map just the click and double-click, the template will make it easy, but that option won’t return any meaningful data for long presses. Setting it to just values gives us what we need, without dropping support for the press & hold functionality!

{
	"status": "click",
	"voltage": 3.092,
	"voltage_level": "high",
	"time": 1547997692968,
	"device": "Button"
}

I’m checking msg.payload.status to filter out unwanted behaviour, but before I can apply my actions, I want to see what is the current state of my Yeelight. Bear in mind that if you have more than one lightbulb, you will have to pick one which you are going to monitor. The other bulbs will get their statuses overridden. The best way to pass these values over is to save them as flow variables. I know I will use more than the current status so I’m saving them in bulk with a function node:

FUNCTION NODE: Update variables
flow.set('yeelight1_on',msg.payload.state.on);
flow.set('yeelight1_bri',msg.payload.state.bri);
flow.set('yeelight1_colormode',msg.payload.state.colormode);
flow.set('yeelight1_ct',msg.payload.state.ct);
flow.set('yeelight1_hex',msg.payload.state.hex);
flow.set('yeelight1_hue',msg.payload.state.hue);
flow.set('yeelight1_sat',msg.payload.state.sat);

Once I know the current values, I can perform the opposite action to create the toggle behaviour. Because I’m dealing with more than just one value, I’m going to use another function node for that.

FUNCTION NODE: toggle
var on = flow.get('yeelight1_on');
var bri = flow.get('yeelight1_bri');
var colormode = flow.get('yeelight1_colormode');
var ct = flow.get('yeelight1_ct');
var hex = flow.get('yeelight1_hex');
var hue = flow.get('yeelight1_hue');
var sat = flow.get('yeelight1_sat');

if(on === true){
    var x = false;
}

if(on === false){
    var x = true;
    }
msg.payload = {
        "on": x,
        "bri": bri,
        "colormode": colormode,
        "ct": ct,
        "hex": hex,
        "hue": hue,
        "sat": sat
    };

return msg;

If you monitor more than one lightbulb you have to make sure that you won’t end up in the not toggable state. Each time I create an object with all the variables stored previously. This way, the Yeelight will change just the ON|OFF state, keeping all the previous values intact.

Lastly, all I need is to send this msg.payload to the Yeelight node to send the new command.

Click, click… action, click, click… action

Since the double click is also available, I want to toggle through my preferred colour values. I’m not going to use anything outlandish (reds, blues, greens). I just want to modify the temperature of the lights to suit the mood and situation. I will create 3 presets, but following this example you can map a virtually unlimited number of presets.

I’m going to use the same flow variables as in the click option, so I don’t have to recreate the Yeelight update flow. A simple switch node with “double_click” string will filter out all the messages sent by Xiaomi MiHome Switch in NodeRED.

{
	"status": "double_click",
	"voltage": 3.092,
	"voltage_level": "high",
	"time": 1547999032815,
	"device": "Button"
}

Then I picked three prefered presets, and modified the JSON files accordingly. Each preset has temperature & colour, while other values are pulled once again from the previously saved flow variables:

msg.payload = {
	"on": true,
	"bri": bri,
	"colormode": "ct",
	"ct": 1902,
	"hex": "#FF8400",
	"hue": hue,
	"sat": sat}

To toggle through the presets, I will iterate a number which is stored in a context variable. Each time the payload reaches this function node the x increases by one. Then I simply set hardcoded values as msg.payload and send it to the Yeelight node. The x resets to 1 in the last preset, so it would circle through again.

FUNCTION NODE: Select Preset
var x = flow.get('color_preset');
var bri = flow.get('yeelight1_bri');
var ct;
var hex;
var colormode = "ct";
var hue = flow.get('yeelight1_hue');
var sat = flow.get('yeelight1_sat');

if(x === null || x === undefined|| x == 1 || x > 3){
    ct  = 1902;
	hex = "#FF8400";
	x=1;
	}
if(x == 2){
    ct  = 5347;
	hex = "#FFFCF6";
	}
if(x == 3){
    ct  = 6600;
	hex = "#FFEFE1";
	}	
	

msg.payload = {
	"on": true,
	"bri": bri,
	"colormode": "ct",
	"ct": ct,
	"hex": hex,
	"hue": hue,
	"sat": sat
}; 	
x++;
flow.set('color_preset', x);
msg.xvalue = x;
return msg;

You could use arrays to store the presets as well, but for such a small number of presets, this method is efficient enough.

Smart Dimmer

The most time-consuming task was to figure out how to dim lights using Xiaomi MiHome Switch in NodeRED. I had to overcome some limitations to combat the timeouts of the Yeelight node and create a dimmer that will recognise what I am trying to do. I settled for a dimmer that works like this:

  • increase/decrease brightness in 8 steps over 4 seconds
  • save brightness value when the button is released
  • provide the visual feedback while a button is pressed
  • works with multiple lights
  • retains other values like colour or temperature
  • dims lights if brightness is set above 50%
  • increases lights if brightness is below 50%

I’m really pleased with the result. The number of steps and the timing can be modified a little as long as you don’t spam the Yeelight node too much with constant updates.

Support NotEnoughTech

Trigger

When the button is pressed down for longer than what would constitute a click it, Xiaomi MiHome Switch in NodeRED issues two payloads. Once is associated with the button pressed action, the other one appears when the button is released:

{
	"status": "long_click_press",
	"voltage": 3.082,
	"voltage_level": "high",
	"time": 1548000078945,
	"device": "Button"
}
{
	"status": "long_click_release",
	"voltage": 3.082,
	"voltage_level": "high",
	"time": 1548000078998,
	"device": "Button"
}

I can use this to start iterating through the brightness steps until the button is released. I talked about controlling the flows in my NodeRED for beginners: Tip & Tricks. I can start the flow (“true”) when the button is pressed and stop (“false”) it when the button is released.

Smart dimming

When the button is pressed down, I want to check the initial brightness value. I can do this by looking up the global variable against simple IF statement:

FUNCTION NODE: Read Bri
var x = flow.get('yeelight1_bri')

if(x <= 125){ 
    msg.payload = x; 
    return[msg,null]; 
} 
if(x > 125){
    msg.payload = x;
    return[null,msg];
}

Note, that I’m only checking the value of a single Yeelight bulb. I will override the other bulbs.

 

Dim/Bright up loop

The main function iterates the brightness levels – starting from the saved in the variable level, and iterating up or down depending on the initial value. I picked 30 as a reasonable modifier. I wanted to have a reasonable degree of control and not wait forever for the lights to go from 0-254.

The script isn’t complicated, but I had to add the limiting conditions for where the brightness value would fall outside of the 0-254 range.

FUNCTION NODE: brightness up
var colormode = flow.get('yeelight1_colormode');
var ct = flow.get('yeelight1_ct');
var hex = flow.get('yeelight1_hex');
var hue = flow.get('yeelight1_hue');
var sat = flow.get('yeelight1_sat');

var brightness =  msg.payload;

if(brightness > 0){
    brightness = brightness + 30;    
	}
	
if(brightness >= 255){
    brightness = 254;
    flow.set('yeelight1_bri',brightness);
    flow.set('press', false);
    }

msg.payload = {
        "on": true,
        "bri": brightness,
        "colormode": colormode,
        "ct": ct,
        "hex": hex,
        "hue": hue,
        "sat": sat
    };
	msg.feedback = brightness;
	flow.set('yeelight1_bri', brightness);
return msg;
FUNCTION NODE: brightness down
var colormode = flow.get('yeelight1_colormode');
var ct = flow.get('yeelight1_ct');
var hex = flow.get('yeelight1_hex');
var hue = flow.get('yeelight1_hue');
var sat = flow.get('yeelight1_sat');

var brightness =  msg.payload;

if(brightness <= 255){
    brightness = brightness - 30;    
	}
	
if(brightness <= 0){
    brightness = 1;
    flow.set('yeelight1_bri',brightness);
    flow.set('press', false);
    }

msg.payload = {
        "on": true,
        "bri": brightness,
        "colormode": colormode,
        "ct": ct,
        "hex": hex,
        "hue": hue,
        "sat": sat
    };
	msg.feedback = brightness;
	flow.set('yeelight1_bri', brightness);
return msg;

The payload with the new brightness level is sent back to the same function with a 500ms delay, and passed over as msg.payload and submitted to Yeelight node to provide visual feedback. The loop is controlled by the node, so it only passes the payload for iteration while the button is pressed.

Bonus

If you don’t want a dimmer, but you rather pick actions in NodeRED based on how long the button is held for, you can do this too! I can easily measure at what time the button has been pressed. Subtracting that from the timestamp of the button being released will give me the number of milliseconds the button had been pressed for. Just note that the actual timing is shorter as the event doesn’t start until the “long_click_press” is issued (after a timeout responsible for the “click” event).

The long presses of the Xiaomi MiHome Switch in NodeRED provided me with the time in ms. To calculate this I used the function node:

FUNCTION NODE: get press time
if(msg.topic === "press"){
    var d = new Date();
    var x = d.getTime();
    context.set('press', x);
}
if(msg.topic === "letgo"){
    var d = new Date();
    var y = d.getTime();
    context.set('letgo', y);
}


var press = context.get('press');
var letgo = context.get('letgo');

if(press !== undefined && letgo !== undefined){
    var sec = letgo - press;
    msg.payload = sec;
    context.set('letgo', undefined);
    context.set('press', undefined);
    return msg;
}

Because the function node would send the payload instantly after receiving the press down notification, I used the topics and the context variable to store the timing of the press down and release. This way, unless both the press down and release values are noted, the function returns nothing.

Conclusion

It’s simply impressive how much you can achieve with a simple button. The home automation is about ease of use and efficiency. This tutorial shows how you can take advantage of theoretically limiting ON|OFF functionality and use the Xiaomi MiHome Switch in NodeRED in a really creative way.

Support NotEnoughTech
A lot of time and effort goes into keeping NotEnoughTech alive! If my work helped you out, consider buying me a coffee or check out exclusive rewards available to Patreon supporters.
SHARE