The “reloadable intelligence” of OOBD is realized as Lua script. What this means and how this works, is described here.
If Lua runs inside another program, without further assistance it does it like in a black box: No connection from the inside to the outside, no inputs, no feedbacks. This is obviously not very senseful, so when Lua is implemented into another program, there are normally some interfaces made up to let Lua and its host communicate to each other.
From the Lua perspective, these interfaces act as normal Lua function calls, but when such a function is called, the execution branches into the outer program, does something there and finally returns from that function back to Lua.
In the moment the OOBD interface supports two kinds of interfaces for Lua: Some for creating the menus, and some to talk to the serial line 1)
Important To allow it during debugging to replace the hardcoded extended Lua functions against some own debugging code, normally the hardcoded functions are assigned to some Lua variables first, which then are used in the later script:
local serFlush =serFlushCall serflush()
So by naming convention the hardcoded extended functions ends with xxx“Call”, while the name used later is without “Call”
Please keep this in mind while reading this document or writing own code.
But before we look in detail in the program flow itself, we need to understand how the lua compiler (luac) works and what that means for the program initialization:
To save memory and reduce the startup time, the Lua interpreter inside OOBD is not feed with the Lua programm source code text directly as this would need to be compiled first to be executed, which is time & memory consuming. Instead the compiling is done beforehand and just the compiled Lua program code is than used to be loaded into OOBD.
The compile process works like that, that the Lua compiler (a command line program called luac) translates the source code(s) (with the extension .lua) into a single file (with the extension .lbc) which contains the compiled program.
The command would be like this:
luac -o outputfile.lbc inputsource1.lua inputsource2.lua ...
In the OOBD source code repository there are some samples about how to do this.
If you want to set up your own compile process, there are two things you need to know, as luac works partly different as normal compilers
Because of item 2, the lua function, which sets up the whole lua structures, should be the last and only command in the lasr file given to luac.
To give the OOBD application the possibility to reinitialize the system (like after an communication loss), the name of such a initialization function is defined: Start(“”,“”). Each OOBD program has to contain this function; please make sure that also your program contain this and that your Start() function contains all necessary initialization code.
OOBD uses just tree functions to set up the menu structures. Let's have a look into some sample code first:
function Start(oldvalue,id) identifyOOBDInterface() setSendID("$7E8") -- set not UDS compatible sender (=answer) address for OOBD firmware openPage("OOBD-ME Main") addElement("Sensor Data >", "createCMD01Menu",">>>",0x1, "") addElement("Snapshot Data >", "createCMD02Menu",">>>",0x1, "") addElement("Dynamic Menu3 >", "createCMD03Menu",">>>",0x1, "") addElement("Trouble Codes", "showdtcs","-",0x1, "") addElement("VIN Number", "vin","-",0x2, "") addElement("Clear Trouble Codes", "clearDTC","-",0x0, "") addElement("System Info >>>", "SysInfo_Menu",">>>",0x1, "") addElement("Greetings", "greet","",0x1, "") pageDone() return oldvalue end ----------------- Do the initial settings -------------- Start("","") return
(BTW: Here you also see the start() function in action)
So what do we found here? First we have the
This function tells OOBD that a new menu should be made. The title is used, what a surprise, as title for that new menu.
Then the menu is filled with
This call adds a single element to the list. For now these function has the following parameters
The pageDone() function is easy, it just tells OOBD that the menu is defined now and ready to be shown.
The final result looks than similar like this (you'll noticed that the script has slightly changed after the screenshot has been made)
As shown above, we've now set up the menu items - but now we want something happen when the user selects a menu item, won't we?
So let's assume the user selects an item. What happens now?
That's already all, that does the job
OOBD uses (actual) serial communication to talk to the diagnostic device. To allow Lua to send and receive data from the serial port, a few extended Lua functions exists:
It just clears the input buffer (from anything which was maybe received in the meantime and which is not needed anymore)
Sends String to the output (which is normally the serial port)
waits milliseconds
reads from input until a LF (dez. 10 hex 0x0A) appears and returns that input as String
In case you want to wait for some input strings, of which several could appear, you fill the Optionstring with string1|string2|..|stringN. If then one of this string appears at the input, the index of the found string is returned (first string=1). If the string does not appear within msTimeout, the function returns 0.
the whole IO command section is in pure experimental state. There's no guarantee that these functions are even exist, are implemented as described or not subject to change.
OOBD uses its own scheme for file IO, mainly driven by the restriction of a remote user and the need of complex data buffer handling.
Similar to the LUA simple IO model, OOBD has always only just input and output file handle. As long as not redirected, these are stdin and stdout.
As OOBD knows more as only one type of data sources, the parameter message
is misused to control the behaviour.
.
Tries to open the file accourding to the following parameter combinations:
filename | message | Effect |
---|---|---|
/unix-path/.. | any message | Open a file Open dialog with preselected path (and file). At success returns the file path, otherwise null |
/unix-path/.. | 'direct' | Open the given file without Dialog, At success returns the file path, otherwise null actual either complete path are supported, relative paths or filenames only. Filenames and relative paths are relative to the actual script directory |
URL like HTTP:// .. | 'html' | Reads the URL as input.At success returns the URL, otherwise null |
URL like HTTP:// .. or file:// .. | 'json' | JSON-RFC call: The parameter file_extension can be a lua table or a JSON string, in case of a lua table, it's translated into a JSON string and send as POST to the given URL. In case of a file://- URL, the local file is read as a JSON file |
After opening the file with IOInput(), it can be read with ioRead()
Read one line of the input file ending with either \r\n or \n as string without the EOL. In case of input EOF or read errors the function returns nil. By this the whole file can be read line per line
Read the whole file as string
Here it's assumed that the input file consists of a JSON string containing data. This string is translated into a lua table structure and returned
This mainly for testing purposes. It returns the sha256 checksum of that file
this whole Buffer section is not implemented and actual just here as a reference for discussion
len, newfilename= loadbuffer(start , filelen , file_name , file_extension , message)
Reads the file “file_name” into the telegram buffer starting at position “start” by reading “filelen” number of bytes.
The data source can be defined as explained for the ioInput command.
The number of bytes read will be returned in “len”, a negative value means a load error
The following conditions apply:
len, newfilename = savebuffer(start , filelen , file_name , file_extension , message)
Writes the telegram buffer starting at position “start” by reading “filelen” number of bytes into the file “filename”.
The number of bytes written will be returned in “len”, a negative value means a load error
The following conditions apply:
setBuffer(bufferNr , newSize )
Changes the actual buffer used to buffer number “buffernr”. OOBD supports 10 buffers, counted from 0 to 9. The startup buffer is nr. 0.
If newsize is <> 0, the old buffer is deleted and new memory with size newmem is allocated
copyBuffer(bufferNr )
Copies the content of buffer “bufferNr” into the actual buffer.
BlitBuffer(frombuffer , startpos , topos , blocklen )
Copies a memory block from the buffer “frombuffer” starting at position “startpos” to the actual buffer to position “topos” with the length of “blocklen” bytes.
In case the buffer len needs to be bigger, the buffer len is increased accourdingly.
newSize= SetBufferLen( newSize)
All other buffer commands can increase the 'len' of a buffer, but none of them can make a buffer shorter, except SetBufferLen.
SetBufferLen sets the 'len' of the current Buffer to 'newSize'.
The success of the SetBufferLen - operation is returned as function result as follows:
input value of newSize | return value |
---|---|
< = 0 | available size of the buffer in bytes. This can be used to read the real allocated memory size of that buffer |
1.. available size | new available size (= requested size) |
> available size | available size of the buffer as negative value . This is a fault condition |
As seen, a negative value given back indicates a fault condition, all other returned values are positive.
Sends the actual buffer
As default the command
serDisplayWrite(String)
writes String to the build in output console. But with an optional secound parameter as command this behavior can be changed:
serDisplayWrite(parameter, command)
The different commands have the following effects:
parameter | command | Function |
---|---|---|
buffername | setbuffer | redirects the following DisplayWrite()'s output into buffer “bufferName”. If the buffer does not exists, it's automatically generated The default output window buffername, which is also active at start, is display To write some output there after using some other buffers before, set buffer back to display |
- | clear | clears actual buffer content |
- | clearall | clears all buffers. Senseful at start of scripts, if wanted, as the buffers contain their contents between scripts runs |
filename | save | saves actual buffer to filename without further asking |
filename | saveas | saves actual buffer to filename by let the user first confirm the filename |
filename | append | appends actual buffer to filename without further asking |
filename | appendas | appends actual buffer to filename by let the user first confirm the filename |
Searches in the db-file for all entries with index searchstring. The db-file needs to be in the same directory as the Lua- script itself. The db-file itself is made by oodbCreate.
dbLookup() returns a Lua table
myTable = dbLookup("dtc.oodb", "0815")
myTable.len tells the success status:
When something has been found, than myTable contains two sections, header and data.
The header section is needed in case you don't know in which column your wanted result is stored; you can identify the column by its column header name instead:
col= myTable.header["DTC-Text"] print (col) 2
myTable.header.size tells the number of colums in total without the first index column, which is always surpressed.
The data section then contains the found data itself, arranged as a two dimensional array, sorted by rows and columns.
result=myTable.data[row][column]
ATTENTION: Although the row and column indexes are expressed as numbers (1,2,3,4..), they are internally represented as string values (“1”,”2”,”3”,”4”…), so to read the result correctly, numeric row and column counters need to be converted to strings first to address the array correctly
column=3 row=2 result=myTable.data[tostring(row)][tostring(column)])
Here after all a piece of sample code
myTable= dbLookupCall("dtc.oodb","005") print ("header") for k,v in pairs (myTable.header) do print (k,"=",v) end nrOfColumns = myTable.header.size nrOfRows = myTable.len print ("Rows x Columns:" ,nrOfRows, nrOfColumns) for row = 1 , nrOfRows , 1 do for column = 1 , nrOfColumns, 1 do print (cy, cx, myTable.data[tostring(row)][tostring(column)]) end end
OOBD can work as VehicleDatasource for openXC, which means OOBD can send datasets to the openXC system (which needs to be installed on the same android device too, obviously).
To do so, a lua table is filled with the right indentifiers and correct formated values accourding to the OpenXC Message Format Specification, one value per call.
With that table openXCVehicleData() is called and the data are been transferred to the openXC backbone task for further handling.
openXCVehicleData({timestamp= 1332794087.675514, name= "longitude", value= -83.237427})
So everything which is understood by openXC can be generated out of a OOBD lua script.
Coming from old fashioned programming languages, you might be used to simple and static variable types like numbers and Strings. But Lua offers much more comfort here, especially with the support of nested (associative) Arrays. With that some things can be realized quite easy.
Let's assume a common scenario: You want to use a number of menu items, which basically do all the same (getting a value from a vehice) just with different parameters.
In former days you had to write one function for each single value and a long list of menu items, but in Lua, you can store all parameters, their meanings and even the function reference to get them in a single array like
local Menu2Data = { id0x0815 = { byte = 1 , size = 1 , mult = 0.392156862745 , offset = 0, unit = " %", title="Part Number", call = "readAscPid"} , id0x4711 = { byte = 1 , size = 1 , mult = 0.392156862745 , offset = 0, unit = " %", title="Software Level", call = "readAscPid"} , -- ... here are all the other parameter settings }
then, during your menu initialisation, you can let Lua run though the array and building the menu out of it
openPage("MyMenu") for key,value in pairs(Menu2Data) do res=_G[value.call]("-",key) --nice trick to call a function just by name :-) addElement(value.title, value.call,res,0x1, key) end pageDone()
And again: That's it
When than later on a function is called by its menu item, it gets the hash key of the Menu2Data array as its “id” parameter, so the function can then read out its personal parameter set out of the Menu2Data Array to calculate the correct values.
Please also note the use of the _G array in the sample above: Lua stores also all functions into its global _G array, where they can be refenced by their name and used as a normal function. So the res=_G[value.call](“-”,key)
call does the following:
By that it's possible without big efforts to fill a menu with real data already during initialization, so the actual data is visible straight from the beginning.