diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 97e11f7..32600a1 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -3,17 +3,23 @@ function rtu_init() discrete_inputs = {}, coils = {}, input_regs = {}, - holding_regs = {} + holding_regs = {}, + io_count_cache = { 0, 0, 0, 0 } } - local count_io = function () - return #self.discrete_inputs, #self.coils, #self.input_regs, #self.holding_regs + local __count_io = function () + self.io_count_cache = { #self.discrete_inputs, #self.coils, #self.input_regs, #self.holding_regs } + end + + local io_count = function () + return self.io_count_cache[0], self.io_count_cache[1], self.io_count_cache[2], self.io_count_cache[3] end -- discrete inputs: single bit read-only local connect_di = function (f) table.insert(self.discrete_inputs, f) + __count_io() return #self.discrete_inputs end @@ -25,6 +31,7 @@ function rtu_init() local connect_coil = function (f_read, f_write) table.insert(self.coils, { read = f_read, write = f_write }) + __count_io() return #self.coils end @@ -40,6 +47,7 @@ function rtu_init() local connect_input_reg = function (f) table.insert(self.input_regs, f) + __count_io() return #self.input_regs end @@ -51,6 +59,7 @@ function rtu_init() local connect_holding_reg = function (f_read, f_write) table.insert(self.holding_regs, { read = f_read, write = f_write }) + __count_io() return #self.holding_regs end @@ -63,7 +72,7 @@ function rtu_init() end return { - count_io = count_io, + io_count = io_count, connect_di = connect_di, read_di = read_di, connect_coil = connect_coil, diff --git a/scada-common/modbus.lua b/scada-common/modbus.lua index 1e47f9c..e6d5eba 100644 --- a/scada-common/modbus.lua +++ b/scada-common/modbus.lua @@ -1,14 +1,209 @@ +-- 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 +} + +-- new modbus comms handler object function modbus_init(rtu_dev) local self = { rtu = rtu_dev } - function _1_read_coils(c_channel_start, count) + local _1_read_coils = function (c_addr_start, count) + local readings = {} + local _, coils, _, _ = self.rtu.io_count() + local return_ok = (c_addr_start + count) <= coils + + if return_ok then + for i = 0, (count - 1) do + readings[i] = self.rtu.read_coil(c_addr_start + i) + end + end + + return return_ok, readings end - function _2_read_discrete_inputs(di_channel_start, count) + local _2_read_discrete_inputs = function (di_addr_start, count) + local readings = {} + 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 + readings[i] = self.rtu.read_di(di_addr_start + i) + end + end + + return return_ok, readings end - function _3_read_multiple_holding_registers(hr_channel_start, count) + local _3_read_multiple_holding_registers = function (hr_addr_start, count) + local readings = {} + 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 + readings[i] = self.rtu.read_holding_reg(hr_addr_start + i) + end + end + + return return_ok, readings + end + + local _4_read_input_registers = function (ir_addr_start, count) + local readings = {} + 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 + readings[i] = self.rtu.read_input_reg(ir_addr_start + i) + end + end + + return return_ok, readings + end + + local _5_write_single_coil = function (c_addr, value) + local _, coils, _, _ = self.rtu.io_count() + local return_ok = c_addr <= coils + + if return_ok then + self.rtu.write_coil(c_addr, value) + end + + return return_ok + end + + local _6_write_single_holding_register = function (hr_addr, value) + local _, _, _, hold_regs = self.rtu.io_count() + local return_ok = hr_addr <= hold_regs + + if return_ok then + self.rtu.write_holding_reg(hr_addr, value) + end + + return return_ok + end + + local _15_write_multiple_coils = function (c_addr_start, values) + 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 + self.rtu.write_coil(c_addr_start + i, values[i + 1]) + end + end + + return return_ok + end + + local _16_write_multiple_holding_registers = function (hr_addr_start, values) + 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 + self.rtu.write_coil(hr_addr_start + i, values[i + 1]) + end + end + + return return_ok + end + + local handle_packet = function (packet) + local return_code = true + local readings = nil + + if #packet.data == 2 then + -- handle by function code + if packet.func_code == MODBUS_FCODE.READ_COILS then + return_code, readings = _1_read_coils(packet.data[1], packet.data[2]) + elseif packet.func_code == MODBUS_FCODE.READ_DISCRETE_INPUTS then + return_code, readings = _2_read_discrete_inputs(packet.data[1], packet.data[2]) + elseif packet.func_code == MODBUS_FCODE.READ_MUL_HOLD_REGS then + return_code, readings = _3_read_multiple_holding_registers(packet.data[1], packet.data[2]) + elseif packet.func_code == MODBUS_FCODE.READ_INPUT_REGISTERS then + return_code, readings = _4_read_input_registers(packet.data[1], packet.data[2]) + elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_COIL then + return_code = _5_write_single_coil(packet.data[1], packet.data[2]) + elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_HOLD_REG then + return_code = _6_write_single_holding_register(packet.data[1], packet.data[2]) + elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_COILS then + return_code = _15_write_multiple_coils(packet.data[1], packet.data[2]) + elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_HOLD_REGS then + return_code = _16_write_multiple_holding_registers(packet.data[1], packet.data[2]) + else + -- unknown function + return_code = false + end + else + -- invalid length + return_code = false + end + + if return_code then + -- response (default is to echo back) + response = packet + if readings ~= nil then + response.length = #readings + response.data = readings + end + else + -- echo back with error flag + response = packet + response.func_code = bit.bor(packet.func_code, ERROR_FLAG) + end + + return return_code, response + end + + return { + handle_packet = handle_packet + } +end + +-- create new modbus packet +function new_modbus_packet(txn_id, protocol, length, unit_id, func_code, data) + return { + txn_id = txn_id, + protocol = protocol, + length = length, + unit_id = unit_id, + func_code = func_code, + data = data + } +end + +-- parse raw table data as a modbus packet +function parse_modbus_packet(raw) + if #raw ~= 6 then + return nil + else + return new_modbus_packet(raw[1], raw[2], raw[3], raw[4], raw[5], raw[6]) end end + +-- create raw table data from a modbus packet +function modbus_to_raw(packet) + return { + packet.txn_id, + packet.protocol, + packet.length, + packet.unit_id, + packet.func_code, + packet.data + } +end