Skip to main content

Modbus TCP

modbustcp module implements Modbus TCP communication protocol.

modbustcp.new

-- @param address string Address of Modbus device in a `host:port` format
-- @return table
function modbustcp.new(address)
end

Creates a new Modbus connection object (referred as modbus_conn below). It does not yet initiate an actual TCP connection. Instead it will be initiated on a first Modbus request.

Example

local modbus_conn = modbustcp.new("192.168.1.110:502")

modbus_conn Object

read_coils

-- @param unit_id number Unit ID of Modbus device
-- @param start_register number Number of the first register to read
-- @param registers_count number Number of registers to read
-- @param timeout number Time to wait for the response in milliseconds
-- @return table|nil, number
function modbus_conn:read_coils(unit_id, start_register, registers_count, timeout)
end

Reads coil registers, Modbus function 0x01.

Returns Lua table with register contents or nil in case of error. Error code is returned as a second return value (use modbustcp.err_to_str to convert it to string representation).

Example

-- Read two coil registers (numbers 215 and 216), timeout 1 second
local registers, result = modbus_conn:read_coils(1, 215, 2, 1000)
if registers then
enapter.log("Coils: "..tostring(registers[1]).." "..tostring(registers[2]))
else
enapter.log("Error reading Modbus: "..tostring(result).." "..modbustcp.err_to_str(result), "error", true)
end

read_discrete_inputs

-- @param unit_id number Unit ID of Modbus device
-- @param start_register number Number of the first register to read
-- @param registers_count number Number of registers to read
-- @param timeout number Time to wait for the response in milliseconds
-- @return table|nil, number
function modbus_conn:read_discrete_inputs(unit_id, start_register, registers_count, timeout)
end

Reads discrete input registers, Modbus function 0x02.

Returns Lua table with register contents or nil in case of error. Error code is returned as a second return value (use modbustcp.err_to_str to convert it to string representation).

Example

-- Read two discrete input registers (numbers 220 and 221), timeout 1 second
local registers, result = modbus_conn:read_discrete_inputs(1, 220, 2, 1000)
if registers then
enapter.log("Discrete input: "..tostring(registers[1]).." "..tostring(registers[2]))
else
enapter.log("Error reading Modbus: "..tostring(result).." "..modbustcp.err_to_str(result), "error", true)
end

read_holdings

-- @param unit_id number Unit ID of Modbus device
-- @param start_register number Number of the first register to read
-- @param registers_count number Number of registers to read
-- @param timeout number Time to wait for the response in milliseconds
-- @return table|nil, number
function modbus_conn:read_holdings(unit_id, start_register, registers_count, timeout)
end

Reads holding registers, Modbus function 0x03.

Returns Lua table with register contents or nil in case of error. Error code is returned as a second return value (use modbustcp.err_to_str to convert it to string representation).

Example

-- Read two holding registers (numbers 230 and 231), timeout 1 second
local registers, result = modbus_conn:read_holdings(1, 230, 2, 1000)
if registers then
enapter.log("Holding: "..tostring(registers[1]).." "..tostring(registers[2]))
else
enapter.log("Error reading Modbus: "..tostring(result).." "..modbustcp.err_to_str(result), "error", true)
end

read_inputs

-- @param unit_id number Unit ID of Modbus device
-- @param start_register number Number of the first register to read
-- @param registers_count number Number of registers to read
-- @param timeout number Time to wait for the response in milliseconds
-- @return table|nil, number
function modbus_conn:read_inputs(unit_id, start_register, registers_count, timeout)
end

Reads input registers, Modbus function 0x04.

Returns Lua table with register contents or nil in case of error. Error code is returned as a second return value (use modbustcp.err_to_str to convert it to string representation).

Example

-- Read two input registers (numbers 240 and 241), timeout 1 second
local registers, result = modbus_conn:read_inputs(1, 240, 2, 1000)
if registers then
enapter.log("Input: "..tostring(registers[1]).." "..tostring(registers[2]))
else
enapter.log("Error reading Modbus: "..tostring(result).." "..modbustcp.err_to_str(result), "error", true)
end

write_coil

-- @param unit_id number Unit ID of Modbus device
-- @param register number Register number to write to
-- @param value number Value to write
-- @param timeout number Time to wait for the response in milliseconds
-- @return number
function modbus_conn:write_coil(unit_id, register, value, timeout)
end

Writes coil register, Modbus function 0x05.

Returns 0 if the value is written successfully, otherwise returns error code (use modbustcp.err_to_str to convert it to string representation).

Example

-- Write 0 to coil register number 503, timeout 1 second
local result = modbus_conn:write_coil(1, 503, 0, 1000)
if result ~= 0 then
enapter.log("Error writing Modbus: "..tostring(result).." "..modbustcp.err_to_str(result), "error", true)
end

write_holding

-- @param unit_id number Unit ID of Modbus device
-- @param register number Register number to write to
-- @param value number Value to write
-- @param timeout number Time to wait for the response in milliseconds
-- @return number
function modbus_conn:write_holding(unit_id, register, value, timeout)
end

Writes holding register, Modbus function 0x06.

Returns 0 if the value is written successfully, otherwise returns error code (use modbustcp.err_to_str to convert it to string representation).

Example

-- Write 12 to holding register number 610, timeout 1 second
local result = modbus_conn:write_holding(1, 610, 12, 1000)
if result ~= 0 then
enapter.log("Error writing Modbus: "..tostring(result).." "..modbustcp.err_to_str(result), "error", true)
end

write_multiple_coils

-- @param unit_id number Unit ID of Modbus device
-- @param start_register number Number of the first register to write to
-- @param values table Array of values to write to registers
-- @param timeout number Time to wait for the response in milliseconds
-- @return number
function modbus_conn:write_multiple_coils(unit_id, start_register, values, timeout)
end

Writes the sequence of coil registers, Modbus function 0x0F. The values table must contain integer values to be written to the registers.

Returns 0 if values are written successfully, otherwise returns error code (use modbustcp.err_to_str to convert it to string representation).

Example

-- Write zeroes to coil registers 700 and 701, timeout 1 second
local values = {0, 0}
local result = modbus_conn:write_multiple_coils(1, 700, values, 1000)
if result ~= 0 then
enapter.log("Error writing Modbus: "..tostring(result).." "..modbustcp.err_to_str(result), "error", true)
end

write_multiple_holdings

-- @param unit_id number Unit ID of Modbus device
-- @param start_register number Number of the first register to write to
-- @param values table Array of values to write to registers
-- @param timeout number Time to wait for the response in milliseconds
-- @return number
function modbus_conn:write_multiple_holdings(unit_id, start_register, values, timeout)
end

Writes the sequence of holding registers, Modbus function 0x10. The values table must contain integer values to be written to the registers.

Returns 0 if values are written successfully, otherwise returns error code (use modbustcp.err_to_str to convert it to string representation).

Example

-- Write values to holding registers 800 and 801, timeout 1 second
local values = {14, 15}
local result = modbus_conn:write_multiple_coils(1, 800, values, 1000)
if result ~= 0 then
enapter.log("Error writing Modbus: "..tostring(result).." "..modbustcp.err_to_str(result), "error", true)
end

modbustcp.err_to_str

-- @param error number
-- @return string
function modbustcp.err_to_str(error)
end

Returns string representation of modbustcp function return code.

Byte Order

A Modbus register is composed of two bytes. Typically, the register value is transferred in Big-Endian byte order. However, the most modern CPUs, including the one in Enapter Gateway, use Little-Endian byte order. Given this, the UCM automatically performs endianness conversion, under the assumption that the Modbus server transfers values in Big-Endian order. Consequently, there is no need for you to manually perform this conversion.

Let's take an example. Imagine you have the register number 112 which holds unsigned integer value 4660 (it may be 46.6 ℃), which is equal to 0x1234 hexadecimal (two bytes – 0x12 and 0x34). The value is transferred as 0x3412 (13330 decimal), but when you read it:

-- Create Modbus TCP connection object
local modbus_conn = modbustcp.new("192.168.1.110:502")
-- Read the value of the register 112
local registers, result = modbus_conn:read_holdings(1, 112, 1, 1000)
if registers then
value = register[1]
enapter.log("Register value: "..tostring(value))
end

You'll get this logged as expected without any conversions:

Register value: 4660

All Rights Reserved © 2024 Enapter AG.