OpenMarine
Reviews invited - multi tank processing SK + NR-dashboard code. - Printable Version

+- OpenMarine (https://forum.openmarine.net)
+-- Forum: OpenPlotter (https://forum.openmarine.net/forumdisplay.php?fid=1)
+--- Forum: Node Red (https://forum.openmarine.net/forumdisplay.php?fid=15)
+--- Thread: Reviews invited - multi tank processing SK + NR-dashboard code. (/showthread.php?tid=3287)



Reviews invited - multi tank processing SK + NR-dashboard code. - Mub - 2021-02-20

Firstly I am new to this higher level programming thing, and although this works I invite constructive comments to help me improve, and may be help others along.

Back story..  the water gauges stopped working several years ago, they were replaced with resistive -> N2K translators.
This has worked well,  but the viewing locations were limited to the MFD.
Including a Victron GX device improved this greatly.  But with several other control challenges going on I decided to spend some quality COVID time with SK and Node Red.

Mostly not wanting to mess with a working thing I 'invested' in a Raspberry Pi 4, touch screen and POE hat with the aim of adding a new control surface.  This runs the SK and Node Red, and connects via Ethernet to the data sources ( the Victron display ).

Potentially more on the Poco lighting control CF later ( If you are all interested ) which was the genesis for this.

But I made a simple dashboard layout with the tanks in colour changing gauges and dynamic information - YAY ME.

Data is obtained through the http interface and took a few rainy evenings from scratch.
The displayed the 3 water tanks, 2 black water tanks and fuel tank.

Then after a weekend away when the NMEA bus was off none of the gauges displayed the correct information.

A little digging showed that the tank object 'number' assigned in SK are done in the registration order of the devices on the NMEA bus.  So "Fresh Water Tank 1".. might appear as anywhere from vessels.self.tanks.freshWater.0.** to vessels.self.tanks.freshWater.4.**.

Very frustrating for my simple programing brain.

What was needed was a unique way of tying the sender to the guage.
NMEA devices have unique serial numbers, it is displayed when you calibrate the device.  ( OK it is a hash of the serial number but it is network unique )

So the new flow..


  1. Start with a known array of tank sender serial numbers in tank number order.
  2. requests all the tanks.freshWater objects in JSON format
  3. uses a jsonata expression to build an array of tank id strings.
  4. recuses through the tanks.freshWater objects looking for the serial number that maps to the gauge in that path
  5. pulls and display that information.
WOOT WOOT..  works.

For the vessels.self.tanks.fuel object - I took a short cut and just searched out the capacity.value and currentLevel.value fields.  I am not likely to add a second fuel tank and so ( for me ) this was a good alternate method.

--
Known weaknesses.


Now I know if the number of tank sensors on the network in each class does not match the length of serial numbers that things will get messed up.
But this is either a FIXIT event, or something is being added/changed.

Also if there is a second path for tank data objects... say I add a YDEN-2 then more wrappers might need to be added.

But I felt that this was good enough for a review and to provide a basis for others to think on this problem.
I would like pointers on more elegant ways - if any - of solving this problem.
And directions for robustness.

THANKS
Mub

--

Here is the tank section of the flow - with a timer to trigger updates.

Code:
[{"id":"4eee88a9.bd7b28","type":"tab","label":"Flow 3","disabled":false,"info":""},{"id":"845049c4.f86178","type":"inject","z":"4eee88a9.bd7b28","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"60","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":170,"y":200,"wires":[["d0e80d4b.3890c"]]},{"id":"f13cc268.3e2de","type":"ui_gauge","z":"4eee88a9.bd7b28","name":"","group":"7067103.e483bf","order":1,"width":0,"height":0,"gtype":"gage","title":"Tank1","label":"","format":"{{payload}} {{unit}}","min":"0","max":"1","colors":["#b30000","#f00000","#59cb3a"],"seg1":"","seg2":"","x":1290,"y":260,"wires":[]},{"id":"d0e80d4b.3890c","type":"function","z":"4eee88a9.bd7b28","name":"set address","func":"\nmsg.root_address = \"http://192.168.1.16:3000/signalk/v1/api/vessels\";\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":910,"y":200,"wires":[["59324902.9775f8"]]},{"id":"ff6e73df.d51a6","type":"function","z":"4eee88a9.bd7b28","name":"Process Buttons","func":"global.set(\"Guages_Relative\",false);\nmsg.ui_control = {onicon:\"check\",oncolor:\"yellow\",officon:\"close\",offcolor:\"grey\"};\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":340,"y":160,"wires":[["ac7ca2e4.6ce78"]]},{"id":"26a40215.298f7e","type":"inject","z":"4eee88a9.bd7b28","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":170,"y":160,"wires":[["ff6e73df.d51a6"]]},{"id":"59324902.9775f8","type":"link out","z":"4eee88a9.bd7b28","name":"","links":["1ca54c9f.af0a43","3ee89e1c.ed5d92","3dc807c9.942fb8","5fe052ba.21b58c","abc30394.2b0cc","ea42623c.d84ff","a0cb698.11e2c98","78de6f31.a8b9","2d9866ea.f4db1a","ebdbaaaa.87b908","507fb285.d7c56c","46a6dde2.527124","69946338.1d30ec","2b99d5a5.e54ada","3bc846af.96e12a","f908b26.a63e55","e80c261a.460cf8","b7919991.408308","a1ec118e.3dc3","c7a60013.06a15","a679541b.708528"],"x":1115,"y":200,"wires":[]},{"id":"6039c23f.ff341c","type":"ui_gauge","z":"4eee88a9.bd7b28","name":"","group":"7067103.e483bf","order":2,"width":0,"height":0,"gtype":"gage","title":"Tank2","label":"","format":"{{payload}} {{unit}}","min":"0","max":"100","colors":["#b30000","#e6e600","#59cb3a"],"seg1":"","seg2":"","x":1290,"y":300,"wires":[]},{"id":"ceaaa5ef.73b9e8","type":"ui_gauge","z":"4eee88a9.bd7b28","name":"","group":"7067103.e483bf","order":3,"width":0,"height":0,"gtype":"gage","title":"Tank3","label":"","format":"{{payload}} {{unit}}","min":"0","max":"100","colors":["#b30000","#e6e600","#59cb3a"],"seg1":"","seg2":"","x":1290,"y":340,"wires":[]},{"id":"bd5eca27.de9648","type":"ui_gauge","z":"4eee88a9.bd7b28","name":"","group":"7067103.e483bf","order":6,"width":0,"height":0,"gtype":"gage","title":"Stern Holding","label":"","format":"{{payload}} {{unit}}","min":"0","max":"100","colors":["#1dcb3a","#e6e600","#b30000"],"seg1":"","seg2":"","x":1260,"y":380,"wires":[]},{"id":"b34c0f2.75f90f","type":"function","z":"4eee88a9.bd7b28","name":"","func":"display_black_tank=1;\n\nseg1b=0.70;\nseg2b=0.90;\n\nwanted_serial_no=msg.tank_SN[display_black_tank-1];\n\nfor(i=0; i<msg.tank_SN.length; i++)\n{\n    if (msg.payload[msg.id_txt[i].toString(10)].capacity[\"$source\"].includes(wanted_serial_no))\n    {\n        capacity = parseFloat(msg.payload[msg.id_txt[i].toString(10)].capacity.value);\n        currentLevel = parseFloat(msg.payload[msg.id_txt[i].toString(10)].currentLevel.value);\n    }\n}\n\nif (global.get(\"Guages_Relative\") == true)\n{\n    msg.payload = (currentLevel*100).toFixed(0);\n    msg.unit=\"%\";\n    guage_min = 0\n    guage_max = 100\n    seg1= seg1b*guage_max;\n    seg2= seg2b*guage_max;\n}\nelse\n{\n    msg.payload = (currentLevel*capacity*1000).toFixed(0);\n    msg.unit=\"l\";\n    guage_min = 0\n    guage_max = (capacity*1000).toFixed(0)\n    seg1= seg1b* guage_max;\n    seg2= seg2b*guage_max;\n}\nmsg.ui_control = {\n    \"min\":guage_min,\n    \"max\":guage_max,\n    \"seg1\":seg1,\n    \"seg2\":seg2,\n    \"options\":{\"levelColors\":[\"#b30000\", \"#e6e600\", \"#59cb3a\"]}\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1040,"y":380,"wires":[["bd5eca27.de9648"]]},{"id":"46a6dde2.527124","type":"link in","z":"4eee88a9.bd7b28","name":"","links":["59324902.9775f8"],"x":95,"y":380,"wires":[["6d9b854f.65c44c"]]},{"id":"147048b4.b076a7","type":"ui_gauge","z":"4eee88a9.bd7b28","name":"","group":"7067103.e483bf","order":6,"width":0,"height":0,"gtype":"gage","title":"Bow Holding","label":"","format":"{{payload}} {{unit}}","min":"0","max":"100","colors":["#b30000","#e6e600","#59cb3a"],"seg1":"","seg2":"","x":1270,"y":420,"wires":[]},{"id":"f99c0556.9bd6c8","type":"function","z":"4eee88a9.bd7b28","name":"set paths","func":"device_path = \"/self/tanks/fuel\";\n\nmsg.address_level = msg.root_address+device_path\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":200,"y":460,"wires":[["8381badd.578228"]]},{"id":"8381badd.578228","type":"http request","z":"4eee88a9.bd7b28","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"{{{address_level}}}","tls":"","persist":false,"proxy":"","authType":"","x":350,"y":460,"wires":[["45b36480.181a4c"]]},{"id":"45b36480.181a4c","type":"change","z":"4eee88a9.bd7b28","name":"","rules":[{"t":"set","p":"currentLevel","pt":"msg","to":"**.currentLevel","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":540,"y":460,"wires":[["e21fc007.92b54"]]},{"id":"b7e41ce3.f55e7","type":"ui_gauge","z":"4eee88a9.bd7b28","name":"","group":"7067103.e483bf","order":6,"width":0,"height":0,"gtype":"gage","title":"Fuel","label":"","format":"{{payload}} {{unit}}","min":"0","max":"100","colors":["#b30000","#e6e600","#59cb3a"],"seg1":"","seg2":"","x":1290,"y":460,"wires":[]},{"id":"69946338.1d30ec","type":"link in","z":"4eee88a9.bd7b28","name":"","links":["59324902.9775f8"],"x":95,"y":460,"wires":[["f99c0556.9bd6c8"]]},{"id":"ac7ca2e4.6ce78","type":"ui_switch","z":"4eee88a9.bd7b28","name":"","label":"Shift","tooltip":"1","group":"7067103.e483bf","order":2,"width":"3","height":"1","passthru":true,"decouple":"false","topic":"1","topicType":"str","style":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","animate":true,"x":490,"y":160,"wires":[["a84864db.994738"]]},{"id":"a84864db.994738","type":"function","z":"4eee88a9.bd7b28","name":"Process Buttons","func":"global.set(\"Power_Guages_Relative\",msg.payload);\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":660,"y":160,"wires":[["d0e80d4b.3890c"]]},{"id":"49edbe9c.69076","type":"function","z":"4eee88a9.bd7b28","name":"","func":"seg1b=0.10;\nseg2b=0.25;\n\n//since we have only one fuel tank this becomes a little easier\n\nif (global.get(\"Guages_Relative\") == true)\n{\n    msg.payload = (parseFloat(msg.currentLevel.value)*100).toFixed(0);\n    msg.unit=\"%\";\n    guage_min = 0\n    guage_max = 100\n    seg1= seg1b*guage_max;\n    seg2= seg2b*guage_max;\n}\nelse\n{\n    msg.payload = (parseFloat(msg.currentLevel.value)*parseFloat(msg.capacity.value)*1000).toFixed(0);\n    msg.unit=\"l\";\n    guage_min = 0\n    guage_max = (parseFloat(msg.capacity.value)*1000).toFixed(0)\n    seg1= seg1b* guage_max;\n    seg2= seg2b*guage_max;\n}\n\nmsg.ui_control = {\n    \"min\":guage_min,\n    \"max\":guage_max,\n    \"seg1\":seg1,\n    \"seg2\":seg2,\n    \"options\":{\"levelColors\":[\"#b30000\", \"#e6e600\", \"#59cb3a\"]}\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1040,"y":460,"wires":[["b7e41ce3.f55e7"]]},{"id":"e21fc007.92b54","type":"change","z":"4eee88a9.bd7b28","name":"","rules":[{"t":"set","p":"capacity","pt":"msg","to":"**.capacity","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":750,"y":460,"wires":[["49edbe9c.69076"]]},{"id":"a679541b.708528","type":"link in","z":"4eee88a9.bd7b28","name":"","links":["59324902.9775f8"],"x":95,"y":260,"wires":[["32de48ee.3f1f88"]]},{"id":"32de48ee.3f1f88","type":"function","z":"4eee88a9.bd7b28","name":"set paths","func":"msg.tank_SN = [\"2969\",\"3022\",\"760\"];\nmsg.id_txt = [];\n\ndevice_path = \"/self/tanks/freshWater\";\n\nmsg.address_level = msg.root_address+device_path;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":200,"y":260,"wires":[["b77f3447.f94698"]]},{"id":"b77f3447.f94698","type":"http request","z":"4eee88a9.bd7b28","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"{{{address_level}}}","tls":"","persist":false,"proxy":"","authType":"","x":350,"y":260,"wires":[["18afd13b.052eff"]]},{"id":"18afd13b.052eff","type":"change","z":"4eee88a9.bd7b28","name":"","rules":[{"t":"set","p":"id_txt","pt":"msg","to":"$keys($.payload)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":530,"y":260,"wires":[["4fc1e1c0.d2e4c","23680ce8.e8c754","e99a430a.7d96b"]]},{"id":"4fc1e1c0.d2e4c","type":"function","z":"4eee88a9.bd7b28","name":"","func":"display_water_tank=1;\nseg1b=0.05;\nseg2b=0.15;\n\nwanted_serial_no=msg.tank_SN[display_water_tank-1];\n\nfor(i=0; i<msg.tank_SN.length; i++)\n{\n    if (msg.payload[msg.id_txt[i].toString(10)].capacity[\"$source\"].includes(wanted_serial_no))\n    {\n        capacity = parseFloat(msg.payload[msg.id_txt[i].toString(10)].capacity.value);\n        currentLevel = parseFloat(msg.payload[msg.id_txt[i].toString(10)].currentLevel.value);\n    }\n}\n\n\nif (global.get(\"Guages_Relative\") == true)\n{\n    msg.payload = (currentLevel*100).toFixed(0);\n    msg.unit=\"%\";\n    guage_min = 0\n    guage_max = 100\n    seg1= seg1b*guage_max;\n    seg2= seg2b*guage_max;\n}\nelse\n{\n    msg.payload = (currentLevel*capacity*1000).toFixed(0);\n    msg.unit=\"l\";\n    guage_min = 0\n    guage_max = (capacity*1000).toFixed(0)\n    seg1= seg1b* guage_max;\n    seg2= seg2b*guage_max;\n}\nmsg.ui_control = {\n    \"min\":guage_min,\n    \"max\":guage_max,\n    \"seg1\":seg1,\n    \"seg2\":seg2,\n    \"options\":{\"levelColors\":[\"#b30000\", \"#e6e600\", \"#59cb3a\"]}\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1040,"y":260,"wires":[["f13cc268.3e2de"]]},{"id":"23680ce8.e8c754","type":"function","z":"4eee88a9.bd7b28","name":"","func":"display_water_tank=2;\nseg1b=0.05;\nseg2b=0.15;\n\nwanted_serial_no=msg.tank_SN[display_water_tank-1];\n\nfor(i=0; i<msg.tank_SN.length; i++)\n{\n    if (msg.payload[msg.id_txt[i].toString(10)].capacity[\"$source\"].includes(wanted_serial_no))\n    {\n        capacity = parseFloat(msg.payload[msg.id_txt[i].toString(10)].capacity.value);\n        currentLevel = parseFloat(msg.payload[msg.id_txt[i].toString(10)].currentLevel.value);\n    }\n}\n\n\nif (global.get(\"Guages_Relative\") == true)\n{\n    msg.payload = (currentLevel*100).toFixed(0);\n    msg.unit=\"%\";\n    guage_min = 0\n    guage_max = 100\n    seg1= seg1b*guage_max;\n    seg2= seg2b*guage_max;\n}\nelse\n{\n    msg.payload = (currentLevel*capacity*1000).toFixed(0);\n    msg.unit=\"l\";\n    guage_min = 0\n    guage_max = (capacity*1000).toFixed(0)\n    seg1= seg1b* guage_max;\n    seg2= seg2b*guage_max;\n}\nmsg.ui_control = {\n    \"min\":guage_min,\n    \"max\":guage_max,\n    \"seg1\":seg1,\n    \"seg2\":seg2,\n    \"options\":{\"levelColors\":[\"#b30000\", \"#e6e600\", \"#59cb3a\"]}\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1040,"y":300,"wires":[["6039c23f.ff341c"]]},{"id":"e99a430a.7d96b","type":"function","z":"4eee88a9.bd7b28","name":"","func":"display_water_tank=3;\nseg1b=0.05;\nseg2b=0.15;\n\nwanted_serial_no=msg.tank_SN[display_water_tank-1];\n\nfor(i=0; i<msg.tank_SN.length; i++)\n{\n    if (msg.payload[msg.id_txt[i].toString(10)].capacity[\"$source\"].includes(wanted_serial_no))\n    {\n        capacity = parseFloat(msg.payload[msg.id_txt[i].toString(10)].capacity.value);\n        currentLevel = parseFloat(msg.payload[msg.id_txt[i].toString(10)].currentLevel.value);\n    }\n}\n\n\nif (global.get(\"Guages_Relative\") == true)\n{\n    msg.payload = (currentLevel*100).toFixed(0);\n    msg.unit=\"%\";\n    guage_min = 0\n    guage_max = 100\n    seg1= seg1b*guage_max;\n    seg2= seg2b*guage_max;\n}\nelse\n{\n    msg.payload = (currentLevel*capacity*1000).toFixed(0);\n    msg.unit=\"l\";\n    guage_min = 0\n    guage_max = (capacity*1000).toFixed(0)\n    seg1= seg1b* guage_max;\n    seg2= seg2b*guage_max;\n}\nmsg.ui_control = {\n    \"min\":guage_min,\n    \"max\":guage_max,\n    \"seg1\":seg1,\n    \"seg2\":seg2,\n    \"options\":{\"levelColors\":[\"#b30000\", \"#e6e600\", \"#59cb3a\"]}\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1040,"y":340,"wires":[["ceaaa5ef.73b9e8"]]},{"id":"6d9b854f.65c44c","type":"function","z":"4eee88a9.bd7b28","name":"set paths","func":"msg.tank_SN = [\"1507158\",\"1507327\"];\nmsg.id_txt = [];\n\ndevice_path = \"/self/tanks/blackWater\";\n\nmsg.address_level = msg.root_address+device_path;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":200,"y":380,"wires":[["d5aa14a3.8177d8"]]},{"id":"d5aa14a3.8177d8","type":"http request","z":"4eee88a9.bd7b28","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"{{{address_level}}}","tls":"","persist":false,"proxy":"","authType":"","x":350,"y":380,"wires":[["9ac5c046.4d554"]]},{"id":"9ac5c046.4d554","type":"change","z":"4eee88a9.bd7b28","name":"","rules":[{"t":"set","p":"id_txt","pt":"msg","to":"$keys($.payload)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":530,"y":380,"wires":[["b34c0f2.75f90f","a5e0c123.6f4e3"]]},{"id":"a5e0c123.6f4e3","type":"function","z":"4eee88a9.bd7b28","name":"","func":"display_black_tank=2;\n\nseg1b=0.70;\nseg2b=0.90;\n\nwanted_serial_no=msg.tank_SN[display_black_tank-1];\n\nfor(i=0; i<msg.tank_SN.length; i++)\n{\n    if (msg.payload[msg.id_txt[i].toString(10)].capacity[\"$source\"].includes(wanted_serial_no))\n    {\n        capacity = parseFloat(msg.payload[msg.id_txt[i].toString(10)].capacity.value);\n        currentLevel = parseFloat(msg.payload[msg.id_txt[i].toString(10)].currentLevel.value);\n    }\n}\n\nif (global.get(\"Guages_Relative\") == true)\n{\n    msg.payload = (currentLevel*100).toFixed(0);\n    msg.unit=\"%\";\n    guage_min = 0\n    guage_max = 100\n    seg1= seg1b*guage_max;\n    seg2= seg2b*guage_max;\n}\nelse\n{\n    msg.payload = (currentLevel*capacity*1000).toFixed(0);\n    msg.unit=\"l\";\n    guage_min = 0\n    guage_max = (capacity*1000).toFixed(0)\n    seg1= seg1b* guage_max;\n    seg2= seg2b*guage_max;\n}\nmsg.ui_control = {\n    \"min\":guage_min,\n    \"max\":guage_max,\n    \"seg1\":seg1,\n    \"seg2\":seg2,\n    \"options\":{\"levelColors\":[\"#b30000\", \"#e6e600\", \"#59cb3a\"]}\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1040,"y":420,"wires":[["147048b4.b076a7"]]},{"id":"7067103.e483bf","type":"ui_group","name":"tanks(cut)","tab":"a30ce661.173488","order":1,"disp":true,"width":"6","collapse":false},{"id":"a30ce661.173488","type":"ui_tab","name":"Tank2","icon":"dashboard","disabled":false,"hidden":false}]

Mub


RE: Reviews invited - multi tank processing SK + NR-dashboard code. - SCarns - 2021-04-16

Interesting. So you are essentially reading N2K data and sending it to the Victron and then getting it into SignalK? Also, it looks like you kind of "force" the data into SK, rather than just inserting it with "signalk-send-pathvalue"?

I'm still VERY new at Node-Red, so unfortunately, I can't offer advise. Just looking your flows over to see what you are doing. Makes sense to me, but "more elegance" isn't something I can offer advise on.