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.
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.
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.
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.
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.
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.
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:
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
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.
Creates and connects a Modbus client instance:
mb, err=require"mobdbus.client".connect(addr,op)
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.
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.
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.
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
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")
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.
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.
Returns true if the client is connected to a Modbus server, otherwise false is returned.
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.