Skip to main content
Skip table of contents

25.0 Lua Scripting [T-Series Datasheet]

Overview

T-Series devices can execute Lua code to allow custom, independent operation. A Lua script can be used to collect data without a host computer or be used to perform complex tasks producing simple results that a host can read.

For maximum speed and benchmarking, see Lua Script Performance.

If you have some scripting experience (with a language like Python, for example), the information in this Lua Scripting section combined with the Lua examples are typically sufficient to achieve desired programmatic behavior

Subsections

Getting Started

1. Connect your device to your computer, launch Kipling, and navigate to the Lua Script Debugger tab.

2. Open the "Get Device Temperature" example and click the Run button. The console should show the current device temperature. Now click the Stop button.

3. Try out some other examples.

Running a script when the device powers up

A T-Series device can be configured to run a script when it powers on or resets.  Typically you should test scripts for a while with the Run/Stop buttons while viewing the debug output in the console. Then once everything is working correctly, enable the script at startup and close Kipling.

To enable the script at startup:

1. Click Save Script to Flash.

2. Click Enable Script at Startup.  Now when the device is powered on or reset, it will run your script.

3. After power cycling the device, if it becomes un-usable and the COMM/Status lights are constantly blinking the Lua Script is likely causing the device to enter an invalid state.  The device can be fixed by connecting a jumper wire between the SPC terminal and either FIO0 or FIO4, see the SPC section of the datasheet for more details.

A short video tutorial describing how to do this is available on the Screen Casting and Lua Script Tutorials news post.

Learning more about Lua

Learning Lua is very easy. There are good tutorials on Lua.org as well as on several other independent sites. If you are familiar with the basics of programming, such as loops and functions, then you should be able to get going just by looking at the examples.

Lua for T-Series Devices

Try to keep names short. String length directly affects execution speed and code size.

Use local variables instead of global variables (it's faster and less error-prone). For example, use:

local a = 10

instead of:

a = 10

You can also assign a function to a local for further optimization:

local locW = MB.W
locW(2003, 0, 1) --Write to address 2003

Lua supports multi-return. Here, both table and error are returned values:

table, error = MB.RA(Address, dataType, nValues)

Freeing Lua memory

Since Lua occupies system RAM, it's good to clean up a Lua script when it's no longer needed. When a Lua script ends, by default it does not free memory used by the Lua. The following methods clean up Lua memory:

  • Press the stop button in the Lua script tab in Kipling

  • Write 0 to LUA_RUN

  • Write 0 to LUA_RUN as the last command of your Lua script

For more memory cleaning options, see the system RAM section.

Limitations of Lua on T-Series Devices

Lua on the T-series devices has several limitations:

No Direct Network Communication: Lua scripts cannot directly send or receive data. Lua scripts cannot act as a network client. As an alternative, Lua scripts can output to USER_RAM, which an external service can then poll. More generally, T-series devices act only as servers and cannot act as clients.

Speed: Maximum data rates can only be achieved with a host computer. Lua can not handle as much data, but is not limited by the communication overhead that a host computer is. The lack of overhead means that Lua can respond more quickly. See Lua script performance for more information.

Data Types: Depending on which T-series device you are using, Lua's numeric data type is either IEEE 754 single precision (float) or double precision (double). Refer to the table below to determine which datatype your device uses.

Device

Data Type

T4

single precision (float)

T7

single precision (float)

T8

double precision (double)

When writing a script for a device that uses single precision, there are precision limitations which must be understood and worked with. Here is a good article on floating point numbers and their pitfalls: Floating Point Numbers. The single precision data type means that working with 32-bit values requires extra consideration. See the Working with 32-bit Values section below.

Script size: Lua scripts run within the device's RAM. The Lua virtual machine requires about 25 kBytes. Script code adds memory requirements from there. Both the code itself and RAM allocated by the code require space in RAM. The amount of RAM available depends on which T-Series device is being used as described in our RAM documentation.

When memory starts to get full (when running a script through Kipling, a message "lua: not enough memory" is displayed), there are two places that are likely to throw errors:

  1. When a Lua script is loaded and started, but has not yet started running. When a script is started, the source code is transferred to the device and that code is compiled into byte code. Memory for the compilation process can be freed up by reducing comments and the lengths of variables and functions.

  2. Memory can be freed up using the collectgarbage function. Collecting prevents garbage from building up as much. If more memory is still needed, the script needs to be simplified or shrunk. See the information in the Weak Table Lua Documentation.

Notice: From a practical standpoint, Lua Scripts will start becoming too long and throwing out-of-ram errors after around 150 lines (depending on how long each line of code is).  Once this limitation is encountered, there are a few tricks that can be used to implement additional features.  They all involve making your code less readable but will assist in implementing additional features.

  1. Remove any comments from your code as these consume precious RAM.

  2. Edit the lua script file using an editor outside of Kipling and upload a minified version of the script instead of the full script file.  The suggested tool to use for minifying Lua Scripts is here: https://mothereff.in/lua-minifier. Another Lua minifier is here: https://goonlinetools.com/lua-minifier/

Most Systems Still Involve A Host Computer

Lua scripting allows basic standalone operation, and some applications are totally isolated and totally standalone (also see SD Card section of this datasheet), but most systems use on-board Lua scripting in conjunction with a host computer to enable elegant solutions to complex challenges.

If you are considering Lua scripting to avoid the cost/size/power of a full-blown desktop host computer, consider that a host computer can be a simple SBC (single board computer) such as Raspberry Pi, BeagleBone, or various options from Technologic Systems.  Combining a LabJack with a Linux SBC provides a low-cost solution that is very powerful and flexible.

Lua libraries

Most of the Lua 5.1 libraries are available, with the exception of functions that rely on a host operating systems, such as Time and Networking.

There are some LabJack-specific libraries:

  • I2C Library: Provides functions which simplify and reduce the memory requirements of scripts that use I2C.

  • Bit Library: Provides bitwise functions such as AND, OR, NOR, and XOR.

  • LabJack Library: Provides control of script timing and access hardware features of the LabJack device.

Passing data into/out of Lua

User RAM consists of a list of volatile Modbus addresses where data can be sent to, and read from, a Lua script. Lua writes to the Modbus registers, and then a host device can read that information. 

There are a total of 200 registers of pre-allocated RAM, which is split into several groups so that users may access it conveniently with different data types.

Use the following USER_RAM registers to store information:

USER_RAM example script - lua

LUA
LJ.IntervalConfig(0, 1000)
previous = MB.readName("AIN0")
MB.writeName("USER_RAM0_U16", 1)

while true do
  if LJ.CheckInterval(0) then
    enable = MB.readName("USER_RAM0_U16") --Host may disable portion of the script
    if enable >= 1 then
      current = MB.readName("AIN0")
      twoValueAvg = (previous + current) / 2
      print("Rolling average of the last two measurements: ", twoValueAvg)
      MB.writeName("USER_RAM0_F32", twoValueAvg) --Provide a new value to host
      previous = current
    end
  end
end

For MB.writeName and MB.readName, the T7 requires firmware 1.0287 or later and the T4 requires firmware 1.0027 or later.

There is also a more advanced system for passing data to/from a Lua script referred to as FIFO buffers. These buffers are useful if you want to send an array of information in sequence to/from a Lua script. Usually 2 buffers are used for each endpoint, one buffer dedicated for each communication direction (read and write). For example, a host may write new data for the Lua script into FIFO0, then once the script reads the data out of that buffer, it responds by writing data into FIFO1, and then the host may read the data out of FIFO1.

Note that the following _DATA registers are buffered registers.

USER_RAM_FIFO example script - lua

LUA
aF32_Out= {}  --array of 5 values(floats)
aF32_Out[1] = 10.0
aF32_Out[2] = 20.1
aF32_Out[3] = 30.2
aF32_Out[4] = 40.3
aF32_Out[5] = 50.4

aF32_In = {}
numValuesFIO0 = 5
ValueSizeInBytes = 4
numBytesAllocFIFO0 = numValuesFIO0*ValueSizeInBytes
MB.W(47900, 1, numBytesAllocFIFO0) --allocate USER_RAM_FIFO0_NUM_BYTES_IN_FIFO to 20 bytes

LJ.IntervalConfig(0, 2000)
while true do
  if LJ.CheckInterval(0) then
    --write out to the host with FIFO0
    for i=1, numValuesFIO0 do
      ValOutOfLua = aF32_Out[i]
      numBytesFIFO0 = MB.R(47910, 1)
      if (numBytesFIFO0 < numBytesAllocFIFO0) then
        MB.W(47030, 3, ValOutOfLua)  --provide a new array to host
        print ("Next Value FIFO0: ", ValOutOfLua)
      else
        print ("FIFO0 buffer is full.")
      end
    end
    --read in new data from the host with FIFO1
    --Note that an external computer must have previously written to FIFO1
    numBytesFIFO1 = MB.R(47912, 1) --USER_RAM_FIFO1_NUM_BYTES_IN_FIFO
    if (numBytesFIFO1 == 0) then
      print ("FIFO1 buffer is empty.")
    end
    for i=1, ((numBytesFIFO1+1)/ValueSizeInBytes) do
      ValIntoLua = MB.R(47032, 3)
      aF32_In[i] = ValIntoLua
      print ("Next Value FIFO1: ", ValIntoLua)
    end
  end
end

Working with 32-bit Values

On some T-Series devices, Lua's only numeric data type is IEEE 754 single precision (float). This means that working with 32-bit integer registers is difficult (see examples below). If any integer exceeds 24-bits (including sign), the lower bits will be lost. The workaround is to access the Modbus register using two numbers, each 16-bits. Lua can specify the data type for the register being written, so if you are expecting a large number that will not fit in a float (>24 bits), such as a MAC address, then read or write the value as a series of 16-bit integers.

If you expect the value to be counting up or down, use MB.RA or MB.RW to access the U32 as a contiguous set of 4 bytes.

If the value isn't going to increment (e.g. the MAC address) it is permissible to read it in two separate packets using MB.R.

Read a 32-bit register - lua

LUA
--If value is expected to be changing and is >24 bits: Use MB.RA
aU32[1] = 0x00
aU32[2] = 0x00
aU32, error = MB.RA(3000, 0, 2)   --DIO0_EF_READ_A. Type is 0 instead of 1
DIO0_EF_READ_A_MSW = aU32[1]
DIO0_EF_READ_A_LSW = aU32[2]

--If value is constant and is >16,777,216 (24 bits): Use MB.R twice
--Read ETHERNET_MAC (address 60020) 
MAC_MSW = MB.R(60020, 0)  --Read upper 16 bits. Type is 0 instead of 1
MAC_LSW = MB.R(60021, 0)  --Read lower 16 bits.

--If value is <16,777,216 (24 bits): Use MB.R
--Read AIN0_EF_INDEX (address 9000)
AIN0_index = MB.R(9000, 1)  --Type can be 1, since the value will be smaller than 24 bits.

Write a 32-bit register - lua

LUA
--If value might be changed or incremented by the T7 and is >24 bits: Use MB.WA

aU32[2] = 0xFB5F
error = MB.WA(44300, 0, 2, aU32) --Write DIO0_EF_VALUE_A. Type is 0 instead of 1

--If value is constant and is >24 bits: Use MB.W twice
MB.W(44300, 0, 0xFF2A) --Write upper 16 bits. Type is 0 instead of 1
MB.W(44301, 0, 0xFB5F) --Write lower 16 bits.

--If value is <16,777,216 (24 bits): Use MB.W
--Write DIO0_EF_INDEX (address 44100)
MB.W(44100, 1, 7)  --Type can be 1, since the value(7) is smaller than 24 bits.

Is a register 32-bit?

To determine if a register is 32-bit, you can use the Modbus Map tool. If you need to determine programmatically, you can use MB.nameToAddress of the LabJack Library.

Moving a Lua Script from Flash to RAM

Before a Lua script is ran,  it must be loaded to RAM. To move a script saved to flash into RAM, write any value to the UINT32 register LUA_LOAD_SAVED.

Anytime a Lua script is stopped in a host application (done by writing a 0 to LUA_RUN) the current Lua script is wiped from RAM. Users who wish to restart their Lua script in a host application must first write a value to LUA_LOAD_SAVED to load a script from flash or manually write a script to RAM using the LUA_SOURCE registers, then write a 1 to LUA_RUN to start the script.

Load Lua Script Manually To Device

While Kipling handles Lua scripting details easily and automatically, the example below shows how to load a Lua script to a T7 manually using the following registers:

The process is as follows:

  1. Define or load a Lua Script as an ASCII encoded string and make sure that a connection to your LabJack device has been opened.

  2. Make sure that there is a null-character at the end of the Lua script string. T-series devices interpret the Lua script as a C string, so a null terminating character is necessary to define the script bounds.

  3. Make sure a Lua Script is not currently running (ensure LUA_RUN=0).  If a script is running, write 0 to LUA_RUN and wait for it to be stopped.

  4. Write to the LUA_SOURCE_SIZE and LUA_SOURCE_WRITE registers to instruct the LabJack to allocate space for a script and to transfer it to the device.

  5. (Optional) Enable ging by writing 1 to LUA_DEBUG_ENABLE.

  6. Instruct the T-Series device to run the loaded Lua Script by writing 1 to LUA_RUN.

  7. (Optional) write 1 to LUA_SAVE_TO_FLASH to save the script to flash memory. This should only be done after testing the script operation.

The C example below opens a T7, shuts down any Lua script that may be running, loads the script, and runs the script.

C
const char * luaScript =
    "LJ.IntervalConfig(0, 500)\n"
    "while true do\n"
    "  if LJ.CheckInterval(0) then\n"
    "    print(LJ.Tick())\n"
    "  end\n"
    "end\n"
    "\0";
    
// strlen does not include the null-terminating character, so we add 1
// byte to include it.
const unsigned scriptLength = strlen(luaScript) + 1;

int handle = OpenOrDie(LJM_dtT7, LJM_ctANY, "LJM_idANY");

// Disable a running script by writing 0 to LUA_RUN twice
WriteNameOrDie(handle, "LUA_RUN", 0);

// Wait for the Lua VM to shut down (and some T7 firmware versions need
// a longer time to shut down than others):
MillisecondSleep(600);
WriteNameOrDie(handle, "LUA_RUN", 0);

// Write the size and the Lua Script to the device
WriteNameOrDie(handle, "LUA_SOURCE_SIZE", scriptLength);
WriteNameByteArrayOrDie(handle, "LUA_SOURCE_WRITE", scriptLength, luaScript);

// Start the script with debug output enabled
WriteNameOrDie(handle, "LUA_DEBUG_ENABLE", 1);
WriteNameOrDie(handle, "LUA_DEBUG_ENABLE_DEFAULT", 1);
WriteNameOrDie(handle, "LUA_RUN", 1);

The above example is valid C code, where the following functions are error-handling functions that cause the program to exit if an error occurs:

The Lua script in the example above is in the form of a C-string, e.g. a string with a null-terminator as the last byte.

To download a version of the above example, see utilities/lua_script_basic.c, which includes a function that reads debug data from the T7.

Debug Data/Print Statements

All print statements save the data written to a buffer. That buffer then needs to be read by a host application. Kipling will read and display the data automatically. By default, the buffer is 1024 Bytes. If the buffer is too full to accept the string from a print command then the entire string will be discarded. This can cause seemingly random gaps in the debug data.

Reading Debug Data

Data in the debug buffer can be read by checking the size available from "LUA_DEBUG_NUM_BYTES" and then reading an appropriate number of bytes from "LUA_DEBUG_DATA". The following pseudocode roughly outlines how Lua debug data can be monitored:

C
double debugBytes;
char debugData[1024];
int err;
int programRunning = 1;
LJM_eWriteName(handle, "LUA_DEBUG_ENABLE", 1); // Enable debugging
while (programRunning) {
    LJM_eReadName(handle, "LUA_DEBUG_NUM_BYTES", &debugBytes); // Check for debug data
    if ((int)debugBytes > 0) {
        LJM_eReadNameByteArray(handle, "LUA_DEBUG_DATA", (int)debugBytes, &debugData, &err);
        printf("%.*s\n", (int)debugBytes, debugData);
    }
}

Moving a Lua Script from Flash to RAM

Before a Lua script is ran,  it must be loaded to RAM. To move a script saved to flash into RAM, write any value to the UINT32 register LUA_LOAD_SAVED.

Anytime a Lua script is stopped in a host application (done by writing a 0 to LUA_RUN) the current Lua script is wiped from RAM. Users who wish to restart their Lua script in a host application must first write a value to LUA_LOAD_SAVED to load a script from flash or manually write a script to RAM using the LUA_SOURCE registers, then write a 1 to LUA_RUN to start the script.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.