From Napkin to Tangible

The end goal of my project is to create a live dashboard displaying vehicle data that can be used for both diagnostics and race spectating. I initially put together this rough sketch of the different pieces to help explain this idea to my mentor.

My crude napkin sketch road-map

In this post I’ll concentrate on 1) Understanding the automotive data port 2) Interfacing with a car’s computer 3) Placing the car data in the cloud.

Before I can build my live dashboard display in the browser, I need to tap into a source of data from the vehicle. For this I’ll be using the On-Board Diagnostic port, often referred to as OBD for short. How did this port come to be? For that, we have to go back roughly 25 years.

1: An Abridged History of the OBD-II Port

It’s 1991, the first web browser is released, the Teenage Mutant Ninja Turtles II is playing on the big screen, and we were still safe from the Pog craze for another two years. Also, I’m pretty sure my mentor hasn’t even been born yet, but I digress. California would hit a 30 year low in smog pollution due to an aggressive effort to decrees pollution. One of the most significant being automobiles. State legislation mandated that all cars sold in California starting in 91' would require an On-Board Diagnostic port. This mandated port would allow vehicle owners and technicians access to the status of various subsystems on the car to quickly diagnose issues.

This early implementation of OBD lacked a standardized protocol, often differing from manufacturer to manufacturer. In 1994, California mandated that all cars sold in the state would be equipped with the next revision of OBD. OBD-II would be based on protocol standards set by the Society of Automotive Engineers (SAE). In 1996 this port would be mandated for all cars sold in the United States.

OBDII Port

Although this port is capable of displaying trouble codes, it can do a whole lot more. It offers a door into the inner workings of the car’s operations and subsystems. OBD-II uses Parameter IDs, PIDs for short, to request data from a vehicle’s Engine Control Unit (ECU). There are nearly 200 standard OBD-II PIDs defined by SAE. These PIDs run cover almost all sensors that are part of the cars electrical system. SAE standards also mandate that the port must be within 2 ft of the steering wheel, how nice of them!

2: Talking to the OBD-II Port

Let’s pop the top on this barrel full of data!
ELM327 Based OBDII Interface

There are a number of OBD-II diagnostic interfaces on Amazon, most under 15 dollars. I opted for the USB version over Bluetooth to avoid the headache of reliably pairing a BT device with my Raspberry PI.

The micro-controller in most of these devices is the ELM327, or more likely some cheap clone of the ELM327. This controller handles translating the communication between our vehicle and car. Basic communicate can be done through your favorite serial monitor software.

We first start by addressing the ELM327 itself over serial and set it up for our PID commands.

#Reset our ELM with 'atz' the ELM will return the version number
>atz
ELM327 v2.1
#enable line feed
>atl1
OK
#disable header
>ath0
OK
#Set protocol connection to Auto
>atsp0
OK

Now that we have addressed the basic setup for our ELM327, we can pass our PIDs to the ECU itself.

Requests are sent as two hex values. FF FF. The first set of values we send represent the ‘mode’ value. For live data from the car’s ECU, we will be dealing with mode 1, or 01 in Hex. The second hex contains our Parameter ID request or PID.

As mentioned before, there are nearly 200 standard OBD-II PIDs defined by SAE. You can view them all along with the different modes here. Let’s query the OBD port for the engine RPM. We would send “01 0C” (01 — to indicate mode 1 and 0C- to query PID 12, the engine RPM.)

>01 0C
41 0C 0D 84

We are interested in the two hex values at the end of the return. 0D and 84. Let’s convert our hex values to decimal. 0D = 13 and 84 = 132. We can now put these into our formula found below and calculate our engine RPM.

((256*13)+132) / 4 = 3,460 rpm

Now, this was fun, but there has to be a quicker way to do this. This is supposed to be an exercise in Python right? Well prime your PIP installer and get ready to fire up Python-OBD!

You can download the example code from my GitHub here!

Python-OBD is a module that takes out the tedious work of sending and decoding PIDs and lets you call different values by key arguments.

Let’s take our engine rpm example from above and re-write it as a python script using Python-OBD.

#import our obd library
import obd
#OBD() will search for a serial connection
connection = obd.OBD()
#We will pass in our argument "RPM" and query the ECU for a response
pid = 'RPM'
cmd = obd.commands[pid]
response = connection.query(cmd)
#print said response
print(response)
>>>3460.0 revolutions_per_minute

engine_rpm.py example

This library saves us the time of configuring our ELM based controller, looking up our hex value for our desired PID, and converting the return bytes to a readable value. More importantly, we can now pass easily understood arguments into the command’s function!

If we wrap this code up in a function we can easily run a whole list of PIDs through.

import obd
connection = obd.OBD()
def obdRequest(pidList):
for pid in pidList:
cmd = obd.commands[pid]
response = connection.query(cmd)
print(response)
list = ['SPEED','RPM','RUN_TIME']
obdRequest(list)
>>> 0 kph
>>> 980 revolutions_per_minute
>>> 451 second

engine_pids.py example

Now I have talked about how the PIDs are standardized, but manufacturers are not required to support every PID. Some cars are simply not equipped with certain hardware that would warrant the use of certain PIDs. Because of this, we may get a message saying “XZY not supported” when running our list though.

When running this code in a loop, it doesn’t make sense to continually loop through these unsupported values. It would also be nice to take the supported values and create a dictionary that I could store in the future elsewhere.

The solution to this was to create a list of all the possible arguments from the Python-OBD Command Table and pass it through. I would then write a dictionary containing a key and value for each argument if it did not return a “None” response.

#import time so we can slow down the print out
import time
import obd
connection = obd.OBD()
#Create a list of all possible PIDs
pidSens = ['ENGINE_LOAD',
'COOLANT_TEMP',
'SHORT_FUEL_TRIM_1',
'LONG_FUEL_TRIM_1',
'SHORT_FUEL_TRIM_2',
'LONG_FUEL_TRIM_2',
'FUEL_PRESSURE',
'INTAKE_PRESSURE',
--- Trimmed for Space ---
'OIL_TEMP',
'FUEL_INJECT_TIMING',
'FUEL_RATE']
#Empty dictionary to hold supported PIDs & Values
supPids = {}
#Take full list of PIDs and pass them to the car's ECU, if a "None" response is received, they will not be added to supPids
def obdPidTest(pids):
for pid in pids:
cmd = obd.commands[pid]
response = connection.query(cmd)
if str(response) != 'None':
supPids[pid]=response
#Take supPids dictionary and update the values
def obdRequest(dict):
for key in dict:
cmd = obd.commands[key]
response = connection.query(cmd)
dict[key] = str(response)
#Pass pidSens list to generate supPids dictionary
obdPidTest(pidSens)
#While statement to loop and print updates
while True:
#Update supported PIDs and print the PID and Value
obdRequest(supPids)
for key, val in supPids.items():
print(key + ': ' + val)
#Add a tenth of a second between value print out
time.sleep(.1)

In the code above, I took a list of all my possible PIDs and passed them into the obdRequest function. The obdRequest populated my supPids dictionary with only the keys that returned a value other than “None”. From there I looped over my supPids dictionary and printed the key value pairs for each argument. I added the time.sleep function to help with readability for this example.

engine_all.py example

3: Placing our data in the cloud

Now that we have some workable data, I need to place it somewhere…

Firebase to the rescue! My mentor Cam and I had some discussion about the best way to host and serve real-time data. Firebase data is stored in a JSON (JavaScript Orientated Notation) format and is accessible from a mobile device or web browser with updates in the millisecond.

The topic of setting up a Firebase real-time database has been covered a thousand times over by people who can explain it better than I, so check out this link on how to get the basics going so you can start uploading to your database.

To initiate my Firebase database, I pip install firebase-admin and import the modules in the code below. ‘OBDII.json’ references my API credentials stored in a separate file. The reference to my database is passed into the initialize_app function.

#import necessary modules for firebase
import firebase_admin
from firebase_admin import credentials
from firebase_admin import db
ref = db.reference('/')
# Fetch the service account key for firebase from JSON file contents
cred = credentials.Certificate('OBDII.json')
# Initialize the app with a service account, granting admin privileges
firebase_admin.initialize_app(cred,
{'databaseURL': https://obdiidata.firebaseio.com/'})

I can now pass my completed dictionary of supported PIDs by using the ref.set function win the while loop.

#While statement to loop and print updates
while True:
#Update supported PIDs and print the PID and Value
obdRequest(supPids)
for key, val in supPids:
print(key + ': ' + val)
#Upload supPids to Firebase
ref.set({
('object'):
supPids
})

And what does this look like in Firebase?

Live Firebase data feed

Sweet Sweet Data!

You’ve probably noticed I have been running my code from the Windows command line and not the Raspberry-pi I alluded to. In my next post, I will cover my hardware and how I plan to take data from Firebase and make something meaningful with it!