My home automation projects built with MQTT and Node Red

Part 3 – Outside Lighting

This doesn’t sound like a very interesting home automation subject, but actually it makes a big difference to the way that we enjoy the outside space at our home in Spain, especially when we socialise outside in the warm summer evenings and nights.

I’ve installed some external LED floodlights that are linked to PIR detectors and divided into zones. Each zone covers a different outside area and has two or three PIR detectors so that no matter which direction you enter the zone from it turns the lights on for a period of time.

I wanted to add a method of overriding the PIR detectors, which would put the lights on for a longer period – handy when you’re sitting outside eating and drinking, but not moving about enough to keep the PIR detectors constantly triggered; also handy when you’re parking the car, as the movement of the car doesn’t normally trigger the PIRs.

I started by adding a Sonoff Basic to each of the zones and using Blynk as a way of triggering a countdown timer to put the lights on for a pre-set time – 60 minutes in the eating area and 5 minutes in the area where the car is parked.
This worked well, but meant that everyone who wanted to control the lights would need the Blynk app installed on their phone and access to the shared project. Not very convenient for guests, and this approach would mean that our guests would still be able to control the lights long after they’d returned home. I think the fact that it’s not possible to revoke access for specific users is one of the main drawbacks of the Blynk [Legacy] shared app.

As I’d already created a 433MHz to MQTT gateway for my door entry system (see Part 2), I decided to use this as an additional way of starting the countdown timers.
I also added-in Amazon Alexa voice support (I explained this briefly in Part 1), so that it’s very easy to turn on all of the outside lights for a few minutes simply by saying “Alexa, turn the Outside Lights on”, when you’re within earshot of an Alexa device. This is handy when we’re sat indoors at night and hear a strange noise outside and want to quickly put the lights on to see what has caused it. So far all we’ve seen are the local cats disappearing into the shadows, but it’s good to have a quick and simple to use way of scarring-off potential prowlers.

These are the 433MHz fobs that I give to guests:

The red button (A) activates the lights in the seating area for 60 minutes. The other button (B) cancels the timer.
I also have a 4 button fob on my keyring. Buttons A and B work the same as the other fob, Button C puts the lights on in the parking area for 5 minutes and Button D puts all the lights on for 2 minutes. Button B cancels all of these operations.
You’ll see me demonstrating buttons A and B on this fob this in the video at the end.

To explain in more detail what the coding behind this this looks like I created an example in Node-Red that controls just one lighting zone, to demonstrate the countdown timer process and show how the various trigger devices link together. I also decided to use a Slider widget in the example to allow the user to control how long the light should stay on for, once triggered.

Let’s start by looking at the trigger processes (don’t worry about the spider’s web of connections at this stage, I’ll come to those later):

Flow 1

This part of flow shows inputs from

  • The Alexa output from an Alexa device I’ve named “Outside Lights”
  • A Blynk button widget on V1
  • Two “Inject” nodes that allow off/on (0/1) commands to be issued directly from the Node-Red flow
  • The MQTT commands that come from the 433MHz to MQTT gateway (or any MQTT software that can publish to a topic)

The part of the flow that gets the value from the Slider widget on V2 looks like this:

The value from the slider widget (in minutes) arrives into the green node. The orange Function node simply multiplies the number of minutes set using the slider by 60 to turn it into seconds, then stores the value in a global variable called Countdown_Timer.

The code for that looks like looks like this:

global.set('Countdown_Timer',(msg.payload*60)); // Take the minutes value from te slider widget and convert to seconds

Now we’ll see how these triggers and the slider link together:

The wiring looks twice as complex as it could, because of the decision to use a slider to set the countdown time period.
This slider is attached to V2, and whenever an On command is received from any of the trigger devices we need to do the equivalent of a Blynk.syncVirtual(V2); command in C++. This is done with the green Sync node that I’ve called “Sync value from V2 slider”. This simply goes and fetches the slider value from the Blynk cloud server and triggers the equivalent of a BLYNK_WRITE(V2);, so that the current value of the slider setting is pushed to the green “Slider Widget on V2 - Countdown minutes” node.

The trigger device nodes are also connected to a function “Start/Cancel Countdown Timer” Once one of the trigger devices has sent a command to this function it simply simply updates a few global variables, depending on whether it received a 1 or a 0. The code looks like this:

if (msg.payload ==="1")                         // Do this if we received an On command "1"
{
    global.set('Light_On_Flag',true);           // We annt the light to be on 
    global.set('MQTT_Msg_Sent_Flag',false);     // But the MQTT message to turn the light on hasnt been sent yet
}

else                                            // Do this if we received an Off command "0"
{
    global.set('Countdown_Timer',0);            // Reset the countdown timer to zero
}

return msg;                                     // Output the "1" or "0" as the message payload   

The command is also passed out the other side of the function to update the button widget on V1. This is so that the button in the app stays synchronised with all the other trigger devices. If Alexa turns the lights on then we also want the Blynk button widget to reflect this.

Because the Alexa node can be used to both turn the light on/off and to set the duration in minutes that you want the light to stay on for, it has two outputs. The code within the function directs the appropriate command to the correct output, and does a bit of sense-checking on the duration. It would be perfectly acceptable to Alexa to say “Alexa, set Outside light to minus 100”, or “… to 50,000”. In this example, I’ve set the slider widget so that it’s range is 1 to 60, so my code ensures that these are the minimum and maximum values that are outputted from the function node.
Of course, if I say “Alexa, set outside light to 50,000” Alexa will respond “Outside is set to 50,000” but the slider value and the duration of the countdown will be capped at 60 minutes.

Here’s the code for that:

var OnOff;
var Duration;

if (msg.command==='TurnOnRequest')
{
    OnOff="1";
    return [null, {'payload': OnOff}];
}

if (msg.command==='TurnOffRequest')
{
        OnOff="0";
        return [null, {'payload': OnOff}];
}

if (msg.command=="SetTargetTemperatureRequest")
{
    Duration=msg.payload;
    
    if (Duration <=0)
    {
        Temp = 1
    }
    if (Duration >60)
    {
        Duration = 60
    }
    return [{'payload': Temp}, null];
}

The real processing work is done in this part of the flow:

The blue/grey node on the left hand side called “Auto inject something once every second” does exactly what it says on the tin – it sends a pulse of data out once every second. It doesn’t matter what this data is, it’s simply needed to trigger the “Reduce remaining time by 1 sec” function. Think of it like a Blynk timer calling a function once every second.

The orange “Reduce remaining time by 1 sec” function has two outputs. One sends the remaining countdown time in MM:SS format to the Blynk button widget on V1. This is done using the green node which is a Blynk Set Property node. In this case the property that is being updated is the “On Label” property for the button, so that when the light is on, the button shows the remaining time:

The other output goes to the pink MQTT Output node. This is used to turn the Sonoff Basic that controls the lights on and off. The code running on the Sonoff subscribes to the MQTT topic called “Spain/Outside_Lighting/Seating_Area” and when it sees a “1” in this topic it activates the lights, when it sees a “0” in this topic it deactivates the lights.
I only want to send an MQTT message once at the start and once at the end of the countdown timer process, so the code in my function handles this.
This is the code in the “Reduce remaining time by 1 sec” function. You have to remember that it’s called once every second, regardless of whether the lights are on or off, and that each time it’s called the local variables are wiped clean.

// Initialise the local variables and update them with the GLOBAL variables
var Countdown_Timer = global.get('Countdown_Timer')             //Remaining time that the light stays on for
var Light_On_Flag = global.get('Light_On_Flag')                 // Flag to show if the light is/should be on
var MQTT_Msg_Sent_Flag = global.get('MQTT_Msg_Sent_Flag')       // Flag to show if we've sent the MQTT On message yet
var Completed_Flag = global.get('Completed_Flag')               // Flag to show if we've finished the countdown yet

// Initialise the local variables
var Minutes = 0 // Intermediate variable to hold remaining whole minutes as Integer
var Seconds = 0 // Intermediate variable to hold remaining seconds (0-59) as Integer
var Minutes_String = "" // Intermediate variable to hold remaining whole minutes as String
var Seconds_String = "" // Intermediate variable to hold remaining seconds (0-59) as String
var MM_SS_String = "" // Intermediate variable to hold remaining in MM:SS format String


if (Light_On_Flag && Countdown_Timer>=1)    // If the light is/should be on then we execute this loop until countdown reaches 1
{
    Countdown_Timer = Countdown_Timer-1;    // Decrement the countdown
    global.set('Countdown_Timer',Countdown_Timer);  // write the new countdown value back to the global variable to store it
    Completed_Flag = false;                         // We're not done yet
    global.set('Completed_Flag',false);             // write the completed flag back to the global variable to store it
    Secs_to_MM_SS();                                // Call the function to convert the remaining time in seconds to MM:SS format
    node.status({fill:"green",shape:"dot", text: MM_SS_String});    // Display the result on the node, to give feedback

   if (MQTT_Msg_Sent_Flag!==true)   // If we've not sent the MQTT message to turn the light on yet, do it now...
    {
        node.send ([{'payload': MM_SS_String}, {'payload': "1"}]); // Write the MM:SS to output 1 and the MQTT on instruction ("1") to output 2
        global.set('MQTT_Msg_Sent_Flag', true);     // Set the flag to show that we've now sent the MQTT instruction
    }
    else    // If we have sent the MQTT On instruction then we don't needs to send it again...
    {
        node.send ([{'payload': MM_SS_String}, null]); // Write the MM:SS to output 1 and nothing to output 2
    }
}
else    // We get here if the countdown timer is 0 - This will happen once every second when the light is off!! 
{
    if (Light_On_Flag && Completed_Flag!==true) // Ifr the MQTT message to turn the light off hasn't been sent yet, do this... 
    {
        node.send ([{'payload': "--:--"}, {'payload': "0"}]); // Write "--:--" to output 1 and an MQTT Off command ("0") to output 2 
        Completed_Flag = true   // we're now done
        global.set('Completed_Flag', Completed_Flag);   // write the completed flag back to the global variable to store it
        node.status({text: " "});       // Clear the display on the node
        Light_On_Flag = false   // The light is now off
        global.set('Light_On_Flag', Light_On_Flag); //Update the flag in the global variable
    }       
}


function Secs_to_MM_SS()    // function to convert the remaining time in seconds to MM:SS format
{
    Minutes=parseInt(Countdown_Timer/60);   // Get the number of whole minutes remaining - Use and integer to round correctly
    Seconds = Countdown_Timer-(Minutes*60); // Get the number of seconds remaining (0 to 59) for the MM:SS display 
    
    if (Minutes<10)
    {
        Minutes_String = "0" + Minutes.toString()   // If less than 10 minutes remaining then add a leading zero and convert to a string       
    }
    else
    {
        Minutes_String = Minutes.toString() // If more than 10 minutes remaining then no need to add a leading zero - just convert to a string             
    }

    if (Seconds<10)
    {
        Seconds_String = "0" + Seconds.toString()    // If less than 10 seconds remaining then add a leading zero and convert to a string           
    }
    else
    {
        Seconds_String = Seconds.toString()  // If more than 10 seconds remaining then no need to add a leading zero - just convert to a string                 
    }
    
    MM_SS_String = Minutes_String + ":" + Seconds_String // Create the MM:SS string from the minute and second strings
}

I’m not going to explain the code in detail here, as I think the in-code comments do that fairly well. The only thing that may not make any sense if you’ve never seen Node-Red before is this bit of code:
node.status({fill:"green",shape:"dot", text: MM_SS_String});
Each node has the ability to display text below the code, to provide information to the user. You’ll have noticed messages like “connected to pin V1”. These are very handy when troubleshooting, and the line of code above displays a green dot with the remaining countdown time in MM:SS format when the light is on.

Flow 5

Here’s the full flow:
You’ll obviously have been paying attention, so you’ll have spotted couple of things that I haven’t mentioned yet…

There’s a connection from the “Start/Cancel Countdown Timer” function to the “Reduce remaining time by 1 sec” function. This isn’t really necessary, but it ensures that when one of the input devices pushes a message into the “Start/Cancel Countdown Timer” function it immediately passes through to the “Reduce remaining time by 1 sec” function. This triggers the function immediately, rather than having to wait until the next 1 second trigger come from the auto inject node. This just makes the lights a bit more responsive, otherwise there could be a response time of up to 1 second before the lights go on or off.

There’s also a connection from the second output of the “Reduce remaining time by 1 sec” to the “Button Widget V1 - Update Start/Cancel switch status” node. This is important, as when the countdown timer reaches the end and sends it’s “0” message to the MQTT node to turn off the light, it also needs to turn the widget button off in the app, to keep everything synchronised.

I made a short video showing it in action. In the video I’ve mirrored the Blynk screen from my phone so that you can see more clearly what’s happening with the button and slider widgets. I’ve also demonstrated that you can use any MQTT client to publish a 1 or a 0 to the “Spain/433/Outside_Light” topic to turn the light on and off, in this case using a program called MQTTfx.

Turn up the sound if you want to hear the Alexa commands and responses.

As you can see, any combination of the input devices can be used to turn the timer on and off. What I’ve not shown is the timeout timer ticking down to zero and switching the light off automatically, but trust me that this works too.

The next instalment will be about my heating/air conditioning controller. This will be a very high-level overview as it’s quite a complex system, but it might provide a bit of food for thought for anyone wanting to embark on a similar project.

Pete.

12 Likes