2022-04-21 14:26:02 +00:00
|
|
|
-- #REQUIRES comms.lua
|
|
|
|
|
2022-01-14 17:42:11 +00:00
|
|
|
-- modbus function codes
|
|
|
|
local MODBUS_FCODE = {
|
|
|
|
READ_COILS = 0x01,
|
|
|
|
READ_DISCRETE_INPUTS = 0x02,
|
|
|
|
READ_MUL_HOLD_REGS = 0x03,
|
|
|
|
READ_INPUT_REGS = 0x04,
|
|
|
|
WRITE_SINGLE_COIL = 0x05,
|
|
|
|
WRITE_SINGLE_HOLD_REG = 0x06,
|
|
|
|
WRITE_MUL_COILS = 0x0F,
|
|
|
|
WRITE_MUL_HOLD_REGS = 0x10,
|
|
|
|
ERROR_FLAG = 0x80
|
|
|
|
}
|
|
|
|
|
2022-04-18 01:12:25 +00:00
|
|
|
-- modbus exception codes
|
|
|
|
local MODBUS_EXCODE = {
|
|
|
|
ILLEGAL_FUNCTION = 0x01,
|
|
|
|
ILLEGAL_DATA_ADDR = 0x02,
|
|
|
|
ILLEGAL_DATA_VALUE = 0x03,
|
|
|
|
SERVER_DEVICE_FAIL = 0x04,
|
|
|
|
ACKNOWLEDGE = 0x05,
|
|
|
|
SERVER_DEVICE_BUSY = 0x06,
|
|
|
|
NEG_ACKNOWLEDGE = 0x07,
|
|
|
|
MEMORY_PARITY_ERROR = 0x08,
|
|
|
|
GATEWAY_PATH_UNAVAILABLE = 0x0A,
|
|
|
|
GATEWAY_TARGET_TIMEOUT = 0x0B
|
|
|
|
}
|
|
|
|
|
2022-01-14 17:42:11 +00:00
|
|
|
-- new modbus comms handler object
|
2022-04-18 04:10:47 +00:00
|
|
|
function new(rtu_dev)
|
2022-01-13 15:12:44 +00:00
|
|
|
local self = {
|
|
|
|
rtu = rtu_dev
|
|
|
|
}
|
|
|
|
|
2022-01-14 17:42:11 +00:00
|
|
|
local _1_read_coils = function (c_addr_start, count)
|
|
|
|
local readings = {}
|
2022-04-18 01:12:25 +00:00
|
|
|
local access_fault = false
|
2022-01-14 17:42:11 +00:00
|
|
|
local _, coils, _, _ = self.rtu.io_count()
|
|
|
|
local return_ok = (c_addr_start + count) <= coils
|
|
|
|
|
|
|
|
if return_ok then
|
|
|
|
for i = 0, (count - 1) do
|
2022-04-18 01:12:25 +00:00
|
|
|
readings[i], access_fault = self.rtu.read_coil(c_addr_start + i)
|
|
|
|
|
|
|
|
if access_fault then
|
|
|
|
return_ok = false
|
|
|
|
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
|
|
|
|
break
|
|
|
|
end
|
2022-01-14 17:42:11 +00:00
|
|
|
end
|
2022-04-18 01:12:25 +00:00
|
|
|
else
|
|
|
|
readings = MODBUS_EXCODE.ILLEGAL_DATA_ADDR
|
2022-01-14 17:42:11 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
return return_ok, readings
|
|
|
|
end
|
|
|
|
|
|
|
|
local _2_read_discrete_inputs = function (di_addr_start, count)
|
|
|
|
local readings = {}
|
2022-04-18 01:12:25 +00:00
|
|
|
local access_fault = false
|
2022-01-14 17:42:11 +00:00
|
|
|
local discrete_inputs, _, _, _ = self.rtu.io_count()
|
|
|
|
local return_ok = (di_addr_start + count) <= discrete_inputs
|
|
|
|
|
|
|
|
if return_ok then
|
|
|
|
for i = 0, (count - 1) do
|
2022-04-18 01:12:25 +00:00
|
|
|
readings[i], access_fault = self.rtu.read_di(di_addr_start + i)
|
|
|
|
|
|
|
|
if access_fault then
|
|
|
|
return_ok = false
|
|
|
|
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
|
|
|
|
break
|
|
|
|
end
|
2022-01-14 17:42:11 +00:00
|
|
|
end
|
2022-04-18 01:12:25 +00:00
|
|
|
else
|
|
|
|
readings = MODBUS_EXCODE.ILLEGAL_DATA_ADDR
|
2022-01-14 17:42:11 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
return return_ok, readings
|
|
|
|
end
|
|
|
|
|
|
|
|
local _3_read_multiple_holding_registers = function (hr_addr_start, count)
|
|
|
|
local readings = {}
|
2022-04-18 01:12:25 +00:00
|
|
|
local access_fault = false
|
2022-01-14 17:42:11 +00:00
|
|
|
local _, _, _, hold_regs = self.rtu.io_count()
|
|
|
|
local return_ok = (hr_addr_start + count) <= hold_regs
|
|
|
|
|
|
|
|
if return_ok then
|
|
|
|
for i = 0, (count - 1) do
|
2022-04-18 01:12:25 +00:00
|
|
|
readings[i], access_fault = self.rtu.read_holding_reg(hr_addr_start + i)
|
|
|
|
|
|
|
|
if access_fault then
|
|
|
|
return_ok = false
|
|
|
|
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
|
|
|
|
break
|
|
|
|
end
|
2022-01-14 17:42:11 +00:00
|
|
|
end
|
2022-04-18 01:12:25 +00:00
|
|
|
else
|
|
|
|
readings = MODBUS_EXCODE.ILLEGAL_DATA_ADDR
|
2022-01-14 17:42:11 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
return return_ok, readings
|
|
|
|
end
|
|
|
|
|
|
|
|
local _4_read_input_registers = function (ir_addr_start, count)
|
|
|
|
local readings = {}
|
2022-04-18 01:12:25 +00:00
|
|
|
local access_fault = false
|
2022-01-14 17:42:11 +00:00
|
|
|
local _, _, input_regs, _ = self.rtu.io_count()
|
|
|
|
local return_ok = (ir_addr_start + count) <= input_regs
|
|
|
|
|
|
|
|
if return_ok then
|
|
|
|
for i = 0, (count - 1) do
|
2022-04-18 01:12:25 +00:00
|
|
|
readings[i], access_fault = self.rtu.read_input_reg(ir_addr_start + i)
|
|
|
|
|
|
|
|
if access_fault then
|
|
|
|
return_ok = false
|
|
|
|
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
|
|
|
|
break
|
|
|
|
end
|
2022-01-14 17:42:11 +00:00
|
|
|
end
|
2022-04-18 01:12:25 +00:00
|
|
|
else
|
|
|
|
readings = MODBUS_EXCODE.ILLEGAL_DATA_ADDR
|
2022-01-14 17:42:11 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
return return_ok, readings
|
|
|
|
end
|
|
|
|
|
|
|
|
local _5_write_single_coil = function (c_addr, value)
|
2022-04-18 01:12:25 +00:00
|
|
|
local response = nil
|
2022-01-14 17:42:11 +00:00
|
|
|
local _, coils, _, _ = self.rtu.io_count()
|
|
|
|
local return_ok = c_addr <= coils
|
2022-04-18 01:12:25 +00:00
|
|
|
|
2022-01-14 17:42:11 +00:00
|
|
|
if return_ok then
|
2022-04-18 01:12:25 +00:00
|
|
|
local access_fault = self.rtu.write_coil(c_addr, value)
|
|
|
|
|
|
|
|
if access_fault then
|
|
|
|
return_ok = false
|
|
|
|
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
|
|
|
|
end
|
|
|
|
else
|
|
|
|
response = MODBUS_EXCODE.ILLEGAL_DATA_ADDR
|
2022-01-14 17:42:11 +00:00
|
|
|
end
|
|
|
|
|
2022-04-18 01:12:25 +00:00
|
|
|
return return_ok, response
|
2022-01-14 17:42:11 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
local _6_write_single_holding_register = function (hr_addr, value)
|
2022-04-18 01:12:25 +00:00
|
|
|
local response = nil
|
2022-01-14 17:42:11 +00:00
|
|
|
local _, _, _, hold_regs = self.rtu.io_count()
|
|
|
|
local return_ok = hr_addr <= hold_regs
|
|
|
|
|
|
|
|
if return_ok then
|
2022-04-18 01:12:25 +00:00
|
|
|
local access_fault = self.rtu.write_holding_reg(hr_addr, value)
|
|
|
|
|
|
|
|
if access_fault then
|
|
|
|
return_ok = false
|
|
|
|
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
|
|
|
|
end
|
2022-01-14 17:42:11 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
return return_ok
|
2022-01-13 15:12:44 +00:00
|
|
|
end
|
|
|
|
|
2022-01-14 17:42:11 +00:00
|
|
|
local _15_write_multiple_coils = function (c_addr_start, values)
|
2022-04-18 01:12:25 +00:00
|
|
|
local response = nil
|
2022-01-14 17:42:11 +00:00
|
|
|
local _, coils, _, _ = self.rtu.io_count()
|
|
|
|
local count = #values
|
|
|
|
local return_ok = (c_addr_start + count) <= coils
|
|
|
|
|
|
|
|
if return_ok then
|
|
|
|
for i = 0, (count - 1) do
|
2022-04-18 01:12:25 +00:00
|
|
|
local access_fault = self.rtu.write_coil(c_addr_start + i, values[i + 1])
|
|
|
|
|
|
|
|
if access_fault then
|
|
|
|
return_ok = false
|
|
|
|
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
|
|
|
|
break
|
|
|
|
end
|
2022-01-14 17:42:11 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-04-18 01:12:25 +00:00
|
|
|
return return_ok, response
|
2022-01-14 17:42:11 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
local _16_write_multiple_holding_registers = function (hr_addr_start, values)
|
2022-04-18 01:12:25 +00:00
|
|
|
local response = nil
|
2022-01-14 17:42:11 +00:00
|
|
|
local _, _, _, hold_regs = self.rtu.io_count()
|
|
|
|
local count = #values
|
|
|
|
local return_ok = (hr_addr_start + count) <= hold_regs
|
|
|
|
|
|
|
|
if return_ok then
|
|
|
|
for i = 0, (count - 1) do
|
2022-04-18 01:12:25 +00:00
|
|
|
local access_fault = self.rtu.write_coil(hr_addr_start + i, values[i + 1])
|
|
|
|
|
|
|
|
if access_fault then
|
|
|
|
return_ok = false
|
|
|
|
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
|
|
|
|
break
|
|
|
|
end
|
2022-01-14 17:42:11 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-04-18 01:12:25 +00:00
|
|
|
return return_ok, response
|
2022-01-14 17:42:11 +00:00
|
|
|
end
|
|
|
|
|
2022-04-29 17:19:01 +00:00
|
|
|
-- validate a request without actually executing it
|
|
|
|
local check_request = function (packet)
|
|
|
|
local return_code = true
|
|
|
|
local response = { MODBUS_EXCODE.ACKNOWLEDGE }
|
|
|
|
|
|
|
|
if #packet.data == 2 then
|
|
|
|
-- handle by function code
|
|
|
|
if packet.func_code == MODBUS_FCODE.READ_COILS then
|
|
|
|
elseif packet.func_code == MODBUS_FCODE.READ_DISCRETE_INPUTS then
|
|
|
|
elseif packet.func_code == MODBUS_FCODE.READ_MUL_HOLD_REGS then
|
|
|
|
elseif packet.func_code == MODBUS_FCODE.READ_INPUT_REGISTERS then
|
|
|
|
elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_COIL then
|
|
|
|
elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_HOLD_REG then
|
|
|
|
elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_COILS then
|
|
|
|
elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_HOLD_REGS then
|
|
|
|
else
|
|
|
|
-- unknown function
|
|
|
|
return_code = false
|
|
|
|
response = { MODBUS_EXCODE.ILLEGAL_FUNCTION }
|
|
|
|
end
|
|
|
|
else
|
|
|
|
-- invalid length
|
|
|
|
return_code = false
|
|
|
|
response = { MODBUS_EXCODE.NEG_ACKNOWLEDGE }
|
|
|
|
end
|
|
|
|
|
|
|
|
-- default is to echo back
|
|
|
|
local func_code = packet.func_code
|
|
|
|
if not return_code then
|
|
|
|
-- echo back with error flag
|
|
|
|
func_code = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- create reply
|
|
|
|
local reply = comms.modbus_packet()
|
|
|
|
reply.make(packet.txn_id, packet.unit_id, func_code, response)
|
|
|
|
|
|
|
|
return return_code, reply
|
|
|
|
end
|
|
|
|
|
2022-04-23 00:21:28 +00:00
|
|
|
-- handle a MODBUS TCP packet and generate a reply
|
2022-01-14 17:42:11 +00:00
|
|
|
local handle_packet = function (packet)
|
|
|
|
local return_code = true
|
2022-04-18 01:12:25 +00:00
|
|
|
local response = nil
|
2022-01-14 17:42:11 +00:00
|
|
|
|
|
|
|
if #packet.data == 2 then
|
|
|
|
-- handle by function code
|
|
|
|
if packet.func_code == MODBUS_FCODE.READ_COILS then
|
2022-04-18 01:12:25 +00:00
|
|
|
return_code, response = _1_read_coils(packet.data[1], packet.data[2])
|
2022-01-14 17:42:11 +00:00
|
|
|
elseif packet.func_code == MODBUS_FCODE.READ_DISCRETE_INPUTS then
|
2022-04-18 01:12:25 +00:00
|
|
|
return_code, response = _2_read_discrete_inputs(packet.data[1], packet.data[2])
|
2022-01-14 17:42:11 +00:00
|
|
|
elseif packet.func_code == MODBUS_FCODE.READ_MUL_HOLD_REGS then
|
2022-04-18 01:12:25 +00:00
|
|
|
return_code, response = _3_read_multiple_holding_registers(packet.data[1], packet.data[2])
|
2022-01-14 17:42:11 +00:00
|
|
|
elseif packet.func_code == MODBUS_FCODE.READ_INPUT_REGISTERS then
|
2022-04-18 01:12:25 +00:00
|
|
|
return_code, response = _4_read_input_registers(packet.data[1], packet.data[2])
|
2022-01-14 17:42:11 +00:00
|
|
|
elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_COIL then
|
2022-04-18 01:12:25 +00:00
|
|
|
return_code, response = _5_write_single_coil(packet.data[1], packet.data[2])
|
2022-01-14 17:42:11 +00:00
|
|
|
elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_HOLD_REG then
|
2022-04-18 01:12:25 +00:00
|
|
|
return_code, response = _6_write_single_holding_register(packet.data[1], packet.data[2])
|
2022-01-14 17:42:11 +00:00
|
|
|
elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_COILS then
|
2022-04-18 01:12:25 +00:00
|
|
|
return_code, response = _15_write_multiple_coils(packet.data[1], packet.data[2])
|
2022-01-14 17:42:11 +00:00
|
|
|
elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_HOLD_REGS then
|
2022-04-18 01:12:25 +00:00
|
|
|
return_code, response = _16_write_multiple_holding_registers(packet.data[1], packet.data[2])
|
2022-01-14 17:42:11 +00:00
|
|
|
else
|
|
|
|
-- unknown function
|
|
|
|
return_code = false
|
2022-04-18 01:12:25 +00:00
|
|
|
response = MODBUS_EXCODE.ILLEGAL_FUNCTION
|
2022-01-14 17:42:11 +00:00
|
|
|
end
|
|
|
|
else
|
|
|
|
-- invalid length
|
|
|
|
return_code = false
|
|
|
|
end
|
|
|
|
|
2022-04-23 00:21:28 +00:00
|
|
|
-- default is to echo back
|
|
|
|
local func_code = packet.func_code
|
|
|
|
if not return_code then
|
2022-01-14 17:42:11 +00:00
|
|
|
-- echo back with error flag
|
2022-04-23 00:21:28 +00:00
|
|
|
func_code = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
2022-04-18 01:12:25 +00:00
|
|
|
|
|
|
|
if type(response) == "nil" then
|
2022-04-23 00:21:28 +00:00
|
|
|
response = { }
|
2022-04-18 01:12:25 +00:00
|
|
|
elseif type(response) == "number" then
|
2022-04-23 00:21:28 +00:00
|
|
|
response = { response }
|
2022-04-18 01:12:25 +00:00
|
|
|
elseif type(response) == "table" then
|
2022-04-23 00:21:28 +00:00
|
|
|
response = response
|
2022-04-18 01:12:25 +00:00
|
|
|
end
|
2022-01-14 17:42:11 +00:00
|
|
|
end
|
|
|
|
|
2022-04-23 00:21:28 +00:00
|
|
|
-- create reply
|
|
|
|
local reply = comms.modbus_packet()
|
|
|
|
reply.make(packet.txn_id, packet.unit_id, func_code, response)
|
|
|
|
|
2022-04-18 01:12:25 +00:00
|
|
|
return return_code, reply
|
2022-01-13 15:12:44 +00:00
|
|
|
end
|
|
|
|
|
2022-04-29 17:19:01 +00:00
|
|
|
-- return a SERVER_DEVICE_BUSY error reply
|
|
|
|
local reply__srv_device_busy = function (packet)
|
|
|
|
-- reply back with error flag and exception code
|
|
|
|
local reply = comms.modbus_packet()
|
|
|
|
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
|
|
|
local data = { MODBUS_EXCODE.SERVER_DEVICE_BUSY }
|
|
|
|
reply.make(packet.txn_id, packet.unit_id, fcode, data)
|
|
|
|
return reply
|
|
|
|
end
|
|
|
|
|
2022-04-23 00:21:28 +00:00
|
|
|
-- return a NEG_ACKNOWLEDGE error reply
|
|
|
|
local reply__neg_ack = function (packet)
|
|
|
|
-- reply back with error flag and exception code
|
|
|
|
local reply = comms.modbus_packet()
|
|
|
|
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
|
|
|
local data = { MODBUS_EXCODE.NEG_ACKNOWLEDGE }
|
|
|
|
reply.make(packet.txn_id, packet.unit_id, fcode, data)
|
|
|
|
return reply
|
|
|
|
end
|
|
|
|
|
|
|
|
-- return a GATEWAY_PATH_UNAVAILABLE error reply
|
|
|
|
local reply__gw_unavailable = function (packet)
|
|
|
|
-- reply back with error flag and exception code
|
|
|
|
local reply = comms.modbus_packet()
|
|
|
|
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
|
|
|
local data = { MODBUS_EXCODE.GATEWAY_PATH_UNAVAILABLE }
|
|
|
|
reply.make(packet.txn_id, packet.unit_id, fcode, data)
|
|
|
|
return reply
|
|
|
|
end
|
|
|
|
|
2022-01-14 17:42:11 +00:00
|
|
|
return {
|
2022-04-29 17:19:01 +00:00
|
|
|
check_request = check_request,
|
2022-04-23 00:21:28 +00:00
|
|
|
handle_packet = handle_packet,
|
2022-04-29 17:19:01 +00:00
|
|
|
reply__srv_device_busy = reply__srv_device_busy,
|
2022-04-23 00:21:28 +00:00
|
|
|
reply__neg_ack = reply__neg_ack,
|
|
|
|
reply__gw_unavailable = reply__gw_unavailable
|
2022-01-14 17:42:11 +00:00
|
|
|
}
|
|
|
|
end
|