Now we need to start proving some of our theories about how Easimap is actually talking to the ECU. And it seemed to me that if I was going to prove anything then I’d need to pass a lot of data through any theory I came up with, and that meant writing a program – since bit bashing (doing the decoding by hand) hundreds of packets of CAN bus data was probably going to take as long to decode by hand as it would to write a program to do it. Having a program would also mean I could push lots of examples through my code to check out my theories, but also tweak my theories and quickly run my code again to check them.
And if I was going to write a program then it was going to need to translate the binary data on the CAN bus into something human readable. Of course that’s what Easimap uses the EC2 files for and I wanted to use that same information in any programs I wrote.
The EC2 files are plain text files using a similar, but not quite the same, layout as legacy Windows ini files. There a lots of programs that now use this file format and it seems MBE/SBD have come up with their own variation – at least I’ve not seen anything quite like it before.
It uses the regular square brackets “[ ]” to create sections in the file, but it then nests these sections to create hierarchies. And it means that standard libraries to read in an EC2 file won’t work. Time to write some code then…
EC2 File Sections
The 9A4 EC2 file has the following hierarchical, top-level sections, each contained in the EC2 file and having square brackets around them:
- PARAMETER PROTOTYPES
- NUMERIC SCALES
- STRING SCALES
- PARAMETER DEFINITIONS
- SPECIAL FUNCTIONS
- MAPPING VECTORS
- MAPPING CONTROLS
- SETUP PROTOTYPES
- SETUP DEFINITIONS
- MAP PROGRAMS
- SPECIAL INTERFACE PROTOTYPES
So, in the EC2 file the History section has a line with [ HISTORY ] in it.
There’s clearly a lot of those sections that will be interesting to look into at some point. But I had a very focused desire to get the raw sensor diagnostic data out of the car and that meant I only needed to look at three sections for the moment.
After a bunch of trial and error it seemed I needed to use the Parameter Definitions to get a list of all the data variables that the car could divulge.
So for Engine RPM the corresponding Parameter Definition is:
[RT_ENGINESPEED] Number of Dimensions = 0 Page = F8 Address = 237C Bytes per Cell = 2 0 = SCALE_ENGINESPEED
That allowed me to figure out what the request was that I needed to make to the ECU in order for the ECU to respond with Enging RPM. See the previous post for a breakdown of that.
It also told me that I was going to get two bytes of data back from the ECU (Bytes per Cell = 2).
But 2 bytes of data can range from 0 to 65535 (assuming its a positive integer). And I wasn’t sure how to interpret the 2 bytes. Other data variables, like battery voltage, were going to be floating point numbers and while Engine RPM could be a 2 byte integer with no scaling or offset, that wasn’t going to be the case for most of the other data coming from the car.
That’s where the Numeric Scales top level section comes in…
It turns out that the line:
0 = SCALE_ENGINESPEED
refers to a SCALE_ENGINSPEED section in the Numeric Scales section of the EC2 file. The “0 = ” bit refers to the fact that this is the zeroth dimension for the variable, something that becomes important for 1D, 2D and 3D maps where there are more scales defined for each of the axies on the map. The definition of SCALE_ENGINESPEED looks like this:
[SCALE_ENGINESPEED] Units = RPM Scale Minimum = 0.000000 Scale Maximum = 65535.000000 Display Minimum = 0.000000 Display Maximum = 12000.000000 Display Interval = 1000.000000
I suspect that’s fairly obvious what’s going on here, but the idea is that these Numeric Scales set the limits for the data coming back from the car, along with any scale factor that needs to be applied and any offset.
For instance, the temperate variables have a scale (allowing them to be floating point numbers when the result is divided by the scale) and offsets (meaning that the data coming from the car needs to be offset by a particular value, allowing the number to also be negative – as might happen with air temperature on a cold day).
You can also see that the Numeric Scale also sets the characters we can use for the Units, RPM in this case.
Finally, I wanted to be able to see the data coming from the car in a human readable form. And while RT_ENGINESPEED is reasonably simple to understand, there are other variables that are much more obtuse, like RT_TPSFUEL+TRIMBANK1.
That’s where the Parameter Prototype section comes in with English definitions of what each variable is. For instance, RT_ENGINESPEED has this parameter prototype:
RT_ENGINESPEED, Engine Speed, EngSpeed, Engine Speed in rpm as measured by the crankshaft sensor. This is the primary input for the fuel and ignition maps
I spread that definition across a few lines, it’s on a single line in the EC2 file.
One thing to note about these Parameter Prototypes is that at the end of the line in the EC2 file, you can get the word “DISABLED”. I ignored this at first but soon came to realise that it really did mean “disabled”. If I didn’t take account of it in the code I wrote, then I found there were variables defined more than once. Only one of the definitions was not disabled, so by ignoring the disabled ones I was able to get a clean list of valid variables.
Now I knew the layout of the EC2 file, I could write some code. My weapon of choice here is Python. I’m reasonably new to Python but there’s a huge library base now behind Python and I can code with it on Linux, Mac and PC reasonably easily.
The code for ec2parse.py can be found here. The code is far from pretty… but it does a job. I may go and tidy it up when I get a chance (but I think everyone says that and I always advocate writing neat code in the first place so you don’t have to go back to it later – do as I say, not a I do!).
This Python code will take an EC2 file and create an output file that can be used later, i.e. in the mbepcap2txt.py example below. For the moment I’ve been using the JSON output that I created which creates a formatted file that another Python file can read (use) instead of it having to decode the EC2 file every time.
Once I’d written this parsing code for the EC2 files I also the wrote a test app to take a CAN bus Wireshark capture file (.pcap or .pcapng) and turn it into something human readable – remember this is an onion with protocol layers… CAN bus being turned into a SocketCAN format, decoded as CAN then as ISOTP – phew!.
This allowed me to take many hundreds of messages sent between Easimap and the ECU and verify that I had all my theories validated before I actually used any of this software I’d written to send any messages to the car…. I didn’t want to have to fork out for a new ECU, or even worse a new engine because I’d sent some random message to the ECU.
And it all worked… here’s some output in something more human readable that the CAN bus messages:
REQUEST: 0100000000f83031363744454c4d4e4f50515a5b5c5d646a6b7c7d9e9fa0a1d8d9dadb RESPONSE: 814e39094a9c52b886e8844e39a80d70a000b04f00005695800900800080 RT_THROTTLEANGLE1(RAW)=1.1192 V ( Throttle Angle 1 (Raw) ) RT_AIRTEMP1(LIM)=16.273 ( Air Temp ) RT_COOLANTTEMP1(LIM)=21.632 ( Coolant Temp ) RT_COOLANTFUELFACTOR=110.5 ( Coolant Fuel Factor ) RT_AIRTEMPFUELFACTOR=3.8346 ( Air Temp Fuel Factor ) RT_THROTTLEANGLEINCREASING=1.1192 V ( Throttle Angle Increasing ) RT_TPSFUEL+TRIMBANK1=5.6013 ms ( TPS Fuel + Trim ) RT_TPSVSSPEEDIGN+TRIM1=4.7938 ( TPS vs Speed Ign + Trim ) RT_THROTTLESITE1=0.0 Site ( Throttle Site 1 ) RT_BAROSCALEDLIM=1.04 Bar ( Baro Pressure ) RT_ENGINESPEED=0.0 RPM ( Engine Speed ) RT_BATTERYVOLTAGE(LIM)=11.667 V ( Battery Voltage ) RT_BATTERYVOLTAGECOMP=0.48725 ms ( Battery Voltage Comp ) RT_MAPPINGPOT1LIM=0.0015259 ( Mapping Pot 1 ) RT_MAPPINGPOT2LIM=0.0015259 ( Mapping Pot 2 ) ## ISOTP REQUEST :0100000000f9babbbcbd ## ISOTP RESPONSE :81781edc1e RT_SOFTCUTTIME=7800.0 RPM ( Soft Cut ) RT_HARDCUTTIME=7900.0 RPM ( Hard Cut )
Remember this is all test data that I’ve “captured” or “sniffed” when Easimap was communicating with the cars ECU. To this point none of my code is sending anything to the car. So, before I could do some real damage to the car and send my own data to the ECU, there was one more test I wanted to do to make sure everything was going to be as safe as I could make it. I wanted to connect up a Logic Analyser to the CAN bus so I could make sure the communications were working as expected…