Extracting Time to set Relais to ON and OFF

Hello, I have a function node which gives me 4 different hours. I would like to switch on a relay during this time.
I don't know how I can extract the time from the text and then convert it to an ON and OFF message. Does anyone have any ideas on how to implement this?

Function Node:

var timestamp = Date.now();
var maxLoadingDuration = 4;

var cheapestHours = msg.payload.data
    .sort((a,b) => a.marketprice - b.marketprice)
    .slice(0,maxLoadingDuration)
    .sort((a,b) => a.start_timestamp - b.start_timestamp);
    var currentHour = cheapestHours.filter(d => d.start_timestamp < timestamp
                                        && d.end_timestamp > timestamp);
msg.payload =
{
    soc:msg.payload,
    cheapestHours: cheapestHours
};
return msg;

Normally msg.payload : Object is always at the top
but here only msg : Object. I don't know if that makes any difference.

Hi. before I respond, please, in future include actual usable data to help the responder help you. For example, if you had provided usable data from the debug instead of a picture, this would have been much easier. Essentially, if you use the "Copy Value" button I would not have typed out the sample data from your screenshot.

OK, I do something very similar with my Octopus Energy data. I created a web page that I can select the best times to charge my car. I then fed that into a cron-plus node to set up schedules.

Here is a quick adaption for your data:

chrome_sP730Knh7V

[{"id":"6b39e36054edcc22","type":"inject","z":"b1993d2a213b2bb0","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":2120,"y":180,"wires":[["32388c4b0af7a5b1"]]},{"id":"32388c4b0af7a5b1","type":"template","z":"b1993d2a213b2bb0","name":"Your cheapestHours sample","field":"payload","fieldType":"msg","format":"json","syntax":"mustache","template":"{\n      \"soc\": {},\n      \"cheapestHours\": [\n        {\n          \"start_timestamp\": 1746543600000,\n          \"end_timestamp\": 1746547200000,\n          \"marketprice\": 66.02,\n          \"unit\": \"Eur/MWh\"\n        },\n        {\n          \"start_timestamp\": 1746612000000,\n          \"end_timestamp\": 1746615600000,\n          \"marketprice\": 62.48,\n          \"unit\": \"Eur/MWh\"\n        },\n        {\n          \"start_timestamp\": 1746615600000,\n          \"end_timestamp\": 1746619200000,\n          \"marketprice\": 60.66,\n          \"unit\": \"Eur/MWh\"\n        }\n      ]\n    }","output":"json","x":2340,"y":180,"wires":[["89c2acf51e7fbc9c"]]},{"id":"89c2acf51e7fbc9c","type":"function","z":"b1993d2a213b2bb0","name":"generate schedules","func":"const makeStart = (time, suffix) => {\n  const title = formatTime(time) + \"-on\"\n  const name = suffix ? `${title} (${suffix}) ` : title\n  return {\n    \"command\": \"add\",\n    \"name\": name,\n    \"expression\": time,\n    \"expressionType\": \"dates\",\n    \"payloadType\": \"str\",\n    \"payload\": \"start\",\n  }\n}\nconst makeStop = (time, suffix) => {\n  const title = formatTime(time) + \"-off\"\n  const name = suffix ? `${title} (${suffix}) ` : title\n  return {\n    \"command\": \"add\",\n    \"name\": name,\n    \"expression\": time,\n    \"expressionType\": \"dates\",\n    \"payloadType\": \"str\",\n    \"payload\": \"stop\",\n  }\n}\n\nconst formatTime = (date) => {\n  const d = new Date(date)\n  const hh = (\"\" + d.getHours()).padStart(2, \"0\")\n  const mm = (\"\" + d.getMinutes()).padStart(2, \"0\")\n  return `${hh}:${mm}`\n}\nconst add30mins = (date) => {\n  const d = new Date(date)\n  const hh = (\"\" + d.getHours()).padStart(2, \"0\")\n  const mm = (\"\" + d.getMinutes()).padStart(2, \"0\")\n  return `${hh}:${mm}`\n}\n\n\nfunction addMinutesToTime(time, minutesToAdd) {\n    const d = new Date(time)\n    d.setMinutes(d.getMinutes() + minutesToAdd)\n    return d\n}\n\nconst payload = []\nconst inputData = msg.payload.cheapestHours\n\nnode.send({topic: \"remove-all-dynamic\"});\n\nlet endTime = null\nlet running = false\nlet suffix = ''\nfor (let index = 0; index < inputData.length; index++) {\n  const element = inputData[index];\n  if (element?.end_timestamp) {\n    suffix = element.marketprice + \" \" + element.unit\n    endTime = new Date(element?.end_timestamp)\n    const time = formatTime(element.end_timestamp)\n    if (!running) {\n        payload.push(makeStart(element.end_timestamp, suffix))\n        running = true\n    } else if (running) {\n      payload.push(makeStop(element.end_timestamp, suffix))\n      running = false \n    }\n  }  \n}\nif (running && endTime) {\n  payload.push(makeStop(endTime, suffix))\n}\n\nmsg.payload = payload\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":2140,"y":240,"wires":[["b6a5d42c7751fae2"]]},{"id":"b6a5d42c7751fae2","type":"cronplus","z":"b1993d2a213b2bb0","name":"","outputField":"payload","timeZone":"","storeName":"","commandResponseMsgOutput":"output1","defaultLocation":"","defaultLocationType":"default","outputs":1,"options":[],"x":2400,"y":240,"wires":[["32a88825fe3edf37","5e5497cff754ef73"]]},{"id":"827e16c3f02d1962","type":"inject","z":"b1993d2a213b2bb0","name":"","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"remove-all-dynamic","x":2150,"y":300,"wires":[["b6a5d42c7751fae2"]]},{"id":"32a88825fe3edf37","type":"switch","z":"b1993d2a213b2bb0","name":"start","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"start","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":2630,"y":200,"wires":[["e84ba501a1405907"]]},{"id":"5e5497cff754ef73","type":"switch","z":"b1993d2a213b2bb0","name":"stop","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"payload","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":2630,"y":240,"wires":[["b2e8c3606bd04897"]]},{"id":"e84ba501a1405907","type":"debug","z":"b1993d2a213b2bb0","name":"start something","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":2840,"y":200,"wires":[]},{"id":"b2e8c3606bd04897","type":"debug","z":"b1993d2a213b2bb0","name":"stop something","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":2840,"y":240,"wires":[]}]

To use it, replace the inject and template with your data & then use the output of the cron node to trigger whatever it is you need to trigger. You can customise the start/stop payloads in the function (or use a switch node to differentiate start from stop messages by checking the payload).

@Steve-Mcl , sorry... here ist a copy from my flow with yours integrated:

[
    {
        "id": "92c274190e6b227c",
        "type": "tab",
        "label": "Flow 1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "0f969cd96da84b2e",
        "type": "http request",
        "z": "92c274190e6b227c",
        "name": "",
        "method": "GET",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "https://api.awattar.at/v1/marketdata",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 310,
        "y": 140,
        "wires": [
            [
                "32e08539d8339f74"
            ]
        ]
    },
    {
        "id": "2a7465d67deae278",
        "type": "inject",
        "z": "92c274190e6b227c",
        "name": "",
        "props": [],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 130,
        "y": 140,
        "wires": [
            [
                "0f969cd96da84b2e"
            ]
        ]
    },
    {
        "id": "32e08539d8339f74",
        "type": "function",
        "z": "92c274190e6b227c",
        "name": "Billigsten 4 Stunden",
        "func": "var timestamp = Date.now();\nvar maxLoadingDuration = 4;\n\nvar cheapestHours = msg.payload.data\n    .sort((a,b) => a.marketprice - b.marketprice)\n    .slice(0,maxLoadingDuration)\n    .sort((a,b) => a.start_timestamp - b.start_timestamp);\n    var currentHour = cheapestHours.filter(d => d.start_timestamp < timestamp\n                                        && d.end_timestamp > timestamp);\nmsg.payload =\n{\n    soc:msg.payload,\n    cheapestHours: cheapestHours\n};\nreturn msg;\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 540,
        "y": 140,
        "wires": [
            [
                "32388c4b0af7a5b1"
            ]
        ]
    },
    {
        "id": "6b39e36054edcc22",
        "type": "inject",
        "z": "92c274190e6b227c",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 320,
        "y": 220,
        "wires": [
            [
                "32388c4b0af7a5b1"
            ]
        ]
    },
    {
        "id": "32388c4b0af7a5b1",
        "type": "template",
        "z": "92c274190e6b227c",
        "name": "Your cheapestHours sample",
        "field": "payload",
        "fieldType": "msg",
        "format": "json",
        "syntax": "mustache",
        "template": "{\n      \"soc\": {},\n      \"cheapestHours\": [\n        {\n          \"start_timestamp\": 1746543600000,\n          \"end_timestamp\": 1746547200000,\n          \"marketprice\": 66.02,\n          \"unit\": \"Eur/MWh\"\n        },\n        {\n          \"start_timestamp\": 1746612000000,\n          \"end_timestamp\": 1746615600000,\n          \"marketprice\": 62.48,\n          \"unit\": \"Eur/MWh\"\n        },\n        {\n          \"start_timestamp\": 1746615600000,\n          \"end_timestamp\": 1746619200000,\n          \"marketprice\": 60.66,\n          \"unit\": \"Eur/MWh\"\n        }\n      ]\n    }",
        "output": "json",
        "x": 580,
        "y": 220,
        "wires": [
            [
                "89c2acf51e7fbc9c"
            ]
        ]
    },
    {
        "id": "89c2acf51e7fbc9c",
        "type": "function",
        "z": "92c274190e6b227c",
        "name": "generate schedules",
        "func": "const makeStart = (time, suffix) => {\n  const title = formatTime(time) + \"-on\"\n  const name = suffix ? `${title} (${suffix}) ` : title\n  return {\n    \"command\": \"add\",\n    \"name\": name,\n    \"expression\": time,\n    \"expressionType\": \"dates\",\n    \"payloadType\": \"str\",\n    \"payload\": \"start\",\n  }\n}\nconst makeStop = (time, suffix) => {\n  const title = formatTime(time) + \"-off\"\n  const name = suffix ? `${title} (${suffix}) ` : title\n  return {\n    \"command\": \"add\",\n    \"name\": name,\n    \"expression\": time,\n    \"expressionType\": \"dates\",\n    \"payloadType\": \"str\",\n    \"payload\": \"stop\",\n  }\n}\n\nconst formatTime = (date) => {\n  const d = new Date(date)\n  const hh = (\"\" + d.getHours()).padStart(2, \"0\")\n  const mm = (\"\" + d.getMinutes()).padStart(2, \"0\")\n  return `${hh}:${mm}`\n}\nconst add30mins = (date) => {\n  const d = new Date(date)\n  const hh = (\"\" + d.getHours()).padStart(2, \"0\")\n  const mm = (\"\" + d.getMinutes()).padStart(2, \"0\")\n  return `${hh}:${mm}`\n}\n\n\nfunction addMinutesToTime(time, minutesToAdd) {\n    const d = new Date(time)\n    d.setMinutes(d.getMinutes() + minutesToAdd)\n    return d\n}\n\nconst payload = []\nconst inputData = msg.payload.cheapestHours\n\nnode.send({topic: \"remove-all-dynamic\"});\n\nlet endTime = null\nlet running = false\nlet suffix = ''\nfor (let index = 0; index < inputData.length; index++) {\n  const element = inputData[index];\n  if (element?.end_timestamp) {\n    suffix = element.marketprice + \" \" + element.unit\n    endTime = new Date(element?.end_timestamp)\n    const time = formatTime(element.end_timestamp)\n    if (!running) {\n        payload.push(makeStart(element.end_timestamp, suffix))\n        running = true\n    } else if (running) {\n      payload.push(makeStop(element.end_timestamp, suffix))\n      running = false \n    }\n  }  \n}\nif (running && endTime) {\n  payload.push(makeStop(endTime, suffix))\n}\n\nmsg.payload = payload\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 380,
        "y": 280,
        "wires": [
            [
                "b6a5d42c7751fae2"
            ]
        ]
    },
    {
        "id": "827e16c3f02d1962",
        "type": "inject",
        "z": "92c274190e6b227c",
        "name": "",
        "props": [
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "remove-all-dynamic",
        "x": 390,
        "y": 340,
        "wires": [
            [
                "b6a5d42c7751fae2"
            ]
        ]
    },
    {
        "id": "32a88825fe3edf37",
        "type": "switch",
        "z": "92c274190e6b227c",
        "name": "start",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "start",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 870,
        "y": 240,
        "wires": [
            [
                "e84ba501a1405907"
            ]
        ]
    },
    {
        "id": "5e5497cff754ef73",
        "type": "switch",
        "z": "92c274190e6b227c",
        "name": "stop",
        "property": "topic",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "payload",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 870,
        "y": 280,
        "wires": [
            [
                "b2e8c3606bd04897"
            ]
        ]
    },
    {
        "id": "e84ba501a1405907",
        "type": "debug",
        "z": "92c274190e6b227c",
        "name": "start something",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 1080,
        "y": 240,
        "wires": []
    },
    {
        "id": "b2e8c3606bd04897",
        "type": "debug",
        "z": "92c274190e6b227c",
        "name": "stop something",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 1080,
        "y": 280,
        "wires": []
    },
    {
        "id": "b6a5d42c7751fae2",
        "type": "cronplus",
        "z": "92c274190e6b227c",
        "name": "",
        "outputField": "payload",
        "timeZone": "",
        "storeName": "",
        "commandResponseMsgOutput": "output1",
        "defaultLocation": "",
        "defaultLocationType": "default",
        "outputs": 1,
        "options": [],
        "x": 640,
        "y": 280,
        "wires": [
            [
                "32a88825fe3edf37",
                "5e5497cff754ef73"
            ]
        ]
    }
]

Not sure why you are sorry :wink:

That is exactly the same flows I posted.

You were a bit too fast. I edited the post above.

I meant because you had to write it off.

1 Like

No bother.

The issue with what you have posted is you lose your data because you pass it through the sample data node

Connect your data to the "generate schedules" node.

If i inject a http request (This should then happen automatically every 24 hours) I do not get a stop signal.
I don't yet know whether this would be necessary. But would like to question this, because if a professional looks over it :slight_smile:

Later, a one-hour ON phase should be initiated with a switch.

You mean if the start has been issued, the associated stop is lost (when operating the request) due to the new times being in the future?

You could handle that with a bit of logic. Simply query the cron node and see if the last-active entry is a "stop" schedule, add that to the generated schedules and it is kept.

I meant that when I send an http request, neither a start nor a stop signal is sent. So the exact time will probably have to arrive first in order to receive a command.

Unfortunately, I find this programming very difficult.
I have now increased the most favorable hours from 4 to 5. Now there is an ON and OFF command at the same time.

[
    {
        "id": "0f969cd96da84b2e",
        "type": "http request",
        "z": "92c274190e6b227c",
        "name": "",
        "method": "GET",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "https://api.awattar.at/v1/marketdata",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 310,
        "y": 140,
        "wires": [
            [
                "32e08539d8339f74"
            ]
        ]
    },
    {
        "id": "2a7465d67deae278",
        "type": "inject",
        "z": "92c274190e6b227c",
        "name": "",
        "props": [],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 130,
        "y": 140,
        "wires": [
            [
                "0f969cd96da84b2e"
            ]
        ]
    },
    {
        "id": "32e08539d8339f74",
        "type": "function",
        "z": "92c274190e6b227c",
        "name": "Billigsten 4 Stunden",
        "func": "var timestamp = Date.now();\nvar maxLoadingDuration = 5;\n\nvar cheapestHours = msg.payload.data\n    .sort((a,b) => a.marketprice - b.marketprice)\n    .slice(0,maxLoadingDuration)\n    .sort((a,b) => a.start_timestamp - b.start_timestamp);\n    var currentHour = cheapestHours.filter(d => d.start_timestamp < timestamp\n                                        && d.end_timestamp > timestamp);\nmsg.payload =\n{\n    soc:msg.payload,\n    cheapestHours: cheapestHours\n};\nreturn msg;\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 540,
        "y": 140,
        "wires": [
            [
                "db357e86c21eeb8a"
            ]
        ]
    },
    {
        "id": "6b39e36054edcc22",
        "type": "inject",
        "z": "92c274190e6b227c",
        "d": true,
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 320,
        "y": 220,
        "wires": [
            [
                "32388c4b0af7a5b1"
            ]
        ]
    },
    {
        "id": "32388c4b0af7a5b1",
        "type": "template",
        "z": "92c274190e6b227c",
        "d": true,
        "name": "Your cheapestHours sample",
        "field": "payload",
        "fieldType": "msg",
        "format": "json",
        "syntax": "mustache",
        "template": "{\n      \"soc\": {},\n      \"cheapestHours\": [\n        {\n          \"start_timestamp\": 1746543600000,\n          \"end_timestamp\": 1746547200000,\n          \"marketprice\": 66.02,\n          \"unit\": \"Eur/MWh\"\n        },\n        {\n          \"start_timestamp\": 1746612000000,\n          \"end_timestamp\": 1746615600000,\n          \"marketprice\": 62.48,\n          \"unit\": \"Eur/MWh\"\n        },\n        {\n          \"start_timestamp\": 1746615600000,\n          \"end_timestamp\": 1746619200000,\n          \"marketprice\": 60.66,\n          \"unit\": \"Eur/MWh\"\n        }\n      ]\n    }",
        "output": "json",
        "x": 580,
        "y": 220,
        "wires": [
            [
                "db357e86c21eeb8a"
            ]
        ]
    },
    {
        "id": "89c2acf51e7fbc9c",
        "type": "function",
        "z": "92c274190e6b227c",
        "name": "generate schedules",
        "func": "const makeStart = (time, suffix) => {\n  const title = formatTime(time) + \"-on\"\n  const name = suffix ? `${title} (${suffix}) ` : title\n  return {\n    \"command\": \"add\",\n    \"name\": name,\n    \"expression\": time,\n    \"expressionType\": \"dates\",\n    \"payloadType\": \"str\",\n    \"payload\": \"start\",\n  }\n}\nconst makeStop = (time, suffix) => {\n  const title = formatTime(time) + \"-off\"\n  const name = suffix ? `${title} (${suffix}) ` : title\n  return {\n    \"command\": \"add\",\n    \"name\": name,\n    \"expression\": time,\n    \"expressionType\": \"dates\",\n    \"payloadType\": \"str\",\n    \"payload\": \"stop\",\n  }\n}\n\nconst formatTime = (date) => {\n  const d = new Date(date)\n  const hh = (\"\" + d.getHours()).padStart(2, \"0\")\n  const mm = (\"\" + d.getMinutes()).padStart(2, \"0\")\n  return `${hh}:${mm}`\n}\nconst add30mins = (date) => {\n  const d = new Date(date)\n  const hh = (\"\" + d.getHours()).padStart(2, \"0\")\n  const mm = (\"\" + d.getMinutes()).padStart(2, \"0\")\n  return `${hh}:${mm}`\n}\n\n\nfunction addMinutesToTime(time, minutesToAdd) {\n    const d = new Date(time)\n    d.setMinutes(d.getMinutes() + minutesToAdd)\n    return d\n}\n\nconst payload = []\nconst inputData = msg.payload.cheapestHours\n\nnode.send({topic: \"remove-all-dynamic\"});\n\nlet endTime = null\nlet running = false\nlet suffix = ''\nfor (let index = 0; index < inputData.length; index++) {\n  const element = inputData[index];\n  if (element?.end_timestamp) {\n    suffix = element.marketprice + \" \" + element.unit\n    endTime = new Date(element?.end_timestamp)\n    const time = formatTime(element.end_timestamp)\n    if (!running) {\n        payload.push(makeStart(element.end_timestamp, suffix))\n        running = true\n    } else if (running) {\n      payload.push(makeStop(element.end_timestamp, suffix))\n      running = false \n    }\n  }  \n}\nif (running && endTime) {\n  payload.push(makeStop(endTime, suffix))\n}\n\nmsg.payload = payload\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 380,
        "y": 280,
        "wires": [
            [
                "b6a5d42c7751fae2"
            ]
        ]
    },
    {
        "id": "827e16c3f02d1962",
        "type": "inject",
        "z": "92c274190e6b227c",
        "name": "",
        "props": [
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "remove-all-dynamic",
        "x": 390,
        "y": 340,
        "wires": [
            [
                "b6a5d42c7751fae2"
            ]
        ]
    },
    {
        "id": "32a88825fe3edf37",
        "type": "switch",
        "z": "92c274190e6b227c",
        "name": "start",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "start",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 870,
        "y": 240,
        "wires": [
            [
                "e84ba501a1405907"
            ]
        ]
    },
    {
        "id": "5e5497cff754ef73",
        "type": "switch",
        "z": "92c274190e6b227c",
        "name": "stop",
        "property": "topic",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "payload",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 870,
        "y": 280,
        "wires": [
            [
                "b2e8c3606bd04897"
            ]
        ]
    },
    {
        "id": "e84ba501a1405907",
        "type": "debug",
        "z": "92c274190e6b227c",
        "name": "start something",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "payload",
        "statusType": "auto",
        "x": 1080,
        "y": 240,
        "wires": []
    },
    {
        "id": "b2e8c3606bd04897",
        "type": "debug",
        "z": "92c274190e6b227c",
        "name": "stop something",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "payload",
        "statusType": "auto",
        "x": 1080,
        "y": 280,
        "wires": []
    },
    {
        "id": "b6a5d42c7751fae2",
        "type": "cronplus",
        "z": "92c274190e6b227c",
        "name": "",
        "outputField": "payload",
        "timeZone": "",
        "storeName": "",
        "commandResponseMsgOutput": "output1",
        "defaultLocation": "",
        "defaultLocationType": "default",
        "outputs": 1,
        "options": [],
        "x": 640,
        "y": 280,
        "wires": [
            [
                "32a88825fe3edf37",
                "5e5497cff754ef73"
            ]
        ]
    },
    {
        "id": "db357e86c21eeb8a",
        "type": "junction",
        "z": "92c274190e6b227c",
        "x": 740,
        "y": 220,
        "wires": [
            [
                "89c2acf51e7fbc9c"
            ]
        ]
    }
]

Would it be a little easier to create an ON - OFF program with this flow?

[
    {
        "id": "4b808ca5a2a6749d",
        "type": "http request",
        "z": "1d0ce182b4d67ec2",
        "name": "",
        "method": "GET",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "https://api.awattar.at/v1/marketdata",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 350,
        "y": 100,
        "wires": [
            [
                "28d16307f96a3f84"
            ]
        ]
    },
    {
        "id": "c1517ce1c78ba426",
        "type": "inject",
        "z": "1d0ce182b4d67ec2",
        "name": "",
        "props": [],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 150,
        "y": 100,
        "wires": [
            [
                "4b808ca5a2a6749d"
            ]
        ]
    },
    {
        "id": "28d16307f96a3f84",
        "type": "json",
        "z": "1d0ce182b4d67ec2",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 510,
        "y": 100,
        "wires": [
            [
                "39491c1c89018353"
            ]
        ]
    },
    {
        "id": "39491c1c89018353",
        "type": "function",
        "z": "1d0ce182b4d67ec2",
        "name": "Billigsten 5 Stunden",
        "func": "var timestamp = Date.now();\nvar maxLoadingDuration = 5;\n\nvar cheapestHours = msg.payload.data\n    .sort((a,b) => a.marketprice - b.marketprice)\n    .slice(0,maxLoadingDuration)\n    .sort((a,b) => a.start_timestamp - b.start_timestamp);\n    var currentHour = cheapestHours.filter(d => d.start_timestamp < timestamp\n                                        && d.end_timestamp > timestamp);\nmsg.payload =\n{\n    soc:msg.payload,\n    cheapestHours: cheapestHours\n};\nreturn msg;\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 240,
        "y": 200,
        "wires": [
            [
                "f48d01da4104b0bb"
            ]
        ]
    },
    {
        "id": "3b473eda30295fd0",
        "type": "debug",
        "z": "1d0ce182b4d67ec2",
        "name": "5 cheapest hours",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 770,
        "y": 200,
        "wires": []
    },
    {
        "id": "f48d01da4104b0bb",
        "type": "split",
        "z": "1d0ce182b4d67ec2",
        "name": "",
        "splt": "\\n",
        "spltType": "str",
        "arraySplt": 1,
        "arraySpltType": "len",
        "stream": false,
        "addname": "",
        "x": 430,
        "y": 200,
        "wires": [
            [
                "877ce9ce241f4b8d"
            ]
        ]
    },
    {
        "id": "877ce9ce241f4b8d",
        "type": "switch",
        "z": "1d0ce182b4d67ec2",
        "name": "Array",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "istype",
                "v": "array",
                "vt": "array"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 570,
        "y": 200,
        "wires": [
            [
                "3b473eda30295fd0"
            ]
        ]
    }
]

I would like to give an ON command at every object at every start time and an OFF command only once at the last object (4) at the end time.

Yeah, I realised the structure of your source data was too different to mine.

Here is a re-jigged flow.

Thats all i can do for now - if it is not quite right, I hope there are enough clues for you to fix it up yourself.

chrome_ND4Xecis1r

[{"id":"0f969cd96da84b2e","type":"http request","z":"f559cf0eb4eca11c","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://api.awattar.at/v1/marketdata","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":450,"y":220,"wires":[["32e08539d8339f74"]]},{"id":"2a7465d67deae278","type":"inject","z":"f559cf0eb4eca11c","name":"get liveData","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":290,"y":220,"wires":[["0f969cd96da84b2e"]]},{"id":"32e08539d8339f74","type":"function","z":"f559cf0eb4eca11c","name":"Billigsten 5 Stunden -> msg.liveData","func":"var timestamp = Date.now();\nvar maxLoadingDuration = 5;\n\nvar cheapestHours = msg.payload.data\n    .sort((a,b) => a.marketprice - b.marketprice)\n    .slice(0,maxLoadingDuration)\n    .sort((a,b) => a.start_timestamp - b.start_timestamp);\n    var currentHour = cheapestHours.filter(d => d.start_timestamp < timestamp\n                                        && d.end_timestamp > timestamp);\nmsg.liveData = {\n    soc:msg.payload,\n    cheapestHours: cheapestHours\n}\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":280,"wires":[["5c56b3ca5ac4f7c2"]]},{"id":"89c2acf51e7fbc9c","type":"function","z":"f559cf0eb4eca11c","name":"merge active and new schedules","func":"\nconst inputData = msg.liveData.cheapestHours\nconst activeSchedules = msg.activeSchedules || []\n\n// Clear existing schedules\nnode.send({ topic: \"remove-all-dynamic\" });\n\n// helper function\nconst makeSchedule = (start, time, suffix) => {\n    const isStart = start == true || start === \"start\" || start === \"on\" || start == 1\n    const title = formatTime(time) + (isStart ? \"-on\" : \"-off\")\n    const name = suffix ? `${title} (${suffix}) ` : title\n    return {\n        \"command\": \"add\",\n        \"name\": name,\n        \"expression\": time,\n        \"expressionType\": \"dates\",\n        \"payloadType\": \"str\",\n        \"payload\": isStart ? \"start\" : \"stop\",\n    }\n}\n// helper function\nconst formatTime = (date) => {\n    const d = new Date(date)\n    const hh = (\"\" + d.getHours()).padStart(2, \"0\")\n    const mm = (\"\" + d.getMinutes()).padStart(2, \"0\")\n    return `${hh}:${mm}`\n}\n\n// vars\nconst newSchedules = []\nconst mergedSchedules = []\nconst keepSchedules = []\n\nfor (let index = 0; index < inputData.length; index++) {\n    const element = inputData[index];\n    let suffix = ''\n    suffix = element.marketprice + \" \" + element.unit\n    const startTime = new Date(element.start_timestamp)\n    const endTime = new Date(element.end_timestamp)\n    newSchedules.push(makeSchedule(\"start\", startTime.valueOf(), suffix))\n    newSchedules.push(makeSchedule(\"stop\", endTime.valueOf(), suffix))\n}\n\n\n// if there are any existing schedules not yet operated\n// and they are before the first in the new data, lets keep them\nif (newSchedules?.length) {\n    const firstNewSchedule = newSchedules[0]\n    const existingSchedulesBeforeFirstNew = activeSchedules?.filter(e => e.expression < firstNewSchedule.expression)\n    if (existingSchedulesBeforeFirstNew?.length) {\n        keepSchedules.push(...existingSchedulesBeforeFirstNew.map(e => {\n            const n = e.name\n            const k = makeSchedule (e.payload, e.expression) \n            k.name = n.replace(' (keep)', '') + \" (keep)\"\n            return k\n        }))\n    }\n} else {\n    // keep all existing active schedules (as there are no new ones)\n    keepSchedules.push(...activeSchedules)\n}\n\nmergedSchedules.push(...newSchedules, ...keepSchedules)\nmergedSchedules.sort((a, b) => a.expression - b.expression) // order by expression asc\n\n// deduplicate the schedules by seeing if there are consecutive on/off schedules\nlet deduplicatedSchedules = [];\nlet prevSchedule\nfor (let i = 0; i < mergedSchedules.length; i++) {\n    const currentSchedule = mergedSchedules[i]\n    if (i === 0) {\n        deduplicatedSchedules.push(currentSchedule);\n    } else {\n        if (currentSchedule.payload === prevSchedule.payload) {\n            // to scheules are the same (start/start or stop/stop)\n            if (currentSchedule.payload === \"start\") {\n                // ignore this one as the previous one was a \"start\" (and due to sorting, this one is superfluous)\n            } else if (currentSchedule.payload === \"stop\") {\n                // since the previous one was a \"stop\", we need to update the previous schedule to the new stop time\n                prevSchedule.expression = currentSchedule.expression\n            }\n        } else {\n            deduplicatedSchedules.push(currentSchedule); // push the current schedule\n        }\n    }\n    prevSchedule = currentSchedule\n}\n\n// node.warn({newSchedules, keepSchedules, mergedSchedules, deduplicatedSchedules}) // debug\nmsg.payload = deduplicatedSchedules\nmsg.topic = \"update schedules\"\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":740,"y":340,"wires":[["b6a5d42c7751fae2"]]},{"id":"827e16c3f02d1962","type":"inject","z":"f559cf0eb4eca11c","name":"Clear all scedules","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"remove-all-dynamic","x":310,"y":380,"wires":[["b6a5d42c7751fae2"]]},{"id":"32a88825fe3edf37","type":"switch","z":"f559cf0eb4eca11c","name":"start","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"start","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":1250,"y":280,"wires":[["e84ba501a1405907"]]},{"id":"5e5497cff754ef73","type":"switch","z":"f559cf0eb4eca11c","name":"stop","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"payload","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":1250,"y":320,"wires":[["b2e8c3606bd04897"]]},{"id":"e84ba501a1405907","type":"debug","z":"f559cf0eb4eca11c","name":"start something","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":1460,"y":280,"wires":[]},{"id":"b2e8c3606bd04897","type":"debug","z":"f559cf0eb4eca11c","name":"stop something","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":1460,"y":320,"wires":[]},{"id":"b6a5d42c7751fae2","type":"cronplus","z":"f559cf0eb4eca11c","name":"","outputField":"payload","timeZone":"","storeName":"","commandResponseMsgOutput":"output2","defaultLocation":"","defaultLocationType":"default","outputs":2,"options":[],"x":1020,"y":380,"wires":[["32a88825fe3edf37","5e5497cff754ef73"],["fe51aaba903612eb"]]},{"id":"5c56b3ca5ac4f7c2","type":"link call","z":"f559cf0eb4eca11c","name":"","links":["ee57d78d1c769d1b"],"linkType":"static","timeout":"30","x":730,"y":280,"wires":[["89c2acf51e7fbc9c"]]},{"id":"b4cf44f6c7bd1461","type":"link in","z":"f559cf0eb4eca11c","name":"link in 4","links":["29dcf0872d3f7ca4"],"x":505,"y":320,"wires":[["5c56b3ca5ac4f7c2"]]},{"id":"fe51aaba903612eb","type":"link out","z":"f559cf0eb4eca11c","name":"link out 17","mode":"link","links":["a1c12404f726b02f"],"x":1215,"y":380,"wires":[]},{"id":"51b3de02bcf463a3","type":"group","z":"f559cf0eb4eca11c","name":"test data","style":{"label":true},"nodes":["6b39e36054edcc22","32388c4b0af7a5b1","73a529bfd3742503","43aa8690b4167bcc","dae8031ee5f37eb5","29dcf0872d3f7ca4"],"x":214,"y":579,"w":522,"h":122},{"id":"6b39e36054edcc22","type":"inject","z":"f559cf0eb4eca11c","g":"51b3de02bcf463a3","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":320,"y":620,"wires":[["32388c4b0af7a5b1"]]},{"id":"32388c4b0af7a5b1","type":"template","z":"f559cf0eb4eca11c","g":"51b3de02bcf463a3","name":"cheapestHours sample","field":"liveData","fieldType":"msg","format":"json","syntax":"mustache","template":"{\n      \"soc\": {},\n      \"cheapestHours\": [\n        {\n          \"start_timestamp\": \"2025-05-07 07:00:00\",\n          \"end_timestamp\": \"2025-05-07 12:00:00\",\n          \"marketprice\": 66.02,\n          \"unit\": \"Eur/MWh\"\n        },\n        {\n          \"start_timestamp\": \"2025-05-07 20:00:00\",\n          \"end_timestamp\": \"2025-05-07 21:00:00\",\n          \"marketprice\": 62.48,\n          \"unit\": \"Eur/MWh\"\n        }\n      ]\n    }","output":"json","x":500,"y":620,"wires":[["dae8031ee5f37eb5"]]},{"id":"73a529bfd3742503","type":"inject","z":"f559cf0eb4eca11c","g":"51b3de02bcf463a3","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":320,"y":660,"wires":[["43aa8690b4167bcc"]]},{"id":"43aa8690b4167bcc","type":"template","z":"f559cf0eb4eca11c","g":"51b3de02bcf463a3","name":"cheapestHours overlap","field":"liveData","fieldType":"msg","format":"json","syntax":"mustache","template":"{\n      \"soc\": {},\n      \"cheapestHours\": [\n        {\n          \"start_timestamp\": \"2025-05-07 08:00:00\",\n          \"end_timestamp\": \"2025-05-07 11:00:00\",\n          \"marketprice\": 66.02,\n          \"unit\": \"Eur/MWh\"\n        },\n        {\n          \"start_timestamp\": \"2025-05-07 20:00:00\",\n          \"end_timestamp\": \"2025-05-07 21:00:00\",\n          \"marketprice\": 62.48,\n          \"unit\": \"Eur/MWh\"\n        }\n      ]\n    }","output":"json","x":510,"y":660,"wires":[["dae8031ee5f37eb5"]]},{"id":"dae8031ee5f37eb5","type":"junction","z":"f559cf0eb4eca11c","g":"51b3de02bcf463a3","x":660,"y":640,"wires":[["29dcf0872d3f7ca4"]]},{"id":"29dcf0872d3f7ca4","type":"link out","z":"f559cf0eb4eca11c","g":"51b3de02bcf463a3","name":"link out 16","mode":"link","links":["b4cf44f6c7bd1461"],"x":695,"y":640,"wires":[]},{"id":"f3ff5331698e1750","type":"group","z":"f559cf0eb4eca11c","name":"Subroutine for getting active schedules","style":{"label":true},"nodes":["ee57d78d1c769d1b","9956e352b1728160","aba3e0f7de7c6f54","cc753a34602daf70","e4b0cb5dbf1fcbcc","a1c12404f726b02f"],"x":594,"y":439,"w":712,"h":82},{"id":"ee57d78d1c769d1b","type":"link in","z":"f559cf0eb4eca11c","g":"f3ff5331698e1750","name":"get-active-dynamic schedules","links":[],"x":740,"y":480,"wires":[["9956e352b1728160"]],"l":true},{"id":"9956e352b1728160","type":"change","z":"f559cf0eb4eca11c","g":"f3ff5331698e1750","name":"","rules":[{"t":"set","p":"topic","pt":"msg","to":"export-active-dynamic","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":905,"y":480,"wires":[["b6a5d42c7751fae2"]],"l":false},{"id":"aba3e0f7de7c6f54","type":"change","z":"f559cf0eb4eca11c","g":"f3ff5331698e1750","name":"","rules":[{"t":"move","p":"payload.result","pt":"msg","to":"activeSchedules","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1215,"y":480,"wires":[["cc753a34602daf70"]],"l":false},{"id":"cc753a34602daf70","type":"link out","z":"f559cf0eb4eca11c","g":"f3ff5331698e1750","name":"link-return","mode":"return","links":[],"x":1265,"y":480,"wires":[]},{"id":"e4b0cb5dbf1fcbcc","type":"switch","z":"f559cf0eb4eca11c","g":"f3ff5331698e1750","name":"","property":"_linkSource","propertyType":"msg","rules":[{"t":"istype","v":"array","vt":"array"}],"checkall":"true","repair":false,"outputs":1,"x":1165,"y":480,"wires":[["aba3e0f7de7c6f54"]],"l":false},{"id":"a1c12404f726b02f","type":"link in","z":"f559cf0eb4eca11c","g":"f3ff5331698e1750","name":"link in 12","links":["fe51aaba903612eb"],"x":1105,"y":480,"wires":[["e4b0cb5dbf1fcbcc"]]}]
1 Like

Solution:

[
    {
        "id": "cb780ae0abda2e3f",
        "type": "inject",
        "z": "a64b091a.a1bf2",
        "name": "Jetzt starten",
        "props": [],
        "repeat": "",
        "crontab": "0 0-22 * * *",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 180,
        "y": 100,
        "wires": [
            [
                "1c9939410dfc3224"
            ]
        ]
    },
    {
        "id": "1c9939410dfc3224",
        "type": "http request",
        "z": "a64b091a.a1bf2",
        "name": "aWATTar Preise abrufen",
        "method": "GET",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "https://api.awattar.at/v1/marketdata",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 410,
        "y": 100,
        "wires": [
            [
                "ce75890a7fae2d9b",
                "165734011ab9cfa3",
                "f5aec7e1cd1854e2"
            ]
        ]
    },
    {
        "id": "ce75890a7fae2d9b",
        "type": "json",
        "z": "a64b091a.a1bf2",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 650,
        "y": 220,
        "wires": [
            [
                "0df7e80736a2a236"
            ]
        ]
    },
    {
        "id": "0df7e80736a2a236",
        "type": "function",
        "z": "a64b091a.a1bf2",
        "name": "Billigsten 5 Stunden",
        "func": "var timestamp = Date.now();\nvar maxLoadingDuration = 5;\n\n// Sortiere nach Preis und wähle die 5 günstigsten Stunden\nvar cheapestHours = msg.payload.data\n    .sort((a, b) => a.marketprice - b.marketprice)\n    .slice(0, maxLoadingDuration)\n    .sort((a, b) => a.start_timestamp - b.start_timestamp);\n\n// Prüfe, ob aktuelle Zeit in einer der günstigen Stunden liegt\nvar nowIsCheap = cheapestHours.some(d => d.start_timestamp <= timestamp && d.end_timestamp > timestamp);\n\n// Setze Payload auf ON oder OFF\nmsg.payload = nowIsCheap ? \"ON\" : \"OFF\";\n\nreturn msg;\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 920,
        "y": 220,
        "wires": [
            [
                "150de57d614c6f8c",
                "3bddd1daa92cb16e"
            ]
        ]
    }
]