For car enthusiasts and DIY mechanics, accessing and understanding vehicle data is crucial. OBD-II (On-Board Diagnostics II) systems provide a wealth of information about your car’s performance, from engine temperature to speed and RPM. But what if you could take this data beyond simple readings and integrate it into your smart home system or a custom dashboard? This is where Node-RED comes in.
Node-RED, a powerful low-code platform, allows you to easily create flows to process and visualize data from various sources. By combining Node-RED with an OBD2 adapter and the Torque Android app, you can unlock real-time monitoring of your vehicle’s diagnostics and performance parameters. This guide will show you how to set up Node-RED to receive OBD2 data streamed from your car using the Torque app.
Setting Up Torque App to Send Data to Node-RED
The Torque app is a popular OBD2 software for Android devices that can read data from your car’s ECU (Engine Control Unit) via a Bluetooth OBD2 adapter. To send this data to Node-RED, we’ll utilize Torque’s “Upload to web server” feature.
Here’s how to configure Torque:
-
Enable Data Logging and Upload: Open Torque settings and navigate to “Data Logging and Upload.”
-
Select What to Log: Choose “All PID’s you want to send to node-red.” This ensures all the parameters you select within Torque are transmitted.
-
Enable Webserver Upload: Check the box next to “Upload to web server.”
-
Enter Webserver URL: This is the crucial step. You need to point Torque to your Node-RED server. Enter the URL in the format:
http://your.nodered.server:1880/torque/
- Replace
your.nodered.server
with the actual IP address or hostname of your Node-RED server. :1880
is the default port for Node-RED. If you’ve changed your Node-RED port, use that instead./torque/
is the endpoint we will configure in Node-RED to receive data.
- Replace
Alt text: Screenshot of the Torque app settings menu, highlighting the “Data Logging and Upload” section and the “Webserver URL” field where the Node-RED server address is entered.
Node-RED Flow for Receiving OBD2 Data
Now, let’s set up the Node-RED flow to receive and process the data sent by Torque. You can import the following flow directly into your Node-RED instance. This flow is designed to:
- Receive HTTP GET requests from the Torque app.
- Parse the incoming data.
- Extract specific OBD2 parameters.
- Optionally store data in a database (MySQL in this example).
- Display real-time gauges on a Node-RED dashboard.
- Send data to an MQTT broker (optional).
Here is the Node-RED flow JSON code:
[
{
"id": "2ce0055.8743efa",
"type": "tab",
"label": "Torque",
"disabled": false,
"info": ""
},
{
"id": "67436221.b54c0c",
"type": "http in",
"z": "2ce0055.8743efa",
"name": "",
"url": "/torque",
"method": "get",
"upload": true,
"swaggerDoc": "",
"x": 221,
"y": 143,
"wires": [
[
"f98f22cc.807ba",
"557d7766.022168",
"33d571b3.db242e",
"69b56cf.77bc694",
"683933fa.d76f1c",
"ab2ff853.e8b808",
"c005b8b1.f06b98",
"96abaa53.c70988",
"ee5bc723.594bf8",
"dfdc4763.7a8fd8",
"cee9fa09.98b6d8",
"f2d748c5.d92da8",
"936bc33f.bcd27",
"97ab65f2.1a0728"
]
]
},
{
"id": "f98f22cc.807ba",
"type": "http response",
"z": "2ce0055.8743efa",
"name": "",
"statusCode": "",
"headers": {
"OK!": ""
},
"x": 1290,
"y": 140,
"wires": []
},
{
"id": "557d7766.022168",
"type": "change",
"z": "2ce0055.8743efa",
"name": "Barometer",
"rules": [
{
"t": "move",
"p": "payload.kff1270",
"pt": "msg",
"to": "payload",
"tot": "msg"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "torque\phone\barometer",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 731,
"y": 403,
"wires": [
[
"536ae3f0.1980dc",
"5ef6a21e.75ce4c"
]
]
},
{
"id": "3b30fefa.3f9012",
"type": "ui_gauge",
"z": "2ce0055.8743efa",
"name": "",
"group": "9b558fbc.438aa",
"order": 1,
"width": "6",
"height": "6",
"gtype": "gage",
"title": "RPM",
"label": "RPM",
"format": "{{value}}",
"min": 0,
"max": "7000",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"x": 1061,
"y": 443,
"wires": []
},
{
"id": "33d571b3.db242e",
"type": "change",
"z": "2ce0055.8743efa",
"name": "RPM",
"rules": [
{
"t": "move",
"p": "payload.kc",
"pt": "msg",
"to": "payload",
"tot": "msg"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "torque\vehicle\rpm",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 721,
"y": 443,
"wires": [
[
"3b30fefa.3f9012",
"5ef6a21e.75ce4c"
]
]
},
{
"id": "69b56cf.77bc694",
"type": "change",
"z": "2ce0055.8743efa",
"name": "Mass Air Flow Rate",
"rules": [
{
"t": "move",
"p": "payload.k10",
"pt": "msg",
"to": "payload",
"tot": "msg"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "torque\vehicle\mafrate",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 761,
"y": 483,
"wires": [
[
"7ee21162.28cdb",
"5ef6a21e.75ce4c"
]
]
},
{
"id": "683933fa.d76f1c",
"type": "change",
"z": "2ce0055.8743efa",
"name": "Throttle Position",
"rules": [
{
"t": "move",
"p": "payload.k11",
"pt": "msg",
"to": "payload",
"tot": "msg"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "torque\vehicle\throttle",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 751,
"y": 523,
"wires": [
[
"3765c6b.fb70f3a",
"5ef6a21e.75ce4c"
]
]
},
{
"id": "ab2ff853.e8b808",
"type": "change",
"z": "2ce0055.8743efa",
"name": "Coolant Temp",
"rules": [
{
"t": "move",
"p": "payload.k5",
"pt": "msg",
"to": "payload",
"tot": "msg"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "torque\vehicle\coolant",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 741,
"y": 563,
"wires": [
[
"258b6f62.e82ab"
]
]
},
{
"id": "c005b8b1.f06b98",
"type": "change",
"z": "2ce0055.8743efa",
"name": "Volage",
"rules": [
{
"t": "move",
"p": "payload.kff1238",
"pt": "msg",
"to": "payload",
"tot": "msg"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "torque\obd\voltage",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 721,
"y": 603,
"wires": [
[
"5b8bdb33.d96134",
"5ef6a21e.75ce4c"
]
]
},
{
"id": "96abaa53.c70988",
"type": "change",
"z": "2ce0055.8743efa",
"name": "Vacuum",
"rules": [
{
"t": "move",
"p": "payload.kff1202",
"pt": "msg",
"to": "payload",
"tot": "msg"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "torque\vehicle\vacuum",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 731,
"y": 643,
"wires": [
[
"913a6311.540cd",
"5ef6a21e.75ce4c"
]
]
},
{
"id": "3765c6b.fb70f3a",
"type": "ui_gauge",
"z": "2ce0055.8743efa",
"name": "",
"group": "9b558fbc.438aa",
"order": 2,
"width": "3",
"height": "3",
"gtype": "donut",
"title": "Throttle Position",
"label": "%",
"format": "{{value}}",
"min": 0,
"max": "100",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"x": 1091,
"y": 523,
"wires": []
},
{
"id": "ee5bc723.594bf8",
"type": "change",
"z": "2ce0055.8743efa",
"name": "Speed",
"rules": [
{
"t": "move",
"p": "payload.kd",
"pt": "msg",
"to": "payload",
"tot": "msg"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "torque\vehicle\speed",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 721,
"y": 683,
"wires": [
[
"c3863c64.b8c2d",
"5ef6a21e.75ce4c"
]
]
},
{
"id": "c3863c64.b8c2d",
"type": "ui_gauge",
"z": "2ce0055.8743efa",
"name": "",
"group": "ea9cdc42.4bbc8",
"order": 0,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "Speed",
"label": "MPH",
"format": "{{value}}",
"min": 0,
"max": "100",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"x": 1061,
"y": 683,
"wires": []
},
{
"id": "7ee21162.28cdb",
"type": "ui_gauge",
"z": "2ce0055.8743efa",
"name": "",
"group": "9b558fbc.438aa",
"order": 3,
"width": "2",
"height": "3",
"gtype": "wave",
"title": "Mass Air Flow",
"label": "g/s",
"format": "{{value}}",
"min": 0,
"max": "100",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"x": 1091,
"y": 483,
"wires": []
},
{
"id": "536ae3f0.1980dc",
"type": "ui_gauge",
"z": "2ce0055.8743efa",
"name": "",
"group": "26a930d9.ff8c",
"order": 0,
"width": "3",
"height": "3",
"gtype": "gage",
"title": "Barometer",
"label": "units",
"format": "{{value}}",
"min": "800",
"max": "1100",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"x": 1071,
"y": 403,
"wires": []
},
{
"id": "dfdc4763.7a8fd8",
"type": "change",
"z": "2ce0055.8743efa",
"name": "Speed GPS",
"rules": [
{
"t": "move",
"p": "payload.kff1001",
"pt": "msg",
"to": "payload",
"tot": "msg"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "torque\phone\speed",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 741,
"y": 723,
"wires": [
[
"535662ad.f1628c",
"5ef6a21e.75ce4c"
]
]
},
{
"id": "535662ad.f1628c",
"type": "ui_gauge",
"z": "2ce0055.8743efa",
"name": "",
"group": "26a930d9.ff8c",
"order": 0,
"width": "3",
"height": "3",
"gtype": "gage",
"title": "Speed GPS",
"label": "MPH",
"format": "{{value}}",
"min": 0,
"max": "100",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"x": 1081,
"y": 723,
"wires": []
},
{
"id": "5b8bdb33.d96134",
"type": "ui_gauge",
"z": "2ce0055.8743efa",
"name": "",
"group": "ea9cdc42.4bbc8",
"order": 0,
"width": "3",
"height": "3",
"gtype": "gage",
"title": "Voltage",
"label": "V",
"format": "{{value}}",
"min": "9",
"max": "15",
"colors": [
"#ff0000",
"#008000",
"#ff0000"
],
"seg1": "13",
"seg2": "14.3",
"x": 1071,
"y": 603,
"wires": []
},
{
"id": "b0123228.4a063",
"type": "ui_gauge",
"z": "2ce0055.8743efa",
"name": "",
"group": "9b558fbc.438aa",
"order": 5,
"width": "3",
"height": "3",
"gtype": "gage",
"title": "Coolant Temp",
"label": "F",
"format": "{{value}}",
"min": 0,
"max": "250",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"x": 1081,
"y": 563,
"wires": []
},
{
"id": "913a6311.540cd",
"type": "ui_gauge",
"z": "2ce0055.8743efa",
"name": "",
"group": "9b558fbc.438aa",
"order": 6,
"width": "3",
"height": "3",
"gtype": "donut",
"title": "Vacuum",
"label": "",
"format": "{{value}}",
"min": "-20",
"max": "0",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"x": 1071,
"y": 643,
"wires": []
},
{
"id": "cee9fa09.98b6d8",
"type": "json",
"z": "2ce0055.8743efa",
"name": "",
"pretty": false,
"x": 751,
"y": 198,
"wires": [
[
"3b9be5d8.84e04a"
]
]
},
{
"id": "f2d748c5.d92da8",
"type": "function",
"z": "2ce0055.8743efa",
"name": "Parse Date",
"func": "var date = parseInt(msg.payload.time);nnmsg.payload = new Date(date* 1).toLocaleString([], { hour12: true}).slice(0, 19).replace('T', ' ');nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 730,
"y": 780,
"wires": [
[
"88bfc628.5727b8"
]
]
},
{
"id": "88bfc628.5727b8",
"type": "ui_text",
"z": "2ce0055.8743efa",
"group": "26a930d9.ff8c",
"order": 1,
"width": 0,
"height": 0,
"name": "Last Update",
"label": "Updated",
"format": "{{msg.payload}}",
"layout": "row-spread",
"x": 1070,
"y": 780,
"wires": []
},
{
"id": "258b6f62.e82ab",
"type": "function",
"z": "2ce0055.8743efa",
"name": "CtoF",
"func": "var tempc = msg.payload;n tempf = tempc * 9/5 + 32;n tempf = Math.round(tempf * 10) / 10;n msg.payload = tempf;n return msg;",
"outputs": 1,
"noerr": 0,
"x": 910,
"y": 560,
"wires": [
[
"b0123228.4a063",
"5ef6a21e.75ce4c"
]
]
},
{
"id": "936bc33f.bcd27",
"type": "change",
"z": "2ce0055.8743efa",
"name": "IntakeTemp",
"rules": [
{
"t": "move",
"p": "payload.kf",
"pt": "msg",
"to": "payload",
"tot": "msg"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "torque\vehicle\intake",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 730,
"y": 840,
"wires": [
[
"a6732e91.735a9"
]
]
},
{
"id": "ee354102.4a8d8",
"type": "ui_gauge",
"z": "2ce0055.8743efa",
"name": "",
"group": "9b558fbc.438aa",
"order": 4,
"width": "3",
"height": "3",
"gtype": "gage",
"title": "Intake Temp",
"label": "F",
"format": "{{value}}",
"min": 0,
"max": "250",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"x": 1070,
"y": 840,
"wires": []
},
{
"id": "a6732e91.735a9",
"type": "function",
"z": "2ce0055.8743efa",
"name": "CtoF",
"func": "var tempc = msg.payload;n tempf = tempc * 9/5 + 32;n tempf = Math.round(tempf * 10) / 10;n msg.payload = tempf;n return msg;",
"outputs": 1,
"noerr": 0,
"x": 899,
"y": 837,
"wires": [
[
"ee354102.4a8d8",
"5ef6a21e.75ce4c"
]
]
},
{
"id": "3b9be5d8.84e04a",
"type": "change",
"z": "2ce0055.8743efa",
"name": "",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "torque\raw",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 1034,
"y": 202,
"wires": [
[
"5ef6a21e.75ce4c"
]
]
},
{
"id": "5ef6a21e.75ce4c",
"type": "mqtt out",
"z": "2ce0055.8743efa",
"name": "",
"topic": "",
"qos": "0",
"retain": "true",
"broker": "fee1fac2.261a88",
"x": 1292,
"y": 193,
"wires": []
},
{
"id": "97ab65f2.1a0728",
"type": "function",
"z": "2ce0055.8743efa",
"name": "copy to json",
"func": "msg.payload.json = JSON.stringify(msg.payload);nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 770,
"y": 100,
"wires": [
[
"72347975.0e2688"
]
]
},
{
"id": "72347975.0e2688",
"type": "function",
"z": "2ce0055.8743efa",
"name": "Create query in topic",
"func": "var out = "INSERT INTO torque_json (timestamp,json)"nvar date = Number(msg.payload.time);nout = out + "VALUES ('" + new Date(date* 1).toISOString().slice(0, 19).replace('T', ' ') + "','" nout = out + String(msg.payload.json) + "');"n nmsg.topic=out;nnreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 960,
"y": 100,
"wires": [
[
"dba0040d.49d798"
]
]
},
{
"id": "dba0040d.49d798",
"type": "mysql",
"z": "2ce0055.8743efa",
"mydb": "4cd2c8.ee368d38",
"name": "",
"x": 1300,
"y": 100,
"wires": [
[]
]
},
{
"id": "9b558fbc.438aa",
"type": "ui_group",
"z": "",
"name": "Motor",
"tab": "ac99e377.fdb83",
"order": 1,
"disp": true,
"width": "9"
},
{
"id": "ea9cdc42.4bbc8",
"type": "ui_group",
"z": "",
"name": "Van",
"tab": "ac99e377.fdb83",
"order": 2,
"disp": true,
"width": "6"
},
{
"id": "26a930d9.ff8c",
"type": "ui_group",
"z": "",
"name": "Phone",
"tab": "ac99e377.fdb83",
"order": 3,
"disp": true,
"width": "6"
},
{
"id": "fee1fac2.261a88",
"type": "mqtt-broker",
"z": "",
"broker": "mqtt.YOUR.SERVER",
"port": "1883",
"clientid": "nodered",
"usetls": false,
"compatmode": true,
"keepalive": "60",
"cleansession": true,
"willTopic": "",
"willQos": "0",
"willPayload": "",
"birthTopic": "",
"birthQos": "0",
"birthPayload": ""
},
{
"id": "4cd2c8.ee368d38",
"type": "MySQLdatabase",
"z": "",
"host": "drobo5n.YOUR.SERVER",
"port": "3306",
"db": "openhab",
"tz": "CST"
},
{
"id": "ac99e377.fdb83",
"type": "ui_tab",
"z": "",
"name": "Torque",
"icon": "fa-car",
"order": 2
}
]
Explanation of the Node-RED Flow:
- HTTP In Node (
/torque
): This node acts as the webhook endpoint, listening for GET requests from the Torque app at the/torque
URL path. It receives the OBD2 data sent by Torque. - HTTP Response Node: This node immediately sends an “OK!” response back to the Torque app, confirming successful data reception. This is essential for Torque to continue sending data.
- Change Nodes (e.g.,
RPM
,Speed
,Coolant Temp
): These nodes are crucial for parsing the incoming data. Torque sends data as key-value pairs within thepayload
. Each “Change” node:- Moves a specific PID (Parameter ID) value from
payload.PID_KEY
to the mainpayload
. For example,payload.kc
contains RPM data,payload.kd
is speed, and so on. - Sets the
topic
of the message to a descriptive string (e.g.,torquevehiclerpm
). This topic can be used for routing and organizing data later.
- Moves a specific PID (Parameter ID) value from
- Gauge Nodes (UI Nodes): These nodes create visual gauges on your Node-RED dashboard to display the real-time OBD2 data. There are gauges for RPM, Speed, Throttle Position, Mass Air Flow, Coolant Temp, Voltage, Vacuum, Barometer, Intake Temp, and GPS Speed.
- Function Nodes (
CtoF
,Parse Date
,copy to json
,Create query in topic
):CtoF
(Celsius to Fahrenheit): Converts temperature readings from Celsius (default OBD2) to Fahrenheit.Parse Date
: Formats the timestamp from Torque into a human-readable date and time.copy to json
: Converts the entire payload to a JSON string for database storage.Create query in topic
: Generates a MySQL INSERT query to store the raw JSON data in a database table namedtorque_json
.
- JSON Node: Parses the incoming payload as JSON.
- MQTT Out Node: (Optional) Publishes the data to an MQTT broker. This allows you to integrate the OBD2 data with other systems or applications that use MQTT, such as home automation platforms like Home Assistant or openHAB.
- MySQL Node: (Optional) Executes the SQL INSERT query generated by the “Create query in topic” function to store data in a MySQL database.
- UI Text Node (
Last Update
): Displays the last updated timestamp on the dashboard. - UI Group and Tab Nodes: Organize the gauges and text elements into groups and tabs on the Node-RED dashboard for better visualization.
Alt text: Visual representation of the Node-RED flow diagram, showing the connection of nodes from HTTP In to HTTP Response, Change nodes for data parsing, UI Gauge nodes for visualization, and optional MQTT and MySQL output nodes for data integration and storage. The flow illustrates real-time OBD2 data processing and display within Node-RED.
To use this flow:
- Import the JSON code: In your Node-RED palette, go to “Import” and paste the JSON code.
- Configure MySQL and MQTT (Optional):
- If you want to use MySQL storage, you’ll need to configure the
MySQLdatabase
node with your database credentials and update themysql
node with the correct database configuration. - If you want to use MQTT, configure the
mqtt-broker
node with your MQTT broker details.
- If you want to use MySQL storage, you’ll need to configure the
- Deploy the flow: Click the “Deploy” button in Node-RED.
- Access the dashboard: Open your Node-RED dashboard (usually at
http://your.nodered.server:1880/ui
) to see the real-time gauges displaying your car’s data when Torque is sending data while you drive.
Real-Time Car Monitoring and Beyond
By setting up this Node-RED flow with Torque and an OBD2 adapter, you’ve created a powerful system for real-time car monitoring. You can now visualize crucial vehicle parameters on a custom dashboard, log data for analysis, and even integrate this data into your smart home ecosystem for advanced automation scenarios. This setup opens up a wide range of possibilities for car diagnostics, performance analysis, and creating your own connected car applications.