I am usually verbose, but for this part I will try to be succinct. This project is:
An Intel Edison + a GPS receiver + a Bluetooth OBD II reader + (optional aftermarket car sensors) + a car.
Add in some software and you can monitor the position and ECU parameters (rpm, temperature, gear, speed, throttle position, etc.) from “afar” in a nice and neat dashboard from almost any device with a web browser (In this I use freeboard because it is easily configured in the browser and looks good).
It is a good idea to read this instructable that I wrote a few weeks ago https://www.instructables.com/id/Intel-Edison-Sens... It goes over in great detail how to set up an Edison to take generic sensor data and put it in a web dashboard. I will gloss over or refer to that instructable for some of the steps in this one.
I hope to present a cohesive system here but I believe that several of these items are takeaways that could be useful for other people:
- How to get GPS working and output as json data via a quick web frontent.
- The same for independent sensors
- And the same for OBD II data
In each section you will see annotated diagrams of the hookup for each sensor. I present them separately so that it is easier to tell what is going on without looking at a rats nest of wire.
I had wanted this instructable to be more than it is, but work and some technical difficulties (I'm having issues with my bluetooth obd II device...so there is no video of things working yet) got in the way of creating what I really wanted. So, this is more a prototype with two hurdles that I have researched solutions to but have not implemented yet. I’m putting this all in the introduction because I think it is important to point out where this is not a fully finished and engineered solution:
- On track or even at any decent distance wifi is not very effective. The Edison has a great (for its size) onboard wifi antenna and a UnF connector for adding a large external antenna if necessary.
- Would a well-designed external antenna extend the range of this device to acceptable levels conducive over distance of 2 or 3 miles. (The local track close to me is Willow Springs/Streets of Willow. Using a google earth guestimate the max distance from the pits to an on-track location is about 2,400 ft). Your average wifi router is good for about 200 ft. A cantenna like this (http://www.turnpoint.net/wireless/cantennahowto.html) usually can extend things to about a mile but is highly directional (ie you would have to point it at the car to get a link).
- Would a store and forward protocol solve this issue without resorting to “exotic” hardware?
- What other comms channels are available and how easy is it to implement then on the Yocto distro and the Edison hardware.
Cellular – This would work, but is a bit overkill and has service costs associated with it. If one were tracking cars over longer distance this would probably be the right way to go. (Something like this might work: https://www.sparkfun.com/products/13120 and this person has a GPRS modem working: https://www.sparkfun.com/products/13120
Freeboard itself only supports an update rate of about 1 update a second per data source. Further my code isn’t that great and blocks a bit when waiting for things like GPS position. If this is really going to be used as a car-on-track monitoring tool then it has to update on the order of 10-20 times a second (this will allow for seeing things like mid-corner throttle modulation, fuel starvation mid-corner, accurate acceleration and deceleration points, etc.
Skip using freeboard. Dump raw data to an app on your phone or computer using UDP and parse it there. Then you would get it as fast as the comms link would support (and depending on how many values you are getting back you might have to (auto?) adjust the rate you are reporting them.
Step 1: Necessary Supplies
(most of the items are repeats from this Instructable that I wrote a few weeks ago https://www.instructables.com/id/Intel-Edison-Sens...
An Intel Edison – Why am I using this instead of a raspberry pi or some other single board computer? For starters Intel is seeding the Instructables community and the academic community with these in hopes that they get adopted with the furor of the Arduino. I was lucky enough to be generously given one of these by Intel for the purposes of entering this contest. Still, there are compelling reasons for using this board: 1) Ease of working as a wifi accesspoint, 2) low power requirements (I power it via cig lighter and usb cable, but I have tested it with one of those backup batteries for charging cell phones on trips and it ran for hours and hours.) 3) built in Bluetooth with a software stack that works for the most part (more on that later) 4) mraa. Grove GPS sensor – For this I’m using the Sim 28 module. It was given as part of the “Transportation and Safety” starter package. It is a good, accurate sensor, with available documentation.
Generic Bluetooth OBD II device – Some of these are better than others. I like the ones with on-off switches built in because otherwise if you leave them plugged in they will slowly drain the battery in your car (OBD II standard is such that the power pin goes to the 12v line and no the line that only comes on after you turn the key to “accessory”)
A car built after 1995 – for the OBDII part you will need a car that does OBD II stuff. Most will work, some will give you trouble.
Optionally you can add off the shelf aftermarket sensors. For these I like autometer because they willingly give documentation to hobbyists documentation about their sensors. I have sample code for an oil pressure (pn: 2242) and a temp sensor (pn: 2258) that they make. To use these you will also need resistors. Base on the code and setup I use (to maximize the resolution in certain areas of the readings given 1024 ADC steps) a 100 ohm resistor with the oil pressure sensor and a 150 ohm resistor with the oil temperature sensor. I set them up in a voltage divider configuration (more on that later).
A wifi device that has a web browser – I’ve tested this out with various android phones and a laptop.
A power source for the edsion – This one is up to you. I have a temporary setup right now using a usb car charger. You could also hard wire it for a more permanent setup. Even better you could use an external battery pack and have less wires to mess with.
Various misc electronics parts - wire stripper, hookup wires, maybe a breadboard, etc. Stuff you would normally have in a workspace for doing a project of this type (I if miss something or you think this is too generic let me know). Other instructable has supplies and software choices in a list on the first page: https://www.instructables.com/id/Intel-Edison-Sens...
Step 2: Set Up the Prerequisites
Go to my other instructable. Seriously. Go there first and get most of that working. Install the requisite software. Get a sample reading from something on a web browser and then you will be ready for the next steps.
Step 3: GPS
Here I am using the one intel provided to me as part of the contest. It is a SIM28 chip with an antenna on breakout board that Grove sells (http://www.seeedstudio.com/depot/grove-gps-p-959.html?cPath=144_145). What is nice is that there is a upm piece written for this chip. What is not so nice (at the time of writing this) is that that code doesn’t work (here is the relevant code sample: https://github.com/intel-iot-devkit/upm/blob/2f1b... and the relevant support forum thread: https://github.com/intel-iot-devkit/upm/blob/2f1b... (with my temporary inefficient fix) ).
Back on the fortunate side of things this little chip does all the hard GPS lifting for your and outputs NMEA sentences in plain text over a UART serial connection. All you have to do is parse them.
As you can see in the pictures above the hardware configuration for this is pretty straightforward. Power to power, ground to ground, rx to tx and tx to rx (yes you read that right, you cross them so that whatever the gps device transmits is received on the receive pin of the edison and vice a versa).
I also show here how I hook up the grove sensors using hookup wire (since the contest kit didn’t have the grove shield and I didn’t order one because it wouldn’t have come in time). Here is the code I have parsing it out:
#gps sensor #for use with the seeed grove gps sensor (SIM28 based) #uses UART (serial) 9600 8-N-1 import mraa
def getLocation(): u = mraa.Uart(0) u.setBaudRate(9600) GPSData = [0,0,0] while(True): if(u.dataAvailable()): buff = u.readStr(256) if buff.find("GPGGA") != -1: #this is the easiest to parse smallerbuff = buff[buff.find("GPGGA"): buff.find("\n")] splitbuff = smallerbuff.strip().split(",") #print(splitbuff) latnmea = splitbuff latdir = splitbuff lonnmea = splitbuff londir = splitbuff lat = float(latnmea[0:2]) + float(latnmea[2:])/60 lon = float(lonnmea[0:3]) + float(lonnmea[3:])/60 if londir == "W": lon = lon * -1 alt = float(splitbuff) GPSData = [lat,lon,alt] return GPSData
Some todos I have in this department are
- Change away from the default baud rate of 9600 to something higher. Probably get more responses quicker, especially with the inefficient way I’m getting data.
- Switch to using upm once it is update (there are several threads on this in the intel forums and they seem somewhat responsive about the issue).
- Configure the SIM28 to only output some of the NMEA sentences which should also speed things up. The first and the last on this list can be accomplished by diving into the documentation here: http://www.seeedstudio.com/wiki/images/d/d7/SIMCo... .
Step 4: OBD II Data
OBD II data is pretty neat and there is a lot written on it out on the internet. So, if it is something you are interested in please go do some reading. It will be worth your while. Here are some salient points:
- There are actually several specs implemented that fall under the colloquial umbrella of OBD II. The cool part is you don’t have to know about this most of the time as modern OBD II readers and adapters will figure this out for you.
- There is also CANBUS data out there (usually accessible via the OBD II port and often via other physical interfaces). The unfortunate part is a lot of the specifications for that are proprietary (or intentionally undocumented). For more on that go here: https://www.instructables.com/id/CAN-Bus-Sniffing-...
- Not all cars will support all of the “standard” OBD II PIDs. They will support most. For instance my car doesn’t report oil temperature unless you send it a CAN sequence. One is a header and one is the query (or in CANBUS parlance it is more like a subscription in some ways).
Physical hookup is pretty simple. Plug the OBD II device into the OBD II port on your car, which is usually located somewhere under the dash on the driver’s side.
What I’ve done for this part is to use the python-OBD library to grab some standard OBD II data and expose it as JSON for easy IoT style consumption. The repository for this is here: https://github.com/brendanwhitfield/python-OBD . However, you can install it via pip with a:
pip install obd . Bredan’s documentation is actually pretty good. There are several forks of this as well, which do some interesting things. In particular this one: https://github.com/Pbartek/pyobd-pi is interesting as it focuses on being used with the raspberry pi.
In order to get OBD II data to your Edison you will need a physical interface. A usb interface will work, but for convenience I use a bluetooth one. If you chose a USB interface you will have less to do and the statement in my code:
connection = obd.OBD('/dev/rfcomm0') should probably read
connection = obd.OBD() or the section in the parentheses should refer to the linux serial device that is your usb device.
For those using a bluetooth device this is what I had to do. You may have to try a few other things to get it to work properly. Bluetooth setup seems to be a finiky thing in linux still and I’m not sure why.
- Start bluetooth https://software.intel.com/en-us/articles/intel-e... full bluetooth documentation is available here: https://software.intel.com/en-us/articles/intel-e...
- Add an agent - the docs don't say what this means - basically there are preset names, but I couldn’t figure out what they meant. I stuck with the one they used in the documentation and it seems to work.
- Pair (use bluez control util to set it up as in the documentation listed above.
- Then run
rfcomm bind rfcomm0This will bind that bluetooth connection to the linux device /dev/rfcomm0. If you look in my code listing this is what I refer to when setting up the OBD II connection.
- If you want to run a script that does this at startup follow the directions here: https://communities.intel.com/message/292186#2921... Although I have had on-and-off success with this. The bluetooth pairing should survive a reboot.
The OBD II data is then reachable via several web contexts in the flask main program that will expose them as JSON data which can then be used by IoT services like freeboard.
#OBD II data using pyobd
import obd from obd import OBDCommand from obd.utils import unhex #call this from the main program...because we only want to do it once def setupOBD(): connection = obd.OBD('/dev/rfcomm0') counter = 0 #while not(connection.is_connected() or counter < 20): # connection = obd.OBD('/dev/rfcomm0') # counter = counter + 1 #return (connection)
def rpm(connection): response = connection.query(obd.commands.RPM) return response.value
def engine_load(connection): response = connection.query(obd.commands.ENGINE_LOAD) return response.value
def coolant_temp(connection): response = connection.query(obd.commands.COOLANT_TEMP) return response.value
def intake_pressure(connection): response = connection.query(obd.commands.INTAKE_PRESSURE) return response.value
def speed(connection): response = connection.query(obd.commands.SPEED) return response.value
def timing_advance(connection): response = connection.query(obd.commands.TIMING_ADVANCE) return response.value
def intake_temp(connection): response = connection.query(obd.commands.INTAKE_TEMP) return response.value
def throttle_pos(connection): response = connection.query(obd.commands.THROTTLE_POS) return response.value
def voltage(connection): response = connection.query(obd.commands) readyresponse = unhex(response) return readyresponse #needs to be tested with a real car...not sure if this will work
Step 5: Off the Shelf Sensors (Oil Temperature and Oil Pressure)
Off the shelf sensors are used with aftermarket gauges to allow car enthusiasts and racers view more detail about engine function than is available via the dashboard and possibly even the OBD II port. They should be installed in the car according to the manufacturer's directions to give accurate and safe readings.
Both of the sensors I use here are from a company called Autometer who has been making gauges for many years and whose products are used by many professional race teams (even ones they don’t sponsor). They are also forthcoming with plots showing what happens electrically when the sensors work. Both of these happen to work with changing resistance values in a curvilinear fashion with respect to the things they sense. Microcontrollers or more specifically analog to digital converters work on voltage logic and assign digital values to voltages picked up off of inputs. Many are 5 volt and 3 volt logic.
In order to convert resistance to voltage something called a voltage divider is used. A voltage divider is a simple circuit that uses the variable resistance of the sensor and an unchanging resistor to output a voltage somewhere between 0 and the input voltage. For more on that look here: https://www.instructables.com/id/Simple-Voltage-Divider/ and here https://learn.sparkfun.com/tutorials/voltage-dividers
In the case of the analog to digital converter on the edison you get 1024 steps in a 5 volt range, which is fairly standard on microcontrollers today (you get the same thing on the arduino). In order to use the maximum range of that 1024 digital steps you have to optimize for a certain part of the resistance to reading curve for your sensors. Luckily this has been done for your and a simple formula has been written to solve this issue.
Then pick the off the shelf resistor that is closest to the answer.
In the pictures for this page you will see hookup diagrams for these sensors. Note that usually the threaded part of the sensors is the ground and gets grounded to the engine simply by screwing it into the gallery plug or sensor distribution point you are using. You can easily test these sensors by heating some oil in a pan or using a bike pump.
Here is my code for the two sensors:
#based on the autometer 2258(9) fluid temp sender #use with a 150 ohm resistor at R2 and the sensor at R1 of a voltage divider import mraa
def getOilTemp(pin): try: a = mraa.Aio(pin) tval = a.read() if (tval < 120): return (0) if (tval < 904): return((-.194 * tval + 195)) if ((tval >= 120 )and(tval < 179)): return (-7.111 * tval + 1981) if ((tval >= 179 )and(tval < 252)): return (-3.407 * tval + 1318) if ((tval >= 252 )and(tval < 293)): return (-2.081 * tval + 984) if ((tval >= 293 )and(tval < 381)): return (-1.375 * tval + 777) if ((tval >= 381 )and(tval < 473)): return (-.853 * tval + 578) if ((tval >= 473 )and(tval < 563)): return (-0.578 * tval + 448) if ((tval >= 563 )and(tval < 571)): return (-4.78 * tval + 392) if ((tval >= 571 )and(tval < 602)): return (-0.447 * tval + 374) if ((tval >= 602 )and(tval < 643)): return (-0.397 * tval + 344) if ((tval >= 643 )and(tval < 714)): return (-0.335 * tval + 304) if ((tval >= 714 )and(tval < 800)): return (-0.269 * tval + 257) if ((tval >= 800 )and(tval < 844)): return (-.228 * tval + 224) if ((tval >= 844 )and(tval < 878)): return (-.207 * tval + 207) if ((tval >= 878 )and(tval < 904)): return (-.194 * tval + 195) except: print("can't ADC for this sensor")
#oil pressure sensor
#based on the autometer 2242 oil pressure sender #voltage divider with the sensor as R2 and R1 as a 100 Ohm resistor. #returns a value that is the actual value * 10 import mraa
def getOilPressure(pin): try: a = mraa.Aio(pin) tval = a.read() if (psival > 722): return 0 if (psival < 257): return 9999 if ((psival <= 722)and(psival > 619)): return 1747 - (psival*240)/100 if ((psival <= 619)and(psival > 520)): return 1802 - (psival*250)/100 if ((psival <= 520)and(psival > 411)): return 1694 - (psival*230)/100 if ((psival <= 411)and(psival > 257)): return 1418 - (psival*160)/100 except: print("can't ADC for this sensor")
Please noted that I have divided the curved response function up into several steps. In some cases where the curve is mathematically complex (or the microcontroller isn’t adept at that kind of math) this may shorten response times.