# Lighting controller ideas

**URL:** <https://discourse.nodered.org/t/lighting-controller-ideas/50876>\
**Category:** General\
**Tags:** lighting\
**Created:** [10 September 2021 22:46 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876 "2021-09-10T22:46:03Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Paul-Reed](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/paul-reed/32/66906_2.png) [@Paul-Reed](https://discourse.nodered.org/u/Paul-Reed)\
**Post date:** [10 September 2021 22:46 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/1 "2021-09-10T22:46:03Z")

</div>

For the past 20 years I've used a [GJD lighting controller](https://www.gjd.co.uk/products/security-lighting/emerald-3000-mk3-gjd010) which switches on security/garden lighting if the GJD PIR's are activated. But with the addition of a [shelly UNI](https://shelly.cloud/products/shelly-uni-smart-home-automation-device/), 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](https://flows.nodered.org/node/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](https://github.com/niklaswall/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?

---

<div class="post-metadata">

**Author:** ![Steve-Mcl](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/steve-mcl/32/4826_2.png) [@Steve-Mcl](https://discourse.nodered.org/u/Steve-Mcl)\
**Post date:** [11 September 2021 06:08 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/2 "2021-09-11T06:08:32Z")

</div>

> [@Paul-Reed](#):
>
> I've played with [node-red-contrib-cron-plus](https://flows.nodered.org/node/node-red-contrib-cron-plus) today, using dynamic schedules, and got close, but still failed to implement the functionality in a meaningful way

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

---

<div class="post-metadata">

**Author:** ![dynamicdave](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/dynamicdave/32/96_2.png) [@dynamicdave](https://discourse.nodered.org/u/dynamicdave)\
**Post date:** [11 September 2021 06:58 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/3 "2021-09-11T06:58:39Z")

</div>

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.

 ![Screen Shot 09-11-21 at 07.43 AM](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/6/c/6c801eb834fea8988a0f0314e29d921d3bf04617.png)  
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.  
 ![Screen Shot 09-11-21 at 08.04 AM](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/1/2/127b49baba22a2be6b062298079f37005f207d93.png)

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.

 ![Screen Shot 09-11-21 at 07.45 AM](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/d/6/d6bbfb407f522adbb4eb6d92089d7c9f65881fc2.png)  
Hope this gives you some ideas of what can be achieved.

---

<div class="post-metadata">

**Author:** ![Paul-Reed](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/paul-reed/32/66906_2.png) [@Paul-Reed](https://discourse.nodered.org/u/Paul-Reed)\
**Post date:** [11 September 2021 08:11 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/4 "2021-09-11T08:11:28Z")

</div>

> [@Steve-Mcl](#):
>
> do you want to share your progress

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.

```auto
[{"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":[]}]

```

---

<div class="post-metadata">

**Author:** ![Paul-Reed](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/paul-reed/32/66906_2.png) [@Paul-Reed](https://discourse.nodered.org/u/Paul-Reed)\
**Post date:** [11 September 2021 08:18 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/5 "2021-09-11T08:18:52Z")

</div>

> [@dynamicdave](#):
>
> Here's a view of the dashboard for my home lighting system

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 😰).

---

<div class="post-metadata">

**Author:** ![dynamicdave](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/dynamicdave/32/96_2.png) [@dynamicdave](https://discourse.nodered.org/u/dynamicdave)\
**Post date:** [11 September 2021 09:28 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/6 "2021-09-11T09:28:29Z")

</div>

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](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/6/c/6c2a6e6a67868d9e00dab29b2fb5e18d53cbd0e9.png)  
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.

---

<div class="post-metadata">

**Author:** ![Steve-Mcl](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/steve-mcl/32/4826_2.png) [@Steve-Mcl](https://discourse.nodered.org/u/Steve-Mcl)\
**Post date:** [11 September 2021 10:54 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/7 "2021-09-11T10:54:09Z")

</div>

> [@Paul-Reed](#):
>
> 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

You dont need the 30s pulse Paul.

This might put you on the right track...

```auto
[{"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"]]}]

```

---

<div class="post-metadata">

**Author:** ![E1cid](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/e1cid/32/77971_2.png) [@E1cid](https://discourse.nodered.org/u/E1cid)\
**Post date:** [11 September 2021 11:02 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/8 "2021-09-11T11:02:23Z")

</div>

Hi  
I have been beta testing MSR you can get release 1.0 [here](https://reactor.toggledbits.com/download/1.0/) 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](https://reactor.toggledbits.com/docs/)  
there is support [here](https://smarthome.community/category/22/multi-system-reactor)

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [11 September 2021 11:13 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/9 "2021-09-11T11:13:03Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![Steve-Mcl](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/steve-mcl/32/4826_2.png) [@Steve-Mcl](https://discourse.nodered.org/u/Steve-Mcl)\
**Post date:** [11 September 2021 11:15 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/10 "2021-09-11T11:15:38Z")

</div>

> [@TotallyInformation](#):
>
> I separately track whether it is daylight or not

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

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [11 September 2021 11:19 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/11 "2021-09-11T11:19:24Z")

</div>

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. 🙂

* * *

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](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/6/b/6bce5770aafbeeae5cd41838871330438799ebc3.png)

Sleep and daylight switches are set using CRON+

Everything is saved to globals as well as MQTT.

---

<div class="post-metadata">

**Author:** ![dynamicdave](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/dynamicdave/32/96_2.png) [@dynamicdave](https://discourse.nodered.org/u/dynamicdave)\
**Post date:** [11 September 2021 11:42 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/12 "2021-09-11T11:42:33Z")

</div>

Here you go...

 ![Screen Shot 09-11-21 at 12.33 PM](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/a/f/afff59421dd35a89b0225380a511fbf06ee7367d.png)  
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.

```auto
[{"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.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [11 September 2021 11:46 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/13 "2021-09-11T11:46:26Z")

</div>

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

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

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [11 September 2021 16:02 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/14 "2021-09-11T16:02:25Z")

</div>

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

![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/b/2/b24a2094fe7bfd02519653a6b1b104ea7fb9c383.png)

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

```auto
/** 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.

---

<div class="post-metadata">

**Author:** ![dceejay](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/dceejay/32/38_2.png) [@dceejay](https://discourse.nodered.org/u/dceejay)\
**Post date:** [11 September 2021 16:48 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/15 "2021-09-11T16:48:46Z")

</div>

> [@TotallyInformation](#):
>
> I would generally use a proper dashboard of course.

… of course … 😉😇

---

<div class="post-metadata">

**Author:** ![Steve-Mcl](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/steve-mcl/32/4826_2.png) [@Steve-Mcl](https://discourse.nodered.org/u/Steve-Mcl)\
**Post date:** [11 September 2021 17:03 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/16 "2021-09-11T17:03:18Z")

</div>

> [@TotallyInformation](#):
>
> I **REALLY** wish that Node-RED's context/flow/global variables had events attached so that you could have a simple listener node.

I made my own if you are interested?

![piJZP4h6xK](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/f/3/f3fdfbfba28767e0002293a00089bde42cb491f9.gif)

---

<div class="post-metadata">

**Author:** ![dceejay](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/dceejay/32/38_2.png) [@dceejay](https://discourse.nodered.org/u/dceejay)\
**Post date:** [11 September 2021 17:09 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/17 "2021-09-11T17:09:18Z")

</div>

![](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/0/c/0c1a6af47cf2026fa360bc73f2973ff44bf8df41.gif)

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [11 September 2021 17:24 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/18 "2021-09-11T17:24:04Z")

</div>

Yes, I am interested 🙂

---

<div class="post-metadata">

**Author:** ![Steve-Mcl](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/steve-mcl/32/4826_2.png) [@Steve-Mcl](https://discourse.nodered.org/u/Steve-Mcl)\
**Post date:** [11 September 2021 17:32 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/19 "2021-09-11T17:32:11Z")

</div>

Here you go...

```auto
[{"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":[]}]

```

---

<div class="post-metadata">

**Author:** ![Paul-Reed](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/paul-reed/32/66906_2.png) [@Paul-Reed](https://discourse.nodered.org/u/Paul-Reed)\
**Post date:** [11 September 2021 20:37 UTC](https://discourse.nodered.org/t/lighting-controller-ideas/50876/20 "2021-09-11T20:37:39Z")

</div>

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 😀

[Next page](https://discourse.nodered.org/t/lighting-controller-ideas/50876.md?page=2)
