273 lines
8.3 KiB
Lua
273 lines
8.3 KiB
Lua
--
|
|
-- Nuclear Generation Facility SCADA Coordinator
|
|
--
|
|
|
|
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 util = require("scada-common.util")
|
|
|
|
local backplane = require("coordinator.backplane")
|
|
local configure = require("coordinator.configure")
|
|
local coordinator = require("coordinator.coordinator")
|
|
local iocontrol = require("coordinator.iocontrol")
|
|
local renderer = require("coordinator.renderer")
|
|
local sounder = require("coordinator.sounder")
|
|
local threads = require("coordinator.threads")
|
|
|
|
local COORDINATOR_VERSION = "v1.7.0"
|
|
|
|
local CHUNK_LOAD_DELAY_S = 30.0
|
|
|
|
local println = util.println
|
|
local println_ts = util.println_ts
|
|
|
|
local log_render = coordinator.log_render
|
|
local log_sys = coordinator.log_sys
|
|
local log_boot = coordinator.log_boot
|
|
local log_comms = coordinator.log_comms
|
|
local log_crypto = coordinator.log_crypto
|
|
|
|
----------------------------------------
|
|
-- get configuration
|
|
----------------------------------------
|
|
|
|
-- first pass configuration check before validating monitors
|
|
if not coordinator.load_config() then
|
|
-- try to reconfigure (user action)
|
|
local success, error = configure.configure(1)
|
|
if success then
|
|
if not coordinator.load_config() then
|
|
println("failed to load a valid configuration, please reconfigure")
|
|
return
|
|
end
|
|
else
|
|
println("configuration error: " .. error)
|
|
return
|
|
end
|
|
end
|
|
|
|
local config = coordinator.config
|
|
|
|
----------------------------------------
|
|
-- log init
|
|
----------------------------------------
|
|
|
|
log.init(config.LogPath, config.LogMode, config.LogDebug)
|
|
|
|
log.info("========================================")
|
|
log.info("BOOTING coordinator.startup " .. COORDINATOR_VERSION)
|
|
log.info("========================================")
|
|
println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<")
|
|
|
|
crash.set_env("coordinator", COORDINATOR_VERSION)
|
|
crash.dbg_log_env()
|
|
|
|
----------------------------------------
|
|
-- display init
|
|
----------------------------------------
|
|
|
|
-- mount connected devices (required for monitor setup)
|
|
ppm.mount_all()
|
|
|
|
local wait_on_load = true
|
|
|
|
local disp_ok, disp_err = backplane.init_displays(config)
|
|
|
|
-- 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
|
|
while wait_on_load and (not disp_ok) and os.clock() < CHUNK_LOAD_DELAY_S do
|
|
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())))
|
|
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()
|
|
disp_ok, disp_err = backplane.init_displays(config)
|
|
break
|
|
elseif event == "mouse_click" or event == "terminate" then
|
|
wait_on_load = false
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
if not disp_ok then
|
|
-- try to reconfigure (user action)
|
|
local success, error = configure.configure(2, disp_err)
|
|
if success then
|
|
if not coordinator.load_config() then
|
|
println("failed to load a valid configuration, please reconfigure")
|
|
return
|
|
else
|
|
disp_ok, disp_err = backplane.init_displays(config)
|
|
|
|
if not disp_ok then
|
|
println(disp_err)
|
|
println("please reconfigure")
|
|
return
|
|
end
|
|
end
|
|
else
|
|
println("configuration error: " .. error)
|
|
return
|
|
end
|
|
end
|
|
|
|
----------------------------------------
|
|
-- main application
|
|
----------------------------------------
|
|
|
|
local function main()
|
|
----------------------------------------
|
|
-- system startup
|
|
----------------------------------------
|
|
|
|
-- report versions
|
|
iocontrol.fp_versions(COORDINATOR_VERSION, comms.version)
|
|
|
|
-- init renderer
|
|
renderer.configure(config)
|
|
renderer.init_displays(backplane.displays())
|
|
renderer.init_dmesg()
|
|
|
|
-- lets get started!
|
|
log.info("monitors ready, dmesg output incoming...")
|
|
|
|
log_render("displays connected and reset")
|
|
log_sys("system start on " .. os.date("%c"))
|
|
log_boot("starting " .. COORDINATOR_VERSION)
|
|
|
|
-- message authentication init
|
|
if type(config.AuthKey) == "string" and string.len(config.AuthKey) > 0 then
|
|
local init_time = network.init_mac(config.AuthKey)
|
|
log_crypto("HMAC init took " .. init_time .. "ms")
|
|
end
|
|
|
|
----------------------------------------
|
|
-- 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,
|
|
ui_ok = true, -- default true, used to abort on fail
|
|
link_fail = false,
|
|
shutdown = false
|
|
},
|
|
|
|
-- system objects
|
|
---@class crd_sys
|
|
crd_sys = {
|
|
coord_comms = nil, ---@type coord_comms
|
|
conn_watchdog = nil ---@type watchdog
|
|
},
|
|
|
|
-- message queues
|
|
q = {
|
|
mq_render = mqueue.new()
|
|
},
|
|
|
|
-- message queue message types
|
|
q_types = {
|
|
MQ__RENDER_CMD = {
|
|
START_MAIN_UI = 1,
|
|
CLOSE_MAIN_UI = 2
|
|
},
|
|
MQ__RENDER_DATA = {
|
|
MON_CONNECT = 1,
|
|
MON_DISCONNECT = 2,
|
|
MON_RESIZE = 3
|
|
}
|
|
}
|
|
}
|
|
|
|
local smem_sys = __shared_memory.crd_sys
|
|
local crd_state = __shared_memory.crd_state
|
|
|
|
----------------------------------------
|
|
-- init system
|
|
----------------------------------------
|
|
|
|
-- modem and speaker initialization
|
|
if not backplane.init(config, __shared_memory) then return end
|
|
|
|
-- start front panel
|
|
log_render("starting front panel UI...")
|
|
|
|
local fp_message
|
|
crd_state.fp_ok, fp_message = renderer.try_start_fp()
|
|
if not crd_state.fp_ok then
|
|
log_render(util.c("front panel UI error: ", fp_message))
|
|
println_ts("front panel UI creation failed")
|
|
log.fatal(util.c("front panel GUI render failed with error ", fp_message))
|
|
return
|
|
else log_render("front panel ready") end
|
|
|
|
-- create connection watchdog
|
|
smem_sys.conn_watchdog = util.new_watchdog(config.SVR_Timeout)
|
|
smem_sys.conn_watchdog.cancel()
|
|
log.debug("startup> conn watchdog created")
|
|
|
|
-- setup comms
|
|
smem_sys.coord_comms = coordinator.comms(COORDINATOR_VERSION, backplane.active_nic(), backplane.wireless_nic(), smem_sys.conn_watchdog)
|
|
log.debug("startup> comms init")
|
|
log_comms("comms initialized")
|
|
|
|
----------------------------------------
|
|
-- start system
|
|
----------------------------------------
|
|
|
|
-- init threads
|
|
local main_thread = threads.thread__main(__shared_memory)
|
|
local render_thread = threads.thread__render(__shared_memory)
|
|
|
|
log.info("startup> completed")
|
|
|
|
-- run threads
|
|
parallel.waitForAll(main_thread.p_exec, render_thread.p_exec)
|
|
|
|
renderer.close_ui()
|
|
renderer.close_fp()
|
|
sounder.stop()
|
|
log_sys("system shutdown")
|
|
|
|
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
|
|
|
|
-- close on error exit (such as UI error)
|
|
if smem_sys.coord_comms.is_linked() then smem_sys.coord_comms.close() end
|
|
|
|
println_ts("exited")
|
|
log.info("exited")
|
|
end
|
|
|
|
if not xpcall(main, crash.handler) then
|
|
pcall(renderer.close_ui)
|
|
pcall(renderer.close_fp)
|
|
pcall(sounder.stop)
|
|
crash.exit()
|
|
else
|
|
log.close()
|
|
end
|