Alarm from time captured in Google

In short: I have a time that comes as a spoken decimal "9.51" and I need to

  • Convert in a time (solved below)
  • Check time "now" and convert to 24hrs if needed
  • (if time now is earlier than 9, then leave it as is, else, add 12hours to it)

My project: I have several google speakers and I'm trying to do what google cant:
Ask google to "ping me on all speakers at a specific time". (an alarm that rings on all speakers sort of).
I'm stuck at time conversion. Google, via IFTTT lets me speaks a number and a string and passes it on.
Main problem: I will say "9.51" when I mean 21:51 hrs - I need to smart convert it - and I have no use for dates.

So this works - I speak to google, it goes to IFTTT, comes back as webhookrelay, time is split into an alarm that waits and then text is released as casting to google.
(the Inject node has the ouput that IFTTT sends me - to test)

[
    {
        "id": "fa2f6841.a631b8",
        "type": "tab",
        "label": "Flow 5",
        "disabled": false,
        "info": ""
    },
    {
        "id": "fc018262.379eb",
        "type": "webhookrelay",
        "z": "fa2f6841.a631b8",
        "buckets": "gactions",
        "x": 130,
        "y": 220,
        "wires": [
            [
                "9f58bb33.4d4bb8"
            ]
        ]
    },
    {
        "id": "9f58bb33.4d4bb8",
        "type": "function",
        "z": "fa2f6841.a631b8",
        "name": "extract body",
        "func": "return {\n    payload: msg.payload.body\n};",
        "outputs": 1,
        "noerr": 0,
        "x": 310,
        "y": 200,
        "wires": [
            [
                "dffeeb79.6fc858"
            ]
        ]
    },
    {
        "id": "dffeeb79.6fc858",
        "type": "json",
        "z": "fa2f6841.a631b8",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": true,
        "x": 490,
        "y": 200,
        "wires": [
            [
                "99de2126.f06bc"
            ]
        ]
    },
    {
        "id": "c1fd9702.f5d0b8",
        "type": "inject",
        "z": "fa2f6841.a631b8",
        "name": "",
        "topic": "",
        "payload": "{\"type\":\"webhook\",\"meta\":{\"id\":\"815a9995-208e-44fd-be82-45ec2112dd6f\",\"bucked_id\":\"1ecec89d-b44e-4c53-87c5-f2abcc38392d\",\"bucket_name\":\"gactions\",\"input_id\":\"68922408-5099-4ab7-89b5-844210568a24\",\"input_name\":\"Default public endpoint\",\"output_name\":\"\",\"output_destination\":\"\"},\"headers\":{\"X-Newrelic-Id\":[\"VwAOU1RRGwAFUFZUAwQE\"],\"X-Newrelic-Transaction\":[\"PxQGUgNUClYGB1lWVgFSUUYdUFIOFQZOElMLBw8KUQRQXQ0AAQQEQEgUUQMDW1kEVQZDPw==\"],\"Content-Type\":[\"application/json\"],\"Content-Length\":[\"58\"]},\"query\":\"\",\"body\":\"{ \\\"time_google\\\": \\\"22.07\\\", \\\"message_google\\\": \\\"about potatoes\\\" }\",\"method\":\"POST\"}",
        "payloadType": "json",
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "x": 130,
        "y": 180,
        "wires": [
            [
                "9f58bb33.4d4bb8"
            ]
        ]
    },
    {
        "id": "cb6697ab.006328",
        "type": "cast-to-client",
        "z": "fa2f6841.a631b8",
        "name": "",
        "url": "",
        "contentType": "",
        "message": "",
        "language": "en",
        "ip": "192.168.86.247",
        "port": "",
        "volume": "",
        "x": 860,
        "y": 320,
        "wires": [
            []
        ]
    },
    {
        "id": "99de2126.f06bc",
        "type": "function",
        "z": "fa2f6841.a631b8",
        "name": "Convert to Payload and change to variables needed later",
        "func": "var msg = {payload:{ontime: msg.payload.time_google, message: msg.payload.message_google}};\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 790,
        "y": 200,
        "wires": [
            [
                "7b0cd13d.dd3c4"
            ]
        ]
    },
    {
        "id": "7c7ea65e.0ff4a8",
        "type": "schedex",
        "z": "fa2f6841.a631b8",
        "name": "alarm clock",
        "suspended": false,
        "lat": "",
        "lon": "",
        "ontime": "",
        "ontopic": "",
        "onpayload": "wake up!",
        "onoffset": 0,
        "onrandomoffset": 0,
        "offtime": "",
        "offtopic": "",
        "offpayload": "wake up!!",
        "offoffset": "",
        "offrandomoffset": 0,
        "mon": true,
        "tue": true,
        "wed": true,
        "thu": true,
        "fri": true,
        "sat": true,
        "sun": true,
        "x": 650,
        "y": 320,
        "wires": [
            [
                "cb6697ab.006328"
            ]
        ]
    },
    {
        "id": "7b0cd13d.dd3c4",
        "type": "change",
        "z": "fa2f6841.a631b8",
        "name": "convert decimals to time",
        "rules": [
            {
                "t": "change",
                "p": "payload.ontime",
                "pt": "msg",
                "from": ".",
                "fromt": "str",
                "to": ":",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 690,
        "y": 260,
        "wires": [
            [
                "3fb820a.0ff20e"
            ]
        ]
    },
    {
        "id": "3fb820a.0ff20e",
        "type": "moment",
        "z": "fa2f6841.a631b8",
        "name": "",
        "topic": "",
        "input": "payload.ontime",
        "inputType": "msg",
        "inTz": "Europe/Amsterdam",
        "adjAmount": 0,
        "adjType": "days",
        "adjDir": "add",
        "format": "",
        "locale": "C",
        "output": "",
        "outputType": "msg",
        "outTz": "Europe/Amsterdam",
        "x": 940,
        "y": 260,
        "wires": [
            [
                "7c7ea65e.0ff4a8"
            ]
        ]
    }
]

But I fail (since I'm useless in coding) to do the time calculation with the if/else to add the 12 hours.

Indeed this use case requires some JavaScript coding.

A possible solution could be this code inside a function node. It will convert your string to one that the schedex node accepts (HH:MM), adding the time offset you want.

let pay = msg.payload.split(".");

let splitHour   = pay[0].padStart(2, "0");
let splitMinute = pay[1].padStart(2, "0");
let checkHour = parseInt(splitHour);

if ( checkHour < 9) {
    checkHour = checkHour + 12;
    splitHour = checkHour.toString();
}


msg.payload = {};
msg.payload.ontime = `${splitHour}:${splitMinute}`
return msg;

Testing flow:

[{"id":"36ca9e61.e42be2","type":"tab","label":"Flow 5","disabled":false,"info":""},{"id":"77907a79.a18a14","type":"inject","z":"36ca9e61.e42be2","name":"","topic":"","payload":"9.51","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":100,"wires":[["9169ee8e.1941a"]]},{"id":"856943b6.e2503","type":"debug","z":"36ca9e61.e42be2","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":470,"y":100,"wires":[]},{"id":"9169ee8e.1941a","type":"function","z":"36ca9e61.e42be2","name":"Compute ontime","func":"let pay = msg.payload.split(\".\");\n\nlet splitHour   = pay[0].padStart(2, \"0\");\nlet splitMinute = pay[1].padStart(2, \"0\");\nlet checkHour = parseInt(splitHour);\n\nif ( checkHour < 9) {\n    checkHour = checkHour + 12;\n    splitHour = checkHour.toString();\n}\n\n\nmsg.payload = {};\nmsg.payload.ontime = `${splitHour}:${splitMinute}`\nreturn msg;","outputs":1,"noerr":0,"x":300,"y":100,"wires":[["856943b6.e2503","cc45d6f5.d54f08"]]},{"id":"b434f05.2e9441","type":"inject","z":"36ca9e61.e42be2","name":"","topic":"","payload":"3.3","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":140,"wires":[["9169ee8e.1941a"]]},{"id":"cc45d6f5.d54f08","type":"schedex","z":"36ca9e61.e42be2","name":"","suspended":false,"lat":"-25.45322","lon":"-49.2245","ontime":"","ontopic":"","onpayload":"","onoffset":0,"onrandomoffset":0,"offtime":"goldenHour","offtopic":"","offpayload":"","offoffset":0,"offrandomoffset":0,"mon":true,"tue":true,"wed":true,"thu":true,"fri":true,"sat":true,"sun":true,"x":520,"y":180,"wires":[["910a51ab.57b1a"]]},{"id":"910a51ab.57b1a","type":"debug","z":"36ca9e61.e42be2","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":700,"y":180,"wires":[]}]

After revisiting this post I am not sure about the proposed solution. It is likely that i misread this requirement:

(if time now is earlier than 9, then leave it as is, else, add 12hours to it)

I am not sure what do you mean by "time now".

Hi and thanks for the help. I am exploring your code trying to see if I can use it to crack what I need.
What I meant is

  • Let's say I speak "ok google": ring all my speakers at 10.35 with the message "don't forget to call dad". (Google/IFTTT limitation here: I cannot say 10.35am - I need to speak a number)
  • I will not use this with a date - so any of those requests is for an alarm on the day itself.

My issue is that if it's 9am (let's call it timenow) when I make that request, I want the alarm to go at 10.35am (let's call it ontime). If it's 2PM (timenow) when I call the request, I want it to be 22.35 (ontime).

I think I can figure out to use your code - but I'm stuck (a bit embarassing) at very very basic... I don't know how to "merge" or call the two sources of time into my function (you did it with 2 "injects".

I already have the code that delivers an object the variable "ontime"
The code below will generate the "timenow" from an inject node with timestamp. But how:

  1. Can I trigger this by linking to my webhook timestamp or some sort or the time at nodered execution?
  2. Can use the result of that node into your formula?
[
    {
        "id": "8e59cc3e.07a34",
        "type": "tab",
        "label": "Flow 1",
        "disabled": false,
        "info": ""
    },
    {
        "id": "1f99af7f.5daac1",
        "type": "moment",
        "z": "8e59cc3e.07a34",
        "name": "",
        "topic": "",
        "input": "",
        "inputType": "msg",
        "inTz": "Europe/Amsterdam",
        "adjAmount": "1",
        "adjType": "hours",
        "adjDir": "add",
        "format": "",
        "locale": "C",
        "output": "payload",
        "outputType": "msg",
        "outTz": "Europe/Amsterdam",
        "x": 360,
        "y": 240,
        "wires": [
            [
                "7611886c.919438"
            ]
        ]
    },
    {
        "id": "7611886c.919438",
        "type": "change",
        "z": "8e59cc3e.07a34",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "$substring(payload,11,5)",
                "tot": "jsonata"
            },
            {
                "t": "change",
                "p": "payload",
                "pt": "msg",
                "from": ":",
                "fromt": "str",
                "to": ".",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "$number(payload)",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 580,
        "y": 240,
        "wires": [
            [
                "8d88e316.37bcd"
            ]
        ]
    },
    {
        "id": "e294f979.b62a58",
        "type": "inject",
        "z": "8e59cc3e.07a34",
        "name": "",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "x": 160,
        "y": 240,
        "wires": [
            [
                "1f99af7f.5daac1"
            ]
        ]
    },
    {
        "id": "8d88e316.37bcd",
        "type": "debug",
        "z": "8e59cc3e.07a34",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "x": 770,
        "y": 240,
        "wires": []
    }
]

Considering that you can not use the AM / PM qualifier there will be always an ambiguity to be resolved. There are two possible moments in a day that satisfy the time you provide. You are proposing to resolve the ambiguity using the current time. This is not always possible though.

Consider this case (your own example).

It is 9 am and you speak to your voice assistance 10.35. How the function would know if you want to setup the alarm for 10:35 (one hour and 35 minutes ahead of the time) or to 22:35 in the night ?

HI Andrei,

You are right... but since google doesn't let me input time as "time" but only number, I'd rather chose manually chosing to say "22.30" as an exception for the case you mention

ok, understood. I guess there is a simpler way to do what you want, without using the moment node. I will spare some time later today to have a look.

At the end it was not complicated. I created the function the will select the proper moment, based in your rule and will convert it to the format required by the schedex node (HH.MM).

You should test this flow by changing the inject times to different values (I tested only once).

If the flows does it correctly then the next step is to integrate it to your flow. I did not even try as you use nodes that i will not install in my computer.

[{"id":"8d905fd8.4312a","type":"tab","label":"Flow 4","disabled":false,"info":""},{"id":"64ab8db7.8e2e04","type":"inject","z":"8d905fd8.4312a","name":"","topic":"","payload":"10.11","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":190,"y":200,"wires":[["246a1b3a.937b64"]]},{"id":"dc1a3820.8e3468","type":"debug","z":"8d905fd8.4312a","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.ontime","targetType":"msg","x":600,"y":160,"wires":[]},{"id":"246a1b3a.937b64","type":"function","z":"8d905fd8.4312a","name":"Compute ontime","func":"// msg.payload : is the time we want to trigger an event. Format H.MM\n// ontime : is the time we want to trigger an event. Format HH:MM\n\n// This function will:\n// 1- Transform payload format H.MM to HH.MM\n// 2- Create ontime so as it is teh next moment in the future\n\nlet ontimeHour;\nlet ontimeMinute;\n\n// Calculate number of minutes from midnight until NOW\nlet d = new Date();\nlet timeNowHours = d.getHours();\nlet timeNowMinutes = d.getMinutes();\nlet minutesNow = timeNowMinutes + 60 * timeNowHours;\n\n// Calculate number of minutes from midnight until onTime\nlet pay = msg.payload.split(\".\");\n\nlet splitHour = parseInt(pay[0]);\nlet splitMinute = parseInt(pay[1]);\n\nlet minutesOntime1 = splitMinute + 60 * splitHour;\nlet minutesOntime2 = splitMinute + 60 * (12 + splitHour);\n\n\n// Assign ontimeHour to the closer positive time\nlet choice1 = minutesOntime1 - minutesNow;\nlet choice2 = minutesOntime2 - minutesNow;\n\nif (choice1 > 0 && choice1 < choice2) {\n    ontimeHour = splitHour;\n} else {\n    ontimeHour = splitHour + 12;\n}\n\n// Change Hour and Minute to string (format HH.MM)\nontimeHour = ontimeHour.toString().padStart(2, \"0\");\nontimeMinute = splitMinute.toString().padStart(2, \"0\");\n\nmsg.payload = {};\nmsg.payload.ontime = `${ontimeHour}:${ontimeMinute}`;\nreturn msg;","outputs":1,"noerr":0,"x":380,"y":160,"wires":[["dc1a3820.8e3468","73190a04.817d14"]]},{"id":"d80d43c5.20bfe","type":"inject","z":"8d905fd8.4312a","name":"","topic":"","payload":"3.11","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":190,"y":160,"wires":[["246a1b3a.937b64"]]},{"id":"73190a04.817d14","type":"schedex","z":"8d905fd8.4312a","name":"msg.ontime","suspended":false,"lat":"-25.55958","lon":"-49.33487","ontime":"","ontopic":"","onpayload":"","onoffset":0,"onrandomoffset":0,"offtime":"23:59","offtopic":"","offpayload":"","offoffset":0,"offrandomoffset":0,"mon":true,"tue":true,"wed":true,"thu":true,"fri":true,"sat":true,"sun":true,"x":570,"y":200,"wires":[[]]}]

Edit: My first assumption was that the spoken time would be always between 1 and 12. The code proposed above will not work well if you say time greater then 12, for instance "22.30". I need to fix the code to handle that case. I will work tonight on that.

Thanks a lot. In the meantime, I was searching for myself and I think I also cracked it which was a good learning moment for me. The flow is solely the time check and change.

Basically my approach is to "replace" the : to . in the timestamp (and truncating the date and other info) formats to make it a number,
check if timestamp is smaller than ontime (alarm)
If yes, do nothing to ontime, if no, add 12 hours to it
And at that point I change the . to : for ontime and send it to schedex.

Still have to test all that :wink:

[
    {
        "id": "9feb7292.26316",
        "type": "tab",
        "label": "Ontime & Timestamp",
        "disabled": false,
        "info": ""
    },
    {
        "id": "3e3f34b4.8f285c",
        "type": "inject",
        "z": "9feb7292.26316",
        "name": "ontime alarm (11.45)",
        "topic": "",
        "payload": "11.45",
        "payloadType": "num",
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "x": 170,
        "y": 80,
        "wires": [
            [
                "d56215d7.88cbe8"
            ]
        ]
    },
    {
        "id": "18b218b1.47a037",
        "type": "debug",
        "z": "9feb7292.26316",
        "name": "Alarm time",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "x": 590,
        "y": 160,
        "wires": []
    },
    {
        "id": "d56215d7.88cbe8",
        "type": "function",
        "z": "9feb7292.26316",
        "name": "ontime",
        "func": "flow.set(\"ontime\", msg.payload);\nmsg.payload = flow.get('ontime');\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 350,
        "y": 80,
        "wires": [
            [
                "112dbc79.08fa84"
            ]
        ]
    },
    {
        "id": "71f47cc1.73b674",
        "type": "inject",
        "z": "9feb7292.26316",
        "name": "Timestamp (11.47)",
        "topic": "",
        "payload": "11.47",
        "payloadType": "num",
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "x": 170,
        "y": 140,
        "wires": [
            [
                "3fc5b478.119ffc"
            ]
        ]
    },
    {
        "id": "3fc5b478.119ffc",
        "type": "function",
        "z": "9feb7292.26316",
        "name": "timestamp",
        "func": "flow.set(\"timestamp\", msg.payload);\nmsg.payload = flow.get('timestamp');\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 370,
        "y": 140,
        "wires": [
            [
                "112dbc79.08fa84"
            ]
        ]
    },
    {
        "id": "112dbc79.08fa84",
        "type": "function",
        "z": "9feb7292.26316",
        "name": "Make PM if needed",
        "func": "ontime = flow.get('ontime');\ntimestamp = flow.get('timestamp');\nif (timestamp < ontime) {\n   ontime = ontime;\n} else {\n   ontime = ontime + 12;\n}\nmsg.payload = ontime\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 550,
        "y": 100,
        "wires": [
            [
                "18b218b1.47a037"
            ]
        ]
    }
]

Brilliant. I never considered making the time a number. Indeed, that makes easier to compare two different times.

Using your code. Say it is 22:10 and you want to set the alarm to 22:40. Does it work ?

I guess I finished a working flow. All code is inside a single function node, so no need to use the moment node. It is very simple. Considering the time frame as today the flow will check if the input time (the one you speak to the voice assistant) is past or future (in regards to the currrent time). If the input time is past the function will return the next time as input time + 12 hours. If input time is in the future the function node will return the input time but correctly formatted for the schedex node (HH.MM).

Apparently it works well now.

[{"id":"c619b602.dbf908","type":"tab","label":"Flow 3","disabled":false,"info":""},{"id":"78b25b35.bdbaa4","type":"inject","z":"c619b602.dbf908","name":"","topic":"","payload":"17.45","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":280,"wires":[["e3c00dad.d96bb"]]},{"id":"e3c00dad.d96bb","type":"function","z":"c619b602.dbf908","name":"Compute ontime","func":"// msg.payload : is the time we want to trigger an event. Format H.MM\n// ontime : is the time we want to trigger an event. Format HH:MM\n// timeNow and payTime are number of seconds since midnight\n\nlet onTime;\n\nfunction isPast(pay) {\n    let d = new Date();\n    let timeNow = 3600 * d.getHours() + 60 * d.getMinutes();\n\n    let splitPay = pay.split(\".\");\n    let splitHour = parseInt(splitPay[0]);\n    let splitMinute = parseInt(splitPay[1]);\n    let payTime = 60 * splitMinute + 3600 * splitHour;\n    return payTime - timeNow < 0 ? true: false;\n}\n\nfunction nextMoment(pay) {\n    let splitPay = pay.split(\".\");\n    let splitHour = parseInt(splitPay[0]);\n    let splitMinute = parseInt(splitPay[1]);\n\n    if (splitHour < 12) {\n        splitHour = splitHour + 12;\n    } else {\n        splitHour = splitHour - 12;\n    }\n    return splitHour.toString().padStart(2,\"0\")+\":\"+splitMinute.toString().padStart(2,\"0\");\n}\n\n\nfunction formatMoment(pay) {\n    let splitPay = pay.split(\".\");\n    let splitHour = parseInt(splitPay[0]);\n    let splitMinute = parseInt(splitPay[1]);\n    return splitHour.toString().padStart(2,\"0\")+\":\"+splitMinute.toString().padStart(2,\"0\");\n}\n\n\nif (isPast(msg.payload)) {\n    onTime = nextMoment(msg.payload);\n} else {\n    onTime = formatMoment(msg.payload);\n}\n\nnode.status({text: onTime});\n\nmsg.payload = {};\nmsg.payload.ontime = onTime;\n\nreturn msg;","outputs":1,"noerr":0,"x":340,"y":220,"wires":[["3981bbe5.a778c4"]]},{"id":"4a1b2dc9.a541e4","type":"inject","z":"c619b602.dbf908","name":"","topic":"","payload":"3.05","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":160,"wires":[["e3c00dad.d96bb"]]},{"id":"ef4862a3.7b28","type":"inject","z":"c619b602.dbf908","name":"","topic":"","payload":"15.5","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":200,"wires":[["e3c00dad.d96bb"]]},{"id":"e9d28f67.bd8a8","type":"inject","z":"c619b602.dbf908","name":"","topic":"","payload":"15.05","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":240,"wires":[["e3c00dad.d96bb"]]},{"id":"3981bbe5.a778c4","type":"schedex","z":"c619b602.dbf908","name":"","suspended":false,"lat":"-25.45327","lon":"-49.22448","ontime":"","ontopic":"","onpayload":"","onoffset":0,"onrandomoffset":0,"offtime":"23:59","offtopic":"","offpayload":"","offoffset":0,"offrandomoffset":0,"mon":true,"tue":true,"wed":true,"thu":true,"fri":true,"sat":true,"sun":true,"x":500,"y":220,"wires":[[]]}]