2022-03-25 16:17:46 +00:00
|
|
|
--
|
|
|
|
-- Nuclear Generation Facility SCADA Coordinator
|
|
|
|
--
|
|
|
|
|
2022-05-14 17:32:42 +00:00
|
|
|
require("/initenv").init_env()
|
|
|
|
|
2023-07-10 03:31:56 +00:00
|
|
|
local comms = require("scada-common.comms")
|
2023-06-03 21:40:57 +00:00
|
|
|
local crash = require("scada-common.crash")
|
|
|
|
local log = require("scada-common.log")
|
2024-04-07 23:37:06 +00:00
|
|
|
local mqueue = require("scada-common.mqueue")
|
2023-06-21 23:04:39 +00:00
|
|
|
local network = require("scada-common.network")
|
2023-06-03 21:40:57 +00:00
|
|
|
local ppm = require("scada-common.ppm")
|
|
|
|
local util = require("scada-common.util")
|
2022-03-25 16:17:46 +00:00
|
|
|
|
2024-02-18 20:21:00 +00:00
|
|
|
local configure = require("coordinator.configure")
|
2023-06-03 21:40:57 +00:00
|
|
|
local coordinator = require("coordinator.coordinator")
|
|
|
|
local iocontrol = require("coordinator.iocontrol")
|
|
|
|
local renderer = require("coordinator.renderer")
|
|
|
|
local sounder = require("coordinator.sounder")
|
2024-04-07 23:37:06 +00:00
|
|
|
local threads = require("coordinator.threads")
|
2022-03-25 16:17:46 +00:00
|
|
|
|
2024-05-01 00:27:04 +00:00
|
|
|
local COORDINATOR_VERSION = "v1.4.5"
|
2024-02-25 23:02:13 +00:00
|
|
|
|
2024-03-05 22:16:35 +00:00
|
|
|
local CHUNK_LOAD_DELAY_S = 30.0
|
2022-03-25 16:17:46 +00:00
|
|
|
|
2024-04-09 03:59:16 +00:00
|
|
|
local println = util.println
|
2022-04-29 17:32:37 +00:00
|
|
|
local println_ts = util.println_ts
|
2022-03-25 16:17:46 +00:00
|
|
|
|
2024-04-08 00:47:31 +00:00
|
|
|
local log_render = coordinator.log_render
|
2024-04-09 03:59:16 +00:00
|
|
|
local log_sys = coordinator.log_sys
|
|
|
|
local log_boot = coordinator.log_boot
|
|
|
|
local log_comms = coordinator.log_comms
|
2023-06-25 18:00:18 +00:00
|
|
|
local log_crypto = coordinator.log_crypto
|
2022-07-05 16:47:02 +00:00
|
|
|
|
2022-06-05 19:09:02 +00:00
|
|
|
----------------------------------------
|
2024-02-18 20:21:00 +00:00
|
|
|
-- get configuration
|
2022-06-05 19:09:02 +00:00
|
|
|
----------------------------------------
|
|
|
|
|
2024-02-18 20:21:00 +00:00
|
|
|
-- mount connected devices (required for monitor setup)
|
|
|
|
ppm.mount_all()
|
2022-06-05 19:09:02 +00:00
|
|
|
|
2024-03-05 22:16:35 +00:00
|
|
|
local wait_on_load = true
|
2024-02-18 20:21:00 +00:00
|
|
|
local loaded, monitors = coordinator.load_config()
|
2024-02-25 23:02:13 +00:00
|
|
|
|
|
|
|
-- if the computer just started, its chunk may have just loaded (...or the user rebooted)
|
|
|
|
-- if monitor config failed, maybe an adjacent chunk containing all or part of a monitor has not loaded yet, so keep trying
|
2024-03-05 22:16:35 +00:00
|
|
|
while wait_on_load and loaded == 2 and os.clock() < CHUNK_LOAD_DELAY_S do
|
2024-02-25 23:02:13 +00:00
|
|
|
term.clear()
|
|
|
|
term.setCursorPos(1, 1)
|
|
|
|
println("There was a monitor configuration problem at boot.\n")
|
|
|
|
println("Startup will keep trying every 2s in case of chunk load delays.\n")
|
|
|
|
println(util.sprintf("The configurator will be started in %ds if all attempts fail.\n", math.max(0, CHUNK_LOAD_DELAY_S - os.clock())))
|
2024-03-05 22:16:35 +00:00
|
|
|
println("(click to skip to the configurator)")
|
|
|
|
|
|
|
|
local timer_id = util.start_timer(2)
|
|
|
|
|
|
|
|
while true do
|
|
|
|
local event, param1 = util.pull_event()
|
|
|
|
if event == "timer" and param1 == timer_id then
|
|
|
|
-- remount and re-attempt
|
|
|
|
ppm.mount_all()
|
|
|
|
loaded, monitors = coordinator.load_config()
|
|
|
|
break
|
|
|
|
elseif event == "mouse_click" or event == "terminate" then
|
|
|
|
wait_on_load = false
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
2024-02-25 23:02:13 +00:00
|
|
|
end
|
|
|
|
|
2024-02-18 20:21:00 +00:00
|
|
|
if loaded ~= 0 then
|
|
|
|
-- try to reconfigure (user action)
|
|
|
|
local success, error = configure.configure(loaded, monitors)
|
|
|
|
if success then
|
|
|
|
loaded, monitors = coordinator.load_config()
|
2024-03-06 01:17:52 +00:00
|
|
|
if loaded ~= 0 then
|
|
|
|
println(util.trinary(loaded == 2, "monitor configuration invalid", "failed to load a valid configuration") .. ", please reconfigure")
|
|
|
|
return
|
|
|
|
end
|
2024-02-18 20:21:00 +00:00
|
|
|
else
|
2024-03-06 01:17:52 +00:00
|
|
|
println("configuration error: " .. error)
|
|
|
|
return
|
2024-02-18 20:21:00 +00:00
|
|
|
end
|
|
|
|
end
|
2023-02-13 17:27:22 +00:00
|
|
|
|
2024-02-18 20:21:00 +00:00
|
|
|
-- passed checks, good now
|
|
|
|
---@cast monitors monitors_struct
|
|
|
|
|
|
|
|
local config = coordinator.config
|
2022-06-05 19:09:02 +00:00
|
|
|
|
|
|
|
----------------------------------------
|
|
|
|
-- log init
|
|
|
|
----------------------------------------
|
|
|
|
|
2024-02-18 20:21:00 +00:00
|
|
|
log.init(config.LogPath, config.LogMode, config.LogDebug)
|
2022-04-29 17:32:37 +00:00
|
|
|
|
2022-05-04 17:37:01 +00:00
|
|
|
log.info("========================================")
|
|
|
|
log.info("BOOTING coordinator.startup " .. COORDINATOR_VERSION)
|
|
|
|
log.info("========================================")
|
2022-05-01 19:34:44 +00:00
|
|
|
println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<")
|
2022-03-25 16:17:46 +00:00
|
|
|
|
2022-11-13 20:56:27 +00:00
|
|
|
crash.set_env("coordinator", COORDINATOR_VERSION)
|
2024-03-23 04:49:19 +00:00
|
|
|
crash.dbg_log_env()
|
2022-11-13 20:56:27 +00:00
|
|
|
|
2022-06-05 19:09:02 +00:00
|
|
|
----------------------------------------
|
2022-11-13 20:56:27 +00:00
|
|
|
-- main application
|
2022-06-05 19:09:02 +00:00
|
|
|
----------------------------------------
|
|
|
|
|
2022-11-13 20:56:27 +00:00
|
|
|
local function main()
|
|
|
|
----------------------------------------
|
|
|
|
-- system startup
|
|
|
|
----------------------------------------
|
2022-03-25 16:17:46 +00:00
|
|
|
|
2024-02-22 01:33:07 +00:00
|
|
|
-- log mounts now since mounting was done before logging was ready
|
|
|
|
ppm.log_mounts()
|
2024-02-19 23:56:24 +00:00
|
|
|
|
2023-07-10 03:31:56 +00:00
|
|
|
-- report versions/init fp PSIL
|
|
|
|
iocontrol.init_fp(COORDINATOR_VERSION, comms.version)
|
|
|
|
|
2022-11-13 20:56:27 +00:00
|
|
|
-- init renderer
|
2024-03-12 03:31:31 +00:00
|
|
|
renderer.configure(config)
|
2022-11-13 20:56:27 +00:00
|
|
|
renderer.set_displays(monitors)
|
2023-02-28 04:59:46 +00:00
|
|
|
renderer.init_displays()
|
2022-11-13 20:56:27 +00:00
|
|
|
renderer.init_dmesg()
|
2022-05-29 18:34:09 +00:00
|
|
|
|
2023-02-03 20:19:00 +00:00
|
|
|
-- lets get started!
|
|
|
|
log.info("monitors ready, dmesg output incoming...")
|
|
|
|
|
2024-04-08 00:47:31 +00:00
|
|
|
log_render("displays connected and reset")
|
2022-11-13 20:56:27 +00:00
|
|
|
log_sys("system start on " .. os.date("%c"))
|
|
|
|
log_boot("starting " .. COORDINATOR_VERSION)
|
2022-07-05 16:47:02 +00:00
|
|
|
|
2024-04-07 23:37:06 +00:00
|
|
|
----------------------------------------
|
|
|
|
-- memory allocation
|
|
|
|
----------------------------------------
|
|
|
|
|
|
|
|
-- shared memory across threads
|
|
|
|
---@class crd_shared_memory
|
|
|
|
local __shared_memory = {
|
|
|
|
-- time and date format for display
|
|
|
|
date_format = util.trinary(config.Time24Hour, "%X \x04 %A, %B %d %Y", "%r \x04 %A, %B %d %Y"),
|
|
|
|
|
|
|
|
-- coordinator system state flags
|
|
|
|
---@class crd_state
|
|
|
|
crd_state = {
|
|
|
|
fp_ok = false,
|
2024-04-08 00:55:07 +00:00
|
|
|
ui_ok = true, -- default true, used to abort on fail
|
2024-04-07 23:37:06 +00:00
|
|
|
link_fail = false,
|
|
|
|
shutdown = false
|
|
|
|
},
|
|
|
|
|
|
|
|
-- core coordinator devices
|
|
|
|
crd_dev = {
|
|
|
|
speaker = ppm.get_device("speaker"),
|
|
|
|
modem = ppm.get_wireless_modem()
|
|
|
|
},
|
|
|
|
|
|
|
|
-- system objects
|
|
|
|
crd_sys = {
|
|
|
|
nic = nil, ---@type nic
|
|
|
|
coord_comms = nil, ---@type coord_comms
|
|
|
|
conn_watchdog = nil ---@type watchdog
|
|
|
|
},
|
|
|
|
|
|
|
|
-- message queues
|
|
|
|
q = {
|
|
|
|
mq_render = mqueue.new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
local smem_dev = __shared_memory.crd_dev
|
|
|
|
local smem_sys = __shared_memory.crd_sys
|
|
|
|
|
|
|
|
local crd_state = __shared_memory.crd_state
|
|
|
|
|
2022-12-04 18:59:10 +00:00
|
|
|
----------------------------------------
|
|
|
|
-- setup alarm sounder subsystem
|
|
|
|
----------------------------------------
|
|
|
|
|
2024-04-07 23:37:06 +00:00
|
|
|
if smem_dev.speaker == nil then
|
2022-12-04 18:59:10 +00:00
|
|
|
log_boot("annunciator alarm speaker not found")
|
2023-02-23 04:09:47 +00:00
|
|
|
println("startup> speaker not found")
|
2022-12-04 18:59:10 +00:00
|
|
|
log.fatal("no annunciator alarm speaker found")
|
|
|
|
return
|
|
|
|
else
|
|
|
|
local sounder_start = util.time_ms()
|
|
|
|
log_boot("annunciator alarm speaker connected")
|
2024-04-07 23:37:06 +00:00
|
|
|
sounder.init(smem_dev.speaker, config.SpeakerVolume)
|
2022-12-04 18:59:10 +00:00
|
|
|
log_boot("tone generation took " .. (util.time_ms() - sounder_start) .. "ms")
|
|
|
|
log_sys("annunciator alarm configured")
|
2023-07-10 03:31:56 +00:00
|
|
|
iocontrol.fp_has_speaker(true)
|
2022-12-04 18:59:10 +00:00
|
|
|
end
|
|
|
|
|
2022-11-13 20:56:27 +00:00
|
|
|
----------------------------------------
|
|
|
|
-- setup communications
|
|
|
|
----------------------------------------
|
2022-07-05 16:47:02 +00:00
|
|
|
|
2023-06-25 18:00:18 +00:00
|
|
|
-- message authentication init
|
2024-02-18 20:21:00 +00:00
|
|
|
if type(config.AuthKey) == "string" and string.len(config.AuthKey) > 0 then
|
|
|
|
local init_time = network.init_mac(config.AuthKey)
|
2023-06-25 18:00:18 +00:00
|
|
|
log_crypto("HMAC init took " .. init_time .. "ms")
|
|
|
|
end
|
|
|
|
|
2022-11-13 20:56:27 +00:00
|
|
|
-- get the communications modem
|
2024-04-07 23:37:06 +00:00
|
|
|
if smem_dev.modem == nil then
|
2022-11-13 20:56:27 +00:00
|
|
|
log_comms("wireless modem not found")
|
2023-02-23 04:09:47 +00:00
|
|
|
println("startup> wireless modem not found")
|
2022-11-13 20:56:27 +00:00
|
|
|
log.fatal("no wireless modem on startup")
|
|
|
|
return
|
|
|
|
else
|
|
|
|
log_comms("wireless modem connected")
|
2023-07-10 03:31:56 +00:00
|
|
|
iocontrol.fp_has_modem(true)
|
2022-11-13 20:56:27 +00:00
|
|
|
end
|
2022-09-13 20:07:21 +00:00
|
|
|
|
2022-11-13 20:56:27 +00:00
|
|
|
-- create connection watchdog
|
2024-04-07 23:37:06 +00:00
|
|
|
smem_sys.conn_watchdog = util.new_watchdog(config.SVR_Timeout)
|
|
|
|
smem_sys.conn_watchdog.cancel()
|
2023-02-23 04:09:47 +00:00
|
|
|
log.debug("startup> conn watchdog created")
|
2022-11-13 20:56:27 +00:00
|
|
|
|
2023-06-25 16:59:38 +00:00
|
|
|
-- create network interface then setup comms
|
2024-04-07 23:37:06 +00:00
|
|
|
smem_sys.nic = network.nic(smem_dev.modem)
|
|
|
|
smem_sys.coord_comms = coordinator.comms(COORDINATOR_VERSION, smem_sys.nic, smem_sys.conn_watchdog)
|
2023-02-23 04:09:47 +00:00
|
|
|
log.debug("startup> comms init")
|
2022-11-13 20:56:27 +00:00
|
|
|
log_comms("comms initialized")
|
|
|
|
|
2023-07-10 03:31:56 +00:00
|
|
|
----------------------------------------
|
2024-04-07 23:37:06 +00:00
|
|
|
-- start front panel
|
2023-07-10 03:31:56 +00:00
|
|
|
----------------------------------------
|
|
|
|
|
2024-04-08 00:47:31 +00:00
|
|
|
log_render("starting front panel UI...")
|
2023-07-10 03:31:56 +00:00
|
|
|
|
2024-04-07 23:37:06 +00:00
|
|
|
local fp_message
|
|
|
|
crd_state.fp_ok, fp_message = renderer.try_start_fp()
|
|
|
|
if not crd_state.fp_ok then
|
2024-04-08 00:47:31 +00:00
|
|
|
log_render(util.c("front panel UI error: ", fp_message))
|
2023-07-10 03:31:56 +00:00
|
|
|
println_ts("front panel UI creation failed")
|
|
|
|
log.fatal(util.c("front panel GUI render failed with error ", fp_message))
|
|
|
|
return
|
2024-04-08 00:47:31 +00:00
|
|
|
else log_render("front panel ready") end
|
2023-07-10 03:31:56 +00:00
|
|
|
|
2022-11-13 20:56:27 +00:00
|
|
|
----------------------------------------
|
2024-04-07 23:37:06 +00:00
|
|
|
-- start system
|
2022-11-13 20:56:27 +00:00
|
|
|
----------------------------------------
|
2022-09-03 15:51:27 +00:00
|
|
|
|
2024-04-07 23:37:06 +00:00
|
|
|
-- init threads
|
|
|
|
local main_thread = threads.thread__main(__shared_memory)
|
|
|
|
local render_thread = threads.thread__render(__shared_memory)
|
2022-09-13 20:07:21 +00:00
|
|
|
|
2024-04-07 23:37:06 +00:00
|
|
|
log.info("startup> completed")
|
2023-07-11 17:32:26 +00:00
|
|
|
|
2024-04-07 23:37:06 +00:00
|
|
|
-- run threads
|
|
|
|
parallel.waitForAll(main_thread.p_exec, render_thread.p_exec)
|
2022-07-05 16:47:02 +00:00
|
|
|
|
2022-11-13 20:56:27 +00:00
|
|
|
renderer.close_ui()
|
2023-07-10 03:31:56 +00:00
|
|
|
renderer.close_fp()
|
2022-12-04 18:59:10 +00:00
|
|
|
sounder.stop()
|
2022-11-13 20:56:27 +00:00
|
|
|
log_sys("system shutdown")
|
2022-06-25 20:21:57 +00:00
|
|
|
|
2024-04-07 23:37:06 +00:00
|
|
|
if crd_state.link_fail then println_ts("failed to connect to supervisor") end
|
|
|
|
if not crd_state.ui_ok then println_ts("main UI creation failed") end
|
2023-07-11 17:32:26 +00:00
|
|
|
|
2023-08-26 01:02:24 +00:00
|
|
|
-- close on error exit (such as UI error)
|
2024-04-07 23:37:06 +00:00
|
|
|
if smem_sys.coord_comms.is_linked() then smem_sys.coord_comms.close() end
|
2023-08-26 01:02:24 +00:00
|
|
|
|
2022-11-13 20:56:27 +00:00
|
|
|
println_ts("exited")
|
|
|
|
log.info("exited")
|
|
|
|
end
|
2022-06-25 20:21:57 +00:00
|
|
|
|
2022-12-10 18:58:17 +00:00
|
|
|
if not xpcall(main, crash.handler) then
|
|
|
|
pcall(renderer.close_ui)
|
2023-07-10 03:31:56 +00:00
|
|
|
pcall(renderer.close_fp)
|
2022-12-10 18:58:17 +00:00
|
|
|
pcall(sounder.stop)
|
|
|
|
crash.exit()
|
2023-04-21 01:00:10 +00:00
|
|
|
else
|
|
|
|
log.close()
|
2022-12-10 18:58:17 +00:00
|
|
|
end
|