#124 debug stack trace on error

This commit is contained in:
Mikayla Fischler 2022-11-13 15:56:27 -05:00
parent e679b5a25a
commit 9761228b8e
6 changed files with 816 additions and 725 deletions

View File

@ -13,6 +13,7 @@
"window", "window",
"read", "read",
"periphemu", "periphemu",
"mekanismEnergyHelper" "mekanismEnergyHelper",
"_HOST"
] ]
} }

View File

@ -4,6 +4,7 @@
require("/initenv").init_env() require("/initenv").init_env()
local crash = require("scada-common.crash")
local log = require("scada-common.log") local log = require("scada-common.log")
local ppm = require("scada-common.ppm") local ppm = require("scada-common.ppm")
local tcallbackdsp = require("scada-common.tcallbackdsp") local tcallbackdsp = require("scada-common.tcallbackdsp")
@ -16,7 +17,7 @@ local config = require("coordinator.config")
local coordinator = require("coordinator.coordinator") local coordinator = require("coordinator.coordinator")
local renderer = require("coordinator.renderer") local renderer = require("coordinator.renderer")
local COORDINATOR_VERSION = "alpha-v0.6.11" local COORDINATOR_VERSION = "alpha-v0.6.12"
local print = util.print local print = util.print
local println = util.println local println = util.println
@ -57,262 +58,272 @@ log.info("BOOTING coordinator.startup " .. COORDINATOR_VERSION)
log.info("========================================") log.info("========================================")
println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<") println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<")
---------------------------------------- crash.set_env("coordinator", COORDINATOR_VERSION)
-- system startup
----------------------------------------
-- mount connected devices
ppm.mount_all()
-- setup monitors
local configured, monitors = coordinator.configure_monitors(config.NUM_UNITS)
if not configured or monitors == nil then
println("boot> monitor setup failed")
log.fatal("monitor configuration failed")
return
end
log.info("monitors ready, dmesg output incoming...")
-- init renderer
renderer.set_displays(monitors)
renderer.reset(config.RECOLOR)
renderer.init_dmesg()
log_graphics("displays connected and reset")
log_sys("system start on " .. os.date("%c"))
log_boot("starting " .. COORDINATOR_VERSION)
---------------------------------------- ----------------------------------------
-- setup communications -- main application
---------------------------------------- ----------------------------------------
-- get the communications modem local function main()
local modem = ppm.get_wireless_modem() ----------------------------------------
if modem == nil then -- system startup
log_comms("wireless modem not found") ----------------------------------------
println("boot> wireless modem not found")
log.fatal("no wireless modem on startup")
return
else
log_comms("wireless modem connected")
end
-- create connection watchdog -- mount connected devices
local conn_watchdog = util.new_watchdog(5) ppm.mount_all()
conn_watchdog.cancel()
log.debug("boot> conn watchdog created")
-- start comms, open all channels -- setup monitors
local coord_comms = coordinator.comms(COORDINATOR_VERSION, modem, config.SCADA_SV_PORT, config.SCADA_SV_LISTEN, config.SCADA_API_LISTEN, conn_watchdog) local configured, monitors = coordinator.configure_monitors(config.NUM_UNITS)
log.debug("boot> comms init") if not configured or monitors == nil then
log_comms("comms initialized") println("boot> monitor setup failed")
log.fatal("monitor configuration failed")
-- base loop clock (2Hz, 10 ticks) return
local MAIN_CLOCK = 0.5
local loop_clock = util.new_clock(MAIN_CLOCK)
----------------------------------------
-- connect to the supervisor
----------------------------------------
-- attempt to connect to the supervisor or exit
local function init_connect_sv()
local tick_waiting, task_done = log_comms_connecting("attempting to connect to configured supervisor on channel " .. config.SCADA_SV_PORT)
-- attempt to establish a connection with the supervisory computer
if not coord_comms.sv_connect(60, tick_waiting, task_done) then
log_comms("supervisor connection failed")
log.fatal("failed to connect to supervisor")
return false
end end
return true log.info("monitors ready, dmesg output incoming...")
end
if not init_connect_sv() then -- init renderer
println("boot> failed to connect to supervisor") renderer.set_displays(monitors)
log_sys("system shutdown") renderer.reset(config.RECOLOR)
return renderer.init_dmesg()
else
log_sys("supervisor connected, proceeding to UI start")
end
---------------------------------------- log_graphics("displays connected and reset")
-- start the UI log_sys("system start on " .. os.date("%c"))
---------------------------------------- log_boot("starting " .. COORDINATOR_VERSION)
-- start up the UI ----------------------------------------
---@return boolean ui_ok started ok -- setup communications
local function init_start_ui() ----------------------------------------
log_graphics("starting UI...")
local draw_start = util.time_ms() -- get the communications modem
local modem = ppm.get_wireless_modem()
local ui_ok, message = pcall(renderer.start_ui) if modem == nil then
if not ui_ok then log_comms("wireless modem not found")
renderer.close_ui() println("boot> wireless modem not found")
log_graphics(util.c("UI crashed: ", message)) log.fatal("no wireless modem on startup")
println_ts("UI crashed") return
log.fatal(util.c("ui crashed with error ", message))
else else
log_graphics("first UI draw took " .. (util.time_ms() - draw_start) .. "ms") log_comms("wireless modem connected")
-- start clock
loop_clock.start()
end end
return ui_ok -- create connection watchdog
end local conn_watchdog = util.new_watchdog(5)
conn_watchdog.cancel()
log.debug("boot> conn watchdog created")
local ui_ok = init_start_ui() -- start comms, open all channels
local coord_comms = coordinator.comms(COORDINATOR_VERSION, modem, config.SCADA_SV_PORT, config.SCADA_SV_LISTEN, config.SCADA_API_LISTEN, conn_watchdog)
log.debug("boot> comms init")
log_comms("comms initialized")
---------------------------------------- -- base loop clock (2Hz, 10 ticks)
-- main event loop local MAIN_CLOCK = 0.5
---------------------------------------- local loop_clock = util.new_clock(MAIN_CLOCK)
local no_modem = false ----------------------------------------
-- connect to the supervisor
----------------------------------------
if ui_ok then -- attempt to connect to the supervisor or exit
-- start connection watchdog local function init_connect_sv()
conn_watchdog.feed() local tick_waiting, task_done = log_comms_connecting("attempting to connect to configured supervisor on channel " .. config.SCADA_SV_PORT)
log.debug("boot> conn watchdog started")
log_sys("system started successfully") -- attempt to establish a connection with the supervisory computer
end if not coord_comms.sv_connect(60, tick_waiting, task_done) then
log_comms("supervisor connection failed")
log.fatal("failed to connect to supervisor")
return false
end
-- main event loop return true
while ui_ok do end
local event, param1, param2, param3, param4, param5 = util.pull_event()
-- handle event if not init_connect_sv() then
if event == "peripheral_detach" then println("boot> failed to connect to supervisor")
local type, device = ppm.handle_unmount(param1) log_sys("system shutdown")
return
else
log_sys("supervisor connected, proceeding to UI start")
end
if type ~= nil and device ~= nil then ----------------------------------------
if type == "modem" then -- start the UI
-- we only really care if this is our wireless modem ----------------------------------------
if device == modem then
no_modem = true
log_sys("comms modem disconnected")
println_ts("wireless modem disconnected!")
log.error("comms modem disconnected!")
-- close out UI -- start up the UI
renderer.close_ui() ---@return boolean ui_ok started ok
local function init_start_ui()
log_graphics("starting UI...")
-- alert user to status local draw_start = util.time_ms()
log_sys("awaiting comms modem reconnect...")
else local ui_ok, message = pcall(renderer.start_ui)
log_sys("non-comms modem disconnected") if not ui_ok then
log.warning("non-comms modem disconnected") renderer.close_ui()
end log_graphics(util.c("UI crashed: ", message))
elseif type == "monitor" then println_ts("UI crashed")
if renderer.is_monitor_used(device) then log.fatal(util.c("ui crashed with error ", message))
-- "halt and catch fire" style handling else
log_sys("lost a configured monitor, system will now exit") log_graphics("first UI draw took " .. (util.time_ms() - draw_start) .. "ms")
break
else -- start clock
log_sys("lost unused monitor, ignoring") loop_clock.start()
end
return ui_ok
end
local ui_ok = init_start_ui()
----------------------------------------
-- main event loop
----------------------------------------
local no_modem = false
if ui_ok then
-- start connection watchdog
conn_watchdog.feed()
log.debug("boot> conn watchdog started")
log_sys("system started successfully")
end
-- main event loop
while ui_ok do
local event, param1, param2, param3, param4, param5 = util.pull_event()
-- handle event
if event == "peripheral_detach" then
local type, device = ppm.handle_unmount(param1)
if type ~= nil and device ~= nil then
if type == "modem" then
-- we only really care if this is our wireless modem
if device == modem then
no_modem = true
log_sys("comms modem disconnected")
println_ts("wireless modem disconnected!")
log.error("comms modem disconnected!")
-- close out UI
renderer.close_ui()
-- alert user to status
log_sys("awaiting comms modem reconnect...")
else
log_sys("non-comms modem disconnected")
log.warning("non-comms modem disconnected")
end
elseif type == "monitor" then
if renderer.is_monitor_used(device) then
-- "halt and catch fire" style handling
log_sys("lost a configured monitor, system will now exit")
break
else
log_sys("lost unused monitor, ignoring")
end
end end
end end
end elseif event == "peripheral" then
elseif event == "peripheral" then local type, device = ppm.mount(param1)
local type, device = ppm.mount(param1)
if type ~= nil and device ~= nil then if type ~= nil and device ~= nil then
if type == "modem" then if type == "modem" then
if device.isWireless() then if device.isWireless() then
-- reconnected modem -- reconnected modem
no_modem = false no_modem = false
modem = device modem = device
coord_comms.reconnect_modem(modem) coord_comms.reconnect_modem(modem)
log_sys("comms modem reconnected") log_sys("comms modem reconnected")
println_ts("wireless modem reconnected.") println_ts("wireless modem reconnected.")
-- re-init system -- re-init system
if not init_connect_sv() then break end
ui_ok = init_start_ui()
else
log_sys("wired modem reconnected")
end
elseif type == "monitor" then
-- not supported, system will exit on loss of in-use monitors
end
end
elseif event == "timer" then
if loop_clock.is_clock(param1) then
-- main loop tick
-- free any closed sessions
--apisessions.free_all_closed()
loop_clock.start()
elseif conn_watchdog.is_timer(param1) then
-- supervisor watchdog timeout
local msg = "supervisor server timeout"
log_comms(msg)
println_ts(msg)
log.warning(msg)
-- close connection and UI
coord_comms.close()
renderer.close_ui()
if not no_modem then
-- try to re-connect to the supervisor
if not init_connect_sv() then break end if not init_connect_sv() then break end
ui_ok = init_start_ui() ui_ok = init_start_ui()
else
log_sys("wired modem reconnected")
end end
elseif type == "monitor" then else
-- not supported, system will exit on loss of in-use monitors -- a non-clock/main watchdog timer event
--check API watchdogs
--apisessions.check_all_watchdogs(param1)
-- notify timer callback dispatcher
tcallbackdsp.handle(param1)
end end
elseif event == "modem_message" then
-- got a packet
local packet = coord_comms.parse_packet(param1, param2, param3, param4, param5)
coord_comms.handle_packet(packet)
-- check if it was a disconnect
if not coord_comms.is_linked() then
log_comms("supervisor closed connection")
-- close connection and UI
coord_comms.close()
renderer.close_ui()
if not no_modem then
-- try to re-connect to the supervisor
if not init_connect_sv() then break end
ui_ok = init_start_ui()
end
end
elseif event == "monitor_touch" then
-- handle a monitor touch event
renderer.handle_touch(core.events.touch(param1, param2, param3))
end end
elseif event == "timer" then
if loop_clock.is_clock(param1) then
-- main loop tick
-- free any closed sessions -- check for termination request
--apisessions.free_all_closed() if event == "terminate" or ppm.should_terminate() then
println_ts("terminate requested, closing connections...")
loop_clock.start() log_comms("terminate requested, closing supervisor connection...")
elseif conn_watchdog.is_timer(param1) then
-- supervisor watchdog timeout
local msg = "supervisor server timeout"
log_comms(msg)
println_ts(msg)
log.warning(msg)
-- close connection and UI
coord_comms.close() coord_comms.close()
renderer.close_ui() log_comms("supervisor connection closed")
log_comms("closing api sessions...")
if not no_modem then apisessions.close_all()
-- try to re-connect to the supervisor log_comms("api sessions closed")
if not init_connect_sv() then break end break
ui_ok = init_start_ui()
end
else
-- a non-clock/main watchdog timer event
--check API watchdogs
--apisessions.check_all_watchdogs(param1)
-- notify timer callback dispatcher
tcallbackdsp.handle(param1)
end end
elseif event == "modem_message" then
-- got a packet
local packet = coord_comms.parse_packet(param1, param2, param3, param4, param5)
coord_comms.handle_packet(packet)
-- check if it was a disconnect
if not coord_comms.is_linked() then
log_comms("supervisor closed connection")
-- close connection and UI
coord_comms.close()
renderer.close_ui()
if not no_modem then
-- try to re-connect to the supervisor
if not init_connect_sv() then break end
ui_ok = init_start_ui()
end
end
elseif event == "monitor_touch" then
-- handle a monitor touch event
renderer.handle_touch(core.events.touch(param1, param2, param3))
end end
-- check for termination request renderer.close_ui()
if event == "terminate" or ppm.should_terminate() then log_sys("system shutdown")
println_ts("terminate requested, closing connections...")
log_comms("terminate requested, closing supervisor connection...") println_ts("exited")
coord_comms.close() log.info("exited")
log_comms("supervisor connection closed")
log_comms("closing api sessions...")
apisessions.close_all()
log_comms("api sessions closed")
break
end
end end
renderer.close_ui() if not xpcall(main, crash.handler) then crash.exit() end
log_sys("system shutdown")
println_ts("exited")
log.info("exited")

View File

@ -4,6 +4,7 @@
require("/initenv").init_env() require("/initenv").init_env()
local crash = require("scada-common.crash")
local log = require("scada-common.log") local log = require("scada-common.log")
local mqueue = require("scada-common.mqueue") local mqueue = require("scada-common.mqueue")
local ppm = require("scada-common.ppm") local ppm = require("scada-common.ppm")
@ -13,7 +14,7 @@ local config = require("reactor-plc.config")
local plc = require("reactor-plc.plc") local plc = require("reactor-plc.plc")
local threads = require("reactor-plc.threads") local threads = require("reactor-plc.threads")
local R_PLC_VERSION = "beta-v0.9.6" local R_PLC_VERSION = "beta-v0.9.7"
local print = util.print local print = util.print
local println = util.println local println = util.println
@ -45,166 +46,176 @@ log.info("BOOTING reactor-plc.startup " .. R_PLC_VERSION)
log.info("========================================") log.info("========================================")
println(">> Reactor PLC " .. R_PLC_VERSION .. " <<") println(">> Reactor PLC " .. R_PLC_VERSION .. " <<")
crash.set_env("plc", R_PLC_VERSION)
---------------------------------------- ----------------------------------------
-- startup -- main application
---------------------------------------- ----------------------------------------
-- mount connected devices local function main()
ppm.mount_all() ----------------------------------------
-- startup
----------------------------------------
-- shared memory across threads -- mount connected devices
---@class plc_shared_memory ppm.mount_all()
local __shared_memory = {
-- networked setting
networked = config.NETWORKED, ---@type boolean
-- PLC system state flags -- shared memory across threads
---@class plc_state ---@class plc_shared_memory
plc_state = { local __shared_memory = {
init_ok = true, -- networked setting
shutdown = false, networked = config.NETWORKED, ---@type boolean
degraded = false,
reactor_formed = true,
no_reactor = false,
no_modem = false
},
-- control setpoints -- PLC system state flags
---@class setpoints ---@class plc_state
setpoints = { plc_state = {
burn_rate_en = false, init_ok = true,
burn_rate = 0.0 shutdown = false,
}, degraded = false,
reactor_formed = true,
no_reactor = false,
no_modem = false
},
-- core PLC devices -- control setpoints
plc_dev = { ---@class setpoints
reactor = ppm.get_fission_reactor(), setpoints = {
modem = ppm.get_wireless_modem() burn_rate_en = false,
}, burn_rate = 0.0
},
-- system objects -- core PLC devices
plc_sys = { plc_dev = {
rps = nil, ---@type rps reactor = ppm.get_fission_reactor(),
plc_comms = nil, ---@type plc_comms modem = ppm.get_wireless_modem()
conn_watchdog = nil ---@type watchdog },
},
-- message queues -- system objects
q = { plc_sys = {
mq_rps = mqueue.new(), rps = nil, ---@type rps
mq_comms_tx = mqueue.new(), plc_comms = nil, ---@type plc_comms
mq_comms_rx = mqueue.new() conn_watchdog = nil ---@type watchdog
},
-- message queues
q = {
mq_rps = mqueue.new(),
mq_comms_tx = mqueue.new(),
mq_comms_rx = mqueue.new()
}
} }
}
local smem_dev = __shared_memory.plc_dev local smem_dev = __shared_memory.plc_dev
local smem_sys = __shared_memory.plc_sys local smem_sys = __shared_memory.plc_sys
local plc_state = __shared_memory.plc_state local plc_state = __shared_memory.plc_state
-- we need a reactor, can at least do some things even if it isn't formed though -- we need a reactor, can at least do some things even if it isn't formed though
if smem_dev.reactor == nil then if smem_dev.reactor == nil then
println("boot> fission reactor not found"); 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.init_ok = false
plc_state.degraded = true plc_state.degraded = true
plc_state.no_reactor = true plc_state.no_reactor = true
elseif not smem_dev.reactor.isFormed() then elseif not smem_dev.reactor.isFormed() then
println("boot> fission reactor not formed"); println("boot> fission reactor not formed");
log.warning("reactor logic adapter present, but reactor is not formed") log.warning("reactor logic adapter present, but reactor is not formed")
plc_state.degraded = true plc_state.degraded = true
plc_state.reactor_formed = false plc_state.reactor_formed = false
end
-- modem is required if networked
if __shared_memory.networked and smem_dev.modem == nil then
println("boot> wireless modem not found")
log.warning("no wireless modem on startup")
-- scram reactor if present and enabled
if (smem_dev.reactor ~= nil) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then
smem_dev.reactor.scram()
end end
plc_state.init_ok = false -- modem is required if networked
plc_state.degraded = true if __shared_memory.networked and smem_dev.modem == nil then
plc_state.no_modem = true println("boot> wireless modem not found")
end log.warning("no wireless modem on startup")
-- PLC init -- scram reactor if present and enabled
--- if (smem_dev.reactor ~= nil) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then
--- EVENT_CONSUMER: this function consumes events
local function init()
if plc_state.init_ok then
-- just booting up, no fission allowed (neutrons stay put thanks)
if plc_state.reactor_formed and smem_dev.reactor.getStatus() then
smem_dev.reactor.scram() smem_dev.reactor.scram()
end end
-- init reactor protection system plc_state.init_ok = false
smem_sys.rps = plc.rps_init(smem_dev.reactor, plc_state.reactor_formed) plc_state.degraded = true
log.debug("init> rps init") plc_state.no_modem = true
end
if __shared_memory.networked then -- PLC init
-- comms watchdog, 3 second timeout ---
smem_sys.conn_watchdog = util.new_watchdog(3) --- EVENT_CONSUMER: this function consumes events
log.debug("init> conn watchdog started") local function init()
if plc_state.init_ok then
-- just booting up, no fission allowed (neutrons stay put thanks)
if plc_state.reactor_formed and smem_dev.reactor.getStatus() then
smem_dev.reactor.scram()
end
-- start comms -- init reactor protection system
smem_sys.plc_comms = plc.comms(config.REACTOR_ID, R_PLC_VERSION, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, smem_sys.rps = plc.rps_init(smem_dev.reactor, plc_state.reactor_formed)
smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog) log.debug("init> rps init")
log.debug("init> comms init")
if __shared_memory.networked then
-- comms watchdog, 3 second timeout
smem_sys.conn_watchdog = util.new_watchdog(3)
log.debug("init> conn watchdog started")
-- start comms
smem_sys.plc_comms = plc.comms(config.REACTOR_ID, R_PLC_VERSION, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT,
smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog)
log.debug("init> comms init")
else
println("boot> starting in offline mode")
log.debug("init> running without networking")
end
util.push_event("clock_start")
println("boot> completed")
log.debug("init> boot completed")
else else
println("boot> starting in offline mode") println("boot> system in degraded state, awaiting devices...")
log.debug("init> running without networking") log.warning("init> booted in a degraded state, awaiting peripheral connections...")
end end
end
util.push_event("clock_start") ----------------------------------------
-- start system
----------------------------------------
println("boot> completed") -- initialize PLC
log.debug("init> boot completed") init()
-- init threads
local main_thread = threads.thread__main(__shared_memory, init)
local rps_thread = threads.thread__rps(__shared_memory)
if __shared_memory.networked then
-- init comms threads
local comms_thread_tx = threads.thread__comms_tx(__shared_memory)
local comms_thread_rx = threads.thread__comms_rx(__shared_memory)
-- setpoint control only needed when networked
local sp_ctrl_thread = threads.thread__setpoint_control(__shared_memory)
-- run threads
parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec, comms_thread_tx.p_exec, comms_thread_rx.p_exec, sp_ctrl_thread.p_exec)
if plc_state.init_ok then
-- send status one last time after RPS shutdown
smem_sys.plc_comms.send_status(plc_state.no_reactor, plc_state.reactor_formed)
smem_sys.plc_comms.send_rps_status()
-- close connection
smem_sys.plc_comms.close()
end
else else
println("boot> system in degraded state, awaiting devices...") -- run threads, excluding comms
log.warning("init> booted in a degraded state, awaiting peripheral connections...") parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec)
end end
println_ts("exited")
log.info("exited")
end end
---------------------------------------- if not xpcall(main, crash.handler) then crash.exit() end
-- start system
----------------------------------------
-- initialize PLC
init()
-- init threads
local main_thread = threads.thread__main(__shared_memory, init)
local rps_thread = threads.thread__rps(__shared_memory)
if __shared_memory.networked then
-- init comms threads
local comms_thread_tx = threads.thread__comms_tx(__shared_memory)
local comms_thread_rx = threads.thread__comms_rx(__shared_memory)
-- setpoint control only needed when networked
local sp_ctrl_thread = threads.thread__setpoint_control(__shared_memory)
-- run threads
parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec, comms_thread_tx.p_exec, comms_thread_rx.p_exec, sp_ctrl_thread.p_exec)
if plc_state.init_ok then
-- send status one last time after RPS shutdown
smem_sys.plc_comms.send_status(plc_state.no_reactor, plc_state.reactor_formed)
smem_sys.plc_comms.send_rps_status()
-- close connection
smem_sys.plc_comms.close()
end
else
-- run threads, excluding comms
parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec)
end
println_ts("exited")
log.info("exited")

View File

@ -4,6 +4,7 @@
require("/initenv").init_env() require("/initenv").init_env()
local crash = require("scada-common.crash")
local log = require("scada-common.log") local log = require("scada-common.log")
local mqueue = require("scada-common.mqueue") local mqueue = require("scada-common.mqueue")
local ppm = require("scada-common.ppm") local ppm = require("scada-common.ppm")
@ -24,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu")
local sps_rtu = require("rtu.dev.sps_rtu") local sps_rtu = require("rtu.dev.sps_rtu")
local turbinev_rtu = require("rtu.dev.turbinev_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu")
local RTU_VERSION = "beta-v0.9.3" local RTU_VERSION = "beta-v0.9.4"
local rtu_t = types.rtu_t local rtu_t = types.rtu_t
@ -58,341 +59,351 @@ log.info("BOOTING rtu.startup " .. RTU_VERSION)
log.info("========================================") log.info("========================================")
println(">> RTU GATEWAY " .. RTU_VERSION .. " <<") println(">> RTU GATEWAY " .. RTU_VERSION .. " <<")
crash.set_env("rtu", RTU_VERSION)
---------------------------------------- ----------------------------------------
-- startup -- main application
---------------------------------------- ----------------------------------------
-- mount connected devices local function main()
ppm.mount_all() ----------------------------------------
-- startup
----------------------------------------
---@class rtu_shared_memory -- mount connected devices
local __shared_memory = { ppm.mount_all()
-- RTU system state flags
---@class rtu_state
rtu_state = {
linked = false,
shutdown = false
},
-- core RTU devices ---@class rtu_shared_memory
rtu_dev = { local __shared_memory = {
modem = ppm.get_wireless_modem() -- RTU system state flags
}, ---@class rtu_state
rtu_state = {
linked = false,
shutdown = false
},
-- system objects -- core RTU devices
rtu_sys = { rtu_dev = {
rtu_comms = nil, ---@type rtu_comms modem = ppm.get_wireless_modem()
conn_watchdog = nil, ---@type watchdog },
units = {} ---@type table
},
-- message queues -- system objects
q = { rtu_sys = {
mq_comms = mqueue.new() rtu_comms = nil, ---@type rtu_comms
conn_watchdog = nil, ---@type watchdog
units = {} ---@type table
},
-- message queues
q = {
mq_comms = mqueue.new()
}
} }
}
local smem_dev = __shared_memory.rtu_dev local smem_dev = __shared_memory.rtu_dev
local smem_sys = __shared_memory.rtu_sys local smem_sys = __shared_memory.rtu_sys
-- get modem -- get modem
if smem_dev.modem == nil then if smem_dev.modem == nil then
println("boot> wireless modem not found") println("boot> wireless modem not found")
log.fatal("no wireless modem on startup") log.fatal("no wireless modem on startup")
return return
end end
---------------------------------------- ----------------------------------------
-- interpret config and init units -- interpret config and init units
---------------------------------------- ----------------------------------------
local units = __shared_memory.rtu_sys.units local units = __shared_memory.rtu_sys.units
local rtu_redstone = config.RTU_REDSTONE local rtu_redstone = config.RTU_REDSTONE
local rtu_devices = config.RTU_DEVICES local rtu_devices = config.RTU_DEVICES
-- configure RTU gateway based on config file definitions -- configure RTU gateway based on config file definitions
local function configure() local function configure()
-- redstone interfaces -- redstone interfaces
for entry_idx = 1, #rtu_redstone do for entry_idx = 1, #rtu_redstone do
local rs_rtu = redstone_rtu.new() local rs_rtu = redstone_rtu.new()
local io_table = rtu_redstone[entry_idx].io ---@type table local io_table = rtu_redstone[entry_idx].io ---@type table
local io_reactor = rtu_redstone[entry_idx].for_reactor ---@type integer local io_reactor = rtu_redstone[entry_idx].for_reactor ---@type integer
-- CHECK: reactor ID must be >= to 1 -- CHECK: reactor ID must be >= to 1
if (not util.is_int(io_reactor)) or (io_reactor <= 0) then if (not util.is_int(io_reactor)) or (io_reactor <= 0) then
println(util.c("configure> redstone entry #", entry_idx, " : ", io_reactor, " isn't an integer >= 1")) println(util.c("configure> redstone entry #", entry_idx, " : ", io_reactor, " isn't an integer >= 1"))
return false return false
end end
-- CHECK: io table exists -- CHECK: io table exists
if type(io_table) ~= "table" then if type(io_table) ~= "table" then
println(util.c("configure> redstone entry #", entry_idx, " no IO table found")) println(util.c("configure> redstone entry #", entry_idx, " no IO table found"))
return false return false
end end
local capabilities = {} local capabilities = {}
log.debug(util.c("configure> starting redstone RTU I/O linking for reactor ", io_reactor, "...")) log.debug(util.c("configure> starting redstone RTU I/O linking for reactor ", io_reactor, "..."))
local continue = true local continue = true
-- check for duplicate entries -- check for duplicate entries
for i = 1, #units do for i = 1, #units do
local unit = units[i] ---@type rtu_unit_registry_entry local unit = units[i] ---@type rtu_unit_registry_entry
if unit.reactor == io_reactor and unit.type == rtu_t.redstone then if unit.reactor == io_reactor and unit.type == rtu_t.redstone then
-- duplicate entry -- duplicate entry
local message = util.c("configure> skipping definition block #", entry_idx, " for reactor ", io_reactor, local message = util.c("configure> skipping definition block #", entry_idx, " for reactor ", io_reactor,
" with already defined redstone I/O") " with already defined redstone I/O")
println(message) println(message)
log.warning(message) log.warning(message)
continue = false continue = false
break break
end
end
-- not a duplicate
if continue then
for i = 1, #io_table do
local valid = false
local conf = io_table[i]
-- verify configuration
if rsio.is_valid_channel(conf.channel) and rsio.is_valid_side(conf.side) then
if conf.bundled_color then
valid = rsio.is_color(conf.bundled_color)
else
valid = true
end
end
if not valid then
local message = util.c("configure> invalid redstone definition at index ", i, " in definition block #", entry_idx,
" (for reactor ", io_reactor, ")")
println(message)
log.error(message)
return false
else
-- link redstone in RTU
local mode = rsio.get_io_mode(conf.channel)
if mode == rsio.IO_MODE.DIGITAL_IN then
-- can't have duplicate inputs
if util.table_contains(capabilities, conf.channel) then
local message = util.c("configure> skipping duplicate input for channel ", rsio.to_string(conf.channel), " on side ", conf.side)
println(message)
log.warning(message)
else
rs_rtu.link_di(conf.side, conf.bundled_color)
end
elseif mode == rsio.IO_MODE.DIGITAL_OUT then
rs_rtu.link_do(conf.channel, conf.side, conf.bundled_color)
elseif mode == rsio.IO_MODE.ANALOG_IN then
-- can't have duplicate inputs
if util.table_contains(capabilities, conf.channel) then
local message = util.c("configure> skipping duplicate input for channel ", rsio.to_string(conf.channel), " on side ", conf.side)
println(message)
log.warning(message)
else
rs_rtu.link_ai(conf.side)
end
elseif mode == rsio.IO_MODE.ANALOG_OUT then
rs_rtu.link_ao(conf.side)
else
-- should be unreachable code, we already validated channels
log.error("configure> fell through if chain attempting to identify IO mode", true)
println("configure> encountered a software error, check logs")
return false
end
table.insert(capabilities, conf.channel)
log.debug(util.c("configure> linked redstone ", #capabilities, ": ", rsio.to_string(conf.channel),
" (", conf.side, ") for reactor ", io_reactor))
end
end
---@class rtu_unit_registry_entry
local unit = {
name = "redstone_io",
type = rtu_t.redstone,
index = entry_idx,
reactor = io_reactor,
device = capabilities, -- use device field for redstone channels
formed = nil, ---@type boolean|nil
rtu = rs_rtu, ---@type rtu_device|rtu_rs_device
modbus_io = modbus.new(rs_rtu, false),
pkt_queue = nil, ---@type mqueue|nil
thread = nil
}
table.insert(units, unit)
log.debug(util.c("init> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for reactor ", io_reactor))
end end
end end
-- not a duplicate -- mounted peripherals
if continue then for i = 1, #rtu_devices do
for i = 1, #io_table do local name = rtu_devices[i].name
local valid = false local index = rtu_devices[i].index
local conf = io_table[i] local for_reactor = rtu_devices[i].for_reactor
-- verify configuration -- CHECK: name is a string
if rsio.is_valid_channel(conf.channel) and rsio.is_valid_side(conf.side) then if type(name) ~= "string" then
if conf.bundled_color then println(util.c("configure> device entry #", i, ": device ", name, " isn't a string"))
valid = rsio.is_color(conf.bundled_color) return false
else end
valid = true
end
end
if not valid then -- CHECK: index is an integer >= 1
local message = util.c("configure> invalid redstone definition at index ", i, " in definition block #", entry_idx, if (not util.is_int(index)) or (index <= 0) then
" (for reactor ", io_reactor, ")") println(util.c("configure> device entry #", i, ": index ", index, " isn't an integer >= 1"))
println(message) return false
log.error(message) end
-- CHECK: reactor is an integer >= 1
if (not util.is_int(for_reactor)) or (for_reactor <= 0) then
println(util.c("configure> device entry #", i, ": reactor ", for_reactor, " isn't an integer >= 1"))
return false
end
local device = ppm.get_periph(name)
local type = nil
local rtu_iface = nil ---@type rtu_device
local rtu_type = ""
local formed = nil ---@type boolean|nil
if device == nil then
local message = util.c("configure> '", name, "' not found, using placeholder")
println(message)
log.warning(message)
-- mount a virtual (placeholder) device
type, device = ppm.mount_virtual()
else
type = ppm.get_type(name)
end
if type == "boilerValve" then
-- boiler multiblock
rtu_type = rtu_t.boiler_valve
rtu_iface = boilerv_rtu.new(device)
formed = device.isFormed()
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
println_ts(util.c("configure> failed to check if '", name, "' is formed"))
log.fatal(util.c("configure> failed to check if '", name, "' is a formed boiler multiblock"))
return false return false
else
-- link redstone in RTU
local mode = rsio.get_io_mode(conf.channel)
if mode == rsio.IO_MODE.DIGITAL_IN then
-- can't have duplicate inputs
if util.table_contains(capabilities, conf.channel) then
local message = util.c("configure> skipping duplicate input for channel ", rsio.to_string(conf.channel), " on side ", conf.side)
println(message)
log.warning(message)
else
rs_rtu.link_di(conf.side, conf.bundled_color)
end
elseif mode == rsio.IO_MODE.DIGITAL_OUT then
rs_rtu.link_do(conf.channel, conf.side, conf.bundled_color)
elseif mode == rsio.IO_MODE.ANALOG_IN then
-- can't have duplicate inputs
if util.table_contains(capabilities, conf.channel) then
local message = util.c("configure> skipping duplicate input for channel ", rsio.to_string(conf.channel), " on side ", conf.side)
println(message)
log.warning(message)
else
rs_rtu.link_ai(conf.side)
end
elseif mode == rsio.IO_MODE.ANALOG_OUT then
rs_rtu.link_ao(conf.side)
else
-- should be unreachable code, we already validated channels
log.error("configure> fell through if chain attempting to identify IO mode", true)
println("configure> encountered a software error, check logs")
return false
end
table.insert(capabilities, conf.channel)
log.debug(util.c("configure> linked redstone ", #capabilities, ": ", rsio.to_string(conf.channel),
" (", conf.side, ") for reactor ", io_reactor))
end end
elseif type == "turbineValve" then
-- turbine multiblock
rtu_type = rtu_t.turbine_valve
rtu_iface = turbinev_rtu.new(device)
formed = device.isFormed()
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
println_ts(util.c("configure> failed to check if '", name, "' is formed"))
log.fatal(util.c("configure> failed to check if '", name, "' is a formed turbine multiblock"))
return false
end
elseif type == "inductionPort" then
-- induction matrix multiblock
rtu_type = rtu_t.induction_matrix
rtu_iface = imatrix_rtu.new(device)
formed = device.isFormed()
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
println_ts(util.c("configure> failed to check if '", name, "' is formed"))
log.fatal(util.c("configure> failed to check if '", name, "' is a formed induction matrix multiblock"))
return false
end
elseif type == "spsPort" then
-- SPS multiblock
rtu_type = rtu_t.sps
rtu_iface = sps_rtu.new(device)
formed = device.isFormed()
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
println_ts(util.c("configure> failed to check if '", name, "' is formed"))
log.fatal(util.c("configure> failed to check if '", name, "' is a formed SPS multiblock"))
return false
end
elseif type == "solarNeutronActivator" then
-- SNA
rtu_type = rtu_t.sna
rtu_iface = sna_rtu.new(device)
elseif type == "environmentDetector" then
-- advanced peripherals environment detector
rtu_type = rtu_t.env_detector
rtu_iface = envd_rtu.new(device)
elseif type == ppm.VIRTUAL_DEVICE_TYPE then
-- placeholder device
rtu_type = "virtual"
rtu_iface = rtu.init_unit().interface()
else
local message = util.c("configure> device '", name, "' is not a known type (", type, ")")
println_ts(message)
log.fatal(message)
return false
end end
---@class rtu_unit_registry_entry ---@class rtu_unit_registry_entry
local unit = { local rtu_unit = {
name = "redstone_io", name = name,
type = rtu_t.redstone, type = rtu_type,
index = entry_idx, index = index,
reactor = io_reactor, reactor = for_reactor,
device = capabilities, -- use device field for redstone channels device = device,
formed = nil, ---@type boolean|nil formed = formed,
rtu = rs_rtu, ---@type rtu_device|rtu_rs_device rtu = rtu_iface, ---@type rtu_device|rtu_rs_device
modbus_io = modbus.new(rs_rtu, false), modbus_io = modbus.new(rtu_iface, true),
pkt_queue = nil, ---@type mqueue|nil pkt_queue = mqueue.new(), ---@type mqueue|nil
thread = nil thread = nil
} }
table.insert(units, unit) rtu_unit.thread = threads.thread__unit_comms(__shared_memory, rtu_unit)
log.debug(util.c("init> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for reactor ", io_reactor)) table.insert(units, rtu_unit)
log.debug(util.c("configure> initialized RTU unit #", #units, ": ", name, " (", rtu_type, ") [", index, "] for reactor ", for_reactor))
end end
-- we made it through all that trusting-user-to-write-a-config-file chaos
return true
end end
-- mounted peripherals ----------------------------------------
for i = 1, #rtu_devices do -- start system
local name = rtu_devices[i].name ----------------------------------------
local index = rtu_devices[i].index
local for_reactor = rtu_devices[i].for_reactor
-- CHECK: name is a string log.debug("boot> running configure()")
if type(name) ~= "string" then
println(util.c("configure> device entry #", i, ": device ", name, " isn't a string"))
return false
end
-- CHECK: index is an integer >= 1 if configure() then
if (not util.is_int(index)) or (index <= 0) then -- start connection watchdog
println(util.c("configure> device entry #", i, ": index ", index, " isn't an integer >= 1")) smem_sys.conn_watchdog = util.new_watchdog(5)
return false log.debug("boot> conn watchdog started")
end
-- CHECK: reactor is an integer >= 1 -- setup comms
if (not util.is_int(for_reactor)) or (for_reactor <= 0) then smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, smem_sys.conn_watchdog)
println(util.c("configure> device entry #", i, ": reactor ", for_reactor, " isn't an integer >= 1")) log.debug("boot> comms init")
return false
end
local device = ppm.get_periph(name) -- init threads
local main_thread = threads.thread__main(__shared_memory)
local comms_thread = threads.thread__comms(__shared_memory)
local type = nil -- assemble thread list
local rtu_iface = nil ---@type rtu_device local _threads = { main_thread.p_exec, comms_thread.p_exec }
local rtu_type = "" for i = 1, #units do
local formed = nil ---@type boolean|nil if units[i].thread ~= nil then
table.insert(_threads, units[i].thread.p_exec)
if device == nil then
local message = util.c("configure> '", name, "' not found, using placeholder")
println(message)
log.warning(message)
-- mount a virtual (placeholder) device
type, device = ppm.mount_virtual()
else
type = ppm.get_type(name)
end
if type == "boilerValve" then
-- boiler multiblock
rtu_type = rtu_t.boiler_valve
rtu_iface = boilerv_rtu.new(device)
formed = device.isFormed()
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
println_ts(util.c("configure> failed to check if '", name, "' is formed"))
log.fatal(util.c("configure> failed to check if '", name, "' is a formed boiler multiblock"))
return false
end end
elseif type == "turbineValve" then
-- turbine multiblock
rtu_type = rtu_t.turbine_valve
rtu_iface = turbinev_rtu.new(device)
formed = device.isFormed()
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
println_ts(util.c("configure> failed to check if '", name, "' is formed"))
log.fatal(util.c("configure> failed to check if '", name, "' is a formed turbine multiblock"))
return false
end
elseif type == "inductionPort" then
-- induction matrix multiblock
rtu_type = rtu_t.induction_matrix
rtu_iface = imatrix_rtu.new(device)
formed = device.isFormed()
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
println_ts(util.c("configure> failed to check if '", name, "' is formed"))
log.fatal(util.c("configure> failed to check if '", name, "' is a formed induction matrix multiblock"))
return false
end
elseif type == "spsPort" then
-- SPS multiblock
rtu_type = rtu_t.sps
rtu_iface = sps_rtu.new(device)
formed = device.isFormed()
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
println_ts(util.c("configure> failed to check if '", name, "' is formed"))
log.fatal(util.c("configure> failed to check if '", name, "' is a formed SPS multiblock"))
return false
end
elseif type == "solarNeutronActivator" then
-- SNA
rtu_type = rtu_t.sna
rtu_iface = sna_rtu.new(device)
elseif type == "environmentDetector" then
-- advanced peripherals environment detector
rtu_type = rtu_t.env_detector
rtu_iface = envd_rtu.new(device)
elseif type == ppm.VIRTUAL_DEVICE_TYPE then
-- placeholder device
rtu_type = "virtual"
rtu_iface = rtu.init_unit().interface()
else
local message = util.c("configure> device '", name, "' is not a known type (", type, ")")
println_ts(message)
log.fatal(message)
return false
end end
---@class rtu_unit_registry_entry -- run threads
local rtu_unit = { parallel.waitForAll(table.unpack(_threads))
name = name, else
type = rtu_type, println("configuration failed, exiting...")
index = index,
reactor = for_reactor,
device = device,
formed = formed,
rtu = rtu_iface, ---@type rtu_device|rtu_rs_device
modbus_io = modbus.new(rtu_iface, true),
pkt_queue = mqueue.new(), ---@type mqueue|nil
thread = nil
}
rtu_unit.thread = threads.thread__unit_comms(__shared_memory, rtu_unit)
table.insert(units, rtu_unit)
log.debug(util.c("configure> initialized RTU unit #", #units, ": ", name, " (", rtu_type, ") [", index, "] for reactor ", for_reactor))
end end
-- we made it through all that trusting-user-to-write-a-config-file chaos println_ts("exited")
return true log.info("exited")
end end
---------------------------------------- if not xpcall(main, crash.handler) then crash.exit() end
-- start system
----------------------------------------
log.debug("boot> running configure()")
if configure() then
-- start connection watchdog
smem_sys.conn_watchdog = util.new_watchdog(5)
log.debug("boot> conn watchdog started")
-- setup comms
smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, smem_sys.conn_watchdog)
log.debug("boot> comms init")
-- init threads
local main_thread = threads.thread__main(__shared_memory)
local comms_thread = threads.thread__comms(__shared_memory)
-- assemble thread list
local _threads = { main_thread.p_exec, comms_thread.p_exec }
for i = 1, #units do
if units[i].thread ~= nil then
table.insert(_threads, units[i].thread.p_exec)
end
end
-- run threads
parallel.waitForAll(table.unpack(_threads))
else
println("configuration failed, exiting...")
end
println_ts("exited")
log.info("exited")

46
scada-common/crash.lua Normal file
View File

@ -0,0 +1,46 @@
--
-- Crash Handler
--
local comms = require("scada-common.comms")
local log = require("scada-common.log")
local util = require("scada-common.util")
local crash = {}
local app = "unknown"
local ver = "v0.0.0"
local err = ""
-- set crash environment
---@param application string app name
---@param version string version
function crash.set_env(application, version)
app = application
ver = version
end
-- handle a crash error
---@param error string error message
function crash.handler(error)
err = error
log.info("=====> FATAL SOFTWARE FAULT <=====")
log.fatal(error)
log.info("----------------------------------")
log.info(util.c("RUNTIME: ", _HOST))
log.info(util.c("LUA VERSION: ", _VERSION))
log.info(util.c("APPLICATION: ", app))
log.info(util.c("FIRMWARE VERSION: ", ver))
log.info(util.c("COMMS VERSION: ", comms.version))
log.info("----------------------------------")
log.info(debug.traceback("--- begin debug trace ---", 1))
log.info("--- end debug trace ---")
end
-- final error print on failed xpcall, app exits here
function crash.exit()
util.println("fatal error occured in main application:")
error(err, 0)
end
return crash

View File

@ -4,6 +4,7 @@
require("/initenv").init_env() require("/initenv").init_env()
local crash = require("scada-common.crash")
local log = require("scada-common.log") local log = require("scada-common.log")
local ppm = require("scada-common.ppm") local ppm = require("scada-common.ppm")
local util = require("scada-common.util") local util = require("scada-common.util")
@ -13,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions")
local config = require("supervisor.config") local config = require("supervisor.config")
local supervisor = require("supervisor.supervisor") local supervisor = require("supervisor.supervisor")
local SUPERVISOR_VERSION = "beta-v0.7.5" local SUPERVISOR_VERSION = "beta-v0.7.6"
local print = util.print local print = util.print
local println = util.println local println = util.println
@ -57,95 +58,105 @@ log.info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION)
log.info("========================================") log.info("========================================")
println(">> SCADA Supervisor " .. SUPERVISOR_VERSION .. " <<") println(">> SCADA Supervisor " .. SUPERVISOR_VERSION .. " <<")
crash.set_env("supervisor", SUPERVISOR_VERSION)
---------------------------------------- ----------------------------------------
-- startup -- main application
---------------------------------------- ----------------------------------------
-- mount connected devices local function main()
ppm.mount_all() ----------------------------------------
-- startup
----------------------------------------
local modem = ppm.get_wireless_modem() -- mount connected devices
if modem == nil then ppm.mount_all()
println("boot> wireless modem not found")
log.fatal("no wireless modem on startup")
return
end
-- start comms, open all channels local modem = ppm.get_wireless_modem()
local superv_comms = supervisor.comms(SUPERVISOR_VERSION, config.NUM_REACTORS, config.REACTOR_COOLING, modem, if modem == nil then
config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN) println("boot> wireless modem not found")
log.fatal("no wireless modem on startup")
-- base loop clock (6.67Hz, 3 ticks) return
local MAIN_CLOCK = 0.15
local loop_clock = util.new_clock(MAIN_CLOCK)
-- start clock
loop_clock.start()
-- event loop
while true do
local event, param1, param2, param3, param4, param5 = util.pull_event()
-- handle event
if event == "peripheral_detach" then
local type, device = ppm.handle_unmount(param1)
if type ~= nil and device ~= nil then
if type == "modem" then
-- we only care if this is our wireless modem
if device == modem then
println_ts("wireless modem disconnected!")
log.error("comms modem disconnected!")
else
log.warning("non-comms modem disconnected")
end
end
end
elseif event == "peripheral" then
local type, device = ppm.mount(param1)
if type ~= nil and device ~= nil then
if type == "modem" then
if device.isWireless() then
-- reconnected modem
modem = device
superv_comms.reconnect_modem(modem)
println_ts("wireless modem reconnected.")
log.info("comms modem reconnected.")
else
log.info("wired modem reconnected.")
end
end
end
elseif event == "timer" and loop_clock.is_clock(param1) then
-- main loop tick
-- iterate sessions
svsessions.iterate_all()
-- free any closed sessions
svsessions.free_all_closed()
loop_clock.start()
elseif event == "timer" then
-- a non-clock timer event, check watchdogs
svsessions.check_all_watchdogs(param1)
elseif event == "modem_message" then
-- got a packet
local packet = superv_comms.parse_packet(param1, param2, param3, param4, param5)
superv_comms.handle_packet(packet)
end end
-- check for termination request -- start comms, open all channels
if event == "terminate" or ppm.should_terminate() then local superv_comms = supervisor.comms(SUPERVISOR_VERSION, config.NUM_REACTORS, config.REACTOR_COOLING, modem,
println_ts("closing sessions...") config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN)
log.info("terminate requested, closing sessions...")
svsessions.close_all() -- base loop clock (6.67Hz, 3 ticks)
log.info("sessions closed") local MAIN_CLOCK = 0.15
break local loop_clock = util.new_clock(MAIN_CLOCK)
-- start clock
loop_clock.start()
-- event loop
while true do
local event, param1, param2, param3, param4, param5 = util.pull_event()
-- handle event
if event == "peripheral_detach" then
local type, device = ppm.handle_unmount(param1)
if type ~= nil and device ~= nil then
if type == "modem" then
-- we only care if this is our wireless modem
if device == modem then
println_ts("wireless modem disconnected!")
log.error("comms modem disconnected!")
else
log.warning("non-comms modem disconnected")
end
end
end
elseif event == "peripheral" then
local type, device = ppm.mount(param1)
if type ~= nil and device ~= nil then
if type == "modem" then
if device.isWireless() then
-- reconnected modem
modem = device
superv_comms.reconnect_modem(modem)
println_ts("wireless modem reconnected.")
log.info("comms modem reconnected.")
else
log.info("wired modem reconnected.")
end
end
end
elseif event == "timer" and loop_clock.is_clock(param1) then
-- main loop tick
-- iterate sessions
svsessions.iterate_all()
-- free any closed sessions
svsessions.free_all_closed()
loop_clock.start()
elseif event == "timer" then
-- a non-clock timer event, check watchdogs
svsessions.check_all_watchdogs(param1)
elseif event == "modem_message" then
-- got a packet
local packet = superv_comms.parse_packet(param1, param2, param3, param4, param5)
superv_comms.handle_packet(packet)
end
-- check for termination request
if event == "terminate" or ppm.should_terminate() then
println_ts("closing sessions...")
log.info("terminate requested, closing sessions...")
svsessions.close_all()
log.info("sessions closed")
break
end
end end
println_ts("exited")
log.info("exited")
end end
println_ts("exited") if not xpcall(main, crash.handler) then crash.exit() end
log.info("exited")