Captured: 2016-01-11 at 11:33 from nikrooz.co.uk
We down-graded but up-sized our car, to a Vauxhall / Opel Zafira. It's only a 2002 model but does come with cruise control, air con and all electric windows, mirrors, etc. However, unlike our old Peugeot 307, it doesn't tell you what you've set the cruise control to. Or, in fact, a very accurate speed reading. And the built-in trip computer only shows one piece of information at a time; you have to scroll all the way round 7 options to get from average MPG to instant, for example.
The stock display (not mine)
Car Raspberry Pi computer
I got myself an OBD II bluetooth interface off ebay for about £7. The OBD port on the Zafira sits just underneath the handbrake lever, and gets you connected to the engine ECU, as well as the ABS and power steering ECUs.
I started experimenting with some android apps to start with. Most of these use the open PIDS available here on Wikipedia. The Mode 1 PIDS give you a fair amount of information - officially. But on my car, they didn't. I could get RPM, speed and water temperature, but not much else. To replace my OEM display, I'd need at least to get hold of the Miles Per Gallon and as many other readings as I could. So, I found a couple of Opel-specific apps that gave some other bits of information - MAF flow rate, injection fuel quantity, fuel temperature, and lots of other goodies. That's enough to work out MPG! If only I could get my hands on these manufacturer-specific PIDs, I'd be able to get the same readings on my Pi and display them nicely.
At this point I had Xbian running on the R-pi, and had installed a bluetooth dongle and got comms established between the Pi and the scanner. See this page on how TODO!. I tried looking for ways to snoop the serial comms between my android phone and the scanner. Apparently, eavesdropping on bluetooth comms is extremely difficult, since it channel-hops constantly and you won't know which channel it about to hop to unless you were there and on the right channel at the start of the conversation. Suffice to say, a standard £1 bluetooth dongle won't let you eavesdrop on communications.
So, how do I find out what to send to my ECU to get the needed telemetry?
Strace is a linux command that helps you debug processes. Importantly for us, it logs writes to the serial port. So, run it on android as root and it'll tell you what your processes are talking about.
Below is a snippet of what I got back from strace after attaching it to the right process. I had to root my phone, copy in an strace binary, run it as root, and point it to the right Process ID.
If you're familiar with AT commands at all, you may recognize some of the command sent. Either way, the ELM327 AT commands sheet has them all listed. Of course, my £7 dongle is not an official ELM327 based device, it's a Chinese copy! But they did keep the AT commands the same for compatibility.
So, here's how one piece of software initializes the bus:
atz #reset``ete0 #echo off``ATL0 #linefeeds off``ATAL #allow long messages >7``ATSP5 #Set protocol ISO 14230-4 (KWP FAST)``ATH1 #headers on``ATSTFF #timeout big (ST32 is good)``ATSH8111F1 #header 8111F1
I already know from research that my Bosch EDC15M ECU communicates over KWP, but what's really interesting here is the header, 8111F1. Thanks to a Russian site with an explanation of KWP, I could work out what this header means (see below). The communications continue:
3E #tester present``1A80 #ECU ID table``1A81``1800FF00 #Mode 18 request for DTCs``3E``82``ATZ``ATSH8128F1 #28 is the ABS``3E``3E``3E``82``ATZ``ATSH8131F1 #31 is the EPS``3E``1A80``1A81``2101``2101``2101
The KeyWord Protocol, KWP2000 is not very well documented as far as I can find. One amazingly helpful resource was a Russian site with a document detailing one Russian ECU manufacturer's implementation of KWP. Of course, their implementation is not exactly the same as Opel/Vauxhall's, but it gives an insight into ISO 14230, which otherwise is inaccessible to those not willing to pay tens of thousands for access to the standard. It's worth a read - open the page in Chrome if you do want a look, and it'll translate it into English-ish for you.
Here's my notes on it:
Header: Format - Target - Source - [Len]
Rest of message: Sld Data (63bytes)[255] Checksum
So, the header 81 11 F1 means: Format 81 (Start Comms, as per below) --- TO 11 (as above, 28 refers to the ABS in this car and 31 is the EPS controller) --- FROM F1 (I think this is abritrary, as long as it's no used on the bus)
Format name Answer (id always 7f; bit 6=1)
81 startComms c1
82 stopComms c2
01 readDataByPID
10 startDiagnosticSess 50
20 stopDiags 60
11 ecuReset 51
12 freezeframeData
14 clearDTC 54
18 readDTCs 58
1A readECUID 5A
21 readDatabyLocID 61
22 readDatabyCommonID + 2 bytes
23 readMembyAddr 63
25 StopRepeatedDatatrans
26 SetDataRate
27 SecurityAccess
28 DisableNormComms
2C DefineLocalID
2E WriteDatabyCommonID
2F IOcontrolByCommonID
30 ioControlByLocID 70
3B writeDataByLocID 7B Corresponding to 21
3E testerPresent 7E Apparently send this every 2.5s
GMLAN-specific:
A2 ReportProgrammingState
A5 EnterProgramming
A9 CheckCodes
AA ReadDPID
AE DeviceControl
So those are the header types, now for the query payload:
10 startDiagnosticSess packet can be followed with 81 (diagnostic mode pls) and baud rate (0A, normal:26, high:39, enhanced)
To stop send a 20 (should get 60 back. 7F is bad news)
3E Testerpresent followed by 01 (reponse pls) or 02 (its ok don't reply)
11 ecuReset followed by 01 for power on
1A ecuID followed by 1 byte:
80 - complete table.
1A 80 response:
5a 80 [3-21]VIN [22-37]ECUhardwareno [38-57]sysSupplierECUSW [58-72]Engine [73-79]repairshopCode [80-89]progDate [90-97]vehManECUID
5A 90 91 92 93 94 98 9f
1A A0 possibly ODO reading?
1A 90 VIN
14 clearDTC followed by 0000 Pwertrain or FF00 All systems. 54 reply contains same 2 bytes
18 ReadDTCs complicated
21 readDatabylocID:
01 - AftersalesServiceRecord (->128b)
02 - endOfAssemblyLine (->128)
03 - factoryTest (->128)
A0 - immobilizerRecord (2)
A1 - Body serial No (7)
A2 - Engine Serial No (7)
A3 - Manufacture date (10)
A1 for example gives 61 A1 +7 bytes returned
C3 - Possibly TRANS
C4 - Aircon request at byte 1
D3 - Cruise control switches??
21 A0 Immobilizer record
21 30 IO control
21 30 IObelow Param state
IOs:
01 injector1OutputControl This setting informs the control unit, the tester prompts direct control of the nozzle 1. I1OC
02 injector2OutputControl This setting informs the control unit, the tester prompts direct control nozzle 2. I2OC
03 injector3OutputControl This setting informs the control unit, the tester prompts direct control of the nozzle 3. I3OC
04 injector4OutputControl This setting informs the control unit, the tester prompts direct control nozzle 4. I4OC
05 ignition1OutputControl This setting informs the control unit, the tester prompts direct control of the ignition coil 1 and 4 cylinders. IGN1OC
06 ignition2OutputControl This setting informs the control unit, the tester prompts direct control of the ignition coil 2 and 3 cylinders. IGN2OC
09 fuelPumpRelayOutputControl This setting informs the control unit, the tester prompts direct control of the fuel pump relay. FPROC
0A coolingSytemFanRelayOutputControl This setting informs the control unit, the tester prompts direct control of the cooling fan relay motor. CSFROC
0B airConditionRelayOutputControl This setting informs the control unit, the tester prompts direct control relay unit. ACROC
0C malfunctionIndicationLampOutputControl This setting informs the control unit, the tester prompts direct control of the MIL. MILOC
0 D canisterPurgeValve OutputControl This setting informs the control unit, the tester prompts direct control canister purge valve. CPV OC
41 idleStepMotorPositionAdjustment This setting informs the control unit, the tester prompts direct position control idle speed regulator. ISMPA
42 idleEngineSpeedAdjustment This setting informs the control unit, the tester prompts direct control idle speed. IESA
Params: 00 returntoECU. 01 reportState. 02 reportIOconditions. 03 reportIOscaling. 04 resetDefault. 05 freezeCurr. 06 executeControlOption. 07 shortTermAdjust. 08 longTermAdjust. 09 reportIOcalibration
---------------
---------------
23 Read mem by address
23 MemType=0 AddrMSB AddrLSB Memsize
Response 63 xx xx xx xx
-----
3B Write Data by LocID
90 - VIN
98 - RepairShopCode
A1 bodySerNum
A2 engineSerNum
A3 manufactDate
Eg. 3B 90 + 19 ASCII chars
3B 90 xx xx xx xx -> Wait for flow control??? 30 00 00
Response 7B 90 or 7F 3B errCode
------------
27 Security
27 subFunct
01 SeedRequest
02 KeyResponse
03 SeedAnother
04 ResponseAnother.....
I won't go on too much more about KWP, suffice to say sending a header 8111F1 and payload 2101 gives the "AftersalesServiceRecord", which contains all the data we need:
2101: 80 F1 11 4C 61 01 00 00 00 00 00 00 00 00 0C 0C 00 00 0B C1 00 00 02 4C 00 00 00 00 03 E3 00 00 00 00 32 C8 03 E7 04 29 03 38 E5 62 00 00 00 00 03 84 00 00 00 00 09 3D 00 00 0C 09 A0 00 01 90 00 00 A9 00 00 20 00 00 01 BD 0C 2F 03 E8 00 02 9D
The response starts 80 F1 11 (Response, TO F1, from 11 (ECU)
Then 4C = Error (OK) code, and:
21 01 Responses:
# 1 Positive response readDataByLocalIdentifier 61 no
# 2 afterSalesServiceRecordLocalIdentifier 01 no
# 3 Word of equipment 1 08 no
# 4 Word of picking 2 35 no
# 5 The word mode 1 XX no
# 6 Word of mode 2 XX no
# 7 Memory word current faults 1 XX no
# 8 Memory word current faults 2 XX no
# 9 Memory word current faults 3 XX no
# 10 Memory word fault current 4 XX no
Then things get interesting. The following pairs of bytes (WORDs) give all sorts of info: Throttle position, coolant, air and oil temperature, desired & actual RPM, desired & actual boost pressure, EGR pulse ratio, road speed, cruise control set speed, battery voltage, brake pedal position, MAF flow rate, air pressure and others.
Sorry, I'm not going to tell you that! It took ages of getting sensor readings under different conditions - different temperatures, different load conditions, lots of throttle blipping to figure out which reading is the 'desired' value and which is the 'actual' value for many of the values. Oh, and things like the temperatures being measured in Kelvin instead of degrees (free clue!)
EDIT:
That wasn't very nice, sorry. The info is all in the Github project XBMC-rpi-service.skin.OBD and some explanation is in my followup post here
Excel PID working out - This .xlsx file has my workings out of how I figured out which byte of the 2101 response was which. It's not nicely formatted but scroll to the right to see my formulae and adjusted values
2101-on.txt has the original values used to create the above spreadsheet
I seem to have lost the original python code used to generate that .txt file, but it's pretty straightforward: I just kept sending 2101 requests after setting the header ATSH8111F1. Once I figured out that the values came in byte pairs, I used excel to translate these to numbers, and a knowledge of how the engine works to figure out which data should trend which way.
The Raspberry Pi is running the Xbian build of XBMC - it's the fastest by far, and has most bits you'll need pre-installed.
One thing that got missed out for some reason is libtiff. If you get error messages like the below in your logs (/home/xbian/.xbmc/temp/xbmc.log), you'll need to install libtiff4.
15:46:20 T:2913350720 DEBUG: Loading: /usr/local/lib/xbmc/system/ImageLib-arm.so
15:46:20 T:2913350720 ERROR: Unable to load /usr/local/lib/xbmc/system/ImageLib-arm.so, reason: libtiff.so.4: cannot open shared object file: No such file or directory
15:46:20 T:2837853248 DEBUG: SECTION:LoadDLL(special://xbmcbin/system/ImageLib-arm.so)
15:46:20 T:2837853248 DEBUG: Loading: /usr/local/lib/xbmc/system/ImageLib-arm.so
15:46:20 T:2837853248 ERROR: Unable to load /usr/local/lib/xbmc/system/ImageLib-arm.so, reason: libtiff.so.4: cannot open shared object file: No such file or directory
^C
root@AntCar:/home/xbian# apt-get install libtiff4
I did a lot of work on getting the GPIO working in and out straight from the XBMC skin. I'll cover that in a separate post.
Here's what I did to get bluetooth connecting to the ELM327 scanner:
root@AntCar:``/home/xbian``# apt-get install bluez``root@AntCar:``/home/xbian``# hcitool scan``Scanning ...``00:01:E3:8B:AE:43 Mark``00:01:E3:8E:F4:E9 Ant Dect``00:0D:18:27:FA:E6 Vgate``root@AntCar:``/home/xbian``# cat /etc/bluetooth/rfcomm.conf``#``# RFCOMM configuration file.``#``rfcomm99 {``bind ``yes``;``device 00:0D:18:27:FA:E6;``channel 1;``comment ``"ELM327 based OBD II test tool"``;``}``root@AntCar:~``# cat /etc/udev/rules.d/99-custom.rules ``KERNEL==``"rfcomm99"``, ATTR{address}==``"00:0d:18:27:fa:e6"``, ATTR{channel}==``"1"``, OWNER=``"xbian"``, GROUP=``"dialout"``, SYMLINK+=``"elm327"``cat
/etc/init``.d``/elm327``#!/bin/bash``DevNum=99 ``# DevNum is depending on the rfcom settings /etc/bluetooth/rfcom.cfg``case
$1 ``in``start)``rfcomm bind $DevNum``;;``stop)``rfcomm release $DevNum``;;``status)``rfcomm show $DevNum``;;``*)``cat``<
And some testing:
apt-get install python-serial
screen /dev/rfcomm99 38400
python to start Python.
import serial
ser = serial.Serial('/dev/rfcomm99', 38400, timeout=1)
Nser.write("01 0D \r")
speed_hex = ser.readline().split(' ')
speed = float(int('0x'+speed_hex[3], 0 ))
print 'Speed: ', speed, 'km/h'
See my followup post for more info and links to github projects