Lighting controller ideas

For the past 20 years I've used a GJD lighting controller which switches on security/garden lighting if the GJD PIR's are activated. But with the addition of a shelly UNI, I'm bringing this up to date, so that I can have more control.
The Shelly works great, and now I get MQTT data into node-RED whenever a PIR is activated.

I want to be able to achieve the lights being illuminated;

  • during hours of darkness only
  • whenever a PIR is activated (during darkness only)
  • between dusk and dusk+1hr
  • manually via a dashboard interface

I've played with node-red-contrib-cron-plus today, using dynamic schedules, and got close, but still failed to implement the functionality in a meaningful way.
I've also looked at node-red-contrib-light-scheduler which currently appears unsupported by the developer & has a number of outstanding issues.

Has anyone any suggestions of a suitable framework?

Paul, do you want to share your progress and I'll take a look.

1 Like

Hi Paul,
Here's a view of the dashboard for my home lighting system. It uses BigTimer to determine when to operate the lights (dusk to 22:00 hrs). It has auto/manual mode so I can control it if I wanted to. I can also select which lights should be 'controlled' by the system. The system is over 4-years ago - it uses Sonoff-S20 switches to operate the wall lights at the front of the house and various light-clusters in the garden.


Probably a bit of an over-kill... it uses a finite state machine (FSM) to handle the 'System status' and Manual controls'. I love FSMs and this was a great excuse to make use of one.

Courtsey lighting (which was added about a year ago) uses triggers from my infra-red detectors that are part of my Home Security System. Again I'm using BigTimer to define when the system is activated.


Hope this gives you some ideas of what can be achieved.

2 Likes

As a basic framework, I was intending to inject (possibly by the ui) different scenarios as dynamic schedules, which would then stop/start an additional cron+ node that emits on or off every 30 seconds, which would then be passed to the garden lights using MQTT.
The big flaw, is that if I change a dynamic schedule, there is no initial output, as the timed event may have occurred at an earlier time, and therefore the existing '30 second' output continues regardless.

[{"id":"0e0d405c4867eb2e","type":"cronplus","z":"f8cf7ea5344d6287","name":"schedule","outputField":"payload","timeZone":"","persistDynamic":true,"commandResponseMsgOutput":"output1","outputs":1,"options":[{"name":"schedule1","topic":"schedule1","payloadType":"default","payload":"","expressionType":"solar","expression":"0 * * * * * *","location":"53.50993548027587 -1.0997486114501953","offset":"106","solarType":"selected","solarEvents":"morningGoldenHourEnd"}],"x":295,"y":2680,"wires":[["51ace23ab19da439","eba261c67c41cf78"]]},{"id":"51ace23ab19da439","type":"cronplus","z":"f8cf7ea5344d6287","name":"overide","outputField":"payload","timeZone":"","persistDynamic":true,"commandResponseMsgOutput":"output1","outputs":1,"options":[{"name":"overide","topic":"relayboard/relay2","payloadType":"str","payload":"on","expressionType":"cron","expression":"0/30 * * * * * *","location":"","offset":"0","solarType":"all","solarEvents":"sunrise,sunset"}],"x":495,"y":2660,"wires":[["12149f56fdf9a1db"]]},{"id":"eba261c67c41cf78","type":"debug","z":"f8cf7ea5344d6287","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":480,"y":2625,"wires":[]},{"id":"6ee32238224b239a","type":"inject","z":"f8cf7ea5344d6287","name":"autoOn","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"command\":\"remove-all-dynamic\"},{\"command\":\"add\",\"topic\":\"start\",\"name\":\"autoOn\",\"payloadType\":\"str\",\"payload\":\"overide\",\"limit\":null,\"expressionType\":\"solar\",\"solarType\":\"selected\",\"solarEvents\":\"civilDusk\",\"location\":\"53.47300061858879 -1.0548551380634308\",\"offset\":\"0\"},{\"command\":\"add\",\"topic\":\"stop\",\"name\":\"autoOff\",\"payloadType\":\"str\",\"payload\":\"overide\",\"limit\":null,\"expressionType\":\"solar\",\"solarType\":\"selected\",\"solarEvents\":\"civilDusk\",\"location\":\"53.47300061858879 -1.0548551380634308\",\"offset\":\"60\"}]","payloadType":"json","x":125,"y":2665,"wires":[["0e0d405c4867eb2e"]]},{"id":"da9eac8284c3a9f0","type":"inject","z":"f8cf7ea5344d6287","name":"dusk","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"command\":\"remove-all-dynamic\"},{\"command\":\"add\",\"topic\":\"start\",\"name\":\"duskStart\",\"payloadType\":\"str\",\"payload\":\"overide\",\"limit\":null,\"expressionType\":\"solar\",\"solarType\":\"selected\",\"solarEvents\":\"civilDusk\",\"location\":\"53.47300061858879 -1.0548551380634308\",\"offset\":\"0\"},{\"command\":\"add\",\"topic\":\"stop\",\"name\":\"duskEnd\",\"payloadType\":\"str\",\"payload\":\"overide\",\"limit\":null,\"expressionType\":\"solar\",\"solarType\":\"selected\",\"solarEvents\":\"civilDawn\",\"location\":\"53.47300061858879 -1.0548551380634308\",\"offset\":\"0\"}]","payloadType":"json","x":125,"y":2700,"wires":[["0e0d405c4867eb2e"]]},{"id":"12149f56fdf9a1db","type":"debug","z":"f8cf7ea5344d6287","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":650,"y":2660,"wires":[]}]

Thanks Dave, I had not considered a FSM, and will have a good look this evening.
(I've been told that I've got to paint a room today :cold_sweat:).

FSMs are a great way to deal with sequential events.
Here's a pictorial view of the very simple state machine for my Home Lighting system.
FSM_house_lighting
S0 - system in 'automatic' mode
S1 - system in 'manual' mode and all lights are OFF
S2 - system in 'manual' mode and all lights are ON

Pressing the various buttons forces a transition according to the diagram.

You dont need the 30s pulse Paul.

This might put you on the right track...

[{"id":"6ee32238224b239a","type":"inject","z":"62a0e5a5.fc59bc","name":"create Dusk-Dusk+1h","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"command\":\"remove-all-dynamic\"},{\"command\":\"add\",\"topic\":\"start\",\"name\":\"autoOn\",\"payloadType\":\"str\",\"payload\":\"on\",\"limit\":null,\"expressionType\":\"solar\",\"solarType\":\"selected\",\"solarEvents\":\"civilDusk\",\"location\":\"53.47300061858879 -1.0548551380634308\",\"offset\":\"0\"},{\"command\":\"add\",\"topic\":\"stop\",\"name\":\"autoOff\",\"payloadType\":\"str\",\"payload\":\"off\",\"limit\":null,\"expressionType\":\"solar\",\"solarType\":\"selected\",\"solarEvents\":\"civilDusk\",\"location\":\"53.47300061858879 -1.0548551380634308\",\"offset\":\"60\"}]","payloadType":"json","x":760,"y":1220,"wires":[["c52c9f72081d0bca"]]},{"id":"a04c11f75d25b278","type":"inject","z":"62a0e5a5.fc59bc","name":"Manual On","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"on","payloadType":"str","x":720,"y":1260,"wires":[["9423851524e2d7c1"]]},{"id":"7605bdb19280141d","type":"inject","z":"62a0e5a5.fc59bc","name":"Manual Off","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"off","payloadType":"str","x":720,"y":1300,"wires":[["9423851524e2d7c1"]]},{"id":"9423851524e2d7c1","type":"debug","z":"62a0e5a5.fc59bc","name":"To MQTT","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":1540,"y":1200,"wires":[]},{"id":"c52c9f72081d0bca","type":"cronplus","z":"62a0e5a5.fc59bc","name":"schedule","outputField":"payload","timeZone":"","persistDynamic":true,"commandResponseMsgOutput":"output2","outputs":2,"options":[],"x":1060,"y":1200,"wires":[["9423851524e2d7c1"],[]]},{"id":"666e44e6646803c6","type":"inject","z":"62a0e5a5.fc59bc","name":"PIR signal","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":720,"y":1080,"wires":[["1309559d26b658c0"]]},{"id":"ae3d321225f6ab1b","type":"debug","z":"62a0e5a5.fc59bc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1555,"y":1060,"wires":[],"l":false},{"id":"7b98c8c3c4510dd4","type":"group","z":"62a0e5a5.fc59bc","name":"Test for Day / Night time","style":{"fill":"#bfdbef","fill-opacity":"0.78","label":true,"color":"#000000"},"nodes":["1309559d26b658c0","4137ccddc21bb441","e8a5d153a305ec81","06f7ff3dc13a233f","7bf0a9108416ec08"],"env":[],"x":794,"y":1019,"w":672,"h":122},{"id":"1309559d26b658c0","type":"change","z":"62a0e5a5.fc59bc","g":"7b98c8c3c4510dd4","name":"Get Solar Status","rules":[{"t":"set","p":"payload","pt":"msg","to":"[     {         \"command\": \"describe\",         \"expressionType\": \"solar\",         \"solarType\": \"selected\",         \"solarEvents\": \"civilDusk,civilDawn\",         \"location\": \"53.47300061858879 -1.0548551380634308\"     } ]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":900,"y":1080,"wires":[["4137ccddc21bb441"]]},{"id":"4137ccddc21bb441","type":"cronplus","z":"62a0e5a5.fc59bc","g":"7b98c8c3c4510dd4","name":"describe","outputField":"payload","timeZone":"","persistDynamic":true,"commandResponseMsgOutput":"output2","outputs":2,"options":[],"x":1060,"y":1080,"wires":[[],["e8a5d153a305ec81"]]},{"id":"e8a5d153a305ec81","type":"switch","z":"62a0e5a5.fc59bc","g":"7b98c8c3c4510dd4","name":"","property":"payload.result.nextEvent","propertyType":"msg","rules":[{"t":"eq","v":"civilDusk","vt":"str"},{"t":"eq","v":"civilDawn","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":1210,"y":1080,"wires":[["06f7ff3dc13a233f"],["7bf0a9108416ec08"]]},{"id":"06f7ff3dc13a233f","type":"change","z":"62a0e5a5.fc59bc","g":"7b98c8c3c4510dd4","name":"is Day time","rules":[{"t":"set","p":"payload","pt":"msg","to":"off","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1370,"y":1060,"wires":[["ae3d321225f6ab1b"]]},{"id":"7bf0a9108416ec08","type":"change","z":"62a0e5a5.fc59bc","g":"7b98c8c3c4510dd4","name":"is Night time","rules":[{"t":"set","p":"payload","pt":"msg","to":"on","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1370,"y":1100,"wires":[["9423851524e2d7c1"]]}]

Hi
I have been beta testing MSR you can get release 1.0 here it runs on any system that runs nodejs and you can link multiple HA controllers. so far Vera,ezlo,hubitat,home assistant and others.
It is a logic engine for control of smart devices. and is very powerful.
Take a look
docs
there is support here

1 Like

Any chance you could share the FSM code Dave? My logic is similar to yours in that I separately track whether it is daylight or not (and I also track whether it is "sleep time" or not). However, I'll admit that some of the logic has gotten a bit arcane and could do with some simplification.

The flow I posted above does that part if you are interested.

Thanks Steve, I'm looking at that. But it was Dave's FSM that I was interested in because I'd like to learn more about them. :slight_smile:


I keep track of the sun-rise/set using inject-enhanced. And keep track of the sun and moon positions using the sun-position nodes. These are written to MQTT every 60s

image

Sleep and daylight switches are set using CRON+

Everything is saved to globals as well as MQTT.

Here you go...


I wrote the code years ago, so could do with being updated (when I have some spare time).

Basically I have three flow variables called 'state_register', 'mode' and 'all'.
The 'state_register' can be S0, S1 or S2.
'mode' can be "automatic" or "manual" and 'all' can be "on" or "off".

Here's the NR flow.

[{"id":"3e9f6604.fcbc32","type":"function","z":"48b4f0d6.1b2b4","g":"9f19bc62.be1678","name":"FSM - State Transition Table","func":"if (msg.topic == \"reset\")\n{\n    node.send( {payload: \"turn_lights_off\"} );\n}\n\n\nvar fsm_state = flow.get(\"state_register\") || 0;\n\nvar mode  = flow.get(\"mode\")  || \"automatic\";\nvar all   = flow.get(\"all\")   || \"off\";\nvar timer = flow.get(\"timer\") || \"off\";\n\nswitch (fsm_state)\n   {\n        case \"S0\":\n        if (mode == \"manual\")\n        {\n            fsm_state = \"S1\";\n            flow.set(\"all\", \"off\");\n            node.send( {payload: \"all_on_button\"} );\n        }\n        else\n        {\n            if (timer == \"on\")\n            {\n             flow.set(\"light_command\",\"lights_on\");   \n            }\n            else \n            {\n              flow.set(\"light_command\",\"lights_off\");  \n            }\n            fsm_state = \"S0\";\n        }\n        break;\n       \n        case \"S1\":\n        if (mode == \"automatic\")\n        {\n            fsm_state = \"S0\";\n            flow.set(\"mode\", \"automatic\");\n            node.send( {payload: \"grey_out_buttons\"} );\n        }\n        else if (all == \"on\")\n        {\n            fsm_state = \"S2\";\n            flow.set(\"light_command\",\"lights_on\");\n            node.send( {payload: \"activate_lights\"} );\n        }\n        else\n        {\n            fsm_state = \"S1\";\n        }\n        break;\n       \n        case \"S2\":\n        if (mode == \"automatic\")\n        {   \n            fsm_state = \"S0\";\n            if (timer == \"on\")\n           {\n            flow.set(\"light_command\",\"lights_on\");\n            node.send( {payload: \"activate_lights\"} );\n            node.send( {payload: \"grey_out_buttons\"} );\n           }\n           else\n           {\n            flow.set(\"light_command\",\"lights_off\");\n            node.send( {payload: \"deactivate_lights\"} );\n            node.send( {payload: \"grey_out_buttons\"} );\n           }\n        }\n        else if (all == \"off\")\n        {\n            fsm_state = \"S1\";\n            flow.set(\"light_command\",\"lights_off\");\n            node.send( {payload: \"deactivate_lights\"} );\n           \n        }\n        else\n        {\n            fsm_state = \"S2\";\n        }\n        break;\n       \n   }\n\nflow.set(\"state_register\", fsm_state);\nnode.status({text:\"State register = \" + fsm_state});\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":500,"y":140,"wires":[["aeaf20f9.affd18"]]},{"id":"277b503b.2381a","type":"inject","z":"48b4f0d6.1b2b4","g":"9f19bc62.be1678","name":"Trigger every 3 seconds","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"3","crontab":"","once":true,"onceDelay":"1","topic":"","payload":"1","payloadType":"num","x":190,"y":140,"wires":[["3e9f6604.fcbc32","b8d885b6.09eb98"]]},{"id":"f4a8e0bf.1e14a","type":"inject","z":"48b4f0d6.1b2b4","g":"9f19bc62.be1678","name":"Reset","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":"3","topic":"reset","payload":"true","payloadType":"bool","x":130,"y":260,"wires":[["ed31b0ee.37273","a2214b89.cff708","3e9f6604.fcbc32"]]},{"id":"ed31b0ee.37273","type":"change","z":"48b4f0d6.1b2b4","g":"9f19bc62.be1678","name":"","rules":[{"t":"set","p":"state_register","pt":"flow","to":"S0","tot":"str"},{"t":"set","p":"mode","pt":"flow","to":"automatic","tot":"str"},{"t":"set","p":"all","pt":"flow","to":"off","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":460,"y":260,"wires":[[]]},{"id":"a2214b89.cff708","type":"link out","z":"48b4f0d6.1b2b4","g":"9f19bc62.be1678","name":"initial_reset","links":["32502511.321e02","54ea055.4bf1bfc"],"x":355,"y":220,"wires":[]},{"id":"aeaf20f9.affd18","type":"link out","z":"48b4f0d6.1b2b4","g":"9f19bc62.be1678","name":"button_control","links":["2d42e5b7.3c3cf2","3957f9ee.e0bf0e","5cdb7089.2899d8"],"x":675,"y":140,"wires":[]},{"id":"b8d885b6.09eb98","type":"link out","z":"48b4f0d6.1b2b4","g":"9f19bc62.be1678","name":"System_Clock","links":[],"x":355,"y":180,"wires":[]},{"id":"952dad2d.44e8a8","type":"comment","z":"48b4f0d6.1b2b4","g":"9f19bc62.be1678","name":"Finite State Machine - the clever bit","info":"","x":200,"y":80,"wires":[]},{"id":"dcce51f5.98cd6","type":"comment","z":"48b4f0d6.1b2b4","g":"9f19bc62.be1678","name":"System Clock","info":"","x":150,"y":180,"wires":[]}]

Hope you find it useful.

2 Likes

Thanks Dave, always good to look at other people's code :slight_smile:

Part of the fun of doing this stuff is to learn new or improve old techniques.

1 Like

And so prompted, a new flow for the light by the front door:

image

Output is to MQTT and it updates the Ikea Zigbee plug via Zigbee2MQTT

/** Control a plug based on daylight & sleep settings
 * If either HOME/daylight or HOME/sleep MQTT msgs received,
 * this decides whether the status of the plug should change.
 */

const state = context.get('state', 'file') || ''
let newState = state 

// These are set elsewhere, just read-only here
const homeinfo = global.get('homeInfo', 'file')
const sleep = homeinfo.sleep
const daylight = homeinfo.daylight

// Allow on/off or mqtt device inputs to just update the status
if (msg.topic !== 'zigbee2mqtt/Ikea_Plug_01/set' && msg.topic !== 'zigbee2mqtt/Ikea_Plug_01/state' ) {
    
    // Always off during daylight
    if ( daylight === true ) {
        newState = 'OFF'
    } else { // daylight === false
        // During nighttime, turn off during sleep
        if ( sleep === 'On' ) newState = 'OFF'
        else newState = 'ON'
    }
}

let colour, shape
if ( newState === 'ON' ) {
    colour = 'yellow'
    shape = 'dot'
} else {
    colour = 'grey'
    shape = 'ring'
}
node.status({ fill: colour, shape: shape, text: newState })

// Only send the MQTT update if status changes
if ( state !== newState ) {
    context.set('state', newState, 'file')
    msg.topic = 'zigbee2mqtt/Ikea_Plug_01/set'
    msg.payload = { "state": newState }
    return msg
}

The added complexity is simple to enable the fn node to also act as a quick display of whether the light is on or off. I would generally use a proper dashboard of course.

The HOME topics simply act as an event trigger - I REALLY wish that Node-RED's context/flow/global variables had events attached so that you could have a simple listener node.

1 Like

… of course … :wink::innocent:

1 Like

I made my own if you are interested?

piJZP4h6xK

2 Likes

2 Likes

Yes, I am interested :slight_smile:

Here you go...

[{"id":"3984433f.b0374c","type":"function","z":"553814a2.1248ec","name":"Make Observable","func":"\n/**\n* make properties of `object` Observable\n* @param {string} name  key name to store `object` in `global` or `flow` context\n* @param {object} object  the object to make observable\n*/\nfunction makeObservable(name, object) {\n    let store = null;\n    let nameParts = name.split(\".\");\n    if(nameParts.length < 2) {\n        node.error(\"name must be global.xxx or flow.xxx\", msg);\n        return null;\n    }\n\n    switch (nameParts[0]) {\n        case \"flow\":\n            store = flow;\n            break;\n        case \"global\":\n            store = global;\n            break;\n        default:\n            node.error(\"name must be global.xxx or flow.xxx\", msg);\n            return null;\n    }\n\n    nameParts.shift();//drop the first item\n    name = nameParts.join(\".\");\n\n    if (!object) {\n        object = store.get(name);\n    }\n    if (!object || typeof object != \"object\") {\n        node.error(`msg.payload or '${name}' is not an object`, msg);\n        return null;\n    }\n\n\n    observe(object);\n    store.set(name, object);\n\n    return object;\n\n    /**\n    * @param {string} name\n    * @param {any} value\n    */\n    function notify(name, value) {\n        var m = {\n            topic: name,\n            payload: value\n        }\n        node.send(m);\n    }\n\n    /**\n    * @param {object} obj  the object to make reactive\n    * @param {string} key  the item in the data object\n    */\n    function makeReactive(obj, key) {\n        let val = obj[key];\n        Object.defineProperty(obj, key, {\n            get() {\n                return val;\n            },\n            set(newVal) {\n                val = newVal;\n                notify(name + \".\" + key, val);\n            }\n        })\n    }\n\n    /**\n    * @param {object} obj  the object to observe\n    */\n    function observe(obj) {\n        for (let key in obj) {\n            if (obj.hasOwnProperty(key)) {\n                makeReactive(obj, key);\n            }\n        }\n    }\n}\n\nmsg.payload = makeObservable(msg.topic, msg.payload);\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":2610,"y":480,"wires":[["d3ad2877.7bdec8"]]},{"id":"83d19ad4.6d3768","type":"inject","z":"553814a2.1248ec","name":"Observe global.sensor.values","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"global.sensor.values","payload":"{\"sensor1\":0,\"sensor2\":0,\"sensor3\":0,\"sensor4\":0}","payloadType":"json","x":2260,"y":360,"wires":[["3984433f.b0374c"]]},{"id":"e0229828.fbc678","type":"inject","z":"553814a2.1248ec","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"false","payloadType":"bool","x":2670,"y":620,"wires":[["97b6542.bc93aa8"]]},{"id":"49baae3b.93f0f","type":"inject","z":"553814a2.1248ec","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":2670,"y":580,"wires":[["97b6542.bc93aa8"]]},{"id":"97b6542.bc93aa8","type":"change","z":"553814a2.1248ec","name":"","rules":[{"t":"set","p":"sensor.values.sensor2","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":2890,"y":600,"wires":[[]]},{"id":"6cff8090.6601f","type":"inject","z":"553814a2.1248ec","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"high","payloadType":"str","x":2190,"y":620,"wires":[["e30a0606.89df38"]]},{"id":"8cc38216.10979","type":"inject","z":"553814a2.1248ec","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"low","payloadType":"str","x":2190,"y":580,"wires":[["e30a0606.89df38"]]},{"id":"e30a0606.89df38","type":"change","z":"553814a2.1248ec","name":"","rules":[{"t":"set","p":"sensor.values.sensor1","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":2430,"y":600,"wires":[[]]},{"id":"a0fba143.0a78a","type":"inject","z":"553814a2.1248ec","name":"Observe flow.plc_values","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"flow.plc_values","payload":"{\"DM0001\":0,\"DM0002\":0,\"DM0003\":0,\"DM0004\":0}","payloadType":"json","x":2250,"y":460,"wires":[["3984433f.b0374c"]]},{"id":"c3d1d0db.df3a8","type":"change","z":"553814a2.1248ec","name":"","rules":[{"t":"set","p":"plc_values.DM0001","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":2420,"y":800,"wires":[[]]},{"id":"d7a4a609.804718","type":"inject","z":"553814a2.1248ec","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"2222","payloadType":"num","x":2190,"y":820,"wires":[["c3d1d0db.df3a8"]]},{"id":"4b832cc0.a11d94","type":"inject","z":"553814a2.1248ec","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1111","payloadType":"num","x":2190,"y":780,"wires":[["c3d1d0db.df3a8"]]},{"id":"8173e9b.5b56e18","type":"change","z":"553814a2.1248ec","name":"","rules":[{"t":"set","p":"plc_values.DM0005","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":2880,"y":800,"wires":[[]]},{"id":"eba61d84.683d9","type":"inject","z":"553814a2.1248ec","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"66","payloadType":"num","x":2670,"y":820,"wires":[["8173e9b.5b56e18"]]},{"id":"ac24a89f.7fd018","type":"inject","z":"553814a2.1248ec","name":"","props":[{"p":"payload"},{"p":"topic","v":"55","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"55","payloadType":"num","x":2670,"y":780,"wires":[["8173e9b.5b56e18"]]},{"id":"9b185180.5b2b3","type":"inject","z":"553814a2.1248ec","name":"Refresh flow.plc_values","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"flow.plc_values","x":2240,"y":500,"wires":[["3984433f.b0374c"]]},{"id":"45313bd2.09c144","type":"inject","z":"553814a2.1248ec","name":"Refresh global.sensor.values","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"global.sensor.values","x":2260,"y":400,"wires":[["3984433f.b0374c"]]},{"id":"d6f4bd71.a2c8e","type":"change","z":"553814a2.1248ec","name":"","rules":[{"t":"set","p":"sensor.values.sensor5","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":2430,"y":680,"wires":[[]]},{"id":"f641e704.8a0338","type":"inject","z":"553814a2.1248ec","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"123.456","payloadType":"num","x":2200,"y":700,"wires":[["d6f4bd71.a2c8e"]]},{"id":"21bf4e02.f248d2","type":"inject","z":"553814a2.1248ec","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"543.210","payloadType":"num","x":2200,"y":660,"wires":[["d6f4bd71.a2c8e"]]},{"id":"698c39c4dd359765","type":"group","z":"553814a2.1248ec","name":"","style":{"stroke":"#ff7f7f","stroke-opacity":"0.86","label":true,"fill":"#ff7f7f","fill-opacity":"0.28"},"nodes":["9df73953f604e906","880a13bd644bad88","eac6e314211ea3f0","d3ad2877.7bdec8","b0703dcf4fb665d8","1d96f8b68f16c3b3","7e2c7bd2158d0f13","7749ee1d409c6e16","103d8b20eec86085","ffcfd386d852e8f3","e2d004abed594f59","82c2e22730c8deb1","1ea1e6eac0f30b6a"],"env":[],"x":2794,"y":279,"w":212,"h":242},{"id":"9df73953f604e906","type":"comment","z":"553814a2.1248ec","g":"698c39c4dd359765","name":"WATCH THIS","info":"","x":2895,"y":440,"wires":[],"l":false},{"id":"880a13bd644bad88","type":"comment","z":"553814a2.1248ec","g":"698c39c4dd359765","name":"WATCH THIS","info":"","x":2895,"y":420,"wires":[],"l":false},{"id":"eac6e314211ea3f0","type":"comment","z":"553814a2.1248ec","g":"698c39c4dd359765","name":"WATCH THIS","info":"","x":2895,"y":420,"wires":[],"l":false},{"id":"d3ad2877.7bdec8","type":"debug","z":"553814a2.1248ec","g":"698c39c4dd359765","name":"","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"topic & \": \" & payload","statusType":"jsonata","x":2890,"y":480,"wires":[]},{"id":"b0703dcf4fb665d8","type":"comment","z":"553814a2.1248ec","g":"698c39c4dd359765","name":"WATCH THIS","info":"","x":2875,"y":420,"wires":[],"l":false},{"id":"1d96f8b68f16c3b3","type":"comment","z":"553814a2.1248ec","g":"698c39c4dd359765","name":"WATCH THIS","info":"","x":2915,"y":420,"wires":[],"l":false},{"id":"7e2c7bd2158d0f13","type":"comment","z":"553814a2.1248ec","g":"698c39c4dd359765","name":"WATCH THIS","info":"","x":2855,"y":400,"wires":[],"l":false},{"id":"7749ee1d409c6e16","type":"comment","z":"553814a2.1248ec","g":"698c39c4dd359765","name":"WATCH THIS","info":"","x":2935,"y":400,"wires":[],"l":false},{"id":"103d8b20eec86085","type":"comment","z":"553814a2.1248ec","g":"698c39c4dd359765","name":"WATCH THIS","info":"","x":2895,"y":400,"wires":[],"l":false},{"id":"ffcfd386d852e8f3","type":"comment","z":"553814a2.1248ec","g":"698c39c4dd359765","name":"WATCH THIS","info":"","x":2895,"y":380,"wires":[],"l":false},{"id":"e2d004abed594f59","type":"comment","z":"553814a2.1248ec","g":"698c39c4dd359765","name":"WATCH THIS","info":"","x":2895,"y":360,"wires":[],"l":false},{"id":"82c2e22730c8deb1","type":"comment","z":"553814a2.1248ec","g":"698c39c4dd359765","name":"WATCH THIS","info":"","x":2895,"y":340,"wires":[],"l":false},{"id":"1ea1e6eac0f30b6a","type":"comment","z":"553814a2.1248ec","g":"698c39c4dd359765","name":"WATCH THIS","info":"","x":2890,"y":320,"wires":[]}]
4 Likes

Thanks everyone for the examples, you've given me lots to think about.
The way forward for me seems to be taking parts from each of the suggestions and build it in a function node, with timing support from Cron+.
It's a lot clearer now :grinning: