That's very responsible / perfectionistic of you
Unfortunately, I have no shame, so I'll post the updated / unfinished version of my lighting / zone / scene "database" project here
- Updated version shows "luminaire" to "zone" mappings within the "zone" table, each with its own "remove" button, which removes the mapping.
- There are notes in the comment node within the flow posted here, more work to do. e.g. delete mappings when you delete a light or a zone.
[{"id":"8a93364b.187ff8","type":"comment","z":"705d403b.f2b8","name":"Version 2","info":"Stuff to tidy\n- delete zone -> remove mappings\n- delete fixture -> remove mappings\n\nRight now if we delete a zone, when there's\nalready a mapping, it crashes the zone view!\n\nWe should therefore do the above AND\nput some error checking into the zone list\nget function.\n\n\n6/10/2019\n---------\nStart with the things we want to do / make easy for installation\n- Ongoing\n - tweak lighting scenes\n - re-assign roving lights to different zones\n\n- Setup\n - Create fixings from the UI? If we are using DMX we could standardise the\n output message, and set this in the UI as well. Otherwise, what's the\n point? We'd still have to create the thing in Node-RED that sends the\n message to the light. Well, maybe there is a point. Maybe it's just\n useful to see the contents of memory in realtime from the front end,\n as you add devices. We could set the MQTT path from front end as well.\n Then all we'd have to do is set up the device and \"attach\" to the MQTT\n path.\n - Create zones and map fixings to zones\n - Create zone presets\n - List out the zones and mapped fixings\n - List out the zone presets\n\nThen move on to how we want to store stuff in memory.\n- home.light.fixture\n- home.light.zone\n- home.light.preset\n- home.light.mapping.fixture_zone\n- home.light.mapping.preset_zone\n\n\n\nLights are standalone items\nWithin their definition, their location is not set\n\nWe also have room definitions, each room is a simple list of light\nindexes.\n\n\n{\n\t\"lights\" : [\n\t\t{\n\t\t\t\"id\"\t\t\t:\t\"3f_bedroom_ceiling\",\n\t\t\t\"friendly\"\t\t: \t\"3rd floor bedroom ceiling\",\n\t\t\t\"type\"\t\t\t:\t\"dimmable single colour\",\n\t\t\t\"topic\"\t\t\t:\t\"home/light/1\",\n\t\t\t\"defaultstate\"\t:\t20,\n\t\t\t\"currentstate\"\t:\t50\n\t\t},\n\t\t{\n\t\t\t\"id\"\t\t\t:\t\"3f_bedroom_bedside\",\n\t\t\t\"friendly\"\t\t: \t\"3rd floor bedroom bedside\",\n\t\t\t\"type\"\t\t\t:\t\"dimmable single colour\",\n\t\t\t\"topic\"\t\t\t:\t\"home/light/2\",\n\t\t\t\"defaultstate\"\t:\t20,\n\t\t\t\"currentstate\"\t:\t50\n\t\t}\n\t],\n\t\"zones\"\t: [\n\t\t{\n\t\t\t\"id\"\t\t\t: \"3f_bedroom\",\n\t\t\t\"friendly\"\t\t: \"Master bedroom\"\n\t\t\t\"members\"\t\t: [\"3f_bedroom_ceiling\", \"3f_bedroom_bedside\"],\n\t\t\t\"scenes\"\t\t: [\n\t\t\t\t{\n\t\t\t\t\t\"Name\":\"Off\",\n\t\t\t\t\t\"Values\" : [\n\t\t\t\t\t\t\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"id\"\t\t\t: \"3f_living\",\n\t\t\t\"friendly\"\t\t: \"Living Room\",\n\t\t\t\"members\"\t\t: [\"3f_living_ceiling\", \"3f_living_wall\",\"huego1\"]\n\t\t}\n\t]\n}\n\n\n","x":2800,"y":1860,"wires":[]},{"id":"7dc1ca73.ae1494","type":"ui_text_input","z":"705d403b.f2b8","name":"","label":"ID","tooltip":"","group":"1a37e545.435d2b","order":1,"width":3,"height":1,"passthru":false,"mode":"text","delay":"350","topic":"","x":3150,"y":1900,"wires":[["e5afc774.989118"]]},{"id":"e5afc774.989118","type":"change","z":"705d403b.f2b8","name":"","rules":[{"t":"set","p":"newfixture.ID","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":3420,"y":1900,"wires":[[]]},{"id":"40d1dfbc.93ecb","type":"ui_button","z":"705d403b.f2b8","name":"","group":"1a37e545.435d2b","order":5,"width":1,"height":1,"passthru":false,"label":"Save","tooltip":"","color":"","bgcolor":"","icon":"","payload":"nothing","payloadType":"str","topic":"","x":2810,"y":2020,"wires":[["678588e9.2d24c8"]]},{"id":"7dbff401.90176c","type":"ui_text_input","z":"705d403b.f2b8","name":"","label":"Friendly location","tooltip":"","group":"1a37e545.435d2b","order":2,"width":3,"height":1,"passthru":true,"mode":"text","delay":"1000","topic":"","x":3180,"y":1920,"wires":[["65ce0f79.994dc"]]},{"id":"65ce0f79.994dc","type":"change","z":"705d403b.f2b8","name":"","rules":[{"t":"set","p":"newfixture.friendly_location","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":3460,"y":1920,"wires":[[]]},{"id":"678588e9.2d24c8","type":"function","z":"705d403b.f2b8","name":"New fixture","func":"// Save new fixture entered in Dashboard to global context\n// Includes form validation and duplicate check\n// and supports clearing the form when done correctly\n\n// check form was filled in properly\nif (typeof flow.get(\"newfixture\") == 'undefined') {\n msg.payload = \"You didn't enter anything - fixture not saved\"\n msg.error = true\n return msg\n}\n\n// assume newfixture object exists (some part of form was filled)\nvar newfixture = flow.get(\"newfixture\")\n\n// if form not complete\nif (typeof newfixture.ID == 'undefined' ||\n newfixture.ID === \"\" ||\n typeof newfixture.friendly_location == 'undefined' ||\n newfixture.friendly_location === \"\" ||\n typeof newfixture.friendly_name == 'undefined' ||\n newfixture.friendly_name === \"\" ||\n typeof newfixture.type == 'undefined' ||\n newfixture.type === \"\") {\n msg.payload = \"Missing info - fixture not saved\"\n msg.error = true\n return msg\n}\n\n// if we've got this far, we have a good new fixture to add\n// create global fixtures object if it's not there\nif (typeof global.get(\"home.light.config.fixtures\") == 'undefined') {\n global.set(\"home.light.config.fixtures\", {})\n}\n\n// get light fixtures object\nvar fixtures = global.get(\"home.light.config.fixtures\")\n\n// does fixture ID exist?\n\nif (fixtures.hasOwnProperty(newfixture.ID)) {\n msg.payload = \"oops, ID already exists\"\n msg.error = true\n return msg\n} else {\n d = new Date().toLocaleDateString(); t = new Date().toLocaleTimeString();\n fixtures[newfixture.ID] = {\n \"friendly_location\": newfixture.friendly_location,\n \"friendly_name\": newfixture.friendly_name,\n \"type\": newfixture.type,\n \"created_date\": d,\n \"created_time\": t\n }\n \n global.set(\"home.light.config.fixtures\",fixtures)\n msg.payload = \"Success - added fixture\"\n msg.error = false\n // now remove item from flow context, otherwise our\n // validation tests above will always pass\n flow.set(\"newfixture\", undefined);\n \n return msg\n}","outputs":1,"noerr":0,"x":2950,"y":2020,"wires":[["13efd823.365108","e05ac37e.8fa61"]]},{"id":"178c7bb6.0832d4","type":"ui_dropdown","z":"705d403b.f2b8","name":"Type","label":"","tooltip":"","place":"Control Type","group":"1a37e545.435d2b","order":4,"width":3,"height":1,"passthru":true,"options":[{"label":"Non Dimmable (Switch)","value":"1_non_dim","type":"str"},{"label":"Dimmable Single Colour","value":"2_single_colour","type":"str"},{"label":"RGB","value":"3_rgb","type":"str"},{"label":"RGBW","value":"4_RGBW","type":"str"},{"label":"RGBWW","value":"5_RGBWW","type":"str"},{"label":"Individually Addressable","value":"6_individually_addressable","type":"str"}],"payload":"","topic":"","x":3150,"y":1960,"wires":[["8715f8dc.f16588"]]},{"id":"8715f8dc.f16588","type":"change","z":"705d403b.f2b8","name":"","rules":[{"t":"set","p":"newfixture.type","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":3430,"y":1960,"wires":[[]]},{"id":"13efd823.365108","type":"ui_template","z":"705d403b.f2b8","group":"1a37e545.435d2b","name":"New fixture validation","order":6,"width":"8","height":1,"format":"{{msg.payload}}","storeOutMessages":true,"fwdInMessages":false,"templateScope":"local","x":3400,"y":2020,"wires":[[]]},{"id":"efc2b541.5fa958","type":"delay","z":"705d403b.f2b8","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":3220,"y":2060,"wires":[["13efd823.365108"]]},{"id":"e05ac37e.8fa61","type":"function","z":"705d403b.f2b8","name":"Success","func":"// Save new fixture: clear form & result if successful\n\nif (!msg.error) {\n msg.payload = \"\"\n return msg;\n}\n","outputs":1,"noerr":0,"x":3060,"y":2060,"wires":[["efc2b541.5fa958","7dc1ca73.ae1494","7dbff401.90176c","178c7bb6.0832d4","9126cc5a.af8f3","c501b125.62688"]]},{"id":"77a3d35d.3ceecc","type":"change","z":"705d403b.f2b8","name":"","rules":[{"t":"set","p":"newfixture.friendly_name","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":3460,"y":1940,"wires":[[]]},{"id":"9126cc5a.af8f3","type":"ui_text_input","z":"705d403b.f2b8","name":"","label":"Friendly Fitting Type","tooltip":"","group":"1a37e545.435d2b","order":3,"width":3,"height":1,"passthru":true,"mode":"text","delay":"1000","topic":"","x":3200,"y":1940,"wires":[["77a3d35d.3ceecc"]]},{"id":"c501b125.62688","type":"change","z":"705d403b.f2b8","name":"Get","rules":[{"t":"set","p":"fixtures","pt":"msg","to":"home.light.config.fixtures","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":3210,"y":2120,"wires":[["8c47badb.7f7df8"]]},{"id":"8c47badb.7f7df8","type":"ui_template","z":"705d403b.f2b8","group":"1a37e545.435d2b","name":"Fixtures List","order":7,"width":"12","height":"8","format":"<h3>Register of Light Fixtures</h3>\n\n<table>\n <tr style=\"font-weight:800; background-color:rgba(20,20,20,.6);\">\n <td>ID</td>\n <td>Location</td>\n <td>Name</td>\n <td>Type</td>\n <td>Added</td>\n <td>Delete*</td>\n </tr>\n <tr ng-repeat=\"(key, value) in msg.fixtures\">\n <td>{{key}}</td>\n <td>{{value.friendly_location}}</td>\n <td>{{value.friendly_name}}</td>\n <td>{{value.type}}</td>\n <td style=\"color:#888;\">{{value.created_time}} {{value.created_date}}</td>\n <td><md-button ng-click=\"send({action: 'delete', topic: key})\">Del</md-button></td>\n </tr>\n</table>","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":3350,"y":2120,"wires":[["7dadd06a.5a505"]]},{"id":"7dadd06a.5a505","type":"function","z":"705d403b.f2b8","name":"Set","func":"// get object from memory\n// NB we are using \"bracket notation\" just in case the ID\n// (i.e. the object name, represented here as msg.topic)\n// had a space\n\nif (msg.action == \"delete\") {\n obj = global.get(\"home.light.config.fixtures\")\n delete obj[msg.topic]\n global.set(\"home.light.config.fixtures\", obj)\n}\n\nreturn msg;\n","outputs":1,"noerr":0,"x":3490,"y":2120,"wires":[["c501b125.62688"]]},{"id":"5ea5059b.ac0cac","type":"inject","z":"705d403b.f2b8","name":"Update view","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":3030,"y":2120,"wires":[["c501b125.62688"]]},{"id":"4714f8ee.5b3398","type":"ui_text_input","z":"705d403b.f2b8","name":"","label":"ID","tooltip":"","group":"1386e1df.83b3ae","order":1,"width":3,"height":1,"passthru":false,"mode":"text","delay":"350","topic":"","x":3150,"y":2240,"wires":[["7797971f.f78778"]]},{"id":"7797971f.f78778","type":"change","z":"705d403b.f2b8","name":"","rules":[{"t":"set","p":"newzone.ID","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":3420,"y":2240,"wires":[[]]},{"id":"5b4809a9.073948","type":"ui_button","z":"705d403b.f2b8","name":"","group":"1386e1df.83b3ae","order":3,"width":1,"height":1,"passthru":false,"label":"Save","tooltip":"","color":"","bgcolor":"","icon":"","payload":"nothing","payloadType":"str","topic":"","x":2810,"y":2320,"wires":[["77413dc7.e8c9b4"]]},{"id":"77413dc7.e8c9b4","type":"function","z":"705d403b.f2b8","name":"New zone","func":"// Save new zone entered in Dashboard to global context\n// Includes form validation and duplicate check\n// and supports clearing the form when done correctly\n\n// check form was filled in properly\nif (typeof flow.get(\"newzone\") == 'undefined') {\n msg.payload = \"You didn't enter anything - zone not saved\"\n msg.error = true\n return msg\n}\n\n// assume newzone object exists (some part of form was filled)\nvar newzone = flow.get(\"newzone\")\n\n// if form not complete\nif (typeof newzone.ID == 'undefined' ||\n newzone.ID === \"\" ||\n typeof newzone.friendly_name == 'undefined' ||\n newzone.friendly_name === \"\") {\n msg.payload = \"Missing info - zone not saved\"\n msg.error = true\n return msg\n}\n\n// if we've got this far, we have a good new zone to add\n// create global zones object if it's not there\nif (typeof global.get(\"home.light.config.zones\") == 'undefined') {\n global.set(\"home.light.config.zones\", {})\n}\n\n// get light zones object\nvar zones = global.get(\"home.light.config.zones\")\n\n// does fixture ID exist?\n\nif (zones.hasOwnProperty(newzone.ID)) {\n msg.payload = \"oops, ID already exists\"\n msg.error = true\n return msg\n} else {\n d = new Date().toLocaleDateString(); t = new Date().toLocaleTimeString();\n zones[newzone.ID] = {\n \"friendly_name\": newzone.friendly_name,\n \"created_date\": d,\n \"created_time\": t\n }\n \n global.set(\"home.light.config.zones\",zones)\n msg.payload = \"Success - added zone\"\n msg.error = false\n // now remove item from flow context, otherwise our\n // validation tests above will always pass\n flow.set(\"newzone\", undefined);\n \n return msg\n}","outputs":1,"noerr":0,"x":2940,"y":2320,"wires":[["58c8c589.5d48dc","549ddba1.fb5e44"]]},{"id":"58c8c589.5d48dc","type":"ui_template","z":"705d403b.f2b8","group":"1386e1df.83b3ae","name":"New zone validation","order":4,"width":"8","height":1,"format":"{{msg.payload}}","storeOutMessages":true,"fwdInMessages":false,"templateScope":"local","x":3400,"y":2320,"wires":[[]]},{"id":"dcf2a212.18aa1","type":"delay","z":"705d403b.f2b8","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":3220,"y":2360,"wires":[["58c8c589.5d48dc"]]},{"id":"549ddba1.fb5e44","type":"function","z":"705d403b.f2b8","name":"Success","func":"// Save new zone: clear form & result if successful\n\nif (!msg.error) {\n msg.payload = \"\"\n return msg;\n}\n","outputs":1,"noerr":0,"x":3060,"y":2360,"wires":[["dcf2a212.18aa1","4714f8ee.5b3398","ddc87a2a.8ae778","6a236e08.0a12d"]]},{"id":"8a402bb0.963de8","type":"ui_template","z":"705d403b.f2b8","group":"1386e1df.83b3ae","name":"Zones List","order":5,"width":"12","height":"8","format":"<style>\n table {border: none;}\n .nr-dashboard-theme .nr-dashboard-template .md-button:not(:first-of-type) {\n margin-top: 0px;\n }\n .nr-dashboard-theme .nr-dashboard-template .md-button {\n margin-right: 0 10px 0 0;\n min-height: 0;\n min-width: unset;\n line-height: unset;\n height: 21px;\n }\n .tags {\n font-size: 11px;\n color: #ddd;\n border: 1px solid #555;\n background-color: rgba(9, 116, 121, 0.31);\n padding: 2px;\n margin: 0 0px 0 5px;\n }\n .datarow td {\n padding: 5px 0 0px 0px;\n background-color: #404040;\n }\n .tagcol td {\n padding: 0 0 10px 0;\n background: #404040;\n border-bottom: 10px solid #333; \n }\n .tagcol {\n\n }\n</style>\n\n<h3>Register of Light Zones</h3>\n\n<table cellspacing=\"0\" cellpadding=\"0\">\n <tr style=\"font-weight:800; background-color:rgba(20,20,20,.6);\">\n <td>ID</td>\n <td>Name</td>\n <td>Added</td>\n <td>Delete*</td>\n </tr>\n <tr class=\"datarow\" ng-repeat-start=\"(key, value) in msg.zones\">\n <td>{{key}}</td>\n <td>{{value.friendly_name}}</td>\n <td style=\"color:#888;\">{{value.created_time}} {{value.created_date}}</td>\n <td><md-button ng-click=\"send({action: 'delete', topic: key})\">Del</md-button></td>\n </tr>\n <tr class=\"tagcol\" ng-repeat-end>\n <td colspan=\"5\">\n <span class=\"tags\" ng-repeat-start=\"val1 in value.fixtures\">{{val1}}</span>\n <md-button class=\"minibutton\" ng-repeat-end ng-click=\"send({action: 'deletefixturemapping', topic: [key,val1]})\">x</md-button>\n </td>\n </tr>\n</table>\n","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":3350,"y":2420,"wires":[["ab08a35d.f692d","cb5e87f1.d11458"]]},{"id":"ab08a35d.f692d","type":"function","z":"705d403b.f2b8","name":"Set","func":"// get object from memory\n// NB we are using \"bracket notation\" just in case the ID\n// (i.e. the object name, represented here as msg.topic)\n// had a space\n\nif (msg.action == \"delete\") {\n obj = global.get(\"home.light.config.zones\")\n delete obj[msg.topic]\n global.set(\"home.light.config.zones\", obj)\n}\n\nif (msg.action == \"deletefixturemapping\") {\n obj = global.get(\"home.light.config.fixture_zone_mappings\")\n delete obj[msg.topic[0] + \"_\" + msg.topic[1]]\n global.set(\"home.light.config.fixture_zone_mappings\", obj)\n}\n\nreturn msg;\n","outputs":1,"noerr":0,"x":3490,"y":2420,"wires":[["6a236e08.0a12d","e2fb5079.17931"]]},{"id":"1dabe67a.a9504a","type":"inject","z":"705d403b.f2b8","name":"Update view","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":3030,"y":2420,"wires":[["6a236e08.0a12d"]]},{"id":"ddc87a2a.8ae778","type":"ui_text_input","z":"705d403b.f2b8","name":"","label":"Friendly Name","tooltip":"","group":"1386e1df.83b3ae","order":2,"width":3,"height":1,"passthru":true,"mode":"text","delay":"1000","topic":"","x":3180,"y":2260,"wires":[["9c619228.80fde"]]},{"id":"9c619228.80fde","type":"change","z":"705d403b.f2b8","name":"","rules":[{"t":"set","p":"newzone.friendly_name","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":3450,"y":2260,"wires":[[]]},{"id":"aed3ce62.0a993","type":"change","z":"705d403b.f2b8","name":"","rules":[{"t":"set","p":"new_fixture_zone_mapping.fixtureID","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":3490,"y":2580,"wires":[[]]},{"id":"c45f942b.25fb78","type":"ui_button","z":"705d403b.f2b8","name":"","group":"1598a8ff.884b77","order":3,"width":1,"height":1,"passthru":false,"label":"Assign","tooltip":"","color":"","bgcolor":"","icon":"","payload":"nothing","payloadType":"str","topic":"","x":2710,"y":2660,"wires":[["bcc049ac.db8cd8"]]},{"id":"bcc049ac.db8cd8","type":"function","z":"705d403b.f2b8","name":"New fixture_zone_mapping","func":"// NEEDS RE-WRITING FOR FIXTURE TO ZONE ASSIGNMENT\n// ID will be automatically generated as per:\n// zoneID_fixtureID\n// if such an ID exists, then message: already exists in zone\n// Don't forget to update code for deleting a zone and\n// a fixture. Each should check for corresponding entries\n// in the mappings object and remove these entries\n\n\n// Save new zone entered in Dashboard to global context\n// Includes form validation and duplicate check\n// and supports clearing the form when done correctly\n\n// check form was filled in properly\nif (typeof flow.get(\"new_fixture_zone_mapping\") == 'undefined') {\n msg.payload = \"You didn't enter anything - fixture not assigned\"\n msg.error = true\n return msg\n}\n\n// assume newzone object exists (some part of form was filled)\nvar new_fixture_zone_mapping = flow.get(\"new_fixture_zone_mapping\")\n\n// if form not complete\nif (typeof new_fixture_zone_mapping.fixtureID == 'undefined' ||\n new_fixture_zone_mapping.fixtureID === \"\" ||\n typeof new_fixture_zone_mapping.zoneID == 'undefined' ||\n new_fixture_zone_mapping.zoneID === \"\") {\n msg.payload = \"Missing info - fixture not assigned\"\n msg.error = true\n return msg\n}\n\n// if we've got this far, we have a good assignment to create\n// create global assignment object if it's not there\nif (typeof global.get(\"home.light.config.fixture_zone_mappings\") == 'undefined') {\n global.set(\"home.light.config.fixture_zone_mappings\", {})\n}\n\n// get light fixture zone mappings object\nvar fixture_zone_mappings = global.get(\"home.light.config.fixture_zone_mappings\")\n\n// does mapping ID exist? (it will look like this: zoneID_fixtureID)\nvar newID = new_fixture_zone_mapping.zoneID + \"_\" + new_fixture_zone_mapping.fixtureID\n\nif (fixture_zone_mappings.hasOwnProperty(newID)) {\n msg.payload = \"oops, fixture already added to zone\"\n msg.error = true\n return msg\n} else {\n d = new Date().toLocaleDateString(); t = new Date().toLocaleTimeString();\n fixture_zone_mappings[newID] = {\n \"zoneID\": new_fixture_zone_mapping.zoneID, // technically not necessary\n \"fixtureID\": new_fixture_zone_mapping.fixtureID, // technically not necessary\n \"created_date\": d,\n \"created_time\": t\n }\n \n global.set(\"home.light.config.fixture_zone_mappings\",fixture_zone_mappings)\n msg.payload = \"Success - assigned fixture to zone\"\n msg.error = false\n // now remove item from flow context, otherwise our\n // validation tests above will always pass\n flow.set(\"new_fixture_zone_mapping\", undefined);\n \n return msg\n}","outputs":1,"noerr":0,"x":2900,"y":2660,"wires":[["4bafda67.14c724","c903cb21.a24cd8"]]},{"id":"4bafda67.14c724","type":"ui_template","z":"705d403b.f2b8","group":"1598a8ff.884b77","name":"New fixture-zone validation","order":4,"width":"8","height":1,"format":"{{msg.payload}}","storeOutMessages":true,"fwdInMessages":false,"templateScope":"local","x":3420,"y":2660,"wires":[[]]},{"id":"121995cb.dae1aa","type":"delay","z":"705d403b.f2b8","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":3220,"y":2700,"wires":[["4bafda67.14c724"]]},{"id":"c903cb21.a24cd8","type":"function","z":"705d403b.f2b8","name":"Success","func":"// Save new zone: clear form & result if successful\n\nif (!msg.error) {\n msg.payload = \"\"\n return msg;\n}\n","outputs":1,"noerr":0,"x":3060,"y":2700,"wires":[["121995cb.dae1aa","a3100af0.716338","b7e6e6f8.691188","a944297e.5ee6f8","1e90979b.b14858"]]},{"id":"a3100af0.716338","type":"change","z":"705d403b.f2b8","name":"Get","rules":[{"t":"set","p":"fixture_zone_mappings","pt":"msg","to":"home.light.config.fixture_zone_mappings","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":3210,"y":2760,"wires":[["4e545849.410518"]]},{"id":"4e545849.410518","type":"ui_template","z":"705d403b.f2b8","group":"1598a8ff.884b77","name":"Fixture to Zone Mappings List","order":5,"width":"12","height":"8","format":"<h3>Register of Fixture to Zone Mappings</h3>\n\n<table>\n <tr style=\"font-weight:800; background-color:rgba(20,20,20,.6);\">\n <td>ID</td>\n <td>Zone</td>\n <td>Fixture</td>\n <td>Added</td>\n <td>Delete*</td>\n </tr>\n <tr ng-repeat=\"(key, value) in msg.fixture_zone_mappings\">\n <td>{{key}}</td>\n <td>{{value.zoneID}}</td>\n <td>{{value.fixtureID}}</td>\n <td style=\"color:#888;\">{{value.created_time}} {{value.created_date}}</td>\n <td><md-button ng-click=\"send({action: 'delete', topic: key})\">Del</md-button></td>\n </tr>\n</table>","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":3410,"y":2760,"wires":[["c0c7b85b.4670e8"]]},{"id":"c0c7b85b.4670e8","type":"function","z":"705d403b.f2b8","name":"Set","func":"// get object from memory\n// NB we are using \"bracket notation\" just in case the ID\n// (i.e. the object name, represented here as msg.topic)\n// had a space\n\nif (msg.action == \"delete\") {\n obj = global.get(\"home.light.config.fixture_zone_mappings\")\n delete obj[msg.topic]\n global.set(\"home.light.config.fixture_zone_mappings\", obj)\n}\n\n\nreturn msg;\n","outputs":1,"noerr":0,"x":3610,"y":2760,"wires":[["a3100af0.716338","1e90979b.b14858"]]},{"id":"863b3a11.caffb8","type":"inject","z":"705d403b.f2b8","name":"Update view","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":3030,"y":2760,"wires":[["a3100af0.716338"]]},{"id":"66275f51.ec58b","type":"change","z":"705d403b.f2b8","name":"","rules":[{"t":"set","p":"new_fixture_zone_mapping.zoneID","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":3490,"y":2600,"wires":[[]]},{"id":"551ae558.ece26c","type":"ui_dropdown","z":"705d403b.f2b8","name":"Fixture Select","label":"","tooltip":"","place":"Select Fixture","group":"1598a8ff.884b77","order":1,"width":"3","height":"1","passthru":true,"options":[],"payload":"","topic":"","x":3220,"y":2580,"wires":[["aed3ce62.0a993"]]},{"id":"b7e6e6f8.691188","type":"function","z":"705d403b.f2b8","name":"","func":"msg.options = Object.keys((global.get(\"home.light.config.fixtures\")))\nreturn msg;","outputs":1,"noerr":0,"x":3050,"y":2580,"wires":[["551ae558.ece26c"]]},{"id":"8a237566.217618","type":"inject","z":"705d403b.f2b8","name":"manually refresh select lists","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":2760,"y":2580,"wires":[["b7e6e6f8.691188","a944297e.5ee6f8"]]},{"id":"cb5e87f1.d11458","type":"debug","z":"705d403b.f2b8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":3530,"y":2500,"wires":[]},{"id":"3ebfbf6e.973d6","type":"ui_dropdown","z":"705d403b.f2b8","name":"Zone Select","label":"","tooltip":"","place":"Select Zone","group":"1598a8ff.884b77","order":2,"width":"3","height":"1","passthru":true,"options":[],"payload":"","topic":"","x":3210,"y":2600,"wires":[["66275f51.ec58b"]]},{"id":"a944297e.5ee6f8","type":"function","z":"705d403b.f2b8","name":"","func":"msg.options = Object.keys((global.get(\"home.light.config.zones\")))\nreturn msg;","outputs":1,"noerr":0,"x":3050,"y":2600,"wires":[["3ebfbf6e.973d6"]]},{"id":"6a236e08.0a12d","type":"function","z":"705d403b.f2b8","name":"Get","func":"// list out the zones\n// also show light fixtures assigned to each zone\n// as zones and assignments are two separate objects,\n// we shall insert the mappings for a given zone as an array into the\n// zone object returned\nmsg.zones = {}\nzones = global.get(\"home.light.config.zones\")\n\n// copy our zones into the msg to send out, also set up fixtures array\n// so we can populate this for the list view\nObject.keys(zones).forEach(key => {\n msg.zones[key] = {\n \"friendly_name\": zones[key].friendly_name,\n \"created_date\": zones[key].created_date,\n \"created_time\": zones[key].created_time,\n \"fixtures\": []\n }\n})\n\n\n// now populate fixtures arrays in each zone\n// loop through every mapping, get the zoneID, then insert the fixtureID\n// into the corresponding zone now in msg.zones\n\nfixture_zone_mappings = global.get(\"home.light.config.fixture_zone_mappings\")\n\nObject.keys(fixture_zone_mappings).forEach(key => {\n // (the IF ensures we don't insert fixture into zone that\n // does not exist). It will just fail silently.\n // Right now, the zone won't be shown, but the mappings will still\n // exist. We should remove mappings when zone is removed...\n if (typeof msg.zones[fixture_zone_mappings[key].zoneID] !== 'undefined') {\n msg.zones[fixture_zone_mappings[key].zoneID].fixtures.push(fixture_zone_mappings[key].fixtureID)\n }\n\n// console.log(key); // the name of the current key.\n// console.log(myObj[key]); // the value of the current key.\n});\n\nreturn msg","outputs":1,"noerr":0,"x":3210,"y":2420,"wires":[["8a402bb0.963de8"]]},{"id":"f37b3ccd.a5de4","type":"link in","z":"705d403b.f2b8","name":"To zone","links":["1e90979b.b14858"],"x":3075,"y":2460,"wires":[["6a236e08.0a12d"]]},{"id":"1e90979b.b14858","type":"link out","z":"705d403b.f2b8","name":"From fixture_zone_mapping","links":["f37b3ccd.a5de4"],"x":3175,"y":2820,"wires":[]},{"id":"e2fb5079.17931","type":"link out","z":"705d403b.f2b8","name":"From zone","links":["e4329ecb.85cc8"],"x":3615,"y":2420,"wires":[]},{"id":"e4329ecb.85cc8","type":"link in","z":"705d403b.f2b8","name":"To fixture_zone_mapping","links":["e2fb5079.17931"],"x":3075,"y":2820,"wires":[["a3100af0.716338"]]},{"id":"1a37e545.435d2b","type":"ui_group","z":"","name":"New Fixture","tab":"869c06ba.dfea88","order":1,"disp":true,"width":"12","collapse":false},{"id":"1386e1df.83b3ae","type":"ui_group","z":"","name":"New Zone","tab":"869c06ba.dfea88","order":2,"disp":true,"width":"12","collapse":false},{"id":"1598a8ff.884b77","type":"ui_group","z":"","name":"Fixture to Zone Assignment","tab":"869c06ba.dfea88","order":3,"disp":true,"width":"12","collapse":false},{"id":"869c06ba.dfea88","type":"ui_tab","z":"","name":"Manage Presets","icon":"dashboard","disabled":false,"hidden":false}]