#33 lua module/require architecture changeover

This commit is contained in:
Mikayla Fischler
2022-05-04 13:37:01 -04:00
parent 7bcb260712
commit b575899d46
33 changed files with 679 additions and 518 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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