Modbus Client

See the Modbus Protocol page for an introduction to this module.

The Modbus TCP client is an Ethernet implementation of the widely supported, widely used Modbus protocol. The Modbus client is designed in Lua and utilizes the Barracuda App Server socket API. The Modbus client enables business logic, implemented in the Lua scripting language, to communicate with Modbus TCP servers. The Modbus client can also be used for bridging Modbus with protocols such as HTTP, WebSockets, SMQ, MQTT, etc.

Modbus RTU

The Modbus TCP Client is designed for versatility and can function as a Modbus RTU client when paired with the appropriate driver. Specifically, the ESP32 port of the Barracuda App Server comes equipped with this necessary driver. Please refer to the ESP32 Modbus RTU Client for details.

Conversion between Modbus and Lua types

Modbus supports bit values and 16 bit words. Bit values are converted to/from Lua boolean values. Word values are converted to/from Lua numbers. Lua can store both integer and floating point values in the number type.

The Modbus client supports additional types that enable the conversion of types to and from the Modbus 16 bit word value. The following additional types are supported: dword (4 bytes), float (4 bytes), double (8 bytes) and string (raw data). The length parameter is automatically adjusted to/from the Modbus word value. For example, requesting the Modbus client to read 4 'double' values from the server makes the Modbus client send a request for reading 4*8 word values. Writing a 5 byte string value (raw data) sends 3 word values, where the last byte in the last word is zero padded. Note that a Lua string may contain any value and is often used for storing and working with binary data.

Error handling

All functions return a value on success and nil,error on failure. The error code can be any of the socket error codes or one of the Modbus exception codes (number 0x01 to 0x0B).

When in cosocket mode, all functions return the Modbus transaction number if the Modbus socket write operation succeeds. Any Modbus response exception code will be sent to the callback function -- i.e., on error, the callback will receive the arguments: nil,error.

Any type of error will force a disconnect on the TCP connection; thus if any error occurs, a new connection must be established.

Modes of operation

The Modbus client can operate in the two socket modes provided by the Barracuda App Server socket API: blocking mode, and cosocket mode.

The default socket blocking mode is designed exclusively for LSP pages, where a connection may be opened, a request sent, and the LSP page waits in blocking mode for the Modbus response. Modbus code, not operating in an LSP page context, must use cosocket mode. When in cosocket mode, it is essential to understand the limitations that apply to method socket:write(), which is used by the Modbus stack when sending data.

Blocking mode:
mb,err = require"modbus.client".connect("localhost")
if mb then
   local data,err = mb:rcoil(0, 5)
   -- Print return table as JSON
   if data then trace(ba.json.encode(data)) end
   mb:close()
end

In the above code, the Modbus instance uses a blocking mode, meaning the mb:rcoil() method pauses execution until the Modbus server responds. This mode is optimal for LSP pages, which establish a Modbus TCP connection and then sequentially execute commands. It ensures synchronous operation, with each command processed in order.

Cosocket mode:

For most applications, the cosocket mode (non-blocking mode) is the preferred choice. This approach is exemplified in the following code, which demonstrates the non-blocking mode's usage. It is important to note that when using this mode, you need to specify a callback function.

local function callback(data, err)
   if data then
      trace(ba.json.encode(data))
   end
end

mb,err = require"modbus.client".connect("localhost",{async=true})
if mb then
   mb:rcoil(0, 5, callback)
end

The cosocket mode enables pipelining of Modbus requests and enables multiple requests to be issued without having to wait for the Modbus response.

The Modbus client creates its own cosocket when operating in cosocket mode (config option async). When operating in cosocket mode, the cosocket waits for TCP messages sent from the Modbus server, parses the response independently from any sent messages, and dispatches the parsed response to the asynchronous callback function provided when the request was initiated.

The Modbus methods do not return the result when operating in cosocket mode. Instead, the methods return the Modbus transaction number. The same transaction number is passed into the callback function when the server sends the response message.

The callback function receives four arguments, where you are required to use at least two. The following two functions illustrate the data passed into the callback and how the data may be used:

function callback(data, err, transaction, mb) -- if all arguments are used function callback(data, err) -- Minimum set of arguments

Callback function's arguments:

The following example shows how to open a Modbus server connection, send one asynchronous message, and how to close the connection. The purpose with the two assert calls is to further explain the callback function's arguments.

local mb -- The Modbus instance
local transaction -- last sent transaction number

local function mycallback(data, err, _transaction, _mb)
   assert(transaction == _transaction) -- integrity check
   assert(mb == _mb) -- Integrity check
   if data then
      -- data is a table with boolean values
   else
      trace("Failed, error code:", err)
   end
   mb:close() -- Close connection
end

mb,err = require"modbus.client".connect("localhost", {async=true})
if mb then
   transaction = mb:rcoil(0, 5, mycallback) -- Initiate request
end

API for Creating a Modbus Client

All methods take the following optional arguments: [,uid] [,onresp].

The brackets indicate they are optional. The onresp callback function is required when operating in Cosocket mode. The 'uid', defaulting to one, is the Unit Identifier. This identifier is required for Modbus/TCP devices that function as composites of multiple Modbus devices, such as in Modbus/TCP to Modbus RTU gateways. It specifies the Slave Address of the device behind the gateway. However, devices natively capable of Modbus/TCP typically overlook the Unit Identifier.

function connect(addr, opt)

Creates and connects a Modbus client instance:
mb, err=require"mobdbus.client".connect(addr,op)

Modbus Object Methods

method mb:rcoil(addr, len [,uid] [,onresp])

Read coil(s) (function code 1)

Returns table | nil,err - method 'rcoil' returns a Lua table with boolean (true/false) values on success and nil,err on error.

method mb:wcoil(addr, val [,uid] [,onresp])

Write single coil (function code 5) or write multiple coils (function code 15)

Returns boolean | nil,err - method 'wcoil' returns true on success and nil,err on error.

method mb:discrete(addr, len [,uid] [,onresp])

Read discrete input (function code 2)

Returns table | nil,err - method 'discrete' returns a Lua table with boolean (true/false) values on success and nil,err on error.

method mb:rholding(addr, len [,vtype] [,uid] [,onresp])

Read holding register(s) (function code 3)

Returns table | nil,err - method 'rholding' returns a Lua table with values on success and nil,err on error.

-- Example:
local data = mb:rholding(4000, 10, "dword")
if data then trace(ba.json.encode(data)) end
method mb:wholding(addr, val [,vtype] [,uid] [,onresp])

Write single register (function code 6) or write multiple registers (function code 16)

Returns boolean | nil,err - method 'wholding' returns true on success and nil,err on error.

-- Example:
mb:wholding(4000, {1,2,3,4,5,6,7,8,9,10}, "dword")
method mb:register(addr, len [,vtype] [,uid] [,onresp])

Read input register(s) aka analog input register(s) (function code 4)

Returns table | nil,err - method 'register ' returns a Lua table with values on success and nil,err on error.

method mb:readwrite(raddr, rlen, waddr, wval [,vtype] [,uid] [,onresp])

Read/write multiple registers (function code 23). This function code performs a combination of one read operation and one write operation in a single Modbus transaction. Note that this operation is not widely supported by Modbus servers.

Returns table | nil,err - method 'readwrite' returns a Lua table with values on success and nil,err on error.

method mb:connected()

Returns true if the client is connected to a Modbus server, otherwise false is returned.

method mb:close()

Closes the socket connection. This function also terminates the cosocket if the Modbus client is operating in asynchronous mode. The optional onclose callback will not be called when this method is called.