Link Search Menu Expand Document Moon Sun
Table of contents
  1. Timer with JS
    1. Requirements
    2. Features
    3. Save file
    4. Timer sound
    5. Description
      1. Load Timers
      2. Intent SetTimer
      3. Injection resetTimers
      4. Injection refreshTimers
      5. Intent StopTimer
      6. Intent NextTimer
    6. Download the flow

Timer with JS

We created a timer feature in NodeRed with JS Functions. Here is a small overview:

Timer with JavaScript

Requirements

Features

  • It can load and save timers from a file. So when the Raspberry Pi turns off and on, the alarms are will be loaded again.
  • Set a timer via voice you can set hours, minutes and seconds.
  • You can clear all timers.
  • Stop a timer via voice
  • You can ask when the next timer is set. The answer will be output via TTS.
  • 1 second refresh time.
  • TTS tells you what time the timer was set

Save file

The timers are saved in the profile folder:

/home/pi/.config/rhasspy/profiles/de/data/timers.json

Timer sound

The timer sound is set up to this file:

/home/pi/.config/rhasspy/profiles/de/data/alarm.mp3

You can edit it or set another path. Important is that you use an absolut path to link the mp3 file. Otherwise NodeRed looks in its own workfolder.

Description

I start to describe the flow from top to bottom. so we start with the loading the alarms from a file.

Load Timers

Load Timers
Loading all timers from a file will be triggered at the start of NodeRed. The injector loadTimers triggers the file in node. The file in node reads the whole file and hands it to the json node, which converts the JSON data format to a JavaScript(JS) object. This JS object goes to the function setTimersGlobal. This function saves all timers in a global array in NodeRed, so we can access them from everywhere.

Intent SetTimer

Set new timer
When an SetTimer intent enters the mqtt-Server, the function createTimerObject will be triggered. This function extract the hour, minutes and seconds from the message and create a alarm object in this format:

{timerSet : timerSetAt, timerAt : timerAt, timerIn: timerIn}

Here is the sourcecode of this function:

var seconds = 0;
var minutes = 0;
var hours = 0;

for(var i = 0; i < msg.payload.slots.length; i++)
{
    if(msg.payload.slots[i].slotName === "seconds")
        seconds = msg.payload.slots[i].value.value;
    if(msg.payload.slots[i].slotName === "minutes")
        minutes = msg.payload.slots[i].value.value;
    if(msg.payload.slots[i].slotName === "hours")
        hours = msg.payload.slots[i].value.value;
}
var timerSetAt = new Date();
var timerAt = new Date(timerSetAt);
timerAt.setHours(timerAt.getHours() + hours);
timerAt.setMinutes(timerAt.getMinutes() + minutes);
timerAt.setSeconds(timerAt.getSeconds() + seconds);

var timerIn = new Date(0);
timerIn.setHours(timerIn.getHours() + hours);
timerIn.setMinutes(timerIn.getMinutes() + minutes);
timerIn.setSeconds(timerIn.getSeconds() + seconds);

msg.payload = {timerSet : timerSetAt, timerAt : timerAt, timerIn: timerIn};
return msg;

This timer object goes to two functions, to createTTSText and to AddToGlobalTimers. createTTSText creates the message the TTS should say. here is the code:

var timerIn = new Date(msg.payload.timerIn);
var hours = timerIn.getHours() -1 ;
var minutes = timerIn.getMinutes();
var seconds = timerIn.getSeconds();

var text = "Es wurde ein Teimer in ";
if(hours !== 0)
    text += hours + " Stunden ";
if(minutes !== 0)
    text += minutes + " Minuten ";
if(seconds !== 0)
    text += seconds + " Sekunden ";
    
text += "gesetzt.";
msg.payload = {text: text, siteId: "default", id: "testID"};

return msg;

After this function the TTS says the text in msg.payload.

The function AddToGlobalTimers adds the new tomer to the global list of timers:

var timers = global.get("timers");
if(timers === undefined)
    timers = [];

if(msg.payload != null)
{
    timers.push(msg.payload);
    global.set("timers", timers);
}

msg.payload = timers;
return msg;

After this function the timer list will be saved with the node file to the file /home/pi/.config/rhasspy/profiles/de/data/timers.json.

Injection resetTimers

Reset all timers
This part is for deleting all timers from the global list and from the save file. After you click on resetTimers the functions resetAllTimers starts. This function sets the global timers array to an empty array:

global.set("timers", []);
msg.payload = null;
return msg;

After this the timers will be saved in the save file again.

Injection refreshTimers

Check timers
The injection refreshTimers triggers the function checkAllTimers every second. checkAllTimers goes through every timer in the global timer list and checks if it’s time to ring. When a timer is out of time it will be add to the readyTimers array, which will be the output for the function. If readyTimers is empty the function will return null.

Here is the Code:

var dateNow = new Date();
var readyTimers = [];

for(var i = 0; i < msg.payload.timers.length; i++)
{
    var thenTime = new Date(msg.payload.timers[i].timerAt);
    if(dateNow.getTime() >= thenTime.getTime())
    {
        readyTimers.push(msg.payload.timers[i]);
    }
}
if(readyTimers.length > 0)
    msg.payload = readyTimers;
else
    msg.payload = null;
return msg;

After this function a switch checks if the message is null or not. If it’s null it ignores the message, and if not the message goes to two functions. The function removeFromGlobalTimers removes the timer from the global array and saves the file.

var timers = global.get("timers");
if(timers === undefined)
    timers = [];

if(msg.payload != null)
{
    for(var i = 0; i < msg.payload.length; i++)
    {
        for( var j = 0; j < timers.length; j++) 
        { 
            if (timers[j].timerSet.toString() == msg.payload[i].timerSet.toString() && timers[j].timerAt.toString() == msg.payload[i].timerAt.toString())
            {
                timers.splice(j, 1); 
            }
        }
    }
    global.set("timers", timers);
}

msg.payload = timers;
return msg;

And the message from checkAllTimers also goes to the function startRinging. In it the message will be set to "start" and the global variable timerRinging is set to 1:

global.set("timerRinging", 1);
msg.payload = "start";
return msg;

The PlaySound node will now play the file on this path:

/home/pi/.config/rhasspy/profiles/de/data/alarm.mp3

When the file ends the switch checkTimerRinging checks if the global variable timerRinging is 0. If not the PlaySound node starts playing again.

Intent StopTimer

Stop an timer
The Intent StopTimerClock stops the ringing of an alarm. It runs the function stopRinging. This sets the global variable timerRinging to 0 and sets the payload to "stop". This message goes to the PlaySound node, it should stop playing the file.

Here is the code:

global.set("timerRinging", 0);
msg.payload = "stop";
return msg;

Intent NextTimer

Next alarm
The Intent NextTimer picks the next due timer and creates a text the TTS should say to inform the user when the next timer rings.

The Intent first triggers the function checkAllTimers. This function goes through all timers listed in the global timers variable and put the next due timer in msg.payload.

var timers = global.get("timers");
var nextDate = new Date(new Date().getFullYear()+2000, 0, 0, 0, 0, 0);

for(var i = 0; i < timers.length; i++)
{
    var thenTime = new Date(timers[i].timerAt);
    if(nextDate.getTime() > thenTime.getTime())
    {
        nextDate = new Date(thenTime);
    }
}
msg.payload = nextDate;
return msg;

The function createAnswerText is the next function in the flow. It converts the datetime from the alarm in a sentence a human can understand.

var currentDate = new Date();

var timerDate = new Date(msg.payload);

var fullSecondsRemain = (timerDate.getTime() - currentDate.getTime())/1000;
var minutesRemain = Math.floor(fullSecondsRemain/60);
var secondsRemain = Math.floor(fullSecondsRemain%60);

var text = "Der nächste Teimer geht in " + minutesRemain + " Minuten und " + secondsRemain + " Sekunden";

msg.payload = {text: text, siteId: "default", id: "testID"};

return msg;

The output of this function goes directly to the Mqtt TTS node.

Download the flow