2022-01-13 15:23:38 +00:00
--
2022-03-25 16:18:33 +00:00
-- Nuclear Generation Facility SCADA Supervisor
--
2022-05-14 17:32:42 +00:00
require ( " /initenv " ) . init_env ( )
2022-11-13 20:56:27 +00:00
local crash = require ( " scada-common.crash " )
2023-05-05 17:04:13 +00:00
local comms = require ( " scada-common.comms " )
2022-08-28 16:12:30 +00:00
local log = require ( " scada-common.log " )
2023-06-25 16:59:38 +00:00
local network = require ( " scada-common.network " )
2022-08-28 16:12:30 +00:00
local ppm = require ( " scada-common.ppm " )
2023-06-03 21:40:57 +00:00
local tcd = require ( " scada-common.tcd " )
2022-08-28 16:12:30 +00:00
local util = require ( " scada-common.util " )
2022-03-25 16:18:33 +00:00
2023-05-05 17:04:13 +00:00
local core = require ( " graphics.core " )
2023-12-29 18:58:28 +00:00
local configure = require ( " supervisor.configure " )
2023-05-05 17:04:13 +00:00
local databus = require ( " supervisor.databus " )
local renderer = require ( " supervisor.renderer " )
2022-05-11 15:31:02 +00:00
local supervisor = require ( " supervisor.supervisor " )
2022-04-22 15:07:59 +00:00
2023-04-17 23:48:03 +00:00
local svsessions = require ( " supervisor.session.svsessions " )
2024-03-06 00:35:54 +00:00
local SUPERVISOR_VERSION = " v1.2.9 "
2022-03-25 16:18:33 +00:00
2022-04-18 15:07:16 +00:00
local println = util.println
local println_ts = util.println_ts
2022-03-25 16:18:33 +00:00
2022-06-05 19:09:02 +00:00
----------------------------------------
2023-12-29 18:58:28 +00:00
-- get configuration
2022-06-05 19:09:02 +00:00
----------------------------------------
2023-12-29 18:58:28 +00:00
if not supervisor.load_config ( ) then
-- try to reconfigure (user action)
local success , error = configure.configure ( true )
if success then
2024-02-19 01:21:07 +00:00
assert ( supervisor.load_config ( ) , " failed to load valid configuration " )
2023-12-29 18:58:28 +00:00
else
assert ( success , " supervisor configuration error: " .. error )
end
end
local config = supervisor.config
2022-06-05 19:09:02 +00:00
local cfv = util.new_validator ( )
2023-12-29 18:58:28 +00:00
cfv.assert_eq ( # config.CoolingConfig , config.UnitCount )
assert ( cfv.valid ( ) , " startup> the number of reactor cooling configurations is different than the number of units " )
for i = 1 , config.UnitCount do
cfv.assert_type_table ( config.CoolingConfig [ i ] )
assert ( cfv.valid ( ) , " startup> missing cooling entry for reactor unit " .. i )
cfv.assert_type_int ( config.CoolingConfig [ i ] . BoilerCount )
cfv.assert_type_int ( config.CoolingConfig [ i ] . TurbineCount )
cfv.assert_type_bool ( config.CoolingConfig [ i ] . TankConnection )
assert ( cfv.valid ( ) , " startup> missing boiler/turbine/tank fields for reactor unit " .. i )
cfv.assert_range ( config.CoolingConfig [ i ] . BoilerCount , 0 , 2 )
cfv.assert_range ( config.CoolingConfig [ i ] . TurbineCount , 1 , 3 )
assert ( cfv.valid ( ) , " startup> out-of-range number of boilers and/or turbines provided for reactor unit " .. i )
2022-06-05 19:09:02 +00:00
end
2023-12-31 19:14:35 +00:00
if config.FacilityTankMode > 0 then
assert ( config.UnitCount == # config.FacilityTankDefs , " startup> the number of facility tank definitions must be equal to the number of units in facility tank mode " )
for i = 1 , config.UnitCount do
local def = config.FacilityTankDefs [ i ]
cfv.assert_type_int ( def )
cfv.assert_range ( def , 0 , 2 )
assert ( cfv.valid ( ) , " startup> invalid facility tank definition for reactor unit " .. i )
end
end
2022-06-05 19:09:02 +00:00
----------------------------------------
-- log init
----------------------------------------
2023-12-31 00:21:44 +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 supervisor.startup " .. SUPERVISOR_VERSION )
log.info ( " ======================================== " )
2022-04-18 15:07:16 +00:00
println ( " >> SCADA Supervisor " .. SUPERVISOR_VERSION .. " << " )
2022-03-25 16:18:33 +00:00
2022-11-13 20:56:27 +00:00
crash.set_env ( " supervisor " , SUPERVISOR_VERSION )
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 ( )
----------------------------------------
-- startup
----------------------------------------
2022-03-25 16:18:33 +00:00
2023-05-05 17:04:13 +00:00
-- record firmware versions and ID
databus.tx_versions ( SUPERVISOR_VERSION , comms.version )
2022-11-13 20:56:27 +00:00
-- mount connected devices
ppm.mount_all ( )
2023-06-25 18:00:18 +00:00
-- message authentication init
2023-12-31 03:57:30 +00:00
if type ( config.AuthKey ) == " string " and string.len ( config.AuthKey ) > 0 then
2023-12-29 18:58:28 +00:00
network.init_mac ( config.AuthKey )
2023-06-25 18:00:18 +00:00
end
-- get modem
2022-11-13 20:56:27 +00:00
local modem = ppm.get_wireless_modem ( )
if modem == nil then
2023-02-25 04:36:16 +00:00
println ( " startup> wireless modem not found " )
2022-11-13 20:56:27 +00:00
log.fatal ( " no wireless modem on startup " )
return
end
2022-03-25 16:18:33 +00:00
2023-05-05 17:04:13 +00:00
databus.tx_hw_modem ( true )
-- start UI
2023-09-30 17:31:41 +00:00
local fp_ok , message = renderer.try_start_ui ( )
2023-05-05 17:04:13 +00:00
if not fp_ok then
println_ts ( util.c ( " UI error: " , message ) )
2023-07-11 19:15:44 +00:00
log.error ( util.c ( " front panel GUI render failed with error " , message ) )
2023-05-05 17:04:13 +00:00
else
2023-06-03 19:45:48 +00:00
-- redefine println_ts local to not print as we have the front panel running
println_ts = function ( _ ) end
end
2023-06-25 16:59:38 +00:00
-- create network interface then setup comms
local nic = network.nic ( modem )
local superv_comms = supervisor.comms ( SUPERVISOR_VERSION , nic , fp_ok )
2022-11-13 20:56:27 +00:00
-- base loop clock (6.67Hz, 3 ticks)
local MAIN_CLOCK = 0.15
local loop_clock = util.new_clock ( MAIN_CLOCK )
-- start clock
loop_clock.start ( )
2023-06-03 19:45:48 +00:00
-- halve the rate heartbeat LED flash
local heartbeat_toggle = true
2022-11-13 20:56:27 +00:00
-- 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
2023-06-25 16:59:38 +00:00
if nic.is_modem ( device ) then
nic.disconnect ( )
2022-11-13 20:56:27 +00:00
println_ts ( " wireless modem disconnected! " )
2023-02-25 04:36:16 +00:00
log.warning ( " comms modem disconnected " )
2023-06-25 16:59:38 +00:00
2023-07-11 22:22:09 +00:00
local other_modem = ppm.get_wireless_modem ( )
if other_modem then
log.info ( " found another wireless modem, using it for comms " )
nic.connect ( other_modem )
else
databus.tx_hw_modem ( false )
end
2022-11-13 20:56:27 +00:00
else
log.warning ( " non-comms modem disconnected " )
end
2022-05-10 21:08:38 +00:00
end
2022-04-18 15:07:16 +00:00
end
2022-11-13 20:56:27 +00:00
elseif event == " peripheral " then
local type , device = ppm.mount ( param1 )
if type ~= nil and device ~= nil then
if type == " modem " then
2023-07-11 22:22:09 +00:00
if device.isWireless ( ) and not nic.is_connected ( ) then
2022-11-13 20:56:27 +00:00
-- reconnected modem
2023-06-25 16:59:38 +00:00
nic.connect ( device )
2022-11-13 20:56:27 +00:00
println_ts ( " wireless modem reconnected. " )
2023-02-25 04:36:16 +00:00
log.info ( " comms modem reconnected " )
2023-06-03 19:45:48 +00:00
databus.tx_hw_modem ( true )
2023-07-12 01:06:47 +00:00
elseif device.isWireless ( ) then
log.info ( " unused wireless modem reconnected " )
2022-11-13 20:56:27 +00:00
else
2023-02-25 04:36:16 +00:00
log.info ( " wired modem reconnected " )
2022-11-13 20:56:27 +00:00
end
2022-05-10 21:08:38 +00:00
end
2022-04-18 15:07:16 +00:00
end
2022-11-13 20:56:27 +00:00
elseif event == " timer " and loop_clock.is_clock ( param1 ) then
-- main loop tick
2023-06-03 19:45:48 +00:00
if heartbeat_toggle then databus.heartbeat ( ) end
heartbeat_toggle = not heartbeat_toggle
2022-11-13 20:56:27 +00:00
-- 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 )
2023-06-03 19:45:48 +00:00
-- notify timer callback dispatcher
tcd.handle ( param1 )
2022-11-13 20:56:27 +00:00
elseif event == " modem_message " then
-- got a packet
local packet = superv_comms.parse_packet ( param1 , param2 , param3 , param4 , param5 )
superv_comms.handle_packet ( packet )
2023-10-04 02:52:13 +00:00
elseif event == " mouse_click " or event == " mouse_up " or event == " mouse_drag " or event == " mouse_scroll " or
2023-09-23 16:58:09 +00:00
event == " double_click " then
2023-06-03 19:45:48 +00:00
-- handle a mouse event
renderer.handle_mouse ( core.events . new_mouse_event ( event , param1 , param2 , param3 ) )
2022-04-18 15:07:16 +00:00
end
2022-11-13 20:56:27 +00:00
-- 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
2022-03-25 16:18:33 +00:00
end
2022-11-13 20:56:27 +00:00
2023-06-03 19:45:48 +00:00
renderer.close_ui ( )
util.println_ts ( " exited " )
2022-11-13 20:56:27 +00:00
log.info ( " exited " )
2022-03-25 16:18:33 +00:00
end
2022-04-18 15:07:16 +00:00
2023-05-05 17:04:13 +00:00
if not xpcall ( main , crash.handler ) then
pcall ( renderer.close_ui )
crash.exit ( )
else
log.close ( )
end