Skip to content

Files

Latest commit

Taha Dhiaeddine AmdouniTaha Dhiaeddine Amdouni
Taha Dhiaeddine Amdouni
and
Taha Dhiaeddine Amdouni
Aug 8, 2017
b4d689d · Aug 8, 2017

History

History
248 lines (175 loc) · 25.3 KB

Raspberry Pi Car-puter.md

File metadata and controls

248 lines (175 loc) · 25.3 KB

Raspberry Pi Car-puter

Captured: 2016-01-11 at 11:33 from nikrooz.co.uk

The problem

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.

Zafira-display

The stock display (not mine)

The Solution - A Raspberry Pi, of course!

PiPuter

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.

Hacking the bluetooth comms

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.

The OBD comms

So, how do I find out what to send to my ECU to get the needed telemetry?

Strace!

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

KWP

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.....

Bored yet?

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.

Which byte is which?

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.

Back to the Pi

So, how does the Pi work?

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.

Bluetooth

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