diff --git a/reactor-plc/panel/front_panel.lua b/reactor-plc/panel/front_panel.lua
index a59a016..cf04194 100644
--- a/reactor-plc/panel/front_panel.lua
+++ b/reactor-plc/panel/front_panel.lua
@@ -5,8 +5,8 @@
local types = require("scada-common.types")
local util = require("scada-common.util")
-local config = require("reactor-plc.config")
local databus = require("reactor-plc.databus")
+local plc = require("reactor-plc.plc")
local style = require("reactor-plc.panel.style")
@@ -86,7 +86,7 @@ local function init(panel)
local active = LED{parent=status,x=2,width=12,label="RCT ACTIVE",colors=ind_grn}
-- only show emergency coolant LED if emergency coolant is configured for this device
- if type(config.EMERGENCY_COOL) == "table" then
+ if plc.config.EmerCoolEnable then
local emer_cool = LED{parent=status,x=2,width=14,label="EMER COOLANT",colors=cpair(colors.yellow,colors.yellow_off)}
emer_cool.register(databus.ps, "emer_cool", emer_cool.update)
end
diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua
index bbb59ff..9140841 100644
--- a/reactor-plc/plc.lua
+++ b/reactor-plc/plc.lua
@@ -26,14 +26,58 @@ local RPS_LIMITS = const.RPS_LIMITS
local PCALL_SCRAM_MSG = "pcall: Scram requires the reactor to be active."
local PCALL_START_MSG = "pcall: Reactor is already active."
+---@type plc_config
+local config = {}
+
+plc.config = config
+
+-- load the PLC configuration
+function plc.load_config()
+ config.Networked = settings.get("Networked")
+ config.UnitID = settings.get("UnitID")
+ config.EmerCoolEnable = settings.get("EmerCoolEnable")
+ config.EmerCoolSide = settings.get("EmerCoolSide")
+ config.EmerCoolColor = settings.get("EmerCoolColor")
+ config.SVR_Channel = settings.get("SVR_Channel")
+ config.PLC_Channel = settings.get("PLC_Channel")
+ config.ConnTimeout = settings.get("ConnTimeout")
+ config.TrustedRange = settings.get("TrustedRange")
+ config.AuthKey = settings.get("AuthKey")
+ config.LogMode = settings.get("LogMode")
+ config.LogPath = settings.get("LogPath")
+ config.LogDebug = settings.get("LogDebug")
+
+ local cfv = util.new_validator()
+
+ cfv.assert_type_bool(config.Networked)
+ cfv.assert_type_int(config.UnitID)
+ cfv.assert_type_bool(config.EmerCoolEnable)
+ cfv.assert_channel(config.SVR_Channel)
+ cfv.assert_channel(config.PLC_Channel)
+ cfv.assert_type_int(config.ConnTimeout)
+ cfv.assert_min(config.ConnTimeout, 2)
+ cfv.assert_type_num(config.TrustedRange)
+ cfv.assert_type_str(config.AuthKey)
+ cfv.assert_type_int(config.LogMode)
+ cfv.assert_type_str(config.LogPath)
+ cfv.assert_type_bool(config.LogDebug)
+
+ -- check emergency coolant configuration if enabled
+ if config.EmerCoolEnable then
+ cfv.assert_eq(rsio.is_valid_side(config.EmerCoolSide), true)
+ cfv.assert_eq(config.EmerCoolColor == nil or rsio.is_color(config.EmerCoolColor), true)
+ end
+
+ return cfv.valid()
+end
+
-- RPS: Reactor Protection System
-- identifies dangerous states and SCRAMs reactor if warranted
-- autonomous from main SCADA supervisor/coordinator control
---@nodiscard
---@param reactor table
---@param is_formed boolean
----@param emer_cool nil|table emergency coolant configuration
-function plc.rps_init(reactor, is_formed, emer_cool)
+function plc.rps_init(reactor, is_formed)
local state_keys = {
high_dmg = 1,
high_temp = 2,
@@ -73,22 +117,22 @@ function plc.rps_init(reactor, is_formed, emer_cool)
---@param state boolean true to enable emergency coolant, false to disable
local function _set_emer_cool(state)
-- check if this was configured: if it's a table, fields have already been validated.
- if type(emer_cool) == "table" then
+ if config.EmerCoolEnable then
local level = rsio.digital_write_active(rsio.IO.U_EMER_COOL, state)
if level ~= false then
- if rsio.is_color(emer_cool.color) then
- local output = rs.getBundledOutput(emer_cool.side)
+ if rsio.is_color(config.EmerCoolColor) then
+ local output = rs.getBundledOutput(config.EmerCoolSide)
if rsio.digital_write(level) then
- output = colors.combine(output, emer_cool.color)
+ output = colors.combine(output, config.EmerCoolColor)
else
- output = colors.subtract(output, emer_cool.color)
+ output = colors.subtract(output, config.EmerCoolColor)
end
- rs.setBundledOutput(emer_cool.side, output)
+ rs.setBundledOutput(config.EmerCoolSide, output)
else
- rs.setOutput(emer_cool.side, rsio.digital_write(level))
+ rs.setOutput(config.EmerCoolSide, rsio.digital_write(level))
end
if state ~= self.emer_cool_active then
@@ -443,16 +487,12 @@ end
-- Reactor PLC Communications
---@nodiscard
----@param id integer reactor ID
---@param version string PLC version
---@param nic nic network interface device
----@param plc_channel integer PLC comms channel
----@param svr_channel integer supervisor server channel
----@param range integer trusted device connection range
---@param reactor table reactor device
---@param rps rps RPS reference
---@param conn_watchdog watchdog watchdog reference
-function plc.comms(id, version, nic, plc_channel, svr_channel, range, reactor, rps, conn_watchdog)
+function plc.comms(version, nic, reactor, rps, conn_watchdog)
local self = {
sv_addr = comms.BROADCAST,
seq_num = 0,
@@ -466,13 +506,13 @@ function plc.comms(id, version, nic, plc_channel, svr_channel, range, reactor, r
max_burn_rate = nil
}
- comms.set_trusted_range(range)
+ comms.set_trusted_range(config.TrustedRange)
-- PRIVATE FUNCTIONS --
-- configure network channels
nic.closeAll()
- nic.open(plc_channel)
+ nic.open(config.PLC_Channel)
-- send an RPLC packet
---@param msg_type RPLC_TYPE
@@ -481,10 +521,10 @@ function plc.comms(id, version, nic, plc_channel, svr_channel, range, reactor, r
local s_pkt = comms.scada_packet()
local r_pkt = comms.rplc_packet()
- r_pkt.make(id, msg_type, msg)
+ r_pkt.make(config.UnitID, msg_type, msg)
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.RPLC, r_pkt.raw_sendable())
- nic.transmit(svr_channel, plc_channel, s_pkt)
+ nic.transmit(config.SVR_Channel, config.PLC_Channel, s_pkt)
self.seq_num = self.seq_num + 1
end
@@ -498,7 +538,7 @@ function plc.comms(id, version, nic, plc_channel, svr_channel, range, reactor, r
m_pkt.make(msg_type, msg)
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
- nic.transmit(svr_channel, plc_channel, s_pkt)
+ nic.transmit(config.SVR_Channel, config.PLC_Channel, s_pkt)
self.seq_num = self.seq_num + 1
end
@@ -673,7 +713,7 @@ function plc.comms(id, version, nic, plc_channel, svr_channel, range, reactor, r
-- attempt to establish link with supervisor
function public.send_link_req()
- _send_mgmt(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PLC, id })
+ _send_mgmt(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PLC, config.UnitID })
end
-- send live status information
@@ -769,7 +809,7 @@ function plc.comms(id, version, nic, plc_channel, svr_channel, range, reactor, r
local src_addr = packet.scada_frame.src_addr()
-- handle packets now that we have prints setup
- if l_chan == plc_channel then
+ if l_chan == config.PLC_Channel then
-- check sequence number
if self.r_seq_num == nil then
self.r_seq_num = packet.scada_frame.seq_num()
diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua
index 97ac227..300fd16 100644
--- a/reactor-plc/startup.lua
+++ b/reactor-plc/startup.lua
@@ -4,58 +4,46 @@
require("/initenv").init_env()
-local comms = require("scada-common.comms")
-local crash = require("scada-common.crash")
-local log = require("scada-common.log")
-local mqueue = require("scada-common.mqueue")
-local network = require("scada-common.network")
-local ppm = require("scada-common.ppm")
-local rsio = require("scada-common.rsio")
-local util = require("scada-common.util")
+local comms = require("scada-common.comms")
+local crash = require("scada-common.crash")
+local log = require("scada-common.log")
+local mqueue = require("scada-common.mqueue")
+local network = require("scada-common.network")
+local ppm = require("scada-common.ppm")
+local util = require("scada-common.util")
-local config = require("reactor-plc.config")
-local databus = require("reactor-plc.databus")
-local plc = require("reactor-plc.plc")
-local renderer = require("reactor-plc.renderer")
-local threads = require("reactor-plc.threads")
+local configure = require("reactor-plc.configure")
+local databus = require("reactor-plc.databus")
+local plc = require("reactor-plc.plc")
+local renderer = require("reactor-plc.renderer")
+local threads = require("reactor-plc.threads")
-local R_PLC_VERSION = "v1.5.10"
+local R_PLC_VERSION = "v1.6.0"
local println = util.println
local println_ts = util.println_ts
----------------------------------------
--- config validation
+-- get configuration
----------------------------------------
-local cfv = util.new_validator()
-
-cfv.assert_type_bool(config.NETWORKED)
-cfv.assert_type_int(config.REACTOR_ID)
-cfv.assert_channel(config.SVR_CHANNEL)
-cfv.assert_channel(config.PLC_CHANNEL)
-cfv.assert_type_int(config.TRUSTED_RANGE)
-cfv.assert_type_num(config.COMMS_TIMEOUT)
-cfv.assert_min(config.COMMS_TIMEOUT, 2)
-cfv.assert_type_str(config.LOG_PATH)
-cfv.assert_type_int(config.LOG_MODE)
-
-assert(cfv.valid(), "bad config file: missing/invalid fields")
-
--- check emergency coolant configuration
-if type(config.EMERGENCY_COOL) == "table" then
- if not rsio.is_valid_side(config.EMERGENCY_COOL.side) then
- assert(false, "bad config file: emergency coolant side unrecognized")
- elseif config.EMERGENCY_COOL.color ~= nil and not rsio.is_color(config.EMERGENCY_COOL.color) then
- assert(false, "bad config file: emergency coolant invalid redstone channel color provided")
+if not plc.load_config() then
+ -- try to reconfigure (user action)
+ local success, error = configure.configure(true)
+ if success then
+ assert(plc.load_config(), "failed to load valid reactor PLC configuration")
+ else
+ assert(success, "reactor PLC configuration error: " .. error)
end
end
+local config = plc.config
+
----------------------------------------
-- log init
----------------------------------------
-log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true)
+log.init(config.LogPath, config.LogMode, config.LogDebug)
log.info("========================================")
log.info("BOOTING reactor-plc.startup " .. R_PLC_VERSION)
@@ -75,32 +63,32 @@ local function main()
-- record firmware versions and ID
databus.tx_versions(R_PLC_VERSION, comms.version)
- databus.tx_id(config.REACTOR_ID)
+ databus.tx_id(config.UnitID)
-- mount connected devices
ppm.mount_all()
-- message authentication init
- if type(config.AUTH_KEY) == "string" then
- network.init_mac(config.AUTH_KEY)
+ if string.len(config.AuthKey) > 0 then
+ network.init_mac(config.AuthKey)
end
-- shared memory across threads
---@class plc_shared_memory
local __shared_memory = {
-- networked setting
- networked = config.NETWORKED, ---@type boolean
+ networked = config.Networked,
-- PLC system state flags
---@class plc_state
plc_state = {
- init_ok = true,
- fp_ok = false,
- shutdown = false,
- degraded = true,
+ init_ok = true,
+ fp_ok = false,
+ shutdown = false,
+ degraded = true,
reactor_formed = true,
- no_reactor = true,
- no_modem = true
+ no_reactor = true,
+ no_modem = true
},
-- control setpoints
@@ -118,10 +106,10 @@ local function main()
-- system objects
plc_sys = {
- rps = nil, ---@type rps
- nic = nil, ---@type nic
- plc_comms = nil, ---@type plc_comms
- conn_watchdog = nil ---@type watchdog
+ rps = nil, ---@type rps
+ nic = nil, ---@type nic
+ plc_comms = nil, ---@type plc_comms
+ conn_watchdog = nil---@type watchdog
},
-- message queues
@@ -196,18 +184,17 @@ local function main()
if plc_state.init_ok then
-- init reactor protection system
- smem_sys.rps = plc.rps_init(smem_dev.reactor, plc_state.reactor_formed, config.EMERGENCY_COOL)
+ smem_sys.rps = plc.rps_init(smem_dev.reactor, plc_state.reactor_formed)
log.debug("init> rps init")
if __shared_memory.networked then
-- comms watchdog
- smem_sys.conn_watchdog = util.new_watchdog(config.COMMS_TIMEOUT)
+ smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout)
log.debug("init> conn watchdog started")
-- create network interface then setup comms
smem_sys.nic = network.nic(smem_dev.modem)
- smem_sys.plc_comms = plc.comms(config.REACTOR_ID, R_PLC_VERSION, smem_sys.nic, config.PLC_CHANNEL, config.SVR_CHANNEL,
- config.TRUSTED_RANGE, smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog)
+ smem_sys.plc_comms = plc.comms(R_PLC_VERSION, smem_sys.nic, smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog)
log.debug("init> comms init")
else
_println_no_fp("init> starting in offline mode")
@@ -215,7 +202,7 @@ local function main()
end
-- notify user of emergency coolant configuration status
- if config.EMERGENCY_COOL ~= nil then
+ if config.EmerCoolEnable then
println("init> emergency coolant control ready")
log.info("init> running with emergency coolant control available")
end