Hi, first a big thank you to all that have contributed to this fantastic node.
I have built a tracker for our over adventurous rescue lurcher to give us some piece of mind when walking in remote areas in the UK where there may be no or patchy mobile coverage.
Setup
The display and node-red host is an old mobile phone (android 10) without internet access. Connected via an OTG micro USB to micro USB cable I have an Adafruit Feather M0 RFM69HCW 433MHz packet Radio.
The Feather is programmed to output the tracker data as name:"map_unique_marker_id",lat:<deg. latitude>,lon:<deg. longitude>,label:"My Dogs Name"\r\n once a second.
The flow
[{"id":"946a67e51f2c3165","type":"tab","label":"Flow 1","disabled":false,"info":"","env":[]},{"id":"2f44ef68491c2556","type":"ui_worldmap","z":"946a67e51f2c3165","group":"e386b85f061e29ad","order":0,"width":0,"height":0,"name":"","lat":"","lon":"","zoom":"18","layer":"OSMC","cluster":"","maxage":"","usermenu":"show","layers":"show","panit":"true","panlock":"false","zoomlock":"false","hiderightclick":"true","coords":"deg","showgrid":"true","allowFileDrop":"true","path":"/worldmap","overlist":"DR,CO,RA,DN,HM","maplist":"OSMG,OSMC,EsriC,EsriS,EsriT,EsriDG,UKOS,OS45,OpTop,SW","mapname":"","mapurl":"","mapopt":"","mapwms":false,"x":720,"y":120,"wires":[]},{"id":"96a9c9cc19f9c075","type":"udp in","z":"946a67e51f2c3165","name":"","iface":"","port":"2947","ipv":"udp4","multicast":"false","group":"","datatype":"utf8","x":220,"y":80,"wires":[["c8e713d5d0560962"]]},{"id":"f2ded905a2c7dbe2","type":"exec","z":"946a67e51f2c3165","command":"termux-usb","addpay":"payload","append":"","useSpawn":"false","timer":"1","winHide":false,"oldrc":false,"name":"","x":490,"y":280,"wires":[["5fd91a18ad5bcb84","ac72e9ce32272928"],["acfe7724118dd9b6"],["acfe7724118dd9b6"]]},{"id":"0febfe6cf212344e","type":"inject","z":"946a67e51f2c3165","name":"-e usbtest ","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"1","crontab":"","once":true,"onceDelay":"0.3","topic":"","payload":"-e ../home/usbtest /dev/bus/usb/002/002","payloadType":"str","x":130,"y":340,"wires":[["983377e2f4b521b3"]]},{"id":"c535299196141b9f","type":"inject","z":"946a67e51f2c3165","name":"Request Access","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"2","crontab":"","once":true,"onceDelay":"0.2","topic":"","payload":"-r /dev/bus/usb/002/002","payloadType":"str","x":150,"y":260,"wires":[["4e893cb8670f307a"]]},{"id":"9217822122ea6753","type":"function","z":"946a67e51f2c3165","name":"nameLatLonLabel","func":"var topic = String(msg.topic).replace(/(\\r\\n|\\n|\\n\\r|\\.)/gm, \"\");\nvar status = flow.get(node.name) || {\n Phone:{state:undefined,\n name:undefined,lat:undefined,lon:undefined,\n label:undefined},\n Tracker:{state:undefined,\n name:undefined,lat:undefined,lon:undefined,\n label:undefined}};\n\nswitch(topic){\n case \"gpsd\":\n var fields = String(msg.payload).split(\",\");\n var latI=-1, lonI=-1,latA=-1, lonA=-1;\n switch (fields[0]) {\n case \"$GPGGA\":\n latI = 2; latA = 3;\n lonI = 4; lonA = 5;\n break;\n case \"$GPRMC\":\n latI = 3; latA = 4;\n lonI = 5; lonA = 6;\n break;\n default:\n break;\n }\n if( latI > 0 && lonI > 0 && latA > 0 && lonA > 0 &&\n fields[latI].length === 9 &&\n fields[lonI].length === 10 &&\n fields[latA].length === 1 &&\n fields[lonA].length === 1 ) {\n var latS = fields[latA] === \"N\" ? 1 : -1,\n lonS = fields[lonA] === \"E\" ? 1 : -1,\n latD = precisionRound(fields[latI]/100,0),\n lonD = precisionRound(fields[lonI]/100,0),\n latM = precisionRound((fields[latI]-100*latD)/60,6),\n lonM = precisionRound((fields[lonI]-100*lonD)/60,6); \n\n status.Phone.state = \"GPS Lock\";\n status.Phone.name = \"Me\";\n status.Phone.lat = latS*(latD + latM);\n status.Phone.lon = lonS*(lonD + lonM);\n status.Phone.label = \"Me\";\n }else{\n status.Phone.state=\"GPS No Lock\";\n status.Phone.name = \"Me\";\n status.Phone.label = undefined;\n }\n break;\n case \"initialise\":\n case \"Access denied\":\n status.Tracker.name = \"brock\";\n status.Tracker.lat = undefined;\n status.Tracker.lon = undefined;\n status.Tracker.label = undefined;\n case \"Access granted\":\n status.Tracker.state = topic;\n break;\n default:\n /* Tracker radio response */\n var kv = String(msg.payload).split(\",\"); \n //name:brock,lat:55.079239,lon:-1.469798,label:Brock\n status.Tracker.name = String(kv[0]).split(\":\")[1];\n status.Tracker.lat = String(kv[1]).split(\":\")[1];\n status.Tracker.lon = String(kv[2]).split(\":\")[1];\n status.Tracker.label = String(String(kv[3]).split(\":\")[1]).replace(/(\\r\\n|\\n|\\r)/gm, \"\");\n if(status.name == undefined)\n status.Tracker.state = \"Disconnected\";\n break;\n}\nflow.set(node.name,status);\nif(status.Tracker.state !== \"Access granted\" || status.Tracker.label == undefined){\n // show tracker \n node.send({payload:{deleted:true,name:\"brock\"}});\n}else{\n // delete tracker \n node.send({payload:{name:status.Tracker.name,lat:status.Tracker.lat,lon:status.Tracker.lon,label:status.Tracker.label}});\n}\nif(status.Phone.state !== \"GPS Lock\" || status.Phone.label == undefined){\n // delete me\n node.send({payload:{deleted:true,name:\"Me\"}});\n}else{\n // show me and center map on me\n node.send({payload:{name:status.Phone.name,lat:status.Phone.lat,lon:status.Phone.lon,label:\"Me\"}});\n node.send({payload:{command:{lat:status.Phone.lat,lon:status.Phone.lon}}});\n}\nreturn;\n\nfunction precisionRound(number, precision) {\n var factor = Math.pow(10, precision);\n return Math.round(number * factor) / factor;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":550,"y":120,"wires":[["2f44ef68491c2556"]]},{"id":"ca766bcc680dc53c","type":"inject","z":"946a67e51f2c3165","name":"Init","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"initialise","x":110,"y":120,"wires":[["9217822122ea6753"]]},{"id":"5fd91a18ad5bcb84","type":"change","z":"946a67e51f2c3165","name":"","rules":[{"t":"set","p":"topic","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":200,"wires":[["9217822122ea6753","f8284af1cde3779a"]]},{"id":"983377e2f4b521b3","type":"switch","z":"946a67e51f2c3165","name":"","property":"nameLatLonLabel.Tracker.state","propertyType":"flow","rules":[{"t":"eq","v":"Access granted","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":310,"y":340,"wires":[["f2ded905a2c7dbe2","582b3d0614488c55"]]},{"id":"4e893cb8670f307a","type":"switch","z":"946a67e51f2c3165","name":"","property":"nameLatLonLabel.Tracker.state","propertyType":"flow","rules":[{"t":"neq","v":"Access granted","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":310,"y":260,"wires":[["f2ded905a2c7dbe2","7e4697d6140d29ee"]]},{"id":"c8e713d5d0560962","type":"change","z":"946a67e51f2c3165","name":"t = gpsd","rules":[{"t":"set","p":"topic","pt":"msg","to":"gpsd","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":340,"y":80,"wires":[["9217822122ea6753"]]},{"id":"52cd7a4d39068189","type":"comment","z":"946a67e51f2c3165","name":"Phone GPSd In","info":"","x":320,"y":40,"wires":[]},{"id":"10f44e113352a1fc","type":"comment","z":"946a67e51f2c3165","name":"termux-usb usbtest.c GPS In","info":"","x":280,"y":160,"wires":[]},{"id":"7e4697d6140d29ee","type":"debug","z":"946a67e51f2c3165","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":310,"y":300,"wires":[]},{"id":"582b3d0614488c55","type":"debug","z":"946a67e51f2c3165","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":310,"y":380,"wires":[]},{"id":"ac72e9ce32272928","type":"debug","z":"946a67e51f2c3165","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":670,"y":260,"wires":[]},{"id":"acfe7724118dd9b6","type":"debug","z":"946a67e51f2c3165","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":670,"y":300,"wires":[]},{"id":"f8284af1cde3779a","type":"debug","z":"946a67e51f2c3165","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":670,"y":200,"wires":[]},{"id":"6c88f17532727b1f","type":"inject","z":"946a67e51f2c3165","name":"Init","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Dog\",\"lat\":\"55.200000\",\"lon\":\"-2.400000\",\"label\":\"Dog\"}","payloadType":"json","x":530,"y":40,"wires":[["2f44ef68491c2556"]]},{"id":"1058566bd72863bc","type":"inject","z":"946a67e51f2c3165","name":"Init","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"0.2","topic":"","payload":"{\"name\":\"Phone\",\"lat\":\"55.205000\",\"lon\":\"-2.405000\",\"label\":\"Phone\"}","payloadType":"json","x":530,"y":80,"wires":[["2f44ef68491c2556"]]},{"id":"e386b85f061e29ad","type":"ui_group","name":"Home","tab":"f80eb58ffa262d83","order":1,"disp":true,"width":"6","collapse":true,"className":""},{"id":"f80eb58ffa262d83","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]
shows a marker for the phone GPDd position and a second marker for the dog.
I have searched for but not found an opensource offline mapserver for android.
Also for our use case an offline map server seems an unnecessary complication considering we would know in advance where we would be walking and a map 10mile x 10Mile would be more than sufficient.
I have just three questions;
- Could anyone please tell me if a single pre-downloaded map file covering an area of 10mile x 10mile would contain the same detail when fully zoomed in as an online viewed version ?
- Could anyone please tell me if it is possible to just patch the node-red-config-web-worldmap node some how so it points to a single offline leaflet in local storage ?
- if 2 then any idea how and where to look ?
many thanks in advance,
oz
P.S. The biggest challenge so far has been that of getting data into node-red on android. I could not find a working usb/bluetooth LE node so had to resort to physical OTG cable and writing my own libusb serial driver.