diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 96d766e..8089be8 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -1,4 +1,4 @@ --- #REQUIRES comms.lua +local comms = require("scada-common.comms") -- coordinator communications function coord_comms() diff --git a/coordinator/startup.lua b/coordinator/startup.lua index fe0bf48..5ee3d17 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -2,15 +2,14 @@ -- Nuclear Generation Facility SCADA Coordinator -- -os.loadAPI("scada-common/log.lua") -os.loadAPI("scada-common/util.lua") -os.loadAPI("scada-common/ppm.lua") -os.loadAPI("scada-common/comms.lua") +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") -os.loadAPI("coordinator/config.lua") -os.loadAPI("coordinator/coordinator.lua") +local config = require("config") +local coordinator = require("coordinator") -local COORDINATOR_VERSION = "alpha-v0.1.1" +local COORDINATOR_VERSION = "alpha-v0.1.2" local print = util.print local println = util.println @@ -19,9 +18,9 @@ local println_ts = util.println_ts log.init("/log.txt", log.MODE.APPEND) -log._info("========================================") -log._info("BOOTING coordinator.startup " .. COORDINATOR_VERSION) -log._info("========================================") +log.info("========================================") +log.info("BOOTING coordinator.startup " .. COORDINATOR_VERSION) +log.info("========================================") println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<") -- mount connected devices diff --git a/reactor-plc/config.lua b/reactor-plc/config.lua index 43086d5..99edc92 100644 --- a/reactor-plc/config.lua +++ b/reactor-plc/config.lua @@ -1,14 +1,18 @@ +local config = {} + -- set to false to run in offline mode (safety regulation only) -NETWORKED = true +config.NETWORKED = true -- unique reactor ID -REACTOR_ID = 1 +config.REACTOR_ID = 1 -- port to send packets TO server -SERVER_PORT = 16000 +config.SERVER_PORT = 16000 -- port to listen to incoming packets FROM server -LISTEN_PORT = 14001 +config.LISTEN_PORT = 14001 -- log path -LOG_PATH = "/log.txt" +config.LOG_PATH = "/log.txt" -- log mode -- 0 = APPEND (adds to existing file on start) -- 1 = NEW (replaces existing file on start) -LOG_MODE = 0 +config.LOG_MODE = 0 + +return config diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 8aa65b1..54f47d2 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -1,7 +1,10 @@ --- #REQUIRES types.lua --- #REQUIRES comms.lua --- #REQUIRES ppm.lua --- #REQUIRES util.lua +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") +local types = require("scada-common.types") +local util = require("scada-common.util") + +local plc = {} local iss_status_t = types.iss_status_t @@ -18,7 +21,7 @@ local println_ts = util.println_ts -- Internal Safety System -- identifies dangerous states and SCRAMs reactor if warranted -- autonomous from main SCADA supervisor/coordinator control -function iss_init(reactor) +plc.iss_init = function (reactor) local self = { reactor = reactor, cache = { false, false, false, false, false, false, false }, @@ -34,7 +37,7 @@ function iss_init(reactor) local damage_percent = self.reactor.getDamagePercent() if damage_percent == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log._error("ISS: failed to check reactor damage") + log.error("ISS: failed to check reactor damage") return false else return damage_percent >= 100 @@ -46,7 +49,7 @@ function iss_init(reactor) local hc_needed = self.reactor.getHeatedCoolantNeeded() if hc_needed == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log._error("ISS: failed to check reactor heated coolant level") + log.error("ISS: failed to check reactor heated coolant level") return false else return hc_needed == 0 @@ -58,7 +61,7 @@ function iss_init(reactor) local w_needed = self.reactor.getWasteNeeded() if w_needed == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log._error("ISS: failed to check reactor waste level") + log.error("ISS: failed to check reactor waste level") return false else return w_needed == 0 @@ -71,7 +74,7 @@ function iss_init(reactor) local temp = self.reactor.getTemperature() if temp == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log._error("ISS: failed to check reactor temperature") + log.error("ISS: failed to check reactor temperature") return false else return temp >= 1200 @@ -83,7 +86,7 @@ function iss_init(reactor) local fuel = self.reactor.getFuel() if fuel == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log._error("ISS: failed to check reactor fuel level") + log.error("ISS: failed to check reactor fuel level") return false else return fuel == 0 @@ -95,7 +98,7 @@ function iss_init(reactor) local coolant_filled = self.reactor.getCoolantFilledPercentage() if coolant_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log._error("ISS: failed to check reactor coolant level") + log.error("ISS: failed to check reactor coolant level") return false else return coolant_filled < 0.02 @@ -134,25 +137,25 @@ function iss_init(reactor) if self.tripped then status = self.trip_cause elseif self.cache[1] then - log._warning("ISS: damage critical!") + log.warning("ISS: damage critical!") status = iss_status_t.dmg_crit elseif self.cache[4] then - log._warning("ISS: high temperature!") + log.warning("ISS: high temperature!") status = iss_status_t.high_temp elseif self.cache[2] then - log._warning("ISS: heated coolant backup!") + log.warning("ISS: heated coolant backup!") status = iss_status_t.ex_hcoolant elseif self.cache[6] then - log._warning("ISS: no coolant!") + log.warning("ISS: no coolant!") status = iss_status_t.no_coolant elseif self.cache[3] then - log._warning("ISS: full waste!") + log.warning("ISS: full waste!") status = iss_status_t.ex_waste elseif self.cache[5] then - log._warning("ISS: no fuel!") + log.warning("ISS: no fuel!") status = iss_status_t.no_fuel elseif self.cache[7] then - log._warning("ISS: supervisor connection timeout!") + log.warning("ISS: supervisor connection timeout!") status = iss_status_t.timeout else self.tripped = false @@ -161,7 +164,7 @@ function iss_init(reactor) -- if a new trip occured... local first_trip = false if not was_tripped and status ~= iss_status_t.ok then - log._warning("ISS: reactor SCRAM") + log.warning("ISS: reactor SCRAM") first_trip = true self.tripped = true @@ -169,7 +172,7 @@ function iss_init(reactor) self.reactor.scram() if self.reactor.__p_is_faulted() then - log._error("ISS: failed reactor SCRAM") + log.error("ISS: failed reactor SCRAM") end end @@ -198,7 +201,7 @@ function iss_init(reactor) end -- reactor PLC communications -function comms_init(id, modem, local_port, server_port, reactor, iss) +plc.comms = function (id, modem, local_port, server_port, reactor, iss) local self = { id = id, seq_num = 0, @@ -355,7 +358,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) if not self.reactor.__p_is_faulted() then _send(RPLC_TYPES.MEK_STRUCT, mek_data) else - log._error("failed to send structure: PPM fault") + log.error("failed to send structure: PPM fault") end end @@ -417,7 +420,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) if not self.reactor.__p_is_faulted() then _send(RPLC_TYPES.STATUS, sys_status) else - log._error("failed to send status: PPM fault") + log.error("failed to send status: PPM fault") end end end @@ -463,7 +466,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) pkt = mgmt_pkt.get() end else - log._error("illegal packet type " .. s_pkt.protocol(), true) + log.error("illegal packet type " .. s_pkt.protocol(), true) end end @@ -477,7 +480,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) if self.r_seq_num == nil then self.r_seq_num = packet.scada_frame.seq_num() elseif self.linked and self.r_seq_num >= packet.scada_frame.seq_num() then - log._warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) return else self.r_seq_num = packet.scada_frame.seq_num() @@ -496,19 +499,19 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) local trip_time = util.time() - timestamp if trip_time > 500 then - log._warning("PLC KEEP_ALIVE trip time > 500ms (" .. trip_time .. ")") + log.warning("PLC KEEP_ALIVE trip time > 500ms (" .. trip_time .. ")") end - -- log._debug("RPLC RTT = ".. trip_time .. "ms") + -- log.debug("RPLC RTT = ".. trip_time .. "ms") _send_keep_alive_ack(timestamp) else - log._debug("RPLC keep alive packet length mismatch") + log.debug("RPLC keep alive packet length mismatch") end elseif packet.type == RPLC_TYPES.LINK_REQ then -- link request confirmation if packet.length == 1 then - log._debug("received unsolicited link request response") + log.debug("received unsolicited link request response") local link_ack = packet.data[1] @@ -516,31 +519,31 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) self.status_cache = nil _send_struct() send_status(plc_state.degraded) - log._debug("re-sent initial status data") + log.debug("re-sent initial status data") elseif link_ack == RPLC_LINKING.DENY then println_ts("received unsolicited link denial, unlinking") - log._debug("unsolicited RPLC link request denied") + log.debug("unsolicited RPLC link request denied") elseif link_ack == RPLC_LINKING.COLLISION then println_ts("received unsolicited link collision, unlinking") - log._warning("unsolicited RPLC link request collision") + log.warning("unsolicited RPLC link request collision") else println_ts("invalid unsolicited link response") - log._error("unsolicited unknown RPLC link request response") + log.error("unsolicited unknown RPLC link request response") end self.linked = link_ack == RPLC_LINKING.ALLOW else - log._debug("RPLC link req packet length mismatch") + log.debug("RPLC link req packet length mismatch") end elseif packet.type == RPLC_TYPES.STATUS then -- request of full status, clear cache first self.status_cache = nil send_status(plc_state.degraded) - log._debug("sent out status cache again, did supervisor miss it?") + log.debug("sent out status cache again, did supervisor miss it?") elseif packet.type == RPLC_TYPES.MEK_STRUCT then -- request for physical structure _send_struct() - log._debug("sent out structure again, did supervisor miss it?") + log.debug("sent out structure again, did supervisor miss it?") elseif packet.type == RPLC_TYPES.MEK_SCRAM then -- disable the reactor self.scrammed = true @@ -576,14 +579,14 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) _send_ack(packet.type, success) else - log._debug("RPLC set burn rate packet length mismatch") + log.debug("RPLC set burn rate packet length mismatch") end elseif packet.type == RPLC_TYPES.ISS_CLEAR then -- clear the ISS status iss.reset() _send_ack(packet.type, true) else - log._warning("received unknown RPLC packet type " .. packet.type) + log.warning("received unknown RPLC packet type " .. packet.type) end elseif packet.type == RPLC_TYPES.LINK_REQ then -- link request confirmation @@ -592,7 +595,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) if link_ack == RPLC_LINKING.ALLOW then println_ts("linked!") - log._debug("RPLC link request approved") + log.debug("RPLC link request approved") -- reset remote sequence number and cache self.r_seq_num = nil @@ -601,24 +604,24 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) _send_struct() send_status(plc_state.degraded) - log._debug("sent initial status data") + log.debug("sent initial status data") elseif link_ack == RPLC_LINKING.DENY then println_ts("link request denied, retrying...") - log._debug("RPLC link request denied") + log.debug("RPLC link request denied") elseif link_ack == RPLC_LINKING.COLLISION then println_ts("reactor PLC ID collision (check config), retrying...") - log._warning("RPLC link request collision") + log.warning("RPLC link request collision") else println_ts("invalid link response, bad channel? retrying...") - log._error("unknown RPLC link request response") + log.error("unknown RPLC link request response") end self.linked = link_ack == RPLC_LINKING.ALLOW else - log._debug("RPLC link req packet length mismatch") + log.debug("RPLC link req packet length mismatch") end else - log._debug("discarding non-link packet before linked") + log.debug("discarding non-link packet before linked") end elseif packet.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then -- handle session close @@ -626,9 +629,9 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) conn_watchdog.cancel() unlink() println_ts("server connection closed by remote host") - log._warning("server connection closed by remote host") + log.warning("server connection closed by remote host") else - log._warning("received unknown SCADA_MGMT packet type " .. packet.type) + log.warning("received unknown SCADA_MGMT packet type " .. packet.type) end end end @@ -652,3 +655,5 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) is_linked = is_linked } end + +return plc diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 21c8372..dde16e7 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -2,18 +2,16 @@ -- Reactor Programmable Logic Controller -- -os.loadAPI("scada-common/log.lua") -os.loadAPI("scada-common/types.lua") -os.loadAPI("scada-common/util.lua") -os.loadAPI("scada-common/ppm.lua") -os.loadAPI("scada-common/comms.lua") -os.loadAPI("scada-common/mqueue.lua") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") -os.loadAPI("config.lua") -os.loadAPI("plc.lua") -os.loadAPI("threads.lua") +local config = require("config") +local plc = require("plc") +local threads = require("threads") -local R_PLC_VERSION = "alpha-v0.5.2" +local R_PLC_VERSION = "alpha-v0.6.0" local print = util.print local println = util.println @@ -22,9 +20,9 @@ local println_ts = util.println_ts log.init(config.LOG_PATH, config.LOG_MODE) -log._info("========================================") -log._info("BOOTING reactor-plc.startup " .. R_PLC_VERSION) -log._info("========================================") +log.info("========================================") +log.info("BOOTING reactor-plc.startup " .. R_PLC_VERSION) +log.info("========================================") println(">> Reactor PLC " .. R_PLC_VERSION .. " <<") -- mount connected devices @@ -78,7 +76,7 @@ local plc_state = __shared_memory.plc_state -- we need a reactor and a modem if smem_dev.reactor == nil then println("boot> fission reactor not found"); - log._warning("no reactor on startup") + log.warning("no reactor on startup") plc_state.init_ok = false plc_state.degraded = true @@ -86,7 +84,7 @@ if smem_dev.reactor == nil then end if networked and smem_dev.modem == nil then println("boot> wireless modem not found") - log._warning("no wireless modem on startup") + log.warning("no wireless modem on startup") if smem_dev.reactor ~= nil then smem_dev.reactor.scram() @@ -104,19 +102,19 @@ function init() -- init internal safety system smem_sys.iss = plc.iss_init(smem_dev.reactor) - log._debug("iss init") + log.debug("iss init") if __shared_memory.networked then -- start comms - smem_sys.plc_comms = plc.comms_init(config.REACTOR_ID, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, smem_dev.reactor, smem_sys.iss) - log._debug("comms init") + smem_sys.plc_comms = plc.comms(config.REACTOR_ID, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, smem_dev.reactor, smem_sys.iss) + log.debug("comms init") -- comms watchdog, 3 second timeout smem_sys.conn_watchdog = util.new_watchdog(3) - log._debug("conn watchdog started") + log.debug("conn watchdog started") else println("boot> starting in offline mode"); - log._debug("running without networking") + log.debug("running without networking") end os.queueEvent("clock_start") @@ -124,7 +122,7 @@ function init() println("boot> completed"); else println("boot> system in degraded state, awaiting devices...") - log._warning("booted in a degraded state, awaiting peripheral connections...") + log.warning("booted in a degraded state, awaiting peripheral connections...") end end @@ -148,11 +146,11 @@ if __shared_memory.networked then if plc_state.init_ok then -- send status one last time after ISS shutdown - plc_comms.send_status(plc_state.degraded) - plc_comms.send_iss_status() + smem_sys.plc_comms.send_status(plc_state.degraded) + smem_sys.plc_comms.send_iss_status() -- close connection - plc_comms.close(conn_watchdog) + smem_sys.plc_comms.close(smem_sys.conn_watchdog) end else -- run threads, excluding comms @@ -160,4 +158,4 @@ else end println_ts("exited") -log._info("exited") +log.info("exited") diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 4ba1ba5..f689955 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -1,7 +1,9 @@ --- #REQUIRES comms.lua --- #REQUIRES log.lua --- #REQUIRES ppm.lua --- #REQUIRES util.lua +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") + +local threads = {} local print = util.print local println = util.println @@ -28,10 +30,10 @@ local MQ__COMM_CMD = { } -- main thread -function thread__main(smem, init) +threads.thread__main = function (smem, init) -- execute thread local exec = function () - log._debug("main thread init, clock inactive") + log.debug("main thread init, clock inactive") -- send status updates at 2Hz (every 10 server ticks) (every loop tick) -- send link requests at 0.5Hz (every 40 server ticks) (every 4 loop ticks) @@ -89,14 +91,14 @@ function thread__main(smem, init) if device.type == "fissionReactor" then println_ts("reactor disconnected!") - log._error("reactor disconnected!") + log.error("reactor disconnected!") plc_state.no_reactor = true plc_state.degraded = true elseif networked and device.type == "modem" then -- we only care if this is our wireless modem if device.dev == plc_dev.modem then println_ts("wireless modem disconnected!") - log._error("comms modem disconnected!") + log.error("comms modem disconnected!") plc_state.no_modem = true if plc_state.init_ok then @@ -106,7 +108,7 @@ function thread__main(smem, init) plc_state.degraded = true else - log._warning("non-comms modem disconnected") + log.warning("non-comms modem disconnected") end end elseif event == "peripheral" then @@ -120,7 +122,7 @@ function thread__main(smem, init) smem.q.mq_iss.push_command(MQ__ISS_CMD.SCRAM) println_ts("reactor reconnected.") - log._info("reactor reconnected.") + log.info("reactor reconnected.") plc_state.no_reactor = false if plc_state.init_ok then @@ -144,7 +146,7 @@ function thread__main(smem, init) end println_ts("wireless modem reconnected.") - log._info("comms modem reconnected.") + log.info("comms modem reconnected.") plc_state.no_modem = false -- determine if we are still in a degraded state @@ -152,7 +154,7 @@ function thread__main(smem, init) plc_state.degraded = false end else - log._info("wired modem reconnected.") + log.info("wired modem reconnected.") end end @@ -163,12 +165,12 @@ function thread__main(smem, init) elseif event == "clock_start" then -- start loop clock loop_clock = os.startTimer(MAIN_CLOCK) - log._debug("main thread clock started") + log.debug("main thread clock started") end -- check for termination request if event == "terminate" or ppm.should_terminate() then - log._info("terminate requested, main thread exiting") + log.info("terminate requested, main thread exiting") -- iss handles reactor shutdown plc_state.shutdown = true break @@ -180,10 +182,10 @@ function thread__main(smem, init) end -- ISS monitor thread -function thread__iss(smem) +threads.thread__iss = function (smem) -- execute thread local exec = function () - log._debug("iss thread start") + log.debug("iss thread start") -- load in from shared memory local networked = smem.networked @@ -257,17 +259,17 @@ function thread__iss(smem) plc_state.scram = true if reactor.scram() then println_ts("successful reactor SCRAM") - log._error("successful reactor SCRAM") + log.error("successful reactor SCRAM") else println_ts("failed reactor SCRAM") - log._error("failed reactor SCRAM") + log.error("failed reactor SCRAM") end elseif msg.message == MQ__ISS_CMD.TRIP_TIMEOUT then -- watchdog tripped plc_state.scram = true iss.trip_timeout() println_ts("server timeout") - log._warning("server timeout") + log.warning("server timeout") end elseif msg.qtype == mqueue.TYPE.DATA then -- received data @@ -282,19 +284,19 @@ function thread__iss(smem) -- check for termination request if plc_state.shutdown then -- safe exit - log._info("iss thread shutdown initiated") + log.info("iss thread shutdown initiated") if plc_state.init_ok then plc_state.scram = true reactor.scram() if reactor.__p_is_ok() then println_ts("reactor disabled") - log._info("iss thread reactor SCRAM OK") + log.info("iss thread reactor SCRAM OK") else println_ts("exiting, reactor failed to disable") - log._error("iss thread failed to SCRAM reactor on exit") + log.error("iss thread failed to SCRAM reactor on exit") end end - log._info("iss thread exiting") + log.info("iss thread exiting") break end @@ -307,10 +309,10 @@ function thread__iss(smem) end -- communications sender thread -function thread__comms_tx(smem) +threads.thread__comms_tx = function (smem) -- execute thread local exec = function () - log._debug("comms tx thread start") + log.debug("comms tx thread start") -- load in from shared memory local plc_state = smem.plc_state @@ -345,7 +347,7 @@ function thread__comms_tx(smem) -- check for termination request if plc_state.shutdown then - log._info("comms tx thread exiting") + log.info("comms tx thread exiting") break end @@ -358,10 +360,10 @@ function thread__comms_tx(smem) end -- communications handler thread -function thread__comms_rx(smem) +threads.thread__comms_rx = function (smem) -- execute thread local exec = function () - log._debug("comms rx thread start") + log.debug("comms rx thread start") -- load in from shared memory local plc_state = smem.plc_state @@ -397,7 +399,7 @@ function thread__comms_rx(smem) -- check for termination request if plc_state.shutdown then - log._info("comms rx thread exiting") + log.info("comms rx thread exiting") break end @@ -410,10 +412,10 @@ function thread__comms_rx(smem) end -- apply setpoints -function thread__setpoint_control(smem) +threads.thread__setpoint_control = function (smem) -- execute thread local exec = function () - log._debug("setpoint control thread start") + log.debug("setpoint control thread start") -- load in from shared memory local plc_state = smem.plc_state @@ -434,10 +436,10 @@ function thread__setpoint_control(smem) if not plc_state.scram then if math.abs(setpoints.burn_rate - last_sp_burn) <= 5 then -- update without ramp if <= 5 mB/t change - log._debug("setting burn rate directly to " .. setpoints.burn_rate .. "mB/t") + log.debug("setting burn rate directly to " .. setpoints.burn_rate .. "mB/t") reactor.setBurnRate(setpoints.burn_rate) else - log._debug("starting burn rate ramp from " .. last_sp_burn .. "mB/t to " .. setpoints.burn_rate .. "mB/t") + log.debug("starting burn rate ramp from " .. last_sp_burn .. "mB/t to " .. setpoints.burn_rate .. "mB/t") running = true end @@ -489,7 +491,7 @@ function thread__setpoint_control(smem) -- check for termination request if plc_state.shutdown then - log._info("setpoint control thread exiting") + log.info("setpoint control thread exiting") break end @@ -500,3 +502,5 @@ function thread__setpoint_control(smem) return { exec = exec } end + +return threads diff --git a/rtu/config.lua b/rtu/config.lua index 6ba5653..ec2b047 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -1,47 +1,49 @@ --- #REQUIRES rsio.lua +local rsio = require("scada-common.rsio") + +local config = {} -- port to send packets TO server -SERVER_PORT = 16000 +config.SERVER_PORT = 16000 -- port to listen to incoming packets FROM server -LISTEN_PORT = 15001 +config.LISTEN_PORT = 15001 -- log path -LOG_PATH = "/log.txt" +config.LOG_PATH = "/log.txt" -- log mode -- 0 = APPEND (adds to existing file on start) -- 1 = NEW (replaces existing file on start) -LOG_MODE = 0 +config.LOG_MODE = 0 -- RTU peripheral devices (named: side/network device name) -RTU_DEVICES = { +config.RTU_DEVICES = { { - name = "boiler_0", + name = "boiler_1", index = 1, for_reactor = 1 }, { - name = "turbine_0", + name = "turbine_1", index = 1, for_reactor = 1 } } -- RTU redstone interface definitions -RTU_REDSTONE = { +config.RTU_REDSTONE = { { for_reactor = 1, io = { { - channel = rsio.RS_IO.WASTE_PO, + channel = rsio.IO.WASTE_PO, side = "top", bundled_color = colors.blue, for_reactor = 1 }, { - channel = rsio.RS_IO.WASTE_PU, + channel = rsio.IO.WASTE_PU, side = "top", bundled_color = colors.cyan, for_reactor = 1 }, { - channel = rsio.RS_IO.WASTE_AM, + channel = rsio.IO.WASTE_AM, side = "top", bundled_color = colors.purple, for_reactor = 1 @@ -49,3 +51,5 @@ RTU_REDSTONE = { } } } + +return config diff --git a/rtu/dev/boiler_rtu.lua b/rtu/dev/boiler_rtu.lua index 861a34f..322c511 100644 --- a/rtu/dev/boiler_rtu.lua +++ b/rtu/dev/boiler_rtu.lua @@ -1,8 +1,10 @@ --- #REQUIRES rtu.lua +local rtu = require("rtu") -function new(boiler) +local boiler_rtu = {} + +boiler_rtu.new = function (boiler) local self = { - rtu = rtu.rtu_init(), + rtu = rtu.init_unit(), boiler = boiler } @@ -49,3 +51,5 @@ function new(boiler) rtu_interface = rtu_interface } end + +return boiler_rtu diff --git a/rtu/dev/boilerv_rtu.lua b/rtu/dev/boilerv_rtu.lua index c23b6e1..a609588 100644 --- a/rtu/dev/boilerv_rtu.lua +++ b/rtu/dev/boilerv_rtu.lua @@ -1,8 +1,10 @@ --- #REQUIRES rtu.lua +local rtu = require("rtu") -function new(boiler) +local boilerv_rtu = {} + +boilerv_rtu.new = function (boiler) local self = { - rtu = rtu.rtu_init(), + rtu = rtu.init_unit(), boiler = boiler } @@ -54,3 +56,5 @@ function new(boiler) rtu_interface = rtu_interface } end + +return boilerv_rtu diff --git a/rtu/dev/energymachine_rtu.lua b/rtu/dev/energymachine_rtu.lua index b3f004a..d2aee3f 100644 --- a/rtu/dev/energymachine_rtu.lua +++ b/rtu/dev/energymachine_rtu.lua @@ -1,8 +1,10 @@ --- #REQUIRES rtu.lua +local rtu = require("rtu") -function new(machine) +local energymachine_rtu = {} + +energymachine_rtu.new = function (machine) local self = { - rtu = rtu.rtu_init(), + rtu = rtu.init_unit(), machine = machine } @@ -31,3 +33,5 @@ function new(machine) rtu_interface = rtu_interface } end + +return energymachine_rtu diff --git a/rtu/dev/imatrix_rtu.lua b/rtu/dev/imatrix_rtu.lua index f646da2..12fd942 100644 --- a/rtu/dev/imatrix_rtu.lua +++ b/rtu/dev/imatrix_rtu.lua @@ -1,8 +1,10 @@ --- #REQUIRES rtu.lua +local rtu = require("rtu") -function new(imatrix) +local imatrix_rtu = {} + +imatrix_rtu.new = function (imatrix) local self = { - rtu = rtu.rtu_init(), + rtu = rtu.init_unit(), imatrix = imatrix } @@ -42,3 +44,5 @@ function new(imatrix) rtu_interface = rtu_interface } end + +return imatrix_rtu diff --git a/rtu/dev/redstone_rtu.lua b/rtu/dev/redstone_rtu.lua index d81cebb..163b749 100644 --- a/rtu/dev/redstone_rtu.lua +++ b/rtu/dev/redstone_rtu.lua @@ -1,13 +1,14 @@ --- #REQUIRES rtu.lua --- #REQUIRES rsio.lua --- note: this RTU makes extensive use of the programming concept of closures +local rtu = require("rtu") +local rsio = require("scada-common.rsio") + +local redstone_rtu = {} local digital_read = rsio.digital_read local digital_is_active = rsio.digital_is_active -function new() +redstone_rtu.new = function () local self = { - rtu = rtu.rtu_init() + rtu = rtu.init_unit() } local rtu_interface = function () @@ -91,3 +92,5 @@ function new() link_ao = link_ao } end + +return redstone_rtu diff --git a/rtu/dev/turbine_rtu.lua b/rtu/dev/turbine_rtu.lua index 7584270..1f1827f 100644 --- a/rtu/dev/turbine_rtu.lua +++ b/rtu/dev/turbine_rtu.lua @@ -1,8 +1,10 @@ --- #REQUIRES rtu.lua +local rtu = require("rtu") -function new(turbine) +local turbine_rtu = {} + +turbine_rtu.new = function (turbine) local self = { - rtu = rtu.rtu_init(), + rtu = rtu.init_unit(), turbine = turbine } @@ -44,3 +46,5 @@ function new(turbine) rtu_interface = rtu_interface } end + +return turbine_rtu diff --git a/rtu/dev/turbinev_rtu.lua b/rtu/dev/turbinev_rtu.lua index 9a38a55..2be532b 100644 --- a/rtu/dev/turbinev_rtu.lua +++ b/rtu/dev/turbinev_rtu.lua @@ -1,8 +1,10 @@ --- #REQUIRES rtu.lua +local rtu = require("rtu") -function new(turbine) +local turbinev_rtu = {} + +turbinev_rtu.new = function (turbine) local self = { - rtu = rtu.rtu_init(), + rtu = rtu.init_unit(), turbine = turbine } @@ -14,8 +16,8 @@ function new(turbine) -- none -- coils -- - self.rtu.connect_coil(function () self.turbine.incrementDumpingMode() end), function () end) - self.rtu.connect_coil(function () self.turbine.decrementDumpingMode() end), function () end) + self.rtu.connect_coil(function () self.turbine.incrementDumpingMode() end, function () end) + self.rtu.connect_coil(function () self.turbine.decrementDumpingMode() end, function () end) -- input registers -- -- multiblock properties @@ -54,3 +56,5 @@ function new(turbine) rtu_interface = rtu_interface } end + +return turbinev_rtu diff --git a/rtu/modbus.lua b/rtu/modbus.lua index 7ea6108..ac26fc1 100644 --- a/rtu/modbus.lua +++ b/rtu/modbus.lua @@ -1,10 +1,13 @@ --- #REQUIRES types.lua +local comms = require("scada-common.comms") +local types = require("scada-common.types") + +local modbus = {} local MODBUS_FCODE = types.MODBUS_FCODE local MODBUS_EXCODE = types.MODBUS_EXCODE -- new modbus comms handler object -function new(rtu_dev, use_parallel_read) +modbus.new = function (rtu_dev, use_parallel_read) local self = { rtu = rtu_dev, use_parallel = use_parallel_read @@ -401,3 +404,5 @@ function new(rtu_dev, use_parallel_read) reply__gw_unavailable = reply__gw_unavailable } end + +return modbus diff --git a/rtu/rtu.lua b/rtu/rtu.lua index b9a3061..9c22559 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -1,12 +1,15 @@ --- #REQUIRES comms.lua --- #REQUIRES modbus.lua --- #REQUIRES ppm.lua +local comms = require("scada-common.comms") +local ppm = require("scada-common.ppm") + +local modbus = require("modbus") + +local rtu = {} local PROTOCOLS = comms.PROTOCOLS local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local RTU_ADVERT_TYPES = comms.RTU_ADVERT_TYPES -function rtu_init() +rtu.init_unit = function () local self = { discrete_inputs = {}, coils = {}, @@ -117,7 +120,7 @@ function rtu_init() } end -function rtu_comms(modem, local_port, server_port) +rtu.comms = function (modem, local_port, server_port) local self = { seq_num = 0, r_seq_num = nil, @@ -187,7 +190,7 @@ function rtu_comms(modem, local_port, server_port) pkt = mgmt_pkt.get() end else - log._error("illegal packet type " .. s_pkt.protocol(), true) + log.error("illegal packet type " .. s_pkt.protocol(), true) end end @@ -203,7 +206,7 @@ function rtu_comms(modem, local_port, server_port) if self.r_seq_num == nil then self.r_seq_num = packet.scada_frame.seq_num() elseif rtu_state.linked and self.r_seq_num >= packet.scada_frame.seq_num() then - log._warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) return else self.r_seq_num = packet.scada_frame.seq_num() @@ -224,7 +227,7 @@ function rtu_comms(modem, local_port, server_port) -- immediately execute redstone RTU requests local return_code, reply = unit.modbus_io.handle_packet(packet) if not return_code then - log._warning("requested MODBUS operation failed") + log.warning("requested MODBUS operation failed") end else -- check validity then pass off to unit comms thread @@ -237,13 +240,13 @@ function rtu_comms(modem, local_port, server_port) unit.pkt_queue.push(packet) end else - log._warning("cannot perform requested MODBUS operation") + log.warning("cannot perform requested MODBUS operation") end end else -- unit ID out of range? reply = modbus.reply__gw_unavailable(packet) - log._error("MODBUS packet requesting non-existent unit") + log.error("MODBUS packet requesting non-existent unit") end send_modbus(reply) @@ -253,7 +256,7 @@ function rtu_comms(modem, local_port, server_port) -- close connection conn_watchdog.cancel() unlink(rtu_state) - if packet.type == SCADA_MGMT_TYPES.REMOTE_LINKED then + elseif packet.type == SCADA_MGMT_TYPES.REMOTE_LINKED then -- acknowledgement rtu_state.linked = true self.r_seq_num = nil @@ -262,11 +265,11 @@ function rtu_comms(modem, local_port, server_port) send_advertisement(units) else -- not supported - log._warning("RTU got unexpected SCADA message type " .. packet.type, true) + log.warning("RTU got unexpected SCADA message type " .. packet.type, true) end else -- should be unreachable assuming packet is from parse_packet() - log._error("illegal packet type " .. protocol, true) + log.error("illegal packet type " .. protocol, true) end end end @@ -337,3 +340,5 @@ function rtu_comms(modem, local_port, server_port) close = close } end + +return rtu diff --git a/rtu/startup.lua b/rtu/startup.lua index 697a528..4edcbc9 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -2,28 +2,27 @@ -- RTU: Remote Terminal Unit -- -os.loadAPI("scada-common/log.lua") -os.loadAPI("scada-common/types.lua") -os.loadAPI("scada-common/util.lua") -os.loadAPI("scada-common/ppm.lua") -os.loadAPI("scada-common/comms.lua") -os.loadAPI("scada-common/mqueue.lua") -os.loadAPI("scada-common/rsio.lua") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local ppm = require("scada-common.ppm") +local rsio = require("scada-common.rsio") +local types = require("scada-common.types") +local util = require("scada-common.util") -os.loadAPI("config.lua") -os.loadAPI("modbus.lua") -os.loadAPI("rtu.lua") -os.loadAPI("threads.lua") +local config = require("config") +local modbus = require("modbus") +local rtu = require("rtu") +local threads = require("threads") -os.loadAPI("dev/redstone_rtu.lua") -os.loadAPI("dev/boiler_rtu.lua") -os.loadAPI("dev/boilerv_rtu.lua") -os.loadAPI("dev/energymachine_rtu.lua") -os.loadAPI("dev/imatrix_rtu.lua") -os.loadAPI("dev/turbine_rtu.lua") -os.loadAPI("dev/turbinev_rtu.lua") +local redstone_rtu = require("dev.redstone_rtu") +local boiler_rtu = require("dev.boiler_rtu") +local boilerv_rtu = require("dev.boilerv_rtu") +local energymachine_rtu = require("dev.energymachine_rtu") +local imatrix_rtu = require("dev.imatrix_rtu") +local turbine_rtu = require("dev.turbine_rtu") +local turbinev_rtu = require("dev.turbinev_rtu") -local RTU_VERSION = "alpha-v0.5.0" +local RTU_VERSION = "alpha-v0.6.0" local rtu_t = types.rtu_t @@ -34,9 +33,9 @@ local println_ts = util.println_ts log.init(config.LOG_PATH, config.LOG_MODE) -log._info("========================================") -log._info("BOOTING rtu.startup " .. RTU_VERSION) -log._info("========================================") +log.info("========================================") +log.info("BOOTING rtu.startup " .. RTU_VERSION) +log.info("========================================") println(">> RTU " .. RTU_VERSION .. " <<") ---------------------------------------- @@ -77,11 +76,11 @@ local smem_sys = __shared_memory.rtu_sys -- get modem if smem_dev.modem == nil then println("boot> wireless modem not found") - log._warning("no wireless modem on startup") + log.warning("no wireless modem on startup") return end -smem_sys.rtu_comms = rtu.rtu_comms(smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT) +smem_sys.rtu_comms = rtu.comms(smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT) ---------------------------------------- -- interpret config and init units @@ -99,7 +98,7 @@ for reactor_idx = 1, #rtu_redstone do local capabilities = {} - log._debug("init> starting redstone RTU I/O linking for reactor " .. rtu_redstone[reactor_idx].for_reactor .. "...") + log.debug("init> starting redstone RTU I/O linking for reactor " .. rtu_redstone[reactor_idx].for_reactor .. "...") for i = 1, #io_table do local valid = false @@ -118,7 +117,7 @@ for reactor_idx = 1, #rtu_redstone do local message = "init> invalid redstone definition at index " .. i .. " in definition block #" .. reactor_idx .. " (for reactor " .. rtu_redstone[reactor_idx].for_reactor .. ")" println_ts(message) - log._warning(message) + log.warning(message) else -- link redstone in RTU local mode = rsio.get_io_mode(conf.channel) @@ -132,13 +131,13 @@ for reactor_idx = 1, #rtu_redstone do rs_rtu.link_ao(conf.channel, conf.side) else -- should be unreachable code, we already validated channels - log._error("init> fell through if chain attempting to identify IO mode", true) + log.error("init> fell through if chain attempting to identify IO mode", true) break end table.insert(capabilities, conf.channel) - log._debug("init> linked redstone " .. #capabilities .. ": " .. rsio.to_string(conf.channel) .. " (" .. conf.side .. + log.debug("init> linked redstone " .. #capabilities .. ": " .. rsio.to_string(conf.channel) .. " (" .. conf.side .. ") for reactor " .. rtu_redstone[reactor_idx].for_reactor) end end @@ -156,7 +155,7 @@ for reactor_idx = 1, #rtu_redstone do thread = nil }) - log._debug("init> initialized RTU unit #" .. #units .. ": redstone_io (redstone) [1] for reactor " .. rtu_redstone[reactor_idx].for_reactor) + log.debug("init> initialized RTU unit #" .. #units .. ": redstone_io (redstone) [1] for reactor " .. rtu_redstone[reactor_idx].for_reactor) end -- mounted peripherals @@ -166,7 +165,7 @@ for i = 1, #rtu_devices do if device == nil then local message = "init> '" .. rtu_devices[i].name .. "' not found" println_ts(message) - log._warning(message) + log.warning(message) else local type = ppm.get_type(rtu_devices[i].name) local rtu_iface = nil @@ -200,7 +199,7 @@ for i = 1, #rtu_devices do else local message = "init> device '" .. rtu_devices[i].name .. "' is not a known type (" .. type .. ")" println_ts(message) - log._warning(message) + log.warning(message) end if rtu_iface ~= nil then @@ -221,7 +220,7 @@ for i = 1, #rtu_devices do table.insert(units, rtu_unit) - log._debug("init> initialized RTU unit #" .. #units .. ": " .. rtu_devices[i].name .. " (" .. rtu_type .. ") [" .. + log.debug("init> initialized RTU unit #" .. #units .. ": " .. rtu_devices[i].name .. " (" .. rtu_type .. ") [" .. rtu_devices[i].index .. "] for reactor " .. rtu_devices[i].for_reactor) end end @@ -237,7 +236,7 @@ local comms_thread = threads.thread__comms(__shared_memory) -- start connection watchdog smem_sys.conn_watchdog = util.new_watchdog(5) -log._debug("init> conn watchdog started") +log.debug("init> conn watchdog started") -- assemble thread list local _threads = { main_thread.exec, comms_thread.exec } @@ -251,4 +250,4 @@ end parallel.waitForAll(table.unpack(_threads)) println_ts("exited") -log._info("exited") +log.info("exited") diff --git a/rtu/threads.lua b/rtu/threads.lua index 9cb2923..12f90f7 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -1,7 +1,22 @@ --- #REQUIRES comms.lua --- #REQUIRES log.lua --- #REQUIRES ppm.lua --- #REQUIRES util.lua +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") +local types = require("scada-common.types") +local util = require("scada-common.util") + +local redstone_rtu = require("dev.redstone_rtu") +local boiler_rtu = require("dev.boiler_rtu") +local boilerv_rtu = require("dev.boilerv_rtu") +local energymachine_rtu = require("dev.energymachine_rtu") +local imatrix_rtu = require("dev.imatrix_rtu") +local turbine_rtu = require("dev.turbine_rtu") +local turbinev_rtu = require("dev.turbinev_rtu") + +local modbus = require("modbus") + +local threads = {} + +local rtu_t = types.rtu_t local print = util.print local println = util.println @@ -14,10 +29,10 @@ local MAIN_CLOCK = 2 -- (2Hz, 40 ticks) local COMMS_SLEEP = 150 -- (150ms, 3 ticks) -- main thread -function thread__main(smem) +threads.thread__main = function (smem) -- execute thread local exec = function () - log._debug("main thread start") + log.debug("main thread start") -- advertisement/heartbeat clock local loop_clock = os.startTimer(MAIN_CLOCK) @@ -62,9 +77,9 @@ function thread__main(smem) -- we only care if this is our wireless modem if device.dev == rtu_dev.modem then println_ts("wireless modem disconnected!") - log._warning("comms modem disconnected!") + log.warning("comms modem disconnected!") else - log._warning("non-comms modem disconnected") + log.warning("non-comms modem disconnected") end else for i = 1, #units do @@ -88,9 +103,9 @@ function thread__main(smem) rtu_comms.reconnect_modem(rtu_dev.modem) println_ts("wireless modem reconnected.") - log._info("comms modem reconnected.") + log.info("comms modem reconnected.") else - log._info("wired modem reconnected.") + log.info("wired modem reconnected.") end else -- relink lost peripheral to correct unit entry @@ -102,11 +117,17 @@ function thread__main(smem) -- found, re-link unit.device = device - if unit.type == "boiler" then + if unit.type == rtu_t.boiler then unit.rtu = boiler_rtu.new(device) - elseif unit.type == "turbine" then + elseif unit.type == rtu_t.boiler_valve then + unit.rtu = boilerv_rtu.new(device) + elseif unit.type == rtu_t.turbine then unit.rtu = turbine_rtu.new(device) - elseif unit.type == "imatrix" then + elseif unit.type == rtu_t.turbine_valve then + unit.rtu = turbinev_rtu.new(device) + elseif unit.type == rtu_t.energy_machine then + unit.rtu = energymachine_rtu.new(device) + elseif unit.type == rtu_t.induction_matrix then unit.rtu = imatrix_rtu.new(device) end @@ -121,7 +142,7 @@ function thread__main(smem) -- check for termination request if event == "terminate" or ppm.should_terminate() then rtu_state.shutdown = true - log._info("terminate requested, main thread exiting") + log.info("terminate requested, main thread exiting") break end end @@ -131,10 +152,10 @@ function thread__main(smem) end -- communications handler thread -function thread__comms(smem) +threads.thread__comms = function (smem) -- execute thread local exec = function () - log._debug("comms thread start") + log.debug("comms thread start") -- load in from shared memory local rtu_state = smem.rtu_state @@ -169,8 +190,8 @@ function thread__comms(smem) -- check for termination request if rtu_state.shutdown then - rtu_comms.close() - log._info("comms thread exiting") + rtu_comms.close(rtu_state) + log.info("comms thread exiting") break end @@ -183,10 +204,10 @@ function thread__comms(smem) end -- per-unit communications handler thread -function thread__unit_comms(smem, unit) +threads.thread__unit_comms = function (smem, unit) -- execute thread local exec = function () - log._debug("rtu unit thread start -> " .. unit.name .. "(" .. unit.type .. ")") + log.debug("rtu unit thread start -> " .. unit.name .. "(" .. unit.type .. ")") -- load in from shared memory local rtu_state = smem.rtu_state @@ -219,7 +240,7 @@ function thread__unit_comms(smem, unit) -- check for termination request if rtu_state.shutdown then - log._info("rtu unit thread exiting -> " .. unit.name .. "(" .. unit.type .. ")") + log.info("rtu unit thread exiting -> " .. unit.name .. "(" .. unit.type .. ")") break end @@ -230,3 +251,5 @@ function thread__unit_comms(smem, unit) return { exec = exec } end + +return threads diff --git a/scada-common/alarm.lua b/scada-common/alarm.lua index e8464a5..7c39bc4 100644 --- a/scada-common/alarm.lua +++ b/scada-common/alarm.lua @@ -1,4 +1,6 @@ --- #REQUIRES util.lua +local util = require("scada-common.util") + +local alarm = {} SEVERITY = { INFO = 0, -- basic info message @@ -9,7 +11,27 @@ SEVERITY = { EMERGENCY = 5 -- critical safety alarm } -function scada_alarm(severity, device, message) +alarm.SEVERITY = SEVERITY + +alarm.severity_to_string = function (severity) + if severity == SEVERITY.INFO then + return "INFO" + elseif severity == SEVERITY.WARNING then + return "WARNING" + elseif severity == SEVERITY.ALERT then + return "ALERT" + elseif severity == SEVERITY.FACILITY then + return "FACILITY" + elseif severity == SEVERITY.SAFETY then + return "SAFETY" + elseif severity == SEVERITY.EMERGENCY then + return "EMERGENCY" + else + return "UNKNOWN" + end +end + +alarm.scada_alarm = function (severity, device, message) local self = { time = util.time(), ts_string = os.date("[%H:%M:%S]"), @@ -19,7 +41,7 @@ function scada_alarm(severity, device, message) } local format = function () - return self.ts_string .. " [" .. severity_to_string(self.severity) .. "] (" .. self.device ") >> " .. self.message + return self.ts_string .. " [" .. alarm.severity_to_string(self.severity) .. "] (" .. self.device ") >> " .. self.message end local properties = function () @@ -37,20 +59,4 @@ function scada_alarm(severity, device, message) } end -function severity_to_string(severity) - if severity == SEVERITY.INFO then - return "INFO" - elseif severity == SEVERITY.WARNING then - return "WARNING" - elseif severity == SEVERITY.ALERT then - return "ALERT" - elseif severity == SEVERITY.FACILITY then - return "FACILITY" - elseif severity == SEVERITY.SAFETY then - return "SAFETY" - elseif severity == SEVERITY.EMERGENCY then - return "EMERGENCY" - else - return "UNKNOWN" - end -end +return alarm diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 581e493..17050a8 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -1,4 +1,10 @@ -PROTOCOLS = { +-- +-- Communications +-- + +local comms = {} + +local PROTOCOLS = { MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol RPLC = 1, -- reactor PLC protocol SCADA_MGMT = 2, -- SCADA supervisor management, device advertisements, etc @@ -6,7 +12,7 @@ PROTOCOLS = { COORD_API = 4 -- data/control packets for pocket computers to/from coordinators } -RPLC_TYPES = { +local RPLC_TYPES = { KEEP_ALIVE = 0, -- keep alive packets LINK_REQ = 1, -- linking requests STATUS = 2, -- reactor/system status @@ -19,13 +25,13 @@ RPLC_TYPES = { ISS_CLEAR = 9 -- clear ISS trip (if in bad state, will trip immediately) } -RPLC_LINKING = { +local RPLC_LINKING = { ALLOW = 0, -- link approved DENY = 1, -- link denied COLLISION = 2 -- link denied due to existing active link } -SCADA_MGMT_TYPES = { +local SCADA_MGMT_TYPES = { PING = 0, -- generic ping CLOSE = 1, -- close a connection REMOTE_LINKED = 2, -- remote device linked @@ -33,15 +39,21 @@ SCADA_MGMT_TYPES = { RTU_HEARTBEAT = 4 -- RTU heartbeat } -RTU_ADVERT_TYPES = { +local RTU_ADVERT_TYPES = { BOILER = 0, -- boiler TURBINE = 1, -- turbine IMATRIX = 2, -- induction matrix REDSTONE = 3 -- redstone I/O } +comms.PROTOCOLS = PROTOCOLS +comms.RPLC_TYPES = RPLC_TYPES +comms.RPLC_LINKING = RPLC_LINKING +comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES +comms.RTU_ADVERT_TYPES = RTU_ADVERT_TYPES + -- generic SCADA packet object -function scada_packet() +comms.scada_packet = function () local self = { modem_msg_in = nil, valid = false, @@ -124,7 +136,7 @@ end -- MODBUS packet -- modeled after MODBUS TCP packet -function modbus_packet() +comms.modbus_packet = function () local self = { frame = nil, raw = nil, @@ -165,11 +177,11 @@ function modbus_packet() return size_ok else - log._debug("attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true) + log.debug("attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true) return false end else - log._debug("nil frame encountered", true) + log.debug("nil frame encountered", true) return false end end @@ -201,7 +213,7 @@ function modbus_packet() end -- reactor PLC packet -function rplc_packet() +comms.rplc_packet = function () local self = { frame = nil, raw = nil, @@ -256,11 +268,11 @@ function rplc_packet() return ok else - log._debug("attempted RPLC parse of incorrect protocol " .. frame.protocol(), true) + log.debug("attempted RPLC parse of incorrect protocol " .. frame.protocol(), true) return false end else - log._debug("nil frame encountered", true) + log.debug("nil frame encountered", true) return false end end @@ -291,7 +303,7 @@ function rplc_packet() end -- SCADA management packet -function mgmt_packet() +comms.mgmt_packet = function () local self = { frame = nil, raw = nil, @@ -339,11 +351,11 @@ function mgmt_packet() return ok else - log._debug("attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true) + log.debug("attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true) return false end else - log._debug("nil frame encountered", true) + log.debug("nil frame encountered", true) return false end end @@ -374,7 +386,7 @@ end -- SCADA coordinator packet -- @todo -function coord_packet() +comms.coord_packet = function () local self = { frame = nil, raw = nil, @@ -418,11 +430,11 @@ function coord_packet() return ok else - log._debug("attempted COORD_DATA parse of incorrect protocol " .. frame.protocol(), true) + log.debug("attempted COORD_DATA parse of incorrect protocol " .. frame.protocol(), true) return false end else - log._debug("nil frame encountered", true) + log.debug("nil frame encountered", true) return false end end @@ -453,7 +465,7 @@ end -- coordinator API (CAPI) packet -- @todo -function capi_packet() +comms.capi_packet = function () local self = { frame = nil, raw = nil, @@ -497,11 +509,11 @@ function capi_packet() return ok else - log._debug("attempted COORD_API parse of incorrect protocol " .. frame.protocol(), true) + log.debug("attempted COORD_API parse of incorrect protocol " .. frame.protocol(), true) return false end else - log._debug("nil frame encountered", true) + log.debug("nil frame encountered", true) return false end end @@ -529,3 +541,5 @@ function capi_packet() get = get } end + +return comms diff --git a/scada-common/log.lua b/scada-common/log.lua index 1aafda3..39069ca 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -2,14 +2,17 @@ -- File System Logger -- --- 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 = {} -MODE = { +-- we use extra short abbreviations since computer craft screens are very small + +local MODE = { APPEND = 0, NEW = 1 } +log.MODE = MODE + local LOG_DEBUG = true local log_path = "/log.txt" @@ -50,7 +53,7 @@ local _log = function (msg) end end -function init(path, write_mode) +log.init = function (path, write_mode) log_path = path mode = write_mode @@ -61,7 +64,7 @@ function init(path, write_mode) end end -function _debug(msg, trace) +log.debug = function (msg, trace) if LOG_DEBUG then local dbg_info = "" @@ -80,15 +83,15 @@ function _debug(msg, trace) end end -function _info(msg) +log.info = function (msg) _log("[INF] " .. msg) end -function _warning(msg) +log.warning = function (msg) _log("[WRN] " .. msg) end -function _error(msg, trace) +log.error = function (msg, trace) local dbg_info = "" if trace then @@ -105,6 +108,8 @@ function _error(msg, trace) _log("[ERR] " .. dbg_info .. msg) end -function _fatal(msg) +log.fatal = function (msg) _log("[FTL] " .. msg) end + +return log diff --git a/scada-common/mqueue.lua b/scada-common/mqueue.lua index dc3e47f..8ba14cd 100644 --- a/scada-common/mqueue.lua +++ b/scada-common/mqueue.lua @@ -2,13 +2,17 @@ -- Message Queue -- -TYPE = { +local mqueue = {} + +local TYPE = { COMMAND = 0, DATA = 1, PACKET = 2 } -function new() +mqueue.TYPE = TYPE + +mqueue.new = function () local queue = {} local length = function () @@ -57,3 +61,5 @@ function new() pop = pop } end + +return mqueue diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index f383bae..5e15724 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -1,10 +1,14 @@ --- #REQUIRES log.lua +local log = require("scada-common.log") -- -- Protected Peripheral Manager -- -ACCESS_FAULT = nil +local ppm = {} + +local ACCESS_FAULT = nil + +ppm.ACCESS_FAULT = ACCESS_FAULT ---------------------------- -- PRIVATE DATA/FUNCTIONS -- @@ -46,7 +50,7 @@ local peri_init = function (iface) _ppm_sys.faulted = true if not _ppm_sys.mute then - log._error("PPM: protected " .. key .. "() -> " .. result) + log.error("PPM: protected " .. key .. "() -> " .. result) end if result == "Terminated" then @@ -88,48 +92,48 @@ end -- REPORTING -- -- silence error prints -function disable_reporting() +ppm.disable_reporting = function () _ppm_sys.mute = true end -- allow error prints -function enable_reporting() +ppm.enable_reporting = function () _ppm_sys.mute = false end -- FAULT MEMORY -- -- enable automatically clearing fault flag -function enable_afc() +ppm.enable_afc = function () _ppm_sys.auto_cf = true end -- disable automatically clearing fault flag -function disable_afc() +ppm.disable_afc = function () _ppm_sys.auto_cf = false end -- check fault flag -function is_faulted() +ppm.is_faulted = function () return _ppm_sys.faulted end -- clear fault flag -function clear_fault() +ppm.clear_fault = function () _ppm_sys.faulted = false end -- TERMINATION -- -- if a caught error was a termination request -function should_terminate() +ppm.should_terminate = function () return _ppm_sys.terminate end -- MOUNTING -- -- mount all available peripherals (clears mounts first) -function mount_all() +ppm.mount_all = function () local ifaces = peripheral.getNames() _ppm_sys.mounts = {} @@ -137,23 +141,23 @@ function mount_all() for i = 1, #ifaces do _ppm_sys.mounts[ifaces[i]] = peri_init(ifaces[i]) - log._info("PPM: found a " .. _ppm_sys.mounts[ifaces[i]].type .. " (" .. ifaces[i] .. ")") + log.info("PPM: found a " .. _ppm_sys.mounts[ifaces[i]].type .. " (" .. ifaces[i] .. ")") end if #ifaces == 0 then - log._warning("PPM: mount_all() -> no devices found") + log.warning("PPM: mount_all() -> no devices found") end end -- mount a particular device -function mount(iface) +ppm.mount = function (iface) local ifaces = peripheral.getNames() local pm_dev = nil local pm_type = nil for i = 1, #ifaces do if iface == ifaces[i] then - log._info("PPM: mount(" .. iface .. ") -> found a " .. peripheral.getType(iface)) + log.info("PPM: mount(" .. iface .. ") -> found a " .. peripheral.getType(iface)) _ppm_sys.mounts[iface] = peri_init(iface) @@ -167,15 +171,15 @@ function mount(iface) end -- handle peripheral_detach event -function handle_unmount(iface) +ppm.handle_unmount = function (iface) -- what got disconnected? local lost_dev = _ppm_sys.mounts[iface] if lost_dev then local type = lost_dev.type - log._warning("PPM: lost device " .. type .. " mounted to " .. iface) + log.warning("PPM: lost device " .. type .. " mounted to " .. iface) else - log._error("PPM: lost device unknown to the PPM mounted to " .. iface) + log.error("PPM: lost device unknown to the PPM mounted to " .. iface) end return lost_dev @@ -184,31 +188,31 @@ end -- GENERAL ACCESSORS -- -- list all available peripherals -function list_avail() +ppm.list_avail = function () return peripheral.getNames() end -- list mounted peripherals -function list_mounts() +ppm.list_mounts = function () return _ppm_sys.mounts end -- get a mounted peripheral by side/interface -function get_periph(iface) +ppm.get_periph = function (iface) if _ppm_sys.mounts[iface] then return _ppm_sys.mounts[iface].dev else return nil end end -- get a mounted peripheral type by side/interface -function get_type(iface) +ppm.get_type = function (iface) if _ppm_sys.mounts[iface] then return _ppm_sys.mounts[iface].type else return nil end end -- get all mounted peripherals by type -function get_all_devices(name) +ppm.get_all_devices = function (name) local devices = {} for side, data in pairs(_ppm_sys.mounts) do @@ -221,7 +225,7 @@ function get_all_devices(name) end -- get a mounted peripheral by type (if multiple, returns the first) -function get_device(name) +ppm.get_device = function (name) local device = nil for side, data in pairs(_ppm_sys.mounts) do @@ -237,12 +241,12 @@ end -- SPECIFIC DEVICE ACCESSORS -- -- get the fission reactor (if multiple, returns the first) -function get_fission_reactor() - return get_device("fissionReactor") +ppm.get_fission_reactor = function () + return ppm.get_device("fissionReactor") end -- get the wireless modem (if multiple, returns the first) -function get_wireless_modem() +ppm.get_wireless_modem = function () local w_modem = nil for side, device in pairs(_ppm_sys.mounts) do @@ -256,6 +260,8 @@ function get_wireless_modem() end -- list all connected monitors -function list_monitors() - return get_all_devices("monitor") +ppm.list_monitors = function () + return ppm.get_all_devices("monitor") end + +return ppm diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index f56a8a4..fd71247 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -1,21 +1,27 @@ -IO_LVL = { +-- +-- Redstone I/O +-- + +local rsio = {} + +local IO_LVL = { LOW = 0, HIGH = 1 } -IO_DIR = { +local IO_DIR = { IN = 0, OUT = 1 } -IO_MODE = { +local IO_MODE = { DIGITAL_OUT = 0, DIGITAL_IN = 1, ANALOG_OUT = 2, ANALOG_IN = 3 } -RS_IO = { +local RS_IO = { -- digital inputs -- -- facility @@ -53,7 +59,12 @@ RS_IO = { A_T_FLOW_RATE = 21 -- turbine flow rate percentage } -function to_string(channel) +rsio.IO_LVL = IO_LVL +rsio.IO_DIR = IO_DIR +rsio.IO_MODE = IO_MODE +rsio.IO = RS_IO + +rsio.to_string = function (channel) local names = { "F_SCRAM", "F_AE2_LIVE", @@ -85,11 +96,11 @@ function to_string(channel) end end -function is_valid_channel(channel) +rsio.is_valid_channel = function (channel) return channel ~= nil and channel > 0 and channel <= RS_IO.A_T_FLOW_RATE end -function is_valid_side(side) +rsio.is_valid_side = function (side) if side ~= nil then for _, s in pairs(rs.getSides()) do if s == side then return true end @@ -98,7 +109,7 @@ function is_valid_side(side) return false end -function is_color(color) +rsio.is_color = function (color) return (color > 0) and (bit.band(color, (color - 1)) == 0); end @@ -149,7 +160,7 @@ local RS_DIO_MAP = { { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT } } -function get_io_mode(channel) +rsio.get_io_mode = function (channel) local modes = { IO_MODE.DIGITAL_IN, -- F_SCRAM IO_MODE.DIGITAL_IN, -- F_AE2_LIVE @@ -182,7 +193,7 @@ function get_io_mode(channel) end -- get digital IO level reading -function digital_read(rs_value) +rsio.digital_read = function (rs_value) if rs_value then return IO_LVL.HIGH else @@ -191,7 +202,7 @@ function digital_read(rs_value) end -- returns the level corresponding to active -function digital_write(channel, active) +rsio.digital_write = function (channel, active) if channel < RS_IO.WASTE_PO or channel > RS_IO.R_PLC_TIMEOUT then return IO_LVL.LOW else @@ -200,10 +211,12 @@ function digital_write(channel, active) end -- returns true if the level corresponds to active -function digital_is_active(channel, level) +rsio.digital_is_active = function (channel, level) if channel > RS_IO.R_ENABLE or channel > RS_IO.R_PLC_TIMEOUT then return false else return RS_DIO_MAP[channel]._f(level) end end + +return rsio diff --git a/scada-common/types.lua b/scada-common/types.lua index 8ae93cc..5346bca 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -1,6 +1,10 @@ +-- -- Global Types +-- -rtu_t = { +local types = {} + +types.rtu_t = { redstone = "redstone", boiler = "boiler", boiler_valve = "boiler_valve", @@ -10,7 +14,7 @@ rtu_t = { induction_matrix = "induction_matrix" } -iss_status_t = { +types.iss_status_t = { ok = "ok", dmg_crit = "dmg_crit", ex_hcoolant = "heated_coolant_backup", @@ -24,7 +28,7 @@ iss_status_t = { -- MODBUS -- modbus function codes -local MODBUS_FCODE = { +types.MODBUS_FCODE = { READ_COILS = 0x01, READ_DISCRETE_INPUTS = 0x02, READ_MUL_HOLD_REGS = 0x03, @@ -37,7 +41,7 @@ local MODBUS_FCODE = { } -- modbus exception codes -local MODBUS_EXCODE = { +types.MODBUS_EXCODE = { ILLEGAL_FUNCTION = 0x01, ILLEGAL_DATA_ADDR = 0x02, ILLEGAL_DATA_VALUE = 0x03, @@ -49,3 +53,5 @@ local MODBUS_EXCODE = { GATEWAY_PATH_UNAVAILABLE = 0x0A, GATEWAY_TARGET_TIMEOUT = 0x0B } + +return types diff --git a/scada-common/util.lua b/scada-common/util.lua index a226e9f..a963a08 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -1,70 +1,69 @@ +local util = {} + -- PRINT -- --- we are overwriting 'print' so save it first -local _print = print - -- print -function print(message) +util.print = function (message) term.write(message) end -- print line -function println(message) - _print(message) +util.println = function (message) + print(message) end -- timestamped print -function print_ts(message) +util.print_ts = function (message) term.write(os.date("[%H:%M:%S] ") .. message) end -- timestamped print line -function println_ts(message) - _print(os.date("[%H:%M:%S] ") .. message) +util.println_ts = function (message) + print(os.date("[%H:%M:%S] ") .. message) end -- TIME -- -function time_ms() +util.time_ms = function () return os.epoch('local') end -function time_s() +util.time_s = function () return os.epoch('local') / 1000 end -function time() - return time_ms() +util.time = function () + return util.time_ms() end -- PARALLELIZATION -- -- protected sleep call so we still are in charge of catching termination -function psleep(t) +util.psleep = function (t) pcall(os.sleep, t) end -- no-op to provide a brief pause (and a yield) -- EVENT_CONSUMER: this function consumes events -function nop() - psleep(0.05) +util.nop = function () + util.psleep(0.05) end -- attempt to maintain a minimum loop timing (duration of execution) -function adaptive_delay(target_timing, last_update) - local sleep_for = target_timing - (time() - last_update) +util.adaptive_delay = function (target_timing, last_update) + local sleep_for = target_timing - (util.time() - last_update) -- only if >50ms since worker loops already yield 0.05s if sleep_for >= 50 then - psleep(sleep_for / 1000.0) + util.psleep(sleep_for / 1000.0) end - return time() + return util.time() end -- WATCHDOG -- -- ComputerCraft OS Timer based Watchdog -- triggers a timer event if not fed within 'timeout' seconds -function new_watchdog(timeout) +util.new_watchdog = function (timeout) local self = { _timeout = timeout, _wd_timer = os.startTimer(timeout) @@ -93,3 +92,5 @@ function new_watchdog(timeout) cancel = cancel } end + +return util diff --git a/supervisor/config.lua b/supervisor/config.lua index b8ba7fa..734e820 100644 --- a/supervisor/config.lua +++ b/supervisor/config.lua @@ -1,12 +1,16 @@ +local config = {} + -- scada network listen for PLC's and RTU's -SCADA_DEV_LISTEN = 16000 +config.SCADA_DEV_LISTEN = 16000 -- listen port for SCADA supervisor access by coordinators -SCADA_SV_LISTEN = 16100 +config.SCADA_SV_LISTEN = 16100 -- expected number of reactors -NUM_REACTORS = 4 +config.NUM_REACTORS = 4 -- log path -LOG_PATH = "/log.txt" +config.LOG_PATH = "/log.txt" -- log mode -- 0 = APPEND (adds to existing file on start) -- 1 = NEW (replaces existing file on start) -LOG_MODE = 0 +config.LOG_MODE = 0 + +return config diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index e69de29..afa28c0 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -0,0 +1,3 @@ +local coordinator = {} + +return coordinator diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 09aa033..ee11819 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -1,7 +1,9 @@ --- #REQUIRES mqueue.lua --- #REQUIRES comms.lua --- #REQUIRES log.lua --- #REQUIRES util.lua +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local util = require("scada-common.util") + +local plc = {} local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES @@ -16,19 +18,21 @@ local println_ts = util.println_ts local INITIAL_WAIT = 1500 local RETRY_PERIOD = 1000 -PLC_S_CMDS = { +local PLC_S_CMDS = { SCRAM = 0, ENABLE = 1, BURN_RATE = 2, ISS_CLEAR = 3 } +plc.PLC_S_CMDS = PLC_S_CMDS + local PERIODICS = { KEEP_ALIVE = 2.0 } -- PLC supervisor session -function new_session(id, for_reactor, in_queue, out_queue) +plc.new_session = function (id, for_reactor, in_queue, out_queue) local log_header = "plc_session(" .. id .. "): " local self = { @@ -204,7 +208,7 @@ function new_session(id, for_reactor, in_queue, out_queue) if pkt.length == 1 then return pkt.data[1] else - log._warning(log_header .. "RPLC ACK length mismatch") + log.warning(log_header .. "RPLC ACK length mismatch") return nil end end @@ -215,7 +219,7 @@ function new_session(id, for_reactor, in_queue, out_queue) if self.r_seq_num == nil then self.r_seq_num = pkt.scada_frame.seq_num() elseif self.r_seq_num >= pkt.scada_frame.seq_num() then - log._warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else self.r_seq_num = pkt.scada_frame.seq_num() @@ -225,7 +229,7 @@ function new_session(id, for_reactor, in_queue, out_queue) if pkt.scada_frame.protocol() == PROTOCOLS.RPLC then -- check reactor ID if pkt.id ~= for_reactor then - log._warning(log_header .. "RPLC packet with ID not matching reactor ID: reactor " .. self.for_reactor .. " != " .. pkt.id) + log.warning(log_header .. "RPLC packet with ID not matching reactor ID: reactor " .. self.for_reactor .. " != " .. pkt.id) return end @@ -242,13 +246,13 @@ function new_session(id, for_reactor, in_queue, out_queue) self.last_rtt = srv_now - srv_start if self.last_rtt > 500 then - log._warning(log_header .. "PLC KEEP_ALIVE round trip time > 500ms (" .. self.last_rtt .. ")") + log.warning(log_header .. "PLC KEEP_ALIVE round trip time > 500ms (" .. self.last_rtt .. ")") end - -- log._debug(log_header .. "RPLC RTT = ".. self.last_rtt .. "ms") - -- log._debug(log_header .. "RPLC TT = ".. (srv_now - plc_send) .. "ms") + -- log.debug(log_header .. "RPLC RTT = ".. self.last_rtt .. "ms") + -- log.debug(log_header .. "RPLC TT = ".. (srv_now - plc_send) .. "ms") else - log._debug(log_header .. "RPLC keep alive packet length mismatch") + log.debug(log_header .. "RPLC keep alive packet length mismatch") end elseif pkt.type == RPLC_TYPES.STATUS then -- status packet received, update data @@ -267,11 +271,11 @@ function new_session(id, for_reactor, in_queue, out_queue) self.received_status_cache = true else -- error copying status data - log._error(log_header .. "failed to parse status packet data") + log.error(log_header .. "failed to parse status packet data") end end else - log._debug(log_header .. "RPLC status packet length mismatch") + log.debug(log_header .. "RPLC status packet length mismatch") end elseif pkt.type == RPLC_TYPES.MEK_STRUCT then -- received reactor structure, record it @@ -282,10 +286,10 @@ function new_session(id, for_reactor, in_queue, out_queue) self.received_struct = true else -- error copying structure data - log._error(log_header .. "failed to parse struct packet data") + log.error(log_header .. "failed to parse struct packet data") end else - log._debug(log_header .. "RPLC struct packet length mismatch") + log.debug(log_header .. "RPLC struct packet length mismatch") end elseif pkt.type == RPLC_TYPES.MEK_SCRAM then -- SCRAM acknowledgement @@ -294,7 +298,7 @@ function new_session(id, for_reactor, in_queue, out_queue) self.acks.scram = true self.sDB.control_state = false elseif ack == false then - log._debug(log_header .. "SCRAM failed!") + log.debug(log_header .. "SCRAM failed!") end elseif pkt.type == RPLC_TYPES.MEK_ENABLE then -- enable acknowledgement @@ -303,7 +307,7 @@ function new_session(id, for_reactor, in_queue, out_queue) self.acks.enable = true self.sDB.control_state = true elseif ack == false then - log._debug(log_header .. "enable failed!") + log.debug(log_header .. "enable failed!") end elseif pkt.type == RPLC_TYPES.MEK_BURN_RATE then -- burn rate acknowledgement @@ -311,7 +315,7 @@ function new_session(id, for_reactor, in_queue, out_queue) if ack then self.acks.burn_rate = true elseif ack == false then - log._debug(log_header .. "burn rate update failed!") + log.debug(log_header .. "burn rate update failed!") end elseif pkt.type == RPLC_TYPES.ISS_STATUS then -- ISS status packet received, copy data @@ -321,10 +325,10 @@ function new_session(id, for_reactor, in_queue, out_queue) -- copied in ISS status data OK else -- error copying ISS status data - log._error(log_header .. "failed to parse ISS status packet data") + log.error(log_header .. "failed to parse ISS status packet data") end else - log._debug(log_header .. "RPLC ISS status packet length mismatch") + log.debug(log_header .. "RPLC ISS status packet length mismatch") end elseif pkt.type == RPLC_TYPES.ISS_ALARM then -- ISS alarm @@ -337,10 +341,10 @@ function new_session(id, for_reactor, in_queue, out_queue) -- copied in ISS status data OK else -- error copying ISS status data - log._error(log_header .. "failed to parse ISS alarm status data") + log.error(log_header .. "failed to parse ISS alarm status data") end else - log._debug(log_header .. "RPLC ISS alarm packet length mismatch") + log.debug(log_header .. "RPLC ISS alarm packet length mismatch") end elseif pkt.type == RPLC_TYPES.ISS_CLEAR then -- ISS clear acknowledgement @@ -350,17 +354,17 @@ function new_session(id, for_reactor, in_queue, out_queue) self.sDB.iss_tripped = false self.sDB.iss_trip_cause = "ok" elseif ack == false then - log._debug(log_header .. "ISS clear failed") + log.debug(log_header .. "ISS clear failed") end else - log._debug(log_header .. "handler received unsupported RPLC packet type " .. pkt.type) + log.debug(log_header .. "handler received unsupported RPLC packet type " .. pkt.type) end elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then if pkt.type == SCADA_MGMT_TYPES.CLOSE then -- close the session self.connected = false else - log._debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) + log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) end end end @@ -402,7 +406,7 @@ function new_session(id, for_reactor, in_queue, out_queue) self.connected = false _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) println("connection to reactor " .. self.for_reactor .. " PLC closed by server") - log._info(log_header .. "session closed by server") + log.info(log_header .. "session closed by server") end -- iterate the session @@ -454,7 +458,7 @@ function new_session(id, for_reactor, in_queue, out_queue) -- max 100ms spent processing queue if util.time() - handle_start > 100 then - log._warning(log_header .. "exceeded 100ms queue process limit") + log.warning(log_header .. "exceeded 100ms queue process limit") break end end @@ -463,7 +467,7 @@ function new_session(id, for_reactor, in_queue, out_queue) if not self.connected then self.plc_conn_watchdog.cancel() println("connection to reactor " .. self.for_reactor .. " PLC closed by remote host") - log._info(log_header .. "session closed by remote host") + log.info(log_header .. "session closed by remote host") return self.connected end @@ -559,3 +563,5 @@ function new_session(id, for_reactor, in_queue, out_queue) iterate = iterate } end + +return plc diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index e69de29..9051425 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -0,0 +1,3 @@ +local rtu = {} + +return rtu diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 6612730..578c8ae 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -1,14 +1,22 @@ --- #REQUIRES mqueue.lua --- #REQUIRES log.lua +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") + +local coordinator = require("session.coordinator") +local plc = require("session.plc") +local rtu = require("session.rtu") -- Supervisor Sessions Handler -SESSION_TYPE = { +local svsessions = {} + +local SESSION_TYPE = { RTU_SESSION = 0, PLC_SESSION = 1, COORD_SESSION = 2 } +svsessions.SESSION_TYPE = SESSION_TYPE + local self = { modem = nil, num_reactors = 0, @@ -20,12 +28,97 @@ local self = { next_coord_id = 0 } -function link_modem(modem) +-- PRIVATE FUNCTIONS -- + +-- iterate all the given sessions +local function _iterate(sessions) + for i = 1, #sessions do + local session = sessions[i] + if session.open then + local ok = session.instance.iterate() + if ok then + -- send packets in out queue + while session.out_queue.ready() do + local msg = session.out_queue.pop() + if msg.qtype == mqueue.TYPE.PACKET then + self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) + end + end + else + session.open = false + end + end + end +end + +-- cleanly close a session +local function _shutdown(session) + session.open = false + session.instance.close() + + -- send packets in out queue (namely the close packet) + while session.out_queue.ready() do + local msg = session.out_queue.pop() + if msg.qtype == mqueue.TYPE.PACKET then + self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) + end + end + + log.debug("closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) +end + +-- close connections +local function _close(sessions) + for i = 1, #sessions do + local session = sessions[i] + if session.open then + _shutdown(session) + end + end +end + +-- check if a watchdog timer event matches that of one of the provided sessions +local function _check_watchdogs(sessions, timer_event) + for i = 1, #sessions do + local session = sessions[i] + if session.open then + local triggered = session.instance.check_wd(timer_event) + if triggered then + log.debug("watchdog closing session " .. session.instance.get_id() .. " on remote port " .. session.r_port .. "...") + _shutdown(session) + end + end + end +end + +-- delete any closed sessions +local function _free_closed(sessions) + local move_to = 1 + for i = 1, #sessions do + local session = sessions[i] + if session ~= nil then + if sessions[i].open then + if sessions[move_to] == nil then + sessions[move_to] = session + sessions[i] = nil + end + move_to = move_to + 1 + else + log.debug("free'ing closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) + sessions[i] = nil + end + end + end +end + +-- PUBLIC FUNCTIONS -- + +svsessions.link_modem = function (modem) self.modem = modem end -- find a session by the remote port -function find_session(remote_port) +svsessions.find_session = function (remote_port) -- check RTU sessions for i = 1, #self.rtu_sessions do if self.rtu_sessions[i].r_port == remote_port then @@ -51,7 +144,7 @@ function find_session(remote_port) end -- get a session by reactor ID -function get_reactor_session(reactor) +svsessions.get_reactor_session = function (reactor) local session = nil for i = 1, #self.plc_sessions do @@ -64,8 +157,8 @@ function get_reactor_session(reactor) end -- establish a new PLC session -function establish_plc_session(local_port, remote_port, for_reactor) - if get_reactor_session(for_reactor) == nil then +svsessions.establish_plc_session = function (local_port, remote_port, for_reactor) + if svsessions.get_reactor_session(for_reactor) == nil then local plc_s = { open = true, reactor = for_reactor, @@ -79,7 +172,7 @@ function establish_plc_session(local_port, remote_port, for_reactor) plc_s.instance = plc.new_session(self.next_plc_id, for_reactor, plc_s.in_queue, plc_s.out_queue) table.insert(self.plc_sessions, plc_s) - log._debug("established new PLC session to " .. remote_port .. " with ID " .. self.next_plc_id) + log.debug("established new PLC session to " .. remote_port .. " with ID " .. self.next_plc_id) self.next_plc_id = self.next_plc_id + 1 @@ -91,38 +184,8 @@ function establish_plc_session(local_port, remote_port, for_reactor) end end --- cleanly close a session -local function _shutdown(session) - session.open = false - session.instance.close() - - -- send packets in out queue (namely the close packet) - while session.out_queue.ready() do - local msg = session.out_queue.pop() - if msg.qtype == mqueue.TYPE.PACKET then - self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) - end - end - - log._debug("closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) -end - --- check if a watchdog timer event matches that of one of the provided sessions -local function _check_watchdogs(sessions, timer_event) - for i = 1, #sessions do - local session = sessions[i] - if session.open then - local triggered = session.instance.check_wd(timer_event) - if triggered then - log._debug("watchdog closing session " .. session.instance.get_id() .. " on remote port " .. session.r_port .. "...") - _shutdown(session) - end - end - end -end - -- attempt to identify which session's watchdog timer fired -function check_all_watchdogs(timer_event) +svsessions.check_all_watchdogs = function (timer_event) -- check RTU session watchdogs _check_watchdogs(self.rtu_sessions, timer_event) @@ -133,29 +196,8 @@ function check_all_watchdogs(timer_event) _check_watchdogs(self.coord_sessions, timer_event) end --- iterate all the given sessions -local function _iterate(sessions) - for i = 1, #sessions do - local session = sessions[i] - if session.open then - local ok = session.instance.iterate() - if ok then - -- send packets in out queue - while session.out_queue.ready() do - local msg = session.out_queue.pop() - if msg.qtype == mqueue.TYPE.PACKET then - self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) - end - end - else - session.open = false - end - end - end -end - -- iterate all sessions -function iterate_all() +svsessions.iterate_all = function () -- iterate RTU sessions _iterate(self.rtu_sessions) @@ -166,28 +208,8 @@ function iterate_all() _iterate(self.coord_sessions) end --- delete any closed sessions -local function _free_closed(sessions) - local move_to = 1 - for i = 1, #sessions do - local session = sessions[i] - if session ~= nil then - if sessions[i].open then - if sessions[move_to] == nil then - sessions[move_to] = session - sessions[i] = nil - end - move_to = move_to + 1 - else - log._debug("free'ing closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) - sessions[i] = nil - end - end - end -end - -- delete all closed sessions -function free_all_closed() +svsessions.free_all_closed = function () -- free closed RTU sessions _free_closed(self.rtu_sessions) @@ -198,23 +220,15 @@ function free_all_closed() _free_closed(self.coord_sessions) end --- close connections -local function _close(sessions) - for i = 1, #sessions do - local session = sessions[i] - if session.open then - _shutdown(session) - end - end -end - -- close all open connections -function close_all() +svsessions.close_all = function () -- close sessions _close(self.rtu_sessions) _close(self.plc_sessions) _close(self.coord_sessions) -- free sessions - free_all_closed() + svsessions.free_all_closed() end + +return svsessions diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 53f6cfc..3059bab 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -2,23 +2,19 @@ -- Nuclear Generation Facility SCADA Supervisor -- -os.loadAPI("scada-common/log.lua") -os.loadAPI("scada-common/types.lua") -os.loadAPI("scada-common/util.lua") -os.loadAPI("scada-common/ppm.lua") -os.loadAPI("scada-common/comms.lua") -os.loadAPI("scada-common/mqueue.lua") +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") -os.loadAPI("config.lua") +local coordinator = require("session.coordinator") +local plc = require("session.plc") +local rtu = require("session.rtu") +local svsessions = require("session.svsessions") -os.loadAPI("session/rtu.lua") -os.loadAPI("session/plc.lua") -os.loadAPI("session/coordinator.lua") -os.loadAPI("session/svsessions.lua") +local config = require("config") +local supervisor = require("supervisor") -os.loadAPI("supervisor.lua") - -local SUPERVISOR_VERSION = "alpha-v0.2.0" +local SUPERVISOR_VERSION = "alpha-v0.3.0" local print = util.print local println = util.println @@ -27,9 +23,9 @@ local println_ts = util.println_ts log.init(config.LOG_PATH, config.LOG_MODE) -log._info("========================================") -log._info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION) -log._info("========================================") +log.info("========================================") +log.info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION) +log.info("========================================") println(">> SCADA Supervisor " .. SUPERVISOR_VERSION .. " <<") -- mount connected devices @@ -38,12 +34,12 @@ ppm.mount_all() local modem = ppm.get_wireless_modem() if modem == nil then println("boot> wireless modem not found") - log._warning("no wireless modem on startup") + log.warning("no wireless modem on startup") return end -- start comms, open all channels -local superv_comms = supervisor.superv_comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN) +local superv_comms = supervisor.comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN) -- base loop clock (6.67Hz, 3 ticks) local MAIN_CLOCK = 0.15 @@ -61,9 +57,9 @@ while true do -- we only care if this is our wireless modem if device.dev == modem then println_ts("wireless modem disconnected!") - log._error("comms modem disconnected!") + log.error("comms modem disconnected!") else - log._warning("non-comms modem disconnected") + log.warning("non-comms modem disconnected") end end elseif event == "peripheral" then @@ -76,9 +72,9 @@ while true do superv_comms.reconnect_modem(modem) println_ts("wireless modem reconnected.") - log._info("comms modem reconnected.") + log.info("comms modem reconnected.") else - log._info("wired modem reconnected.") + log.info("wired modem reconnected.") end end elseif event == "timer" and param1 == loop_clock then @@ -103,12 +99,12 @@ while true do -- check for termination request if event == "terminate" or ppm.should_terminate() then println_ts("closing sessions...") - log._info("terminate requested, closing sessions...") + log.info("terminate requested, closing sessions...") svsessions.close_all() - log._info("sessions closed") + log.info("sessions closed") break end end println_ts("exited") -log._info("exited") +log.info("exited") diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index edda143..3d4fa17 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -1,7 +1,10 @@ --- #REQUIRES comms.lua --- #REQUIRES mqueue.lua --- #REQUIRES util.lua --- #REQUIRES svsessions.lua +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local util = require("scada-common.util") + +local svsessions = require("session.svsessions") + +local supervisor = {} local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES @@ -17,7 +20,7 @@ local print_ts = util.print_ts local println_ts = util.println_ts -- supervisory controller communications -function superv_comms(num_reactors, modem, dev_listen, coord_listen) +supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) local self = { ln_seq_num = 0, num_reactors = num_reactors, @@ -101,7 +104,7 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) pkt = coord_pkt.get() end else - log._debug("attempted parse of illegal packet type " .. s_pkt.protocol(), true) + log.debug("attempted parse of illegal packet type " .. s_pkt.protocol(), true) end end @@ -126,7 +129,7 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) if session then if packet.type == RPLC_TYPES.LINK_REQ then -- new device on this port? that's a collision - log._debug("PLC_LNK: request from existing connection received on " .. r_port .. ", responding with collision") + log.debug("PLC_LNK: request from existing connection received on " .. r_port .. ", responding with collision") _send_plc_linking(r_port, { RPLC_LINKING.COLLISION }) else -- pass the packet onto the session handler @@ -140,20 +143,20 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) local plc_id = svsessions.establish_plc_session(l_port, r_port, packet.data[1]) if plc_id == false then -- reactor already has a PLC assigned - log._debug("PLC_LNK: assignment collision with reactor " .. packet.data[1]) + log.debug("PLC_LNK: assignment collision with reactor " .. packet.data[1]) _send_plc_linking(r_port, { RPLC_LINKING.COLLISION }) else -- got an ID; assigned to a reactor successfully println("connected to reactor " .. packet.data[1] .. " PLC (port " .. r_port .. ")") - log._debug("PLC_LNK: allowed for device at " .. r_port) + log.debug("PLC_LNK: allowed for device at " .. r_port) _send_plc_linking(r_port, { RPLC_LINKING.ALLOW }) end else - log._debug("PLC_LNK: new linking packet length mismatch") + log.debug("PLC_LNK: new linking packet length mismatch") end else -- force a re-link - log._debug("PLC_LNK: no session but not a link, force relink") + log.debug("PLC_LNK: no session but not a link, force relink") _send_plc_linking(r_port, { RPLC_LINKING.DENY }) end end @@ -164,7 +167,7 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) session.in_queue.push_packet(packet) end else - log._debug("illegal packet type " .. protocol .. " on device listening channel") + log.debug("illegal packet type " .. protocol .. " on device listening channel") end -- coordinator listening channel elseif l_port == self.coord_listen then @@ -173,10 +176,10 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) elseif protocol == PROTOCOLS.COORD_DATA then -- coordinator packet else - log._debug("illegal packet type " .. protocol .. " on coordinator listening channel") + log.debug("illegal packet type " .. protocol .. " on coordinator listening channel") end else - log._error("received packet on unused channel " .. l_port, true) + log.error("received packet on unused channel " .. l_port, true) end end end @@ -187,3 +190,5 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) handle_packet = handle_packet } end + +return supervisor