Stuck with cronplus and time problem

I have an event happen at 06:31 daily.

But am wanting to make it a bit more flexible.

In winter 06:31 is a nicer time to SUNRISE as that is way (WAY) later.
But summer, it is the opposite.

So I was wondering:

CronPlus sends out a SUNRISE signal and a signal at 06:31.

When it gets the SECOND of the two - which ever order - the action is done.

Can that be done solely in CronPlus do I do need external node help?

And of course: either way, I'm not understanding how to do it.

Any thoughts?

I don't think you can set cronplus to trigger at sunrise on a specified date range.
But using the cron option you can specify a different time to trigger in each month.

So for example this gives 6:30 in two 3-month ranges, 7:00 in two other months:

Ok, I'm not sure you understood.
(No blame)

I can put one output for 06:31 all year.
I make another output sunrise all year.

So I want to combine them so on the receipt of the SECOND ONE the message is passed on.

Sort of like a threshold (or maybe hysteresis?) of the earliest time is 06:31 or LATER.

Clearer?

Well you did ask "Can that be done solely in CronPlus do I do need external node help?".

Your thought of generating two messages every day and ignoring one of them will surely work but that way lies flow spaghetti.

It seemed to me more elegant to only inject a message at [roughly] the time you want it.

This is what I'm wanting to see/get (graph)

Thoughts on how to do this?

Apparently you can enable and disable a particular schedule ("pause"?) by means of an input message.
At the equinoxes send a control message (also from cron-plus, firing on specified dates) to disable the sunrise solar schedule and enable the fixed time one, or vice-versa.

Does this look right?

[{"id":"f0270c2556184277","type":"function","z":"235f16ee6e459f2c","name":"Block before 06:31","func":"let now = new Date()\nlet hour = now.getHours()\nlet minute = now.getMinutes()\n\nif (hour > 6 || (hour === 6 && minute >= 31)) {\n    return msg; // After 06:31\n} else {\n    return null; // Before 06:31 - block message\n}\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":460,"y":5710,"wires":[[]]},{"id":"2a7816dfeea3de4f","type":"cronplus","z":"235f16ee6e459f2c","name":"Sunrise","outputField":"payload","timeZone":"","persistDynamic":false,"commandResponseMsgOutput":"output1","outputs":1,"options":[{"name":"schedule1","topic":"topic1","payloadType":"str","payload":"SUNRISE","expressionType":"solar","expression":"0 * * * * * *","location":"-33.880541739956485 151.1226511001587","offset":"0","solarType":"selected","solarEvents":"sunrise"}],"x":270,"y":5710,"wires":[["f0270c2556184277"]]}]

(Sorry, not sure why the flow isn't shown)

Ok, that kinda didn't work to the full requirements.

This one.....

[{"id":"2a7816dfeea3de4f","type":"cronplus","z":"235f16ee6e459f2c","name":"Sunrise","outputField":"payload","timeZone":"","persistDynamic":false,"commandResponseMsgOutput":"output1","outputs":1,"options":[{"name":"schedule1","topic":"topic1","payloadType":"str","payload":"SUNRISE","expressionType":"solar","expression":"0 * * * * * *","location":"-33.880541739956485 151.1226511001587","offset":"0","solarType":"selected","solarEvents":"sunrise"},{"name":"schedule2","topic":"topic2","payloadType":"str","payload":"NOW","expressionType":"cron","expression":"0 31 6 * * * *","location":"","offset":"0","solarType":"all","solarEvents":"sunrise,sunset"}],"x":270,"y":5710,"wires":[["e94a73c29888fba3"]]},{"id":"e94a73c29888fba3","type":"function","z":"235f16ee6e459f2c","name":"Block before 06:31","func":"let now = new Date()\nlet hour = now.getHours()\nlet minute = now.getMinutes()\n\nlet stored = context.get(\"stored_msg\")\n\n// Check if it's the special \"NOW\" message\nif (msg.payload === \"NOW\") {\n    if (stored) {\n        context.set(\"stored_msg\", null)\n        return stored // release stored message\n    } else {\n        return null // nothing to release\n    }\n}\n\n// Otherwise, it's a normal incoming message\nif (hour > 6 || (hour === 6 && minute >= 31)) {\n    return msg // already past 06:31 — send immediately\n} else {\n    context.set(\"stored_msg\", msg) // store it\n    return null // suppress for now\n}\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":5710,"wires":[["0add96b239088263","07eb6cb82143660f"]]}]

Not sure if CRON+ can do this but certainly node-red-contrib-sun-position (node) - Node-RED can. Chose to run the flow when the sun is x degrees above the horizon. That should work all year round.

I think a change node may do what you want;

[{"id":"b9a55710d75cd4be","type":"inject","z":"53a4dcf62732d6d9","name":"","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":630,"y":3840,"wires":[["731be6193db11e63"]]},{"id":"e73cb0826f2d23f0","type":"debug","z":"53a4dcf62732d6d9","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1300,"y":3840,"wires":[]},{"id":"731be6193db11e63","type":"function","z":"53a4dcf62732d6d9","name":"function 5","func":"const fixedDate = new Date(\"August, 2025 06:31:00\")\nconst sunrise = new Date(\"August, 2025 05:45:00\")\n\nmsg.payload = {}\nmsg.payload.fixedDate = fixedDate.getTime()\nmsg.payload.sunrise = sunrise.getTime()\n\nreturn msg","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":800,"y":3840,"wires":[["74ccd6e63f0cde19"]]},{"id":"74ccd6e63f0cde19","type":"change","z":"53a4dcf62732d6d9","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"(payload.fixedDate > payload.sunrise) ? payload.fixedDate : payload.sunrise","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":1040,"y":3840,"wires":[["e73cb0826f2d23f0"]]}]

You may have to massage the inputs as this works with epoch times

Did anyone check the second piece of code I posted in post 7?

Apologies Andrew I thought of what I wanted and then messed up. From your graph it looks as though you are looking for the SECOND message out, in which case this function should do the trick. (it's boggling my mind because here sunrise in the SUMMER is earlier).

if (msg.payload === 'NOW' || msg.payload === 'SUNRISE') {
    const firstIn = context.get('firstIn') ?? 'done'

    if (firstIn === undefined) {
        context.set('firstIn', 'done')

    } else {
        context.set('firstIn', undefined)

        msg = null

    }
}

return msg

This is the FINAL - working - bit of the flow that gives me what is needed.

(Sorry @Buckskin - I think your idea is kind of what I have used but got there a slightly different way.)

[{"id":"812985a1196cf373","type":"cronplus","z":"235f16ee6e459f2c","g":"a73d23da6479c40e","name":"Dawn / Dusk signal","outputField":"payload","timeZone":"","persistDynamic":false,"commandResponseMsgOutput":"output1","outputs":1,"options":[{"name":"Dawn","topic":"schedule1","payloadType":"str","payload":"Dawn","expressionType":"solar","expression":"0 0 6 * 3-8 ? *","location":"-33.51 151.12","offset":"0","solarType":"selected","solarEvents":"sunrise"},{"name":"Dusk","topic":"schedule3","payloadType":"str","payload":"Dusk","expressionType":"solar","expression":"0 * * * * * *","location":"-33.51 151.12","offset":"0","solarType":"selected","solarEvents":"civilDusk"},{"name":"schedule3","topic":"topic3","payloadType":"str","payload":"NOW","expressionType":"cron","expression":"0 31 6 * * * *","location":"","offset":"0","solarType":"all","solarEvents":"sunrise,sunset"}],"x":290,"y":1590,"wires":[["28b1917a09144f26","d73ef66c7899c7ec"]]},{"id":"d73ef66c7899c7ec","type":"junction","z":"235f16ee6e459f2c","g":"a73d23da6479c40e","x":500,"y":1600,"wires":[["92fb3b3ab2c30ea8","50fe326e78eca1b2"]]},{"id":"50fe326e78eca1b2","type":"switch","z":"235f16ee6e459f2c","g":"a73d23da6479c40e","name":"","property":"payload","propertyType":"msg","rules":[{"t":"neq","v":"Dusk","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":565,"y":1580,"wires":[["e94a73c29888fba3"]],"l":false},{"id":"e94a73c29888fba3","type":"function","z":"235f16ee6e459f2c","g":"a73d23da6479c40e","name":"Block before 06:31","func":"let now = new Date()\nlet hour = now.getHours()\nlet minute = now.getMinutes()\n\nlet stored = context.get(\"stored_msg\")\n\n// Check if it's the special \"NOW\" message\nif (msg.payload === \"NOW\") {\n    if (stored) {\n        context.set(\"stored_msg\", null)\n        return stored // release stored message\n    } else {\n        return null // nothing to release\n    }\n}\n\n// Otherwise, it's a normal incoming message\nif (hour > 6 || (hour === 6 && minute >= 31)) {\n    return msg // already past 06:31 — send immediately\n} else {\n    context.set(\"stored_msg\", msg) // store it\n    return null // suppress for now\n}\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":700,"y":1580,"wires":[["0aee3323358e854f","821e3b13014e32b4"]]}]

Nodes used - as it is taken from the working flow now:
cronplus node
junction node
switch node
function node