User Tools

Site Tools


Make your own OOBD Scripts

The “reloadable intelligence” of OOBD is realized as Lua script. What this means and how this works, is described here.

The Lua - OOBD Interface

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

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:

Compiling and Startup code

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

  1. luac does not evaluate any require() or doFile() statements, so these files would be missed in the output file. All needed files must be listed instead as input files to be contained in the output
  2. also the sequence of the input files is important: Luac glues the files together in the same sequence as their are given; and in the same sequence the file is executed later.
    Here you have to take care that variables and functions are been declared before they are used first time, otherways you'll get a “variable/function is null” error

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.

The Start("","") Command

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.

The Menu commands

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)
	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, "")
	return oldvalue
----------------- Do the initial settings --------------

(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

addElement(Description, function, initialValue, Flagset, id)

This call adds a single element to the list. For now these function has the following parameters

  • Description(String): This is what appears as description on this menu item
  • function: Each menu is assigned to a Lua function, where one function can be shared between several menu items. The name of this function is given here (please take care about correct upper/lower characters)
  • initialValue(String): This is what is shown as initial value when the menu is displayed the first time.
  • Flagset(Integer): This is an integer value, where each bit represents a flag. The meanings of these flags are explained in the Client design guide (look for System Flags)
  • id(String): The id is used to add an individual marker to a menu item. This is widely used when one single Lua function should support many menu items. This is descripted in detail in the Menu Function Calls below.

"advanced" addElement(Description, function, initialValue, Flagset, id, type)


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)

The Menu Function Call

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?

  1. OOBD looks, which Lua function is assigned to that menu item
  2. than that Lua function is called, with two parameters:
    • the actual displayed value of that item
    • and the id with we gave to that menu item during initialization
  3. than the called Lua function does something, e.g. a calculation or it starts to generate another submenu
  4. when the function is finished, it returns a string value
  5. that string value is then displayed as new value of the menu item

That's already all, that does the job :-)

The Communication Commands

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

serWait(OptionString, msTimeout)

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 IO Commands

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


ioInput(file_name ,file_extension,message)

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

ioRead ("%%*%%all")

Read the whole file as string

ioRead ("%%*%%json")

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

ioRead ("%%*%%sha256")

This mainly for testing purposes. It returns the sha256 checksum of that file

The Buffer Commands

FIXME 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:

  • The selected filename will be returned in “filename”.
  • If filename is 0, the whole file will be read. If the length exceeds the telegram length, an error will be raised.


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:

  • The selected file name will be returned in “filename”.
  • If len is 0, the whole telegram buffer will be written.


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

WriteString Command Syntax

As default the command


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

Miscellaneous Commands

dbLookup(db-File , searchstring)

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:

  • if myTable.len < 0 then an error has occured
  • if myTable.len = 0 then searchstring has not been found
  • if myTable.len > 0 then myTable.len tells the number of entries found

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)

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


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)
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,[tostring(row)][tostring(column)])

openXCVehicleData(lua table)

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.

Some Programming Tricks

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

	for key,value in pairs(Menu2Data) do
		res=_G[]("-",key) --nice trick to call a function just by name :-)
		addElement(value.title,,res,0x1, key)

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[](“-”,key) call does the following:

  1. it finds a function by its name
  2. then it calls these function as if the user would do it
  3. it saves the actual return value (which represents e.g. the actual measuring data) in res
  4. res is then be used as initial displayed value when setting up the menu

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.

this is certainly subject to change in the future, when OOBD becomes more generic and the data links become more abstract
This website uses cookies. By using the website, you agree with storing cookies on your computer. Also you acknowledge that you have read and understand our Privacy Policy. If you do not agree leave the website.More information about cookies
doc/lua_make-your-own-scripts.txt · Last modified: 2019/04/08 18:43 by admin