From 1444008479148c37197db24fa30e92eaefdc27f0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Jul 2022 23:48:01 -0400 Subject: [PATCH] #74 comms establish on boot --- coordinator/config.lua | 2 +- coordinator/coordinator.lua | 116 ++++++++++++++++++++++++++---------- coordinator/database.lua | 54 +++++++++++++++++ coordinator/startup.lua | 18 +++++- scada-common/util.lua | 2 +- 5 files changed, 154 insertions(+), 38 deletions(-) create mode 100644 coordinator/database.lua diff --git a/coordinator/config.lua b/coordinator/config.lua index 12dbeab..48088b5 100644 --- a/coordinator/config.lua +++ b/coordinator/config.lua @@ -6,7 +6,7 @@ config.SCADA_SV_PORT = 16100 config.SCADA_SV_LISTEN = 16101 -- listen port for SCADA coordinator API access config.SCADA_API_LISTEN = 16200 --- expected number of reactor units +-- expected number of reactor units, used only to require that number of unit monitors config.NUM_UNITS = 4 -- graphics color config.RECOLOR = true diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 42fd39e..16aa3e3 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -1,19 +1,15 @@ - local comms = require("scada-common.comms") local log = require("scada-common.log") local ppm = require("scada-common.ppm") -local psil = require("scada-common.psil") local util = require("scada-common.util") local apisessions = require("coordinator.apisessions") +local database = require("coordinator.database") local dialog = require("coordinator.util.dialog") local coordinator = {} ----@class coord_db -local db = {} - local print = util.print local println = util.println local print_ts = util.print_ts @@ -148,36 +144,12 @@ function coordinator.configure_monitors(num_units) return true, monitors end --- initialize the coordinator database ----@param num_units integer number of units expected -function coordinator.init_database(num_units) - db.facility = { - num_units = num_units, - ps = psil.create() - } - - db.units = {} - for i = 1, num_units do - table.insert(db.units, { - uint_id = i, - initialized = false, - - reactor_ps = psil.create(), - reactor_data = {}, - - boiler_ps_tbl = {}, - boiler_data_tbl = {}, - - turbine_ps_tbl = {}, - turbine_data_tbl = {} - }) - end -end - -- dmesg print wrapper ---@param message string message ---@param dmesg_tag string tag -local function log_dmesg(message, dmesg_tag) +---@param working? boolean to use dmesg_working +---@return function? update, function? done +local function log_dmesg(message, dmesg_tag, working) local colors = { GRAPHICS = colors.green, SYSTEM = colors.cyan, @@ -185,7 +157,11 @@ local function log_dmesg(message, dmesg_tag) COMMS = colors.purple } - log.dmesg(message, dmesg_tag, colors[dmesg_tag]) + if working then + return log.dmesg_working(message, dmesg_tag, colors[dmesg_tag]) + else + log.dmesg(message, dmesg_tag, colors[dmesg_tag]) + end end function coordinator.log_graphics(message) log_dmesg(message, "GRAPHICS") end @@ -193,6 +169,10 @@ function coordinator.log_sys(message) log_dmesg(message, "SYSTEM") end function coordinator.log_boot(message) log_dmesg(message, "BOOT") end function coordinator.log_comms(message) log_dmesg(message, "COMMS") end +---@param message string +---@return function update, function done +function coordinator.log_comms_connecting(message) return log_dmesg(message, "COMMS", true) end + -- coordinator communications ---@param version string ---@param modem table @@ -202,6 +182,7 @@ function coordinator.log_comms(message) log_dmesg(message, "COMMS") end ---@param sv_watchdog watchdog function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_watchdog) local self = { + sv_linked = false, sv_seq_num = 0, sv_r_seq_num = nil, modem = modem, @@ -259,6 +240,51 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa _open_channels() end + -- attempt to connect to the subervisor + ---@param timeout_s number timeout in seconds + ---@param tick_dmesg_waiting function callback to tick dmesg waiting + ---@param task_done function callback to show done on dmesg + ---@return boolean sv_linked true if connected, false otherwise + --- EVENT_CONSUMER: this function consumes events + function public.sv_connect(timeout_s, tick_dmesg_waiting, task_done) + local clock = util.new_clock(1) + local start = util.time_s() + local terminated = false + + _send_sv(PROTOCOLS.COORD_DATA, COORD_TYPES.ESTABLISH, {}) + + clock.start() + + while (util.time_s() - start) < timeout_s and not self.sv_linked do +---@diagnostic disable-next-line: undefined-field + local event, p1, p2, p3, p4, p5 = os.pullEventRaw() + + if event == "timer" and clock.is_clock(p1) then + -- timed out attempt, try again + tick_dmesg_waiting(math.max(0, timeout_s - (util.time_s() - start))) + _send_sv(PROTOCOLS.COORD_DATA, COORD_TYPES.ESTABLISH, {}) + clock.start() + elseif event == "modem_message" then + -- handle message + local packet = public.parse_packet(p1, p2, p3, p4, p5) + if packet ~= nil and packet.type == COORD_TYPES.ESTABLISH then + public.handle_packet(packet) + end + elseif event == "terminate" then + terminated = true + task_done(false) + coordinator.log_comms("supervisor connection attempt cancelled by user") + break + end + end + + if not terminated then + task_done(self.sv_linked) + end + + return self.sv_linked + end + -- parse a packet ---@param side string ---@param sender integer @@ -325,6 +351,30 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa -- handle packet if protocol == PROTOCOLS.COORD_DATA then if packet.type == COORD_TYPES.ESTABLISH then + -- connection with supervisor established + if packet.length > 1 then + -- get configuration + + ---@class facility_conf + local conf = { + num_units = packet.data[1], + defs = {} -- boilers and turbines + } + + if (packet.length - 1) == (conf.num_units * 2) then + -- record sequence of pairs of [#boilers, #turbines] per unit + for i = 2, packet.length do + table.insert(conf.defs, packet.data[i]) + end + + -- init database structure + database.init(conf) + else + log.debug("supervisor conn establish packet length mismatch") + end + else + log.debug("supervisor conn establish packet length mismatch") + end elseif packet.type == COORD_TYPES.QUERY_UNIT then elseif packet.type == COORD_TYPES.QUERY_FACILITY then elseif packet.type == COORD_TYPES.COMMAND_UNIT then diff --git a/coordinator/database.lua b/coordinator/database.lua new file mode 100644 index 0000000..b15bf05 --- /dev/null +++ b/coordinator/database.lua @@ -0,0 +1,54 @@ +local psil = require("scada-common.psil") + +local database = {} + +database.WASTE = { Pu = 0, Po = 1, AntiMatter = 2 } + +---@class coord_db +local db = {} + +-- initialize the coordinator database +---@param conf facility_conf configuration +function database.init(conf) + db.facility = { + scram = false, + num_units = conf.num_units, + ps = psil.create() + } + + db.units = {} + for i = 1, conf.num_units do + ---@class coord_db_entry + local entry = { + unit_id = i, ---@type integer + initialized = false, + + waste_mode = 0, + + reactor_ps = psil.create(), + reactor_data = {}, ---@type reactor_db + + boiler_ps_tbl = {}, + boiler_data_tbl = {}, + + turbine_ps_tbl = {}, + turbine_data_tbl = {} + } + + for _ = 1, conf.defs[(i * 2) - 1] do + local data = {} ---@type boiler_session_db|boilerv_session_db + table.insert(entry.boiler_ps_tbl, psil.create()) + table.insert(entry.boiler_data_tbl, data) + end + + for _ = 1, conf.defs[i * 2] do + local data = {} ---@type turbine_session_db|turbinev_session_db + table.insert(entry.turbine_ps_tbl, psil.create()) + table.insert(entry.turbine_data_tbl, data) + end + + table.insert(db.units, entry) + end +end + +return database diff --git a/coordinator/startup.lua b/coordinator/startup.lua index ee0dda4..de36d5b 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -13,7 +13,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.2.4" +local COORDINATOR_VERSION = "alpha-v0.3.0" local print = util.print local println = util.println @@ -24,6 +24,7 @@ local log_graphics = coordinator.log_graphics local log_sys = coordinator.log_sys local log_boot = coordinator.log_boot local log_comms = coordinator.log_comms +local log_comms_connecting = coordinator.log_comms_connecting ---------------------------------------- -- config validation @@ -100,10 +101,21 @@ local coord_comms = coordinator.comms(COORDINATOR_VERSION, modem, config.SCADA_S log.debug("boot> comms init") log_comms("comms initialized") --- base loop clock (6.67Hz, 3 ticks) -local MAIN_CLOCK = 0.15 +-- base loop clock (2Hz, 10 ticks) +local MAIN_CLOCK = 0.5 local loop_clock = util.new_clock(MAIN_CLOCK) +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") + println("boot> failed to connect to supervisor") + log.fatal("failed to connect to supervisor") + log_sys("system shutdown") + return +end + ---------------------------------------- -- start the UI ---------------------------------------- diff --git a/scada-common/util.lua b/scada-common/util.lua index 54a820c..2bd1903 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -201,7 +201,7 @@ end ---@param target_timing integer minimum amount of milliseconds to wait for ---@param last_update integer millisecond time of last update ---@return integer time_now --- EVENT_CONSUMER: this function consumes events +--- EVENT_CONSUMER: this function consumes events function util.adaptive_delay(target_timing, last_update) local sleep_for = target_timing - (util.time() - last_update) -- only if >50ms since worker loops already yield 0.05s