For sake of simplicity we assume that we know that the CAN bus is operating at 500000 (500k) baud which is the most common baudrate found on vehicles OBD ports, and that the ECUs on the vehicle are communicating so the passive connection with can.init will suffice.
In this section we will develop together a simple LUA script that will leverage the StartDiag System LUA Interpreter and CAN interface to read the VIN number of the connected vehicle and display it.
In order to do this, we will try to use the service 0x09 which provides vehicle information, specifically the sub-function 0x02 to request the VIN in 17 bytes format.
Let's do a clarification here: not all the vehicles implement this sub-function, which means that some ECMs may not reply to this command, or respond with a NAK response (which means that the request has been rejected). Even if your vehicle refuses this command, this section is very relevant for you, in fact this will discuss how to develop the script from the ground up providing some hints on the design process, after you will be able to adapt it to your specific vehicle.
To begin with, let's make sure our vehicle is recognized by the StartDiag System. Start up the System and go into "Info" -> "Vehicle Information", if you see a VIN displayed, it means that StartDiag already knows how to read the VIN, and how to do it is written in that same page, take notes of the "Remote CAN ID" and "Local CAN ID" which will be used for our request. Write down also the "VIN request" value, in fact the first 4 digits show the Service ID used and the other 4 digit show the sub-function/DID.
If do not get a VIN in "Vehicle Information", please make sure you have the vehicle in Key ON and if it fails also after that check, please follow the section about troubleshooting in the StartDiag Device Guide document. You can still follow this section, but you will not be able to try it out directly.
So, let's start with the assumptions and initial hypothesis:
- the ECM that we are going to ask for the VIN responds on CAN ID = 0x7E0 (this is the most commonly used currently)
- the ECM responds with CAN ID = 0x7E8 (also this is the most used currently for these requests)
- the request will use Service ID = 0x09 and parameter ID = 0x02
- we use the UDS protocol and we are going to use the LUA API for it (uds.xmit)
Now, let's do a brief design of the steps needed to get the information we want. In this case it is quite simple, we have to do the request, wait for the response and then we have the VIN.
rc = can.init(500000)
rc, data = uds.xmit(0x7E0, 0x09, 0x02, '', 0x7E8, 100)
Save the file on the SD, I used the name Test.sds but it is not mandatory as long as the name is at most 8 characters long.
Looking at the (uds.xmit) we can see that it returns two values: the return code (0 = OK, Error otherwise) and the data read (the bytes of the VIN in this case).
If we stop here and we try to execute this simple script, it starts, and then just finish, nothing is presented on the screen. Ok, let's make it a bit more interesting
rc = can.init(500000)
rc, data = uds.xmit(0x7E0, 0x09, 0x02, '', 0x7E8, 100)
for i = 1,#data do
print( string.sub(data, i, i) )
end
Time to explain a bit more about the FOR loop.
FOR loops are used when you need to repeat some instructions a specific number of times. In this case we use it because we know we have to work on a group of bytes and we can easily get the total number of bytes we have by using the # operator to get the length of it (#data is equal to the number of bytes in data).
The syntax of the FOR loop is the following:
FOR counrintg_variable = start_value, end_value DO
instructions
END
Note that FOR, DO and END are all mandatory.
In the above script the (end value) is the size of data, in this case #data returns the length of data which is a group of bytes and LUA handles that as a string made of bytes.
OK, let's explain what we did here:
- we do the same request as before, so we should get the data = the bytes of the VIN
- we do a loop, to cycle on all the received data, and for each iteration of the loop we get a byte from the data and we simply "print" it on the screen.
If you try to run this script, you may notice that the characters of the VIN are printed, but one per line, so you at the end you do not really see your VIN, but only the last 9 chars. This is because the "print" function goes to a new line every time it has done with printing what it is required to.
Let's try to print all on the same line. In order to do this we will use a feature of LUA which is the string concatenation. What it does is to take strings and put them together, this is done putting the two the strings to be merged one after the other separated by two dots (string1 .. string2 .. string3 and so on)
Ex:
txt = 'Hello '
txt = txt .. 'World!'
print (txt)
To use this for printing the VIN we have to
- add a variable to hold the VIN string while we are building it
- inside the FOR loop we do not print the char but we add to the VIN string
- at the end of the FOR loop we print the resulting VIN string
rc = can.init(500000)
VINString = ''
rc, data = uds.xmit(0x7E0, 0x09, 0x02, '', 0x7E8, 100)
for i = 1,#data do
VINString = VINString .. string.sub(data, i, i)
end
print( VINString )
And here we have it! If all went fine, this time we should have on the StartDiag Display the VIN of the connected vehicle!
But wait, something is still missing.
Let's try this: turn key OFF and restart the script (many ECM will still respond to requests for some time, even for 10 minutes or more!), so if you do not get an error here, wait more)
Start the script again and let's see the result.
This time we get LUA error complaining about using a trying to get the length of a nil value, this is a good occasion to learn more about reading the LUA errors:
ABORTED: unprotected error in call to Lua API
(Script\Test.sds:3) attempt to get length of a nil value
(global 'data'))
What is happening here?
To discover what is going on we have to start from the error message itself...
It is telling us that the error happened in line 3 (the number next to the script name, Test.sds in my case), also it is telling us that we are trying to get the length of a nil value. nil is a valid value but it is basically a "nothing" value, and Lua does not know how to get the length of such a value. To finish our diagnosis, the name of the nil variable is "data".
The point here is that #data is trying to get the length of a nil value, but why?
The root cause is that since the vehicle is in key OFF, we can't really ask him anything, it will not respond.
Since it is not responding, the uds.xmit returned an error (we can check that rc in this case has a value different than 0, it has 1 which means timeout). We sent the request to the vehicle and we did not get the response in the set 1 second time because the key is OFF.
Having a script fail because of unexpected error is not a very nice thing to do, it is much better to show an error on the screen and then, in case, leave or do the cleanup that may be needed.
Let's do some proper error handling to the example code. For this we will use the IF statement of LUA, which allows us to do something only if a certain condition is true (for the IF to work it is necessary that is called on a statement that evaluate to true or false).
The IF statement , in LUA, has the following syntax:
IF condition THEN
instructions
END
IF, THEN, and END are all part of the mandatory syntax.
The (instructions) are executed only if (condition) evaluate to true.
Let's see how to use it to verify the rc in the previous example.
rc = can.init(500000)
VINString = ''
rc, data = uds.xmit(0x7E0, 0x09, 0x02, '', 0x7E8, 100)
if rc ~= 0 then
print('VIN request failed.')
return
end
for i = 1,#data do
VINString = VINString .. string.sub(data, i, i)
end
print( VINString )
Now, in the same conditions as before we do not see the error from LUA but we have the proper error message.
A small clarification: here we handled the problem of the nil value on data considering it as only possible when the request fails. Another option would have been to instead ignore the rc value and decide that we are going to prepare the VIN string only if there are data to be used.
rc = can.init(500000)
VINString = ''
rc, data = uds.xmit(0x7E0, 0x09, 0x02, '', 0x7E8, 100)
if data == nil then
VINString = 'Got no VIN data.'
else
for i = 1,#data do
VINString = VINString .. string.sub(data, i, i)
end
end
print( VINString )
In this case we see the "Got no VIN data" message.
The behavior may looks the same, but in reality the logic behind it is very different: in the second case we are deciding that since we need the VIN data we handle the lack of that, and not an error in the communication.
This approach covers not only the case of a negative response from the vehicle, but also the case in which the response is OK but there is no data sent in it!
Also this was a good occasion to show you another construct: IF .. ELSE: this not only provides you with a way to so something if a given condition is true, but also allows you to define what to do otherwise.
The syntax of the IF .. ELSE statement is the following:
IF condition THEN
instructions_for_true
ELSE
instructions_for_false
END
The keywords IF, THEN, ELSE, END are all mandatory.
For this statement, instructions_for_true are executed only if condition is true, otherwise the instructions_for_false are executed instead.
This section starts with some assumptions, like fixing in the script the cAN baudrate to use. For more dynamic approaches, take a look at the sample code in section LUA CAN module.