diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index f8cfc76..6845b37 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -230,16 +230,21 @@ function plc.rps_init(reactor, is_formed) local was_tripped = self.tripped local first_trip = false - -- update state - parallel.waitForAll( - _is_formed, - _damage_critical, - _high_temp, - _no_coolant, - _excess_waste, - _excess_heated_coolant, - _insufficient_fuel - ) + if self.formed then + -- update state + parallel.waitForAll( + _is_formed, + _damage_critical, + _high_temp, + _no_coolant, + _excess_waste, + _excess_heated_coolant, + _insufficient_fuel + ) + else + -- check to see if its now formed + _is_formed() + end -- check system states in order of severity if self.tripped then @@ -284,7 +289,11 @@ function plc.rps_init(reactor, is_formed) self.tripped = true self.trip_cause = status - public.scram() + if self.formed then + public.scram() + else + log.warning("RPS: skipping SCRAM due to not being formed") + end end return self.tripped, status, first_trip @@ -322,19 +331,16 @@ end ---@param conn_watchdog watchdog function plc.comms(id, version, modem, local_port, server_port, reactor, rps, conn_watchdog) local self = { - id = id, - version = version, seq_num = 0, r_seq_num = nil, modem = modem, s_port = server_port, l_port = local_port, reactor = reactor, - rps = rps, - conn_watchdog = conn_watchdog, scrammed = false, linked = false, status_cache = nil, + resend_build = false, max_burn_rate = nil } @@ -358,7 +364,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co local s_pkt = comms.scada_packet() local r_pkt = comms.rplc_packet() - r_pkt.make(self.id, msg_type, msg) + r_pkt.make(id, msg_type, msg) s_pkt.make(self.seq_num, PROTOCOLS.RPLC, r_pkt.raw_sendable()) self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) @@ -514,6 +520,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co if not self.reactor.__p_is_faulted() then _send(RPLC_TYPES.MEK_STRUCT, mek_data) + self.resend_build = false else log.error("failed to send structure: PPM fault") end @@ -535,6 +542,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co function public.reconnect_reactor(reactor) self.reactor = reactor self.status_cache = nil + self.resend_build = true end -- unlink from the server @@ -546,30 +554,27 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co -- close the connection to the server function public.close() - self.conn_watchdog.cancel() + conn_watchdog.cancel() public.unlink() _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) end -- attempt to establish link with supervisor function public.send_link_req() - _send(RPLC_TYPES.LINK_REQ, { self.id, self.version }) + _send(RPLC_TYPES.LINK_REQ, { id, version }) end -- send live status information ---@param no_reactor boolean PLC lost reactor connection - ---@param formed boolean reactor formed + ---@param formed boolean reactor formed (from PLC state) function public.send_status(no_reactor, formed) if self.linked then local mek_data = nil ---@type table - local heating_rate = nil ---@type number + local heating_rate = 0.0 ---@type number - if (not no_reactor) and formed then + if (not no_reactor) and rps.is_formed() then if _update_status_cache() then mek_data = self.status_cache - log.debug("sent updated status") - else - log.debug("sent cached status") end heating_rate = self.reactor.getHeatingRate() @@ -586,6 +591,10 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co } _send(RPLC_TYPES.STATUS, sys_status) + + if self.resend_build then + _send_struct() + end end end @@ -661,7 +670,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co end -- feed the watchdog first so it doesn't uhh...eat our packets :) - self.conn_watchdog.feed() + conn_watchdog.feed() local protocol = packet.scada_frame.protocol() @@ -739,11 +748,11 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co elseif packet.type == RPLC_TYPES.RPS_ENABLE then -- enable the reactor self.scrammed = false - _send_ack(packet.type, self.rps.activate()) + _send_ack(packet.type, rps.activate()) elseif packet.type == RPLC_TYPES.RPS_SCRAM then -- disable the reactor self.scrammed = true - self.rps.trip_manual() + rps.trip_manual() _send_ack(packet.type, true) elseif packet.type == RPLC_TYPES.RPS_RESET then -- reset the RPS status @@ -809,7 +818,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co end elseif packet.type == SCADA_MGMT_TYPES.CLOSE then -- handle session close - self.conn_watchdog.cancel() + conn_watchdog.cancel() public.unlink() println_ts("server connection closed by remote host") log.warning("server connection closed by remote host") diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 22dd79a..d26291c 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -13,7 +13,7 @@ local config = require("reactor-plc.config") local plc = require("reactor-plc.plc") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "beta-v0.9.0" +local R_PLC_VERSION = "beta-v0.9.1" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index b700fbd..1369a51 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -84,6 +84,7 @@ function threads.thread__main(smem, init) -- push a connect event and unmount it from the PPM local iface = ppm.get_iface(plc_dev.reactor) if iface then + log.info("unmounting and remounting unformed reactor") ppm.unmount(plc_dev.reactor) local type, device = ppm.mount(iface) @@ -91,27 +92,32 @@ function threads.thread__main(smem, init) if type ~= "fissionReactorLogicAdapter" and device ~= nil then -- reconnect reactor plc_dev.reactor = device - - smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM) - - println_ts("reactor reconnected as formed.") - log.info("reactor reconnected as formed") - plc_state.reactor_formed = device.isFormed() - rps.reconnect_reactor(plc_dev.reactor) - if networked then - plc_comms.reconnect_reactor(plc_dev.reactor) + if plc_state.reactor_formed then + println_ts("reactor reconnected as formed.") + log.info("reactor reconnected as formed") + + -- SCRAM newly connected reactor + smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM) + else + println_ts("reactor reconnected but still not formed.") + log.info("reactor reconnected but still not formed") end -- determine if we are still in a degraded state if not networked or not plc_state.no_modem then plc_state.degraded = false end + + rps.reconnect_reactor(plc_dev.reactor) + if networked then + plc_comms.reconnect_reactor(plc_dev.reactor) + end else -- fully lost the reactor now :( println_ts("reactor lost (failed reconnect)!") - log.error("reactor lost (failed reconnect!") + log.error("reactor lost (failed reconnect)") plc_state.no_reactor = true plc_state.degraded = true @@ -141,7 +147,7 @@ function threads.thread__main(smem, init) if type ~= nil and device ~= nil then if type == "fissionReactorLogicAdapter" then println_ts("reactor disconnected!") - log.error("reactor disconnected!") + log.error("reactor logic adapter disconnected") plc_state.no_reactor = true plc_state.degraded = true @@ -149,7 +155,7 @@ function threads.thread__main(smem, init) -- we only care if this is our wireless modem if device == plc_dev.modem then println_ts("comms modem disconnected!") - log.error("comms modem disconnected!") + log.error("comms modem disconnected") plc_state.no_modem = true @@ -173,25 +179,27 @@ function threads.thread__main(smem, init) -- reconnected reactor plc_dev.reactor = device - smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM) - println_ts("reactor reconnected.") log.info("reactor reconnected") plc_state.no_reactor = false plc_state.reactor_formed = device.isFormed() + -- determine if we are still in a degraded state + if (not networked or not plc_state.no_modem) and plc_state.reactor_formed then + plc_state.degraded = false + end + if plc_state.init_ok then + if plc_state.reactor_formed then + smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM) + end + rps.reconnect_reactor(plc_dev.reactor) if networked then plc_comms.reconnect_reactor(plc_dev.reactor) end end - - -- determine if we are still in a degraded state - if (not networked or not plc_state.no_modem) and plc_state.reactor_formed then - plc_state.degraded = false - end elseif networked and type == "modem" then if device.isWireless() then -- reconnected modem @@ -302,7 +310,7 @@ function threads.thread__rps(smem) -- if we tried to SCRAM but failed, keep trying -- in that case, SCRAM won't be called until it reconnects (this is the expected use of this check) ---@diagnostic disable-next-line: need-check-nil - if not plc_state.no_reactor and rps.is_tripped() and reactor.getStatus() then + if (not plc_state.no_reactor) and rps.is_formed() and rps.is_tripped() and reactor.getStatus() then rps.scram() end diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index e536ca6..d7753cd 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -115,35 +115,35 @@ local function peri_init(iface) -- add default index function to catch undefined indicies - local mt = {} - - function mt.__index(_, key) - -- this will continuously be counting calls here as faults - -- unlike other functions, faults here can't be cleared as it is just not defined - if self.fault_counts[key] == nil then - self.fault_counts[key] = 0 - end - - -- function failed - self.faulted = true - self.last_fault = UNDEFINED_FIELD - - _ppm_sys.faulted = true - _ppm_sys.last_fault = UNDEFINED_FIELD - - if not _ppm_sys.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then - local count_str = "" - if self.fault_counts[key] > 0 then - count_str = " [" .. self.fault_counts[key] .. " total calls]" + local mt = { + __index = function (_, key) + -- this will continuously be counting calls here as faults + -- unlike other functions, faults here can't be cleared as it is just not defined + if self.fault_counts[key] == nil then + self.fault_counts[key] = 0 end - log.error(util.c("PPM: caught undefined function ", key, "()", count_str)) + -- function failed + self.faulted = true + self.last_fault = UNDEFINED_FIELD + + _ppm_sys.faulted = true + _ppm_sys.last_fault = UNDEFINED_FIELD + + if not _ppm_sys.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then + local count_str = "" + if self.fault_counts[key] > 0 then + count_str = " [" .. self.fault_counts[key] .. " total calls]" + end + + log.error(util.c("PPM: caught undefined function ", key, "()", count_str)) + end + + self.fault_counts[key] = self.fault_counts[key] + 1 + + return (function () return ACCESS_FAULT end) end - - self.fault_counts[key] = self.fault_counts[key] + 1 - - return ACCESS_FAULT - end + } setmetatable(self.device, mt) diff --git a/scada-common/types.lua b/scada-common/types.lua index 54b4455..de49cf0 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -105,7 +105,8 @@ types.rps_status_t = { no_fuel = "no_fuel", fault = "fault", timeout = "timeout", - manual = "manual" + manual = "manual", + sys_fail = "sys_fail" } -- turbine steam dumping modes