QField sensor with termux and node red

It is a bit rough, but a proof of concept for the new sensor feature in QField.

Setup:

Screen cast:

Remarks:

Calculation of direction is set up to look ‘nice’ in QField/QGIS. I had to tweak the values (from -180°…180° to 90°…450°). I think thats ok for a proof of concept, but not for production use.

Node-red Flow:

[ { "id": "491a6cbdbe77f041", "type": "tab", "label": "Flow 2", "disabled": false, "info": "", "env": [] }, { "id": "9b064615129a5962", "type": "json", "z": "491a6cbdbe77f041", "name": "", "property": "payload", "action": "", "pretty": false, "x": 970, "y": 420, "wires": [ [ "5af39c2b07607afe", "890e1732aac8ca43", "ec60eb94962beb7f", "eb3baec5b79e3ca1", "692f7216f2c5dfe3" ] ] }, { "id": "5af39c2b07607afe", "type": "function", "z": "491a6cbdbe77f041", "name": "Get the number X", "func": "msg.payload = Math.round(msg.payload[\"AK09918 Magnetometer\"][\"values\"][0]*1000)/1000;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1170, "y": 320, "wires": [ [ "ca476548b30219a4" ] ] }, { "id": "ca476548b30219a4", "type": "debug", "z": "491a6cbdbe77f041", "name": "x", "active": false, "tosidebar": true, "console": true, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 1390, "y": 320, "wires": [] }, { "id": "890e1732aac8ca43", "type": "debug", "z": "491a6cbdbe77f041", "name": "debug 12", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 940, "y": 340, "wires": [] }, { "id": "6a945747f4cf57e8", "type": "debug", "z": "491a6cbdbe77f041", "name": "debug 13", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 940, "y": 600, "wires": [] }, { "id": "ec60eb94962beb7f", "type": "function", "z": "491a6cbdbe77f041", "name": "Get the number Y", "func": "msg.payload = Math.round(msg.payload[\"AK09918 Magnetometer\"][\"values\"][1]*1000)/1000;\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1170, "y": 380, "wires": [ [ "1e41f0f904c929f2" ] ] }, { "id": "eb3baec5b79e3ca1", "type": "function", "z": "491a6cbdbe77f041", "name": "Get the number Z", "func": "msg.payload = msg.payload[\"AK09918 Magnetometer\"][\"values\"][2];\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1170, "y": 260, "wires": [ [ "479142d984452a38" ] ] }, { "id": "82c6b3dcb13cc70d", "type": "udp out", "z": "491a6cbdbe77f041", "name": "", "addr": "127.0.0.1", "iface": "", "port": "50001", "ipv": "udp4", "outport": "", "base64": false, "multicast": "false", "x": 1740, "y": 440, "wires": [] }, { "id": "1e41f0f904c929f2", "type": "debug", "z": "491a6cbdbe77f041", "name": "y", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 1390, "y": 380, "wires": [] }, { "id": "479142d984452a38", "type": "debug", "z": "491a6cbdbe77f041", "name": "debug 14", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 1400, "y": 260, "wires": [] }, { "id": "692f7216f2c5dfe3", "type": "function", "z": "491a6cbdbe77f041", "name": "direction degrees", "func": "msg.payload = Math.round(\n (\n (\n (Math.atan2((msg.payload[\"AK09918 Magnetometer\"][\"values\"][1]),\n (msg.payload[\"AK09918 Magnetometer\"][\"values\"][0])\n )\n )*180\n ) / Math.PI +270\n )*10\n )/10 ;\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1170, "y": 460, "wires": [ [ "168090b23fd3f5f5", "82c6b3dcb13cc70d" ] ] }, { "id": "168090b23fd3f5f5", "type": "debug", "z": "491a6cbdbe77f041", "name": "deg", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 1690, "y": 500, "wires": [] }, { "id": "be67f6f65eb73158", "type": "function", "z": "491a6cbdbe77f041", "name": "name sensor 1", "func": "msg.payload = \"Magnetometer Calc Degree\"\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1160, "y": 520, "wires": [ [ "528f403a101caa45", "33cd2facc047c7de" ] ] }, { "id": "528f403a101caa45", "type": "debug", "z": "491a6cbdbe77f041", "name": "debug 15", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 1700, "y": 620, "wires": [] }, { "id": "33cd2facc047c7de", "type": "udp out", "z": "491a6cbdbe77f041", "name": "", "addr": "127.0.0.1", "iface": "", "port": "50011", "ipv": "udp4", "outport": "", "base64": false, "multicast": "false", "x": 1740, "y": 560, "wires": [] }, { "id": "fb47fed99b6b2711", "type": "exec", "z": "491a6cbdbe77f041", "command": "termux-sensor -s \"AK09918 Magnetometer\" -n 1", "addpay": "payload", "append": "", "useSpawn": "false", "timer": "", "winHide": true, "oldrc": false, "name": "", "x": 660, "y": 420, "wires": [ [ "9b064615129a5962", "6a945747f4cf57e8" ], [], [] ] }, { "id": "187b66167d4f67e0", "type": "inject", "z": "491a6cbdbe77f041", "name": "", "props": [ { "p": "payload" } ], "repeat": "3", "crontab": "", "once": true, "onceDelay": "", "topic": "", "payload": "", "payloadType": "date", "x": 550, "y": 260, "wires": [ [ "fb47fed99b6b2711", "be67f6f65eb73158" ] ] } ]


Imported from GitHub discussion by @soester on 2023-04-19T08:27:29Z

soester , I love this, thanks for sharing.

Being able to run node red on android opens quite a few doors.

For internal device magnetometer sensor access, you could also use this free Android app called HyperIMU, it streams (over UDP/TCP) pretty much any sensor it can find on the device its running on.


Imported from GitHub comment by @nirvn on 2023-04-19T08:31:23Z

With node-red also [enhancement proposal]: Implementation of an MQTT-Client · Issue #2435 · opengisch/QField · GitHub could be implemented.
I am not a geologist but i think the proof of concept above is very close to Feature request: geologic compass · Issue #1882 · opengisch/QField · GitHub.


Imported from GitHub comment by @soester on 2023-04-19T08:31:23Z

nirvn HyperIMU looks much simpler to access than the termux-sensor setup! List of sensors on my device is exactly the same as with termux-sensor.
I think node-red comes in handy to preprocess data / make calculations. Or also to stream in a sensor name or other metadata.


Imported from GitHub comment by @soester on 2023-04-19T08:44:33Z

Yep, node-red remains really useful in many cases.


Imported from GitHub comment by @nirvn on 2023-04-19T08:47:02Z

Thoughts while setting up the projects:
Sensors are usually bound to devices and not projects (wich are distributed to different devices).
It would be really handy if the sensor name, port, host and connection type in QGIS could be controled by expressions. And if (project) variables would be editable in qfield, one could alter setups in qfield and adapt to the device.


Imported from GitHub comment by @soester on 2023-04-19T09:39:57Z

soester , long story short here is that it can’t really be done efficiently; i.e., if you use an expression to try and fetch data from a sensor by passing a sensor configuration (vs. a saved configuration name), you’ll end up having to connect to the sensor, and wait for a value to come, and repeat that on every expression call. That’s not practical.

We could eventually allow for some minimal configuration on the QField side (i.e. editing an IP address or port nb for e.g.) of saved sensors within a project. But that’d require further thoughts on use cases, etc.

One thing you can do is use the coalesce function to try and fetch data from first non-null sensor name, e.g.: coalesce(sensor_data('sensor1'), sensor_data('sensor2'), ...) , that would allow us to run multiple configurations within one project.


Imported from GitHub comment by @nirvn on 2023-04-19T09:48:12Z