From 0d7302dc8ea4c4e600fe16cdebf60307c684e86b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 21 Apr 2025 22:18:09 -0400 Subject: [PATCH] #604 start of total rework of redstone RTUs for relay functionalitiy --- rtu/config/redstone.lua | 1 + rtu/dev/redstone_rtu.lua | 12 ++++++- rtu/modbus.lua | 46 +++++++++++++-------------- rtu/rtu.lua | 10 ++++-- rtu/startup.lua | 69 +++++++++++++++++++++++++--------------- rtu/threads.lua | 2 ++ 6 files changed, 87 insertions(+), 53 deletions(-) diff --git a/rtu/config/redstone.lua b/rtu/config/redstone.lua index 9826cb1..854d6b9 100644 --- a/rtu/config/redstone.lua +++ b/rtu/config/redstone.lua @@ -18,6 +18,7 @@ local NumberField = require("graphics.elements.form.NumberField") ---@class rtu_rs_definition ---@field unit integer|nil ---@field port IO_PORT +---@field relay string|nil ---@field side side ---@field color color|nil ---@field invert true|nil diff --git a/rtu/dev/redstone_rtu.lua b/rtu/dev/redstone_rtu.lua index 8c3e5ee..366e960 100644 --- a/rtu/dev/redstone_rtu.lua +++ b/rtu/dev/redstone_rtu.lua @@ -11,10 +11,14 @@ local digital_write = rsio.digital_write -- create new redstone device ---@nodiscard +---@param relay? table optional redstone relay to use instead of the computer's redstone interface ---@return rtu_rs_device interface, boolean faulted -function redstone_rtu.new() +function redstone_rtu.new(relay) local unit = rtu.init_unit() + -- physical interface to use + local phy = relay or rs + -- get RTU interface local interface = unit.interface() @@ -30,6 +34,12 @@ function redstone_rtu.new() write_holding_reg = interface.write_holding_reg } + -- change the phy in use (a relay or rs) + ---@param new_phy table + function public.change_phy(new_phy) phy = new_phy end + + -- NOTE: for runtime speed, inversion logic results in extra code here but less code when functions are called + -- link digital input ---@param side string ---@param color integer diff --git a/rtu/modbus.lua b/rtu/modbus.lua index d55907f..ed8ee19 100644 --- a/rtu/modbus.lua +++ b/rtu/modbus.lua @@ -399,43 +399,41 @@ function modbus.new(rtu_dev, use_parallel_read) return public end +-- create an error reply +---@nodiscard +---@param packet modbus_frame MODBUS packet frame +---@param code MODBUS_EXCODE exception code +---@return modbus_packet reply +local function excode_reply(packet, code) + -- reply back with error flag and exception code + local reply = comms.modbus_packet() + local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) + reply.make(packet.txn_id, packet.unit_id, fcode, { code }) + return reply +end + +-- return a SERVER_DEVICE_FAIL error reply +---@nodiscard +---@param packet modbus_frame MODBUS packet frame +---@return modbus_packet reply +function modbus.reply__srv_device_fail(packet) return excode_reply(packet, MODBUS_EXCODE.SERVER_DEVICE_FAIL) end + -- return a SERVER_DEVICE_BUSY error reply ---@nodiscard ---@param packet modbus_frame MODBUS packet frame ---@return modbus_packet reply -function modbus.reply__srv_device_busy(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 +function modbus.reply__srv_device_busy(packet) return excode_reply(packet, MODBUS_EXCODE.SERVER_DEVICE_BUSY) end -- return a NEG_ACKNOWLEDGE error reply ---@nodiscard ---@param packet modbus_frame MODBUS packet frame ---@return modbus_packet reply -function modbus.reply__neg_ack(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 +function modbus.reply__neg_ack(packet) return excode_reply(packet, MODBUS_EXCODE.NEG_ACKNOWLEDGE) end -- return a GATEWAY_PATH_UNAVAILABLE error reply ---@nodiscard ---@param packet modbus_frame MODBUS packet frame ---@return modbus_packet reply -function modbus.reply__gw_unavailable(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 +function modbus.reply__gw_unavailable(packet) return excode_reply(packet, MODBUS_EXCODE.GATEWAY_PATH_UNAVAILABLE) end return modbus diff --git a/rtu/rtu.lua b/rtu/rtu.lua index d7a576e..c7401fe 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -477,9 +477,15 @@ function rtu.comms(version, nic, conn_watchdog) local unit = units[packet.unit_id] local unit_dbg_tag = " (unit " .. packet.unit_id .. ")" - if unit.name == "redstone_io" then + if unit.type == RTU_UNIT_TYPE.REDSTONE then -- immediately execute redstone RTU requests - return_code, reply = unit.modbus_io.handle_packet(packet) + if not unit.device then + reply = modbus.reply__srv_device_fail(packet) + return_code = false + else + return_code, reply = unit.modbus_io.handle_packet(packet) + end + if not return_code then log.warning("requested MODBUS operation failed" .. unit_dbg_tag) end diff --git a/rtu/startup.lua b/rtu/startup.lua index c1413a6..d875ae6 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -143,29 +143,23 @@ local function main() -- configure RTU gateway based on settings file definitions local function sys_config() -- redstone interfaces - local rs_rtus = {} ---@type { rtu: rtu_rs_device, capabilities: IO_PORT[] }[] + local rs_rtus = {} ---@type { name: string, rtu: rtu_rs_device, phy: table|nil, banks: IO_PORT[][] }[] -- go through redstone definitions list for entry_idx = 1, #rtu_redstone do local entry = rtu_redstone[entry_idx] + local assignment local for_reactor = entry.unit - local iface_name = util.trinary(entry.color ~= nil, util.c(entry.side, "/", rsio.color_name(entry.color)), entry.side) + local phy = entry.relay or 0 + local iface_name = util.trinary(entry.color ~= nil, util.c(entry.side, "/", rsio.color_name(entry.color)), entry.side) if util.is_int(entry.unit) and entry.unit > 0 and entry.unit < 5 then ---@cast for_reactor integer assignment = "reactor unit " .. entry.unit - if rs_rtus[for_reactor] == nil then - log.debug(util.c("sys_config> allocated redstone RTU for reactor unit ", entry.unit)) - rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} } - end elseif entry.unit == nil then assignment = "facility" for_reactor = 0 - if rs_rtus[for_reactor] == nil then - log.debug(util.c("sys_config> allocated redstone RTU for the facility")) - rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} } - end else local message = util.c("sys_config> invalid unit assignment at block index #", entry_idx) println(message) @@ -173,6 +167,31 @@ local function main() return false end + -- create the appropriate RTU if it doesn't exist and check relay name validity + if entry.relay then + if type(entry.relay) ~= "string" then + local message = util.c("sys_config> invalid redstone relay '", entry.relay, '"') + println(message) + log.fatal(message) + return false + elseif not rs_rtus[entry.relay] then + log.debug(util.c("sys_config> allocated relay redstone RTU for interface ", entry.relay)) + + local relay = ppm.get_device(entry.relay) + + if not relay then + log.warning(util.c("sys_config> redstone relay ", entry.relay, " not connected")) + elseif ppm.get_type(entry.relay) ~= "redstone_relay" then + log.warning(util.c("sys_config> redstone relay ", entry.relay, " is not a redstone relay")) + end + + rs_rtus[entry.relay] = { name = entry.relay, rtu = redstone_rtu.new(relay), phy = relay, banks = {} } + end + elseif rs_rtus[0] == nil then + log.debug(util.c("sys_config> allocated local redstone RTU")) + rs_rtus[0] = { name = "redstone_local", rtu = redstone_rtu.new(), phy = rs, banks = {} } + end + -- verify configuration local valid = false if rsio.is_valid_port(entry.port) and rsio.is_valid_side(entry.side) then @@ -180,7 +199,7 @@ local function main() end local rs_rtu = rs_rtus[for_reactor].rtu - local capabilities = rs_rtus[for_reactor].capabilities + local conns = rs_rtus[phy].banks[for_reactor] if not valid then local message = util.c("sys_config> invalid redstone definition at block index #", entry_idx) @@ -192,7 +211,7 @@ local function main() local mode = rsio.get_io_mode(entry.port) if mode == rsio.IO_MODE.DIGITAL_IN then -- can't have duplicate inputs - if util.table_contains(capabilities, entry.port) then + if util.table_contains(conns, entry.port) then local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name) println(message) log.warning(message) @@ -203,7 +222,7 @@ local function main() rs_rtu.link_do(entry.side, entry.color, entry.invert) elseif mode == rsio.IO_MODE.ANALOG_IN then -- can't have duplicate inputs - if util.table_contains(capabilities, entry.port) then + if util.table_contains(conns, entry.port) then local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name) println(message) log.warning(message) @@ -219,25 +238,28 @@ local function main() return false end - table.insert(capabilities, entry.port) + table.insert(conns, entry.port) - log.debug(util.c("sys_config> linked redstone ", #capabilities, ": ", rsio.to_string(entry.port), " (", iface_name, ") for ", assignment)) + log.debug(util.c("sys_config> linked redstone ", #conns, ": ", rsio.to_string(entry.port), " (", iface_name, ") for ", assignment)) end end -- create unit entries for redstone RTUs - for for_reactor, def in pairs(rs_rtus) do + for _, def in pairs(rs_rtus) do + local hw_state = util.trinary(def.phy, RTU_HW_STATE.OK, RTU_HW_STATE.OFFLINE) + ---@class rtu_registry_entry local unit = { uid = 0, ---@type integer - name = "redstone_io", ---@type string + name = def.name, ---@type string type = RTU_UNIT_TYPE.REDSTONE, ---@type RTU_UNIT_TYPE index = false, ---@type integer|false - reactor = for_reactor, ---@type integer - device = def.capabilities, ---@type IO_PORT[] use device field for redstone ports + reactor = nil, ---@type nil + device = def.phy, ---@type table|nil + banks = def.banks, ---@type IO_PORT[][] is_multiblock = false, ---@type boolean formed = nil, ---@type boolean|nil - hw_state = RTU_HW_STATE.OK, ---@type RTU_HW_STATE + hw_state = hw_state, ---@type RTU_HW_STATE rtu = def.rtu, ---@type rtu_device|rtu_rs_device modbus_io = modbus.new(def.rtu, false), pkt_queue = nil, ---@type mqueue|nil @@ -246,12 +268,7 @@ local function main() table.insert(units, unit) - local for_message = "facility" - if util.is_int(for_reactor) then - for_message = util.c("reactor unit ", for_reactor) - end - - log.info(util.c("sys_config> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for ", for_message)) + log.info(util.c("sys_config> initialized RTU unit #", #units, ": ", unit.name, " (redstone)")) unit.uid = #units diff --git a/rtu/threads.lua b/rtu/threads.lua index e1d8c6c..7b872bf 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -132,6 +132,8 @@ local function handle_unit_mount(smem, println_ts, iface, type, device, unit) unit.rtu, faulted = sna_rtu.new(device) elseif unit.type == RTU_UNIT_TYPE.ENV_DETECTOR then unit.rtu, faulted = envd_rtu.new(device) + elseif unit.type == RTU_UNIT_TYPE.REDSTONE then + unit.rtu.change_phy(device) else unknown = true log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true)