From 60674ec95c399edc660b75077c5775fb45b3f15a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 23 Mar 2022 15:41:08 -0400 Subject: [PATCH] RTU startup code and comms --- rtu/config.lua | 33 ++++++++---- rtu/startup.lua | 117 +++++++++++++++++++++++++++++++++++++---- scada-common/comms.lua | 98 ++++++++++++++++++++++++++-------- scada-common/log.lua | 37 +++++++++---- 4 files changed, 233 insertions(+), 52 deletions(-) diff --git a/rtu/config.lua b/rtu/config.lua index 66b2154..b06305e 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -17,15 +17,26 @@ RTU_DEVICES = { RTU_REDSTONE = { { - io = RS_IO.WASTE_PO, - for_reactor = 1 - }, - { - io = RS_IO.WASTE_PU, - for_reactor = 1 - }, - { - io = RS_IO.WASTE_AM, - for_reactor = 1 - }, + for_reactor = 1, + io = { + { + channel = RS_IO.WASTE_PO, + side = "top", + bundled_color = colors.blue, + for_reactor = 1 + }, + { + channel = RS_IO.WASTE_PU, + side = "top", + bundled_color = colors.cyan, + for_reactor = 1 + }, + { + channel = RS_IO.WASTE_AM, + side = "top", + bundled_color = colors.purple, + for_reactor = 1 + } + } + } } diff --git a/rtu/startup.lua b/rtu/startup.lua index dfbad8b..483aaee 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -19,6 +19,13 @@ local RTU_VERSION = "alpha-v0.1.0" local print_ts = util.print_ts +---------------------------------------- +-- startup +---------------------------------------- + +local units = {} +local linked = false + -- mount connected devices ppm.mount_all() @@ -36,8 +43,68 @@ end local rtu_comms = comms.rtu_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) +---------------------------------------- -- determine configuration -local units = {} +---------------------------------------- + +-- redstone interfaces +for reactor_idx = 1, #RTU_REDSTONE do + local rs_rtu = redstone_rtu() + local io_table = RTU_REDSTONE[reactor_idx].io + + local capabilities = {} + + for i = 1, #io_table do + local valid = false + local config = io_table[i] + + -- verify configuration + if is_valid_channel(config.channel) and is_valid_side(config.side) then + if config.bundled_color then + valid = is_color(config.bundled_color) + else + valid = true + end + end + + if ~valid then + local message = "invalid redstone configuration at index " .. i + print_ts(message .. "\n") + log._warning(message) + else + -- link redstone in RTU + local mode = rsio.get_io_mode(config.channel) + if mode == rsio.IO_MODE.DIGITAL_IN then + rs_rtu.link_di(config.channel, config.side, config.bundled_color) + elseif mode == rsio.IO_MODE.DIGITAL_OUT then + rs_rtu.link_do(config.channel, config.side, config.bundled_color) + elseif mode == rsio.IO_MODE.ANALOG_IN then + rs_rtu.link_ai(config.channel, config.side) + elseif mode == rsio.IO_MODE.ANALOG_OUT then + rs_rtu.link_ao(config.channel, config.side) + else + -- should be unreachable code, we already validated channels + log._error("fell through if chain attempting to identify IO mode", true) + break + end + + table.insert(capabilities, config.channel) + + log._debug("startup> linked redstone " .. #capabilities .. ": " .. rsio.to_string(config.channel) .. " (" .. config.side .. + ") for reactor " .. RTU_REDSTONE[reactor_idx].for_reactor) + end + end + + table.insert(units, { + name = "redstone_io", + type = "redstone", + index = 1, + reactor = RTU_REDSTONE[reactor_idx].for_reactor, + device = capabilities, -- use device field for redstone channels + rtu = rs_rtu, + modbus_io = modbus_init(rs_rtu) + }) +end -- mounted peripherals for i = 1, #RTU_DEVICES do @@ -45,7 +112,7 @@ for i = 1, #RTU_DEVICES do if device == nil then local message = "'" .. RTU_DEVICES[i].name .. "' not found" - print_ts(message) + print_ts(message .. "\n") log._warning(message) else local type = ppm.get_type(RTU_DEVICES[i].name) @@ -66,7 +133,7 @@ for i = 1, #RTU_DEVICES do rtu_iface = imatrix_rtu(device) else local message = "device '" .. RTU_DEVICES[i].name .. "' is not a known type (" .. type .. ")" - print_ts(message) + print_ts(message .. "\n") log._warning(message) end @@ -77,16 +144,46 @@ for i = 1, #RTU_DEVICES do index = RTU_DEVICES[i].index, reactor = RTU_DEVICES[i].for_reactor, device = device, - rtu = rtu_iface + rtu = rtu_iface, + modbus_io = modbus_init(rtu_iface) }) + + log._debug("startup> initialized RTU unit #" .. #units .. ": " .. RTU_DEVICES[i].name .. " (" .. rtu_type .. ") [" .. + RTU_DEVICES[i].index .. "] for reactor " .. RTU_DEVICES[i].for_reactor) end end end --- redstone devices -for i = 1, #RTU_REDSTONE do +---------------------------------------- +-- main loop +---------------------------------------- + +-- advertisement/heartbeat clock (every 2 seconds) +local loop_tick = os.startTimer(2) + +-- event loop +while true do + local event, param1, param2, param3, param4, param5 = os.pullEventRaw() + + if event == "peripheral_detach" then + ppm.handle_unmount(param1) + + -- todo: handle unit change + elseif event == "timer" and param1 == loop_tick then + -- period tick, if we are linked send heartbeat, if not send advertisement + if linked then + rtu_comms.send_heartbeat() + else + -- advertise units + rtu_comms.send_advertisement(units) + end + elseif event == "modem_message" then + -- got a packet + + local packet = rtu_comms.parse_packet(p1, p2, p3, p4, p5) + rtu_comms.handle_packet(packet) + + elseif event == "terminate" then + return + end end - --- advertise units -rtu_comms.send_advertisement(units) - diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 6ea907b..dfea4bf 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -17,15 +17,12 @@ RPLC_TYPES = { LINK_REQ = 1, -- linking requests STATUS = 2, -- reactor/system status MEK_STRUCT = 3, -- mekanism build structure - RS_IO_CONNS = 4, -- redstone I/O connections - RS_IO_SET = 5, -- set redstone outputs - RS_IO_GET = 6, -- get redstone inputs - MEK_SCRAM = 7, -- SCRAM reactor - MEK_ENABLE = 8, -- enable reactor - MEK_BURN_RATE = 9, -- set burn rate - ISS_ALARM = 10, -- ISS alarm broadcast - ISS_GET = 11, -- get ISS status - ISS_CLEAR = 12 -- clear ISS trip (if in bad state, will trip immediately) + MEK_SCRAM = 4, -- SCRAM reactor + MEK_ENABLE = 5, -- enable reactor + MEK_BURN_RATE = 6, -- set burn rate + ISS_ALARM = 7, -- ISS alarm broadcast + ISS_GET = 8, -- get ISS status + ISS_CLEAR = 9 -- clear ISS trip (if in bad state, will trip immediately) } RPLC_LINKING = { @@ -44,7 +41,8 @@ SCADA_MGMT_TYPES = { RTU_ADVERT_TYPES = { BOILER = 0, -- boiler TURBINE = 1, -- turbine - IMATRIX = 2 -- induction matrix + IMATRIX = 2, -- induction matrix + REDSTONE = 3 -- redstone I/O } -- generic SCADA packet object @@ -187,13 +185,50 @@ function rtu_comms(modem, local_port, server_port) length = #body - 1, body = { table.unpack(body, 2, 1 + #body) } } + elseif #body == 1 then + pkt = { + scada_frame = s_pkt, + type = body[1], + length = #body - 1, + body = nil + } + else + log._error("Malformed SCADA packet has no length field") end + else + log._error("Illegal packet type " .. s_pkt.protocol(), true) end end return pkt end + local handle_packet = function(packet, units) + if packet ~= nil then + local protocol = packet.scada_frame.protocol() + + if protocol == PROTOCOLS.MODBUS_TCP then + -- MODBUS instruction + if packet.modbus_frame.unit_id <= #units then + local return_code, response = units.modbus_io.handle_packet(packet.modbus_frame) + _send(response, PROTOCOLS.MODBUS_TCP) + + if not return_code then + log._warning("MODBUS operation failed") + end + else + -- unit ID out of range? + log._error("MODBUS packet requesting non-existent unit") + end + elseif protocol == PROTOCOLS.SCADA_MGMT then + -- SCADA management packet + else + -- should be unreachable assuming packet is from parse_packet() + log._error("Illegal packet type " .. protocol, true) + end + end + end + -- send capability advertisement local send_advertisement = function (units) local advertisement = { @@ -210,22 +245,47 @@ function rtu_comms(modem, local_port, server_port) type = RTU_ADVERT_TYPES.TURBINE elseif units[i].type == "imatrix" then type = RTU_ADVERT_TYPES.IMATRIX + elseif units[i].type == "redstone" then + type = RTU_ADVERT_TYPES.REDSTONE end if type ~= nil then - table.insert(advertisement.units, { - type = type, - index = units[i].index, - reactor = units[i].for_reactor - }) + if type == RTU_ADVERT_TYPES.REDSTONE then + table.insert(advertisement.units, { + unit = i, + type = type, + index = units[i].index, + reactor = units[i].for_reactor, + rsio = units[i].device + }) + else + table.insert(advertisement.units, { + unit = i, + type = type, + index = units[i].index, + reactor = units[i].for_reactor, + rsio = nil + }) + end end end _send(advertisement, PROTOCOLS.SCADA_MGMT) end + local send_heartbeat = function () + local heartbeat = { + type = SCADA_MGMT_TYPES.RTU_HEARTBEAT + } + + _send(heartbeat, PROTOCOLS.SCADA_MGMT) + end + return { - parse_packet = parse_packet + parse_packet = parse_packet, + handle_packet = handle_packet, + send_advertisement = send_advertisement, + send_heartbeat = send_heartbeat } end @@ -408,16 +468,12 @@ function rplc_comms(id, modem, local_port, server_port, reactor) _send(sys_status) end - local send_rs_io_conns = function () - end - return { parse_packet = parse_packet, handle_link = handle_link, handle_packet = handle_packet, send_link_req = send_link_req, send_struct = send_struct, - send_status = send_status, - send_rs_io_conns = send_rs_io_conns + send_status = send_status } end diff --git a/scada-common/log.lua b/scada-common/log.lua index 29fc688..3a56919 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -5,6 +5,8 @@ -- we use extra short abbreviations since computer craft screens are very small -- underscores are used since some of these names are used elsewhere (e.g. 'debug' is a lua table) +local LOG_DEBUG = true + local file_handle = fs.open("/log.txt", "a") local _log = function (msg) @@ -14,6 +16,29 @@ local _log = function (msg) end function _debug(msg, trace) + if LOG_DEBUG then + local dbg_info = "" + + if trace then + local name = "" + + if debug.getinfo(2).name ~= nil then + name = ":" .. debug.getinfo(2).name .. "():" + end + + dbg_info = debug.getinfo(2).short_src .. ":" .. name .. + debug.getinfo(2).currentline .. " > " + end + + _log("[DBG] " .. dbg_info .. msg .. "\n") + end +end + +function _warning(msg) + _log("[WRN] " .. msg .. "\n") +end + +function _error(msg, trace) local dbg_info = "" if trace then @@ -27,17 +52,9 @@ function _debug(msg, trace) debug.getinfo(2).currentline .. " > " end - _log("[DBG] " .. dbg_info .. msg) -end - -function _warning(msg) - _log("[WRN] " .. msg) -end - -function _error(msg) - _log("[ERR] " .. msg) + _log("[ERR] " .. dbg_info .. msg .. "\n") end function _fatal(msg) - _log("[FTL] " .. msg) + _log("[FTL] " .. msg .. "\n") end