From 0dac25d9e7d792761018adb44b439078bb572fcd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 28 Dec 2021 21:46:38 -0500 Subject: [PATCH 001/587] reorganization --- {main => controller}/controller.lua | 0 {main => controller}/defs.lua | 0 {main => controller}/log.lua | 0 {main => controller}/reactor.lua | 0 {main => controller}/regulator.lua | 0 {main => controller}/render.lua | 0 {main => controller}/server.lua | 0 signal-router.lua => rcss/signal-router.lua | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename {main => controller}/controller.lua (100%) rename {main => controller}/defs.lua (100%) rename {main => controller}/log.lua (100%) rename {main => controller}/reactor.lua (100%) rename {main => controller}/regulator.lua (100%) rename {main => controller}/render.lua (100%) rename {main => controller}/server.lua (100%) rename signal-router.lua => rcss/signal-router.lua (100%) diff --git a/main/controller.lua b/controller/controller.lua similarity index 100% rename from main/controller.lua rename to controller/controller.lua diff --git a/main/defs.lua b/controller/defs.lua similarity index 100% rename from main/defs.lua rename to controller/defs.lua diff --git a/main/log.lua b/controller/log.lua similarity index 100% rename from main/log.lua rename to controller/log.lua diff --git a/main/reactor.lua b/controller/reactor.lua similarity index 100% rename from main/reactor.lua rename to controller/reactor.lua diff --git a/main/regulator.lua b/controller/regulator.lua similarity index 100% rename from main/regulator.lua rename to controller/regulator.lua diff --git a/main/render.lua b/controller/render.lua similarity index 100% rename from main/render.lua rename to controller/render.lua diff --git a/main/server.lua b/controller/server.lua similarity index 100% rename from main/server.lua rename to controller/server.lua diff --git a/signal-router.lua b/rcss/signal-router.lua similarity index 100% rename from signal-router.lua rename to rcss/signal-router.lua From 26cce3a46adabc5f858e83fb268852bfcd2e27cd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 1 Jan 2022 19:45:33 -0500 Subject: [PATCH 002/587] reactor control and safety system attempting server connection --- common/comms.lua | 157 ++++++++++++++++++++++++++++++ common/util.lua | 29 ++++++ rcass/config.lua | 6 ++ rcass/rcass.lua | 122 +++++++++++++++++++++++ rcass/safety.lua | 80 +++++++++++++++ {rcss => rcass}/signal-router.lua | 0 rcass/startup.lua | 13 +++ 7 files changed, 407 insertions(+) create mode 100644 common/comms.lua create mode 100644 common/util.lua create mode 100644 rcass/config.lua create mode 100644 rcass/rcass.lua create mode 100644 rcass/safety.lua rename {rcss => rcass}/signal-router.lua (100%) create mode 100644 rcass/startup.lua diff --git a/common/comms.lua b/common/comms.lua new file mode 100644 index 0000000..4e47eb3 --- /dev/null +++ b/common/comms.lua @@ -0,0 +1,157 @@ + +function server_comms() + local self = { + reactor_struct_cache = nil + } + + local record_struct = function (id, mek_data) + end + + -- send the structure data by request to pocket computers + local send_struct = function () + end + + local command_waste = function () + end +end + +function rcass_comms(id, modem, local_port, server_port, reactor) + local self = { + _id = id, + _modem = modem, + _server = server_port, + _local = local_port, + _reactor = reactor, + _status_cache = nil, + + _send = function (msg) + self._modem.transmit(self._server, self._local, msg) + end + } + + local _send = function (msg) + self._modem.transmit(self._server, self._local, msg) + end + + -- variable reactor status information, excluding heating rate + local _reactor_status = function () + return { + status = self._reactor.getStatus(), + burn_rate = self._reactor.getBurnRate(), + act_burn_r = self._reactor.getActualBurnRate(), + temp = self._reactor.getTemperature(), + damage = self._reactor.getDamagePercent(), + boil_eff = self._reactor.getBoilEfficiency(), + env_loss = self._reactor.getEnvironmentalLoss(), + + fuel = self._reactor.getFuel(), + fuel_need = self._reactor.getFuelNeeded(), + fuel_fill = self._reactor.getFuelFilledPercentage(), + waste = self._reactor.getWaste(), + waste_need = self._reactor.getWasteNeeded(), + waste_fill = self._reactor.getWasteFilledPercentage(), + cool_type = self._reactor.getCoolant()['name'], + cool_amnt = self._reactor.getCoolant()['amount'], + cool_need = self._reactor.getCoolantNeeded(), + cool_fill = self._reactor.getCoolantFilledPercentage(), + hcool_type = self._reactor.getHeatedCoolant()['name'], + hcool_amnt = self._reactor.getHeatedCoolant()['amount'], + hcool_need = self._reactor.getHeatedCoolantNeeded(), + hcool_fill = self._reactor.getHeatedCoolantFilledPercentage() + } + end + + local _status_changed = function () + local status = self._reactor_status() + local changed = false + + for key, value in pairs() do + if value ~= _status_cache[key] then + changed = true + break + end + end + + return changed + end + + -- attempt to establish link with + local send_link_req = function () + local linking_data = { + id = self._id, + type = "link_req" + } + + _send(linking_data) + end + + -- send structure properties (these should not change) + -- server will cache these + local send_struct = function () + local mek_data = { + heat_cap = self._reactor.getHeatCapacity(), + fuel_asm = self._reactor.getFuelAssemblies(), + fuel_sa = self._reactor.getFuelSurfaceArea(), + fuel_cap = self._reactor.getFuelCapacity(), + waste_cap = self._reactor.getWasteCapacity(), + cool_cap = self._reactor.getCoolantCapacity(), + hcool_cap = self._reactor.getHeatedCoolantCapacity(), + max_burn = self._reactor.getMaxBurnRate() + } + + local struct_packet = { + id = self._id, + type = "struct_data", + mek_data = mek_data + } + + _send(struct_packet) + end + + -- send live status information + local send_status = function () + local mek_data = self._reactor_status() + + local sys_data = { + timestamp = os.time(), + control_state = false, + overridden = false, + faults = {}, + waste_production = "antimatter" -- "plutonium", "polonium", "antimatter" + } + end + + local send_keep_alive = function () + -- heating rate is volatile, so it is skipped in status + -- send it with keep alive packets + local mek_data = { + heating_rate = self._reactor.getHeatingRate() + } + + -- basic keep alive packet to server + local keep_alive_packet = { + id = self._id, + type = "keep_alive", + timestamp = os.time(), + mek_data = mek_data + } + + _send(keep_alive_packet) + end + + local handle_link = function (packet) + if packet.type == "link_response" then + return packet.accepted + else + return "wrong_type" + end + end + + return { + send_link_req = send_link_req, + send_struct = send_struct, + send_status = send_status, + send_keep_alive = send_keep_alive, + handle_link = handle_link + } +end \ No newline at end of file diff --git a/common/util.lua b/common/util.lua new file mode 100644 index 0000000..f6fd611 --- /dev/null +++ b/common/util.lua @@ -0,0 +1,29 @@ +-- timestamped print +function print_ts(message) + term.write(os.date("[%H:%M:%S] ") .. message) +end + +-- ComputerCraft OS Timer based Watchdog +-- triggers a timer event if not fed within 'timeout' seconds +function new_watchdog(timeout) + local self = { + _timeout = timeout, + _wd_timer = os.startTimer(_timeout) + } + + local get_timer = function () + return self._wd_timer + end + + local feed = function () + if self._wd_timer ~= nil then + os.cancelTimer(self._wd_timer) + end + self._wd_timer = os.startTimer(self._timeout) + end + + return { + get_timer = get_timer, + feed = feed + } +end diff --git a/rcass/config.lua b/rcass/config.lua new file mode 100644 index 0000000..f79895c --- /dev/null +++ b/rcass/config.lua @@ -0,0 +1,6 @@ +-- unique reactor ID +REACTOR_ID = 1 +-- port to send packets TO server +SERVER_PORT = 1000 +-- port to listen to incoming packets FROM server +LISTEN_PORT = 1001 diff --git a/rcass/rcass.lua b/rcass/rcass.lua new file mode 100644 index 0000000..8738896 --- /dev/null +++ b/rcass/rcass.lua @@ -0,0 +1,122 @@ +-- +-- RCaSS: Reactor Controller and Safety Subsystem +-- + +os.loadAPI("common/util.lua") +os.loadAPI("common/comms.lua") +os.loadAPI("rcass/config.lua") +os.loadAPI("rcass/safety.lua") + +local RCASS_VERSION = "alpha-v0.1" + +local print_ts = util.print_ts + +local reactor = peripheral.find("fissionReactor") +local modem = peripheral.find("modem") + +print(">> RCaSS " .. RCASS_VERSION .. " <<") + +-- we need a reactor and a modem +if reactor == nil then + print("Fission reactor not found, exiting..."); + return +elseif modem == nil then + print("No modem found, disabling reactor and exiting...") + reactor.scram() + return +end + +-- just booting up, no fission allowed (neutrons stay put thanks) +reactor.scram() + +-- init internal safety system +local iss = safety.iss_init(reactor) +local iss_status = "ok" +local iss_tripped = false + +-- read config + +-- start comms +if not modem.isOpen(config.LISTEN_PORT) then + modem.open(config.LISTEN_PORT) +end + +local comms = comms.rcass_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) + +-- attempt server connection +local linked = false +local link_timeout = os.startTimer(5) +comms.send_link_req() +print_ts("sent link request") +repeat + local event, param1, param2, param3, param4, param5 = os.pullEvent() + + -- handle event + if event == "timer" and param1 == link_timeout then + -- no response yet + print("...no response"); + comms.send_link_req() + print_ts("sent link request") + link_timeout = os.startTimer(5) + elseif event == "modem_message" then + -- server response? cancel timeout + if link_timeout ~= nil then + os.cancelTimer(link_timeout) + end + + local packet = { + side = param1, + sender = param2, + reply_to = param3, + message = param4, + distance = param5 + } + + -- handle response + response = comms.handle_link(packet) + if response == "wrong_type" then + print_ts("invalid link response, bad channel?\n") + return + elseif response == true then + print_ts("...linked!\n") + linked = true + else + print_ts("...denied, exiting\n") + return + end + end +until linked + +-- comms watchdog, 3 second timeout +local conn_watchdog = watchdog.new_watchdog(3) + +-- loop clock (10Hz, 2 ticks) +-- send status updates at 4Hz (every 5 ticks) +local loop_tick = os.startTimer(0.05) +local ticks_to_update = 5 + +-- event loop +while true do + local event, param1, param2, param3, param4, param5 = os.pullEvent() + + -- check safety (SCRAM occurs if tripped) + iss_status, iss_tripped = iss.check() + + -- handle event + if event == "timer" and param1 == loop_tick then + -- basic event tick, send updated data if it is time + ticks_to_update = ticks_to_update - 1 + if ticks_to_update == 0 then + ticks_to_update = 5 + end + elseif event == "modem_message" then + -- got a packet + -- feed the watchdog first so it doesn't eat our packets + conn_watchdog.feed() + + elseif event == "timer" and param1 == conn_watchdog.get_timer() then + -- haven't heard from server recently? shutdown + reactor.scram() + print_ts("[alert] server timeout, reactor disabled\n") + end +end diff --git a/rcass/safety.lua b/rcass/safety.lua new file mode 100644 index 0000000..29733ab --- /dev/null +++ b/rcass/safety.lua @@ -0,0 +1,80 @@ +-- Internal Safety System +-- identifies dangerous states and SCRAMs reactor if warranted +-- autonomous from main control +function iss_init(reactor) + local self = { + _reactor = reactor, + _tripped = false, + _trip_cause = "" + } + + local check = function () + local status = "ok" + + -- check system states in order of severity + if self.damage_critical() then + status = "dmg_crit" + elseif self.high_temp() then + status = "high_temp" + elseif self.excess_heated_coolant() then + status = "heated_coolant_backup" + elseif self.excess_waste() then + status = "full_waste" + elseif self.insufficient_fuel() then + status = "no_fuel" + elseif self._tripped then + status = self._trip_cause + else + self._tripped = false + end + + if status ~= "ok" then + self._tripped = true + self._trip_cause = status + self._reactor.scram() + end + + return self._tripped, status + end + + local reset = function () + self._tripped = false + self._trip_cause = "" + end + + local damage_critical = function () + return self._reactor.getDamagePercent() >= 100 + end + + local excess_heated_coolant = function () + return self._reactor.getHeatedCoolantNeeded() == 0 + end + + local excess_waste = function () + return self._reactor.getWasteNeeded() == 0 + end + + local high_temp = function () + -- mekanism: MAX_DAMAGE_TEMPERATURE = 1_200 + return self._reactor.getTemperature() >= 1200 + end + + local insufficient_fuel = function () + return self._reactor.getFuel() == 0 + end + + local no_coolant = function() + return self._reactor.getCoolantFilledPercentage() < 2 + end + + return { + check = check, + reset = reset, + damage_critical = damage_critical, + excess_heated_coolant = excess_heated_coolant, + excess_waste = excess_waste, + high_temp = high_temp, + insufficient_fuel = insufficient_fuel, + no_coolant = no_coolant + } +end diff --git a/rcss/signal-router.lua b/rcass/signal-router.lua similarity index 100% rename from rcss/signal-router.lua rename to rcass/signal-router.lua diff --git a/rcass/startup.lua b/rcass/startup.lua new file mode 100644 index 0000000..a0b746a --- /dev/null +++ b/rcass/startup.lua @@ -0,0 +1,13 @@ +print(">>RCASS LOADER START<<") +print(">>CHECKING SETTINGS...") +loaded = settings.load("rcass.settings") +if loaded then + print(">>SETTINGS FOUND, VERIFIYING INTEGRITY...") + settings.getNames() +else + print(">>SETTINGS NOT FOUND") + print(">>LAUNCHING CONFIGURATOR...") + shell.run("config") +end +print(">>LAUNCHING RCASS...") +shell.run("rcass") From ab49322fec3eff984f9237ba9f2c7a7402eb7c83 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 1 Jan 2022 21:01:05 -0500 Subject: [PATCH 003/587] archive old controller --- controller/{ => old-controller}/controller.lua | 0 controller/{ => old-controller}/defs.lua | 0 controller/{ => old-controller}/log.lua | 0 controller/{ => old-controller}/reactor.lua | 0 controller/{ => old-controller}/regulator.lua | 0 controller/{ => old-controller}/render.lua | 0 controller/{ => old-controller}/server.lua | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename controller/{ => old-controller}/controller.lua (100%) rename controller/{ => old-controller}/defs.lua (100%) rename controller/{ => old-controller}/log.lua (100%) rename controller/{ => old-controller}/reactor.lua (100%) rename controller/{ => old-controller}/regulator.lua (100%) rename controller/{ => old-controller}/render.lua (100%) rename controller/{ => old-controller}/server.lua (100%) diff --git a/controller/controller.lua b/controller/old-controller/controller.lua similarity index 100% rename from controller/controller.lua rename to controller/old-controller/controller.lua diff --git a/controller/defs.lua b/controller/old-controller/defs.lua similarity index 100% rename from controller/defs.lua rename to controller/old-controller/defs.lua diff --git a/controller/log.lua b/controller/old-controller/log.lua similarity index 100% rename from controller/log.lua rename to controller/old-controller/log.lua diff --git a/controller/reactor.lua b/controller/old-controller/reactor.lua similarity index 100% rename from controller/reactor.lua rename to controller/old-controller/reactor.lua diff --git a/controller/regulator.lua b/controller/old-controller/regulator.lua similarity index 100% rename from controller/regulator.lua rename to controller/old-controller/regulator.lua diff --git a/controller/render.lua b/controller/old-controller/render.lua similarity index 100% rename from controller/render.lua rename to controller/old-controller/render.lua diff --git a/controller/server.lua b/controller/old-controller/server.lua similarity index 100% rename from controller/server.lua rename to controller/old-controller/server.lua From 3b492ead929c17713254761a593872fe421fb5cc Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 13 Jan 2022 10:06:55 -0500 Subject: [PATCH 004/587] changed to SCADA terminology, changed RCaSS to reactor PLC, maybe changed other things --- common/comms.lua | 157 ------------------ common/util.lua | 29 ---- .../old-controller/controller.lua | 0 .../old-controller/defs.lua | 0 .../old-controller/log.lua | 0 .../old-controller/reactor.lua | 0 .../old-controller/regulator.lua | 0 .../old-controller/render.lua | 0 .../old-controller/server.lua | 0 coordinator/scada-coordinator.lua | 0 {rcass => reactor-plc}/config.lua | 4 +- rcass/rcass.lua => reactor-plc/plc.lua | 16 +- {rcass => reactor-plc}/safety.lua | 0 {rcass => reactor-plc}/signal-router.lua | 0 {rcass => reactor-plc}/startup.lua | 0 15 files changed, 10 insertions(+), 196 deletions(-) delete mode 100644 common/comms.lua delete mode 100644 common/util.lua rename {controller => coordinator}/old-controller/controller.lua (100%) rename {controller => coordinator}/old-controller/defs.lua (100%) rename {controller => coordinator}/old-controller/log.lua (100%) rename {controller => coordinator}/old-controller/reactor.lua (100%) rename {controller => coordinator}/old-controller/regulator.lua (100%) rename {controller => coordinator}/old-controller/render.lua (100%) rename {controller => coordinator}/old-controller/server.lua (100%) create mode 100644 coordinator/scada-coordinator.lua rename {rcass => reactor-plc}/config.lua (75%) rename rcass/rcass.lua => reactor-plc/plc.lua (88%) rename {rcass => reactor-plc}/safety.lua (100%) rename {rcass => reactor-plc}/signal-router.lua (100%) rename {rcass => reactor-plc}/startup.lua (100%) diff --git a/common/comms.lua b/common/comms.lua deleted file mode 100644 index 4e47eb3..0000000 --- a/common/comms.lua +++ /dev/null @@ -1,157 +0,0 @@ - -function server_comms() - local self = { - reactor_struct_cache = nil - } - - local record_struct = function (id, mek_data) - end - - -- send the structure data by request to pocket computers - local send_struct = function () - end - - local command_waste = function () - end -end - -function rcass_comms(id, modem, local_port, server_port, reactor) - local self = { - _id = id, - _modem = modem, - _server = server_port, - _local = local_port, - _reactor = reactor, - _status_cache = nil, - - _send = function (msg) - self._modem.transmit(self._server, self._local, msg) - end - } - - local _send = function (msg) - self._modem.transmit(self._server, self._local, msg) - end - - -- variable reactor status information, excluding heating rate - local _reactor_status = function () - return { - status = self._reactor.getStatus(), - burn_rate = self._reactor.getBurnRate(), - act_burn_r = self._reactor.getActualBurnRate(), - temp = self._reactor.getTemperature(), - damage = self._reactor.getDamagePercent(), - boil_eff = self._reactor.getBoilEfficiency(), - env_loss = self._reactor.getEnvironmentalLoss(), - - fuel = self._reactor.getFuel(), - fuel_need = self._reactor.getFuelNeeded(), - fuel_fill = self._reactor.getFuelFilledPercentage(), - waste = self._reactor.getWaste(), - waste_need = self._reactor.getWasteNeeded(), - waste_fill = self._reactor.getWasteFilledPercentage(), - cool_type = self._reactor.getCoolant()['name'], - cool_amnt = self._reactor.getCoolant()['amount'], - cool_need = self._reactor.getCoolantNeeded(), - cool_fill = self._reactor.getCoolantFilledPercentage(), - hcool_type = self._reactor.getHeatedCoolant()['name'], - hcool_amnt = self._reactor.getHeatedCoolant()['amount'], - hcool_need = self._reactor.getHeatedCoolantNeeded(), - hcool_fill = self._reactor.getHeatedCoolantFilledPercentage() - } - end - - local _status_changed = function () - local status = self._reactor_status() - local changed = false - - for key, value in pairs() do - if value ~= _status_cache[key] then - changed = true - break - end - end - - return changed - end - - -- attempt to establish link with - local send_link_req = function () - local linking_data = { - id = self._id, - type = "link_req" - } - - _send(linking_data) - end - - -- send structure properties (these should not change) - -- server will cache these - local send_struct = function () - local mek_data = { - heat_cap = self._reactor.getHeatCapacity(), - fuel_asm = self._reactor.getFuelAssemblies(), - fuel_sa = self._reactor.getFuelSurfaceArea(), - fuel_cap = self._reactor.getFuelCapacity(), - waste_cap = self._reactor.getWasteCapacity(), - cool_cap = self._reactor.getCoolantCapacity(), - hcool_cap = self._reactor.getHeatedCoolantCapacity(), - max_burn = self._reactor.getMaxBurnRate() - } - - local struct_packet = { - id = self._id, - type = "struct_data", - mek_data = mek_data - } - - _send(struct_packet) - end - - -- send live status information - local send_status = function () - local mek_data = self._reactor_status() - - local sys_data = { - timestamp = os.time(), - control_state = false, - overridden = false, - faults = {}, - waste_production = "antimatter" -- "plutonium", "polonium", "antimatter" - } - end - - local send_keep_alive = function () - -- heating rate is volatile, so it is skipped in status - -- send it with keep alive packets - local mek_data = { - heating_rate = self._reactor.getHeatingRate() - } - - -- basic keep alive packet to server - local keep_alive_packet = { - id = self._id, - type = "keep_alive", - timestamp = os.time(), - mek_data = mek_data - } - - _send(keep_alive_packet) - end - - local handle_link = function (packet) - if packet.type == "link_response" then - return packet.accepted - else - return "wrong_type" - end - end - - return { - send_link_req = send_link_req, - send_struct = send_struct, - send_status = send_status, - send_keep_alive = send_keep_alive, - handle_link = handle_link - } -end \ No newline at end of file diff --git a/common/util.lua b/common/util.lua deleted file mode 100644 index f6fd611..0000000 --- a/common/util.lua +++ /dev/null @@ -1,29 +0,0 @@ --- timestamped print -function print_ts(message) - term.write(os.date("[%H:%M:%S] ") .. message) -end - --- ComputerCraft OS Timer based Watchdog --- triggers a timer event if not fed within 'timeout' seconds -function new_watchdog(timeout) - local self = { - _timeout = timeout, - _wd_timer = os.startTimer(_timeout) - } - - local get_timer = function () - return self._wd_timer - end - - local feed = function () - if self._wd_timer ~= nil then - os.cancelTimer(self._wd_timer) - end - self._wd_timer = os.startTimer(self._timeout) - end - - return { - get_timer = get_timer, - feed = feed - } -end diff --git a/controller/old-controller/controller.lua b/coordinator/old-controller/controller.lua similarity index 100% rename from controller/old-controller/controller.lua rename to coordinator/old-controller/controller.lua diff --git a/controller/old-controller/defs.lua b/coordinator/old-controller/defs.lua similarity index 100% rename from controller/old-controller/defs.lua rename to coordinator/old-controller/defs.lua diff --git a/controller/old-controller/log.lua b/coordinator/old-controller/log.lua similarity index 100% rename from controller/old-controller/log.lua rename to coordinator/old-controller/log.lua diff --git a/controller/old-controller/reactor.lua b/coordinator/old-controller/reactor.lua similarity index 100% rename from controller/old-controller/reactor.lua rename to coordinator/old-controller/reactor.lua diff --git a/controller/old-controller/regulator.lua b/coordinator/old-controller/regulator.lua similarity index 100% rename from controller/old-controller/regulator.lua rename to coordinator/old-controller/regulator.lua diff --git a/controller/old-controller/render.lua b/coordinator/old-controller/render.lua similarity index 100% rename from controller/old-controller/render.lua rename to coordinator/old-controller/render.lua diff --git a/controller/old-controller/server.lua b/coordinator/old-controller/server.lua similarity index 100% rename from controller/old-controller/server.lua rename to coordinator/old-controller/server.lua diff --git a/coordinator/scada-coordinator.lua b/coordinator/scada-coordinator.lua new file mode 100644 index 0000000..e69de29 diff --git a/rcass/config.lua b/reactor-plc/config.lua similarity index 75% rename from rcass/config.lua rename to reactor-plc/config.lua index f79895c..539946e 100644 --- a/rcass/config.lua +++ b/reactor-plc/config.lua @@ -1,6 +1,6 @@ -- unique reactor ID REACTOR_ID = 1 -- port to send packets TO server -SERVER_PORT = 1000 +SERVER_PORT = 16000 -- port to listen to incoming packets FROM server -LISTEN_PORT = 1001 +LISTEN_PORT = 14001 diff --git a/rcass/rcass.lua b/reactor-plc/plc.lua similarity index 88% rename from rcass/rcass.lua rename to reactor-plc/plc.lua index 8738896..b30a00e 100644 --- a/rcass/rcass.lua +++ b/reactor-plc/plc.lua @@ -1,20 +1,20 @@ -- --- RCaSS: Reactor Controller and Safety Subsystem +-- Reactor Programmable Logic Controller -- -os.loadAPI("common/util.lua") -os.loadAPI("common/comms.lua") -os.loadAPI("rcass/config.lua") -os.loadAPI("rcass/safety.lua") +os.loadAPI("scada-common/util.lua") +os.loadAPI("scada-common/comms.lua") +os.loadAPI("reactor-plc/config.lua") +os.loadAPI("reactor-plc/safety.lua") -local RCASS_VERSION = "alpha-v0.1" +local R_PLC_VERSION = "alpha-v0.1" local print_ts = util.print_ts local reactor = peripheral.find("fissionReactor") local modem = peripheral.find("modem") -print(">> RCaSS " .. RCASS_VERSION .. " <<") +print(">> Reactor PLC " .. R_PLC_VERSION .. " <<") -- we need a reactor and a modem if reactor == nil then @@ -41,7 +41,7 @@ if not modem.isOpen(config.LISTEN_PORT) then modem.open(config.LISTEN_PORT) end -local comms = comms.rcass_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) +local comms = comms.rplc_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) -- attempt server connection local linked = false diff --git a/rcass/safety.lua b/reactor-plc/safety.lua similarity index 100% rename from rcass/safety.lua rename to reactor-plc/safety.lua diff --git a/rcass/signal-router.lua b/reactor-plc/signal-router.lua similarity index 100% rename from rcass/signal-router.lua rename to reactor-plc/signal-router.lua diff --git a/rcass/startup.lua b/reactor-plc/startup.lua similarity index 100% rename from rcass/startup.lua rename to reactor-plc/startup.lua From c78db71b1494d63303b99ca3fa17dc262994f8de Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 13 Jan 2022 10:11:42 -0500 Subject: [PATCH 005/587] comms and util files --- scada-common/comms.lua | 157 +++++++++++++++++++++++++++++++++++++++++ scada-common/util.lua | 29 ++++++++ 2 files changed, 186 insertions(+) create mode 100644 scada-common/comms.lua create mode 100644 scada-common/util.lua diff --git a/scada-common/comms.lua b/scada-common/comms.lua new file mode 100644 index 0000000..f1ed07f --- /dev/null +++ b/scada-common/comms.lua @@ -0,0 +1,157 @@ + +function server_comms() + local self = { + reactor_struct_cache = nil + } + + local record_struct = function (id, mek_data) + end + + -- send the structure data by request to pocket computers + local send_struct = function () + end + + local command_waste = function () + end +end + +function rplc_comms(id, modem, local_port, server_port, reactor) + local self = { + _id = id, + _modem = modem, + _server = server_port, + _local = local_port, + _reactor = reactor, + _status_cache = nil, + + _send = function (msg) + self._modem.transmit(self._server, self._local, msg) + end + } + + local _send = function (msg) + self._modem.transmit(self._server, self._local, msg) + end + + -- variable reactor status information, excluding heating rate + local _reactor_status = function () + return { + status = self._reactor.getStatus(), + burn_rate = self._reactor.getBurnRate(), + act_burn_r = self._reactor.getActualBurnRate(), + temp = self._reactor.getTemperature(), + damage = self._reactor.getDamagePercent(), + boil_eff = self._reactor.getBoilEfficiency(), + env_loss = self._reactor.getEnvironmentalLoss(), + + fuel = self._reactor.getFuel(), + fuel_need = self._reactor.getFuelNeeded(), + fuel_fill = self._reactor.getFuelFilledPercentage(), + waste = self._reactor.getWaste(), + waste_need = self._reactor.getWasteNeeded(), + waste_fill = self._reactor.getWasteFilledPercentage(), + cool_type = self._reactor.getCoolant()['name'], + cool_amnt = self._reactor.getCoolant()['amount'], + cool_need = self._reactor.getCoolantNeeded(), + cool_fill = self._reactor.getCoolantFilledPercentage(), + hcool_type = self._reactor.getHeatedCoolant()['name'], + hcool_amnt = self._reactor.getHeatedCoolant()['amount'], + hcool_need = self._reactor.getHeatedCoolantNeeded(), + hcool_fill = self._reactor.getHeatedCoolantFilledPercentage() + } + end + + local _status_changed = function () + local status = self._reactor_status() + local changed = false + + for key, value in pairs() do + if value ~= _status_cache[key] then + changed = true + break + end + end + + return changed + end + + -- attempt to establish link with + local send_link_req = function () + local linking_data = { + id = self._id, + type = "link_req" + } + + _send(linking_data) + end + + -- send structure properties (these should not change) + -- server will cache these + local send_struct = function () + local mek_data = { + heat_cap = self._reactor.getHeatCapacity(), + fuel_asm = self._reactor.getFuelAssemblies(), + fuel_sa = self._reactor.getFuelSurfaceArea(), + fuel_cap = self._reactor.getFuelCapacity(), + waste_cap = self._reactor.getWasteCapacity(), + cool_cap = self._reactor.getCoolantCapacity(), + hcool_cap = self._reactor.getHeatedCoolantCapacity(), + max_burn = self._reactor.getMaxBurnRate() + } + + local struct_packet = { + id = self._id, + type = "struct_data", + mek_data = mek_data + } + + _send(struct_packet) + end + + -- send live status information + local send_status = function () + local mek_data = self._reactor_status() + + local sys_data = { + timestamp = os.time(), + control_state = false, + overridden = false, + faults = {}, + waste_production = "antimatter" -- "plutonium", "polonium", "antimatter" + } + end + + local send_keep_alive = function () + -- heating rate is volatile, so it is skipped in status + -- send it with keep alive packets + local mek_data = { + heating_rate = self._reactor.getHeatingRate() + } + + -- basic keep alive packet to server + local keep_alive_packet = { + id = self._id, + type = "keep_alive", + timestamp = os.time(), + mek_data = mek_data + } + + _send(keep_alive_packet) + end + + local handle_link = function (packet) + if packet.type == "link_response" then + return packet.accepted + else + return "wrong_type" + end + end + + return { + send_link_req = send_link_req, + send_struct = send_struct, + send_status = send_status, + send_keep_alive = send_keep_alive, + handle_link = handle_link + } +end \ No newline at end of file diff --git a/scada-common/util.lua b/scada-common/util.lua new file mode 100644 index 0000000..f6fd611 --- /dev/null +++ b/scada-common/util.lua @@ -0,0 +1,29 @@ +-- timestamped print +function print_ts(message) + term.write(os.date("[%H:%M:%S] ") .. message) +end + +-- ComputerCraft OS Timer based Watchdog +-- triggers a timer event if not fed within 'timeout' seconds +function new_watchdog(timeout) + local self = { + _timeout = timeout, + _wd_timer = os.startTimer(_timeout) + } + + local get_timer = function () + return self._wd_timer + end + + local feed = function () + if self._wd_timer ~= nil then + os.cancelTimer(self._wd_timer) + end + self._wd_timer = os.startTimer(self._timeout) + end + + return { + get_timer = get_timer, + feed = feed + } +end From 78cbb9e67d3f8ade3e96d42443a007d6bd84da59 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 13 Jan 2022 10:12:44 -0500 Subject: [PATCH 006/587] RTU object and started modbus --- rtu/config.lua | 10 ++++++ rtu/rtu.lua | 78 +++++++++++++++++++++++++++++++++++++++++ rtu/startup.lua | 6 ++++ scada-common/modbus.lua | 14 ++++++++ 4 files changed, 108 insertions(+) create mode 100644 rtu/config.lua create mode 100644 rtu/rtu.lua create mode 100644 rtu/startup.lua create mode 100644 scada-common/modbus.lua diff --git a/rtu/config.lua b/rtu/config.lua new file mode 100644 index 0000000..39b23ad --- /dev/null +++ b/rtu/config.lua @@ -0,0 +1,10 @@ +RTU__DEVICES = { + { + name = "boiler_0", + reactor_owner = 1 + }, + { + name = "turbine_0", + reactor_owner = 1 + } +} diff --git a/rtu/rtu.lua b/rtu/rtu.lua new file mode 100644 index 0000000..97e11f7 --- /dev/null +++ b/rtu/rtu.lua @@ -0,0 +1,78 @@ +function rtu_init() + local self = { + discrete_inputs = {}, + coils = {}, + input_regs = {}, + holding_regs = {} + } + + local count_io = function () + return #self.discrete_inputs, #self.coils, #self.input_regs, #self.holding_regs + end + + -- discrete inputs: single bit read-only + + local connect_di = function (f) + table.insert(self.discrete_inputs, f) + return #self.discrete_inputs + end + + local read_di = function (di_addr) + return self.discrete_inputs[di_addr]() + end + + -- coils: single bit read-write + + local connect_coil = function (f_read, f_write) + table.insert(self.coils, { read = f_read, write = f_write }) + return #self.coils + end + + local read_coil = function (coil_addr) + return self.coils[coil_addr].read() + end + + local write_coil = function (coil_addr, value) + self.coils[coil_addr].write(value) + end + + -- input registers: multi-bit read-only + + local connect_input_reg = function (f) + table.insert(self.input_regs, f) + return #self.input_regs + end + + local read_input_reg = function (reg_addr) + return self.coils[reg_addr]() + end + + -- holding registers: multi-bit read-write + + local connect_holding_reg = function (f_read, f_write) + table.insert(self.holding_regs, { read = f_read, write = f_write }) + return #self.holding_regs + end + + local read_holding_reg = function (reg_addr) + return self.coils[reg_addr].read() + end + + local write_holding_reg = function (reg_addr, value) + self.coils[reg_addr].write(value) + end + + return { + count_io = count_io, + connect_di = connect_di, + read_di = read_di, + connect_coil = connect_coil, + read_coil = read_coil, + write_coil = write_coil, + connect_input_reg = connect_input_reg, + read_input_reg = read_input_reg, + connect_holding_reg = connect_holding_reg, + read_holding_reg = read_holding_reg, + write_holding_reg = write_holding_reg + } +end diff --git a/rtu/startup.lua b/rtu/startup.lua new file mode 100644 index 0000000..de9a784 --- /dev/null +++ b/rtu/startup.lua @@ -0,0 +1,6 @@ +-- +-- RTU: Remote Terminal Unit +-- + +os.loadAPI("config.lua") +os.loadAPI("rtu.lua") diff --git a/scada-common/modbus.lua b/scada-common/modbus.lua new file mode 100644 index 0000000..1e47f9c --- /dev/null +++ b/scada-common/modbus.lua @@ -0,0 +1,14 @@ +function modbus_init(rtu_dev) + local self = { + rtu = rtu_dev + } + + function _1_read_coils(c_channel_start, count) + end + + function _2_read_discrete_inputs(di_channel_start, count) + end + + function _3_read_multiple_holding_registers(hr_channel_start, count) + end +end From 4dfdb218e271bc46786c849f38ce4a4ac2deaa7b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 13 Jan 2022 10:23:38 -0500 Subject: [PATCH 007/587] SCADA supervisor code started --- scada-common/comms.lua | 16 +++++------- supervisor/config.lua | 18 +++++++++++++ supervisor/scada-supervisor.lua | 45 +++++++++++++++++++++++++++++++++ supervisor/startup.lua | 3 +++ 4 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 supervisor/config.lua create mode 100644 supervisor/scada-supervisor.lua create mode 100644 supervisor/startup.lua diff --git a/scada-common/comms.lua b/scada-common/comms.lua index f1ed07f..34de9cb 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -1,18 +1,14 @@ -function server_comms() +function coord_comms() local self = { reactor_struct_cache = nil } +end - local record_struct = function (id, mek_data) - end - - -- send the structure data by request to pocket computers - local send_struct = function () - end - - local command_waste = function () - end +function superv_comms() + local self = { + reactor_struct_cache = nil + } end function rplc_comms(id, modem, local_port, server_port, reactor) diff --git a/supervisor/config.lua b/supervisor/config.lua new file mode 100644 index 0000000..39adeef --- /dev/null +++ b/supervisor/config.lua @@ -0,0 +1,18 @@ +-- type ('active','backup') +-- 'active' system carries through instructions and control +-- 'backup' system serves as a hot backup, still recieving data +-- from all PLCs and coordinator(s) while in backup to allow +-- instant failover if active goes offline without re-sync +SYSTEM_TYPE = 'active' + +-- scada network +SCADA_NET_PFX = 16000 +-- failover synchronization +SCADA_FO_CHANNEL = 16001 +-- listen port for SCADA supervisor access +SCADA_SV_CHANNEL = 16002 +-- listen port for PLC's +SCADA_PLC_LISTEN = 16003 + +-- expected number of reactors +NUM_REACTORS = 4 diff --git a/supervisor/scada-supervisor.lua b/supervisor/scada-supervisor.lua new file mode 100644 index 0000000..772c572 --- /dev/null +++ b/supervisor/scada-supervisor.lua @@ -0,0 +1,45 @@ +-- +-- Nuclear Generation Facility SCADA Supervisor +-- + +os.loadAPI("scada-common/util.lua") +os.loadAPI("scada-common/comms.lua") +os.loadAPI("supervisor/config.lua") + +local SUPERVISOR_VERSION = "alpha-v0.1" + +local print_ts = util.print_ts + +local modem = peripheral.find("modem") + +print("| SCADA Supervisor - " .. SUPERVISOR_VERSION .. " |") + +-- we need a modem +if modem == nil then + print("No modem found, exiting...") + return +end + +-- read config + +-- start comms +if not modem.isOpen(config.LISTEN_PORT) then + modem.open(config.LISTEN_PORT) +end + +local comms = comms.superv_comms(config.NUM_REACTORS, modem, config.SCADA_PLC_LISTEN, config.SCADA_SV_CHANNEL) + +-- base loop clock (4Hz, 5 ticks) +local loop_tick = os.startTimer(0.25) + +-- event loop +while true do + local event, param1, param2, param3, param4, param5 = os.pullEvent() + + -- handle event + if event == "timer" and param1 == loop_tick then + -- basic event tick, send keep-alives + elseif event == "modem_message" then + -- got a packet + end +end diff --git a/supervisor/startup.lua b/supervisor/startup.lua new file mode 100644 index 0000000..2ee4d40 --- /dev/null +++ b/supervisor/startup.lua @@ -0,0 +1,3 @@ +-- +-- Multi-Reactor Controller Server & GUI +-- \ No newline at end of file From e47b4d795987629e096a171e03bd0ad680b5060c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 13 Jan 2022 10:23:56 -0500 Subject: [PATCH 008/587] placeholders for pocket computer access in the future --- pocket/config.lua | 0 pocket/startup.lua | 3 +++ 2 files changed, 3 insertions(+) create mode 100644 pocket/config.lua create mode 100644 pocket/startup.lua diff --git a/pocket/config.lua b/pocket/config.lua new file mode 100644 index 0000000..e69de29 diff --git a/pocket/startup.lua b/pocket/startup.lua new file mode 100644 index 0000000..aeeaef4 --- /dev/null +++ b/pocket/startup.lua @@ -0,0 +1,3 @@ +-- +-- SCADA Coordinator Access on a Pocket Computer +-- \ No newline at end of file From 00a81ab4f0d69b24d58c98d52eb23baf5f006843 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 14 Jan 2022 12:42:11 -0500 Subject: [PATCH 009/587] modbus comms implementation --- rtu/rtu.lua | 17 +++- scada-common/modbus.lua | 201 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 211 insertions(+), 7 deletions(-) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 97e11f7..32600a1 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -3,17 +3,23 @@ function rtu_init() discrete_inputs = {}, coils = {}, input_regs = {}, - holding_regs = {} + holding_regs = {}, + io_count_cache = { 0, 0, 0, 0 } } - local count_io = function () - return #self.discrete_inputs, #self.coils, #self.input_regs, #self.holding_regs + local __count_io = function () + self.io_count_cache = { #self.discrete_inputs, #self.coils, #self.input_regs, #self.holding_regs } + end + + local io_count = function () + return self.io_count_cache[0], self.io_count_cache[1], self.io_count_cache[2], self.io_count_cache[3] end -- discrete inputs: single bit read-only local connect_di = function (f) table.insert(self.discrete_inputs, f) + __count_io() return #self.discrete_inputs end @@ -25,6 +31,7 @@ function rtu_init() local connect_coil = function (f_read, f_write) table.insert(self.coils, { read = f_read, write = f_write }) + __count_io() return #self.coils end @@ -40,6 +47,7 @@ function rtu_init() local connect_input_reg = function (f) table.insert(self.input_regs, f) + __count_io() return #self.input_regs end @@ -51,6 +59,7 @@ function rtu_init() local connect_holding_reg = function (f_read, f_write) table.insert(self.holding_regs, { read = f_read, write = f_write }) + __count_io() return #self.holding_regs end @@ -63,7 +72,7 @@ function rtu_init() end return { - count_io = count_io, + io_count = io_count, connect_di = connect_di, read_di = read_di, connect_coil = connect_coil, diff --git a/scada-common/modbus.lua b/scada-common/modbus.lua index 1e47f9c..e6d5eba 100644 --- a/scada-common/modbus.lua +++ b/scada-common/modbus.lua @@ -1,14 +1,209 @@ +-- modbus function codes +local MODBUS_FCODE = { + READ_COILS = 0x01, + READ_DISCRETE_INPUTS = 0x02, + READ_MUL_HOLD_REGS = 0x03, + READ_INPUT_REGS = 0x04, + WRITE_SINGLE_COIL = 0x05, + WRITE_SINGLE_HOLD_REG = 0x06, + WRITE_MUL_COILS = 0x0F, + WRITE_MUL_HOLD_REGS = 0x10, + ERROR_FLAG = 0x80 +} + +-- new modbus comms handler object function modbus_init(rtu_dev) local self = { rtu = rtu_dev } - function _1_read_coils(c_channel_start, count) + local _1_read_coils = function (c_addr_start, count) + local readings = {} + local _, coils, _, _ = self.rtu.io_count() + local return_ok = (c_addr_start + count) <= coils + + if return_ok then + for i = 0, (count - 1) do + readings[i] = self.rtu.read_coil(c_addr_start + i) + end + end + + return return_ok, readings end - function _2_read_discrete_inputs(di_channel_start, count) + local _2_read_discrete_inputs = function (di_addr_start, count) + local readings = {} + local discrete_inputs, _, _, _ = self.rtu.io_count() + local return_ok = (di_addr_start + count) <= discrete_inputs + + if return_ok then + for i = 0, (count - 1) do + readings[i] = self.rtu.read_di(di_addr_start + i) + end + end + + return return_ok, readings end - function _3_read_multiple_holding_registers(hr_channel_start, count) + local _3_read_multiple_holding_registers = function (hr_addr_start, count) + local readings = {} + local _, _, _, hold_regs = self.rtu.io_count() + local return_ok = (hr_addr_start + count) <= hold_regs + + if return_ok then + for i = 0, (count - 1) do + readings[i] = self.rtu.read_holding_reg(hr_addr_start + i) + end + end + + return return_ok, readings + end + + local _4_read_input_registers = function (ir_addr_start, count) + local readings = {} + local _, _, input_regs, _ = self.rtu.io_count() + local return_ok = (ir_addr_start + count) <= input_regs + + if return_ok then + for i = 0, (count - 1) do + readings[i] = self.rtu.read_input_reg(ir_addr_start + i) + end + end + + return return_ok, readings + end + + local _5_write_single_coil = function (c_addr, value) + local _, coils, _, _ = self.rtu.io_count() + local return_ok = c_addr <= coils + + if return_ok then + self.rtu.write_coil(c_addr, value) + end + + return return_ok + end + + local _6_write_single_holding_register = function (hr_addr, value) + local _, _, _, hold_regs = self.rtu.io_count() + local return_ok = hr_addr <= hold_regs + + if return_ok then + self.rtu.write_holding_reg(hr_addr, value) + end + + return return_ok + end + + local _15_write_multiple_coils = function (c_addr_start, values) + local _, coils, _, _ = self.rtu.io_count() + local count = #values + local return_ok = (c_addr_start + count) <= coils + + if return_ok then + for i = 0, (count - 1) do + self.rtu.write_coil(c_addr_start + i, values[i + 1]) + end + end + + return return_ok + end + + local _16_write_multiple_holding_registers = function (hr_addr_start, values) + local _, _, _, hold_regs = self.rtu.io_count() + local count = #values + local return_ok = (hr_addr_start + count) <= hold_regs + + if return_ok then + for i = 0, (count - 1) do + self.rtu.write_coil(hr_addr_start + i, values[i + 1]) + end + end + + return return_ok + end + + local handle_packet = function (packet) + local return_code = true + local readings = nil + + if #packet.data == 2 then + -- handle by function code + if packet.func_code == MODBUS_FCODE.READ_COILS then + return_code, readings = _1_read_coils(packet.data[1], packet.data[2]) + elseif packet.func_code == MODBUS_FCODE.READ_DISCRETE_INPUTS then + return_code, readings = _2_read_discrete_inputs(packet.data[1], packet.data[2]) + elseif packet.func_code == MODBUS_FCODE.READ_MUL_HOLD_REGS then + return_code, readings = _3_read_multiple_holding_registers(packet.data[1], packet.data[2]) + elseif packet.func_code == MODBUS_FCODE.READ_INPUT_REGISTERS then + return_code, readings = _4_read_input_registers(packet.data[1], packet.data[2]) + elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_COIL then + return_code = _5_write_single_coil(packet.data[1], packet.data[2]) + elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_HOLD_REG then + return_code = _6_write_single_holding_register(packet.data[1], packet.data[2]) + elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_COILS then + return_code = _15_write_multiple_coils(packet.data[1], packet.data[2]) + elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_HOLD_REGS then + return_code = _16_write_multiple_holding_registers(packet.data[1], packet.data[2]) + else + -- unknown function + return_code = false + end + else + -- invalid length + return_code = false + end + + if return_code then + -- response (default is to echo back) + response = packet + if readings ~= nil then + response.length = #readings + response.data = readings + end + else + -- echo back with error flag + response = packet + response.func_code = bit.bor(packet.func_code, ERROR_FLAG) + end + + return return_code, response + end + + return { + handle_packet = handle_packet + } +end + +-- create new modbus packet +function new_modbus_packet(txn_id, protocol, length, unit_id, func_code, data) + return { + txn_id = txn_id, + protocol = protocol, + length = length, + unit_id = unit_id, + func_code = func_code, + data = data + } +end + +-- parse raw table data as a modbus packet +function parse_modbus_packet(raw) + if #raw ~= 6 then + return nil + else + return new_modbus_packet(raw[1], raw[2], raw[3], raw[4], raw[5], raw[6]) end end + +-- create raw table data from a modbus packet +function modbus_to_raw(packet) + return { + packet.txn_id, + packet.protocol, + packet.length, + packet.unit_id, + packet.func_code, + packet.data + } +end From 018b22897670a32d93fe2e10ec3c25291a858b92 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 14 Jan 2022 16:32:20 -0500 Subject: [PATCH 010/587] some comms cleanup and added wrapper for generic packet --- scada-common/comms.lua | 72 ++++++++++++++++++++++++++++++++++++++++- scada-common/modbus.lua | 54 ++++++++++++++++++------------- 2 files changed, 103 insertions(+), 23 deletions(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 34de9cb..3007cf9 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -1,16 +1,86 @@ +PROTOCOLS = { + MODBUS_TCP = 0, -- our "modbus tcp"-esque protocol + RPLC = 1, -- reactor plc protocol + SCADA_MGMT = 2, -- SCADA supervisor intercommunication + COORD_DATA = 3 -- data packets for coordinators to/from supervisory controller +} +-- generic SCADA packet object +function scada_packet() + local self = { + modem_msg_in = nil, + raw = nil + seq_id = nil, + protocol = nil, + length = nil + } + + local receive = function (side, sender, reply_to, message, distance) + self.modem_msg_in = { + iface = side, + s_port = sender, + r_port = reply_to, + msg = message, + dist = distance + } + + self.raw = self.modem_msg_in.msg + + if #self.raw < 3 then + -- malformed + return false + else + self.seq_id = self.raw[0] + self.protocol = self.raw[1] + self.length = self.raw[2] + end + end + + local seq_id = function (packet) + return self.seq_id + end + + local protocol = function (packet) + return self.protocol + end + + local length = function (packet) + return self.length + end + + local raw = function (packet) + return self.raw + end + + local modem_event = function (packet) + return self.modem_msg_in + end + + return { + receive = receive, + seq_id = seq_id, + protocol = protocol, + length = length, + raw = raw, + modem_event = modem_event + } +end + +-- coordinator communications function coord_comms() local self = { reactor_struct_cache = nil } end +-- supervisory controller communications function superv_comms() local self = { reactor_struct_cache = nil } end +-- reactor PLC communications function rplc_comms(id, modem, local_port, server_port, reactor) local self = { _id = id, @@ -150,4 +220,4 @@ function rplc_comms(id, modem, local_port, server_port, reactor) send_keep_alive = send_keep_alive, handle_link = handle_link } -end \ No newline at end of file +end diff --git a/scada-common/modbus.lua b/scada-common/modbus.lua index e6d5eba..6721c44 100644 --- a/scada-common/modbus.lua +++ b/scada-common/modbus.lua @@ -1,3 +1,5 @@ +-- #REQUIRES comms.lua + -- modbus function codes local MODBUS_FCODE = { READ_COILS = 0x01, @@ -175,9 +177,8 @@ function modbus_init(rtu_dev) } end --- create new modbus packet -function new_modbus_packet(txn_id, protocol, length, unit_id, func_code, data) - return { +function modbus_packet() + local self = { txn_id = txn_id, protocol = protocol, length = length, @@ -185,25 +186,34 @@ function new_modbus_packet(txn_id, protocol, length, unit_id, func_code, data) func_code = func_code, data = data } -end --- parse raw table data as a modbus packet -function parse_modbus_packet(raw) - if #raw ~= 6 then - return nil - else - return new_modbus_packet(raw[1], raw[2], raw[3], raw[4], raw[5], raw[6]) + local receive = function (raw) + local size_ok = #raw ~= 6 + + if size_ok then + set(raw[1], raw[2], raw[3], raw[4], raw[5], raw[6]) + end + + return size_ok and self.protocol == comms.PROTOCOLS.MODBUS_TCP + end + + local set = function (txn_id, protocol, length, unit_id, func_code, data) + self.txn_id = txn_id + self.protocol = protocol + self.length = length + self.unit_id = unit_id + self.func_code = func_code + self.data = data + end + + local get = function () + return { + txn_id = self.txn_id, + protocol = self.protocol, + length = self.length, + unit_id = self.unit_id, + func_code = self.func_code, + data = self.data + } end end - --- create raw table data from a modbus packet -function modbus_to_raw(packet) - return { - packet.txn_id, - packet.protocol, - packet.length, - packet.unit_id, - packet.func_code, - packet.data - } -end From b3a2cfabc63ac98d829359298a99688a65c747a3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 14 Jan 2022 16:33:09 -0500 Subject: [PATCH 011/587] reactor plc reorganization and some comms updates --- reactor-plc/plc.lua | 221 ++++++++++++++++++++-------------------- reactor-plc/safety.lua | 80 --------------- reactor-plc/startup.lua | 89 +++++++++++++--- scada-common/comms.lua | 8 +- 4 files changed, 189 insertions(+), 209 deletions(-) delete mode 100644 reactor-plc/safety.lua diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index b30a00e..ae6b21c 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -1,122 +1,121 @@ --- --- Reactor Programmable Logic Controller --- +function scada_link(comms) + local linked = false + local link_timeout = os.startTimer(5) -os.loadAPI("scada-common/util.lua") -os.loadAPI("scada-common/comms.lua") -os.loadAPI("reactor-plc/config.lua") -os.loadAPI("reactor-plc/safety.lua") + comms.send_link_req() + print_ts("sent link request") + + repeat + local event, p1, p2, p3, p4, p5 = os.pullEvent() + + -- handle event + if event == "timer" and param1 == link_timeout then + -- no response yet + print("...no response"); + comms.send_link_req() + print_ts("sent link request") + link_timeout = os.startTimer(5) + elseif event == "modem_message" then + -- server response? cancel timeout + if link_timeout ~= nil then + os.cancelTimer(link_timeout) + end -local R_PLC_VERSION = "alpha-v0.1" + local packet = comms.make_packet(p1, p2, p3, p4, p5) -local print_ts = util.print_ts - -local reactor = peripheral.find("fissionReactor") -local modem = peripheral.find("modem") - -print(">> Reactor PLC " .. R_PLC_VERSION .. " <<") - --- we need a reactor and a modem -if reactor == nil then - print("Fission reactor not found, exiting..."); - return -elseif modem == nil then - print("No modem found, disabling reactor and exiting...") - reactor.scram() - return -end - --- just booting up, no fission allowed (neutrons stay put thanks) -reactor.scram() - --- init internal safety system -local iss = safety.iss_init(reactor) -local iss_status = "ok" -local iss_tripped = false - --- read config - --- start comms -if not modem.isOpen(config.LISTEN_PORT) then - modem.open(config.LISTEN_PORT) -end - -local comms = comms.rplc_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) - --- attempt server connection -local linked = false -local link_timeout = os.startTimer(5) -comms.send_link_req() -print_ts("sent link request") -repeat - local event, param1, param2, param3, param4, param5 = os.pullEvent() - - -- handle event - if event == "timer" and param1 == link_timeout then - -- no response yet - print("...no response"); - comms.send_link_req() - print_ts("sent link request") - link_timeout = os.startTimer(5) - elseif event == "modem_message" then - -- server response? cancel timeout - if link_timeout ~= nil then - os.cancelTimer(link_timeout) + -- handle response + local response = comms.handle_link(packet) + if response == nil then + print_ts("invalid link response, bad channel?\n") + return + elseif response == true then + print_ts("...linked!\n") + linked = true + else + print_ts("...denied, exiting\n") + return + end end + until linked +end - local packet = { - side = param1, - sender = param2, - reply_to = param3, - message = param4, - distance = param5 - } +-- Internal Safety System +-- identifies dangerous states and SCRAMs reactor if warranted +-- autonomous from main control +function iss_init(reactor) + local self = { + _reactor = reactor, + _tripped = false, + _trip_cause = "" + } - -- handle response - response = comms.handle_link(packet) - if response == "wrong_type" then - print_ts("invalid link response, bad channel?\n") - return - elseif response == true then - print_ts("...linked!\n") - linked = true + local check = function () + local status = "ok" + + -- check system states in order of severity + if self.damage_critical() then + status = "dmg_crit" + elseif self.high_temp() then + status = "high_temp" + elseif self.excess_heated_coolant() then + status = "heated_coolant_backup" + elseif self.excess_waste() then + status = "full_waste" + elseif self.insufficient_fuel() then + status = "no_fuel" + elseif self._tripped then + status = self._trip_cause else - print_ts("...denied, exiting\n") - return + self._tripped = false end - end -until linked - --- comms watchdog, 3 second timeout -local conn_watchdog = watchdog.new_watchdog(3) - --- loop clock (10Hz, 2 ticks) --- send status updates at 4Hz (every 5 ticks) -local loop_tick = os.startTimer(0.05) -local ticks_to_update = 5 - --- event loop -while true do - local event, param1, param2, param3, param4, param5 = os.pullEvent() - - -- check safety (SCRAM occurs if tripped) - iss_status, iss_tripped = iss.check() - - -- handle event - if event == "timer" and param1 == loop_tick then - -- basic event tick, send updated data if it is time - ticks_to_update = ticks_to_update - 1 - if ticks_to_update == 0 then - ticks_to_update = 5 + + if status ~= "ok" then + self._tripped = true + self._trip_cause = status + self._reactor.scram() end - elseif event == "modem_message" then - -- got a packet - -- feed the watchdog first so it doesn't eat our packets - conn_watchdog.feed() - - elseif event == "timer" and param1 == conn_watchdog.get_timer() then - -- haven't heard from server recently? shutdown - reactor.scram() - print_ts("[alert] server timeout, reactor disabled\n") + + return self._tripped, status end + + local reset = function () + self._tripped = false + self._trip_cause = "" + end + + local damage_critical = function () + return self._reactor.getDamagePercent() >= 100 + end + + local excess_heated_coolant = function () + return self._reactor.getHeatedCoolantNeeded() == 0 + end + + local excess_waste = function () + return self._reactor.getWasteNeeded() == 0 + end + + local high_temp = function () + -- mekanism: MAX_DAMAGE_TEMPERATURE = 1_200 + return self._reactor.getTemperature() >= 1200 + end + + local insufficient_fuel = function () + return self._reactor.getFuel() == 0 + end + + local no_coolant = function() + return self._reactor.getCoolantFilledPercentage() < 2 + end + + return { + check = check, + reset = reset, + damage_critical = damage_critical, + excess_heated_coolant = excess_heated_coolant, + excess_waste = excess_waste, + high_temp = high_temp, + insufficient_fuel = insufficient_fuel, + no_coolant = no_coolant + } end diff --git a/reactor-plc/safety.lua b/reactor-plc/safety.lua deleted file mode 100644 index 29733ab..0000000 --- a/reactor-plc/safety.lua +++ /dev/null @@ -1,80 +0,0 @@ --- Internal Safety System --- identifies dangerous states and SCRAMs reactor if warranted --- autonomous from main control -function iss_init(reactor) - local self = { - _reactor = reactor, - _tripped = false, - _trip_cause = "" - } - - local check = function () - local status = "ok" - - -- check system states in order of severity - if self.damage_critical() then - status = "dmg_crit" - elseif self.high_temp() then - status = "high_temp" - elseif self.excess_heated_coolant() then - status = "heated_coolant_backup" - elseif self.excess_waste() then - status = "full_waste" - elseif self.insufficient_fuel() then - status = "no_fuel" - elseif self._tripped then - status = self._trip_cause - else - self._tripped = false - end - - if status ~= "ok" then - self._tripped = true - self._trip_cause = status - self._reactor.scram() - end - - return self._tripped, status - end - - local reset = function () - self._tripped = false - self._trip_cause = "" - end - - local damage_critical = function () - return self._reactor.getDamagePercent() >= 100 - end - - local excess_heated_coolant = function () - return self._reactor.getHeatedCoolantNeeded() == 0 - end - - local excess_waste = function () - return self._reactor.getWasteNeeded() == 0 - end - - local high_temp = function () - -- mekanism: MAX_DAMAGE_TEMPERATURE = 1_200 - return self._reactor.getTemperature() >= 1200 - end - - local insufficient_fuel = function () - return self._reactor.getFuel() == 0 - end - - local no_coolant = function() - return self._reactor.getCoolantFilledPercentage() < 2 - end - - return { - check = check, - reset = reset, - damage_critical = damage_critical, - excess_heated_coolant = excess_heated_coolant, - excess_waste = excess_waste, - high_temp = high_temp, - insufficient_fuel = insufficient_fuel, - no_coolant = no_coolant - } -end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index a0b746a..e10024a 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -1,13 +1,78 @@ -print(">>RCASS LOADER START<<") -print(">>CHECKING SETTINGS...") -loaded = settings.load("rcass.settings") -if loaded then - print(">>SETTINGS FOUND, VERIFIYING INTEGRITY...") - settings.getNames() -else - print(">>SETTINGS NOT FOUND") - print(">>LAUNCHING CONFIGURATOR...") - shell.run("config") +-- +-- Reactor Programmable Logic Controller +-- + +os.loadAPI("scada-common/util.lua") +os.loadAPI("scada-common/comms.lua") +os.loadAPI("reactor-plc/config.lua") +os.loadAPI("reactor-plc/plc.lua") + +local R_PLC_VERSION = "alpha-v0.1" + +local print_ts = util.print_ts + +local reactor = peripheral.find("fissionReactor") +local modem = peripheral.find("modem") + +print(">> Reactor PLC " .. R_PLC_VERSION .. " <<") + +-- we need a reactor and a modem +if reactor == nil then + print("Fission reactor not found, exiting..."); + return +elseif modem == nil then + print("No modem found, disabling reactor and exiting...") + reactor.scram() + return +end + +-- just booting up, no fission allowed (neutrons stay put thanks) +reactor.scram() + +-- init internal safety system +local iss = plc.iss_init(reactor) + +-- start comms +if not modem.isOpen(config.LISTEN_PORT) then + modem.open(config.LISTEN_PORT) +end + +local comms = comms.rplc_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) + +-- attempt server connection +-- exits application if connection is denied +plc.scada_link(comms) + +-- comms watchdog, 3 second timeout +local conn_watchdog = watchdog.new_watchdog(3) + +-- loop clock (10Hz, 2 ticks) +-- send status updates at 4Hz (every 5 ticks) +local loop_tick = os.startTimer(0.05) +local ticks_to_update = 5 + +-- event loop +while true do + local event, param1, param2, param3, param4, param5 = os.pullEvent() + + -- check safety (SCRAM occurs if tripped) + local iss_status, iss_tripped = iss.check() + + -- handle event + if event == "timer" and param1 == loop_tick then + -- basic event tick, send updated data if it is time + ticks_to_update = ticks_to_update - 1 + if ticks_to_update == 0 then + ticks_to_update = 5 + end + elseif event == "modem_message" then + -- got a packet + -- feed the watchdog first so it doesn't eat our packets + conn_watchdog.feed() + + elseif event == "timer" and param1 == conn_watchdog.get_timer() then + -- haven't heard from server recently? shutdown + reactor.scram() + print_ts("[alert] server timeout, reactor disabled\n") + end end -print(">>LAUNCHING RCASS...") -shell.run("rcass") diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 3007cf9..aa2ab38 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -88,11 +88,7 @@ function rplc_comms(id, modem, local_port, server_port, reactor) _server = server_port, _local = local_port, _reactor = reactor, - _status_cache = nil, - - _send = function (msg) - self._modem.transmit(self._server, self._local, msg) - end + _status_cache = nil } local _send = function (msg) @@ -209,7 +205,7 @@ function rplc_comms(id, modem, local_port, server_port, reactor) if packet.type == "link_response" then return packet.accepted else - return "wrong_type" + return nil end end From c6722c4cbe28ed6d1ad338f9c9ccdc37549e9b24 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 14 Jan 2022 16:34:40 -0500 Subject: [PATCH 012/587] updated README for repo rename --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b9668b6..2ce9de1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# cc-mek-reactor-controller -Configurable ComputerCraft multi-reactor control for Mekanism with a GUI, automatic safety features, waste processing control, and more! +# cc-mek-scada +Configurable ComputerCraft SCADA system for multi-reactor control of Mekanism fission reactors with a GUI, automatic safety features, waste processing control, and more! From ffca88845bb8bce3c61a3e010993f077e6253846 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 22 Jan 2022 14:26:25 -0500 Subject: [PATCH 013/587] work on PLC comms --- reactor-plc/plc.lua | 128 +++++++++++++++------- reactor-plc/startup.lua | 35 ++++-- scada-common/comms.lua | 235 +++++++++++++++++++++++++++------------- 3 files changed, 277 insertions(+), 121 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index ae6b21c..d1fda80 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -1,8 +1,8 @@ -function scada_link(comms) +function scada_link(plc_comms) local linked = false local link_timeout = os.startTimer(5) - comms.send_link_req() + plc_comms.send_link_req() print_ts("sent link request") repeat @@ -12,31 +12,40 @@ function scada_link(comms) if event == "timer" and param1 == link_timeout then -- no response yet print("...no response"); - comms.send_link_req() - print_ts("sent link request") - link_timeout = os.startTimer(5) elseif event == "modem_message" then -- server response? cancel timeout if link_timeout ~= nil then os.cancelTimer(link_timeout) end - local packet = comms.make_packet(p1, p2, p3, p4, p5) - - -- handle response - local response = comms.handle_link(packet) - if response == nil then - print_ts("invalid link response, bad channel?\n") - return - elseif response == true then - print_ts("...linked!\n") - linked = true - else - print_ts("...denied, exiting\n") - return + local s_packet = comms.scada_packet() + s_packet.receive(p1, p2, p3, p4, p5) + local packet = s_packet.as_rplc() + if packet then + -- handle response + local response = plc_comms.handle_link(packet) + if response == nil then + print_ts("invalid link response, bad channel?\n") + break + elseif response == comms.RPLC_LINKING.COLLISION then + print_ts("...reactor PLC ID collision (check config), exiting...\n") + break + elseif response == comms.RPLC_LINKING.ALLOW then + print_ts("...linked!\n") + linked = true + plc_comms.send_rs_io_conns() + plc_comms.send_struct() + plc_comms.send_status() + print_ts("sent initial data\n") + else + print_ts("...denied, exiting...\n") + break + end end end until linked + + return linked end -- Internal Safety System @@ -44,13 +53,15 @@ end -- autonomous from main control function iss_init(reactor) local self = { - _reactor = reactor, - _tripped = false, - _trip_cause = "" + reactor = reactor, + timed_out = false, + tripped = false, + trip_cause = "" } local check = function () local status = "ok" + local was_tripped = self.tripped -- check system states in order of severity if self.damage_critical() then @@ -63,59 +74,100 @@ function iss_init(reactor) status = "full_waste" elseif self.insufficient_fuel() then status = "no_fuel" - elseif self._tripped then - status = self._trip_cause + elseif self.tripped then + status = self.trip_cause else - self._tripped = false + self.tripped = false end if status ~= "ok" then - self._tripped = true - self._trip_cause = status - self._reactor.scram() + self.tripped = true + self.trip_cause = status + self.reactor.scram() end + + local first_trip = ~was_tripped and self.tripped - return self._tripped, status + return self.tripped, status, first_trip + end + + local trip_timeout = function () + self.tripped = false + self.trip_cause = "timeout" + self.timed_out = true + self.reactor.scram() end local reset = function () - self._tripped = false - self._trip_cause = "" + self.timed_out = false + self.tripped = false + self.trip_cause = "" + end + + local status = function (named) + if named then + return { + damage_critical = damage_critical(), + excess_heated_coolant = excess_heated_coolant(), + excess_waste = excess_waste(), + high_temp = high_temp(), + insufficient_fuel = insufficient_fuel(), + no_coolant = no_coolant(), + timed_out = timed_out() + } + else + return { + damage_critical(), + excess_heated_coolant(), + excess_waste(), + high_temp(), + insufficient_fuel(), + no_coolant(), + timed_out() + } + end end local damage_critical = function () - return self._reactor.getDamagePercent() >= 100 + return self.reactor.getDamagePercent() >= 100 end local excess_heated_coolant = function () - return self._reactor.getHeatedCoolantNeeded() == 0 + return self.reactor.getHeatedCoolantNeeded() == 0 end local excess_waste = function () - return self._reactor.getWasteNeeded() == 0 + return self.reactor.getWasteNeeded() == 0 end local high_temp = function () -- mekanism: MAX_DAMAGE_TEMPERATURE = 1_200 - return self._reactor.getTemperature() >= 1200 + return self.reactor.getTemperature() >= 1200 end local insufficient_fuel = function () - return self._reactor.getFuel() == 0 + return self.reactor.getFuel() == 0 end - local no_coolant = function() - return self._reactor.getCoolantFilledPercentage() < 2 + local no_coolant = function () + return self.reactor.getCoolantFilledPercentage() < 2 + end + + local timed_out = function () + return self.timed_out end return { check = check, + trip_timeout = trip_timeout, reset = reset, + status = status, damage_critical = damage_critical, excess_heated_coolant = excess_heated_coolant, excess_waste = excess_waste, high_temp = high_temp, insufficient_fuel = insufficient_fuel, - no_coolant = no_coolant + no_coolant = no_coolant, + timed_out = timed_out } end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index e10024a..6d2d9de 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -37,11 +37,13 @@ if not modem.isOpen(config.LISTEN_PORT) then modem.open(config.LISTEN_PORT) end -local comms = comms.rplc_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) +local plc_comms = comms.rplc_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) -- attempt server connection --- exits application if connection is denied -plc.scada_link(comms) +-- exit application if connection is denied +if ~plc.scada_link(plc_comms) then + return +end -- comms watchdog, 3 second timeout local conn_watchdog = watchdog.new_watchdog(3) @@ -51,28 +53,47 @@ local conn_watchdog = watchdog.new_watchdog(3) local loop_tick = os.startTimer(0.05) local ticks_to_update = 5 +-- runtime variables +local control_state = false + -- event loop while true do local event, param1, param2, param3, param4, param5 = os.pullEvent() + if event == "peripheral_detach" then + print_ts("[fatal] lost a peripheral, stopping...\n") + -- todo: determine which disconnected and what is left + -- hopefully it wasn't the reactor + reactor.scram() + -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_DC) ? + return + end + -- check safety (SCRAM occurs if tripped) - local iss_status, iss_tripped = iss.check() + local iss_status, iss_tripped, iss_first = iss.check() + if iss_first then + plc_comms.send_iss_alarm(iss_status) + end -- handle event if event == "timer" and param1 == loop_tick then - -- basic event tick, send updated data if it is time + -- basic event tick, send updated data if it is time (4Hz) ticks_to_update = ticks_to_update - 1 if ticks_to_update == 0 then + plc_comms.send_status(control_state, iss_tripped) ticks_to_update = 5 end elseif event == "modem_message" then -- got a packet - -- feed the watchdog first so it doesn't eat our packets + -- feed the watchdog first so it doesn't uhh,,,eat our packets conn_watchdog.feed() + local packet = comms.make_packet(p1, p2, p3, p4, p5) + plc_comms.handle_packet(packet) + elseif event == "timer" and param1 == conn_watchdog.get_timer() then -- haven't heard from server recently? shutdown - reactor.scram() + iss.trip_timeout() print_ts("[alert] server timeout, reactor disabled\n") end end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index aa2ab38..23af375 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -5,16 +5,47 @@ PROTOCOLS = { COORD_DATA = 3 -- data packets for coordinators to/from supervisory controller } +RPLC_TYPES = { + KEEP_ALIVE = 0, -- keep alive packets + LINK_REQ = 1, -- linking requests + STATUS = 2, -- reactor/system status + MEK_STRUCT = 3, -- mekanism build structure + RS_IO_CONNS = 4, -- redstone I/O connections + RS_IO_SET = 5, -- set redstone outputs + RS_IO_GET = 6, -- get redstone inputs + MEK_SCRAM = 7, -- SCRAM reactor + MEK_ENABLE = 8, -- enable reactor + MEK_BURN_RATE = 9, -- set burn rate + ISS_ALARM = 10, -- ISS alarm broadcast + ISS_GET = 11, -- get ISS status + ISS_CLEAR = 12 -- clear ISS trip (if in bad state, will trip immideatly) +} + +RPLC_LINKING = { + ALLOW = 0, + DENY = 1, + COLLISION = 2 +} + -- generic SCADA packet object function scada_packet() local self = { modem_msg_in = nil, - raw = nil + valid = false, seq_id = nil, protocol = nil, - length = nil + length = nil, + raw = nil } + local make = function (seq_id, protocol, payload) + self.valid = true + self.seq_id = seq_id + self.protocol = protocol + self.length = #payload + self.raw = { self.seq_id, self.protocol, self.length, payload } + end + local receive = function (side, sender, reply_to, message, distance) self.modem_msg_in = { iface = side, @@ -30,9 +61,10 @@ function scada_packet() -- malformed return false else - self.seq_id = self.raw[0] - self.protocol = self.raw[1] - self.length = self.raw[2] + self.valid = true + self.seq_id = self.raw[1] + self.protocol = self.raw[2] + self.length = self.raw[3] end end @@ -48,6 +80,14 @@ function scada_packet() return self.length end + local data = function (packet) + local subset = nil + if self.valid then + subset = { table.unpack(self.raw, 4, 3 + self.length) } + end + return subset + end + local raw = function (packet) return self.raw end @@ -56,7 +96,24 @@ function scada_packet() return self.modem_msg_in end + local as_rplc = function () + local pkt = nil + if self.valid and self.protocol == PROTOCOLS.RPLC then + local body = data() + if #body > 2 then + pkt = { + id = body[1], + type = body[2], + length = #body - 2, + body = { table.unpack(body, 3, 2 + #body) } + } + end + end + return pkt + end + return { + make = make, receive = receive, seq_id = seq_id, protocol = protocol, @@ -83,65 +140,77 @@ end -- reactor PLC communications function rplc_comms(id, modem, local_port, server_port, reactor) local self = { - _id = id, - _modem = modem, - _server = server_port, - _local = local_port, - _reactor = reactor, - _status_cache = nil + id = id, + seq_id = 0, + modem = modem, + s_port = server_port, + l_port = local_port, + reactor = reactor, + status_cache = nil } + -- PRIVATE FUNCTIONS -- + local _send = function (msg) - self._modem.transmit(self._server, self._local, msg) + local packet = scada_packet() + packet.make(self.seq_id, PROTOCOLS.RPLC, msg) + self.modem.transmit(self.s_port, self.l_port, packet.raw()) + self.seq_id = self.seq_id + 1 end -- variable reactor status information, excluding heating rate local _reactor_status = function () return { - status = self._reactor.getStatus(), - burn_rate = self._reactor.getBurnRate(), - act_burn_r = self._reactor.getActualBurnRate(), - temp = self._reactor.getTemperature(), - damage = self._reactor.getDamagePercent(), - boil_eff = self._reactor.getBoilEfficiency(), - env_loss = self._reactor.getEnvironmentalLoss(), + status = self.reactor.getStatus(), + burn_rate = self.reactor.getBurnRate(), + act_burn_r = self.reactor.getActualBurnRate(), + temp = self.reactor.getTemperature(), + damage = self.reactor.getDamagePercent(), + boil_eff = self.reactor.getBoilEfficiency(), + env_loss = self.reactor.getEnvironmentalLoss(), - fuel = self._reactor.getFuel(), - fuel_need = self._reactor.getFuelNeeded(), - fuel_fill = self._reactor.getFuelFilledPercentage(), - waste = self._reactor.getWaste(), - waste_need = self._reactor.getWasteNeeded(), - waste_fill = self._reactor.getWasteFilledPercentage(), - cool_type = self._reactor.getCoolant()['name'], - cool_amnt = self._reactor.getCoolant()['amount'], - cool_need = self._reactor.getCoolantNeeded(), - cool_fill = self._reactor.getCoolantFilledPercentage(), - hcool_type = self._reactor.getHeatedCoolant()['name'], - hcool_amnt = self._reactor.getHeatedCoolant()['amount'], - hcool_need = self._reactor.getHeatedCoolantNeeded(), - hcool_fill = self._reactor.getHeatedCoolantFilledPercentage() + fuel = self.reactor.getFuel(), + fuel_need = self.reactor.getFuelNeeded(), + fuel_fill = self.reactor.getFuelFilledPercentage(), + waste = self.reactor.getWaste(), + waste_need = self.reactor.getWasteNeeded(), + waste_fill = self.reactor.getWasteFilledPercentage(), + cool_type = self.reactor.getCoolant()['name'], + cool_amnt = self.reactor.getCoolant()['amount'], + cool_need = self.reactor.getCoolantNeeded(), + cool_fill = self.reactor.getCoolantFilledPercentage(), + hcool_type = self.reactor.getHeatedCoolant()['name'], + hcool_amnt = self.reactor.getHeatedCoolant()['amount'], + hcool_need = self.reactor.getHeatedCoolantNeeded(), + hcool_fill = self.reactor.getHeatedCoolantFilledPercentage() } end - local _status_changed = function () - local status = self._reactor_status() + local _update_status_cache = function () + local status = _reactor_status() local changed = false - for key, value in pairs() do - if value ~= _status_cache[key] then + for key, value in pairs(status) do + if value ~= self.status_cache[key] then changed = true break end end + if changed then + self.status_cache = status + end + return changed end - -- attempt to establish link with + -- PUBLIC FUNCTIONS -- + + -- attempt to establish link with supervisor local send_link_req = function () local linking_data = { - id = self._id, - type = "link_req" + id = self.id, + type = RPLC_TYPES.LINK_REQ } _send(linking_data) @@ -151,19 +220,19 @@ function rplc_comms(id, modem, local_port, server_port, reactor) -- server will cache these local send_struct = function () local mek_data = { - heat_cap = self._reactor.getHeatCapacity(), - fuel_asm = self._reactor.getFuelAssemblies(), - fuel_sa = self._reactor.getFuelSurfaceArea(), - fuel_cap = self._reactor.getFuelCapacity(), - waste_cap = self._reactor.getWasteCapacity(), - cool_cap = self._reactor.getCoolantCapacity(), - hcool_cap = self._reactor.getHeatedCoolantCapacity(), - max_burn = self._reactor.getMaxBurnRate() + heat_cap = self.reactor.getHeatCapacity(), + fuel_asm = self.reactor.getFuelAssemblies(), + fuel_sa = self.reactor.getFuelSurfaceArea(), + fuel_cap = self.reactor.getFuelCapacity(), + waste_cap = self.reactor.getWasteCapacity(), + cool_cap = self.reactor.getCoolantCapacity(), + hcool_cap = self.reactor.getHeatedCoolantCapacity(), + max_burn = self.reactor.getMaxBurnRate() } local struct_packet = { - id = self._id, - type = "struct_data", + id = self.id, + type = RPLC_TYPES.MEK_STRUCT, mek_data = mek_data } @@ -171,49 +240,63 @@ function rplc_comms(id, modem, local_port, server_port, reactor) end -- send live status information - local send_status = function () - local mek_data = self._reactor_status() + -- control_state: acknowledged control state from supervisor + -- overridden: if ISS force disabled reactor + local send_status = function (control_state, overridden) + local mek_data = nil - local sys_data = { - timestamp = os.time(), - control_state = false, - overridden = false, - faults = {}, - waste_production = "antimatter" -- "plutonium", "polonium", "antimatter" - } - end - - local send_keep_alive = function () - -- heating rate is volatile, so it is skipped in status - -- send it with keep alive packets - local mek_data = { - heating_rate = self._reactor.getHeatingRate() - } - - -- basic keep alive packet to server - local keep_alive_packet = { - id = self._id, - type = "keep_alive", + if _update_status_cache() then + mek_data = self.status_cache + end + + local sys_status = { + id = self.id, + type = RPLC_TYPES.STATUS, timestamp = os.time(), + control_state = control_state, + overridden = overridden, + heating_rate = self.reactor.getHeatingRate(), mek_data = mek_data } - _send(keep_alive_packet) + _send(sys_status) + end + + local send_rs_io_conns = function () end local handle_link = function (packet) - if packet.type == "link_response" then - return packet.accepted + if packet.type == RPLC_TYPES.LINK_REQ then + return packet.data[1] == RPLC_LINKING.ALLOW else return nil end end + local handle_packet = function (packet) + if packet.type == RPLC_TYPES.KEEP_ALIVE then + -- keep alive request received, nothing to do except feed watchdog + elseif packet.type == RPLC_TYPES.MEK_STRUCT then + -- request for physical structure + send_struct() + elseif packet.type == RPLC_TYPES.RS_IO_CONNS then + -- request for redstone connections + send_rs_io_conns() + elseif packet.type == RPLC_TYPES.RS_IO_GET then + elseif packet.type == RPLC_TYPES.RS_IO_SET then + elseif packet.type == RPLC_TYPES.MEK_SCRAM then + elseif packet.type == RPLC_TYPES.MEK_ENABLE then + elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then + elseif packet.type == RPLC_TYPES.ISS_GET then + elseif packet.type == RPLC_TYPES.ISS_CLEAR then + end + end + return { send_link_req = send_link_req, send_struct = send_struct, send_status = send_status, - send_keep_alive = send_keep_alive, + send_rs_io_conns = send_rs_io_conns, handle_link = handle_link } end From 14cb7f96fccec529b0215b05f48eb618290c37f3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 22 Jan 2022 14:47:54 -0500 Subject: [PATCH 014/587] supervisor comms init --- scada-common/comms.lua | 14 +++++++++++++- supervisor/config.lua | 8 +++----- supervisor/scada-supervisor.lua | 22 ++++++++++++++++------ 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 23af375..5641cba 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -5,6 +5,11 @@ PROTOCOLS = { COORD_DATA = 3 -- data packets for coordinators to/from supervisory controller } +SCADA_SV_MODES = { + ACTIVE = 0, + BACKUP = 1 +} + RPLC_TYPES = { KEEP_ALIVE = 0, -- keep alive packets LINK_REQ = 1, -- linking requests @@ -131,8 +136,15 @@ function coord_comms() end -- supervisory controller communications -function superv_comms() +function superv_comms(mode, num_reactors, modem, dev_listen, fo_channel, sv_channel) local self = { + mode = mode, + seq_id = 0, + num_reactors = num_reactors, + modem = modem, + dev_listen = dev_listen, + fo_channel = fo_channel, + sv_channel = sv_channel, reactor_struct_cache = nil } end diff --git a/supervisor/config.lua b/supervisor/config.lua index 39adeef..bc02177 100644 --- a/supervisor/config.lua +++ b/supervisor/config.lua @@ -5,14 +5,12 @@ -- instant failover if active goes offline without re-sync SYSTEM_TYPE = 'active' --- scada network -SCADA_NET_PFX = 16000 +-- scada network listen for PLC's and RTU's +SCADA_DEV_LISTEN = 16000 -- failover synchronization SCADA_FO_CHANNEL = 16001 --- listen port for SCADA supervisor access +-- listen port for SCADA supervisor access by coordinators SCADA_SV_CHANNEL = 16002 --- listen port for PLC's -SCADA_PLC_LISTEN = 16003 -- expected number of reactors NUM_REACTORS = 4 diff --git a/supervisor/scada-supervisor.lua b/supervisor/scada-supervisor.lua index 772c572..28a7e40 100644 --- a/supervisor/scada-supervisor.lua +++ b/supervisor/scada-supervisor.lua @@ -20,14 +20,24 @@ if modem == nil then return end --- read config - --- start comms -if not modem.isOpen(config.LISTEN_PORT) then - modem.open(config.LISTEN_PORT) +-- determine active/backup mode +local mode = comms.SCADA_SV_MODES.BACKUP +if config.SYSTEM_TYPE == "active" then + mode = comms.SCADA_SV_MODES.ACTIVE end -local comms = comms.superv_comms(config.NUM_REACTORS, modem, config.SCADA_PLC_LISTEN, config.SCADA_SV_CHANNEL) +-- start comms, open all channels +if not modem.isOpen(config.SCADA_DEV_LISTEN) then + modem.open(config.SCADA_DEV_LISTEN) +end +if not modem.isOpen(config.SCADA_FO_CHANNEL) then + modem.open(config.SCADA_FO_CHANNEL) +end +if not modem.isOpen(config.SCADA_SV_CHANNEL) then + modem.open(config.SCADA_SV_CHANNEL) +end + +local comms = comms.superv_comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_FO_CHANNEL, config.SCADA_SV_CHANNEL) -- base loop clock (4Hz, 5 ticks) local loop_tick = os.startTimer(0.25) From 8429cbfd6e39ae43c5ac855ffd223092f8fe59c5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 25 Jan 2022 13:51:43 -0500 Subject: [PATCH 015/587] scada alarms --- scada-common/alarm.lua | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 scada-common/alarm.lua diff --git a/scada-common/alarm.lua b/scada-common/alarm.lua new file mode 100644 index 0000000..b93df73 --- /dev/null +++ b/scada-common/alarm.lua @@ -0,0 +1,54 @@ +SEVERITY = { + INFO = 0, -- basic info message + WARNING = 1, -- warning about some abnormal state + ALERT = 2, -- important device state changes + FACILITY = 3, -- facility-wide alert + SAFETY = 4, -- safety alerts + EMERGENCY = 5 -- critical safety alarm +} + +function scada_alarm(severity, device, message) + local self = { + time = os.time(), + ts_string = os.date("[%H:%M:%S]"), + severity = severity, + device = device, + message = message + } + + local format = function () + return self.ts_string .. " [" .. severity_to_string(self.severity) .. "] (" .. self.device ") >> " .. self.message + end + + local properties = function () + return { + time = self.time, + severity = self.severity, + device = self.device, + message = self.message + } + end + + return { + format = format, + properties = properties + } +end + +function severity_to_string(severity) + if severity == SEVERITY.INFO then + return "INFO" + elseif severity == SEVERITY.WARNING then + return "WARNING" + elseif severity == SEVERITY.ALERT then + return "ALERT" + elseif severity == SEVERITY.FACILITY then + return "FACILITY" + elseif severity == SEVERITY.SAFETY then + return "SAFETY" + elseif severity == SEVERITY.EMERGENCY then + return "EMERGENCY" + else + return "UNKNOWN" + end +end From d6a68ee3d954193d1c4d04feb5843068840a88f0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 25 Jan 2022 14:51:33 -0500 Subject: [PATCH 016/587] rtu's for boiler, induction matrix, and turbine --- rtu/dev/boiler.lua | 51 +++++++++++++++++++++++++++++++++++++++++++++ rtu/dev/imatrix.lua | 33 +++++++++++++++++++++++++++++ rtu/dev/turbine.lua | 46 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 rtu/dev/boiler.lua create mode 100644 rtu/dev/imatrix.lua create mode 100644 rtu/dev/turbine.lua diff --git a/rtu/dev/boiler.lua b/rtu/dev/boiler.lua new file mode 100644 index 0000000..d76107a --- /dev/null +++ b/rtu/dev/boiler.lua @@ -0,0 +1,51 @@ +-- #REQUIRES rtu.lua + +function boiler_rtu(boiler) + local self = { + rtu = rtu_init(), + boiler = boiler + } + + local rtu_interface = function () + return self.rtu + end + + -- discrete inputs -- + -- none + + -- coils -- + -- none + + -- input registers -- + -- build properties + self.rtu.connect_input_reg(self.boiler.getBoilCapacity) + self.rtu.connect_input_reg(self.boiler.getSteamCapacity) + self.rtu.connect_input_reg(self.boiler.getWaterCapacity) + self.rtu.connect_input_reg(self.boiler.getHeatedCoolantCapacity) + self.rtu.connect_input_reg(self.boiler.getCooledCoolantCapacity) + self.rtu.connect_input_reg(self.boiler.getSuperheaters) + self.rtu.connect_input_reg(self.boiler.getMaxBoilRate) + -- current state + self.rtu.connect_input_reg(self.boiler.getTemperature) + self.rtu.connect_input_reg(self.boiler.getBoilRate) + -- tanks + self.rtu.connect_input_reg(self.boiler.getSteam) + self.rtu.connect_input_reg(self.boiler.getSteamNeeded) + self.rtu.connect_input_reg(self.boiler.getSteamFilledPercentage) + self.rtu.connect_input_reg(self.boiler.getWater) + self.rtu.connect_input_reg(self.boiler.getWaterNeeded) + self.rtu.connect_input_reg(self.boiler.getWaterFilledPercentage) + self.rtu.connect_input_reg(self.boiler.getHeatedCoolant) + self.rtu.connect_input_reg(self.boiler.getHeatedCoolantNeeded) + self.rtu.connect_input_reg(self.boiler.getHeatedCoolantFilledPercentage) + self.rtu.connect_input_reg(self.boiler.getCooledCoolant) + self.rtu.connect_input_reg(self.boiler.getCooledCoolantNeeded) + self.rtu.connect_input_reg(self.boiler.getCooledCoolantFilledPercentage) + + -- holding registers -- + -- none + + return { + rtu_interface = rtu_interface + } +end diff --git a/rtu/dev/imatrix.lua b/rtu/dev/imatrix.lua new file mode 100644 index 0000000..f87bdbf --- /dev/null +++ b/rtu/dev/imatrix.lua @@ -0,0 +1,33 @@ +-- #REQUIRES rtu.lua + +function imatrix_rtu(imatrix) + local self = { + rtu = rtu_init(), + imatrix = imatrix + } + + local rtu_interface = function () + return self.rtu + end + + -- discrete inputs -- + -- none + + -- coils -- + -- none + + -- input registers -- + -- build properties + self.rtu.connect_input_reg(self.imatrix.getTotalMaxEnergy) + -- containers + self.rtu.connect_input_reg(self.imatrix.getTotalEnergy) + self.rtu.connect_input_reg(self.imatrix.getTotalEnergyNeeded) + self.rtu.connect_input_reg(self.imatrix.getTotalEnergyFilledPercentage) + + -- holding registers -- + -- none + + return { + rtu_interface = rtu_interface + } +end diff --git a/rtu/dev/turbine.lua b/rtu/dev/turbine.lua new file mode 100644 index 0000000..4ecc156 --- /dev/null +++ b/rtu/dev/turbine.lua @@ -0,0 +1,46 @@ +-- #REQUIRES rtu.lua + +function turbine_rtu(turbine) + local self = { + rtu = rtu_init(), + turbine = turbine + } + + local rtu_interface = function () + return self.rtu + end + + -- discrete inputs -- + -- none + + -- coils -- + -- none + + -- input registers -- + -- build properties + self.rtu.connect_input_reg(self.turbine.getBlades) + self.rtu.connect_input_reg(self.turbine.getCoils) + self.rtu.connect_input_reg(self.turbine.getVents) + self.rtu.connect_input_reg(self.turbine.getDispersers) + self.rtu.connect_input_reg(self.turbine.getCondensers) + self.rtu.connect_input_reg(self.turbine.getDumpingMode) + self.rtu.connect_input_reg(self.turbine.getSteamCapacity) + self.rtu.connect_input_reg(self.turbine.getMaxFlowRate) + self.rtu.connect_input_reg(self.turbine.getMaxWaterOutput) + self.rtu.connect_input_reg(self.turbine.getMaxProduction) + -- current state + self.rtu.connect_input_reg(self.turbine.getFlowRate) + self.rtu.connect_input_reg(self.turbine.getProductionRate) + self.rtu.connect_input_reg(self.turbine.getLastSteamInputRate) + -- tanks + self.rtu.connect_input_reg(self.turbine.getSteam) + self.rtu.connect_input_reg(self.turbine.getSteamNeeded) + self.rtu.connect_input_reg(self.turbine.getSteamFilledPercentage) + + -- holding registers -- + -- none + + return { + rtu_interface = rtu_interface + } +end From 9cd0079d9e76e35c2141a936c19f79af837a327d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 25 Jan 2022 15:48:01 -0500 Subject: [PATCH 017/587] updated README --- README.md | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/README.md b/README.md index 2ce9de1..abc3fda 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,54 @@ # cc-mek-scada Configurable ComputerCraft SCADA system for multi-reactor control of Mekanism fission reactors with a GUI, automatic safety features, waste processing control, and more! + + +## [SCADA](https://en.wikipedia.org/wiki/SCADA) +> Supervisory control and data acquisition (SCADA) is a control system architecture comprising computers, networked data communications and graphical user interfaces for high-level supervision of machines and processes. It also covers sensors and other devices, such as programmable logic controllers, which interface with process plant or machinery. + +This project implements concepts of a SCADA system in ComputerCraft (because why not? ..okay don't answer that). I recommend reviewing that linked wikipedia page on SCADA if you want to understand the concepts used here. + +![Architecture](https://upload.wikimedia.org/wikipedia/commons/thumb/1/10/Functional_levels_of_a_Distributed_Control_System.svg/1000px-Functional_levels_of_a_Distributed_Control_System.svg.png) + +SCADA and industrial automation terminology is used throughout the project, such as: +- Supervisory Computer: Gathers data and control the process +- Coordinating Computer: Used as the HMI component, user requests high-level processing operations +- RTU: Remote Terminal Unit +- PLC: Programmable Logic Controller + +## ComputerCraft Architecture + +### Coordinating Computers + +There can be one or more of these. They can be either an Advanced Computer or a Pocket Computer. + +### Supervisory Computers + +There can be at most two of these in an active-backup configuration. If a backup is configured, it will act as a hot backup. This means it will be live, all data will be recieved by both it and the active computer, but it will not be commanding anything unless it hears that the active supervisor is shutting down or loses communication with the active supervisor. + +### RTUs + +RTUs are effectively basic connections between a device and the SCADA system with no internal logic providing the system with I/O capabilities. A single Advanced Computer can represent multiple RTUs as instead I am modeling an RTU as the wired modems connected to that computer rather than the computer itself. Each RTU is referenced separately with an identifier in the modbus communications (see Communications section), so a single computer can distribute instructions to multiple devices. This should save on having a pile of computers everywhere (but if you want to have that, no one's stopping you). + +The RTU control code is relatively unique, as instead of having instructions be decoded simply, due to using modbus, I implemented a generalized RTU interface. To fulfill this, each type of I/O operation is linked to a function rather than implementing the logic itself. For example, to connect an input register to a turbine getFlowRate call, the function reference itself is passed to the `connect_input_reg()` function. A call to `read_input_reg()` on that register address will call the `turbine.getFlowRate()` function and return the result. + +### PLCs + +PLCs are advanced devices that allow for both reporting and control to/from the SCADA system in addition to programed behaviors independent of the SCADA system. Currently there is only one type of PLC, and that is the reactor PLC. This is responsible for reporting on and controlling the reactor as a part of the SCADA system, and independently regulating the safety of the reactor. It checks the status for multiple hazard scenarios and shuts down the reactor if any condition is satisfied. + +There can and should only be one of these per reactor. A single Advanced Computer will act as the PLC, with either a direct connection (physical contact) or a wired modem connection to the reactor logic port. + +## Communications + +A vaguely-modbus [modbus](https://en.wikipedia.org/wiki/Modbus) communication protocol is used for communication with RTUs. Useful terminology for you to know: +- Discrete Inputs: Single Bit Read-Only (digital inputs) +- Coils: Single Bit Read/Write (digital I/O) +- Input Registers: Multi-Byte Read-Only (analog inputs) +- Holding Registers: Multi-Byte Read/Write (analog I/O) + +### Security and Encryption + +TBD, I am planning on AES symmetric encryption for security + HMAC to prevent replay attacks. This will be done utilizing this codebase: https://github.com/somesocks/lua-lockbox. + +This is somewhat important here as otherwise anyone can just control your setup, which is undeseriable. Unlike normal Minecraft PVP chaos, it would be very difficult to identify who is messing with your system, as with an Ender Modem they can do it from effectively anywhere and the server operators would have to check every computer's filesystem to find suspicious code. + +The only other possible security mitigation for commanding (no effect on monitoring) is to enforce a maximum authorized transmission range (which I will probably also do, or maybe fall back to), as modem message events contain the transmission distance. From 1c6244d2357b9ec3b80307f038cba98e741f0fae Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 25 Jan 2022 17:07:42 -0500 Subject: [PATCH 018/587] README formatting --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index abc3fda..37b9124 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ There can be at most two of these in an active-backup configuration. If a backup RTUs are effectively basic connections between a device and the SCADA system with no internal logic providing the system with I/O capabilities. A single Advanced Computer can represent multiple RTUs as instead I am modeling an RTU as the wired modems connected to that computer rather than the computer itself. Each RTU is referenced separately with an identifier in the modbus communications (see Communications section), so a single computer can distribute instructions to multiple devices. This should save on having a pile of computers everywhere (but if you want to have that, no one's stopping you). -The RTU control code is relatively unique, as instead of having instructions be decoded simply, due to using modbus, I implemented a generalized RTU interface. To fulfill this, each type of I/O operation is linked to a function rather than implementing the logic itself. For example, to connect an input register to a turbine getFlowRate call, the function reference itself is passed to the `connect_input_reg()` function. A call to `read_input_reg()` on that register address will call the `turbine.getFlowRate()` function and return the result. +The RTU control code is relatively unique, as instead of having instructions be decoded simply, due to using modbus, I implemented a generalized RTU interface. To fulfill this, each type of I/O operation is linked to a function rather than implementing the logic itself. For example, to connect an input register to a turbine `getFlowRate()` call, the function reference itself is passed to the `connect_input_reg()` function. A call to `read_input_reg()` on that register address will call the `turbine.getFlowRate()` function and return the result. ### PLCs From 3c67ee08a867cb5fc50e6fd665f9860b339153c3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 8 Feb 2022 15:42:06 -0500 Subject: [PATCH 019/587] redstone RTU I/O --- rtu/dev/redstone.lua | 88 +++++++++++++++++++++++++++++++++++++++++++ scada-common/rsio.lua | 37 ++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 rtu/dev/redstone.lua create mode 100644 scada-common/rsio.lua diff --git a/rtu/dev/redstone.lua b/rtu/dev/redstone.lua new file mode 100644 index 0000000..0ec1283 --- /dev/null +++ b/rtu/dev/redstone.lua @@ -0,0 +1,88 @@ +-- #REQUIRES rtu.lua +-- note: this RTU makes extensive use of the programming concept of closures + +function redstone_rtu() + local self = { + rtu = rtu_init() + } + + local rtu_interface = function () + return self.rtu + end + + local link_di = function (side, color) + local f_read = nil + + if color then + f_read = function () + return rs.testBundledInput(side, color) + end + else + f_read = function () + return rs.getInput(side) + end + end + + self.rtu.connect_di(f_read) + end + + local link_do = function (side, color) + local f_read = nil + local f_write = nil + + if color then + f_read = function () + return colors.test(rs.getBundledOutput(side), color) + end + + f_write = function (value) + local output = rs.getBundledOutput(side) + + if value then + colors.combine(output, value) + else + colors.subtract(output, value) + end + + rs.setBundledOutput(side, output) + end + else + f_read = function () + return rs.getOutput(side) + end + + f_write = function (value) + rs.setOutput(side, color) + end + end + + self.rtu.connect_coil(f_read, f_write) + end + + local link_ai = function (side) + self.rtu.connect_input_reg( + function () + return rs.getAnalogInput(side) + end + ) + end + + local link_ao = function (side) + self.rtu.connect_holding_reg( + function () + return rs.getAnalogOutput(side) + end, + function (value) + rs.setAnalogOutput(side, value) + end + ) + end + + return { + rtu_interface = rtu_interface, + link_di = link_di, + link_do = link_do, + link_ai = link_ai, + link_ao = link_ao + } +end diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua new file mode 100644 index 0000000..ebc91f4 --- /dev/null +++ b/scada-common/rsio.lua @@ -0,0 +1,37 @@ +RS_IO = { + -- digital inputs -- + + -- facility + F_SCRAM, -- active high, facility-wide scram + F_AE2_LIVE, -- active high, indicates whether AE2 network is online (hint: use redstone P2P) + + -- reactor + R_SCRAM, -- active high, reactor scram + R_ENABLE, -- active high, reactor enable + + -- digital outputs -- + + -- waste + WASTE_PO, -- active low, polonium routing + WASTE_PU, -- active low, plutonium routing + WASTE_AM, -- active low, antimatter routing + + -- reactor + R_SCRAMMED, -- if the reactor is scrammed + R_AUTO_SCRAM, -- if the reactor was automatically scrammed + R_ACTIVE, -- if the reactor is active + R_AUTO_CTRL, -- if the reactor burn rate is automatic + R_DMG_CRIT, -- if the reactor damage is critical + R_HIGH_TEMP, -- if the reactor is at a high temperature + R_NO_COOLANT, -- if the reactor has no coolant + R_EXCESS_HC, -- if the reactor has excess heated coolant + R_EXCESS_WS, -- if the reactor has excess waste + R_INSUFF_FUEL, -- if the reactor has insufficent fuel + R_PLC_TIMEOUT, -- if the reactor PLC has not been heard from + + -- analog outputs -- + + A_R_BURN_RATE, -- reactor burn rate percentage + A_B_BOIL_RATE, -- boiler boil rate percentage + A_T_FLOW_RATE -- turbine flow rate percentage +} From ea84563bb430bee503da323036a7bd0a17f37992 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 10 Mar 2022 14:09:21 -0500 Subject: [PATCH 020/587] added protected peripheral manager and file system logger --- scada-common/log.lua | 43 +++++++++++++++ scada-common/ppm.lua | 127 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 scada-common/log.lua create mode 100644 scada-common/ppm.lua diff --git a/scada-common/log.lua b/scada-common/log.lua new file mode 100644 index 0000000..29fc688 --- /dev/null +++ b/scada-common/log.lua @@ -0,0 +1,43 @@ +-- +-- File System Logger +-- + +-- we use extra short abbreviations since computer craft screens are very small +-- underscores are used since some of these names are used elsewhere (e.g. 'debug' is a lua table) + +local file_handle = fs.open("/log.txt", "a") + +local _log = function (msg) + local stamped = os.date("[%c] ") .. msg + file_handle.writeLine(stamped) + file_handle.flush() +end + +function _debug(msg, trace) + local dbg_info = "" + + if trace then + local name = "" + + if debug.getinfo(2).name ~= nil then + name = ":" .. debug.getinfo(2).name .. "():" + end + + dbg_info = debug.getinfo(2).short_src .. ":" .. name .. + debug.getinfo(2).currentline .. " > " + end + + _log("[DBG] " .. dbg_info .. msg) +end + +function _warning(msg) + _log("[WRN] " .. msg) +end + +function _error(msg) + _log("[ERR] " .. msg) +end + +function _fatal(msg) + _log("[FTL] " .. msg) +end diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua new file mode 100644 index 0000000..32b05ae --- /dev/null +++ b/scada-common/ppm.lua @@ -0,0 +1,127 @@ +-- #REQUIRES log.lua + +-- +-- Protected Peripheral Manager +-- + +function ppm() + local self = { + mounts = {} + } + + -- wrap peripheral calls with lua protected call + -- ex. reason: we don't want a disconnect to crash the program before a SCRAM + local peri_init = function (device) + for key, func in pairs(device) do + device[key] = function (...) + local status, result = pcall(func, ...) + + if status then + return result + else + -- function failed + log._error("protected " .. key .. "() -> " .. result) + return nil + end + end + end + end + + -- mount all available peripherals (clears mounts first) + local mount_all = function () + local ifaces = peripheral.getNames() + + self.mounts = {} + + for i = 1, #ifaces do + local pm_dev = peripheral.wrap(ifaces[i]) + peri_init(pm_dev) + self.mounts[ifaces[i]] = { peripheral.getType(ifaces[i]), pm_dev } + end + end + + -- mount a particular device + local mount = function (name) + local ifaces = peripheral.getNames() + local pm_dev = nil + + for i = 1, #ifaces do + if name == peripheral.getType(ifaces[i]) then + pm_dev = peripheral.wrap(ifaces[i]) + peri_init(pm_dev) + + self.mounts[ifaces[i]] = { + type = peripheral.getType(ifaces[i]), + device = pm_dev + } + break + end + end + + return pm_dev + end + + -- handle peripheral_detach event + local unmount_handler = function (iface) + -- what got disconnected? + local lost_dev = self.mounts[iface] + local type = lost_dev.type + + log._warning("PMGR: lost device " .. type .. " mounted to " .. iface) + + return self.mounts[iface] + end + + -- list all available peripherals + local list_avail = function () + return peripheral.getNames() + end + + -- list mounted peripherals + local list_mounts = function () + return self.mounts + end + + -- get a mounted peripheral by side/interface + local get_periph = function (iface) + return self.mounts[iface].device + end + + -- get a mounted peripheral by type + local get_device = function (name) + local device = nil + + for side, data in pairs(self.mounts) do + if data.type == name then + device = data.device + break + end + end + + return device + end + + -- list all connected monitors + local list_monitors = function () + local monitors = {} + + for side, data in pairs(self.mounts) do + if data.type == "monitor" then + monitors[side] = data.device + end + end + + return monitors + end + + return { + mount_all = mount_all, + mount = mount, + umount = unmount_handler, + list_avail = list_avail, + list_mounts = list_mounts, + get_periph = get_periph, + get_device = get_device, + list_monitors = list_monitors + } +end From a0b2c1f3e2dbc872b9f921b69cde80c237d9b016 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 10 Mar 2022 14:12:07 -0500 Subject: [PATCH 021/587] changed ppm to not wrap under ppm() function --- scada-common/ppm.lua | 217 ++++++++++++++++++++----------------------- 1 file changed, 102 insertions(+), 115 deletions(-) diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 32b05ae..2eaa9ab 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -4,124 +4,111 @@ -- Protected Peripheral Manager -- -function ppm() - local self = { - mounts = {} - } +local self = { + mounts = {} +} - -- wrap peripheral calls with lua protected call - -- ex. reason: we don't want a disconnect to crash the program before a SCRAM - local peri_init = function (device) - for key, func in pairs(device) do - device[key] = function (...) - local status, result = pcall(func, ...) +-- wrap peripheral calls with lua protected call +-- ex. reason: we don't want a disconnect to crash the program before a SCRAM +local peri_init = function (device) + for key, func in pairs(device) do + device[key] = function (...) + local status, result = pcall(func, ...) - if status then - return result - else - -- function failed - log._error("protected " .. key .. "() -> " .. result) - return nil - end + if status then + return result + else + -- function failed + log._error("protected " .. key .. "() -> " .. result) + return nil end end end - - -- mount all available peripherals (clears mounts first) - local mount_all = function () - local ifaces = peripheral.getNames() - - self.mounts = {} - - for i = 1, #ifaces do - local pm_dev = peripheral.wrap(ifaces[i]) - peri_init(pm_dev) - self.mounts[ifaces[i]] = { peripheral.getType(ifaces[i]), pm_dev } - end - end - - -- mount a particular device - local mount = function (name) - local ifaces = peripheral.getNames() - local pm_dev = nil - - for i = 1, #ifaces do - if name == peripheral.getType(ifaces[i]) then - pm_dev = peripheral.wrap(ifaces[i]) - peri_init(pm_dev) - - self.mounts[ifaces[i]] = { - type = peripheral.getType(ifaces[i]), - device = pm_dev - } - break - end - end - - return pm_dev - end - - -- handle peripheral_detach event - local unmount_handler = function (iface) - -- what got disconnected? - local lost_dev = self.mounts[iface] - local type = lost_dev.type - - log._warning("PMGR: lost device " .. type .. " mounted to " .. iface) - - return self.mounts[iface] - end - - -- list all available peripherals - local list_avail = function () - return peripheral.getNames() - end - - -- list mounted peripherals - local list_mounts = function () - return self.mounts - end - - -- get a mounted peripheral by side/interface - local get_periph = function (iface) - return self.mounts[iface].device - end - - -- get a mounted peripheral by type - local get_device = function (name) - local device = nil - - for side, data in pairs(self.mounts) do - if data.type == name then - device = data.device - break - end - end - - return device - end - - -- list all connected monitors - local list_monitors = function () - local monitors = {} - - for side, data in pairs(self.mounts) do - if data.type == "monitor" then - monitors[side] = data.device - end - end - - return monitors - end - - return { - mount_all = mount_all, - mount = mount, - umount = unmount_handler, - list_avail = list_avail, - list_mounts = list_mounts, - get_periph = get_periph, - get_device = get_device, - list_monitors = list_monitors - } +end + +-- mount all available peripherals (clears mounts first) +function mount_all() + local ifaces = peripheral.getNames() + + self.mounts = {} + + for i = 1, #ifaces do + local pm_dev = peripheral.wrap(ifaces[i]) + peri_init(pm_dev) + self.mounts[ifaces[i]] = { peripheral.getType(ifaces[i]), pm_dev } + end +end + +-- mount a particular device +function mount(name) + local ifaces = peripheral.getNames() + local pm_dev = nil + + for i = 1, #ifaces do + if name == peripheral.getType(ifaces[i]) then + pm_dev = peripheral.wrap(ifaces[i]) + peri_init(pm_dev) + + self.mounts[ifaces[i]] = { + type = peripheral.getType(ifaces[i]), + device = pm_dev + } + break + end + end + + return pm_dev +end + +-- handle peripheral_detach event +function unmount_handler(iface) + -- what got disconnected? + local lost_dev = self.mounts[iface] + local type = lost_dev.type + + log._warning("PMGR: lost device " .. type .. " mounted to " .. iface) + + return self.mounts[iface] +end + +-- list all available peripherals +function list_avail() + return peripheral.getNames() +end + +-- list mounted peripherals +function list_mounts() + return self.mounts +end + +-- get a mounted peripheral by side/interface +function get_periph(iface) + return self.mounts[iface].device +end + +-- get a mounted peripheral by type +function get_device(name) + local device = nil + + for side, data in pairs(self.mounts) do + if data.type == name then + device = data.device + break + end + end + + return device +end + +-- list all connected monitors +function list_monitors() + local monitors = {} + + for side, data in pairs(self.mounts) do + if data.type == "monitor" then + monitors[side] = data.device + end + end + + return monitors end From 5cff346cb55085909d239cfce2c2c7ff47f5b542 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 10 Mar 2022 14:21:03 -0500 Subject: [PATCH 022/587] ppm function renames, edited log messages, and changed protected calls to return true if function has no return --- scada-common/ppm.lua | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 2eaa9ab..4b9e2f1 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -16,10 +16,15 @@ local peri_init = function (device) local status, result = pcall(func, ...) if status then - return result + -- assume nil is only for functions with no return, so return status + if result == nil then + return true + else + return result + end else -- function failed - log._error("protected " .. key .. "() -> " .. result) + log._error("PPM: protected " .. key .. "() -> " .. result) return nil end end @@ -37,6 +42,10 @@ function mount_all() peri_init(pm_dev) self.mounts[ifaces[i]] = { peripheral.getType(ifaces[i]), pm_dev } end + + if #ifaces == 0 then + log._warning("PPM: mount_all() -> no devices found") + end end -- mount a particular device @@ -61,12 +70,12 @@ function mount(name) end -- handle peripheral_detach event -function unmount_handler(iface) +function handle_unmount(iface) -- what got disconnected? local lost_dev = self.mounts[iface] local type = lost_dev.type - log._warning("PMGR: lost device " .. type .. " mounted to " .. iface) + log._warning("PPM: lost device " .. type .. " mounted to " .. iface) return self.mounts[iface] end From ac4ca3e56e485b7caf0dced05ec2b7b01c55287b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 10 Mar 2022 14:23:14 -0500 Subject: [PATCH 023/587] reactor plc utilizes ppm and is now changed to use pullEventRaw --- reactor-plc/startup.lua | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 6d2d9de..01c80a8 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -2,6 +2,8 @@ -- Reactor Programmable Logic Controller -- +os.loadAPI("scada-common/log.lua") +os.loadAPI("scada-common/ppm.lua") os.loadAPI("scada-common/util.lua") os.loadAPI("scada-common/comms.lua") os.loadAPI("reactor-plc/config.lua") @@ -11,8 +13,10 @@ local R_PLC_VERSION = "alpha-v0.1" local print_ts = util.print_ts -local reactor = peripheral.find("fissionReactor") -local modem = peripheral.find("modem") +ppm.mount_all() + +local reactor = ppm.get_device("fissionReactor") +local modem = ppm.get_device("modem") print(">> Reactor PLC " .. R_PLC_VERSION .. " <<") @@ -58,14 +62,19 @@ local control_state = false -- event loop while true do - local event, param1, param2, param3, param4, param5 = os.pullEvent() + local event, param1, param2, param3, param4, param5 = os.pullEventRaw() if event == "peripheral_detach" then - print_ts("[fatal] lost a peripheral, stopping...\n") - -- todo: determine which disconnected and what is left - -- hopefully it wasn't the reactor - reactor.scram() - -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_DC) ? + ppm.handle_unmount(param1) + + -- try to scram reactor if it is still connected + if reactor.scram() then + print_ts("[fatal] PLC lost a peripheral: successful SCRAM, now exiting...\n") + else + print_ts("[fatal] PLC lost a peripheral: failed SCRAM, now exiting...\n") + end + + -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_PERI_DC) ? return end @@ -95,5 +104,11 @@ while true do -- haven't heard from server recently? shutdown iss.trip_timeout() print_ts("[alert] server timeout, reactor disabled\n") + elseif event == "terminate" then + -- safe exit + reactor.scram() + -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_SHUTDOWN) ? + print_ts("[alert] exiting, reactor disabled\n") + return end end From 17874c46584617cf7daa25e7e2fe750e711b2240 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 14 Mar 2022 14:19:14 -0400 Subject: [PATCH 024/587] cleanup/improvements to PLC comms --- reactor-plc/plc.lua | 4 +- reactor-plc/startup.lua | 15 ++-- scada-common/comms.lua | 169 +++++++++++++++++++++------------------- scada-common/modbus.lua | 2 +- 4 files changed, 101 insertions(+), 89 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index d1fda80..decfd0b 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -18,9 +18,7 @@ function scada_link(plc_comms) os.cancelTimer(link_timeout) end - local s_packet = comms.scada_packet() - s_packet.receive(p1, p2, p3, p4, p5) - local packet = s_packet.as_rplc() + local packet = plc_comms.parse_packet(p1, p2, p3, p4, p5) if packet then -- handle response local response = plc_comms.handle_link(packet) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 01c80a8..3f1c29a 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -3,13 +3,14 @@ -- os.loadAPI("scada-common/log.lua") -os.loadAPI("scada-common/ppm.lua") os.loadAPI("scada-common/util.lua") +os.loadAPI("scada-common/ppm.lua") os.loadAPI("scada-common/comms.lua") + os.loadAPI("reactor-plc/config.lua") os.loadAPI("reactor-plc/plc.lua") -local R_PLC_VERSION = "alpha-v0.1" +local R_PLC_VERSION = "alpha-v0.1.0" local print_ts = util.print_ts @@ -97,7 +98,7 @@ while true do -- feed the watchdog first so it doesn't uhh,,,eat our packets conn_watchdog.feed() - local packet = comms.make_packet(p1, p2, p3, p4, p5) + local packet = plc_comms.parse_packet(p1, p2, p3, p4, p5) plc_comms.handle_packet(packet) elseif event == "timer" and param1 == conn_watchdog.get_timer() then @@ -106,9 +107,13 @@ while true do print_ts("[alert] server timeout, reactor disabled\n") elseif event == "terminate" then -- safe exit - reactor.scram() + if reactor.scram() then + print_ts("[alert] exiting, reactor disabled\n") + else + -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_LOST_CONTROL) ? + print_ts("[alert] exiting, reactor failed to disable\n") + end -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_SHUTDOWN) ? - print_ts("[alert] exiting, reactor disabled\n") return end end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 5641cba..ec0dd9b 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -1,7 +1,7 @@ PROTOCOLS = { MODBUS_TCP = 0, -- our "modbus tcp"-esque protocol RPLC = 1, -- reactor plc protocol - SCADA_MGMT = 2, -- SCADA supervisor intercommunication + SCADA_MGMT = 2, -- SCADA supervisor intercommunication and device advertisements COORD_DATA = 3 -- data packets for coordinators to/from supervisory controller } @@ -37,18 +37,18 @@ function scada_packet() local self = { modem_msg_in = nil, valid = false, - seq_id = nil, + seq_num = nil, protocol = nil, length = nil, raw = nil } - local make = function (seq_id, protocol, payload) + local make = function (seq_num, protocol, payload) self.valid = true - self.seq_id = seq_id + self.seq_num = seq_num self.protocol = protocol self.length = #payload - self.raw = { self.seq_id, self.protocol, self.length, payload } + self.raw = { self.seq_num, self.protocol, self.length, payload } end local receive = function (side, sender, reply_to, message, distance) @@ -67,23 +67,18 @@ function scada_packet() return false else self.valid = true - self.seq_id = self.raw[1] + self.seq_num = self.raw[1] self.protocol = self.raw[2] self.length = self.raw[3] end end - local seq_id = function (packet) - return self.seq_id - end - - local protocol = function (packet) - return self.protocol - end - - local length = function (packet) - return self.length - end + -- basic gets + local modem_event = function (packet) return self.modem_msg_in end + local raw = function (packet) return self.raw end + local seq_num = function (packet) return self.seq_num end + local protocol = function (packet) return self.protocol end + local length = function (packet) return self.length end local data = function (packet) local subset = nil @@ -93,38 +88,15 @@ function scada_packet() return subset end - local raw = function (packet) - return self.raw - end - - local modem_event = function (packet) - return self.modem_msg_in - end - - local as_rplc = function () - local pkt = nil - if self.valid and self.protocol == PROTOCOLS.RPLC then - local body = data() - if #body > 2 then - pkt = { - id = body[1], - type = body[2], - length = #body - 2, - body = { table.unpack(body, 3, 2 + #body) } - } - end - end - return pkt - end - return { make = make, receive = receive, - seq_id = seq_id, + modem_event = modem_event, + raw = raw + seq_num = seq_num, protocol = protocol, length = length, - raw = raw, - modem_event = modem_event + data = data } end @@ -139,7 +111,7 @@ end function superv_comms(mode, num_reactors, modem, dev_listen, fo_channel, sv_channel) local self = { mode = mode, - seq_id = 0, + seq_num = 0, num_reactors = num_reactors, modem = modem, dev_listen = dev_listen, @@ -149,11 +121,20 @@ function superv_comms(mode, num_reactors, modem, dev_listen, fo_channel, sv_chan } end +function rtu_comms(modem, local_port, server_port) + local self = { + txn_id = 0, + modem = modem, + s_port = server_port, + l_port = local_port + } +end + -- reactor PLC communications function rplc_comms(id, modem, local_port, server_port, reactor) local self = { id = id, - seq_id = 0, + seq_num = 0, modem = modem, s_port = server_port, l_port = local_port, @@ -165,9 +146,9 @@ function rplc_comms(id, modem, local_port, server_port, reactor) local _send = function (msg) local packet = scada_packet() - packet.make(self.seq_id, PROTOCOLS.RPLC, msg) + packet.make(self.seq_num, PROTOCOLS.RPLC, msg) self.modem.transmit(self.s_port, self.l_port, packet.raw()) - self.seq_id = self.seq_id + 1 + self.seq_num = self.seq_num + 1 end -- variable reactor status information, excluding heating rate @@ -218,6 +199,59 @@ function rplc_comms(id, modem, local_port, server_port, reactor) -- PUBLIC FUNCTIONS -- + -- parse an RPLC packet + local parse_packet = function(side, sender, reply_to, message, distance) + local pkt = nil + local s_pkt = scada_packet() + + -- parse packet as generic SCADA packet + s_pkt.recieve(side, sender, reply_to, message, distance) + + -- get using RPLC protocol format + if self.valid and self.protocol == PROTOCOLS.RPLC then + local body = data() + if #body > 2 then + pkt = { + id = body[1], + type = body[2], + length = #body - 2, + body = { table.unpack(body, 3, 2 + #body) } + } + end + end + + return pkt + end + + -- handle a linking packet + local handle_link = function (packet) + if packet.type == RPLC_TYPES.LINK_REQ then + return packet.data[1] == RPLC_LINKING.ALLOW + else + return nil + end + end + + -- handle an RPLC packet + local handle_packet = function (packet) + if packet.type == RPLC_TYPES.KEEP_ALIVE then + -- keep alive request received, nothing to do except feed watchdog + elseif packet.type == RPLC_TYPES.MEK_STRUCT then + -- request for physical structure + send_struct() + elseif packet.type == RPLC_TYPES.RS_IO_CONNS then + -- request for redstone connections + send_rs_io_conns() + elseif packet.type == RPLC_TYPES.RS_IO_GET then + elseif packet.type == RPLC_TYPES.RS_IO_SET then + elseif packet.type == RPLC_TYPES.MEK_SCRAM then + elseif packet.type == RPLC_TYPES.MEK_ENABLE then + elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then + elseif packet.type == RPLC_TYPES.ISS_GET then + elseif packet.type == RPLC_TYPES.ISS_CLEAR then + end + end + -- attempt to establish link with supervisor local send_link_req = function () local linking_data = { @@ -229,7 +263,7 @@ function rplc_comms(id, modem, local_port, server_port, reactor) end -- send structure properties (these should not change) - -- server will cache these + -- (server will cache these) local send_struct = function () local mek_data = { heat_cap = self.reactor.getHeatCapacity(), @@ -252,8 +286,8 @@ function rplc_comms(id, modem, local_port, server_port, reactor) end -- send live status information - -- control_state: acknowledged control state from supervisor - -- overridden: if ISS force disabled reactor + -- control_state : acknowledged control state from supervisor + -- overridden : if ISS force disabled reactor local send_status = function (control_state, overridden) local mek_data = nil @@ -277,38 +311,13 @@ function rplc_comms(id, modem, local_port, server_port, reactor) local send_rs_io_conns = function () end - local handle_link = function (packet) - if packet.type == RPLC_TYPES.LINK_REQ then - return packet.data[1] == RPLC_LINKING.ALLOW - else - return nil - end - end - - local handle_packet = function (packet) - if packet.type == RPLC_TYPES.KEEP_ALIVE then - -- keep alive request received, nothing to do except feed watchdog - elseif packet.type == RPLC_TYPES.MEK_STRUCT then - -- request for physical structure - send_struct() - elseif packet.type == RPLC_TYPES.RS_IO_CONNS then - -- request for redstone connections - send_rs_io_conns() - elseif packet.type == RPLC_TYPES.RS_IO_GET then - elseif packet.type == RPLC_TYPES.RS_IO_SET then - elseif packet.type == RPLC_TYPES.MEK_SCRAM then - elseif packet.type == RPLC_TYPES.MEK_ENABLE then - elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then - elseif packet.type == RPLC_TYPES.ISS_GET then - elseif packet.type == RPLC_TYPES.ISS_CLEAR then - end - end - return { + parse_packet = parse_packet, + handle_link = handle_link, + handle_packet = handle_packet, send_link_req = send_link_req, send_struct = send_struct, send_status = send_status, - send_rs_io_conns = send_rs_io_conns, - handle_link = handle_link + send_rs_io_conns = send_rs_io_conns } end diff --git a/scada-common/modbus.lua b/scada-common/modbus.lua index 6721c44..d2b18f0 100644 --- a/scada-common/modbus.lua +++ b/scada-common/modbus.lua @@ -1,4 +1,4 @@ --- #REQUIRES comms.lua +-- #REQUIRES rtu.lua -- modbus function codes local MODBUS_FCODE = { From a9d4458103dc2f16b2f715613cf4732e48419757 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 15 Mar 2022 11:58:08 -0400 Subject: [PATCH 025/587] redstone I/O constants defined, digital I/O functions with active high/low mappings added --- scada-common/rsio.lua | 120 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 99 insertions(+), 21 deletions(-) diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index ebc91f4..468a1fa 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -1,37 +1,115 @@ +IO_LVL = { + LOW = 0, + HIGH = 1 +} + RS_IO = { -- digital inputs -- -- facility - F_SCRAM, -- active high, facility-wide scram - F_AE2_LIVE, -- active high, indicates whether AE2 network is online (hint: use redstone P2P) + F_SCRAM = 1, -- active low, facility-wide scram + F_AE2_LIVE = 2, -- active high, indicates whether AE2 network is online (hint: use redstone P2P) -- reactor - R_SCRAM, -- active high, reactor scram - R_ENABLE, -- active high, reactor enable + R_SCRAM = 3, -- active low, reactor scram + R_ENABLE = 4, -- active high, reactor enable -- digital outputs -- -- waste - WASTE_PO, -- active low, polonium routing - WASTE_PU, -- active low, plutonium routing - WASTE_AM, -- active low, antimatter routing + WASTE_PO = 5, -- active low, polonium routing + WASTE_PU = 6, -- active low, plutonium routing + WASTE_AM = 7, -- active low, antimatter routing -- reactor - R_SCRAMMED, -- if the reactor is scrammed - R_AUTO_SCRAM, -- if the reactor was automatically scrammed - R_ACTIVE, -- if the reactor is active - R_AUTO_CTRL, -- if the reactor burn rate is automatic - R_DMG_CRIT, -- if the reactor damage is critical - R_HIGH_TEMP, -- if the reactor is at a high temperature - R_NO_COOLANT, -- if the reactor has no coolant - R_EXCESS_HC, -- if the reactor has excess heated coolant - R_EXCESS_WS, -- if the reactor has excess waste - R_INSUFF_FUEL, -- if the reactor has insufficent fuel - R_PLC_TIMEOUT, -- if the reactor PLC has not been heard from + R_SCRAMMED = 8, -- active high, if the reactor is scrammed + R_AUTO_SCRAM = 9, -- active high, if the reactor was automatically scrammed + R_ACTIVE = 10, -- active high, if the reactor is active + R_AUTO_CTRL = 11, -- active high, if the reactor burn rate is automatic + R_DMG_CRIT = 12, -- active high, if the reactor damage is critical + R_HIGH_TEMP = 13, -- active high, if the reactor is at a high temperature + R_NO_COOLANT = 14, -- active high, if the reactor has no coolant + R_EXCESS_HC = 15, -- active high, if the reactor has excess heated coolant + R_EXCESS_WS = 16, -- active high, if the reactor has excess waste + R_INSUFF_FUEL = 17, -- active high, if the reactor has insufficent fuel + R_PLC_TIMEOUT = 18, -- active high, if the reactor PLC has not been heard from -- analog outputs -- - A_R_BURN_RATE, -- reactor burn rate percentage - A_B_BOIL_RATE, -- boiler boil rate percentage - A_T_FLOW_RATE -- turbine flow rate percentage + A_R_BURN_RATE = 19, -- reactor burn rate percentage + A_B_BOIL_RATE = 20, -- boiler boil rate percentage + A_T_FLOW_RATE = 21 -- turbine flow rate percentage } + +local _TRINARY = function (cond, t, f) if cond then return t else return f end end + +local _DI_ACTIVE_HIGH = function (level) return level == IO_LVL.HIGH end +local _DI_ACTIVE_LOW = function (level) return level == IO_LVL.LOW end +local _DO_ACTIVE_HIGH = function (on) return _TRINARY(on, IO_LVL.HIGH, IO_LVL.LOW) end +local _DO_ACTIVE_LOW = function (on) return _TRINARY(on, IO_LVL.LOW, IO_LVL.HIGH) end + +local RS_DIO_MAP = { + -- F_SCRAM + _DI_ACTIVE_LOW, + -- F_AE2_LIVE + _DI_ACTIVE_HIGH, + -- R_SCRAM + _DI_ACTIVE_LOW, + -- R_ENABLE + _DI_ACTIVE_HIGH, + -- WASTE_PO + _DO_ACTIVE_LOW, + -- WASTE_PU + _DO_ACTIVE_LOW, + -- WASTE_AM + _DO_ACTIVE_LOW, + -- R_SCRAMMED + _DO_ACTIVE_HIGH, + -- R_AUTO_SCRAM + _DO_ACTIVE_HIGH, + -- R_ACTIVE + _DO_ACTIVE_HIGH, + -- R_AUTO_CTRL + _DO_ACTIVE_HIGH, + -- R_DMG_CRIT + _DO_ACTIVE_HIGH, + -- R_HIGH_TEMP + _DO_ACTIVE_HIGH, + -- R_NO_COOLANT + _DO_ACTIVE_HIGH, + -- R_EXCESS_HC + _DO_ACTIVE_HIGH, + -- R_EXCESS_WS + _DO_ACTIVE_HIGH, + -- R_INSUFF_FUEL + _DO_ACTIVE_HIGH, + -- R_PLC_TIMEOUT + _DO_ACTIVE_HIGH +} + +-- get digital IO level reading +function digital_input_read(rs_value) + if rs_value then + return IO_LVL.HIGH + else + return IO_LVL.LOW + end +end + +-- returns true if the level corresponds to active +function digital_input_is_active(channel, level) + if channel > RS_IO.R_ENABLE then + return false + else + return RS_DIO_MAP[channel](level) + end +end + +-- returns the level corresponding to active +function digital_output_write(channel, active) + if channel < RS_IO.WASTE_PO or channel > RS_IO.R_PLC_TIMEOUT then + return IO_LVL.LOW + else + return RS_DIO_MAP[channel](level) + end +end From 6e1e4c4685f89119b1f9d3d192d67f874d8b83be Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 15 Mar 2022 11:58:22 -0400 Subject: [PATCH 026/587] ppm includes get_type function now --- scada-common/ppm.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 4b9e2f1..5e19636 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -95,6 +95,11 @@ function get_periph(iface) return self.mounts[iface].device end +-- get a mounted peripheral type by side/interface +function get_type(iface) + return self.mounts[iface].type +end + -- get a mounted peripheral by type function get_device(name) local device = nil From 5642e3283dfc35f9a63edea75be6eea9e9b64ce4 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 15 Mar 2022 11:58:52 -0400 Subject: [PATCH 027/587] fixes to modbus_packet() --- scada-common/modbus.lua | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/scada-common/modbus.lua b/scada-common/modbus.lua index d2b18f0..de21a2e 100644 --- a/scada-common/modbus.lua +++ b/scada-common/modbus.lua @@ -1,5 +1,3 @@ --- #REQUIRES rtu.lua - -- modbus function codes local MODBUS_FCODE = { READ_COILS = 0x01, @@ -178,6 +176,8 @@ function modbus_init(rtu_dev) end function modbus_packet() + local MODBUS_TCP = 0 + local self = { txn_id = txn_id, protocol = protocol, @@ -187,17 +187,7 @@ function modbus_packet() data = data } - local receive = function (raw) - local size_ok = #raw ~= 6 - - if size_ok then - set(raw[1], raw[2], raw[3], raw[4], raw[5], raw[6]) - end - - return size_ok and self.protocol == comms.PROTOCOLS.MODBUS_TCP - end - - local set = function (txn_id, protocol, length, unit_id, func_code, data) + local make = function (txn_id, protocol, length, unit_id, func_code, data) self.txn_id = txn_id self.protocol = protocol self.length = length @@ -206,6 +196,16 @@ function modbus_packet() self.data = data end + local receive = function (raw) + local size_ok = #raw ~= 6 + + if size_ok then + make(raw[1], raw[2], raw[3], raw[4], raw[5], raw[6]) + end + + return size_ok and self.protocol == MODBUS_TCP + end + local get = function () return { txn_id = self.txn_id, @@ -216,4 +216,10 @@ function modbus_packet() data = self.data } end + + return { + make = make, + receive = receive, + get = get + } end From 1e23a2fd67dbfc554dd702e8c3dc489cd473d967 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 15 Mar 2022 12:02:31 -0400 Subject: [PATCH 028/587] work on RTU startup and comms --- rtu/config.lua | 27 +++++++- rtu/startup.lua | 86 +++++++++++++++++++++++++ scada-common/comms.lua | 140 +++++++++++++++++++++++++++++++++++------ 3 files changed, 230 insertions(+), 23 deletions(-) diff --git a/rtu/config.lua b/rtu/config.lua index 39b23ad..66b2154 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -1,10 +1,31 @@ -RTU__DEVICES = { +-- #REQUIRES rsio.lua + +SCADA_SERVER = 16000 + +RTU_DEVICES = { { name = "boiler_0", - reactor_owner = 1 + index = 1, + for_reactor = 1 }, { name = "turbine_0", - reactor_owner = 1 + index = 1, + for_reactor = 1 } } + +RTU_REDSTONE = { + { + io = RS_IO.WASTE_PO, + for_reactor = 1 + }, + { + io = RS_IO.WASTE_PU, + for_reactor = 1 + }, + { + io = RS_IO.WASTE_AM, + for_reactor = 1 + }, +} diff --git a/rtu/startup.lua b/rtu/startup.lua index de9a784..dfbad8b 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -2,5 +2,91 @@ -- RTU: Remote Terminal Unit -- +os.loadAPI("scada-common/log.lua") +os.loadAPI("scada-common/util.lua") +os.loadAPI("scada-common/ppm.lua") +os.loadAPI("scada-common/modbus.lua") +os.loadAPI("scada-common/rsio.lua") + os.loadAPI("config.lua") os.loadAPI("rtu.lua") + +os.loadAPI("dev/boiler.lua") +os.loadAPI("dev/imatrix.lua") +os.loadAPI("dev/turbine.lua") + +local RTU_VERSION = "alpha-v0.1.0" + +local print_ts = util.print_ts + +-- mount connected devices +ppm.mount_all() + +-- get modem +local modem = ppm.get_device("modem") +if modem == nil then + print("No modem found, exiting...") + return +end + +-- start comms +if not modem.isOpen(config.LISTEN_PORT) then + modem.open(config.LISTEN_PORT) +end + +local rtu_comms = comms.rtu_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) + +-- determine configuration +local units = {} + +-- mounted peripherals +for i = 1, #RTU_DEVICES do + local device = ppm.get_periph(RTU_DEVICES[i].name) + + if device == nil then + local message = "'" .. RTU_DEVICES[i].name .. "' not found" + print_ts(message) + log._warning(message) + else + local type = ppm.get_type(RTU_DEVICES[i].name) + local rtu_iface = nil + local rtu_type = "" + + if type == "boiler" then + -- boiler multiblock + rtu_type = "boiler" + rtu_iface = boiler_rtu(device) + elseif type == "turbine" then + -- turbine multiblock + rtu_type = "turbine" + rtu_iface = turbine_rtu(device) + elseif type == "mekanismMachine" then + -- assumed to be an induction matrix multiblock + rtu_type = "imatrix" + rtu_iface = imatrix_rtu(device) + else + local message = "device '" .. RTU_DEVICES[i].name .. "' is not a known type (" .. type .. ")" + print_ts(message) + log._warning(message) + end + + if rtu_iface ~= nil then + table.insert(units, { + name = RTU_DEVICES[i].name, + type = rtu_type, + index = RTU_DEVICES[i].index, + reactor = RTU_DEVICES[i].for_reactor, + device = device, + rtu = rtu_iface + }) + end + end +end + +-- redstone devices +for i = 1, #RTU_REDSTONE do +end + +-- advertise units +rtu_comms.send_advertisement(units) + diff --git a/scada-common/comms.lua b/scada-common/comms.lua index ec0dd9b..6ea907b 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -1,13 +1,15 @@ +-- #REQUIRES modbus.lua + PROTOCOLS = { - MODBUS_TCP = 0, -- our "modbus tcp"-esque protocol - RPLC = 1, -- reactor plc protocol - SCADA_MGMT = 2, -- SCADA supervisor intercommunication and device advertisements - COORD_DATA = 3 -- data packets for coordinators to/from supervisory controller + MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol + RPLC = 1, -- reactor PLC protocol + SCADA_MGMT = 2, -- SCADA supervisor intercommunication, device advertisements, etc + COORD_DATA = 3 -- data packets for coordinators to/from supervisory controller } SCADA_SV_MODES = { - ACTIVE = 0, - BACKUP = 1 + ACTIVE = 0, -- supervisor running as primary + BACKUP = 1 -- supervisor running as hot backup } RPLC_TYPES = { @@ -23,13 +25,26 @@ RPLC_TYPES = { MEK_BURN_RATE = 9, -- set burn rate ISS_ALARM = 10, -- ISS alarm broadcast ISS_GET = 11, -- get ISS status - ISS_CLEAR = 12 -- clear ISS trip (if in bad state, will trip immideatly) + ISS_CLEAR = 12 -- clear ISS trip (if in bad state, will trip immediately) } RPLC_LINKING = { - ALLOW = 0, - DENY = 1, - COLLISION = 2 + ALLOW = 0, -- link approved + DENY = 1, -- link denied + COLLISION = 2 -- link denied due to existing active link +} + +SCADA_MGMT_TYPES = { + PING = 0, -- generic ping + SV_HEARTBEAT = 1, -- supervisor heartbeat + RTU_HEARTBEAT = 2, -- RTU heartbeat + RTU_ADVERT = 3 -- RTU capability advertisement +} + +RTU_ADVERT_TYPES = { + BOILER = 0, -- boiler + TURBINE = 1, -- turbine + IMATRIX = 2 -- induction matrix } -- generic SCADA packet object @@ -73,14 +88,16 @@ function scada_packet() end end - -- basic gets - local modem_event = function (packet) return self.modem_msg_in end - local raw = function (packet) return self.raw end - local seq_num = function (packet) return self.seq_num end - local protocol = function (packet) return self.protocol end - local length = function (packet) return self.length end + local modem_event = function () return self.modem_msg_in end + local raw = function () return self.raw end - local data = function (packet) + local is_valid = function () return self.valid end + + local seq_num = function () return self.seq_num end + local protocol = function () return self.protocol end + local length = function () return self.length end + + local data = function () local subset = nil if self.valid then subset = { table.unpack(self.raw, 4, 3 + self.length) } @@ -92,7 +109,8 @@ function scada_packet() make = make, receive = receive, modem_event = modem_event, - raw = raw + raw = raw, + is_valid = is_valid, seq_num = seq_num, protocol = protocol, length = length, @@ -123,11 +141,92 @@ end function rtu_comms(modem, local_port, server_port) local self = { + seq_num = 0, txn_id = 0, modem = modem, s_port = server_port, l_port = local_port } + + -- PRIVATE FUNCTIONS -- + + local _send = function (protocol, msg) + local packet = scada_packet() + packet.make(self.seq_num, protocol, msg) + self.modem.transmit(self.s_port, self.l_port, packet.raw()) + self.seq_num = self.seq_num + 1 + end + + -- PUBLIC FUNCTIONS -- + + -- parse a MODBUS/SCADA packet + local parse_packet = function(side, sender, reply_to, message, distance) + local pkt = nil + local s_pkt = scada_packet() + + -- parse packet as generic SCADA packet + s_pkt.recieve(side, sender, reply_to, message, distance) + + if s_pkt.is_valid() then + -- get as MODBUS TCP packet + if s_pkt.protocol() == PROTOCOLS.MODBUS_TCP then + local m_pkt = modbus_packet() + m_pkt.receive(s_pkt.data()) + + pkt = { + scada_frame = s_pkt, + modbus_frame = m_pkt + } + -- get as SCADA management packet + elseif s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then + local body = s_pkt.data() + if #body > 1 then + pkt = { + scada_frame = s_pkt, + type = body[1], + length = #body - 1, + body = { table.unpack(body, 2, 1 + #body) } + } + end + end + end + + return pkt + end + + -- send capability advertisement + local send_advertisement = function (units) + local advertisement = { + type = SCADA_MGMT_TYPES.RTU_ADVERT, + units = {} + } + + for i = 1, #units do + local type = nil + + if units[i].type == "boiler" then + type = RTU_ADVERT_TYPES.BOILER + elseif units[i].type == "turbine" then + type = RTU_ADVERT_TYPES.TURBINE + elseif units[i].type == "imatrix" then + type = RTU_ADVERT_TYPES.IMATRIX + end + + if type ~= nil then + table.insert(advertisement.units, { + type = type, + index = units[i].index, + reactor = units[i].for_reactor + }) + end + end + + _send(advertisement, PROTOCOLS.SCADA_MGMT) + end + + return { + parse_packet = parse_packet + } end -- reactor PLC communications @@ -208,10 +307,11 @@ function rplc_comms(id, modem, local_port, server_port, reactor) s_pkt.recieve(side, sender, reply_to, message, distance) -- get using RPLC protocol format - if self.valid and self.protocol == PROTOCOLS.RPLC then - local body = data() + if s_pkt.is_valid() and s_pkt.protocol() == PROTOCOLS.RPLC then + local body = s_pkt.data() if #body > 2 then pkt = { + scada_frame = s_pkt, id = body[1], type = body[2], length = #body - 2, From 74ae57324b43a1517312f650fb663a3a20009cec Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 23 Mar 2022 15:36:14 -0400 Subject: [PATCH 029/587] redstone I/O rework --- rtu/dev/redstone.lua | 33 +++++---- scada-common/rsio.lua | 152 +++++++++++++++++++++++++++++++++--------- 2 files changed, 141 insertions(+), 44 deletions(-) diff --git a/rtu/dev/redstone.lua b/rtu/dev/redstone.lua index 0ec1283..da2f7e2 100644 --- a/rtu/dev/redstone.lua +++ b/rtu/dev/redstone.lua @@ -1,6 +1,10 @@ -- #REQUIRES rtu.lua +-- #REQUIRES rsio.lua -- note: this RTU makes extensive use of the programming concept of closures +local digital_read = rsio.digital_read +local digital_is_active = rsio.digital_is_active + function redstone_rtu() local self = { rtu = rtu_init() @@ -10,56 +14,57 @@ function redstone_rtu() return self.rtu end - local link_di = function (side, color) + local link_di = function (channel, side, color) local f_read = nil if color then f_read = function () - return rs.testBundledInput(side, color) + return digital_read(rs.testBundledInput(side, color)) end else f_read = function () - return rs.getInput(side) + return digital_read(rs.getInput(side)) end end self.rtu.connect_di(f_read) end - local link_do = function (side, color) + local link_do = function (channel, side, color) local f_read = nil local f_write = nil if color then f_read = function () - return colors.test(rs.getBundledOutput(side), color) + return digital_read(colors.test(rs.getBundledOutput(side), color)) end - f_write = function (value) + f_write = function (level) local output = rs.getBundledOutput(side) + local active = digital_is_active(channel, level) - if value then - colors.combine(output, value) + if active then + colors.combine(output, color) else - colors.subtract(output, value) + colors.subtract(output, color) end rs.setBundledOutput(side, output) end else f_read = function () - return rs.getOutput(side) + return digital_read(rs.getOutput(side)) end - f_write = function (value) - rs.setOutput(side, color) + f_write = function (level) + rs.setOutput(side, digital_is_active(channel, level)) end end self.rtu.connect_coil(f_read, f_write) end - local link_ai = function (side) + local link_ai = function (channel, side) self.rtu.connect_input_reg( function () return rs.getAnalogInput(side) @@ -67,7 +72,7 @@ function redstone_rtu() ) end - local link_ao = function (side) + local link_ao = function (channel, side) self.rtu.connect_holding_reg( function () return rs.getAnalogOutput(side) diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 468a1fa..9dbe9ef 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -3,6 +3,18 @@ IO_LVL = { HIGH = 1 } +IO_DIR = { + IN = 0, + OUT = 1 +} + +IO_MODE = { + DIGITAL_OUT = 0, + DIGITAL_IN = 1, + ANALOG_OUT = 2, + ANALOG_IN = 3 +} + RS_IO = { -- digital inputs -- @@ -41,6 +53,53 @@ RS_IO = { A_T_FLOW_RATE = 21 -- turbine flow rate percentage } +function to_string(channel) + local names = { + "F_SCRAM", + "F_AE2_LIVE", + "R_SCRAM", + "R_ENABLE", + "WASTE_PO", + "WASTE_PU", + "WASTE_AM", + "R_SCRAMMED", + "R_AUTO_SCRAM", + "R_ACTIVE", + "R_AUTO_CTRL", + "R_DMG_CRIT", + "R_HIGH_TEMP", + "R_NO_COOLANT", + "R_EXCESS_HC", + "R_EXCESS_WS", + "R_INSUFF_FUEL", + "R_PLC_TIMEOUT", + "A_R_BURN_RATE", + "A_B_BOIL_RATE", + "A_T_FLOW_RATE" + } + + if channel > 0 and channel <= #names then + return names[channel] + else + return "" + end +end + +function is_valid_channel(channel) + return channel > 0 and channel <= A_T_FLOW_RATE +end + +function is_valid_side(side) + for _, s in pairs(redstone.getSides()) do + if s == side then return true end + end + return false +end + +function is_color(color) + return (color > 0) and (bit.band(color, (color - 1)) == 0); +end + local _TRINARY = function (cond, t, f) if cond then return t else return f end end local _DI_ACTIVE_HIGH = function (level) return level == IO_LVL.HIGH end @@ -48,47 +107,80 @@ local _DI_ACTIVE_LOW = function (level) return level == IO_LVL.LOW end local _DO_ACTIVE_HIGH = function (on) return _TRINARY(on, IO_LVL.HIGH, IO_LVL.LOW) end local _DO_ACTIVE_LOW = function (on) return _TRINARY(on, IO_LVL.LOW, IO_LVL.HIGH) end +-- I/O mappings to I/O function and I/O mode local RS_DIO_MAP = { -- F_SCRAM - _DI_ACTIVE_LOW, + { _f = _DI_ACTIVE_LOW, mode = IO_DIR.IN }, -- F_AE2_LIVE - _DI_ACTIVE_HIGH, + { _f = _DI_ACTIVE_HIGH, mode = IO_DIR.IN }, -- R_SCRAM - _DI_ACTIVE_LOW, + { _f = _DI_ACTIVE_LOW, mode = IO_DIR.IN }, -- R_ENABLE - _DI_ACTIVE_HIGH, + { _f = _DI_ACTIVE_HIGH, mode = IO_DIR.IN }, -- WASTE_PO - _DO_ACTIVE_LOW, + { _f = _DO_ACTIVE_LOW, mode = IO_DIR.OUT }, -- WASTE_PU - _DO_ACTIVE_LOW, + { _f = _DO_ACTIVE_LOW, mode = IO_DIR.OUT }, -- WASTE_AM - _DO_ACTIVE_LOW, + { _f = _DO_ACTIVE_LOW, mode = IO_DIR.OUT }, -- R_SCRAMMED - _DO_ACTIVE_HIGH, + { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_AUTO_SCRAM - _DO_ACTIVE_HIGH, + { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_ACTIVE - _DO_ACTIVE_HIGH, + { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_AUTO_CTRL - _DO_ACTIVE_HIGH, + { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_DMG_CRIT - _DO_ACTIVE_HIGH, + { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_HIGH_TEMP - _DO_ACTIVE_HIGH, + { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_NO_COOLANT - _DO_ACTIVE_HIGH, + { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_EXCESS_HC - _DO_ACTIVE_HIGH, + { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_EXCESS_WS - _DO_ACTIVE_HIGH, + { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_INSUFF_FUEL - _DO_ACTIVE_HIGH, + { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_PLC_TIMEOUT - _DO_ACTIVE_HIGH + { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT } } +function get_io_mode(channel) + local modes = { + IO_MODE.DIGITAL_IN, -- F_SCRAM + IO_MODE.DIGITAL_IN, -- F_AE2_LIVE + IO_MODE.DIGITAL_IN, -- R_SCRAM + IO_MODE.DIGITAL_IN, -- R_ENABLE + IO_MODE.DIGITAL_OUT, -- WASTE_PO + IO_MODE.DIGITAL_OUT, -- WASTE_PU + IO_MODE.DIGITAL_OUT, -- WASTE_AM + IO_MODE.DIGITAL_OUT, -- R_SCRAMMED + IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM + IO_MODE.DIGITAL_OUT, -- R_ACTIVE + IO_MODE.DIGITAL_OUT, -- R_AUTO_CTRL + IO_MODE.DIGITAL_OUT, -- R_DMG_CRIT + IO_MODE.DIGITAL_OUT, -- R_HIGH_TEMP + IO_MODE.DIGITAL_OUT, -- R_NO_COOLANT + IO_MODE.DIGITAL_OUT, -- R_EXCESS_HC + IO_MODE.DIGITAL_OUT, -- R_EXCESS_WS + IO_MODE.DIGITAL_OUT, -- R_INSUFF_FUEL + IO_MODE.DIGITAL_OUT, -- R_PLC_TIMEOUT + IO_MODE.ANALOG_OUT, -- A_R_BURN_RATE + IO_MODE.ANALOG_OUT, -- A_B_BOIL_RATE + IO_MODE.ANALOG_OUT -- A_T_FLOW_RATE + } + + if channel > 0 and channel <= #modes then + return modes[channel] + else + return IO_MODE.ANALOG_IN + end +end + -- get digital IO level reading -function digital_input_read(rs_value) +function digital_read(rs_value) if rs_value then return IO_LVL.HIGH else @@ -96,20 +188,20 @@ function digital_input_read(rs_value) end end --- returns true if the level corresponds to active -function digital_input_is_active(channel, level) - if channel > RS_IO.R_ENABLE then - return false - else - return RS_DIO_MAP[channel](level) - end -end - -- returns the level corresponding to active -function digital_output_write(channel, active) +function digital_write(channel, active) if channel < RS_IO.WASTE_PO or channel > RS_IO.R_PLC_TIMEOUT then return IO_LVL.LOW else - return RS_DIO_MAP[channel](level) + return RS_DIO_MAP[channel]._f(level) + end +end + +-- returns true if the level corresponds to active +function digital_is_active(channel, level) + if channel > RS_IO.R_ENABLE or channel > RS_IO.R_PLC_TIMEOUT then + return false + else + return RS_DIO_MAP[channel]._f(level) end end From 60674ec95c399edc660b75077c5775fb45b3f15a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 23 Mar 2022 15:41:08 -0400 Subject: [PATCH 030/587] RTU startup code and comms --- rtu/config.lua | 33 ++++++++---- rtu/startup.lua | 117 +++++++++++++++++++++++++++++++++++++---- scada-common/comms.lua | 98 ++++++++++++++++++++++++++-------- scada-common/log.lua | 37 +++++++++---- 4 files changed, 233 insertions(+), 52 deletions(-) diff --git a/rtu/config.lua b/rtu/config.lua index 66b2154..b06305e 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -17,15 +17,26 @@ RTU_DEVICES = { RTU_REDSTONE = { { - io = RS_IO.WASTE_PO, - for_reactor = 1 - }, - { - io = RS_IO.WASTE_PU, - for_reactor = 1 - }, - { - io = RS_IO.WASTE_AM, - for_reactor = 1 - }, + for_reactor = 1, + io = { + { + channel = RS_IO.WASTE_PO, + side = "top", + bundled_color = colors.blue, + for_reactor = 1 + }, + { + channel = RS_IO.WASTE_PU, + side = "top", + bundled_color = colors.cyan, + for_reactor = 1 + }, + { + channel = RS_IO.WASTE_AM, + side = "top", + bundled_color = colors.purple, + for_reactor = 1 + } + } + } } diff --git a/rtu/startup.lua b/rtu/startup.lua index dfbad8b..483aaee 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -19,6 +19,13 @@ local RTU_VERSION = "alpha-v0.1.0" local print_ts = util.print_ts +---------------------------------------- +-- startup +---------------------------------------- + +local units = {} +local linked = false + -- mount connected devices ppm.mount_all() @@ -36,8 +43,68 @@ end local rtu_comms = comms.rtu_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) +---------------------------------------- -- determine configuration -local units = {} +---------------------------------------- + +-- redstone interfaces +for reactor_idx = 1, #RTU_REDSTONE do + local rs_rtu = redstone_rtu() + local io_table = RTU_REDSTONE[reactor_idx].io + + local capabilities = {} + + for i = 1, #io_table do + local valid = false + local config = io_table[i] + + -- verify configuration + if is_valid_channel(config.channel) and is_valid_side(config.side) then + if config.bundled_color then + valid = is_color(config.bundled_color) + else + valid = true + end + end + + if ~valid then + local message = "invalid redstone configuration at index " .. i + print_ts(message .. "\n") + log._warning(message) + else + -- link redstone in RTU + local mode = rsio.get_io_mode(config.channel) + if mode == rsio.IO_MODE.DIGITAL_IN then + rs_rtu.link_di(config.channel, config.side, config.bundled_color) + elseif mode == rsio.IO_MODE.DIGITAL_OUT then + rs_rtu.link_do(config.channel, config.side, config.bundled_color) + elseif mode == rsio.IO_MODE.ANALOG_IN then + rs_rtu.link_ai(config.channel, config.side) + elseif mode == rsio.IO_MODE.ANALOG_OUT then + rs_rtu.link_ao(config.channel, config.side) + else + -- should be unreachable code, we already validated channels + log._error("fell through if chain attempting to identify IO mode", true) + break + end + + table.insert(capabilities, config.channel) + + log._debug("startup> linked redstone " .. #capabilities .. ": " .. rsio.to_string(config.channel) .. " (" .. config.side .. + ") for reactor " .. RTU_REDSTONE[reactor_idx].for_reactor) + end + end + + table.insert(units, { + name = "redstone_io", + type = "redstone", + index = 1, + reactor = RTU_REDSTONE[reactor_idx].for_reactor, + device = capabilities, -- use device field for redstone channels + rtu = rs_rtu, + modbus_io = modbus_init(rs_rtu) + }) +end -- mounted peripherals for i = 1, #RTU_DEVICES do @@ -45,7 +112,7 @@ for i = 1, #RTU_DEVICES do if device == nil then local message = "'" .. RTU_DEVICES[i].name .. "' not found" - print_ts(message) + print_ts(message .. "\n") log._warning(message) else local type = ppm.get_type(RTU_DEVICES[i].name) @@ -66,7 +133,7 @@ for i = 1, #RTU_DEVICES do rtu_iface = imatrix_rtu(device) else local message = "device '" .. RTU_DEVICES[i].name .. "' is not a known type (" .. type .. ")" - print_ts(message) + print_ts(message .. "\n") log._warning(message) end @@ -77,16 +144,46 @@ for i = 1, #RTU_DEVICES do index = RTU_DEVICES[i].index, reactor = RTU_DEVICES[i].for_reactor, device = device, - rtu = rtu_iface + rtu = rtu_iface, + modbus_io = modbus_init(rtu_iface) }) + + log._debug("startup> initialized RTU unit #" .. #units .. ": " .. RTU_DEVICES[i].name .. " (" .. rtu_type .. ") [" .. + RTU_DEVICES[i].index .. "] for reactor " .. RTU_DEVICES[i].for_reactor) end end end --- redstone devices -for i = 1, #RTU_REDSTONE do +---------------------------------------- +-- main loop +---------------------------------------- + +-- advertisement/heartbeat clock (every 2 seconds) +local loop_tick = os.startTimer(2) + +-- event loop +while true do + local event, param1, param2, param3, param4, param5 = os.pullEventRaw() + + if event == "peripheral_detach" then + ppm.handle_unmount(param1) + + -- todo: handle unit change + elseif event == "timer" and param1 == loop_tick then + -- period tick, if we are linked send heartbeat, if not send advertisement + if linked then + rtu_comms.send_heartbeat() + else + -- advertise units + rtu_comms.send_advertisement(units) + end + elseif event == "modem_message" then + -- got a packet + + local packet = rtu_comms.parse_packet(p1, p2, p3, p4, p5) + rtu_comms.handle_packet(packet) + + elseif event == "terminate" then + return + end end - --- advertise units -rtu_comms.send_advertisement(units) - diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 6ea907b..dfea4bf 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -17,15 +17,12 @@ RPLC_TYPES = { LINK_REQ = 1, -- linking requests STATUS = 2, -- reactor/system status MEK_STRUCT = 3, -- mekanism build structure - RS_IO_CONNS = 4, -- redstone I/O connections - RS_IO_SET = 5, -- set redstone outputs - RS_IO_GET = 6, -- get redstone inputs - MEK_SCRAM = 7, -- SCRAM reactor - MEK_ENABLE = 8, -- enable reactor - MEK_BURN_RATE = 9, -- set burn rate - ISS_ALARM = 10, -- ISS alarm broadcast - ISS_GET = 11, -- get ISS status - ISS_CLEAR = 12 -- clear ISS trip (if in bad state, will trip immediately) + MEK_SCRAM = 4, -- SCRAM reactor + MEK_ENABLE = 5, -- enable reactor + MEK_BURN_RATE = 6, -- set burn rate + ISS_ALARM = 7, -- ISS alarm broadcast + ISS_GET = 8, -- get ISS status + ISS_CLEAR = 9 -- clear ISS trip (if in bad state, will trip immediately) } RPLC_LINKING = { @@ -44,7 +41,8 @@ SCADA_MGMT_TYPES = { RTU_ADVERT_TYPES = { BOILER = 0, -- boiler TURBINE = 1, -- turbine - IMATRIX = 2 -- induction matrix + IMATRIX = 2, -- induction matrix + REDSTONE = 3 -- redstone I/O } -- generic SCADA packet object @@ -187,13 +185,50 @@ function rtu_comms(modem, local_port, server_port) length = #body - 1, body = { table.unpack(body, 2, 1 + #body) } } + elseif #body == 1 then + pkt = { + scada_frame = s_pkt, + type = body[1], + length = #body - 1, + body = nil + } + else + log._error("Malformed SCADA packet has no length field") end + else + log._error("Illegal packet type " .. s_pkt.protocol(), true) end end return pkt end + local handle_packet = function(packet, units) + if packet ~= nil then + local protocol = packet.scada_frame.protocol() + + if protocol == PROTOCOLS.MODBUS_TCP then + -- MODBUS instruction + if packet.modbus_frame.unit_id <= #units then + local return_code, response = units.modbus_io.handle_packet(packet.modbus_frame) + _send(response, PROTOCOLS.MODBUS_TCP) + + if not return_code then + log._warning("MODBUS operation failed") + end + else + -- unit ID out of range? + log._error("MODBUS packet requesting non-existent unit") + end + elseif protocol == PROTOCOLS.SCADA_MGMT then + -- SCADA management packet + else + -- should be unreachable assuming packet is from parse_packet() + log._error("Illegal packet type " .. protocol, true) + end + end + end + -- send capability advertisement local send_advertisement = function (units) local advertisement = { @@ -210,22 +245,47 @@ function rtu_comms(modem, local_port, server_port) type = RTU_ADVERT_TYPES.TURBINE elseif units[i].type == "imatrix" then type = RTU_ADVERT_TYPES.IMATRIX + elseif units[i].type == "redstone" then + type = RTU_ADVERT_TYPES.REDSTONE end if type ~= nil then - table.insert(advertisement.units, { - type = type, - index = units[i].index, - reactor = units[i].for_reactor - }) + if type == RTU_ADVERT_TYPES.REDSTONE then + table.insert(advertisement.units, { + unit = i, + type = type, + index = units[i].index, + reactor = units[i].for_reactor, + rsio = units[i].device + }) + else + table.insert(advertisement.units, { + unit = i, + type = type, + index = units[i].index, + reactor = units[i].for_reactor, + rsio = nil + }) + end end end _send(advertisement, PROTOCOLS.SCADA_MGMT) end + local send_heartbeat = function () + local heartbeat = { + type = SCADA_MGMT_TYPES.RTU_HEARTBEAT + } + + _send(heartbeat, PROTOCOLS.SCADA_MGMT) + end + return { - parse_packet = parse_packet + parse_packet = parse_packet, + handle_packet = handle_packet, + send_advertisement = send_advertisement, + send_heartbeat = send_heartbeat } end @@ -408,16 +468,12 @@ function rplc_comms(id, modem, local_port, server_port, reactor) _send(sys_status) end - local send_rs_io_conns = function () - end - return { parse_packet = parse_packet, handle_link = handle_link, handle_packet = handle_packet, send_link_req = send_link_req, send_struct = send_struct, - send_status = send_status, - send_rs_io_conns = send_rs_io_conns + send_status = send_status } end diff --git a/scada-common/log.lua b/scada-common/log.lua index 29fc688..3a56919 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -5,6 +5,8 @@ -- we use extra short abbreviations since computer craft screens are very small -- underscores are used since some of these names are used elsewhere (e.g. 'debug' is a lua table) +local LOG_DEBUG = true + local file_handle = fs.open("/log.txt", "a") local _log = function (msg) @@ -14,6 +16,29 @@ local _log = function (msg) end function _debug(msg, trace) + if LOG_DEBUG then + local dbg_info = "" + + if trace then + local name = "" + + if debug.getinfo(2).name ~= nil then + name = ":" .. debug.getinfo(2).name .. "():" + end + + dbg_info = debug.getinfo(2).short_src .. ":" .. name .. + debug.getinfo(2).currentline .. " > " + end + + _log("[DBG] " .. dbg_info .. msg .. "\n") + end +end + +function _warning(msg) + _log("[WRN] " .. msg .. "\n") +end + +function _error(msg, trace) local dbg_info = "" if trace then @@ -27,17 +52,9 @@ function _debug(msg, trace) debug.getinfo(2).currentline .. " > " end - _log("[DBG] " .. dbg_info .. msg) -end - -function _warning(msg) - _log("[WRN] " .. msg) -end - -function _error(msg) - _log("[ERR] " .. msg) + _log("[ERR] " .. dbg_info .. msg .. "\n") end function _fatal(msg) - _log("[FTL] " .. msg) + _log("[FTL] " .. msg .. "\n") end From be73b17d46fd22d3af367dfaf684a7a68d3df029 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 23 Mar 2022 16:17:58 -0400 Subject: [PATCH 031/587] RTU linking and requesting advertisement --- rtu/startup.lua | 8 ++++++-- scada-common/comms.lua | 17 ++++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/rtu/startup.lua b/rtu/startup.lua index 483aaee..ce6adb8 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -179,11 +179,15 @@ while true do end elseif event == "modem_message" then -- got a packet - + local link_ref = { linked = linked } local packet = rtu_comms.parse_packet(p1, p2, p3, p4, p5) - rtu_comms.handle_packet(packet) + rtu_comms.handle_packet(packet, units, link_ref) + + -- if linked, stop sending advertisements + linked = link_ref.linked elseif event == "terminate" then + print_ts("Exiting...\n") return end end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index dfea4bf..bbcedde 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -34,8 +34,9 @@ RPLC_LINKING = { SCADA_MGMT_TYPES = { PING = 0, -- generic ping SV_HEARTBEAT = 1, -- supervisor heartbeat - RTU_HEARTBEAT = 2, -- RTU heartbeat - RTU_ADVERT = 3 -- RTU capability advertisement + REMOTE_LINKED = 2, -- remote device linked + RTU_ADVERT = 3, -- RTU capability advertisement + RTU_HEARTBEAT = 4, -- RTU heartbeat } RTU_ADVERT_TYPES = { @@ -203,7 +204,7 @@ function rtu_comms(modem, local_port, server_port) return pkt end - local handle_packet = function(packet, units) + local handle_packet = function(packet, units, ref) if packet ~= nil then local protocol = packet.scada_frame.protocol() @@ -222,6 +223,16 @@ function rtu_comms(modem, local_port, server_port) end elseif protocol == PROTOCOLS.SCADA_MGMT then -- SCADA management packet + if packet.type == SCADA_MGMT_TYPES.REMOTE_LINKED then + -- acknowledgement + ref.linked = true + elseif packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then + -- request for capabilities again + send_advertisement(units) + else + -- not supported + log._warning("RTU got unexpected SCADA message type " .. packet.type, true) + end else -- should be unreachable assuming packet is from parse_packet() log._error("Illegal packet type " .. protocol, true) From 2ee503946c625364e93857d5d6b371277198518b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 25 Mar 2022 11:50:03 -0400 Subject: [PATCH 032/587] plc cleanup, removed old code --- reactor-plc/signal-router.lua | 159 ---------------------------------- reactor-plc/startup.lua | 4 +- 2 files changed, 2 insertions(+), 161 deletions(-) delete mode 100644 reactor-plc/signal-router.lua diff --git a/reactor-plc/signal-router.lua b/reactor-plc/signal-router.lua deleted file mode 100644 index 37a7610..0000000 --- a/reactor-plc/signal-router.lua +++ /dev/null @@ -1,159 +0,0 @@ --- reactor signal router --- transmits status information and controls enable state - --- bundeled redstone key --- top: --- black (in): insufficent fuel --- brown (in): excess waste --- orange (in): overheat --- red (in): damage critical --- right: --- cyan (out): plutonium/plutonium pellet pipe --- green (out): polonium pipe --- magenta (out): polonium pellet pipe --- purple (out): antimatter pipe --- white (out): reactor enable - --- constants -REACTOR_ID = 1 -DEST_PORT = 1000 - -local state = { - id = REACTOR_ID, - run = false, - no_fuel = false, - full_waste = false, - high_temp = false, - damage_crit = false -} - -local waste_production = "antimatter" - -local listen_port = 1000 + REACTOR_ID -local modem = peripheral.wrap("left") - -print("Reactor Signal Router v1.0") -print("Configured for Reactor #" .. REACTOR_ID) - -if not modem.isOpen(listen_port) then - modem.open(listen_port) -end - --- greeting -modem.transmit(DEST_PORT, listen_port, REACTOR_ID) - --- queue event to read initial state and make sure reactor starts off -os.queueEvent("redstone") -rs.setBundledOutput("right", colors.white) -rs.setBundledOutput("right", 0) -re_eval_output = true - -local connection_timeout = os.startTimer(3) - --- event loop -while true do - local event, param1, param2, param3, param4, param5 = os.pullEvent() - - if event == "redstone" then - -- redstone state change - input = rs.getBundledInput("top") - - if state.no_fuel ~= colors.test(input, colors.black) then - state.no_fuel = colors.test(input, colors.black) - if state.no_fuel then - print("insufficient fuel") - end - end - - if state.full_waste ~= colors.test(input, colors.brown) then - state.full_waste = colors.test(input, colors.brown) - if state.full_waste then - print("waste tank full") - end - end - - if state.high_temp ~= colors.test(input, colors.orange) then - state.high_temp = colors.test(input, colors.orange) - if state.high_temp then - print("high temperature") - end - end - - if state.damage_crit ~= colors.test(input, colors.red) then - state.damage_crit = colors.test(input, colors.red) - if state.damage_crit then - print("damage critical") - end - end - elseif event == "modem_message" then - -- got data, reset timer - if connection_timeout ~= nil then - os.cancelTimer(connection_timeout) - end - connection_timeout = os.startTimer(3) - - if type(param4) == "number" and param4 == 0 then - print("[info] controller server startup detected") - modem.transmit(DEST_PORT, listen_port, REACTOR_ID) - elseif type(param4) == "number" and param4 == 1 then - -- keep-alive, do nothing, just had to reset timer - elseif type(param4) == "boolean" then - state.run = param4 - - if state.run then - print("[alert] reactor enabled") - else - print("[alert] reactor disabled") - end - - re_eval_output = true - elseif type(param4) == "string" then - if param4 == "plutonium" then - print("[alert] switching to plutonium production") - waste_production = param4 - re_eval_output = true - elseif param4 == "polonium" then - print("[alert] switching to polonium production") - waste_production = param4 - re_eval_output = true - elseif param4 == "antimatter" then - print("[alert] switching to antimatter production") - waste_production = param4 - re_eval_output = true - end - else - print("[error] got unknown packet (" .. param4 .. ")") - end - elseif event == "timer" and param1 == connection_timeout then - -- haven't heard from server in 3 seconds? shutdown - -- timer won't be restarted until next packet, so no need to do anything with it - print("[alert] server timeout, reactor disabled") - state.run = false - re_eval_output = true - end - - -- check for control state changes - if re_eval_output then - re_eval_output = false - - local run_color = 0 - if state.run then - run_color = colors.white - end - - -- values are swapped, as on disables and off enables - local waste_color - if waste_production == "plutonium" then - waste_color = colors.green - elseif waste_production == "polonium" then - waste_color = colors.cyan + colors.purple - else - -- antimatter (default) - waste_color = colors.cyan + colors.magenta - end - - rs.setBundledOutput("right", run_color + waste_color) - end - - modem.transmit(DEST_PORT, listen_port, state) -end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 3f1c29a..11a9982 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -95,14 +95,14 @@ while true do end elseif event == "modem_message" then -- got a packet - -- feed the watchdog first so it doesn't uhh,,,eat our packets + -- feed the watchdog first so it doesn't uhh...eat our packets conn_watchdog.feed() local packet = plc_comms.parse_packet(p1, p2, p3, p4, p5) plc_comms.handle_packet(packet) elseif event == "timer" and param1 == conn_watchdog.get_timer() then - -- haven't heard from server recently? shutdown + -- haven't heard from server recently? shutdown reactor iss.trip_timeout() print_ts("[alert] server timeout, reactor disabled\n") elseif event == "terminate" then From 5eaeb50000fa67567dfd7cb7cebe1f400d9ee6dd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 25 Mar 2022 12:17:46 -0400 Subject: [PATCH 033/587] broke up comms file, setup base coordinator code --- coordinator/coordinator.lua | 8 + coordinator/scada-coordinator.lua | 0 coordinator/startup.lua | 27 +++ reactor-plc/plc.lua | 191 +++++++++++++++ reactor-plc/startup.lua | 2 +- rtu/rtu.lua | 165 +++++++++++++ rtu/startup.lua | 2 +- scada-common/comms.lua | 374 ------------------------------ 8 files changed, 393 insertions(+), 376 deletions(-) create mode 100644 coordinator/coordinator.lua delete mode 100644 coordinator/scada-coordinator.lua create mode 100644 coordinator/startup.lua diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua new file mode 100644 index 0000000..96d766e --- /dev/null +++ b/coordinator/coordinator.lua @@ -0,0 +1,8 @@ +-- #REQUIRES comms.lua + +-- coordinator communications +function coord_comms() + local self = { + reactor_struct_cache = nil + } +end diff --git a/coordinator/scada-coordinator.lua b/coordinator/scada-coordinator.lua deleted file mode 100644 index e69de29..0000000 diff --git a/coordinator/startup.lua b/coordinator/startup.lua new file mode 100644 index 0000000..20be7a3 --- /dev/null +++ b/coordinator/startup.lua @@ -0,0 +1,27 @@ +-- +-- Nuclear Generation Facility SCADA Coordinator +-- + +os.loadAPI("scada-common/log.lua") +os.loadAPI("scada-common/util.lua") +os.loadAPI("scada-common/ppm.lua") +os.loadAPI("scada-common/comms.lua") + +os.loadAPI("coordinator/config.lua") +os.loadAPI("coordinator/coordinator.lua") + +local COORDINATOR_VERSION = "alpha-v0.1.0" + +local print_ts = util.print_ts + +ppm.mount_all() + +local modem = ppm.get_device("modem") + +print("| SCADA Coordinator - " .. COORDINATOR_VERSION .. " |") + +-- we need a modem +if modem == nil then + print("Please connect a modem.") + return +end diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index decfd0b..675bc70 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -1,3 +1,5 @@ +-- #REQUIRES comms.lua + function scada_link(plc_comms) local linked = false local link_timeout = os.startTimer(5) @@ -169,3 +171,192 @@ function iss_init(reactor) timed_out = timed_out } end + +-- reactor PLC communications +function rplc_comms(id, modem, local_port, server_port, reactor) + local self = { + id = id, + seq_num = 0, + modem = modem, + s_port = server_port, + l_port = local_port, + reactor = reactor, + status_cache = nil + } + + -- PRIVATE FUNCTIONS -- + + local _send = function (msg) + local packet = scada_packet() + packet.make(self.seq_num, PROTOCOLS.RPLC, msg) + self.modem.transmit(self.s_port, self.l_port, packet.raw()) + self.seq_num = self.seq_num + 1 + end + + -- variable reactor status information, excluding heating rate + local _reactor_status = function () + return { + status = self.reactor.getStatus(), + burn_rate = self.reactor.getBurnRate(), + act_burn_r = self.reactor.getActualBurnRate(), + temp = self.reactor.getTemperature(), + damage = self.reactor.getDamagePercent(), + boil_eff = self.reactor.getBoilEfficiency(), + env_loss = self.reactor.getEnvironmentalLoss(), + + fuel = self.reactor.getFuel(), + fuel_need = self.reactor.getFuelNeeded(), + fuel_fill = self.reactor.getFuelFilledPercentage(), + waste = self.reactor.getWaste(), + waste_need = self.reactor.getWasteNeeded(), + waste_fill = self.reactor.getWasteFilledPercentage(), + cool_type = self.reactor.getCoolant()['name'], + cool_amnt = self.reactor.getCoolant()['amount'], + cool_need = self.reactor.getCoolantNeeded(), + cool_fill = self.reactor.getCoolantFilledPercentage(), + hcool_type = self.reactor.getHeatedCoolant()['name'], + hcool_amnt = self.reactor.getHeatedCoolant()['amount'], + hcool_need = self.reactor.getHeatedCoolantNeeded(), + hcool_fill = self.reactor.getHeatedCoolantFilledPercentage() + } + end + + local _update_status_cache = function () + local status = _reactor_status() + local changed = false + + for key, value in pairs(status) do + if value ~= self.status_cache[key] then + changed = true + break + end + end + + if changed then + self.status_cache = status + end + + return changed + end + + -- PUBLIC FUNCTIONS -- + + -- parse an RPLC packet + local parse_packet = function(side, sender, reply_to, message, distance) + local pkt = nil + local s_pkt = scada_packet() + + -- parse packet as generic SCADA packet + s_pkt.recieve(side, sender, reply_to, message, distance) + + -- get using RPLC protocol format + if s_pkt.is_valid() and s_pkt.protocol() == PROTOCOLS.RPLC then + local body = s_pkt.data() + if #body > 2 then + pkt = { + scada_frame = s_pkt, + id = body[1], + type = body[2], + length = #body - 2, + body = { table.unpack(body, 3, 2 + #body) } + } + end + end + + return pkt + end + + -- handle a linking packet + local handle_link = function (packet) + if packet.type == RPLC_TYPES.LINK_REQ then + return packet.data[1] == RPLC_LINKING.ALLOW + else + return nil + end + end + + -- handle an RPLC packet + local handle_packet = function (packet) + if packet.type == RPLC_TYPES.KEEP_ALIVE then + -- keep alive request received, nothing to do except feed watchdog + elseif packet.type == RPLC_TYPES.MEK_STRUCT then + -- request for physical structure + send_struct() + elseif packet.type == RPLC_TYPES.RS_IO_CONNS then + -- request for redstone connections + send_rs_io_conns() + elseif packet.type == RPLC_TYPES.RS_IO_GET then + elseif packet.type == RPLC_TYPES.RS_IO_SET then + elseif packet.type == RPLC_TYPES.MEK_SCRAM then + elseif packet.type == RPLC_TYPES.MEK_ENABLE then + elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then + elseif packet.type == RPLC_TYPES.ISS_GET then + elseif packet.type == RPLC_TYPES.ISS_CLEAR then + end + end + + -- attempt to establish link with supervisor + local send_link_req = function () + local linking_data = { + id = self.id, + type = RPLC_TYPES.LINK_REQ + } + + _send(linking_data) + end + + -- send structure properties (these should not change) + -- (server will cache these) + local send_struct = function () + local mek_data = { + heat_cap = self.reactor.getHeatCapacity(), + fuel_asm = self.reactor.getFuelAssemblies(), + fuel_sa = self.reactor.getFuelSurfaceArea(), + fuel_cap = self.reactor.getFuelCapacity(), + waste_cap = self.reactor.getWasteCapacity(), + cool_cap = self.reactor.getCoolantCapacity(), + hcool_cap = self.reactor.getHeatedCoolantCapacity(), + max_burn = self.reactor.getMaxBurnRate() + } + + local struct_packet = { + id = self.id, + type = RPLC_TYPES.MEK_STRUCT, + mek_data = mek_data + } + + _send(struct_packet) + end + + -- send live status information + -- control_state : acknowledged control state from supervisor + -- overridden : if ISS force disabled reactor + local send_status = function (control_state, overridden) + local mek_data = nil + + if _update_status_cache() then + mek_data = self.status_cache + end + + local sys_status = { + id = self.id, + type = RPLC_TYPES.STATUS, + timestamp = os.time(), + control_state = control_state, + overridden = overridden, + heating_rate = self.reactor.getHeatingRate(), + mek_data = mek_data + } + + _send(sys_status) + end + + return { + parse_packet = parse_packet, + handle_link = handle_link, + handle_packet = handle_packet, + send_link_req = send_link_req, + send_struct = send_struct, + send_status = send_status + } +end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 11a9982..6875bcb 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -42,7 +42,7 @@ if not modem.isOpen(config.LISTEN_PORT) then modem.open(config.LISTEN_PORT) end -local plc_comms = comms.rplc_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) +local plc_comms = plc.rplc_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) -- attempt server connection -- exit application if connection is denied diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 32600a1..e682787 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -1,3 +1,6 @@ +-- #REQUIRES comms.lua +-- #REQUIRES modbus.lua + function rtu_init() local self = { discrete_inputs = {}, @@ -85,3 +88,165 @@ function rtu_init() write_holding_reg = write_holding_reg } end + +function rtu_comms(modem, local_port, server_port) + local self = { + seq_num = 0, + txn_id = 0, + modem = modem, + s_port = server_port, + l_port = local_port + } + + -- PRIVATE FUNCTIONS -- + + local _send = function (protocol, msg) + local packet = scada_packet() + packet.make(self.seq_num, protocol, msg) + self.modem.transmit(self.s_port, self.l_port, packet.raw()) + self.seq_num = self.seq_num + 1 + end + + -- PUBLIC FUNCTIONS -- + + -- parse a MODBUS/SCADA packet + local parse_packet = function(side, sender, reply_to, message, distance) + local pkt = nil + local s_pkt = scada_packet() + + -- parse packet as generic SCADA packet + s_pkt.recieve(side, sender, reply_to, message, distance) + + if s_pkt.is_valid() then + -- get as MODBUS TCP packet + if s_pkt.protocol() == PROTOCOLS.MODBUS_TCP then + local m_pkt = modbus_packet() + m_pkt.receive(s_pkt.data()) + + pkt = { + scada_frame = s_pkt, + modbus_frame = m_pkt + } + -- get as SCADA management packet + elseif s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then + local body = s_pkt.data() + if #body > 1 then + pkt = { + scada_frame = s_pkt, + type = body[1], + length = #body - 1, + body = { table.unpack(body, 2, 1 + #body) } + } + elseif #body == 1 then + pkt = { + scada_frame = s_pkt, + type = body[1], + length = #body - 1, + body = nil + } + else + log._error("Malformed SCADA packet has no length field") + end + else + log._error("Illegal packet type " .. s_pkt.protocol(), true) + end + end + + return pkt + end + + local handle_packet = function(packet, units, ref) + if packet ~= nil then + local protocol = packet.scada_frame.protocol() + + if protocol == PROTOCOLS.MODBUS_TCP then + -- MODBUS instruction + if packet.modbus_frame.unit_id <= #units then + local return_code, response = units.modbus_io.handle_packet(packet.modbus_frame) + _send(response, PROTOCOLS.MODBUS_TCP) + + if not return_code then + log._warning("MODBUS operation failed") + end + else + -- unit ID out of range? + log._error("MODBUS packet requesting non-existent unit") + end + elseif protocol == PROTOCOLS.SCADA_MGMT then + -- SCADA management packet + if packet.type == SCADA_MGMT_TYPES.REMOTE_LINKED then + -- acknowledgement + ref.linked = true + elseif packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then + -- request for capabilities again + send_advertisement(units) + else + -- not supported + log._warning("RTU got unexpected SCADA message type " .. packet.type, true) + end + else + -- should be unreachable assuming packet is from parse_packet() + log._error("Illegal packet type " .. protocol, true) + end + end + end + + -- send capability advertisement + local send_advertisement = function (units) + local advertisement = { + type = SCADA_MGMT_TYPES.RTU_ADVERT, + units = {} + } + + for i = 1, #units do + local type = nil + + if units[i].type == "boiler" then + type = RTU_ADVERT_TYPES.BOILER + elseif units[i].type == "turbine" then + type = RTU_ADVERT_TYPES.TURBINE + elseif units[i].type == "imatrix" then + type = RTU_ADVERT_TYPES.IMATRIX + elseif units[i].type == "redstone" then + type = RTU_ADVERT_TYPES.REDSTONE + end + + if type ~= nil then + if type == RTU_ADVERT_TYPES.REDSTONE then + table.insert(advertisement.units, { + unit = i, + type = type, + index = units[i].index, + reactor = units[i].for_reactor, + rsio = units[i].device + }) + else + table.insert(advertisement.units, { + unit = i, + type = type, + index = units[i].index, + reactor = units[i].for_reactor, + rsio = nil + }) + end + end + end + + _send(advertisement, PROTOCOLS.SCADA_MGMT) + end + + local send_heartbeat = function () + local heartbeat = { + type = SCADA_MGMT_TYPES.RTU_HEARTBEAT + } + + _send(heartbeat, PROTOCOLS.SCADA_MGMT) + end + + return { + parse_packet = parse_packet, + handle_packet = handle_packet, + send_advertisement = send_advertisement, + send_heartbeat = send_heartbeat + } +end diff --git a/rtu/startup.lua b/rtu/startup.lua index ce6adb8..b287a53 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -41,7 +41,7 @@ if not modem.isOpen(config.LISTEN_PORT) then modem.open(config.LISTEN_PORT) end -local rtu_comms = comms.rtu_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) +local rtu_comms = rtu.rtu_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) ---------------------------------------- -- determine configuration diff --git a/scada-common/comms.lua b/scada-common/comms.lua index bbcedde..f760352 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -1,5 +1,3 @@ --- #REQUIRES modbus.lua - PROTOCOLS = { MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol RPLC = 1, -- reactor PLC protocol @@ -116,375 +114,3 @@ function scada_packet() data = data } end - --- coordinator communications -function coord_comms() - local self = { - reactor_struct_cache = nil - } -end - --- supervisory controller communications -function superv_comms(mode, num_reactors, modem, dev_listen, fo_channel, sv_channel) - local self = { - mode = mode, - seq_num = 0, - num_reactors = num_reactors, - modem = modem, - dev_listen = dev_listen, - fo_channel = fo_channel, - sv_channel = sv_channel, - reactor_struct_cache = nil - } -end - -function rtu_comms(modem, local_port, server_port) - local self = { - seq_num = 0, - txn_id = 0, - modem = modem, - s_port = server_port, - l_port = local_port - } - - -- PRIVATE FUNCTIONS -- - - local _send = function (protocol, msg) - local packet = scada_packet() - packet.make(self.seq_num, protocol, msg) - self.modem.transmit(self.s_port, self.l_port, packet.raw()) - self.seq_num = self.seq_num + 1 - end - - -- PUBLIC FUNCTIONS -- - - -- parse a MODBUS/SCADA packet - local parse_packet = function(side, sender, reply_to, message, distance) - local pkt = nil - local s_pkt = scada_packet() - - -- parse packet as generic SCADA packet - s_pkt.recieve(side, sender, reply_to, message, distance) - - if s_pkt.is_valid() then - -- get as MODBUS TCP packet - if s_pkt.protocol() == PROTOCOLS.MODBUS_TCP then - local m_pkt = modbus_packet() - m_pkt.receive(s_pkt.data()) - - pkt = { - scada_frame = s_pkt, - modbus_frame = m_pkt - } - -- get as SCADA management packet - elseif s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then - local body = s_pkt.data() - if #body > 1 then - pkt = { - scada_frame = s_pkt, - type = body[1], - length = #body - 1, - body = { table.unpack(body, 2, 1 + #body) } - } - elseif #body == 1 then - pkt = { - scada_frame = s_pkt, - type = body[1], - length = #body - 1, - body = nil - } - else - log._error("Malformed SCADA packet has no length field") - end - else - log._error("Illegal packet type " .. s_pkt.protocol(), true) - end - end - - return pkt - end - - local handle_packet = function(packet, units, ref) - if packet ~= nil then - local protocol = packet.scada_frame.protocol() - - if protocol == PROTOCOLS.MODBUS_TCP then - -- MODBUS instruction - if packet.modbus_frame.unit_id <= #units then - local return_code, response = units.modbus_io.handle_packet(packet.modbus_frame) - _send(response, PROTOCOLS.MODBUS_TCP) - - if not return_code then - log._warning("MODBUS operation failed") - end - else - -- unit ID out of range? - log._error("MODBUS packet requesting non-existent unit") - end - elseif protocol == PROTOCOLS.SCADA_MGMT then - -- SCADA management packet - if packet.type == SCADA_MGMT_TYPES.REMOTE_LINKED then - -- acknowledgement - ref.linked = true - elseif packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then - -- request for capabilities again - send_advertisement(units) - else - -- not supported - log._warning("RTU got unexpected SCADA message type " .. packet.type, true) - end - else - -- should be unreachable assuming packet is from parse_packet() - log._error("Illegal packet type " .. protocol, true) - end - end - end - - -- send capability advertisement - local send_advertisement = function (units) - local advertisement = { - type = SCADA_MGMT_TYPES.RTU_ADVERT, - units = {} - } - - for i = 1, #units do - local type = nil - - if units[i].type == "boiler" then - type = RTU_ADVERT_TYPES.BOILER - elseif units[i].type == "turbine" then - type = RTU_ADVERT_TYPES.TURBINE - elseif units[i].type == "imatrix" then - type = RTU_ADVERT_TYPES.IMATRIX - elseif units[i].type == "redstone" then - type = RTU_ADVERT_TYPES.REDSTONE - end - - if type ~= nil then - if type == RTU_ADVERT_TYPES.REDSTONE then - table.insert(advertisement.units, { - unit = i, - type = type, - index = units[i].index, - reactor = units[i].for_reactor, - rsio = units[i].device - }) - else - table.insert(advertisement.units, { - unit = i, - type = type, - index = units[i].index, - reactor = units[i].for_reactor, - rsio = nil - }) - end - end - end - - _send(advertisement, PROTOCOLS.SCADA_MGMT) - end - - local send_heartbeat = function () - local heartbeat = { - type = SCADA_MGMT_TYPES.RTU_HEARTBEAT - } - - _send(heartbeat, PROTOCOLS.SCADA_MGMT) - end - - return { - parse_packet = parse_packet, - handle_packet = handle_packet, - send_advertisement = send_advertisement, - send_heartbeat = send_heartbeat - } -end - --- reactor PLC communications -function rplc_comms(id, modem, local_port, server_port, reactor) - local self = { - id = id, - seq_num = 0, - modem = modem, - s_port = server_port, - l_port = local_port, - reactor = reactor, - status_cache = nil - } - - -- PRIVATE FUNCTIONS -- - - local _send = function (msg) - local packet = scada_packet() - packet.make(self.seq_num, PROTOCOLS.RPLC, msg) - self.modem.transmit(self.s_port, self.l_port, packet.raw()) - self.seq_num = self.seq_num + 1 - end - - -- variable reactor status information, excluding heating rate - local _reactor_status = function () - return { - status = self.reactor.getStatus(), - burn_rate = self.reactor.getBurnRate(), - act_burn_r = self.reactor.getActualBurnRate(), - temp = self.reactor.getTemperature(), - damage = self.reactor.getDamagePercent(), - boil_eff = self.reactor.getBoilEfficiency(), - env_loss = self.reactor.getEnvironmentalLoss(), - - fuel = self.reactor.getFuel(), - fuel_need = self.reactor.getFuelNeeded(), - fuel_fill = self.reactor.getFuelFilledPercentage(), - waste = self.reactor.getWaste(), - waste_need = self.reactor.getWasteNeeded(), - waste_fill = self.reactor.getWasteFilledPercentage(), - cool_type = self.reactor.getCoolant()['name'], - cool_amnt = self.reactor.getCoolant()['amount'], - cool_need = self.reactor.getCoolantNeeded(), - cool_fill = self.reactor.getCoolantFilledPercentage(), - hcool_type = self.reactor.getHeatedCoolant()['name'], - hcool_amnt = self.reactor.getHeatedCoolant()['amount'], - hcool_need = self.reactor.getHeatedCoolantNeeded(), - hcool_fill = self.reactor.getHeatedCoolantFilledPercentage() - } - end - - local _update_status_cache = function () - local status = _reactor_status() - local changed = false - - for key, value in pairs(status) do - if value ~= self.status_cache[key] then - changed = true - break - end - end - - if changed then - self.status_cache = status - end - - return changed - end - - -- PUBLIC FUNCTIONS -- - - -- parse an RPLC packet - local parse_packet = function(side, sender, reply_to, message, distance) - local pkt = nil - local s_pkt = scada_packet() - - -- parse packet as generic SCADA packet - s_pkt.recieve(side, sender, reply_to, message, distance) - - -- get using RPLC protocol format - if s_pkt.is_valid() and s_pkt.protocol() == PROTOCOLS.RPLC then - local body = s_pkt.data() - if #body > 2 then - pkt = { - scada_frame = s_pkt, - id = body[1], - type = body[2], - length = #body - 2, - body = { table.unpack(body, 3, 2 + #body) } - } - end - end - - return pkt - end - - -- handle a linking packet - local handle_link = function (packet) - if packet.type == RPLC_TYPES.LINK_REQ then - return packet.data[1] == RPLC_LINKING.ALLOW - else - return nil - end - end - - -- handle an RPLC packet - local handle_packet = function (packet) - if packet.type == RPLC_TYPES.KEEP_ALIVE then - -- keep alive request received, nothing to do except feed watchdog - elseif packet.type == RPLC_TYPES.MEK_STRUCT then - -- request for physical structure - send_struct() - elseif packet.type == RPLC_TYPES.RS_IO_CONNS then - -- request for redstone connections - send_rs_io_conns() - elseif packet.type == RPLC_TYPES.RS_IO_GET then - elseif packet.type == RPLC_TYPES.RS_IO_SET then - elseif packet.type == RPLC_TYPES.MEK_SCRAM then - elseif packet.type == RPLC_TYPES.MEK_ENABLE then - elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then - elseif packet.type == RPLC_TYPES.ISS_GET then - elseif packet.type == RPLC_TYPES.ISS_CLEAR then - end - end - - -- attempt to establish link with supervisor - local send_link_req = function () - local linking_data = { - id = self.id, - type = RPLC_TYPES.LINK_REQ - } - - _send(linking_data) - end - - -- send structure properties (these should not change) - -- (server will cache these) - local send_struct = function () - local mek_data = { - heat_cap = self.reactor.getHeatCapacity(), - fuel_asm = self.reactor.getFuelAssemblies(), - fuel_sa = self.reactor.getFuelSurfaceArea(), - fuel_cap = self.reactor.getFuelCapacity(), - waste_cap = self.reactor.getWasteCapacity(), - cool_cap = self.reactor.getCoolantCapacity(), - hcool_cap = self.reactor.getHeatedCoolantCapacity(), - max_burn = self.reactor.getMaxBurnRate() - } - - local struct_packet = { - id = self.id, - type = RPLC_TYPES.MEK_STRUCT, - mek_data = mek_data - } - - _send(struct_packet) - end - - -- send live status information - -- control_state : acknowledged control state from supervisor - -- overridden : if ISS force disabled reactor - local send_status = function (control_state, overridden) - local mek_data = nil - - if _update_status_cache() then - mek_data = self.status_cache - end - - local sys_status = { - id = self.id, - type = RPLC_TYPES.STATUS, - timestamp = os.time(), - control_state = control_state, - overridden = overridden, - heating_rate = self.reactor.getHeatingRate(), - mek_data = mek_data - } - - _send(sys_status) - end - - return { - parse_packet = parse_packet, - handle_link = handle_link, - handle_packet = handle_packet, - send_link_req = send_link_req, - send_struct = send_struct, - send_status = send_status - } -end From 013656bc4d324aeff9b7af445e0aac2ff6335bf2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 25 Mar 2022 12:18:33 -0400 Subject: [PATCH 034/587] supervisor code moved around --- supervisor/scada-supervisor.lua | 55 --------------------------- supervisor/startup.lua | 67 ++++++++++++++++++++++++++++++++- supervisor/supervisor.lua | 15 ++++++++ 3 files changed, 80 insertions(+), 57 deletions(-) delete mode 100644 supervisor/scada-supervisor.lua create mode 100644 supervisor/supervisor.lua diff --git a/supervisor/scada-supervisor.lua b/supervisor/scada-supervisor.lua deleted file mode 100644 index 28a7e40..0000000 --- a/supervisor/scada-supervisor.lua +++ /dev/null @@ -1,55 +0,0 @@ --- --- Nuclear Generation Facility SCADA Supervisor --- - -os.loadAPI("scada-common/util.lua") -os.loadAPI("scada-common/comms.lua") -os.loadAPI("supervisor/config.lua") - -local SUPERVISOR_VERSION = "alpha-v0.1" - -local print_ts = util.print_ts - -local modem = peripheral.find("modem") - -print("| SCADA Supervisor - " .. SUPERVISOR_VERSION .. " |") - --- we need a modem -if modem == nil then - print("No modem found, exiting...") - return -end - --- determine active/backup mode -local mode = comms.SCADA_SV_MODES.BACKUP -if config.SYSTEM_TYPE == "active" then - mode = comms.SCADA_SV_MODES.ACTIVE -end - --- start comms, open all channels -if not modem.isOpen(config.SCADA_DEV_LISTEN) then - modem.open(config.SCADA_DEV_LISTEN) -end -if not modem.isOpen(config.SCADA_FO_CHANNEL) then - modem.open(config.SCADA_FO_CHANNEL) -end -if not modem.isOpen(config.SCADA_SV_CHANNEL) then - modem.open(config.SCADA_SV_CHANNEL) -end - -local comms = comms.superv_comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_FO_CHANNEL, config.SCADA_SV_CHANNEL) - --- base loop clock (4Hz, 5 ticks) -local loop_tick = os.startTimer(0.25) - --- event loop -while true do - local event, param1, param2, param3, param4, param5 = os.pullEvent() - - -- handle event - if event == "timer" and param1 == loop_tick then - -- basic event tick, send keep-alives - elseif event == "modem_message" then - -- got a packet - end -end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 2ee4d40..4ff56a9 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -1,3 +1,66 @@ -- --- Multi-Reactor Controller Server & GUI --- \ No newline at end of file +-- Nuclear Generation Facility SCADA Supervisor +-- + +os.loadAPI("scada-common/log.lua") +os.loadAPI("scada-common/util.lua") +os.loadAPI("scada-common/ppm.lua") +os.loadAPI("scada-common/comms.lua") + +os.loadAPI("supervisor/config.lua") +os.loadAPI("supervisor/supervisor.lua") + +local SUPERVISOR_VERSION = "alpha-v0.1.0" + +local print_ts = util.print_ts + +ppm.mount_all() + +local modem = ppm.get_device("modem") + +print("| SCADA Supervisor - " .. SUPERVISOR_VERSION .. " |") + +-- we need a modem +if modem == nil then + print("Please connect a modem.") + return +end + +-- determine active/backup mode +local mode = comms.SCADA_SV_MODES.BACKUP +if config.SYSTEM_TYPE == "active" then + mode = comms.SCADA_SV_MODES.ACTIVE +end + +-- start comms, open all channels +if not modem.isOpen(config.SCADA_DEV_LISTEN) then + modem.open(config.SCADA_DEV_LISTEN) +end +if not modem.isOpen(config.SCADA_FO_CHANNEL) then + modem.open(config.SCADA_FO_CHANNEL) +end +if not modem.isOpen(config.SCADA_SV_CHANNEL) then + modem.open(config.SCADA_SV_CHANNEL) +end + +local comms = supervisor.superv_comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_FO_CHANNEL, config.SCADA_SV_CHANNEL) + +-- base loop clock (4Hz, 5 ticks) +local loop_tick = os.startTimer(0.25) + +-- event loop +while true do + local event, param1, param2, param3, param4, param5 = os.pullEventRaw() + + -- handle event + if event == "timer" and param1 == loop_tick then + -- basic event tick, send keep-alives + elseif event == "modem_message" then + -- got a packet + elseif event == "terminate" then + -- safe exit + print_ts("[alert] terminated\n") + -- todo: attempt failover, alert hot backup + return + end +end diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua new file mode 100644 index 0000000..5f988a8 --- /dev/null +++ b/supervisor/supervisor.lua @@ -0,0 +1,15 @@ +-- #REQUIRES comms.lua + +-- supervisory controller communications +function superv_comms(mode, num_reactors, modem, dev_listen, fo_channel, sv_channel) + local self = { + mode = mode, + seq_num = 0, + num_reactors = num_reactors, + modem = modem, + dev_listen = dev_listen, + fo_channel = fo_channel, + sv_channel = sv_channel, + reactor_struct_cache = nil + } +end From 36fb4587a15d81ffc87ad55a4c8b5fd3654d6c56 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 2 Apr 2022 08:28:43 -0400 Subject: [PATCH 035/587] consistent packet constructors/receiving --- reactor-plc/plc.lua | 98 ++++++++++++++++++++++++++++++++++++----- rtu/rtu.lua | 38 +++++----------- scada-common/comms.lua | 64 +++++++++++++++++++++++++++ scada-common/modbus.lua | 29 ++++++++---- 4 files changed, 182 insertions(+), 47 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 675bc70..8212875 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -172,6 +172,78 @@ function iss_init(reactor) } end +function rplc_packet() + local self = { + frame = nil, + id = nil, + type = nil, + length = nil, + body = nil + } + + local _rplc_type_valid = function () + return self.type == RPLC_TYPES.KEEP_ALIVE or + self.type == RPLC_TYPES.LINK_REQ or + self.type == RPLC_TYPES.STATUS or + self.type == RPLC_TYPES.MEK_STRUCT or + self.type == RPLC_TYPES.MEK_SCRAM or + self.type == RPLC_TYPES.MEK_ENABLE or + self.type == RPLC_TYPES.MEK_BURN_RATE or + self.type == RPLC_TYPES.ISS_ALARM or + self.type == RPLC_TYPES.ISS_GET or + self.type == RPLC_TYPES.ISS_CLEAR + end + + -- make an RPLC packet + local make = function (id, packet_type, length, data) + self.id = id + self.type = packet_type + self.length = length + self.data = data + end + + -- decode an RPLC packet from a SCADA frame + local decode = function (frame) + if frame then + self.frame = frame + + if frame.protocol() == comms.PROTOCOLS.RPLC then + local data = frame.data() + local ok = #data > 2 + + if ok then + make(data[1], data[2], data[3], { table.unpack(data, 4, #data) }) + ok = _rplc_type_valid() + end + + return ok + else + log._debug("attempted RPLC parse of incorrect protocol " .. frame.protocol(), true) + return false + end + else + log._debug("nil frame encountered", true) + return false + end + end + + local get = function () + return { + scada_frame = self.frame, + id = self.id, + type = self.type, + length = self.length, + data = self.data + } + end + + return { + make = make, + decode = decode, + get = get + } +end + -- reactor PLC communications function rplc_comms(id, modem, local_port, server_port, reactor) local self = { @@ -249,17 +321,21 @@ function rplc_comms(id, modem, local_port, server_port, reactor) -- parse packet as generic SCADA packet s_pkt.recieve(side, sender, reply_to, message, distance) - -- get using RPLC protocol format - if s_pkt.is_valid() and s_pkt.protocol() == PROTOCOLS.RPLC then - local body = s_pkt.data() - if #body > 2 then - pkt = { - scada_frame = s_pkt, - id = body[1], - type = body[2], - length = #body - 2, - body = { table.unpack(body, 3, 2 + #body) } - } + if s_pkt.is_valid() then + -- get as RPLC packet + if s_pkt.protocol() == PROTOCOLS.RPLC then + local rplc_pkt = rplc_packet() + if rplc_pkt.decode(s_pkt) then + pkt = rplc_pkt.get() + end + -- get as SCADA management packet + elseif s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then + local mgmt_pkt = mgmt_packet() + if mgmt_pkt.decode(s_pkt) then + pkt = mgmt_packet.get() + end + else + log._error("illegal packet type " .. s_pkt.protocol(), true) end end diff --git a/rtu/rtu.lua b/rtu/rtu.lua index e682787..a80e3f1 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -121,34 +121,17 @@ function rtu_comms(modem, local_port, server_port) -- get as MODBUS TCP packet if s_pkt.protocol() == PROTOCOLS.MODBUS_TCP then local m_pkt = modbus_packet() - m_pkt.receive(s_pkt.data()) - - pkt = { - scada_frame = s_pkt, - modbus_frame = m_pkt - } + if m_pkt.decode(s_pkt) then + pkt = m_pkt.get() + end -- get as SCADA management packet elseif s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then - local body = s_pkt.data() - if #body > 1 then - pkt = { - scada_frame = s_pkt, - type = body[1], - length = #body - 1, - body = { table.unpack(body, 2, 1 + #body) } - } - elseif #body == 1 then - pkt = { - scada_frame = s_pkt, - type = body[1], - length = #body - 1, - body = nil - } - else - log._error("Malformed SCADA packet has no length field") + local mgmt_pkt = mgmt_packet() + if mgmt_pkt.decode(s_pkt) then + pkt = mgmt_packet.get() end else - log._error("Illegal packet type " .. s_pkt.protocol(), true) + log._error("illegal packet type " .. s_pkt.protocol(), true) end end @@ -161,8 +144,9 @@ function rtu_comms(modem, local_port, server_port) if protocol == PROTOCOLS.MODBUS_TCP then -- MODBUS instruction - if packet.modbus_frame.unit_id <= #units then - local return_code, response = units.modbus_io.handle_packet(packet.modbus_frame) + if packet.unit_id <= #units then + local unit = units[packet.unit_id] + local return_code, response = unit.modbus_io.handle_packet(packet) _send(response, PROTOCOLS.MODBUS_TCP) if not return_code then @@ -186,7 +170,7 @@ function rtu_comms(modem, local_port, server_port) end else -- should be unreachable assuming packet is from parse_packet() - log._error("Illegal packet type " .. protocol, true) + log._error("illegal packet type " .. protocol, true) end end end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index f760352..ac0c3c5 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -114,3 +114,67 @@ function scada_packet() data = data } end + +function mgmt_packet() + local self = { + frame = nil, + type = nil, + length = nil, + data = nil + } + + local _scada_type_valid = function () + return self.type == SCADA_MGMT_TYPES.PING or + self.type == SCADA_MGMT_TYPES.SV_HEARTBEAT or + self.type == SCADA_MGMT_TYPES.REMOTE_LINKED or + self.type == SCADA_MGMT_TYPES.RTU_ADVERT or + self.type == SCADA_MGMT_TYPES.RTU_HEARTBEAT + end + + -- make a SCADA management packet + local make = function (packet_type, length, data) + self.type = packet_type + self.length = length + self.data = data + end + + -- decode a SCADA management packet from a SCADA frame + local decode = function (frame) + if frame then + self.frame = frame + + if frame.protocol() == comms.PROTOCOLS.SCADA_MGMT then + local data = frame.data() + local ok = #data > 1 + + if ok then + make(data[1], data[2], { table.unpack(data, 3, #data) }) + ok = _scada_type_valid() + end + + return ok + else + log._debug("attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true) + return false + end + else + log._debug("nil frame encountered", true) + return false + end + end + + local get = function () + return { + scada_frame = self.frame, + type = self.type, + length = self.length, + data = self.data + } + end + + return { + make = make, + decode = decode, + get = get + } +end diff --git a/scada-common/modbus.lua b/scada-common/modbus.lua index de21a2e..9d2899d 100644 --- a/scada-common/modbus.lua +++ b/scada-common/modbus.lua @@ -176,9 +176,8 @@ function modbus_init(rtu_dev) end function modbus_packet() - local MODBUS_TCP = 0 - local self = { + frame = nil, txn_id = txn_id, protocol = protocol, length = length, @@ -187,6 +186,7 @@ function modbus_packet() data = data } + -- make a MODBUS packet local make = function (txn_id, protocol, length, unit_id, func_code, data) self.txn_id = txn_id self.protocol = protocol @@ -196,18 +196,29 @@ function modbus_packet() self.data = data end - local receive = function (raw) - local size_ok = #raw ~= 6 + -- decode a MODBUS packet from a SCADA frame + local decode = function (frame) + if frame then + self.frame = frame + + local data = frame.data() + local size_ok = #data ~= 6 - if size_ok then - make(raw[1], raw[2], raw[3], raw[4], raw[5], raw[6]) + if size_ok then + make(data[1], data[2], data[3], data[4], data[5], data[6]) + end + + return size_ok and self.protocol == comms.PROTOCOLS.MODBUS_TCP + else + log._debug("nil frame encountered", true) + return false end - - return size_ok and self.protocol == MODBUS_TCP end + -- get this packet local get = function () return { + scada_frame = self.frame, txn_id = self.txn_id, protocol = self.protocol, length = self.length, @@ -219,7 +230,7 @@ function modbus_packet() return { make = make, - receive = receive, + decode = decode, get = get } end From a77946ce2ceb5a4a7c9ebb7ded831677123f8daf Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 2 Apr 2022 11:22:44 -0400 Subject: [PATCH 036/587] #1 PLC does not shut down if failed link, repeatedly tries to maintain link as part of main loop --- reactor-plc/plc.lua | 134 ++++++++++++++++++---------------------- reactor-plc/startup.lua | 45 ++++++++------ 2 files changed, 87 insertions(+), 92 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 8212875..bbe2021 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -1,53 +1,5 @@ -- #REQUIRES comms.lua -function scada_link(plc_comms) - local linked = false - local link_timeout = os.startTimer(5) - - plc_comms.send_link_req() - print_ts("sent link request") - - repeat - local event, p1, p2, p3, p4, p5 = os.pullEvent() - - -- handle event - if event == "timer" and param1 == link_timeout then - -- no response yet - print("...no response"); - elseif event == "modem_message" then - -- server response? cancel timeout - if link_timeout ~= nil then - os.cancelTimer(link_timeout) - end - - local packet = plc_comms.parse_packet(p1, p2, p3, p4, p5) - if packet then - -- handle response - local response = plc_comms.handle_link(packet) - if response == nil then - print_ts("invalid link response, bad channel?\n") - break - elseif response == comms.RPLC_LINKING.COLLISION then - print_ts("...reactor PLC ID collision (check config), exiting...\n") - break - elseif response == comms.RPLC_LINKING.ALLOW then - print_ts("...linked!\n") - linked = true - plc_comms.send_rs_io_conns() - plc_comms.send_struct() - plc_comms.send_status() - print_ts("sent initial data\n") - else - print_ts("...denied, exiting...\n") - break - end - end - end - until linked - - return linked -end - -- Internal Safety System -- identifies dangerous states and SCRAMs reactor if warranted -- autonomous from main control @@ -253,7 +205,8 @@ function rplc_comms(id, modem, local_port, server_port, reactor) s_port = server_port, l_port = local_port, reactor = reactor, - status_cache = nil + status_cache = nil, + linked = false } -- PRIVATE FUNCTIONS -- @@ -342,32 +295,60 @@ function rplc_comms(id, modem, local_port, server_port, reactor) return pkt end - -- handle a linking packet - local handle_link = function (packet) - if packet.type == RPLC_TYPES.LINK_REQ then - return packet.data[1] == RPLC_LINKING.ALLOW - else - return nil - end - end - -- handle an RPLC packet local handle_packet = function (packet) - if packet.type == RPLC_TYPES.KEEP_ALIVE then - -- keep alive request received, nothing to do except feed watchdog - elseif packet.type == RPLC_TYPES.MEK_STRUCT then - -- request for physical structure - send_struct() - elseif packet.type == RPLC_TYPES.RS_IO_CONNS then - -- request for redstone connections - send_rs_io_conns() - elseif packet.type == RPLC_TYPES.RS_IO_GET then - elseif packet.type == RPLC_TYPES.RS_IO_SET then - elseif packet.type == RPLC_TYPES.MEK_SCRAM then - elseif packet.type == RPLC_TYPES.MEK_ENABLE then - elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then - elseif packet.type == RPLC_TYPES.ISS_GET then - elseif packet.type == RPLC_TYPES.ISS_CLEAR then + if packet ~= nil then + if packet.scada_frame.protocol() == PROTOCOLS.RPLC then + if self.linked then + if packet.type == RPLC_TYPES.KEEP_ALIVE then + -- keep alive request received, nothing to do except feed watchdog + elseif packet.type == RPLC_TYPES.LINK_REQ then + -- link request confirmation + log._debug("received link request response after already being linked") + elseif packet.type == RPLC_TYPES.MEK_STRUCT then + -- request for physical structure + send_struct() + elseif packet.type == RPLC_TYPES.RS_IO_CONNS then + -- request for redstone connections + send_rs_io_conns() + elseif packet.type == RPLC_TYPES.RS_IO_GET then + elseif packet.type == RPLC_TYPES.RS_IO_SET then + elseif packet.type == RPLC_TYPES.MEK_SCRAM then + elseif packet.type == RPLC_TYPES.MEK_ENABLE then + elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then + elseif packet.type == RPLC_TYPES.ISS_GET then + elseif packet.type == RPLC_TYPES.ISS_CLEAR then + end + elseif packet.type == RPLC_TYPES.LINK_REQ then + -- link request confirmation + local link_ack = packet.data[1] + + if link_ack == RPLC_LINKING.ALLOW then + print_ts("...linked!\n") + log._debug("rplc link request approved") + + plc_comms.send_rs_io_conns() + plc_comms.send_struct() + plc_comms.send_status() + + log._debug("sent initial status data") + elseif link_ack == RPLC_LINKING.DENY then + print_ts("...denied, retrying...\n") + log._debug("rplc link request denied") + elseif link_ack == RPLC_LINKING.COLLISION then + print_ts("reactor PLC ID collision (check config), retrying...\n") + log._warning("rplc link request collision") + else + print_ts("invalid link response, bad channel? retrying...\n") + log._error("unknown rplc link request response") + end + + self.linked = link_ack == RPLC_LINKING.ALLOW + else + log._("discarding non-link packet before linked") + end + elseif packet.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then + end end end @@ -427,12 +408,17 @@ function rplc_comms(id, modem, local_port, server_port, reactor) _send(sys_status) end + local linked = function () return self.linked end + local unlink = function () self.linked = false end + return { parse_packet = parse_packet, handle_link = handle_link, handle_packet = handle_packet, send_link_req = send_link_req, send_struct = send_struct, - send_status = send_status + send_status = send_status, + linked = linked, + unlink = unlink } end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 6875bcb..0554d26 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,13 +14,14 @@ local R_PLC_VERSION = "alpha-v0.1.0" local print_ts = util.print_ts +print(">> Reactor PLC " .. R_PLC_VERSION .. " <<") + +-- mount connected devices ppm.mount_all() local reactor = ppm.get_device("fissionReactor") local modem = ppm.get_device("modem") -print(">> Reactor PLC " .. R_PLC_VERSION .. " <<") - -- we need a reactor and a modem if reactor == nil then print("Fission reactor not found, exiting..."); @@ -44,19 +45,19 @@ end local plc_comms = plc.rplc_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) --- attempt server connection --- exit application if connection is denied -if ~plc.scada_link(plc_comms) then - return -end - -- comms watchdog, 3 second timeout local conn_watchdog = watchdog.new_watchdog(3) -- loop clock (10Hz, 2 ticks) --- send status updates at 4Hz (every 5 ticks) local loop_tick = os.startTimer(0.05) -local ticks_to_update = 5 + +-- send status updates at ~3.33Hz (every 6 server ticks) (every 3 loop ticks) +-- send link requests at 0.5Hz (every 40 server ticks) (every 20 loop ticks) +local UPDATE_TICKS = 3 +local LINK_TICKS = 20 + +-- start by linking +local ticks_to_update = LINK_TICKS -- runtime variables local control_state = false @@ -70,13 +71,12 @@ while true do -- try to scram reactor if it is still connected if reactor.scram() then - print_ts("[fatal] PLC lost a peripheral: successful SCRAM, now exiting...\n") + print_ts("[fatal] PLC lost a peripheral: successful SCRAM\n") else - print_ts("[fatal] PLC lost a peripheral: failed SCRAM, now exiting...\n") + print_ts("[fatal] PLC lost a peripheral: failed SCRAM\n") end -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_PERI_DC) ? - return end -- check safety (SCRAM occurs if tripped) @@ -87,11 +87,20 @@ while true do -- handle event if event == "timer" and param1 == loop_tick then - -- basic event tick, send updated data if it is time (4Hz) + -- basic event tick, send updated data if it is time (~3.33Hz) + -- iss was already checked (main reason for this tick rate) ticks_to_update = ticks_to_update - 1 - if ticks_to_update == 0 then - plc_comms.send_status(control_state, iss_tripped) - ticks_to_update = 5 + + if plc_comms.linked() then + if ticks_to_update <= 0 then + plc_comms.send_status(control_state, iss_tripped) + ticks_to_update = UPDATE_TICKS + end + else + if ticks_to_update <= 0 then + plc_comms.send_link_req() + ticks_to_update = LINK_TICKS + end end elseif event == "modem_message" then -- got a packet @@ -100,9 +109,9 @@ while true do local packet = plc_comms.parse_packet(p1, p2, p3, p4, p5) plc_comms.handle_packet(packet) - elseif event == "timer" and param1 == conn_watchdog.get_timer() then -- haven't heard from server recently? shutdown reactor + plc_comms.unlink() iss.trip_timeout() print_ts("[alert] server timeout, reactor disabled\n") elseif event == "terminate" then From 7c2d89e70ffc5fd9fc5ac234f1cad5064b389ba3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 2 Apr 2022 11:45:43 -0400 Subject: [PATCH 037/587] allow suppressing of PPM errors --- scada-common/ppm.lua | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 5e19636..6f33117 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -5,7 +5,8 @@ -- local self = { - mounts = {} + mounts = {}, + mute = false } -- wrap peripheral calls with lua protected call @@ -24,13 +25,25 @@ local peri_init = function (device) end else -- function failed - log._error("PPM: protected " .. key .. "() -> " .. result) + if not mute then + log._error("PPM: protected " .. key .. "() -> " .. result) + end return nil end end end end +-- silence error prints +function disable_reporting() + self.mute = true +end + +-- allow error prints +function enable_reporting() + self.mute = false +end + -- mount all available peripherals (clears mounts first) function mount_all() local ifaces = peripheral.getNames() From ed997d53e18192f620747b82b9a2e70228e1ed60 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 2 Apr 2022 11:46:14 -0400 Subject: [PATCH 038/587] #6 PLC retry SCRAM until reactor confirms unpowered --- reactor-plc/plc.lua | 11 +++++++++-- reactor-plc/startup.lua | 17 ++++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index bbe2021..8aaa9b6 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -206,6 +206,7 @@ function rplc_comms(id, modem, local_port, server_port, reactor) l_port = local_port, reactor = reactor, status_cache = nil, + scrammed = false, linked = false } @@ -314,7 +315,11 @@ function rplc_comms(id, modem, local_port, server_port, reactor) elseif packet.type == RPLC_TYPES.RS_IO_GET then elseif packet.type == RPLC_TYPES.RS_IO_SET then elseif packet.type == RPLC_TYPES.MEK_SCRAM then + self.scrammed = true + self.reactor.scram() elseif packet.type == RPLC_TYPES.MEK_ENABLE then + self.scrammed = false + self.reactor.activate() elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then elseif packet.type == RPLC_TYPES.ISS_GET then elseif packet.type == RPLC_TYPES.ISS_CLEAR then @@ -408,7 +413,8 @@ function rplc_comms(id, modem, local_port, server_port, reactor) _send(sys_status) end - local linked = function () return self.linked end + local is_scrammed = function () return self.scrammed end + local is_linked = function () return self.linked end local unlink = function () self.linked = false end return { @@ -418,7 +424,8 @@ function rplc_comms(id, modem, local_port, server_port, reactor) send_link_req = send_link_req, send_struct = send_struct, send_status = send_status, - linked = linked, + is_scrammed = is_scrammed, + is_linked = is_linked, unlink = unlink } end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 0554d26..8122557 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -61,15 +61,26 @@ local ticks_to_update = LINK_TICKS -- runtime variables local control_state = false +local reactor_scram = true -- treated as latching e-stop -- event loop while true do local event, param1, param2, param3, param4, param5 = os.pullEventRaw() + -- if we tried to SCRAM but failed, keep trying + -- if it disconnected, isPowered will return nil (and error logs will get spammed at 10Hz, so disable reporting) + -- in that case, SCRAM won't be called until it reconnects (this is the expected use of this check) + ppm.disable_reporting() + if reactor_scram and reactor.isPowered() then + reactor.scram() + end + ppm.enable_reporting() + if event == "peripheral_detach" then ppm.handle_unmount(param1) -- try to scram reactor if it is still connected + reactor_scram = true if reactor.scram() then print_ts("[fatal] PLC lost a peripheral: successful SCRAM\n") else @@ -81,6 +92,7 @@ while true do -- check safety (SCRAM occurs if tripped) local iss_status, iss_tripped, iss_first = iss.check() + reactor_scram = reactor_scram or iss_tripped if iss_first then plc_comms.send_iss_alarm(iss_status) end @@ -91,7 +103,7 @@ while true do -- iss was already checked (main reason for this tick rate) ticks_to_update = ticks_to_update - 1 - if plc_comms.linked() then + if plc_comms.is_linked() then if ticks_to_update <= 0 then plc_comms.send_status(control_state, iss_tripped) ticks_to_update = UPDATE_TICKS @@ -109,13 +121,16 @@ while true do local packet = plc_comms.parse_packet(p1, p2, p3, p4, p5) plc_comms.handle_packet(packet) + reactor_scram = reactor_scram or plc_comms.is_scrammed() elseif event == "timer" and param1 == conn_watchdog.get_timer() then -- haven't heard from server recently? shutdown reactor + reactor_scram = true plc_comms.unlink() iss.trip_timeout() print_ts("[alert] server timeout, reactor disabled\n") elseif event == "terminate" then -- safe exit + reactor_scram = true if reactor.scram() then print_ts("[alert] exiting, reactor disabled\n") else From 34fc625602bce0e45b63b8f18a075d7a55f54376 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 2 Apr 2022 14:43:36 -0400 Subject: [PATCH 039/587] #5 finished implementing PLC packet handler, bugfixes --- reactor-plc/plc.lua | 167 ++++++++++++++++++++++++++++++---------- reactor-plc/startup.lua | 4 +- 2 files changed, 130 insertions(+), 41 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 8aaa9b6..5a5ce4a 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -197,7 +197,7 @@ function rplc_packet() end -- reactor PLC communications -function rplc_comms(id, modem, local_port, server_port, reactor) +function rplc_comms(id, modem, local_port, server_port, reactor, iss) local self = { id = id, seq_num = 0, @@ -205,6 +205,7 @@ function rplc_comms(id, modem, local_port, server_port, reactor) s_port = server_port, l_port = local_port, reactor = reactor, + iss = iss, status_cache = nil, scrammed = false, linked = false @@ -265,6 +266,61 @@ function rplc_comms(id, modem, local_port, server_port, reactor) return changed end + -- keep alive ack + local _send_keep_alive_ack = function () + local keep_alive_data = { + id = self.id, + timestamp = os.time(), + type = RPLC_TYPES.KEEP_ALIVE + } + + _send(keep_alive_data) + end + + -- general ack + local _send_ack = function (type, succeeded) + local ack_data = { + id = self.id, + type = type, + ack = succeeded + } + + _send(ack_data) + end + + -- send structure properties (these should not change) + -- (server will cache these) + local _send_struct = function () + local mek_data = { + heat_cap = self.reactor.getHeatCapacity(), + fuel_asm = self.reactor.getFuelAssemblies(), + fuel_sa = self.reactor.getFuelSurfaceArea(), + fuel_cap = self.reactor.getFuelCapacity(), + waste_cap = self.reactor.getWasteCapacity(), + cool_cap = self.reactor.getCoolantCapacity(), + hcool_cap = self.reactor.getHeatedCoolantCapacity(), + max_burn = self.reactor.getMaxBurnRate() + } + + local struct_packet = { + id = self.id, + type = RPLC_TYPES.MEK_STRUCT, + mek_data = mek_data + } + + _send(struct_packet) + end + + local _send_iss_status = function () + local iss_status = { + id = self.id, + type = RPLC_TYPES.ISS_GET, + status = iss.status() + } + + _send(iss_status) + end + -- PUBLIC FUNCTIONS -- -- parse an RPLC packet @@ -302,27 +358,74 @@ function rplc_comms(id, modem, local_port, server_port, reactor) if packet.scada_frame.protocol() == PROTOCOLS.RPLC then if self.linked then if packet.type == RPLC_TYPES.KEEP_ALIVE then - -- keep alive request received, nothing to do except feed watchdog + -- keep alive request received, echo back + local timestamp = packet.data[1] + local trip_time = os.time() - ts + + if trip_time < 0 then + log._warning("PLC KEEP_ALIVE trip time less than 0 (" .. trip_time .. ")") + elseif trip_time > 1 then + log._warning("PLC KEEP_ALIVE trip time > 1s (" .. trip_time .. ")") + end + + _send_keep_alive_ack() elseif packet.type == RPLC_TYPES.LINK_REQ then -- link request confirmation - log._debug("received link request response after already being linked") + log._debug("received unsolicited link request response") + + local link_ack = packet.data[1] + + if link_ack == RPLC_LINKING.ALLOW then + _send_struct() + send_status() + log._debug("re-sent initial status data") + elseif link_ack == RPLC_LINKING.DENY then + -- @todo: make sure this doesn't become an MITM security risk + print_ts("received unsolicited link denial, unlinking\n") + log._debug("unsolicited rplc link request denied") + elseif link_ack == RPLC_LINKING.COLLISION then + -- @todo: make sure this doesn't become an MITM security risk + print_ts("received unsolicited link collision, unlinking\n") + log._warning("unsolicited rplc link request collision") + else + print_ts("invalid unsolicited link response\n") + log._error("unsolicited unknown rplc link request response") + end + + self.linked = link_ack == RPLC_LINKING.ALLOW elseif packet.type == RPLC_TYPES.MEK_STRUCT then -- request for physical structure - send_struct() - elseif packet.type == RPLC_TYPES.RS_IO_CONNS then - -- request for redstone connections - send_rs_io_conns() - elseif packet.type == RPLC_TYPES.RS_IO_GET then - elseif packet.type == RPLC_TYPES.RS_IO_SET then + _send_struct() elseif packet.type == RPLC_TYPES.MEK_SCRAM then + -- disable the reactor self.scrammed = true - self.reactor.scram() + _send_ack(packet.type, self.reactor.scram()) elseif packet.type == RPLC_TYPES.MEK_ENABLE then + -- enable the reactor self.scrammed = false - self.reactor.activate() + _send_ack(packet.type, self.reactor.activate()) elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then + -- set the burn rate + local burn_rate = packet.data[1] + local max_burn_rate = self.reactor.getMaxBurnRate() + local success = false + + if max_burn_rate is not nil then + if burn_rate > 0 and burn_rate <= max_burn_rate then + success = self.reactor.setBurnRate(burn_rate) + end + end + + _send_ack(packet.type, success) elseif packet.type == RPLC_TYPES.ISS_GET then + -- get the ISS status + _send_iss_status(iss.status()) elseif packet.type == RPLC_TYPES.ISS_CLEAR then + -- clear the ISS status + iss.reset() + _send_ack(packet.type, true) + else + log._warning("received unknown RPLC packet type " .. packet.type) end elseif packet.type == RPLC_TYPES.LINK_REQ then -- link request confirmation @@ -332,9 +435,8 @@ function rplc_comms(id, modem, local_port, server_port, reactor) print_ts("...linked!\n") log._debug("rplc link request approved") - plc_comms.send_rs_io_conns() - plc_comms.send_struct() - plc_comms.send_status() + _send_struct() + send_status() log._debug("sent initial status data") elseif link_ack == RPLC_LINKING.DENY then @@ -367,29 +469,6 @@ function rplc_comms(id, modem, local_port, server_port, reactor) _send(linking_data) end - -- send structure properties (these should not change) - -- (server will cache these) - local send_struct = function () - local mek_data = { - heat_cap = self.reactor.getHeatCapacity(), - fuel_asm = self.reactor.getFuelAssemblies(), - fuel_sa = self.reactor.getFuelSurfaceArea(), - fuel_cap = self.reactor.getFuelCapacity(), - waste_cap = self.reactor.getWasteCapacity(), - cool_cap = self.reactor.getCoolantCapacity(), - hcool_cap = self.reactor.getHeatedCoolantCapacity(), - max_burn = self.reactor.getMaxBurnRate() - } - - local struct_packet = { - id = self.id, - type = RPLC_TYPES.MEK_STRUCT, - mek_data = mek_data - } - - _send(struct_packet) - end - -- send live status information -- control_state : acknowledged control state from supervisor -- overridden : if ISS force disabled reactor @@ -413,17 +492,27 @@ function rplc_comms(id, modem, local_port, server_port, reactor) _send(sys_status) end + local send_iss_alarm = function (cause) + local iss_alarm = { + id = self.id, + type = RPLC_TYPES.ISS_ALARM, + cause = cause, + status = iss.status() + } + + _send(iss_alarm) + end + local is_scrammed = function () return self.scrammed end local is_linked = function () return self.linked end local unlink = function () self.linked = false end return { parse_packet = parse_packet, - handle_link = handle_link, handle_packet = handle_packet, send_link_req = send_link_req, - send_struct = send_struct, send_status = send_status, + send_iss_alarm = send_iss_alarm, is_scrammed = is_scrammed, is_linked = is_linked, unlink = unlink diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 8122557..a85d11f 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -43,7 +43,7 @@ if not modem.isOpen(config.LISTEN_PORT) then modem.open(config.LISTEN_PORT) end -local plc_comms = plc.rplc_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) +local plc_comms = plc.rplc_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor, iss) -- comms watchdog, 3 second timeout local conn_watchdog = watchdog.new_watchdog(3) @@ -91,7 +91,7 @@ while true do end -- check safety (SCRAM occurs if tripped) - local iss_status, iss_tripped, iss_first = iss.check() + local iss_tripped, iss_status, iss_first = iss.check() reactor_scram = reactor_scram or iss_tripped if iss_first then plc_comms.send_iss_alarm(iss_status) From 02763c9cb312c0104446e8605b55cbb004addb71 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 3 Apr 2022 12:08:22 -0400 Subject: [PATCH 040/587] #4 PLC peripheral disconnect handling and small bugfixes/cleanup --- reactor-plc/plc.lua | 31 ++++++- reactor-plc/startup.lua | 193 ++++++++++++++++++++++++++++------------ scada-common/log.lua | 4 + scada-common/ppm.lua | 2 +- 4 files changed, 170 insertions(+), 60 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 5a5ce4a..9a168bb 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -11,6 +11,10 @@ function iss_init(reactor) trip_cause = "" } + local reconnect_reactor = function (reactor) + self.reactor = reactor + end + local check = function () local status = "ok" local was_tripped = self.tripped @@ -110,6 +114,7 @@ function iss_init(reactor) end return { + reconnect_reactor = reconnect_reactor, check = check, trip_timeout = trip_timeout, reset = reset, @@ -197,7 +202,7 @@ function rplc_packet() end -- reactor PLC communications -function rplc_comms(id, modem, local_port, server_port, reactor, iss) +function comms_init(id, modem, local_port, server_port, reactor, iss) local self = { id = id, seq_num = 0, @@ -211,6 +216,11 @@ function rplc_comms(id, modem, local_port, server_port, reactor, iss) linked = false } + -- open modem + if not self.modem.isOpen(self.l_port) then + self.modem.open(self.l_port) + end + -- PRIVATE FUNCTIONS -- local _send = function (msg) @@ -323,6 +333,21 @@ function rplc_comms(id, modem, local_port, server_port, reactor, iss) -- PUBLIC FUNCTIONS -- + -- reconnect a newly connected modem + local reconnect_modem = function (modem) + self.modem = modem + + -- open modem + if not self.modem.isOpen(self.l_port) then + self.modem.open(self.l_port) + end + end + + -- reconnect a newly connected reactor + local reconnect_reactor = function (reactor) + self.reactor = reactor + end + -- parse an RPLC packet local parse_packet = function(side, sender, reply_to, message, distance) local pkt = nil @@ -410,7 +435,7 @@ function rplc_comms(id, modem, local_port, server_port, reactor, iss) local max_burn_rate = self.reactor.getMaxBurnRate() local success = false - if max_burn_rate is not nil then + if max_burn_rate ~= nil then if burn_rate > 0 and burn_rate <= max_burn_rate then success = self.reactor.setBurnRate(burn_rate) end @@ -508,6 +533,8 @@ function rplc_comms(id, modem, local_port, server_port, reactor, iss) local unlink = function () self.linked = false end return { + reconnect_modem = reconnect_modem, + reconnect_reactor = reconnect_reactor, parse_packet = parse_packet, handle_packet = handle_packet, send_link_req = send_link_req, diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index a85d11f..29522e1 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -22,35 +22,50 @@ ppm.mount_all() local reactor = ppm.get_device("fissionReactor") local modem = ppm.get_device("modem") +local init_ok = true +local control_degraded = { degraded = false, no_reactor = false, no_modem = false } + -- we need a reactor and a modem if reactor == nil then - print("Fission reactor not found, exiting..."); - return -elseif modem == nil then - print("No modem found, disabling reactor and exiting...") + print_ts("Fission reactor not found. Running in a degraded state...\n"); + log._warning("no reactor on startup") + init_ok = false + control_degraded.degraded = true + control_degraded.no_reactor = true +end +if modem == nil then + print_ts("No modem found. Disabling reactor and running in a degraded state...\n") + log._warning("no modem on startup") reactor.scram() - return + init_ok = false + control_degraded.degraded = true + control_degraded.no_modem = true end --- just booting up, no fission allowed (neutrons stay put thanks) -reactor.scram() +::init:: +if init_ok then + -- just booting up, no fission allowed (neutrons stay put thanks) + reactor.scram() --- init internal safety system -local iss = plc.iss_init(reactor) + -- init internal safety system + local iss = plc.iss_init(reactor) + log._debug("iss init") --- start comms -if not modem.isOpen(config.LISTEN_PORT) then - modem.open(config.LISTEN_PORT) + -- start comms + local plc_comms = plc.comms_init(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor, iss) + log._debug("comms init") + + -- comms watchdog, 3 second timeout + local conn_watchdog = watchdog.new_watchdog(3) + log._debug("conn watchdog started") + + -- loop clock (10Hz, 2 ticks) + local loop_tick = os.startTimer(0.05) + log._debug("loop clock started") +else + log._warning("booted in a degraded state, awaiting peripheral connections...") end -local plc_comms = plc.rplc_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor, iss) - --- comms watchdog, 3 second timeout -local conn_watchdog = watchdog.new_watchdog(3) - --- loop clock (10Hz, 2 ticks) -local loop_tick = os.startTimer(0.05) - -- send status updates at ~3.33Hz (every 6 server ticks) (every 3 loop ticks) -- send link requests at 0.5Hz (every 40 server ticks) (every 20 loop ticks) local UPDATE_TICKS = 3 @@ -67,51 +82,112 @@ local reactor_scram = true -- treated as latching e-stop while true do local event, param1, param2, param3, param4, param5 = os.pullEventRaw() - -- if we tried to SCRAM but failed, keep trying - -- if it disconnected, isPowered will return nil (and error logs will get spammed at 10Hz, so disable reporting) - -- in that case, SCRAM won't be called until it reconnects (this is the expected use of this check) - ppm.disable_reporting() - if reactor_scram and reactor.isPowered() then - reactor.scram() + if init_ok then + -- if we tried to SCRAM but failed, keep trying + -- if it disconnected, isPowered will return nil (and error logs will get spammed at 10Hz, so disable reporting) + -- in that case, SCRAM won't be called until it reconnects (this is the expected use of this check) + ppm.disable_reporting() + if reactor_scram and reactor.isPowered() then + reactor.scram() + end + ppm.enable_reporting() end - ppm.enable_reporting() + -- check for peripheral changes before ISS checks if event == "peripheral_detach" then - ppm.handle_unmount(param1) + local device = ppm.handle_unmount(param1) - -- try to scram reactor if it is still connected - reactor_scram = true - if reactor.scram() then - print_ts("[fatal] PLC lost a peripheral: successful SCRAM\n") - else - print_ts("[fatal] PLC lost a peripheral: failed SCRAM\n") + if device.type == "fissionReactor" then + print_ts("reactor disconnected!\n") + log._error("reactor disconnected!") + control_degraded.no_reactor = true + -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_PERI_DC) ? + elseif device.type == "modem" then + print_ts("modem disconnected!\n") + log._error("modem disconnected!") + control_degraded.no_modem = true + + if init_ok then + -- try to scram reactor if it is still connected + reactor_scram = true + if reactor.scram() then + print_ts("successful reactor SCRAM\n") + else + print_ts("failed reactor SCRAM\n") + end + end + + control_degraded.degraded = true + end + elseif event == "peripheral" then + local device = ppm.mount(param1) + + if device.type == "fissionReactor" then + -- reconnected reactor + reactor_scram = true + device.scram() + + print_ts("reactor reconnected.\n") + log._info("reactor reconnected.") + control_degraded.no_reactor = false + + if init_ok then + iss.reconnect_reactor(device) + plc_comms.reconnect_reactor(device) + end + + -- determine if we are still in a degraded state + if get_device("modem") not nil then + control_degraded.degraded = false + end + elseif device.type == "modem" then + -- reconnected modem + if init_ok then + plc_comms.reconnect_modem(device) + end + + print_ts("modem reconnected.\n") + log._info("modem reconnected.") + control_degraded.no_modem = false + + -- determine if we are still in a degraded state + if ppm.get_device("fissionReactor") not nil then + control_degraded.degraded = false + end end - -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_PERI_DC) ? + if not init_ok and not control_degraded.degraded then + init_ok = false + goto init + end end -- check safety (SCRAM occurs if tripped) - local iss_tripped, iss_status, iss_first = iss.check() - reactor_scram = reactor_scram or iss_tripped - if iss_first then - plc_comms.send_iss_alarm(iss_status) + if not control_degraded.degraded then + local iss_tripped, iss_status, iss_first = iss.check() + reactor_scram = reactor_scram or iss_tripped + if iss_first then + plc_comms.send_iss_alarm(iss_status) + end end -- handle event if event == "timer" and param1 == loop_tick then - -- basic event tick, send updated data if it is time (~3.33Hz) - -- iss was already checked (main reason for this tick rate) - ticks_to_update = ticks_to_update - 1 + if not control_degraded.no_modem then + -- basic event tick, send updated data if it is time (~3.33Hz) + -- iss was already checked (main reason for this tick rate) + ticks_to_update = ticks_to_update - 1 - if plc_comms.is_linked() then - if ticks_to_update <= 0 then - plc_comms.send_status(control_state, iss_tripped) - ticks_to_update = UPDATE_TICKS - end - else - if ticks_to_update <= 0 then - plc_comms.send_link_req() - ticks_to_update = LINK_TICKS + if plc_comms.is_linked() then + if ticks_to_update <= 0 then + plc_comms.send_status(control_state, iss_tripped) + ticks_to_update = UPDATE_TICKS + end + else + if ticks_to_update <= 0 then + plc_comms.send_link_req() + ticks_to_update = LINK_TICKS + end end end elseif event == "modem_message" then @@ -130,14 +206,17 @@ while true do print_ts("[alert] server timeout, reactor disabled\n") elseif event == "terminate" then -- safe exit - reactor_scram = true - if reactor.scram() then - print_ts("[alert] exiting, reactor disabled\n") - else - -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_LOST_CONTROL) ? - print_ts("[alert] exiting, reactor failed to disable\n") + if init_ok then + reactor_scram = true + if reactor.scram() then + print_ts("[alert] exiting, reactor disabled\n") + else + -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_LOST_CONTROL) ? + print_ts("[alert] exiting, reactor failed to disable\n") + end end -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_SHUTDOWN) ? + print_ts("[alert] exited") return end end diff --git a/scada-common/log.lua b/scada-common/log.lua index 3a56919..79296c6 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -34,6 +34,10 @@ function _debug(msg, trace) end end +function _info(msg) + _log("[INF] " .. msg .. "\n") +end + function _warning(msg) _log("[WRN] " .. msg .. "\n") end diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 6f33117..9924007 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -90,7 +90,7 @@ function handle_unmount(iface) log._warning("PPM: lost device " .. type .. " mounted to " .. iface) - return self.mounts[iface] + return lost_dev end -- list all available peripherals From 13b0fcf65f83c2a522c72d45423a118d0e61f4dd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Apr 2022 09:41:06 -0400 Subject: [PATCH 041/587] PLC state code cleanup and bugfixes --- reactor-plc/plc.lua | 7 ++-- reactor-plc/startup.lua | 84 ++++++++++++++++++++++------------------- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 9a168bb..47f84b5 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -495,9 +495,8 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) end -- send live status information - -- control_state : acknowledged control state from supervisor - -- overridden : if ISS force disabled reactor - local send_status = function (control_state, overridden) + -- overridden : if ISS force disabled reactor + local send_status = function (overridden) local mek_data = nil if _update_status_cache() then @@ -508,7 +507,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) id = self.id, type = RPLC_TYPES.STATUS, timestamp = os.time(), - control_state = control_state, + control_state = ~self.scrammed, overridden = overridden, heating_rate = self.reactor.getHeatingRate(), mek_data = mek_data diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 29522e1..65dfe39 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -22,28 +22,38 @@ ppm.mount_all() local reactor = ppm.get_device("fissionReactor") local modem = ppm.get_device("modem") -local init_ok = true -local control_degraded = { degraded = false, no_reactor = false, no_modem = false } +local plc_state = { + init_ok = true, + scram = true, -- treated as latching e-stop, all conditions must be OK to set false + degraded = false, + no_reactor = false, + no_modem = false +} -- we need a reactor and a modem if reactor == nil then print_ts("Fission reactor not found. Running in a degraded state...\n"); log._warning("no reactor on startup") - init_ok = false - control_degraded.degraded = true - control_degraded.no_reactor = true + plc_state.init_ok = false + plc_state.degraded = true + plc_state.no_reactor = true end if modem == nil then - print_ts("No modem found. Disabling reactor and running in a degraded state...\n") + if reactor ~= nil then + print_ts("No modem found. Disabling reactor and running in a degraded state...\n") + reactor.scram() + else + print_ts("No modem found. Running in a degraded state...\n") + end + log._warning("no modem on startup") - reactor.scram() - init_ok = false - control_degraded.degraded = true - control_degraded.no_modem = true + plc_state.init_ok = false + plc_state.degraded = true + plc_state.no_modem = true end ::init:: -if init_ok then +if plc_state.init_ok then -- just booting up, no fission allowed (neutrons stay put thanks) reactor.scram() @@ -74,20 +84,16 @@ local LINK_TICKS = 20 -- start by linking local ticks_to_update = LINK_TICKS --- runtime variables -local control_state = false -local reactor_scram = true -- treated as latching e-stop - -- event loop while true do local event, param1, param2, param3, param4, param5 = os.pullEventRaw() - if init_ok then + if plc_state.init_ok then -- if we tried to SCRAM but failed, keep trying -- if it disconnected, isPowered will return nil (and error logs will get spammed at 10Hz, so disable reporting) -- in that case, SCRAM won't be called until it reconnects (this is the expected use of this check) ppm.disable_reporting() - if reactor_scram and reactor.isPowered() then + if plc_state.scram and reactor.isPowered() then reactor.scram() end ppm.enable_reporting() @@ -100,16 +106,16 @@ while true do if device.type == "fissionReactor" then print_ts("reactor disconnected!\n") log._error("reactor disconnected!") - control_degraded.no_reactor = true + plc_state.no_reactor = true -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_PERI_DC) ? elseif device.type == "modem" then print_ts("modem disconnected!\n") log._error("modem disconnected!") - control_degraded.no_modem = true + plc_state.no_modem = true - if init_ok then + if plc_state.init_ok then -- try to scram reactor if it is still connected - reactor_scram = true + plc_state.scram = true if reactor.scram() then print_ts("successful reactor SCRAM\n") else @@ -117,55 +123,55 @@ while true do end end - control_degraded.degraded = true + plc_state.degraded = true end elseif event == "peripheral" then local device = ppm.mount(param1) if device.type == "fissionReactor" then -- reconnected reactor - reactor_scram = true + plc_state.scram = true device.scram() print_ts("reactor reconnected.\n") log._info("reactor reconnected.") - control_degraded.no_reactor = false + plc_state.no_reactor = false - if init_ok then + if plc_state.init_ok then iss.reconnect_reactor(device) plc_comms.reconnect_reactor(device) end -- determine if we are still in a degraded state if get_device("modem") not nil then - control_degraded.degraded = false + plc_state.degraded = false end elseif device.type == "modem" then -- reconnected modem - if init_ok then + if plc_state.init_ok then plc_comms.reconnect_modem(device) end print_ts("modem reconnected.\n") log._info("modem reconnected.") - control_degraded.no_modem = false + plc_state.no_modem = false -- determine if we are still in a degraded state if ppm.get_device("fissionReactor") not nil then - control_degraded.degraded = false + plc_state.degraded = false end end - if not init_ok and not control_degraded.degraded then - init_ok = false + if not plc_state.init_ok and not plc_state.degraded then + plc_state.init_ok = false goto init end end -- check safety (SCRAM occurs if tripped) - if not control_degraded.degraded then + if not plc_state.degraded then local iss_tripped, iss_status, iss_first = iss.check() - reactor_scram = reactor_scram or iss_tripped + plc_state.scram = plc_state.scram or iss_tripped if iss_first then plc_comms.send_iss_alarm(iss_status) end @@ -173,14 +179,14 @@ while true do -- handle event if event == "timer" and param1 == loop_tick then - if not control_degraded.no_modem then + if not plc_state.no_modem then -- basic event tick, send updated data if it is time (~3.33Hz) -- iss was already checked (main reason for this tick rate) ticks_to_update = ticks_to_update - 1 if plc_comms.is_linked() then if ticks_to_update <= 0 then - plc_comms.send_status(control_state, iss_tripped) + plc_comms.send_status(iss_tripped) ticks_to_update = UPDATE_TICKS end else @@ -197,17 +203,17 @@ while true do local packet = plc_comms.parse_packet(p1, p2, p3, p4, p5) plc_comms.handle_packet(packet) - reactor_scram = reactor_scram or plc_comms.is_scrammed() + plc_state.scram = plc_state.scram or plc_comms.is_scrammed() elseif event == "timer" and param1 == conn_watchdog.get_timer() then -- haven't heard from server recently? shutdown reactor - reactor_scram = true + plc_state.scram = true plc_comms.unlink() iss.trip_timeout() print_ts("[alert] server timeout, reactor disabled\n") elseif event == "terminate" then -- safe exit - if init_ok then - reactor_scram = true + if plc_state.init_ok then + plc_state.scram = true if reactor.scram() then print_ts("[alert] exiting, reactor disabled\n") else From dbf7377c027e07521cd5e4e6443c02740a4076a3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Apr 2022 15:56:48 -0400 Subject: [PATCH 042/587] #11 configurable 'networked' setting for PLCs that allows for standalone ISS-only mode --- reactor-plc/config.lua | 2 + reactor-plc/startup.lua | 109 ++++++++++++++++++++++------------------ 2 files changed, 62 insertions(+), 49 deletions(-) diff --git a/reactor-plc/config.lua b/reactor-plc/config.lua index 539946e..25f750c 100644 --- a/reactor-plc/config.lua +++ b/reactor-plc/config.lua @@ -1,3 +1,5 @@ +-- set to false to run in standalone mode (safety regulation only) +NETWORKED = true -- unique reactor ID REACTOR_ID = 1 -- port to send packets TO server diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 65dfe39..dcac721 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -22,6 +22,8 @@ ppm.mount_all() local reactor = ppm.get_device("fissionReactor") local modem = ppm.get_device("modem") +local networked = config.NETWORKED + local plc_state = { init_ok = true, scram = true, -- treated as latching e-stop, all conditions must be OK to set false @@ -38,7 +40,7 @@ if reactor == nil then plc_state.degraded = true plc_state.no_reactor = true end -if modem == nil then +if networked and modem == nil then if reactor ~= nil then print_ts("No modem found. Disabling reactor and running in a degraded state...\n") reactor.scram() @@ -52,37 +54,46 @@ if modem == nil then plc_state.no_modem = true end -::init:: -if plc_state.init_ok then - -- just booting up, no fission allowed (neutrons stay put thanks) - reactor.scram() - - -- init internal safety system - local iss = plc.iss_init(reactor) - log._debug("iss init") - - -- start comms - local plc_comms = plc.comms_init(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor, iss) - log._debug("comms init") - - -- comms watchdog, 3 second timeout - local conn_watchdog = watchdog.new_watchdog(3) - log._debug("conn watchdog started") - - -- loop clock (10Hz, 2 ticks) - local loop_tick = os.startTimer(0.05) - log._debug("loop clock started") -else - log._warning("booted in a degraded state, awaiting peripheral connections...") -end +local iss = nil +local plc_comms = nil +local conn_watchdog = nil -- send status updates at ~3.33Hz (every 6 server ticks) (every 3 loop ticks) -- send link requests at 0.5Hz (every 40 server ticks) (every 20 loop ticks) local UPDATE_TICKS = 3 local LINK_TICKS = 20 --- start by linking -local ticks_to_update = LINK_TICKS +local loop_tick = nil +local ticks_to_update = LINK_TICKS -- start by linking + +-- initialize PLC +::init:: +if plc_state.init_ok then + -- just booting up, no fission allowed (neutrons stay put thanks) + reactor.scram() + + -- init internal safety system + iss = plc.iss_init(reactor) + log._debug("iss init") + + if networked then + -- start comms + plc_comms = plc.comms_init(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor, iss) + log._debug("comms init") + + -- comms watchdog, 3 second timeout + conn_watchdog = watchdog.new_watchdog(3) + log._debug("conn watchdog started") + else + log._debug("running without networking") + end + + -- loop clock (10Hz, 2 ticks) + loop_tick = os.startTimer(0.05) + log._debug("loop clock started") +else + log._warning("booted in a degraded state, awaiting peripheral connections...") +end -- event loop while true do @@ -93,7 +104,7 @@ while true do -- if it disconnected, isPowered will return nil (and error logs will get spammed at 10Hz, so disable reporting) -- in that case, SCRAM won't be called until it reconnects (this is the expected use of this check) ppm.disable_reporting() - if plc_state.scram and reactor.isPowered() then + if plc_state.degraded or (plc_state.scram and reactor.isPowered()) then reactor.scram() end ppm.enable_reporting() @@ -108,7 +119,7 @@ while true do log._error("reactor disconnected!") plc_state.no_reactor = true -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_PERI_DC) ? - elseif device.type == "modem" then + elseif networked and device.type == "modem" then print_ts("modem disconnected!\n") log._error("modem disconnected!") plc_state.no_modem = true @@ -139,14 +150,16 @@ while true do if plc_state.init_ok then iss.reconnect_reactor(device) - plc_comms.reconnect_reactor(device) + if networked then + plc_comms.reconnect_reactor(device) + end end -- determine if we are still in a degraded state - if get_device("modem") not nil then + if not networked or get_device("modem") not nil then plc_state.degraded = false end - elseif device.type == "modem" then + elseif networked and device.type == "modem" then -- reconnected modem if plc_state.init_ok then plc_comms.reconnect_modem(device) @@ -172,31 +185,29 @@ while true do if not plc_state.degraded then local iss_tripped, iss_status, iss_first = iss.check() plc_state.scram = plc_state.scram or iss_tripped - if iss_first then + if networked and iss_first then plc_comms.send_iss_alarm(iss_status) end end -- handle event - if event == "timer" and param1 == loop_tick then - if not plc_state.no_modem then - -- basic event tick, send updated data if it is time (~3.33Hz) - -- iss was already checked (main reason for this tick rate) - ticks_to_update = ticks_to_update - 1 + if event == "timer" and param1 == loop_tick and networked and not plc_state.no_modem then + -- basic event tick, send updated data if it is time (~3.33Hz) + -- iss was already checked (that's the main reason for this tick rate) + ticks_to_update = ticks_to_update - 1 - if plc_comms.is_linked() then - if ticks_to_update <= 0 then - plc_comms.send_status(iss_tripped) - ticks_to_update = UPDATE_TICKS - end - else - if ticks_to_update <= 0 then - plc_comms.send_link_req() - ticks_to_update = LINK_TICKS - end + if plc_comms.is_linked() then + if ticks_to_update <= 0 then + plc_comms.send_status(iss_tripped) + ticks_to_update = UPDATE_TICKS + end + else + if ticks_to_update <= 0 then + plc_comms.send_link_req() + ticks_to_update = LINK_TICKS end end - elseif event == "modem_message" then + elseif event == "modem_message" and networked and not plc_state.no_modem then -- got a packet -- feed the watchdog first so it doesn't uhh...eat our packets conn_watchdog.feed() @@ -204,7 +215,7 @@ while true do local packet = plc_comms.parse_packet(p1, p2, p3, p4, p5) plc_comms.handle_packet(packet) plc_state.scram = plc_state.scram or plc_comms.is_scrammed() - elseif event == "timer" and param1 == conn_watchdog.get_timer() then + elseif event == "timer" and param1 == conn_watchdog.get_timer() and networked then -- haven't heard from server recently? shutdown reactor plc_state.scram = true plc_comms.unlink() From 5b32f838903a8ed6ba8bf31372af1d4c475c8c25 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Apr 2022 16:08:55 -0400 Subject: [PATCH 043/587] writeLine has newline of course.. --- scada-common/log.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scada-common/log.lua b/scada-common/log.lua index 79296c6..fbe2d66 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -30,16 +30,16 @@ function _debug(msg, trace) debug.getinfo(2).currentline .. " > " end - _log("[DBG] " .. dbg_info .. msg .. "\n") + _log("[DBG] " .. dbg_info .. msg) end end function _info(msg) - _log("[INF] " .. msg .. "\n") + _log("[INF] " .. msg) end function _warning(msg) - _log("[WRN] " .. msg .. "\n") + _log("[WRN] " .. msg) end function _error(msg, trace) @@ -56,9 +56,9 @@ function _error(msg, trace) debug.getinfo(2).currentline .. " > " end - _log("[ERR] " .. dbg_info .. msg .. "\n") + _log("[ERR] " .. dbg_info .. msg) end function _fatal(msg) - _log("[FTL] " .. msg .. "\n") + _log("[FTL] " .. msg) end From f24b21422905baff4e236e2c1b004563d822b857 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Apr 2022 16:09:29 -0400 Subject: [PATCH 044/587] fixed bugs and removed goto as lua 5.1 does not have goto --- reactor-plc/plc.lua | 4 +-- reactor-plc/startup.lua | 59 ++++++++++++++++++++++------------------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 47f84b5..12d018e 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -42,7 +42,7 @@ function iss_init(reactor) self.reactor.scram() end - local first_trip = ~was_tripped and self.tripped + local first_trip = not was_tripped and self.tripped return self.tripped, status, first_trip end @@ -507,7 +507,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) id = self.id, type = RPLC_TYPES.STATUS, timestamp = os.time(), - control_state = ~self.scrammed, + control_state = not self.scrammed, overridden = overridden, heating_rate = self.reactor.getHeatingRate(), mek_data = mek_data diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index dcac721..669f777 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -7,10 +7,10 @@ os.loadAPI("scada-common/util.lua") os.loadAPI("scada-common/ppm.lua") os.loadAPI("scada-common/comms.lua") -os.loadAPI("reactor-plc/config.lua") -os.loadAPI("reactor-plc/plc.lua") +os.loadAPI("config.lua") +os.loadAPI("plc.lua") -local R_PLC_VERSION = "alpha-v0.1.0" +local R_PLC_VERSION = "alpha-v0.1.1" local print_ts = util.print_ts @@ -66,35 +66,38 @@ local LINK_TICKS = 20 local loop_tick = nil local ticks_to_update = LINK_TICKS -- start by linking --- initialize PLC -::init:: -if plc_state.init_ok then - -- just booting up, no fission allowed (neutrons stay put thanks) - reactor.scram() +function init() + if plc_state.init_ok then + -- just booting up, no fission allowed (neutrons stay put thanks) + reactor.scram() - -- init internal safety system - iss = plc.iss_init(reactor) - log._debug("iss init") + -- init internal safety system + iss = plc.iss_init(reactor) + log._debug("iss init") - if networked then - -- start comms - plc_comms = plc.comms_init(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor, iss) - log._debug("comms init") + if networked then + -- start comms + plc_comms = plc.comms_init(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor, iss) + log._debug("comms init") - -- comms watchdog, 3 second timeout - conn_watchdog = watchdog.new_watchdog(3) - log._debug("conn watchdog started") + -- comms watchdog, 3 second timeout + conn_watchdog = watchdog.new_watchdog(3) + log._debug("conn watchdog started") + else + log._debug("running without networking") + end + + -- loop clock (10Hz, 2 ticks) + loop_tick = os.startTimer(0.05) + log._debug("loop clock started") else - log._debug("running without networking") + log._warning("booted in a degraded state, awaiting peripheral connections...") end - - -- loop clock (10Hz, 2 ticks) - loop_tick = os.startTimer(0.05) - log._debug("loop clock started") -else - log._warning("booted in a degraded state, awaiting peripheral connections...") end +-- initialize PLC +init() + -- event loop while true do local event, param1, param2, param3, param4, param5 = os.pullEventRaw() @@ -156,7 +159,7 @@ while true do end -- determine if we are still in a degraded state - if not networked or get_device("modem") not nil then + if not networked or get_device("modem") ~= nil then plc_state.degraded = false end elseif networked and device.type == "modem" then @@ -170,14 +173,14 @@ while true do plc_state.no_modem = false -- determine if we are still in a degraded state - if ppm.get_device("fissionReactor") not nil then + if ppm.get_device("fissionReactor") ~= nil then plc_state.degraded = false end end if not plc_state.init_ok and not plc_state.degraded then plc_state.init_ok = false - goto init + init() end end From c47f45ea46a7d4203477eb40bff75a0cf3babf51 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Apr 2022 17:25:56 -0400 Subject: [PATCH 045/587] fixed bad function references in ISS --- reactor-plc/plc.lua | 68 ++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 12d018e..99d2bef 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -15,20 +15,49 @@ function iss_init(reactor) self.reactor = reactor end + local damage_critical = function () + return self.reactor.getDamagePercent() >= 100 + end + + local excess_heated_coolant = function () + return self.reactor.getHeatedCoolantNeeded() == 0 + end + + local excess_waste = function () + return self.reactor.getWasteNeeded() == 0 + end + + local high_temp = function () + -- mekanism: MAX_DAMAGE_TEMPERATURE = 1_200 + return self.reactor.getTemperature() >= 1200 + end + + local insufficient_fuel = function () + return self.reactor.getFuel() == 0 + end + + local no_coolant = function () + return self.reactor.getCoolantFilledPercentage() < 2 + end + + local timed_out = function () + return self.timed_out + end + local check = function () local status = "ok" local was_tripped = self.tripped -- check system states in order of severity - if self.damage_critical() then + if damage_critical() then status = "dmg_crit" - elseif self.high_temp() then + elseif high_temp() then status = "high_temp" - elseif self.excess_heated_coolant() then + elseif excess_heated_coolant() then status = "heated_coolant_backup" - elseif self.excess_waste() then + elseif excess_waste() then status = "full_waste" - elseif self.insufficient_fuel() then + elseif insufficient_fuel() then status = "no_fuel" elseif self.tripped then status = self.trip_cause @@ -83,35 +112,6 @@ function iss_init(reactor) } end end - - local damage_critical = function () - return self.reactor.getDamagePercent() >= 100 - end - - local excess_heated_coolant = function () - return self.reactor.getHeatedCoolantNeeded() == 0 - end - - local excess_waste = function () - return self.reactor.getWasteNeeded() == 0 - end - - local high_temp = function () - -- mekanism: MAX_DAMAGE_TEMPERATURE = 1_200 - return self.reactor.getTemperature() >= 1200 - end - - local insufficient_fuel = function () - return self.reactor.getFuel() == 0 - end - - local no_coolant = function () - return self.reactor.getCoolantFilledPercentage() < 2 - end - - local timed_out = function () - return self.timed_out - end return { reconnect_reactor = reconnect_reactor, From 895750ea141a7445721adb3d92817af5a916cbac Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Apr 2022 17:28:19 -0400 Subject: [PATCH 046/587] print, println, println_ts --- scada-common/util.lua | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/scada-common/util.lua b/scada-common/util.lua index f6fd611..1d4e11c 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -1,14 +1,33 @@ +-- we are overwriting 'print' so save it first +local _print = print + +-- print +function print(message) + term.write(message) +end + +-- print line +function println(message) + _print(message) +end + -- timestamped print function print_ts(message) term.write(os.date("[%H:%M:%S] ") .. message) end +-- timestamped print line +function println_ts(message) + _print(os.date("[%H:%M:%S] ") .. message) +end + + -- ComputerCraft OS Timer based Watchdog -- triggers a timer event if not fed within 'timeout' seconds function new_watchdog(timeout) local self = { _timeout = timeout, - _wd_timer = os.startTimer(_timeout) + _wd_timer = os.startTimer(timeout) } local get_timer = function () From ba1dd1b50e266ff5a5b40facbb4b1447869dd2a0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Apr 2022 17:29:27 -0400 Subject: [PATCH 047/587] #4 PLC degraded start and reconnects appear to be working now, fixed prints, and bugfixes to PPM --- reactor-plc/startup.lua | 76 +++++++++++++++++++++++++---------------- scada-common/ppm.lua | 32 +++++++++++------ 2 files changed, 68 insertions(+), 40 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 669f777..f6e79be 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -10,11 +10,17 @@ os.loadAPI("scada-common/comms.lua") os.loadAPI("config.lua") os.loadAPI("plc.lua") -local R_PLC_VERSION = "alpha-v0.1.1" +local R_PLC_VERSION = "alpha-v0.1.2" +local print = util.print +local println = util.println local print_ts = util.print_ts +local println_ts = util.println_ts -print(">> Reactor PLC " .. R_PLC_VERSION .. " <<") +log._info("========================================") +log._info("BOOTING reactor-plc.startup " .. R_PLC_VERSION) +log._info("========================================") +println(">> Reactor PLC " .. R_PLC_VERSION .. " <<") -- mount connected devices ppm.mount_all() @@ -34,21 +40,21 @@ local plc_state = { -- we need a reactor and a modem if reactor == nil then - print_ts("Fission reactor not found. Running in a degraded state...\n"); + println("boot> fission reactor not found"); log._warning("no reactor on startup") + plc_state.init_ok = false plc_state.degraded = true plc_state.no_reactor = true end if networked and modem == nil then + println("boot> modem not found") + log._warning("no modem on startup") + if reactor ~= nil then - print_ts("No modem found. Disabling reactor and running in a degraded state...\n") reactor.scram() - else - print_ts("No modem found. Running in a degraded state...\n") end - log._warning("no modem on startup") plc_state.init_ok = false plc_state.degraded = true plc_state.no_modem = true @@ -81,7 +87,7 @@ function init() log._debug("comms init") -- comms watchdog, 3 second timeout - conn_watchdog = watchdog.new_watchdog(3) + conn_watchdog = util.new_watchdog(3) log._debug("conn watchdog started") else log._debug("running without networking") @@ -90,7 +96,10 @@ function init() -- loop clock (10Hz, 2 ticks) loop_tick = os.startTimer(0.05) log._debug("loop clock started") + + println("boot> completed"); else + println("boot> system in degraded state, awaiting devices...") log._warning("booted in a degraded state, awaiting peripheral connections...") end end @@ -107,7 +116,7 @@ while true do -- if it disconnected, isPowered will return nil (and error logs will get spammed at 10Hz, so disable reporting) -- in that case, SCRAM won't be called until it reconnects (this is the expected use of this check) ppm.disable_reporting() - if plc_state.degraded or (plc_state.scram and reactor.isPowered()) then + if plc_state.scram and reactor.getStatus() then reactor.scram() end ppm.enable_reporting() @@ -118,12 +127,13 @@ while true do local device = ppm.handle_unmount(param1) if device.type == "fissionReactor" then - print_ts("reactor disconnected!\n") + println_ts("reactor disconnected!") log._error("reactor disconnected!") plc_state.no_reactor = true + plc_state.degraded = true -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_PERI_DC) ? elseif networked and device.type == "modem" then - print_ts("modem disconnected!\n") + println_ts("modem disconnected!") log._error("modem disconnected!") plc_state.no_modem = true @@ -131,44 +141,48 @@ while true do -- try to scram reactor if it is still connected plc_state.scram = true if reactor.scram() then - print_ts("successful reactor SCRAM\n") + println_ts("successful reactor SCRAM") else - print_ts("failed reactor SCRAM\n") + println_ts("failed reactor SCRAM") end end plc_state.degraded = true end elseif event == "peripheral" then - local device = ppm.mount(param1) + local type, device = ppm.mount(param1) - if device.type == "fissionReactor" then + if type == "fissionReactor" then -- reconnected reactor - plc_state.scram = true - device.scram() + reactor = device - print_ts("reactor reconnected.\n") + plc_state.scram = true + reactor.scram() + + println_ts("reactor reconnected.") log._info("reactor reconnected.") plc_state.no_reactor = false if plc_state.init_ok then - iss.reconnect_reactor(device) + iss.reconnect_reactor(reactor) if networked then - plc_comms.reconnect_reactor(device) + plc_comms.reconnect_reactor(reactor) end end -- determine if we are still in a degraded state - if not networked or get_device("modem") ~= nil then + if not networked or ppm.get_device("modem") ~= nil then plc_state.degraded = false end - elseif networked and device.type == "modem" then + elseif networked and type == "modem" then -- reconnected modem + modem = device + if plc_state.init_ok then - plc_comms.reconnect_modem(device) + plc_comms.reconnect_modem(modem) end - print_ts("modem reconnected.\n") + println_ts("modem reconnected.") log._info("modem reconnected.") plc_state.no_modem = false @@ -179,7 +193,7 @@ while true do end if not plc_state.init_ok and not plc_state.degraded then - plc_state.init_ok = false + plc_state.init_ok = true init() end end @@ -191,6 +205,8 @@ while true do if networked and iss_first then plc_comms.send_iss_alarm(iss_status) end + elseif plc_state.init_ok then + reactor.scram() end -- handle event @@ -223,20 +239,22 @@ while true do plc_state.scram = true plc_comms.unlink() iss.trip_timeout() - print_ts("[alert] server timeout, reactor disabled\n") + println_ts("server timeout, reactor disabled") + log._warning("server timeout, reactor disabled") elseif event == "terminate" then -- safe exit if plc_state.init_ok then plc_state.scram = true if reactor.scram() then - print_ts("[alert] exiting, reactor disabled\n") + println_ts("reactor disabled") else -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_LOST_CONTROL) ? - print_ts("[alert] exiting, reactor failed to disable\n") + println_ts("exiting, reactor failed to disable") end end -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_SHUTDOWN) ? - print_ts("[alert] exited") + println_ts("exited") + log._info("terminate requested, exiting") return end end diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 9924007..6799d4a 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -53,7 +53,13 @@ function mount_all() for i = 1, #ifaces do local pm_dev = peripheral.wrap(ifaces[i]) peri_init(pm_dev) - self.mounts[ifaces[i]] = { peripheral.getType(ifaces[i]), pm_dev } + + self.mounts[ifaces[i]] = { + type = peripheral.getType(ifaces[i]), + dev = pm_dev + } + + log._debug("PPM: found a " .. self.mounts[ifaces[i]].type) end if #ifaces == 0 then @@ -62,24 +68,28 @@ function mount_all() end -- mount a particular device -function mount(name) +function mount(iface) local ifaces = peripheral.getNames() local pm_dev = nil + local type = nil for i = 1, #ifaces do - if name == peripheral.getType(ifaces[i]) then - pm_dev = peripheral.wrap(ifaces[i]) + if iface == ifaces[i] then + log._debug("PPM: mount(" .. iface .. ") -> found a " .. peripheral.getType(iface)) + + type = peripheral.getType(iface) + pm_dev = peripheral.wrap(iface) peri_init(pm_dev) - self.mounts[ifaces[i]] = { - type = peripheral.getType(ifaces[i]), - device = pm_dev + self.mounts[iface] = { + type = peripheral.getType(iface), + dev = pm_dev } break end end - return pm_dev + return type, pm_dev end -- handle peripheral_detach event @@ -105,7 +115,7 @@ end -- get a mounted peripheral by side/interface function get_periph(iface) - return self.mounts[iface].device + return self.mounts[iface].dev end -- get a mounted peripheral type by side/interface @@ -119,7 +129,7 @@ function get_device(name) for side, data in pairs(self.mounts) do if data.type == name then - device = data.device + device = data.dev break end end @@ -133,7 +143,7 @@ function list_monitors() for side, data in pairs(self.mounts) do if data.type == "monitor" then - monitors[side] = data.device + table.insert(monitors, data.dev) end end From 7e7e98ff6b09ca1234007aedd4270b825437bf29 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Apr 2022 17:58:23 -0400 Subject: [PATCH 048/587] #11 standalone de-asserts SCRAM and resets ISS before check, added prints to ISS, fixed non-networked mode related bugs, cleaned up ISS check call in startup --- reactor-plc/plc.lua | 10 +++++- reactor-plc/startup.lua | 71 ++++++++++++++++++++++++++--------------- 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 99d2bef..484a5c7 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -50,14 +50,19 @@ function iss_init(reactor) -- check system states in order of severity if damage_critical() then + log._warning("ISS: damage critical!") status = "dmg_crit" elseif high_temp() then + log._warning("ISS: high temperature!") status = "high_temp" elseif excess_heated_coolant() then + log._warning("ISS: heated coolant backup!") status = "heated_coolant_backup" elseif excess_waste() then + log._warning("ISS: full waste!") status = "full_waste" elseif insufficient_fuel() then + log._warning("ISS: no fuel!") status = "no_fuel" elseif self.tripped then status = self.trip_cause @@ -66,6 +71,7 @@ function iss_init(reactor) end if status ~= "ok" then + log._warning("ISS: reactor SCRAM") self.tripped = true self.trip_cause = status self.reactor.scram() @@ -378,7 +384,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) end -- handle an RPLC packet - local handle_packet = function (packet) + local handle_packet = function (packet, plc_state) if packet ~= nil then if packet.scada_frame.protocol() == PROTOCOLS.RPLC then if self.linked then @@ -424,10 +430,12 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) elseif packet.type == RPLC_TYPES.MEK_SCRAM then -- disable the reactor self.scrammed = true + plc_state.scram = true _send_ack(packet.type, self.reactor.scram()) elseif packet.type == RPLC_TYPES.MEK_ENABLE then -- enable the reactor self.scrammed = false + plc_state.scram = false _send_ack(packet.type, self.reactor.activate()) elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then -- set the burn rate diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index f6e79be..02da651 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -10,7 +10,7 @@ os.loadAPI("scada-common/comms.lua") os.loadAPI("config.lua") os.loadAPI("plc.lua") -local R_PLC_VERSION = "alpha-v0.1.2" +local R_PLC_VERSION = "alpha-v0.1.3" local print = util.print local println = util.println @@ -32,7 +32,7 @@ local networked = config.NETWORKED local plc_state = { init_ok = true, - scram = true, -- treated as latching e-stop, all conditions must be OK to set false + scram = true, degraded = false, no_reactor = false, no_modem = false @@ -69,7 +69,7 @@ local conn_watchdog = nil local UPDATE_TICKS = 3 local LINK_TICKS = 20 -local loop_tick = nil +local loop_clock = nil local ticks_to_update = LINK_TICKS -- start by linking function init() @@ -94,7 +94,7 @@ function init() end -- loop clock (10Hz, 2 ticks) - loop_tick = os.startTimer(0.05) + loop_clock = os.startTimer(0.05) log._debug("loop clock started") println("boot> completed"); @@ -198,43 +198,62 @@ while true do end end - -- check safety (SCRAM occurs if tripped) - if not plc_state.degraded then - local iss_tripped, iss_status, iss_first = iss.check() - plc_state.scram = plc_state.scram or iss_tripped - if networked and iss_first then - plc_comms.send_iss_alarm(iss_status) + -- ISS + if plc_state.init_ok then + -- if we are in standalone mode, continuously reset ISS + -- ISS will trip again if there are faults, but if it isn't cleared, the user can't re-enable + if not networked then + plc_state.scram = false + iss.reset() + end + + -- check safety (SCRAM occurs if tripped) + if not plc_state.degraded then + local iss_tripped, iss_status, iss_first = iss.check() + plc_state.scram = plc_state.scram or iss_tripped + + if iss_first then + println_ts("[ISS] reactor shutdown, safety tripped: " .. iss_status) + if networked then + plc_comms.send_iss_alarm(iss_status) + end + end + else + reactor.scram() end - elseif plc_state.init_ok then - reactor.scram() end -- handle event - if event == "timer" and param1 == loop_tick and networked and not plc_state.no_modem then + if event == "timer" and param1 == loop_clock then -- basic event tick, send updated data if it is time (~3.33Hz) -- iss was already checked (that's the main reason for this tick rate) - ticks_to_update = ticks_to_update - 1 + if networked and not plc_state.no_modem then + ticks_to_update = ticks_to_update - 1 - if plc_comms.is_linked() then - if ticks_to_update <= 0 then - plc_comms.send_status(iss_tripped) - ticks_to_update = UPDATE_TICKS - end - else - if ticks_to_update <= 0 then - plc_comms.send_link_req() - ticks_to_update = LINK_TICKS + if plc_comms.is_linked() then + if ticks_to_update <= 0 then + plc_comms.send_status(iss_tripped) + ticks_to_update = UPDATE_TICKS + end + else + if ticks_to_update <= 0 then + plc_comms.send_link_req() + ticks_to_update = LINK_TICKS + end end end + + -- start next clock timer + loop_clock = os.startTimer(0.05) elseif event == "modem_message" and networked and not plc_state.no_modem then -- got a packet -- feed the watchdog first so it doesn't uhh...eat our packets conn_watchdog.feed() + -- handle the packet (plc_state passed to allow clearing SCRAM flag) local packet = plc_comms.parse_packet(p1, p2, p3, p4, p5) - plc_comms.handle_packet(packet) - plc_state.scram = plc_state.scram or plc_comms.is_scrammed() - elseif event == "timer" and param1 == conn_watchdog.get_timer() and networked then + plc_comms.handle_packet(packet, plc_state) + elseif event == "timer" and networked and param1 == conn_watchdog.get_timer() then -- haven't heard from server recently? shutdown reactor plc_state.scram = true plc_comms.unlink() From 03f9284f30ebb2a2c410dd8d4f2deb7edd4ab27c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 7 Apr 2022 11:12:41 -0400 Subject: [PATCH 049/587] #13 ISS tolerant of failed PPM calls, added comments --- reactor-plc/plc.lua | 69 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 484a5c7..c5f1bf2 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -2,7 +2,7 @@ -- Internal Safety System -- identifies dangerous states and SCRAMs reactor if warranted --- autonomous from main control +-- autonomous from main SCADA supervisor/coordinator control function iss_init(reactor) local self = { reactor = reactor, @@ -11,39 +11,90 @@ function iss_init(reactor) trip_cause = "" } + -- re-link a reactor after a peripheral re-connect local reconnect_reactor = function (reactor) self.reactor = reactor end + -- check for critical damage local damage_critical = function () - return self.reactor.getDamagePercent() >= 100 + local damage_percent = self.reactor.getDamagePercent() + if damage_percent == nil then + -- lost the peripheral or terminated, handled later + log._error("ISS: failed to check reactor damage") + return false + else + return damage_percent >= 100 + end end + -- check for heated coolant backup local excess_heated_coolant = function () - return self.reactor.getHeatedCoolantNeeded() == 0 + local hc_needed = self.reactor.getHeatedCoolantNeeded() + if hc_needed == nil then + -- lost the peripheral or terminated, handled later + log._error("ISS: failed to check reactor heated coolant level") + return false + else + return hc_needed == 0 + end end + -- check for excess waste local excess_waste = function () - return self.reactor.getWasteNeeded() == 0 + local w_needed = self.reactor.getWasteNeeded() + if w_needed == nil then + -- lost the peripheral or terminated, handled later + log._error("ISS: failed to check reactor waste level") + return false + else + return w_needed == 0 + end end + -- check if the reactor is at a critically high temperature local high_temp = function () -- mekanism: MAX_DAMAGE_TEMPERATURE = 1_200 - return self.reactor.getTemperature() >= 1200 + local temp = self.reactor.getTemperature() + if temp == nil then + -- lost the peripheral or terminated, handled later + log._error("ISS: failed to check reactor temperature") + return false + else + return temp >= 1200 + end end + -- check if there is no fuel local insufficient_fuel = function () - return self.reactor.getFuel() == 0 + local fuel = self.reactor.getFuel() + if fuel == nil then + -- lost the peripheral or terminated, handled later + log._error("ISS: failed to check reactor fuel level") + return false + else + return fuel == 0 + end end + -- check if there is no coolant local no_coolant = function () - return self.reactor.getCoolantFilledPercentage() < 2 + local coolant_filled = self.reactor.getCoolantFilledPercentage() + if coolant_filled == nil then + -- lost the peripheral or terminated, handled later + log._error("ISS: failed to check reactor coolant level") + return false + else + return coolant_filled < 2 + end end + -- if PLC timed out local timed_out = function () return self.timed_out end + -- check all safety conditions local check = function () local status = "ok" local was_tripped = self.tripped @@ -70,6 +121,7 @@ function iss_init(reactor) self.tripped = false end + -- if a new trip occured... if status ~= "ok" then log._warning("ISS: reactor SCRAM") self.tripped = true @@ -82,6 +134,7 @@ function iss_init(reactor) return self.tripped, status, first_trip end + -- report a PLC comms timeout local trip_timeout = function () self.tripped = false self.trip_cause = "timeout" @@ -89,12 +142,14 @@ function iss_init(reactor) self.reactor.scram() end + -- reset the ISS local reset = function () self.timed_out = false self.tripped = false self.trip_cause = "" end + -- get the ISS status local status = function (named) if named then return { From b085baf91bc64673a38e846916013d7639429084 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 7 Apr 2022 11:44:17 -0400 Subject: [PATCH 050/587] #12 specifically get wireless modems --- reactor-plc/startup.lua | 65 ++++++++++++++++++++++++----------------- rtu/startup.lua | 5 ++-- scada-common/ppm.lua | 56 ++++++++++++++++++++++++++++++----- 3 files changed, 89 insertions(+), 37 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 02da651..bd7f49e 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -25,8 +25,8 @@ println(">> Reactor PLC " .. R_PLC_VERSION .. " <<") -- mount connected devices ppm.mount_all() -local reactor = ppm.get_device("fissionReactor") -local modem = ppm.get_device("modem") +local reactor = ppm.get_fission_reactor() +local modem = ppm.get_wireless_modem() local networked = config.NETWORKED @@ -48,8 +48,8 @@ if reactor == nil then plc_state.no_reactor = true end if networked and modem == nil then - println("boot> modem not found") - log._warning("no modem on startup") + println("boot> wireless modem not found") + log._warning("no wireless modem on startup") if reactor ~= nil then reactor.scram() @@ -133,21 +133,28 @@ while true do plc_state.degraded = true -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_PERI_DC) ? elseif networked and device.type == "modem" then - println_ts("modem disconnected!") - log._error("modem disconnected!") - plc_state.no_modem = true + -- we only care if this is our wireless modem + if device.dev == modem then + println_ts("wireless modem disconnected!") + log._error("comms modem disconnected!") + plc_state.no_modem = true - if plc_state.init_ok then - -- try to scram reactor if it is still connected - plc_state.scram = true - if reactor.scram() then - println_ts("successful reactor SCRAM") - else - println_ts("failed reactor SCRAM") + if plc_state.init_ok then + -- try to scram reactor if it is still connected + plc_state.scram = true + if reactor.scram() then + println_ts("successful reactor SCRAM") + log._error("successful reactor SCRAM") + else + println_ts("failed reactor SCRAM") + log._error("failed reactor SCRAM") + end end - end - plc_state.degraded = true + plc_state.degraded = true + else + log._warning("non-comms modem disconnected") + end end elseif event == "peripheral" then local type, device = ppm.mount(param1) @@ -175,20 +182,24 @@ while true do plc_state.degraded = false end elseif networked and type == "modem" then - -- reconnected modem - modem = device + if device.isWireless() then + -- reconnected modem + modem = device - if plc_state.init_ok then - plc_comms.reconnect_modem(modem) - end + if plc_state.init_ok then + plc_comms.reconnect_modem(modem) + end - println_ts("modem reconnected.") - log._info("modem reconnected.") - plc_state.no_modem = false + println_ts("wireless modem reconnected.") + log._info("comms modem reconnected.") + plc_state.no_modem = false - -- determine if we are still in a degraded state - if ppm.get_device("fissionReactor") ~= nil then - plc_state.degraded = false + -- determine if we are still in a degraded state + if ppm.get_device("fissionReactor") ~= nil then + plc_state.degraded = false + end + else + log._info("wired modem reconnected.") end end diff --git a/rtu/startup.lua b/rtu/startup.lua index b287a53..e9635f3 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -30,9 +30,10 @@ local linked = false ppm.mount_all() -- get modem -local modem = ppm.get_device("modem") +local modem = ppm.get_wireless_modem() if modem == nil then - print("No modem found, exiting...") + println("boot> wireless modem not found") + log._warning("no wireless modem on startup") return end diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 6799d4a..1775b77 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -4,6 +4,10 @@ -- Protected Peripheral Manager -- +---------------------------- +-- PRIVATE DATA/FUNCTIONS -- +---------------------------- + local self = { mounts = {}, mute = false @@ -34,6 +38,12 @@ local peri_init = function (device) end end +---------------------- +-- PUBLIC FUNCTIONS -- +---------------------- + +-- REPORTING -- + -- silence error prints function disable_reporting() self.mute = true @@ -44,6 +54,8 @@ function enable_reporting() self.mute = false end +-- MOUNTING -- + -- mount all available peripherals (clears mounts first) function mount_all() local ifaces = peripheral.getNames() @@ -103,6 +115,8 @@ function handle_unmount(iface) return lost_dev end +-- GENERAL ACCESSORS -- + -- list all available peripherals function list_avail() return peripheral.getNames() @@ -123,7 +137,20 @@ function get_type(iface) return self.mounts[iface].type end --- get a mounted peripheral by type +-- get all mounted peripherals by type +function get_all_devices(name) + local devices = {} + + for side, data in pairs(self.mounts) do + if data.type == name then + table.insert(devices, data.dev) + end + end + + return devices +end + +-- get a mounted peripheral by type (if multiple, returns the first) function get_device(name) local device = nil @@ -137,15 +164,28 @@ function get_device(name) return device end --- list all connected monitors -function list_monitors() - local monitors = {} +-- SPECIFIC DEVICE ACCESSORS -- - for side, data in pairs(self.mounts) do - if data.type == "monitor" then - table.insert(monitors, data.dev) +-- get the fission reactor (if multiple, returns the first) +function get_fission_reactor() + return get_device("fissionReactor") +end + +-- get the wireless modem (if multiple, returns the first) +function get_wireless_modem() + local w_modem = nil + + for side, device in pairs(self.mounts) do + if device.type == "modem" and device.dev.isWireless() then + w_modem = device.dev + break end end - return monitors + return w_modem +end + +-- list all connected monitors +function list_monitors() + return get_all_devices("monitor") end From 28b1c03e039acaab0a1ed3d807cfd8fe59d4421a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 7 Apr 2022 11:45:01 -0400 Subject: [PATCH 051/587] upped version --- reactor-plc/startup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index bd7f49e..b5b23e0 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -10,7 +10,7 @@ os.loadAPI("scada-common/comms.lua") os.loadAPI("config.lua") os.loadAPI("plc.lua") -local R_PLC_VERSION = "alpha-v0.1.3" +local R_PLC_VERSION = "alpha-v0.1.4" local print = util.print local println = util.println From 203d868aeb1017625a128583499661f8d6e979c5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 11 Apr 2022 11:08:46 -0400 Subject: [PATCH 052/587] RTU print fixes, config fixes, comms init fixes and moved modem open --- rtu/config.lua | 9 ++++++--- rtu/rtu.lua | 5 +++++ rtu/startup.lua | 35 +++++++++++++++++++---------------- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/rtu/config.lua b/rtu/config.lua index b06305e..cd9e70c 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -1,7 +1,10 @@ -- #REQUIRES rsio.lua -SCADA_SERVER = 16000 - +-- port to send packets TO server +SERVER_PORT = 16000 +-- port to listen to incoming packets FROM server +LISTEN_PORT = 15001 +-- RTU peripheral devices (named: side/network device name) RTU_DEVICES = { { name = "boiler_0", @@ -14,7 +17,7 @@ RTU_DEVICES = { for_reactor = 1 } } - +-- RTU redstone interface definitions RTU_REDSTONE = { { for_reactor = 1, diff --git a/rtu/rtu.lua b/rtu/rtu.lua index a80e3f1..6bbb1d1 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -98,6 +98,11 @@ function rtu_comms(modem, local_port, server_port) l_port = local_port } + -- open modem + if not self.modem.isOpen(self.l_port) then + self.modem.open(self.l_port) + end + -- PRIVATE FUNCTIONS -- local _send = function (protocol, msg) diff --git a/rtu/startup.lua b/rtu/startup.lua index e9635f3..bde8732 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -17,7 +17,15 @@ os.loadAPI("dev/turbine.lua") local RTU_VERSION = "alpha-v0.1.0" +local print = util.print +local println = util.println local print_ts = util.print_ts +local println_ts = util.println_ts + +log._info("========================================") +log._info("BOOTING rtu.startup " .. RTU_VERSION) +log._info("========================================") +println(">> RTU " .. RTU_VERSION .. " <<") ---------------------------------------- -- startup @@ -37,12 +45,7 @@ if modem == nil then return end --- start comms -if not modem.isOpen(config.LISTEN_PORT) then - modem.open(config.LISTEN_PORT) -end - -local rtu_comms = rtu.rtu_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) +local rtu_comms = rtu.rtu_comms(modem, config.LISTEN_PORT, config.SERVER_PORT) ---------------------------------------- -- determine configuration @@ -69,8 +72,8 @@ for reactor_idx = 1, #RTU_REDSTONE do end if ~valid then - local message = "invalid redstone configuration at index " .. i - print_ts(message .. "\n") + local message = "init> invalid redstone definition at index " .. i + println_ts(message) log._warning(message) else -- link redstone in RTU @@ -85,13 +88,13 @@ for reactor_idx = 1, #RTU_REDSTONE do rs_rtu.link_ao(config.channel, config.side) else -- should be unreachable code, we already validated channels - log._error("fell through if chain attempting to identify IO mode", true) + log._error("init> fell through if chain attempting to identify IO mode", true) break end table.insert(capabilities, config.channel) - log._debug("startup> linked redstone " .. #capabilities .. ": " .. rsio.to_string(config.channel) .. " (" .. config.side .. + log._debug("init> linked redstone " .. #capabilities .. ": " .. rsio.to_string(config.channel) .. " (" .. config.side .. ") for reactor " .. RTU_REDSTONE[reactor_idx].for_reactor) end end @@ -112,8 +115,8 @@ for i = 1, #RTU_DEVICES do local device = ppm.get_periph(RTU_DEVICES[i].name) if device == nil then - local message = "'" .. RTU_DEVICES[i].name .. "' not found" - print_ts(message .. "\n") + local message = "init> '" .. RTU_DEVICES[i].name .. "' not found" + println_ts(message) log._warning(message) else local type = ppm.get_type(RTU_DEVICES[i].name) @@ -133,8 +136,8 @@ for i = 1, #RTU_DEVICES do rtu_type = "imatrix" rtu_iface = imatrix_rtu(device) else - local message = "device '" .. RTU_DEVICES[i].name .. "' is not a known type (" .. type .. ")" - print_ts(message .. "\n") + local message = "init> device '" .. RTU_DEVICES[i].name .. "' is not a known type (" .. type .. ")" + println_ts(message) log._warning(message) end @@ -149,7 +152,7 @@ for i = 1, #RTU_DEVICES do modbus_io = modbus_init(rtu_iface) }) - log._debug("startup> initialized RTU unit #" .. #units .. ": " .. RTU_DEVICES[i].name .. " (" .. rtu_type .. ") [" .. + log._debug("init> initialized RTU unit #" .. #units .. ": " .. RTU_DEVICES[i].name .. " (" .. rtu_type .. ") [" .. RTU_DEVICES[i].index .. "] for reactor " .. RTU_DEVICES[i].for_reactor) end end @@ -188,7 +191,7 @@ while true do -- if linked, stop sending advertisements linked = link_ref.linked elseif event == "terminate" then - print_ts("Exiting...\n") + println_ts("exiting...") return end end From 945b761fc2e06f36f31d6627dc9607bb6cdbebde Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 11 Apr 2022 17:27:57 -0400 Subject: [PATCH 053/587] #2 RTU handle disconnects/reconnects --- rtu/startup.lua | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/rtu/startup.lua b/rtu/startup.lua index bde8732..2caa50d 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -170,9 +170,43 @@ while true do local event, param1, param2, param3, param4, param5 = os.pullEventRaw() if event == "peripheral_detach" then - ppm.handle_unmount(param1) + -- handle loss of a device + local device = ppm.handle_unmount(param1) - -- todo: handle unit change + for i = 1, #units do + -- find disconnected device + if units[i].device == device then + -- we are going to let the PPM prevent crashes + -- return fault flags/codes to MODBUS queries + local unit = units[i] + println_ts("lost the " .. unit.type .. " on interface " .. unit.name) + end + end + elseif event == "peripheral" then + -- relink lost peripheral to correct unit entry + local type, device = ppm.mount(param1) + + for i = 1, #units do + local unit = units[i] + + -- find disconnected device to reconnect + if unit.name == param1 then + -- found, re-link + unit.device = device + + if unit.type == "boiler" then + unit.rtu = boiler_rtu(device) + elseif unit.type == "turbine" then + unit.rtu = turbine_rtu(device) + elseif unit.type == "imatrix" then + unit.rtu = imatrix_rtu(device) + end + + unit.modbus_io = modbus_init(unit.rtu) + + println_ts("reconnected the " .. unit.type .. " on interface " .. unit.name) + end + end elseif event == "timer" and param1 == loop_tick then -- period tick, if we are linked send heartbeat, if not send advertisement if linked then From 2a21d7d0be1c8ec4d8b14a200efce10022fbeac8 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 17 Apr 2022 21:12:25 -0400 Subject: [PATCH 054/587] #14, #15 ppm access fault handling, report modbus exceptions, handle ppm faults in PLC/RTU code --- reactor-plc/plc.lua | 65 +++++++++++------- reactor-plc/startup.lua | 6 +- rtu/rtu.lua | 42 +++++++++--- scada-common/modbus.lua | 147 ++++++++++++++++++++++++++++++++-------- scada-common/ppm.lua | 35 +++++++++- 5 files changed, 227 insertions(+), 68 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index c5f1bf2..6843303 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -1,4 +1,5 @@ -- #REQUIRES comms.lua +-- #REQUIRES ppm.lua -- Internal Safety System -- identifies dangerous states and SCRAMs reactor if warranted @@ -19,7 +20,7 @@ function iss_init(reactor) -- check for critical damage local damage_critical = function () local damage_percent = self.reactor.getDamagePercent() - if damage_percent == nil then + if damage_percent == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later log._error("ISS: failed to check reactor damage") return false @@ -31,7 +32,7 @@ function iss_init(reactor) -- check for heated coolant backup local excess_heated_coolant = function () local hc_needed = self.reactor.getHeatedCoolantNeeded() - if hc_needed == nil then + if hc_needed == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later log._error("ISS: failed to check reactor heated coolant level") return false @@ -43,7 +44,7 @@ function iss_init(reactor) -- check for excess waste local excess_waste = function () local w_needed = self.reactor.getWasteNeeded() - if w_needed == nil then + if w_needed == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later log._error("ISS: failed to check reactor waste level") return false @@ -56,7 +57,7 @@ function iss_init(reactor) local high_temp = function () -- mekanism: MAX_DAMAGE_TEMPERATURE = 1_200 local temp = self.reactor.getTemperature() - if temp == nil then + if temp == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later log._error("ISS: failed to check reactor temperature") return false @@ -68,7 +69,7 @@ function iss_init(reactor) -- check if there is no fuel local insufficient_fuel = function () local fuel = self.reactor.getFuel() - if fuel == nil then + if fuel == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later log._error("ISS: failed to check reactor fuel level") return false @@ -80,7 +81,7 @@ function iss_init(reactor) -- check if there is no coolant local no_coolant = function () local coolant_filled = self.reactor.getCoolantFilledPercentage() - if coolant_filled == nil then + if coolant_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later log._error("ISS: failed to check reactor coolant level") return false @@ -126,7 +127,9 @@ function iss_init(reactor) log._warning("ISS: reactor SCRAM") self.tripped = true self.trip_cause = status - self.reactor.scram() + if self.reactor.scram() == ppm.ACCESS_FAULT then + log._error("ISS: failed reactor SCRAM") + end end local first_trip = not was_tripped and self.tripped @@ -293,6 +296,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- variable reactor status information, excluding heating rate local _reactor_status = function () + ppm.clear_fault() return { status = self.reactor.getStatus(), burn_rate = self.reactor.getBurnRate(), @@ -316,17 +320,19 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) hcool_amnt = self.reactor.getHeatedCoolant()['amount'], hcool_need = self.reactor.getHeatedCoolantNeeded(), hcool_fill = self.reactor.getHeatedCoolantFilledPercentage() - } + }, ppm.faulted() end local _update_status_cache = function () - local status = _reactor_status() + local status, faulted = _reactor_status() local changed = false - for key, value in pairs(status) do - if value ~= self.status_cache[key] then - changed = true - break + if not faulted then + for key, value in pairs(status) do + if value ~= self.status_cache[key] then + changed = true + break + end end end @@ -362,6 +368,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- send structure properties (these should not change) -- (server will cache these) local _send_struct = function () + ppm.clear_fault() local mek_data = { heat_cap = self.reactor.getHeatCapacity(), fuel_asm = self.reactor.getFuelAssemblies(), @@ -373,13 +380,17 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) max_burn = self.reactor.getMaxBurnRate() } - local struct_packet = { - id = self.id, - type = RPLC_TYPES.MEK_STRUCT, - mek_data = mek_data - } + if not faulted then + local struct_packet = { + id = self.id, + type = RPLC_TYPES.MEK_STRUCT, + mek_data = mek_data + } - _send(struct_packet) + _send(struct_packet) + else + log._error("failed to send structure: PPM fault") + end end local _send_iss_status = function () @@ -407,6 +418,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- reconnect a newly connected reactor local reconnect_reactor = function (reactor) self.reactor = reactor + _update_status_cache() end -- parse an RPLC packet @@ -486,25 +498,25 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- disable the reactor self.scrammed = true plc_state.scram = true - _send_ack(packet.type, self.reactor.scram()) + _send_ack(packet.type, self.reactor.scram() == ppm.ACCESS_OK) elseif packet.type == RPLC_TYPES.MEK_ENABLE then -- enable the reactor self.scrammed = false plc_state.scram = false - _send_ack(packet.type, self.reactor.activate()) + _send_ack(packet.type, self.reactor.activate() == ppm.ACCESS_OK) elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then -- set the burn rate local burn_rate = packet.data[1] local max_burn_rate = self.reactor.getMaxBurnRate() local success = false - if max_burn_rate ~= nil then + if max_burn_rate ~= ppm.ACCESS_FAULT then if burn_rate > 0 and burn_rate <= max_burn_rate then success = self.reactor.setBurnRate(burn_rate) end end - _send_ack(packet.type, success) + _send_ack(packet.type, success == ppm.ACCESS_OK) elseif packet.type == RPLC_TYPES.ISS_GET then -- get the ISS status _send_iss_status(iss.status()) @@ -540,9 +552,10 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) self.linked = link_ack == RPLC_LINKING.ALLOW else - log._("discarding non-link packet before linked") + log._debug("discarding non-link packet before linked") end elseif packet.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then + -- todo end end end @@ -559,7 +572,8 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- send live status information -- overridden : if ISS force disabled reactor - local send_status = function (overridden) + -- degraded : if PLC status is degraded + local send_status = function (overridden, degraded) local mek_data = nil if _update_status_cache() then @@ -572,6 +586,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) timestamp = os.time(), control_state = not self.scrammed, overridden = overridden, + degraded = degraded, heating_rate = self.reactor.getHeatingRate(), mek_data = mek_data } diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index b5b23e0..869be2f 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -10,7 +10,7 @@ os.loadAPI("scada-common/comms.lua") os.loadAPI("config.lua") os.loadAPI("plc.lua") -local R_PLC_VERSION = "alpha-v0.1.4" +local R_PLC_VERSION = "alpha-v0.1.5" local print = util.print local println = util.println @@ -243,7 +243,7 @@ while true do if plc_comms.is_linked() then if ticks_to_update <= 0 then - plc_comms.send_status(iss_tripped) + plc_comms.send_status(iss_tripped, plc_state.degraded) ticks_to_update = UPDATE_TICKS end else @@ -275,7 +275,7 @@ while true do -- safe exit if plc_state.init_ok then plc_state.scram = true - if reactor.scram() then + if reactor.scram() ~= ppm.ACCESS_FAULT then println_ts("reactor disabled") else -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_LOST_CONTROL) ? diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 6bbb1d1..176b37d 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -1,5 +1,6 @@ -- #REQUIRES comms.lua -- #REQUIRES modbus.lua +-- #REQUIRES ppm.lua function rtu_init() local self = { @@ -10,68 +11,91 @@ function rtu_init() io_count_cache = { 0, 0, 0, 0 } } - local __count_io = function () + local _count_io = function () self.io_count_cache = { #self.discrete_inputs, #self.coils, #self.input_regs, #self.holding_regs } end + -- return : IO count table local io_count = function () return self.io_count_cache[0], self.io_count_cache[1], self.io_count_cache[2], self.io_count_cache[3] end -- discrete inputs: single bit read-only + -- return : count of discrete inputs local connect_di = function (f) table.insert(self.discrete_inputs, f) - __count_io() + _count_io() return #self.discrete_inputs end + -- return : value, access fault local read_di = function (di_addr) - return self.discrete_inputs[di_addr]() + ppm.clear_fault() + local value = self.discrete_inputs[di_addr]() + return value, ppm.is_faulted() end -- coils: single bit read-write + -- return : count of coils local connect_coil = function (f_read, f_write) table.insert(self.coils, { read = f_read, write = f_write }) - __count_io() + _count_io() return #self.coils end + -- return : value, access fault local read_coil = function (coil_addr) - return self.coils[coil_addr].read() + ppm.clear_fault() + local value = self.coils[coil_addr].read() + return value, ppm.is_faulted() end + -- return : access fault local write_coil = function (coil_addr, value) + ppm.clear_fault() self.coils[coil_addr].write(value) + return ppm.is_faulted() end -- input registers: multi-bit read-only + -- return : count of input registers local connect_input_reg = function (f) table.insert(self.input_regs, f) - __count_io() + _count_io() return #self.input_regs end + -- return : value, access fault local read_input_reg = function (reg_addr) - return self.coils[reg_addr]() + ppm.clear_fault() + local value = self.coils[reg_addr]() + return value, ppm.is_faulted() end -- holding registers: multi-bit read-write + -- return : count of holding registers local connect_holding_reg = function (f_read, f_write) table.insert(self.holding_regs, { read = f_read, write = f_write }) - __count_io() + _count_io() return #self.holding_regs end + -- return : value, access fault local read_holding_reg = function (reg_addr) - return self.coils[reg_addr].read() + ppm.clear_fault() + local value = self.coils[reg_addr].read() + return value, ppm.is_faulted() end + -- return : access fault local write_holding_reg = function (reg_addr, value) + ppm.clear_fault() self.coils[reg_addr].write(value) + return ppm.is_faulted() end return { diff --git a/scada-common/modbus.lua b/scada-common/modbus.lua index 9d2899d..dc73e64 100644 --- a/scada-common/modbus.lua +++ b/scada-common/modbus.lua @@ -11,6 +11,20 @@ local MODBUS_FCODE = { ERROR_FLAG = 0x80 } +-- modbus exception codes +local MODBUS_EXCODE = { + ILLEGAL_FUNCTION = 0x01, + ILLEGAL_DATA_ADDR = 0x02, + ILLEGAL_DATA_VALUE = 0x03, + SERVER_DEVICE_FAIL = 0x04, + ACKNOWLEDGE = 0x05, + SERVER_DEVICE_BUSY = 0x06, + NEG_ACKNOWLEDGE = 0x07, + MEMORY_PARITY_ERROR = 0x08, + GATEWAY_PATH_UNAVAILABLE = 0x0A, + GATEWAY_TARGET_TIMEOUT = 0x0B +} + -- new modbus comms handler object function modbus_init(rtu_dev) local self = { @@ -19,13 +33,22 @@ function modbus_init(rtu_dev) local _1_read_coils = function (c_addr_start, count) local readings = {} + local access_fault = false local _, coils, _, _ = self.rtu.io_count() local return_ok = (c_addr_start + count) <= coils if return_ok then for i = 0, (count - 1) do - readings[i] = self.rtu.read_coil(c_addr_start + i) + readings[i], access_fault = self.rtu.read_coil(c_addr_start + i) + + if access_fault then + return_ok = false + readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL + break + end end + else + readings = MODBUS_EXCODE.ILLEGAL_DATA_ADDR end return return_ok, readings @@ -33,13 +56,22 @@ function modbus_init(rtu_dev) local _2_read_discrete_inputs = function (di_addr_start, count) local readings = {} + local access_fault = false local discrete_inputs, _, _, _ = self.rtu.io_count() local return_ok = (di_addr_start + count) <= discrete_inputs if return_ok then for i = 0, (count - 1) do - readings[i] = self.rtu.read_di(di_addr_start + i) + readings[i], access_fault = self.rtu.read_di(di_addr_start + i) + + if access_fault then + return_ok = false + readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL + break + end end + else + readings = MODBUS_EXCODE.ILLEGAL_DATA_ADDR end return return_ok, readings @@ -47,13 +79,22 @@ function modbus_init(rtu_dev) local _3_read_multiple_holding_registers = function (hr_addr_start, count) local readings = {} + local access_fault = false local _, _, _, hold_regs = self.rtu.io_count() local return_ok = (hr_addr_start + count) <= hold_regs if return_ok then for i = 0, (count - 1) do - readings[i] = self.rtu.read_holding_reg(hr_addr_start + i) + readings[i], access_fault = self.rtu.read_holding_reg(hr_addr_start + i) + + if access_fault then + return_ok = false + readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL + break + end end + else + readings = MODBUS_EXCODE.ILLEGAL_DATA_ADDR end return return_ok, readings @@ -61,93 +102,132 @@ function modbus_init(rtu_dev) local _4_read_input_registers = function (ir_addr_start, count) local readings = {} + local access_fault = false local _, _, input_regs, _ = self.rtu.io_count() local return_ok = (ir_addr_start + count) <= input_regs if return_ok then for i = 0, (count - 1) do - readings[i] = self.rtu.read_input_reg(ir_addr_start + i) + readings[i], access_fault = self.rtu.read_input_reg(ir_addr_start + i) + + if access_fault then + return_ok = false + readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL + break + end end + else + readings = MODBUS_EXCODE.ILLEGAL_DATA_ADDR end return return_ok, readings end local _5_write_single_coil = function (c_addr, value) + local response = nil local _, coils, _, _ = self.rtu.io_count() local return_ok = c_addr <= coils - + if return_ok then - self.rtu.write_coil(c_addr, value) + local access_fault = self.rtu.write_coil(c_addr, value) + + if access_fault then + return_ok = false + readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL + end + else + response = MODBUS_EXCODE.ILLEGAL_DATA_ADDR end - return return_ok + return return_ok, response end local _6_write_single_holding_register = function (hr_addr, value) + local response = nil local _, _, _, hold_regs = self.rtu.io_count() local return_ok = hr_addr <= hold_regs if return_ok then - self.rtu.write_holding_reg(hr_addr, value) + local access_fault = self.rtu.write_holding_reg(hr_addr, value) + + if access_fault then + return_ok = false + readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL + end end return return_ok end local _15_write_multiple_coils = function (c_addr_start, values) + local response = nil local _, coils, _, _ = self.rtu.io_count() local count = #values local return_ok = (c_addr_start + count) <= coils if return_ok then for i = 0, (count - 1) do - self.rtu.write_coil(c_addr_start + i, values[i + 1]) + local access_fault = self.rtu.write_coil(c_addr_start + i, values[i + 1]) + + if access_fault then + return_ok = false + readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL + break + end end end - return return_ok + return return_ok, response end local _16_write_multiple_holding_registers = function (hr_addr_start, values) + local response = nil local _, _, _, hold_regs = self.rtu.io_count() local count = #values local return_ok = (hr_addr_start + count) <= hold_regs if return_ok then for i = 0, (count - 1) do - self.rtu.write_coil(hr_addr_start + i, values[i + 1]) + local access_fault = self.rtu.write_coil(hr_addr_start + i, values[i + 1]) + + if access_fault then + return_ok = false + readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL + break + end end end - return return_ok + return return_ok, response end local handle_packet = function (packet) local return_code = true - local readings = nil + local response = nil + local reply = packet if #packet.data == 2 then -- handle by function code if packet.func_code == MODBUS_FCODE.READ_COILS then - return_code, readings = _1_read_coils(packet.data[1], packet.data[2]) + return_code, response = _1_read_coils(packet.data[1], packet.data[2]) elseif packet.func_code == MODBUS_FCODE.READ_DISCRETE_INPUTS then - return_code, readings = _2_read_discrete_inputs(packet.data[1], packet.data[2]) + return_code, response = _2_read_discrete_inputs(packet.data[1], packet.data[2]) elseif packet.func_code == MODBUS_FCODE.READ_MUL_HOLD_REGS then - return_code, readings = _3_read_multiple_holding_registers(packet.data[1], packet.data[2]) + return_code, response = _3_read_multiple_holding_registers(packet.data[1], packet.data[2]) elseif packet.func_code == MODBUS_FCODE.READ_INPUT_REGISTERS then - return_code, readings = _4_read_input_registers(packet.data[1], packet.data[2]) + return_code, response = _4_read_input_registers(packet.data[1], packet.data[2]) elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_COIL then - return_code = _5_write_single_coil(packet.data[1], packet.data[2]) + return_code, response = _5_write_single_coil(packet.data[1], packet.data[2]) elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_HOLD_REG then - return_code = _6_write_single_holding_register(packet.data[1], packet.data[2]) + return_code, response = _6_write_single_holding_register(packet.data[1], packet.data[2]) elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_COILS then - return_code = _15_write_multiple_coils(packet.data[1], packet.data[2]) + return_code, response = _15_write_multiple_coils(packet.data[1], packet.data[2]) elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_HOLD_REGS then - return_code = _16_write_multiple_holding_registers(packet.data[1], packet.data[2]) + return_code, response = _16_write_multiple_holding_registers(packet.data[1], packet.data[2]) else -- unknown function return_code = false + response = MODBUS_EXCODE.ILLEGAL_FUNCTION end else -- invalid length @@ -155,19 +235,28 @@ function modbus_init(rtu_dev) end if return_code then - -- response (default is to echo back) - response = packet - if readings ~= nil then - response.length = #readings - response.data = readings + -- default is to echo back + if type(response) == "table" then + reply.length = #response + reply.data = response end else -- echo back with error flag - response = packet - response.func_code = bit.bor(packet.func_code, ERROR_FLAG) + reply.func_code = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) + + if type(response) == "nil" then + reply.length = 0 + reply.data = {} + elseif type(response) == "number" then + reply.length = 1 + reply.data = { response } + elseif type(response) == "table" then + reply.length = #response + reply.data = response + end end - return return_code, response + return return_code, reply end return { diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 1775b77..3eb3977 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -4,12 +4,17 @@ -- Protected Peripheral Manager -- +ACCESS_OK = true +ACCESS_FAULT = nil + ---------------------------- -- PRIVATE DATA/FUNCTIONS -- ---------------------------- local self = { mounts = {}, + auto_cf = false, + faulted = false, mute = false } @@ -21,18 +26,22 @@ local peri_init = function (device) local status, result = pcall(func, ...) if status then + -- auto fault clear + if self.auto_cf then self.faulted = false end + -- assume nil is only for functions with no return, so return status if result == nil then - return true + return ACCESS_OK else return result end else -- function failed + self.faulted = true if not mute then log._error("PPM: protected " .. key .. "() -> " .. result) end - return nil + return ACCESS_FAULT end end end @@ -54,6 +63,28 @@ function enable_reporting() self.mute = false end +-- FAULT MEMORY -- + +-- enable automatically clearing fault flag +function enable_afc() + self.auto_cf = true +end + +-- disable automatically clearing fault flag +function disable_afc() + self.auto_cf = false +end + +-- check fault flag +function is_faulted() + return self.faulted +end + +-- clear fault flag +function clear_fault() + self.faulted = false +end + -- MOUNTING -- -- mount all available peripherals (clears mounts first) From ba5975f29b1a94ee1ca570d703baecd9b22e3b50 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 17 Apr 2022 22:34:31 -0400 Subject: [PATCH 055/587] RTU config fixed missing rsio reference --- rtu/config.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rtu/config.lua b/rtu/config.lua index cd9e70c..71804a4 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -23,19 +23,19 @@ RTU_REDSTONE = { for_reactor = 1, io = { { - channel = RS_IO.WASTE_PO, + channel = rsio.RS_IO.WASTE_PO, side = "top", bundled_color = colors.blue, for_reactor = 1 }, { - channel = RS_IO.WASTE_PU, + channel = rsio.RS_IO.WASTE_PU, side = "top", bundled_color = colors.cyan, for_reactor = 1 }, { - channel = RS_IO.WASTE_AM, + channel = rsio.RS_IO.WASTE_AM, side = "top", bundled_color = colors.purple, for_reactor = 1 From 0c5eb77cbae0bc6bfa4970657baa50462ac1472f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 17 Apr 2022 22:36:18 -0400 Subject: [PATCH 056/587] fixed some bugs with RTU startup referencing external data/functions --- rtu/startup.lua | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/rtu/startup.lua b/rtu/startup.lua index 2caa50d..59bc477 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -11,17 +11,23 @@ os.loadAPI("scada-common/rsio.lua") os.loadAPI("config.lua") os.loadAPI("rtu.lua") +os.loadAPI("dev/redstone.lua") os.loadAPI("dev/boiler.lua") os.loadAPI("dev/imatrix.lua") os.loadAPI("dev/turbine.lua") -local RTU_VERSION = "alpha-v0.1.0" +local RTU_VERSION = "alpha-v0.1.1" local print = util.print local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts +local redstone_rtu = redstone.redstone_rtu +local boiler_rtu = boiler.boiler_rtu +local turbine_rtu = turbine.turbine_rtu +local imatrix_rtu = imatrix.imatrix_rtu + log._info("========================================") log._info("BOOTING rtu.startup " .. RTU_VERSION) log._info("========================================") @@ -51,10 +57,13 @@ local rtu_comms = rtu.rtu_comms(modem, config.LISTEN_PORT, config.SERVER_PORT) -- determine configuration ---------------------------------------- +local rtu_redstone = config.RTU_REDSTONE +local rtu_devices = config.RTU_DEVICES + -- redstone interfaces -for reactor_idx = 1, #RTU_REDSTONE do +for reactor_idx = 1, #rtu_redstone do local rs_rtu = redstone_rtu() - local io_table = RTU_REDSTONE[reactor_idx].io + local io_table = rtu_redstone[reactor_idx].io local capabilities = {} @@ -71,7 +80,7 @@ for reactor_idx = 1, #RTU_REDSTONE do end end - if ~valid then + if not valid then local message = "init> invalid redstone definition at index " .. i println_ts(message) log._warning(message) @@ -95,7 +104,7 @@ for reactor_idx = 1, #RTU_REDSTONE do table.insert(capabilities, config.channel) log._debug("init> linked redstone " .. #capabilities .. ": " .. rsio.to_string(config.channel) .. " (" .. config.side .. - ") for reactor " .. RTU_REDSTONE[reactor_idx].for_reactor) + ") for reactor " .. rtu_redstone[reactor_idx].for_reactor) end end @@ -103,7 +112,7 @@ for reactor_idx = 1, #RTU_REDSTONE do name = "redstone_io", type = "redstone", index = 1, - reactor = RTU_REDSTONE[reactor_idx].for_reactor, + reactor = rtu_redstone[reactor_idx].for_reactor, device = capabilities, -- use device field for redstone channels rtu = rs_rtu, modbus_io = modbus_init(rs_rtu) @@ -111,15 +120,15 @@ for reactor_idx = 1, #RTU_REDSTONE do end -- mounted peripherals -for i = 1, #RTU_DEVICES do - local device = ppm.get_periph(RTU_DEVICES[i].name) +for i = 1, #rtu_devices do + local device = ppm.get_periph(rtu_devices[i].name) if device == nil then - local message = "init> '" .. RTU_DEVICES[i].name .. "' not found" + local message = "init> '" .. rtu_devices[i].name .. "' not found" println_ts(message) log._warning(message) else - local type = ppm.get_type(RTU_DEVICES[i].name) + local type = ppm.get_type(rtu_devices[i].name) local rtu_iface = nil local rtu_type = "" @@ -136,24 +145,24 @@ for i = 1, #RTU_DEVICES do rtu_type = "imatrix" rtu_iface = imatrix_rtu(device) else - local message = "init> device '" .. RTU_DEVICES[i].name .. "' is not a known type (" .. type .. ")" + local message = "init> device '" .. rtu_devices[i].name .. "' is not a known type (" .. type .. ")" println_ts(message) log._warning(message) end if rtu_iface ~= nil then table.insert(units, { - name = RTU_DEVICES[i].name, + name = rtu_devices[i].name, type = rtu_type, - index = RTU_DEVICES[i].index, - reactor = RTU_DEVICES[i].for_reactor, + index = rtu_devices[i].index, + reactor = rtu_devices[i].for_reactor, device = device, rtu = rtu_iface, modbus_io = modbus_init(rtu_iface) }) - log._debug("init> initialized RTU unit #" .. #units .. ": " .. RTU_DEVICES[i].name .. " (" .. rtu_type .. ") [" .. - RTU_DEVICES[i].index .. "] for reactor " .. RTU_DEVICES[i].for_reactor) + log._debug("init> initialized RTU unit #" .. #units .. ": " .. rtu_devices[i].name .. " (" .. rtu_type .. ") [" .. + rtu_devices[i].index .. "] for reactor " .. rtu_devices[i].for_reactor) end end end From 6d6953d7958541494f0ce356418868a91bffb8b6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 17 Apr 2022 22:37:09 -0400 Subject: [PATCH 057/587] RTU device inits now correctly use rtu.rtu_init() not rtu_init() --- rtu/dev/boiler.lua | 2 +- rtu/dev/imatrix.lua | 2 +- rtu/dev/redstone.lua | 2 +- rtu/dev/turbine.lua | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rtu/dev/boiler.lua b/rtu/dev/boiler.lua index d76107a..b20cd0f 100644 --- a/rtu/dev/boiler.lua +++ b/rtu/dev/boiler.lua @@ -2,7 +2,7 @@ function boiler_rtu(boiler) local self = { - rtu = rtu_init(), + rtu = rtu.rtu_init(), boiler = boiler } diff --git a/rtu/dev/imatrix.lua b/rtu/dev/imatrix.lua index f87bdbf..39c647b 100644 --- a/rtu/dev/imatrix.lua +++ b/rtu/dev/imatrix.lua @@ -2,7 +2,7 @@ function imatrix_rtu(imatrix) local self = { - rtu = rtu_init(), + rtu = rtu.rtu_init(), imatrix = imatrix } diff --git a/rtu/dev/redstone.lua b/rtu/dev/redstone.lua index da2f7e2..aafa47d 100644 --- a/rtu/dev/redstone.lua +++ b/rtu/dev/redstone.lua @@ -7,7 +7,7 @@ local digital_is_active = rsio.digital_is_active function redstone_rtu() local self = { - rtu = rtu_init() + rtu = rtu.rtu_init() } local rtu_interface = function () diff --git a/rtu/dev/turbine.lua b/rtu/dev/turbine.lua index 4ecc156..d5a6920 100644 --- a/rtu/dev/turbine.lua +++ b/rtu/dev/turbine.lua @@ -2,7 +2,7 @@ function turbine_rtu(turbine) local self = { - rtu = rtu_init(), + rtu = rtu.rtu_init(), turbine = turbine } From a6e1134dc3cf482aed6dc4b04f43b146ebd2ef3e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 18 Apr 2022 00:10:47 -0400 Subject: [PATCH 058/587] changed modbus init function name, fixed bugs with RTU startup, improved PPM debug prints --- rtu/startup.lua | 10 +++++----- scada-common/modbus.lua | 2 +- scada-common/ppm.lua | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rtu/startup.lua b/rtu/startup.lua index 59bc477..6a21753 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -72,9 +72,9 @@ for reactor_idx = 1, #rtu_redstone do local config = io_table[i] -- verify configuration - if is_valid_channel(config.channel) and is_valid_side(config.side) then + if rsio.is_valid_channel(config.channel) and rsio.is_valid_side(config.side) then if config.bundled_color then - valid = is_color(config.bundled_color) + valid = rsio.is_color(config.bundled_color) else valid = true end @@ -115,7 +115,7 @@ for reactor_idx = 1, #rtu_redstone do reactor = rtu_redstone[reactor_idx].for_reactor, device = capabilities, -- use device field for redstone channels rtu = rs_rtu, - modbus_io = modbus_init(rs_rtu) + modbus_io = modbus.new(rs_rtu) }) end @@ -158,7 +158,7 @@ for i = 1, #rtu_devices do reactor = rtu_devices[i].for_reactor, device = device, rtu = rtu_iface, - modbus_io = modbus_init(rtu_iface) + modbus_io = modbus.new(rtu_iface) }) log._debug("init> initialized RTU unit #" .. #units .. ": " .. rtu_devices[i].name .. " (" .. rtu_type .. ") [" .. @@ -211,7 +211,7 @@ while true do unit.rtu = imatrix_rtu(device) end - unit.modbus_io = modbus_init(unit.rtu) + unit.modbus_io = modbus.new(unit.rtu) println_ts("reconnected the " .. unit.type .. " on interface " .. unit.name) end diff --git a/scada-common/modbus.lua b/scada-common/modbus.lua index dc73e64..b192cc9 100644 --- a/scada-common/modbus.lua +++ b/scada-common/modbus.lua @@ -26,7 +26,7 @@ local MODBUS_EXCODE = { } -- new modbus comms handler object -function modbus_init(rtu_dev) +function new(rtu_dev) local self = { rtu = rtu_dev } diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 3eb3977..f11f0e2 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -102,7 +102,7 @@ function mount_all() dev = pm_dev } - log._debug("PPM: found a " .. self.mounts[ifaces[i]].type) + log._debug("PPM: found a " .. self.mounts[ifaces[i]].type .. " (" .. ifaces[i] .. ")") end if #ifaces == 0 then From 7d9a664d38cfcc44a6fc74b7d2f86a95c769d5c2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 18 Apr 2022 00:11:23 -0400 Subject: [PATCH 059/587] rsio bugfixes --- scada-common/rsio.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 9dbe9ef..f56a8a4 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -86,12 +86,14 @@ function to_string(channel) end function is_valid_channel(channel) - return channel > 0 and channel <= A_T_FLOW_RATE + return channel ~= nil and channel > 0 and channel <= RS_IO.A_T_FLOW_RATE end function is_valid_side(side) - for _, s in pairs(redstone.getSides()) do - if s == side then return true end + if side ~= nil then + for _, s in pairs(rs.getSides()) do + if s == side then return true end + end end return false end From 377cf8e6fc3c2c271123b982501cbf088d43e2ed Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 18 Apr 2022 09:35:08 -0400 Subject: [PATCH 060/587] scope fixes, load comms api, debug prints --- rtu/rtu.lua | 12 ++++++++---- rtu/startup.lua | 10 ++++++++-- scada-common/modbus.lua | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 176b37d..8b83bfd 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -2,6 +2,10 @@ -- #REQUIRES modbus.lua -- #REQUIRES ppm.lua +local PROTOCOLS = comms.PROTOCOLS +local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES +local RTU_ADVERT_TYPES = comms.RTU_ADVERT_TYPES + function rtu_init() local self = { discrete_inputs = {}, @@ -130,7 +134,7 @@ function rtu_comms(modem, local_port, server_port) -- PRIVATE FUNCTIONS -- local _send = function (protocol, msg) - local packet = scada_packet() + local packet = comms.scada_packet() packet.make(self.seq_num, protocol, msg) self.modem.transmit(self.s_port, self.l_port, packet.raw()) self.seq_num = self.seq_num + 1 @@ -141,7 +145,7 @@ function rtu_comms(modem, local_port, server_port) -- parse a MODBUS/SCADA packet local parse_packet = function(side, sender, reply_to, message, distance) local pkt = nil - local s_pkt = scada_packet() + local s_pkt = comms.scada_packet() -- parse packet as generic SCADA packet s_pkt.recieve(side, sender, reply_to, message, distance) @@ -149,13 +153,13 @@ function rtu_comms(modem, local_port, server_port) if s_pkt.is_valid() then -- get as MODBUS TCP packet if s_pkt.protocol() == PROTOCOLS.MODBUS_TCP then - local m_pkt = modbus_packet() + local m_pkt = modbus.packet() if m_pkt.decode(s_pkt) then pkt = m_pkt.get() end -- get as SCADA management packet elseif s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then - local mgmt_pkt = mgmt_packet() + local mgmt_pkt = comms.mgmt_packet() if mgmt_pkt.decode(s_pkt) then pkt = mgmt_packet.get() end diff --git a/rtu/startup.lua b/rtu/startup.lua index 6a21753..232cf8f 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -5,6 +5,7 @@ os.loadAPI("scada-common/log.lua") os.loadAPI("scada-common/util.lua") os.loadAPI("scada-common/ppm.lua") +os.loadAPI("scada-common/comms.lua") os.loadAPI("scada-common/modbus.lua") os.loadAPI("scada-common/rsio.lua") @@ -16,7 +17,7 @@ os.loadAPI("dev/boiler.lua") os.loadAPI("dev/imatrix.lua") os.loadAPI("dev/turbine.lua") -local RTU_VERSION = "alpha-v0.1.1" +local RTU_VERSION = "alpha-v0.1.2" local print = util.print local println = util.println @@ -67,6 +68,8 @@ for reactor_idx = 1, #rtu_redstone do local capabilities = {} + log._debug("init> starting redstone RTU I/O linking for reactor " .. rtu_redstone[reactor_idx].for_reactor .. "...") + for i = 1, #io_table do local valid = false local config = io_table[i] @@ -81,7 +84,8 @@ for reactor_idx = 1, #rtu_redstone do end if not valid then - local message = "init> invalid redstone definition at index " .. i + local message = "init> invalid redstone definition at index " .. i .. " in definition block #" .. reactor_idx .. + " (for reactor " .. rtu_redstone[reactor_idx].for_reactor .. ")" println_ts(message) log._warning(message) else @@ -117,6 +121,8 @@ for reactor_idx = 1, #rtu_redstone do rtu = rs_rtu, modbus_io = modbus.new(rs_rtu) }) + + log._debug("init> initialized RTU unit #" .. #units .. ": redstone_io (redstone) [1] for reactor " .. rtu_redstone[reactor_idx].for_reactor) end -- mounted peripherals diff --git a/scada-common/modbus.lua b/scada-common/modbus.lua index b192cc9..8a4137f 100644 --- a/scada-common/modbus.lua +++ b/scada-common/modbus.lua @@ -264,7 +264,7 @@ function new(rtu_dev) } end -function modbus_packet() +function packet() local self = { frame = nil, txn_id = txn_id, From 2278469a8b4f92871dea7bea4fb7382e16464bce Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 18 Apr 2022 10:09:44 -0400 Subject: [PATCH 061/587] refactored RTU devices --- rtu/dev/{boiler.lua => boiler_rtu.lua} | 2 +- rtu/dev/{imatrix.lua => imatrix_rtu.lua} | 2 +- rtu/dev/{redstone.lua => redstone_rtu.lua} | 2 +- rtu/dev/{turbine.lua => turbine_rtu.lua} | 2 +- rtu/startup.lua | 29 +++++++++------------- 5 files changed, 16 insertions(+), 21 deletions(-) rename rtu/dev/{boiler.lua => boiler_rtu.lua} (98%) rename rtu/dev/{imatrix.lua => imatrix_rtu.lua} (95%) rename rtu/dev/{redstone.lua => redstone_rtu.lua} (98%) rename rtu/dev/{turbine.lua => turbine_rtu.lua} (97%) diff --git a/rtu/dev/boiler.lua b/rtu/dev/boiler_rtu.lua similarity index 98% rename from rtu/dev/boiler.lua rename to rtu/dev/boiler_rtu.lua index b20cd0f..861a34f 100644 --- a/rtu/dev/boiler.lua +++ b/rtu/dev/boiler_rtu.lua @@ -1,6 +1,6 @@ -- #REQUIRES rtu.lua -function boiler_rtu(boiler) +function new(boiler) local self = { rtu = rtu.rtu_init(), boiler = boiler diff --git a/rtu/dev/imatrix.lua b/rtu/dev/imatrix_rtu.lua similarity index 95% rename from rtu/dev/imatrix.lua rename to rtu/dev/imatrix_rtu.lua index 39c647b..529a1f8 100644 --- a/rtu/dev/imatrix.lua +++ b/rtu/dev/imatrix_rtu.lua @@ -1,6 +1,6 @@ -- #REQUIRES rtu.lua -function imatrix_rtu(imatrix) +function new(imatrix) local self = { rtu = rtu.rtu_init(), imatrix = imatrix diff --git a/rtu/dev/redstone.lua b/rtu/dev/redstone_rtu.lua similarity index 98% rename from rtu/dev/redstone.lua rename to rtu/dev/redstone_rtu.lua index aafa47d..d81cebb 100644 --- a/rtu/dev/redstone.lua +++ b/rtu/dev/redstone_rtu.lua @@ -5,7 +5,7 @@ local digital_read = rsio.digital_read local digital_is_active = rsio.digital_is_active -function redstone_rtu() +function new() local self = { rtu = rtu.rtu_init() } diff --git a/rtu/dev/turbine.lua b/rtu/dev/turbine_rtu.lua similarity index 97% rename from rtu/dev/turbine.lua rename to rtu/dev/turbine_rtu.lua index d5a6920..7584270 100644 --- a/rtu/dev/turbine.lua +++ b/rtu/dev/turbine_rtu.lua @@ -1,6 +1,6 @@ -- #REQUIRES rtu.lua -function turbine_rtu(turbine) +function new(turbine) local self = { rtu = rtu.rtu_init(), turbine = turbine diff --git a/rtu/startup.lua b/rtu/startup.lua index 232cf8f..03d4873 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -12,23 +12,18 @@ os.loadAPI("scada-common/rsio.lua") os.loadAPI("config.lua") os.loadAPI("rtu.lua") -os.loadAPI("dev/redstone.lua") -os.loadAPI("dev/boiler.lua") -os.loadAPI("dev/imatrix.lua") -os.loadAPI("dev/turbine.lua") +os.loadAPI("dev/redstone_rtu.lua") +os.loadAPI("dev/boiler_rtu.lua") +os.loadAPI("dev/imatrix_rtu.lua") +os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.1.2" +local RTU_VERSION = "alpha-v0.1.3" local print = util.print local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts -local redstone_rtu = redstone.redstone_rtu -local boiler_rtu = boiler.boiler_rtu -local turbine_rtu = turbine.turbine_rtu -local imatrix_rtu = imatrix.imatrix_rtu - log._info("========================================") log._info("BOOTING rtu.startup " .. RTU_VERSION) log._info("========================================") @@ -63,7 +58,7 @@ local rtu_devices = config.RTU_DEVICES -- redstone interfaces for reactor_idx = 1, #rtu_redstone do - local rs_rtu = redstone_rtu() + local rs_rtu = redstone_rtu.new() local io_table = rtu_redstone[reactor_idx].io local capabilities = {} @@ -141,15 +136,15 @@ for i = 1, #rtu_devices do if type == "boiler" then -- boiler multiblock rtu_type = "boiler" - rtu_iface = boiler_rtu(device) + rtu_iface = boiler_rtu.new(device) elseif type == "turbine" then -- turbine multiblock rtu_type = "turbine" - rtu_iface = turbine_rtu(device) + rtu_iface = turbine_rtu.new(device) elseif type == "mekanismMachine" then -- assumed to be an induction matrix multiblock rtu_type = "imatrix" - rtu_iface = imatrix_rtu(device) + rtu_iface = imatrix_rtu.new(device) else local message = "init> device '" .. rtu_devices[i].name .. "' is not a known type (" .. type .. ")" println_ts(message) @@ -210,11 +205,11 @@ while true do unit.device = device if unit.type == "boiler" then - unit.rtu = boiler_rtu(device) + unit.rtu = boiler_rtu.new(device) elseif unit.type == "turbine" then - unit.rtu = turbine_rtu(device) + unit.rtu = turbine_rtu.new(device) elseif unit.type == "imatrix" then - unit.rtu = imatrix_rtu(device) + unit.rtu = imatrix_rtu.new(device) end unit.modbus_io = modbus.new(unit.rtu) From 91079eeb780b9f89482a9985fa1cb8d22d8d636b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 18 Apr 2022 10:21:29 -0400 Subject: [PATCH 062/587] fixed RTU comms bad function calls, fixed loop clock, changed terminate logic/prints --- rtu/rtu.lua | 6 +++--- rtu/startup.lua | 16 +++++++++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 8b83bfd..efa31f2 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -180,7 +180,7 @@ function rtu_comms(modem, local_port, server_port) if packet.unit_id <= #units then local unit = units[packet.unit_id] local return_code, response = unit.modbus_io.handle_packet(packet) - _send(response, PROTOCOLS.MODBUS_TCP) + _send(PROTOCOLS.MODBUS_TCP, response) if not return_code then log._warning("MODBUS operation failed") @@ -249,7 +249,7 @@ function rtu_comms(modem, local_port, server_port) end end - _send(advertisement, PROTOCOLS.SCADA_MGMT) + _send(PROTOCOLS.SCADA_MGMT, advertisement) end local send_heartbeat = function () @@ -257,7 +257,7 @@ function rtu_comms(modem, local_port, server_port) type = SCADA_MGMT_TYPES.RTU_HEARTBEAT } - _send(heartbeat, PROTOCOLS.SCADA_MGMT) + _send(PROTOCOLS.SCADA_MGMT, heartbeat) end return { diff --git a/rtu/startup.lua b/rtu/startup.lua index 03d4873..8f03a35 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -17,7 +17,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.1.3" +local RTU_VERSION = "alpha-v0.1.4" local print = util.print local println = util.println @@ -173,7 +173,7 @@ end ---------------------------------------- -- advertisement/heartbeat clock (every 2 seconds) -local loop_tick = os.startTimer(2) +local loop_clock = os.startTimer(2) -- event loop while true do @@ -217,7 +217,7 @@ while true do println_ts("reconnected the " .. unit.type .. " on interface " .. unit.name) end end - elseif event == "timer" and param1 == loop_tick then + elseif event == "timer" and param1 == loop_clock then -- period tick, if we are linked send heartbeat, if not send advertisement if linked then rtu_comms.send_heartbeat() @@ -225,6 +225,9 @@ while true do -- advertise units rtu_comms.send_advertisement(units) end + + -- start next clock timer + loop_clock = os.startTimer(2) elseif event == "modem_message" then -- got a packet local link_ref = { linked = linked } @@ -235,7 +238,10 @@ while true do -- if linked, stop sending advertisements linked = link_ref.linked elseif event == "terminate" then - println_ts("exiting...") - return + log._warning("terminate requested, exiting...") + break end end + +println_ts("exited") +log._info("exited") From 6a5e0243be60f363b4a88590a6e916d291ef05d0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 18 Apr 2022 10:31:24 -0400 Subject: [PATCH 063/587] catch terminations that are caught by PPM --- reactor-plc/startup.lua | 19 +++++++++++++------ rtu/startup.lua | 7 +++++-- scada-common/ppm.lua | 14 ++++++++++++++ 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 869be2f..5692899 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -10,7 +10,7 @@ os.loadAPI("scada-common/comms.lua") os.loadAPI("config.lua") os.loadAPI("plc.lua") -local R_PLC_VERSION = "alpha-v0.1.5" +local R_PLC_VERSION = "alpha-v0.1.6" local print = util.print local println = util.println @@ -271,7 +271,12 @@ while true do iss.trip_timeout() println_ts("server timeout, reactor disabled") log._warning("server timeout, reactor disabled") - elseif event == "terminate" then + end + + -- check for termination request + if event == "terminate" or ppm.should_terminate() then + log._warning("terminate requested, exiting...") + -- safe exit if plc_state.init_ok then plc_state.scram = true @@ -282,9 +287,11 @@ while true do println_ts("exiting, reactor failed to disable") end end - -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_SHUTDOWN) ? - println_ts("exited") - log._info("terminate requested, exiting") - return + + break end end + +-- send an alarm: plc_comms.send_alarm(ALARMS.PLC_SHUTDOWN) ? +println_ts("exited") +log._info("exited") diff --git a/rtu/startup.lua b/rtu/startup.lua index 8f03a35..0e2bfaa 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -17,7 +17,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.1.4" +local RTU_VERSION = "alpha-v0.1.5" local print = util.print local println = util.println @@ -237,7 +237,10 @@ while true do -- if linked, stop sending advertisements linked = link_ref.linked - elseif event == "terminate" then + end + + -- check for termination request + if event == "terminate" or ppm.should_terminate() then log._warning("terminate requested, exiting...") break end diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index f11f0e2..fd92b23 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -15,6 +15,7 @@ local self = { mounts = {}, auto_cf = false, faulted = false, + terminate = false, mute = false } @@ -38,9 +39,15 @@ local peri_init = function (device) else -- function failed self.faulted = true + if not mute then log._error("PPM: protected " .. key .. "() -> " .. result) end + + if result == "Terminated" then + self.terminate = true + end + return ACCESS_FAULT end end @@ -85,6 +92,13 @@ function clear_fault() self.faulted = false end +-- TERMINATION -- + +-- if a caught error was a termination request +function should_terminate() + return self.terminate +end + -- MOUNTING -- -- mount all available peripherals (clears mounts first) From b89724ad59326bb2ceda4bb597c424c64e0fd634 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 18 Apr 2022 10:49:05 -0400 Subject: [PATCH 064/587] version updates --- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 5692899..4d13e74 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -10,7 +10,7 @@ os.loadAPI("scada-common/comms.lua") os.loadAPI("config.lua") os.loadAPI("plc.lua") -local R_PLC_VERSION = "alpha-v0.1.6" +local R_PLC_VERSION = "alpha-v0.2.0" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index 0e2bfaa..576dd47 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -17,7 +17,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.1.5" +local RTU_VERSION = "alpha-v0.2.0" local print = util.print local println = util.println From 3da7b74cfba0c4a61633e0906196907440949e09 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 18 Apr 2022 11:07:16 -0400 Subject: [PATCH 065/587] initial base supervisor code --- supervisor/startup.lua | 78 +++++++++++++++++++++++++++------------ supervisor/supervisor.lua | 27 ++++++++++++++ 2 files changed, 81 insertions(+), 24 deletions(-) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 4ff56a9..cec58c0 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -7,22 +7,29 @@ os.loadAPI("scada-common/util.lua") os.loadAPI("scada-common/ppm.lua") os.loadAPI("scada-common/comms.lua") -os.loadAPI("supervisor/config.lua") -os.loadAPI("supervisor/supervisor.lua") +os.loadAPI("config.lua") +os.loadAPI("supervisor.lua") local SUPERVISOR_VERSION = "alpha-v0.1.0" +local print = util.print +local println = util.println local print_ts = util.print_ts +local println_ts = util.println_ts +log._info("========================================") +log._info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION) +log._info("========================================") + +println(">> SCADA Supervisor " .. SUPERVISOR_VERSION .. " <<") + +-- mount connected devices ppm.mount_all() -local modem = ppm.get_device("modem") - -print("| SCADA Supervisor - " .. SUPERVISOR_VERSION .. " |") - --- we need a modem +local modem = ppm.get_wireless_modem() if modem == nil then - print("Please connect a modem.") + println("boot> wireless modem not found") + log._warning("no wireless modem on startup") return end @@ -33,34 +40,57 @@ if config.SYSTEM_TYPE == "active" then end -- start comms, open all channels -if not modem.isOpen(config.SCADA_DEV_LISTEN) then - modem.open(config.SCADA_DEV_LISTEN) -end -if not modem.isOpen(config.SCADA_FO_CHANNEL) then - modem.open(config.SCADA_FO_CHANNEL) -end -if not modem.isOpen(config.SCADA_SV_CHANNEL) then - modem.open(config.SCADA_SV_CHANNEL) -end - local comms = supervisor.superv_comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_FO_CHANNEL, config.SCADA_SV_CHANNEL) -- base loop clock (4Hz, 5 ticks) -local loop_tick = os.startTimer(0.25) +local loop_clock = os.startTimer(0.25) -- event loop while true do local event, param1, param2, param3, param4, param5 = os.pullEventRaw() -- handle event - if event == "timer" and param1 == loop_tick then + if event == "peripheral_detach" then + local device = ppm.handle_unmount(param1) + + if device.type == "modem" then + -- we only care if this is our wireless modem + if device.dev == modem then + println_ts("wireless modem disconnected!") + log._error("comms modem disconnected!") + else + log._warning("non-comms modem disconnected") + end + end + elseif event == "peripheral" then + local type, device = ppm.mount(param1) + + 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 + elseif event == "timer" and param1 == loop_clock then -- basic event tick, send keep-alives + loop_clock = os.startTimer(0.25) elseif event == "modem_message" then -- got a packet - elseif event == "terminate" then - -- safe exit - print_ts("[alert] terminated\n") + end + + -- check for termination request + if event == "terminate" or ppm.should_terminate() then + log._warning("terminate requested, exiting...") -- todo: attempt failover, alert hot backup - return + break end end + +println_ts("exited") +log._info("exited") diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 5f988a8..d9b125c 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -12,4 +12,31 @@ function superv_comms(mode, num_reactors, modem, dev_listen, fo_channel, sv_chan sv_channel = sv_channel, reactor_struct_cache = nil } + + -- PRIVATE FUNCTIONS -- + + -- open all channels + local _open_channels = function () + if not self.modem.isOpen(self.dev_listen) then + self.modem.open(self.dev_listen) + end + if not self.modem.isOpen(self.fo_channel) then + self.modem.open(self.fo_channel) + end + if not self.modem.isOpen(self.sv_channel) then + self.modem.open(self.sv_channel) + end + end + + -- PUBLIC FUNCTIONS -- + + -- reconnect a newly connected modem + local reconnect_modem = function (modem) + self.modem = modem + _open_channels() + end + + return { + reconnect_modem = reconnect_modem + } end From 04f8dc7d75b6333f84e3e6f33c510d7a0095af64 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 21 Apr 2022 10:17:14 -0400 Subject: [PATCH 066/587] readme update about coordinator --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 37b9124..ac2d6c4 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,16 @@ This project implements concepts of a SCADA system in ComputerCraft (because why ![Architecture](https://upload.wikimedia.org/wikipedia/commons/thumb/1/10/Functional_levels_of_a_Distributed_Control_System.svg/1000px-Functional_levels_of_a_Distributed_Control_System.svg.png) SCADA and industrial automation terminology is used throughout the project, such as: -- Supervisory Computer: Gathers data and control the process +- Supervisory Computer: Gathers data and controls the process - Coordinating Computer: Used as the HMI component, user requests high-level processing operations - RTU: Remote Terminal Unit - PLC: Programmable Logic Controller ## ComputerCraft Architecture -### Coordinating Computers +### Coordinator Server -There can be one or more of these. They can be either an Advanced Computer or a Pocket Computer. +There can only be one of these. This server acts as a hybrid of levels 3 & 4 in the SCADA diagram above. In addition to viewing status and controlling processes with advanced monitors, it can host access for one or more Pocket computers. ### Supervisory Computers From 4842f9cb0dfc3ddd105315cd522766d96874078d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 21 Apr 2022 10:26:02 -0400 Subject: [PATCH 067/587] moved packet constructors and fixes to comms namespace references in plc comms code --- reactor-plc/plc.lua | 84 +++--------------------- reactor-plc/startup.lua | 2 +- rtu/rtu.lua | 2 +- rtu/startup.lua | 2 +- scada-common/comms.lua | 140 ++++++++++++++++++++++++++++++++++++++++ scada-common/modbus.lua | 62 +----------------- 6 files changed, 153 insertions(+), 139 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 6843303..3389ae7 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -1,6 +1,10 @@ -- #REQUIRES comms.lua -- #REQUIRES ppm.lua +local PROTOCOLS = comms.PROTOCOLS +local RPLC_TYPES = comms.RPLC_TYPES +local RPLC_LINKING = comms.RPLC_LINKING + -- Internal Safety System -- identifies dangerous states and SCRAMs reactor if warranted -- autonomous from main SCADA supervisor/coordinator control @@ -193,78 +197,6 @@ function iss_init(reactor) } end -function rplc_packet() - local self = { - frame = nil, - id = nil, - type = nil, - length = nil, - body = nil - } - - local _rplc_type_valid = function () - return self.type == RPLC_TYPES.KEEP_ALIVE or - self.type == RPLC_TYPES.LINK_REQ or - self.type == RPLC_TYPES.STATUS or - self.type == RPLC_TYPES.MEK_STRUCT or - self.type == RPLC_TYPES.MEK_SCRAM or - self.type == RPLC_TYPES.MEK_ENABLE or - self.type == RPLC_TYPES.MEK_BURN_RATE or - self.type == RPLC_TYPES.ISS_ALARM or - self.type == RPLC_TYPES.ISS_GET or - self.type == RPLC_TYPES.ISS_CLEAR - end - - -- make an RPLC packet - local make = function (id, packet_type, length, data) - self.id = id - self.type = packet_type - self.length = length - self.data = data - end - - -- decode an RPLC packet from a SCADA frame - local decode = function (frame) - if frame then - self.frame = frame - - if frame.protocol() == comms.PROTOCOLS.RPLC then - local data = frame.data() - local ok = #data > 2 - - if ok then - make(data[1], data[2], data[3], { table.unpack(data, 4, #data) }) - ok = _rplc_type_valid() - end - - return ok - else - log._debug("attempted RPLC parse of incorrect protocol " .. frame.protocol(), true) - return false - end - else - log._debug("nil frame encountered", true) - return false - end - end - - local get = function () - return { - scada_frame = self.frame, - id = self.id, - type = self.type, - length = self.length, - data = self.data - } - end - - return { - make = make, - decode = decode, - get = get - } -end - -- reactor PLC communications function comms_init(id, modem, local_port, server_port, reactor, iss) local self = { @@ -432,13 +364,13 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) if s_pkt.is_valid() then -- get as RPLC packet if s_pkt.protocol() == PROTOCOLS.RPLC then - local rplc_pkt = rplc_packet() + local rplc_pkt = comms.rplc_packet() if rplc_pkt.decode(s_pkt) then pkt = rplc_pkt.get() end -- get as SCADA management packet elseif s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then - local mgmt_pkt = mgmt_packet() + local mgmt_pkt = comms.mgmt_packet() if mgmt_pkt.decode(s_pkt) then pkt = mgmt_packet.get() end @@ -478,11 +410,11 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) send_status() log._debug("re-sent initial status data") elseif link_ack == RPLC_LINKING.DENY then - -- @todo: make sure this doesn't become an MITM security risk + -- @todo: make sure this doesn't become a MITM security risk print_ts("received unsolicited link denial, unlinking\n") log._debug("unsolicited rplc link request denied") elseif link_ack == RPLC_LINKING.COLLISION then - -- @todo: make sure this doesn't become an MITM security risk + -- @todo: make sure this doesn't become a MITM security risk print_ts("received unsolicited link collision, unlinking\n") log._warning("unsolicited rplc link request collision") else diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 4d13e74..80cb626 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -10,7 +10,7 @@ os.loadAPI("scada-common/comms.lua") os.loadAPI("config.lua") os.loadAPI("plc.lua") -local R_PLC_VERSION = "alpha-v0.2.0" +local R_PLC_VERSION = "alpha-v0.2.1" local print = util.print local println = util.println diff --git a/rtu/rtu.lua b/rtu/rtu.lua index efa31f2..23ace96 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -153,7 +153,7 @@ function rtu_comms(modem, local_port, server_port) if s_pkt.is_valid() then -- get as MODBUS TCP packet if s_pkt.protocol() == PROTOCOLS.MODBUS_TCP then - local m_pkt = modbus.packet() + local m_pkt = comms.modbus_packet() if m_pkt.decode(s_pkt) then pkt = m_pkt.get() end diff --git a/rtu/startup.lua b/rtu/startup.lua index 576dd47..bb7e3e1 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -17,7 +17,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.2.0" +local RTU_VERSION = "alpha-v0.2.1" local print = util.print local println = util.println diff --git a/scada-common/comms.lua b/scada-common/comms.lua index ac0c3c5..4b77fb1 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -88,6 +88,9 @@ function scada_packet() local modem_event = function () return self.modem_msg_in end local raw = function () return self.raw end + local sender = function () return self.s_port end + local receiver = function () return self.r_port end + local is_valid = function () return self.valid end local seq_num = function () return self.seq_num end @@ -107,6 +110,8 @@ function scada_packet() receive = receive, modem_event = modem_event, raw = raw, + sender = sender, + receiver = receiver, is_valid = is_valid, seq_num = seq_num, protocol = protocol, @@ -115,6 +120,141 @@ function scada_packet() } end +-- MODBUS packet +function modbus_packet() + local self = { + frame = nil, + txn_id = txn_id, + protocol = protocol, + length = length, + unit_id = unit_id, + func_code = func_code, + data = data + } + + -- make a MODBUS packet + local make = function (txn_id, protocol, length, unit_id, func_code, data) + self.txn_id = txn_id + self.protocol = protocol + self.length = length + self.unit_id = unit_id + self.func_code = func_code + self.data = data + end + + -- decode a MODBUS packet from a SCADA frame + local decode = function (frame) + if frame then + self.frame = frame + + local data = frame.data() + local size_ok = #data ~= 6 + + if size_ok then + make(data[1], data[2], data[3], data[4], data[5], data[6]) + end + + return size_ok and self.protocol == comms.PROTOCOLS.MODBUS_TCP + else + log._debug("nil frame encountered", true) + return false + end + end + + -- get this packet + local get = function () + return { + scada_frame = self.frame, + txn_id = self.txn_id, + protocol = self.protocol, + length = self.length, + unit_id = self.unit_id, + func_code = self.func_code, + data = self.data + } + end + + return { + make = make, + decode = decode, + get = get + } +end + +-- reactor PLC packet +function rplc_packet() + local self = { + frame = nil, + id = nil, + type = nil, + length = nil, + body = nil + } + + local _rplc_type_valid = function () + return self.type == RPLC_TYPES.KEEP_ALIVE or + self.type == RPLC_TYPES.LINK_REQ or + self.type == RPLC_TYPES.STATUS or + self.type == RPLC_TYPES.MEK_STRUCT or + self.type == RPLC_TYPES.MEK_SCRAM or + self.type == RPLC_TYPES.MEK_ENABLE or + self.type == RPLC_TYPES.MEK_BURN_RATE or + self.type == RPLC_TYPES.ISS_ALARM or + self.type == RPLC_TYPES.ISS_GET or + self.type == RPLC_TYPES.ISS_CLEAR + end + + -- make an RPLC packet + local make = function (id, packet_type, length, data) + self.id = id + self.type = packet_type + self.length = length + self.data = data + end + + -- decode an RPLC packet from a SCADA frame + local decode = function (frame) + if frame then + self.frame = frame + + if frame.protocol() == comms.PROTOCOLS.RPLC then + local data = frame.data() + local ok = #data > 2 + + if ok then + make(data[1], data[2], data[3], { table.unpack(data, 4, #data) }) + ok = _rplc_type_valid() + end + + return ok + else + log._debug("attempted RPLC parse of incorrect protocol " .. frame.protocol(), true) + return false + end + else + log._debug("nil frame encountered", true) + return false + end + end + + local get = function () + return { + scada_frame = self.frame, + id = self.id, + type = self.type, + length = self.length, + data = self.data + } + end + + return { + make = make, + decode = decode, + get = get + } +end + +-- SCADA management packet function mgmt_packet() local self = { frame = nil, diff --git a/scada-common/modbus.lua b/scada-common/modbus.lua index 8a4137f..a8148b7 100644 --- a/scada-common/modbus.lua +++ b/scada-common/modbus.lua @@ -1,3 +1,5 @@ +-- #REQUIRES comms.lua + -- modbus function codes local MODBUS_FCODE = { READ_COILS = 0x01, @@ -263,63 +265,3 @@ function new(rtu_dev) handle_packet = handle_packet } end - -function packet() - local self = { - frame = nil, - txn_id = txn_id, - protocol = protocol, - length = length, - unit_id = unit_id, - func_code = func_code, - data = data - } - - -- make a MODBUS packet - local make = function (txn_id, protocol, length, unit_id, func_code, data) - self.txn_id = txn_id - self.protocol = protocol - self.length = length - self.unit_id = unit_id - self.func_code = func_code - self.data = data - end - - -- decode a MODBUS packet from a SCADA frame - local decode = function (frame) - if frame then - self.frame = frame - - local data = frame.data() - local size_ok = #data ~= 6 - - if size_ok then - make(data[1], data[2], data[3], data[4], data[5], data[6]) - end - - return size_ok and self.protocol == comms.PROTOCOLS.MODBUS_TCP - else - log._debug("nil frame encountered", true) - return false - end - end - - -- get this packet - local get = function () - return { - scada_frame = self.frame, - txn_id = self.txn_id, - protocol = self.protocol, - length = self.length, - unit_id = self.unit_id, - func_code = self.func_code, - data = self.data - } - end - - return { - make = make, - decode = decode, - get = get - } -end From 0c132f6e43d78d83bb4427c6f69e0348381d2b45 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 21 Apr 2022 10:44:43 -0400 Subject: [PATCH 068/587] todo comment format --- reactor-plc/plc.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 3389ae7..7b750e4 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -487,7 +487,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) log._debug("discarding non-link packet before linked") end elseif packet.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then - -- todo + -- @todo end end end From b10a8d94799657b5031b613c7e1abecd974a7a07 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 21 Apr 2022 12:40:21 -0400 Subject: [PATCH 069/587] send ISS status automatically along with PLC status --- reactor-plc/config.lua | 2 +- reactor-plc/plc.lua | 32 ++++++++++++++++++++++++-------- reactor-plc/startup.lua | 14 +++++++------- scada-common/comms.lua | 6 +++--- 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/reactor-plc/config.lua b/reactor-plc/config.lua index 25f750c..dceeb21 100644 --- a/reactor-plc/config.lua +++ b/reactor-plc/config.lua @@ -1,4 +1,4 @@ --- set to false to run in standalone mode (safety regulation only) +-- set to false to run in offline mode (safety regulation only) NETWORKED = true -- unique reactor ID REACTOR_ID = 1 diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 7b750e4..5c50065 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -207,9 +207,10 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) l_port = local_port, reactor = reactor, iss = iss, - status_cache = nil, scrammed = false, - linked = false + linked = false, + status_cache = nil, + max_burn_rate = nil } -- open modem @@ -328,7 +329,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) local _send_iss_status = function () local iss_status = { id = self.id, - type = RPLC_TYPES.ISS_GET, + type = RPLC_TYPES.ISS_STATUS, status = iss.status() } @@ -438,10 +439,17 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) _send_ack(packet.type, self.reactor.activate() == ppm.ACCESS_OK) elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then -- set the burn rate - local burn_rate = packet.data[1] - local max_burn_rate = self.reactor.getMaxBurnRate() local success = false + local burn_rate = packet.data[1] + local max_burn_rate = self.max_burn_rate + -- if no known max burn rate, check again + if max_burn_rate == nil then + max_burn_rate = self.reactor.getMaxBurnRate() + self.max_burn_rate = max_burn_rate + end + + -- if we know our max burn rate, update current burn rate if in range if max_burn_rate ~= ppm.ACCESS_FAULT then if burn_rate > 0 and burn_rate <= max_burn_rate then success = self.reactor.setBurnRate(burn_rate) @@ -449,9 +457,6 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) end _send_ack(packet.type, success == ppm.ACCESS_OK) - elseif packet.type == RPLC_TYPES.ISS_GET then - -- get the ISS status - _send_iss_status(iss.status()) elseif packet.type == RPLC_TYPES.ISS_CLEAR then -- clear the ISS status iss.reset() @@ -526,6 +531,16 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) _send(sys_status) end + local send_iss_status = function () + local iss_status = { + id = self.id, + type = RPLC_TYPES.ISS_STATUS, + status = iss.status() + } + + _send(iss_status) + end + local send_iss_alarm = function (cause) local iss_alarm = { id = self.id, @@ -548,6 +563,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) handle_packet = handle_packet, send_link_req = send_link_req, send_status = send_status, + send_iss_status = send_iss_status, send_iss_alarm = send_iss_alarm, is_scrammed = is_scrammed, is_linked = is_linked, diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 80cb626..1737f3f 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -10,7 +10,7 @@ os.loadAPI("scada-common/comms.lua") os.loadAPI("config.lua") os.loadAPI("plc.lua") -local R_PLC_VERSION = "alpha-v0.2.1" +local R_PLC_VERSION = "alpha-v0.2.2" local print = util.print local println = util.println @@ -90,6 +90,7 @@ function init() conn_watchdog = util.new_watchdog(3) log._debug("conn watchdog started") else + println("boot> starting in offline mode"); log._debug("running without networking") end @@ -220,13 +221,13 @@ while true do -- check safety (SCRAM occurs if tripped) if not plc_state.degraded then - local iss_tripped, iss_status, iss_first = iss.check() + local iss_tripped, iss_status_string, iss_first = iss.check() plc_state.scram = plc_state.scram or iss_tripped if iss_first then - println_ts("[ISS] reactor shutdown, safety tripped: " .. iss_status) + println_ts("[ISS] reactor shutdown, safety tripped: " .. iss_status_string) if networked then - plc_comms.send_iss_alarm(iss_status) + plc_comms.send_iss_alarm(iss_status_string) end end else @@ -244,6 +245,7 @@ while true do if plc_comms.is_linked() then if ticks_to_update <= 0 then plc_comms.send_status(iss_tripped, plc_state.degraded) + plc_comms.send_iss_status() ticks_to_update = UPDATE_TICKS end else @@ -275,9 +277,8 @@ while true do -- check for termination request if event == "terminate" or ppm.should_terminate() then - log._warning("terminate requested, exiting...") - -- safe exit + log._warning("terminate requested, exiting...") if plc_state.init_ok then plc_state.scram = true if reactor.scram() ~= ppm.ACCESS_FAULT then @@ -287,7 +288,6 @@ while true do println_ts("exiting, reactor failed to disable") end end - break end end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 4b77fb1..9181ce3 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -18,8 +18,8 @@ RPLC_TYPES = { MEK_SCRAM = 4, -- SCRAM reactor MEK_ENABLE = 5, -- enable reactor MEK_BURN_RATE = 6, -- set burn rate - ISS_ALARM = 7, -- ISS alarm broadcast - ISS_GET = 8, -- get ISS status + ISS_STATUS = 7, -- ISS status + ISS_ALARM = 8, -- ISS alarm broadcast ISS_CLEAR = 9 -- clear ISS trip (if in bad state, will trip immediately) } @@ -200,7 +200,7 @@ function rplc_packet() self.type == RPLC_TYPES.MEK_ENABLE or self.type == RPLC_TYPES.MEK_BURN_RATE or self.type == RPLC_TYPES.ISS_ALARM or - self.type == RPLC_TYPES.ISS_GET or + self.type == RPLC_TYPES.ISS_STATUS or self.type == RPLC_TYPES.ISS_CLEAR end From 991c855c11837822530db3cbd17642d532a625e9 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 21 Apr 2022 12:44:46 -0400 Subject: [PATCH 070/587] message queue --- supervisor/mqueue.lua | 48 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 supervisor/mqueue.lua diff --git a/supervisor/mqueue.lua b/supervisor/mqueue.lua new file mode 100644 index 0000000..1aa5869 --- /dev/null +++ b/supervisor/mqueue.lua @@ -0,0 +1,48 @@ +-- +-- Message Queue +-- + +TYPE = { + COMMAND = 0, + PACKET = 1 +} + +function new() + local queue = {} + + local length = function () + return #queue + end + + local empty = function () + return #queue == 0 + end + + local _push = function (qtype, message) + table.insert(queue, { qtype = qtype, message = message }) + end + + local push_packet = function (message) + push(TYPE.PACKET, message) + end + + local push_command = function (message) + push(TYPE.COMMAND, message) + end + + local pop = function () + if #queue > 0 then + return table.remove(queue) + else + return nil + end + end + + return { + length = length, + empty = empty, + push_packet = push_packet, + push_command = push_command, + pop = pop + } +end From f7c11febe526659d4322b2b8c6b72671b33b86b2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 22 Apr 2022 10:21:15 -0400 Subject: [PATCH 071/587] check if interface exists before trying to get its device or type --- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- scada-common/ppm.lua | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 1737f3f..b57845c 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -10,7 +10,7 @@ os.loadAPI("scada-common/comms.lua") os.loadAPI("config.lua") os.loadAPI("plc.lua") -local R_PLC_VERSION = "alpha-v0.2.2" +local R_PLC_VERSION = "alpha-v0.2.3" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index bb7e3e1..0074625 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -17,7 +17,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.2.1" +local RTU_VERSION = "alpha-v0.2.2" local print = util.print local println = util.println diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index fd92b23..f751342 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -174,12 +174,16 @@ end -- get a mounted peripheral by side/interface function get_periph(iface) - return self.mounts[iface].dev + if self.mounts[iface] then + return self.mounts[iface].dev + else return nil end end -- get a mounted peripheral type by side/interface function get_type(iface) - return self.mounts[iface].type + if self.mounts[iface] then + return self.mounts[iface].type + else return nil end end -- get all mounted peripherals by type From 17d0213d58bbb1cadf1fce67ca4f81df1c82d595 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 22 Apr 2022 10:58:18 -0400 Subject: [PATCH 072/587] RTU/PPM bugfixes --- reactor-plc/plc.lua | 10 ---------- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 4 ++-- scada-common/ppm.lua | 14 +++++++++----- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 5c50065..c7a6346 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -326,16 +326,6 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) end end - local _send_iss_status = function () - local iss_status = { - id = self.id, - type = RPLC_TYPES.ISS_STATUS, - status = iss.status() - } - - _send(iss_status) - end - -- PUBLIC FUNCTIONS -- -- reconnect a newly connected modem diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index b57845c..d413684 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -10,7 +10,7 @@ os.loadAPI("scada-common/comms.lua") os.loadAPI("config.lua") os.loadAPI("plc.lua") -local R_PLC_VERSION = "alpha-v0.2.3" +local R_PLC_VERSION = "alpha-v0.2.4" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index 0074625..42779c6 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -17,7 +17,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.2.2" +local RTU_VERSION = "alpha-v0.2.3" local print = util.print local println = util.println @@ -185,7 +185,7 @@ while true do for i = 1, #units do -- find disconnected device - if units[i].device == device then + if units[i].device == device.dev then -- we are going to let the PPM prevent crashes -- return fault flags/codes to MODBUS queries local unit = units[i] diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index f751342..66f26a7 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -116,7 +116,7 @@ function mount_all() dev = pm_dev } - log._debug("PPM: found a " .. self.mounts[ifaces[i]].type .. " (" .. ifaces[i] .. ")") + log._info("PPM: found a " .. self.mounts[ifaces[i]].type .. " (" .. ifaces[i] .. ")") end if #ifaces == 0 then @@ -132,7 +132,7 @@ function mount(iface) for i = 1, #ifaces do if iface == ifaces[i] then - log._debug("PPM: mount(" .. iface .. ") -> found a " .. peripheral.getType(iface)) + log._info("PPM: mount(" .. iface .. ") -> found a " .. peripheral.getType(iface)) type = peripheral.getType(iface) pm_dev = peripheral.wrap(iface) @@ -153,9 +153,13 @@ end function handle_unmount(iface) -- what got disconnected? local lost_dev = self.mounts[iface] - local type = lost_dev.type - - log._warning("PPM: lost device " .. type .. " mounted to " .. iface) + + if lost_dev then + local type = lost_dev.type + log._warning("PPM: lost device " .. type .. " mounted to " .. iface) + else + log._error("PPM: lost device unknown to the PPM mounted to " .. iface) + end return lost_dev end From 1bf0d352a12fc190ed22624f4a9108be1feeb8f6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 22 Apr 2022 11:07:59 -0400 Subject: [PATCH 073/587] supervisor sessions work in progress --- scada-common/comms.lua | 63 ++++++++ supervisor/config.lua | 7 +- supervisor/session/coordinator.lua | 0 supervisor/session/plc.lua | 248 +++++++++++++++++++++++++++++ supervisor/session/rtu.lua | 0 supervisor/session/svsessions.lua | 141 ++++++++++++++++ supervisor/startup.lua | 17 +- supervisor/supervisor.lua | 152 ++++++++++++++++-- 8 files changed, 611 insertions(+), 17 deletions(-) create mode 100644 supervisor/session/coordinator.lua create mode 100644 supervisor/session/plc.lua create mode 100644 supervisor/session/rtu.lua create mode 100644 supervisor/session/svsessions.lua diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 9181ce3..cb203bf 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -318,3 +318,66 @@ function mgmt_packet() get = get } end + +-- SCADA coordinator packet +-- @todo +function coord_packet() + local self = { + frame = nil, + type = nil, + length = nil, + data = nil + } + + local _coord_type_valid = function () + -- @todo + return false + end + + -- make a coordinator packet + local make = function (packet_type, length, data) + self.type = packet_type + self.length = length + self.data = data + end + + -- decode a coordinator packet from a SCADA frame + local decode = function (frame) + if frame then + self.frame = frame + + if frame.protocol() == comms.PROTOCOLS.COORD_DATA then + local data = frame.data() + local ok = #data > 1 + + if ok then + make(data[1], data[2], { table.unpack(data, 3, #data) }) + ok = _coord_type_valid() + end + + return ok + else + log._debug("attempted COORD_DATA parse of incorrect protocol " .. frame.protocol(), true) + return false + end + else + log._debug("nil frame encountered", true) + return false + end + end + + local get = function () + return { + scada_frame = self.frame, + type = self.type, + length = self.length, + data = self.data + } + end + + return { + make = make, + decode = decode, + get = get + } +end diff --git a/supervisor/config.lua b/supervisor/config.lua index bc02177..de10492 100644 --- a/supervisor/config.lua +++ b/supervisor/config.lua @@ -4,13 +4,12 @@ -- from all PLCs and coordinator(s) while in backup to allow -- instant failover if active goes offline without re-sync SYSTEM_TYPE = 'active' - -- scada network listen for PLC's and RTU's SCADA_DEV_LISTEN = 16000 -- failover synchronization -SCADA_FO_CHANNEL = 16001 +SCADA_FO_LOCAL = 16101 +SCADA_FO_PEER = 16102 -- listen port for SCADA supervisor access by coordinators -SCADA_SV_CHANNEL = 16002 - +SCADA_SV_LISTEN = 16201 -- expected number of reactors NUM_REACTORS = 4 diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua new file mode 100644 index 0000000..e69de29 diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua new file mode 100644 index 0000000..4008a33 --- /dev/null +++ b/supervisor/session/plc.lua @@ -0,0 +1,248 @@ +-- #REQUIRES mqueue.lua +-- #REQUIRES comms.lua +-- #REQUIRES log.lua +-- #REQUIRES util.lua + +local RPLC_TYPES = comms.RPLC_TYPES + +PLC_S_COMMANDS = { + SCRAM = 0, + ENABLE = 1, + ISS_CLEAR = 2 +} + +-- PLC supervisor session +function new_session(id, for_reactor, in_queue, out_queue) + local log_header = "plc_session(" .. id .. "): " + + local self = { + id = id, + for_reactor = for_reactor, + in_q = in_queue, + out_q = out_queue, + commanded_state = false, + -- connection properties + seq_num = 0, + connected = true, + received_struct = false, + plc_conn_watchdog = util.new_watchdog(3) + -- when to next retry one of these requests + retry_times = { + struct_req = 0, + scram_req = 0, + enable_req = 0 + }, + -- session PLC status database + sDB = { + control_state = false, + overridden = false, + degraded = false, + iss_status = { + dmg_crit = false, + ex_hcool = false, + ex_waste = false, + high_temp = false, + no_fuel = false, + no_cool = false, + timed_out = false + }, + mek_status = { + heating_rate = 0, + + status = false, + burn_rate = 0, + act_burn_rate = 0, + temp = 0, + damage = 0, + boil_eff = 0, + env_loss = 0, + + fuel = 0, + fuel_need = 0, + fuel_fill = 0, + waste = 0, + waste_need = 0, + waste_fill = 0, + cool_type = "?", + cool_amnt = 0, + cool_need = 0, + cool_fill = 0, + hcool_type = "?", + hcool_amnt = 0, + hcool_need = 0, + hcool_fill = 0 + }, + mek_struct = { + heat_cap = 0, + fuel_asm = 0, + fuel_sa = 0, + fuel_cap = 0, + waste_cap = 0, + cool_cap = 0, + hcool_cap = 0, + max_burn = 0 + } + } + } + + local _copy_iss_status = function (iss_status) + self.sDB.iss_status.dmg_crit = iss_status[1] + self.sDB.iss_status.ex_hcool = iss_status[2] + self.sDB.iss_status.ex_waste = iss_status[3] + self.sDB.iss_status.high_temp = iss_status[4] + self.sDB.iss_status.no_fuel = iss_status[5] + self.sDB.iss_status.no_cool = iss_status[6] + self.sDB.iss_status.timed_out = iss_status[7] + end + + local _copy_status = function (heating_rate, mek_data) + self.sDB.mek_status.heating_rate = heating_rate + for key, value in pairs(mek_data) do + self.sDB.mek_status[key] = value + end + end + + local _copy_struct = function (mek_data) + for key, value in pairs(mek_data) do + self.sDB.mek_struct[key] = value + end + end + + local _get_ack = function (pkt) + if rplc_packet.length == 1 then + return rplc_packet.data[1] + else + log._warning(log_header .. "RPLC ACK length mismatch") + return nil + end + end + + local get_id = function () return self.id end + + local close = function () self.connected = false end + + local check_wd = function (timer) + return timer == plc_conn_watchdog + end + + local get_struct = function () + if self.received_struct then + return self.sDB.mek_struct + else + -- @todo: need a system in place to re-request this periodically + return nil + end + end + + local iterate = function () + if self.connected and ~self.in_q.empty() then + -- get a new message to process + local message = self.in_q.pop() + + if message.qtype == mqueue.TYPE.PACKET then + -- handle an incoming packet from the PLC + rplc_pkt = message.message.get() + + if rplc_pkt.id == for_reactor then + if rplc_pkt.type == RPLC_TYPES.KEEP_ALIVE then + -- periodic keep alive + elseif rplc_pkt.type == RPLC_TYPES.STATUS then + -- status packet received, update data + if rplc_packet.length == 6 then + -- @todo [1] is timestamp, determine how this will be used (if at all) + self.sDB.control_state = rplc_packet.data[2] + self.sDB.overridden = rplc_packet.data[3] + self.sDB.degraded = rplc_packet.data[4] + + -- attempt to read mek_data table + if rplc_packet.data[6] ~= nil then + local status = pcall(_copy_status, rplc_packet.data[5], rplc_packet.data[6]) + if status then + -- copied in status data OK + else + -- error copying status data + log._error(log_header .. "failed to parse status packet data") + end + else + self.sDB.mek_status.heating_rate = rplc_packet.data[5] + end + else + log._warning(log_header .. "RPLC status packet length mismatch") + end + elseif rplc_pkt.type == RPLC_TYPES.MEK_STRUCT then + -- received reactor structure, record it + if rplc_packet.length == 1 then + local status = pcall(_copy_struct, rplc_packet.data[1]) + if status then + -- copied in structure data OK + else + -- error copying structure data + log._error(log_header .. "failed to parse struct packet data") + end + else + log._warning(log_header .. "RPLC struct packet length mismatch") + end + elseif rplc_pkt.type == RPLC_TYPES.MEK_SCRAM then + -- SCRAM acknowledgement + local ack = _get_ack(rplc_pkt) + if ack then + self.sDB.control_state = false + elseif ack == false then + log._warning(log_header .. "SCRAM failed!") + end + elseif rplc_pkt.type == RPLC_TYPES.MEK_ENABLE then + -- enable acknowledgement + local ack = _get_ack(rplc_pkt) + if ack then + self.sDB.control_state = true + elseif ack == false then + log._warning(log_header .. "enable failed!") + end + elseif rplc_pkt.type == RPLC_TYPES.MEK_BURN_RATE then + -- burn rate acknowledgement + if _get_ack(rplc_pkt) == false then + log._warning(log_header .. "burn rate update failed!") + end + elseif rplc_pkt.type == RPLC_TYPES.ISS_STATUS then + -- ISS status packet received, copy data + if rplc_packet.length == 1 then + local status = pcall(_copy_iss_status, rplc_packet.data[1]) + if status then + -- copied in ISS status data OK + else + -- error copying ISS status data + log._error(log_header .. "failed to parse ISS status packet data") + end + else + log._warning(log_header .. "RPLC ISS status packet length mismatch") + end + elseif rplc_pkt.type == RPLC_TYPES.ISS_ALARM then + -- ISS alarm + self.sDB.overridden = true + -- @todo + elseif rplc_pkt.type == RPLC_TYPES.ISS_CLEAR then + -- ISS clear acknowledgement + -- @todo + else + log._warning(log_header .. "handler received unsupported RPLC packet type " .. rplc_pkt.type) + end + else + log._warning(log_header .. "RPLC packet with ID not matching reactor ID: reactor " .. self.for_reactor .. " != " .. rplc_pkt.id) + end + elseif message.qtype == mqueue.TYPE.COMMAND then + -- handle instruction + + end + end + + return self.connected + end + + return { + get_id = get_id, + check_wd = check_wd, + get_struct = get_struct, + close = close, + iterate = iterate + } +end diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua new file mode 100644 index 0000000..e69de29 diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua new file mode 100644 index 0000000..fcc6950 --- /dev/null +++ b/supervisor/session/svsessions.lua @@ -0,0 +1,141 @@ +-- #REQUIRES mqueue.lua +-- #REQUIRES log.lua + +-- Supervisor Sessions Handler + +SESSION_TYPE = { + RTU_SESSION = 0, + PLC_SESSION = 1, + COORD_SESSION = 2 +} + +local self = { + num_reactors = 0, + rtu_sessions = {}, + plc_sessions = {}, + coord_sessions = {}, + next_rtu_id = 0, + next_plc_id = 0, + next_coord_id = 0 +} + +function alloc_reactor_plcs(num_reactors) + self.num_reactors = num_reactors + for i = 1, num_reactors do + table.insert(self.plc_sessions, false) + end +end + +function find_session(stype, remote_port) + if stype == SESSION_TYPE.RTU_SESSION then + for i = 1, #self.rtu_sessions do + if self.rtu_sessions[i].r_host == remote_port then + return self.rtu_sessions[i] + end + end + elseif stype == SESSION_TYPE.PLC_SESSION then + for i = 1, #self.plc_sessions do + if self.plc_sessions[i].r_host == remote_port then + return self.plc_sessions[i] + end + end + elseif stype == SESSION_TYPE.COORD_SESSION then + for i = 1, #self.coord_sessions do + if self.coord_sessions[i].r_host == remote_port then + return self.coord_sessions[i] + end + end + else + log._error("cannot search for unknown session type " .. stype, true) + end + + return nil +end + +function get_reactor_session(reactor) + local session = nil + + for i = 1, #self.plc_sessions do + if self.plc_sessions[i].reactor == reactor then + session = self.plc_sessions[i] + end + end + + return session +end + +function establish_plc_session(remote_port, for_reactor) + if get_reactor_session(for_reactor) == nil then + local plc_s = { + open = true, + reactor = for_reactor, + r_host = remote_port, + in_queue = mqueue.new(), + out_queue = mqueue.new(), + instance = nil + } + + plc_s.instance = plc.new_session(next_plc_id, plc_s.in_queue, plc_s.out_queue) + table.insert(self.plc_sessions, plc_s) + next_plc_id = next_plc_id + 1 + + -- success + return plc_s.instance.get_id() + else + -- reactor already assigned to a PLC + return false + end +end + +local function _iterate(sessions) + for i = 1, #sessions do + local session = sessions[i] + if session.open then + local ok = session.instance.iterate() + if not ok then + session.open = false + session.instance.close() + end + end + end +end + +function iterate_all() + -- iterate RTU sessions + _iterate(self.rtu_sessions) + + -- iterate PLC sessions + _iterate(self.plc_sessions) + + -- iterate coordinator sessions + _iterate(self.coord_sessions) +end + +local function _free_closed(sessions) + local move_to = 1 + for i = 1, #sessions do + local session = sessions[i] + if session ~= nil then + if sessions[i].open then + if sessions[move_to] == nil then + sessions[move_to] = session + sessions[i] = nil + end + move_to = move_to + 1 + else + sessions[i] = nil + end + end + end +end + +function free_all_closed() + -- free closed RTU sessions + _free_closed(self.rtu_sessions) + + -- free closed PLC sessions + _free_closed(self.plc_sessions) + + -- free closed coordinator sessions + _free_closed(self.coord_sessions) +end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index cec58c0..f58efd2 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -6,11 +6,18 @@ os.loadAPI("scada-common/log.lua") os.loadAPI("scada-common/util.lua") os.loadAPI("scada-common/ppm.lua") os.loadAPI("scada-common/comms.lua") +os.loadAPI("scada-common/modbus.lua") os.loadAPI("config.lua") +os.loadAPI("mqueue.lua") os.loadAPI("supervisor.lua") -local SUPERVISOR_VERSION = "alpha-v0.1.0" +os.loadAPI("session/rtu.lua") +os.loadAPI("session/plc.lua") +os.loadAPI("session/coordinator.lua") +os.loadAPI("session/svsessions.lua") + +local SUPERVISOR_VERSION = "alpha-v0.1.1" local print = util.print local println = util.println @@ -20,7 +27,6 @@ local println_ts = util.println_ts log._info("========================================") log._info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION) log._info("========================================") - println(">> SCADA Supervisor " .. SUPERVISOR_VERSION .. " <<") -- mount connected devices @@ -40,7 +46,8 @@ if config.SYSTEM_TYPE == "active" then end -- start comms, open all channels -local comms = supervisor.superv_comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_FO_CHANNEL, config.SCADA_SV_CHANNEL) +local comms = supervisor.superv_comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_FO_LOCAL, config.SCADA_FO_PEER, + config.SCADA_SV_CHANNEL) -- base loop clock (4Hz, 5 ticks) local loop_clock = os.startTimer(0.25) @@ -82,12 +89,14 @@ while true do loop_clock = os.startTimer(0.25) elseif event == "modem_message" then -- got a packet + local packet = superv_comms.parse_packet(p1, p2, p3, p4, p5) + superv_comms.handle_packet(packet) end -- check for termination request if event == "terminate" or ppm.should_terminate() then log._warning("terminate requested, exiting...") - -- todo: attempt failover, alert hot backup + -- @todo: attempt failover, alert hot backup break end end diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index d9b125c..d6bfd54 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -1,15 +1,27 @@ -- #REQUIRES comms.lua +-- #REQUIRES modbus.lua +-- #REQUIRES mqueue.lua +-- #REQUIRES svsessions.lua + +local PROTOCOLS = comms.PROTOCOLS +local RPLC_TYPES = comms.RPLC_TYPES +local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES +local RTU_ADVERT_TYPES = comms.RTU_ADVERT_TYPES + +local SESSION_TYPE = svsessions.SESSION_TYPE -- supervisory controller communications -function superv_comms(mode, num_reactors, modem, dev_listen, fo_channel, sv_channel) +function superv_comms(mode, num_reactors, modem, dev_listen, fo_local, fo_peer, coord_listen) local self = { mode = mode, - seq_num = 0, + fo_seq_num = 0, + ln_seq_num = 0, num_reactors = num_reactors, modem = modem, dev_listen = dev_listen, - fo_channel = fo_channel, - sv_channel = sv_channel, + fo_rx = fo_local, + fo_tx = fo_peer, + coord_listen = coord_listen, reactor_struct_cache = nil } @@ -20,14 +32,28 @@ function superv_comms(mode, num_reactors, modem, dev_listen, fo_channel, sv_chan if not self.modem.isOpen(self.dev_listen) then self.modem.open(self.dev_listen) end - if not self.modem.isOpen(self.fo_channel) then - self.modem.open(self.fo_channel) + if not self.modem.isOpen(self.fo_rx) then + self.modem.open(self.fo_rx) end - if not self.modem.isOpen(self.sv_channel) then - self.modem.open(self.sv_channel) + if not self.modem.isOpen(self.coord_listen) then + self.modem.open(self.coord_listen) end end + local _send_fo = function (msg) + local packet = comms.scada_packet() + packet.make(self.fo_seq_num, PROTOCOLS.SCADA_MGMT, msg) + self.modem.transmit(self.fo_tx, self.fo_rx, packet.raw()) + self.fo_seq_num = self.fo_seq_num + 1 + end + + local _send_plc_linking = function (dest, msg) + local packet = comms.scada_packet() + packet.make(self.ln_seq_num, PROTOCOLS.RPLC, msg) + self.modem.transmit(dest, self.dev_listen, packet.raw()) + self.ln_seq_num = self.ln_seq_num + 1 + end + -- PUBLIC FUNCTIONS -- -- reconnect a newly connected modem @@ -36,7 +62,115 @@ function superv_comms(mode, num_reactors, modem, dev_listen, fo_channel, sv_chan _open_channels() end + -- parse a packet + local parse_packet = function(side, sender, reply_to, message, distance) + local pkt = nil + local s_pkt = scada_packet() + + -- parse packet as generic SCADA packet + s_pkt.recieve(side, sender, reply_to, message, distance) + + if s_pkt.is_valid() then + -- get as MODBUS TCP packet + if s_pkt.protocol() == PROTOCOLS.MODBUS_TCP then + local m_pkt = comms.modbus_packet() + if m_pkt.decode(s_pkt) then + pkt = m_pkt.get() + end + -- get as RPLC packet + elseif s_pkt.protocol() == PROTOCOLS.RPLC then + local rplc_pkt = comms.rplc_packet() + if rplc_pkt.decode(s_pkt) then + pkt = rplc_pkt.get() + end + -- get as SCADA management packet + elseif s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then + local mgmt_pkt = comms.mgmt_packet() + if mgmt_pkt.decode(s_pkt) then + pkt = mgmt_packet.get() + end + -- get as coordinator packet + elseif s_pkt.protocol() == PROTOCOLS.COORD_DATA then + local coord_pkt = comms.coord_packet() + if coord_pkt.decode(s_pkt) then + pkt = coord_pkt.get() + end + else + log._debug("attempted parse of illegal packet type " .. s_pkt.protocol(), true) + end + end + + return pkt + end + + local handle_packet = function(packet) + if packet ~= nil then + local sender = packet.scada_frame.sender() + local receiver = packet.scada_frame.receiver() + local protocol = packet.scada_frame.protocol() + + -- device (RTU/PLC) listening channel + if receiver == self.dev_listen then + if protocol == PROTOCOLS.MODBUS_TCP then + -- MODBUS response + elseif protocol == PROTOCOLS.RPLC then + -- reactor PLC packet + local session = svsessions.find_session(SESSION_TYPE.PLC_SESSION, sender) + if session then + if packet.type == RPLC_TYPES.LINK_REQ then + -- new device on this port? that's a collision + _send_plc_linking(sender, { RPLC_LINKING.COLLISION }) + else + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + end + else + -- unknown session, is this a linking request? + if packet.type == RPLC_TYPES.LINK_REQ then + -- this is a linking request + local plc_id = svsessions.establish_plc_session(sender) + if plc_id == false then + -- reactor already has a PLC assigned + _send_plc_linking(sender, { RPLC_LINKING.COLLISION }) + else + -- got an ID; assigned to a reactor successfully + _send_plc_linking(sender, { RPLC_LINKING.ALLOW }) + end + else + -- force a re-link + _send_plc_linking(sender, { RPLC_LINKING.DENY }) + end + end + elseif protocol == PROTOCOLS.SCADA_MGMT then + -- SCADA management packet + else + log._debug("illegal packet type " .. protocol .. " on device listening channel") + end + -- failover listening channel + elseif receiver == self.fo_rx then + if protocol == PROTOCOLS.SCADA_MGMT then + -- SCADA management packet + else + log._debug("illegal packet type " .. protocol .. " on failover listening channel") + end + -- coordinator listening channel + elseif reciever == self.coord_listen then + if protocol == PROTOCOLS.SCADA_MGMT then + -- SCADA management packet + elseif protocol == PROTOCOLS.COORD_DATA then + -- coordinator packet + else + log._debug("illegal packet type " .. protocol .. " on coordinator listening channel") + end + else + log._error("received packet on unused channel " .. receiver, true) + end + end + end + return { - reconnect_modem = reconnect_modem + reconnect_modem = reconnect_modem, + parse_packet = parse_packet, + handle_packet = handle_packet } end From 6daf6df2d04ec34c09faa7d868ee71b63759face Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 22 Apr 2022 11:15:16 -0400 Subject: [PATCH 074/587] active-backup supervisor setups are no longer planned --- README.md | 4 +++- scada-common/comms.lua | 7 +------ supervisor/config.lua | 11 +---------- supervisor/startup.lua | 10 +--------- supervisor/supervisor.lua | 22 +--------------------- 5 files changed, 7 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index ac2d6c4..1aa8880 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # cc-mek-scada Configurable ComputerCraft SCADA system for multi-reactor control of Mekanism fission reactors with a GUI, automatic safety features, waste processing control, and more! +This requires CC: Tweaked and Mekanism v10.0+ (10.1 recommended for full feature set). + ## [SCADA](https://en.wikipedia.org/wiki/SCADA) > Supervisory control and data acquisition (SCADA) is a control system architecture comprising computers, networked data communications and graphical user interfaces for high-level supervision of machines and processes. It also covers sensors and other devices, such as programmable logic controllers, which interface with process plant or machinery. @@ -23,7 +25,7 @@ There can only be one of these. This server acts as a hybrid of levels 3 & 4 in ### Supervisory Computers -There can be at most two of these in an active-backup configuration. If a backup is configured, it will act as a hot backup. This means it will be live, all data will be recieved by both it and the active computer, but it will not be commanding anything unless it hears that the active supervisor is shutting down or loses communication with the active supervisor. +There should be one of these per facility system. Currently, that means only one. In the future, multiple supervisors would provide the capability of coordinating between multiple facilities (like a fission facility, fusion facility, etc). ### RTUs diff --git a/scada-common/comms.lua b/scada-common/comms.lua index cb203bf..28529ac 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -1,15 +1,10 @@ PROTOCOLS = { MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol RPLC = 1, -- reactor PLC protocol - SCADA_MGMT = 2, -- SCADA supervisor intercommunication, device advertisements, etc + SCADA_MGMT = 2, -- SCADA supervisor management, device advertisements, etc COORD_DATA = 3 -- data packets for coordinators to/from supervisory controller } -SCADA_SV_MODES = { - ACTIVE = 0, -- supervisor running as primary - BACKUP = 1 -- supervisor running as hot backup -} - RPLC_TYPES = { KEEP_ALIVE = 0, -- keep alive packets LINK_REQ = 1, -- linking requests diff --git a/supervisor/config.lua b/supervisor/config.lua index de10492..fde20b3 100644 --- a/supervisor/config.lua +++ b/supervisor/config.lua @@ -1,15 +1,6 @@ --- type ('active','backup') --- 'active' system carries through instructions and control --- 'backup' system serves as a hot backup, still recieving data --- from all PLCs and coordinator(s) while in backup to allow --- instant failover if active goes offline without re-sync -SYSTEM_TYPE = 'active' -- scada network listen for PLC's and RTU's SCADA_DEV_LISTEN = 16000 --- failover synchronization -SCADA_FO_LOCAL = 16101 -SCADA_FO_PEER = 16102 -- listen port for SCADA supervisor access by coordinators -SCADA_SV_LISTEN = 16201 +SCADA_SV_LISTEN = 16100 -- expected number of reactors NUM_REACTORS = 4 diff --git a/supervisor/startup.lua b/supervisor/startup.lua index f58efd2..6a46f5d 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -39,15 +39,8 @@ if modem == nil then return end --- determine active/backup mode -local mode = comms.SCADA_SV_MODES.BACKUP -if config.SYSTEM_TYPE == "active" then - mode = comms.SCADA_SV_MODES.ACTIVE -end - -- start comms, open all channels -local comms = supervisor.superv_comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_FO_LOCAL, config.SCADA_FO_PEER, - config.SCADA_SV_CHANNEL) +local comms = supervisor.superv_comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN) -- base loop clock (4Hz, 5 ticks) local loop_clock = os.startTimer(0.25) @@ -96,7 +89,6 @@ while true do -- check for termination request if event == "terminate" or ppm.should_terminate() then log._warning("terminate requested, exiting...") - -- @todo: attempt failover, alert hot backup break end end diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index d6bfd54..86e35a2 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -11,16 +11,13 @@ local RTU_ADVERT_TYPES = comms.RTU_ADVERT_TYPES local SESSION_TYPE = svsessions.SESSION_TYPE -- supervisory controller communications -function superv_comms(mode, num_reactors, modem, dev_listen, fo_local, fo_peer, coord_listen) +function superv_comms(mode, num_reactors, modem, dev_listen, coord_listen) local self = { mode = mode, - fo_seq_num = 0, ln_seq_num = 0, num_reactors = num_reactors, modem = modem, dev_listen = dev_listen, - fo_rx = fo_local, - fo_tx = fo_peer, coord_listen = coord_listen, reactor_struct_cache = nil } @@ -32,21 +29,11 @@ function superv_comms(mode, num_reactors, modem, dev_listen, fo_local, fo_peer, if not self.modem.isOpen(self.dev_listen) then self.modem.open(self.dev_listen) end - if not self.modem.isOpen(self.fo_rx) then - self.modem.open(self.fo_rx) - end if not self.modem.isOpen(self.coord_listen) then self.modem.open(self.coord_listen) end end - local _send_fo = function (msg) - local packet = comms.scada_packet() - packet.make(self.fo_seq_num, PROTOCOLS.SCADA_MGMT, msg) - self.modem.transmit(self.fo_tx, self.fo_rx, packet.raw()) - self.fo_seq_num = self.fo_seq_num + 1 - end - local _send_plc_linking = function (dest, msg) local packet = comms.scada_packet() packet.make(self.ln_seq_num, PROTOCOLS.RPLC, msg) @@ -146,13 +133,6 @@ function superv_comms(mode, num_reactors, modem, dev_listen, fo_local, fo_peer, else log._debug("illegal packet type " .. protocol .. " on device listening channel") end - -- failover listening channel - elseif receiver == self.fo_rx then - if protocol == PROTOCOLS.SCADA_MGMT then - -- SCADA management packet - else - log._debug("illegal packet type " .. protocol .. " on failover listening channel") - end -- coordinator listening channel elseif reciever == self.coord_listen then if protocol == PROTOCOLS.SCADA_MGMT then From 78a1073e2ad0b5b5f2bf1d780bc7a136197bf375 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 22 Apr 2022 15:43:25 -0400 Subject: [PATCH 075/587] #30 comms rework --- scada-common/comms.lua | 242 +++++++++++++++++++++++++++++++++-------- 1 file changed, 198 insertions(+), 44 deletions(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 28529ac..c4b8791 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -2,7 +2,8 @@ PROTOCOLS = { MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol RPLC = 1, -- reactor PLC protocol SCADA_MGMT = 2, -- SCADA supervisor management, device advertisements, etc - COORD_DATA = 3 -- data packets for coordinators to/from supervisory controller + COORD_DATA = 3, -- data/control packets for coordinators to/from supervisory controllers + COORD_API = 4 -- data/control packets for pocket computers to/from coordinators } RPLC_TYPES = { @@ -44,20 +45,24 @@ function scada_packet() local self = { modem_msg_in = nil, valid = false, + raw = nil, seq_num = nil, protocol = nil, length = nil, - raw = nil + payload = nil } + -- make a SCADA packet local make = function (seq_num, protocol, payload) self.valid = true self.seq_num = seq_num self.protocol = protocol self.length = #payload - self.raw = { self.seq_num, self.protocol, self.length, payload } + self.payload = payload + self.raw = { self.seq_num, self.protocol, self.payload } end + -- parse in a modem message as a SCADA packet local receive = function (side, sender, reply_to, message, distance) self.modem_msg_in = { iface = side, @@ -69,17 +74,19 @@ function scada_packet() self.raw = self.modem_msg_in.msg - if #self.raw < 3 then - -- malformed - return false - else + if #self.raw >= 3 then self.valid = true self.seq_num = self.raw[1] self.protocol = self.raw[2] - self.length = self.raw[3] + self.length = #self.raw[3] + self.payload = self.raw[3] end + + return self.valid end + -- public accessors -- + local modem_event = function () return self.modem_msg_in end local raw = function () return self.raw end @@ -91,23 +98,21 @@ function scada_packet() local seq_num = function () return self.seq_num end local protocol = function () return self.protocol end local length = function () return self.length end - - local data = function () - local subset = nil - if self.valid then - subset = { table.unpack(self.raw, 4, 3 + self.length) } - end - return subset - end + local data = function () return self.payload end return { + -- construct make = make, receive = receive, + -- raw access modem_event = modem_event, raw = raw, + -- sender/receiver sender = sender, receiver = receiver, + -- well-formed is_valid = is_valid, + -- packet properties seq_num = seq_num, protocol = protocol, length = length, @@ -115,10 +120,12 @@ function scada_packet() } end --- MODBUS packet +-- MODBUS packet +-- modeled after MODBUS TCP packet, but length is not transmitted since these are tables (#data = length, easy) function modbus_packet() local self = { frame = nil, + raw = nil, txn_id = txn_id, protocol = protocol, length = length, @@ -128,13 +135,19 @@ function modbus_packet() } -- make a MODBUS packet - local make = function (txn_id, protocol, length, unit_id, func_code, data) + local make = function (txn_id, protocol, unit_id, func_code, data) self.txn_id = txn_id self.protocol = protocol - self.length = length + self.length = #data self.unit_id = unit_id self.func_code = func_code self.data = data + + -- populate raw array + self.raw = { self.txn_id, self.protocol, self.unit_id, self.func_code } + for i = 1, self.length do + table.insert(self.raw, data[i]) + end end -- decode a MODBUS packet from a SCADA frame @@ -142,20 +155,28 @@ function modbus_packet() if frame then self.frame = frame - local data = frame.data() - local size_ok = #data ~= 6 - - if size_ok then - make(data[1], data[2], data[3], data[4], data[5], data[6]) + if frame.protocol() == PROTOCOLS.MODBUS_TCP then + local size_ok = frame.length() >= 4 + + if size_ok then + local data = frame.data() + make(data[1], data[2], data[3], data[4], { table.unpack(data, 5, #data) }) + end + + return size_ok + else + log._debug("attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true) + return false end - - return size_ok and self.protocol == comms.PROTOCOLS.MODBUS_TCP else log._debug("nil frame encountered", true) return false end end + -- get raw to send + local raw_sendable = function () return self.raw end + -- get this packet local get = function () return { @@ -170,8 +191,12 @@ function modbus_packet() end return { + -- construct make = make, decode = decode, + -- raw access + raw_sendable = raw_sendable, + -- formatted access get = get } end @@ -180,12 +205,14 @@ end function rplc_packet() local self = { frame = nil, + raw = nil, id = nil, type = nil, length = nil, body = nil } + -- check that type is known local _rplc_type_valid = function () return self.type == RPLC_TYPES.KEEP_ALIVE or self.type == RPLC_TYPES.LINK_REQ or @@ -200,11 +227,18 @@ function rplc_packet() end -- make an RPLC packet - local make = function (id, packet_type, length, data) + local make = function (id, packet_type, data) + -- packet accessor properties self.id = id self.type = packet_type - self.length = length + self.length = #data self.data = data + + -- populate raw array + self.raw = { self.id, self.type } + for i = 1, #data do + table.insert(self.raw, data[i]) + end end -- decode an RPLC packet from a SCADA frame @@ -212,12 +246,12 @@ function rplc_packet() if frame then self.frame = frame - if frame.protocol() == comms.PROTOCOLS.RPLC then - local data = frame.data() - local ok = #data > 2 + if frame.protocol() == PROTOCOLS.RPLC then + local ok = frame.length() >= 2 if ok then - make(data[1], data[2], data[3], { table.unpack(data, 4, #data) }) + local data = frame.data() + make(data[1], data[2], { table.unpack(data, 3, #data) }) ok = _rplc_type_valid() end @@ -232,6 +266,10 @@ function rplc_packet() end end + -- get raw to send + local raw_sendable = function () return self.raw end + + -- get this packet local get = function () return { scada_frame = self.frame, @@ -243,8 +281,12 @@ function rplc_packet() end return { + -- construct make = make, decode = decode, + -- raw access + raw_sendable = raw_sendable, + -- formatted access get = get } end @@ -253,11 +295,13 @@ end function mgmt_packet() local self = { frame = nil, + raw = nil, type = nil, length = nil, data = nil } + -- check that type is known local _scada_type_valid = function () return self.type == SCADA_MGMT_TYPES.PING or self.type == SCADA_MGMT_TYPES.SV_HEARTBEAT or @@ -267,10 +311,17 @@ function mgmt_packet() end -- make a SCADA management packet - local make = function (packet_type, length, data) + local make = function (packet_type, data) + -- packet accessor properties self.type = packet_type - self.length = length + self.length = #data self.data = data + + -- populate raw array + self.raw = { self.type } + for i = 1, #data do + table.insert(self.raw, data[i]) + end end -- decode a SCADA management packet from a SCADA frame @@ -278,12 +329,12 @@ function mgmt_packet() if frame then self.frame = frame - if frame.protocol() == comms.PROTOCOLS.SCADA_MGMT then - local data = frame.data() - local ok = #data > 1 + if frame.protocol() == PROTOCOLS.SCADA_MGMT then + local ok = #data >= 1 if ok then - make(data[1], data[2], { table.unpack(data, 3, #data) }) + local data = frame.data() + make(data[1], { table.unpack(data, 2, #data) }) ok = _scada_type_valid() end @@ -298,6 +349,10 @@ function mgmt_packet() end end + -- get raw to send + local raw_sendable = function () return self.raw end + + -- get this packet local get = function () return { scada_frame = self.frame, @@ -308,8 +363,12 @@ function mgmt_packet() end return { + -- construct make = make, decode = decode, + -- raw access + raw_sendable = raw_sendable, + -- formatted access get = get } end @@ -319,6 +378,7 @@ end function coord_packet() local self = { frame = nil, + raw = nil, type = nil, length = nil, data = nil @@ -330,10 +390,17 @@ function coord_packet() end -- make a coordinator packet - local make = function (packet_type, length, data) + local make = function (packet_type, data) + -- packet accessor properties self.type = packet_type - self.length = length + self.length = #data self.data = data + + -- populate raw array + self.raw = { self.type } + for i = 1, #data do + table.insert(self.raw, data[i]) + end end -- decode a coordinator packet from a SCADA frame @@ -341,12 +408,12 @@ function coord_packet() if frame then self.frame = frame - if frame.protocol() == comms.PROTOCOLS.COORD_DATA then - local data = frame.data() - local ok = #data > 1 + if frame.protocol() == PROTOCOLS.COORD_DATA then + local ok = #data >= 1 if ok then - make(data[1], data[2], { table.unpack(data, 3, #data) }) + local data = frame.data() + make(data[1], { table.unpack(data, 2, #data) }) ok = _coord_type_valid() end @@ -361,6 +428,10 @@ function coord_packet() end end + -- get raw to send + local raw_sendable = function () return self.raw end + + -- get this packet local get = function () return { scada_frame = self.frame, @@ -371,8 +442,91 @@ function coord_packet() end return { + -- construct make = make, decode = decode, + -- raw access + raw_sendable = raw_sendable, + -- formatted access + get = get + } +end + +-- coordinator API (CAPI) packet +-- @todo +function capi_packet() + local self = { + frame = nil, + raw = nil, + type = nil, + length = nil, + data = nil + } + + local _coord_type_valid = function () + -- @todo + return false + end + + -- make a coordinator packet + local make = function (packet_type, data) + -- packet accessor properties + self.type = packet_type + self.length = #data + self.data = data + + -- populate raw array + self.raw = { self.type } + for i = 1, #data do + table.insert(self.raw, data[i]) + end + end + + -- decode a coordinator packet from a SCADA frame + local decode = function (frame) + if frame then + self.frame = frame + + if frame.protocol() == PROTOCOLS.COORD_API then + local ok = #data >= 1 + + if ok then + local data = frame.data() + make(data[1], { table.unpack(data, 2, #data) }) + ok = _coord_type_valid() + end + + return ok + else + log._debug("attempted COORD_API parse of incorrect protocol " .. frame.protocol(), true) + return false + end + else + log._debug("nil frame encountered", true) + return false + end + end + + -- get raw to send + local raw_sendable = function () return self.raw end + + -- get this packet + local get = function () + return { + scada_frame = self.frame, + type = self.type, + length = self.length, + data = self.data + } + end + + return { + -- construct + make = make, + decode = decode, + -- raw access + raw_sendable = raw_sendable, + -- formatted access get = get } end From 912011bfede5c239cbf170a203a1a9d575d715f8 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 22 Apr 2022 20:21:28 -0400 Subject: [PATCH 076/587] #30 modbus comms changes --- scada-common/comms.lua | 17 ++++++-------- scada-common/modbus.lua | 51 ++++++++++++++++++++++++++++------------- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index c4b8791..972f768 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -88,7 +88,7 @@ function scada_packet() -- public accessors -- local modem_event = function () return self.modem_msg_in end - local raw = function () return self.raw end + local raw_sendable = function () return self.raw end local sender = function () return self.s_port end local receiver = function () return self.r_port end @@ -106,7 +106,7 @@ function scada_packet() receive = receive, -- raw access modem_event = modem_event, - raw = raw, + raw_sendable = raw_sendable, -- sender/receiver sender = sender, receiver = receiver, @@ -121,13 +121,12 @@ function scada_packet() end -- MODBUS packet --- modeled after MODBUS TCP packet, but length is not transmitted since these are tables (#data = length, easy) +-- modeled after MODBUS TCP packet function modbus_packet() local self = { frame = nil, raw = nil, txn_id = txn_id, - protocol = protocol, length = length, unit_id = unit_id, func_code = func_code, @@ -135,16 +134,15 @@ function modbus_packet() } -- make a MODBUS packet - local make = function (txn_id, protocol, unit_id, func_code, data) + local make = function (txn_id, unit_id, func_code, data) self.txn_id = txn_id - self.protocol = protocol self.length = #data self.unit_id = unit_id self.func_code = func_code self.data = data -- populate raw array - self.raw = { self.txn_id, self.protocol, self.unit_id, self.func_code } + self.raw = { self.txn_id, self.unit_id, self.func_code } for i = 1, self.length do table.insert(self.raw, data[i]) end @@ -156,11 +154,11 @@ function modbus_packet() self.frame = frame if frame.protocol() == PROTOCOLS.MODBUS_TCP then - local size_ok = frame.length() >= 4 + local size_ok = frame.length() >= 3 if size_ok then local data = frame.data() - make(data[1], data[2], data[3], data[4], { table.unpack(data, 5, #data) }) + make(data[1], data[2], data[3], { table.unpack(data, 4, #data) }) end return size_ok @@ -182,7 +180,6 @@ function modbus_packet() return { scada_frame = self.frame, txn_id = self.txn_id, - protocol = self.protocol, length = self.length, unit_id = self.unit_id, func_code = self.func_code, diff --git a/scada-common/modbus.lua b/scada-common/modbus.lua index a8148b7..494fde8 100644 --- a/scada-common/modbus.lua +++ b/scada-common/modbus.lua @@ -203,10 +203,10 @@ function new(rtu_dev) return return_ok, response end + -- handle a MODBUS TCP packet and generate a reply local handle_packet = function (packet) local return_code = true local response = nil - local reply = packet if #packet.data == 2 then -- handle by function code @@ -236,32 +236,51 @@ function new(rtu_dev) return_code = false end - if return_code then - -- default is to echo back - if type(response) == "table" then - reply.length = #response - reply.data = response - end - else + -- default is to echo back + local func_code = packet.func_code + if not return_code then -- echo back with error flag - reply.func_code = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) + func_code = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) if type(response) == "nil" then - reply.length = 0 - reply.data = {} + response = { } elseif type(response) == "number" then - reply.length = 1 - reply.data = { response } + response = { response } elseif type(response) == "table" then - reply.length = #response - reply.data = response + response = response end end + -- create reply + local reply = comms.modbus_packet() + reply.make(packet.txn_id, packet.unit_id, func_code, response) + return return_code, reply end + -- return a NEG_ACKNOWLEDGE error reply + local reply__neg_ack = function (packet) + -- reply back with error flag and exception code + local reply = comms.modbus_packet() + local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) + local data = { MODBUS_EXCODE.NEG_ACKNOWLEDGE } + reply.make(packet.txn_id, packet.unit_id, fcode, data) + return reply + end + + -- return a GATEWAY_PATH_UNAVAILABLE error reply + local reply__gw_unavailable = function (packet) + -- reply back with error flag and exception code + local reply = comms.modbus_packet() + local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) + local data = { MODBUS_EXCODE.GATEWAY_PATH_UNAVAILABLE } + reply.make(packet.txn_id, packet.unit_id, fcode, data) + return reply + end + return { - handle_packet = handle_packet + handle_packet = handle_packet, + reply__neg_ack = reply__neg_ack, + reply__gw_unavailable = reply__gw_unavailable } end From 554f09c81749b9e080915bc8615e792b9fda7e8d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 22 Apr 2022 20:23:40 -0400 Subject: [PATCH 077/587] #30 RTU comms code updated for new comms design --- rtu/rtu.lua | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 23ace96..dde73af 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -133,10 +133,21 @@ function rtu_comms(modem, local_port, server_port) -- PRIVATE FUNCTIONS -- - local _send = function (protocol, msg) - local packet = comms.scada_packet() - packet.make(self.seq_num, protocol, msg) - self.modem.transmit(self.s_port, self.l_port, packet.raw()) + local _send = function (msg_type, msg) + local s_pkt = comms.scada_packet() + local m_pkt = comms.mgmt_packet() + + m_pkt.make(msg_type, msg) + s_pkt.make(self.seq_num, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) + + self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) + self.seq_num = self.seq_num + 1 + end + + local _send_modbus = function (m_pkt) + local s_pkt = comms.scada_packet() + s_pkt.make(self.seq_num, PROTOCOLS.MODBUS_TCP, m_pkt.raw_sendable()) + self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) self.seq_num = self.seq_num + 1 end @@ -171,24 +182,29 @@ function rtu_comms(modem, local_port, server_port) return pkt end + -- handle a MODBUS/SCADA packet local handle_packet = function(packet, units, ref) if packet ~= nil then local protocol = packet.scada_frame.protocol() if protocol == PROTOCOLS.MODBUS_TCP then + local reply = modbus.reply__neg_ack(packet) + -- MODBUS instruction if packet.unit_id <= #units then local unit = units[packet.unit_id] - local return_code, response = unit.modbus_io.handle_packet(packet) - _send(PROTOCOLS.MODBUS_TCP, response) + local return_code, reply = unit.modbus_io.handle_packet(packet) if not return_code then log._warning("MODBUS operation failed") end else -- unit ID out of range? + reply = modbus.reply__gw_unavailable(packet) log._error("MODBUS packet requesting non-existent unit") end + + _send_modbus(reply) elseif protocol == PROTOCOLS.SCADA_MGMT then -- SCADA management packet if packet.type == SCADA_MGMT_TYPES.REMOTE_LINKED then @@ -210,10 +226,7 @@ function rtu_comms(modem, local_port, server_port) -- send capability advertisement local send_advertisement = function (units) - local advertisement = { - type = SCADA_MGMT_TYPES.RTU_ADVERT, - units = {} - } + local advertisement = {} for i = 1, #units do local type = nil @@ -230,7 +243,7 @@ function rtu_comms(modem, local_port, server_port) if type ~= nil then if type == RTU_ADVERT_TYPES.REDSTONE then - table.insert(advertisement.units, { + table.insert(advertisement, { unit = i, type = type, index = units[i].index, @@ -238,7 +251,7 @@ function rtu_comms(modem, local_port, server_port) rsio = units[i].device }) else - table.insert(advertisement.units, { + table.insert(advertisement, { unit = i, type = type, index = units[i].index, @@ -249,15 +262,11 @@ function rtu_comms(modem, local_port, server_port) end end - _send(PROTOCOLS.SCADA_MGMT, advertisement) + _send(SCADA_MGMT_TYPES.RTU_ADVERT, advertisement) end local send_heartbeat = function () - local heartbeat = { - type = SCADA_MGMT_TYPES.RTU_HEARTBEAT - } - - _send(PROTOCOLS.SCADA_MGMT, heartbeat) + _send(SCADA_MGMT_TYPES.RTU_HEARTBEAT, {}) end return { From b25d95eeb7db482765b29f528588f306ac109979 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 22 Apr 2022 21:39:03 -0400 Subject: [PATCH 078/587] #30 PLC comms code updated for new comms design --- reactor-plc/plc.lua | 148 ++++++++++++++++++-------------------------- 1 file changed, 60 insertions(+), 88 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index c7a6346..6f9622d 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -220,10 +220,14 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- PRIVATE FUNCTIONS -- - local _send = function (msg) - local packet = scada_packet() - packet.make(self.seq_num, PROTOCOLS.RPLC, msg) - self.modem.transmit(self.s_port, self.l_port, packet.raw()) + local _send = function (msg_type, msg) + local s_pkt = comms.scada_packet() + local r_pkt = comms.rplc_packet() + + r_pkt.make(self.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()) self.seq_num = self.seq_num + 1 end @@ -231,28 +235,28 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) local _reactor_status = function () ppm.clear_fault() return { - status = self.reactor.getStatus(), - burn_rate = self.reactor.getBurnRate(), - act_burn_r = self.reactor.getActualBurnRate(), - temp = self.reactor.getTemperature(), - damage = self.reactor.getDamagePercent(), - boil_eff = self.reactor.getBoilEfficiency(), - env_loss = self.reactor.getEnvironmentalLoss(), + self.reactor.getStatus(), + self.reactor.getBurnRate(), + self.reactor.getActualBurnRate(), + self.reactor.getTemperature(), + self.reactor.getDamagePercent(), + self.reactor.getBoilEfficiency(), + self.reactor.getEnvironmentalLoss(), - fuel = self.reactor.getFuel(), - fuel_need = self.reactor.getFuelNeeded(), - fuel_fill = self.reactor.getFuelFilledPercentage(), - waste = self.reactor.getWaste(), - waste_need = self.reactor.getWasteNeeded(), - waste_fill = self.reactor.getWasteFilledPercentage(), - cool_type = self.reactor.getCoolant()['name'], - cool_amnt = self.reactor.getCoolant()['amount'], - cool_need = self.reactor.getCoolantNeeded(), - cool_fill = self.reactor.getCoolantFilledPercentage(), - hcool_type = self.reactor.getHeatedCoolant()['name'], - hcool_amnt = self.reactor.getHeatedCoolant()['amount'], - hcool_need = self.reactor.getHeatedCoolantNeeded(), - hcool_fill = self.reactor.getHeatedCoolantFilledPercentage() + self.reactor.getFuel(), + self.reactor.getFuelNeeded(), + self.reactor.getFuelFilledPercentage(), + self.reactor.getWaste(), + self.reactor.getWasteNeeded(), + self.reactor.getWasteFilledPercentage(), + self.reactor.getCoolant()['name'], + self.reactor.getCoolant()['amount'], + self.reactor.getCoolantNeeded(), + self.reactor.getCoolantFilledPercentage(), + self.reactor.getHeatedCoolant()['name'], + self.reactor.getHeatedCoolant()['amount'], + self.reactor.getHeatedCoolantNeeded(), + self.reactor.getHeatedCoolantFilledPercentage() }, ppm.faulted() end @@ -261,8 +265,8 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) local changed = false if not faulted then - for key, value in pairs(status) do - if value ~= self.status_cache[key] then + for i = 1, #status do + if status[i] ~= self.status_cache[i] then changed = true break end @@ -277,50 +281,33 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) end -- keep alive ack - local _send_keep_alive_ack = function () - local keep_alive_data = { - id = self.id, - timestamp = os.time(), - type = RPLC_TYPES.KEEP_ALIVE - } - - _send(keep_alive_data) + local _send_keep_alive_ack = function (srv_time) + _send(RPLC_TYPES.KEEP_ALIVE, { srv_time, os.time() }) end -- general ack - local _send_ack = function (type, succeeded) - local ack_data = { - id = self.id, - type = type, - ack = succeeded - } - - _send(ack_data) + local _send_ack = function (msg_type, succeeded) + _send(msg_type, { succeeded }) end -- send structure properties (these should not change) -- (server will cache these) local _send_struct = function () ppm.clear_fault() + local mek_data = { - heat_cap = self.reactor.getHeatCapacity(), - fuel_asm = self.reactor.getFuelAssemblies(), - fuel_sa = self.reactor.getFuelSurfaceArea(), - fuel_cap = self.reactor.getFuelCapacity(), - waste_cap = self.reactor.getWasteCapacity(), - cool_cap = self.reactor.getCoolantCapacity(), - hcool_cap = self.reactor.getHeatedCoolantCapacity(), - max_burn = self.reactor.getMaxBurnRate() + self.reactor.getHeatCapacity(), + self.reactor.getFuelAssemblies(), + self.reactor.getFuelSurfaceArea(), + self.reactor.getFuelCapacity(), + self.reactor.getWasteCapacity(), + self.reactor.getCoolantCapacity(), + self.reactor.getHeatedCoolantCapacity(), + self.reactor.getMaxBurnRate() } - if not faulted then - local struct_packet = { - id = self.id, - type = RPLC_TYPES.MEK_STRUCT, - mek_data = mek_data - } - - _send(struct_packet) + if not ppm.is_faulted() then + _send(RPLC_TYPES.MEK_STRUCT, mek_data) else log._error("failed to send structure: PPM fault") end @@ -381,7 +368,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) if packet.type == RPLC_TYPES.KEEP_ALIVE then -- keep alive request received, echo back local timestamp = packet.data[1] - local trip_time = os.time() - ts + local trip_time = os.time() - timestamp if trip_time < 0 then log._warning("PLC KEEP_ALIVE trip time less than 0 (" .. trip_time .. ")") @@ -389,7 +376,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) log._warning("PLC KEEP_ALIVE trip time > 1s (" .. trip_time .. ")") end - _send_keep_alive_ack() + _send_keep_alive_ack(timestamp) elseif packet.type == RPLC_TYPES.LINK_REQ then -- link request confirmation log._debug("received unsolicited link request response") @@ -489,12 +476,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- attempt to establish link with supervisor local send_link_req = function () - local linking_data = { - id = self.id, - type = RPLC_TYPES.LINK_REQ - } - - _send(linking_data) + _send(RPLC_TYPES.LINK_REQ, {}) end -- send live status information @@ -508,38 +490,28 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) end local sys_status = { - id = self.id, - type = RPLC_TYPES.STATUS, - timestamp = os.time(), - control_state = not self.scrammed, - overridden = overridden, - degraded = degraded, - heating_rate = self.reactor.getHeatingRate(), - mek_data = mek_data + os.time(), + (not self.scrammed), + overridden, + degraded, + self.reactor.getHeatingRate(), + mek_data } - _send(sys_status) + _send(RPLC_TYPES.STATUS, sys_status) end local send_iss_status = function () - local iss_status = { - id = self.id, - type = RPLC_TYPES.ISS_STATUS, - status = iss.status() - } - - _send(iss_status) + _send(RPLC_TYPES.ISS_STATUS, iss.status()) end local send_iss_alarm = function (cause) local iss_alarm = { - id = self.id, - type = RPLC_TYPES.ISS_ALARM, - cause = cause, - status = iss.status() + cause, + iss.status() } - _send(iss_alarm) + _send(RPLC_TYPES.ISS_ALARM, iss_alarm) end local is_scrammed = function () return self.scrammed end From 89ff502964b10e99e6c1857bcbab4306cb82efc9 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 22 Apr 2022 21:44:33 -0400 Subject: [PATCH 079/587] #30 supervisor comms code updated for new comms design --- supervisor/supervisor.lua | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 86e35a2..0ce314a 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -29,15 +29,24 @@ function superv_comms(mode, num_reactors, modem, dev_listen, coord_listen) if not self.modem.isOpen(self.dev_listen) then self.modem.open(self.dev_listen) end + if not self.modem.isOpen(self.coord_listen) then self.modem.open(self.coord_listen) end end + -- open at construct time + _open_channels() + + -- send PLC link request responses local _send_plc_linking = function (dest, msg) - local packet = comms.scada_packet() - packet.make(self.ln_seq_num, PROTOCOLS.RPLC, msg) - self.modem.transmit(dest, self.dev_listen, packet.raw()) + local s_pkt = comms.scada_packet() + local r_pkt = comms.rplc_packet() + + r_pkt.make(0, RPLC_TYPES.LINK_REQ, msg) + s_pkt.make(self.ln_seq_num, PROTOCOLS.RPLC, r_pkt.raw_sendable()) + + self.modem.transmit(dest, self.dev_listen, s_pkt.raw_sendable()) self.ln_seq_num = self.ln_seq_num + 1 end From cd289ffb1e05f88f15076b53c0b0d4971b92c330 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 22 Apr 2022 21:55:26 -0400 Subject: [PATCH 080/587] #30 svsessions PLC comms code updated for new comms design --- supervisor/session/plc.lua | 68 +++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 4008a33..bd5679c 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -95,22 +95,45 @@ function new_session(id, for_reactor, in_queue, out_queue) self.sDB.iss_status.timed_out = iss_status[7] end - local _copy_status = function (heating_rate, mek_data) - self.sDB.mek_status.heating_rate = heating_rate - for key, value in pairs(mek_data) do - self.sDB.mek_status[key] = value - end + local _copy_status = function (mek_data) + self.sDB.mek_status.status = mek_data[1] + self.sDB.mek_status.burn_rate = mek_data[2] + self.sDB.mek_status.act_burn_rate = mek_data[3] + self.sDB.mek_status.temp = mek_data[4] + self.sDB.mek_status.damage = mek_data[5] + self.sDB.mek_status.boil_eff = mek_data[6] + self.sDB.mek_status.env_loss = mek_data[7] + + self.sDB.mek_status.fuel = mek_data[8] + self.sDB.mek_status.fuel_need = mek_data[9] + self.sDB.mek_status.fuel_fill = mek_data[10] + self.sDB.mek_status.waste = mek_data[11] + self.sDB.mek_status.waste_need = mek_data[12] + self.sDB.mek_status.waste_fill = mek_data[13] + self.sDB.mek_status.cool_type = mek_data[14] + self.sDB.mek_status.cool_amnt = mek_data[15] + self.sDB.mek_status.cool_need = mek_data[16] + self.sDB.mek_status.cool_fill = mek_data[17] + self.sDB.mek_status.hcool_type = mek_data[18] + self.sDB.mek_status.hcool_amnt = mek_data[19] + self.sDB.mek_status.hcool_need = mek_data[20] + self.sDB.mek_status.hcool_fill = mek_data[21] end local _copy_struct = function (mek_data) - for key, value in pairs(mek_data) do - self.sDB.mek_struct[key] = value - end + self.sDB.mek_struct.heat_cap = mek_data[1] + self.sDB.mek_struct.fuel_asm = mek_data[2] + self.sDB.mek_struct.fuel_sa = mek_data[3] + self.sDB.mek_struct.fuel_cap = mek_data[4] + self.sDB.mek_struct.waste_cap = mek_data[5] + self.sDB.mek_struct.cool_cap = mek_data[6] + self.sDB.mek_struct.hcool_cap = mek_data[7] + self.sDB.mek_struct.max_burn = mek_data[8] end local _get_ack = function (pkt) - if rplc_packet.length == 1 then - return rplc_packet.data[1] + if pkt.length == 1 then + return pkt.data[1] else log._warning(log_header .. "RPLC ACK length mismatch") return nil @@ -145,34 +168,33 @@ function new_session(id, for_reactor, in_queue, out_queue) if rplc_pkt.id == for_reactor then if rplc_pkt.type == RPLC_TYPES.KEEP_ALIVE then - -- periodic keep alive + -- keep alive reply elseif rplc_pkt.type == RPLC_TYPES.STATUS then -- status packet received, update data - if rplc_packet.length == 6 then + if rplc_pkt.length >= 5 then -- @todo [1] is timestamp, determine how this will be used (if at all) - self.sDB.control_state = rplc_packet.data[2] - self.sDB.overridden = rplc_packet.data[3] - self.sDB.degraded = rplc_packet.data[4] + self.sDB.control_state = rplc_pkt.data[2] + self.sDB.overridden = rplc_pkt.data[3] + self.sDB.degraded = rplc_pkt.data[4] + self.sDB.mek_status.heating_rate = rplc_pkt.data[5] -- attempt to read mek_data table - if rplc_packet.data[6] ~= nil then - local status = pcall(_copy_status, rplc_packet.data[5], rplc_packet.data[6]) + if rplc_pkt.data[6] ~= nil then + local status = pcall(_copy_status, rplc_pkt.data[6]) if status then -- copied in status data OK else -- error copying status data log._error(log_header .. "failed to parse status packet data") end - else - self.sDB.mek_status.heating_rate = rplc_packet.data[5] end else log._warning(log_header .. "RPLC status packet length mismatch") end elseif rplc_pkt.type == RPLC_TYPES.MEK_STRUCT then -- received reactor structure, record it - if rplc_packet.length == 1 then - local status = pcall(_copy_struct, rplc_packet.data[1]) + if rplc_pkt.length == 8 then + local status = pcall(_copy_struct, rplc_pkt.data) if status then -- copied in structure data OK else @@ -205,8 +227,8 @@ function new_session(id, for_reactor, in_queue, out_queue) end elseif rplc_pkt.type == RPLC_TYPES.ISS_STATUS then -- ISS status packet received, copy data - if rplc_packet.length == 1 then - local status = pcall(_copy_iss_status, rplc_packet.data[1]) + if rplc_pkt.length == 7 then + local status = pcall(_copy_iss_status, rplc_pkt.data) if status then -- copied in ISS status data OK else From 812d10f374c40241a025c463685784fa9e68e7fd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 23 Apr 2022 11:54:09 -0400 Subject: [PATCH 081/587] use epoch() instead of time() --- reactor-plc/plc.lua | 6 +++--- scada-common/alarm.lua | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 6f9622d..f0f00b1 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -282,7 +282,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- keep alive ack local _send_keep_alive_ack = function (srv_time) - _send(RPLC_TYPES.KEEP_ALIVE, { srv_time, os.time() }) + _send(RPLC_TYPES.KEEP_ALIVE, { srv_time, os.epoch() }) end -- general ack @@ -368,7 +368,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) if packet.type == RPLC_TYPES.KEEP_ALIVE then -- keep alive request received, echo back local timestamp = packet.data[1] - local trip_time = os.time() - timestamp + local trip_time = os.epoch() - timestamp if trip_time < 0 then log._warning("PLC KEEP_ALIVE trip time less than 0 (" .. trip_time .. ")") @@ -490,7 +490,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) end local sys_status = { - os.time(), + os.epoch(), (not self.scrammed), overridden, degraded, diff --git a/scada-common/alarm.lua b/scada-common/alarm.lua index b93df73..d6670a4 100644 --- a/scada-common/alarm.lua +++ b/scada-common/alarm.lua @@ -9,7 +9,7 @@ SEVERITY = { function scada_alarm(severity, device, message) local self = { - time = os.time(), + time = os.epoch(), ts_string = os.date("[%H:%M:%S]"), severity = severity, device = device, From 3285f829f6e85a09c7f0ef8d1f083d10284ffd63 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 23 Apr 2022 11:54:52 -0400 Subject: [PATCH 082/587] updated version for using epoch() --- reactor-plc/startup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index d413684..07bb078 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -10,7 +10,7 @@ os.loadAPI("scada-common/comms.lua") os.loadAPI("config.lua") os.loadAPI("plc.lua") -local R_PLC_VERSION = "alpha-v0.2.4" +local R_PLC_VERSION = "alpha-v0.2.5" local print = util.print local println = util.println From 852161317dcf943e314122226de1ed3da999cb75 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 23 Apr 2022 12:12:33 -0400 Subject: [PATCH 083/587] #7 initial PLC session supervisor code --- supervisor/session/plc.lua | 295 ++++++++++++++++++++---------- supervisor/session/svsessions.lua | 47 ++++- supervisor/startup.lua | 14 +- supervisor/supervisor.lua | 4 + 4 files changed, 258 insertions(+), 102 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index bd5679c..7b17feb 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -3,6 +3,7 @@ -- #REQUIRES log.lua -- #REQUIRES util.lua +local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES PLC_S_COMMANDS = { @@ -11,6 +12,10 @@ PLC_S_COMMANDS = { ISS_CLEAR = 2 } +local PERIODICS = { + KEEP_ALIVE = 1.0 +} + -- PLC supervisor session function new_session(id, for_reactor, in_queue, out_queue) local log_header = "plc_session(" .. id .. "): " @@ -23,16 +28,22 @@ function new_session(id, for_reactor, in_queue, out_queue) commanded_state = false, -- connection properties seq_num = 0, + r_seq_num = nil, connected = true, received_struct = false, - plc_conn_watchdog = util.new_watchdog(3) + plc_conn_watchdog = util.new_watchdog(3), + last_rtt = 0, -- when to next retry one of these requests + periodics = { + last_update = 0 + keep_alive = 0 + }, retry_times = { struct_req = 0, scram_req = 0, enable_req = 0 }, - -- session PLC status database + -- session database sDB = { control_state = false, overridden = false, @@ -140,12 +151,167 @@ function new_session(id, for_reactor, in_queue, out_queue) end end + local _handle_packet = function (message) + local checks_ok = true + + -- handle an incoming packet from the PLC + rplc_pkt = message.get() + + -- check sequence number + if self.r_seq_num == nil then + self.r_seq_num = rplc_pkt.scada_frame.seq_num() + elseif self.r_seq_num >= rplc_pkt.scada_frame.seq_num() then + log._warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. rplc_pkt.scada_frame.seq_num()) + checks_ok = false + else + self.r_seq_num = rplc_pkt.scada_frame.seq_num() + end + + -- check reactor ID + if rplc_pkt.id ~= for_reactor then + log._warning(log_header .. "RPLC packet with ID not matching reactor ID: reactor " .. self.for_reactor .. " != " .. rplc_pkt.id) + checks_ok = false + end + + -- process packet + if checks_ok then + -- feed watchdog + self.plc_conn_watchdog.feed() + + -- handle packet by type + if rplc_pkt.type == RPLC_TYPES.KEEP_ALIVE then + -- keep alive reply + if rplc_pkt.length == 2 then + local srv_start = rplc_pkt.data[1] + local plc_send = rplc_pkt.data[2] + local srv_now = os.epoch() + self.last_rtt = srv_now - srv_start + + if self.last_rtt < 0 then + log._warning(log_header .. "PLC KEEP_ALIVE round trip time less than 0 (" .. trip_time .. ")") + elseif trip_time > 1 then + log._warning(log_header .. "PLC KEEP_ALIVE round trip time > 1s (" .. trip_time .. ")") + end + + log._debug(log_header .. "RPLC RTT = ".. trip_time) + else + log._debug(log_header .. "RPLC keep alive packet length mismatch") + end + elseif rplc_pkt.type == RPLC_TYPES.STATUS then + -- status packet received, update data + if rplc_pkt.length >= 5 then + -- @todo [1] is timestamp, determine how this will be used (if at all) + self.sDB.control_state = rplc_pkt.data[2] + self.sDB.overridden = rplc_pkt.data[3] + self.sDB.degraded = rplc_pkt.data[4] + self.sDB.mek_status.heating_rate = rplc_pkt.data[5] + + -- attempt to read mek_data table + if rplc_pkt.data[6] ~= nil then + local status = pcall(_copy_status, rplc_pkt.data[6]) + if status then + -- copied in status data OK + else + -- error copying status data + log._error(log_header .. "failed to parse status packet data") + end + end + else + log._debug(log_header .. "RPLC status packet length mismatch") + end + elseif rplc_pkt.type == RPLC_TYPES.MEK_STRUCT then + -- received reactor structure, record it + if rplc_pkt.length == 8 then + local status = pcall(_copy_struct, rplc_pkt.data) + if status then + -- copied in structure data OK + else + -- error copying structure data + log._error(log_header .. "failed to parse struct packet data") + end + else + log._debug(log_header .. "RPLC struct packet length mismatch") + end + elseif rplc_pkt.type == RPLC_TYPES.MEK_SCRAM then + -- SCRAM acknowledgement + local ack = _get_ack(rplc_pkt) + if ack then + self.sDB.control_state = false + elseif ack == false then + log._warning(log_header .. "SCRAM failed!") + end + elseif rplc_pkt.type == RPLC_TYPES.MEK_ENABLE then + -- enable acknowledgement + local ack = _get_ack(rplc_pkt) + if ack then + self.sDB.control_state = true + elseif ack == false then + log._warning(log_header .. "enable failed!") + end + elseif rplc_pkt.type == RPLC_TYPES.MEK_BURN_RATE then + -- burn rate acknowledgement + if _get_ack(rplc_pkt) == false then + log._warning(log_header .. "burn rate update failed!") + end + elseif rplc_pkt.type == RPLC_TYPES.ISS_STATUS then + -- ISS status packet received, copy data + if rplc_pkt.length == 7 then + local status = pcall(_copy_iss_status, rplc_pkt.data) + if status then + -- copied in ISS status data OK + else + -- error copying ISS status data + log._error(log_header .. "failed to parse ISS status packet data") + end + else + log._debug(log_header .. "RPLC ISS status packet length mismatch") + end + elseif rplc_pkt.type == RPLC_TYPES.ISS_ALARM then + -- ISS alarm + self.sDB.overridden = true + if rplc_pkt.length == 7 then + local status = pcall(_copy_iss_status, rplc_pkt.data) + if status then + -- copied in ISS status data OK + else + -- error copying ISS status data + log._error(log_header .. "failed to parse ISS status packet data") + end + else + log._debug(log_header .. "RPLC ISS alarm packet length mismatch") + end + elseif rplc_pkt.type == RPLC_TYPES.ISS_CLEAR then + -- ISS clear acknowledgement + if _get_ack(rplc_pkt) == false then + log._warning(log_header .. "ISS clear failed") + end + else + log._debug(log_header .. "handler received unsupported RPLC packet type " .. rplc_pkt.type) + end + end + end + + local _send = function (msg_type, msg) + local s_pkt = comms.scada_packet() + local r_pkt = comms.rplc_packet() + + r_pkt.make(self.id, msg_type, msg) + s_pkt.make(self.seq_num, PROTOCOLS.RPLC, r_pkt.raw_sendable()) + + self.out_q.push_packet(s_pkt) + self.seq_num = self.seq_num + 1 + end + + -- PUBLIC FUNCTIONS -- + local get_id = function () return self.id end + local get_db = function () return self.sDB end + local close = function () self.connected = false end local check_wd = function (timer) - return timer == plc_conn_watchdog + return timer == self.plc_conn_watchdog.get_timer() end local get_struct = function () @@ -158,103 +324,37 @@ function new_session(id, for_reactor, in_queue, out_queue) end local iterate = function () - if self.connected and ~self.in_q.empty() then - -- get a new message to process - local message = self.in_q.pop() + if self.connected then + ------------------ + -- handle queue -- + ------------------ - if message.qtype == mqueue.TYPE.PACKET then - -- handle an incoming packet from the PLC - rplc_pkt = message.message.get() + if ~self.in_q.empty() then + -- get a new message to process + local message = self.in_q.pop() + + if message.qtype == mqueue.TYPE.PACKET then + _handle_packet(message.message) + elseif message.qtype == mqueue.TYPE.COMMAND then + -- handle instruction - if rplc_pkt.id == for_reactor then - if rplc_pkt.type == RPLC_TYPES.KEEP_ALIVE then - -- keep alive reply - elseif rplc_pkt.type == RPLC_TYPES.STATUS then - -- status packet received, update data - if rplc_pkt.length >= 5 then - -- @todo [1] is timestamp, determine how this will be used (if at all) - self.sDB.control_state = rplc_pkt.data[2] - self.sDB.overridden = rplc_pkt.data[3] - self.sDB.degraded = rplc_pkt.data[4] - self.sDB.mek_status.heating_rate = rplc_pkt.data[5] - - -- attempt to read mek_data table - if rplc_pkt.data[6] ~= nil then - local status = pcall(_copy_status, rplc_pkt.data[6]) - if status then - -- copied in status data OK - else - -- error copying status data - log._error(log_header .. "failed to parse status packet data") - end - end - else - log._warning(log_header .. "RPLC status packet length mismatch") - end - elseif rplc_pkt.type == RPLC_TYPES.MEK_STRUCT then - -- received reactor structure, record it - if rplc_pkt.length == 8 then - local status = pcall(_copy_struct, rplc_pkt.data) - if status then - -- copied in structure data OK - else - -- error copying structure data - log._error(log_header .. "failed to parse struct packet data") - end - else - log._warning(log_header .. "RPLC struct packet length mismatch") - end - elseif rplc_pkt.type == RPLC_TYPES.MEK_SCRAM then - -- SCRAM acknowledgement - local ack = _get_ack(rplc_pkt) - if ack then - self.sDB.control_state = false - elseif ack == false then - log._warning(log_header .. "SCRAM failed!") - end - elseif rplc_pkt.type == RPLC_TYPES.MEK_ENABLE then - -- enable acknowledgement - local ack = _get_ack(rplc_pkt) - if ack then - self.sDB.control_state = true - elseif ack == false then - log._warning(log_header .. "enable failed!") - end - elseif rplc_pkt.type == RPLC_TYPES.MEK_BURN_RATE then - -- burn rate acknowledgement - if _get_ack(rplc_pkt) == false then - log._warning(log_header .. "burn rate update failed!") - end - elseif rplc_pkt.type == RPLC_TYPES.ISS_STATUS then - -- ISS status packet received, copy data - if rplc_pkt.length == 7 then - local status = pcall(_copy_iss_status, rplc_pkt.data) - if status then - -- copied in ISS status data OK - else - -- error copying ISS status data - log._error(log_header .. "failed to parse ISS status packet data") - end - else - log._warning(log_header .. "RPLC ISS status packet length mismatch") - end - elseif rplc_pkt.type == RPLC_TYPES.ISS_ALARM then - -- ISS alarm - self.sDB.overridden = true - -- @todo - elseif rplc_pkt.type == RPLC_TYPES.ISS_CLEAR then - -- ISS clear acknowledgement - -- @todo - else - log._warning(log_header .. "handler received unsupported RPLC packet type " .. rplc_pkt.type) - end - else - log._warning(log_header .. "RPLC packet with ID not matching reactor ID: reactor " .. self.for_reactor .. " != " .. rplc_pkt.id) end - elseif message.qtype == mqueue.TYPE.COMMAND then - -- handle instruction - end + + ---------------------- + -- update periodics -- + ---------------------- + + local elapsed = os.clock() - self.periodics.last_update + + self.periodics.keep_alive += elapsed + + if self.periodics.keep_alive >= PERIODICS.KEEP_ALIVE then + _send(RPLC_TYPES.KEEP_ALIVE, { os.epoch() }) + self.periodics.keep_alive = 0 + end + + self.periodics.last_update = os.clock() end return self.connected @@ -262,9 +362,10 @@ function new_session(id, for_reactor, in_queue, out_queue) return { get_id = get_id, + get_db = get_db, + close = close, check_wd = check_wd, get_struct = get_struct, - close = close, iterate = iterate } end diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index fcc6950..4e0c75e 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -10,6 +10,7 @@ SESSION_TYPE = { } local self = { + modem = nil num_reactors = 0, rtu_sessions = {}, plc_sessions = {}, @@ -19,6 +20,10 @@ local self = { next_coord_id = 0 } +function link_modem(modem) + self.modem = modem +end + function alloc_reactor_plcs(num_reactors) self.num_reactors = num_reactors for i = 1, num_reactors do @@ -64,12 +69,13 @@ function get_reactor_session(reactor) return session end -function establish_plc_session(remote_port, for_reactor) +function establish_plc_session(local_port, remote_port, for_reactor) if get_reactor_session(for_reactor) == nil then local plc_s = { open = true, reactor = for_reactor, - r_host = remote_port, + l_port = local_port, + r_port = remote_port, in_queue = mqueue.new(), out_queue = mqueue.new(), instance = nil @@ -87,12 +93,46 @@ function establish_plc_session(remote_port, for_reactor) end end +local function _check_watchdogs(sessions, timer_event) + for i = 1, #sessions do + local session = sessions[i] + if session.open then + local triggered = session.instance.check_wd(timer_event) + if triggered then + log._debug("watchdog closing session " .. session.instance.get_id() .. " on remote port " .. session.r_port) + session.open = false + session.instance.close() + end + end + end +end + +function check_all_watchdogs(timer_event) + -- check RTU session watchdogs + _check_watchdogs(self.rtu_sessions, timer_event) + + -- check PLC session watchdogs + _check_watchdogs(self.plc_sessions, timer_event) + + -- check coordinator session watchdogs + _check_watchdogs(self.coord_sessions, timer_event) +end + local function _iterate(sessions) for i = 1, #sessions do local session = sessions[i] if session.open then local ok = session.instance.iterate() - if not ok then + if ok then + -- send packets in out queue + -- @todo handle commands if that's being used too + while not session.out_queue.empty() do + local msg = session.out_queue.pop() + if msg.qtype == mqueue.TYPE.PACKET then + self.modem.transmit(self.r_port, self.l_port, msg.message.raw_sendable()) + end + end + else session.open = false session.instance.close() end @@ -123,6 +163,7 @@ local function _free_closed(sessions) end move_to = move_to + 1 else + log._debug("free'ing closing session " .. session.instance.get_id() .. " on remote port " .. session.r_port) sessions[i] = nil end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 6a46f5d..1ad31bb 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -17,7 +17,7 @@ os.loadAPI("session/plc.lua") os.loadAPI("session/coordinator.lua") os.loadAPI("session/svsessions.lua") -local SUPERVISOR_VERSION = "alpha-v0.1.1" +local SUPERVISOR_VERSION = "alpha-v0.1.2" local print = util.print local println = util.println @@ -78,8 +78,18 @@ while true do end end elseif event == "timer" and param1 == loop_clock then - -- basic event tick, send keep-alives + -- main loop tick + + -- iterate sessions + svsessions.iterate_all() + + -- free any closed sessions + svsessions.free_all_closed() + loop_clock = os.startTimer(0.25) + elseif event == "timer" then + -- another timer event, check watchdogs + svsessions.check_all_watchdogs(param1) elseif event == "modem_message" then -- got a packet local packet = superv_comms.parse_packet(p1, p2, p3, p4, p5) diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 0ce314a..a3d2d2a 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -38,6 +38,9 @@ function superv_comms(mode, num_reactors, modem, dev_listen, coord_listen) -- open at construct time _open_channels() + -- link modem to svsessions + svsessions.link_modem(self.modem) + -- send PLC link request responses local _send_plc_linking = function (dest, msg) local s_pkt = comms.scada_packet() @@ -55,6 +58,7 @@ function superv_comms(mode, num_reactors, modem, dev_listen, coord_listen) -- reconnect a newly connected modem local reconnect_modem = function (modem) self.modem = modem + svsessions.link_modem(self.modem) _open_channels() end From fa19af308d4eda964c362aedd8ff9e7e1fd7d57e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 23 Apr 2022 20:46:01 -0400 Subject: [PATCH 084/587] bugfix and use timestamp in packet --- supervisor/session/plc.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 7b17feb..7694ef4 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -35,7 +35,7 @@ function new_session(id, for_reactor, in_queue, out_queue) last_rtt = 0, -- when to next retry one of these requests periodics = { - last_update = 0 + last_update = 0, keep_alive = 0 }, retry_times = { @@ -45,6 +45,7 @@ function new_session(id, for_reactor, in_queue, out_queue) }, -- session database sDB = { + last_status_update = 0, control_state = false, overridden = false, degraded = false, @@ -200,7 +201,7 @@ function new_session(id, for_reactor, in_queue, out_queue) elseif rplc_pkt.type == RPLC_TYPES.STATUS then -- status packet received, update data if rplc_pkt.length >= 5 then - -- @todo [1] is timestamp, determine how this will be used (if at all) + self.sDB.last_status_update = rplc_pkt.data[1] self.sDB.control_state = rplc_pkt.data[2] self.sDB.overridden = rplc_pkt.data[3] self.sDB.degraded = rplc_pkt.data[4] From 416255f41a8a48ad2cf2fa6e6004294a5f895c42 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 23 Apr 2022 21:10:25 -0400 Subject: [PATCH 085/587] PLC check sequence numbers, corrected trip time to ms --- reactor-plc/plc.lua | 14 +++++++++++++- supervisor/session/plc.lua | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index f0f00b1..64adb88 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -202,6 +202,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) local self = { id = id, seq_num = 0, + r_seq_num = nil, modem = modem, s_port = server_port, l_port = local_port, @@ -363,6 +364,17 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- handle an RPLC packet local handle_packet = function (packet, plc_state) if packet ~= nil then + -- check sequence number + if self.r_seq_num == nil then + self.r_seq_num = packet.scada_frame.seq_num() + elseif self.r_seq_num >= packet.scada_frame.seq_num() then + log._warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + return + else + self.r_seq_num = packet.scada_frame.seq_num() + end + + -- handle packet if packet.scada_frame.protocol() == PROTOCOLS.RPLC then if self.linked then if packet.type == RPLC_TYPES.KEEP_ALIVE then @@ -372,7 +384,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) if trip_time < 0 then log._warning("PLC KEEP_ALIVE trip time less than 0 (" .. trip_time .. ")") - elseif trip_time > 1 then + elseif trip_time > 1000 then log._warning("PLC KEEP_ALIVE trip time > 1s (" .. trip_time .. ")") end diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 7694ef4..9fbed9b 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -190,11 +190,11 @@ function new_session(id, for_reactor, in_queue, out_queue) if self.last_rtt < 0 then log._warning(log_header .. "PLC KEEP_ALIVE round trip time less than 0 (" .. trip_time .. ")") - elseif trip_time > 1 then + elseif trip_time > 1000 then log._warning(log_header .. "PLC KEEP_ALIVE round trip time > 1s (" .. trip_time .. ")") end - log._debug(log_header .. "RPLC RTT = ".. trip_time) + log._debug(log_header .. "RPLC RTT = ".. trip_time .. "ms") else log._debug(log_header .. "RPLC keep alive packet length mismatch") end From 86b0d155fa8d2d5e2202ca22968086464bf6f78d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 24 Apr 2022 12:04:31 -0400 Subject: [PATCH 086/587] #31 PPM cannot assume that we will get a fault on failure, apparently sometimes we will only get a nil return so the system can no longer check ACCESS_OK, now each device has its own fault tracking --- reactor-plc/plc.lua | 14 +++-- reactor-plc/startup.lua | 5 +- rtu/startup.lua | 2 +- scada-common/ppm.lua | 115 +++++++++++++++++++++---------------- supervisor/session/plc.lua | 2 +- supervisor/startup.lua | 2 +- 6 files changed, 81 insertions(+), 59 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 64adb88..f0f26c8 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -131,7 +131,8 @@ function iss_init(reactor) log._warning("ISS: reactor SCRAM") self.tripped = true self.trip_cause = status - if self.reactor.scram() == ppm.ACCESS_FAULT then + self.reactor.scram() + if self.reactor.__p_is_faulted() then log._error("ISS: failed reactor SCRAM") end end @@ -420,12 +421,14 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- disable the reactor self.scrammed = true plc_state.scram = true - _send_ack(packet.type, self.reactor.scram() == ppm.ACCESS_OK) + self.reactor.scram() + _send_ack(packet.type, self.reactor.__p_is_ok()) elseif packet.type == RPLC_TYPES.MEK_ENABLE then -- enable the reactor self.scrammed = false plc_state.scram = false - _send_ack(packet.type, self.reactor.activate() == ppm.ACCESS_OK) + self.reactor.activate() + _send_ack(packet.type, self.reactor.__p_is_ok()) elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then -- set the burn rate local success = false @@ -441,11 +444,12 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- if we know our max burn rate, update current burn rate if in range if max_burn_rate ~= ppm.ACCESS_FAULT then if burn_rate > 0 and burn_rate <= max_burn_rate then - success = self.reactor.setBurnRate(burn_rate) + self.reactor.setBurnRate(burn_rate) + success = self.reactor.__p_is_ok() end end - _send_ack(packet.type, success == ppm.ACCESS_OK) + _send_ack(packet.type, success) elseif packet.type == RPLC_TYPES.ISS_CLEAR then -- clear the ISS status iss.reset() diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 07bb078..7cef958 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -10,7 +10,7 @@ os.loadAPI("scada-common/comms.lua") os.loadAPI("config.lua") os.loadAPI("plc.lua") -local R_PLC_VERSION = "alpha-v0.2.5" +local R_PLC_VERSION = "alpha-v0.2.6" local print = util.print local println = util.println @@ -281,7 +281,8 @@ while true do log._warning("terminate requested, exiting...") if plc_state.init_ok then plc_state.scram = true - if reactor.scram() ~= ppm.ACCESS_FAULT then + reactor.scram() + if reactor.__p_is_ok() then println_ts("reactor disabled") else -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_LOST_CONTROL) ? diff --git a/rtu/startup.lua b/rtu/startup.lua index 42779c6..4534f18 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -17,7 +17,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.2.3" +local RTU_VERSION = "alpha-v0.2.4" local print = util.print local println = util.println diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 66f26a7..f383bae 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -4,14 +4,13 @@ -- Protected Peripheral Manager -- -ACCESS_OK = true ACCESS_FAULT = nil ---------------------------- -- PRIVATE DATA/FUNCTIONS -- ---------------------------- -local self = { +local _ppm_sys = { mounts = {}, auto_cf = false, faulted = false, @@ -20,38 +19,66 @@ local self = { } -- wrap peripheral calls with lua protected call --- ex. reason: we don't want a disconnect to crash the program before a SCRAM -local peri_init = function (device) - for key, func in pairs(device) do - device[key] = function (...) +-- we don't want a disconnect to crash a program +-- also provides peripheral-specific fault checks (auto-clear fault defaults to true) +local peri_init = function (iface) + local self = { + faulted = false, + auto_cf = true, + type = peripheral.getType(iface), + device = peripheral.wrap(iface) + } + + -- initialization process (re-map) + + for key, func in pairs(self.device) do + self.device[key] = function (...) local status, result = pcall(func, ...) if status then -- auto fault clear if self.auto_cf then self.faulted = false end - - -- assume nil is only for functions with no return, so return status - if result == nil then - return ACCESS_OK - else - return result - end + if _ppm_sys.auto_cf then _ppm_sys.faulted = false end + return result else -- function failed self.faulted = true + _ppm_sys.faulted = true - if not mute then + if not _ppm_sys.mute then log._error("PPM: protected " .. key .. "() -> " .. result) end if result == "Terminated" then - self.terminate = true + _ppm_sys.terminate = true end return ACCESS_FAULT end end end + + -- fault management functions + + local clear_fault = function () self.faulted = false end + local is_faulted = function () return self.faulted end + local is_ok = function () return not self.faulted end + + local enable_afc = function () self.auto_cf = true end + local disable_afc = function () self.auto_cf = false end + + -- append to device functions + + self.device.__p_clear_fault = clear_fault + self.device.__p_is_faulted = is_faulted + self.device.__p_is_ok = is_ok + self.device.__p_enable_afc = enable_afc + self.device.__p_disable_afc = disable_afc + + return { + type = self.type, + dev = self.device + } end ---------------------- @@ -62,41 +89,41 @@ end -- silence error prints function disable_reporting() - self.mute = true + _ppm_sys.mute = true end -- allow error prints function enable_reporting() - self.mute = false + _ppm_sys.mute = false end -- FAULT MEMORY -- -- enable automatically clearing fault flag function enable_afc() - self.auto_cf = true + _ppm_sys.auto_cf = true end -- disable automatically clearing fault flag function disable_afc() - self.auto_cf = false + _ppm_sys.auto_cf = false end -- check fault flag function is_faulted() - return self.faulted + return _ppm_sys.faulted end -- clear fault flag function clear_fault() - self.faulted = false + _ppm_sys.faulted = false end -- TERMINATION -- -- if a caught error was a termination request function should_terminate() - return self.terminate + return _ppm_sys.terminate end -- MOUNTING -- @@ -105,18 +132,12 @@ end function mount_all() local ifaces = peripheral.getNames() - self.mounts = {} + _ppm_sys.mounts = {} for i = 1, #ifaces do - local pm_dev = peripheral.wrap(ifaces[i]) - peri_init(pm_dev) + _ppm_sys.mounts[ifaces[i]] = peri_init(ifaces[i]) - self.mounts[ifaces[i]] = { - type = peripheral.getType(ifaces[i]), - dev = pm_dev - } - - log._info("PPM: found a " .. self.mounts[ifaces[i]].type .. " (" .. ifaces[i] .. ")") + log._info("PPM: found a " .. _ppm_sys.mounts[ifaces[i]].type .. " (" .. ifaces[i] .. ")") end if #ifaces == 0 then @@ -128,31 +149,27 @@ end function mount(iface) local ifaces = peripheral.getNames() local pm_dev = nil - local type = nil + local pm_type = nil for i = 1, #ifaces do if iface == ifaces[i] then log._info("PPM: mount(" .. iface .. ") -> found a " .. peripheral.getType(iface)) - type = peripheral.getType(iface) - pm_dev = peripheral.wrap(iface) - peri_init(pm_dev) + _ppm_sys.mounts[iface] = peri_init(iface) - self.mounts[iface] = { - type = peripheral.getType(iface), - dev = pm_dev - } + pm_type = _ppm_sys.mounts[iface].type + pm_dev = _ppm_sys.mounts[iface].dev break end end - return type, pm_dev + return pm_type, pm_dev end -- handle peripheral_detach event function handle_unmount(iface) -- what got disconnected? - local lost_dev = self.mounts[iface] + local lost_dev = _ppm_sys.mounts[iface] if lost_dev then local type = lost_dev.type @@ -173,20 +190,20 @@ end -- list mounted peripherals function list_mounts() - return self.mounts + return _ppm_sys.mounts end -- get a mounted peripheral by side/interface function get_periph(iface) - if self.mounts[iface] then - return self.mounts[iface].dev + if _ppm_sys.mounts[iface] then + return _ppm_sys.mounts[iface].dev else return nil end end -- get a mounted peripheral type by side/interface function get_type(iface) - if self.mounts[iface] then - return self.mounts[iface].type + if _ppm_sys.mounts[iface] then + return _ppm_sys.mounts[iface].type else return nil end end @@ -194,7 +211,7 @@ end function get_all_devices(name) local devices = {} - for side, data in pairs(self.mounts) do + for side, data in pairs(_ppm_sys.mounts) do if data.type == name then table.insert(devices, data.dev) end @@ -207,7 +224,7 @@ end function get_device(name) local device = nil - for side, data in pairs(self.mounts) do + for side, data in pairs(_ppm_sys.mounts) do if data.type == name then device = data.dev break @@ -228,7 +245,7 @@ end function get_wireless_modem() local w_modem = nil - for side, device in pairs(self.mounts) do + for side, device in pairs(_ppm_sys.mounts) do if device.type == "modem" and device.dev.isWireless() then w_modem = device.dev break diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 9fbed9b..91fbacc 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -330,7 +330,7 @@ function new_session(id, for_reactor, in_queue, out_queue) -- handle queue -- ------------------ - if ~self.in_q.empty() then + if not self.in_q.empty() then -- get a new message to process local message = self.in_q.pop() diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 1ad31bb..3395b81 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -17,7 +17,7 @@ os.loadAPI("session/plc.lua") os.loadAPI("session/coordinator.lua") os.loadAPI("session/svsessions.lua") -local SUPERVISOR_VERSION = "alpha-v0.1.2" +local SUPERVISOR_VERSION = "alpha-v0.1.3" local print = util.print local println = util.println From 74168707c68c6bd8c203e48569a04391d3bdb3fa Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 24 Apr 2022 13:21:55 -0400 Subject: [PATCH 087/587] PLC clock timing fix --- reactor-plc/startup.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 7cef958..0c97472 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -10,7 +10,7 @@ os.loadAPI("scada-common/comms.lua") os.loadAPI("config.lua") os.loadAPI("plc.lua") -local R_PLC_VERSION = "alpha-v0.2.6" +local R_PLC_VERSION = "alpha-v0.2.7" local print = util.print local println = util.println @@ -95,7 +95,7 @@ function init() end -- loop clock (10Hz, 2 ticks) - loop_clock = os.startTimer(0.05) + loop_clock = os.startTimer(0.1) log._debug("loop clock started") println("boot> completed"); @@ -257,7 +257,7 @@ while true do end -- start next clock timer - loop_clock = os.startTimer(0.05) + loop_clock = os.startTimer(0.1) elseif event == "modem_message" and networked and not plc_state.no_modem then -- got a packet -- feed the watchdog first so it doesn't uhh...eat our packets From 074f6448e1c739e6c5666e821af55fdb9a453d52 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 24 Apr 2022 13:22:45 -0400 Subject: [PATCH 088/587] some supervisor bugfixes --- supervisor/session/plc.lua | 2 +- supervisor/session/svsessions.lua | 2 +- supervisor/startup.lua | 7 ++++--- supervisor/supervisor.lua | 3 +-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 91fbacc..3adb85d 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -348,7 +348,7 @@ function new_session(id, for_reactor, in_queue, out_queue) local elapsed = os.clock() - self.periodics.last_update - self.periodics.keep_alive += elapsed + self.periodics.keep_alive = self.periodics.keep_alive + elapsed if self.periodics.keep_alive >= PERIODICS.KEEP_ALIVE then _send(RPLC_TYPES.KEEP_ALIVE, { os.epoch() }) diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 4e0c75e..edc2ea2 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -10,7 +10,7 @@ SESSION_TYPE = { } local self = { - modem = nil + modem = nil, num_reactors = 0, rtu_sessions = {}, plc_sessions = {}, diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 3395b81..fa4de82 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -10,14 +10,15 @@ os.loadAPI("scada-common/modbus.lua") os.loadAPI("config.lua") os.loadAPI("mqueue.lua") -os.loadAPI("supervisor.lua") os.loadAPI("session/rtu.lua") os.loadAPI("session/plc.lua") os.loadAPI("session/coordinator.lua") os.loadAPI("session/svsessions.lua") -local SUPERVISOR_VERSION = "alpha-v0.1.3" +os.loadAPI("supervisor.lua") + +local SUPERVISOR_VERSION = "alpha-v0.1.4" local print = util.print local println = util.println @@ -40,7 +41,7 @@ if modem == nil then end -- start comms, open all channels -local comms = supervisor.superv_comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN) +local superv_comms = supervisor.superv_comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN) -- base loop clock (4Hz, 5 ticks) local loop_clock = os.startTimer(0.25) diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index a3d2d2a..25154bb 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -11,9 +11,8 @@ local RTU_ADVERT_TYPES = comms.RTU_ADVERT_TYPES local SESSION_TYPE = svsessions.SESSION_TYPE -- supervisory controller communications -function superv_comms(mode, num_reactors, modem, dev_listen, coord_listen) +function superv_comms(num_reactors, modem, dev_listen, coord_listen) local self = { - mode = mode, ln_seq_num = 0, num_reactors = num_reactors, modem = modem, From 1744527a41e08766cb15c1307441556c842456e6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 25 Apr 2022 10:34:41 -0400 Subject: [PATCH 089/587] ISS cleanup --- reactor-plc/plc.lua | 104 ++++++++++++++++++---------------------- reactor-plc/startup.lua | 5 +- 2 files changed, 49 insertions(+), 60 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index f0f26c8..6e92cfd 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -11,18 +11,16 @@ local RPLC_LINKING = comms.RPLC_LINKING function iss_init(reactor) local self = { reactor = reactor, + cache = { false, false, false, false, false, false, false }, timed_out = false, tripped = false, trip_cause = "" } - -- re-link a reactor after a peripheral re-connect - local reconnect_reactor = function (reactor) - self.reactor = reactor - end + -- PRIVATE FUNCTIONS -- -- check for critical damage - local damage_critical = function () + local _damage_critical = function () local damage_percent = self.reactor.getDamagePercent() if damage_percent == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later @@ -34,7 +32,7 @@ function iss_init(reactor) end -- check for heated coolant backup - local excess_heated_coolant = function () + local _excess_heated_coolant = function () local hc_needed = self.reactor.getHeatedCoolantNeeded() if hc_needed == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later @@ -46,7 +44,7 @@ function iss_init(reactor) end -- check for excess waste - local excess_waste = function () + local _excess_waste = function () local w_needed = self.reactor.getWasteNeeded() if w_needed == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later @@ -58,7 +56,7 @@ function iss_init(reactor) end -- check if the reactor is at a critically high temperature - local high_temp = function () + local _high_temp = function () -- mekanism: MAX_DAMAGE_TEMPERATURE = 1_200 local temp = self.reactor.getTemperature() if temp == ppm.ACCESS_FAULT then @@ -71,7 +69,7 @@ function iss_init(reactor) end -- check if there is no fuel - local insufficient_fuel = function () + local _insufficient_fuel = function () local fuel = self.reactor.getFuel() if fuel == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later @@ -83,7 +81,7 @@ function iss_init(reactor) end -- check if there is no coolant - local no_coolant = function () + local _no_coolant = function () local coolant_filled = self.reactor.getCoolantFilledPercentage() if coolant_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later @@ -94,39 +92,63 @@ function iss_init(reactor) end end - -- if PLC timed out - local timed_out = function () - return self.timed_out + -- PUBLIC FUNCTIONS -- + + -- re-link a reactor after a peripheral re-connect + local reconnect_reactor = function (reactor) + self.reactor = reactor + end + + -- report a PLC comms timeout + local trip_timeout = function () + self.timed_out = true end -- check all safety conditions local check = function () local status = "ok" local was_tripped = self.tripped + + -- update cache + self.cache = { + _damage_critical(), + _excess_heated_coolant(), + _excess_waste(), + _high_temp(), + _insufficient_fuel(), + _no_coolant(), + self.timed_out + } -- check system states in order of severity - if damage_critical() then + if self.cache[1] then log._warning("ISS: damage critical!") status = "dmg_crit" - elseif high_temp() then + elseif self.cache[4] then log._warning("ISS: high temperature!") status = "high_temp" - elseif excess_heated_coolant() then + elseif self.cache[2] then log._warning("ISS: heated coolant backup!") status = "heated_coolant_backup" - elseif excess_waste() then + elseif self.cache[6] then + log._warning("ISS: no coolant!") + status = "no_coolant" + elseif self.cache[3] then log._warning("ISS: full waste!") status = "full_waste" - elseif insufficient_fuel() then + elseif self.cache[5] then log._warning("ISS: no fuel!") status = "no_fuel" + elseif self.timed_out then + log._warning("ISS: supervisor connection timeout!") + status = "timeout" elseif self.tripped then status = self.trip_cause else self.tripped = false end - -- if a new trip occured... + -- if a trip occured... if status ~= "ok" then log._warning("ISS: reactor SCRAM") self.tripped = true @@ -137,19 +159,12 @@ function iss_init(reactor) end end + -- evaluate if this is a new trip local first_trip = not was_tripped and self.tripped return self.tripped, status, first_trip end - -- report a PLC comms timeout - local trip_timeout = function () - self.tripped = false - self.trip_cause = "timeout" - self.timed_out = true - self.reactor.scram() - end - -- reset the ISS local reset = function () self.timed_out = false @@ -158,43 +173,16 @@ function iss_init(reactor) end -- get the ISS status - local status = function (named) - if named then - return { - damage_critical = damage_critical(), - excess_heated_coolant = excess_heated_coolant(), - excess_waste = excess_waste(), - high_temp = high_temp(), - insufficient_fuel = insufficient_fuel(), - no_coolant = no_coolant(), - timed_out = timed_out() - } - else - return { - damage_critical(), - excess_heated_coolant(), - excess_waste(), - high_temp(), - insufficient_fuel(), - no_coolant(), - timed_out() - } - end + local status = function () + return self.cache end return { reconnect_reactor = reconnect_reactor, - check = check, trip_timeout = trip_timeout, + check = check, reset = reset, - status = status, - damage_critical = damage_critical, - excess_heated_coolant = excess_heated_coolant, - excess_waste = excess_waste, - high_temp = high_temp, - insufficient_fuel = insufficient_fuel, - no_coolant = no_coolant, - timed_out = timed_out + status = status } end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 0c97472..acf19b2 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -10,7 +10,7 @@ os.loadAPI("scada-common/comms.lua") os.loadAPI("config.lua") os.loadAPI("plc.lua") -local R_PLC_VERSION = "alpha-v0.2.7" +local R_PLC_VERSION = "alpha-v0.2.8" local print = util.print local println = util.println @@ -230,7 +230,8 @@ while true do plc_comms.send_iss_alarm(iss_status_string) end end - else + elseif not plc_state.no_reactor then + -- degraded but we have a reactor reactor.scram() end end From c46a7b2486eef802ae235648764be3136dc55f99 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 25 Apr 2022 10:36:47 -0400 Subject: [PATCH 090/587] added time functions to util, also task_wait --- reactor-plc/plc.lua | 7 ++++--- scada-common/alarm.lua | 4 +++- scada-common/util.lua | 24 ++++++++++++++++++++++++ supervisor/session/plc.lua | 4 ++-- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 6e92cfd..67708d5 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -1,5 +1,6 @@ -- #REQUIRES comms.lua -- #REQUIRES ppm.lua +-- #REQUIRES util.lua local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES @@ -272,7 +273,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- keep alive ack local _send_keep_alive_ack = function (srv_time) - _send(RPLC_TYPES.KEEP_ALIVE, { srv_time, os.epoch() }) + _send(RPLC_TYPES.KEEP_ALIVE, { srv_time, util.time() }) end -- general ack @@ -369,7 +370,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) if packet.type == RPLC_TYPES.KEEP_ALIVE then -- keep alive request received, echo back local timestamp = packet.data[1] - local trip_time = os.epoch() - timestamp + local trip_time = util.time() - timestamp if trip_time < 0 then log._warning("PLC KEEP_ALIVE trip time less than 0 (" .. trip_time .. ")") @@ -494,7 +495,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) end local sys_status = { - os.epoch(), + util.time(), (not self.scrammed), overridden, degraded, diff --git a/scada-common/alarm.lua b/scada-common/alarm.lua index d6670a4..e8464a5 100644 --- a/scada-common/alarm.lua +++ b/scada-common/alarm.lua @@ -1,3 +1,5 @@ +-- #REQUIRES util.lua + SEVERITY = { INFO = 0, -- basic info message WARNING = 1, -- warning about some abnormal state @@ -9,7 +11,7 @@ SEVERITY = { function scada_alarm(severity, device, message) local self = { - time = os.epoch(), + time = util.time(), ts_string = os.date("[%H:%M:%S]"), severity = severity, device = device, diff --git a/scada-common/util.lua b/scada-common/util.lua index 1d4e11c..050b18a 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -1,3 +1,5 @@ +-- PRINT -- + -- we are overwriting 'print' so save it first local _print = print @@ -21,6 +23,28 @@ function println_ts(message) _print(os.date("[%H:%M:%S] ") .. message) end +-- TIME -- + +function time_ms() + return os.epoch('local') +end + +function time_s() + return os.epoch('local') / 1000 +end + +function time() + return time_ms() +end + +-- PARALLELIZATION -- + +-- block waiting for parallel call +function task_wait(f) + parallel.waitForAll(f) +end + +-- WATCHDOG -- -- ComputerCraft OS Timer based Watchdog -- triggers a timer event if not fed within 'timeout' seconds diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 3adb85d..695b34b 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -185,7 +185,7 @@ function new_session(id, for_reactor, in_queue, out_queue) if rplc_pkt.length == 2 then local srv_start = rplc_pkt.data[1] local plc_send = rplc_pkt.data[2] - local srv_now = os.epoch() + local srv_now = util.time() self.last_rtt = srv_now - srv_start if self.last_rtt < 0 then @@ -351,7 +351,7 @@ function new_session(id, for_reactor, in_queue, out_queue) self.periodics.keep_alive = self.periodics.keep_alive + elapsed if self.periodics.keep_alive >= PERIODICS.KEEP_ALIVE then - _send(RPLC_TYPES.KEEP_ALIVE, { os.epoch() }) + _send(RPLC_TYPES.KEEP_ALIVE, { util.time() }) self.periodics.keep_alive = 0 end From 0fc49d312d21db6f0e31a8262df85287f1667d73 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 25 Apr 2022 11:40:53 -0400 Subject: [PATCH 091/587] #32 parallel reactor PLC code --- reactor-plc/plc.lua | 19 +-- reactor-plc/startup.lua | 261 ++++++---------------------------- reactor-plc/threads.lua | 304 ++++++++++++++++++++++++++++++++++++++++ scada-common/util.lua | 2 +- 4 files changed, 358 insertions(+), 228 deletions(-) create mode 100644 reactor-plc/threads.lua diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 67708d5..50092a3 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -89,7 +89,7 @@ function iss_init(reactor) log._error("ISS: failed to check reactor coolant level") return false else - return coolant_filled < 2 + return coolant_filled < 0.02 end end @@ -122,7 +122,9 @@ function iss_init(reactor) } -- check system states in order of severity - if self.cache[1] then + if self.tripped then + status = self.trip_cause + elseif self.cache[1] then log._warning("ISS: damage critical!") status = "dmg_crit" elseif self.cache[4] then @@ -143,25 +145,24 @@ function iss_init(reactor) elseif self.timed_out then log._warning("ISS: supervisor connection timeout!") status = "timeout" - elseif self.tripped then - status = self.trip_cause else self.tripped = false end - -- if a trip occured... - if status ~= "ok" then + -- if a new trip occured... + local first_trip = false + if not was_tripped and status ~= "ok" then log._warning("ISS: reactor SCRAM") + + first_trip = true self.tripped = true self.trip_cause = status + self.reactor.scram() if self.reactor.__p_is_faulted() then log._error("ISS: failed reactor SCRAM") end end - - -- evaluate if this is a new trip - local first_trip = not was_tripped and self.tripped return self.tripped, status, first_trip end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index acf19b2..c79ec77 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -9,8 +9,9 @@ os.loadAPI("scada-common/comms.lua") os.loadAPI("config.lua") os.loadAPI("plc.lua") +os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.2.8" +local R_PLC_VERSION = "alpha-v0.3.0" local print = util.print local println = util.println @@ -25,21 +26,37 @@ println(">> Reactor PLC " .. R_PLC_VERSION .. " <<") -- mount connected devices ppm.mount_all() -local reactor = ppm.get_fission_reactor() -local modem = ppm.get_wireless_modem() +-- shared memory across threads +local __shared_memory = { + networked = config.NETWORKED, -local networked = config.NETWORKED + plc_state = { + init_ok = true, + scram = true, + degraded = false, + no_reactor = false, + no_modem = false + }, + + plc_devices = { + reactor = ppm.get_fission_reactor(), + modem = ppm.get_wireless_modem() + }, -local plc_state = { - init_ok = true, - scram = true, - degraded = false, - no_reactor = false, - no_modem = false + system = { + iss = nil, + plc_comms = nil, + conn_watchdog = nil + } } +local smem_dev = __shared_memory.plc_devices +local smem_sys = __shared_memory.system + +local plc_state = __shared_memory.plc_state + -- we need a reactor and a modem -if reactor == nil then +if smem_dev.reactor == nil then println("boot> fission reactor not found"); log._warning("no reactor on startup") @@ -47,12 +64,12 @@ if reactor == nil then plc_state.degraded = true plc_state.no_reactor = true end -if networked and modem == nil then +if networked and smem_dev.modem == nil then println("boot> wireless modem not found") log._warning("no wireless modem on startup") - if reactor ~= nil then - reactor.scram() + if smem_dev.reactor ~= nil then + smem_dev.reactor.scram() end plc_state.init_ok = false @@ -60,43 +77,29 @@ if networked and modem == nil then plc_state.no_modem = true end -local iss = nil -local plc_comms = nil -local conn_watchdog = nil - --- send status updates at ~3.33Hz (every 6 server ticks) (every 3 loop ticks) --- send link requests at 0.5Hz (every 40 server ticks) (every 20 loop ticks) -local UPDATE_TICKS = 3 -local LINK_TICKS = 20 - -local loop_clock = nil -local ticks_to_update = LINK_TICKS -- start by linking - function init() if plc_state.init_ok then -- just booting up, no fission allowed (neutrons stay put thanks) - reactor.scram() + smem_dev.reactor.scram() -- init internal safety system - iss = plc.iss_init(reactor) + smem_sys.iss = plc.iss_init(smem_dev.reactor) log._debug("iss init") - if networked then + if __shared_memory.networked then -- start comms - plc_comms = plc.comms_init(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor, iss) + smem_sys.plc_comms = plc.comms_init(config.REACTOR_ID, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, smem_dev.reactor, smem_sys.iss) log._debug("comms init") -- comms watchdog, 3 second timeout - conn_watchdog = util.new_watchdog(3) + smem_sys.conn_watchdog = util.new_watchdog(3) log._debug("conn watchdog started") else println("boot> starting in offline mode"); log._debug("running without networking") end - -- loop clock (10Hz, 2 ticks) - loop_clock = os.startTimer(0.1) - log._debug("loop clock started") + os.queueEvent("clock_start") println("boot> completed"); else @@ -108,191 +111,13 @@ end -- initialize PLC init() --- event loop -while true do - local event, param1, param2, param3, param4, param5 = os.pullEventRaw() +-- init threads +local main_thread = threads.thread__main(__shared_memory, init) +local iss_thread = threads.thread__iss(__shared_memory) +-- local comms_thread = plc.thread__comms(__shared_memory) - if plc_state.init_ok then - -- if we tried to SCRAM but failed, keep trying - -- if it disconnected, isPowered will return nil (and error logs will get spammed at 10Hz, so disable reporting) - -- in that case, SCRAM won't be called until it reconnects (this is the expected use of this check) - ppm.disable_reporting() - if plc_state.scram and reactor.getStatus() then - reactor.scram() - end - ppm.enable_reporting() - end - - -- check for peripheral changes before ISS checks - if event == "peripheral_detach" then - local device = ppm.handle_unmount(param1) - - if device.type == "fissionReactor" then - println_ts("reactor disconnected!") - log._error("reactor disconnected!") - plc_state.no_reactor = true - plc_state.degraded = true - -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_PERI_DC) ? - elseif networked and device.type == "modem" then - -- we only care if this is our wireless modem - if device.dev == modem then - println_ts("wireless modem disconnected!") - log._error("comms modem disconnected!") - plc_state.no_modem = true - - if plc_state.init_ok then - -- try to scram reactor if it is still connected - plc_state.scram = true - if reactor.scram() then - println_ts("successful reactor SCRAM") - log._error("successful reactor SCRAM") - else - println_ts("failed reactor SCRAM") - log._error("failed reactor SCRAM") - end - end - - plc_state.degraded = true - else - log._warning("non-comms modem disconnected") - end - end - elseif event == "peripheral" then - local type, device = ppm.mount(param1) - - if type == "fissionReactor" then - -- reconnected reactor - reactor = device - - plc_state.scram = true - reactor.scram() - - println_ts("reactor reconnected.") - log._info("reactor reconnected.") - plc_state.no_reactor = false - - if plc_state.init_ok then - iss.reconnect_reactor(reactor) - if networked then - plc_comms.reconnect_reactor(reactor) - end - end - - -- determine if we are still in a degraded state - if not networked or ppm.get_device("modem") ~= nil then - plc_state.degraded = false - end - elseif networked and type == "modem" then - if device.isWireless() then - -- reconnected modem - modem = device - - if plc_state.init_ok then - plc_comms.reconnect_modem(modem) - end - - println_ts("wireless modem reconnected.") - log._info("comms modem reconnected.") - plc_state.no_modem = false - - -- determine if we are still in a degraded state - if ppm.get_device("fissionReactor") ~= nil then - plc_state.degraded = false - end - else - log._info("wired modem reconnected.") - end - end - - if not plc_state.init_ok and not plc_state.degraded then - plc_state.init_ok = true - init() - end - end - - -- ISS - if plc_state.init_ok then - -- if we are in standalone mode, continuously reset ISS - -- ISS will trip again if there are faults, but if it isn't cleared, the user can't re-enable - if not networked then - plc_state.scram = false - iss.reset() - end - - -- check safety (SCRAM occurs if tripped) - if not plc_state.degraded then - local iss_tripped, iss_status_string, iss_first = iss.check() - plc_state.scram = plc_state.scram or iss_tripped - - if iss_first then - println_ts("[ISS] reactor shutdown, safety tripped: " .. iss_status_string) - if networked then - plc_comms.send_iss_alarm(iss_status_string) - end - end - elseif not plc_state.no_reactor then - -- degraded but we have a reactor - reactor.scram() - end - end - - -- handle event - if event == "timer" and param1 == loop_clock then - -- basic event tick, send updated data if it is time (~3.33Hz) - -- iss was already checked (that's the main reason for this tick rate) - if networked and not plc_state.no_modem then - ticks_to_update = ticks_to_update - 1 - - if plc_comms.is_linked() then - if ticks_to_update <= 0 then - plc_comms.send_status(iss_tripped, plc_state.degraded) - plc_comms.send_iss_status() - ticks_to_update = UPDATE_TICKS - end - else - if ticks_to_update <= 0 then - plc_comms.send_link_req() - ticks_to_update = LINK_TICKS - end - end - end - - -- start next clock timer - loop_clock = os.startTimer(0.1) - elseif event == "modem_message" and networked and not plc_state.no_modem then - -- got a packet - -- feed the watchdog first so it doesn't uhh...eat our packets - conn_watchdog.feed() - - -- handle the packet (plc_state passed to allow clearing SCRAM flag) - local packet = plc_comms.parse_packet(p1, p2, p3, p4, p5) - plc_comms.handle_packet(packet, plc_state) - elseif event == "timer" and networked and param1 == conn_watchdog.get_timer() then - -- haven't heard from server recently? shutdown reactor - plc_state.scram = true - plc_comms.unlink() - iss.trip_timeout() - println_ts("server timeout, reactor disabled") - log._warning("server timeout, reactor disabled") - end - - -- check for termination request - if event == "terminate" or ppm.should_terminate() then - -- safe exit - log._warning("terminate requested, exiting...") - if plc_state.init_ok then - plc_state.scram = true - reactor.scram() - if reactor.__p_is_ok() then - println_ts("reactor disabled") - else - -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_LOST_CONTROL) ? - println_ts("exiting, reactor failed to disable") - end - end - break - end -end +-- run threads +parallel.waitForAll(main_thread.exec, iss_thread.exec) -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_SHUTDOWN) ? println_ts("exited") diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua new file mode 100644 index 0000000..2cd282b --- /dev/null +++ b/reactor-plc/threads.lua @@ -0,0 +1,304 @@ +-- #REQUIRES comms.lua +-- #REQUIRES ppm.lua +-- #REQUIRES plc.lua +-- #REQUIRES util.lua + +local print = util.print +local println = util.println +local print_ts = util.print_ts +local println_ts = util.println_ts + +local async_wait = util.async_wait + +local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks) +local ISS_CLOCK = 0.5 -- (2Hz, 10 ticks) + +local ISS_EVENT = { + SCRAM = 1, + DEGRADED_SCRAM = 2, + TRIP_TIMEOUT = 3 +} + +-- main thread +function thread__main(shared_memory, init) + -- execute thread + local exec = function () + -- send status updates at 2Hz (every 10 server ticks) (every loop tick) + -- send link requests at 0.5Hz (every 40 server ticks) (every 4 loop ticks) + local LINK_TICKS = 4 + + local loop_clock = nil + local ticks_to_update = LINK_TICKS -- start by linking + + -- load in from shared memory + local networked = shared_memory.networked + local plc_state = shared_memory.plc_state + local plc_devices = shared_memory.plc_devices + + local iss = shared_memory.system.iss + local plc_comms = shared_memory.system.plc_comms + local conn_watchdog = shared_memory.system.conn_watchdog + + -- debug + -- local last_update = util.time() + + -- event loop + while true do + local event, param1, param2, param3, param4, param5 = os.pullEventRaw() + + -- handle event + if event == "timer" and param1 == loop_clock then + -- core clock tick + if networked then + -- start next clock timer + loop_clock = os.startTimer(MAIN_CLOCK) + + -- send updated data + if not plc_state.no_modem then + + if plc_comms.is_linked() then + async_wait(function () + plc_comms.send_status(iss_tripped, plc_state.degraded) + plc_comms.send_iss_status() + end) + else + ticks_to_update = ticks_to_update - 1 + + if ticks_to_update <= 0 then + plc_comms.send_link_req() + ticks_to_update = LINK_TICKS + end + end + end + + -- debug + -- print(util.time() - last_update) + -- println("ms") + -- last_update = util.time() + end + elseif event == "modem_message" and networked and not plc_state.no_modem then + -- got a packet + -- feed the watchdog first so it doesn't uhh...eat our packets + conn_watchdog.feed() + + -- handle the packet (plc_state passed to allow clearing SCRAM flag) + local packet = plc_comms.parse_packet(p1, p2, p3, p4, p5) + async_wait(function () plc_comms.handle_packet(packet, plc_state) end) + elseif event == "timer" and networked and param1 == conn_watchdog.get_timer() then + -- haven't heard from server recently? shutdown reactor + println("timed out, passing event") + plc_comms.unlink() + os.queueEvent("iss_command", ISS_EVENT.TRIP_TIMEOUT) + elseif event == "peripheral_detach" then + -- peripheral disconnect + local device = ppm.handle_unmount(param1) + + if device.type == "fissionReactor" then + println_ts("reactor disconnected!") + log._error("reactor disconnected!") + plc_state.no_reactor = true + plc_state.degraded = true + -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_PERI_DC) ? + elseif networked and device.type == "modem" then + -- we only care if this is our wireless modem + if device.dev == modem then + println_ts("wireless modem disconnected!") + log._error("comms modem disconnected!") + plc_state.no_modem = true + + if plc_state.init_ok then + -- try to scram reactor if it is still connected + os.queueEvent("iss_command", ISS_EVENT.DEGRADED_SCRAM) + end + + plc_state.degraded = true + else + log._warning("non-comms modem disconnected") + end + end + elseif event == "peripheral" then + -- peripheral connect + local type, device = ppm.mount(param1) + + if type == "fissionReactor" then + -- reconnected reactor + plc_devices.reactor = device + + os.queueEvent("iss_command", ISS_EVENT.SCRAM) + + println_ts("reactor reconnected.") + log._info("reactor reconnected.") + plc_state.no_reactor = false + + if plc_state.init_ok then + iss.reconnect_reactor(plc_devices.reactor) + if networked then + plc_comms.reconnect_reactor(plc_devices.reactor) + end + end + + -- determine if we are still in a degraded state + if not networked or ppm.get_device("modem") ~= nil then + plc_state.degraded = false + end + elseif networked and type == "modem" then + if device.isWireless() then + -- reconnected modem + plc_devices.modem = device + + if plc_state.init_ok then + plc_comms.reconnect_modem(plc_devices.modem) + end + + println_ts("wireless modem reconnected.") + log._info("comms modem reconnected.") + plc_state.no_modem = false + + -- determine if we are still in a degraded state + if ppm.get_device("fissionReactor") ~= nil then + plc_state.degraded = false + end + else + log._info("wired modem reconnected.") + end + end + + if not plc_state.init_ok and not plc_state.degraded then + plc_state.init_ok = true + init() + end + elseif event == "clock_start" then + -- start loop clock + loop_clock = os.startTimer(MAIN_CLOCK) + log._debug("loop clock started") + end + + -- check for termination request + if event == "terminate" or ppm.should_terminate() then + -- iss handles reactor shutdown + log._warning("terminate requested, main thread exiting") + break + end + end + end + + return { exec = exec } +end + +-- ISS monitor thread +function thread__iss(shared_memory) + -- execute thread + local exec = function () + local loop_clock = nil + + -- load in from shared memory + local networked = shared_memory.networked + local plc_state = shared_memory.plc_state + local plc_devices = shared_memory.plc_devices + + local iss = shared_memory.system.iss + local plc_comms = shared_memory.system.plc_comms + + -- debug + -- local last_update = util.time() + + -- event loop + while true do + local event, param1, param2, param3, param4, param5 = os.pullEventRaw() + + local reactor = shared_memory.plc_devices.reactor + + if event == "timer" and param1 == loop_clock then + -- start next clock timer + loop_clock = os.startTimer(ISS_CLOCK) + + -- ISS checks + if plc_state.init_ok then + -- 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) + async_wait(function () + if not plc_state.no_reactor and plc_state.scram and reactor.getStatus() then + reactor.scram() + end + end) + + -- if we are in standalone mode, continuously reset ISS + -- ISS will trip again if there are faults, but if it isn't cleared, the user can't re-enable + if not networked then + plc_state.scram = false + iss.reset() + end + + -- check safety (SCRAM occurs if tripped) + async_wait(function () + if not plc_state.degraded then + local iss_tripped, iss_status_string, iss_first = iss.check() + plc_state.scram = plc_state.scram or iss_tripped + + if iss_first then + println_ts("[ISS] SCRAM! safety trip: " .. iss_status_string) + if networked then + plc_comms.send_iss_alarm(iss_status_string) + end + end + end + end) + end + + -- debug + -- print(util.time() - last_update) + -- println("ms") + -- last_update = util.time() + elseif event == "iss_command" then + -- handle ISS commands + println("got iss command?") + if param1 == ISS_EVENT.SCRAM then + -- basic SCRAM + plc_state.scram = true + async_wait(reactor.scram) + elseif param1 == ISS_EVENT.DEGRADED_SCRAM then + -- SCRAM with print + plc_state.scram = true + async_wait(function () + if reactor.scram() then + println_ts("successful reactor SCRAM") + log._error("successful reactor SCRAM") + else + println_ts("failed reactor SCRAM") + log._error("failed reactor SCRAM") + end + end) + elseif param1 == ISS_EVENT.TRIP_TIMEOUT then + -- watchdog tripped + plc_state.scram = true + iss.trip_timeout() + println_ts("server timeout, reactor disabled") + log._warning("server timeout, reactor disabled") + end + elseif event == "clock_start" then + -- start loop clock + loop_clock = os.startTimer(ISS_CLOCK) + log._debug("loop clock started") + end + + -- check for termination request + if event == "terminate" or ppm.should_terminate() then + -- safe exit + log._warning("terminate requested, iss thread shutdown") + if plc_state.init_ok then + plc_state.scram = true + async_wait(reactor.scram) + if reactor.__p_is_ok() then + println_ts("reactor disabled") + else + -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_LOST_CONTROL) ? + println_ts("exiting, reactor failed to disable") + end + end + break + end + end + end + + return { exec = exec } +end diff --git a/scada-common/util.lua b/scada-common/util.lua index 050b18a..97ce601 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -40,7 +40,7 @@ end -- PARALLELIZATION -- -- block waiting for parallel call -function task_wait(f) +function async_wait(f) parallel.waitForAll(f) end From b1998b61bcf8a4f090a8be5263966cad800ee9e1 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 25 Apr 2022 11:44:34 -0400 Subject: [PATCH 092/587] #32 parallel RTU execution of packet handler --- rtu/startup.lua | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/rtu/startup.lua b/rtu/startup.lua index 4534f18..ecb3f3d 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -17,13 +17,15 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.2.4" +local RTU_VERSION = "alpha-v0.3.0" local print = util.print local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts +local async_wait = util.async_wait + log._info("========================================") log._info("BOOTING rtu.startup " .. RTU_VERSION) log._info("========================================") @@ -218,6 +220,9 @@ while true do end end elseif event == "timer" and param1 == loop_clock then + -- start next clock timer + loop_clock = os.startTimer(2) + -- period tick, if we are linked send heartbeat, if not send advertisement if linked then rtu_comms.send_heartbeat() @@ -225,15 +230,12 @@ while true do -- advertise units rtu_comms.send_advertisement(units) end - - -- start next clock timer - loop_clock = os.startTimer(2) elseif event == "modem_message" then -- got a packet local link_ref = { linked = linked } local packet = rtu_comms.parse_packet(p1, p2, p3, p4, p5) - rtu_comms.handle_packet(packet, units, link_ref) + async_wait(function () rtu_comms.handle_packet(packet, units, link_ref) end) -- if linked, stop sending advertisements linked = link_ref.linked From e119c112044e908f90df14079221a4d9cf376bce Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 25 Apr 2022 15:44:28 -0400 Subject: [PATCH 093/587] removed debug print --- reactor-plc/threads.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 2cd282b..8bd9626 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -251,7 +251,6 @@ function thread__iss(shared_memory) -- last_update = util.time() elseif event == "iss_command" then -- handle ISS commands - println("got iss command?") if param1 == ISS_EVENT.SCRAM then -- basic SCRAM plc_state.scram = true From b861d3f66841e9d24d4942fdf9b921a6ffb810a6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 25 Apr 2022 15:46:32 -0400 Subject: [PATCH 094/587] removed debug print --- reactor-plc/threads.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 8bd9626..201abe1 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -86,7 +86,6 @@ function thread__main(shared_memory, init) async_wait(function () plc_comms.handle_packet(packet, plc_state) end) elseif event == "timer" and networked and param1 == conn_watchdog.get_timer() then -- haven't heard from server recently? shutdown reactor - println("timed out, passing event") plc_comms.unlink() os.queueEvent("iss_command", ISS_EVENT.TRIP_TIMEOUT) elseif event == "peripheral_detach" then From 3ef2902829a7eb317624433a12fa7ab447205a22 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 25 Apr 2022 15:49:04 -0400 Subject: [PATCH 095/587] apparently I forgot how to spell receive a few more times --- reactor-plc/plc.lua | 2 +- rtu/rtu.lua | 2 +- supervisor/supervisor.lua | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 50092a3..a6d260a 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -329,7 +329,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) local s_pkt = scada_packet() -- parse packet as generic SCADA packet - s_pkt.recieve(side, sender, reply_to, message, distance) + s_pkt.receive(side, sender, reply_to, message, distance) if s_pkt.is_valid() then -- get as RPLC packet diff --git a/rtu/rtu.lua b/rtu/rtu.lua index dde73af..ccf6e44 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -159,7 +159,7 @@ function rtu_comms(modem, local_port, server_port) local s_pkt = comms.scada_packet() -- parse packet as generic SCADA packet - s_pkt.recieve(side, sender, reply_to, message, distance) + s_pkt.receive(side, sender, reply_to, message, distance) if s_pkt.is_valid() then -- get as MODBUS TCP packet diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 25154bb..31b032e 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -67,7 +67,7 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) local s_pkt = scada_packet() -- parse packet as generic SCADA packet - s_pkt.recieve(side, sender, reply_to, message, distance) + s_pkt.receive(side, sender, reply_to, message, distance) if s_pkt.is_valid() then -- get as MODBUS TCP packet @@ -146,7 +146,7 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) log._debug("illegal packet type " .. protocol .. " on device listening channel") end -- coordinator listening channel - elseif reciever == self.coord_listen then + elseif receiver == self.coord_listen then if protocol == PROTOCOLS.SCADA_MGMT then -- SCADA management packet elseif protocol == PROTOCOLS.COORD_DATA then From 19a4b3c0ef5a21e84840cb69a8d505955ee5df26 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 25 Apr 2022 15:50:24 -0400 Subject: [PATCH 096/587] ticked up versions --- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index c79ec77..f37280d 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -11,7 +11,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.3.0" +local R_PLC_VERSION = "alpha-v0.3.1" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index ecb3f3d..4626df2 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -17,7 +17,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.3.0" +local RTU_VERSION = "alpha-v0.3.1" local print = util.print local println = util.println From f7f723829c69adf70b2835984b81f7230f27cba0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 25 Apr 2022 21:00:50 -0400 Subject: [PATCH 097/587] #7 work on PLC session comms, bugfixes with comms, general supervisor bugfixes --- reactor-plc/plc.lua | 157 ++++++++++++++++-------------- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 42 ++++---- rtu/startup.lua | 4 +- scada-common/comms.lua | 26 ++--- supervisor/mqueue.lua | 4 +- supervisor/session/svsessions.lua | 12 ++- supervisor/startup.lua | 4 +- supervisor/supervisor.lua | 41 +++++--- 9 files changed, 158 insertions(+), 134 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index a6d260a..da71998 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -6,6 +6,11 @@ local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES local RPLC_LINKING = comms.RPLC_LINKING +local print = util.print +local println = util.println +local print_ts = util.print_ts +local println_ts = util.println_ts + -- Internal Safety System -- identifies dangerous states and SCRAMs reactor if warranted -- autonomous from main SCADA supervisor/coordinator control @@ -167,6 +172,10 @@ function iss_init(reactor) return self.tripped, status, first_trip end + -- get the ISS status + local status = function () return self.cache end + local is_tripped = function () return self.tripped end + -- reset the ISS local reset = function () self.timed_out = false @@ -174,17 +183,13 @@ function iss_init(reactor) self.trip_cause = "" end - -- get the ISS status - local status = function () - return self.cache - end - return { reconnect_reactor = reconnect_reactor, trip_timeout = trip_timeout, check = check, - reset = reset, - status = status + status = status, + is_tripped = is_tripped, + reset = reset } end @@ -225,7 +230,6 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- variable reactor status information, excluding heating rate local _reactor_status = function () - ppm.clear_fault() return { self.reactor.getStatus(), self.reactor.getBurnRate(), @@ -249,20 +253,24 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) self.reactor.getHeatedCoolant()['amount'], self.reactor.getHeatedCoolantNeeded(), self.reactor.getHeatedCoolantFilledPercentage() - }, ppm.faulted() + }, self.reactor.__p_is_faulted() end local _update_status_cache = function () local status, faulted = _reactor_status() local changed = false - if not faulted then - for i = 1, #status do - if status[i] ~= self.status_cache[i] then - changed = true - break + if self.status_cache ~= nil then + if not faulted then + for i = 1, #status do + if status[i] ~= self.status_cache[i] then + changed = true + break + end end end + else + changed = true end if changed then @@ -285,8 +293,6 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- send structure properties (these should not change) -- (server will cache these) local _send_struct = function () - ppm.clear_fault() - local mek_data = { self.reactor.getHeatCapacity(), self.reactor.getFuelAssemblies(), @@ -298,7 +304,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) self.reactor.getMaxBurnRate() } - if not ppm.is_faulted() then + if not self.reactor.__p_is_faulted() then _send(RPLC_TYPES.MEK_STRUCT, mek_data) else log._error("failed to send structure: PPM fault") @@ -323,10 +329,48 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) _update_status_cache() end + -- attempt to establish link with supervisor + local send_link_req = function () + _send(RPLC_TYPES.LINK_REQ, { self.id }) + end + + -- send live status information + local send_status = function (degraded) + local mek_data = nil + + if _update_status_cache() then + mek_data = self.status_cache + end + + local sys_status = { + util.time(), + (not self.scrammed), + iss.is_tripped(), + degraded, + self.reactor.getHeatingRate(), + mek_data + } + + _send(RPLC_TYPES.STATUS, sys_status) + end + + local send_iss_status = function () + _send(RPLC_TYPES.ISS_STATUS, iss.status()) + end + + local send_iss_alarm = function (cause) + local iss_alarm = { + cause, + iss.status() + } + + _send(RPLC_TYPES.ISS_ALARM, iss_alarm) + end + -- parse an RPLC packet local parse_packet = function(side, sender, reply_to, message, distance) local pkt = nil - local s_pkt = scada_packet() + local s_pkt = comms.scada_packet() -- parse packet as generic SCADA packet s_pkt.receive(side, sender, reply_to, message, distance) @@ -358,7 +402,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- check sequence number if self.r_seq_num == nil then self.r_seq_num = packet.scada_frame.seq_num() - elseif self.r_seq_num >= packet.scada_frame.seq_num() then + elseif self.linked and self.r_seq_num >= packet.scada_frame.seq_num() then log._warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) return else @@ -388,19 +432,19 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) if link_ack == RPLC_LINKING.ALLOW then _send_struct() - send_status() + send_status(plc_state.degraded) log._debug("re-sent initial status data") elseif link_ack == RPLC_LINKING.DENY then -- @todo: make sure this doesn't become a MITM security risk - print_ts("received unsolicited link denial, unlinking\n") - log._debug("unsolicited rplc link request denied") + println_ts("received unsolicited link denial, unlinking") + log._debug("unsolicited RPLC link request denied") elseif link_ack == RPLC_LINKING.COLLISION then -- @todo: make sure this doesn't become a MITM security risk - print_ts("received unsolicited link collision, unlinking\n") - log._warning("unsolicited rplc link request collision") + println_ts("received unsolicited link collision, unlinking") + log._warning("unsolicited RPLC link request collision") else - print_ts("invalid unsolicited link response\n") - log._error("unsolicited unknown rplc link request response") + println_ts("invalid unsolicited link response") + log._error("unsolicited unknown RPLC link request response") end self.linked = link_ack == RPLC_LINKING.ALLOW @@ -452,22 +496,25 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) local link_ack = packet.data[1] if link_ack == RPLC_LINKING.ALLOW then - print_ts("...linked!\n") - log._debug("rplc link request approved") + println_ts("linked!") + log._debug("RPLC link request approved") + + -- reset remote sequence number + self.r_seq_num = nil _send_struct() - send_status() + send_status(plc_state.degraded) log._debug("sent initial status data") elseif link_ack == RPLC_LINKING.DENY then - print_ts("...denied, retrying...\n") - log._debug("rplc link request denied") + println_ts("link request denied, retrying...") + log._debug("RPLC link request denied") elseif link_ack == RPLC_LINKING.COLLISION then - print_ts("reactor PLC ID collision (check config), retrying...\n") - log._warning("rplc link request collision") + println_ts("reactor PLC ID collision (check config), retrying...") + log._warning("RPLC link request collision") else - print_ts("invalid link response, bad channel? retrying...\n") - log._error("unknown rplc link request response") + println_ts("invalid link response, bad channel? retrying...") + log._error("unknown RPLC link request response") end self.linked = link_ack == RPLC_LINKING.ALLOW @@ -480,46 +527,6 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) end end - -- attempt to establish link with supervisor - local send_link_req = function () - _send(RPLC_TYPES.LINK_REQ, {}) - end - - -- send live status information - -- overridden : if ISS force disabled reactor - -- degraded : if PLC status is degraded - local send_status = function (overridden, degraded) - local mek_data = nil - - if _update_status_cache() then - mek_data = self.status_cache - end - - local sys_status = { - util.time(), - (not self.scrammed), - overridden, - degraded, - self.reactor.getHeatingRate(), - mek_data - } - - _send(RPLC_TYPES.STATUS, sys_status) - end - - local send_iss_status = function () - _send(RPLC_TYPES.ISS_STATUS, iss.status()) - end - - local send_iss_alarm = function (cause) - local iss_alarm = { - cause, - iss.status() - } - - _send(RPLC_TYPES.ISS_ALARM, iss_alarm) - end - local is_scrammed = function () return self.scrammed end local is_linked = function () return self.linked end local unlink = function () self.linked = false end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index f37280d..a4ac6a8 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -11,7 +11,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.3.1" +local R_PLC_VERSION = "alpha-v0.3.3" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 201abe1..e9f0d3d 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -10,8 +10,8 @@ local println_ts = util.println_ts local async_wait = util.async_wait -local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks) -local ISS_CLOCK = 0.5 -- (2Hz, 10 ticks) +local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks) +local ISS_CLOCK = 0.25 -- (4Hz, 5 ticks) however this is AFTER all the ISS checks, so it is a pause between calls, not start-to-start local ISS_EVENT = { SCRAM = 1, @@ -28,7 +28,7 @@ function thread__main(shared_memory, init) local LINK_TICKS = 4 local loop_clock = nil - local ticks_to_update = LINK_TICKS -- start by linking + local ticks_to_update = 0 -- load in from shared memory local networked = shared_memory.networked @@ -40,7 +40,7 @@ function thread__main(shared_memory, init) local conn_watchdog = shared_memory.system.conn_watchdog -- debug - -- local last_update = util.time() + local last_update = util.time() -- event loop while true do @@ -55,26 +55,25 @@ function thread__main(shared_memory, init) -- send updated data if not plc_state.no_modem then - if plc_comms.is_linked() then async_wait(function () plc_comms.send_status(iss_tripped, plc_state.degraded) plc_comms.send_iss_status() end) else - ticks_to_update = ticks_to_update - 1 - - if ticks_to_update <= 0 then + if ticks_to_update == 0 then plc_comms.send_link_req() ticks_to_update = LINK_TICKS + else + ticks_to_update = ticks_to_update - 1 end end end -- debug - -- print(util.time() - last_update) - -- println("ms") - -- last_update = util.time() + print(util.time() - last_update) + println("ms") + last_update = util.time() end elseif event == "modem_message" and networked and not plc_state.no_modem then -- got a packet @@ -82,8 +81,10 @@ function thread__main(shared_memory, init) conn_watchdog.feed() -- handle the packet (plc_state passed to allow clearing SCRAM flag) - local packet = plc_comms.parse_packet(p1, p2, p3, p4, p5) - async_wait(function () plc_comms.handle_packet(packet, plc_state) end) + async_wait(function () + local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5) + plc_comms.handle_packet(packet, plc_state) + end) elseif event == "timer" and networked and param1 == conn_watchdog.get_timer() then -- haven't heard from server recently? shutdown reactor plc_comms.unlink() @@ -169,7 +170,7 @@ function thread__main(shared_memory, init) elseif event == "clock_start" then -- start loop clock loop_clock = os.startTimer(MAIN_CLOCK) - log._debug("loop clock started") + log._debug("main thread started") end -- check for termination request @@ -208,9 +209,6 @@ function thread__iss(shared_memory) local reactor = shared_memory.plc_devices.reactor if event == "timer" and param1 == loop_clock then - -- start next clock timer - loop_clock = os.startTimer(ISS_CLOCK) - -- ISS checks if plc_state.init_ok then -- if we tried to SCRAM but failed, keep trying @@ -244,6 +242,10 @@ function thread__iss(shared_memory) end) end + -- start next clock timer after all the long operations + -- otherwise we will never get around to other events + loop_clock = os.startTimer(ISS_CLOCK) + -- debug -- print(util.time() - last_update) -- println("ms") @@ -270,13 +272,13 @@ function thread__iss(shared_memory) -- watchdog tripped plc_state.scram = true iss.trip_timeout() - println_ts("server timeout, reactor disabled") - log._warning("server timeout, reactor disabled") + println_ts("server timeout") + log._warning("server timeout") end elseif event == "clock_start" then -- start loop clock loop_clock = os.startTimer(ISS_CLOCK) - log._debug("loop clock started") + log._debug("iss thread started") end -- check for termination request diff --git a/rtu/startup.lua b/rtu/startup.lua index 4626df2..2d4c58f 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -17,7 +17,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.3.1" +local RTU_VERSION = "alpha-v0.3.2" local print = util.print local println = util.println @@ -233,7 +233,7 @@ while true do elseif event == "modem_message" then -- got a packet local link_ref = { linked = linked } - local packet = rtu_comms.parse_packet(p1, p2, p3, p4, p5) + local packet = rtu_comms.parse_packet(param1, param2, param3, param4, param5) async_wait(function () rtu_comms.handle_packet(packet, units, link_ref) end) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 972f768..bca5682 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -74,12 +74,14 @@ function scada_packet() self.raw = self.modem_msg_in.msg - if #self.raw >= 3 then - self.valid = true - self.seq_num = self.raw[1] - self.protocol = self.raw[2] - self.length = #self.raw[3] - self.payload = self.raw[3] + if type(self.raw) == "table" then + if #self.raw >= 3 then + self.valid = true + self.seq_num = self.raw[1] + self.protocol = self.raw[2] + self.length = #self.raw[3] + self.payload = self.raw[3] + end end return self.valid @@ -90,12 +92,12 @@ function scada_packet() local modem_event = function () return self.modem_msg_in end local raw_sendable = function () return self.raw end - local sender = function () return self.s_port end - local receiver = function () return self.r_port end + local local_port = function () return self.modem_msg_in.s_port end + local remote_port = function () return self.modem_msg_in.r_port end local is_valid = function () return self.valid end - local seq_num = function () return self.seq_num end + local seq_num = function () return self.seq_num end local protocol = function () return self.protocol end local length = function () return self.length end local data = function () return self.payload end @@ -107,9 +109,9 @@ function scada_packet() -- raw access modem_event = modem_event, raw_sendable = raw_sendable, - -- sender/receiver - sender = sender, - receiver = receiver, + -- ports + local_port = local_port, + remote_port = remote_port, -- well-formed is_valid = is_valid, -- packet properties diff --git a/supervisor/mqueue.lua b/supervisor/mqueue.lua index 1aa5869..f79e686 100644 --- a/supervisor/mqueue.lua +++ b/supervisor/mqueue.lua @@ -23,11 +23,11 @@ function new() end local push_packet = function (message) - push(TYPE.PACKET, message) + _push(TYPE.PACKET, message) end local push_command = function (message) - push(TYPE.COMMAND, message) + _push(TYPE.COMMAND, message) end local pop = function () diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index edc2ea2..e02619c 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -70,6 +70,7 @@ function get_reactor_session(reactor) end function establish_plc_session(local_port, remote_port, for_reactor) + util.println(remote_port) if get_reactor_session(for_reactor) == nil then local plc_s = { open = true, @@ -81,9 +82,12 @@ function establish_plc_session(local_port, remote_port, for_reactor) instance = nil } - plc_s.instance = plc.new_session(next_plc_id, plc_s.in_queue, plc_s.out_queue) + plc_s.instance = plc.new_session(self.next_plc_id, for_reactor, plc_s.in_queue, plc_s.out_queue) table.insert(self.plc_sessions, plc_s) - next_plc_id = next_plc_id + 1 + + log._debug("established new PLC session to " .. remote_port .. " with ID " .. self.next_plc_id) + + self.next_plc_id = self.next_plc_id + 1 -- success return plc_s.instance.get_id() @@ -129,7 +133,7 @@ local function _iterate(sessions) while not session.out_queue.empty() do local msg = session.out_queue.pop() if msg.qtype == mqueue.TYPE.PACKET then - self.modem.transmit(self.r_port, self.l_port, msg.message.raw_sendable()) + self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) end end else @@ -163,7 +167,7 @@ local function _free_closed(sessions) end move_to = move_to + 1 else - log._debug("free'ing closing session " .. session.instance.get_id() .. " on remote port " .. session.r_port) + log._debug("free'ing closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) sessions[i] = nil end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index fa4de82..27a9db6 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -18,7 +18,7 @@ os.loadAPI("session/svsessions.lua") os.loadAPI("supervisor.lua") -local SUPERVISOR_VERSION = "alpha-v0.1.4" +local SUPERVISOR_VERSION = "alpha-v0.1.5" local print = util.print local println = util.println @@ -93,7 +93,7 @@ while true do svsessions.check_all_watchdogs(param1) elseif event == "modem_message" then -- got a packet - local packet = superv_comms.parse_packet(p1, p2, p3, p4, p5) + local packet = superv_comms.parse_packet(param1, param2, param3, param4, param5) superv_comms.handle_packet(packet) end diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 31b032e..571ab49 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -5,6 +5,7 @@ local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES +local RPLC_LINKING = comms.RPLC_LINKING local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local RTU_ADVERT_TYPES = comms.RTU_ADVERT_TYPES @@ -64,7 +65,7 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) -- parse a packet local parse_packet = function(side, sender, reply_to, message, distance) local pkt = nil - local s_pkt = scada_packet() + local s_pkt = comms.scada_packet() -- parse packet as generic SCADA packet s_pkt.receive(side, sender, reply_to, message, distance) @@ -104,21 +105,22 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) local handle_packet = function(packet) if packet ~= nil then - local sender = packet.scada_frame.sender() - local receiver = packet.scada_frame.receiver() + local l_port = packet.scada_frame.local_port() + local r_port = packet.scada_frame.remote_port() local protocol = packet.scada_frame.protocol() -- device (RTU/PLC) listening channel - if receiver == self.dev_listen then + if l_port == self.dev_listen then if protocol == PROTOCOLS.MODBUS_TCP then -- MODBUS response elseif protocol == PROTOCOLS.RPLC then -- reactor PLC packet - local session = svsessions.find_session(SESSION_TYPE.PLC_SESSION, sender) + local session = svsessions.find_session(SESSION_TYPE.PLC_SESSION, r_port) if session then if packet.type == RPLC_TYPES.LINK_REQ then -- new device on this port? that's a collision - _send_plc_linking(sender, { RPLC_LINKING.COLLISION }) + log._debug("PLC_LNK: request from existing connection received on " .. r_port .. ", responding with collision") + _send_plc_linking(r_port, { RPLC_LINKING.COLLISION }) else -- pass the packet onto the session handler session.in_queue.push_packet(packet) @@ -126,18 +128,25 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) else -- unknown session, is this a linking request? if packet.type == RPLC_TYPES.LINK_REQ then - -- this is a linking request - local plc_id = svsessions.establish_plc_session(sender) - if plc_id == false then - -- reactor already has a PLC assigned - _send_plc_linking(sender, { RPLC_LINKING.COLLISION }) + if packet.length == 1 then + -- this is a linking request + local plc_id = svsessions.establish_plc_session(l_port, r_port, packet.data[1]) + if plc_id == false then + -- reactor already has a PLC assigned + log._debug("PLC_LNK: assignment collision with reactor " .. packet.data[1]) + _send_plc_linking(r_port, { RPLC_LINKING.COLLISION }) + else + -- got an ID; assigned to a reactor successfully + log._debug("PLC_LNK: allowed for device at " .. r_port) + _send_plc_linking(r_port, { RPLC_LINKING.ALLOW }) + end else - -- got an ID; assigned to a reactor successfully - _send_plc_linking(sender, { RPLC_LINKING.ALLOW }) + log._debug("PLC_LNK: new linking packet length mismatch") end else -- force a re-link - _send_plc_linking(sender, { RPLC_LINKING.DENY }) + log._debug("PLC_LNK: no session but not a link, force relink") + _send_plc_linking(r_port, { RPLC_LINKING.DENY }) end end elseif protocol == PROTOCOLS.SCADA_MGMT then @@ -146,7 +155,7 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) log._debug("illegal packet type " .. protocol .. " on device listening channel") end -- coordinator listening channel - elseif receiver == self.coord_listen then + elseif l_port == self.coord_listen then if protocol == PROTOCOLS.SCADA_MGMT then -- SCADA management packet elseif protocol == PROTOCOLS.COORD_DATA then @@ -155,7 +164,7 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) log._debug("illegal packet type " .. protocol .. " on coordinator listening channel") end else - log._error("received packet on unused channel " .. receiver, true) + log._error("received packet on unused channel " .. l_port, true) end end end From 68011d6734fa8526f6dc851d506333d8e77007d8 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 27 Apr 2022 12:21:10 -0400 Subject: [PATCH 098/587] #32 new threaded PLC code --- reactor-plc/startup.lua | 31 ++- reactor-plc/threads.lua | 273 ++++++++++++++---------- {supervisor => scada-common}/mqueue.lua | 23 +- scada-common/util.lua | 7 +- supervisor/session/plc.lua | 2 +- supervisor/session/svsessions.lua | 2 +- supervisor/startup.lua | 4 +- 7 files changed, 210 insertions(+), 132 deletions(-) rename {supervisor => scada-common}/mqueue.lua (78%) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index a4ac6a8..e448e55 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -6,12 +6,13 @@ os.loadAPI("scada-common/log.lua") os.loadAPI("scada-common/util.lua") os.loadAPI("scada-common/ppm.lua") os.loadAPI("scada-common/comms.lua") +os.loadAPI("scada-common/mqueue.lua") os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.3.3" +local R_PLC_VERSION = "alpha-v0.4.0" local print = util.print local println = util.println @@ -28,30 +29,42 @@ ppm.mount_all() -- shared memory across threads local __shared_memory = { + -- networked setting networked = config.NETWORKED, + -- PLC system state flags plc_state = { init_ok = true, + shutdown = false, scram = true, degraded = false, no_reactor = false, no_modem = false }, - plc_devices = { + -- core PLC devices + plc_dev = { reactor = ppm.get_fission_reactor(), modem = ppm.get_wireless_modem() }, - system = { + -- system control objects + plc_sys = { iss = nil, plc_comms = nil, conn_watchdog = nil + }, + + -- message queues + q = { + mq_main = mqueue.new(), + mq_iss = mqueue.new(), + mq_comms = mqeuue.new() } } -local smem_dev = __shared_memory.plc_devices -local smem_sys = __shared_memory.system +local smem_dev = __shared_memory.plc_dev +local smem_sys = __shared_memory.plc_sys local plc_state = __shared_memory.plc_state @@ -112,12 +125,12 @@ end init() -- init threads -local main_thread = threads.thread__main(__shared_memory, init) -local iss_thread = threads.thread__iss(__shared_memory) --- local comms_thread = plc.thread__comms(__shared_memory) +local main_thread = threads.thread__main(__shared_memory, init) +local iss_thread = threads.thread__iss(__shared_memory) +local comms_thread = threads.thread__comms(__shared_memory) -- run threads -parallel.waitForAll(main_thread.exec, iss_thread.exec) +parallel.waitForAll(main_thread.exec, iss_thread.exec, comms_thread.exec) -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_SHUTDOWN) ? println_ts("exited") diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index e9f0d3d..eb5468d 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -8,36 +8,37 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts -local async_wait = util.async_wait +local MAIN_CLOCK = 1 -- (1Hz, 20 ticks) +local ISS_CLOCK = 0.5 -- (2Hz, 10 ticks) +local COMMS_CLOCK = 0.25 -- (4Hz, 5 ticks) -local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks) -local ISS_CLOCK = 0.25 -- (4Hz, 5 ticks) however this is AFTER all the ISS checks, so it is a pause between calls, not start-to-start - -local ISS_EVENT = { +local MQ__ISS_CMD = { SCRAM = 1, DEGRADED_SCRAM = 2, TRIP_TIMEOUT = 3 } +local MQ__COMM_CMD = { + SEND_STATUS = 1 +} + -- main thread -function thread__main(shared_memory, init) +function thread__main(smem, init) -- execute thread local exec = function () -- send status updates at 2Hz (every 10 server ticks) (every loop tick) -- send link requests at 0.5Hz (every 40 server ticks) (every 4 loop ticks) local LINK_TICKS = 4 - - local loop_clock = nil local ticks_to_update = 0 + local loop_clock = nil -- load in from shared memory - local networked = shared_memory.networked - local plc_state = shared_memory.plc_state - local plc_devices = shared_memory.plc_devices - - local iss = shared_memory.system.iss - local plc_comms = shared_memory.system.plc_comms - local conn_watchdog = shared_memory.system.conn_watchdog + local networked = smem.networked + local plc_state = smem.plc_state + local plc_dev = smem.plc_dev + local iss = smem.plc_sys.iss + local plc_comms = smem.plc_sys.plc_comms + local conn_watchdog = smem.plc_sys.conn_watchdog -- debug local last_update = util.time() @@ -56,10 +57,7 @@ function thread__main(shared_memory, init) -- send updated data if not plc_state.no_modem then if plc_comms.is_linked() then - async_wait(function () - plc_comms.send_status(iss_tripped, plc_state.degraded) - plc_comms.send_iss_status() - end) + smem.q.mq_comms.push_command(MQ__COMM_CMD.SEND_STATUS) else if ticks_to_update == 0 then plc_comms.send_link_req() @@ -80,15 +78,15 @@ function thread__main(shared_memory, init) -- feed the watchdog first so it doesn't uhh...eat our packets conn_watchdog.feed() - -- handle the packet (plc_state passed to allow clearing SCRAM flag) - async_wait(function () - local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5) - plc_comms.handle_packet(packet, plc_state) - end) + -- handle the packet + local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5) + if packet ~= nil then + smem.q.mq_comms.puch_packet(packet) + end elseif event == "timer" and networked and param1 == conn_watchdog.get_timer() then -- haven't heard from server recently? shutdown reactor plc_comms.unlink() - os.queueEvent("iss_command", ISS_EVENT.TRIP_TIMEOUT) + smem.q.mq_iss.push_command(MQ__ISS_CMD.TRIP_TIMEOUT) elseif event == "peripheral_detach" then -- peripheral disconnect local device = ppm.handle_unmount(param1) @@ -108,7 +106,7 @@ function thread__main(shared_memory, init) if plc_state.init_ok then -- try to scram reactor if it is still connected - os.queueEvent("iss_command", ISS_EVENT.DEGRADED_SCRAM) + smem.q.mq_iss.push_command(MQ__ISS_CMD.DEGRADED_SCRAM) end plc_state.degraded = true @@ -122,18 +120,18 @@ function thread__main(shared_memory, init) if type == "fissionReactor" then -- reconnected reactor - plc_devices.reactor = device + plc_dev.reactor = device - os.queueEvent("iss_command", ISS_EVENT.SCRAM) + smem.q.mq_iss.push_command(MQ__ISS_CMD.SCRAM) println_ts("reactor reconnected.") log._info("reactor reconnected.") plc_state.no_reactor = false if plc_state.init_ok then - iss.reconnect_reactor(plc_devices.reactor) + iss.reconnect_reactor(plc_dev.reactor) if networked then - plc_comms.reconnect_reactor(plc_devices.reactor) + plc_comms.reconnect_reactor(plc_dev.reactor) end end @@ -144,10 +142,10 @@ function thread__main(shared_memory, init) elseif networked and type == "modem" then if device.isWireless() then -- reconnected modem - plc_devices.modem = device + plc_dev.modem = device if plc_state.init_ok then - plc_comms.reconnect_modem(plc_devices.modem) + plc_comms.reconnect_modem(plc_dev.modem) end println_ts("wireless modem reconnected.") @@ -176,6 +174,7 @@ function thread__main(shared_memory, init) -- check for termination request if event == "terminate" or ppm.should_terminate() then -- iss handles reactor shutdown + plc_state.shutdown = true log._warning("terminate requested, main thread exiting") break end @@ -186,80 +185,66 @@ function thread__main(shared_memory, init) end -- ISS monitor thread -function thread__iss(shared_memory) +function thread__iss(smem) -- execute thread local exec = function () - local loop_clock = nil - -- load in from shared memory - local networked = shared_memory.networked - local plc_state = shared_memory.plc_state - local plc_devices = shared_memory.plc_devices + local networked = smem.networked + local plc_state = smem.plc_state + local plc_dev = smem.plc_dev + local iss = smem.plc_sys.iss + local plc_comms = smem.plc_sys.plc_comms - local iss = shared_memory.system.iss - local plc_comms = shared_memory.system.plc_comms + local iss_queue = smem.q.mq_iss - -- debug - -- local last_update = util.time() + local last_update = util.time() - -- event loop + -- thread loop while true do - local event, param1, param2, param3, param4, param5 = os.pullEventRaw() + local reactor = smem.plc_dev.reactor - local reactor = shared_memory.plc_devices.reactor - - if event == "timer" and param1 == loop_clock then - -- ISS checks - if plc_state.init_ok then - -- 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) - async_wait(function () - if not plc_state.no_reactor and plc_state.scram and reactor.getStatus() then - reactor.scram() - end - end) - - -- if we are in standalone mode, continuously reset ISS - -- ISS will trip again if there are faults, but if it isn't cleared, the user can't re-enable - if not networked then - plc_state.scram = false - iss.reset() - end - - -- check safety (SCRAM occurs if tripped) - async_wait(function () - if not plc_state.degraded then - local iss_tripped, iss_status_string, iss_first = iss.check() - plc_state.scram = plc_state.scram or iss_tripped - - if iss_first then - println_ts("[ISS] SCRAM! safety trip: " .. iss_status_string) - if networked then - plc_comms.send_iss_alarm(iss_status_string) - end - end - end - end) + -- ISS checks + if plc_state.init_ok then + -- 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) + if not plc_state.no_reactor and plc_state.scram and reactor.getStatus() then + reactor.scram() end - -- start next clock timer after all the long operations - -- otherwise we will never get around to other events - loop_clock = os.startTimer(ISS_CLOCK) + -- if we are in standalone mode, continuously reset ISS + -- ISS will trip again if there are faults, but if it isn't cleared, the user can't re-enable + if not networked then + plc_state.scram = false + iss.reset() + end - -- debug - -- print(util.time() - last_update) - -- println("ms") - -- last_update = util.time() - elseif event == "iss_command" then - -- handle ISS commands - if param1 == ISS_EVENT.SCRAM then - -- basic SCRAM - plc_state.scram = true - async_wait(reactor.scram) - elseif param1 == ISS_EVENT.DEGRADED_SCRAM then - -- SCRAM with print - plc_state.scram = true - async_wait(function () + -- check safety (SCRAM occurs if tripped) + if not plc_state.degraded then + local iss_tripped, iss_status_string, iss_first = iss.check() + plc_state.scram = plc_state.scram or iss_tripped + + if iss_first then + println_ts("[ISS] SCRAM! safety trip: " .. iss_status_string) + if networked then + plc_comms.send_iss_alarm(iss_status_string) + end + end + end + end + + -- check for messages in the message queue + while comms_queue.ready() do + local msg = comms_queue.pop() + + if msg.qtype == mqueue.TYPE.COMMAND then + -- received a command + if msg.message == MQ__ISS_CMD.SCRAM then + -- basic SCRAM + plc_state.scram = true + reactor.scram() + elseif msg.message == MQ__ISS_CMD.DEGRADED_SCRAM then + -- SCRAM with print + plc_state.scram = true if reactor.scram() then println_ts("successful reactor SCRAM") log._error("successful reactor SCRAM") @@ -267,38 +252,106 @@ function thread__iss(shared_memory) println_ts("failed reactor SCRAM") log._error("failed reactor SCRAM") end - end) - elseif param1 == ISS_EVENT.TRIP_TIMEOUT then - -- watchdog tripped - plc_state.scram = true - iss.trip_timeout() - println_ts("server timeout") - log._warning("server timeout") + elseif msg.message == MQ__ISS_CMD.TRIP_TIMEOUT then + -- watchdog tripped + plc_state.scram = true + iss.trip_timeout() + println_ts("server timeout") + log._warning("server timeout") + end + elseif msg.qtype == mqueue.TYPE.DATA then + -- received data + elseif msg.qtype == mqueue.TYPE.PACKET then + -- received a packet end - elseif event == "clock_start" then - -- start loop clock - loop_clock = os.startTimer(ISS_CLOCK) - log._debug("iss thread started") + + -- quick yield + if iss_queue.ready() then util.nop() end end -- check for termination request - if event == "terminate" or ppm.should_terminate() then + if plc_state.shutdown then -- safe exit - log._warning("terminate requested, iss thread shutdown") + log._warning("iss thread shutdown initiated") if plc_state.init_ok then plc_state.scram = true - async_wait(reactor.scram) + reactor.scram() if reactor.__p_is_ok() then println_ts("reactor disabled") + log._info("iss thread reactor SCRAM OK") else -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_LOST_CONTROL) ? println_ts("exiting, reactor failed to disable") + log._error("iss thread failed to SCRAM reactor on exit") end end - break + log._warning("iss thread exiting") + return + end + + -- debug + -- print(util.time() - last_update) + -- println("ms") + -- last_update = util.time() + + -- delay before next check + local sleep_for = ISS_CLOCK - (util.time() - last_update) + if sleep_for > 0.05 then + sleep(sleep_for) end end end return { exec = exec } end + +function thread__comms(smem) + -- execute thread + local exec = function () + -- load in from shared memory + local plc_state = smem.plc_state + local plc_comms = smem.plc_sys.plc_comms + + local comms_queue = smem.q.mq_comms + + -- thread loop + while true do + local last_update = util.time() + + -- check for messages in the message queue + while comms_queue.ready() do + local msg = comms_queue.pop() + + if msg.qtype == mqueue.TYPE.COMMAND then + -- received a command + if msg.message == MQ__COMM_CMD.SEND_STATUS then + -- send PLC/ISS status + plc_comms.send_status(plc_state.degraded) + plc_comms.send_iss_status() + end + elseif msg.qtype == mqueue.TYPE.DATA then + -- received data + elseif msg.qtype == mqueue.TYPE.PACKET then + -- received a packet + -- handle the packet (plc_state passed to allow clearing SCRAM flag) + plc_comms.handle_packet(msg.message, plc_state) + end + + -- quick yield + if comms_queue.ready() then util.nop() end + end + + -- check for termination request + if plc_state.shutdown then + log._warning("comms thread exiting") + return + end + + -- delay before next check + local sleep_for = COMMS_CLOCK - (util.time() - last_update) + if sleep_for > 0.05 then + sleep(sleep_for) + end + end + end +end diff --git a/supervisor/mqueue.lua b/scada-common/mqueue.lua similarity index 78% rename from supervisor/mqueue.lua rename to scada-common/mqueue.lua index f79e686..3881d02 100644 --- a/supervisor/mqueue.lua +++ b/scada-common/mqueue.lua @@ -4,7 +4,8 @@ TYPE = { COMMAND = 0, - PACKET = 1 + DATA = 1, + PACKET = 2 } function new() @@ -17,19 +18,27 @@ function new() local empty = function () return #queue == 0 end + + local ready = function () + return #queue > 0 + end local _push = function (qtype, message) table.insert(queue, { qtype = qtype, message = message }) end - local push_packet = function (message) - _push(TYPE.PACKET, message) - end - local push_command = function (message) _push(TYPE.COMMAND, message) end - + + local push_data = function (message) + _push(TYPE.DATA, message) + end + + local push_packet = function (message) + _push(TYPE.PACKET, message) + end + local pop = function () if #queue > 0 then return table.remove(queue) @@ -41,7 +50,9 @@ function new() return { length = length, empty = empty, + ready = ready, push_packet = push_packet, + push_data = push_data, push_command = push_command, pop = pop } diff --git a/scada-common/util.lua b/scada-common/util.lua index 97ce601..11b258e 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -39,9 +39,10 @@ end -- PARALLELIZATION -- --- block waiting for parallel call -function async_wait(f) - parallel.waitForAll(f) +-- no-op to provide a brief pause (and a yield) +-- EVENT_CONSUMER: this function consumes events +function nop() + sleep(0.05) end -- WATCHDOG -- diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 695b34b..3a51666 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -330,7 +330,7 @@ function new_session(id, for_reactor, in_queue, out_queue) -- handle queue -- ------------------ - if not self.in_q.empty() then + if self.in_q.ready() then -- get a new message to process local message = self.in_q.pop() diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index e02619c..9c02ce0 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -130,7 +130,7 @@ local function _iterate(sessions) if ok then -- send packets in out queue -- @todo handle commands if that's being used too - while not session.out_queue.empty() do + while session.out_queue.ready() do local msg = session.out_queue.pop() if msg.qtype == mqueue.TYPE.PACKET then self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 27a9db6..138cfd8 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -7,9 +7,9 @@ os.loadAPI("scada-common/util.lua") os.loadAPI("scada-common/ppm.lua") os.loadAPI("scada-common/comms.lua") os.loadAPI("scada-common/modbus.lua") +os.loadAPI("scada-common/mqueue.lua") os.loadAPI("config.lua") -os.loadAPI("mqueue.lua") os.loadAPI("session/rtu.lua") os.loadAPI("session/plc.lua") @@ -18,7 +18,7 @@ os.loadAPI("session/svsessions.lua") os.loadAPI("supervisor.lua") -local SUPERVISOR_VERSION = "alpha-v0.1.5" +local SUPERVISOR_VERSION = "alpha-v0.1.6" local print = util.print local println = util.println From 1ba5c7f8283406613ab8645b54604a1d45835e21 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 27 Apr 2022 12:27:15 -0400 Subject: [PATCH 099/587] fixed PLC mqueue typo and removed unused mq_main --- reactor-plc/startup.lua | 3 +-- reactor-plc/threads.lua | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index e448e55..56bb072 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -57,9 +57,8 @@ local __shared_memory = { -- message queues q = { - mq_main = mqueue.new(), mq_iss = mqueue.new(), - mq_comms = mqeuue.new() + mq_comms = mqeueu.new() } } diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index eb5468d..060bbe5 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -1,6 +1,5 @@ -- #REQUIRES comms.lua -- #REQUIRES ppm.lua --- #REQUIRES plc.lua -- #REQUIRES util.lua local print = util.print From ccf06956f9c71b6a27e9a44b7277fae48ab8aaf7 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 27 Apr 2022 12:37:28 -0400 Subject: [PATCH 100/587] fixed another typo --- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 56bb072..025c274 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -48,7 +48,7 @@ local __shared_memory = { modem = ppm.get_wireless_modem() }, - -- system control objects + -- system objects plc_sys = { iss = nil, plc_comms = nil, diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 060bbe5..ec0d267 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -1,4 +1,5 @@ -- #REQUIRES comms.lua +-- #REQUIRES log.lua -- #REQUIRES ppm.lua -- #REQUIRES util.lua @@ -80,7 +81,7 @@ function thread__main(smem, init) -- handle the packet local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5) if packet ~= nil then - smem.q.mq_comms.puch_packet(packet) + smem.q.mq_comms.push_packet(packet) end elseif event == "timer" and networked and param1 == conn_watchdog.get_timer() then -- haven't heard from server recently? shutdown reactor From 71be6aca1a09400688e218f5973a71bede67143b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 27 Apr 2022 12:43:32 -0400 Subject: [PATCH 101/587] cleanup and last_update bugfix for comms thread --- reactor-plc/threads.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index ec0d267..f4375c9 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -286,7 +286,7 @@ function thread__iss(smem) end end log._warning("iss thread exiting") - return + break end -- debug @@ -305,6 +305,7 @@ function thread__iss(smem) return { exec = exec } end +-- communications handler thread function thread__comms(smem) -- execute thread local exec = function () @@ -314,10 +315,10 @@ function thread__comms(smem) local comms_queue = smem.q.mq_comms + local last_update = util.time() + -- thread loop while true do - local last_update = util.time() - -- check for messages in the message queue while comms_queue.ready() do local msg = comms_queue.pop() @@ -344,7 +345,7 @@ function thread__comms(smem) -- check for termination request if plc_state.shutdown then log._warning("comms thread exiting") - return + break end -- delay before next check From 8c4598e7a6d6565a8be8944ddb5758f3be94b763 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 27 Apr 2022 12:46:04 -0400 Subject: [PATCH 102/587] #32 new threaded RTU code --- rtu/rtu.lua | 4 +- rtu/startup.lua | 143 ++++++++++++++++------------------------------ rtu/threads.lua | 147 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 95 deletions(-) create mode 100644 rtu/threads.lua diff --git a/rtu/rtu.lua b/rtu/rtu.lua index ccf6e44..54bf780 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -183,7 +183,7 @@ function rtu_comms(modem, local_port, server_port) end -- handle a MODBUS/SCADA packet - local handle_packet = function(packet, units, ref) + local handle_packet = function(packet, units, rtu_state) if packet ~= nil then local protocol = packet.scada_frame.protocol() @@ -209,7 +209,7 @@ function rtu_comms(modem, local_port, server_port) -- SCADA management packet if packet.type == SCADA_MGMT_TYPES.REMOTE_LINKED then -- acknowledgement - ref.linked = true + rtu_state.linked = true elseif packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then -- request for capabilities again send_advertisement(units) diff --git a/rtu/startup.lua b/rtu/startup.lua index 2d4c58f..990819f 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -6,26 +6,26 @@ os.loadAPI("scada-common/log.lua") os.loadAPI("scada-common/util.lua") os.loadAPI("scada-common/ppm.lua") os.loadAPI("scada-common/comms.lua") +os.loadAPI("scada-common/mqueue.lua") os.loadAPI("scada-common/modbus.lua") os.loadAPI("scada-common/rsio.lua") os.loadAPI("config.lua") os.loadAPI("rtu.lua") +os.loadAPI("threads.lua") os.loadAPI("dev/redstone_rtu.lua") os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.3.2" +local RTU_VERSION = "alpha-v0.4.0" local print = util.print local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts -local async_wait = util.async_wait - log._info("========================================") log._info("BOOTING rtu.startup " .. RTU_VERSION) log._info("========================================") @@ -35,15 +35,37 @@ println(">> RTU " .. RTU_VERSION .. " <<") -- startup ---------------------------------------- -local units = {} -local linked = false - -- mount connected devices ppm.mount_all() +local __shared_memory = { + -- RTU system state flags + rtu_state = { + linked = false, + shutdown = false + }, + + -- core RTU devices + rtu_dev = { + modem = ppm.get_wireless_modem() + }, + + -- system objects + rtu_sys = { + rtu_comms = nil, + units = {} + }, + + -- message queues + q = { + mq_comms = mqeueu.new() + } +} + +local smem_dev = __shared_memory.rtu_dev + -- get modem -local modem = ppm.get_wireless_modem() -if modem == nil then +if smem_dev.modem == nil then println("boot> wireless modem not found") log._warning("no wireless modem on startup") return @@ -52,9 +74,11 @@ end local rtu_comms = rtu.rtu_comms(modem, config.LISTEN_PORT, config.SERVER_PORT) ---------------------------------------- --- determine configuration +-- interpret config and init units ---------------------------------------- +local units = __shared_memory.rtu_sys.units + local rtu_redstone = config.RTU_REDSTONE local rtu_devices = config.RTU_DEVICES @@ -69,12 +93,12 @@ for reactor_idx = 1, #rtu_redstone do for i = 1, #io_table do local valid = false - local config = io_table[i] + local conf = io_table[i] -- verify configuration - if rsio.is_valid_channel(config.channel) and rsio.is_valid_side(config.side) then - if config.bundled_color then - valid = rsio.is_color(config.bundled_color) + 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 @@ -87,24 +111,24 @@ for reactor_idx = 1, #rtu_redstone do log._warning(message) else -- link redstone in RTU - local mode = rsio.get_io_mode(config.channel) + local mode = rsio.get_io_mode(conf.channel) if mode == rsio.IO_MODE.DIGITAL_IN then - rs_rtu.link_di(config.channel, config.side, config.bundled_color) + rs_rtu.link_di(conf.channel, conf.side, conf.bundled_color) elseif mode == rsio.IO_MODE.DIGITAL_OUT then - rs_rtu.link_do(config.channel, config.side, config.bundled_color) + rs_rtu.link_do(conf.channel, conf.side, conf.bundled_color) elseif mode == rsio.IO_MODE.ANALOG_IN then - rs_rtu.link_ai(config.channel, config.side) + rs_rtu.link_ai(conf.channel, conf.side) elseif mode == rsio.IO_MODE.ANALOG_OUT then - rs_rtu.link_ao(config.channel, config.side) + rs_rtu.link_ao(conf.channel, conf.side) else -- should be unreachable code, we already validated channels log._error("init> fell through if chain attempting to identify IO mode", true) break end - table.insert(capabilities, config.channel) + table.insert(capabilities, conf.channel) - log._debug("init> linked redstone " .. #capabilities .. ": " .. rsio.to_string(config.channel) .. " (" .. config.side .. + log._debug("init> linked redstone " .. #capabilities .. ": " .. rsio.to_string(conf.channel) .. " (" .. conf.side .. ") for reactor " .. rtu_redstone[reactor_idx].for_reactor) end end @@ -171,82 +195,15 @@ for i = 1, #rtu_devices do end ---------------------------------------- --- main loop +-- start system ---------------------------------------- --- advertisement/heartbeat clock (every 2 seconds) -local loop_clock = os.startTimer(2) +-- init threads +local main_thread = threads.thread__main(__shared_memory) +local comms_thread = threads.thread__comms(__shared_memory) --- event loop -while true do - local event, param1, param2, param3, param4, param5 = os.pullEventRaw() - - if event == "peripheral_detach" then - -- handle loss of a device - local device = ppm.handle_unmount(param1) - - for i = 1, #units do - -- find disconnected device - if units[i].device == device.dev then - -- we are going to let the PPM prevent crashes - -- return fault flags/codes to MODBUS queries - local unit = units[i] - println_ts("lost the " .. unit.type .. " on interface " .. unit.name) - end - end - elseif event == "peripheral" then - -- relink lost peripheral to correct unit entry - local type, device = ppm.mount(param1) - - for i = 1, #units do - local unit = units[i] - - -- find disconnected device to reconnect - if unit.name == param1 then - -- found, re-link - unit.device = device - - if unit.type == "boiler" then - unit.rtu = boiler_rtu.new(device) - elseif unit.type == "turbine" then - unit.rtu = turbine_rtu.new(device) - elseif unit.type == "imatrix" then - unit.rtu = imatrix_rtu.new(device) - end - - unit.modbus_io = modbus.new(unit.rtu) - - println_ts("reconnected the " .. unit.type .. " on interface " .. unit.name) - end - end - elseif event == "timer" and param1 == loop_clock then - -- start next clock timer - loop_clock = os.startTimer(2) - - -- period tick, if we are linked send heartbeat, if not send advertisement - if linked then - rtu_comms.send_heartbeat() - else - -- advertise units - rtu_comms.send_advertisement(units) - end - elseif event == "modem_message" then - -- got a packet - local link_ref = { linked = linked } - local packet = rtu_comms.parse_packet(param1, param2, param3, param4, param5) - - async_wait(function () rtu_comms.handle_packet(packet, units, link_ref) end) - - -- if linked, stop sending advertisements - linked = link_ref.linked - end - - -- check for termination request - if event == "terminate" or ppm.should_terminate() then - log._warning("terminate requested, exiting...") - break - end -end +-- run threads +parallel.waitForAll(main_thread.exec, comms_thread.exec) println_ts("exited") log._info("exited") diff --git a/rtu/threads.lua b/rtu/threads.lua new file mode 100644 index 0000000..bc96d3b --- /dev/null +++ b/rtu/threads.lua @@ -0,0 +1,147 @@ +-- #REQUIRES comms.lua +-- #REQUIRES log.lua +-- #REQUIRES ppm.lua +-- #REQUIRES util.lua + +local print = util.print +local println = util.println +local print_ts = util.print_ts +local println_ts = util.println_ts + +local MAIN_CLOCK = 2 -- (2Hz, 40 ticks) +local COMMS_CLOCK = 0.25 -- (4Hz, 5 ticks) + +-- main thread +function thread__main(smem) + -- execute thread + local exec = function () + -- advertisement/heartbeat clock + local loop_clock = os.startTimer(MAIN_CLOCK) + + -- load in from shared memory + local rtu_state = smem.rtu_state + local rtu_dev = smem.rtu_dev + local rtu_comms = smem.rtu_sys.rtu_comms + + -- event loop + while true do + local event, param1, param2, param3, param4, param5 = os.pullEventRaw() + + if event == "peripheral_detach" then + -- handle loss of a device + local device = ppm.handle_unmount(param1) + + for i = 1, #units do + -- find disconnected device + if units[i].device == device.dev then + -- we are going to let the PPM prevent crashes + -- return fault flags/codes to MODBUS queries + local unit = units[i] + println_ts("lost the " .. unit.type .. " on interface " .. unit.name) + end + end + elseif event == "peripheral" then + -- relink lost peripheral to correct unit entry + local type, device = ppm.mount(param1) + + for i = 1, #units do + local unit = units[i] + + -- find disconnected device to reconnect + if unit.name == param1 then + -- found, re-link + unit.device = device + + if unit.type == "boiler" then + unit.rtu = boiler_rtu.new(device) + elseif unit.type == "turbine" then + unit.rtu = turbine_rtu.new(device) + elseif unit.type == "imatrix" then + unit.rtu = imatrix_rtu.new(device) + end + + unit.modbus_io = modbus.new(unit.rtu) + + println_ts("reconnected the " .. unit.type .. " on interface " .. unit.name) + end + end + elseif event == "timer" and param1 == loop_clock then + -- start next clock timer + loop_clock = os.startTimer(MAIN_CLOCK) + + -- period tick, if we are linked send heartbeat, if not send advertisement + if rtu_state.linked then + rtu_comms.send_heartbeat() + else + -- advertise units + rtu_comms.send_advertisement(units) + end + elseif event == "modem_message" then + -- got a packet + local packet = rtu_comms.parse_packet(param1, param2, param3, param4, param5) + if packet ~= nil then + smem.q.mq_comms.push_packet(packet) + end + + rtu_comms.handle_packet(packet, units, link_ref) + end + + -- check for termination request + if event == "terminate" or ppm.should_terminate() then + rtu_state.shutdown = true + log._warning("terminate requested, main thread exiting") + break + end + end + end + + return { exec = exec } +end + +-- communications handler thread +function thread__comms(smem) + -- execute thread + local exec = function () + -- load in from shared memory + local rtu_state = smem.rtu_state + local rtu_comms = smem.rtu_sys.rtu_comms + local units = smem.rtu_sys.units + + local comms_queue = smem.q.mq_comms + + local last_update = util.time() + + -- thread loop + while true do + -- check for messages in the message queue + while comms_queue.ready() do + local msg = comms_queue.pop() + + if msg.qtype == mqueue.TYPE.COMMAND then + -- received a command + elseif msg.qtype == mqueue.TYPE.DATA then + -- received data + elseif msg.qtype == mqueue.TYPE.PACKET then + -- received a packet + -- handle the packet (rtu_state passed to allow setting link flag) + rtu_comms.handle_packet(msg.message, units, rtu_state) + end + + -- quick yield + if comms_queue.ready() then util.nop() end + end + + -- check for termination request + if rtu_state.shutdown then + log._warning("comms thread exiting") + break + end + + -- delay before next check + local sleep_for = COMMS_CLOCK - (util.time() - last_update) + if sleep_for > 0.05 then + sleep(sleep_for) + end + end + end +end From 14377e734853e5ab5a43186eea57a2ecd5373d1a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 27 Apr 2022 15:01:10 -0400 Subject: [PATCH 103/587] don't run PLC comms thread if not networked --- reactor-plc/startup.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 025c274..23ca80a 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -129,7 +129,11 @@ local iss_thread = threads.thread__iss(__shared_memory) local comms_thread = threads.thread__comms(__shared_memory) -- run threads -parallel.waitForAll(main_thread.exec, iss_thread.exec, comms_thread.exec) +if __shared_memory.networked then + parallel.waitForAll(main_thread.exec, iss_thread.exec, comms_thread.exec) +else + parallel.waitForAll(main_thread.exec, iss_thread.exec) +end -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_SHUTDOWN) ? println_ts("exited") From 67a93016c0f837fa12a4738c2c56a5e88d6689fe Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 27 Apr 2022 15:52:34 -0400 Subject: [PATCH 104/587] threaded RTU/PLC bugfixes --- reactor-plc/startup.lua | 4 ++-- reactor-plc/threads.lua | 28 +++++++++++++++++++++------- rtu/startup.lua | 7 ++++--- rtu/threads.lua | 14 ++++++++++++-- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 23ca80a..df117f5 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -12,7 +12,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.4.0" +local R_PLC_VERSION = "alpha-v0.4.1" local print = util.print local println = util.println @@ -58,7 +58,7 @@ local __shared_memory = { -- message queues q = { mq_iss = mqueue.new(), - mq_comms = mqeueu.new() + mq_comms = mqueue.new() } } diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index f4375c9..17ab22e 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -26,6 +26,8 @@ local MQ__COMM_CMD = { function thread__main(smem, init) -- execute thread local exec = function () + log._debug("main thread init, clock inactive") + -- send status updates at 2Hz (every 10 server ticks) (every loop tick) -- send link requests at 0.5Hz (every 40 server ticks) (every 4 loop ticks) local LINK_TICKS = 4 @@ -168,7 +170,7 @@ function thread__main(smem, init) elseif event == "clock_start" then -- start loop clock loop_clock = os.startTimer(MAIN_CLOCK) - log._debug("main thread started") + log._debug("main thread clock started") end -- check for termination request @@ -188,6 +190,8 @@ end function thread__iss(smem) -- execute thread local exec = function () + log._debug("iss thread start") + -- load in from shared memory local networked = smem.networked local plc_state = smem.plc_state @@ -233,8 +237,8 @@ function thread__iss(smem) end -- check for messages in the message queue - while comms_queue.ready() do - local msg = comms_queue.pop() + while iss_queue.ready() do + local msg = iss_queue.pop() if msg.qtype == mqueue.TYPE.COMMAND then -- received a command @@ -265,7 +269,7 @@ function thread__iss(smem) -- received a packet end - -- quick yield + -- quick yield if we are looping right back if iss_queue.ready() then util.nop() end end @@ -296,8 +300,11 @@ function thread__iss(smem) -- delay before next check local sleep_for = ISS_CLOCK - (util.time() - last_update) - if sleep_for > 0.05 then + last_update = util.time() + if sleep_for > 0 then sleep(sleep_for) + else + sleep(0.05) end end end @@ -309,6 +316,8 @@ end function thread__comms(smem) -- execute thread local exec = function () + log._debug("comms thread start") + -- load in from shared memory local plc_state = smem.plc_state local plc_comms = smem.plc_sys.plc_comms @@ -338,7 +347,7 @@ function thread__comms(smem) plc_comms.handle_packet(msg.message, plc_state) end - -- quick yield + -- quick yield if we are looping right back if comms_queue.ready() then util.nop() end end @@ -350,9 +359,14 @@ function thread__comms(smem) -- delay before next check local sleep_for = COMMS_CLOCK - (util.time() - last_update) - if sleep_for > 0.05 then + last_update = util.time() + if sleep_for > 0 then sleep(sleep_for) + else + sleep(0.05) end end end + + return { exec = exec } end diff --git a/rtu/startup.lua b/rtu/startup.lua index 990819f..fed7dd0 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -19,7 +19,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.4.0" +local RTU_VERSION = "alpha-v0.4.1" local print = util.print local println = util.println @@ -58,11 +58,12 @@ local __shared_memory = { -- message queues q = { - mq_comms = mqeueu.new() + mq_comms = mqueue.new() } } local smem_dev = __shared_memory.rtu_dev +local smem_sys = __shared_memory.rtu_sys -- get modem if smem_dev.modem == nil then @@ -71,7 +72,7 @@ if smem_dev.modem == nil then return end -local rtu_comms = rtu.rtu_comms(modem, config.LISTEN_PORT, config.SERVER_PORT) +smem_sys.rtu_comms = rtu.rtu_comms(smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT) ---------------------------------------- -- interpret config and init units diff --git a/rtu/threads.lua b/rtu/threads.lua index bc96d3b..3f021a6 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -15,6 +15,8 @@ local COMMS_CLOCK = 0.25 -- (4Hz, 5 ticks) function thread__main(smem) -- execute thread local exec = function () + log._debug("main thread start") + -- advertisement/heartbeat clock local loop_clock = os.startTimer(MAIN_CLOCK) @@ -22,6 +24,7 @@ function thread__main(smem) local rtu_state = smem.rtu_state local rtu_dev = smem.rtu_dev local rtu_comms = smem.rtu_sys.rtu_comms + local units = smem.rtu_sys.units -- event loop while true do @@ -102,6 +105,8 @@ end function thread__comms(smem) -- execute thread local exec = function () + log._debug("comms thread start") + -- load in from shared memory local rtu_state = smem.rtu_state local rtu_comms = smem.rtu_sys.rtu_comms @@ -127,7 +132,7 @@ function thread__comms(smem) rtu_comms.handle_packet(msg.message, units, rtu_state) end - -- quick yield + -- quick yield if we are looping right back if comms_queue.ready() then util.nop() end end @@ -139,9 +144,14 @@ function thread__comms(smem) -- delay before next check local sleep_for = COMMS_CLOCK - (util.time() - last_update) - if sleep_for > 0.05 then + last_update = util.time() + if sleep_for > 0 then sleep(sleep_for) + else + sleep(0.05) end end end + + return { exec = exec } end From 146e0bf5693e09dc7478047102025560bc56627e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 27 Apr 2022 15:56:55 -0400 Subject: [PATCH 105/587] protected sleep call --- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 10 ++++++---- rtu/startup.lua | 2 +- rtu/threads.lua | 6 ++++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index df117f5..bca0027 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -12,7 +12,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.4.1" +local R_PLC_VERSION = "alpha-v0.4.2" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 17ab22e..ce17609 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -8,6 +8,8 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts +local psleep = util.psleep + local MAIN_CLOCK = 1 -- (1Hz, 20 ticks) local ISS_CLOCK = 0.5 -- (2Hz, 10 ticks) local COMMS_CLOCK = 0.25 -- (4Hz, 5 ticks) @@ -302,9 +304,9 @@ function thread__iss(smem) local sleep_for = ISS_CLOCK - (util.time() - last_update) last_update = util.time() if sleep_for > 0 then - sleep(sleep_for) + psleep(sleep_for) else - sleep(0.05) + psleep(0.05) end end end @@ -361,9 +363,9 @@ function thread__comms(smem) local sleep_for = COMMS_CLOCK - (util.time() - last_update) last_update = util.time() if sleep_for > 0 then - sleep(sleep_for) + psleep(sleep_for) else - sleep(0.05) + psleep(0.05) end end end diff --git a/rtu/startup.lua b/rtu/startup.lua index fed7dd0..c04a902 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -19,7 +19,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.4.1" +local RTU_VERSION = "alpha-v0.4.2" local print = util.print local println = util.println diff --git a/rtu/threads.lua b/rtu/threads.lua index 3f021a6..692697d 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -8,6 +8,8 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts +local psleep = util.psleep + local MAIN_CLOCK = 2 -- (2Hz, 40 ticks) local COMMS_CLOCK = 0.25 -- (4Hz, 5 ticks) @@ -146,9 +148,9 @@ function thread__comms(smem) local sleep_for = COMMS_CLOCK - (util.time() - last_update) last_update = util.time() if sleep_for > 0 then - sleep(sleep_for) + psleep(sleep_for) else - sleep(0.05) + psleep(0.05) end end end From d40937b46795285ac8385a1a2194ad71bb806497 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 27 Apr 2022 16:06:30 -0400 Subject: [PATCH 106/587] this was supposed to be in that pr merge oops --- scada-common/util.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scada-common/util.lua b/scada-common/util.lua index 11b258e..69bc7f1 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -39,10 +39,15 @@ end -- PARALLELIZATION -- +-- protected sleep call so we still are in charge of catching termination +function psleep(t) + pcall(os.sleep, t) +end + -- no-op to provide a brief pause (and a yield) -- EVENT_CONSUMER: this function consumes events function nop() - sleep(0.05) + psleep(0.05) end -- WATCHDOG -- From 82726520b8b0ab1735364c2fcf4f538a95fca3a6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 27 Apr 2022 16:24:28 -0400 Subject: [PATCH 107/587] that was a stack not a queue, nice --- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- scada-common/mqueue.lua | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index bca0027..659c8a6 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -12,7 +12,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.4.2" +local R_PLC_VERSION = "alpha-v0.4.3" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index c04a902..be27048 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -19,7 +19,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.4.2" +local RTU_VERSION = "alpha-v0.4.3" local print = util.print local println = util.println diff --git a/scada-common/mqueue.lua b/scada-common/mqueue.lua index 3881d02..f14951e 100644 --- a/scada-common/mqueue.lua +++ b/scada-common/mqueue.lua @@ -41,7 +41,7 @@ function new() local pop = function () if #queue > 0 then - return table.remove(queue) + return table.remove(queue, 1) else return nil end From 46a27a3f3a36365ee05d1d58c6d4f83f399274de Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 27 Apr 2022 16:38:41 -0400 Subject: [PATCH 108/587] check shutdown flag in worker loops so they don't lock up the exit process --- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 4 ++-- rtu/startup.lua | 2 +- rtu/threads.lua | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 659c8a6..ae38449 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -12,7 +12,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.4.3" +local R_PLC_VERSION = "alpha-v0.4.4" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index ce17609..b774dbb 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -239,7 +239,7 @@ function thread__iss(smem) end -- check for messages in the message queue - while iss_queue.ready() do + while iss_queue.ready() and not plc_state.shutdown do local msg = iss_queue.pop() if msg.qtype == mqueue.TYPE.COMMAND then @@ -331,7 +331,7 @@ function thread__comms(smem) -- thread loop while true do -- check for messages in the message queue - while comms_queue.ready() do + while comms_queue.ready() and not plc_state.shutdown do local msg = comms_queue.pop() if msg.qtype == mqueue.TYPE.COMMAND then diff --git a/rtu/startup.lua b/rtu/startup.lua index be27048..8fea859 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -19,7 +19,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.4.3" +local RTU_VERSION = "alpha-v0.4.4" local print = util.print local println = util.println diff --git a/rtu/threads.lua b/rtu/threads.lua index 692697d..c8abdd9 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -121,7 +121,7 @@ function thread__comms(smem) -- thread loop while true do -- check for messages in the message queue - while comms_queue.ready() do + while comms_queue.ready() and not plc_state.shutdown do local msg = comms_queue.pop() if msg.qtype == mqueue.TYPE.COMMAND then From fe3b8e6f88641e7c4b61eb9173c05dbef6743356 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 27 Apr 2022 17:59:25 -0400 Subject: [PATCH 109/587] fixed up worker loop delay logic --- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 43 +++++++++++++++-------------------------- rtu/startup.lua | 2 +- rtu/threads.lua | 19 +++++++++--------- 4 files changed, 27 insertions(+), 39 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index ae38449..2ceb289 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -12,7 +12,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.4.4" +local R_PLC_VERSION = "alpha-v0.4.5" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index b774dbb..ed1d93b 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -10,9 +10,9 @@ local println_ts = util.println_ts local psleep = util.psleep -local MAIN_CLOCK = 1 -- (1Hz, 20 ticks) -local ISS_CLOCK = 0.5 -- (2Hz, 10 ticks) -local COMMS_CLOCK = 0.25 -- (4Hz, 5 ticks) +local MAIN_CLOCK = 1 -- (1Hz, 20 ticks) +local ISS_SLEEP = 500 -- (500ms, 10 ticks) +local COMMS_SLEEP = 150 -- (150ms, 3 ticks) local MQ__ISS_CMD = { SCRAM = 1, @@ -44,9 +44,6 @@ function thread__main(smem, init) local plc_comms = smem.plc_sys.plc_comms local conn_watchdog = smem.plc_sys.conn_watchdog - -- debug - local last_update = util.time() - -- event loop while true do local event, param1, param2, param3, param4, param5 = os.pullEventRaw() @@ -71,11 +68,6 @@ function thread__main(smem, init) end end end - - -- debug - print(util.time() - last_update) - println("ms") - last_update = util.time() end elseif event == "modem_message" and networked and not plc_state.no_modem then -- got a packet @@ -271,8 +263,8 @@ function thread__iss(smem) -- received a packet end - -- quick yield if we are looping right back - if iss_queue.ready() then util.nop() end + -- quick yield + util.nop() end -- check for termination request @@ -300,13 +292,11 @@ function thread__iss(smem) -- println("ms") -- last_update = util.time() - -- delay before next check - local sleep_for = ISS_CLOCK - (util.time() - last_update) + -- delay before next check, only if >50ms since we did already yield + local sleep_for = ISS_SLEEP - (util.time() - last_update) last_update = util.time() - if sleep_for > 0 then - psleep(sleep_for) - else - psleep(0.05) + if sleep_for >= 50 then + psleep(sleep_for / 1000.0) end end end @@ -349,8 +339,8 @@ function thread__comms(smem) plc_comms.handle_packet(msg.message, plc_state) end - -- quick yield if we are looping right back - if comms_queue.ready() then util.nop() end + -- quick yield + util.nop() end -- check for termination request @@ -359,13 +349,12 @@ function thread__comms(smem) break end - -- delay before next check - local sleep_for = COMMS_CLOCK - (util.time() - last_update) + -- delay before next check, only if >50ms since we did already yield + local sleep_for = COMMS_SLEEP - (util.time() - last_update) last_update = util.time() - if sleep_for > 0 then - psleep(sleep_for) - else - psleep(0.05) + if sleep_for >= 50 then + println("sleep for " .. (sleep_for / 1000.0)) + psleep(sleep_for / 1000.0) end end end diff --git a/rtu/startup.lua b/rtu/startup.lua index 8fea859..f3e1a8d 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -19,7 +19,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.4.4" +local RTU_VERSION = "alpha-v0.4.5" local print = util.print local println = util.println diff --git a/rtu/threads.lua b/rtu/threads.lua index c8abdd9..c2c1cfe 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -10,8 +10,8 @@ local println_ts = util.println_ts local psleep = util.psleep -local MAIN_CLOCK = 2 -- (2Hz, 40 ticks) -local COMMS_CLOCK = 0.25 -- (4Hz, 5 ticks) +local MAIN_CLOCK = 2 -- (2Hz, 40 ticks) +local COMMS_SLEEP = 150 -- (150ms, 3 ticks) -- main thread function thread__main(smem) @@ -134,8 +134,8 @@ function thread__comms(smem) rtu_comms.handle_packet(msg.message, units, rtu_state) end - -- quick yield if we are looping right back - if comms_queue.ready() then util.nop() end + -- quick yield + util.nop() end -- check for termination request @@ -144,13 +144,12 @@ function thread__comms(smem) break end - -- delay before next check - local sleep_for = COMMS_CLOCK - (util.time() - last_update) + -- delay before next check, only if >50ms since we did already yield + local sleep_for = COMMS_SLEEP - (util.time() - last_update) last_update = util.time() - if sleep_for > 0 then - psleep(sleep_for) - else - psleep(0.05) + if sleep_for >= 50 then + println("sleep for " .. (sleep_for / 1000.0)) + psleep(sleep_for / 1000.0) end end end From f067da31b4c821c17a78635301877d53284d5056 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 27 Apr 2022 18:43:07 -0400 Subject: [PATCH 110/587] #38 handle out of space when logging --- scada-common/log.lua | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/scada-common/log.lua b/scada-common/log.lua index fbe2d66..cf493eb 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -6,13 +6,42 @@ -- underscores are used since some of these names are used elsewhere (e.g. 'debug' is a lua table) local LOG_DEBUG = true +local LOG_PATH = "/log.txt" -local file_handle = fs.open("/log.txt", "a") +local file_handle = fs.open(LOG_PATH, "a") local _log = function (msg) local stamped = os.date("[%c] ") .. msg - file_handle.writeLine(stamped) - file_handle.flush() + + -- attempt to write log + local status, result = pcall(function () + file_handle.writeLine(stamped) + file_handle.flush() + end) + + -- if we don't have much space, we need to create a new log file + local delete_log = fs.getFreeSpace(LOG_PATH) < 100 + + if not status then + if result == "Out of space" then + delete_log = true + elseif result ~= nil then + print("unknown error writing to logfile: " .. result) + end + end + + if delete_log then + -- delete the old log file and open a new one + file_handle.close() + fs.delete(LOG_PATH) + file_handle = fs.open(LOG_PATH, "a") + + -- leave a message + local notif = os.date("[%c] ") .. "recycled log file" + file_handle.writeLine(notif) + file_handle.writeLine(stamped) + file_handle.flush() + end end function _debug(msg, trace) From 7f0f4234504fb94f2b6cbccf45b41ee7afc05d2c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 27 Apr 2022 18:49:54 -0400 Subject: [PATCH 111/587] PLC bugfixes/optimizations, removed some debug prints --- reactor-plc/plc.lua | 38 +++++++++++++++++++++++++++----------- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 6 ------ rtu/threads.lua | 1 - 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index da71998..361c174 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -230,6 +230,24 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- variable reactor status information, excluding heating rate local _reactor_status = function () + local coolant = self.reactor.getCoolant() + local coolant_name = "" + local coolant_amnt = 0 + + local hcoolant = self.reactor.getHeatedCoolant() + local hcoolant_name = "" + local hcoolant_amnt = 0 + + if coolant ~= nil then + coolant_name = coolant.name + coolant_amnt = coolant.amount + end + + if hcoolant ~= nil then + hcoolant_name = hcoolant.name + hcoolant_amnt = hcoolant.amount + end + return { self.reactor.getStatus(), self.reactor.getBurnRate(), @@ -240,18 +258,14 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) self.reactor.getEnvironmentalLoss(), self.reactor.getFuel(), - self.reactor.getFuelNeeded(), self.reactor.getFuelFilledPercentage(), self.reactor.getWaste(), - self.reactor.getWasteNeeded(), self.reactor.getWasteFilledPercentage(), - self.reactor.getCoolant()['name'], - self.reactor.getCoolant()['amount'], - self.reactor.getCoolantNeeded(), + coolant_name, + coolant_amnt, self.reactor.getCoolantFilledPercentage(), - self.reactor.getHeatedCoolant()['name'], - self.reactor.getHeatedCoolant()['amount'], - self.reactor.getHeatedCoolantNeeded(), + hcoolant_name, + hcoolant_amnt, self.reactor.getHeatedCoolantFilledPercentage() }, self.reactor.__p_is_faulted() end @@ -273,7 +287,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) changed = true end - if changed then + if changed and not faulted then self.status_cache = status end @@ -419,10 +433,12 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) if trip_time < 0 then log._warning("PLC KEEP_ALIVE trip time less than 0 (" .. trip_time .. ")") - elseif trip_time > 1000 then - log._warning("PLC KEEP_ALIVE trip time > 1s (" .. trip_time .. ")") + elseif trip_time > 1200 then + log._warning("PLC KEEP_ALIVE trip time > 1.2s (" .. trip_time .. ")") end + -- log._debug("RPLC RTT = ".. trip_time .. "ms") + _send_keep_alive_ack(timestamp) elseif packet.type == RPLC_TYPES.LINK_REQ then -- link request confirmation diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 2ceb289..4709a9c 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -12,7 +12,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.4.5" +local R_PLC_VERSION = "alpha-v0.4.6" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index ed1d93b..5cc80bc 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -287,11 +287,6 @@ function thread__iss(smem) break end - -- debug - -- print(util.time() - last_update) - -- println("ms") - -- last_update = util.time() - -- delay before next check, only if >50ms since we did already yield local sleep_for = ISS_SLEEP - (util.time() - last_update) last_update = util.time() @@ -353,7 +348,6 @@ function thread__comms(smem) local sleep_for = COMMS_SLEEP - (util.time() - last_update) last_update = util.time() if sleep_for >= 50 then - println("sleep for " .. (sleep_for / 1000.0)) psleep(sleep_for / 1000.0) end end diff --git a/rtu/threads.lua b/rtu/threads.lua index c2c1cfe..d736ee1 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -148,7 +148,6 @@ function thread__comms(smem) local sleep_for = COMMS_SLEEP - (util.time() - last_update) last_update = util.time() if sleep_for >= 50 then - println("sleep for " .. (sleep_for / 1000.0)) psleep(sleep_for / 1000.0) end end From f14d7150707b18cb65a24eedcd2471c2e938219c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 27 Apr 2022 18:52:06 -0400 Subject: [PATCH 112/587] #7 PLC session comms link, accept statuses, functional keep-alives --- supervisor/session/plc.lua | 49 ++++++++++++++++--------------- supervisor/session/svsessions.lua | 6 ++-- supervisor/startup.lua | 9 +++--- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 3a51666..48c4a53 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -13,7 +13,7 @@ PLC_S_COMMANDS = { } local PERIODICS = { - KEEP_ALIVE = 1.0 + KEEP_ALIVE = 2.0 } -- PLC supervisor session @@ -117,19 +117,15 @@ function new_session(id, for_reactor, in_queue, out_queue) self.sDB.mek_status.env_loss = mek_data[7] self.sDB.mek_status.fuel = mek_data[8] - self.sDB.mek_status.fuel_need = mek_data[9] - self.sDB.mek_status.fuel_fill = mek_data[10] - self.sDB.mek_status.waste = mek_data[11] - self.sDB.mek_status.waste_need = mek_data[12] - self.sDB.mek_status.waste_fill = mek_data[13] - self.sDB.mek_status.cool_type = mek_data[14] - self.sDB.mek_status.cool_amnt = mek_data[15] - self.sDB.mek_status.cool_need = mek_data[16] - self.sDB.mek_status.cool_fill = mek_data[17] - self.sDB.mek_status.hcool_type = mek_data[18] - self.sDB.mek_status.hcool_amnt = mek_data[19] - self.sDB.mek_status.hcool_need = mek_data[20] - self.sDB.mek_status.hcool_fill = mek_data[21] + self.sDB.mek_status.fuel_fill = mek_data[9] + self.sDB.mek_status.waste = mek_data[10] + self.sDB.mek_status.waste_fill = mek_data[11] + self.sDB.mek_status.cool_type = mek_data[12] + self.sDB.mek_status.cool_amnt = mek_data[13] + self.sDB.mek_status.cool_fill = mek_data[14] + self.sDB.mek_status.hcool_type = mek_data[15] + self.sDB.mek_status.hcool_amnt = mek_data[16] + self.sDB.mek_status.hcool_fill = mek_data[17] end local _copy_struct = function (mek_data) @@ -152,12 +148,9 @@ function new_session(id, for_reactor, in_queue, out_queue) end end - local _handle_packet = function (message) + local _handle_packet = function (rplc_pkt) local checks_ok = true - -- handle an incoming packet from the PLC - rplc_pkt = message.get() - -- check sequence number if self.r_seq_num == nil then self.r_seq_num = rplc_pkt.scada_frame.seq_num() @@ -189,12 +182,13 @@ function new_session(id, for_reactor, in_queue, out_queue) self.last_rtt = srv_now - srv_start if self.last_rtt < 0 then - log._warning(log_header .. "PLC KEEP_ALIVE round trip time less than 0 (" .. trip_time .. ")") - elseif trip_time > 1000 then - log._warning(log_header .. "PLC KEEP_ALIVE round trip time > 1s (" .. trip_time .. ")") + log._warning(log_header .. "PLC KEEP_ALIVE round trip time less than 0 (" .. self.last_rtt .. ")") + elseif self.last_rtt > 1200 then + log._warning(log_header .. "PLC KEEP_ALIVE round trip time > 1.2s (" .. self.last_rtt .. ")") end - log._debug(log_header .. "RPLC RTT = ".. trip_time .. "ms") + -- log._debug(log_header .. "RPLC RTT = ".. self.last_rtt .. "ms") + -- log._debug(log_header .. "RPLC TT = ".. (srv_now - plc_send) .. "ms") else log._debug(log_header .. "RPLC keep alive packet length mismatch") end @@ -330,16 +324,25 @@ function new_session(id, for_reactor, in_queue, out_queue) -- handle queue -- ------------------ - if self.in_q.ready() then + local handle_start = util.time() + + while self.in_q.ready() do -- get a new message to process local message = self.in_q.pop() if message.qtype == mqueue.TYPE.PACKET then + -- handle a packet _handle_packet(message.message) elseif message.qtype == mqueue.TYPE.COMMAND then -- handle instruction end + + -- max 100ms spent processing queue + if util.time() - handle_start > 100 then + log._warning(log_header .. "exceeded 100ms queue process limit") + break + end end ---------------------- diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 9c02ce0..76fdea3 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -34,19 +34,19 @@ end function find_session(stype, remote_port) if stype == SESSION_TYPE.RTU_SESSION then for i = 1, #self.rtu_sessions do - if self.rtu_sessions[i].r_host == remote_port then + if self.rtu_sessions[i].r_port == remote_port then return self.rtu_sessions[i] end end elseif stype == SESSION_TYPE.PLC_SESSION then for i = 1, #self.plc_sessions do - if self.plc_sessions[i].r_host == remote_port then + if self.plc_sessions[i].r_port == remote_port then return self.plc_sessions[i] end end elseif stype == SESSION_TYPE.COORD_SESSION then for i = 1, #self.coord_sessions do - if self.coord_sessions[i].r_host == remote_port then + if self.coord_sessions[i].r_port == remote_port then return self.coord_sessions[i] end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 138cfd8..71f0eae 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -18,7 +18,7 @@ os.loadAPI("session/svsessions.lua") os.loadAPI("supervisor.lua") -local SUPERVISOR_VERSION = "alpha-v0.1.6" +local SUPERVISOR_VERSION = "alpha-v0.1.7" local print = util.print local println = util.println @@ -43,8 +43,9 @@ end -- start comms, open all channels local superv_comms = supervisor.superv_comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN) --- base loop clock (4Hz, 5 ticks) -local loop_clock = os.startTimer(0.25) +-- base loop clock (6.67Hz, 3 ticks) +local MAIN_CLOCK = 0.15 +local loop_clock = os.startTimer(MAIN_CLOCK) -- event loop while true do @@ -87,7 +88,7 @@ while true do -- free any closed sessions svsessions.free_all_closed() - loop_clock = os.startTimer(0.25) + loop_clock = os.startTimer(MAIN_CLOCK) elseif event == "timer" then -- another timer event, check watchdogs svsessions.check_all_watchdogs(param1) From aff166e27d22ab0d80e773fe4f4b46db1b11e813 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 27 Apr 2022 19:06:01 -0400 Subject: [PATCH 113/587] added util adaptive_delay to replace repeated code --- reactor-plc/threads.lua | 16 ++++------------ rtu/threads.lua | 8 ++------ scada-common/util.lua | 10 ++++++++++ 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 5cc80bc..a9b95d7 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -287,12 +287,8 @@ function thread__iss(smem) break end - -- delay before next check, only if >50ms since we did already yield - local sleep_for = ISS_SLEEP - (util.time() - last_update) - last_update = util.time() - if sleep_for >= 50 then - psleep(sleep_for / 1000.0) - end + -- delay before next check + last_update = util.adaptive_delay(ISS_SLEEP, last_update) end end @@ -344,12 +340,8 @@ function thread__comms(smem) break end - -- delay before next check, only if >50ms since we did already yield - local sleep_for = COMMS_SLEEP - (util.time() - last_update) - last_update = util.time() - if sleep_for >= 50 then - psleep(sleep_for / 1000.0) - end + -- delay before next check + last_update = util.adaptive_delay(COMMS_SLEEP, last_update) end end diff --git a/rtu/threads.lua b/rtu/threads.lua index d736ee1..fece382 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -144,12 +144,8 @@ function thread__comms(smem) break end - -- delay before next check, only if >50ms since we did already yield - local sleep_for = COMMS_SLEEP - (util.time() - last_update) - last_update = util.time() - if sleep_for >= 50 then - psleep(sleep_for / 1000.0) - end + -- delay before next check + last_update = util.adaptive_delay(COMMS_SLEEP, last_update) end end diff --git a/scada-common/util.lua b/scada-common/util.lua index 69bc7f1..36761fd 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -50,6 +50,16 @@ function nop() psleep(0.05) end +-- attempt to maintain a minimum loop timing (duration of execution) +function adaptive_delay(target_timing, last_update) + local sleep_for = target_timing - (time() - last_update) + -- only if >50ms since worker loops already yield 0.05s + if sleep_for >= 50 then + psleep(sleep_for / 1000.0) + end + return time() +end + -- WATCHDOG -- -- ComputerCraft OS Timer based Watchdog From 67ec8fbd910bdacade32ffd7ceb625b4e1d03f09 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 28 Apr 2022 22:36:45 -0400 Subject: [PATCH 114/587] rx and tx threads for PLC comms to maintain quick comms and #36 only feed watchdog on valid sequence numbers --- reactor-plc/plc.lua | 11 ++++--- reactor-plc/startup.lua | 15 ++++++--- reactor-plc/threads.lua | 67 +++++++++++++++++++++++++++++++++-------- 3 files changed, 71 insertions(+), 22 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 361c174..127820f 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -411,7 +411,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) end -- handle an RPLC packet - local handle_packet = function (packet, plc_state) + local handle_packet = function (packet, plc_state, conn_watchdog) if packet ~= nil then -- check sequence number if self.r_seq_num == nil then @@ -423,6 +423,9 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) self.r_seq_num = packet.scada_frame.seq_num() end + -- feed the watchdog first so it doesn't uhh...eat our packets + conn_watchdog.feed() + -- handle packet if packet.scada_frame.protocol() == PROTOCOLS.RPLC then if self.linked then @@ -431,10 +434,8 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) local timestamp = packet.data[1] local trip_time = util.time() - timestamp - if trip_time < 0 then - log._warning("PLC KEEP_ALIVE trip time less than 0 (" .. trip_time .. ")") - elseif trip_time > 1200 then - log._warning("PLC KEEP_ALIVE trip time > 1.2s (" .. trip_time .. ")") + if trip_time > 500 then + log._warning("PLC KEEP_ALIVE trip time > 500ms (" .. trip_time .. ")") end -- log._debug("RPLC RTT = ".. trip_time .. "ms") diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 4709a9c..3bf458f 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -12,7 +12,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.4.6" +local R_PLC_VERSION = "alpha-v0.4.7" local print = util.print local println = util.println @@ -58,7 +58,8 @@ local __shared_memory = { -- message queues q = { mq_iss = mqueue.new(), - mq_comms = mqueue.new() + mq_comms_tx = mqueue.new(), + mq_comms_rx = mqueue.new() } } @@ -126,12 +127,16 @@ init() -- init threads local main_thread = threads.thread__main(__shared_memory, init) local iss_thread = threads.thread__iss(__shared_memory) -local comms_thread = threads.thread__comms(__shared_memory) --- run threads if __shared_memory.networked then - parallel.waitForAll(main_thread.exec, iss_thread.exec, comms_thread.exec) + -- init comms threads + local comms_thread_tx = threads.thread__comms_tx(__shared_memory) + local comms_thread_rx = threads.thread__comms_rx(__shared_memory) + + -- run threads + parallel.waitForAll(main_thread.exec, iss_thread.exec, comms_thread_tx.exec, comms_thread_rx.exec) else + -- run threads, excluding comms parallel.waitForAll(main_thread.exec, iss_thread.exec) end diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index a9b95d7..c946ed5 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -58,7 +58,7 @@ function thread__main(smem, init) -- send updated data if not plc_state.no_modem then if plc_comms.is_linked() then - smem.q.mq_comms.push_command(MQ__COMM_CMD.SEND_STATUS) + smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS) else if ticks_to_update == 0 then plc_comms.send_link_req() @@ -71,13 +71,10 @@ function thread__main(smem, init) end elseif event == "modem_message" and networked and not plc_state.no_modem then -- got a packet - -- feed the watchdog first so it doesn't uhh...eat our packets - conn_watchdog.feed() - - -- handle the packet local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5) if packet ~= nil then - smem.q.mq_comms.push_packet(packet) + -- pass the packet onto the comms message queue + smem.q.mq_comms_rx.push_packet(packet) end elseif event == "timer" and networked and param1 == conn_watchdog.get_timer() then -- haven't heard from server recently? shutdown reactor @@ -296,16 +293,16 @@ function thread__iss(smem) end -- communications handler thread -function thread__comms(smem) +function thread__comms_tx(smem) -- execute thread local exec = function () - log._debug("comms thread start") + log._debug("comms tx thread start") -- load in from shared memory local plc_state = smem.plc_state local plc_comms = smem.plc_sys.plc_comms - local comms_queue = smem.q.mq_comms + local comms_queue = smem.q.mq_comms_tx local last_update = util.time() @@ -326,8 +323,6 @@ function thread__comms(smem) -- received data elseif msg.qtype == mqueue.TYPE.PACKET then -- received a packet - -- handle the packet (plc_state passed to allow clearing SCRAM flag) - plc_comms.handle_packet(msg.message, plc_state) end -- quick yield @@ -336,7 +331,55 @@ function thread__comms(smem) -- check for termination request if plc_state.shutdown then - log._warning("comms thread exiting") + log._warning("comms tx thread exiting") + break + end + + -- delay before next check + last_update = util.adaptive_delay(COMMS_SLEEP, last_update) + end + end + + return { exec = exec } +end + +function thread__comms_rx(smem) + -- execute thread + local exec = function () + log._debug("comms rx thread start") + + -- load in from shared memory + local plc_state = smem.plc_state + local plc_comms = smem.plc_sys.plc_comms + local conn_watchdog = smem.plc_sys.conn_watchdog + + local comms_queue = smem.q.mq_comms_rx + + local last_update = util.time() + + -- thread loop + while true do + -- check for messages in the message queue + while comms_queue.ready() and not plc_state.shutdown do + local msg = comms_queue.pop() + + if msg.qtype == mqueue.TYPE.COMMAND then + -- received a command + elseif msg.qtype == mqueue.TYPE.DATA then + -- received data + elseif msg.qtype == mqueue.TYPE.PACKET then + -- received a packet + -- handle the packet (plc_state passed to allow clearing SCRAM flag) + plc_comms.handle_packet(msg.message, plc_state, conn_watchdog) + end + + -- quick yield + util.nop() + end + + -- check for termination request + if plc_state.shutdown then + log._warning("comms rx thread exiting") break end From d688f9a1c62b6464c9ca3e3eeb868d154175d1b6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 28 Apr 2022 22:41:08 -0400 Subject: [PATCH 115/587] supervisor code cleanup, adjusted prints --- supervisor/session/plc.lua | 6 ++---- supervisor/session/svsessions.lua | 1 - supervisor/startup.lua | 2 +- supervisor/supervisor.lua | 7 +++++++ 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 48c4a53..8c08b86 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -181,10 +181,8 @@ function new_session(id, for_reactor, in_queue, out_queue) local srv_now = util.time() self.last_rtt = srv_now - srv_start - if self.last_rtt < 0 then - log._warning(log_header .. "PLC KEEP_ALIVE round trip time less than 0 (" .. self.last_rtt .. ")") - elseif self.last_rtt > 1200 then - log._warning(log_header .. "PLC KEEP_ALIVE round trip time > 1.2s (" .. self.last_rtt .. ")") + if self.last_rtt > 500 then + log._warning(log_header .. "PLC KEEP_ALIVE round trip time > 500ms (" .. self.last_rtt .. ")") end -- log._debug(log_header .. "RPLC RTT = ".. self.last_rtt .. "ms") diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 76fdea3..72f12d4 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -70,7 +70,6 @@ function get_reactor_session(reactor) end function establish_plc_session(local_port, remote_port, for_reactor) - util.println(remote_port) if get_reactor_session(for_reactor) == nil then local plc_s = { open = true, diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 71f0eae..1e5396d 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -18,7 +18,7 @@ os.loadAPI("session/svsessions.lua") os.loadAPI("supervisor.lua") -local SUPERVISOR_VERSION = "alpha-v0.1.7" +local SUPERVISOR_VERSION = "alpha-v0.1.8" local print = util.print local println = util.println diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 571ab49..b8addc2 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -1,6 +1,7 @@ -- #REQUIRES comms.lua -- #REQUIRES modbus.lua -- #REQUIRES mqueue.lua +-- #REQUIRES util.lua -- #REQUIRES svsessions.lua local PROTOCOLS = comms.PROTOCOLS @@ -11,6 +12,11 @@ local RTU_ADVERT_TYPES = comms.RTU_ADVERT_TYPES local SESSION_TYPE = svsessions.SESSION_TYPE +local print = util.print +local println = util.println +local print_ts = util.print_ts +local println_ts = util.println_ts + -- supervisory controller communications function superv_comms(num_reactors, modem, dev_listen, coord_listen) local self = { @@ -137,6 +143,7 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) _send_plc_linking(r_port, { RPLC_LINKING.COLLISION }) else -- got an ID; assigned to a reactor successfully + println("connected to reactor " .. packet.data[1] .. " PLC (port " .. r_port .. ")") log._debug("PLC_LNK: allowed for device at " .. r_port) _send_plc_linking(r_port, { RPLC_LINKING.ALLOW }) end From 4d5cbcf475b1df4fb53258a7d7e6ac107098fb6c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 29 Apr 2022 09:07:29 -0400 Subject: [PATCH 116/587] PLC comms packet length checks --- reactor-plc/plc.lua | 142 ++++++++++++++++++++++------------------ reactor-plc/startup.lua | 2 +- 2 files changed, 80 insertions(+), 64 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 127820f..f76bfb6 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -431,40 +431,48 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) if self.linked then if packet.type == RPLC_TYPES.KEEP_ALIVE then -- keep alive request received, echo back - local timestamp = packet.data[1] - local trip_time = util.time() - timestamp + if packet.length == 1 then + local timestamp = packet.data[1] + local trip_time = util.time() - timestamp - if trip_time > 500 then - log._warning("PLC KEEP_ALIVE trip time > 500ms (" .. trip_time .. ")") + if trip_time > 500 then + log._warning("PLC KEEP_ALIVE trip time > 500ms (" .. trip_time .. ")") + end + + -- log._debug("RPLC RTT = ".. trip_time .. "ms") + + _send_keep_alive_ack(timestamp) + else + log._debug(log_header .. "RPLC keep alive packet length mismatch") end - - -- log._debug("RPLC RTT = ".. trip_time .. "ms") - - _send_keep_alive_ack(timestamp) elseif packet.type == RPLC_TYPES.LINK_REQ then -- link request confirmation - log._debug("received unsolicited link request response") + if packet.length == 1 then + log._debug("received unsolicited link request response") - local link_ack = packet.data[1] - - if link_ack == RPLC_LINKING.ALLOW then - _send_struct() - send_status(plc_state.degraded) - log._debug("re-sent initial status data") - elseif link_ack == RPLC_LINKING.DENY then - -- @todo: make sure this doesn't become a MITM security risk - println_ts("received unsolicited link denial, unlinking") - log._debug("unsolicited RPLC link request denied") - elseif link_ack == RPLC_LINKING.COLLISION then - -- @todo: make sure this doesn't become a MITM security risk - println_ts("received unsolicited link collision, unlinking") - log._warning("unsolicited RPLC link request collision") + local link_ack = packet.data[1] + + if link_ack == RPLC_LINKING.ALLOW then + _send_struct() + send_status(plc_state.degraded) + log._debug("re-sent initial status data") + elseif link_ack == RPLC_LINKING.DENY then + -- @todo: make sure this doesn't become a MITM security risk + println_ts("received unsolicited link denial, unlinking") + log._debug("unsolicited RPLC link request denied") + elseif link_ack == RPLC_LINKING.COLLISION then + -- @todo: make sure this doesn't become a MITM security risk + println_ts("received unsolicited link collision, unlinking") + log._warning("unsolicited RPLC link request collision") + else + println_ts("invalid unsolicited link response") + log._error("unsolicited unknown RPLC link request response") + end + + self.linked = link_ack == RPLC_LINKING.ALLOW else - println_ts("invalid unsolicited link response") - log._error("unsolicited unknown RPLC link request response") + log._debug(log_header .. "RPLC link req packet length mismatch") end - - self.linked = link_ack == RPLC_LINKING.ALLOW elseif packet.type == RPLC_TYPES.MEK_STRUCT then -- request for physical structure _send_struct() @@ -482,25 +490,29 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) _send_ack(packet.type, self.reactor.__p_is_ok()) elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then -- set the burn rate - local success = false - local burn_rate = packet.data[1] - local max_burn_rate = self.max_burn_rate + if packet.length == 1 then + local success = false + local burn_rate = packet.data[1] + local max_burn_rate = self.max_burn_rate - -- if no known max burn rate, check again - if max_burn_rate == nil then - max_burn_rate = self.reactor.getMaxBurnRate() - self.max_burn_rate = max_burn_rate - end - - -- if we know our max burn rate, update current burn rate if in range - if max_burn_rate ~= ppm.ACCESS_FAULT then - if burn_rate > 0 and burn_rate <= max_burn_rate then - self.reactor.setBurnRate(burn_rate) - success = self.reactor.__p_is_ok() + -- if no known max burn rate, check again + if max_burn_rate == nil then + max_burn_rate = self.reactor.getMaxBurnRate() + self.max_burn_rate = max_burn_rate end - end - _send_ack(packet.type, success) + -- if we know our max burn rate, update current burn rate if in range + if max_burn_rate ~= ppm.ACCESS_FAULT then + if burn_rate > 0 and burn_rate <= max_burn_rate then + self.reactor.setBurnRate(burn_rate) + success = self.reactor.__p_is_ok() + end + end + + _send_ack(packet.type, success) + else + log._debug(log_header .. "RPLC set burn rate packet length mismatch") + end elseif packet.type == RPLC_TYPES.ISS_CLEAR then -- clear the ISS status iss.reset() @@ -510,31 +522,35 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) end elseif packet.type == RPLC_TYPES.LINK_REQ then -- link request confirmation - local link_ack = packet.data[1] - - if link_ack == RPLC_LINKING.ALLOW then - println_ts("linked!") - log._debug("RPLC link request approved") + if packet.length == 1 then + local link_ack = packet.data[1] - -- reset remote sequence number - self.r_seq_num = nil + if link_ack == RPLC_LINKING.ALLOW then + println_ts("linked!") + log._debug("RPLC link request approved") - _send_struct() - send_status(plc_state.degraded) + -- reset remote sequence number + self.r_seq_num = nil - log._debug("sent initial status data") - elseif link_ack == RPLC_LINKING.DENY then - println_ts("link request denied, retrying...") - log._debug("RPLC link request denied") - elseif link_ack == RPLC_LINKING.COLLISION then - println_ts("reactor PLC ID collision (check config), retrying...") - log._warning("RPLC link request collision") + _send_struct() + send_status(plc_state.degraded) + + log._debug("sent initial status data") + elseif link_ack == RPLC_LINKING.DENY then + println_ts("link request denied, retrying...") + log._debug("RPLC link request denied") + elseif link_ack == RPLC_LINKING.COLLISION then + println_ts("reactor PLC ID collision (check config), retrying...") + log._warning("RPLC link request collision") + else + println_ts("invalid link response, bad channel? retrying...") + log._error("unknown RPLC link request response") + end + + self.linked = link_ack == RPLC_LINKING.ALLOW else - println_ts("invalid link response, bad channel? retrying...") - log._error("unknown RPLC link request response") + log._debug(log_header .. "RPLC link req packet length mismatch") end - - self.linked = link_ack == RPLC_LINKING.ALLOW else log._debug("discarding non-link packet before linked") end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 3bf458f..7c1de69 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -12,7 +12,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.4.7" +local R_PLC_VERSION = "alpha-v0.4.8" local print = util.print local println = util.println From 07e9101ac7b6456e88fb7c7385d5027174a59cb8 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 29 Apr 2022 09:25:08 -0400 Subject: [PATCH 117/587] PLC modem disconnect bugfix --- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 7c1de69..811f4f2 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -12,7 +12,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.4.8" +local R_PLC_VERSION = "alpha-v0.4.9" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index c946ed5..eb3c026 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -92,7 +92,7 @@ function thread__main(smem, init) -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_PERI_DC) ? elseif networked and device.type == "modem" then -- we only care if this is our wireless modem - if device.dev == modem then + if device.dev == plc_dev.modem then println_ts("wireless modem disconnected!") log._error("comms modem disconnected!") plc_state.no_modem = true From ef1fdc7f393a6393873de5988c89411c5ce6e562 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 29 Apr 2022 09:27:05 -0400 Subject: [PATCH 118/587] #34 RTU modem disconnect/reconnect handling, bugfix in comms thread --- rtu/rtu.lua | 11 +++++++ rtu/startup.lua | 2 +- rtu/threads.lua | 76 +++++++++++++++++++++++++++++++------------------ 3 files changed, 61 insertions(+), 28 deletions(-) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 54bf780..2d7306d 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -153,6 +153,16 @@ function rtu_comms(modem, local_port, server_port) -- PUBLIC FUNCTIONS -- + -- reconnect a newly connected modem + local reconnect_modem = function (modem) + self.modem = modem + + -- open modem + if not self.modem.isOpen(self.l_port) then + self.modem.open(self.l_port) + end + end + -- parse a MODBUS/SCADA packet local parse_packet = function(side, sender, reply_to, message, distance) local pkt = nil @@ -270,6 +280,7 @@ function rtu_comms(modem, local_port, server_port) end return { + reconnect_modem = reconnect_modem, parse_packet = parse_packet, handle_packet = handle_packet, send_advertisement = send_advertisement, diff --git a/rtu/startup.lua b/rtu/startup.lua index f3e1a8d..9219bff 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -19,7 +19,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.4.5" +local RTU_VERSION = "alpha-v0.4.6" local print = util.print local println = util.println diff --git a/rtu/threads.lua b/rtu/threads.lua index fece382..049ca89 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -36,38 +36,62 @@ function thread__main(smem) -- handle loss of a device local device = ppm.handle_unmount(param1) - for i = 1, #units do - -- find disconnected device - if units[i].device == device.dev then - -- we are going to let the PPM prevent crashes - -- return fault flags/codes to MODBUS queries - local unit = units[i] - println_ts("lost the " .. unit.type .. " on interface " .. unit.name) + if device.type == "modem" then + -- we only care if this is our wireless modem + if device.dev == rtu_dev.modem then + println_ts("wireless modem disconnected!") + log._warning("comms modem disconnected!") + else + log._warning("non-comms modem disconnected") + end + else + for i = 1, #units do + -- find disconnected device + if units[i].device == device.dev then + -- we are going to let the PPM prevent crashes + -- return fault flags/codes to MODBUS queries + local unit = units[i] + println_ts("lost the " .. unit.type .. " on interface " .. unit.name) + end end end elseif event == "peripheral" then - -- relink lost peripheral to correct unit entry + -- peripheral connect local type, device = ppm.mount(param1) - for i = 1, #units do - local unit = units[i] + if type == "modem" then + if device.isWireless() then + -- reconnected modem + rtu_dev.modem = device + rtu_comms.reconnect_modem(rtu_dev.modem) - -- find disconnected device to reconnect - if unit.name == param1 then - -- found, re-link - unit.device = device + println_ts("wireless modem reconnected.") + log._info("comms modem reconnected.") + else + log._info("wired modem reconnected.") + end + else + -- relink lost peripheral to correct unit entry + for i = 1, #units do + local unit = units[i] - if unit.type == "boiler" then - unit.rtu = boiler_rtu.new(device) - elseif unit.type == "turbine" then - unit.rtu = turbine_rtu.new(device) - elseif unit.type == "imatrix" then - unit.rtu = imatrix_rtu.new(device) + -- find disconnected device to reconnect + if unit.name == param1 then + -- found, re-link + unit.device = device + + if unit.type == "boiler" then + unit.rtu = boiler_rtu.new(device) + elseif unit.type == "turbine" then + unit.rtu = turbine_rtu.new(device) + elseif unit.type == "imatrix" then + unit.rtu = imatrix_rtu.new(device) + end + + unit.modbus_io = modbus.new(unit.rtu) + + println_ts("reconnected the " .. unit.type .. " on interface " .. unit.name) end - - unit.modbus_io = modbus.new(unit.rtu) - - println_ts("reconnected the " .. unit.type .. " on interface " .. unit.name) end end elseif event == "timer" and param1 == loop_clock then @@ -87,8 +111,6 @@ function thread__main(smem) if packet ~= nil then smem.q.mq_comms.push_packet(packet) end - - rtu_comms.handle_packet(packet, units, link_ref) end -- check for termination request @@ -121,7 +143,7 @@ function thread__comms(smem) -- thread loop while true do -- check for messages in the message queue - while comms_queue.ready() and not plc_state.shutdown do + while comms_queue.ready() and not rtu_state.shutdown do local msg = comms_queue.pop() if msg.qtype == mqueue.TYPE.COMMAND then From e833176c65359a74d301450927802339d99393af Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 29 Apr 2022 10:19:05 -0400 Subject: [PATCH 119/587] #40 RTU sequence number verification --- reactor-plc/plc.lua | 8 ++++-- reactor-plc/startup.lua | 2 +- rtu/rtu.lua | 27 ++++++++++++++++-- rtu/startup.lua | 7 ++++- rtu/threads.lua | 63 +++++++++++++++++++++++------------------ 5 files changed, 72 insertions(+), 35 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index f76bfb6..c1ffbf0 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -457,11 +457,9 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) send_status(plc_state.degraded) log._debug("re-sent initial status data") elseif link_ack == RPLC_LINKING.DENY then - -- @todo: make sure this doesn't become a MITM security risk println_ts("received unsolicited link denial, unlinking") log._debug("unsolicited RPLC link request denied") elseif link_ack == RPLC_LINKING.COLLISION then - -- @todo: make sure this doesn't become a MITM security risk println_ts("received unsolicited link collision, unlinking") log._warning("unsolicited RPLC link request collision") else @@ -562,7 +560,11 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) local is_scrammed = function () return self.scrammed end local is_linked = function () return self.linked end - local unlink = function () self.linked = false end + + local unlink = function () + self.linked = false + self.r_seq_num = nil + end return { reconnect_modem = reconnect_modem, diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 811f4f2..37ac6de 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -12,7 +12,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.4.9" +local R_PLC_VERSION = "alpha-v0.4.10" local print = util.print local println = util.println diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 2d7306d..27f18c6 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -120,6 +120,7 @@ end function rtu_comms(modem, local_port, server_port) local self = { seq_num = 0, + r_seq_num = nil, txn_id = 0, modem = modem, s_port = server_port, @@ -193,8 +194,23 @@ function rtu_comms(modem, local_port, server_port) end -- handle a MODBUS/SCADA packet - local handle_packet = function(packet, units, rtu_state) + local handle_packet = function(packet, units, rtu_state, conn_watchdog) if packet ~= nil then + local seq_ok = true + + -- check sequence number + if self.r_seq_num == nil then + self.r_seq_num = packet.scada_frame.seq_num() + elseif rtu_state.linked and self.r_seq_num >= packet.scada_frame.seq_num() then + log._warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + return + else + self.r_seq_num = packet.scada_frame.seq_num() + end + + -- feed watchdog on valid sequence number + conn_watchdog.feed() + local protocol = packet.scada_frame.protocol() if protocol == PROTOCOLS.MODBUS_TCP then @@ -220,6 +236,7 @@ function rtu_comms(modem, local_port, server_port) if packet.type == SCADA_MGMT_TYPES.REMOTE_LINKED then -- acknowledgement rtu_state.linked = true + self.r_seq_num = nil elseif packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then -- request for capabilities again send_advertisement(units) @@ -279,11 +296,17 @@ function rtu_comms(modem, local_port, server_port) _send(SCADA_MGMT_TYPES.RTU_HEARTBEAT, {}) end + local unlink = function (rtu_state) + rtu_state.linked = false + self.r_seq_num = nil + end + return { reconnect_modem = reconnect_modem, parse_packet = parse_packet, handle_packet = handle_packet, send_advertisement = send_advertisement, - send_heartbeat = send_heartbeat + send_heartbeat = send_heartbeat, + unlink = unlink } end diff --git a/rtu/startup.lua b/rtu/startup.lua index 9219bff..d4360b5 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -19,7 +19,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.4.6" +local RTU_VERSION = "alpha-v0.4.7" local print = util.print local println = util.println @@ -53,6 +53,7 @@ local __shared_memory = { -- system objects rtu_sys = { rtu_comms = nil, + conn_watchdog = nil, units = {} }, @@ -203,6 +204,10 @@ end local main_thread = threads.thread__main(__shared_memory) local comms_thread = threads.thread__comms(__shared_memory) +-- start connection watchdog +smem_sys.conn_watchdog = util.new_watchdog(5) +log._debug("init> conn watchdog started") + -- run threads parallel.waitForAll(main_thread.exec, comms_thread.exec) diff --git a/rtu/threads.lua b/rtu/threads.lua index 049ca89..b02aafb 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -23,16 +23,38 @@ function thread__main(smem) local loop_clock = os.startTimer(MAIN_CLOCK) -- load in from shared memory - local rtu_state = smem.rtu_state - local rtu_dev = smem.rtu_dev - local rtu_comms = smem.rtu_sys.rtu_comms - local units = smem.rtu_sys.units + local rtu_state = smem.rtu_state + local rtu_dev = smem.rtu_dev + local rtu_comms = smem.rtu_sys.rtu_comms + local conn_watchdog = smem.rtu_sys.conn_watchdog + local units = smem.rtu_sys.units -- event loop while true do local event, param1, param2, param3, param4, param5 = os.pullEventRaw() - if event == "peripheral_detach" then + if event == "timer" and param1 == loop_clock then + -- start next clock timer + loop_clock = os.startTimer(MAIN_CLOCK) + + -- period tick, if we are linked send heartbeat, if not send advertisement + if rtu_state.linked then + rtu_comms.send_heartbeat() + else + -- advertise units + rtu_comms.send_advertisement(units) + end + elseif event == "modem_message" then + -- got a packet + local packet = rtu_comms.parse_packet(param1, param2, param3, param4, param5) + if packet ~= nil then + -- pass the packet onto the comms message queue + smem.q.mq_comms.push_packet(packet) + end + elseif event == "timer" and param1 == conn_watchdog.get_timer() then + -- haven't heard from server recently? unlink + rtu_comms.unlink(rtu_state) + elseif event == "peripheral_detach" then -- handle loss of a device local device = ppm.handle_unmount(param1) @@ -94,23 +116,6 @@ function thread__main(smem) end end end - elseif event == "timer" and param1 == loop_clock then - -- start next clock timer - loop_clock = os.startTimer(MAIN_CLOCK) - - -- period tick, if we are linked send heartbeat, if not send advertisement - if rtu_state.linked then - rtu_comms.send_heartbeat() - else - -- advertise units - rtu_comms.send_advertisement(units) - end - elseif event == "modem_message" then - -- got a packet - local packet = rtu_comms.parse_packet(param1, param2, param3, param4, param5) - if packet ~= nil then - smem.q.mq_comms.push_packet(packet) - end end -- check for termination request @@ -132,13 +137,14 @@ function thread__comms(smem) log._debug("comms thread start") -- load in from shared memory - local rtu_state = smem.rtu_state - local rtu_comms = smem.rtu_sys.rtu_comms - local units = smem.rtu_sys.units + local rtu_state = smem.rtu_state + local rtu_comms = smem.rtu_sys.rtu_comms + local conn_watchdog = smem.rtu_sys.conn_watchdog + local units = smem.rtu_sys.units - local comms_queue = smem.q.mq_comms + local comms_queue = smem.q.mq_comms - local last_update = util.time() + local last_update = util.time() -- thread loop while true do @@ -153,7 +159,8 @@ function thread__comms(smem) elseif msg.qtype == mqueue.TYPE.PACKET then -- received a packet -- handle the packet (rtu_state passed to allow setting link flag) - rtu_comms.handle_packet(msg.message, units, rtu_state) + -- (conn_watchdog passed to allow feeding watchdog) + rtu_comms.handle_packet(msg.message, units, rtu_state, conn_watchdog) end -- quick yield From 84e7ad43bc81222d6062c09ac323e8b850e864be Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 29 Apr 2022 13:19:01 -0400 Subject: [PATCH 120/587] #39 RTU unit threads --- rtu/rtu.lua | 43 +++++++++++++++++++++++----------- rtu/startup.lua | 30 +++++++++++++++++++----- rtu/threads.lua | 49 ++++++++++++++++++++++++++++++++++++++ scada-common/modbus.lua | 52 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 19 deletions(-) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 27f18c6..a35770d 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -145,13 +145,6 @@ function rtu_comms(modem, local_port, server_port) self.seq_num = self.seq_num + 1 end - local _send_modbus = function (m_pkt) - local s_pkt = comms.scada_packet() - s_pkt.make(self.seq_num, PROTOCOLS.MODBUS_TCP, m_pkt.raw_sendable()) - self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) - self.seq_num = self.seq_num + 1 - end - -- PUBLIC FUNCTIONS -- -- reconnect a newly connected modem @@ -164,6 +157,14 @@ function rtu_comms(modem, local_port, server_port) end end + -- send a MODBUS TCP packet + local send_modbus = function (m_pkt) + local s_pkt = comms.scada_packet() + s_pkt.make(self.seq_num, PROTOCOLS.MODBUS_TCP, m_pkt.raw_sendable()) + self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) + self.seq_num = self.seq_num + 1 + end + -- parse a MODBUS/SCADA packet local parse_packet = function(side, sender, reply_to, message, distance) local pkt = nil @@ -216,13 +217,28 @@ function rtu_comms(modem, local_port, server_port) if protocol == PROTOCOLS.MODBUS_TCP then local reply = modbus.reply__neg_ack(packet) - -- MODBUS instruction + -- handle MODBUS instruction if packet.unit_id <= #units then local unit = units[packet.unit_id] - local return_code, reply = unit.modbus_io.handle_packet(packet) - - if not return_code then - log._warning("MODBUS operation failed") + if unit.name == "redstone_io" then + -- immediately execute redstone RTU requests + local return_code, reply = unit.modbus_io.handle_packet(packet) + if not return_code then + log._warning("requested MODBUS operation failed") + end + else + -- check validity then pass off to unit comms thread + local return_code, reply = unit.modbus_io.check_request(packet) + if return_code then + -- check if an operation is already in progress for this unit + if unit.modbus_busy then + reply = unit.modbus_io.reply__srv_device_busy(packet) + else + unit.pkt_queue.push(packet) + end + else + log._warning("cannot perform requested MODBUS operation") + end end else -- unit ID out of range? @@ -230,7 +246,7 @@ function rtu_comms(modem, local_port, server_port) log._error("MODBUS packet requesting non-existent unit") end - _send_modbus(reply) + send_modbus(reply) elseif protocol == PROTOCOLS.SCADA_MGMT then -- SCADA management packet if packet.type == SCADA_MGMT_TYPES.REMOTE_LINKED then @@ -302,6 +318,7 @@ function rtu_comms(modem, local_port, server_port) end return { + send_modbus = send_modbus, reconnect_modem = reconnect_modem, parse_packet = parse_packet, handle_packet = handle_packet, diff --git a/rtu/startup.lua b/rtu/startup.lua index d4360b5..aad3483 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -19,7 +19,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.4.7" +local RTU_VERSION = "alpha-v0.4.8" local print = util.print local println = util.println @@ -142,7 +142,10 @@ for reactor_idx = 1, #rtu_redstone do reactor = rtu_redstone[reactor_idx].for_reactor, device = capabilities, -- use device field for redstone channels rtu = rs_rtu, - modbus_io = modbus.new(rs_rtu) + modbus_io = modbus.new(rs_rtu), + modbus_busy = false, + pkt_queue = nil, + thread = nil }) log._debug("init> initialized RTU unit #" .. #units .. ": redstone_io (redstone) [1] for reactor " .. rtu_redstone[reactor_idx].for_reactor) @@ -180,15 +183,22 @@ for i = 1, #rtu_devices do end if rtu_iface ~= nil then - table.insert(units, { + local rtu_unit = { name = rtu_devices[i].name, type = rtu_type, index = rtu_devices[i].index, reactor = rtu_devices[i].for_reactor, device = device, rtu = rtu_iface, - modbus_io = modbus.new(rtu_iface) - }) + modbus_io = modbus.new(rtu_iface), + modbus_busy = false, + pkt_queue = mqueue.new(), + thread = nil + } + + rtu_unit.thread = threads.thread__unit_comms(__shared_memory, rtu_unit) + + table.insert(units, rtu_unit) log._debug("init> initialized RTU unit #" .. #units .. ": " .. rtu_devices[i].name .. " (" .. rtu_type .. ") [" .. rtu_devices[i].index .. "] for reactor " .. rtu_devices[i].for_reactor) @@ -208,8 +218,16 @@ local comms_thread = threads.thread__comms(__shared_memory) smem_sys.conn_watchdog = util.new_watchdog(5) log._debug("init> conn watchdog started") +-- assemble thread list +local _threads = { main_thread.exec, comms_thread.exec } +for i = 1, #units do + if units[i].thread ~= nil then + table.insert(_threads, units[i].thread.exec) + end +end + -- run threads -parallel.waitForAll(main_thread.exec, comms_thread.exec) +parallel.waitForAll(table.unpack(_threads)) println_ts("exited") log._info("exited") diff --git a/rtu/threads.lua b/rtu/threads.lua index b02aafb..8aef13f 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -180,3 +180,52 @@ function thread__comms(smem) return { exec = exec } end + +-- per-unit communications handler thread +function thread__unit_comms(smem, unit) + -- execute thread + local exec = function () + log._debug("rtu unit thread start -> " .. unit.name .. "(" .. unit.type .. ")") + + -- load in from shared memory + local rtu_state = smem.rtu_state + + local packet_queue = unit.pkt_queue + + local last_update = util.time() + + -- thread loop + while true do + -- check for messages in the message queue + while packet_queue.ready() and not rtu_state.shutdown do + local msg = packet_queue.pop() + + if msg.qtype == mqueue.TYPE.COMMAND then + -- received a command + elseif msg.qtype == mqueue.TYPE.DATA then + -- received data + elseif msg.qtype == mqueue.TYPE.PACKET then + -- received a packet + unit.modbus_busy = true + local return_code, reply = unit.modbus_io.handle_packet(packet) + rtu.send_modbus(reply) + unit.modbus_busy = false + end + + -- quick yield + util.nop() + end + + -- check for termination request + if rtu_state.shutdown then + log._warning("rtu unit thread exiting -> " .. unit.name .. "(" .. unit.type .. ")") + break + end + + -- delay before next check + last_update = util.adaptive_delay(COMMS_SLEEP, last_update) + end + end + + return { exec = exec } +end diff --git a/scada-common/modbus.lua b/scada-common/modbus.lua index 494fde8..a2a1cc3 100644 --- a/scada-common/modbus.lua +++ b/scada-common/modbus.lua @@ -203,6 +203,46 @@ function new(rtu_dev) return return_ok, response end + -- validate a request without actually executing it + local check_request = function (packet) + local return_code = true + local response = { MODBUS_EXCODE.ACKNOWLEDGE } + + if #packet.data == 2 then + -- handle by function code + if packet.func_code == MODBUS_FCODE.READ_COILS then + elseif packet.func_code == MODBUS_FCODE.READ_DISCRETE_INPUTS then + elseif packet.func_code == MODBUS_FCODE.READ_MUL_HOLD_REGS then + elseif packet.func_code == MODBUS_FCODE.READ_INPUT_REGISTERS then + elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_COIL then + elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_HOLD_REG then + elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_COILS then + elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_HOLD_REGS then + else + -- unknown function + return_code = false + response = { MODBUS_EXCODE.ILLEGAL_FUNCTION } + end + else + -- invalid length + return_code = false + response = { MODBUS_EXCODE.NEG_ACKNOWLEDGE } + end + + -- default is to echo back + local func_code = packet.func_code + if not return_code then + -- echo back with error flag + func_code = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) + end + + -- create reply + local reply = comms.modbus_packet() + reply.make(packet.txn_id, packet.unit_id, func_code, response) + + return return_code, reply + end + -- handle a MODBUS TCP packet and generate a reply local handle_packet = function (packet) local return_code = true @@ -258,6 +298,16 @@ function new(rtu_dev) return return_code, reply end + -- return a SERVER_DEVICE_BUSY error reply + local reply__srv_device_busy = function (packet) + -- reply back with error flag and exception code + local reply = comms.modbus_packet() + local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) + local data = { MODBUS_EXCODE.SERVER_DEVICE_BUSY } + reply.make(packet.txn_id, packet.unit_id, fcode, data) + return reply + end + -- return a NEG_ACKNOWLEDGE error reply local reply__neg_ack = function (packet) -- reply back with error flag and exception code @@ -279,7 +329,9 @@ function new(rtu_dev) end return { + check_request = check_request, handle_packet = handle_packet, + reply__srv_device_busy = reply__srv_device_busy, reply__neg_ack = reply__neg_ack, reply__gw_unavailable = reply__gw_unavailable } From 358735221936935b9930f510f8f3ae942f9c750b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 29 Apr 2022 13:20:56 -0400 Subject: [PATCH 121/587] log exit notices as info messages not warnings --- reactor-plc/threads.lua | 10 +++++----- rtu/threads.lua | 6 +++--- supervisor/startup.lua | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index eb3c026..93a243e 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -168,7 +168,7 @@ function thread__main(smem, init) if event == "terminate" or ppm.should_terminate() then -- iss handles reactor shutdown plc_state.shutdown = true - log._warning("terminate requested, main thread exiting") + log._info("terminate requested, main thread exiting") break end end @@ -267,7 +267,7 @@ function thread__iss(smem) -- check for termination request if plc_state.shutdown then -- safe exit - log._warning("iss thread shutdown initiated") + log._info("iss thread shutdown initiated") if plc_state.init_ok then plc_state.scram = true reactor.scram() @@ -280,7 +280,7 @@ function thread__iss(smem) log._error("iss thread failed to SCRAM reactor on exit") end end - log._warning("iss thread exiting") + log._info("iss thread exiting") break end @@ -331,7 +331,7 @@ function thread__comms_tx(smem) -- check for termination request if plc_state.shutdown then - log._warning("comms tx thread exiting") + log._info("comms tx thread exiting") break end @@ -379,7 +379,7 @@ function thread__comms_rx(smem) -- check for termination request if plc_state.shutdown then - log._warning("comms rx thread exiting") + log._info("comms rx thread exiting") break end diff --git a/rtu/threads.lua b/rtu/threads.lua index 8aef13f..c5af330 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -121,7 +121,7 @@ function thread__main(smem) -- check for termination request if event == "terminate" or ppm.should_terminate() then rtu_state.shutdown = true - log._warning("terminate requested, main thread exiting") + log._info("terminate requested, main thread exiting") break end end @@ -169,7 +169,7 @@ function thread__comms(smem) -- check for termination request if rtu_state.shutdown then - log._warning("comms thread exiting") + log._info("comms thread exiting") break end @@ -218,7 +218,7 @@ function thread__unit_comms(smem, unit) -- check for termination request if rtu_state.shutdown then - log._warning("rtu unit thread exiting -> " .. unit.name .. "(" .. unit.type .. ")") + log._info("rtu unit thread exiting -> " .. unit.name .. "(" .. unit.type .. ")") break end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 1e5396d..917280d 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -100,7 +100,7 @@ while true do -- check for termination request if event == "terminate" or ppm.should_terminate() then - log._warning("terminate requested, exiting...") + log._info("terminate requested, exiting...") break end end From c805b6e0c54aa71fcdb59d487f0d442d0e7e13cc Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 29 Apr 2022 13:32:37 -0400 Subject: [PATCH 122/587] log init function to set path and write mode --- coordinator/startup.lua | 19 ++++++++++++++----- reactor-plc/startup.lua | 4 +++- rtu/startup.lua | 4 +++- scada-common/log.lua | 29 +++++++++++++++++++++++------ supervisor/startup.lua | 4 +++- 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 20be7a3..a3f12d4 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -10,18 +10,27 @@ os.loadAPI("scada-common/comms.lua") os.loadAPI("coordinator/config.lua") os.loadAPI("coordinator/coordinator.lua") -local COORDINATOR_VERSION = "alpha-v0.1.0" +local COORDINATOR_VERSION = "alpha-v0.1.1" +local print = util.print +local println = util.println local print_ts = util.print_ts +local println_ts = util.println_ts +log.init("/log.txt", log.MODE.APPEND) + +log._info("========================================") +log._info("BOOTING coordinator.startup " .. COORDINATOR_VERSION) +log._info("========================================") +println(">> RTU " .. COORDINATOR_VERSION .. " <<") + +-- mount connected devices ppm.mount_all() -local modem = ppm.get_device("modem") - -print("| SCADA Coordinator - " .. COORDINATOR_VERSION .. " |") +local modem = ppm.get_wireless_modem() -- we need a modem if modem == nil then - print("Please connect a modem.") + println("please connect a wireless modem") return end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 37ac6de..2952191 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -12,13 +12,15 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.4.10" +local R_PLC_VERSION = "alpha-v0.4.11" local print = util.print local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts +log.init("/log.txt", log.MODE.APPEND) + log._info("========================================") log._info("BOOTING reactor-plc.startup " .. R_PLC_VERSION) log._info("========================================") diff --git a/rtu/startup.lua b/rtu/startup.lua index aad3483..6220ad1 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -19,13 +19,15 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.4.8" +local RTU_VERSION = "alpha-v0.4.9" local print = util.print local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts +log.init("/log.txt", log.MODE.APPEND) + log._info("========================================") log._info("BOOTING rtu.startup " .. RTU_VERSION) log._info("========================================") diff --git a/scada-common/log.lua b/scada-common/log.lua index cf493eb..1aafda3 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -5,10 +5,16 @@ -- we use extra short abbreviations since computer craft screens are very small -- underscores are used since some of these names are used elsewhere (e.g. 'debug' is a lua table) -local LOG_DEBUG = true -local LOG_PATH = "/log.txt" +MODE = { + APPEND = 0, + NEW = 1 +} -local file_handle = fs.open(LOG_PATH, "a") +local LOG_DEBUG = true + +local log_path = "/log.txt" +local mode = MODE.APPEND +local file_handle = nil local _log = function (msg) local stamped = os.date("[%c] ") .. msg @@ -20,7 +26,7 @@ local _log = function (msg) end) -- if we don't have much space, we need to create a new log file - local delete_log = fs.getFreeSpace(LOG_PATH) < 100 + local delete_log = fs.getFreeSpace(log_path) < 100 if not status then if result == "Out of space" then @@ -33,8 +39,8 @@ local _log = function (msg) if delete_log then -- delete the old log file and open a new one file_handle.close() - fs.delete(LOG_PATH) - file_handle = fs.open(LOG_PATH, "a") + fs.delete(log_path) + init(log_path, mode) -- leave a message local notif = os.date("[%c] ") .. "recycled log file" @@ -44,6 +50,17 @@ local _log = function (msg) end end +function init(path, write_mode) + log_path = path + mode = write_mode + + if mode == MODE.APPEND then + file_handle = fs.open(path, "a") + else + file_handle = fs.open(path, "w+") + end +end + function _debug(msg, trace) if LOG_DEBUG then local dbg_info = "" diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 917280d..9cf704a 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -18,13 +18,15 @@ os.loadAPI("session/svsessions.lua") os.loadAPI("supervisor.lua") -local SUPERVISOR_VERSION = "alpha-v0.1.8" +local SUPERVISOR_VERSION = "alpha-v0.1.9" local print = util.print local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts +log.init("/log.txt", log.MODE.APPEND) + log._info("========================================") log._info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION) log._info("========================================") From e1135eac019bfb4a8aee33b19724da169e46fff4 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 29 Apr 2022 13:36:00 -0400 Subject: [PATCH 123/587] log init parameters in config files --- reactor-plc/config.lua | 6 ++++++ reactor-plc/startup.lua | 2 +- rtu/config.lua | 6 ++++++ rtu/startup.lua | 2 +- supervisor/config.lua | 6 ++++++ supervisor/startup.lua | 2 +- 6 files changed, 21 insertions(+), 3 deletions(-) diff --git a/reactor-plc/config.lua b/reactor-plc/config.lua index dceeb21..43086d5 100644 --- a/reactor-plc/config.lua +++ b/reactor-plc/config.lua @@ -6,3 +6,9 @@ REACTOR_ID = 1 SERVER_PORT = 16000 -- port to listen to incoming packets FROM server LISTEN_PORT = 14001 +-- log path +LOG_PATH = "/log.txt" +-- log mode +-- 0 = APPEND (adds to existing file on start) +-- 1 = NEW (replaces existing file on start) +LOG_MODE = 0 diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 2952191..69038d3 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -19,7 +19,7 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts -log.init("/log.txt", log.MODE.APPEND) +log.init(config.LOG_PATH, config.LOG_MODE) log._info("========================================") log._info("BOOTING reactor-plc.startup " .. R_PLC_VERSION) diff --git a/rtu/config.lua b/rtu/config.lua index 71804a4..6ba5653 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -4,6 +4,12 @@ SERVER_PORT = 16000 -- port to listen to incoming packets FROM server LISTEN_PORT = 15001 +-- log path +LOG_PATH = "/log.txt" +-- log mode +-- 0 = APPEND (adds to existing file on start) +-- 1 = NEW (replaces existing file on start) +LOG_MODE = 0 -- RTU peripheral devices (named: side/network device name) RTU_DEVICES = { { diff --git a/rtu/startup.lua b/rtu/startup.lua index 6220ad1..59c4354 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -26,7 +26,7 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts -log.init("/log.txt", log.MODE.APPEND) +log.init(config.LOG_PATH, config.LOG_MODE) log._info("========================================") log._info("BOOTING rtu.startup " .. RTU_VERSION) diff --git a/supervisor/config.lua b/supervisor/config.lua index fde20b3..b8ba7fa 100644 --- a/supervisor/config.lua +++ b/supervisor/config.lua @@ -4,3 +4,9 @@ SCADA_DEV_LISTEN = 16000 SCADA_SV_LISTEN = 16100 -- expected number of reactors NUM_REACTORS = 4 +-- log path +LOG_PATH = "/log.txt" +-- log mode +-- 0 = APPEND (adds to existing file on start) +-- 1 = NEW (replaces existing file on start) +LOG_MODE = 0 diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 9cf704a..946ef0c 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -25,7 +25,7 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts -log.init("/log.txt", log.MODE.APPEND) +log.init(config.LOG_PATH, config.LOG_MODE) log._info("========================================") log._info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION) From 10aa34a8e801dc0949725ee0eae8f24e56c9cbc9 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 29 Apr 2022 22:27:54 -0400 Subject: [PATCH 124/587] #17 PLC ramp burn rate to setpoint --- reactor-plc/plc.lua | 20 ++++----- reactor-plc/startup.lua | 11 ++++- reactor-plc/threads.lua | 99 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 111 insertions(+), 19 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index c1ffbf0..a8c7c5e 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -357,12 +357,12 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) end local sys_status = { - util.time(), - (not self.scrammed), - iss.is_tripped(), - degraded, - self.reactor.getHeatingRate(), - mek_data + util.time(), -- timestamp + (not self.scrammed), -- enabled + iss.is_tripped(), -- overridden + degraded, -- degraded + self.reactor.getHeatingRate(), -- heating rate + mek_data -- mekanism status data } _send(RPLC_TYPES.STATUS, sys_status) @@ -411,7 +411,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) end -- handle an RPLC packet - local handle_packet = function (packet, plc_state, conn_watchdog) + local handle_packet = function (packet, plc_state, setpoints, conn_watchdog) if packet ~= nil then -- check sequence number if self.r_seq_num == nil then @@ -499,11 +499,11 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) self.max_burn_rate = max_burn_rate end - -- if we know our max burn rate, update current burn rate if in range + -- if we know our max burn rate, update current burn rate setpoint if in range if max_burn_rate ~= ppm.ACCESS_FAULT then if burn_rate > 0 and burn_rate <= max_burn_rate then - self.reactor.setBurnRate(burn_rate) - success = self.reactor.__p_is_ok() + setpoints.burn_rate = burn_rate + success = true end end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 69038d3..2328a00 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -12,7 +12,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.4.11" +local R_PLC_VERSION = "alpha-v0.4.12" local print = util.print local println = util.println @@ -43,6 +43,10 @@ local __shared_memory = { no_reactor = false, no_modem = false }, + + setpoints = { + burn_rate = 0.0 + }, -- core PLC devices plc_dev = { @@ -135,8 +139,11 @@ if __shared_memory.networked then 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.exec, iss_thread.exec, comms_thread_tx.exec, comms_thread_rx.exec) + parallel.waitForAll(main_thread.exec, iss_thread.exec, comms_thread_tx.exec, comms_thread_rx.exec, sp_ctrl_thread.exec) else -- run threads, excluding comms parallel.waitForAll(main_thread.exec, iss_thread.exec) diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 93a243e..6a55e7f 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -10,9 +10,12 @@ local println_ts = util.println_ts local psleep = util.psleep -local MAIN_CLOCK = 1 -- (1Hz, 20 ticks) -local ISS_SLEEP = 500 -- (500ms, 10 ticks) -local COMMS_SLEEP = 150 -- (150ms, 3 ticks) +local MAIN_CLOCK = 1 -- (1Hz, 20 ticks) +local ISS_SLEEP = 500 -- (500ms, 10 ticks) +local COMMS_SLEEP = 150 -- (150ms, 3 ticks) +local SP_CTRL_SLEEP = 250 -- (250ms, 5 ticks) + +local BURN_RATE_RAMP_mB_s = 5.0 local MQ__ISS_CMD = { SCRAM = 1, @@ -196,7 +199,7 @@ function thread__iss(smem) -- thread loop while true do - local reactor = smem.plc_dev.reactor + local reactor = plc_dev.reactor -- ISS checks if plc_state.init_ok then @@ -292,7 +295,7 @@ function thread__iss(smem) return { exec = exec } end --- communications handler thread +-- communications sender thread function thread__comms_tx(smem) -- execute thread local exec = function () @@ -343,6 +346,7 @@ function thread__comms_tx(smem) return { exec = exec } end +-- communications handler thread function thread__comms_rx(smem) -- execute thread local exec = function () @@ -350,6 +354,7 @@ function thread__comms_rx(smem) -- load in from shared memory local plc_state = smem.plc_state + local setpoints = smem.setpoints local plc_comms = smem.plc_sys.plc_comms local conn_watchdog = smem.plc_sys.conn_watchdog @@ -369,8 +374,10 @@ function thread__comms_rx(smem) -- received data elseif msg.qtype == mqueue.TYPE.PACKET then -- received a packet - -- handle the packet (plc_state passed to allow clearing SCRAM flag) - plc_comms.handle_packet(msg.message, plc_state, conn_watchdog) + -- handle the packet (setpoints passed to update burn rate setpoint) + -- (plc_state passed to allow clearing SCRAM flag and check if degraded) + -- (conn_watchdog passed to allow feeding the watchdog) + plc_comms.handle_packet(msg.message, setpoints, plc_state, conn_watchdog) end -- quick yield @@ -390,3 +397,81 @@ function thread__comms_rx(smem) return { exec = exec } end + +-- apply setpoints +function thread__setpoint_control(smem) + -- execute thread + local exec = function () + log._debug("comms rx thread start") + + -- load in from shared memory + local plc_state = smem.plc_state + local setpoints = smem.setpoints + local plc_dev = smem.plc_dev + + local last_update = util.time() + local running = false + + local last_sp_burn = 0 + + -- thread loop + while true do + local reactor = plc_dev.reactor + + -- check if we should start ramping + if setpoints.burn_rate ~= last_sp_burn then + last_sp_burn = setpoints.burn_rate + running = true + end + + -- only check I/O if active to save on processing time + if running then + -- do not use the actual elapsed time, it could spike + -- we do not want to have big jumps as that is what we are trying to avoid in the first place + local min_elapsed_s = SETPOINT_CTRL_SLEEP / 1000.0 + + -- clear so we can later evaluate if we should keep running + running = false + + -- adjust burn rate (setpoints.burn_rate) + if not plc_state.scram then + local current_burn_rate = reactor.getBurnRate() + if (current_burn_rate ~= ppm.ACCESS_FAULT) and (current_burn_rate ~= setpoints.burn_rate) then + -- calculate new burn rate + local new_burn_rate = current_burn_rate + + if setpoints.burn_rate > current_burn_rate then + -- need to ramp up + local new_burn_rate = current_burn_rate + (BURN_RATE_RAMP_mB_s * min_elapsed_s) + if new_burn_rate > setpoints.burn_rate then + new_burn_rate = setpoints.burn_rate + end + else + -- need to ramp down + local new_burn_rate = current_burn_rate - (BURN_RATE_RAMP_mB_s * min_elapsed_s) + if new_burn_rate < setpoints.burn_rate then + new_burn_rate = setpoints.burn_rate + end + end + + -- set the burn rate + reactor.setBurnRate(new_burn_rate) + + running = running or (new_burn_rate ~= setpoints.burn_rate) + end + end + end + + -- check for termination request + if plc_state.shutdown then + log._info("setpoint control thread exiting") + break + end + + -- delay before next check + last_update = util.adaptive_delay(SETPOINT_CTRL_SLEEP, last_update) + end + end + + return { exec = exec } +end From aeda38fa0133f96817ec4d07e40126aca2850147 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 30 Apr 2022 03:03:34 -0400 Subject: [PATCH 125/587] #17 set burn rate right away if within range, reset last setpoint on SCRAM --- reactor-plc/threads.lua | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 6a55e7f..b1b331a 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -420,8 +420,20 @@ function thread__setpoint_control(smem) -- check if we should start ramping if setpoints.burn_rate ~= last_sp_burn then - last_sp_burn = setpoints.burn_rate - running = true + if not plc_state.scram then + if math.abs(setpoints.burn_rate - last_sp_burn) <= 5 then + -- update without ramp if <= 5 mB/t change + log._debug("setting burn rate directly to " .. setpoints.burn_rate .. "mB/t") + reactor.setBurnRate(setpoints.burn_rate) + else + log._debug("starting burn rate ramp from " .. last_sp_burn .. "mB/t to " .. setpoints.burn_rate .. "mB/t") + running = true + end + + last_sp_burn = setpoints.burn_rate + else + last_sp_burn = 0 + end end -- only check I/O if active to save on processing time @@ -459,6 +471,8 @@ function thread__setpoint_control(smem) running = running or (new_burn_rate ~= setpoints.burn_rate) end + else + last_sp_burn = 0 end end From 3fe47f99a991d100f8340b0980fe85375c1db549 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 30 Apr 2022 13:44:28 -0400 Subject: [PATCH 126/587] PLC bugfix --- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 2328a00..3905f0e 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -12,7 +12,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.4.12" +local R_PLC_VERSION = "alpha-v0.4.13" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index b1b331a..36d3179 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -483,7 +483,7 @@ function thread__setpoint_control(smem) end -- delay before next check - last_update = util.adaptive_delay(SETPOINT_CTRL_SLEEP, last_update) + last_update = util.adaptive_delay(SP_CTRL_SLEEP, last_update) end end From 479194b589c7ddf89c7b478b7ff75d233fdc26e3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 1 May 2022 13:26:02 -0400 Subject: [PATCH 127/587] ISS alarm status packet adjustments --- reactor-plc/plc.lua | 6 ++++-- reactor-plc/startup.lua | 4 ++-- supervisor/session/plc.lua | 13 ++++++++++--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index a8c7c5e..52bd16c 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -147,7 +147,7 @@ function iss_init(reactor) elseif self.cache[5] then log._warning("ISS: no fuel!") status = "no_fuel" - elseif self.timed_out then + elseif self.cache[7] then log._warning("ISS: supervisor connection timeout!") status = "timeout" else @@ -368,14 +368,16 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) _send(RPLC_TYPES.STATUS, sys_status) end + -- send safety system status local send_iss_status = function () _send(RPLC_TYPES.ISS_STATUS, iss.status()) end + -- send safety system alarm local send_iss_alarm = function (cause) local iss_alarm = { cause, - iss.status() + table.unpack(iss.status()) } _send(RPLC_TYPES.ISS_ALARM, iss_alarm) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 3905f0e..17e4a2e 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -131,8 +131,8 @@ end init() -- init threads -local main_thread = threads.thread__main(__shared_memory, init) -local iss_thread = threads.thread__iss(__shared_memory) +local main_thread = threads.thread__main(__shared_memory, init) +local iss_thread = threads.thread__iss(__shared_memory) if __shared_memory.networked then -- init comms threads diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 8c08b86..bf95dbb 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -49,6 +49,8 @@ function new_session(id, for_reactor, in_queue, out_queue) control_state = false, overridden = false, degraded = false, + iss_tripped = false, + iss_trip_cause = "ok", iss_status = { dmg_crit = false, ex_hcool = false, @@ -262,13 +264,15 @@ function new_session(id, for_reactor, in_queue, out_queue) elseif rplc_pkt.type == RPLC_TYPES.ISS_ALARM then -- ISS alarm self.sDB.overridden = true - if rplc_pkt.length == 7 then - local status = pcall(_copy_iss_status, rplc_pkt.data) + if rplc_pkt.length == 8 then + self.sDB.iss_tripped = true + self.sDB.iss_trip_cause = rplc_pkt.data[1] + local status = pcall(_copy_iss_status, { table.unpack(rplc_pkt.data, 2, #rplc_pkt.length) }) if status then -- copied in ISS status data OK else -- error copying ISS status data - log._error(log_header .. "failed to parse ISS status packet data") + log._error(log_header .. "failed to parse ISS alarm status data") end else log._debug(log_header .. "RPLC ISS alarm packet length mismatch") @@ -277,6 +281,9 @@ function new_session(id, for_reactor, in_queue, out_queue) -- ISS clear acknowledgement if _get_ack(rplc_pkt) == false then log._warning(log_header .. "ISS clear failed") + else + self.sDB.iss_tripped = false + self.sDB.iss_trip_cause = "ok" end else log._debug(log_header .. "handler received unsupported RPLC packet type " .. rplc_pkt.type) From b76871aa0702ce098654e899dbb451983529b134 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 1 May 2022 15:34:44 -0400 Subject: [PATCH 128/587] fixed incorrect program type in startup message --- coordinator/startup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index a3f12d4..fe0bf48 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -22,7 +22,7 @@ log.init("/log.txt", log.MODE.APPEND) log._info("========================================") log._info("BOOTING coordinator.startup " .. COORDINATOR_VERSION) log._info("========================================") -println(">> RTU " .. COORDINATOR_VERSION .. " <<") +println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<") -- mount connected devices ppm.mount_all() From cd46c69a6639dc107e9f4628dbea52af9d6c028b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 1 May 2022 15:35:07 -0400 Subject: [PATCH 129/587] defined push_data to be implemented --- scada-common/mqueue.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scada-common/mqueue.lua b/scada-common/mqueue.lua index f14951e..dc3e47f 100644 --- a/scada-common/mqueue.lua +++ b/scada-common/mqueue.lua @@ -31,8 +31,8 @@ function new() _push(TYPE.COMMAND, message) end - local push_data = function (message) - _push(TYPE.DATA, message) + local push_data = function (key, value) + _push(TYPE.DATA, { key = key, val = value }) end local push_packet = function (message) From 7ff0e257113ce846a6b9870ea49705ce8aab5e63 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 1 May 2022 17:04:38 -0400 Subject: [PATCH 130/587] #7 sending commands to PLCs, retrying failed sends until confirmed --- supervisor/session/plc.lua | 148 ++++++++++++++++++++++++++---- supervisor/session/svsessions.lua | 1 - supervisor/startup.lua | 2 +- 3 files changed, 130 insertions(+), 21 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index bf95dbb..fa805c4 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -6,10 +6,15 @@ local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES -PLC_S_COMMANDS = { +-- retry time constants in ms +local INITIAL_WAIT = 1500 +local RETRY_PERIOD = 1000 + +PLC_S_CMDS = { SCRAM = 0, ENABLE = 1, - ISS_CLEAR = 2 + BURN_RATE = 2, + ISS_CLEAR = 3 } local PERIODICS = { @@ -26,6 +31,7 @@ function new_session(id, for_reactor, in_queue, out_queue) in_q = in_queue, out_q = out_queue, commanded_state = false, + commanded_burn_rate = 0.0, -- connection properties seq_num = 0, r_seq_num = nil, @@ -33,15 +39,25 @@ function new_session(id, for_reactor, in_queue, out_queue) received_struct = false, plc_conn_watchdog = util.new_watchdog(3), last_rtt = 0, - -- when to next retry one of these requests + -- periodic messages periodics = { last_update = 0, keep_alive = 0 }, + -- when to next retry one of these requests retry_times = { - struct_req = 0, + struct_req = 500, scram_req = 0, - enable_req = 0 + enable_req = 0, + burn_rate_req = 0, + iss_clear_req = 0 + }, + -- command acknowledgements + acks = { + scram = true, + enable = true, + burn_rate = true, + iss_clear = true }, -- session database sDB = { @@ -110,6 +126,7 @@ function new_session(id, for_reactor, in_queue, out_queue) end local _copy_status = function (mek_data) + -- copy status information self.sDB.mek_status.status = mek_data[1] self.sDB.mek_status.burn_rate = mek_data[2] self.sDB.mek_status.act_burn_rate = mek_data[3] @@ -118,6 +135,7 @@ function new_session(id, for_reactor, in_queue, out_queue) self.sDB.mek_status.boil_eff = mek_data[6] self.sDB.mek_status.env_loss = mek_data[7] + -- copy container information self.sDB.mek_status.fuel = mek_data[8] self.sDB.mek_status.fuel_fill = mek_data[9] self.sDB.mek_status.waste = mek_data[10] @@ -128,6 +146,14 @@ function new_session(id, for_reactor, in_queue, out_queue) self.sDB.mek_status.hcool_type = mek_data[15] self.sDB.mek_status.hcool_amnt = mek_data[16] self.sDB.mek_status.hcool_fill = mek_data[17] + + -- update computable fields if we have our structure + if self.received_struct then + self.sDB.mek_status.fuel_need = self.sDB.mek_struct.fuel_cap - self.sDB.mek_status.fuel_fill + self.sDB.mek_status.waste_need = self.sDB.mek_struct.waste_cap - self.sDB.mek_status.waste_fill + self.sDB.mek_status.cool_need = self.sDB.mek_struct.cool_cap - self.sDB.mek_status.cool_fill + self.sDB.mek_status.hcool_need = self.sDB.mek_struct.hcool_cap - self.sDB.mek_status.hcool_fill + end end local _copy_struct = function (mek_data) @@ -231,22 +257,26 @@ function new_session(id, for_reactor, in_queue, out_queue) -- SCRAM acknowledgement local ack = _get_ack(rplc_pkt) if ack then + self.acks.scram = true self.sDB.control_state = false elseif ack == false then - log._warning(log_header .. "SCRAM failed!") + log._debug(log_header .. "SCRAM failed!") end elseif rplc_pkt.type == RPLC_TYPES.MEK_ENABLE then -- enable acknowledgement local ack = _get_ack(rplc_pkt) if ack then + self.acks.enable = true self.sDB.control_state = true elseif ack == false then - log._warning(log_header .. "enable failed!") + log._debug(log_header .. "enable failed!") end elseif rplc_pkt.type == RPLC_TYPES.MEK_BURN_RATE then -- burn rate acknowledgement - if _get_ack(rplc_pkt) == false then - log._warning(log_header .. "burn rate update failed!") + if _get_ack(rplc_pkt) then + self.acks.burn_rate = true + else + log._debug(log_header .. "burn rate update failed!") end elseif rplc_pkt.type == RPLC_TYPES.ISS_STATUS then -- ISS status packet received, copy data @@ -279,11 +309,12 @@ function new_session(id, for_reactor, in_queue, out_queue) end elseif rplc_pkt.type == RPLC_TYPES.ISS_CLEAR then -- ISS clear acknowledgement - if _get_ack(rplc_pkt) == false then - log._warning(log_header .. "ISS clear failed") - else + if _get_ack(rplc_pkt) then + self.acks.iss_tripped = true self.sDB.iss_tripped = false self.sDB.iss_trip_cause = "ok" + else + log._debug(log_header .. "ISS clear failed") end else log._debug(log_header .. "handler received unsupported RPLC packet type " .. rplc_pkt.type) @@ -318,7 +349,6 @@ function new_session(id, for_reactor, in_queue, out_queue) if self.received_struct then return self.sDB.mek_struct else - -- @todo: need a system in place to re-request this periodically return nil end end @@ -340,7 +370,33 @@ function new_session(id, for_reactor, in_queue, out_queue) _handle_packet(message.message) elseif message.qtype == mqueue.TYPE.COMMAND then -- handle instruction - + local cmd = message.message + if cmd == PLC_S_CMDS.SCRAM then + -- SCRAM reactor + self.acks.scram = false + self.retry_times.scram_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.MEK_SCRAM, {}) + elseif cmd == PLC_S_CMDS.ENABLE then + -- enable reactor + self.acks.enable = false + self.retry_times.enable_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.MEK_ENABLE, {}) + elseif cmd == PLC_S_CMDS.ISS_CLEAR then + -- clear ISS + self.acks.iss_clear = false + self.retry_times.iss_clear_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.ISS_CLEAR, {}) + end + elseif message.qtype == mqueue.TYPE.DATA then + -- instruction with body + local cmd = message.message + if cmd.key == PLC_S_CMDS.BURN_RATE then + -- update burn rate + self.commanded_burn_rate = cmd.val + self.acks.burn_rate = false + self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate }) + end end -- max 100ms spent processing queue @@ -354,16 +410,70 @@ function new_session(id, for_reactor, in_queue, out_queue) -- update periodics -- ---------------------- - local elapsed = os.clock() - self.periodics.last_update + local elapsed = util.time() - self.periodics.last_update - self.periodics.keep_alive = self.periodics.keep_alive + elapsed + local periodics = self.periodics - if self.periodics.keep_alive >= PERIODICS.KEEP_ALIVE then + -- keep alive + + periodics.keep_alive = periodics.keep_alive + elapsed + if periodics.keep_alive >= PERIODICS.KEEP_ALIVE then _send(RPLC_TYPES.KEEP_ALIVE, { util.time() }) - self.periodics.keep_alive = 0 + periodics.keep_alive = 0 end - self.periodics.last_update = os.clock() + self.periodics.last_update = util.time() + + --------------------- + -- attempt retries -- + --------------------- + + local rtimes = self.rtimes + + -- struct request retry + + if not self.received_struct then + if rtimes.struct_req - util.time() <= 0 then + _send(RPLC_TYPES.LINK_REQ, {}) + rtimes.struct_req = util.time() + RETRY_PERIOD + end + end + + -- SCRAM request retry + + if not self.acks.scram then + if rtimes.scram_req - util.time() <= 0 then + _send(RPLC_TYPES.MEK_SCRAM, {}) + rtimes.scram_req = util.time() + RETRY_PERIOD + end + end + + -- enable request retry + + if not self.acks.enable then + if rtimes.enable_req - util.time() <= 0 then + _send(RPLC_TYPES.MEK_ENABLE, {}) + rtimes.enable_req = util.time() + RETRY_PERIOD + end + end + + -- burn rate request retry + + if not self.acks.burn_rate then + if rtimes.burn_rate_req - util.time() <= 0 then + _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate }) + rtimes.burn_rate_req = util.time() + RETRY_PERIOD + end + end + + -- ISS clear request retry + + if not self.acks.iss_clear then + if rtimes.iss_clear_req - util.time() <= 0 then + _send(RPLC_TYPES.ISS_CLEAR, {}) + rtimes.iss_clear_req = util.time() + RETRY_PERIOD + end + end end return self.connected diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 72f12d4..6304384 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -128,7 +128,6 @@ local function _iterate(sessions) local ok = session.instance.iterate() if ok then -- send packets in out queue - -- @todo handle commands if that's being used too while session.out_queue.ready() do local msg = session.out_queue.pop() if msg.qtype == mqueue.TYPE.PACKET then diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 946ef0c..dceeeb9 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -18,7 +18,7 @@ os.loadAPI("session/svsessions.lua") os.loadAPI("supervisor.lua") -local SUPERVISOR_VERSION = "alpha-v0.1.9" +local SUPERVISOR_VERSION = "alpha-v0.1.10" local print = util.print local println = util.println From 76c81395b74aa147b92706fdcdd2b739bb422ae3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 2 May 2022 11:42:24 -0400 Subject: [PATCH 131/587] #41 close session connections --- scada-common/comms.lua | 4 +- supervisor/session/plc.lua | 139 +++++++++++++++++++----------- supervisor/session/svsessions.lua | 71 +++++++++------ supervisor/startup.lua | 7 +- supervisor/supervisor.lua | 8 +- 5 files changed, 149 insertions(+), 80 deletions(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index bca5682..998e914 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -27,10 +27,10 @@ RPLC_LINKING = { SCADA_MGMT_TYPES = { PING = 0, -- generic ping - SV_HEARTBEAT = 1, -- supervisor heartbeat + CLOSE = 1, -- close a connection REMOTE_LINKED = 2, -- remote device linked RTU_ADVERT = 3, -- RTU capability advertisement - RTU_HEARTBEAT = 4, -- RTU heartbeat + RTU_HEARTBEAT = 4 -- RTU heartbeat } RTU_ADVERT_TYPES = { diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index fa805c4..810d8df 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -167,6 +167,7 @@ function new_session(id, for_reactor, in_queue, out_queue) self.sDB.mek_struct.max_burn = mek_data[8] end + -- get an ACK status local _get_ack = function (pkt) if pkt.length == 1 then return pkt.data[1] @@ -176,36 +177,35 @@ function new_session(id, for_reactor, in_queue, out_queue) end end - local _handle_packet = function (rplc_pkt) - local checks_ok = true - + -- handle a packet + local _handle_packet = function (pkt) -- check sequence number if self.r_seq_num == nil then - self.r_seq_num = rplc_pkt.scada_frame.seq_num() - elseif self.r_seq_num >= rplc_pkt.scada_frame.seq_num() then - log._warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. rplc_pkt.scada_frame.seq_num()) - checks_ok = false + self.r_seq_num = pkt.scada_frame.seq_num() + elseif self.r_seq_num >= pkt.scada_frame.seq_num() then + log._warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + return else - self.r_seq_num = rplc_pkt.scada_frame.seq_num() - end - - -- check reactor ID - if rplc_pkt.id ~= for_reactor then - log._warning(log_header .. "RPLC packet with ID not matching reactor ID: reactor " .. self.for_reactor .. " != " .. rplc_pkt.id) - checks_ok = false + self.r_seq_num = pkt.scada_frame.seq_num() end -- process packet - if checks_ok then + if pkt.scada_frame.protocol() == PROTOCOLS.RPLC then + -- check reactor ID + if pkt.id ~= for_reactor then + log._warning(log_header .. "RPLC packet with ID not matching reactor ID: reactor " .. self.for_reactor .. " != " .. pkt.id) + return + end + -- feed watchdog self.plc_conn_watchdog.feed() -- handle packet by type - if rplc_pkt.type == RPLC_TYPES.KEEP_ALIVE then + if pkt.type == RPLC_TYPES.KEEP_ALIVE then -- keep alive reply - if rplc_pkt.length == 2 then - local srv_start = rplc_pkt.data[1] - local plc_send = rplc_pkt.data[2] + if pkt.length == 2 then + local srv_start = pkt.data[1] + local plc_send = pkt.data[2] local srv_now = util.time() self.last_rtt = srv_now - srv_start @@ -218,18 +218,18 @@ function new_session(id, for_reactor, in_queue, out_queue) else log._debug(log_header .. "RPLC keep alive packet length mismatch") end - elseif rplc_pkt.type == RPLC_TYPES.STATUS then + elseif pkt.type == RPLC_TYPES.STATUS then -- status packet received, update data - if rplc_pkt.length >= 5 then - self.sDB.last_status_update = rplc_pkt.data[1] - self.sDB.control_state = rplc_pkt.data[2] - self.sDB.overridden = rplc_pkt.data[3] - self.sDB.degraded = rplc_pkt.data[4] - self.sDB.mek_status.heating_rate = rplc_pkt.data[5] + if pkt.length >= 5 then + self.sDB.last_status_update = pkt.data[1] + self.sDB.control_state = pkt.data[2] + self.sDB.overridden = pkt.data[3] + self.sDB.degraded = pkt.data[4] + self.sDB.mek_status.heating_rate = pkt.data[5] -- attempt to read mek_data table - if rplc_pkt.data[6] ~= nil then - local status = pcall(_copy_status, rplc_pkt.data[6]) + if pkt.data[6] ~= nil then + local status = pcall(_copy_status, pkt.data[6]) if status then -- copied in status data OK else @@ -240,10 +240,10 @@ function new_session(id, for_reactor, in_queue, out_queue) else log._debug(log_header .. "RPLC status packet length mismatch") end - elseif rplc_pkt.type == RPLC_TYPES.MEK_STRUCT then + elseif pkt.type == RPLC_TYPES.MEK_STRUCT then -- received reactor structure, record it - if rplc_pkt.length == 8 then - local status = pcall(_copy_struct, rplc_pkt.data) + if pkt.length == 8 then + local status = pcall(_copy_struct, pkt.data) if status then -- copied in structure data OK else @@ -253,35 +253,36 @@ function new_session(id, for_reactor, in_queue, out_queue) else log._debug(log_header .. "RPLC struct packet length mismatch") end - elseif rplc_pkt.type == RPLC_TYPES.MEK_SCRAM then + elseif pkt.type == RPLC_TYPES.MEK_SCRAM then -- SCRAM acknowledgement - local ack = _get_ack(rplc_pkt) + local ack = _get_ack(pkt) if ack then self.acks.scram = true self.sDB.control_state = false elseif ack == false then log._debug(log_header .. "SCRAM failed!") end - elseif rplc_pkt.type == RPLC_TYPES.MEK_ENABLE then + elseif pkt.type == RPLC_TYPES.MEK_ENABLE then -- enable acknowledgement - local ack = _get_ack(rplc_pkt) + local ack = _get_ack(pkt) if ack then self.acks.enable = true self.sDB.control_state = true elseif ack == false then log._debug(log_header .. "enable failed!") end - elseif rplc_pkt.type == RPLC_TYPES.MEK_BURN_RATE then + elseif pkt.type == RPLC_TYPES.MEK_BURN_RATE then -- burn rate acknowledgement - if _get_ack(rplc_pkt) then + local ack = _get_ack(pkt) + if ack then self.acks.burn_rate = true - else + elseif ack == false then log._debug(log_header .. "burn rate update failed!") end - elseif rplc_pkt.type == RPLC_TYPES.ISS_STATUS then + elseif pkt.type == RPLC_TYPES.ISS_STATUS then -- ISS status packet received, copy data - if rplc_pkt.length == 7 then - local status = pcall(_copy_iss_status, rplc_pkt.data) + if pkt.length == 7 then + local status = pcall(_copy_iss_status, pkt.data) if status then -- copied in ISS status data OK else @@ -291,13 +292,13 @@ function new_session(id, for_reactor, in_queue, out_queue) else log._debug(log_header .. "RPLC ISS status packet length mismatch") end - elseif rplc_pkt.type == RPLC_TYPES.ISS_ALARM then + elseif pkt.type == RPLC_TYPES.ISS_ALARM then -- ISS alarm self.sDB.overridden = true - if rplc_pkt.length == 8 then + if pkt.length == 8 then self.sDB.iss_tripped = true - self.sDB.iss_trip_cause = rplc_pkt.data[1] - local status = pcall(_copy_iss_status, { table.unpack(rplc_pkt.data, 2, #rplc_pkt.length) }) + self.sDB.iss_trip_cause = pkt.data[1] + local status = pcall(_copy_iss_status, { table.unpack(pkt.data, 2, #pkt.length) }) if status then -- copied in ISS status data OK else @@ -307,21 +308,30 @@ function new_session(id, for_reactor, in_queue, out_queue) else log._debug(log_header .. "RPLC ISS alarm packet length mismatch") end - elseif rplc_pkt.type == RPLC_TYPES.ISS_CLEAR then + elseif pkt.type == RPLC_TYPES.ISS_CLEAR then -- ISS clear acknowledgement - if _get_ack(rplc_pkt) then + local ack = _get_ack(pkt) + if ack then self.acks.iss_tripped = true self.sDB.iss_tripped = false self.sDB.iss_trip_cause = "ok" - else + elseif ack == false then log._debug(log_header .. "ISS clear failed") end else - log._debug(log_header .. "handler received unsupported RPLC packet type " .. rplc_pkt.type) + log._debug(log_header .. "handler received unsupported RPLC packet type " .. pkt.type) + end + elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then + if pkt.type == SCADA_MGMT_TYPES.CLOSE then + -- close the session + self.connected = false + else + log._debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) end end end + -- send an RPLC packet local _send = function (msg_type, msg) local s_pkt = comms.scada_packet() local r_pkt = comms.rplc_packet() @@ -333,18 +343,38 @@ function new_session(id, for_reactor, in_queue, out_queue) self.seq_num = self.seq_num + 1 end + -- send a SCADA management packet + local _send_mgmt = function (msg_type, msg) + local s_pkt = comms.scada_packet() + local m_pkt = comms.mgmt_packet() + + m_pkt.make(msg_type, msg) + s_pkt.make(self.seq_num, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) + + self.out_q.push_packet(s_pkt) + self.seq_num = self.seq_num + 1 + end + -- PUBLIC FUNCTIONS -- + -- get the session ID local get_id = function () return self.id end + -- get the session database local get_db = function () return self.sDB end - local close = function () self.connected = false end + -- close the connection + local close = function () + self.connected = false + _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) + end + -- check if a timer matches this session's watchdog local check_wd = function (timer) return timer == self.plc_conn_watchdog.get_timer() end + -- get the reactor structure local get_struct = function () if self.received_struct then return self.sDB.mek_struct @@ -353,6 +383,7 @@ function new_session(id, for_reactor, in_queue, out_queue) end end + -- iterate the session local iterate = function () if self.connected then ------------------ @@ -361,7 +392,7 @@ function new_session(id, for_reactor, in_queue, out_queue) local handle_start = util.time() - while self.in_q.ready() do + while self.in_q.ready() and self.connected do -- get a new message to process local message = self.in_q.pop() @@ -406,6 +437,12 @@ function new_session(id, for_reactor, in_queue, out_queue) end end + -- exit if connection was closed + if not self.connected then + log._info(log_header .. "session closed by remote host") + return false + end + ---------------------- -- update periodics -- ---------------------- diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 6304384..f25427e 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -24,39 +24,33 @@ function link_modem(modem) self.modem = modem end -function alloc_reactor_plcs(num_reactors) - self.num_reactors = num_reactors - for i = 1, num_reactors do - table.insert(self.plc_sessions, false) +-- find a session by the remote port +function find_session(,remote_port) + -- check RTU sessions + for i = 1, #self.rtu_sessions do + if self.rtu_sessions[i].r_port == remote_port then + return self.rtu_sessions[i] + end end -end -function find_session(stype, remote_port) - if stype == SESSION_TYPE.RTU_SESSION then - for i = 1, #self.rtu_sessions do - if self.rtu_sessions[i].r_port == remote_port then - return self.rtu_sessions[i] - end + -- check PLC sessions + for i = 1, #self.plc_sessions do + if self.plc_sessions[i].r_port == remote_port then + return self.plc_sessions[i] end - elseif stype == SESSION_TYPE.PLC_SESSION then - for i = 1, #self.plc_sessions do - if self.plc_sessions[i].r_port == remote_port then - return self.plc_sessions[i] - end + end + + -- check coordinator sessions + for i = 1, #self.coord_sessions do + if self.coord_sessions[i].r_port == remote_port then + return self.coord_sessions[i] end - elseif stype == SESSION_TYPE.COORD_SESSION then - for i = 1, #self.coord_sessions do - if self.coord_sessions[i].r_port == remote_port then - return self.coord_sessions[i] - end - end - else - log._error("cannot search for unknown session type " .. stype, true) end return nil end +-- get a session by reactor ID function get_reactor_session(reactor) local session = nil @@ -69,6 +63,7 @@ function get_reactor_session(reactor) return session end +-- establish a new PLC session function establish_plc_session(local_port, remote_port, for_reactor) if get_reactor_session(for_reactor) == nil then local plc_s = { @@ -96,6 +91,7 @@ function establish_plc_session(local_port, remote_port, for_reactor) end end +-- check if a watchdog timer event matches that of one of the provided sessions local function _check_watchdogs(sessions, timer_event) for i = 1, #sessions do local session = sessions[i] @@ -110,6 +106,7 @@ local function _check_watchdogs(sessions, timer_event) end end +-- attempt to identify which session's watchdog timer fired function check_all_watchdogs(timer_event) -- check RTU session watchdogs _check_watchdogs(self.rtu_sessions, timer_event) @@ -121,6 +118,7 @@ function check_all_watchdogs(timer_event) _check_watchdogs(self.coord_sessions, timer_event) end +-- iterate all the given sessions local function _iterate(sessions) for i = 1, #sessions do local session = sessions[i] @@ -142,6 +140,7 @@ local function _iterate(sessions) end end +-- iterate all sessions function iterate_all() -- iterate RTU sessions _iterate(self.rtu_sessions) @@ -153,6 +152,7 @@ function iterate_all() _iterate(self.coord_sessions) end +-- delete any closed sessions local function _free_closed(sessions) local move_to = 1 for i = 1, #sessions do @@ -172,6 +172,7 @@ local function _free_closed(sessions) end end +-- delete all closed sessions function free_all_closed() -- free closed RTU sessions _free_closed(self.rtu_sessions) @@ -182,3 +183,25 @@ function free_all_closed() -- free closed coordinator sessions _free_closed(self.coord_sessions) end + +-- close connections +local function _close(sessions) + for i = 1, #sessions do + local session = sessions[i] + if session.open then + session.open = false + session.instance.close() + end + end +end + +-- close all open connections +function close_all() + -- close sessions + _close(self.rtu_sessions) + _close(self.plc_sessions) + _close(self.coord_sessions) + + -- free sessions + free_all_closed() +end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index dceeeb9..43599c7 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -18,7 +18,7 @@ os.loadAPI("session/svsessions.lua") os.loadAPI("supervisor.lua") -local SUPERVISOR_VERSION = "alpha-v0.1.10" +local SUPERVISOR_VERSION = "alpha-v0.1.11" local print = util.print local println = util.println @@ -102,7 +102,10 @@ while true do -- check for termination request if event == "terminate" or ppm.should_terminate() then - log._info("terminate requested, exiting...") + println_ts("closing sessions...") + log._info("terminate requested, closing sessions...") + svsessions.close_all() + log._info("sessions closed") break end end diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index b8addc2..9f8a3a6 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -117,11 +117,13 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) -- device (RTU/PLC) listening channel if l_port == self.dev_listen then + -- look for an associated session + local session = svsessions.find_session(r_port) + if protocol == PROTOCOLS.MODBUS_TCP then -- MODBUS response elseif protocol == PROTOCOLS.RPLC then -- reactor PLC packet - local session = svsessions.find_session(SESSION_TYPE.PLC_SESSION, r_port) if session then if packet.type == RPLC_TYPES.LINK_REQ then -- new device on this port? that's a collision @@ -158,6 +160,10 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) end elseif protocol == PROTOCOLS.SCADA_MGMT then -- SCADA management packet + if session then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + end else log._debug("illegal packet type " .. protocol .. " on device listening channel") end From b28020144650618577607ff0dbe6f3bc86f5d645 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 2 May 2022 11:44:10 -0400 Subject: [PATCH 132/587] #41 cancel session watchdog timer --- scada-common/util.lua | 6 ++++++ supervisor/session/plc.lua | 1 + 2 files changed, 7 insertions(+) diff --git a/scada-common/util.lua b/scada-common/util.lua index 36761fd..bcab1b5 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -81,6 +81,12 @@ function new_watchdog(timeout) self._wd_timer = os.startTimer(self._timeout) end + local cancel = function () + if self._wd_timer ~= nil then + os.cancelTimer(self._wd_timer) + end + end + return { get_timer = get_timer, feed = feed diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 810d8df..6f8cb5f 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -365,6 +365,7 @@ function new_session(id, for_reactor, in_queue, out_queue) -- close the connection local close = function () + self.plc_conn_watchdod.cancel() self.connected = false _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) end From 5ce3f84dfa03fd0a50ff7fcec26cfc31ef415ef2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 2 May 2022 12:06:04 -0400 Subject: [PATCH 133/587] #41 PLC connection closing --- reactor-plc/plc.lua | 52 ++++++++++++++++++++++++++++++++++------- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 22 ++++++++++++++--- 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 52bd16c..5aabaae 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -197,6 +197,7 @@ end function comms_init(id, modem, local_port, server_port, reactor, iss) local self = { id = id, + open = false, seq_num = 0, r_seq_num = nil, modem = modem, @@ -218,14 +219,29 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- PRIVATE FUNCTIONS -- local _send = function (msg_type, msg) - local s_pkt = comms.scada_packet() - local r_pkt = comms.rplc_packet() + if self.open then + local s_pkt = comms.scada_packet() + local r_pkt = comms.rplc_packet() - r_pkt.make(self.id, msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOLS.RPLC, r_pkt.raw_sendable()) + r_pkt.make(self.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()) - self.seq_num = self.seq_num + 1 + self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) + self.seq_num = self.seq_num + 1 + end + end + + local _send_mgmt = function (msg_type, msg) + if self.open then + local s_pkt = comms.scada_packet() + local m_pkt = comms.mgmt_packet() + + m_pkt.make(msg_type, msg) + s_pkt.make(self.seq_num, PROTOCOLS.RPLC, m_pkt.raw_sendable()) + + self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) + self.seq_num = self.seq_num + 1 + end end -- variable reactor status information, excluding heating rate @@ -425,6 +441,9 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) self.r_seq_num = packet.scada_frame.seq_num() end + -- mark connection as open + self.open = true + -- feed the watchdog first so it doesn't uhh...eat our packets conn_watchdog.feed() @@ -555,19 +574,34 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) log._debug("discarding non-link packet before linked") end elseif packet.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then - -- @todo + -- handle session close + if packet.type == SCADA_MGMT_TYPES.CLOSE then + self.open = false + conn_watchdog.cancel() + unlink() + else + log._warning("received unknown SCADA_MGMT packet type " .. packet.type) + end end end end local is_scrammed = function () return self.scrammed end local is_linked = function () return self.linked end + local is_closed = function () return not self.open end local unlink = function () self.linked = false self.r_seq_num = nil end + local close = function () + self.open = false + conn_watchdog.cancel() + unlink() + _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) + end + return { reconnect_modem = reconnect_modem, reconnect_reactor = reconnect_reactor, @@ -579,6 +613,8 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) send_iss_alarm = send_iss_alarm, is_scrammed = is_scrammed, is_linked = is_linked, - unlink = unlink + is_closed = is_closed, + unlink = unlink, + close = close } end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 17e4a2e..8070ea1 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -12,7 +12,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.4.13" +local R_PLC_VERSION = "alpha-v0.4.14" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 36d3179..3e9ee2e 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -169,9 +169,11 @@ function thread__main(smem, init) -- check for termination request if event == "terminate" or ppm.should_terminate() then + log._info("terminate requested, main thread exiting") -- iss handles reactor shutdown plc_state.shutdown = true - log._info("terminate requested, main thread exiting") + -- close connection + plc_comms.close() break end end @@ -195,6 +197,7 @@ function thread__iss(smem) local iss_queue = smem.q.mq_iss + local was_closed = true local last_update = util.time() -- thread loop @@ -203,6 +206,19 @@ function thread__iss(smem) -- ISS checks if plc_state.init_ok then + -- SCRAM if no open connection + if networked and plc_comms.is_closed() then + plc_state.scram = true + if not was_closed then + was_closed = true + iss.trip_timeout() + println_ts("server connection closed by remote host") + log._warning("server connection closed by remote host") + end + else + was_closed = false + end + -- 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) if not plc_state.no_reactor and plc_state.scram and reactor.getStatus() then @@ -217,13 +233,13 @@ function thread__iss(smem) end -- check safety (SCRAM occurs if tripped) - if not plc_state.degraded then + if not plc_state.no_reactor then local iss_tripped, iss_status_string, iss_first = iss.check() plc_state.scram = plc_state.scram or iss_tripped if iss_first then println_ts("[ISS] SCRAM! safety trip: " .. iss_status_string) - if networked then + if networked and not plc_state.no_modem then plc_comms.send_iss_alarm(iss_status_string) end end From 4bc50e4bada3cc7302c4ad159e0214e793d3c552 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 2 May 2022 13:15:08 -0400 Subject: [PATCH 134/587] #41 RTU comms closing --- rtu/rtu.lua | 12 +++++++++++- rtu/startup.lua | 2 +- rtu/threads.lua | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index a35770d..fc32fac 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -249,6 +249,10 @@ function rtu_comms(modem, local_port, server_port) send_modbus(reply) elseif protocol == PROTOCOLS.SCADA_MGMT then -- SCADA management packet + if packet.type == SCADA_MGMT_TYPES.CLOSE then + -- close connection + conn_watchdog.cancel() + unlink(rtu_state) if packet.type == SCADA_MGMT_TYPES.REMOTE_LINKED then -- acknowledgement rtu_state.linked = true @@ -317,6 +321,11 @@ function rtu_comms(modem, local_port, server_port) self.r_seq_num = nil end + local close = function (rtu_state) + unlink(rtu_state) + _send(SCADA_MGMT_TYPES.CLOSE, {}) + end + return { send_modbus = send_modbus, reconnect_modem = reconnect_modem, @@ -324,6 +333,7 @@ function rtu_comms(modem, local_port, server_port) handle_packet = handle_packet, send_advertisement = send_advertisement, send_heartbeat = send_heartbeat, - unlink = unlink + unlink = unlink, + close = close } end diff --git a/rtu/startup.lua b/rtu/startup.lua index 59c4354..e73eab6 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -19,7 +19,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.4.9" +local RTU_VERSION = "alpha-v0.4.10" local print = util.print local println = util.println diff --git a/rtu/threads.lua b/rtu/threads.lua index c5af330..9cb2923 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -169,6 +169,7 @@ function thread__comms(smem) -- check for termination request if rtu_state.shutdown then + rtu_comms.close() log._info("comms thread exiting") break end From c19a58380c3cb4edd7e0f9b59d5fdf9f8de33f35 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 2 May 2022 17:33:54 -0400 Subject: [PATCH 135/587] fixed cancel not being exposed for watchdog functions --- scada-common/util.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scada-common/util.lua b/scada-common/util.lua index bcab1b5..a226e9f 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -89,6 +89,7 @@ function new_watchdog(timeout) return { get_timer = get_timer, - feed = feed + feed = feed, + cancel = cancel } end From 1ac4de65a90b7581e7a4143b843ed9722bc1784a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 2 May 2022 17:34:24 -0400 Subject: [PATCH 136/587] added close to valid scada types and fixed length checks for packet decoders --- scada-common/comms.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 998e914..581e493 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -303,7 +303,7 @@ function mgmt_packet() -- check that type is known local _scada_type_valid = function () return self.type == SCADA_MGMT_TYPES.PING or - self.type == SCADA_MGMT_TYPES.SV_HEARTBEAT or + self.type == SCADA_MGMT_TYPES.CLOSE or self.type == SCADA_MGMT_TYPES.REMOTE_LINKED or self.type == SCADA_MGMT_TYPES.RTU_ADVERT or self.type == SCADA_MGMT_TYPES.RTU_HEARTBEAT @@ -329,7 +329,7 @@ function mgmt_packet() self.frame = frame if frame.protocol() == PROTOCOLS.SCADA_MGMT then - local ok = #data >= 1 + local ok = frame.length() >= 1 if ok then local data = frame.data() @@ -408,7 +408,7 @@ function coord_packet() self.frame = frame if frame.protocol() == PROTOCOLS.COORD_DATA then - local ok = #data >= 1 + local ok = frame.length() >= 1 if ok then local data = frame.data() @@ -487,7 +487,7 @@ function capi_packet() self.frame = frame if frame.protocol() == PROTOCOLS.COORD_API then - local ok = #data >= 1 + local ok = frame.length() >= 1 if ok then local data = frame.data() From e3e370d3ab8de00488e0724694282dd6a31d3a9e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 2 May 2022 17:34:57 -0400 Subject: [PATCH 137/587] fixed RTU mgmt_pkt reference --- rtu/rtu.lua | 2 +- rtu/startup.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index fc32fac..b9a3061 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -184,7 +184,7 @@ function rtu_comms(modem, local_port, server_port) elseif s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then local mgmt_pkt = comms.mgmt_packet() if mgmt_pkt.decode(s_pkt) then - pkt = mgmt_packet.get() + pkt = mgmt_pkt.get() end else log._error("illegal packet type " .. s_pkt.protocol(), true) diff --git a/rtu/startup.lua b/rtu/startup.lua index e73eab6..20177e6 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -19,7 +19,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.4.10" +local RTU_VERSION = "alpha-v0.4.11" local print = util.print local println = util.println From 574b85e1772be0ec3dee47bbf3eaa22fd8e7c94a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 2 May 2022 17:40:00 -0400 Subject: [PATCH 138/587] PLC bugfixes and #37 optimized status packets and structure packets --- reactor-plc/plc.lua | 235 ++++++++++++++++++++++------------------ reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 19 ++-- 3 files changed, 139 insertions(+), 117 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 5aabaae..a1e5225 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -5,6 +5,7 @@ local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES local RPLC_LINKING = comms.RPLC_LINKING +local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local print = util.print local println = util.println @@ -197,7 +198,6 @@ end function comms_init(id, modem, local_port, server_port, reactor, iss) local self = { id = id, - open = false, seq_num = 0, r_seq_num = nil, modem = modem, @@ -219,71 +219,83 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- PRIVATE FUNCTIONS -- local _send = function (msg_type, msg) - if self.open then - local s_pkt = comms.scada_packet() - local r_pkt = comms.rplc_packet() + local s_pkt = comms.scada_packet() + local r_pkt = comms.rplc_packet() - r_pkt.make(self.id, msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOLS.RPLC, r_pkt.raw_sendable()) + r_pkt.make(self.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()) - self.seq_num = self.seq_num + 1 - end + self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) + self.seq_num = self.seq_num + 1 end local _send_mgmt = function (msg_type, msg) - if self.open then - local s_pkt = comms.scada_packet() - local m_pkt = comms.mgmt_packet() + local s_pkt = comms.scada_packet() + local m_pkt = comms.mgmt_packet() - m_pkt.make(msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOLS.RPLC, m_pkt.raw_sendable()) + m_pkt.make(msg_type, msg) + s_pkt.make(self.seq_num, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) - self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) - self.seq_num = self.seq_num + 1 - end + self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) + self.seq_num = self.seq_num + 1 end -- variable reactor status information, excluding heating rate local _reactor_status = function () - local coolant = self.reactor.getCoolant() - local coolant_name = "" - local coolant_amnt = 0 + local coolant = nil + local hcoolant = nil - local hcoolant = self.reactor.getHeatedCoolant() - local hcoolant_name = "" - local hcoolant_amnt = 0 + local data_table = { + false, -- getStatus + 0, -- getBurnRate + 0, -- getActualBurnRate + 0, -- getTemperature + 0, -- getDamagePercent + 0, -- getBoilEfficiency + 0, -- getEnvironmentalLoss + 0, -- getFuel + 0, -- getFuelFilledPercentage + 0, -- getWaste + 0, -- getWasteFilledPercentage + "", -- coolant_name + 0, -- coolant_amnt + 0, -- getCoolantFilledPercentage + "", -- hcoolant_name + 0, -- hcoolant_amnt + 0 -- getHeatedCoolantFilledPercentage + } + + local tasks = { + function () data_table[1] = self.reactor.getStatus() end, + function () data_table[2] = self.reactor.getBurnRate() end, + function () data_table[3] = self.reactor.getActualBurnRate() end, + function () data_table[4] = self.reactor.getTemperature() end, + function () data_table[5] = self.reactor.getDamagePercent() end, + function () data_table[6] = self.reactor.getBoilEfficiency() end, + function () data_table[7] = self.reactor.getEnvironmentalLoss() end, + function () data_table[8] = self.reactor.getFuel() end, + function () data_table[9] = self.reactor.getFuelFilledPercentage() end, + function () data_table[10] = self.reactor.getWaste() end, + function () data_table[11] = self.reactor.getWasteFilledPercentage() end, + function () coolant = self.reactor.getCoolant() end, + function () data_table[14] = self.reactor.getCoolantFilledPercentage() end, + function () hcoolant = self.reactor.getHeatedCoolant() end, + function () data_table[17] = self.reactor.getHeatedCoolantFilledPercentage() end + } + + parallel.waitForAll(table.unpack(tasks)) if coolant ~= nil then - coolant_name = coolant.name - coolant_amnt = coolant.amount + data_table[12] = coolant.name + data_table[13] = coolant.amount end if hcoolant ~= nil then - hcoolant_name = hcoolant.name - hcoolant_amnt = hcoolant.amount + data_table[15] = hcoolant.name + data_table[16] = hcoolant.amount end - return { - self.reactor.getStatus(), - self.reactor.getBurnRate(), - self.reactor.getActualBurnRate(), - self.reactor.getTemperature(), - self.reactor.getDamagePercent(), - self.reactor.getBoilEfficiency(), - self.reactor.getEnvironmentalLoss(), - - self.reactor.getFuel(), - self.reactor.getFuelFilledPercentage(), - self.reactor.getWaste(), - self.reactor.getWasteFilledPercentage(), - coolant_name, - coolant_amnt, - self.reactor.getCoolantFilledPercentage(), - hcoolant_name, - hcoolant_amnt, - self.reactor.getHeatedCoolantFilledPercentage() - }, self.reactor.__p_is_faulted() + return data_table, self.reactor.__p_is_faulted() end local _update_status_cache = function () @@ -320,20 +332,23 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) _send(msg_type, { succeeded }) end - -- send structure properties (these should not change) - -- (server will cache these) + -- send structure properties (these should not change, server will cache these) local _send_struct = function () - local mek_data = { - self.reactor.getHeatCapacity(), - self.reactor.getFuelAssemblies(), - self.reactor.getFuelSurfaceArea(), - self.reactor.getFuelCapacity(), - self.reactor.getWasteCapacity(), - self.reactor.getCoolantCapacity(), - self.reactor.getHeatedCoolantCapacity(), - self.reactor.getMaxBurnRate() + local mek_data = { 0, 0, 0, 0, 0, 0, 0, 0 } + + local tasks = { + function () mek_data[1] = self.reactor.getHeatCapacity() end, + function () mek_data[2] = self.reactor.getFuelAssemblies() end, + function () mek_data[3] = self.reactor.getFuelSurfaceArea() end, + function () mek_data[4] = self.reactor.getFuelCapacity() end, + function () mek_data[5] = self.reactor.getWasteCapacity() end, + function () mek_data[6] = self.reactor.getCoolantCapacity() end, + function () mek_data[7] = self.reactor.getHeatedCoolantCapacity() end, + function () mek_data[8] = self.reactor.getMaxBurnRate() end } + parallel.waitForAll(table.unpack(tasks)) + if not self.reactor.__p_is_faulted() then _send(RPLC_TYPES.MEK_STRUCT, mek_data) else @@ -359,6 +374,19 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) _update_status_cache() end + -- unlink from the server + local unlink = function () + self.linked = false + self.r_seq_num = nil + end + + -- close the connection to the server + local close = function (conn_watchdog) + conn_watchdog.cancel() + unlink() + _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) + end + -- attempt to establish link with supervisor local send_link_req = function () _send(RPLC_TYPES.LINK_REQ, { self.id }) @@ -366,37 +394,47 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- send live status information local send_status = function (degraded) - local mek_data = nil + if self.linked then + local mek_data = nil - if _update_status_cache() then - mek_data = self.status_cache + if _update_status_cache() then + mek_data = self.status_cache + end + + local sys_status = { + util.time(), -- timestamp + (not self.scrammed), -- enabled + iss.is_tripped(), -- overridden + degraded, -- degraded + self.reactor.getHeatingRate(), -- heating rate + mek_data -- mekanism status data + } + + if not self.reactor.__p_is_faulted() then + _send(RPLC_TYPES.STATUS, sys_status) + else + log._error("failed to send status: PPM fault") + end end - - local sys_status = { - util.time(), -- timestamp - (not self.scrammed), -- enabled - iss.is_tripped(), -- overridden - degraded, -- degraded - self.reactor.getHeatingRate(), -- heating rate - mek_data -- mekanism status data - } - - _send(RPLC_TYPES.STATUS, sys_status) end -- send safety system status local send_iss_status = function () - _send(RPLC_TYPES.ISS_STATUS, iss.status()) + if self.linked then + _send(RPLC_TYPES.ISS_STATUS, iss.status()) + end end -- send safety system alarm local send_iss_alarm = function (cause) - local iss_alarm = { - cause, - table.unpack(iss.status()) - } + if self.linked then + local iss_alarm = { + cause, + table.unpack(iss.status()) + } - _send(RPLC_TYPES.ISS_ALARM, iss_alarm) + _send(RPLC_TYPES.ISS_ALARM, iss_alarm) + end end -- parse an RPLC packet @@ -418,7 +456,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) elseif s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then local mgmt_pkt = comms.mgmt_packet() if mgmt_pkt.decode(s_pkt) then - pkt = mgmt_packet.get() + pkt = mgmt_pkt.get() end else log._error("illegal packet type " .. s_pkt.protocol(), true) @@ -441,9 +479,6 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) self.r_seq_num = packet.scada_frame.seq_num() end - -- mark connection as open - self.open = true - -- feed the watchdog first so it doesn't uhh...eat our packets conn_watchdog.feed() @@ -464,7 +499,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) _send_keep_alive_ack(timestamp) else - log._debug(log_header .. "RPLC keep alive packet length mismatch") + log._debug("RPLC keep alive packet length mismatch") end elseif packet.type == RPLC_TYPES.LINK_REQ then -- link request confirmation @@ -490,11 +525,12 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) self.linked = link_ack == RPLC_LINKING.ALLOW else - log._debug(log_header .. "RPLC link req packet length mismatch") + log._debug("RPLC link req packet length mismatch") end elseif packet.type == RPLC_TYPES.MEK_STRUCT then -- request for physical structure _send_struct() + log._debug("sent out structure again, did supervisor miss it?") elseif packet.type == RPLC_TYPES.MEK_SCRAM then -- disable the reactor self.scrammed = true @@ -530,7 +566,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) _send_ack(packet.type, success) else - log._debug(log_header .. "RPLC set burn rate packet length mismatch") + log._debug("RPLC set burn rate packet length mismatch") end elseif packet.type == RPLC_TYPES.ISS_CLEAR then -- clear the ISS status @@ -568,7 +604,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) self.linked = link_ack == RPLC_LINKING.ALLOW else - log._debug(log_header .. "RPLC link req packet length mismatch") + log._debug("RPLC link req packet length mismatch") end else log._debug("discarding non-link packet before linked") @@ -576,9 +612,10 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) elseif packet.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then -- handle session close if packet.type == SCADA_MGMT_TYPES.CLOSE then - self.open = false conn_watchdog.cancel() unlink() + println_ts("server connection closed by remote host") + log._warning("server connection closed by remote host") else log._warning("received unknown SCADA_MGMT packet type " .. packet.type) end @@ -588,33 +625,19 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) local is_scrammed = function () return self.scrammed end local is_linked = function () return self.linked end - local is_closed = function () return not self.open end - - local unlink = function () - self.linked = false - self.r_seq_num = nil - end - - local close = function () - self.open = false - conn_watchdog.cancel() - unlink() - _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) - end return { reconnect_modem = reconnect_modem, reconnect_reactor = reconnect_reactor, - parse_packet = parse_packet, - handle_packet = handle_packet, + unlink = unlink, + close = close, send_link_req = send_link_req, send_status = send_status, send_iss_status = send_iss_status, send_iss_alarm = send_iss_alarm, + parse_packet = parse_packet, + handle_packet = handle_packet, is_scrammed = is_scrammed, - is_linked = is_linked, - is_closed = is_closed, - unlink = unlink, - close = close + is_linked = is_linked } end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 8070ea1..cd68962 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -12,7 +12,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.4.14" +local R_PLC_VERSION = "alpha-v0.5.0" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 3e9ee2e..ee23710 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -170,10 +170,10 @@ function thread__main(smem, init) -- check for termination request if event == "terminate" or ppm.should_terminate() then log._info("terminate requested, main thread exiting") + -- close connection + plc_comms.close(conn_watchdog) -- iss handles reactor shutdown plc_state.shutdown = true - -- close connection - plc_comms.close() break end end @@ -197,7 +197,7 @@ function thread__iss(smem) local iss_queue = smem.q.mq_iss - local was_closed = true + local was_linked = false local last_update = util.time() -- thread loop @@ -207,16 +207,15 @@ function thread__iss(smem) -- ISS checks if plc_state.init_ok then -- SCRAM if no open connection - if networked and plc_comms.is_closed() then + if networked and not plc_comms.is_linked() then plc_state.scram = true - if not was_closed then - was_closed = true + if was_linked then + was_linked = false iss.trip_timeout() - println_ts("server connection closed by remote host") - log._warning("server connection closed by remote host") end else - was_closed = false + -- would do elseif not networked but there is no reason to do that extra operation + was_linked = true end -- if we tried to SCRAM but failed, keep trying @@ -418,7 +417,7 @@ end function thread__setpoint_control(smem) -- execute thread local exec = function () - log._debug("comms rx thread start") + log._debug("setpoint control thread start") -- load in from shared memory local plc_state = smem.plc_state From 62b4b63f4a0c62e53a45b9974ad5b730a4613e2a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 2 May 2022 17:43:23 -0400 Subject: [PATCH 139/587] supervisor PLC session bugfixes --- supervisor/session/plc.lua | 60 ++++++++++++++++--------------- supervisor/session/svsessions.lua | 12 ++++++- supervisor/startup.lua | 2 +- supervisor/supervisor.lua | 2 +- 4 files changed, 44 insertions(+), 32 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 6f8cb5f..b4fbdd9 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -5,6 +5,7 @@ local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES +local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES -- retry time constants in ms local INITIAL_WAIT = 1500 @@ -46,7 +47,7 @@ function new_session(id, for_reactor, in_queue, out_queue) }, -- when to next retry one of these requests retry_times = { - struct_req = 500, + struct_req = (util.time() + 500), scram_req = 0, enable_req = 0, burn_rate_req = 0, @@ -167,6 +168,30 @@ function new_session(id, for_reactor, in_queue, out_queue) self.sDB.mek_struct.max_burn = mek_data[8] end + -- send an RPLC packet + local _send = function (msg_type, msg) + local s_pkt = comms.scada_packet() + local r_pkt = comms.rplc_packet() + + r_pkt.make(self.id, msg_type, msg) + s_pkt.make(self.seq_num, PROTOCOLS.RPLC, r_pkt.raw_sendable()) + + self.out_q.push_packet(s_pkt) + self.seq_num = self.seq_num + 1 + end + + -- send a SCADA management packet + local _send_mgmt = function (msg_type, msg) + local s_pkt = comms.scada_packet() + local m_pkt = comms.mgmt_packet() + + m_pkt.make(msg_type, msg) + s_pkt.make(self.seq_num, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) + + self.out_q.push_packet(s_pkt) + self.seq_num = self.seq_num + 1 + end + -- get an ACK status local _get_ack = function (pkt) if pkt.length == 1 then @@ -246,6 +271,7 @@ function new_session(id, for_reactor, in_queue, out_queue) local status = pcall(_copy_struct, pkt.data) if status then -- copied in structure data OK + self.received_struct = true else -- error copying structure data log._error(log_header .. "failed to parse struct packet data") @@ -331,30 +357,6 @@ function new_session(id, for_reactor, in_queue, out_queue) end end - -- send an RPLC packet - local _send = function (msg_type, msg) - local s_pkt = comms.scada_packet() - local r_pkt = comms.rplc_packet() - - r_pkt.make(self.id, msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOLS.RPLC, r_pkt.raw_sendable()) - - self.out_q.push_packet(s_pkt) - self.seq_num = self.seq_num + 1 - end - - -- send a SCADA management packet - local _send_mgmt = function (msg_type, msg) - local s_pkt = comms.scada_packet() - local m_pkt = comms.mgmt_packet() - - m_pkt.make(msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) - - self.out_q.push_packet(s_pkt) - self.seq_num = self.seq_num + 1 - end - -- PUBLIC FUNCTIONS -- -- get the session ID @@ -365,7 +367,7 @@ function new_session(id, for_reactor, in_queue, out_queue) -- close the connection local close = function () - self.plc_conn_watchdod.cancel() + self.plc_conn_watchdog.cancel() self.connected = false _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) end @@ -441,7 +443,7 @@ function new_session(id, for_reactor, in_queue, out_queue) -- exit if connection was closed if not self.connected then log._info(log_header .. "session closed by remote host") - return false + return self.connected end ---------------------- @@ -466,13 +468,13 @@ function new_session(id, for_reactor, in_queue, out_queue) -- attempt retries -- --------------------- - local rtimes = self.rtimes + local rtimes = self.retry_times -- struct request retry if not self.received_struct then if rtimes.struct_req - util.time() <= 0 then - _send(RPLC_TYPES.LINK_REQ, {}) + _send(RPLC_TYPES.MEK_STRUCT, {}) rtimes.struct_req = util.time() + RETRY_PERIOD end end diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index f25427e..c111093 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -25,7 +25,7 @@ function link_modem(modem) end -- find a session by the remote port -function find_session(,remote_port) +function find_session(remote_port) -- check RTU sessions for i = 1, #self.rtu_sessions do if self.rtu_sessions[i].r_port == remote_port then @@ -191,6 +191,16 @@ local function _close(sessions) if session.open then session.open = false session.instance.close() + + -- send packets in out queue (namely the close packet) + while session.out_queue.ready() do + local msg = session.out_queue.pop() + if msg.qtype == mqueue.TYPE.PACKET then + self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) + end + end + + log._debug("closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) end end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 43599c7..fa8b06d 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -18,7 +18,7 @@ os.loadAPI("session/svsessions.lua") os.loadAPI("supervisor.lua") -local SUPERVISOR_VERSION = "alpha-v0.1.11" +local SUPERVISOR_VERSION = "alpha-v0.1.12" local print = util.print local println = util.println diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 9f8a3a6..204e136 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -93,7 +93,7 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) elseif s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then local mgmt_pkt = comms.mgmt_packet() if mgmt_pkt.decode(s_pkt) then - pkt = mgmt_packet.get() + pkt = mgmt_pkt.get() end -- get as coordinator packet elseif s_pkt.protocol() == PROTOCOLS.COORD_DATA then From c76200b0e3a3ef821451e788e32b35a0264fd635 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 3 May 2022 10:44:18 -0400 Subject: [PATCH 140/587] shared global types --- reactor-plc/plc.lua | 23 +++++++++++++---------- reactor-plc/startup.lua | 1 + rtu/startup.lua | 11 +++++++---- scada-common/types.lua | 20 ++++++++++++++++++++ 4 files changed, 41 insertions(+), 14 deletions(-) create mode 100644 scada-common/types.lua diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index a1e5225..cccced9 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -1,7 +1,10 @@ +-- #REQUIRES types.lua -- #REQUIRES comms.lua -- #REQUIRES ppm.lua -- #REQUIRES util.lua +local iss_status_t = types.iss_status_t + local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES local RPLC_LINKING = comms.RPLC_LINKING @@ -113,7 +116,7 @@ function iss_init(reactor) -- check all safety conditions local check = function () - local status = "ok" + local status = iss_status_t.ok local was_tripped = self.tripped -- update cache @@ -132,32 +135,32 @@ function iss_init(reactor) status = self.trip_cause elseif self.cache[1] then log._warning("ISS: damage critical!") - status = "dmg_crit" + status = iss_status_t.dmg_crit elseif self.cache[4] then log._warning("ISS: high temperature!") - status = "high_temp" + status = iss_status_t.high_temp elseif self.cache[2] then log._warning("ISS: heated coolant backup!") - status = "heated_coolant_backup" + status = iss_status_t.ex_hcoolant elseif self.cache[6] then log._warning("ISS: no coolant!") - status = "no_coolant" + status = iss_status_t.no_coolant elseif self.cache[3] then log._warning("ISS: full waste!") - status = "full_waste" + status = iss_status_t.ex_waste elseif self.cache[5] then log._warning("ISS: no fuel!") - status = "no_fuel" + status = iss_status_t.no_fuel elseif self.cache[7] then log._warning("ISS: supervisor connection timeout!") - status = "timeout" + status = iss_status_t.timeout else self.tripped = false end -- if a new trip occured... local first_trip = false - if not was_tripped and status ~= "ok" then + if not was_tripped and status ~= iss_status_t.ok then log._warning("ISS: reactor SCRAM") first_trip = true @@ -181,7 +184,7 @@ function iss_init(reactor) local reset = function () self.timed_out = false self.tripped = false - self.trip_cause = "" + self.trip_cause = iss_status_t.ok end return { diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index cd68962..5c9df15 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -3,6 +3,7 @@ -- os.loadAPI("scada-common/log.lua") +os.loadAPI("scada-common/types.lua") os.loadAPI("scada-common/util.lua") os.loadAPI("scada-common/ppm.lua") os.loadAPI("scada-common/comms.lua") diff --git a/rtu/startup.lua b/rtu/startup.lua index 20177e6..72d1b7e 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -3,6 +3,7 @@ -- os.loadAPI("scada-common/log.lua") +os.loadAPI("scada-common/types.lua") os.loadAPI("scada-common/util.lua") os.loadAPI("scada-common/ppm.lua") os.loadAPI("scada-common/comms.lua") @@ -21,6 +22,8 @@ os.loadAPI("dev/turbine_rtu.lua") local RTU_VERSION = "alpha-v0.4.11" +local rtu_t = types.rtu_t + local print = util.print local println = util.println local print_ts = util.print_ts @@ -139,7 +142,7 @@ for reactor_idx = 1, #rtu_redstone do table.insert(units, { name = "redstone_io", - type = "redstone", + type = rtu_t.redstone, index = 1, reactor = rtu_redstone[reactor_idx].for_reactor, device = capabilities, -- use device field for redstone channels @@ -168,15 +171,15 @@ for i = 1, #rtu_devices do if type == "boiler" then -- boiler multiblock - rtu_type = "boiler" + rtu_type = rtu_t.boiler rtu_iface = boiler_rtu.new(device) elseif type == "turbine" then -- turbine multiblock - rtu_type = "turbine" + rtu_type = rtu_t.turbine rtu_iface = turbine_rtu.new(device) elseif type == "mekanismMachine" then -- assumed to be an induction matrix multiblock - rtu_type = "imatrix" + rtu_type = rtu_t.induction_matrix rtu_iface = imatrix_rtu.new(device) else local message = "init> device '" .. rtu_devices[i].name .. "' is not a known type (" .. type .. ")" diff --git a/scada-common/types.lua b/scada-common/types.lua new file mode 100644 index 0000000..072cd9a --- /dev/null +++ b/scada-common/types.lua @@ -0,0 +1,20 @@ +-- Global Types + +rtu_t = { + redstone = "redstone", + boiler = "boiler", + turbine = "turbine", + energy_machine = "emachine", + induction_matrix = "imatrix" +} + +iss_status_t = { + ok = "ok", + dmg_crit = "dmg_crit", + ex_hcoolant = "heated_coolant_backup", + ex_waste = "full_waste", + high_temp = "high_temp", + no_fuel = "no_fuel", + no_coolant = "no_coolant", + timeout = "timeout" +} From e2f7318922774e32e28f2b45c4737a2631d9bdf4 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 3 May 2022 10:45:35 -0400 Subject: [PATCH 141/587] #27 induction matrix RTU split into two RTUs, supporting pre and post Mekansim 10.1 --- rtu/dev/energymachine_rtu.lua | 33 +++++++++++++++++++++++++++++++++ rtu/dev/imatrix_rtu.lua | 9 +++++++++ rtu/startup.lua | 8 ++++++-- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 rtu/dev/energymachine_rtu.lua diff --git a/rtu/dev/energymachine_rtu.lua b/rtu/dev/energymachine_rtu.lua new file mode 100644 index 0000000..b3f004a --- /dev/null +++ b/rtu/dev/energymachine_rtu.lua @@ -0,0 +1,33 @@ +-- #REQUIRES rtu.lua + +function new(machine) + local self = { + rtu = rtu.rtu_init(), + machine = machine + } + + local rtu_interface = function () + return self.rtu + end + + -- discrete inputs -- + -- none + + -- coils -- + -- none + + -- input registers -- + -- build properties + self.rtu.connect_input_reg(self.machine.getTotalMaxEnergy) + -- containers + self.rtu.connect_input_reg(self.machine.getTotalEnergy) + self.rtu.connect_input_reg(self.machine.getTotalEnergyNeeded) + self.rtu.connect_input_reg(self.machine.getTotalEnergyFilledPercentage) + + -- holding registers -- + -- none + + return { + rtu_interface = rtu_interface + } +end diff --git a/rtu/dev/imatrix_rtu.lua b/rtu/dev/imatrix_rtu.lua index 529a1f8..43f49b4 100644 --- a/rtu/dev/imatrix_rtu.lua +++ b/rtu/dev/imatrix_rtu.lua @@ -17,12 +17,21 @@ function new(imatrix) -- none -- input registers -- + -- @todo check these on Mekanism 10.1+ -- build properties + self.rtu.connect_input_reg(self.imatrix.getTransferCap) + self.rtu.connect_input_reg(self.imatrix.getInstalledCells) + self.rtu.connect_input_reg(self.imatrix.getInstalledProviders) self.rtu.connect_input_reg(self.imatrix.getTotalMaxEnergy) -- containers self.rtu.connect_input_reg(self.imatrix.getTotalEnergy) self.rtu.connect_input_reg(self.imatrix.getTotalEnergyNeeded) self.rtu.connect_input_reg(self.imatrix.getTotalEnergyFilledPercentage) + -- additional fields? check these on 10.1 + self.rtu.connect_input_reg(self.imatrix.getInputItem) + self.rtu.connect_input_reg(self.imatrix.getOutputItem) + self.rtu.connect_input_reg(self.imatrix.getLastInput) + self.rtu.connect_input_reg(self.imatrix.getLastOutput) -- holding registers -- -- none diff --git a/rtu/startup.lua b/rtu/startup.lua index 72d1b7e..5f467e4 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -20,7 +20,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.4.11" +local RTU_VERSION = "alpha-v0.4.12" local rtu_t = types.rtu_t @@ -178,7 +178,11 @@ for i = 1, #rtu_devices do rtu_type = rtu_t.turbine rtu_iface = turbine_rtu.new(device) elseif type == "mekanismMachine" then - -- assumed to be an induction matrix multiblock + -- assumed to be an induction matrix multiblock, pre Mekanism 10.1 + rtu_type = rtu_t.energy_machine + rtu_iface = energymachine_rtu.new(device) + elseif type == "inductionMatrix" then + -- induction matrix multiblock, post Mekanism 10.1 rtu_type = rtu_t.induction_matrix rtu_iface = imatrix_rtu.new(device) else From dc1c1db5e6bdee90c8215e7eb0818fc515e8f63d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 3 May 2022 11:27:40 -0400 Subject: [PATCH 142/587] MODBUS bugfixes, utilize new types file --- scada-common/modbus.lua | 83 +++++++++++++++++---------------------- scada-common/types.lua | 29 ++++++++++++++ supervisor/startup.lua | 2 +- supervisor/supervisor.lua | 1 - 4 files changed, 66 insertions(+), 49 deletions(-) diff --git a/scada-common/modbus.lua b/scada-common/modbus.lua index a2a1cc3..dba239d 100644 --- a/scada-common/modbus.lua +++ b/scada-common/modbus.lua @@ -1,47 +1,25 @@ --- #REQUIRES comms.lua +-- #REQUIRES types.lua --- modbus function codes -local MODBUS_FCODE = { - READ_COILS = 0x01, - READ_DISCRETE_INPUTS = 0x02, - READ_MUL_HOLD_REGS = 0x03, - READ_INPUT_REGS = 0x04, - WRITE_SINGLE_COIL = 0x05, - WRITE_SINGLE_HOLD_REG = 0x06, - WRITE_MUL_COILS = 0x0F, - WRITE_MUL_HOLD_REGS = 0x10, - ERROR_FLAG = 0x80 -} - --- modbus exception codes -local MODBUS_EXCODE = { - ILLEGAL_FUNCTION = 0x01, - ILLEGAL_DATA_ADDR = 0x02, - ILLEGAL_DATA_VALUE = 0x03, - SERVER_DEVICE_FAIL = 0x04, - ACKNOWLEDGE = 0x05, - SERVER_DEVICE_BUSY = 0x06, - NEG_ACKNOWLEDGE = 0x07, - MEMORY_PARITY_ERROR = 0x08, - GATEWAY_PATH_UNAVAILABLE = 0x0A, - GATEWAY_TARGET_TIMEOUT = 0x0B -} +local MODBUS_FCODE = types.MODBUS_FCODE +local MODBUS_EXCODE = types.MODBUS_EXCODE -- new modbus comms handler object -function new(rtu_dev) +function new(rtu_dev, use_parallel_read) local self = { - rtu = rtu_dev + rtu = rtu_dev, + use_parallel = use_parallel_read } local _1_read_coils = function (c_addr_start, count) local readings = {} local access_fault = false local _, coils, _, _ = self.rtu.io_count() - local return_ok = (c_addr_start + count) <= coils + local return_ok = ((c_addr_start + count) <= coils) and (count > 0) if return_ok then - for i = 0, (count - 1) do - readings[i], access_fault = self.rtu.read_coil(c_addr_start + i) + for i = 1, count do + local addr = c_addr_start + i - 1 + readings[i], access_fault = self.rtu.read_coil(addr) if access_fault then return_ok = false @@ -60,11 +38,12 @@ function new(rtu_dev) local readings = {} local access_fault = false local discrete_inputs, _, _, _ = self.rtu.io_count() - local return_ok = (di_addr_start + count) <= discrete_inputs + local return_ok = ((di_addr_start + count) <= discrete_inputs) and (count > 0) if return_ok then - for i = 0, (count - 1) do - readings[i], access_fault = self.rtu.read_di(di_addr_start + i) + for i = 1, count do + local addr = di_addr_start + i - 1 + readings[i], access_fault = self.rtu.read_di(addr) if access_fault then return_ok = false @@ -83,11 +62,12 @@ function new(rtu_dev) local readings = {} local access_fault = false local _, _, _, hold_regs = self.rtu.io_count() - local return_ok = (hr_addr_start + count) <= hold_regs + local return_ok = ((hr_addr_start + count) <= hold_regs) and (count > 0) if return_ok then - for i = 0, (count - 1) do - readings[i], access_fault = self.rtu.read_holding_reg(hr_addr_start + i) + for i = 1, count do + local addr = hr_addr_start + i - 1 + readings[i], access_fault = self.rtu.read_holding_reg(addr) if access_fault then return_ok = false @@ -106,11 +86,12 @@ function new(rtu_dev) local readings = {} local access_fault = false local _, _, input_regs, _ = self.rtu.io_count() - local return_ok = (ir_addr_start + count) <= input_regs + local return_ok = ((ir_addr_start + count) <= input_regs) and (count > 0) if return_ok then - for i = 0, (count - 1) do - readings[i], access_fault = self.rtu.read_input_reg(ir_addr_start + i) + for i = 1, count do + local addr = ir_addr_start + i - 1 + readings[i], access_fault = self.rtu.read_input_reg(addr) if access_fault then return_ok = false @@ -156,6 +137,8 @@ function new(rtu_dev) return_ok = false readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL end + else + response = MODBUS_EXCODE.ILLEGAL_DATA_ADDR end return return_ok @@ -165,11 +148,12 @@ function new(rtu_dev) local response = nil local _, coils, _, _ = self.rtu.io_count() local count = #values - local return_ok = (c_addr_start + count) <= coils + local return_ok = ((c_addr_start + count) <= coils) and (count > 0) if return_ok then - for i = 0, (count - 1) do - local access_fault = self.rtu.write_coil(c_addr_start + i, values[i + 1]) + for i = 1, count do + local addr = c_addr_start + i - 1 + local access_fault = self.rtu.write_coil(addr, values[i]) if access_fault then return_ok = false @@ -177,6 +161,8 @@ function new(rtu_dev) break end end + else + response = MODBUS_EXCODE.ILLEGAL_DATA_ADDR end return return_ok, response @@ -186,11 +172,12 @@ function new(rtu_dev) local response = nil local _, _, _, hold_regs = self.rtu.io_count() local count = #values - local return_ok = (hr_addr_start + count) <= hold_regs + local return_ok = ((hr_addr_start + count) <= hold_regs) and (count > 0) if return_ok then - for i = 0, (count - 1) do - local access_fault = self.rtu.write_coil(hr_addr_start + i, values[i + 1]) + for i = 1, count do + local addr = hr_addr_start + i - 1 + local access_fault = self.rtu.write_coil(addr, values[i]) if access_fault then return_ok = false @@ -198,6 +185,8 @@ function new(rtu_dev) break end end + else + response = MODBUS_EXCODE.ILLEGAL_DATA_ADDR end return return_ok, response diff --git a/scada-common/types.lua b/scada-common/types.lua index 072cd9a..866982a 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -18,3 +18,32 @@ iss_status_t = { no_coolant = "no_coolant", timeout = "timeout" } + +-- MODBUS + +-- modbus function codes +local MODBUS_FCODE = { + READ_COILS = 0x01, + READ_DISCRETE_INPUTS = 0x02, + READ_MUL_HOLD_REGS = 0x03, + READ_INPUT_REGS = 0x04, + WRITE_SINGLE_COIL = 0x05, + WRITE_SINGLE_HOLD_REG = 0x06, + WRITE_MUL_COILS = 0x0F, + WRITE_MUL_HOLD_REGS = 0x10, + ERROR_FLAG = 0x80 +} + +-- modbus exception codes +local MODBUS_EXCODE = { + ILLEGAL_FUNCTION = 0x01, + ILLEGAL_DATA_ADDR = 0x02, + ILLEGAL_DATA_VALUE = 0x03, + SERVER_DEVICE_FAIL = 0x04, + ACKNOWLEDGE = 0x05, + SERVER_DEVICE_BUSY = 0x06, + NEG_ACKNOWLEDGE = 0x07, + MEMORY_PARITY_ERROR = 0x08, + GATEWAY_PATH_UNAVAILABLE = 0x0A, + GATEWAY_TARGET_TIMEOUT = 0x0B +} diff --git a/supervisor/startup.lua b/supervisor/startup.lua index fa8b06d..8d174d4 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -3,10 +3,10 @@ -- os.loadAPI("scada-common/log.lua") +os.loadAPI("scada-common/types.lua") os.loadAPI("scada-common/util.lua") os.loadAPI("scada-common/ppm.lua") os.loadAPI("scada-common/comms.lua") -os.loadAPI("scada-common/modbus.lua") os.loadAPI("scada-common/mqueue.lua") os.loadAPI("config.lua") diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 204e136..edda143 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -1,5 +1,4 @@ -- #REQUIRES comms.lua --- #REQUIRES modbus.lua -- #REQUIRES mqueue.lua -- #REQUIRES util.lua -- #REQUIRES svsessions.lua From 635c70cffddea08960c8ee84dc995bc10a0a38d6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 3 May 2022 11:28:29 -0400 Subject: [PATCH 143/587] moved MODBUS file thanks to utilizing types file --- {scada-common => rtu}/modbus.lua | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {scada-common => rtu}/modbus.lua (100%) diff --git a/scada-common/modbus.lua b/rtu/modbus.lua similarity index 100% rename from scada-common/modbus.lua rename to rtu/modbus.lua From 665b33fa054b2d9149a6ff3f0f964ebe8bdca218 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 3 May 2022 11:39:03 -0400 Subject: [PATCH 144/587] #42 parallel RTU reads --- rtu/modbus.lua | 92 ++++++++++++++++++++++++++++++++++++++++++++----- rtu/startup.lua | 8 ++--- 2 files changed, 88 insertions(+), 12 deletions(-) diff --git a/rtu/modbus.lua b/rtu/modbus.lua index dba239d..7ea6108 100644 --- a/rtu/modbus.lua +++ b/rtu/modbus.lua @@ -11,6 +11,7 @@ function new(rtu_dev, use_parallel_read) } local _1_read_coils = function (c_addr_start, count) + local tasks = {} local readings = {} local access_fault = false local _, coils, _, _ = self.rtu.io_count() @@ -19,12 +20,30 @@ function new(rtu_dev, use_parallel_read) if return_ok then for i = 1, count do local addr = c_addr_start + i - 1 - readings[i], access_fault = self.rtu.read_coil(addr) + + if self.use_parallel then + table.insert(tasks, function () + local reading, fault = self.rtu.read_coil(addr) + if fault then access_fault = true else readings[i] = reading end + end) + else + readings[i], access_fault = self.rtu.read_coil(addr) + + if access_fault then + return_ok = false + readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL + break + end + end + end + + -- run parallel tasks if configured + if self.use_parallel then + parallel.waitForAll(table.unpack(tasks)) if access_fault then return_ok = false readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL - break end end else @@ -35,6 +54,7 @@ function new(rtu_dev, use_parallel_read) end local _2_read_discrete_inputs = function (di_addr_start, count) + local tasks = {} local readings = {} local access_fault = false local discrete_inputs, _, _, _ = self.rtu.io_count() @@ -43,12 +63,30 @@ function new(rtu_dev, use_parallel_read) if return_ok then for i = 1, count do local addr = di_addr_start + i - 1 - readings[i], access_fault = self.rtu.read_di(addr) + + if self.use_parallel then + table.insert(tasks, function () + local reading, fault = self.rtu.read_di(addr) + if fault then access_fault = true else readings[i] = reading end + end) + else + readings[i], access_fault = self.rtu.read_di(addr) + + if access_fault then + return_ok = false + readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL + break + end + end + end + + -- run parallel tasks if configured + if self.use_parallel then + parallel.waitForAll(table.unpack(tasks)) if access_fault then return_ok = false readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL - break end end else @@ -59,6 +97,7 @@ function new(rtu_dev, use_parallel_read) end local _3_read_multiple_holding_registers = function (hr_addr_start, count) + local tasks = {} local readings = {} local access_fault = false local _, _, _, hold_regs = self.rtu.io_count() @@ -67,12 +106,30 @@ function new(rtu_dev, use_parallel_read) if return_ok then for i = 1, count do local addr = hr_addr_start + i - 1 - readings[i], access_fault = self.rtu.read_holding_reg(addr) + + if self.use_parallel then + table.insert(tasks, function () + local reading, fault = self.rtu.read_holding_reg(addr) + if fault then access_fault = true else readings[i] = reading end + end) + else + readings[i], access_fault = self.rtu.read_holding_reg(addr) + + if access_fault then + return_ok = false + readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL + break + end + end + end + + -- run parallel tasks if configured + if self.use_parallel then + parallel.waitForAll(table.unpack(tasks)) if access_fault then return_ok = false readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL - break end end else @@ -83,6 +140,7 @@ function new(rtu_dev, use_parallel_read) end local _4_read_input_registers = function (ir_addr_start, count) + local tasks = {} local readings = {} local access_fault = false local _, _, input_regs, _ = self.rtu.io_count() @@ -91,12 +149,30 @@ function new(rtu_dev, use_parallel_read) if return_ok then for i = 1, count do local addr = ir_addr_start + i - 1 - readings[i], access_fault = self.rtu.read_input_reg(addr) + + if self.use_parallel then + table.insert(tasks, function () + local reading, fault = self.rtu.read_input_reg(addr) + if fault then access_fault = true else readings[i] = reading end + end) + else + readings[i], access_fault = self.rtu.read_input_reg(addr) + + if access_fault then + return_ok = false + readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL + break + end + end + end + + -- run parallel tasks if configured + if self.use_parallel then + parallel.waitForAll(table.unpack(tasks)) if access_fault then return_ok = false readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL - break end end else diff --git a/rtu/startup.lua b/rtu/startup.lua index 5f467e4..1679115 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -8,10 +8,10 @@ os.loadAPI("scada-common/util.lua") os.loadAPI("scada-common/ppm.lua") os.loadAPI("scada-common/comms.lua") os.loadAPI("scada-common/mqueue.lua") -os.loadAPI("scada-common/modbus.lua") os.loadAPI("scada-common/rsio.lua") os.loadAPI("config.lua") +os.loadAPI("modbus.lua") os.loadAPI("rtu.lua") os.loadAPI("threads.lua") @@ -20,7 +20,7 @@ os.loadAPI("dev/boiler_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") -local RTU_VERSION = "alpha-v0.4.12" +local RTU_VERSION = "alpha-v0.4.13" local rtu_t = types.rtu_t @@ -147,7 +147,7 @@ for reactor_idx = 1, #rtu_redstone do reactor = rtu_redstone[reactor_idx].for_reactor, device = capabilities, -- use device field for redstone channels rtu = rs_rtu, - modbus_io = modbus.new(rs_rtu), + modbus_io = modbus.new(rs_rtu, false), modbus_busy = false, pkt_queue = nil, thread = nil @@ -199,7 +199,7 @@ for i = 1, #rtu_devices do reactor = rtu_devices[i].for_reactor, device = device, rtu = rtu_iface, - modbus_io = modbus.new(rtu_iface), + modbus_io = modbus.new(rtu_iface, true), modbus_busy = false, pkt_queue = mqueue.new(), thread = nil From 25c6b311f5cd900e0976d7dcadafe1d22feec1f2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 3 May 2022 17:10:42 -0400 Subject: [PATCH 145/587] clear status cache when connection is lost/reset, allow requesting of full status --- reactor-plc/plc.lua | 11 +++++++++-- reactor-plc/startup.lua | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index cccced9..a97e004 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -374,13 +374,14 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- reconnect a newly connected reactor local reconnect_reactor = function (reactor) self.reactor = reactor - _update_status_cache() + self.status_cache = nil end -- unlink from the server local unlink = function () self.linked = false self.r_seq_num = nil + self.status_cache = nil end -- close the connection to the server @@ -512,6 +513,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) local link_ack = packet.data[1] if link_ack == RPLC_LINKING.ALLOW then + self.status_cache = nil _send_struct() send_status(plc_state.degraded) log._debug("re-sent initial status data") @@ -530,6 +532,10 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) else log._debug("RPLC link req packet length mismatch") end + elseif packet.type == RPLC_TYPES.STATUS then + -- request of full status, clear cache first + self.status_cache = nil + send_status(plc_state.degraded) elseif packet.type == RPLC_TYPES.MEK_STRUCT then -- request for physical structure _send_struct() @@ -587,8 +593,9 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) println_ts("linked!") log._debug("RPLC link request approved") - -- reset remote sequence number + -- reset remote sequence number and cache self.r_seq_num = nil + self.status_cache = nil _send_struct() send_status(plc_state.degraded) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 5c9df15..66dc3b2 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -13,7 +13,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.5.0" +local R_PLC_VERSION = "alpha-v0.5.1" local print = util.print local println = util.println From fe5059dd514912316b0c2570b832356e1c656cad Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 3 May 2022 17:21:34 -0400 Subject: [PATCH 146/587] debug print --- reactor-plc/plc.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index a97e004..8aa65b1 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -536,6 +536,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) -- request of full status, clear cache first self.status_cache = nil send_status(plc_state.degraded) + log._debug("sent out status cache again, did supervisor miss it?") elseif packet.type == RPLC_TYPES.MEK_STRUCT then -- request for physical structure _send_struct() From e253a7b4ff72aaa726f75927ff4ea4ba6bb29326 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 3 May 2022 17:25:31 -0400 Subject: [PATCH 147/587] supervisor PLC session closing, re-requesting status cache if missing --- supervisor/session/plc.lua | 59 +++++++++++++++++++++++-------- supervisor/session/svsessions.lua | 35 +++++++++--------- supervisor/startup.lua | 2 +- 3 files changed, 65 insertions(+), 31 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index b4fbdd9..09aa033 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -7,6 +7,11 @@ local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES +local print = util.print +local println = util.println +local print_ts = util.print_ts +local println_ts = util.println_ts + -- retry time constants in ms local INITIAL_WAIT = 1500 local RETRY_PERIOD = 1000 @@ -38,6 +43,7 @@ function new_session(id, for_reactor, in_queue, out_queue) r_seq_num = nil, connected = true, received_struct = false, + received_status_cache = false, plc_conn_watchdog = util.new_watchdog(3), last_rtt = 0, -- periodic messages @@ -48,6 +54,7 @@ function new_session(id, for_reactor, in_queue, out_queue) -- when to next retry one of these requests retry_times = { struct_req = (util.time() + 500), + status_req = (util.time() + 500), scram_req = 0, enable_req = 0, burn_rate_req = 0, @@ -257,6 +264,7 @@ function new_session(id, for_reactor, in_queue, out_queue) local status = pcall(_copy_status, pkt.data[6]) if status then -- copied in status data OK + self.received_status_cache = true else -- error copying status data log._error(log_header .. "failed to parse status packet data") @@ -365,18 +373,6 @@ function new_session(id, for_reactor, in_queue, out_queue) -- get the session database local get_db = function () return self.sDB end - -- close the connection - local close = function () - self.plc_conn_watchdog.cancel() - self.connected = false - _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) - end - - -- check if a timer matches this session's watchdog - local check_wd = function (timer) - return timer == self.plc_conn_watchdog.get_timer() - end - -- get the reactor structure local get_struct = function () if self.received_struct then @@ -386,6 +382,29 @@ function new_session(id, for_reactor, in_queue, out_queue) end end + -- get the reactor structure + local get_status = function () + if self.received_status_cache then + return self.sDB.mek_status + else + return nil + end + end + + -- check if a timer matches this session's watchdog + local check_wd = function (timer) + return timer == self.plc_conn_watchdog.get_timer() + end + + -- close the connection + local close = function () + self.plc_conn_watchdog.cancel() + self.connected = false + _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) + println("connection to reactor " .. self.for_reactor .. " PLC closed by server") + log._info(log_header .. "session closed by server") + end + -- iterate the session local iterate = function () if self.connected then @@ -442,6 +461,8 @@ function new_session(id, for_reactor, in_queue, out_queue) -- exit if connection was closed if not self.connected then + self.plc_conn_watchdog.cancel() + println("connection to reactor " .. self.for_reactor .. " PLC closed by remote host") log._info(log_header .. "session closed by remote host") return self.connected end @@ -479,6 +500,15 @@ function new_session(id, for_reactor, in_queue, out_queue) end end + -- status cache request retry + + if not self.received_status_cache then + if rtimes.status_req - util.time() <= 0 then + _send(RPLC_TYPES.MEK_STATUS, {}) + rtimes.status_req = util.time() + RETRY_PERIOD + end + end + -- SCRAM request retry if not self.acks.scram then @@ -522,9 +552,10 @@ function new_session(id, for_reactor, in_queue, out_queue) return { get_id = get_id, get_db = get_db, - close = close, - check_wd = check_wd, get_struct = get_struct, + get_status = get_status, + check_wd = check_wd, + close = close, iterate = iterate } end diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index c111093..6612730 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -91,6 +91,22 @@ function establish_plc_session(local_port, remote_port, for_reactor) end end +-- cleanly close a session +local function _shutdown(session) + session.open = false + session.instance.close() + + -- send packets in out queue (namely the close packet) + while session.out_queue.ready() do + local msg = session.out_queue.pop() + if msg.qtype == mqueue.TYPE.PACKET then + self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) + end + end + + log._debug("closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) +end + -- check if a watchdog timer event matches that of one of the provided sessions local function _check_watchdogs(sessions, timer_event) for i = 1, #sessions do @@ -98,9 +114,8 @@ local function _check_watchdogs(sessions, timer_event) if session.open then local triggered = session.instance.check_wd(timer_event) if triggered then - log._debug("watchdog closing session " .. session.instance.get_id() .. " on remote port " .. session.r_port) - session.open = false - session.instance.close() + log._debug("watchdog closing session " .. session.instance.get_id() .. " on remote port " .. session.r_port .. "...") + _shutdown(session) end end end @@ -134,7 +149,6 @@ local function _iterate(sessions) end else session.open = false - session.instance.close() end end end @@ -189,18 +203,7 @@ local function _close(sessions) for i = 1, #sessions do local session = sessions[i] if session.open then - session.open = false - session.instance.close() - - -- send packets in out queue (namely the close packet) - while session.out_queue.ready() do - local msg = session.out_queue.pop() - if msg.qtype == mqueue.TYPE.PACKET then - self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) - end - end - - log._debug("closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) + _shutdown(session) end end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 8d174d4..53f6cfc 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -18,7 +18,7 @@ os.loadAPI("session/svsessions.lua") os.loadAPI("supervisor.lua") -local SUPERVISOR_VERSION = "alpha-v0.1.12" +local SUPERVISOR_VERSION = "alpha-v0.2.0" local print = util.print local println = util.println From 8b7ef47aad5920d23760265e21f297c182c51bd4 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 4 May 2022 10:00:21 -0400 Subject: [PATCH 148/587] removed references to alarms and now sends status on shutdown --- reactor-plc/startup.lua | 12 ++++++++++-- reactor-plc/threads.lua | 4 ---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 66dc3b2..21c8372 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -13,7 +13,7 @@ os.loadAPI("config.lua") os.loadAPI("plc.lua") os.loadAPI("threads.lua") -local R_PLC_VERSION = "alpha-v0.5.1" +local R_PLC_VERSION = "alpha-v0.5.2" local print = util.print local println = util.println @@ -145,11 +145,19 @@ if __shared_memory.networked then -- run threads parallel.waitForAll(main_thread.exec, iss_thread.exec, comms_thread_tx.exec, comms_thread_rx.exec, sp_ctrl_thread.exec) + + if plc_state.init_ok then + -- send status one last time after ISS shutdown + plc_comms.send_status(plc_state.degraded) + plc_comms.send_iss_status() + + -- close connection + plc_comms.close(conn_watchdog) + end else -- run threads, excluding comms parallel.waitForAll(main_thread.exec, iss_thread.exec) end --- send an alarm: plc_comms.send_alarm(ALARMS.PLC_SHUTDOWN) ? println_ts("exited") log._info("exited") diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index ee23710..4ba1ba5 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -92,7 +92,6 @@ function thread__main(smem, init) log._error("reactor disconnected!") plc_state.no_reactor = true plc_state.degraded = true - -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_PERI_DC) ? elseif networked and device.type == "modem" then -- we only care if this is our wireless modem if device.dev == plc_dev.modem then @@ -170,8 +169,6 @@ function thread__main(smem, init) -- check for termination request if event == "terminate" or ppm.should_terminate() then log._info("terminate requested, main thread exiting") - -- close connection - plc_comms.close(conn_watchdog) -- iss handles reactor shutdown plc_state.shutdown = true break @@ -293,7 +290,6 @@ function thread__iss(smem) println_ts("reactor disabled") log._info("iss thread reactor SCRAM OK") else - -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_LOST_CONTROL) ? println_ts("exiting, reactor failed to disable") log._error("iss thread failed to SCRAM reactor on exit") end From 1cb5a0789eb87d03f87d6d09beaa8db72135b9df Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 4 May 2022 11:23:45 -0400 Subject: [PATCH 149/587] #27 mekanism 10.1+ RTU support --- rtu/dev/boilerv_rtu.lua | 56 ++++++++++++++++++++++++++++++++++++++++ rtu/dev/imatrix_rtu.lua | 18 +++++++------ rtu/dev/turbinev_rtu.lua | 55 +++++++++++++++++++++++++++++++++++++++ rtu/startup.lua | 18 ++++++++++--- scada-common/types.lua | 4 ++- 5 files changed, 139 insertions(+), 12 deletions(-) create mode 100644 rtu/dev/boilerv_rtu.lua create mode 100644 rtu/dev/turbinev_rtu.lua diff --git a/rtu/dev/boilerv_rtu.lua b/rtu/dev/boilerv_rtu.lua new file mode 100644 index 0000000..c23b6e1 --- /dev/null +++ b/rtu/dev/boilerv_rtu.lua @@ -0,0 +1,56 @@ +-- #REQUIRES rtu.lua + +function new(boiler) + local self = { + rtu = rtu.rtu_init(), + boiler = boiler + } + + local rtu_interface = function () + return self.rtu + end + + -- discrete inputs -- + -- none + + -- coils -- + -- none + + -- input registers -- + -- multiblock properties + self.rtu.connect_input_reg(self.boiler.isFormed) + self.rtu.connect_input_reg(self.boiler.getLength) + self.rtu.connect_input_reg(self.boiler.getWidth) + self.rtu.connect_input_reg(self.boiler.getHeight) + -- build properties + self.rtu.connect_input_reg(self.boiler.getBoilCapacity) + self.rtu.connect_input_reg(self.boiler.getSteamCapacity) + self.rtu.connect_input_reg(self.boiler.getWaterCapacity) + self.rtu.connect_input_reg(self.boiler.getHeatedCoolantCapacity) + self.rtu.connect_input_reg(self.boiler.getCooledCoolantCapacity) + self.rtu.connect_input_reg(self.boiler.getSuperheaters) + self.rtu.connect_input_reg(self.boiler.getMaxBoilRate) + -- current state + self.rtu.connect_input_reg(self.boiler.getTemperature) + self.rtu.connect_input_reg(self.boiler.getBoilRate) + -- tanks + self.rtu.connect_input_reg(self.boiler.getSteam) + self.rtu.connect_input_reg(self.boiler.getSteamNeeded) + self.rtu.connect_input_reg(self.boiler.getSteamFilledPercentage) + self.rtu.connect_input_reg(self.boiler.getWater) + self.rtu.connect_input_reg(self.boiler.getWaterNeeded) + self.rtu.connect_input_reg(self.boiler.getWaterFilledPercentage) + self.rtu.connect_input_reg(self.boiler.getHeatedCoolant) + self.rtu.connect_input_reg(self.boiler.getHeatedCoolantNeeded) + self.rtu.connect_input_reg(self.boiler.getHeatedCoolantFilledPercentage) + self.rtu.connect_input_reg(self.boiler.getCooledCoolant) + self.rtu.connect_input_reg(self.boiler.getCooledCoolantNeeded) + self.rtu.connect_input_reg(self.boiler.getCooledCoolantFilledPercentage) + + -- holding registers -- + -- none + + return { + rtu_interface = rtu_interface + } +end diff --git a/rtu/dev/imatrix_rtu.lua b/rtu/dev/imatrix_rtu.lua index 43f49b4..f646da2 100644 --- a/rtu/dev/imatrix_rtu.lua +++ b/rtu/dev/imatrix_rtu.lua @@ -17,19 +17,21 @@ function new(imatrix) -- none -- input registers -- - -- @todo check these on Mekanism 10.1+ + -- multiblock properties + self.rtu.connect_input_reg(self.boiler.isFormed) + self.rtu.connect_input_reg(self.boiler.getLength) + self.rtu.connect_input_reg(self.boiler.getWidth) + self.rtu.connect_input_reg(self.boiler.getHeight) -- build properties + self.rtu.connect_input_reg(self.imatrix.getMaxEnergy) self.rtu.connect_input_reg(self.imatrix.getTransferCap) self.rtu.connect_input_reg(self.imatrix.getInstalledCells) self.rtu.connect_input_reg(self.imatrix.getInstalledProviders) - self.rtu.connect_input_reg(self.imatrix.getTotalMaxEnergy) -- containers - self.rtu.connect_input_reg(self.imatrix.getTotalEnergy) - self.rtu.connect_input_reg(self.imatrix.getTotalEnergyNeeded) - self.rtu.connect_input_reg(self.imatrix.getTotalEnergyFilledPercentage) - -- additional fields? check these on 10.1 - self.rtu.connect_input_reg(self.imatrix.getInputItem) - self.rtu.connect_input_reg(self.imatrix.getOutputItem) + self.rtu.connect_input_reg(self.imatrix.getEnergy) + self.rtu.connect_input_reg(self.imatrix.getEnergyNeeded) + self.rtu.connect_input_reg(self.imatrix.getEnergyFilledPercentage) + -- I/O rates self.rtu.connect_input_reg(self.imatrix.getLastInput) self.rtu.connect_input_reg(self.imatrix.getLastOutput) diff --git a/rtu/dev/turbinev_rtu.lua b/rtu/dev/turbinev_rtu.lua new file mode 100644 index 0000000..21525c5 --- /dev/null +++ b/rtu/dev/turbinev_rtu.lua @@ -0,0 +1,55 @@ +-- #REQUIRES rtu.lua + +function new(turbine) + local self = { + rtu = rtu.rtu_init(), + turbine = turbine + } + + local rtu_interface = function () + return self.rtu + end + + -- discrete inputs -- + -- none + + -- coils -- + self.rtu.connect_coil(function () self.turbine.incrementDumpingMode() end), function () end) + self.rtu.connect_coil(function () self.turbine.decrementDumpingMode() end), function () end) + + -- input registers -- + -- multiblock properties + self.rtu.connect_input_reg(self.boiler.isFormed) + self.rtu.connect_input_reg(self.boiler.getLength) + self.rtu.connect_input_reg(self.boiler.getWidth) + self.rtu.connect_input_reg(self.boiler.getHeight) + -- build properties + self.rtu.connect_input_reg(self.turbine.getBlades) + self.rtu.connect_input_reg(self.turbine.getCoils) + self.rtu.connect_input_reg(self.turbine.getVents) + self.rtu.connect_input_reg(self.turbine.getDispersers) + self.rtu.connect_input_reg(self.turbine.getCondensers) + self.rtu.connect_input_reg(self.turbine.getDumpingMode) + self.rtu.connect_input_reg(self.turbine.getSteamCapacity) + self.rtu.connect_input_reg(self.turbine.getMaxFlowRate) + self.rtu.connect_input_reg(self.turbine.getMaxWaterOutput) + self.rtu.connect_input_reg(self.turbine.getMaxProduction) + -- current state + self.rtu.connect_input_reg(self.turbine.getFlowRate) + self.rtu.connect_input_reg(self.turbine.getProductionRate) + self.rtu.connect_input_reg(self.turbine.getLastSteamInputRate) + -- tanks/containers + self.rtu.connect_input_reg(self.turbine.getSteam) + self.rtu.connect_input_reg(self.turbine.getSteamNeeded) + self.rtu.connect_input_reg(self.turbine.getSteamFilledPercentage) + self.rtu.connect_input_reg(self.turbine.getEnergy) + self.rtu.connect_input_reg(self.turbine.getEnergyNeeded) + self.rtu.connect_input_reg(self.turbine.getEnergyFilledPercentage) + + -- holding registers -- + self.rtu.conenct_holding_reg(self.turbine.setDumpingMode, self.turbine.getDumpingMode) + + return { + rtu_interface = rtu_interface + } +end diff --git a/rtu/startup.lua b/rtu/startup.lua index 1679115..697a528 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -17,10 +17,13 @@ os.loadAPI("threads.lua") os.loadAPI("dev/redstone_rtu.lua") os.loadAPI("dev/boiler_rtu.lua") +os.loadAPI("dev/boilerv_rtu.lua") +os.loadAPI("dev/energymachine_rtu.lua") os.loadAPI("dev/imatrix_rtu.lua") os.loadAPI("dev/turbine_rtu.lua") +os.loadAPI("dev/turbinev_rtu.lua") -local RTU_VERSION = "alpha-v0.4.13" +local RTU_VERSION = "alpha-v0.5.0" local rtu_t = types.rtu_t @@ -173,16 +176,25 @@ for i = 1, #rtu_devices do -- boiler multiblock rtu_type = rtu_t.boiler rtu_iface = boiler_rtu.new(device) + elseif type == "boilerValve" then + -- boiler multiblock (10.1+) + rtu_type = rtu_t.boiler_valve + rtu_iface = boilerv_rtu.new(device) elseif type == "turbine" then -- turbine multiblock rtu_type = rtu_t.turbine rtu_iface = turbine_rtu.new(device) + elseif type == "turbineValve" then + -- turbine multiblock (10.1+) + rtu_type = rtu_t.turbine_valve + rtu_iface = turbinev_rtu.new(device) elseif type == "mekanismMachine" then -- assumed to be an induction matrix multiblock, pre Mekanism 10.1 + -- also works with energy cubes rtu_type = rtu_t.energy_machine rtu_iface = energymachine_rtu.new(device) - elseif type == "inductionMatrix" then - -- induction matrix multiblock, post Mekanism 10.1 + elseif type == "inductionPort" then + -- induction matrix multiblock (10.1+) rtu_type = rtu_t.induction_matrix rtu_iface = imatrix_rtu.new(device) else diff --git a/scada-common/types.lua b/scada-common/types.lua index 866982a..8ae93cc 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -3,9 +3,11 @@ rtu_t = { redstone = "redstone", boiler = "boiler", + boiler_valve = "boiler_valve", turbine = "turbine", + turbine_valve = "turbine_valve", energy_machine = "emachine", - induction_matrix = "imatrix" + induction_matrix = "induction_matrix" } iss_status_t = { From 7bcb2607126d0a5a865ac2318d3c6332dcaf8817 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 4 May 2022 12:03:07 -0400 Subject: [PATCH 150/587] #27 added getMaxEnergy for turbine --- rtu/dev/turbinev_rtu.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/rtu/dev/turbinev_rtu.lua b/rtu/dev/turbinev_rtu.lua index 21525c5..9a38a55 100644 --- a/rtu/dev/turbinev_rtu.lua +++ b/rtu/dev/turbinev_rtu.lua @@ -31,6 +31,7 @@ function new(turbine) self.rtu.connect_input_reg(self.turbine.getCondensers) self.rtu.connect_input_reg(self.turbine.getDumpingMode) self.rtu.connect_input_reg(self.turbine.getSteamCapacity) + self.rtu.connect_input_reg(self.turbine.getMaxEnergy) self.rtu.connect_input_reg(self.turbine.getMaxFlowRate) self.rtu.connect_input_reg(self.turbine.getMaxWaterOutput) self.rtu.connect_input_reg(self.turbine.getMaxProduction) From b575899d46bc99cd049245f95f4849b1073777b1 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 4 May 2022 13:37:01 -0400 Subject: [PATCH 151/587] #33 lua module/require architecture changeover --- coordinator/coordinator.lua | 2 +- coordinator/startup.lua | 19 ++- reactor-plc/config.lua | 16 ++- reactor-plc/plc.lua | 99 +++++++------- reactor-plc/startup.lua | 48 ++++--- reactor-plc/threads.lua | 72 +++++----- rtu/config.lua | 28 ++-- rtu/dev/boiler_rtu.lua | 10 +- rtu/dev/boilerv_rtu.lua | 10 +- rtu/dev/energymachine_rtu.lua | 10 +- rtu/dev/imatrix_rtu.lua | 10 +- rtu/dev/redstone_rtu.lua | 13 +- rtu/dev/turbine_rtu.lua | 10 +- rtu/dev/turbinev_rtu.lua | 14 +- rtu/modbus.lua | 9 +- rtu/rtu.lua | 31 +++-- rtu/startup.lua | 67 +++++----- rtu/threads.lua | 65 ++++++--- scada-common/alarm.lua | 46 ++++--- scada-common/comms.lua | 56 +++++--- scada-common/log.lua | 23 ++-- scada-common/mqueue.lua | 10 +- scada-common/ppm.lua | 64 +++++---- scada-common/rsio.lua | 37 ++++-- scada-common/types.lua | 14 +- scada-common/util.lua | 43 +++--- supervisor/config.lua | 14 +- supervisor/session/coordinator.lua | 3 + supervisor/session/plc.lua | 66 +++++----- supervisor/session/rtu.lua | 3 + supervisor/session/svsessions.lua | 204 +++++++++++++++-------------- supervisor/startup.lua | 48 ++++--- supervisor/supervisor.lua | 33 +++-- 33 files changed, 679 insertions(+), 518 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 96d766e..8089be8 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -1,4 +1,4 @@ --- #REQUIRES comms.lua +local comms = require("scada-common.comms") -- coordinator communications function coord_comms() diff --git a/coordinator/startup.lua b/coordinator/startup.lua index fe0bf48..5ee3d17 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -2,15 +2,14 @@ -- Nuclear Generation Facility SCADA Coordinator -- -os.loadAPI("scada-common/log.lua") -os.loadAPI("scada-common/util.lua") -os.loadAPI("scada-common/ppm.lua") -os.loadAPI("scada-common/comms.lua") +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") -os.loadAPI("coordinator/config.lua") -os.loadAPI("coordinator/coordinator.lua") +local config = require("config") +local coordinator = require("coordinator") -local COORDINATOR_VERSION = "alpha-v0.1.1" +local COORDINATOR_VERSION = "alpha-v0.1.2" local print = util.print local println = util.println @@ -19,9 +18,9 @@ local println_ts = util.println_ts log.init("/log.txt", log.MODE.APPEND) -log._info("========================================") -log._info("BOOTING coordinator.startup " .. COORDINATOR_VERSION) -log._info("========================================") +log.info("========================================") +log.info("BOOTING coordinator.startup " .. COORDINATOR_VERSION) +log.info("========================================") println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<") -- mount connected devices diff --git a/reactor-plc/config.lua b/reactor-plc/config.lua index 43086d5..99edc92 100644 --- a/reactor-plc/config.lua +++ b/reactor-plc/config.lua @@ -1,14 +1,18 @@ +local config = {} + -- set to false to run in offline mode (safety regulation only) -NETWORKED = true +config.NETWORKED = true -- unique reactor ID -REACTOR_ID = 1 +config.REACTOR_ID = 1 -- port to send packets TO server -SERVER_PORT = 16000 +config.SERVER_PORT = 16000 -- port to listen to incoming packets FROM server -LISTEN_PORT = 14001 +config.LISTEN_PORT = 14001 -- log path -LOG_PATH = "/log.txt" +config.LOG_PATH = "/log.txt" -- log mode -- 0 = APPEND (adds to existing file on start) -- 1 = NEW (replaces existing file on start) -LOG_MODE = 0 +config.LOG_MODE = 0 + +return config diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 8aa65b1..54f47d2 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -1,7 +1,10 @@ --- #REQUIRES types.lua --- #REQUIRES comms.lua --- #REQUIRES ppm.lua --- #REQUIRES util.lua +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") +local types = require("scada-common.types") +local util = require("scada-common.util") + +local plc = {} local iss_status_t = types.iss_status_t @@ -18,7 +21,7 @@ local println_ts = util.println_ts -- Internal Safety System -- identifies dangerous states and SCRAMs reactor if warranted -- autonomous from main SCADA supervisor/coordinator control -function iss_init(reactor) +plc.iss_init = function (reactor) local self = { reactor = reactor, cache = { false, false, false, false, false, false, false }, @@ -34,7 +37,7 @@ function iss_init(reactor) local damage_percent = self.reactor.getDamagePercent() if damage_percent == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log._error("ISS: failed to check reactor damage") + log.error("ISS: failed to check reactor damage") return false else return damage_percent >= 100 @@ -46,7 +49,7 @@ function iss_init(reactor) local hc_needed = self.reactor.getHeatedCoolantNeeded() if hc_needed == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log._error("ISS: failed to check reactor heated coolant level") + log.error("ISS: failed to check reactor heated coolant level") return false else return hc_needed == 0 @@ -58,7 +61,7 @@ function iss_init(reactor) local w_needed = self.reactor.getWasteNeeded() if w_needed == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log._error("ISS: failed to check reactor waste level") + log.error("ISS: failed to check reactor waste level") return false else return w_needed == 0 @@ -71,7 +74,7 @@ function iss_init(reactor) local temp = self.reactor.getTemperature() if temp == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log._error("ISS: failed to check reactor temperature") + log.error("ISS: failed to check reactor temperature") return false else return temp >= 1200 @@ -83,7 +86,7 @@ function iss_init(reactor) local fuel = self.reactor.getFuel() if fuel == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log._error("ISS: failed to check reactor fuel level") + log.error("ISS: failed to check reactor fuel level") return false else return fuel == 0 @@ -95,7 +98,7 @@ function iss_init(reactor) local coolant_filled = self.reactor.getCoolantFilledPercentage() if coolant_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log._error("ISS: failed to check reactor coolant level") + log.error("ISS: failed to check reactor coolant level") return false else return coolant_filled < 0.02 @@ -134,25 +137,25 @@ function iss_init(reactor) if self.tripped then status = self.trip_cause elseif self.cache[1] then - log._warning("ISS: damage critical!") + log.warning("ISS: damage critical!") status = iss_status_t.dmg_crit elseif self.cache[4] then - log._warning("ISS: high temperature!") + log.warning("ISS: high temperature!") status = iss_status_t.high_temp elseif self.cache[2] then - log._warning("ISS: heated coolant backup!") + log.warning("ISS: heated coolant backup!") status = iss_status_t.ex_hcoolant elseif self.cache[6] then - log._warning("ISS: no coolant!") + log.warning("ISS: no coolant!") status = iss_status_t.no_coolant elseif self.cache[3] then - log._warning("ISS: full waste!") + log.warning("ISS: full waste!") status = iss_status_t.ex_waste elseif self.cache[5] then - log._warning("ISS: no fuel!") + log.warning("ISS: no fuel!") status = iss_status_t.no_fuel elseif self.cache[7] then - log._warning("ISS: supervisor connection timeout!") + log.warning("ISS: supervisor connection timeout!") status = iss_status_t.timeout else self.tripped = false @@ -161,7 +164,7 @@ function iss_init(reactor) -- if a new trip occured... local first_trip = false if not was_tripped and status ~= iss_status_t.ok then - log._warning("ISS: reactor SCRAM") + log.warning("ISS: reactor SCRAM") first_trip = true self.tripped = true @@ -169,7 +172,7 @@ function iss_init(reactor) self.reactor.scram() if self.reactor.__p_is_faulted() then - log._error("ISS: failed reactor SCRAM") + log.error("ISS: failed reactor SCRAM") end end @@ -198,7 +201,7 @@ function iss_init(reactor) end -- reactor PLC communications -function comms_init(id, modem, local_port, server_port, reactor, iss) +plc.comms = function (id, modem, local_port, server_port, reactor, iss) local self = { id = id, seq_num = 0, @@ -355,7 +358,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) if not self.reactor.__p_is_faulted() then _send(RPLC_TYPES.MEK_STRUCT, mek_data) else - log._error("failed to send structure: PPM fault") + log.error("failed to send structure: PPM fault") end end @@ -417,7 +420,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) if not self.reactor.__p_is_faulted() then _send(RPLC_TYPES.STATUS, sys_status) else - log._error("failed to send status: PPM fault") + log.error("failed to send status: PPM fault") end end end @@ -463,7 +466,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) pkt = mgmt_pkt.get() end else - log._error("illegal packet type " .. s_pkt.protocol(), true) + log.error("illegal packet type " .. s_pkt.protocol(), true) end end @@ -477,7 +480,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) if self.r_seq_num == nil then self.r_seq_num = packet.scada_frame.seq_num() elseif self.linked and self.r_seq_num >= packet.scada_frame.seq_num() then - log._warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) return else self.r_seq_num = packet.scada_frame.seq_num() @@ -496,19 +499,19 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) local trip_time = util.time() - timestamp if trip_time > 500 then - log._warning("PLC KEEP_ALIVE trip time > 500ms (" .. trip_time .. ")") + log.warning("PLC KEEP_ALIVE trip time > 500ms (" .. trip_time .. ")") end - -- log._debug("RPLC RTT = ".. trip_time .. "ms") + -- log.debug("RPLC RTT = ".. trip_time .. "ms") _send_keep_alive_ack(timestamp) else - log._debug("RPLC keep alive packet length mismatch") + log.debug("RPLC keep alive packet length mismatch") end elseif packet.type == RPLC_TYPES.LINK_REQ then -- link request confirmation if packet.length == 1 then - log._debug("received unsolicited link request response") + log.debug("received unsolicited link request response") local link_ack = packet.data[1] @@ -516,31 +519,31 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) self.status_cache = nil _send_struct() send_status(plc_state.degraded) - log._debug("re-sent initial status data") + log.debug("re-sent initial status data") elseif link_ack == RPLC_LINKING.DENY then println_ts("received unsolicited link denial, unlinking") - log._debug("unsolicited RPLC link request denied") + log.debug("unsolicited RPLC link request denied") elseif link_ack == RPLC_LINKING.COLLISION then println_ts("received unsolicited link collision, unlinking") - log._warning("unsolicited RPLC link request collision") + log.warning("unsolicited RPLC link request collision") else println_ts("invalid unsolicited link response") - log._error("unsolicited unknown RPLC link request response") + log.error("unsolicited unknown RPLC link request response") end self.linked = link_ack == RPLC_LINKING.ALLOW else - log._debug("RPLC link req packet length mismatch") + log.debug("RPLC link req packet length mismatch") end elseif packet.type == RPLC_TYPES.STATUS then -- request of full status, clear cache first self.status_cache = nil send_status(plc_state.degraded) - log._debug("sent out status cache again, did supervisor miss it?") + log.debug("sent out status cache again, did supervisor miss it?") elseif packet.type == RPLC_TYPES.MEK_STRUCT then -- request for physical structure _send_struct() - log._debug("sent out structure again, did supervisor miss it?") + log.debug("sent out structure again, did supervisor miss it?") elseif packet.type == RPLC_TYPES.MEK_SCRAM then -- disable the reactor self.scrammed = true @@ -576,14 +579,14 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) _send_ack(packet.type, success) else - log._debug("RPLC set burn rate packet length mismatch") + log.debug("RPLC set burn rate packet length mismatch") end elseif packet.type == RPLC_TYPES.ISS_CLEAR then -- clear the ISS status iss.reset() _send_ack(packet.type, true) else - log._warning("received unknown RPLC packet type " .. packet.type) + log.warning("received unknown RPLC packet type " .. packet.type) end elseif packet.type == RPLC_TYPES.LINK_REQ then -- link request confirmation @@ -592,7 +595,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) if link_ack == RPLC_LINKING.ALLOW then println_ts("linked!") - log._debug("RPLC link request approved") + log.debug("RPLC link request approved") -- reset remote sequence number and cache self.r_seq_num = nil @@ -601,24 +604,24 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) _send_struct() send_status(plc_state.degraded) - log._debug("sent initial status data") + log.debug("sent initial status data") elseif link_ack == RPLC_LINKING.DENY then println_ts("link request denied, retrying...") - log._debug("RPLC link request denied") + log.debug("RPLC link request denied") elseif link_ack == RPLC_LINKING.COLLISION then println_ts("reactor PLC ID collision (check config), retrying...") - log._warning("RPLC link request collision") + log.warning("RPLC link request collision") else println_ts("invalid link response, bad channel? retrying...") - log._error("unknown RPLC link request response") + log.error("unknown RPLC link request response") end self.linked = link_ack == RPLC_LINKING.ALLOW else - log._debug("RPLC link req packet length mismatch") + log.debug("RPLC link req packet length mismatch") end else - log._debug("discarding non-link packet before linked") + log.debug("discarding non-link packet before linked") end elseif packet.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then -- handle session close @@ -626,9 +629,9 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) conn_watchdog.cancel() unlink() println_ts("server connection closed by remote host") - log._warning("server connection closed by remote host") + log.warning("server connection closed by remote host") else - log._warning("received unknown SCADA_MGMT packet type " .. packet.type) + log.warning("received unknown SCADA_MGMT packet type " .. packet.type) end end end @@ -652,3 +655,5 @@ function comms_init(id, modem, local_port, server_port, reactor, iss) is_linked = is_linked } end + +return plc diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 21c8372..dde16e7 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -2,18 +2,16 @@ -- Reactor Programmable Logic Controller -- -os.loadAPI("scada-common/log.lua") -os.loadAPI("scada-common/types.lua") -os.loadAPI("scada-common/util.lua") -os.loadAPI("scada-common/ppm.lua") -os.loadAPI("scada-common/comms.lua") -os.loadAPI("scada-common/mqueue.lua") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") -os.loadAPI("config.lua") -os.loadAPI("plc.lua") -os.loadAPI("threads.lua") +local config = require("config") +local plc = require("plc") +local threads = require("threads") -local R_PLC_VERSION = "alpha-v0.5.2" +local R_PLC_VERSION = "alpha-v0.6.0" local print = util.print local println = util.println @@ -22,9 +20,9 @@ local println_ts = util.println_ts log.init(config.LOG_PATH, config.LOG_MODE) -log._info("========================================") -log._info("BOOTING reactor-plc.startup " .. R_PLC_VERSION) -log._info("========================================") +log.info("========================================") +log.info("BOOTING reactor-plc.startup " .. R_PLC_VERSION) +log.info("========================================") println(">> Reactor PLC " .. R_PLC_VERSION .. " <<") -- mount connected devices @@ -78,7 +76,7 @@ local plc_state = __shared_memory.plc_state -- we need a reactor and a modem if smem_dev.reactor == nil then 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.degraded = true @@ -86,7 +84,7 @@ if smem_dev.reactor == nil then end if networked and smem_dev.modem == nil then println("boot> wireless modem not found") - log._warning("no wireless modem on startup") + log.warning("no wireless modem on startup") if smem_dev.reactor ~= nil then smem_dev.reactor.scram() @@ -104,19 +102,19 @@ function init() -- init internal safety system smem_sys.iss = plc.iss_init(smem_dev.reactor) - log._debug("iss init") + log.debug("iss init") if __shared_memory.networked then -- start comms - smem_sys.plc_comms = plc.comms_init(config.REACTOR_ID, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, smem_dev.reactor, smem_sys.iss) - log._debug("comms init") + smem_sys.plc_comms = plc.comms(config.REACTOR_ID, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, smem_dev.reactor, smem_sys.iss) + log.debug("comms init") -- comms watchdog, 3 second timeout smem_sys.conn_watchdog = util.new_watchdog(3) - log._debug("conn watchdog started") + log.debug("conn watchdog started") else println("boot> starting in offline mode"); - log._debug("running without networking") + log.debug("running without networking") end os.queueEvent("clock_start") @@ -124,7 +122,7 @@ function init() println("boot> completed"); else println("boot> system in degraded state, awaiting devices...") - log._warning("booted in a degraded state, awaiting peripheral connections...") + log.warning("booted in a degraded state, awaiting peripheral connections...") end end @@ -148,11 +146,11 @@ if __shared_memory.networked then if plc_state.init_ok then -- send status one last time after ISS shutdown - plc_comms.send_status(plc_state.degraded) - plc_comms.send_iss_status() + smem_sys.plc_comms.send_status(plc_state.degraded) + smem_sys.plc_comms.send_iss_status() -- close connection - plc_comms.close(conn_watchdog) + smem_sys.plc_comms.close(smem_sys.conn_watchdog) end else -- run threads, excluding comms @@ -160,4 +158,4 @@ else end println_ts("exited") -log._info("exited") +log.info("exited") diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 4ba1ba5..f689955 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -1,7 +1,9 @@ --- #REQUIRES comms.lua --- #REQUIRES log.lua --- #REQUIRES ppm.lua --- #REQUIRES util.lua +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") + +local threads = {} local print = util.print local println = util.println @@ -28,10 +30,10 @@ local MQ__COMM_CMD = { } -- main thread -function thread__main(smem, init) +threads.thread__main = function (smem, init) -- execute thread local exec = function () - log._debug("main thread init, clock inactive") + log.debug("main thread init, clock inactive") -- send status updates at 2Hz (every 10 server ticks) (every loop tick) -- send link requests at 0.5Hz (every 40 server ticks) (every 4 loop ticks) @@ -89,14 +91,14 @@ function thread__main(smem, init) if device.type == "fissionReactor" then println_ts("reactor disconnected!") - log._error("reactor disconnected!") + log.error("reactor disconnected!") plc_state.no_reactor = true plc_state.degraded = true elseif networked and device.type == "modem" then -- we only care if this is our wireless modem if device.dev == plc_dev.modem then println_ts("wireless modem disconnected!") - log._error("comms modem disconnected!") + log.error("comms modem disconnected!") plc_state.no_modem = true if plc_state.init_ok then @@ -106,7 +108,7 @@ function thread__main(smem, init) plc_state.degraded = true else - log._warning("non-comms modem disconnected") + log.warning("non-comms modem disconnected") end end elseif event == "peripheral" then @@ -120,7 +122,7 @@ function thread__main(smem, init) smem.q.mq_iss.push_command(MQ__ISS_CMD.SCRAM) println_ts("reactor reconnected.") - log._info("reactor reconnected.") + log.info("reactor reconnected.") plc_state.no_reactor = false if plc_state.init_ok then @@ -144,7 +146,7 @@ function thread__main(smem, init) end println_ts("wireless modem reconnected.") - log._info("comms modem reconnected.") + log.info("comms modem reconnected.") plc_state.no_modem = false -- determine if we are still in a degraded state @@ -152,7 +154,7 @@ function thread__main(smem, init) plc_state.degraded = false end else - log._info("wired modem reconnected.") + log.info("wired modem reconnected.") end end @@ -163,12 +165,12 @@ function thread__main(smem, init) elseif event == "clock_start" then -- start loop clock loop_clock = os.startTimer(MAIN_CLOCK) - log._debug("main thread clock started") + log.debug("main thread clock started") end -- check for termination request if event == "terminate" or ppm.should_terminate() then - log._info("terminate requested, main thread exiting") + log.info("terminate requested, main thread exiting") -- iss handles reactor shutdown plc_state.shutdown = true break @@ -180,10 +182,10 @@ function thread__main(smem, init) end -- ISS monitor thread -function thread__iss(smem) +threads.thread__iss = function (smem) -- execute thread local exec = function () - log._debug("iss thread start") + log.debug("iss thread start") -- load in from shared memory local networked = smem.networked @@ -257,17 +259,17 @@ function thread__iss(smem) plc_state.scram = true if reactor.scram() then println_ts("successful reactor SCRAM") - log._error("successful reactor SCRAM") + log.error("successful reactor SCRAM") else println_ts("failed reactor SCRAM") - log._error("failed reactor SCRAM") + log.error("failed reactor SCRAM") end elseif msg.message == MQ__ISS_CMD.TRIP_TIMEOUT then -- watchdog tripped plc_state.scram = true iss.trip_timeout() println_ts("server timeout") - log._warning("server timeout") + log.warning("server timeout") end elseif msg.qtype == mqueue.TYPE.DATA then -- received data @@ -282,19 +284,19 @@ function thread__iss(smem) -- check for termination request if plc_state.shutdown then -- safe exit - log._info("iss thread shutdown initiated") + log.info("iss thread shutdown initiated") if plc_state.init_ok then plc_state.scram = true reactor.scram() if reactor.__p_is_ok() then println_ts("reactor disabled") - log._info("iss thread reactor SCRAM OK") + log.info("iss thread reactor SCRAM OK") else println_ts("exiting, reactor failed to disable") - log._error("iss thread failed to SCRAM reactor on exit") + log.error("iss thread failed to SCRAM reactor on exit") end end - log._info("iss thread exiting") + log.info("iss thread exiting") break end @@ -307,10 +309,10 @@ function thread__iss(smem) end -- communications sender thread -function thread__comms_tx(smem) +threads.thread__comms_tx = function (smem) -- execute thread local exec = function () - log._debug("comms tx thread start") + log.debug("comms tx thread start") -- load in from shared memory local plc_state = smem.plc_state @@ -345,7 +347,7 @@ function thread__comms_tx(smem) -- check for termination request if plc_state.shutdown then - log._info("comms tx thread exiting") + log.info("comms tx thread exiting") break end @@ -358,10 +360,10 @@ function thread__comms_tx(smem) end -- communications handler thread -function thread__comms_rx(smem) +threads.thread__comms_rx = function (smem) -- execute thread local exec = function () - log._debug("comms rx thread start") + log.debug("comms rx thread start") -- load in from shared memory local plc_state = smem.plc_state @@ -397,7 +399,7 @@ function thread__comms_rx(smem) -- check for termination request if plc_state.shutdown then - log._info("comms rx thread exiting") + log.info("comms rx thread exiting") break end @@ -410,10 +412,10 @@ function thread__comms_rx(smem) end -- apply setpoints -function thread__setpoint_control(smem) +threads.thread__setpoint_control = function (smem) -- execute thread local exec = function () - log._debug("setpoint control thread start") + log.debug("setpoint control thread start") -- load in from shared memory local plc_state = smem.plc_state @@ -434,10 +436,10 @@ function thread__setpoint_control(smem) if not plc_state.scram then if math.abs(setpoints.burn_rate - last_sp_burn) <= 5 then -- update without ramp if <= 5 mB/t change - log._debug("setting burn rate directly to " .. setpoints.burn_rate .. "mB/t") + log.debug("setting burn rate directly to " .. setpoints.burn_rate .. "mB/t") reactor.setBurnRate(setpoints.burn_rate) else - log._debug("starting burn rate ramp from " .. last_sp_burn .. "mB/t to " .. setpoints.burn_rate .. "mB/t") + log.debug("starting burn rate ramp from " .. last_sp_burn .. "mB/t to " .. setpoints.burn_rate .. "mB/t") running = true end @@ -489,7 +491,7 @@ function thread__setpoint_control(smem) -- check for termination request if plc_state.shutdown then - log._info("setpoint control thread exiting") + log.info("setpoint control thread exiting") break end @@ -500,3 +502,5 @@ function thread__setpoint_control(smem) return { exec = exec } end + +return threads diff --git a/rtu/config.lua b/rtu/config.lua index 6ba5653..ec2b047 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -1,47 +1,49 @@ --- #REQUIRES rsio.lua +local rsio = require("scada-common.rsio") + +local config = {} -- port to send packets TO server -SERVER_PORT = 16000 +config.SERVER_PORT = 16000 -- port to listen to incoming packets FROM server -LISTEN_PORT = 15001 +config.LISTEN_PORT = 15001 -- log path -LOG_PATH = "/log.txt" +config.LOG_PATH = "/log.txt" -- log mode -- 0 = APPEND (adds to existing file on start) -- 1 = NEW (replaces existing file on start) -LOG_MODE = 0 +config.LOG_MODE = 0 -- RTU peripheral devices (named: side/network device name) -RTU_DEVICES = { +config.RTU_DEVICES = { { - name = "boiler_0", + name = "boiler_1", index = 1, for_reactor = 1 }, { - name = "turbine_0", + name = "turbine_1", index = 1, for_reactor = 1 } } -- RTU redstone interface definitions -RTU_REDSTONE = { +config.RTU_REDSTONE = { { for_reactor = 1, io = { { - channel = rsio.RS_IO.WASTE_PO, + channel = rsio.IO.WASTE_PO, side = "top", bundled_color = colors.blue, for_reactor = 1 }, { - channel = rsio.RS_IO.WASTE_PU, + channel = rsio.IO.WASTE_PU, side = "top", bundled_color = colors.cyan, for_reactor = 1 }, { - channel = rsio.RS_IO.WASTE_AM, + channel = rsio.IO.WASTE_AM, side = "top", bundled_color = colors.purple, for_reactor = 1 @@ -49,3 +51,5 @@ RTU_REDSTONE = { } } } + +return config diff --git a/rtu/dev/boiler_rtu.lua b/rtu/dev/boiler_rtu.lua index 861a34f..322c511 100644 --- a/rtu/dev/boiler_rtu.lua +++ b/rtu/dev/boiler_rtu.lua @@ -1,8 +1,10 @@ --- #REQUIRES rtu.lua +local rtu = require("rtu") -function new(boiler) +local boiler_rtu = {} + +boiler_rtu.new = function (boiler) local self = { - rtu = rtu.rtu_init(), + rtu = rtu.init_unit(), boiler = boiler } @@ -49,3 +51,5 @@ function new(boiler) rtu_interface = rtu_interface } end + +return boiler_rtu diff --git a/rtu/dev/boilerv_rtu.lua b/rtu/dev/boilerv_rtu.lua index c23b6e1..a609588 100644 --- a/rtu/dev/boilerv_rtu.lua +++ b/rtu/dev/boilerv_rtu.lua @@ -1,8 +1,10 @@ --- #REQUIRES rtu.lua +local rtu = require("rtu") -function new(boiler) +local boilerv_rtu = {} + +boilerv_rtu.new = function (boiler) local self = { - rtu = rtu.rtu_init(), + rtu = rtu.init_unit(), boiler = boiler } @@ -54,3 +56,5 @@ function new(boiler) rtu_interface = rtu_interface } end + +return boilerv_rtu diff --git a/rtu/dev/energymachine_rtu.lua b/rtu/dev/energymachine_rtu.lua index b3f004a..d2aee3f 100644 --- a/rtu/dev/energymachine_rtu.lua +++ b/rtu/dev/energymachine_rtu.lua @@ -1,8 +1,10 @@ --- #REQUIRES rtu.lua +local rtu = require("rtu") -function new(machine) +local energymachine_rtu = {} + +energymachine_rtu.new = function (machine) local self = { - rtu = rtu.rtu_init(), + rtu = rtu.init_unit(), machine = machine } @@ -31,3 +33,5 @@ function new(machine) rtu_interface = rtu_interface } end + +return energymachine_rtu diff --git a/rtu/dev/imatrix_rtu.lua b/rtu/dev/imatrix_rtu.lua index f646da2..12fd942 100644 --- a/rtu/dev/imatrix_rtu.lua +++ b/rtu/dev/imatrix_rtu.lua @@ -1,8 +1,10 @@ --- #REQUIRES rtu.lua +local rtu = require("rtu") -function new(imatrix) +local imatrix_rtu = {} + +imatrix_rtu.new = function (imatrix) local self = { - rtu = rtu.rtu_init(), + rtu = rtu.init_unit(), imatrix = imatrix } @@ -42,3 +44,5 @@ function new(imatrix) rtu_interface = rtu_interface } end + +return imatrix_rtu diff --git a/rtu/dev/redstone_rtu.lua b/rtu/dev/redstone_rtu.lua index d81cebb..163b749 100644 --- a/rtu/dev/redstone_rtu.lua +++ b/rtu/dev/redstone_rtu.lua @@ -1,13 +1,14 @@ --- #REQUIRES rtu.lua --- #REQUIRES rsio.lua --- note: this RTU makes extensive use of the programming concept of closures +local rtu = require("rtu") +local rsio = require("scada-common.rsio") + +local redstone_rtu = {} local digital_read = rsio.digital_read local digital_is_active = rsio.digital_is_active -function new() +redstone_rtu.new = function () local self = { - rtu = rtu.rtu_init() + rtu = rtu.init_unit() } local rtu_interface = function () @@ -91,3 +92,5 @@ function new() link_ao = link_ao } end + +return redstone_rtu diff --git a/rtu/dev/turbine_rtu.lua b/rtu/dev/turbine_rtu.lua index 7584270..1f1827f 100644 --- a/rtu/dev/turbine_rtu.lua +++ b/rtu/dev/turbine_rtu.lua @@ -1,8 +1,10 @@ --- #REQUIRES rtu.lua +local rtu = require("rtu") -function new(turbine) +local turbine_rtu = {} + +turbine_rtu.new = function (turbine) local self = { - rtu = rtu.rtu_init(), + rtu = rtu.init_unit(), turbine = turbine } @@ -44,3 +46,5 @@ function new(turbine) rtu_interface = rtu_interface } end + +return turbine_rtu diff --git a/rtu/dev/turbinev_rtu.lua b/rtu/dev/turbinev_rtu.lua index 9a38a55..2be532b 100644 --- a/rtu/dev/turbinev_rtu.lua +++ b/rtu/dev/turbinev_rtu.lua @@ -1,8 +1,10 @@ --- #REQUIRES rtu.lua +local rtu = require("rtu") -function new(turbine) +local turbinev_rtu = {} + +turbinev_rtu.new = function (turbine) local self = { - rtu = rtu.rtu_init(), + rtu = rtu.init_unit(), turbine = turbine } @@ -14,8 +16,8 @@ function new(turbine) -- none -- coils -- - self.rtu.connect_coil(function () self.turbine.incrementDumpingMode() end), function () end) - self.rtu.connect_coil(function () self.turbine.decrementDumpingMode() end), function () end) + self.rtu.connect_coil(function () self.turbine.incrementDumpingMode() end, function () end) + self.rtu.connect_coil(function () self.turbine.decrementDumpingMode() end, function () end) -- input registers -- -- multiblock properties @@ -54,3 +56,5 @@ function new(turbine) rtu_interface = rtu_interface } end + +return turbinev_rtu diff --git a/rtu/modbus.lua b/rtu/modbus.lua index 7ea6108..ac26fc1 100644 --- a/rtu/modbus.lua +++ b/rtu/modbus.lua @@ -1,10 +1,13 @@ --- #REQUIRES types.lua +local comms = require("scada-common.comms") +local types = require("scada-common.types") + +local modbus = {} local MODBUS_FCODE = types.MODBUS_FCODE local MODBUS_EXCODE = types.MODBUS_EXCODE -- new modbus comms handler object -function new(rtu_dev, use_parallel_read) +modbus.new = function (rtu_dev, use_parallel_read) local self = { rtu = rtu_dev, use_parallel = use_parallel_read @@ -401,3 +404,5 @@ function new(rtu_dev, use_parallel_read) reply__gw_unavailable = reply__gw_unavailable } end + +return modbus diff --git a/rtu/rtu.lua b/rtu/rtu.lua index b9a3061..9c22559 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -1,12 +1,15 @@ --- #REQUIRES comms.lua --- #REQUIRES modbus.lua --- #REQUIRES ppm.lua +local comms = require("scada-common.comms") +local ppm = require("scada-common.ppm") + +local modbus = require("modbus") + +local rtu = {} local PROTOCOLS = comms.PROTOCOLS local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local RTU_ADVERT_TYPES = comms.RTU_ADVERT_TYPES -function rtu_init() +rtu.init_unit = function () local self = { discrete_inputs = {}, coils = {}, @@ -117,7 +120,7 @@ function rtu_init() } end -function rtu_comms(modem, local_port, server_port) +rtu.comms = function (modem, local_port, server_port) local self = { seq_num = 0, r_seq_num = nil, @@ -187,7 +190,7 @@ function rtu_comms(modem, local_port, server_port) pkt = mgmt_pkt.get() end else - log._error("illegal packet type " .. s_pkt.protocol(), true) + log.error("illegal packet type " .. s_pkt.protocol(), true) end end @@ -203,7 +206,7 @@ function rtu_comms(modem, local_port, server_port) if self.r_seq_num == nil then self.r_seq_num = packet.scada_frame.seq_num() elseif rtu_state.linked and self.r_seq_num >= packet.scada_frame.seq_num() then - log._warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) return else self.r_seq_num = packet.scada_frame.seq_num() @@ -224,7 +227,7 @@ function rtu_comms(modem, local_port, server_port) -- immediately execute redstone RTU requests local return_code, reply = unit.modbus_io.handle_packet(packet) if not return_code then - log._warning("requested MODBUS operation failed") + log.warning("requested MODBUS operation failed") end else -- check validity then pass off to unit comms thread @@ -237,13 +240,13 @@ function rtu_comms(modem, local_port, server_port) unit.pkt_queue.push(packet) end else - log._warning("cannot perform requested MODBUS operation") + log.warning("cannot perform requested MODBUS operation") end end else -- unit ID out of range? reply = modbus.reply__gw_unavailable(packet) - log._error("MODBUS packet requesting non-existent unit") + log.error("MODBUS packet requesting non-existent unit") end send_modbus(reply) @@ -253,7 +256,7 @@ function rtu_comms(modem, local_port, server_port) -- close connection conn_watchdog.cancel() unlink(rtu_state) - if packet.type == SCADA_MGMT_TYPES.REMOTE_LINKED then + elseif packet.type == SCADA_MGMT_TYPES.REMOTE_LINKED then -- acknowledgement rtu_state.linked = true self.r_seq_num = nil @@ -262,11 +265,11 @@ function rtu_comms(modem, local_port, server_port) send_advertisement(units) else -- not supported - log._warning("RTU got unexpected SCADA message type " .. packet.type, true) + log.warning("RTU got unexpected SCADA message type " .. packet.type, true) end else -- should be unreachable assuming packet is from parse_packet() - log._error("illegal packet type " .. protocol, true) + log.error("illegal packet type " .. protocol, true) end end end @@ -337,3 +340,5 @@ function rtu_comms(modem, local_port, server_port) close = close } end + +return rtu diff --git a/rtu/startup.lua b/rtu/startup.lua index 697a528..4edcbc9 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -2,28 +2,27 @@ -- RTU: Remote Terminal Unit -- -os.loadAPI("scada-common/log.lua") -os.loadAPI("scada-common/types.lua") -os.loadAPI("scada-common/util.lua") -os.loadAPI("scada-common/ppm.lua") -os.loadAPI("scada-common/comms.lua") -os.loadAPI("scada-common/mqueue.lua") -os.loadAPI("scada-common/rsio.lua") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local ppm = require("scada-common.ppm") +local rsio = require("scada-common.rsio") +local types = require("scada-common.types") +local util = require("scada-common.util") -os.loadAPI("config.lua") -os.loadAPI("modbus.lua") -os.loadAPI("rtu.lua") -os.loadAPI("threads.lua") +local config = require("config") +local modbus = require("modbus") +local rtu = require("rtu") +local threads = require("threads") -os.loadAPI("dev/redstone_rtu.lua") -os.loadAPI("dev/boiler_rtu.lua") -os.loadAPI("dev/boilerv_rtu.lua") -os.loadAPI("dev/energymachine_rtu.lua") -os.loadAPI("dev/imatrix_rtu.lua") -os.loadAPI("dev/turbine_rtu.lua") -os.loadAPI("dev/turbinev_rtu.lua") +local redstone_rtu = require("dev.redstone_rtu") +local boiler_rtu = require("dev.boiler_rtu") +local boilerv_rtu = require("dev.boilerv_rtu") +local energymachine_rtu = require("dev.energymachine_rtu") +local imatrix_rtu = require("dev.imatrix_rtu") +local turbine_rtu = require("dev.turbine_rtu") +local turbinev_rtu = require("dev.turbinev_rtu") -local RTU_VERSION = "alpha-v0.5.0" +local RTU_VERSION = "alpha-v0.6.0" local rtu_t = types.rtu_t @@ -34,9 +33,9 @@ local println_ts = util.println_ts log.init(config.LOG_PATH, config.LOG_MODE) -log._info("========================================") -log._info("BOOTING rtu.startup " .. RTU_VERSION) -log._info("========================================") +log.info("========================================") +log.info("BOOTING rtu.startup " .. RTU_VERSION) +log.info("========================================") println(">> RTU " .. RTU_VERSION .. " <<") ---------------------------------------- @@ -77,11 +76,11 @@ local smem_sys = __shared_memory.rtu_sys -- get modem if smem_dev.modem == nil then println("boot> wireless modem not found") - log._warning("no wireless modem on startup") + log.warning("no wireless modem on startup") return end -smem_sys.rtu_comms = rtu.rtu_comms(smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT) +smem_sys.rtu_comms = rtu.comms(smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT) ---------------------------------------- -- interpret config and init units @@ -99,7 +98,7 @@ for reactor_idx = 1, #rtu_redstone do local capabilities = {} - log._debug("init> starting redstone RTU I/O linking for reactor " .. rtu_redstone[reactor_idx].for_reactor .. "...") + log.debug("init> starting redstone RTU I/O linking for reactor " .. rtu_redstone[reactor_idx].for_reactor .. "...") for i = 1, #io_table do local valid = false @@ -118,7 +117,7 @@ for reactor_idx = 1, #rtu_redstone do local message = "init> invalid redstone definition at index " .. i .. " in definition block #" .. reactor_idx .. " (for reactor " .. rtu_redstone[reactor_idx].for_reactor .. ")" println_ts(message) - log._warning(message) + log.warning(message) else -- link redstone in RTU local mode = rsio.get_io_mode(conf.channel) @@ -132,13 +131,13 @@ for reactor_idx = 1, #rtu_redstone do rs_rtu.link_ao(conf.channel, conf.side) else -- should be unreachable code, we already validated channels - log._error("init> fell through if chain attempting to identify IO mode", true) + log.error("init> fell through if chain attempting to identify IO mode", true) break end table.insert(capabilities, conf.channel) - log._debug("init> linked redstone " .. #capabilities .. ": " .. rsio.to_string(conf.channel) .. " (" .. conf.side .. + log.debug("init> linked redstone " .. #capabilities .. ": " .. rsio.to_string(conf.channel) .. " (" .. conf.side .. ") for reactor " .. rtu_redstone[reactor_idx].for_reactor) end end @@ -156,7 +155,7 @@ for reactor_idx = 1, #rtu_redstone do thread = nil }) - log._debug("init> initialized RTU unit #" .. #units .. ": redstone_io (redstone) [1] for reactor " .. rtu_redstone[reactor_idx].for_reactor) + log.debug("init> initialized RTU unit #" .. #units .. ": redstone_io (redstone) [1] for reactor " .. rtu_redstone[reactor_idx].for_reactor) end -- mounted peripherals @@ -166,7 +165,7 @@ for i = 1, #rtu_devices do if device == nil then local message = "init> '" .. rtu_devices[i].name .. "' not found" println_ts(message) - log._warning(message) + log.warning(message) else local type = ppm.get_type(rtu_devices[i].name) local rtu_iface = nil @@ -200,7 +199,7 @@ for i = 1, #rtu_devices do else local message = "init> device '" .. rtu_devices[i].name .. "' is not a known type (" .. type .. ")" println_ts(message) - log._warning(message) + log.warning(message) end if rtu_iface ~= nil then @@ -221,7 +220,7 @@ for i = 1, #rtu_devices do table.insert(units, rtu_unit) - log._debug("init> initialized RTU unit #" .. #units .. ": " .. rtu_devices[i].name .. " (" .. rtu_type .. ") [" .. + log.debug("init> initialized RTU unit #" .. #units .. ": " .. rtu_devices[i].name .. " (" .. rtu_type .. ") [" .. rtu_devices[i].index .. "] for reactor " .. rtu_devices[i].for_reactor) end end @@ -237,7 +236,7 @@ local comms_thread = threads.thread__comms(__shared_memory) -- start connection watchdog smem_sys.conn_watchdog = util.new_watchdog(5) -log._debug("init> conn watchdog started") +log.debug("init> conn watchdog started") -- assemble thread list local _threads = { main_thread.exec, comms_thread.exec } @@ -251,4 +250,4 @@ end parallel.waitForAll(table.unpack(_threads)) println_ts("exited") -log._info("exited") +log.info("exited") diff --git a/rtu/threads.lua b/rtu/threads.lua index 9cb2923..12f90f7 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -1,7 +1,22 @@ --- #REQUIRES comms.lua --- #REQUIRES log.lua --- #REQUIRES ppm.lua --- #REQUIRES util.lua +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") +local types = require("scada-common.types") +local util = require("scada-common.util") + +local redstone_rtu = require("dev.redstone_rtu") +local boiler_rtu = require("dev.boiler_rtu") +local boilerv_rtu = require("dev.boilerv_rtu") +local energymachine_rtu = require("dev.energymachine_rtu") +local imatrix_rtu = require("dev.imatrix_rtu") +local turbine_rtu = require("dev.turbine_rtu") +local turbinev_rtu = require("dev.turbinev_rtu") + +local modbus = require("modbus") + +local threads = {} + +local rtu_t = types.rtu_t local print = util.print local println = util.println @@ -14,10 +29,10 @@ local MAIN_CLOCK = 2 -- (2Hz, 40 ticks) local COMMS_SLEEP = 150 -- (150ms, 3 ticks) -- main thread -function thread__main(smem) +threads.thread__main = function (smem) -- execute thread local exec = function () - log._debug("main thread start") + log.debug("main thread start") -- advertisement/heartbeat clock local loop_clock = os.startTimer(MAIN_CLOCK) @@ -62,9 +77,9 @@ function thread__main(smem) -- we only care if this is our wireless modem if device.dev == rtu_dev.modem then println_ts("wireless modem disconnected!") - log._warning("comms modem disconnected!") + log.warning("comms modem disconnected!") else - log._warning("non-comms modem disconnected") + log.warning("non-comms modem disconnected") end else for i = 1, #units do @@ -88,9 +103,9 @@ function thread__main(smem) rtu_comms.reconnect_modem(rtu_dev.modem) println_ts("wireless modem reconnected.") - log._info("comms modem reconnected.") + log.info("comms modem reconnected.") else - log._info("wired modem reconnected.") + log.info("wired modem reconnected.") end else -- relink lost peripheral to correct unit entry @@ -102,11 +117,17 @@ function thread__main(smem) -- found, re-link unit.device = device - if unit.type == "boiler" then + if unit.type == rtu_t.boiler then unit.rtu = boiler_rtu.new(device) - elseif unit.type == "turbine" then + elseif unit.type == rtu_t.boiler_valve then + unit.rtu = boilerv_rtu.new(device) + elseif unit.type == rtu_t.turbine then unit.rtu = turbine_rtu.new(device) - elseif unit.type == "imatrix" then + elseif unit.type == rtu_t.turbine_valve then + unit.rtu = turbinev_rtu.new(device) + elseif unit.type == rtu_t.energy_machine then + unit.rtu = energymachine_rtu.new(device) + elseif unit.type == rtu_t.induction_matrix then unit.rtu = imatrix_rtu.new(device) end @@ -121,7 +142,7 @@ function thread__main(smem) -- check for termination request if event == "terminate" or ppm.should_terminate() then rtu_state.shutdown = true - log._info("terminate requested, main thread exiting") + log.info("terminate requested, main thread exiting") break end end @@ -131,10 +152,10 @@ function thread__main(smem) end -- communications handler thread -function thread__comms(smem) +threads.thread__comms = function (smem) -- execute thread local exec = function () - log._debug("comms thread start") + log.debug("comms thread start") -- load in from shared memory local rtu_state = smem.rtu_state @@ -169,8 +190,8 @@ function thread__comms(smem) -- check for termination request if rtu_state.shutdown then - rtu_comms.close() - log._info("comms thread exiting") + rtu_comms.close(rtu_state) + log.info("comms thread exiting") break end @@ -183,10 +204,10 @@ function thread__comms(smem) end -- per-unit communications handler thread -function thread__unit_comms(smem, unit) +threads.thread__unit_comms = function (smem, unit) -- execute thread local exec = function () - log._debug("rtu unit thread start -> " .. unit.name .. "(" .. unit.type .. ")") + log.debug("rtu unit thread start -> " .. unit.name .. "(" .. unit.type .. ")") -- load in from shared memory local rtu_state = smem.rtu_state @@ -219,7 +240,7 @@ function thread__unit_comms(smem, unit) -- check for termination request if rtu_state.shutdown then - log._info("rtu unit thread exiting -> " .. unit.name .. "(" .. unit.type .. ")") + log.info("rtu unit thread exiting -> " .. unit.name .. "(" .. unit.type .. ")") break end @@ -230,3 +251,5 @@ function thread__unit_comms(smem, unit) return { exec = exec } end + +return threads diff --git a/scada-common/alarm.lua b/scada-common/alarm.lua index e8464a5..7c39bc4 100644 --- a/scada-common/alarm.lua +++ b/scada-common/alarm.lua @@ -1,4 +1,6 @@ --- #REQUIRES util.lua +local util = require("scada-common.util") + +local alarm = {} SEVERITY = { INFO = 0, -- basic info message @@ -9,7 +11,27 @@ SEVERITY = { EMERGENCY = 5 -- critical safety alarm } -function scada_alarm(severity, device, message) +alarm.SEVERITY = SEVERITY + +alarm.severity_to_string = function (severity) + if severity == SEVERITY.INFO then + return "INFO" + elseif severity == SEVERITY.WARNING then + return "WARNING" + elseif severity == SEVERITY.ALERT then + return "ALERT" + elseif severity == SEVERITY.FACILITY then + return "FACILITY" + elseif severity == SEVERITY.SAFETY then + return "SAFETY" + elseif severity == SEVERITY.EMERGENCY then + return "EMERGENCY" + else + return "UNKNOWN" + end +end + +alarm.scada_alarm = function (severity, device, message) local self = { time = util.time(), ts_string = os.date("[%H:%M:%S]"), @@ -19,7 +41,7 @@ function scada_alarm(severity, device, message) } local format = function () - return self.ts_string .. " [" .. severity_to_string(self.severity) .. "] (" .. self.device ") >> " .. self.message + return self.ts_string .. " [" .. alarm.severity_to_string(self.severity) .. "] (" .. self.device ") >> " .. self.message end local properties = function () @@ -37,20 +59,4 @@ function scada_alarm(severity, device, message) } end -function severity_to_string(severity) - if severity == SEVERITY.INFO then - return "INFO" - elseif severity == SEVERITY.WARNING then - return "WARNING" - elseif severity == SEVERITY.ALERT then - return "ALERT" - elseif severity == SEVERITY.FACILITY then - return "FACILITY" - elseif severity == SEVERITY.SAFETY then - return "SAFETY" - elseif severity == SEVERITY.EMERGENCY then - return "EMERGENCY" - else - return "UNKNOWN" - end -end +return alarm diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 581e493..17050a8 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -1,4 +1,10 @@ -PROTOCOLS = { +-- +-- Communications +-- + +local comms = {} + +local PROTOCOLS = { MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol RPLC = 1, -- reactor PLC protocol SCADA_MGMT = 2, -- SCADA supervisor management, device advertisements, etc @@ -6,7 +12,7 @@ PROTOCOLS = { COORD_API = 4 -- data/control packets for pocket computers to/from coordinators } -RPLC_TYPES = { +local RPLC_TYPES = { KEEP_ALIVE = 0, -- keep alive packets LINK_REQ = 1, -- linking requests STATUS = 2, -- reactor/system status @@ -19,13 +25,13 @@ RPLC_TYPES = { ISS_CLEAR = 9 -- clear ISS trip (if in bad state, will trip immediately) } -RPLC_LINKING = { +local RPLC_LINKING = { ALLOW = 0, -- link approved DENY = 1, -- link denied COLLISION = 2 -- link denied due to existing active link } -SCADA_MGMT_TYPES = { +local SCADA_MGMT_TYPES = { PING = 0, -- generic ping CLOSE = 1, -- close a connection REMOTE_LINKED = 2, -- remote device linked @@ -33,15 +39,21 @@ SCADA_MGMT_TYPES = { RTU_HEARTBEAT = 4 -- RTU heartbeat } -RTU_ADVERT_TYPES = { +local RTU_ADVERT_TYPES = { BOILER = 0, -- boiler TURBINE = 1, -- turbine IMATRIX = 2, -- induction matrix REDSTONE = 3 -- redstone I/O } +comms.PROTOCOLS = PROTOCOLS +comms.RPLC_TYPES = RPLC_TYPES +comms.RPLC_LINKING = RPLC_LINKING +comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES +comms.RTU_ADVERT_TYPES = RTU_ADVERT_TYPES + -- generic SCADA packet object -function scada_packet() +comms.scada_packet = function () local self = { modem_msg_in = nil, valid = false, @@ -124,7 +136,7 @@ end -- MODBUS packet -- modeled after MODBUS TCP packet -function modbus_packet() +comms.modbus_packet = function () local self = { frame = nil, raw = nil, @@ -165,11 +177,11 @@ function modbus_packet() return size_ok else - log._debug("attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true) + log.debug("attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true) return false end else - log._debug("nil frame encountered", true) + log.debug("nil frame encountered", true) return false end end @@ -201,7 +213,7 @@ function modbus_packet() end -- reactor PLC packet -function rplc_packet() +comms.rplc_packet = function () local self = { frame = nil, raw = nil, @@ -256,11 +268,11 @@ function rplc_packet() return ok else - log._debug("attempted RPLC parse of incorrect protocol " .. frame.protocol(), true) + log.debug("attempted RPLC parse of incorrect protocol " .. frame.protocol(), true) return false end else - log._debug("nil frame encountered", true) + log.debug("nil frame encountered", true) return false end end @@ -291,7 +303,7 @@ function rplc_packet() end -- SCADA management packet -function mgmt_packet() +comms.mgmt_packet = function () local self = { frame = nil, raw = nil, @@ -339,11 +351,11 @@ function mgmt_packet() return ok else - log._debug("attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true) + log.debug("attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true) return false end else - log._debug("nil frame encountered", true) + log.debug("nil frame encountered", true) return false end end @@ -374,7 +386,7 @@ end -- SCADA coordinator packet -- @todo -function coord_packet() +comms.coord_packet = function () local self = { frame = nil, raw = nil, @@ -418,11 +430,11 @@ function coord_packet() return ok else - log._debug("attempted COORD_DATA parse of incorrect protocol " .. frame.protocol(), true) + log.debug("attempted COORD_DATA parse of incorrect protocol " .. frame.protocol(), true) return false end else - log._debug("nil frame encountered", true) + log.debug("nil frame encountered", true) return false end end @@ -453,7 +465,7 @@ end -- coordinator API (CAPI) packet -- @todo -function capi_packet() +comms.capi_packet = function () local self = { frame = nil, raw = nil, @@ -497,11 +509,11 @@ function capi_packet() return ok else - log._debug("attempted COORD_API parse of incorrect protocol " .. frame.protocol(), true) + log.debug("attempted COORD_API parse of incorrect protocol " .. frame.protocol(), true) return false end else - log._debug("nil frame encountered", true) + log.debug("nil frame encountered", true) return false end end @@ -529,3 +541,5 @@ function capi_packet() get = get } end + +return comms diff --git a/scada-common/log.lua b/scada-common/log.lua index 1aafda3..39069ca 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -2,14 +2,17 @@ -- File System Logger -- --- we use extra short abbreviations since computer craft screens are very small --- underscores are used since some of these names are used elsewhere (e.g. 'debug' is a lua table) +local log = {} -MODE = { +-- we use extra short abbreviations since computer craft screens are very small + +local MODE = { APPEND = 0, NEW = 1 } +log.MODE = MODE + local LOG_DEBUG = true local log_path = "/log.txt" @@ -50,7 +53,7 @@ local _log = function (msg) end end -function init(path, write_mode) +log.init = function (path, write_mode) log_path = path mode = write_mode @@ -61,7 +64,7 @@ function init(path, write_mode) end end -function _debug(msg, trace) +log.debug = function (msg, trace) if LOG_DEBUG then local dbg_info = "" @@ -80,15 +83,15 @@ function _debug(msg, trace) end end -function _info(msg) +log.info = function (msg) _log("[INF] " .. msg) end -function _warning(msg) +log.warning = function (msg) _log("[WRN] " .. msg) end -function _error(msg, trace) +log.error = function (msg, trace) local dbg_info = "" if trace then @@ -105,6 +108,8 @@ function _error(msg, trace) _log("[ERR] " .. dbg_info .. msg) end -function _fatal(msg) +log.fatal = function (msg) _log("[FTL] " .. msg) end + +return log diff --git a/scada-common/mqueue.lua b/scada-common/mqueue.lua index dc3e47f..8ba14cd 100644 --- a/scada-common/mqueue.lua +++ b/scada-common/mqueue.lua @@ -2,13 +2,17 @@ -- Message Queue -- -TYPE = { +local mqueue = {} + +local TYPE = { COMMAND = 0, DATA = 1, PACKET = 2 } -function new() +mqueue.TYPE = TYPE + +mqueue.new = function () local queue = {} local length = function () @@ -57,3 +61,5 @@ function new() pop = pop } end + +return mqueue diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index f383bae..5e15724 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -1,10 +1,14 @@ --- #REQUIRES log.lua +local log = require("scada-common.log") -- -- Protected Peripheral Manager -- -ACCESS_FAULT = nil +local ppm = {} + +local ACCESS_FAULT = nil + +ppm.ACCESS_FAULT = ACCESS_FAULT ---------------------------- -- PRIVATE DATA/FUNCTIONS -- @@ -46,7 +50,7 @@ local peri_init = function (iface) _ppm_sys.faulted = true if not _ppm_sys.mute then - log._error("PPM: protected " .. key .. "() -> " .. result) + log.error("PPM: protected " .. key .. "() -> " .. result) end if result == "Terminated" then @@ -88,48 +92,48 @@ end -- REPORTING -- -- silence error prints -function disable_reporting() +ppm.disable_reporting = function () _ppm_sys.mute = true end -- allow error prints -function enable_reporting() +ppm.enable_reporting = function () _ppm_sys.mute = false end -- FAULT MEMORY -- -- enable automatically clearing fault flag -function enable_afc() +ppm.enable_afc = function () _ppm_sys.auto_cf = true end -- disable automatically clearing fault flag -function disable_afc() +ppm.disable_afc = function () _ppm_sys.auto_cf = false end -- check fault flag -function is_faulted() +ppm.is_faulted = function () return _ppm_sys.faulted end -- clear fault flag -function clear_fault() +ppm.clear_fault = function () _ppm_sys.faulted = false end -- TERMINATION -- -- if a caught error was a termination request -function should_terminate() +ppm.should_terminate = function () return _ppm_sys.terminate end -- MOUNTING -- -- mount all available peripherals (clears mounts first) -function mount_all() +ppm.mount_all = function () local ifaces = peripheral.getNames() _ppm_sys.mounts = {} @@ -137,23 +141,23 @@ function mount_all() for i = 1, #ifaces do _ppm_sys.mounts[ifaces[i]] = peri_init(ifaces[i]) - log._info("PPM: found a " .. _ppm_sys.mounts[ifaces[i]].type .. " (" .. ifaces[i] .. ")") + log.info("PPM: found a " .. _ppm_sys.mounts[ifaces[i]].type .. " (" .. ifaces[i] .. ")") end if #ifaces == 0 then - log._warning("PPM: mount_all() -> no devices found") + log.warning("PPM: mount_all() -> no devices found") end end -- mount a particular device -function mount(iface) +ppm.mount = function (iface) local ifaces = peripheral.getNames() local pm_dev = nil local pm_type = nil for i = 1, #ifaces do if iface == ifaces[i] then - log._info("PPM: mount(" .. iface .. ") -> found a " .. peripheral.getType(iface)) + log.info("PPM: mount(" .. iface .. ") -> found a " .. peripheral.getType(iface)) _ppm_sys.mounts[iface] = peri_init(iface) @@ -167,15 +171,15 @@ function mount(iface) end -- handle peripheral_detach event -function handle_unmount(iface) +ppm.handle_unmount = function (iface) -- what got disconnected? local lost_dev = _ppm_sys.mounts[iface] if lost_dev then local type = lost_dev.type - log._warning("PPM: lost device " .. type .. " mounted to " .. iface) + log.warning("PPM: lost device " .. type .. " mounted to " .. iface) else - log._error("PPM: lost device unknown to the PPM mounted to " .. iface) + log.error("PPM: lost device unknown to the PPM mounted to " .. iface) end return lost_dev @@ -184,31 +188,31 @@ end -- GENERAL ACCESSORS -- -- list all available peripherals -function list_avail() +ppm.list_avail = function () return peripheral.getNames() end -- list mounted peripherals -function list_mounts() +ppm.list_mounts = function () return _ppm_sys.mounts end -- get a mounted peripheral by side/interface -function get_periph(iface) +ppm.get_periph = function (iface) if _ppm_sys.mounts[iface] then return _ppm_sys.mounts[iface].dev else return nil end end -- get a mounted peripheral type by side/interface -function get_type(iface) +ppm.get_type = function (iface) if _ppm_sys.mounts[iface] then return _ppm_sys.mounts[iface].type else return nil end end -- get all mounted peripherals by type -function get_all_devices(name) +ppm.get_all_devices = function (name) local devices = {} for side, data in pairs(_ppm_sys.mounts) do @@ -221,7 +225,7 @@ function get_all_devices(name) end -- get a mounted peripheral by type (if multiple, returns the first) -function get_device(name) +ppm.get_device = function (name) local device = nil for side, data in pairs(_ppm_sys.mounts) do @@ -237,12 +241,12 @@ end -- SPECIFIC DEVICE ACCESSORS -- -- get the fission reactor (if multiple, returns the first) -function get_fission_reactor() - return get_device("fissionReactor") +ppm.get_fission_reactor = function () + return ppm.get_device("fissionReactor") end -- get the wireless modem (if multiple, returns the first) -function get_wireless_modem() +ppm.get_wireless_modem = function () local w_modem = nil for side, device in pairs(_ppm_sys.mounts) do @@ -256,6 +260,8 @@ function get_wireless_modem() end -- list all connected monitors -function list_monitors() - return get_all_devices("monitor") +ppm.list_monitors = function () + return ppm.get_all_devices("monitor") end + +return ppm diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index f56a8a4..fd71247 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -1,21 +1,27 @@ -IO_LVL = { +-- +-- Redstone I/O +-- + +local rsio = {} + +local IO_LVL = { LOW = 0, HIGH = 1 } -IO_DIR = { +local IO_DIR = { IN = 0, OUT = 1 } -IO_MODE = { +local IO_MODE = { DIGITAL_OUT = 0, DIGITAL_IN = 1, ANALOG_OUT = 2, ANALOG_IN = 3 } -RS_IO = { +local RS_IO = { -- digital inputs -- -- facility @@ -53,7 +59,12 @@ RS_IO = { A_T_FLOW_RATE = 21 -- turbine flow rate percentage } -function to_string(channel) +rsio.IO_LVL = IO_LVL +rsio.IO_DIR = IO_DIR +rsio.IO_MODE = IO_MODE +rsio.IO = RS_IO + +rsio.to_string = function (channel) local names = { "F_SCRAM", "F_AE2_LIVE", @@ -85,11 +96,11 @@ function to_string(channel) end end -function is_valid_channel(channel) +rsio.is_valid_channel = function (channel) return channel ~= nil and channel > 0 and channel <= RS_IO.A_T_FLOW_RATE end -function is_valid_side(side) +rsio.is_valid_side = function (side) if side ~= nil then for _, s in pairs(rs.getSides()) do if s == side then return true end @@ -98,7 +109,7 @@ function is_valid_side(side) return false end -function is_color(color) +rsio.is_color = function (color) return (color > 0) and (bit.band(color, (color - 1)) == 0); end @@ -149,7 +160,7 @@ local RS_DIO_MAP = { { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT } } -function get_io_mode(channel) +rsio.get_io_mode = function (channel) local modes = { IO_MODE.DIGITAL_IN, -- F_SCRAM IO_MODE.DIGITAL_IN, -- F_AE2_LIVE @@ -182,7 +193,7 @@ function get_io_mode(channel) end -- get digital IO level reading -function digital_read(rs_value) +rsio.digital_read = function (rs_value) if rs_value then return IO_LVL.HIGH else @@ -191,7 +202,7 @@ function digital_read(rs_value) end -- returns the level corresponding to active -function digital_write(channel, active) +rsio.digital_write = function (channel, active) if channel < RS_IO.WASTE_PO or channel > RS_IO.R_PLC_TIMEOUT then return IO_LVL.LOW else @@ -200,10 +211,12 @@ function digital_write(channel, active) end -- returns true if the level corresponds to active -function digital_is_active(channel, level) +rsio.digital_is_active = function (channel, level) if channel > RS_IO.R_ENABLE or channel > RS_IO.R_PLC_TIMEOUT then return false else return RS_DIO_MAP[channel]._f(level) end end + +return rsio diff --git a/scada-common/types.lua b/scada-common/types.lua index 8ae93cc..5346bca 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -1,6 +1,10 @@ +-- -- Global Types +-- -rtu_t = { +local types = {} + +types.rtu_t = { redstone = "redstone", boiler = "boiler", boiler_valve = "boiler_valve", @@ -10,7 +14,7 @@ rtu_t = { induction_matrix = "induction_matrix" } -iss_status_t = { +types.iss_status_t = { ok = "ok", dmg_crit = "dmg_crit", ex_hcoolant = "heated_coolant_backup", @@ -24,7 +28,7 @@ iss_status_t = { -- MODBUS -- modbus function codes -local MODBUS_FCODE = { +types.MODBUS_FCODE = { READ_COILS = 0x01, READ_DISCRETE_INPUTS = 0x02, READ_MUL_HOLD_REGS = 0x03, @@ -37,7 +41,7 @@ local MODBUS_FCODE = { } -- modbus exception codes -local MODBUS_EXCODE = { +types.MODBUS_EXCODE = { ILLEGAL_FUNCTION = 0x01, ILLEGAL_DATA_ADDR = 0x02, ILLEGAL_DATA_VALUE = 0x03, @@ -49,3 +53,5 @@ local MODBUS_EXCODE = { GATEWAY_PATH_UNAVAILABLE = 0x0A, GATEWAY_TARGET_TIMEOUT = 0x0B } + +return types diff --git a/scada-common/util.lua b/scada-common/util.lua index a226e9f..a963a08 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -1,70 +1,69 @@ +local util = {} + -- PRINT -- --- we are overwriting 'print' so save it first -local _print = print - -- print -function print(message) +util.print = function (message) term.write(message) end -- print line -function println(message) - _print(message) +util.println = function (message) + print(message) end -- timestamped print -function print_ts(message) +util.print_ts = function (message) term.write(os.date("[%H:%M:%S] ") .. message) end -- timestamped print line -function println_ts(message) - _print(os.date("[%H:%M:%S] ") .. message) +util.println_ts = function (message) + print(os.date("[%H:%M:%S] ") .. message) end -- TIME -- -function time_ms() +util.time_ms = function () return os.epoch('local') end -function time_s() +util.time_s = function () return os.epoch('local') / 1000 end -function time() - return time_ms() +util.time = function () + return util.time_ms() end -- PARALLELIZATION -- -- protected sleep call so we still are in charge of catching termination -function psleep(t) +util.psleep = function (t) pcall(os.sleep, t) end -- no-op to provide a brief pause (and a yield) -- EVENT_CONSUMER: this function consumes events -function nop() - psleep(0.05) +util.nop = function () + util.psleep(0.05) end -- attempt to maintain a minimum loop timing (duration of execution) -function adaptive_delay(target_timing, last_update) - local sleep_for = target_timing - (time() - last_update) +util.adaptive_delay = function (target_timing, last_update) + local sleep_for = target_timing - (util.time() - last_update) -- only if >50ms since worker loops already yield 0.05s if sleep_for >= 50 then - psleep(sleep_for / 1000.0) + util.psleep(sleep_for / 1000.0) end - return time() + return util.time() end -- WATCHDOG -- -- ComputerCraft OS Timer based Watchdog -- triggers a timer event if not fed within 'timeout' seconds -function new_watchdog(timeout) +util.new_watchdog = function (timeout) local self = { _timeout = timeout, _wd_timer = os.startTimer(timeout) @@ -93,3 +92,5 @@ function new_watchdog(timeout) cancel = cancel } end + +return util diff --git a/supervisor/config.lua b/supervisor/config.lua index b8ba7fa..734e820 100644 --- a/supervisor/config.lua +++ b/supervisor/config.lua @@ -1,12 +1,16 @@ +local config = {} + -- scada network listen for PLC's and RTU's -SCADA_DEV_LISTEN = 16000 +config.SCADA_DEV_LISTEN = 16000 -- listen port for SCADA supervisor access by coordinators -SCADA_SV_LISTEN = 16100 +config.SCADA_SV_LISTEN = 16100 -- expected number of reactors -NUM_REACTORS = 4 +config.NUM_REACTORS = 4 -- log path -LOG_PATH = "/log.txt" +config.LOG_PATH = "/log.txt" -- log mode -- 0 = APPEND (adds to existing file on start) -- 1 = NEW (replaces existing file on start) -LOG_MODE = 0 +config.LOG_MODE = 0 + +return config diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index e69de29..afa28c0 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -0,0 +1,3 @@ +local coordinator = {} + +return coordinator diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 09aa033..ee11819 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -1,7 +1,9 @@ --- #REQUIRES mqueue.lua --- #REQUIRES comms.lua --- #REQUIRES log.lua --- #REQUIRES util.lua +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local util = require("scada-common.util") + +local plc = {} local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES @@ -16,19 +18,21 @@ local println_ts = util.println_ts local INITIAL_WAIT = 1500 local RETRY_PERIOD = 1000 -PLC_S_CMDS = { +local PLC_S_CMDS = { SCRAM = 0, ENABLE = 1, BURN_RATE = 2, ISS_CLEAR = 3 } +plc.PLC_S_CMDS = PLC_S_CMDS + local PERIODICS = { KEEP_ALIVE = 2.0 } -- PLC supervisor session -function new_session(id, for_reactor, in_queue, out_queue) +plc.new_session = function (id, for_reactor, in_queue, out_queue) local log_header = "plc_session(" .. id .. "): " local self = { @@ -204,7 +208,7 @@ function new_session(id, for_reactor, in_queue, out_queue) if pkt.length == 1 then return pkt.data[1] else - log._warning(log_header .. "RPLC ACK length mismatch") + log.warning(log_header .. "RPLC ACK length mismatch") return nil end end @@ -215,7 +219,7 @@ function new_session(id, for_reactor, in_queue, out_queue) if self.r_seq_num == nil then self.r_seq_num = pkt.scada_frame.seq_num() elseif self.r_seq_num >= pkt.scada_frame.seq_num() then - log._warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else self.r_seq_num = pkt.scada_frame.seq_num() @@ -225,7 +229,7 @@ function new_session(id, for_reactor, in_queue, out_queue) if pkt.scada_frame.protocol() == PROTOCOLS.RPLC then -- check reactor ID if pkt.id ~= for_reactor then - log._warning(log_header .. "RPLC packet with ID not matching reactor ID: reactor " .. self.for_reactor .. " != " .. pkt.id) + log.warning(log_header .. "RPLC packet with ID not matching reactor ID: reactor " .. self.for_reactor .. " != " .. pkt.id) return end @@ -242,13 +246,13 @@ function new_session(id, for_reactor, in_queue, out_queue) self.last_rtt = srv_now - srv_start if self.last_rtt > 500 then - log._warning(log_header .. "PLC KEEP_ALIVE round trip time > 500ms (" .. self.last_rtt .. ")") + log.warning(log_header .. "PLC KEEP_ALIVE round trip time > 500ms (" .. self.last_rtt .. ")") end - -- log._debug(log_header .. "RPLC RTT = ".. self.last_rtt .. "ms") - -- log._debug(log_header .. "RPLC TT = ".. (srv_now - plc_send) .. "ms") + -- log.debug(log_header .. "RPLC RTT = ".. self.last_rtt .. "ms") + -- log.debug(log_header .. "RPLC TT = ".. (srv_now - plc_send) .. "ms") else - log._debug(log_header .. "RPLC keep alive packet length mismatch") + log.debug(log_header .. "RPLC keep alive packet length mismatch") end elseif pkt.type == RPLC_TYPES.STATUS then -- status packet received, update data @@ -267,11 +271,11 @@ function new_session(id, for_reactor, in_queue, out_queue) self.received_status_cache = true else -- error copying status data - log._error(log_header .. "failed to parse status packet data") + log.error(log_header .. "failed to parse status packet data") end end else - log._debug(log_header .. "RPLC status packet length mismatch") + log.debug(log_header .. "RPLC status packet length mismatch") end elseif pkt.type == RPLC_TYPES.MEK_STRUCT then -- received reactor structure, record it @@ -282,10 +286,10 @@ function new_session(id, for_reactor, in_queue, out_queue) self.received_struct = true else -- error copying structure data - log._error(log_header .. "failed to parse struct packet data") + log.error(log_header .. "failed to parse struct packet data") end else - log._debug(log_header .. "RPLC struct packet length mismatch") + log.debug(log_header .. "RPLC struct packet length mismatch") end elseif pkt.type == RPLC_TYPES.MEK_SCRAM then -- SCRAM acknowledgement @@ -294,7 +298,7 @@ function new_session(id, for_reactor, in_queue, out_queue) self.acks.scram = true self.sDB.control_state = false elseif ack == false then - log._debug(log_header .. "SCRAM failed!") + log.debug(log_header .. "SCRAM failed!") end elseif pkt.type == RPLC_TYPES.MEK_ENABLE then -- enable acknowledgement @@ -303,7 +307,7 @@ function new_session(id, for_reactor, in_queue, out_queue) self.acks.enable = true self.sDB.control_state = true elseif ack == false then - log._debug(log_header .. "enable failed!") + log.debug(log_header .. "enable failed!") end elseif pkt.type == RPLC_TYPES.MEK_BURN_RATE then -- burn rate acknowledgement @@ -311,7 +315,7 @@ function new_session(id, for_reactor, in_queue, out_queue) if ack then self.acks.burn_rate = true elseif ack == false then - log._debug(log_header .. "burn rate update failed!") + log.debug(log_header .. "burn rate update failed!") end elseif pkt.type == RPLC_TYPES.ISS_STATUS then -- ISS status packet received, copy data @@ -321,10 +325,10 @@ function new_session(id, for_reactor, in_queue, out_queue) -- copied in ISS status data OK else -- error copying ISS status data - log._error(log_header .. "failed to parse ISS status packet data") + log.error(log_header .. "failed to parse ISS status packet data") end else - log._debug(log_header .. "RPLC ISS status packet length mismatch") + log.debug(log_header .. "RPLC ISS status packet length mismatch") end elseif pkt.type == RPLC_TYPES.ISS_ALARM then -- ISS alarm @@ -337,10 +341,10 @@ function new_session(id, for_reactor, in_queue, out_queue) -- copied in ISS status data OK else -- error copying ISS status data - log._error(log_header .. "failed to parse ISS alarm status data") + log.error(log_header .. "failed to parse ISS alarm status data") end else - log._debug(log_header .. "RPLC ISS alarm packet length mismatch") + log.debug(log_header .. "RPLC ISS alarm packet length mismatch") end elseif pkt.type == RPLC_TYPES.ISS_CLEAR then -- ISS clear acknowledgement @@ -350,17 +354,17 @@ function new_session(id, for_reactor, in_queue, out_queue) self.sDB.iss_tripped = false self.sDB.iss_trip_cause = "ok" elseif ack == false then - log._debug(log_header .. "ISS clear failed") + log.debug(log_header .. "ISS clear failed") end else - log._debug(log_header .. "handler received unsupported RPLC packet type " .. pkt.type) + log.debug(log_header .. "handler received unsupported RPLC packet type " .. pkt.type) end elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then if pkt.type == SCADA_MGMT_TYPES.CLOSE then -- close the session self.connected = false else - log._debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) + log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) end end end @@ -402,7 +406,7 @@ function new_session(id, for_reactor, in_queue, out_queue) self.connected = false _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) println("connection to reactor " .. self.for_reactor .. " PLC closed by server") - log._info(log_header .. "session closed by server") + log.info(log_header .. "session closed by server") end -- iterate the session @@ -454,7 +458,7 @@ function new_session(id, for_reactor, in_queue, out_queue) -- max 100ms spent processing queue if util.time() - handle_start > 100 then - log._warning(log_header .. "exceeded 100ms queue process limit") + log.warning(log_header .. "exceeded 100ms queue process limit") break end end @@ -463,7 +467,7 @@ function new_session(id, for_reactor, in_queue, out_queue) if not self.connected then self.plc_conn_watchdog.cancel() println("connection to reactor " .. self.for_reactor .. " PLC closed by remote host") - log._info(log_header .. "session closed by remote host") + log.info(log_header .. "session closed by remote host") return self.connected end @@ -559,3 +563,5 @@ function new_session(id, for_reactor, in_queue, out_queue) iterate = iterate } end + +return plc diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index e69de29..9051425 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -0,0 +1,3 @@ +local rtu = {} + +return rtu diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 6612730..578c8ae 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -1,14 +1,22 @@ --- #REQUIRES mqueue.lua --- #REQUIRES log.lua +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") + +local coordinator = require("session.coordinator") +local plc = require("session.plc") +local rtu = require("session.rtu") -- Supervisor Sessions Handler -SESSION_TYPE = { +local svsessions = {} + +local SESSION_TYPE = { RTU_SESSION = 0, PLC_SESSION = 1, COORD_SESSION = 2 } +svsessions.SESSION_TYPE = SESSION_TYPE + local self = { modem = nil, num_reactors = 0, @@ -20,12 +28,97 @@ local self = { next_coord_id = 0 } -function link_modem(modem) +-- PRIVATE FUNCTIONS -- + +-- iterate all the given sessions +local function _iterate(sessions) + for i = 1, #sessions do + local session = sessions[i] + if session.open then + local ok = session.instance.iterate() + if ok then + -- send packets in out queue + while session.out_queue.ready() do + local msg = session.out_queue.pop() + if msg.qtype == mqueue.TYPE.PACKET then + self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) + end + end + else + session.open = false + end + end + end +end + +-- cleanly close a session +local function _shutdown(session) + session.open = false + session.instance.close() + + -- send packets in out queue (namely the close packet) + while session.out_queue.ready() do + local msg = session.out_queue.pop() + if msg.qtype == mqueue.TYPE.PACKET then + self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) + end + end + + log.debug("closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) +end + +-- close connections +local function _close(sessions) + for i = 1, #sessions do + local session = sessions[i] + if session.open then + _shutdown(session) + end + end +end + +-- check if a watchdog timer event matches that of one of the provided sessions +local function _check_watchdogs(sessions, timer_event) + for i = 1, #sessions do + local session = sessions[i] + if session.open then + local triggered = session.instance.check_wd(timer_event) + if triggered then + log.debug("watchdog closing session " .. session.instance.get_id() .. " on remote port " .. session.r_port .. "...") + _shutdown(session) + end + end + end +end + +-- delete any closed sessions +local function _free_closed(sessions) + local move_to = 1 + for i = 1, #sessions do + local session = sessions[i] + if session ~= nil then + if sessions[i].open then + if sessions[move_to] == nil then + sessions[move_to] = session + sessions[i] = nil + end + move_to = move_to + 1 + else + log.debug("free'ing closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) + sessions[i] = nil + end + end + end +end + +-- PUBLIC FUNCTIONS -- + +svsessions.link_modem = function (modem) self.modem = modem end -- find a session by the remote port -function find_session(remote_port) +svsessions.find_session = function (remote_port) -- check RTU sessions for i = 1, #self.rtu_sessions do if self.rtu_sessions[i].r_port == remote_port then @@ -51,7 +144,7 @@ function find_session(remote_port) end -- get a session by reactor ID -function get_reactor_session(reactor) +svsessions.get_reactor_session = function (reactor) local session = nil for i = 1, #self.plc_sessions do @@ -64,8 +157,8 @@ function get_reactor_session(reactor) end -- establish a new PLC session -function establish_plc_session(local_port, remote_port, for_reactor) - if get_reactor_session(for_reactor) == nil then +svsessions.establish_plc_session = function (local_port, remote_port, for_reactor) + if svsessions.get_reactor_session(for_reactor) == nil then local plc_s = { open = true, reactor = for_reactor, @@ -79,7 +172,7 @@ function establish_plc_session(local_port, remote_port, for_reactor) plc_s.instance = plc.new_session(self.next_plc_id, for_reactor, plc_s.in_queue, plc_s.out_queue) table.insert(self.plc_sessions, plc_s) - log._debug("established new PLC session to " .. remote_port .. " with ID " .. self.next_plc_id) + log.debug("established new PLC session to " .. remote_port .. " with ID " .. self.next_plc_id) self.next_plc_id = self.next_plc_id + 1 @@ -91,38 +184,8 @@ function establish_plc_session(local_port, remote_port, for_reactor) end end --- cleanly close a session -local function _shutdown(session) - session.open = false - session.instance.close() - - -- send packets in out queue (namely the close packet) - while session.out_queue.ready() do - local msg = session.out_queue.pop() - if msg.qtype == mqueue.TYPE.PACKET then - self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) - end - end - - log._debug("closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) -end - --- check if a watchdog timer event matches that of one of the provided sessions -local function _check_watchdogs(sessions, timer_event) - for i = 1, #sessions do - local session = sessions[i] - if session.open then - local triggered = session.instance.check_wd(timer_event) - if triggered then - log._debug("watchdog closing session " .. session.instance.get_id() .. " on remote port " .. session.r_port .. "...") - _shutdown(session) - end - end - end -end - -- attempt to identify which session's watchdog timer fired -function check_all_watchdogs(timer_event) +svsessions.check_all_watchdogs = function (timer_event) -- check RTU session watchdogs _check_watchdogs(self.rtu_sessions, timer_event) @@ -133,29 +196,8 @@ function check_all_watchdogs(timer_event) _check_watchdogs(self.coord_sessions, timer_event) end --- iterate all the given sessions -local function _iterate(sessions) - for i = 1, #sessions do - local session = sessions[i] - if session.open then - local ok = session.instance.iterate() - if ok then - -- send packets in out queue - while session.out_queue.ready() do - local msg = session.out_queue.pop() - if msg.qtype == mqueue.TYPE.PACKET then - self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) - end - end - else - session.open = false - end - end - end -end - -- iterate all sessions -function iterate_all() +svsessions.iterate_all = function () -- iterate RTU sessions _iterate(self.rtu_sessions) @@ -166,28 +208,8 @@ function iterate_all() _iterate(self.coord_sessions) end --- delete any closed sessions -local function _free_closed(sessions) - local move_to = 1 - for i = 1, #sessions do - local session = sessions[i] - if session ~= nil then - if sessions[i].open then - if sessions[move_to] == nil then - sessions[move_to] = session - sessions[i] = nil - end - move_to = move_to + 1 - else - log._debug("free'ing closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) - sessions[i] = nil - end - end - end -end - -- delete all closed sessions -function free_all_closed() +svsessions.free_all_closed = function () -- free closed RTU sessions _free_closed(self.rtu_sessions) @@ -198,23 +220,15 @@ function free_all_closed() _free_closed(self.coord_sessions) end --- close connections -local function _close(sessions) - for i = 1, #sessions do - local session = sessions[i] - if session.open then - _shutdown(session) - end - end -end - -- close all open connections -function close_all() +svsessions.close_all = function () -- close sessions _close(self.rtu_sessions) _close(self.plc_sessions) _close(self.coord_sessions) -- free sessions - free_all_closed() + svsessions.free_all_closed() end + +return svsessions diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 53f6cfc..3059bab 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -2,23 +2,19 @@ -- Nuclear Generation Facility SCADA Supervisor -- -os.loadAPI("scada-common/log.lua") -os.loadAPI("scada-common/types.lua") -os.loadAPI("scada-common/util.lua") -os.loadAPI("scada-common/ppm.lua") -os.loadAPI("scada-common/comms.lua") -os.loadAPI("scada-common/mqueue.lua") +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") -os.loadAPI("config.lua") +local coordinator = require("session.coordinator") +local plc = require("session.plc") +local rtu = require("session.rtu") +local svsessions = require("session.svsessions") -os.loadAPI("session/rtu.lua") -os.loadAPI("session/plc.lua") -os.loadAPI("session/coordinator.lua") -os.loadAPI("session/svsessions.lua") +local config = require("config") +local supervisor = require("supervisor") -os.loadAPI("supervisor.lua") - -local SUPERVISOR_VERSION = "alpha-v0.2.0" +local SUPERVISOR_VERSION = "alpha-v0.3.0" local print = util.print local println = util.println @@ -27,9 +23,9 @@ local println_ts = util.println_ts log.init(config.LOG_PATH, config.LOG_MODE) -log._info("========================================") -log._info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION) -log._info("========================================") +log.info("========================================") +log.info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION) +log.info("========================================") println(">> SCADA Supervisor " .. SUPERVISOR_VERSION .. " <<") -- mount connected devices @@ -38,12 +34,12 @@ ppm.mount_all() local modem = ppm.get_wireless_modem() if modem == nil then println("boot> wireless modem not found") - log._warning("no wireless modem on startup") + log.warning("no wireless modem on startup") return end -- start comms, open all channels -local superv_comms = supervisor.superv_comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN) +local superv_comms = supervisor.comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN) -- base loop clock (6.67Hz, 3 ticks) local MAIN_CLOCK = 0.15 @@ -61,9 +57,9 @@ while true do -- we only care if this is our wireless modem if device.dev == modem then println_ts("wireless modem disconnected!") - log._error("comms modem disconnected!") + log.error("comms modem disconnected!") else - log._warning("non-comms modem disconnected") + log.warning("non-comms modem disconnected") end end elseif event == "peripheral" then @@ -76,9 +72,9 @@ while true do superv_comms.reconnect_modem(modem) println_ts("wireless modem reconnected.") - log._info("comms modem reconnected.") + log.info("comms modem reconnected.") else - log._info("wired modem reconnected.") + log.info("wired modem reconnected.") end end elseif event == "timer" and param1 == loop_clock then @@ -103,12 +99,12 @@ while true do -- check for termination request if event == "terminate" or ppm.should_terminate() then println_ts("closing sessions...") - log._info("terminate requested, closing sessions...") + log.info("terminate requested, closing sessions...") svsessions.close_all() - log._info("sessions closed") + log.info("sessions closed") break end end println_ts("exited") -log._info("exited") +log.info("exited") diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index edda143..3d4fa17 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -1,7 +1,10 @@ --- #REQUIRES comms.lua --- #REQUIRES mqueue.lua --- #REQUIRES util.lua --- #REQUIRES svsessions.lua +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local util = require("scada-common.util") + +local svsessions = require("session.svsessions") + +local supervisor = {} local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES @@ -17,7 +20,7 @@ local print_ts = util.print_ts local println_ts = util.println_ts -- supervisory controller communications -function superv_comms(num_reactors, modem, dev_listen, coord_listen) +supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) local self = { ln_seq_num = 0, num_reactors = num_reactors, @@ -101,7 +104,7 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) pkt = coord_pkt.get() end else - log._debug("attempted parse of illegal packet type " .. s_pkt.protocol(), true) + log.debug("attempted parse of illegal packet type " .. s_pkt.protocol(), true) end end @@ -126,7 +129,7 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) if session then if packet.type == RPLC_TYPES.LINK_REQ then -- new device on this port? that's a collision - log._debug("PLC_LNK: request from existing connection received on " .. r_port .. ", responding with collision") + log.debug("PLC_LNK: request from existing connection received on " .. r_port .. ", responding with collision") _send_plc_linking(r_port, { RPLC_LINKING.COLLISION }) else -- pass the packet onto the session handler @@ -140,20 +143,20 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) local plc_id = svsessions.establish_plc_session(l_port, r_port, packet.data[1]) if plc_id == false then -- reactor already has a PLC assigned - log._debug("PLC_LNK: assignment collision with reactor " .. packet.data[1]) + log.debug("PLC_LNK: assignment collision with reactor " .. packet.data[1]) _send_plc_linking(r_port, { RPLC_LINKING.COLLISION }) else -- got an ID; assigned to a reactor successfully println("connected to reactor " .. packet.data[1] .. " PLC (port " .. r_port .. ")") - log._debug("PLC_LNK: allowed for device at " .. r_port) + log.debug("PLC_LNK: allowed for device at " .. r_port) _send_plc_linking(r_port, { RPLC_LINKING.ALLOW }) end else - log._debug("PLC_LNK: new linking packet length mismatch") + log.debug("PLC_LNK: new linking packet length mismatch") end else -- force a re-link - log._debug("PLC_LNK: no session but not a link, force relink") + log.debug("PLC_LNK: no session but not a link, force relink") _send_plc_linking(r_port, { RPLC_LINKING.DENY }) end end @@ -164,7 +167,7 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) session.in_queue.push_packet(packet) end else - log._debug("illegal packet type " .. protocol .. " on device listening channel") + log.debug("illegal packet type " .. protocol .. " on device listening channel") end -- coordinator listening channel elseif l_port == self.coord_listen then @@ -173,10 +176,10 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) elseif protocol == PROTOCOLS.COORD_DATA then -- coordinator packet else - log._debug("illegal packet type " .. protocol .. " on coordinator listening channel") + log.debug("illegal packet type " .. protocol .. " on coordinator listening channel") end else - log._error("received packet on unused channel " .. l_port, true) + log.error("received packet on unused channel " .. l_port, true) end end end @@ -187,3 +190,5 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen) handle_packet = handle_packet } end + +return supervisor From c4df8eabf95c1c88c23b91871f161d7ff5a0a0dc Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 5 May 2022 11:55:04 -0400 Subject: [PATCH 152/587] #43 rename ISS to RPS --- reactor-plc/plc.lua | 92 +++++++++++++++++++------------------- reactor-plc/startup.lua | 24 +++++----- reactor-plc/threads.lua | 72 ++++++++++++++--------------- scada-common/comms.lua | 12 ++--- scada-common/types.lua | 2 +- supervisor/session/plc.lua | 92 +++++++++++++++++++------------------- supervisor/startup.lua | 2 +- 7 files changed, 148 insertions(+), 148 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 54f47d2..d53ad95 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -6,7 +6,7 @@ local util = require("scada-common.util") local plc = {} -local iss_status_t = types.iss_status_t +local rps_status_t = types.rps_status_t local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES @@ -18,10 +18,10 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts --- Internal Safety System +-- Reactor Protection System -- identifies dangerous states and SCRAMs reactor if warranted -- autonomous from main SCADA supervisor/coordinator control -plc.iss_init = function (reactor) +plc.rps_init = function (reactor) local self = { reactor = reactor, cache = { false, false, false, false, false, false, false }, @@ -37,7 +37,7 @@ plc.iss_init = function (reactor) local damage_percent = self.reactor.getDamagePercent() if damage_percent == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log.error("ISS: failed to check reactor damage") + log.error("RPS: failed to check reactor damage") return false else return damage_percent >= 100 @@ -49,7 +49,7 @@ plc.iss_init = function (reactor) local hc_needed = self.reactor.getHeatedCoolantNeeded() if hc_needed == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log.error("ISS: failed to check reactor heated coolant level") + log.error("RPS: failed to check reactor heated coolant level") return false else return hc_needed == 0 @@ -61,7 +61,7 @@ plc.iss_init = function (reactor) local w_needed = self.reactor.getWasteNeeded() if w_needed == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log.error("ISS: failed to check reactor waste level") + log.error("RPS: failed to check reactor waste level") return false else return w_needed == 0 @@ -74,7 +74,7 @@ plc.iss_init = function (reactor) local temp = self.reactor.getTemperature() if temp == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log.error("ISS: failed to check reactor temperature") + log.error("RPS: failed to check reactor temperature") return false else return temp >= 1200 @@ -86,7 +86,7 @@ plc.iss_init = function (reactor) local fuel = self.reactor.getFuel() if fuel == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log.error("ISS: failed to check reactor fuel level") + log.error("RPS: failed to check reactor fuel level") return false else return fuel == 0 @@ -98,7 +98,7 @@ plc.iss_init = function (reactor) local coolant_filled = self.reactor.getCoolantFilledPercentage() if coolant_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log.error("ISS: failed to check reactor coolant level") + log.error("RPS: failed to check reactor coolant level") return false else return coolant_filled < 0.02 @@ -119,7 +119,7 @@ plc.iss_init = function (reactor) -- check all safety conditions local check = function () - local status = iss_status_t.ok + local status = rps_status_t.ok local was_tripped = self.tripped -- update cache @@ -137,34 +137,34 @@ plc.iss_init = function (reactor) if self.tripped then status = self.trip_cause elseif self.cache[1] then - log.warning("ISS: damage critical!") - status = iss_status_t.dmg_crit + log.warning("RPS: damage critical!") + status = rps_status_t.dmg_crit elseif self.cache[4] then - log.warning("ISS: high temperature!") - status = iss_status_t.high_temp + log.warning("RPS: high temperature!") + status = rps_status_t.high_temp elseif self.cache[2] then - log.warning("ISS: heated coolant backup!") - status = iss_status_t.ex_hcoolant + log.warning("RPS: heated coolant backup!") + status = rps_status_t.ex_hcoolant elseif self.cache[6] then - log.warning("ISS: no coolant!") - status = iss_status_t.no_coolant + log.warning("RPS: no coolant!") + status = rps_status_t.no_coolant elseif self.cache[3] then - log.warning("ISS: full waste!") - status = iss_status_t.ex_waste + log.warning("RPS: full waste!") + status = rps_status_t.ex_waste elseif self.cache[5] then - log.warning("ISS: no fuel!") - status = iss_status_t.no_fuel + log.warning("RPS: no fuel!") + status = rps_status_t.no_fuel elseif self.cache[7] then - log.warning("ISS: supervisor connection timeout!") - status = iss_status_t.timeout + log.warning("RPS: supervisor connection timeout!") + status = rps_status_t.timeout else self.tripped = false end -- if a new trip occured... local first_trip = false - if not was_tripped and status ~= iss_status_t.ok then - log.warning("ISS: reactor SCRAM") + if not was_tripped and status ~= rps_status_t.ok then + log.warning("RPS: reactor SCRAM") first_trip = true self.tripped = true @@ -172,22 +172,22 @@ plc.iss_init = function (reactor) self.reactor.scram() if self.reactor.__p_is_faulted() then - log.error("ISS: failed reactor SCRAM") + log.error("RPS: failed reactor SCRAM") end end return self.tripped, status, first_trip end - -- get the ISS status + -- get the RPS status local status = function () return self.cache end local is_tripped = function () return self.tripped end - -- reset the ISS + -- reset the RPS local reset = function () self.timed_out = false self.tripped = false - self.trip_cause = iss_status_t.ok + self.trip_cause = rps_status_t.ok end return { @@ -201,7 +201,7 @@ plc.iss_init = function (reactor) end -- reactor PLC communications -plc.comms = function (id, modem, local_port, server_port, reactor, iss) +plc.comms = function (id, modem, local_port, server_port, reactor, rps) local self = { id = id, seq_num = 0, @@ -210,7 +210,7 @@ plc.comms = function (id, modem, local_port, server_port, reactor, iss) s_port = server_port, l_port = local_port, reactor = reactor, - iss = iss, + rps = rps, scrammed = false, linked = false, status_cache = nil, @@ -411,7 +411,7 @@ plc.comms = function (id, modem, local_port, server_port, reactor, iss) local sys_status = { util.time(), -- timestamp (not self.scrammed), -- enabled - iss.is_tripped(), -- overridden + rps.is_tripped(), -- overridden degraded, -- degraded self.reactor.getHeatingRate(), -- heating rate mek_data -- mekanism status data @@ -425,22 +425,22 @@ plc.comms = function (id, modem, local_port, server_port, reactor, iss) end end - -- send safety system status - local send_iss_status = function () + -- send reactor protection system status + local send_rps_status = function () if self.linked then - _send(RPLC_TYPES.ISS_STATUS, iss.status()) + _send(RPLC_TYPES.RPS_STATUS, rps.status()) end end - -- send safety system alarm - local send_iss_alarm = function (cause) + -- send reactor protection system alarm + local send_rps_alarm = function (cause) if self.linked then - local iss_alarm = { + local rps_alarm = { cause, - table.unpack(iss.status()) + table.unpack(rps.status()) } - _send(RPLC_TYPES.ISS_ALARM, iss_alarm) + _send(RPLC_TYPES.RPS_ALARM, rps_alarm) end end @@ -581,9 +581,9 @@ plc.comms = function (id, modem, local_port, server_port, reactor, iss) else log.debug("RPLC set burn rate packet length mismatch") end - elseif packet.type == RPLC_TYPES.ISS_CLEAR then - -- clear the ISS status - iss.reset() + elseif packet.type == RPLC_TYPES.RPS_RESET then + -- reset the RPS status + rps.reset() _send_ack(packet.type, true) else log.warning("received unknown RPLC packet type " .. packet.type) @@ -647,8 +647,8 @@ plc.comms = function (id, modem, local_port, server_port, reactor, iss) close = close, send_link_req = send_link_req, send_status = send_status, - send_iss_status = send_iss_status, - send_iss_alarm = send_iss_alarm, + send_rps_status = send_rps_status, + send_rps_alarm = send_rps_alarm, parse_packet = parse_packet, handle_packet = handle_packet, is_scrammed = is_scrammed, diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index dde16e7..e8654dc 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -11,7 +11,7 @@ local config = require("config") local plc = require("plc") local threads = require("threads") -local R_PLC_VERSION = "alpha-v0.6.0" +local R_PLC_VERSION = "alpha-v0.6.1" local print = util.print local println = util.println @@ -55,14 +55,14 @@ local __shared_memory = { -- system objects plc_sys = { - iss = nil, + rps = nil, plc_comms = nil, conn_watchdog = nil }, -- message queues q = { - mq_iss = mqueue.new(), + mq_rps = mqueue.new(), mq_comms_tx = mqueue.new(), mq_comms_rx = mqueue.new() } @@ -100,13 +100,13 @@ function init() -- just booting up, no fission allowed (neutrons stay put thanks) smem_dev.reactor.scram() - -- init internal safety system - smem_sys.iss = plc.iss_init(smem_dev.reactor) - log.debug("iss init") + -- init reactor protection system + smem_sys.rps = plc.rps_init(smem_dev.reactor) + log.debug("rps init") if __shared_memory.networked then -- start comms - smem_sys.plc_comms = plc.comms(config.REACTOR_ID, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, smem_dev.reactor, smem_sys.iss) + smem_sys.plc_comms = plc.comms(config.REACTOR_ID, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, smem_dev.reactor, smem_sys.rps) log.debug("comms init") -- comms watchdog, 3 second timeout @@ -131,7 +131,7 @@ init() -- init threads local main_thread = threads.thread__main(__shared_memory, init) -local iss_thread = threads.thread__iss(__shared_memory) +local rps_thread = threads.thread__rps(__shared_memory) if __shared_memory.networked then -- init comms threads @@ -142,19 +142,19 @@ if __shared_memory.networked then local sp_ctrl_thread = threads.thread__setpoint_control(__shared_memory) -- run threads - parallel.waitForAll(main_thread.exec, iss_thread.exec, comms_thread_tx.exec, comms_thread_rx.exec, sp_ctrl_thread.exec) + parallel.waitForAll(main_thread.exec, rps_thread.exec, comms_thread_tx.exec, comms_thread_rx.exec, sp_ctrl_thread.exec) if plc_state.init_ok then - -- send status one last time after ISS shutdown + -- send status one last time after RPS shutdown smem_sys.plc_comms.send_status(plc_state.degraded) - smem_sys.plc_comms.send_iss_status() + smem_sys.plc_comms.send_rps_status() -- close connection smem_sys.plc_comms.close(smem_sys.conn_watchdog) end else -- run threads, excluding comms - parallel.waitForAll(main_thread.exec, iss_thread.exec) + parallel.waitForAll(main_thread.exec, rps_thread.exec) end println_ts("exited") diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index f689955..5deaf10 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -13,13 +13,13 @@ local println_ts = util.println_ts local psleep = util.psleep local MAIN_CLOCK = 1 -- (1Hz, 20 ticks) -local ISS_SLEEP = 500 -- (500ms, 10 ticks) +local RPS_SLEEP = 500 -- (500ms, 10 ticks) local COMMS_SLEEP = 150 -- (150ms, 3 ticks) local SP_CTRL_SLEEP = 250 -- (250ms, 5 ticks) local BURN_RATE_RAMP_mB_s = 5.0 -local MQ__ISS_CMD = { +local MQ__RPS_CMD = { SCRAM = 1, DEGRADED_SCRAM = 2, TRIP_TIMEOUT = 3 @@ -45,7 +45,7 @@ threads.thread__main = function (smem, init) local networked = smem.networked local plc_state = smem.plc_state local plc_dev = smem.plc_dev - local iss = smem.plc_sys.iss + local rps = smem.plc_sys.rps local plc_comms = smem.plc_sys.plc_comms local conn_watchdog = smem.plc_sys.conn_watchdog @@ -84,7 +84,7 @@ threads.thread__main = function (smem, init) elseif event == "timer" and networked and param1 == conn_watchdog.get_timer() then -- haven't heard from server recently? shutdown reactor plc_comms.unlink() - smem.q.mq_iss.push_command(MQ__ISS_CMD.TRIP_TIMEOUT) + smem.q.mq_rps.push_command(MQ__RPS_CMD.TRIP_TIMEOUT) elseif event == "peripheral_detach" then -- peripheral disconnect local device = ppm.handle_unmount(param1) @@ -103,7 +103,7 @@ threads.thread__main = function (smem, init) if plc_state.init_ok then -- try to scram reactor if it is still connected - smem.q.mq_iss.push_command(MQ__ISS_CMD.DEGRADED_SCRAM) + smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM) end plc_state.degraded = true @@ -119,14 +119,14 @@ threads.thread__main = function (smem, init) -- reconnected reactor plc_dev.reactor = device - smem.q.mq_iss.push_command(MQ__ISS_CMD.SCRAM) + smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM) println_ts("reactor reconnected.") log.info("reactor reconnected.") plc_state.no_reactor = false if plc_state.init_ok then - iss.reconnect_reactor(plc_dev.reactor) + rps.reconnect_reactor(plc_dev.reactor) if networked then plc_comms.reconnect_reactor(plc_dev.reactor) end @@ -171,7 +171,7 @@ threads.thread__main = function (smem, init) -- check for termination request if event == "terminate" or ppm.should_terminate() then log.info("terminate requested, main thread exiting") - -- iss handles reactor shutdown + -- rps handles reactor shutdown plc_state.shutdown = true break end @@ -181,20 +181,20 @@ threads.thread__main = function (smem, init) return { exec = exec } end --- ISS monitor thread -threads.thread__iss = function (smem) +-- RPS operation thread +threads.thread__rps = function (smem) -- execute thread local exec = function () - log.debug("iss thread start") + log.debug("rps thread start") -- load in from shared memory local networked = smem.networked local plc_state = smem.plc_state local plc_dev = smem.plc_dev - local iss = smem.plc_sys.iss + local rps = smem.plc_sys.rps local plc_comms = smem.plc_sys.plc_comms - local iss_queue = smem.q.mq_iss + local rps_queue = smem.q.mq_rps local was_linked = false local last_update = util.time() @@ -203,14 +203,14 @@ threads.thread__iss = function (smem) while true do local reactor = plc_dev.reactor - -- ISS checks + -- RPS checks if plc_state.init_ok then -- SCRAM if no open connection if networked and not plc_comms.is_linked() then plc_state.scram = true if was_linked then was_linked = false - iss.trip_timeout() + rps.trip_timeout() end else -- would do elseif not networked but there is no reason to do that extra operation @@ -223,38 +223,38 @@ threads.thread__iss = function (smem) reactor.scram() end - -- if we are in standalone mode, continuously reset ISS - -- ISS will trip again if there are faults, but if it isn't cleared, the user can't re-enable + -- if we are in standalone mode, continuously reset RPS + -- RPS will trip again if there are faults, but if it isn't cleared, the user can't re-enable if not networked then plc_state.scram = false - iss.reset() + rps.reset() end -- check safety (SCRAM occurs if tripped) if not plc_state.no_reactor then - local iss_tripped, iss_status_string, iss_first = iss.check() - plc_state.scram = plc_state.scram or iss_tripped + local rps_tripped, rps_status_string, rps_first = rps.check() + plc_state.scram = plc_state.scram or rps_tripped - if iss_first then - println_ts("[ISS] SCRAM! safety trip: " .. iss_status_string) + if rps_first then + println_ts("[RPS] SCRAM! safety trip: " .. rps_status_string) if networked and not plc_state.no_modem then - plc_comms.send_iss_alarm(iss_status_string) + plc_comms.send_rps_alarm(rps_status_string) end end end end -- check for messages in the message queue - while iss_queue.ready() and not plc_state.shutdown do - local msg = iss_queue.pop() + while rps_queue.ready() and not plc_state.shutdown do + local msg = rps_queue.pop() if msg.qtype == mqueue.TYPE.COMMAND then -- received a command - if msg.message == MQ__ISS_CMD.SCRAM then + if msg.message == MQ__RPS_CMD.SCRAM then -- basic SCRAM plc_state.scram = true reactor.scram() - elseif msg.message == MQ__ISS_CMD.DEGRADED_SCRAM then + elseif msg.message == MQ__RPS_CMD.DEGRADED_SCRAM then -- SCRAM with print plc_state.scram = true if reactor.scram() then @@ -264,10 +264,10 @@ threads.thread__iss = function (smem) println_ts("failed reactor SCRAM") log.error("failed reactor SCRAM") end - elseif msg.message == MQ__ISS_CMD.TRIP_TIMEOUT then + elseif msg.message == MQ__RPS_CMD.TRIP_TIMEOUT then -- watchdog tripped plc_state.scram = true - iss.trip_timeout() + rps.trip_timeout() println_ts("server timeout") log.warning("server timeout") end @@ -284,24 +284,24 @@ threads.thread__iss = function (smem) -- check for termination request if plc_state.shutdown then -- safe exit - log.info("iss thread shutdown initiated") + log.info("rps thread shutdown initiated") if plc_state.init_ok then plc_state.scram = true reactor.scram() if reactor.__p_is_ok() then println_ts("reactor disabled") - log.info("iss thread reactor SCRAM OK") + log.info("rps thread reactor SCRAM OK") else println_ts("exiting, reactor failed to disable") - log.error("iss thread failed to SCRAM reactor on exit") + log.error("rps thread failed to SCRAM reactor on exit") end end - log.info("iss thread exiting") + log.info("rps thread exiting") break end -- delay before next check - last_update = util.adaptive_delay(ISS_SLEEP, last_update) + last_update = util.adaptive_delay(RPS_SLEEP, last_update) end end @@ -331,9 +331,9 @@ threads.thread__comms_tx = function (smem) if msg.qtype == mqueue.TYPE.COMMAND then -- received a command if msg.message == MQ__COMM_CMD.SEND_STATUS then - -- send PLC/ISS status + -- send PLC/RPS status plc_comms.send_status(plc_state.degraded) - plc_comms.send_iss_status() + plc_comms.send_rps_status() end elseif msg.qtype == mqueue.TYPE.DATA then -- received data diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 17050a8..da6f5d3 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -20,9 +20,9 @@ local RPLC_TYPES = { MEK_SCRAM = 4, -- SCRAM reactor MEK_ENABLE = 5, -- enable reactor MEK_BURN_RATE = 6, -- set burn rate - ISS_STATUS = 7, -- ISS status - ISS_ALARM = 8, -- ISS alarm broadcast - ISS_CLEAR = 9 -- clear ISS trip (if in bad state, will trip immediately) + RPS_STATUS = 7, -- RPS status + RPS_ALARM = 8, -- RPS alarm broadcast + RPS_RESET = 9 -- clear RPS trip (if in bad state, will trip immediately) } local RPLC_LINKING = { @@ -232,9 +232,9 @@ comms.rplc_packet = function () self.type == RPLC_TYPES.MEK_SCRAM or self.type == RPLC_TYPES.MEK_ENABLE or self.type == RPLC_TYPES.MEK_BURN_RATE or - self.type == RPLC_TYPES.ISS_ALARM or - self.type == RPLC_TYPES.ISS_STATUS or - self.type == RPLC_TYPES.ISS_CLEAR + self.type == RPLC_TYPES.RPS_ALARM or + self.type == RPLC_TYPES.RPS_STATUS or + self.type == RPLC_TYPES.RPS_RESET end -- make an RPLC packet diff --git a/scada-common/types.lua b/scada-common/types.lua index 5346bca..855334e 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -14,7 +14,7 @@ types.rtu_t = { induction_matrix = "induction_matrix" } -types.iss_status_t = { +types.rps_status_t = { ok = "ok", dmg_crit = "dmg_crit", ex_hcoolant = "heated_coolant_backup", diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index ee11819..bb79ac1 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -22,7 +22,7 @@ local PLC_S_CMDS = { SCRAM = 0, ENABLE = 1, BURN_RATE = 2, - ISS_CLEAR = 3 + RPS_RESET = 3 } plc.PLC_S_CMDS = PLC_S_CMDS @@ -62,14 +62,14 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) scram_req = 0, enable_req = 0, burn_rate_req = 0, - iss_clear_req = 0 + rps_reset_req = 0 }, -- command acknowledgements acks = { scram = true, enable = true, burn_rate = true, - iss_clear = true + rps_reset = true }, -- session database sDB = { @@ -77,9 +77,9 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) control_state = false, overridden = false, degraded = false, - iss_tripped = false, - iss_trip_cause = "ok", - iss_status = { + rps_tripped = false, + rps_trip_cause = "ok", + rps_status = { dmg_crit = false, ex_hcool = false, ex_waste = false, @@ -127,14 +127,14 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) } } - local _copy_iss_status = function (iss_status) - self.sDB.iss_status.dmg_crit = iss_status[1] - self.sDB.iss_status.ex_hcool = iss_status[2] - self.sDB.iss_status.ex_waste = iss_status[3] - self.sDB.iss_status.high_temp = iss_status[4] - self.sDB.iss_status.no_fuel = iss_status[5] - self.sDB.iss_status.no_cool = iss_status[6] - self.sDB.iss_status.timed_out = iss_status[7] + local _copy_rps_status = function (rps_status) + self.sDB.rps_status.dmg_crit = rps_status[1] + self.sDB.rps_status.ex_hcool = rps_status[2] + self.sDB.rps_status.ex_waste = rps_status[3] + self.sDB.rps_status.high_temp = rps_status[4] + self.sDB.rps_status.no_fuel = rps_status[5] + self.sDB.rps_status.no_cool = rps_status[6] + self.sDB.rps_status.timed_out = rps_status[7] end local _copy_status = function (mek_data) @@ -317,44 +317,44 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) elseif ack == false then log.debug(log_header .. "burn rate update failed!") end - elseif pkt.type == RPLC_TYPES.ISS_STATUS then - -- ISS status packet received, copy data + elseif pkt.type == RPLC_TYPES.RPS_STATUS then + -- RPS status packet received, copy data if pkt.length == 7 then - local status = pcall(_copy_iss_status, pkt.data) + local status = pcall(_copy_rps_status, pkt.data) if status then - -- copied in ISS status data OK + -- copied in RPS status data OK else - -- error copying ISS status data - log.error(log_header .. "failed to parse ISS status packet data") + -- error copying RPS status data + log.error(log_header .. "failed to parse RPS status packet data") end else - log.debug(log_header .. "RPLC ISS status packet length mismatch") + log.debug(log_header .. "RPLC RPS status packet length mismatch") end - elseif pkt.type == RPLC_TYPES.ISS_ALARM then - -- ISS alarm + elseif pkt.type == RPLC_TYPES.RPS_ALARM then + -- RPS alarm self.sDB.overridden = true if pkt.length == 8 then - self.sDB.iss_tripped = true - self.sDB.iss_trip_cause = pkt.data[1] - local status = pcall(_copy_iss_status, { table.unpack(pkt.data, 2, #pkt.length) }) + self.sDB.rps_tripped = true + self.sDB.rps_trip_cause = pkt.data[1] + local status = pcall(_copy_rps_status, { table.unpack(pkt.data, 2, #pkt.length) }) if status then - -- copied in ISS status data OK + -- copied in RPS status data OK else - -- error copying ISS status data - log.error(log_header .. "failed to parse ISS alarm status data") + -- error copying RPS status data + log.error(log_header .. "failed to parse RPS alarm status data") end else - log.debug(log_header .. "RPLC ISS alarm packet length mismatch") + log.debug(log_header .. "RPLC RPS alarm packet length mismatch") end - elseif pkt.type == RPLC_TYPES.ISS_CLEAR then - -- ISS clear acknowledgement + elseif pkt.type == RPLC_TYPES.RPS_RESET then + -- RPS reset acknowledgement local ack = _get_ack(pkt) if ack then - self.acks.iss_tripped = true - self.sDB.iss_tripped = false - self.sDB.iss_trip_cause = "ok" + self.acks.rps_tripped = true + self.sDB.rps_tripped = false + self.sDB.rps_trip_cause = "ok" elseif ack == false then - log.debug(log_header .. "ISS clear failed") + log.debug(log_header .. "RPS reset failed") end else log.debug(log_header .. "handler received unsupported RPLC packet type " .. pkt.type) @@ -438,11 +438,11 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) self.acks.enable = false self.retry_times.enable_req = util.time() + INITIAL_WAIT _send(RPLC_TYPES.MEK_ENABLE, {}) - elseif cmd == PLC_S_CMDS.ISS_CLEAR then - -- clear ISS - self.acks.iss_clear = false - self.retry_times.iss_clear_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.ISS_CLEAR, {}) + elseif cmd == PLC_S_CMDS.RPS_RESET then + -- reset RPS + self.acks.rps_reset = false + self.retry_times.rps_reset_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.RPS_RESET, {}) end elseif message.qtype == mqueue.TYPE.DATA then -- instruction with body @@ -540,12 +540,12 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end end - -- ISS clear request retry + -- RPS reset request retry - if not self.acks.iss_clear then - if rtimes.iss_clear_req - util.time() <= 0 then - _send(RPLC_TYPES.ISS_CLEAR, {}) - rtimes.iss_clear_req = util.time() + RETRY_PERIOD + if not self.acks.rps_reset then + if rtimes.rps_reset_req - util.time() <= 0 then + _send(RPLC_TYPES.RPS_RESET, {}) + rtimes.rps_reset_req = util.time() + RETRY_PERIOD end end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 3059bab..a93dab9 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("session.svsessions") local config = require("config") local supervisor = require("supervisor") -local SUPERVISOR_VERSION = "alpha-v0.3.0" +local SUPERVISOR_VERSION = "alpha-v0.3.1" local print = util.print local println = util.println From 89be79192fccb9982699f34c1f6fc59707476eaa Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 5 May 2022 13:14:14 -0400 Subject: [PATCH 153/587] #44 RPS optimizations, manual trip, RPS handles all reactor state control --- reactor-plc/plc.lua | 257 +++++++++++++++++++++++-------------- reactor-plc/startup.lua | 3 +- reactor-plc/threads.lua | 58 ++++----- scada-common/comms.lua | 10 +- scada-common/types.lua | 10 +- supervisor/session/plc.lua | 38 +++--- supervisor/startup.lua | 2 +- 7 files changed, 217 insertions(+), 161 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index d53ad95..68671b9 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -22,49 +22,48 @@ local println_ts = util.println_ts -- identifies dangerous states and SCRAMs reactor if warranted -- autonomous from main SCADA supervisor/coordinator control plc.rps_init = function (reactor) + local state_keys = { + dmg_crit = 1, + high_temp = 2, + no_coolant = 3, + ex_waste = 4, + ex_hcoolant = 5, + no_fuel = 6, + fault = 7, + timeout = 8, + manual = 9 + } + local self = { reactor = reactor, - cache = { false, false, false, false, false, false, false }, - timed_out = false, + state = { false, false, false, false, false, false, false, false, false }, + reactor_enabled = false, tripped = false, trip_cause = "" } -- PRIVATE FUNCTIONS -- + -- set reactor access fault flag + local _set_fault = function () + self.state[state_keys.fault] = true + end + + -- clear reactor access fault flag + local _clear_fault = function () + self.state[state_keys.fault] = false + end + -- check for critical damage local _damage_critical = function () local damage_percent = self.reactor.getDamagePercent() if damage_percent == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later log.error("RPS: failed to check reactor damage") - return false + _set_fault() + self.state[state_keys.dmg_crit] = false else - return damage_percent >= 100 - end - end - - -- check for heated coolant backup - local _excess_heated_coolant = function () - local hc_needed = self.reactor.getHeatedCoolantNeeded() - if hc_needed == ppm.ACCESS_FAULT then - -- lost the peripheral or terminated, handled later - log.error("RPS: failed to check reactor heated coolant level") - return false - else - return hc_needed == 0 - end - end - - -- check for excess waste - local _excess_waste = function () - local w_needed = self.reactor.getWasteNeeded() - if w_needed == ppm.ACCESS_FAULT then - -- lost the peripheral or terminated, handled later - log.error("RPS: failed to check reactor waste level") - return false - else - return w_needed == 0 + self.state[state_keys.dmg_crit] = damage_percent >= 100 end end @@ -75,9 +74,49 @@ plc.rps_init = function (reactor) if temp == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later log.error("RPS: failed to check reactor temperature") - return false + _set_fault() + self.state[state_keys.high_temp] = false else - return temp >= 1200 + self.state[state_keys.high_temp] = temp >= 1200 + end + end + + -- check if there is no coolant (<2% filled) + local _no_coolant = function () + local coolant_filled = self.reactor.getCoolantFilledPercentage() + if coolant_filled == ppm.ACCESS_FAULT then + -- lost the peripheral or terminated, handled later + log.error("RPS: failed to check reactor coolant level") + _set_fault() + self.state[state_keys.no_coolant] = false + else + self.state[state_keys.no_coolant] = coolant_filled < 0.02 + end + end + + -- check for excess waste (>80% filled) + local _excess_waste = function () + local w_filled = self.reactor.getWasteFilledPercentage() + if w_filled == ppm.ACCESS_FAULT then + -- lost the peripheral or terminated, handled later + log.error("RPS: failed to check reactor waste level") + _set_fault() + self.state[state_keys.ex_waste] = false + else + self.state[state_keys.ex_waste] = w_filled > 0.8 + end + end + + -- check for heated coolant backup (>95% filled) + local _excess_heated_coolant = function () + local hc_filled = self.reactor.getHeatedCoolantFilledPercentage() + if hc_filled == ppm.ACCESS_FAULT then + -- lost the peripheral or terminated, handled later + log.error("RPS: failed to check reactor heated coolant level") + _set_fault() + state[state_keys.ex_hcoolant] = false + else + state[state_keys.ex_hcoolant] = hc_filled > 0.95 end end @@ -86,22 +125,11 @@ plc.rps_init = function (reactor) local fuel = self.reactor.getFuel() if fuel == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log.error("RPS: failed to check reactor fuel level") - return false + log.error("RPS: failed to check reactor fuel") + _set_fault() + state[state_keys.no_fuel] = false else - return fuel == 0 - end - end - - -- check if there is no coolant - local _no_coolant = function () - local coolant_filled = self.reactor.getCoolantFilledPercentage() - if coolant_filled == ppm.ACCESS_FAULT then - -- lost the peripheral or terminated, handled later - log.error("RPS: failed to check reactor coolant level") - return false - else - return coolant_filled < 0.02 + state[state_keys.no_fuel] = fuel.amount == 0 end end @@ -114,78 +142,114 @@ plc.rps_init = function (reactor) -- report a PLC comms timeout local trip_timeout = function () - self.timed_out = true + state[state_keys.timed_out] = true + end + + -- manually SCRAM the reactor + local trip_manual = function () + state[state_keys.manual] = true + end + + -- SCRAM the reactor now + local scram = function () + log.info("RPS: reactor SCRAM") + + self.reactor.scram() + if self.reactor.__p_is_faulted() then + log.error("RPS: failed reactor SCRAM") + return false + else + self.reactor_enabled = false + return true + end + end + + -- start the reactor + local activate = function () + if not self.tripped then + log.info("RPS: reactor start") + + self.reactor.activate() + if self.reactor.__p_is_faulted() then + log.error("RPS: failed reactor start") + else + self.reactor_enabled = true + return true + end + end + + return false end -- check all safety conditions local check = function () local status = rps_status_t.ok local was_tripped = self.tripped + local first_trip = false + + -- update state + parallel.waitForAll( + _damage_critical, + _high_temp, + _no_coolant, + _excess_waste, + _excess_heated_coolant, + _insufficient_fuel + ) - -- update cache - self.cache = { - _damage_critical(), - _excess_heated_coolant(), - _excess_waste(), - _high_temp(), - _insufficient_fuel(), - _no_coolant(), - self.timed_out - } - -- check system states in order of severity if self.tripped then status = self.trip_cause - elseif self.cache[1] then - log.warning("RPS: damage critical!") + elseif self.state[state_keys.dmg_crit] then + log.warning("RPS: damage critical") status = rps_status_t.dmg_crit - elseif self.cache[4] then - log.warning("RPS: high temperature!") + elseif self.state[state_keys.high_temp] then + log.warning("RPS: high temperature") status = rps_status_t.high_temp - elseif self.cache[2] then - log.warning("RPS: heated coolant backup!") - status = rps_status_t.ex_hcoolant - elseif self.cache[6] then - log.warning("RPS: no coolant!") + elseif self.state[state_keys.no_coolant] then + log.warning("RPS: no coolant") status = rps_status_t.no_coolant - elseif self.cache[3] then - log.warning("RPS: full waste!") + elseif self.state[state_keys.ex_waste] then + log.warning("RPS: full waste") status = rps_status_t.ex_waste - elseif self.cache[5] then - log.warning("RPS: no fuel!") + elseif self.state[state_keys.ex_hcoolant] then + log.warning("RPS: heated coolant backup") + status = rps_status_t.ex_hcoolant + elseif self.state[state_keys.no_fuel] then + log.warning("RPS: no fuel") status = rps_status_t.no_fuel - elseif self.cache[7] then - log.warning("RPS: supervisor connection timeout!") + elseif self.state[state_keys.fault] then + log.warning("RPS: reactor access fault") + status = rps_status_t.fault + elseif self.state[state_keys.timeout] then + log.warning("RPS: supervisor connection timeout") status = rps_status_t.timeout + elseif self.state[state_keys.manual] then + log.warning("RPS: manual SCRAM requested") + status = rps_status_t.manual else self.tripped = false end - - -- if a new trip occured... - local first_trip = false - if not was_tripped and status ~= rps_status_t.ok then - log.warning("RPS: reactor SCRAM") + -- if a new trip occured... + if (not was_tripped) and (status ~= rps_status_t.ok) then first_trip = true self.tripped = true self.trip_cause = status - self.reactor.scram() - if self.reactor.__p_is_faulted() then - log.error("RPS: failed reactor SCRAM") - end + scram() end - + return self.tripped, status, first_trip end -- get the RPS status - local status = function () return self.cache end + local status = function () return self.state end local is_tripped = function () return self.tripped end + local is_active = function () return self.reactor_enabled end -- reset the RPS local reset = function () - self.timed_out = false self.tripped = false self.trip_cause = rps_status_t.ok end @@ -193,9 +257,13 @@ plc.rps_init = function (reactor) return { reconnect_reactor = reconnect_reactor, trip_timeout = trip_timeout, + trip_manual = trip_manual, + scram = scram, + activate = activate, check = check, status = status, is_tripped = is_tripped, + is_active = is_active, reset = reset } end @@ -544,18 +612,6 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps) -- request for physical structure _send_struct() log.debug("sent out structure again, did supervisor miss it?") - elseif packet.type == RPLC_TYPES.MEK_SCRAM then - -- disable the reactor - self.scrammed = true - plc_state.scram = true - self.reactor.scram() - _send_ack(packet.type, self.reactor.__p_is_ok()) - elseif packet.type == RPLC_TYPES.MEK_ENABLE then - -- enable the reactor - self.scrammed = false - plc_state.scram = false - self.reactor.activate() - _send_ack(packet.type, self.reactor.__p_is_ok()) elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then -- set the burn rate if packet.length == 1 then @@ -581,6 +637,15 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps) else log.debug("RPLC set burn rate packet length mismatch") end + elseif packet.type == RPLC_TYPES.RPS_ENABLE then + -- enable the reactor + self.scrammed = false + _send_ack(packet.type, self.rps.activate()) + elseif packet.type == RPLC_TYPES.RPS_SCRAM then + -- disable the reactor + self.scrammed = true + self.rps.trip_manual() + _send_ack(packet.type, true) elseif packet.type == RPLC_TYPES.RPS_RESET then -- reset the RPS status rps.reset() diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index e8654dc..cf2bb0f 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -11,7 +11,7 @@ local config = require("config") local plc = require("plc") local threads = require("threads") -local R_PLC_VERSION = "alpha-v0.6.1" +local R_PLC_VERSION = "alpha-v0.6.2" local print = util.print local println = util.println @@ -37,7 +37,6 @@ local __shared_memory = { plc_state = { init_ok = true, shutdown = false, - scram = true, degraded = false, no_reactor = false, no_modem = false diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 5deaf10..42d8e33 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -13,7 +13,7 @@ local println_ts = util.println_ts local psleep = util.psleep local MAIN_CLOCK = 1 -- (1Hz, 20 ticks) -local RPS_SLEEP = 500 -- (500ms, 10 ticks) +local RPS_SLEEP = 250 -- (250ms, 5 ticks) local COMMS_SLEEP = 150 -- (150ms, 3 ticks) local SP_CTRL_SLEEP = 250 -- (250ms, 5 ticks) @@ -207,7 +207,7 @@ threads.thread__rps = function (smem) if plc_state.init_ok then -- SCRAM if no open connection if networked and not plc_comms.is_linked() then - plc_state.scram = true + rps.scram() if was_linked then was_linked = false rps.trip_timeout() @@ -219,21 +219,17 @@ threads.thread__rps = function (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) - if not plc_state.no_reactor and plc_state.scram and reactor.getStatus() then - reactor.scram() + if not plc_state.no_reactor and rps.is_tripped() and reactor.getStatus() then + rps.scram() end -- if we are in standalone mode, continuously reset RPS -- RPS will trip again if there are faults, but if it isn't cleared, the user can't re-enable - if not networked then - plc_state.scram = false - rps.reset() - end + if not networked then rps.reset() end -- check safety (SCRAM occurs if tripped) if not plc_state.no_reactor then local rps_tripped, rps_status_string, rps_first = rps.check() - plc_state.scram = plc_state.scram or rps_tripped if rps_first then println_ts("[RPS] SCRAM! safety trip: " .. rps_status_string) @@ -250,26 +246,19 @@ threads.thread__rps = function (smem) if msg.qtype == mqueue.TYPE.COMMAND then -- received a command - if msg.message == MQ__RPS_CMD.SCRAM then - -- basic SCRAM - plc_state.scram = true - reactor.scram() - elseif msg.message == MQ__RPS_CMD.DEGRADED_SCRAM then - -- SCRAM with print - plc_state.scram = true - if reactor.scram() then - println_ts("successful reactor SCRAM") - log.error("successful reactor SCRAM") - else - println_ts("failed reactor SCRAM") - log.error("failed reactor SCRAM") + if plc_state.init_ok then + if msg.message == MQ__RPS_CMD.SCRAM then + -- SCRAM + rps.scram() + elseif msg.message == MQ__RPS_CMD.DEGRADED_SCRAM then + -- lost peripheral(s) + rps.trip_degraded() + elseif msg.message == MQ__RPS_CMD.TRIP_TIMEOUT then + -- watchdog tripped + rps.trip_timeout() + println_ts("server timeout") + log.warning("server timeout") end - elseif msg.message == MQ__RPS_CMD.TRIP_TIMEOUT then - -- watchdog tripped - plc_state.scram = true - rps.trip_timeout() - println_ts("server timeout") - log.warning("server timeout") end elseif msg.qtype == mqueue.TYPE.DATA then -- received data @@ -286,9 +275,7 @@ threads.thread__rps = function (smem) -- safe exit log.info("rps thread shutdown initiated") if plc_state.init_ok then - plc_state.scram = true - reactor.scram() - if reactor.__p_is_ok() then + if rps.scram() then println_ts("reactor disabled") log.info("rps thread reactor SCRAM OK") else @@ -368,6 +355,8 @@ threads.thread__comms_rx = function (smem) -- load in from shared memory local plc_state = smem.plc_state local setpoints = smem.setpoints + local plc_dev = smem.plc_dev + local rps = smem.plc_sys.rps local plc_comms = smem.plc_sys.plc_comms local conn_watchdog = smem.plc_sys.conn_watchdog @@ -388,7 +377,7 @@ threads.thread__comms_rx = function (smem) elseif msg.qtype == mqueue.TYPE.PACKET then -- received a packet -- handle the packet (setpoints passed to update burn rate setpoint) - -- (plc_state passed to allow clearing SCRAM flag and check if degraded) + -- (plc_state passed to check if degraded) -- (conn_watchdog passed to allow feeding the watchdog) plc_comms.handle_packet(msg.message, setpoints, plc_state, conn_watchdog) end @@ -421,6 +410,7 @@ threads.thread__setpoint_control = function (smem) local plc_state = smem.plc_state local setpoints = smem.setpoints local plc_dev = smem.plc_dev + local rps = smem.plc_sys.rps local last_update = util.time() local running = false @@ -433,7 +423,7 @@ threads.thread__setpoint_control = function (smem) -- check if we should start ramping if setpoints.burn_rate ~= last_sp_burn then - if not plc_state.scram then + if rps.is_active() then if math.abs(setpoints.burn_rate - last_sp_burn) <= 5 then -- update without ramp if <= 5 mB/t change log.debug("setting burn rate directly to " .. setpoints.burn_rate .. "mB/t") @@ -459,7 +449,7 @@ threads.thread__setpoint_control = function (smem) running = false -- adjust burn rate (setpoints.burn_rate) - if not plc_state.scram then + if rps.is_active() then local current_burn_rate = reactor.getBurnRate() if (current_burn_rate ~= ppm.ACCESS_FAULT) and (current_burn_rate ~= setpoints.burn_rate) then -- calculate new burn rate diff --git a/scada-common/comms.lua b/scada-common/comms.lua index da6f5d3..f762c3b 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -17,9 +17,9 @@ local RPLC_TYPES = { LINK_REQ = 1, -- linking requests STATUS = 2, -- reactor/system status MEK_STRUCT = 3, -- mekanism build structure - MEK_SCRAM = 4, -- SCRAM reactor - MEK_ENABLE = 5, -- enable reactor - MEK_BURN_RATE = 6, -- set burn rate + MEK_BURN_RATE = 4, -- set burn rate + RPS_ENABLE = 5, -- enable reactor + RPS_SCRAM = 6, -- SCRAM reactor RPS_STATUS = 7, -- RPS status RPS_ALARM = 8, -- RPS alarm broadcast RPS_RESET = 9 -- clear RPS trip (if in bad state, will trip immediately) @@ -229,9 +229,9 @@ comms.rplc_packet = function () self.type == RPLC_TYPES.LINK_REQ or self.type == RPLC_TYPES.STATUS or self.type == RPLC_TYPES.MEK_STRUCT or - self.type == RPLC_TYPES.MEK_SCRAM or - self.type == RPLC_TYPES.MEK_ENABLE or self.type == RPLC_TYPES.MEK_BURN_RATE or + self.type == RPLC_TYPES.RPS_ENABLE or + self.type == RPLC_TYPES.RPS_SCRAM or self.type == RPLC_TYPES.RPS_ALARM or self.type == RPLC_TYPES.RPS_STATUS or self.type == RPLC_TYPES.RPS_RESET diff --git a/scada-common/types.lua b/scada-common/types.lua index 855334e..5bd747e 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -17,12 +17,14 @@ types.rtu_t = { types.rps_status_t = { ok = "ok", dmg_crit = "dmg_crit", - ex_hcoolant = "heated_coolant_backup", - ex_waste = "full_waste", high_temp = "high_temp", - no_fuel = "no_fuel", no_coolant = "no_coolant", - timeout = "timeout" + ex_waste = "full_waste", + ex_hcoolant = "heated_coolant_backup", + no_fuel = "no_fuel", + fault = "fault", + timeout = "timeout", + manual = "manual" } -- MODBUS diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index bb79ac1..2ee2770 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -291,16 +291,15 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) else log.debug(log_header .. "RPLC struct packet length mismatch") end - elseif pkt.type == RPLC_TYPES.MEK_SCRAM then - -- SCRAM acknowledgement + elseif pkt.type == RPLC_TYPES.MEK_BURN_RATE then + -- burn rate acknowledgement local ack = _get_ack(pkt) if ack then - self.acks.scram = true - self.sDB.control_state = false + self.acks.burn_rate = true elseif ack == false then - log.debug(log_header .. "SCRAM failed!") + log.debug(log_header .. "burn rate update failed!") end - elseif pkt.type == RPLC_TYPES.MEK_ENABLE then + elseif pkt.type == RPLC_TYPES.RPS_ENABLE then -- enable acknowledgement local ack = _get_ack(pkt) if ack then @@ -309,13 +308,14 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) elseif ack == false then log.debug(log_header .. "enable failed!") end - elseif pkt.type == RPLC_TYPES.MEK_BURN_RATE then - -- burn rate acknowledgement + elseif pkt.type == RPLC_TYPES.RPS_SCRAM then + -- SCRAM acknowledgement local ack = _get_ack(pkt) if ack then - self.acks.burn_rate = true + self.acks.scram = true + self.sDB.control_state = false elseif ack == false then - log.debug(log_header .. "burn rate update failed!") + log.debug(log_header .. "SCRAM failed!") end elseif pkt.type == RPLC_TYPES.RPS_STATUS then -- RPS status packet received, copy data @@ -428,16 +428,16 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) elseif message.qtype == mqueue.TYPE.COMMAND then -- handle instruction local cmd = message.message - if cmd == PLC_S_CMDS.SCRAM then - -- SCRAM reactor - self.acks.scram = false - self.retry_times.scram_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.MEK_SCRAM, {}) - elseif cmd == PLC_S_CMDS.ENABLE then + if cmd == PLC_S_CMDS.ENABLE then -- enable reactor self.acks.enable = false self.retry_times.enable_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.MEK_ENABLE, {}) + _send(RPLC_TYPES.RPS_ENABLE, {}) + elseif cmd == PLC_S_CMDS.SCRAM then + -- SCRAM reactor + self.acks.scram = false + self.retry_times.scram_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.RPS_SCRAM, {}) elseif cmd == PLC_S_CMDS.RPS_RESET then -- reset RPS self.acks.rps_reset = false @@ -517,7 +517,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) if not self.acks.scram then if rtimes.scram_req - util.time() <= 0 then - _send(RPLC_TYPES.MEK_SCRAM, {}) + _send(RPLC_TYPES.RPS_SCRAM, {}) rtimes.scram_req = util.time() + RETRY_PERIOD end end @@ -526,7 +526,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) if not self.acks.enable then if rtimes.enable_req - util.time() <= 0 then - _send(RPLC_TYPES.MEK_ENABLE, {}) + _send(RPLC_TYPES.RPS_ENABLE, {}) rtimes.enable_req = util.time() + RETRY_PERIOD end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index a93dab9..72ac729 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("session.svsessions") local config = require("config") local supervisor = require("supervisor") -local SUPERVISOR_VERSION = "alpha-v0.3.1" +local SUPERVISOR_VERSION = "alpha-v0.3.2" local print = util.print local println = util.println From 83fa41bbd055674b563f95cc67adbae7998b66de Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 5 May 2022 16:00:49 -0400 Subject: [PATCH 154/587] #45 burn rate ramping is optional now --- reactor-plc/plc.lua | 23 ++++++++++------- reactor-plc/startup.lua | 3 ++- reactor-plc/threads.lua | 52 ++++++++++++++++++++------------------ supervisor/session/plc.lua | 24 ++++++++++++++---- supervisor/startup.lua | 2 +- 5 files changed, 64 insertions(+), 40 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 68671b9..9010610 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -614,22 +614,27 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps) log.debug("sent out structure again, did supervisor miss it?") elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then -- set the burn rate - if packet.length == 1 then + if packet.length == 2 then local success = false local burn_rate = packet.data[1] - local max_burn_rate = self.max_burn_rate + local ramp = packet.data[2] -- if no known max burn rate, check again - if max_burn_rate == nil then - max_burn_rate = self.reactor.getMaxBurnRate() - self.max_burn_rate = max_burn_rate + if self.max_burn_rate == nil then + self.max_burn_rate = self.reactor.getMaxBurnRate() end -- if we know our max burn rate, update current burn rate setpoint if in range - if max_burn_rate ~= ppm.ACCESS_FAULT then - if burn_rate > 0 and burn_rate <= max_burn_rate then - setpoints.burn_rate = burn_rate - success = true + if self.max_burn_rate ~= ppm.ACCESS_FAULT then + if burn_rate > 0 and burn_rate <= self.max_burn_rate then + if ramp then + setpoints.burn_rate_en = true + setpoints.burn_rate = burn_rate + success = true + else + self.reactor.setBurnRate(burn_rate) + success = not self.reactor.__p_is_faulted() + end end end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index cf2bb0f..deb2b82 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -11,7 +11,7 @@ local config = require("config") local plc = require("plc") local threads = require("threads") -local R_PLC_VERSION = "alpha-v0.6.2" +local R_PLC_VERSION = "alpha-v0.6.3" local print = util.print local println = util.println @@ -43,6 +43,7 @@ local __shared_memory = { }, setpoints = { + burn_rate_en = false, burn_rate = 0.0 }, diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 42d8e33..e984b88 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -422,7 +422,7 @@ threads.thread__setpoint_control = function (smem) local reactor = plc_dev.reactor -- check if we should start ramping - if setpoints.burn_rate ~= last_sp_burn then + if setpoints.burn_rate_en and setpoints.burn_rate ~= last_sp_burn then if rps.is_active() then if math.abs(setpoints.burn_rate - last_sp_burn) <= 5 then -- update without ramp if <= 5 mB/t change @@ -449,33 +449,37 @@ threads.thread__setpoint_control = function (smem) running = false -- adjust burn rate (setpoints.burn_rate) - if rps.is_active() then - local current_burn_rate = reactor.getBurnRate() - if (current_burn_rate ~= ppm.ACCESS_FAULT) and (current_burn_rate ~= setpoints.burn_rate) then - -- calculate new burn rate - local new_burn_rate = current_burn_rate + if setpoints.burn_rate_en then + if rps.is_active() then + local current_burn_rate = reactor.getBurnRate() - if setpoints.burn_rate > current_burn_rate then - -- need to ramp up - local new_burn_rate = current_burn_rate + (BURN_RATE_RAMP_mB_s * min_elapsed_s) - if new_burn_rate > setpoints.burn_rate then - new_burn_rate = setpoints.burn_rate - end - else - -- need to ramp down - local new_burn_rate = current_burn_rate - (BURN_RATE_RAMP_mB_s * min_elapsed_s) - if new_burn_rate < setpoints.burn_rate then - new_burn_rate = setpoints.burn_rate + -- we yielded, check enable again + if setpoints.burn_rate_en and (current_burn_rate ~= ppm.ACCESS_FAULT) and (current_burn_rate ~= setpoints.burn_rate) then + -- calculate new burn rate + local new_burn_rate = current_burn_rate + + if setpoints.burn_rate > current_burn_rate then + -- need to ramp up + local new_burn_rate = current_burn_rate + (BURN_RATE_RAMP_mB_s * min_elapsed_s) + if new_burn_rate > setpoints.burn_rate then + new_burn_rate = setpoints.burn_rate + end + else + -- need to ramp down + local new_burn_rate = current_burn_rate - (BURN_RATE_RAMP_mB_s * min_elapsed_s) + if new_burn_rate < setpoints.burn_rate then + new_burn_rate = setpoints.burn_rate + end end + + -- set the burn rate + reactor.setBurnRate(new_burn_rate) + + running = running or (new_burn_rate ~= setpoints.burn_rate) end - - -- set the burn rate - reactor.setBurnRate(new_burn_rate) - - running = running or (new_burn_rate ~= setpoints.burn_rate) + else + last_sp_burn = 0 end - else - last_sp_burn = 0 end end diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 2ee2770..1206ef6 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -21,11 +21,16 @@ local RETRY_PERIOD = 1000 local PLC_S_CMDS = { SCRAM = 0, ENABLE = 1, - BURN_RATE = 2, - RPS_RESET = 3 + RPS_RESET = 2 +} + +local PLC_S_DATA = { + BURN_RATE = 1, + RAMP_BURN_RATE = 2 } plc.PLC_S_CMDS = PLC_S_CMDS +plc.PLC_S_DATA = PLC_S_DATA local PERIODICS = { KEEP_ALIVE = 2.0 @@ -42,6 +47,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) out_q = out_queue, commanded_state = false, commanded_burn_rate = 0.0, + ramping_rate = false, -- connection properties seq_num = 0, r_seq_num = nil, @@ -447,12 +453,20 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) elseif message.qtype == mqueue.TYPE.DATA then -- instruction with body local cmd = message.message - if cmd.key == PLC_S_CMDS.BURN_RATE then + if cmd.key == PLC_S_DATA.BURN_RATE then -- update burn rate self.commanded_burn_rate = cmd.val + self.ramping_rate = false self.acks.burn_rate = false self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate }) + _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + elseif cmd.key == PLC_S_DATA.RAMP_BURN_RATE then + -- ramp to burn rate + self.commanded_burn_rate = cmd.val + self.ramping_rate = true + self.acks.burn_rate = false + self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) end end @@ -535,7 +549,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) if not self.acks.burn_rate then if rtimes.burn_rate_req - util.time() <= 0 then - _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate }) + _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) rtimes.burn_rate_req = util.time() + RETRY_PERIOD end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 72ac729..a7ebcfe 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("session.svsessions") local config = require("config") local supervisor = require("supervisor") -local SUPERVISOR_VERSION = "alpha-v0.3.2" +local SUPERVISOR_VERSION = "alpha-v0.3.3" local print = util.print local println = util.println From b7e5ced2e8716d81bec604184e2c5f8521d80433 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 6 May 2022 09:10:50 -0400 Subject: [PATCH 155/587] PLC bugfixes --- reactor-plc/plc.lua | 12 ++++++------ reactor-plc/startup.lua | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 9010610..4ed8cb7 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -114,9 +114,9 @@ plc.rps_init = function (reactor) -- lost the peripheral or terminated, handled later log.error("RPS: failed to check reactor heated coolant level") _set_fault() - state[state_keys.ex_hcoolant] = false + self.state[state_keys.ex_hcoolant] = false else - state[state_keys.ex_hcoolant] = hc_filled > 0.95 + self.state[state_keys.ex_hcoolant] = hc_filled > 0.95 end end @@ -127,9 +127,9 @@ plc.rps_init = function (reactor) -- lost the peripheral or terminated, handled later log.error("RPS: failed to check reactor fuel") _set_fault() - state[state_keys.no_fuel] = false + self.state[state_keys.no_fuel] = false else - state[state_keys.no_fuel] = fuel.amount == 0 + self.state[state_keys.no_fuel] = fuel == 0 end end @@ -142,12 +142,12 @@ plc.rps_init = function (reactor) -- report a PLC comms timeout local trip_timeout = function () - state[state_keys.timed_out] = true + self.state[state_keys.timed_out] = true end -- manually SCRAM the reactor local trip_manual = function () - state[state_keys.manual] = true + self.state[state_keys.manual] = true end -- SCRAM the reactor now diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index deb2b82..e1b07ab 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -11,7 +11,7 @@ local config = require("config") local plc = require("plc") local threads = require("threads") -local R_PLC_VERSION = "alpha-v0.6.3" +local R_PLC_VERSION = "alpha-v0.6.4" local print = util.print local println = util.println From d0b2820160efccd2186a5bf70f268e5cbc58f1ce Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 6 May 2022 10:48:46 -0400 Subject: [PATCH 156/587] logging optimizations --- scada-common/log.lua | 77 +++++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/scada-common/log.lua b/scada-common/log.lua index 39069ca..a4f63b2 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -1,11 +1,11 @@ +local util = require("scada-common.util") + -- -- File System Logger -- local log = {} --- we use extra short abbreviations since computer craft screens are very small - local MODE = { APPEND = 0, NEW = 1 @@ -13,54 +13,63 @@ local MODE = { log.MODE = MODE +---------------------------- +-- PRIVATE DATA/FUNCTIONS -- +---------------------------- + local LOG_DEBUG = true -local log_path = "/log.txt" -local mode = MODE.APPEND -local file_handle = nil +local _log_sys = { + path = "/log.txt", + mode = MODE.APPEND, + file = nil +} local _log = function (msg) - local stamped = os.date("[%c] ") .. msg + local time_stamp = os.date("[%c] ") + local stamped = time_stamp .. msg -- attempt to write log local status, result = pcall(function () - file_handle.writeLine(stamped) - file_handle.flush() + _log_sys.file.writeLine(stamped) + _log_sys.file.flush() end) - -- if we don't have much space, we need to create a new log file - local delete_log = fs.getFreeSpace(log_path) < 100 + -- if we don't have space, we need to create a new log file if not status then if result == "Out of space" then - delete_log = true + -- will delete log file elseif result ~= nil then - print("unknown error writing to logfile: " .. result) + util.println("unknown error writing to logfile: " .. result) end end - if delete_log then + if (result == "Out of space") or (fs.getFreeSpace(_log_sys.path) < 100) then -- delete the old log file and open a new one - file_handle.close() - fs.delete(log_path) - init(log_path, mode) + _log_sys.file.close() + fs.delete(_log_sys.path) + init(_log_sys.path, _log_sys.mode) -- leave a message - local notif = os.date("[%c] ") .. "recycled log file" - file_handle.writeLine(notif) - file_handle.writeLine(stamped) - file_handle.flush() + _log_sys.file.writeLine(time_stamp .. "recycled log file") + _log_sys.file.writeLine(stamped) + _log_sys.file.flush() end end -log.init = function (path, write_mode) - log_path = path - mode = write_mode +---------------------- +-- PUBLIC FUNCTIONS -- +---------------------- - if mode == MODE.APPEND then - file_handle = fs.open(path, "a") +log.init = function (path, write_mode) + _log_sys.path = path + _log_sys.mode = write_mode + + if _log_sys.mode == MODE.APPEND then + _log_sys.file = fs.open(path, "a") else - file_handle = fs.open(path, "w+") + _log_sys.file = fs.open(path, "w+") end end @@ -69,14 +78,14 @@ log.debug = function (msg, trace) local dbg_info = "" if trace then + local info = debug.getinfo(2) local name = "" - if debug.getinfo(2).name ~= nil then - name = ":" .. debug.getinfo(2).name .. "():" + if info.name ~= nil then + name = ":" .. info.name .. "():" end - dbg_info = debug.getinfo(2).short_src .. ":" .. name .. - debug.getinfo(2).currentline .. " > " + dbg_info = info.short_src .. ":" .. name .. info.currentline .. " > " end _log("[DBG] " .. dbg_info .. msg) @@ -95,14 +104,14 @@ log.error = function (msg, trace) local dbg_info = "" if trace then + local info = debug.getinfo(2) local name = "" - if debug.getinfo(2).name ~= nil then - name = ":" .. debug.getinfo(2).name .. "():" + if info.name ~= nil then + name = ":" .. info.name .. "():" end - dbg_info = debug.getinfo(2).short_src .. ":" .. name .. - debug.getinfo(2).currentline .. " > " + dbg_info = info.short_src .. ":" .. name .. info.currentline .. " > " end _log("[ERR] " .. dbg_info .. msg) From 17a46ae642d48a482866e4895067f1804756b81e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 6 May 2022 10:53:12 -0400 Subject: [PATCH 157/587] mqueue optimizations --- scada-common/mqueue.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scada-common/mqueue.lua b/scada-common/mqueue.lua index 8ba14cd..c24c15c 100644 --- a/scada-common/mqueue.lua +++ b/scada-common/mqueue.lua @@ -15,6 +15,9 @@ mqueue.TYPE = TYPE mqueue.new = function () local queue = {} + local insert = table.insert + local remove = table.remove + local length = function () return #queue end @@ -24,11 +27,11 @@ mqueue.new = function () end local ready = function () - return #queue > 0 + return #queue ~= 0 end local _push = function (qtype, message) - table.insert(queue, { qtype = qtype, message = message }) + insert(queue, { qtype = qtype, message = message }) end local push_command = function (message) @@ -45,7 +48,7 @@ mqueue.new = function () local pop = function () if #queue > 0 then - return table.remove(queue, 1) + return remove(queue, 1) else return nil end From 4aab75b842a89128853e4c4ff37f41d1dbdaee0e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 6 May 2022 11:11:53 -0400 Subject: [PATCH 158/587] rsio optimizations --- scada-common/rsio.lua | 57 +++++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index fd71247..faf9008 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -4,6 +4,10 @@ local rsio = {} +---------------------- +-- RS I/O CONSTANTS -- +---------------------- + local IO_LVL = { LOW = 0, HIGH = 1 @@ -64,6 +68,11 @@ rsio.IO_DIR = IO_DIR rsio.IO_MODE = IO_MODE rsio.IO = RS_IO +----------------------- +-- UTILITY FUNCTIONS -- +----------------------- + +-- channel to string rsio.to_string = function (channel) local names = { "F_SCRAM", @@ -96,22 +105,7 @@ rsio.to_string = function (channel) end end -rsio.is_valid_channel = function (channel) - return channel ~= nil and channel > 0 and channel <= RS_IO.A_T_FLOW_RATE -end - -rsio.is_valid_side = function (side) - if side ~= nil then - for _, s in pairs(rs.getSides()) do - if s == side then return true end - end - end - return false -end - -rsio.is_color = function (color) - return (color > 0) and (bit.band(color, (color - 1)) == 0); -end +local _B_AND = bit.band local _TRINARY = function (cond, t, f) if cond then return t else return f end end @@ -160,6 +154,7 @@ local RS_DIO_MAP = { { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT } } +-- get the mode of a channel rsio.get_io_mode = function (channel) local modes = { IO_MODE.DIGITAL_IN, -- F_SCRAM @@ -192,6 +187,36 @@ rsio.get_io_mode = function (channel) end end +-------------------- +-- GENERIC CHECKS -- +-------------------- + +local RS_SIDES = rs.getSides() + +-- check if a channel is valid +rsio.is_valid_channel = function (channel) + return channel ~= nil and channel > 0 and channel <= RS_IO.A_T_FLOW_RATE +end + +-- check if a side is valid +rsio.is_valid_side = function (side) + if side ~= nil then + for i = 0, #RS_SIDES do + if RS_SIDES[i] == side then return true end + end + end + return false +end + +-- check if a color is a valid single color +rsio.is_color = function (color) + return (color > 0) and (_B_AND(color, (color - 1)) == 0); +end + +----------------- +-- DIGITAL I/O -- +----------------- + -- get digital IO level reading rsio.digital_read = function (rs_value) if rs_value then From 96e535fdc4c9f98235564bb88e488c7d55606be8 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 7 May 2022 13:39:12 -0400 Subject: [PATCH 159/587] global scope optimizations --- rtu/modbus.lua | 10 ++++++---- rtu/rtu.lua | 14 ++++++++------ scada-common/comms.lua | 12 +++++++----- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/rtu/modbus.lua b/rtu/modbus.lua index ac26fc1..bb1817a 100644 --- a/rtu/modbus.lua +++ b/rtu/modbus.lua @@ -13,6 +13,8 @@ modbus.new = function (rtu_dev, use_parallel_read) use_parallel = use_parallel_read } + local insert = table.insert + local _1_read_coils = function (c_addr_start, count) local tasks = {} local readings = {} @@ -25,7 +27,7 @@ modbus.new = function (rtu_dev, use_parallel_read) local addr = c_addr_start + i - 1 if self.use_parallel then - table.insert(tasks, function () + insert(tasks, function () local reading, fault = self.rtu.read_coil(addr) if fault then access_fault = true else readings[i] = reading end end) @@ -68,7 +70,7 @@ modbus.new = function (rtu_dev, use_parallel_read) local addr = di_addr_start + i - 1 if self.use_parallel then - table.insert(tasks, function () + insert(tasks, function () local reading, fault = self.rtu.read_di(addr) if fault then access_fault = true else readings[i] = reading end end) @@ -111,7 +113,7 @@ modbus.new = function (rtu_dev, use_parallel_read) local addr = hr_addr_start + i - 1 if self.use_parallel then - table.insert(tasks, function () + insert(tasks, function () local reading, fault = self.rtu.read_holding_reg(addr) if fault then access_fault = true else readings[i] = reading end end) @@ -154,7 +156,7 @@ modbus.new = function (rtu_dev, use_parallel_read) local addr = ir_addr_start + i - 1 if self.use_parallel then - table.insert(tasks, function () + insert(tasks, function () local reading, fault = self.rtu.read_input_reg(addr) if fault then access_fault = true else readings[i] = reading end end) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 9c22559..1f5c049 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -18,6 +18,8 @@ rtu.init_unit = function () io_count_cache = { 0, 0, 0, 0 } } + local insert = table.insert + local _count_io = function () self.io_count_cache = { #self.discrete_inputs, #self.coils, #self.input_regs, #self.holding_regs } end @@ -31,7 +33,7 @@ rtu.init_unit = function () -- return : count of discrete inputs local connect_di = function (f) - table.insert(self.discrete_inputs, f) + insert(self.discrete_inputs, f) _count_io() return #self.discrete_inputs end @@ -47,7 +49,7 @@ rtu.init_unit = function () -- return : count of coils local connect_coil = function (f_read, f_write) - table.insert(self.coils, { read = f_read, write = f_write }) + insert(self.coils, { read = f_read, write = f_write }) _count_io() return #self.coils end @@ -70,7 +72,7 @@ rtu.init_unit = function () -- return : count of input registers local connect_input_reg = function (f) - table.insert(self.input_regs, f) + insert(self.input_regs, f) _count_io() return #self.input_regs end @@ -86,7 +88,7 @@ rtu.init_unit = function () -- return : count of holding registers local connect_holding_reg = function (f_read, f_write) - table.insert(self.holding_regs, { read = f_read, write = f_write }) + insert(self.holding_regs, { read = f_read, write = f_write }) _count_io() return #self.holding_regs end @@ -293,7 +295,7 @@ rtu.comms = function (modem, local_port, server_port) if type ~= nil then if type == RTU_ADVERT_TYPES.REDSTONE then - table.insert(advertisement, { + insert(advertisement, { unit = i, type = type, index = units[i].index, @@ -301,7 +303,7 @@ rtu.comms = function (modem, local_port, server_port) rsio = units[i].device }) else - table.insert(advertisement, { + insert(advertisement, { unit = i, type = type, index = units[i].index, diff --git a/scada-common/comms.lua b/scada-common/comms.lua index f762c3b..d0e6fdf 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -4,6 +4,8 @@ local comms = {} +local insert = table.insert + local PROTOCOLS = { MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol RPLC = 1, -- reactor PLC protocol @@ -158,7 +160,7 @@ comms.modbus_packet = function () -- populate raw array self.raw = { self.txn_id, self.unit_id, self.func_code } for i = 1, self.length do - table.insert(self.raw, data[i]) + insert(self.raw, data[i]) end end @@ -248,7 +250,7 @@ comms.rplc_packet = function () -- populate raw array self.raw = { self.id, self.type } for i = 1, #data do - table.insert(self.raw, data[i]) + insert(self.raw, data[i]) end end @@ -331,7 +333,7 @@ comms.mgmt_packet = function () -- populate raw array self.raw = { self.type } for i = 1, #data do - table.insert(self.raw, data[i]) + insert(self.raw, data[i]) end end @@ -410,7 +412,7 @@ comms.coord_packet = function () -- populate raw array self.raw = { self.type } for i = 1, #data do - table.insert(self.raw, data[i]) + insert(self.raw, data[i]) end end @@ -489,7 +491,7 @@ comms.capi_packet = function () -- populate raw array self.raw = { self.type } for i = 1, #data do - table.insert(self.raw, data[i]) + insert(self.raw, data[i]) end end From 469ee29b5a73fb36a27022523e1c02869dc039ae Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 9 May 2022 09:34:26 -0400 Subject: [PATCH 160/587] cleanup of rtu comms --- rtu/rtu.lua | 28 ++++++++------------- scada-common/comms.lua | 56 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 22 deletions(-) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 1f5c049..3048e42 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -1,10 +1,13 @@ local comms = require("scada-common.comms") local ppm = require("scada-common.ppm") +local types = require("scada-common.types") local modbus = require("modbus") local rtu = {} +local rtu_t = types.rtu_t + local PROTOCOLS = comms.PROTOCOLS local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local RTU_ADVERT_TYPES = comms.RTU_ADVERT_TYPES @@ -281,33 +284,22 @@ rtu.comms = function (modem, local_port, server_port) local advertisement = {} for i = 1, #units do - local type = nil - - if units[i].type == "boiler" then - type = RTU_ADVERT_TYPES.BOILER - elseif units[i].type == "turbine" then - type = RTU_ADVERT_TYPES.TURBINE - elseif units[i].type == "imatrix" then - type = RTU_ADVERT_TYPES.IMATRIX - elseif units[i].type == "redstone" then - type = RTU_ADVERT_TYPES.REDSTONE - end + local unit = units[i] + local type = comms.rtu_t_to_advert_type(unit.type) if type ~= nil then if type == RTU_ADVERT_TYPES.REDSTONE then insert(advertisement, { - unit = i, type = type, - index = units[i].index, - reactor = units[i].for_reactor, - rsio = units[i].device + index = unit.index, + reactor = unit.for_reactor, + rsio = unit.device }) else insert(advertisement, { - unit = i, type = type, - index = units[i].index, - reactor = units[i].for_reactor, + index = unit.index, + reactor = unit.for_reactor, rsio = nil }) end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index d0e6fdf..7a41ff7 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -2,8 +2,11 @@ -- Communications -- +local types = require("scada-common.types") + local comms = {} +local rtu_t = types.rtu_t local insert = table.insert local PROTOCOLS = { @@ -42,10 +45,13 @@ local SCADA_MGMT_TYPES = { } local RTU_ADVERT_TYPES = { - BOILER = 0, -- boiler - TURBINE = 1, -- turbine - IMATRIX = 2, -- induction matrix - REDSTONE = 3 -- redstone I/O + REDSTONE = 0, -- redstone I/O + BOILER = 1, -- boiler + BOILER_VALVE = 2, -- boiler mekanism 10.1+ + TURBINE = 3, -- turbine + TURBINE_VALVE = 4, -- turbine, mekanism 10.1+ + EMACHINE = 5, -- energy machine + IMATRIX = 6 -- induction matrix } comms.PROTOCOLS = PROTOCOLS @@ -544,4 +550,46 @@ comms.capi_packet = function () } end +-- convert rtu_t to RTU advertisement type +comms.rtu_t_to_advert_type = function (type) + if type == rtu_t.redstone then + return RTU_ADVERT_TYPES.REDSTONE + elseif type == rtu_t.boiler then + return RTU_ADVERT_TYPES.BOILER + elseif type == rtu_t.boiler_valve then + return RTU_ADVERT_TYPES.BOILER_VALVE + elseif type == rtu_t.turbine then + return RTU_ADVERT_TYPES.TURBINE + elseif type == rtu_t.turbine_valve then + return RTU_ADVERT_TYPES.TURBINE_VALVE + elseif type == rtu_t.energy_machine then + return RTU_ADVERT_TYPES.EMACHINE + elseif type == rtu_t.induction_matrix then + return RTU_ADVERT_TYPES.IMATRIX + end + + return nil +end + +-- convert RTU advertisement type to rtu_t +comms.advert_type_to_rtu_t = function (atype) + if atype == RTU_ADVERT_TYPES.REDSTONE then + return rtu_t.redstone + elseif atype == RTU_ADVERT_TYPES.BOILER then + return rtu_t.boiler + elseif atype == RTU_ADVERT_TYPES.BOILER_VALVE then + return rtu_t.boiler_valve + elseif atype == RTU_ADVERT_TYPES.TURBINE then + return rtu_t.turbine + elseif atype == RTU_ADVERT_TYPES.TURBINE_VALVE then + return rtu_t.turbine_valve + elseif atype == RTU_ADVERT_TYPES.EMACHINE then + return rtu_t.energy_machine + elseif atype == RTU_ADVERT_TYPES.IMATRIX then + return rtu_t.induction_matrix + end + + return nil +end + return comms From 679d98c8bf522a0a31edbfde3ad2b2c620e12d69 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 9 May 2022 09:35:39 -0400 Subject: [PATCH 161/587] #8 work in progress on RTU sessions and added unit object --- supervisor/session/rtu.lua | 180 +++++++++++++++++++++++++++++++++++++ supervisor/unit.lua | 82 +++++++++++++++++ 2 files changed, 262 insertions(+) create mode 100644 supervisor/unit.lua diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 9051425..1cf4e76 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -1,3 +1,183 @@ +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local util = require("scada-common.util") + local rtu = {} +local PROTOCOLS = comms.PROTOCOLS +local RPLC_TYPES = comms.RPLC_TYPES +local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES + +local print = util.print +local println = util.println +local print_ts = util.print_ts +local println_ts = util.println_ts + +rtu.new_session = function (id, in_queue, out_queue) + local log_header = "rtu_session(" .. id .. "): " + + local self = { + id = id, + in_q = in_queue, + out_q = out_queue, + commanded_state = false, + commanded_burn_rate = 0.0, + ramping_rate = false, + -- connection properties + seq_num = 0, + r_seq_num = nil, + connected = true, + received_struct = false, + received_status_cache = false, + rtu_conn_watchdog = util.new_watchdog(3), + last_rtt = 0 + } + + -- send a MODBUS TCP packet + local send_modbus = function (m_pkt) + local s_pkt = comms.scada_packet() + s_pkt.make(self.seq_num, PROTOCOLS.MODBUS_TCP, m_pkt.raw_sendable()) + self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) + self.seq_num = self.seq_num + 1 + end + + -- send a SCADA management packet + local _send_mgmt = function (msg_type, msg) + local s_pkt = comms.scada_packet() + local m_pkt = comms.mgmt_packet() + + m_pkt.make(msg_type, msg) + s_pkt.make(self.seq_num, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) + + self.out_q.push_packet(s_pkt) + self.seq_num = self.seq_num + 1 + end + + -- handle a packet + local _handle_packet = function (pkt) + -- check sequence number + if self.r_seq_num == nil then + self.r_seq_num = pkt.scada_frame.seq_num() + elseif self.r_seq_num >= pkt.scada_frame.seq_num() then + log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + return + else + self.r_seq_num = pkt.scada_frame.seq_num() + end + + -- process packet + if pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then + -- feed watchdog + self.rtu_conn_watchdog.feed() + + elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then + -- feed watchdog + self.rtu_conn_watchdog.feed() + + if pkt.type == SCADA_MGMT_TYPES.CLOSE then + -- close the session + self.connected = false + elseif pkt.type == SCADA_MGMT_TYPES.RTU_ADVERT then + -- RTU unit advertisement + for i = 1, packet.length do + local unit = packet.data[i] + unit + end + elseif pkt.type == SCADA_MGMT_TYPES.RTU_HEARTBEAT then + -- periodic RTU heartbeat + else + log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) + end + end + end + + -- PUBLIC FUNCTIONS -- + + -- get the session ID + local get_id = function () return self.id end + + -- check if a timer matches this session's watchdog + local check_wd = function (timer) + return timer == self.rtu_conn_watchdog.get_timer() + end + + -- close the connection + local close = function () + self.rtu_conn_watchdog.cancel() + self.connected = false + _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) + println(log_header .. "connection to RTU closed by server") + log.info(log_header .. "session closed by server") + end + + -- iterate the session + local iterate = function () + if self.connected then + ------------------ + -- handle queue -- + ------------------ + + local handle_start = util.time() + + while self.in_q.ready() and self.connected do + -- get a new message to process + local message = self.in_q.pop() + + if message.qtype == mqueue.TYPE.PACKET then + -- handle a packet + _handle_packet(message.message) + elseif message.qtype == mqueue.TYPE.COMMAND then + -- handle instruction + local cmd = message.message + elseif message.qtype == mqueue.TYPE.DATA then + -- instruction with body + local cmd = message.message + end + + -- max 100ms spent processing queue + if util.time() - handle_start > 100 then + log.warning(log_header .. "exceeded 100ms queue process limit") + break + end + end + + -- exit if connection was closed + if not self.connected then + self.rtu_conn_watchdog.cancel() + println(log_header .. "connection to RTU closed by remote host") + log.info(log_header .. "session closed by remote host") + return self.connected + end + + ---------------------- + -- update periodics -- + ---------------------- + + local elapsed = util.time() - self.periodics.last_update + + local periodics = self.periodics + + -- keep alive + + periodics.keep_alive = periodics.keep_alive + elapsed + if periodics.keep_alive >= PERIODICS.KEEP_ALIVE then + -- _send(RPLC_TYPES.KEEP_ALIVE, { util.time() }) + periodics.keep_alive = 0 + end + + self.periodics.last_update = util.time() + end + + return self.connected + end + + return { + get_id = get_id, + check_wd = check_wd, + close = close, + iterate = iterate + } +end + return rtu diff --git a/supervisor/unit.lua b/supervisor/unit.lua new file mode 100644 index 0000000..e7569d6 --- /dev/null +++ b/supervisor/unit.lua @@ -0,0 +1,82 @@ +local unit = {} + +unit.new = function (for_reactor) + local public = {} + + local self = { + r_id = for_reactor, + plc_s = nil, + turbines = {}, + boilers = {}, + energy_storage = {}, + redstone = {}, + db = { + annunciator = { + -- RPS + -- reactor + PLCOnline = false, + ReactorTrip = false, + ManualReactorTrip = false, + RCPTrip = false, + RCSFlowLow = false, + ReactorTempHigh = false, + ReactorHighDeltaT = false, + ReactorOverPower = false, + HighStartupRate = false, + -- boiler + BoilerOnline = false, + HeatingRateLow = false, + CoolantFeedMismatch = false, + -- turbine + TurbineOnline = false, + SteamFeedMismatch = false, + SteamDumpOpen = false, + TurbineTrip = false, + TurbineOverUnderSpeed = false + } + } + } + + public.link_plc_session = function (plc_session) + self.plc_s = plc_session + end + + public.add_turbine = function (turbine) + table.insert(self.turbines, turbine) + end + + public.add_boiler = function (turbine) + table.insert(self.boilers, boiler) + end + + public.add_redstone = function (field, accessor) + -- ensure field exists + if redstone[field] == nil then + redstone[field] = {} + end + + -- insert into list + table.insert(redstone[field], accessor) + end + + local _update_annunciator = function () + self.db.annunciator.PLCOnline = (self.plc_s ~= nil) and (self.plc_s.open) + self.db.annunciator.ReactorTrip = false + end + + public.update = function () + -- unlink PLC if session was closed + if not self.plc_s.open then + self.plc_s = nil + end + + -- update annunciator logic + _update_annunciator() + end + + public.get_annunciator = function () return self.db.annunciator end + + return public +end + +return unit From 25558df22d20935c7fc0468baf940589d46648a7 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 9 May 2022 15:00:16 -0400 Subject: [PATCH 162/587] RTU/PLC code cleanup, #46 changed KEEP_ALIVE to scada message type and use it for the RTU too --- reactor-plc/plc.lua | 60 +++++++++-------- reactor-plc/startup.lua | 23 ++++--- rtu/rtu.lua | 135 ++++++++++++++++++++++--------------- rtu/startup.lua | 16 +++-- rtu/threads.lua | 4 +- scada-common/comms.lua | 26 ++++--- supervisor/session/plc.lua | 40 +++++------ supervisor/session/rtu.lua | 33 ++++++--- supervisor/startup.lua | 2 +- 9 files changed, 191 insertions(+), 148 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 4ed8cb7..290ca64 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -269,7 +269,7 @@ plc.rps_init = function (reactor) end -- reactor PLC communications -plc.comms = function (id, modem, local_port, server_port, reactor, rps) +plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_watchdog) local self = { id = id, seq_num = 0, @@ -279,6 +279,7 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps) l_port = local_port, reactor = reactor, rps = rps, + conn_watchdog = conn_watchdog, scrammed = false, linked = false, status_cache = nil, @@ -398,7 +399,7 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps) -- keep alive ack local _send_keep_alive_ack = function (srv_time) - _send(RPLC_TYPES.KEEP_ALIVE, { srv_time, util.time() }) + _send(SCADA_MGMT_TYPES.KEEP_ALIVE, { srv_time, util.time() }) end -- general ack @@ -456,8 +457,8 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps) end -- close the connection to the server - local close = function (conn_watchdog) - conn_watchdog.cancel() + local close = function () + self.conn_watchdog.cancel() unlink() _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) end @@ -478,7 +479,7 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps) local sys_status = { util.time(), -- timestamp - (not self.scrammed), -- enabled + (not self.scrammed), -- requested control state rps.is_tripped(), -- overridden degraded, -- degraded self.reactor.getHeatingRate(), -- heating rate @@ -542,7 +543,7 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps) end -- handle an RPLC packet - local handle_packet = function (packet, plc_state, setpoints, conn_watchdog) + local handle_packet = function (packet, plc_state, setpoints) if packet ~= nil then -- check sequence number if self.r_seq_num == nil then @@ -554,29 +555,13 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps) self.r_seq_num = packet.scada_frame.seq_num() end - -- feed the watchdog first so it doesn't uhh...eat our packets - conn_watchdog.feed() + -- feed the watchdog first so it doesn't uhh...eat our packets :) + self.conn_watchdog.feed() -- handle packet if packet.scada_frame.protocol() == PROTOCOLS.RPLC then if self.linked then - if packet.type == RPLC_TYPES.KEEP_ALIVE then - -- keep alive request received, echo back - if packet.length == 1 then - local timestamp = packet.data[1] - local trip_time = util.time() - timestamp - - if trip_time > 500 then - log.warning("PLC KEEP_ALIVE trip time > 500ms (" .. trip_time .. ")") - end - - -- log.debug("RPLC RTT = ".. trip_time .. "ms") - - _send_keep_alive_ack(timestamp) - else - log.debug("RPLC keep alive packet length mismatch") - end - elseif packet.type == RPLC_TYPES.LINK_REQ then + if packet.type == RPLC_TYPES.LINK_REQ then -- link request confirmation if packet.length == 1 then log.debug("received unsolicited link request response") @@ -694,15 +679,34 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps) log.debug("discarding non-link packet before linked") end elseif packet.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then - -- handle session close - if packet.type == SCADA_MGMT_TYPES.CLOSE then - conn_watchdog.cancel() + if packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + -- keep alive request received, echo back + if packet.length == 1 then + local timestamp = packet.data[1] + local trip_time = util.time() - timestamp + + if trip_time > 500 then + log.warning("PLC KEEP_ALIVE trip time > 500ms (" .. trip_time .. "ms)") + end + + -- log.debug("RPLC RTT = ".. trip_time .. "ms") + + _send_keep_alive_ack(timestamp) + else + log.debug("SCADA keep alive packet length mismatch") + end + elseif packet.type == SCADA_MGMT_TYPES.CLOSE then + -- handle session close + self.conn_watchdog.cancel() unlink() println_ts("server connection closed by remote host") log.warning("server connection closed by remote host") else log.warning("received unknown SCADA_MGMT packet type " .. packet.type) end + else + -- should be unreachable assuming packet is from parse_packet() + log.error("illegal packet type " .. protocol, true) end end end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index e1b07ab..1f3604f 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -11,7 +11,7 @@ local config = require("config") local plc = require("plc") local threads = require("threads") -local R_PLC_VERSION = "alpha-v0.6.4" +local R_PLC_VERSION = "alpha-v0.6.5" local print = util.print local println = util.println @@ -102,30 +102,35 @@ function init() -- init reactor protection system smem_sys.rps = plc.rps_init(smem_dev.reactor) - log.debug("rps init") + log.debug("init> rps init") if __shared_memory.networked then - -- start comms - smem_sys.plc_comms = plc.comms(config.REACTOR_ID, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, smem_dev.reactor, smem_sys.rps) - log.debug("comms init") - -- comms watchdog, 3 second timeout smem_sys.conn_watchdog = util.new_watchdog(3) - log.debug("conn watchdog started") + log.debug("init> conn watchdog started") + + -- start comms + smem_sys.plc_comms = plc.comms(config.REACTOR_ID, 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("running without networking") + log.debug("init> running without networking") end os.queueEvent("clock_start") println("boot> completed"); + log.debug("init> boot completed") else println("boot> system in degraded state, awaiting devices...") - log.warning("booted in a degraded state, awaiting peripheral connections...") + log.warning("init> booted in a degraded state, awaiting peripheral connections...") end end +---------------------------------------- +-- start system +---------------------------------------- + -- initialize PLC init() diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 3048e42..83359d7 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -125,14 +125,15 @@ rtu.init_unit = function () } end -rtu.comms = function (modem, local_port, server_port) +rtu.comms = function (modem, local_port, server_port, conn_watchdog) local self = { seq_num = 0, r_seq_num = nil, txn_id = 0, modem = modem, s_port = server_port, - l_port = local_port + l_port = local_port, + conn_watchdog = conn_watchdog } -- open modem @@ -153,8 +154,21 @@ rtu.comms = function (modem, local_port, server_port) self.seq_num = self.seq_num + 1 end + -- keep alive ack + local _send_keep_alive_ack = function (srv_time) + _send(SCADA_MGMT_TYPES.KEEP_ALIVE, { srv_time, util.time() }) + end + -- PUBLIC FUNCTIONS -- + -- send a MODBUS TCP packet + local send_modbus = function (m_pkt) + local s_pkt = comms.scada_packet() + s_pkt.make(self.seq_num, PROTOCOLS.MODBUS_TCP, m_pkt.raw_sendable()) + self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) + self.seq_num = self.seq_num + 1 + end + -- reconnect a newly connected modem local reconnect_modem = function (modem) self.modem = modem @@ -165,12 +179,47 @@ rtu.comms = function (modem, local_port, server_port) end end - -- send a MODBUS TCP packet - local send_modbus = function (m_pkt) - local s_pkt = comms.scada_packet() - s_pkt.make(self.seq_num, PROTOCOLS.MODBUS_TCP, m_pkt.raw_sendable()) - self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) - self.seq_num = self.seq_num + 1 + -- unlink from the server + local unlink = function (rtu_state) + rtu_state.linked = false + self.r_seq_num = nil + end + + -- close the connection to the server + local close = function (rtu_state) + self.conn_watchdog.cancel() + unlink(rtu_state) + _send(SCADA_MGMT_TYPES.CLOSE, {}) + end + + -- send capability advertisement + local send_advertisement = function (units) + local advertisement = {} + + for i = 1, #units do + local unit = units[i] + local type = comms.rtu_t_to_advert_type(unit.type) + + if type ~= nil then + if type == RTU_ADVERT_TYPES.REDSTONE then + insert(advertisement, { + type = type, + index = unit.index, + reactor = unit.for_reactor, + rsio = unit.device + }) + else + insert(advertisement, { + type = type, + index = unit.index, + reactor = unit.for_reactor, + rsio = nil + }) + end + end + end + + _send(SCADA_MGMT_TYPES.RTU_ADVERT, advertisement) end -- parse a MODBUS/SCADA packet @@ -203,7 +252,7 @@ rtu.comms = function (modem, local_port, server_port) end -- handle a MODBUS/SCADA packet - local handle_packet = function(packet, units, rtu_state, conn_watchdog) + local handle_packet = function(packet, units, rtu_state) if packet ~= nil then local seq_ok = true @@ -218,7 +267,7 @@ rtu.comms = function (modem, local_port, server_port) end -- feed watchdog on valid sequence number - conn_watchdog.feed() + self.conn_watchdog.feed() local protocol = packet.scada_frame.protocol() @@ -257,10 +306,28 @@ rtu.comms = function (modem, local_port, server_port) send_modbus(reply) elseif protocol == PROTOCOLS.SCADA_MGMT then -- SCADA management packet - if packet.type == SCADA_MGMT_TYPES.CLOSE then + if packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + -- keep alive request received, echo back + if packet.length == 1 then + local timestamp = packet.data[1] + local trip_time = util.time() - timestamp + + if trip_time > 500 then + log.warning("RTU KEEP_ALIVE trip time > 500ms (" .. trip_time .. "ms)") + end + + -- log.debug("RTU RTT = ".. trip_time .. "ms") + + _send_keep_alive_ack(timestamp) + else + log.debug("SCADA keep alive packet length mismatch") + end + elseif packet.type == SCADA_MGMT_TYPES.CLOSE then -- close connection - conn_watchdog.cancel() + self.conn_watchdog.cancel() unlink(rtu_state) + println_ts("server connection closed by remote host") + log.warning("server connection closed by remote host") elseif packet.type == SCADA_MGMT_TYPES.REMOTE_LINKED then -- acknowledgement rtu_state.linked = true @@ -279,50 +346,6 @@ rtu.comms = function (modem, local_port, server_port) end end - -- send capability advertisement - local send_advertisement = function (units) - local advertisement = {} - - for i = 1, #units do - local unit = units[i] - local type = comms.rtu_t_to_advert_type(unit.type) - - if type ~= nil then - if type == RTU_ADVERT_TYPES.REDSTONE then - insert(advertisement, { - type = type, - index = unit.index, - reactor = unit.for_reactor, - rsio = unit.device - }) - else - insert(advertisement, { - type = type, - index = unit.index, - reactor = unit.for_reactor, - rsio = nil - }) - end - end - end - - _send(SCADA_MGMT_TYPES.RTU_ADVERT, advertisement) - end - - local send_heartbeat = function () - _send(SCADA_MGMT_TYPES.RTU_HEARTBEAT, {}) - end - - local unlink = function (rtu_state) - rtu_state.linked = false - self.r_seq_num = nil - end - - local close = function (rtu_state) - unlink(rtu_state) - _send(SCADA_MGMT_TYPES.CLOSE, {}) - end - return { send_modbus = send_modbus, reconnect_modem = reconnect_modem, diff --git a/rtu/startup.lua b/rtu/startup.lua index 4edcbc9..5c4a6f4 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -22,7 +22,7 @@ local imatrix_rtu = require("dev.imatrix_rtu") local turbine_rtu = require("dev.turbine_rtu") local turbinev_rtu = require("dev.turbinev_rtu") -local RTU_VERSION = "alpha-v0.6.0" +local RTU_VERSION = "alpha-v0.6.1" local rtu_t = types.rtu_t @@ -80,8 +80,6 @@ if smem_dev.modem == nil then return end -smem_sys.rtu_comms = rtu.comms(smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT) - ---------------------------------------- -- interpret config and init units ---------------------------------------- @@ -230,14 +228,18 @@ end -- start system ---------------------------------------- +-- 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(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) --- start connection watchdog -smem_sys.conn_watchdog = util.new_watchdog(5) -log.debug("init> conn watchdog started") - -- assemble thread list local _threads = { main_thread.exec, comms_thread.exec } for i = 1, #units do diff --git a/rtu/threads.lua b/rtu/threads.lua index 12f90f7..db27c61 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -180,8 +180,7 @@ threads.thread__comms = function (smem) elseif msg.qtype == mqueue.TYPE.PACKET then -- received a packet -- handle the packet (rtu_state passed to allow setting link flag) - -- (conn_watchdog passed to allow feeding watchdog) - rtu_comms.handle_packet(msg.message, units, rtu_state, conn_watchdog) + rtu_comms.handle_packet(msg.message, units, rtu_state) end -- quick yield @@ -211,7 +210,6 @@ threads.thread__unit_comms = function (smem, unit) -- load in from shared memory local rtu_state = smem.rtu_state - local packet_queue = unit.pkt_queue local last_update = util.time() diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 7a41ff7..8685390 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -18,16 +18,15 @@ local PROTOCOLS = { } local RPLC_TYPES = { - KEEP_ALIVE = 0, -- keep alive packets - LINK_REQ = 1, -- linking requests - STATUS = 2, -- reactor/system status - MEK_STRUCT = 3, -- mekanism build structure - MEK_BURN_RATE = 4, -- set burn rate - RPS_ENABLE = 5, -- enable reactor - RPS_SCRAM = 6, -- SCRAM reactor - RPS_STATUS = 7, -- RPS status - RPS_ALARM = 8, -- RPS alarm broadcast - RPS_RESET = 9 -- clear RPS trip (if in bad state, will trip immediately) + LINK_REQ = 0, -- linking requests + STATUS = 1, -- reactor/system status + MEK_STRUCT = 2, -- mekanism build structure + MEK_BURN_RATE = 3, -- set burn rate + RPS_ENABLE = 4, -- enable reactor + RPS_SCRAM = 5, -- SCRAM reactor + RPS_STATUS = 6, -- RPS status + RPS_ALARM = 7, -- RPS alarm broadcast + RPS_RESET = 8 -- clear RPS trip (if in bad state, will trip immediately) } local RPLC_LINKING = { @@ -37,11 +36,10 @@ local RPLC_LINKING = { } local SCADA_MGMT_TYPES = { - PING = 0, -- generic ping + KEEP_ALIVE = 0, -- keep alive packet w/ RTT CLOSE = 1, -- close a connection - REMOTE_LINKED = 2, -- remote device linked - RTU_ADVERT = 3, -- RTU capability advertisement - RTU_HEARTBEAT = 4 -- RTU heartbeat + RTU_ADVERT = 2, -- RTU capability advertisement + REMOTE_LINKED = 3 -- remote device linked } local RTU_ADVERT_TYPES = { diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 1206ef6..22726d3 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -243,24 +243,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) self.plc_conn_watchdog.feed() -- handle packet by type - if pkt.type == RPLC_TYPES.KEEP_ALIVE then - -- keep alive reply - if pkt.length == 2 then - local srv_start = pkt.data[1] - local plc_send = pkt.data[2] - local srv_now = util.time() - self.last_rtt = srv_now - srv_start - - if self.last_rtt > 500 then - log.warning(log_header .. "PLC KEEP_ALIVE round trip time > 500ms (" .. self.last_rtt .. ")") - end - - -- log.debug(log_header .. "RPLC RTT = ".. self.last_rtt .. "ms") - -- log.debug(log_header .. "RPLC TT = ".. (srv_now - plc_send) .. "ms") - else - log.debug(log_header .. "RPLC keep alive packet length mismatch") - end - elseif pkt.type == RPLC_TYPES.STATUS then + if pkt.type == RPLC_TYPES.STATUS then -- status packet received, update data if pkt.length >= 5 then self.sDB.last_status_update = pkt.data[1] @@ -366,7 +349,24 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) log.debug(log_header .. "handler received unsupported RPLC packet type " .. pkt.type) end elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then - if pkt.type == SCADA_MGMT_TYPES.CLOSE then + if pkt.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + -- keep alive reply + if pkt.length == 2 then + local srv_start = pkt.data[1] + local plc_send = pkt.data[2] + local srv_now = util.time() + self.last_rtt = srv_now - srv_start + + if self.last_rtt > 500 then + log.warning(log_header .. "PLC KEEP_ALIVE round trip time > 500ms (" .. self.last_rtt .. "ms)") + end + + -- log.debug(log_header .. "PLC RTT = ".. self.last_rtt .. "ms") + -- log.debug(log_header .. "PLC TT = ".. (srv_now - plc_send) .. "ms") + else + log.debug(log_header .. "SCADA keep alive packet length mismatch") + end + elseif pkt.type == SCADA_MGMT_TYPES.CLOSE then -- close the session self.connected = false else @@ -497,7 +497,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) periodics.keep_alive = periodics.keep_alive + elapsed if periodics.keep_alive >= PERIODICS.KEEP_ALIVE then - _send(RPLC_TYPES.KEEP_ALIVE, { util.time() }) + _send_mgmt(SCADA_MGMT_TYPES.KEEP_ALIVE, { util.time() }) periodics.keep_alive = 0 end diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 1cf4e76..90f6e95 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -35,7 +35,7 @@ rtu.new_session = function (id, in_queue, out_queue) } -- send a MODBUS TCP packet - local send_modbus = function (m_pkt) + local _send_modbus = function (m_pkt) local s_pkt = comms.scada_packet() s_pkt.make(self.seq_num, PROTOCOLS.MODBUS_TCP, m_pkt.raw_sendable()) self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) @@ -66,16 +66,31 @@ rtu.new_session = function (id, in_queue, out_queue) self.r_seq_num = pkt.scada_frame.seq_num() end + -- feed watchdog + self.rtu_conn_watchdog.feed() + -- process packet if pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then - -- feed watchdog - self.rtu_conn_watchdog.feed() - elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then - -- feed watchdog - self.rtu_conn_watchdog.feed() - if pkt.type == SCADA_MGMT_TYPES.CLOSE then + if pkt.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + -- keep alive reply + if pkt.length == 2 then + local srv_start = pkt.data[1] + local rtu_send = pkt.data[2] + local srv_now = util.time() + self.last_rtt = srv_now - srv_start + + if self.last_rtt > 500 then + log.warning(log_header .. "RTU KEEP_ALIVE round trip time > 500ms (" .. self.last_rtt .. "ms)") + end + + -- log.debug(log_header .. "RTU RTT = ".. self.last_rtt .. "ms") + -- log.debug(log_header .. "RTU TT = ".. (srv_now - rtu_send) .. "ms") + else + log.debug(log_header .. "SCADA keep alive packet length mismatch") + end + elseif pkt.type == SCADA_MGMT_TYPES.CLOSE then -- close the session self.connected = false elseif pkt.type == SCADA_MGMT_TYPES.RTU_ADVERT then @@ -84,8 +99,6 @@ rtu.new_session = function (id, in_queue, out_queue) local unit = packet.data[i] unit end - elseif pkt.type == SCADA_MGMT_TYPES.RTU_HEARTBEAT then - -- periodic RTU heartbeat else log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) end @@ -162,7 +175,7 @@ rtu.new_session = function (id, in_queue, out_queue) periodics.keep_alive = periodics.keep_alive + elapsed if periodics.keep_alive >= PERIODICS.KEEP_ALIVE then - -- _send(RPLC_TYPES.KEEP_ALIVE, { util.time() }) + _send_mgmt(SCADA_MGMT_TYPES.KEEP_ALIVE, { util.time() }) periodics.keep_alive = 0 end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index a7ebcfe..825e30c 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("session.svsessions") local config = require("config") local supervisor = require("supervisor") -local SUPERVISOR_VERSION = "alpha-v0.3.3" +local SUPERVISOR_VERSION = "alpha-v0.3.4" local print = util.print local println = util.println From cd0d7aa5a366e3de5f72a8bc772b54dfde7b856e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 10 May 2022 11:35:52 -0400 Subject: [PATCH 163/587] cleanup/fixes of scada common code --- scada-common/comms.lua | 11 ++++----- scada-common/log.lua | 49 +++++++++++++++++++++-------------------- scada-common/mqueue.lua | 6 ++--- scada-common/ppm.lua | 2 +- scada-common/rsio.lua | 2 +- scada-common/util.lua | 8 ++++--- 6 files changed, 41 insertions(+), 37 deletions(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 8685390..5a84fbc 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -2,6 +2,7 @@ -- Communications -- +local log = require("scada-common.log") local types = require("scada-common.types") local comms = {} @@ -146,11 +147,11 @@ comms.modbus_packet = function () local self = { frame = nil, raw = nil, - txn_id = txn_id, - length = length, - unit_id = unit_id, - func_code = func_code, - data = data + txn_id = nil, + length = nil, + unit_id = nil, + func_code = nil, + data = nil } -- make a MODBUS packet diff --git a/scada-common/log.lua b/scada-common/log.lua index a4f63b2..e841b23 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -13,10 +13,6 @@ local MODE = { log.MODE = MODE ----------------------------- --- PRIVATE DATA/FUNCTIONS -- ----------------------------- - local LOG_DEBUG = true local _log_sys = { @@ -25,12 +21,27 @@ local _log_sys = { file = nil } +local free_space = fs.getFreeSpace + +-- initialize logger +log.init = function (path, write_mode) + _log_sys.path = path + _log_sys.mode = write_mode + + if _log_sys.mode == MODE.APPEND then + _log_sys.file = fs.open(path, "a") + else + _log_sys.file = fs.open(path, "w+") + end +end + +-- private log write function local _log = function (msg) local time_stamp = os.date("[%c] ") local stamped = time_stamp .. msg -- attempt to write log - local status, result = pcall(function () + local status, result = pcall(function () _log_sys.file.writeLine(stamped) _log_sys.file.flush() end) @@ -45,11 +56,11 @@ local _log = function (msg) end end - if (result == "Out of space") or (fs.getFreeSpace(_log_sys.path) < 100) then + if (result == "Out of space") or (free_space(_log_sys.path) < 100) then -- delete the old log file and open a new one _log_sys.file.close() fs.delete(_log_sys.path) - init(_log_sys.path, _log_sys.mode) + log.init(_log_sys.path, _log_sys.mode) -- leave a message _log_sys.file.writeLine(time_stamp .. "recycled log file") @@ -58,21 +69,7 @@ local _log = function (msg) end end ----------------------- --- PUBLIC FUNCTIONS -- ----------------------- - -log.init = function (path, write_mode) - _log_sys.path = path - _log_sys.mode = write_mode - - if _log_sys.mode == MODE.APPEND then - _log_sys.file = fs.open(path, "a") - else - _log_sys.file = fs.open(path, "w+") - end -end - +-- log debug messages log.debug = function (msg, trace) if LOG_DEBUG then local dbg_info = "" @@ -92,17 +89,20 @@ log.debug = function (msg, trace) end end +-- log info messages log.info = function (msg) _log("[INF] " .. msg) end +-- log warning messages log.warning = function (msg) _log("[WRN] " .. msg) end +-- log error messages log.error = function (msg, trace) local dbg_info = "" - + if trace then local info = debug.getinfo(2) local name = "" @@ -110,13 +110,14 @@ log.error = function (msg, trace) if info.name ~= nil then name = ":" .. info.name .. "():" end - + dbg_info = info.short_src .. ":" .. name .. info.currentline .. " > " end _log("[ERR] " .. dbg_info .. msg) end +-- log fatal errors log.fatal = function (msg) _log("[FTL] " .. msg) end diff --git a/scada-common/mqueue.lua b/scada-common/mqueue.lua index c24c15c..d1ac5c1 100644 --- a/scada-common/mqueue.lua +++ b/scada-common/mqueue.lua @@ -29,11 +29,11 @@ mqueue.new = function () local ready = function () return #queue ~= 0 end - + local _push = function (qtype, message) insert(queue, { qtype = qtype, message = message }) end - + local push_command = function (message) _push(TYPE.COMMAND, message) end @@ -49,7 +49,7 @@ mqueue.new = function () local pop = function () if #queue > 0 then return remove(queue, 1) - else + else return nil end end diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 5e15724..e834946 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -234,7 +234,7 @@ ppm.get_device = function (name) break end end - + return device end diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index faf9008..d5a3d5a 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -231,7 +231,7 @@ rsio.digital_write = function (channel, active) if channel < RS_IO.WASTE_PO or channel > RS_IO.R_PLC_TIMEOUT then return IO_LVL.LOW else - return RS_DIO_MAP[channel]._f(level) + return RS_DIO_MAP[channel]._f(active) end end diff --git a/scada-common/util.lua b/scada-common/util.lua index a963a08..4ee8567 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -39,6 +39,7 @@ end -- PARALLELIZATION -- -- protected sleep call so we still are in charge of catching termination +-- EVENT_CONSUMER: this function consumes events util.psleep = function (t) pcall(os.sleep, t) end @@ -50,6 +51,7 @@ util.nop = function () end -- attempt to maintain a minimum loop timing (duration of execution) +-- EVENT_CONSUMER: this function consumes events util.adaptive_delay = function (target_timing, last_update) local sleep_for = target_timing - (util.time() - last_update) -- only if >50ms since worker loops already yield 0.05s @@ -64,15 +66,15 @@ end -- ComputerCraft OS Timer based Watchdog -- triggers a timer event if not fed within 'timeout' seconds util.new_watchdog = function (timeout) - local self = { - _timeout = timeout, + local self = { + _timeout = timeout, _wd_timer = os.startTimer(timeout) } local get_timer = function () return self._wd_timer end - + local feed = function () if self._wd_timer ~= nil then os.cancelTimer(self._wd_timer) From d7e38d63930d34fe325282dc81c9517c166374db Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 10 May 2022 11:41:49 -0400 Subject: [PATCH 164/587] supression of warnings, added lua diagnostics global list --- .vscode/settings.json | 9 +++++++++ scada-common/util.lua | 16 ++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7978039 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "Lua.diagnostics.globals": [ + "term", + "fs", + "peripheral", + "rs", + "bit" + ] +} \ No newline at end of file diff --git a/scada-common/util.lua b/scada-common/util.lua index 4ee8567..78ab422 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -25,10 +25,12 @@ end -- TIME -- util.time_ms = function () +---@diagnostic disable-next-line: undefined-field return os.epoch('local') end util.time_s = function () +---@diagnostic disable-next-line: undefined-field return os.epoch('local') / 1000 end @@ -41,6 +43,7 @@ end -- protected sleep call so we still are in charge of catching termination -- EVENT_CONSUMER: this function consumes events util.psleep = function (t) +---@diagnostic disable-next-line: undefined-field pcall(os.sleep, t) end @@ -66,9 +69,14 @@ end -- ComputerCraft OS Timer based Watchdog -- triggers a timer event if not fed within 'timeout' seconds util.new_watchdog = function (timeout) + ---@diagnostic disable-next-line: undefined-field + local start_timer = os.startTimer + ---@diagnostic disable-next-line: undefined-field + local cancel_timer = os.cancelTimer + local self = { _timeout = timeout, - _wd_timer = os.startTimer(timeout) + _wd_timer = start_timer(timeout) } local get_timer = function () @@ -77,14 +85,14 @@ util.new_watchdog = function (timeout) local feed = function () if self._wd_timer ~= nil then - os.cancelTimer(self._wd_timer) + cancel_timer(self._wd_timer) end - self._wd_timer = os.startTimer(self._timeout) + self._wd_timer = start_timer(self._timeout) end local cancel = function () if self._wd_timer ~= nil then - os.cancelTimer(self._wd_timer) + cancel_timer(self._wd_timer) end end From 168341db39b01a8884c2cbfd84b0e5244fb45640 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 10 May 2022 12:01:56 -0400 Subject: [PATCH 165/587] code cleanup and bugfixes --- .vscode/settings.json | 4 +++- reactor-plc/plc.lua | 6 ++++-- reactor-plc/startup.lua | 12 +++++++----- reactor-plc/threads.lua | 3 ++- rtu/dev/redstone_rtu.lua | 4 ++-- rtu/modbus.lua | 12 ++++++------ rtu/rtu.lua | 12 ++++++++++-- rtu/startup.lua | 2 +- rtu/threads.lua | 5 ++++- supervisor/session/rtu.lua | 5 ++++- supervisor/session/svsessions.lua | 2 +- supervisor/unit.lua | 8 ++++---- 12 files changed, 48 insertions(+), 27 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 7978039..75bb696 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,8 @@ "fs", "peripheral", "rs", - "bit" + "bit", + "parallel", + "colors" ] } \ No newline at end of file diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 290ca64..5b84c7f 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -558,8 +558,10 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat -- feed the watchdog first so it doesn't uhh...eat our packets :) self.conn_watchdog.feed() + local protocol = packet.scada_frame.protocol() + -- handle packet - if packet.scada_frame.protocol() == PROTOCOLS.RPLC then + if protocol == PROTOCOLS.RPLC then if self.linked then if packet.type == RPLC_TYPES.LINK_REQ then -- link request confirmation @@ -678,7 +680,7 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat else log.debug("discarding non-link packet before linked") end - elseif packet.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then + elseif protocol == PROTOCOLS.SCADA_MGMT then if packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then -- keep alive request received, echo back if packet.length == 1 then diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 1f3604f..1082afd 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -11,7 +11,7 @@ local config = require("config") local plc = require("plc") local threads = require("threads") -local R_PLC_VERSION = "alpha-v0.6.5" +local R_PLC_VERSION = "alpha-v0.6.6" local print = util.print local println = util.println @@ -46,7 +46,7 @@ local __shared_memory = { burn_rate_en = false, burn_rate = 0.0 }, - + -- core PLC devices plc_dev = { reactor = ppm.get_fission_reactor(), @@ -82,7 +82,7 @@ if smem_dev.reactor == nil then plc_state.degraded = true plc_state.no_reactor = true end -if networked and smem_dev.modem == nil then +if __shared_memory.networked and smem_dev.modem == nil then println("boot> wireless modem not found") log.warning("no wireless modem on startup") @@ -95,7 +95,8 @@ if networked and smem_dev.modem == nil then plc_state.no_modem = true end -function init() +-- PLC init +local init = function () if plc_state.init_ok then -- just booting up, no fission allowed (neutrons stay put thanks) smem_dev.reactor.scram() @@ -117,6 +118,7 @@ function init() log.debug("init> running without networking") end +---@diagnostic disable-next-line: undefined-field os.queueEvent("clock_start") println("boot> completed"); @@ -155,7 +157,7 @@ if __shared_memory.networked then smem_sys.plc_comms.send_rps_status() -- close connection - smem_sys.plc_comms.close(smem_sys.conn_watchdog) + smem_sys.plc_comms.close() end else -- run threads, excluding comms diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index e984b88..9dcb26e 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -51,6 +51,7 @@ threads.thread__main = function (smem, init) -- event loop while true do +---@diagnostic disable-next-line: undefined-field local event, param1, param2, param3, param4, param5 = os.pullEventRaw() -- handle event @@ -443,7 +444,7 @@ threads.thread__setpoint_control = function (smem) if running then -- do not use the actual elapsed time, it could spike -- we do not want to have big jumps as that is what we are trying to avoid in the first place - local min_elapsed_s = SETPOINT_CTRL_SLEEP / 1000.0 + local min_elapsed_s = SP_CTRL_SLEEP / 1000.0 -- clear so we can later evaluate if we should keep running running = false diff --git a/rtu/dev/redstone_rtu.lua b/rtu/dev/redstone_rtu.lua index 163b749..9683f57 100644 --- a/rtu/dev/redstone_rtu.lua +++ b/rtu/dev/redstone_rtu.lua @@ -27,7 +27,7 @@ redstone_rtu.new = function () return digital_read(rs.getInput(side)) end end - + self.rtu.connect_di(f_read) end @@ -61,7 +61,7 @@ redstone_rtu.new = function () rs.setOutput(side, digital_is_active(channel, level)) end end - + self.rtu.connect_coil(f_read, f_write) end diff --git a/rtu/modbus.lua b/rtu/modbus.lua index bb1817a..b64910f 100644 --- a/rtu/modbus.lua +++ b/rtu/modbus.lua @@ -64,7 +64,7 @@ modbus.new = function (rtu_dev, use_parallel_read) local access_fault = false local discrete_inputs, _, _, _ = self.rtu.io_count() local return_ok = ((di_addr_start + count) <= discrete_inputs) and (count > 0) - + if return_ok then for i = 1, count do local addr = di_addr_start + i - 1 @@ -197,7 +197,7 @@ modbus.new = function (rtu_dev, use_parallel_read) if access_fault then return_ok = false - readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL + response = MODBUS_EXCODE.SERVER_DEVICE_FAIL end else response = MODBUS_EXCODE.ILLEGAL_DATA_ADDR @@ -210,13 +210,13 @@ modbus.new = function (rtu_dev, use_parallel_read) local response = nil local _, _, _, hold_regs = self.rtu.io_count() local return_ok = hr_addr <= hold_regs - + if return_ok then local access_fault = self.rtu.write_holding_reg(hr_addr, value) if access_fault then return_ok = false - readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL + response = MODBUS_EXCODE.SERVER_DEVICE_FAIL end else response = MODBUS_EXCODE.ILLEGAL_DATA_ADDR @@ -238,7 +238,7 @@ modbus.new = function (rtu_dev, use_parallel_read) if access_fault then return_ok = false - readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL + response = MODBUS_EXCODE.SERVER_DEVICE_FAIL break end end @@ -262,7 +262,7 @@ modbus.new = function (rtu_dev, use_parallel_read) if access_fault then return_ok = false - readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL + response = MODBUS_EXCODE.SERVER_DEVICE_FAIL break end end diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 83359d7..ec20c93 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -1,6 +1,8 @@ local comms = require("scada-common.comms") local ppm = require("scada-common.ppm") +local log = require("scada-common.log") local types = require("scada-common.types") +local util = require("scada-common.util") local modbus = require("modbus") @@ -12,6 +14,11 @@ local PROTOCOLS = comms.PROTOCOLS local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local RTU_ADVERT_TYPES = comms.RTU_ADVERT_TYPES +local print = util.print +local println = util.println +local print_ts = util.print_ts +local println_ts = util.println_ts + rtu.init_unit = function () local self = { discrete_inputs = {}, @@ -136,6 +143,8 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) conn_watchdog = conn_watchdog } + local insert = table.insert + -- open modem if not self.modem.isOpen(self.l_port) then self.modem.open(self.l_port) @@ -337,7 +346,7 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) send_advertisement(units) else -- not supported - log.warning("RTU got unexpected SCADA message type " .. packet.type, true) + log.warning("RTU got unexpected SCADA message type " .. packet.type) end else -- should be unreachable assuming packet is from parse_packet() @@ -352,7 +361,6 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) parse_packet = parse_packet, handle_packet = handle_packet, send_advertisement = send_advertisement, - send_heartbeat = send_heartbeat, unlink = unlink, close = close } diff --git a/rtu/startup.lua b/rtu/startup.lua index 5c4a6f4..18119c0 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -22,7 +22,7 @@ local imatrix_rtu = require("dev.imatrix_rtu") local turbine_rtu = require("dev.turbine_rtu") local turbinev_rtu = require("dev.turbinev_rtu") -local RTU_VERSION = "alpha-v0.6.1" +local RTU_VERSION = "alpha-v0.6.2" local rtu_t = types.rtu_t diff --git a/rtu/threads.lua b/rtu/threads.lua index db27c61..c113e1d 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -1,5 +1,6 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") local ppm = require("scada-common.ppm") local types = require("scada-common.types") local util = require("scada-common.util") @@ -46,6 +47,7 @@ threads.thread__main = function (smem) -- event loop while true do +---@diagnostic disable-next-line: undefined-field local event, param1, param2, param3, param4, param5 = os.pullEventRaw() if event == "timer" and param1 == loop_clock then @@ -210,6 +212,7 @@ threads.thread__unit_comms = function (smem, unit) -- load in from shared memory local rtu_state = smem.rtu_state + local rtu_comms = smem.rtu_sys.rtu_comms local packet_queue = unit.pkt_queue local last_update = util.time() @@ -228,7 +231,7 @@ threads.thread__unit_comms = function (smem, unit) -- received a packet unit.modbus_busy = true local return_code, reply = unit.modbus_io.handle_packet(packet) - rtu.send_modbus(reply) + rtu_comms.send_modbus(reply) unit.modbus_busy = false end diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 90f6e95..2e79ec8 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -14,6 +14,10 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts +local PERIODICS = { + KEEP_ALIVE = 2.0 +} + rtu.new_session = function (id, in_queue, out_queue) local log_header = "rtu_session(" .. id .. "): " @@ -97,7 +101,6 @@ rtu.new_session = function (id, in_queue, out_queue) -- RTU unit advertisement for i = 1, packet.length do local unit = packet.data[i] - unit end else log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 578c8ae..fa983a6 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -97,7 +97,7 @@ local function _free_closed(sessions) for i = 1, #sessions do local session = sessions[i] if session ~= nil then - if sessions[i].open then + if session.open then if sessions[move_to] == nil then sessions[move_to] = session sessions[i] = nil diff --git a/supervisor/unit.lua b/supervisor/unit.lua index e7569d6..95aa5f1 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -45,18 +45,18 @@ unit.new = function (for_reactor) table.insert(self.turbines, turbine) end - public.add_boiler = function (turbine) + public.add_boiler = function (boiler) table.insert(self.boilers, boiler) end public.add_redstone = function (field, accessor) -- ensure field exists - if redstone[field] == nil then - redstone[field] = {} + if self.redstone[field] == nil then + self.redstone[field] = {} end -- insert into list - table.insert(redstone[field], accessor) + table.insert(self.redstone[field], accessor) end local _update_annunciator = function () From 6e1ece8183c91670a1aa7a77858b6b69041cb92f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 10 May 2022 13:06:13 -0400 Subject: [PATCH 166/587] watchdog cleanup and loop clock object --- reactor-plc/threads.lua | 12 +++---- rtu/threads.lua | 22 ++++++------ scada-common/util.lua | 71 ++++++++++++++++++++++++++++---------- supervisor/session/plc.lua | 2 +- supervisor/session/rtu.lua | 2 +- 5 files changed, 72 insertions(+), 37 deletions(-) diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 9dcb26e..bf05f22 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -39,7 +39,7 @@ threads.thread__main = function (smem, init) -- send link requests at 0.5Hz (every 40 server ticks) (every 4 loop ticks) local LINK_TICKS = 4 local ticks_to_update = 0 - local loop_clock = nil + local loop_clock = util.new_clock(MAIN_CLOCK) -- load in from shared memory local networked = smem.networked @@ -47,7 +47,7 @@ threads.thread__main = function (smem, init) local plc_dev = smem.plc_dev local rps = smem.plc_sys.rps local plc_comms = smem.plc_sys.plc_comms - local conn_watchdog = smem.plc_sys.conn_watchdog + local conn_watchdog = smem.plc_sys.conn_watchdog ---@type watchdog -- event loop while true do @@ -55,11 +55,11 @@ threads.thread__main = function (smem, init) local event, param1, param2, param3, param4, param5 = os.pullEventRaw() -- handle event - if event == "timer" and param1 == loop_clock then + if event == "timer" and loop_clock.is_clock(param1) then -- core clock tick if networked then -- start next clock timer - loop_clock = os.startTimer(MAIN_CLOCK) + loop_clock.start() -- send updated data if not plc_state.no_modem then @@ -82,7 +82,7 @@ threads.thread__main = function (smem, init) -- pass the packet onto the comms message queue smem.q.mq_comms_rx.push_packet(packet) end - elseif event == "timer" and networked and param1 == conn_watchdog.get_timer() then + elseif event == "timer" and networked and conn_watchdog.is_timer(param1) then -- haven't heard from server recently? shutdown reactor plc_comms.unlink() smem.q.mq_rps.push_command(MQ__RPS_CMD.TRIP_TIMEOUT) @@ -165,7 +165,7 @@ threads.thread__main = function (smem, init) end elseif event == "clock_start" then -- start loop clock - loop_clock = os.startTimer(MAIN_CLOCK) + loop_clock.start() log.debug("main thread clock started") end diff --git a/rtu/threads.lua b/rtu/threads.lua index c113e1d..ef30b45 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -35,29 +35,30 @@ threads.thread__main = function (smem) local exec = function () log.debug("main thread start") - -- advertisement/heartbeat clock - local loop_clock = os.startTimer(MAIN_CLOCK) + -- main loop clock + local loop_clock = util.new_clock(MAIN_CLOCK) -- load in from shared memory local rtu_state = smem.rtu_state local rtu_dev = smem.rtu_dev local rtu_comms = smem.rtu_sys.rtu_comms - local conn_watchdog = smem.rtu_sys.conn_watchdog + local conn_watchdog = smem.rtu_sys.conn_watchdog ---@type watchdog local units = smem.rtu_sys.units + -- start clock + loop_clock.start() + -- event loop while true do ---@diagnostic disable-next-line: undefined-field local event, param1, param2, param3, param4, param5 = os.pullEventRaw() - if event == "timer" and param1 == loop_clock then + if event == "timer" and loop_clock.is_clock(param1) then -- start next clock timer - loop_clock = os.startTimer(MAIN_CLOCK) + loop_clock.start() - -- period tick, if we are linked send heartbeat, if not send advertisement - if rtu_state.linked then - rtu_comms.send_heartbeat() - else + -- period tick, if we are not linked send advertisement + if not rtu_state.linked then -- advertise units rtu_comms.send_advertisement(units) end @@ -68,7 +69,7 @@ threads.thread__main = function (smem) -- pass the packet onto the comms message queue smem.q.mq_comms.push_packet(packet) end - elseif event == "timer" and param1 == conn_watchdog.get_timer() then + elseif event == "timer" and conn_watchdog.is_timer(param1) then -- haven't heard from server recently? unlink rtu_comms.unlink(rtu_state) elseif event == "peripheral_detach" then @@ -162,7 +163,6 @@ threads.thread__comms = function (smem) -- load in from shared memory local rtu_state = smem.rtu_state local rtu_comms = smem.rtu_sys.rtu_comms - local conn_watchdog = smem.rtu_sys.conn_watchdog local units = smem.rtu_sys.units local comms_queue = smem.q.mq_comms diff --git a/scada-common/util.lua b/scada-common/util.lua index 78ab422..77ad4c9 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -67,40 +67,75 @@ end -- WATCHDOG -- -- ComputerCraft OS Timer based Watchdog --- triggers a timer event if not fed within 'timeout' seconds +---@param timeout number timeout duration +--- +--- triggers a timer event if not fed within 'timeout' seconds util.new_watchdog = function (timeout) - ---@diagnostic disable-next-line: undefined-field +---@diagnostic disable-next-line: undefined-field local start_timer = os.startTimer - ---@diagnostic disable-next-line: undefined-field +---@diagnostic disable-next-line: undefined-field local cancel_timer = os.cancelTimer local self = { - _timeout = timeout, - _wd_timer = start_timer(timeout) + timeout = timeout, + wd_timer = start_timer(timeout) } - local get_timer = function () - return self._wd_timer + ---@class watchdog + local public = {} + + ---@param timer number timer event timer ID + public.is_timer = function (timer) + return self.wd_timer == timer end - local feed = function () - if self._wd_timer ~= nil then - cancel_timer(self._wd_timer) + -- satiate the beast + public.feed = function () + if self.wd_timer ~= nil then + cancel_timer(self.wd_timer) end - self._wd_timer = start_timer(self._timeout) + self.wd_timer = start_timer(self.timeout) end - local cancel = function () - if self._wd_timer ~= nil then - cancel_timer(self._wd_timer) + -- cancel the watchdog + public.cancel = function () + if self.wd_timer ~= nil then + cancel_timer(self.wd_timer) end end - return { - get_timer = get_timer, - feed = feed, - cancel = cancel + return public +end + +-- LOOP CLOCK -- + +-- ComputerCraft OS Timer based Loop Clock +---@param period number clock period +--- +--- fires a timer event at the specified period, does not start at construct time +util.new_clock = function (period) +---@diagnostic disable-next-line: undefined-field + local start_timer = os.startTimer + + local self = { + period = period, + timer = nil } + + ---@class clock + local public = {} + + ---@param timer number timer event timer ID + public.is_clock = function (timer) + return self.timer == timer + end + + -- start the clock + public.start = function () + self.timer = start_timer(self.period) + end + + return public end return util diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 22726d3..f6a12ca 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -403,7 +403,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) -- check if a timer matches this session's watchdog local check_wd = function (timer) - return timer == self.plc_conn_watchdog.get_timer() + return self.plc_conn_watchdog.is_timer(timer) end -- close the connection diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 2e79ec8..fb60904 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -115,7 +115,7 @@ rtu.new_session = function (id, in_queue, out_queue) -- check if a timer matches this session's watchdog local check_wd = function (timer) - return timer == self.rtu_conn_watchdog.get_timer() + return self.rtu_conn_watchdog.is_timer(timer) end -- close the connection From 3c688bfafa9e97e7994f5a3389314f7cb4e33799 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 10 May 2022 17:06:27 -0400 Subject: [PATCH 167/587] #47 scada-common doc comments --- scada-common/alarm.lua | 23 +++-- scada-common/comms.lua | 222 ++++++++++++++++++++-------------------- scada-common/log.lua | 14 +++ scada-common/mqueue.lua | 59 ++++++----- scada-common/ppm.lua | 40 ++++++-- scada-common/rsio.lua | 23 ++++- scada-common/types.lua | 5 + scada-common/util.lua | 53 +++++++++- 8 files changed, 288 insertions(+), 151 deletions(-) diff --git a/scada-common/alarm.lua b/scada-common/alarm.lua index 7c39bc4..2fcaa19 100644 --- a/scada-common/alarm.lua +++ b/scada-common/alarm.lua @@ -1,7 +1,9 @@ local util = require("scada-common.util") +---@class alarm local alarm = {} +---@alias SEVERITY integer SEVERITY = { INFO = 0, -- basic info message WARNING = 1, -- warning about some abnormal state @@ -13,6 +15,8 @@ SEVERITY = { alarm.SEVERITY = SEVERITY +-- severity integer to string +---@param severity SEVERITY alarm.severity_to_string = function (severity) if severity == SEVERITY.INFO then return "INFO" @@ -31,6 +35,10 @@ alarm.severity_to_string = function (severity) end end +-- create a new scada alarm entry +---@param severity SEVERITY +---@param device string +---@param message string alarm.scada_alarm = function (severity, device, message) local self = { time = util.time(), @@ -40,11 +48,17 @@ alarm.scada_alarm = function (severity, device, message) message = message } - local format = function () + ---@class scada_alarm + local public = {} + + -- format the alarm as a string + ---@return string message + public.format = function () return self.ts_string .. " [" .. alarm.severity_to_string(self.severity) .. "] (" .. self.device ") >> " .. self.message end - local properties = function () + -- get alarm properties + public.properties = function () return { time = self.time, severity = self.severity, @@ -53,10 +67,7 @@ alarm.scada_alarm = function (severity, device, message) } end - return { - format = format, - properties = properties - } + return public end return alarm diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 5a84fbc..9eebb2d 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -5,11 +5,13 @@ local log = require("scada-common.log") local types = require("scada-common.types") +---@class comms local comms = {} local rtu_t = types.rtu_t local insert = table.insert +---@alias PROTOCOLS integer local PROTOCOLS = { MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol RPLC = 1, -- reactor PLC protocol @@ -18,6 +20,7 @@ local PROTOCOLS = { COORD_API = 4 -- data/control packets for pocket computers to/from coordinators } +---@alias RPLC_TYPES integer local RPLC_TYPES = { LINK_REQ = 0, -- linking requests STATUS = 1, -- reactor/system status @@ -30,12 +33,14 @@ local RPLC_TYPES = { RPS_RESET = 8 -- clear RPS trip (if in bad state, will trip immediately) } +---@alias RPLC_LINKING integer local RPLC_LINKING = { ALLOW = 0, -- link approved DENY = 1, -- link denied COLLISION = 2 -- link denied due to existing active link } +---@alias SCADA_MGMT_TYPES integer local SCADA_MGMT_TYPES = { KEEP_ALIVE = 0, -- keep alive packet w/ RTT CLOSE = 1, -- close a connection @@ -43,6 +48,7 @@ local SCADA_MGMT_TYPES = { REMOTE_LINKED = 3 -- remote device linked } +---@alias RTU_ADVERT_TYPES integer local RTU_ADVERT_TYPES = { REDSTONE = 0, -- redstone I/O BOILER = 1, -- boiler @@ -71,8 +77,14 @@ comms.scada_packet = function () payload = nil } + ---@class scada_packet + local public = {} + -- make a SCADA packet - local make = function (seq_num, protocol, payload) + ---@param seq_num integer + ---@param protocol PROTOCOLS + ---@param payload table + public.make = function (seq_num, protocol, payload) self.valid = true self.seq_num = seq_num self.protocol = protocol @@ -82,7 +94,12 @@ comms.scada_packet = function () end -- parse in a modem message as a SCADA packet - local receive = function (side, sender, reply_to, message, distance) + ---@param side string + ---@param sender integer + ---@param reply_to integer + ---@param message any + ---@param distance integer + public.receive = function (side, sender, reply_to, message, distance) self.modem_msg_in = { iface = side, s_port = sender, @@ -108,40 +125,23 @@ comms.scada_packet = function () -- public accessors -- - local modem_event = function () return self.modem_msg_in end - local raw_sendable = function () return self.raw end + public.modem_event = function () return self.modem_msg_in end + public.raw_sendable = function () return self.raw end - local local_port = function () return self.modem_msg_in.s_port end - local remote_port = function () return self.modem_msg_in.r_port end + public.local_port = function () return self.modem_msg_in.s_port end + public.remote_port = function () return self.modem_msg_in.r_port end - local is_valid = function () return self.valid end + public.is_valid = function () return self.valid end - local seq_num = function () return self.seq_num end - local protocol = function () return self.protocol end - local length = function () return self.length end - local data = function () return self.payload end + public.seq_num = function () return self.seq_num end + public.protocol = function () return self.protocol end + public.length = function () return self.length end + public.data = function () return self.payload end - return { - -- construct - make = make, - receive = receive, - -- raw access - modem_event = modem_event, - raw_sendable = raw_sendable, - -- ports - local_port = local_port, - remote_port = remote_port, - -- well-formed - is_valid = is_valid, - -- packet properties - seq_num = seq_num, - protocol = protocol, - length = length, - data = data - } + return public end --- MODBUS packet +-- MODBUS packet -- modeled after MODBUS TCP packet comms.modbus_packet = function () local self = { @@ -154,8 +154,15 @@ comms.modbus_packet = function () data = nil } + ---@class modbus_packet + local public = {} + -- make a MODBUS packet - local make = function (txn_id, unit_id, func_code, data) + ---@param txn_id integer + ---@param unit_id integer + ---@param func_code MODBUS_FCODE + ---@param data table + public.make = function (txn_id, unit_id, func_code, data) self.txn_id = txn_id self.length = #data self.unit_id = unit_id @@ -170,18 +177,20 @@ comms.modbus_packet = function () end -- decode a MODBUS packet from a SCADA frame - local decode = function (frame) + ---@param frame scada_packet + ---@return boolean success + public.decode = function (frame) if frame then self.frame = frame if frame.protocol() == PROTOCOLS.MODBUS_TCP then local size_ok = frame.length() >= 3 - + if size_ok then local data = frame.data() - make(data[1], data[2], data[3], { table.unpack(data, 4, #data) }) + public.make(data[1], data[2], data[3], { table.unpack(data, 4, #data) }) end - + return size_ok else log.debug("attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true) @@ -194,10 +203,10 @@ comms.modbus_packet = function () end -- get raw to send - local raw_sendable = function () return self.raw end + public.raw_sendable = function () return self.raw end -- get this packet - local get = function () + public.get = function () return { scada_frame = self.frame, txn_id = self.txn_id, @@ -208,15 +217,7 @@ comms.modbus_packet = function () } end - return { - -- construct - make = make, - decode = decode, - -- raw access - raw_sendable = raw_sendable, - -- formatted access - get = get - } + return public end -- reactor PLC packet @@ -230,10 +231,12 @@ comms.rplc_packet = function () body = nil } + ---@class rplc_packet + local public = {} + -- check that type is known local _rplc_type_valid = function () - return self.type == RPLC_TYPES.KEEP_ALIVE or - self.type == RPLC_TYPES.LINK_REQ or + return self.type == RPLC_TYPES.LINK_REQ or self.type == RPLC_TYPES.STATUS or self.type == RPLC_TYPES.MEK_STRUCT or self.type == RPLC_TYPES.MEK_BURN_RATE or @@ -245,7 +248,10 @@ comms.rplc_packet = function () end -- make an RPLC packet - local make = function (id, packet_type, data) + ---@param id integer + ---@param packet_type RPLC_TYPES + ---@param data table + public.make = function (id, packet_type, data) -- packet accessor properties self.id = id self.type = packet_type @@ -260,7 +266,9 @@ comms.rplc_packet = function () end -- decode an RPLC packet from a SCADA frame - local decode = function (frame) + ---@param frame scada_packet + ---@return boolean success + public.decode = function (frame) if frame then self.frame = frame @@ -269,7 +277,7 @@ comms.rplc_packet = function () if ok then local data = frame.data() - make(data[1], data[2], { table.unpack(data, 3, #data) }) + public.make(data[1], data[2], { table.unpack(data, 3, #data) }) ok = _rplc_type_valid() end @@ -285,10 +293,10 @@ comms.rplc_packet = function () end -- get raw to send - local raw_sendable = function () return self.raw end + public.raw_sendable = function () return self.raw end -- get this packet - local get = function () + public.get = function () return { scada_frame = self.frame, id = self.id, @@ -298,15 +306,7 @@ comms.rplc_packet = function () } end - return { - -- construct - make = make, - decode = decode, - -- raw access - raw_sendable = raw_sendable, - -- formatted access - get = get - } + return public end -- SCADA management packet @@ -319,17 +319,21 @@ comms.mgmt_packet = function () data = nil } + ---@class mgmt_packet + local public = {} + -- check that type is known local _scada_type_valid = function () - return self.type == SCADA_MGMT_TYPES.PING or + return self.type == SCADA_MGMT_TYPES.KEEP_ALIVE or self.type == SCADA_MGMT_TYPES.CLOSE or self.type == SCADA_MGMT_TYPES.REMOTE_LINKED or - self.type == SCADA_MGMT_TYPES.RTU_ADVERT or - self.type == SCADA_MGMT_TYPES.RTU_HEARTBEAT + self.type == SCADA_MGMT_TYPES.RTU_ADVERT end -- make a SCADA management packet - local make = function (packet_type, data) + ---@param packet_type SCADA_MGMT_TYPES + ---@param data table + public.make = function (packet_type, data) -- packet accessor properties self.type = packet_type self.length = #data @@ -343,19 +347,21 @@ comms.mgmt_packet = function () end -- decode a SCADA management packet from a SCADA frame - local decode = function (frame) + ---@param frame scada_packet + ---@return boolean success + public.decode = function (frame) if frame then self.frame = frame if frame.protocol() == PROTOCOLS.SCADA_MGMT then local ok = frame.length() >= 1 - + if ok then local data = frame.data() - make(data[1], { table.unpack(data, 2, #data) }) + public.make(data[1], { table.unpack(data, 2, #data) }) ok = _scada_type_valid() end - + return ok else log.debug("attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true) @@ -368,10 +374,10 @@ comms.mgmt_packet = function () end -- get raw to send - local raw_sendable = function () return self.raw end + public.raw_sendable = function () return self.raw end -- get this packet - local get = function () + public.get = function () return { scada_frame = self.frame, type = self.type, @@ -380,15 +386,7 @@ comms.mgmt_packet = function () } end - return { - -- construct - make = make, - decode = decode, - -- raw access - raw_sendable = raw_sendable, - -- formatted access - get = get - } + return public end -- SCADA coordinator packet @@ -402,13 +400,18 @@ comms.coord_packet = function () data = nil } + ---@class coord_packet + local public = {} + local _coord_type_valid = function () -- @todo return false end -- make a coordinator packet - local make = function (packet_type, data) + ---@param packet_type any + ---@param data table + public.make = function (packet_type, data) -- packet accessor properties self.type = packet_type self.length = #data @@ -422,7 +425,9 @@ comms.coord_packet = function () end -- decode a coordinator packet from a SCADA frame - local decode = function (frame) + ---@param frame scada_packet + ---@return boolean success + public.decode = function (frame) if frame then self.frame = frame @@ -431,7 +436,7 @@ comms.coord_packet = function () if ok then local data = frame.data() - make(data[1], { table.unpack(data, 2, #data) }) + public.make(data[1], { table.unpack(data, 2, #data) }) ok = _coord_type_valid() end @@ -447,10 +452,10 @@ comms.coord_packet = function () end -- get raw to send - local raw_sendable = function () return self.raw end + public.raw_sendable = function () return self.raw end -- get this packet - local get = function () + public.get = function () return { scada_frame = self.frame, type = self.type, @@ -459,15 +464,7 @@ comms.coord_packet = function () } end - return { - -- construct - make = make, - decode = decode, - -- raw access - raw_sendable = raw_sendable, - -- formatted access - get = get - } + return public end -- coordinator API (CAPI) packet @@ -481,13 +478,18 @@ comms.capi_packet = function () data = nil } + ---@class capi_packet + local public = {} + local _coord_type_valid = function () -- @todo return false end - -- make a coordinator packet - local make = function (packet_type, data) + -- make a coordinator API packet + ---@param packet_type any + ---@param data table + public.make = function (packet_type, data) -- packet accessor properties self.type = packet_type self.length = #data @@ -500,8 +502,10 @@ comms.capi_packet = function () end end - -- decode a coordinator packet from a SCADA frame - local decode = function (frame) + -- decode a coordinator API packet from a SCADA frame + ---@param frame scada_packet + ---@return boolean success + public.decode = function (frame) if frame then self.frame = frame @@ -510,7 +514,7 @@ comms.capi_packet = function () if ok then local data = frame.data() - make(data[1], { table.unpack(data, 2, #data) }) + public.make(data[1], { table.unpack(data, 2, #data) }) ok = _coord_type_valid() end @@ -526,10 +530,10 @@ comms.capi_packet = function () end -- get raw to send - local raw_sendable = function () return self.raw end + public.raw_sendable = function () return self.raw end -- get this packet - local get = function () + public.get = function () return { scada_frame = self.frame, type = self.type, @@ -538,18 +542,12 @@ comms.capi_packet = function () } end - return { - -- construct - make = make, - decode = decode, - -- raw access - raw_sendable = raw_sendable, - -- formatted access - get = get - } + return public end -- convert rtu_t to RTU advertisement type +---@param type rtu_t +---@return RTU_ADVERT_TYPES|nil comms.rtu_t_to_advert_type = function (type) if type == rtu_t.redstone then return RTU_ADVERT_TYPES.REDSTONE @@ -571,6 +569,8 @@ comms.rtu_t_to_advert_type = function (type) end -- convert RTU advertisement type to rtu_t +---@param atype RTU_ADVERT_TYPES +---@return rtu_t|nil comms.advert_type_to_rtu_t = function (atype) if atype == RTU_ADVERT_TYPES.REDSTONE then return rtu_t.redstone diff --git a/scada-common/log.lua b/scada-common/log.lua index e841b23..71e4495 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -4,8 +4,10 @@ local util = require("scada-common.util") -- File System Logger -- +---@class log local log = {} +---@alias MODE integer local MODE = { APPEND = 0, NEW = 1 @@ -13,6 +15,7 @@ local MODE = { log.MODE = MODE +-- whether to log debug messages or not local LOG_DEBUG = true local _log_sys = { @@ -21,9 +24,12 @@ local _log_sys = { file = nil } +---@type function local free_space = fs.getFreeSpace -- initialize logger +---@param path string file path +---@param write_mode MODE log.init = function (path, write_mode) _log_sys.path = path _log_sys.mode = write_mode @@ -36,6 +42,7 @@ log.init = function (path, write_mode) end -- private log write function +---@param msg string local _log = function (msg) local time_stamp = os.date("[%c] ") local stamped = time_stamp .. msg @@ -70,6 +77,8 @@ local _log = function (msg) end -- log debug messages +---@param msg string message +---@param trace? boolean include file trace log.debug = function (msg, trace) if LOG_DEBUG then local dbg_info = "" @@ -90,16 +99,20 @@ log.debug = function (msg, trace) end -- log info messages +---@param msg string message log.info = function (msg) _log("[INF] " .. msg) end -- log warning messages +---@param msg string message log.warning = function (msg) _log("[WRN] " .. msg) end -- log error messages +---@param msg string message +---@param trace? boolean include file trace log.error = function (msg, trace) local dbg_info = "" @@ -118,6 +131,7 @@ log.error = function (msg, trace) end -- log fatal errors +---@param msg string message log.fatal = function (msg) _log("[FTL] " .. msg) end diff --git a/scada-common/mqueue.lua b/scada-common/mqueue.lua index d1ac5c1..db97df4 100644 --- a/scada-common/mqueue.lua +++ b/scada-common/mqueue.lua @@ -4,6 +4,7 @@ local mqueue = {} +---@alias TYPE integer local TYPE = { COMMAND = 0, DATA = 1, @@ -12,41 +13,61 @@ local TYPE = { mqueue.TYPE = TYPE +-- create a new message queue mqueue.new = function () local queue = {} local insert = table.insert local remove = table.remove - local length = function () - return #queue - end + ---@class queue_item + local queue_item = { + qtype = 0, ---@type TYPE + message = 0 ---@type any + } - local empty = function () - return #queue == 0 - end + ---@class mqueue + local public = {} - local ready = function () - return #queue ~= 0 - end + -- get queue length + public.length = function () return #queue end + -- check if queue is empty + ---@return boolean is_empty + public.empty = function () return #queue == 0 end + + -- check if queue has contents + public.ready = function () return #queue ~= 0 end + + -- push a new item onto the queue + ---@param qtype TYPE + ---@param message string local _push = function (qtype, message) insert(queue, { qtype = qtype, message = message }) end - local push_command = function (message) + -- push a command onto the queue + ---@param message any + public.push_command = function (message) _push(TYPE.COMMAND, message) end - local push_data = function (key, value) + -- push data onto the queue + ---@param key any + ---@param value any + public.push_data = function (key, value) _push(TYPE.DATA, { key = key, val = value }) end - local push_packet = function (message) - _push(TYPE.PACKET, message) + -- push a packet onto the queue + ---@param packet scada_packet|modbus_packet|rplc_packet|coord_packet|capi_packet + public.push_packet = function (packet) + _push(TYPE.PACKET, packet) end - local pop = function () + -- get an item off the queue + ---@return queue_item|nil + public.pop = function () if #queue > 0 then return remove(queue, 1) else @@ -54,15 +75,7 @@ mqueue.new = function () end end - return { - length = length, - empty = empty, - ready = ready, - push_packet = push_packet, - push_data = push_data, - push_command = push_command, - pop = pop - } + return public end return mqueue diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index e834946..c5026ea 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -4,9 +4,10 @@ local log = require("scada-common.log") -- Protected Peripheral Manager -- +---@class ppm local ppm = {} -local ACCESS_FAULT = nil +local ACCESS_FAULT = nil ---@type nil ppm.ACCESS_FAULT = ACCESS_FAULT @@ -22,9 +23,12 @@ local _ppm_sys = { mute = false } --- wrap peripheral calls with lua protected call --- we don't want a disconnect to crash a program --- also provides peripheral-specific fault checks (auto-clear fault defaults to true) +-- wrap peripheral calls with lua protected call as we don't want a disconnect to crash a program +--- +---also provides peripheral-specific fault checks (auto-clear fault defaults to true) +--- +---assumes iface is a valid peripheral +---@param iface string CC peripheral interface local peri_init = function (iface) local self = { faulted = false, @@ -150,6 +154,8 @@ ppm.mount_all = function () end -- mount a particular device +---@param iface string CC peripheral interface +---@return string|nil type, table|nil device ppm.mount = function (iface) local ifaces = peripheral.getNames() local pm_dev = nil @@ -171,33 +177,44 @@ ppm.mount = function (iface) end -- handle peripheral_detach event +---@param iface string CC peripheral interface +---@return string|nil type, table|nil device ppm.handle_unmount = function (iface) + local pm_dev = nil + local pm_type = nil + -- what got disconnected? local lost_dev = _ppm_sys.mounts[iface] if lost_dev then - local type = lost_dev.type - log.warning("PPM: lost device " .. type .. " mounted to " .. iface) + pm_type = lost_dev.type + pm_dev = lost_dev.dev + + log.warning("PPM: lost device " .. pm_type .. " mounted to " .. iface) else log.error("PPM: lost device unknown to the PPM mounted to " .. iface) end - return lost_dev + return pm_type, pm_dev end -- GENERAL ACCESSORS -- -- list all available peripherals +---@return table names ppm.list_avail = function () return peripheral.getNames() end -- list mounted peripherals +---@return table mounts ppm.list_mounts = function () return _ppm_sys.mounts end -- get a mounted peripheral by side/interface +---@param iface string CC peripheral interface +---@return table|nil device function table ppm.get_periph = function (iface) if _ppm_sys.mounts[iface] then return _ppm_sys.mounts[iface].dev @@ -205,6 +222,8 @@ ppm.get_periph = function (iface) end -- get a mounted peripheral type by side/interface +---@param iface string CC peripheral interface +---@return string|nil type ppm.get_type = function (iface) if _ppm_sys.mounts[iface] then return _ppm_sys.mounts[iface].type @@ -212,6 +231,8 @@ ppm.get_type = function (iface) end -- get all mounted peripherals by type +---@param name string type name +---@return table devices device function tables ppm.get_all_devices = function (name) local devices = {} @@ -225,6 +246,8 @@ ppm.get_all_devices = function (name) end -- get a mounted peripheral by type (if multiple, returns the first) +---@param name string type name +---@return table|nil device function table ppm.get_device = function (name) local device = nil @@ -241,11 +264,13 @@ end -- SPECIFIC DEVICE ACCESSORS -- -- get the fission reactor (if multiple, returns the first) +---@return table|nil reactor function table ppm.get_fission_reactor = function () return ppm.get_device("fissionReactor") end -- get the wireless modem (if multiple, returns the first) +---@return table|nil modem function table ppm.get_wireless_modem = function () local w_modem = nil @@ -260,6 +285,7 @@ ppm.get_wireless_modem = function () end -- list all connected monitors +---@return table monitors ppm.list_monitors = function () return ppm.get_all_devices("monitor") end diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index d5a3d5a..d71d777 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -8,16 +8,19 @@ local rsio = {} -- RS I/O CONSTANTS -- ---------------------- +---@alias IO_LVL integer local IO_LVL = { LOW = 0, HIGH = 1 } +---@alias IO_DIR integer local IO_DIR = { IN = 0, OUT = 1 } +---@alias IO_MODE integer local IO_MODE = { DIGITAL_OUT = 0, DIGITAL_IN = 1, @@ -25,6 +28,7 @@ local IO_MODE = { ANALOG_IN = 3 } +---@alias RS_IO integer local RS_IO = { -- digital inputs -- @@ -73,6 +77,7 @@ rsio.IO = RS_IO ----------------------- -- channel to string +---@param channel RS_IO rsio.to_string = function (channel) local names = { "F_SCRAM", @@ -155,6 +160,8 @@ local RS_DIO_MAP = { } -- get the mode of a channel +---@param channel RS_IO +---@return IO_MODE rsio.get_io_mode = function (channel) local modes = { IO_MODE.DIGITAL_IN, -- F_SCRAM @@ -194,11 +201,15 @@ end local RS_SIDES = rs.getSides() -- check if a channel is valid +---@param channel RS_IO +---@return boolean valid rsio.is_valid_channel = function (channel) - return channel ~= nil and channel > 0 and channel <= RS_IO.A_T_FLOW_RATE + return (channel ~= nil) and (channel > 0) and (channel <= RS_IO.A_T_FLOW_RATE) end -- check if a side is valid +---@param side string +---@return boolean valid rsio.is_valid_side = function (side) if side ~= nil then for i = 0, #RS_SIDES do @@ -209,6 +220,8 @@ rsio.is_valid_side = function (side) end -- check if a color is a valid single color +---@param color integer +---@return boolean valid rsio.is_color = function (color) return (color > 0) and (_B_AND(color, (color - 1)) == 0); end @@ -218,6 +231,8 @@ end ----------------- -- get digital IO level reading +---@param rs_value boolean +---@return IO_LVL rsio.digital_read = function (rs_value) if rs_value then return IO_LVL.HIGH @@ -227,6 +242,9 @@ rsio.digital_read = function (rs_value) end -- returns the level corresponding to active +---@param channel RS_IO +---@param active boolean +---@return IO_LVL rsio.digital_write = function (channel, active) if channel < RS_IO.WASTE_PO or channel > RS_IO.R_PLC_TIMEOUT then return IO_LVL.LOW @@ -236,6 +254,9 @@ rsio.digital_write = function (channel, active) end -- returns true if the level corresponds to active +---@param channel RS_IO +---@param level IO_LVL +---@return boolean rsio.digital_is_active = function (channel, level) if channel > RS_IO.R_ENABLE or channel > RS_IO.R_PLC_TIMEOUT then return false diff --git a/scada-common/types.lua b/scada-common/types.lua index 5bd747e..372b1d3 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -2,8 +2,10 @@ -- Global Types -- +---@class types local types = {} +---@alias rtu_t string types.rtu_t = { redstone = "redstone", boiler = "boiler", @@ -14,6 +16,7 @@ types.rtu_t = { induction_matrix = "induction_matrix" } +---@alias rps_status_t string types.rps_status_t = { ok = "ok", dmg_crit = "dmg_crit", @@ -30,6 +33,7 @@ types.rps_status_t = { -- MODBUS -- modbus function codes +---@alias MODBUS_FCODE integer types.MODBUS_FCODE = { READ_COILS = 0x01, READ_DISCRETE_INPUTS = 0x02, @@ -43,6 +47,7 @@ types.MODBUS_FCODE = { } -- modbus exception codes +---@alias MODBUS_EXCODE integer types.MODBUS_EXCODE = { ILLEGAL_FUNCTION = 0x01, ILLEGAL_DATA_ADDR = 0x02, diff --git a/scada-common/util.lua b/scada-common/util.lua index 77ad4c9..8dffd40 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -1,3 +1,8 @@ +-- +-- Utility Functions +-- + +---@class util local util = {} -- PRINT -- @@ -24,16 +29,22 @@ end -- TIME -- +-- current time +---@return integer milliseconds util.time_ms = function () ---@diagnostic disable-next-line: undefined-field return os.epoch('local') end +-- current time +---@return integer seconds util.time_s = function () ---@diagnostic disable-next-line: undefined-field return os.epoch('local') / 1000 end +-- current time +---@return integer milliseconds util.time = function () return util.time_ms() end @@ -41,19 +52,24 @@ end -- PARALLELIZATION -- -- protected sleep call so we still are in charge of catching termination --- EVENT_CONSUMER: this function consumes events +---@param t integer seconds +--- EVENT_CONSUMER: this function consumes events util.psleep = function (t) ---@diagnostic disable-next-line: undefined-field pcall(os.sleep, t) end --- no-op to provide a brief pause (and a yield) --- EVENT_CONSUMER: this function consumes events +-- no-op to provide a brief pause (1 tick) to yield +--- +--- EVENT_CONSUMER: this function consumes events util.nop = function () util.psleep(0.05) end -- attempt to maintain a minimum loop timing (duration of execution) +---@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 util.adaptive_delay = function (target_timing, last_update) local sleep_for = target_timing - (util.time() - last_update) @@ -64,6 +80,37 @@ util.adaptive_delay = function (target_timing, last_update) return util.time() end +-- MEKANISM POWER -- + +-- function kFE(fe) return fe / 1000 end +-- function MFE(fe) return fe / 1000000 end +-- function GFE(fe) return fe / 1000000000 end +-- function TFE(fe) return fe / 1000000000000 end + +-- -- FLOATING POINT PRINTS -- + +-- local function fractional_1s(number) +-- return number == math.round(number) +-- end + +-- local function fractional_10ths(number) +-- number = number * 10 +-- return number == math.round(number) +-- end + +-- local function fractional_100ths(number) +-- number = number * 100 +-- return number == math.round(number) +-- end + +-- function power_format(fe) +-- if fe < 1000 then +-- return string.format("%.2f FE", fe) +-- elseif fe < 1000000 then +-- return string.format("%.3f kFE", kFE(fe)) +-- end +-- end + -- WATCHDOG -- -- ComputerCraft OS Timer based Watchdog From e3a4ed53631d78603bbcab7d108d8ddcbb648e60 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 10 May 2022 17:08:38 -0400 Subject: [PATCH 168/587] catch nil cases, supervisor use loop clock --- reactor-plc/threads.lua | 110 +++++++++++++++++++++------------------- rtu/threads.lua | 102 +++++++++++++++++++------------------ supervisor/startup.lua | 47 +++++++++-------- 3 files changed, 136 insertions(+), 123 deletions(-) diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index bf05f22..7231f2a 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -88,74 +88,78 @@ threads.thread__main = function (smem, init) smem.q.mq_rps.push_command(MQ__RPS_CMD.TRIP_TIMEOUT) elseif event == "peripheral_detach" then -- peripheral disconnect - local device = ppm.handle_unmount(param1) - - if device.type == "fissionReactor" then - println_ts("reactor disconnected!") - log.error("reactor disconnected!") - plc_state.no_reactor = true - plc_state.degraded = true - elseif networked and device.type == "modem" then - -- we only care if this is our wireless modem - if device.dev == plc_dev.modem then - println_ts("wireless modem disconnected!") - log.error("comms modem disconnected!") - plc_state.no_modem = true - - if plc_state.init_ok then - -- try to scram reactor if it is still connected - smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM) - end + local type, device = ppm.handle_unmount(param1) + if type ~= nil and device ~= nil then + if type == "fissionReactor" then + println_ts("reactor disconnected!") + log.error("reactor disconnected!") + plc_state.no_reactor = true plc_state.degraded = true - else - log.warning("non-comms modem disconnected") + elseif networked and type == "modem" then + -- we only care if this is our wireless modem + if device == plc_dev.modem then + println_ts("wireless modem disconnected!") + log.error("comms modem disconnected!") + plc_state.no_modem = true + + if plc_state.init_ok then + -- try to scram reactor if it is still connected + smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM) + end + + plc_state.degraded = true + else + log.warning("non-comms modem disconnected") + end end end elseif event == "peripheral" then -- peripheral connect local type, device = ppm.mount(param1) - if type == "fissionReactor" then - -- reconnected reactor - plc_dev.reactor = device + if type ~= nil and device ~= nil then + if type == "fissionReactor" then + -- reconnected reactor + plc_dev.reactor = device - smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM) + smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM) - println_ts("reactor reconnected.") - log.info("reactor reconnected.") - plc_state.no_reactor = false - - if plc_state.init_ok then - 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 ppm.get_device("modem") ~= nil then - plc_state.degraded = false - end - elseif networked and type == "modem" then - if device.isWireless() then - -- reconnected modem - plc_dev.modem = device + println_ts("reactor reconnected.") + log.info("reactor reconnected.") + plc_state.no_reactor = false if plc_state.init_ok then - plc_comms.reconnect_modem(plc_dev.modem) + rps.reconnect_reactor(plc_dev.reactor) + if networked then + plc_comms.reconnect_reactor(plc_dev.reactor) + end end - println_ts("wireless modem reconnected.") - log.info("comms modem reconnected.") - plc_state.no_modem = false - -- determine if we are still in a degraded state - if ppm.get_device("fissionReactor") ~= nil then + if not networked or ppm.get_device("modem") ~= nil then plc_state.degraded = false end - else - log.info("wired modem reconnected.") + elseif networked and type == "modem" then + if device.isWireless() then + -- reconnected modem + plc_dev.modem = device + + if plc_state.init_ok then + plc_comms.reconnect_modem(plc_dev.modem) + end + + println_ts("wireless modem reconnected.") + log.info("comms modem reconnected.") + plc_state.no_modem = false + + -- determine if we are still in a degraded state + if ppm.get_device("fissionReactor") ~= nil then + plc_state.degraded = false + end + else + log.info("wired modem reconnected.") + end end end @@ -203,7 +207,7 @@ threads.thread__rps = function (smem) -- thread loop while true do local reactor = plc_dev.reactor - + -- RPS checks if plc_state.init_ok then -- SCRAM if no open connection @@ -240,7 +244,7 @@ threads.thread__rps = function (smem) end end end - + -- check for messages in the message queue while rps_queue.ready() and not plc_state.shutdown do local msg = rps_queue.pop() diff --git a/rtu/threads.lua b/rtu/threads.lua index ef30b45..5799efb 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -74,24 +74,26 @@ threads.thread__main = function (smem) rtu_comms.unlink(rtu_state) elseif event == "peripheral_detach" then -- handle loss of a device - local device = ppm.handle_unmount(param1) + local type, device = ppm.handle_unmount(param1) - if device.type == "modem" then - -- we only care if this is our wireless modem - if device.dev == rtu_dev.modem then - println_ts("wireless modem disconnected!") - log.warning("comms modem disconnected!") + if type ~= nil and device ~= nil then + if type == "modem" then + -- we only care if this is our wireless modem + if device == rtu_dev.modem then + println_ts("wireless modem disconnected!") + log.warning("comms modem disconnected!") + else + log.warning("non-comms modem disconnected") + end else - log.warning("non-comms modem disconnected") - end - else - for i = 1, #units do - -- find disconnected device - if units[i].device == device.dev then - -- we are going to let the PPM prevent crashes - -- return fault flags/codes to MODBUS queries - local unit = units[i] - println_ts("lost the " .. unit.type .. " on interface " .. unit.name) + for i = 1, #units do + -- find disconnected device + if units[i].device == device then + -- we are going to let the PPM prevent crashes + -- return fault flags/codes to MODBUS queries + local unit = units[i] + println_ts("lost the " .. unit.type .. " on interface " .. unit.name) + end end end end @@ -99,44 +101,46 @@ threads.thread__main = function (smem) -- peripheral connect local type, device = ppm.mount(param1) - if type == "modem" then - if device.isWireless() then - -- reconnected modem - rtu_dev.modem = device - rtu_comms.reconnect_modem(rtu_dev.modem) + if type ~= nil and device ~= nil then + if type == "modem" then + if device.isWireless() then + -- reconnected modem + rtu_dev.modem = device + rtu_comms.reconnect_modem(rtu_dev.modem) - println_ts("wireless modem reconnected.") - log.info("comms modem reconnected.") + println_ts("wireless modem reconnected.") + log.info("comms modem reconnected.") + else + log.info("wired modem reconnected.") + end else - log.info("wired modem reconnected.") - end - else - -- relink lost peripheral to correct unit entry - for i = 1, #units do - local unit = units[i] + -- relink lost peripheral to correct unit entry + for i = 1, #units do + local unit = units[i] - -- find disconnected device to reconnect - if unit.name == param1 then - -- found, re-link - unit.device = device + -- find disconnected device to reconnect + if unit.name == param1 then + -- found, re-link + unit.device = device - if unit.type == rtu_t.boiler then - unit.rtu = boiler_rtu.new(device) - elseif unit.type == rtu_t.boiler_valve then - unit.rtu = boilerv_rtu.new(device) - elseif unit.type == rtu_t.turbine then - unit.rtu = turbine_rtu.new(device) - elseif unit.type == rtu_t.turbine_valve then - unit.rtu = turbinev_rtu.new(device) - elseif unit.type == rtu_t.energy_machine then - unit.rtu = energymachine_rtu.new(device) - elseif unit.type == rtu_t.induction_matrix then - unit.rtu = imatrix_rtu.new(device) + if unit.type == rtu_t.boiler then + unit.rtu = boiler_rtu.new(device) + elseif unit.type == rtu_t.boiler_valve then + unit.rtu = boilerv_rtu.new(device) + elseif unit.type == rtu_t.turbine then + unit.rtu = turbine_rtu.new(device) + elseif unit.type == rtu_t.turbine_valve then + unit.rtu = turbinev_rtu.new(device) + elseif unit.type == rtu_t.energy_machine then + unit.rtu = energymachine_rtu.new(device) + elseif unit.type == rtu_t.induction_matrix then + unit.rtu = imatrix_rtu.new(device) + end + + unit.modbus_io = modbus.new(unit.rtu) + + println_ts("reconnected the " .. unit.type .. " on interface " .. unit.name) end - - unit.modbus_io = modbus.new(unit.rtu) - - println_ts("reconnected the " .. unit.type .. " on interface " .. unit.name) end end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 825e30c..bf0eabc 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -43,41 +43,46 @@ local superv_comms = supervisor.comms(config.NUM_REACTORS, modem, config.SCADA_D -- base loop clock (6.67Hz, 3 ticks) local MAIN_CLOCK = 0.15 -local loop_clock = os.startTimer(MAIN_CLOCK) +local loop_clock = util.new_clock(MAIN_CLOCK) -- event loop while true do +---@diagnostic disable-next-line: undefined-field local event, param1, param2, param3, param4, param5 = os.pullEventRaw() -- handle event if event == "peripheral_detach" then - local device = ppm.handle_unmount(param1) + local type, device = ppm.handle_unmount(param1) - if device.type == "modem" then - -- we only care if this is our wireless modem - if device.dev == modem then - println_ts("wireless modem disconnected!") - log.error("comms modem disconnected!") - else - log.warning("non-comms modem disconnected") + 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 == "modem" then - if device.isWireless() then - -- reconnected modem - modem = device - superv_comms.reconnect_modem(modem) + 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.") + println_ts("wireless modem reconnected.") + log.info("comms modem reconnected.") + else + log.info("wired modem reconnected.") + end end end - elseif event == "timer" and param1 == loop_clock then + elseif event == "timer" and loop_clock.is_clock(param1) then -- main loop tick -- iterate sessions @@ -86,9 +91,9 @@ while true do -- free any closed sessions svsessions.free_all_closed() - loop_clock = os.startTimer(MAIN_CLOCK) + loop_clock.start() elseif event == "timer" then - -- another timer event, check watchdogs + -- a non-clock timer event, check watchdogs svsessions.check_all_watchdogs(param1) elseif event == "modem_message" then -- got a packet From 87de804a9e5fc2400097d267d75533fe5ac7730d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 10 May 2022 17:09:02 -0400 Subject: [PATCH 169/587] proper module format --- coordinator/coordinator.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 8089be8..a6bf236 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -1,8 +1,12 @@ local comms = require("scada-common.comms") +local coordinator = {} + -- coordinator communications -function coord_comms() +coordinator.coord_comms = function () local self = { reactor_struct_cache = nil } end + +return coordinator From f4e397ebb15df9ca7a9a1ecfab1eddf335485208 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 10 May 2022 17:16:34 -0400 Subject: [PATCH 170/587] removed old controller code to not pollute workspace with globals --- coordinator/old-controller/controller.lua | 135 -------- coordinator/old-controller/defs.lua | 23 -- coordinator/old-controller/log.lua | 52 --- coordinator/old-controller/reactor.lua | 28 -- coordinator/old-controller/regulator.lua | 128 -------- coordinator/old-controller/render.lua | 370 ---------------------- coordinator/old-controller/server.lua | 109 ------- 7 files changed, 845 deletions(-) delete mode 100644 coordinator/old-controller/controller.lua delete mode 100644 coordinator/old-controller/defs.lua delete mode 100644 coordinator/old-controller/log.lua delete mode 100644 coordinator/old-controller/reactor.lua delete mode 100644 coordinator/old-controller/regulator.lua delete mode 100644 coordinator/old-controller/render.lua delete mode 100644 coordinator/old-controller/server.lua diff --git a/coordinator/old-controller/controller.lua b/coordinator/old-controller/controller.lua deleted file mode 100644 index b0e18b7..0000000 --- a/coordinator/old-controller/controller.lua +++ /dev/null @@ -1,135 +0,0 @@ --- mekanism reactor controller --- monitors and regulates mekanism reactors - -os.loadAPI("reactor.lua") -os.loadAPI("defs.lua") -os.loadAPI("log.lua") -os.loadAPI("render.lua") -os.loadAPI("server.lua") -os.loadAPI("regulator.lua") - --- constants, aliases, properties -local header = "MEKANISM REACTOR CONTROLLER - v" .. defs.CTRL_VERSION -local monitor_0 = peripheral.wrap(defs.MONITOR_0) -local monitor_1 = peripheral.wrap(defs.MONITOR_1) -local monitor_2 = peripheral.wrap(defs.MONITOR_2) -local monitor_3 = peripheral.wrap(defs.MONITOR_3) - -monitor_0.setBackgroundColor(colors.black) -monitor_0.setTextColor(colors.white) -monitor_0.clear() - -monitor_1.setBackgroundColor(colors.black) -monitor_1.setTextColor(colors.white) -monitor_1.clear() - -monitor_2.setBackgroundColor(colors.black) -monitor_2.setTextColor(colors.white) -monitor_2.clear() - -log.init(monitor_3) - -local main_w, main_h = monitor_0.getSize() -local view = window.create(monitor_0, 1, 1, main_w, main_h) -view.setBackgroundColor(colors.black) -view.clear() - -local stat_w, stat_h = monitor_1.getSize() -local stat_view = window.create(monitor_1, 1, 1, stat_w, stat_h) -stat_view.setBackgroundColor(colors.black) -stat_view.clear() - -local reactors = { - reactor.create(1, view, stat_view, 62, 3, 63, 2), - reactor.create(2, view, stat_view, 42, 3, 43, 2), - reactor.create(3, view, stat_view, 22, 3, 23, 2), - reactor.create(4, view, stat_view, 2, 3, 3, 2) -} -print("[debug] reactor tables created") - -server.init(reactors) -print("[debug] modem server started") - -regulator.init(reactors) -print("[debug] regulator started") - --- header -view.setBackgroundColor(colors.white) -view.setTextColor(colors.black) -view.setCursorPos(1, 1) -local header_pad_x = (main_w - string.len(header)) / 2 -view.write(string.rep(" ", header_pad_x) .. header .. string.rep(" ", header_pad_x)) - --- inital draw of each reactor -for key, rctr in pairs(reactors) do - render.draw_reactor_system(rctr) - render.draw_reactor_status(rctr) -end - --- inital draw of clock -monitor_2.setTextScale(2) -monitor_2.setCursorPos(1, 1) -monitor_2.write(os.date("%Y/%m/%d %H:%M:%S")) - -local clock_update_timer = os.startTimer(1) - -while true do - event, param1, param2, param3, param4, param5 = os.pullEvent() - - if event == "redstone" then - -- redstone state change - regulator.handle_redstone() - elseif event == "modem_message" then - -- received signal router packet - packet = { - side = param1, - sender = param2, - reply = param3, - message = param4, - distance = param5 - } - - server.handle_message(packet, reactors) - elseif event == "monitor_touch" then - if param1 == "monitor_5" then - local tap_x = param2 - local tap_y = param3 - - for key, rctr in pairs(reactors) do - if tap_x >= rctr.render.stat_x and tap_x <= (rctr.render.stat_x + 15) then - local old_val = rctr.waste_production - -- width in range - if tap_y == (rctr.render.stat_y + 12) then - rctr.waste_production = "plutonium" - elseif tap_y == (rctr.render.stat_y + 14) then - rctr.waste_production = "polonium" - elseif tap_y == (rctr.render.stat_y + 16) then - rctr.waste_production = "antimatter" - end - - -- notify reactor of changes - if old_val ~= rctr.waste_production then - server.send(rctr.id, rctr.waste_production) - end - end - end - end - elseif event == "timer" then - -- update the clock about every second - monitor_2.setCursorPos(1, 1) - monitor_2.write(os.date("%Y/%m/%d %H:%M:%S")) - clock_update_timer = os.startTimer(1) - - -- send keep-alive - server.broadcast(1) - end - - -- update reactor display - for key, rctr in pairs(reactors) do - render.draw_reactor_system(rctr) - render.draw_reactor_status(rctr) - end - - -- update system status monitor - render.update_system_monitor(monitor_2, regulator.is_scrammed(), reactors) -end diff --git a/coordinator/old-controller/defs.lua b/coordinator/old-controller/defs.lua deleted file mode 100644 index 13f6803..0000000 --- a/coordinator/old-controller/defs.lua +++ /dev/null @@ -1,23 +0,0 @@ --- configuration definitions - -CTRL_VERSION = "0.7" - --- monitors -MONITOR_0 = "monitor_6" -MONITOR_1 = "monitor_5" -MONITOR_2 = "monitor_7" -MONITOR_3 = "monitor_8" - --- modem server -LISTEN_PORT = 1000 - --- regulator (should match the number of reactors present) -BUNDLE_DEF = { colors.red, colors.orange, colors.yellow, colors.lime } - --- stats calculation -REACTOR_MB_T = 39 -TURBINE_MRF_T = 3.114 -PLUTONIUM_PER_WASTE = 0.1 -POLONIUM_PER_WASTE = 0.1 -SPENT_PER_BYPRODUCT = 1 -ANTIMATTER_PER_POLONIUM = 0.001 diff --git a/coordinator/old-controller/log.lua b/coordinator/old-controller/log.lua deleted file mode 100644 index c4e1cbb..0000000 --- a/coordinator/old-controller/log.lua +++ /dev/null @@ -1,52 +0,0 @@ -os.loadAPI("defs.lua") - -local out, out_w, out_h -local output_full = false - --- initialize the logger to the given monitor --- monitor: monitor to write to (in addition to calling print()) -function init(monitor) - out = monitor - out_w, out_h = out.getSize() - - out.clear() - out.setTextColor(colors.white) - out.setBackgroundColor(colors.black) - - out.setCursorPos(1, 1) - out.write("version " .. defs.CTRL_VERSION) - out.setCursorPos(1, 2) - out.write("system startup at " .. os.date("%Y/%m/%d %H:%M:%S")) - - print("server v" .. defs.CTRL_VERSION .. " started at " .. os.date("%Y/%m/%d %H:%M:%S")) -end - --- write a log message to the log screen and console --- msg: message to write --- color: (optional) color to print in, defaults to white -function write(msg, color) - color = color or colors.white - local _x, _y = out.getCursorPos() - - if output_full then - out.scroll(1) - out.setCursorPos(1, _y) - else - if _y == out_h then - output_full = true - out.scroll(1) - out.setCursorPos(1, _y) - else - out.setCursorPos(1, _y + 1) - end - end - - -- output to screen - out.setTextColor(colors.lightGray) - out.write(os.date("[%H:%M:%S] ")) - out.setTextColor(color) - out.write(msg) - - -- output to console - print(os.date("[%H:%M:%S] ") .. msg) -end diff --git a/coordinator/old-controller/reactor.lua b/coordinator/old-controller/reactor.lua deleted file mode 100644 index 137b46c..0000000 --- a/coordinator/old-controller/reactor.lua +++ /dev/null @@ -1,28 +0,0 @@ --- create a new reactor 'object' --- reactor_id: the ID for this reactor --- main_view: the parent window/monitor for the main display (components) --- status_view: the parent window/monitor for the status display --- main_x: where to create the main window, x coordinate --- main_y: where to create the main window, y coordinate --- status_x: where to create the status window, x coordinate --- status_y: where to create the status window, y coordinate -function create(reactor_id, main_view, status_view, main_x, main_y, status_x, status_y) - return { - id = reactor_id, - render = { - win_main = window.create(main_view, main_x, main_y, 20, 60, true), - win_stat = window.create(status_view, status_x, status_y, 20, 20, true), - stat_x = status_x, - stat_y = status_y - }, - control_state = false, - waste_production = "antimatter", -- "plutonium", "polonium", "antimatter" - state = { - run = false, - no_fuel = false, - full_waste = false, - high_temp = false, - damage_crit = false - } - } -end diff --git a/coordinator/old-controller/regulator.lua b/coordinator/old-controller/regulator.lua deleted file mode 100644 index e8acf55..0000000 --- a/coordinator/old-controller/regulator.lua +++ /dev/null @@ -1,128 +0,0 @@ -os.loadAPI("defs.lua") -os.loadAPI("log.lua") -os.loadAPI("server.lua") - -local reactors -local scrammed -local auto_scram - --- initialize the system regulator which provides safety measures, SCRAM functionality, and handles redstone --- _reactors: reactor table -function init(_reactors) - reactors = _reactors - scrammed = false - auto_scram = false - - -- scram all reactors - server.broadcast(false, reactors) - - -- check initial states - regulator.handle_redstone() -end - --- check if the system is scrammed -function is_scrammed() - return scrammed -end - --- handle redstone state changes -function handle_redstone() - -- check scram button - if not rs.getInput("right") then - if not scrammed then - log.write("user SCRAM", colors.red) - scram() - end - - -- toggling scram will release auto scram state - auto_scram = false - else - scrammed = false - end - - -- check individual control buttons - local input = rs.getBundledInput("left") - for key, rctr in pairs(reactors) do - if colors.test(input, defs.BUNDLE_DEF[key]) ~= rctr.control_state then - -- state changed - rctr.control_state = colors.test(input, defs.BUNDLE_DEF[key]) - if not scrammed then - local safe = true - - if rctr.control_state then - safe = check_enable_safety(reactors[key]) - if safe then - log.write("reactor " .. reactors[key].id .. " enabled", colors.lime) - end - else - log.write("reactor " .. reactors[key].id .. " disabled", colors.cyan) - end - - -- start/stop reactor - if safe then - server.send(rctr.id, rctr.control_state) - end - elseif colors.test(input, defs.BUNDLE_DEF[key]) then - log.write("scrammed: state locked off", colors.yellow) - end - end - end -end - --- make sure enabling the provided reactor is safe --- reactor: reactor to check -function check_enable_safety(reactor) - if reactor.state.no_fuel or reactor.state.full_waste or reactor.state.high_temp or reactor.state.damage_crit then - log.write("RCT-" .. reactor.id .. ": unsafe enable denied", colors.yellow) - return false - else - return true - end -end - --- make sure no running reactors are in a bad state -function enforce_safeties() - for key, reactor in pairs(reactors) do - local overridden = false - local state = reactor.state - - -- check for problems - if state.damage_crit and state.run then - reactor.control_state = false - log.write("RCT-" .. reactor.id .. ": shut down (damage)", colors.yellow) - - -- scram all, so ignore setting overridden - log.write("auto SCRAM all reactors", colors.red) - auto_scram = true - scram() - elseif state.high_temp and state.run then - reactor.control_state = false - overridden = true - log.write("RCT-" .. reactor.id .. ": shut down (temp)", colors.yellow) - elseif state.full_waste and state.run then - reactor.control_state = false - overridden = true - log.write("RCT-" .. reactor.id .. ": shut down (waste)", colors.yellow) - elseif state.no_fuel and state.run then - reactor.control_state = false - overridden = true - log.write("RCT-" .. reactor.id .. ": shut down (fuel)", colors.yellow) - end - - if overridden then - server.send(reactor.id, false) - end - end -end - --- shut down all reactors and prevent enabling them until the scram button is toggled/released -function scram() - scrammed = true - server.broadcast(false, reactors) - - for key, rctr in pairs(reactors) do - if rctr.control_state then - log.write("reactor " .. reactors[key].id .. " disabled", colors.cyan) - end - end -end diff --git a/coordinator/old-controller/render.lua b/coordinator/old-controller/render.lua deleted file mode 100644 index e10614d..0000000 --- a/coordinator/old-controller/render.lua +++ /dev/null @@ -1,370 +0,0 @@ -os.loadAPI("defs.lua") - --- draw pipes between machines --- win: window to render in --- x: starting x coord --- y: starting y coord --- spacing: spacing between the pipes --- color_out: output pipe contents color --- color_ret: return pipe contents color --- tick: tick the pipes for an animation -function draw_pipe(win, x, y, spacing, color_out, color_ret, tick) - local _color - local _off - tick = tick or 0 - - for i = 0, 4, 1 - do - _off = (i + tick) % 2 == 0 or (tick == 1 and i == 0) or (tick == 3 and i == 4) - - if _off then - _color = colors.lightGray - else - _color = color_out - end - - win.setBackgroundColor(_color) - win.setCursorPos(x, y + i) - win.write(" ") - - if not _off then - _color = color_ret - end - - win.setBackgroundColor(_color) - win.setCursorPos(x + spacing, y + i) - win.write(" ") - end -end - --- draw a reactor view consisting of the reactor, boiler, turbine, and pipes --- data: reactor table -function draw_reactor_system(data) - local win = data.render.win_main - local win_w, win_h = win.getSize() - - win.setBackgroundColor(colors.black) - win.setTextColor(colors.black) - win.clear() - win.setCursorPos(1, 1) - - -- draw header -- - - local header = "REACTOR " .. data.id - local header_pad_x = (win_w - string.len(header) - 2) / 2 - local header_color - if data.state.no_fuel then - if data.state.run then - header_color = colors.purple - else - header_color = colors.brown - end - elseif data.state.full_waste then - header_color = colors.yellow - elseif data.state.high_temp then - header_color = colors.orange - elseif data.state.damage_crit then - header_color = colors.red - elseif data.state.run then - header_color = colors.green - else - header_color = colors.lightGray - end - - local running = data.state.run and not data.state.no_fuel - - win.write(" ") - win.setBackgroundColor(header_color) - win.write(string.rep(" ", win_w - 2)) - win.setBackgroundColor(colors.black) - win.write(" ") - win.setCursorPos(1, 2) - win.write(" ") - win.setBackgroundColor(header_color) - win.write(string.rep(" ", header_pad_x) .. header .. string.rep(" ", header_pad_x)) - win.setBackgroundColor(colors.black) - win.write(" ") - - -- create strings for use in blit - local line_text = string.rep(" ", 14) - local line_text_color = string.rep("0", 14) - - -- draw components -- - - -- draw reactor - local rod = "88" - if data.state.high_temp then - rod = "11" - elseif running then - rod = "99" - end - - win.setCursorPos(4, 4) - win.setBackgroundColor(colors.gray) - win.write(line_text) - win.setCursorPos(4, 5) - win.blit(line_text, line_text_color, "77" .. rod .. "77" .. rod .. "77" .. rod .. "77") - win.setCursorPos(4, 6) - win.blit(line_text, line_text_color, "7777" .. rod .. "77" .. rod .. "7777") - win.setCursorPos(4, 7) - win.blit(line_text, line_text_color, "77" .. rod .. "77" .. rod .. "77" .. rod .. "77") - win.setCursorPos(4, 8) - win.blit(line_text, line_text_color, "7777" .. rod .. "77" .. rod .. "7777") - win.setCursorPos(4, 9) - win.blit(line_text, line_text_color, "77" .. rod .. "77" .. rod .. "77" .. rod .. "77") - win.setCursorPos(4, 10) - win.write(line_text) - - -- boiler - local steam = "ffffffffff" - if running then - steam = "0000000000" - end - - win.setCursorPos(4, 16) - win.setBackgroundColor(colors.gray) - win.write(line_text) - win.setCursorPos(4, 17) - win.blit(line_text, line_text_color, "77" .. steam .. "77") - win.setCursorPos(4, 18) - win.blit(line_text, line_text_color, "77" .. steam .. "77") - win.setCursorPos(4, 19) - win.blit(line_text, line_text_color, "77888888888877") - win.setCursorPos(4, 20) - win.blit(line_text, line_text_color, "77bbbbbbbbbb77") - win.setCursorPos(4, 21) - win.blit(line_text, line_text_color, "77bbbbbbbbbb77") - win.setCursorPos(4, 22) - win.blit(line_text, line_text_color, "77bbbbbbbbbb77") - win.setCursorPos(4, 23) - win.setBackgroundColor(colors.gray) - win.write(line_text) - - -- turbine - win.setCursorPos(4, 29) - win.setBackgroundColor(colors.gray) - win.write(line_text) - win.setCursorPos(4, 30) - if running then - win.blit(line_text, line_text_color, "77000000000077") - else - win.blit(line_text, line_text_color, "77ffffffffff77") - end - win.setCursorPos(4, 31) - if running then - win.blit(line_text, line_text_color, "77008000080077") - else - win.blit(line_text, line_text_color, "77ff8ffff8ff77") - end - win.setCursorPos(4, 32) - if running then - win.blit(line_text, line_text_color, "77000800800077") - else - win.blit(line_text, line_text_color, "77fff8ff8fff77") - end - win.setCursorPos(4, 33) - if running then - win.blit(line_text, line_text_color, "77000088000077") - else - win.blit(line_text, line_text_color, "77ffff88ffff77") - end - win.setCursorPos(4, 34) - if running then - win.blit(line_text, line_text_color, "77000800800077") - else - win.blit(line_text, line_text_color, "77fff8ff8fff77") - end - win.setCursorPos(4, 35) - if running then - win.blit(line_text, line_text_color, "77008000080077") - else - win.blit(line_text, line_text_color, "77ff8ffff8ff77") - end - win.setCursorPos(4, 36) - if running then - win.blit(line_text, line_text_color, "77000000000077") - else - win.blit(line_text, line_text_color, "77ffffffffff77") - end - win.setCursorPos(4, 37) - win.setBackgroundColor(colors.gray) - win.write(line_text) - - -- draw reactor coolant pipes - draw_pipe(win, 7, 11, 6, colors.orange, colors.lightBlue) - - -- draw turbine pipes - draw_pipe(win, 7, 24, 6, colors.white, colors.blue) -end - --- draw the reactor statuses on the status screen --- data: reactor table -function draw_reactor_status(data) - local win = data.render.win_stat - - win.setBackgroundColor(colors.black) - win.setTextColor(colors.white) - win.clear() - - -- show control state - win.setCursorPos(1, 1) - if data.control_state then - win.blit(" + ENABLED", "00000000000", "dddffffffff") - else - win.blit(" - DISABLED", "000000000000", "eeefffffffff") - end - - -- show run state - win.setCursorPos(1, 2) - if data.state.run then - win.blit(" + RUNNING", "00000000000", "dddffffffff") - else - win.blit(" - STOPPED", "00000000000", "888ffffffff") - end - - -- show fuel state - win.setCursorPos(1, 4) - if data.state.no_fuel then - win.blit(" - NO FUEL", "00000000000", "eeeffffffff") - else - win.blit(" + FUEL OK", "00000000000", "999ffffffff") - end - - -- show waste state - win.setCursorPos(1, 5) - if data.state.full_waste then - win.blit(" - WASTE FULL", "00000000000000", "eeefffffffffff") - else - win.blit(" + WASTE OK", "000000000000", "999fffffffff") - end - - -- show high temp state - win.setCursorPos(1, 6) - if data.state.high_temp then - win.blit(" - HIGH TEMP", "0000000000000", "eeeffffffffff") - else - win.blit(" + TEMP OK", "00000000000", "999ffffffff") - end - - -- show damage state - win.setCursorPos(1, 7) - if data.state.damage_crit then - win.blit(" - CRITICAL DAMAGE", "0000000000000000000", "eeeffffffffffffffff") - else - win.blit(" + CASING INTACT", "00000000000000000", "999ffffffffffffff") - end - - -- waste processing options -- - win.setTextColor(colors.black) - win.setBackgroundColor(colors.white) - - win.setCursorPos(1, 10) - win.write(" ") - win.setCursorPos(1, 11) - win.write(" WASTE OUTPUT ") - - win.setCursorPos(1, 13) - win.setBackgroundColor(colors.cyan) - if data.waste_production == "plutonium" then - win.write(" > plutonium ") - else - win.write(" plutonium ") - end - - win.setCursorPos(1, 15) - win.setBackgroundColor(colors.green) - if data.waste_production == "polonium" then - win.write(" > polonium ") - else - win.write(" polonium ") - end - - win.setCursorPos(1, 17) - win.setBackgroundColor(colors.purple) - if data.waste_production == "antimatter" then - win.write(" > antimatter ") - else - win.write(" antimatter ") - end -end - --- update the system monitor screen --- mon: monitor to update --- is_scrammed: -function update_system_monitor(mon, is_scrammed, reactors) - if is_scrammed then - -- display scram banner - mon.setTextColor(colors.white) - mon.setBackgroundColor(colors.black) - mon.setCursorPos(1, 2) - mon.clearLine() - mon.setBackgroundColor(colors.red) - mon.setCursorPos(1, 3) - mon.write(" ") - mon.setCursorPos(1, 4) - mon.write(" SCRAM ") - mon.setCursorPos(1, 5) - mon.write(" ") - mon.setBackgroundColor(colors.black) - mon.setCursorPos(1, 6) - mon.clearLine() - mon.setTextColor(colors.white) - else - -- clear where scram banner would be - mon.setCursorPos(1, 3) - mon.clearLine() - mon.setCursorPos(1, 4) - mon.clearLine() - mon.setCursorPos(1, 5) - mon.clearLine() - - -- show production statistics-- - - local mrf_t = 0 - local mb_t = 0 - local plutonium = 0 - local polonium = 0 - local spent_waste = 0 - local antimatter = 0 - - -- determine production values - for key, rctr in pairs(reactors) do - if rctr.state.run then - mrf_t = mrf_t + defs.TURBINE_MRF_T - mb_t = mb_t + defs.REACTOR_MB_T - - if rctr.waste_production == "plutonium" then - plutonium = plutonium + (defs.REACTOR_MB_T * defs.PLUTONIUM_PER_WASTE) - spent_waste = spent_waste + (defs.REACTOR_MB_T * defs.PLUTONIUM_PER_WASTE * defs.SPENT_PER_BYPRODUCT) - elseif rctr.waste_production == "polonium" then - polonium = polonium + (defs.REACTOR_MB_T * defs.POLONIUM_PER_WASTE) - spent_waste = spent_waste + (defs.REACTOR_MB_T * defs.POLONIUM_PER_WASTE * defs.SPENT_PER_BYPRODUCT) - elseif rctr.waste_production == "antimatter" then - antimatter = antimatter + (defs.REACTOR_MB_T * defs.POLONIUM_PER_WASTE * defs.ANTIMATTER_PER_POLONIUM) - end - end - end - - -- draw stats - mon.setTextColor(colors.lightGray) - mon.setCursorPos(1, 2) - mon.clearLine() - mon.write("ENERGY: " .. string.format("%0.2f", mrf_t) .. " MRF/t") - -- mon.setCursorPos(1, 3) - -- mon.clearLine() - -- mon.write("FUEL: " .. mb_t .. " mB/t") - mon.setCursorPos(1, 3) - mon.clearLine() - mon.write("Pu: " .. string.format("%0.2f", plutonium) .. " mB/t") - mon.setCursorPos(1, 4) - mon.clearLine() - mon.write("Po: " .. string.format("%0.2f", polonium) .. " mB/t") - mon.setCursorPos(1, 5) - mon.clearLine() - mon.write("SPENT: " .. string.format("%0.2f", spent_waste) .. " mB/t") - mon.setCursorPos(1, 6) - mon.clearLine() - mon.write("ANTI-M: " .. string.format("%0.2f", antimatter * 1000) .. " uB/t") - mon.setTextColor(colors.white) - end -end diff --git a/coordinator/old-controller/server.lua b/coordinator/old-controller/server.lua deleted file mode 100644 index 61ad386..0000000 --- a/coordinator/old-controller/server.lua +++ /dev/null @@ -1,109 +0,0 @@ -os.loadAPI("defs.lua") -os.loadAPI("log.lua") -os.loadAPI("regulator.lua") - -local modem -local reactors - --- initalize the listener running on the wireless modem --- _reactors: reactor table -function init(_reactors) - modem = peripheral.wrap("top") - reactors = _reactors - - -- open listening port - if not modem.isOpen(defs.LISTEN_PORT) then - modem.open(defs.LISTEN_PORT) - end - - -- send out a greeting to solicit responses for clients that are already running - broadcast(0, reactors) -end - --- handle an incoming message from the modem --- packet: table containing message fields -function handle_message(packet) - if type(packet.message) == "number" then - -- this is a greeting - log.write("reactor " .. packet.message .. " connected", colors.green) - - -- send current control command - for key, rctr in pairs(reactors) do - if rctr.id == packet.message then - send(rctr.id, rctr.control_state) - break - end - end - else - -- got reactor status - local eval_safety = false - - for key, value in pairs(reactors) do - if value.id == packet.message.id then - local tag = "RCT-" .. value.id .. ": " - - if value.state.run ~= packet.message.run then - value.state.run = packet.message.run - if value.state.run then - eval_safety = true - log.write(tag .. "running", colors.green) - end - end - - if value.state.no_fuel ~= packet.message.no_fuel then - value.state.no_fuel = packet.message.no_fuel - if value.state.no_fuel then - eval_safety = true - log.write(tag .. "insufficient fuel", colors.gray) - end - end - - if value.state.full_waste ~= packet.message.full_waste then - value.state.full_waste = packet.message.full_waste - if value.state.full_waste then - eval_safety = true - log.write(tag .. "waste tank full", colors.brown) - end - end - - if value.state.high_temp ~= packet.message.high_temp then - value.state.high_temp = packet.message.high_temp - if value.state.high_temp then - eval_safety = true - log.write(tag .. "high temperature", colors.orange) - end - end - - if value.state.damage_crit ~= packet.message.damage_crit then - value.state.damage_crit = packet.message.damage_crit - if value.state.damage_crit then - eval_safety = true - log.write(tag .. "critical damage", colors.red) - end - end - - break - end - end - - -- check to ensure safe operation - if eval_safety then - regulator.enforce_safeties() - end - end -end - --- send a message to a given reactor --- dest: reactor ID --- message: true or false for enable control or another value for other functionality, like 0 for greeting -function send(dest, message) - modem.transmit(dest + defs.LISTEN_PORT, defs.LISTEN_PORT, message) -end - --- broadcast a message to all reactors --- message: true or false for enable control or another value for other functionality, like 0 for greeting -function broadcast(message) - for key, value in pairs(reactors) do - modem.transmit(value.id + defs.LISTEN_PORT, defs.LISTEN_PORT, message) - end -end From 22a615952032ed2ebca8ad2a26df0281f5ce4542 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 10 May 2022 17:17:55 -0400 Subject: [PATCH 171/587] updated globals list, fixed packet references that were linking to old controller mistakenly --- .vscode/settings.json | 3 ++- rtu/threads.lua | 2 +- supervisor/session/rtu.lua | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 75bb696..77e08ef 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "rs", "bit", "parallel", - "colors" + "colors", + "textutils" ] } \ No newline at end of file diff --git a/rtu/threads.lua b/rtu/threads.lua index 5799efb..213781e 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -234,7 +234,7 @@ threads.thread__unit_comms = function (smem, unit) elseif msg.qtype == mqueue.TYPE.PACKET then -- received a packet unit.modbus_busy = true - local return_code, reply = unit.modbus_io.handle_packet(packet) + local return_code, reply = unit.modbus_io.handle_packet(msg.message) rtu_comms.send_modbus(reply) unit.modbus_busy = false end diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index fb60904..a360be9 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -99,8 +99,8 @@ rtu.new_session = function (id, in_queue, out_queue) self.connected = false elseif pkt.type == SCADA_MGMT_TYPES.RTU_ADVERT then -- RTU unit advertisement - for i = 1, packet.length do - local unit = packet.data[i] + for i = 1, pkt.length do + local unit = pkt.data[i] end else log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) From faac421b63b6be16578e96df60f4d56998715c5b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 10 May 2022 21:49:14 -0400 Subject: [PATCH 172/587] #47 reactor plc docs and bugfixes --- reactor-plc/plc.lua | 148 ++++++++++++++----------- reactor-plc/startup.lua | 12 ++- reactor-plc/threads.lua | 234 +++++++++++++++++++++------------------- 3 files changed, 219 insertions(+), 175 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 5b84c7f..04d63fd 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -1,3 +1,4 @@ +---@diagnostic disable: redefined-local local comms = require("scada-common.comms") local log = require("scada-common.log") local ppm = require("scada-common.ppm") @@ -18,9 +19,11 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts --- Reactor Protection System --- identifies dangerous states and SCRAMs reactor if warranted --- autonomous from main SCADA supervisor/coordinator control +--- RPS: Reactor Protection System +--- +--- identifies dangerous states and SCRAMs reactor if warranted +--- +--- autonomous from main SCADA supervisor/coordinator control plc.rps_init = function (reactor) local state_keys = { dmg_crit = 1, @@ -42,6 +45,9 @@ plc.rps_init = function (reactor) trip_cause = "" } + ---@class rps + local public = {} + -- PRIVATE FUNCTIONS -- -- set reactor access fault flag @@ -136,22 +142,28 @@ plc.rps_init = function (reactor) -- PUBLIC FUNCTIONS -- -- re-link a reactor after a peripheral re-connect - local reconnect_reactor = function (reactor) + public.reconnect_reactor = function (reactor) self.reactor = reactor end - -- report a PLC comms timeout - local trip_timeout = function () + -- trip for lost peripheral + public.trip_fault = function () + _set_fault() + end + + -- trip for a PLC comms timeout + public.trip_timeout = function () self.state[state_keys.timed_out] = true end -- manually SCRAM the reactor - local trip_manual = function () + public.trip_manual = function () self.state[state_keys.manual] = true end -- SCRAM the reactor now - local scram = function () + ---@return boolean success + public.scram = function () log.info("RPS: reactor SCRAM") self.reactor.scram() @@ -165,7 +177,8 @@ plc.rps_init = function (reactor) end -- start the reactor - local activate = function () + ---@return boolean success + public.activate = function () if not self.tripped then log.info("RPS: reactor start") @@ -182,7 +195,8 @@ plc.rps_init = function (reactor) end -- check all safety conditions - local check = function () + ---@return boolean tripped, rps_status_t trip_status, boolean first_trip + public.check = function () local status = rps_status_t.ok local was_tripped = self.tripped local first_trip = false @@ -237,38 +251,37 @@ plc.rps_init = function (reactor) self.tripped = true self.trip_cause = status - scram() + public.scram() end return self.tripped, status, first_trip end - -- get the RPS status - local status = function () return self.state end - local is_tripped = function () return self.tripped end - local is_active = function () return self.reactor_enabled end + public.status = function () return self.state end + public.is_tripped = function () return self.tripped end + public.is_active = function () return self.reactor_enabled end -- reset the RPS - local reset = function () + public.reset = function () self.tripped = false self.trip_cause = rps_status_t.ok + + for i = 1, #self.state do + self.state[i] = false + end end - return { - reconnect_reactor = reconnect_reactor, - trip_timeout = trip_timeout, - trip_manual = trip_manual, - scram = scram, - activate = activate, - check = check, - status = status, - is_tripped = is_tripped, - is_active = is_active, - reset = reset - } + return public end --- reactor PLC communications +-- Reactor PLC Communications +---@param id integer +---@param modem table +---@param local_port integer +---@param server_port integer +---@param reactor table +---@param rps rps +---@param conn_watchdog watchdog plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_watchdog) local self = { id = id, @@ -286,6 +299,9 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat max_burn_rate = nil } + ---@class plc_comms + local public = {} + -- open modem if not self.modem.isOpen(self.l_port) then self.modem.open(self.l_port) @@ -293,6 +309,9 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat -- PRIVATE FUNCTIONS -- + -- send an RPLC packet + ---@param msg_type RPLC_TYPES + ---@param msg string local _send = function (msg_type, msg) local s_pkt = comms.scada_packet() local r_pkt = comms.rplc_packet() @@ -304,6 +323,9 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat self.seq_num = self.seq_num + 1 end + -- send a SCADA management packet + ---@param msg_type SCADA_MGMT_TYPES + ---@param msg string local _send_mgmt = function (msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() @@ -316,6 +338,7 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat end -- variable reactor status information, excluding heating rate + ---@return table data_table, boolean faulted local _reactor_status = function () local coolant = nil local hcoolant = nil @@ -373,6 +396,8 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat return data_table, self.reactor.__p_is_faulted() end + -- update the status cache if changed + ---@return boolean changed local _update_status_cache = function () local status, faulted = _reactor_status() local changed = false @@ -398,11 +423,14 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat end -- keep alive ack + ---@param srv_time integer local _send_keep_alive_ack = function (srv_time) _send(SCADA_MGMT_TYPES.KEEP_ALIVE, { srv_time, util.time() }) end -- general ack + ---@param msg_type RPLC_TYPES + ---@param succeeded boolean local _send_ack = function (msg_type, succeeded) _send(msg_type, { succeeded }) end @@ -434,7 +462,8 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat -- PUBLIC FUNCTIONS -- -- reconnect a newly connected modem - local reconnect_modem = function (modem) + ---@param modem table + public.reconnect_modem = function (modem) self.modem = modem -- open modem @@ -444,32 +473,34 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat end -- reconnect a newly connected reactor - local reconnect_reactor = function (reactor) + ---@param reactor table + public.reconnect_reactor = function (reactor) self.reactor = reactor self.status_cache = nil end -- unlink from the server - local unlink = function () + public.unlink = function () self.linked = false self.r_seq_num = nil self.status_cache = nil end -- close the connection to the server - local close = function () + public.close = function () self.conn_watchdog.cancel() - unlink() + public.unlink() _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) end -- attempt to establish link with supervisor - local send_link_req = function () + public.send_link_req = function () _send(RPLC_TYPES.LINK_REQ, { self.id }) end -- send live status information - local send_status = function (degraded) + ---@param degraded boolean + public.send_status = function (degraded) if self.linked then local mek_data = nil @@ -495,14 +526,15 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat end -- send reactor protection system status - local send_rps_status = function () + public.send_rps_status = function () if self.linked then _send(RPLC_TYPES.RPS_STATUS, rps.status()) end end -- send reactor protection system alarm - local send_rps_alarm = function (cause) + ---@param cause rps_status_t + public.send_rps_alarm = function (cause) if self.linked then local rps_alarm = { cause, @@ -514,7 +546,13 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat end -- parse an RPLC packet - local parse_packet = function(side, sender, reply_to, message, distance) + ---@param side string + ---@param sender integer + ---@param reply_to integer + ---@param message any + ---@param distance integer + ---@return rplc_frame|mgmt_frame|nil packet + public.parse_packet = function(side, sender, reply_to, message, distance) local pkt = nil local s_pkt = comms.scada_packet() @@ -543,7 +581,10 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat end -- handle an RPLC packet - local handle_packet = function (packet, plc_state, setpoints) + ---@param packet rplc_frame|mgmt_frame + ---@param plc_state plc_state + ---@param setpoints setpoints + public.handle_packet = function (packet, plc_state, setpoints) if packet ~= nil then -- check sequence number if self.r_seq_num == nil then @@ -573,7 +614,7 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat if link_ack == RPLC_LINKING.ALLOW then self.status_cache = nil _send_struct() - send_status(plc_state.degraded) + public.send_status(plc_state.degraded) log.debug("re-sent initial status data") elseif link_ack == RPLC_LINKING.DENY then println_ts("received unsolicited link denial, unlinking") @@ -593,7 +634,7 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat elseif packet.type == RPLC_TYPES.STATUS then -- request of full status, clear cache first self.status_cache = nil - send_status(plc_state.degraded) + public.send_status(plc_state.degraded) log.debug("sent out status cache again, did supervisor miss it?") elseif packet.type == RPLC_TYPES.MEK_STRUCT then -- request for physical structure @@ -659,7 +700,7 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat self.status_cache = nil _send_struct() - send_status(plc_state.degraded) + public.send_status(plc_state.degraded) log.debug("sent initial status data") elseif link_ack == RPLC_LINKING.DENY then @@ -700,7 +741,7 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat elseif packet.type == SCADA_MGMT_TYPES.CLOSE then -- handle session close self.conn_watchdog.cancel() - unlink() + public.unlink() println_ts("server connection closed by remote host") log.warning("server connection closed by remote host") else @@ -713,23 +754,10 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat end end - local is_scrammed = function () return self.scrammed end - local is_linked = function () return self.linked end + public.is_scrammed = function () return self.scrammed end + public.is_linked = function () return self.linked end - return { - reconnect_modem = reconnect_modem, - reconnect_reactor = reconnect_reactor, - unlink = unlink, - close = close, - send_link_req = send_link_req, - send_status = send_status, - send_rps_status = send_rps_status, - send_rps_alarm = send_rps_alarm, - parse_packet = parse_packet, - handle_packet = handle_packet, - is_scrammed = is_scrammed, - is_linked = is_linked - } + return public end return plc diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 1082afd..9d79a9c 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -29,11 +29,13 @@ println(">> Reactor PLC " .. R_PLC_VERSION .. " <<") ppm.mount_all() -- shared memory across threads +---@class plc_shared_memory local __shared_memory = { -- networked setting - networked = config.NETWORKED, + networked = config.NETWORKED, ---@type boolean -- PLC system state flags + ---@class plc_state plc_state = { init_ok = true, shutdown = false, @@ -42,6 +44,8 @@ local __shared_memory = { no_modem = false }, + -- control setpoints + ---@class setpoints setpoints = { burn_rate_en = false, burn_rate = 0.0 @@ -55,9 +59,9 @@ local __shared_memory = { -- system objects plc_sys = { - rps = nil, - plc_comms = nil, - conn_watchdog = nil + rps = nil, ---@type rps + plc_comms = nil, ---@type plc_comms + conn_watchdog = nil ---@type watchdog }, -- message queues diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 7231f2a..9531792 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -10,8 +10,6 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts -local psleep = util.psleep - local MAIN_CLOCK = 1 -- (1Hz, 20 ticks) local RPS_SLEEP = 250 -- (250ms, 5 ticks) local COMMS_SLEEP = 150 -- (150ms, 3 ticks) @@ -30,6 +28,8 @@ local MQ__COMM_CMD = { } -- main thread +---@param smem plc_shared_memory +---@param init function threads.thread__main = function (smem, init) -- execute thread local exec = function () @@ -47,7 +47,7 @@ threads.thread__main = function (smem, init) local plc_dev = smem.plc_dev local rps = smem.plc_sys.rps local plc_comms = smem.plc_sys.plc_comms - local conn_watchdog = smem.plc_sys.conn_watchdog ---@type watchdog + local conn_watchdog = smem.plc_sys.conn_watchdog -- event loop while true do @@ -187,6 +187,7 @@ threads.thread__main = function (smem, init) end -- RPS operation thread +---@param smem plc_shared_memory threads.thread__rps = function (smem) -- execute thread local exec = function () @@ -224,6 +225,7 @@ threads.thread__rps = function (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 rps.scram() end @@ -249,26 +251,28 @@ threads.thread__rps = function (smem) while rps_queue.ready() and not plc_state.shutdown do local msg = rps_queue.pop() - if msg.qtype == mqueue.TYPE.COMMAND then - -- received a command - if plc_state.init_ok then - if msg.message == MQ__RPS_CMD.SCRAM then - -- SCRAM - rps.scram() - elseif msg.message == MQ__RPS_CMD.DEGRADED_SCRAM then - -- lost peripheral(s) - rps.trip_degraded() - elseif msg.message == MQ__RPS_CMD.TRIP_TIMEOUT then - -- watchdog tripped - rps.trip_timeout() - println_ts("server timeout") - log.warning("server timeout") + if msg ~= nil then + if msg.qtype == mqueue.TYPE.COMMAND then + -- received a command + if plc_state.init_ok then + if msg.message == MQ__RPS_CMD.SCRAM then + -- SCRAM + rps.scram() + elseif msg.message == MQ__RPS_CMD.DEGRADED_SCRAM then + -- lost peripheral(s) + rps.trip_fault() + elseif msg.message == MQ__RPS_CMD.TRIP_TIMEOUT then + -- watchdog tripped + rps.trip_timeout() + println_ts("server timeout") + log.warning("server timeout") + end end + elseif msg.qtype == mqueue.TYPE.DATA then + -- received data + elseif msg.qtype == mqueue.TYPE.PACKET then + -- received a packet end - elseif msg.qtype == mqueue.TYPE.DATA then - -- received data - elseif msg.qtype == mqueue.TYPE.PACKET then - -- received a packet end -- quick yield @@ -301,6 +305,7 @@ threads.thread__rps = function (smem) end -- communications sender thread +---@param smem plc_shared_memory threads.thread__comms_tx = function (smem) -- execute thread local exec = function () @@ -320,17 +325,19 @@ threads.thread__comms_tx = function (smem) while comms_queue.ready() and not plc_state.shutdown do local msg = comms_queue.pop() - if msg.qtype == mqueue.TYPE.COMMAND then - -- received a command - if msg.message == MQ__COMM_CMD.SEND_STATUS then - -- send PLC/RPS status - plc_comms.send_status(plc_state.degraded) - plc_comms.send_rps_status() + if msg ~= nil then + if msg.qtype == mqueue.TYPE.COMMAND then + -- received a command + if msg.message == MQ__COMM_CMD.SEND_STATUS then + -- send PLC/RPS status + plc_comms.send_status(plc_state.degraded) + plc_comms.send_rps_status() + end + elseif msg.qtype == mqueue.TYPE.DATA then + -- received data + elseif msg.qtype == mqueue.TYPE.PACKET then + -- received a packet end - elseif msg.qtype == mqueue.TYPE.DATA then - -- received data - elseif msg.qtype == mqueue.TYPE.PACKET then - -- received a packet end -- quick yield @@ -352,22 +359,20 @@ threads.thread__comms_tx = function (smem) end -- communications handler thread +---@param smem plc_shared_memory threads.thread__comms_rx = function (smem) -- execute thread local exec = function () log.debug("comms rx thread start") -- load in from shared memory - local plc_state = smem.plc_state - local setpoints = smem.setpoints - local plc_dev = smem.plc_dev - local rps = smem.plc_sys.rps - local plc_comms = smem.plc_sys.plc_comms - local conn_watchdog = smem.plc_sys.conn_watchdog + local plc_state = smem.plc_state + local setpoints = smem.setpoints + local plc_comms = smem.plc_sys.plc_comms - local comms_queue = smem.q.mq_comms_rx + local comms_queue = smem.q.mq_comms_rx - local last_update = util.time() + local last_update = util.time() -- thread loop while true do @@ -375,16 +380,17 @@ threads.thread__comms_rx = function (smem) while comms_queue.ready() and not plc_state.shutdown do local msg = comms_queue.pop() - if msg.qtype == mqueue.TYPE.COMMAND then - -- received a command - elseif msg.qtype == mqueue.TYPE.DATA then - -- received data - elseif msg.qtype == mqueue.TYPE.PACKET then - -- received a packet - -- handle the packet (setpoints passed to update burn rate setpoint) - -- (plc_state passed to check if degraded) - -- (conn_watchdog passed to allow feeding the watchdog) - plc_comms.handle_packet(msg.message, setpoints, plc_state, conn_watchdog) + if msg ~= nil then + if msg.qtype == mqueue.TYPE.COMMAND then + -- received a command + elseif msg.qtype == mqueue.TYPE.DATA then + -- received data + elseif msg.qtype == mqueue.TYPE.PACKET then + -- received a packet + -- handle the packet (setpoints passed to update burn rate setpoint) + -- (plc_state passed to check if degraded) + plc_comms.handle_packet(msg.message, setpoints, plc_state) + end end -- quick yield @@ -406,84 +412,90 @@ threads.thread__comms_rx = function (smem) end -- apply setpoints +---@param smem plc_shared_memory threads.thread__setpoint_control = function (smem) -- execute thread local exec = function () log.debug("setpoint control thread start") -- load in from shared memory - local plc_state = smem.plc_state - local setpoints = smem.setpoints - local plc_dev = smem.plc_dev - local rps = smem.plc_sys.rps + local plc_state = smem.plc_state + local setpoints = smem.setpoints + local plc_dev = smem.plc_dev + local rps = smem.plc_sys.rps - local last_update = util.time() - local running = false + local last_update = util.time() + local running = false - local last_sp_burn = 0 + local last_sp_burn = 0.0 + + -- do not use the actual elapsed time, it could spike + -- we do not want to have big jumps as that is what we are trying to avoid in the first place + local min_elapsed_s = SP_CTRL_SLEEP / 1000.0 -- thread loop while true do local reactor = plc_dev.reactor - -- check if we should start ramping - if setpoints.burn_rate_en and setpoints.burn_rate ~= last_sp_burn then - if rps.is_active() then - if math.abs(setpoints.burn_rate - last_sp_burn) <= 5 then - -- update without ramp if <= 5 mB/t change - log.debug("setting burn rate directly to " .. setpoints.burn_rate .. "mB/t") - reactor.setBurnRate(setpoints.burn_rate) - else - log.debug("starting burn rate ramp from " .. last_sp_burn .. "mB/t to " .. setpoints.burn_rate .. "mB/t") - running = true - end - - last_sp_burn = setpoints.burn_rate - else - last_sp_burn = 0 - end - end - - -- only check I/O if active to save on processing time - if running then - -- do not use the actual elapsed time, it could spike - -- we do not want to have big jumps as that is what we are trying to avoid in the first place - local min_elapsed_s = SP_CTRL_SLEEP / 1000.0 - - -- clear so we can later evaluate if we should keep running - running = false - - -- adjust burn rate (setpoints.burn_rate) - if setpoints.burn_rate_en then + if plc_state.init_ok and not plc_state.no_reactor then + -- check if we should start ramping + if setpoints.burn_rate_en and setpoints.burn_rate ~= last_sp_burn then if rps.is_active() then - local current_burn_rate = reactor.getBurnRate() - - -- we yielded, check enable again - if setpoints.burn_rate_en and (current_burn_rate ~= ppm.ACCESS_FAULT) and (current_burn_rate ~= setpoints.burn_rate) then - -- calculate new burn rate - local new_burn_rate = current_burn_rate - - if setpoints.burn_rate > current_burn_rate then - -- need to ramp up - local new_burn_rate = current_burn_rate + (BURN_RATE_RAMP_mB_s * min_elapsed_s) - if new_burn_rate > setpoints.burn_rate then - new_burn_rate = setpoints.burn_rate - end - else - -- need to ramp down - local new_burn_rate = current_burn_rate - (BURN_RATE_RAMP_mB_s * min_elapsed_s) - if new_burn_rate < setpoints.burn_rate then - new_burn_rate = setpoints.burn_rate - end - end - - -- set the burn rate - reactor.setBurnRate(new_burn_rate) - - running = running or (new_burn_rate ~= setpoints.burn_rate) + if math.abs(setpoints.burn_rate - last_sp_burn) <= 5 then + -- update without ramp if <= 5 mB/t change + log.debug("setting burn rate directly to " .. setpoints.burn_rate .. "mB/t") +---@diagnostic disable-next-line: need-check-nil + reactor.setBurnRate(setpoints.burn_rate) + else + log.debug("starting burn rate ramp from " .. last_sp_burn .. "mB/t to " .. setpoints.burn_rate .. "mB/t") + running = true end + + last_sp_burn = setpoints.burn_rate else - last_sp_burn = 0 + last_sp_burn = 0.0 + end + end + + -- only check I/O if active to save on processing time + if running then + -- clear so we can later evaluate if we should keep running + running = false + + -- adjust burn rate (setpoints.burn_rate) + if setpoints.burn_rate_en then + if rps.is_active() then +---@diagnostic disable-next-line: need-check-nil + local current_burn_rate = reactor.getBurnRate() + + -- we yielded, check enable again + if setpoints.burn_rate_en and (current_burn_rate ~= ppm.ACCESS_FAULT) and (current_burn_rate ~= setpoints.burn_rate) then + -- calculate new burn rate + local new_burn_rate = current_burn_rate + + if setpoints.burn_rate > current_burn_rate then + -- need to ramp up + local new_burn_rate = current_burn_rate + (BURN_RATE_RAMP_mB_s * min_elapsed_s) + if new_burn_rate > setpoints.burn_rate then + new_burn_rate = setpoints.burn_rate + end + else + -- need to ramp down + local new_burn_rate = current_burn_rate - (BURN_RATE_RAMP_mB_s * min_elapsed_s) + if new_burn_rate < setpoints.burn_rate then + new_burn_rate = setpoints.burn_rate + end + end + + -- set the burn rate +---@diagnostic disable-next-line: need-check-nil + reactor.setBurnRate(new_burn_rate) + + running = running or (new_burn_rate ~= setpoints.burn_rate) + end + else + last_sp_burn = 0.0 + end end end end From bced8bf56636d1fc5c3dba6d8ff99002fbbaad37 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 10 May 2022 21:51:04 -0400 Subject: [PATCH 173/587] #47 packet frames --- scada-common/comms.lua | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 9eebb2d..6b4deef 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -207,7 +207,8 @@ comms.modbus_packet = function () -- get this packet public.get = function () - return { + ---@class modbus_frame + local frame = { scada_frame = self.frame, txn_id = self.txn_id, length = self.length, @@ -215,6 +216,8 @@ comms.modbus_packet = function () func_code = self.func_code, data = self.data } + + return frame end return public @@ -297,13 +300,16 @@ comms.rplc_packet = function () -- get this packet public.get = function () - return { + ---@class rplc_frame + local frame = { scada_frame = self.frame, id = self.id, type = self.type, length = self.length, data = self.data } + + return frame end return public @@ -378,12 +384,15 @@ comms.mgmt_packet = function () -- get this packet public.get = function () - return { + ---@class mgmt_frame + local frame = { scada_frame = self.frame, type = self.type, length = self.length, data = self.data } + + return frame end return public @@ -456,12 +465,15 @@ comms.coord_packet = function () -- get this packet public.get = function () - return { + ---@class coord_frame + local frame = { scada_frame = self.frame, type = self.type, length = self.length, data = self.data } + + return frame end return public @@ -534,12 +546,15 @@ comms.capi_packet = function () -- get this packet public.get = function () - return { + ---@class capi_frame + local frame = { scada_frame = self.frame, type = self.type, length = self.length, data = self.data } + + return frame end return public From 02541184bd3fe944fad01308d7aa343105064d94 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 11 May 2022 11:31:02 -0400 Subject: [PATCH 174/587] bootloader --- .vscode/settings.json | 5 +-- coordinator/startup.lua | 4 +-- reactor-plc/startup.lua | 6 ++-- rtu/rtu.lua | 2 +- rtu/startup.lua | 22 ++++++------- rtu/threads.lua | 16 +++++----- startup.lua | 52 +++++++++++++++++++++++++++++++ supervisor/session/svsessions.lua | 6 ++-- supervisor/startup.lua | 12 +++---- supervisor/supervisor.lua | 2 +- 10 files changed, 90 insertions(+), 37 deletions(-) create mode 100644 startup.lua diff --git a/.vscode/settings.json b/.vscode/settings.json index 77e08ef..d2812b6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,7 @@ "bit", "parallel", "colors", - "textutils" + "textutils", + "shell" ] -} \ No newline at end of file +} diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 5ee3d17..ab90f72 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -6,8 +6,8 @@ local log = require("scada-common.log") local ppm = require("scada-common.ppm") local util = require("scada-common.util") -local config = require("config") -local coordinator = require("coordinator") +local config = require("coordinator.config") +local coordinator = require("coordinator.coordinator") local COORDINATOR_VERSION = "alpha-v0.1.2" diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 9d79a9c..0aaaeba 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -7,9 +7,9 @@ local mqueue = require("scada-common.mqueue") local ppm = require("scada-common.ppm") local util = require("scada-common.util") -local config = require("config") -local plc = require("plc") -local threads = require("threads") +local config = require("reactor-plc.config") +local plc = require("reactor-plc.plc") +local threads = require("reactor-plc.threads") local R_PLC_VERSION = "alpha-v0.6.6" diff --git a/rtu/rtu.lua b/rtu/rtu.lua index ec20c93..f7b0432 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -4,7 +4,7 @@ local log = require("scada-common.log") local types = require("scada-common.types") local util = require("scada-common.util") -local modbus = require("modbus") +local modbus = require("rtu.modbus") local rtu = {} diff --git a/rtu/startup.lua b/rtu/startup.lua index 18119c0..ad31460 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -9,18 +9,18 @@ local rsio = require("scada-common.rsio") local types = require("scada-common.types") local util = require("scada-common.util") -local config = require("config") -local modbus = require("modbus") -local rtu = require("rtu") -local threads = require("threads") +local config = require("rtu.config") +local modbus = require("rtu.modbus") +local rtu = require("rtu.rtu") +local threads = require("rtu.threads") -local redstone_rtu = require("dev.redstone_rtu") -local boiler_rtu = require("dev.boiler_rtu") -local boilerv_rtu = require("dev.boilerv_rtu") -local energymachine_rtu = require("dev.energymachine_rtu") -local imatrix_rtu = require("dev.imatrix_rtu") -local turbine_rtu = require("dev.turbine_rtu") -local turbinev_rtu = require("dev.turbinev_rtu") +local redstone_rtu = require("rtu.dev.redstone_rtu") +local boiler_rtu = require("rtu.dev.boiler_rtu") +local boilerv_rtu = require("rtu.dev.boilerv_rtu") +local energymachine_rtu = require("rtu.dev.energymachine_rtu") +local imatrix_rtu = require("rtu.dev.imatrix_rtu") +local turbine_rtu = require("rtu.dev.turbine_rtu") +local turbinev_rtu = require("rtu.dev.turbinev_rtu") local RTU_VERSION = "alpha-v0.6.2" diff --git a/rtu/threads.lua b/rtu/threads.lua index 213781e..447178b 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -5,15 +5,15 @@ local ppm = require("scada-common.ppm") local types = require("scada-common.types") local util = require("scada-common.util") -local redstone_rtu = require("dev.redstone_rtu") -local boiler_rtu = require("dev.boiler_rtu") -local boilerv_rtu = require("dev.boilerv_rtu") -local energymachine_rtu = require("dev.energymachine_rtu") -local imatrix_rtu = require("dev.imatrix_rtu") -local turbine_rtu = require("dev.turbine_rtu") -local turbinev_rtu = require("dev.turbinev_rtu") +local redstone_rtu = require("rtu.dev.redstone_rtu") +local boiler_rtu = require("rtu.dev.boiler_rtu") +local boilerv_rtu = require("rtu.dev.boilerv_rtu") +local energymachine_rtu = require("rtu.dev.energymachine_rtu") +local imatrix_rtu = require("rtu.dev.imatrix_rtu") +local turbine_rtu = require("rtu.dev.turbine_rtu") +local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local modbus = require("modbus") +local modbus = require("rtu.modbus") local threads = {} diff --git a/startup.lua b/startup.lua new file mode 100644 index 0000000..0a5cffb --- /dev/null +++ b/startup.lua @@ -0,0 +1,52 @@ +local util = require("scada-common.util") + +local BOOTLOADER_VERSION = "0.1" + +local println = util.println +local println_ts = util.println_ts + +println("SCADA BOOTLOADER V" .. BOOTLOADER_VERSION) + +local exit_code = false + +println_ts("BOOT> SCANNING FOR APPLICATIONS...") + +if fs.exists("reactor-plc/startup.lua") then + -- found reactor-plc application + println("BOOT> FOUND REACTOR PLC APPLICATION") + println("BOOT> EXEC STARTUP") + exit_code = shell.execute("reactor-plc/startup") +elseif fs.exists("rtu/startup.lua") then + -- found rtu application + println("BOOT> FOUND RTU APPLICATION") + println("BOOT> EXEC STARTUP") + exit_code = shell.execute("rtu/startup") +elseif fs.exists("supervisor/startup.lua") then + -- found supervisor application + println("BOOT> FOUND SUPERVISOR APPLICATION") + println("BOOT> EXEC STARTUP") + exit_code = shell.execute("supervisor/startup") +elseif fs.exists("coordinator/startup.lua") then + -- found coordinator application + println("BOOT> FOUND COORDINATOR APPLICATION") + println("BOOT> EXEC STARTUP") + exit_code = shell.execute("coordinator/startup") +elseif fs.exists("pocket/startup.lua") then + -- found pocket application + println("BOOT> FOUND POCKET APPLICATION") + println("BOOT> EXEC STARTUP") + exit_code = shell.execute("pocket/startup") +else + -- no known applications found + println("BOOT> NO SCADA STARTUP APPLICATION FOUND") + println("BOOT> EXIT") + return false +end + +if exit_code then + println_ts("BOOT> APPLICATION EXITED OK") +else + println_ts("BOOT> APPLICATION CRASHED") +end + +return exit_code diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index fa983a6..e596985 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -1,9 +1,9 @@ local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") -local coordinator = require("session.coordinator") -local plc = require("session.plc") -local rtu = require("session.rtu") +local coordinator = require("supervisor.session.coordinator") +local plc = require("supervisor.session.plc") +local rtu = require("supervisor.session.rtu") -- Supervisor Sessions Handler diff --git a/supervisor/startup.lua b/supervisor/startup.lua index bf0eabc..be9549e 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -6,13 +6,13 @@ local log = require("scada-common.log") local ppm = require("scada-common.ppm") local util = require("scada-common.util") -local coordinator = require("session.coordinator") -local plc = require("session.plc") -local rtu = require("session.rtu") -local svsessions = require("session.svsessions") +local coordinator = require("supervisor.session.coordinator") +local plc = require("supervisor.session.plc") +local rtu = require("supervisor.session.rtu") +local svsessions = require("supervisor.session.svsessions") -local config = require("config") -local supervisor = require("supervisor") +local config = require("supervisor.config") +local supervisor = require("supervisor.supervisor") local SUPERVISOR_VERSION = "alpha-v0.3.4" diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 3d4fa17..f9df71e 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -2,7 +2,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local util = require("scada-common.util") -local svsessions = require("session.svsessions") +local svsessions = require("supervisor.session.svsessions") local supervisor = {} From 5ad14205f30d62ef481c811de0eb15d352705a66 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 11 May 2022 12:01:18 -0400 Subject: [PATCH 175/587] #47 not going to do file level diagnostic disables --- reactor-plc/plc.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 04d63fd..b54354c 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -1,4 +1,3 @@ ----@diagnostic disable: redefined-local local comms = require("scada-common.comms") local log = require("scada-common.log") local ppm = require("scada-common.ppm") @@ -142,6 +141,7 @@ plc.rps_init = function (reactor) -- PUBLIC FUNCTIONS -- -- re-link a reactor after a peripheral re-connect +---@diagnostic disable-next-line: redefined-local public.reconnect_reactor = function (reactor) self.reactor = reactor end @@ -463,6 +463,7 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat -- reconnect a newly connected modem ---@param modem table +---@diagnostic disable-next-line: redefined-local public.reconnect_modem = function (modem) self.modem = modem @@ -474,6 +475,7 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat -- reconnect a newly connected reactor ---@param reactor table +---@diagnostic disable-next-line: redefined-local public.reconnect_reactor = function (reactor) self.reactor = reactor self.status_cache = nil From c6987f6f67b31f7b4f55ab4d080ee8ca6b1cdfc6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 11 May 2022 12:03:15 -0400 Subject: [PATCH 176/587] #47 RTU luadoc, bugfixes --- rtu/modbus.lua | 56 +++++++++++---- rtu/rtu.lua | 183 ++++++++++++++++++++++++++++-------------------- rtu/startup.lua | 18 +++-- rtu/threads.lua | 54 +++++++------- 4 files changed, 192 insertions(+), 119 deletions(-) diff --git a/rtu/modbus.lua b/rtu/modbus.lua index b64910f..0e5e16c 100644 --- a/rtu/modbus.lua +++ b/rtu/modbus.lua @@ -7,14 +7,22 @@ local MODBUS_FCODE = types.MODBUS_FCODE local MODBUS_EXCODE = types.MODBUS_EXCODE -- new modbus comms handler object +---@param rtu_dev rtu RTU device +---@param use_parallel_read boolean whether or not to use parallel calls when reading modbus.new = function (rtu_dev, use_parallel_read) local self = { rtu = rtu_dev, use_parallel = use_parallel_read } + ---@class modbus + local public = {} + local insert = table.insert + ---@param c_addr_start integer + ---@param count integer + ---@return boolean ok, table readings local _1_read_coils = function (c_addr_start, count) local tasks = {} local readings = {} @@ -58,6 +66,9 @@ modbus.new = function (rtu_dev, use_parallel_read) return return_ok, readings end + ---@param di_addr_start integer + ---@param count integer + ---@return boolean ok, table readings local _2_read_discrete_inputs = function (di_addr_start, count) local tasks = {} local readings = {} @@ -101,6 +112,9 @@ modbus.new = function (rtu_dev, use_parallel_read) return return_ok, readings end + ---@param hr_addr_start integer + ---@param count integer + ---@return boolean ok, table readings local _3_read_multiple_holding_registers = function (hr_addr_start, count) local tasks = {} local readings = {} @@ -144,6 +158,9 @@ modbus.new = function (rtu_dev, use_parallel_read) return return_ok, readings end + ---@param ir_addr_start integer + ---@param count integer + ---@return boolean ok, table readings local _4_read_input_registers = function (ir_addr_start, count) local tasks = {} local readings = {} @@ -187,6 +204,9 @@ modbus.new = function (rtu_dev, use_parallel_read) return return_ok, readings end + ---@param c_addr integer + ---@param value any + ---@return boolean ok, MODBUS_EXCODE|nil local _5_write_single_coil = function (c_addr, value) local response = nil local _, coils, _, _ = self.rtu.io_count() @@ -206,6 +226,9 @@ modbus.new = function (rtu_dev, use_parallel_read) return return_ok, response end + ---@param hr_addr integer + ---@param value any + ---@return boolean ok, MODBUS_EXCODE|nil local _6_write_single_holding_register = function (hr_addr, value) local response = nil local _, _, _, hold_regs = self.rtu.io_count() @@ -222,9 +245,12 @@ modbus.new = function (rtu_dev, use_parallel_read) response = MODBUS_EXCODE.ILLEGAL_DATA_ADDR end - return return_ok + return return_ok, response end + ---@param c_addr_start integer + ---@param values any + ---@return boolean ok, MODBUS_EXCODE|nil local _15_write_multiple_coils = function (c_addr_start, values) local response = nil local _, coils, _, _ = self.rtu.io_count() @@ -249,6 +275,9 @@ modbus.new = function (rtu_dev, use_parallel_read) return return_ok, response end + ---@param hr_addr_start integer + ---@param values any + ---@return boolean ok, MODBUS_EXCODE|nil local _16_write_multiple_holding_registers = function (hr_addr_start, values) local response = nil local _, _, _, hold_regs = self.rtu.io_count() @@ -274,7 +303,9 @@ modbus.new = function (rtu_dev, use_parallel_read) end -- validate a request without actually executing it - local check_request = function (packet) + ---@param packet modbus_frame + ---@return boolean return_code, modbus_packet reply + public.check_request = function (packet) local return_code = true local response = { MODBUS_EXCODE.ACKNOWLEDGE } @@ -314,7 +345,9 @@ modbus.new = function (rtu_dev, use_parallel_read) end -- handle a MODBUS TCP packet and generate a reply - local handle_packet = function (packet) + ---@param packet modbus_frame + ---@return boolean return_code, modbus_packet reply + public.handle_packet = function (packet) local return_code = true local response = nil @@ -369,7 +402,8 @@ modbus.new = function (rtu_dev, use_parallel_read) end -- return a SERVER_DEVICE_BUSY error reply - local reply__srv_device_busy = function (packet) + ---@return modbus_packet reply + public.reply__srv_device_busy = function (packet) -- reply back with error flag and exception code local reply = comms.modbus_packet() local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) @@ -379,7 +413,8 @@ modbus.new = function (rtu_dev, use_parallel_read) end -- return a NEG_ACKNOWLEDGE error reply - local reply__neg_ack = function (packet) + ---@return modbus_packet reply + public.reply__neg_ack = function (packet) -- reply back with error flag and exception code local reply = comms.modbus_packet() local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) @@ -389,7 +424,8 @@ modbus.new = function (rtu_dev, use_parallel_read) end -- return a GATEWAY_PATH_UNAVAILABLE error reply - local reply__gw_unavailable = function (packet) + ---@return modbus_packet reply + public.reply__gw_unavailable = function (packet) -- reply back with error flag and exception code local reply = comms.modbus_packet() local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) @@ -398,13 +434,7 @@ modbus.new = function (rtu_dev, use_parallel_read) return reply end - return { - check_request = check_request, - handle_packet = handle_packet, - reply__srv_device_busy = reply__srv_device_busy, - reply__neg_ack = reply__neg_ack, - reply__gw_unavailable = reply__gw_unavailable - } + return public end return modbus diff --git a/rtu/rtu.lua b/rtu/rtu.lua index f7b0432..ab47b0a 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -19,6 +19,7 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts +-- create a new RTU rtu.init_unit = function () local self = { discrete_inputs = {}, @@ -28,28 +29,36 @@ rtu.init_unit = function () io_count_cache = { 0, 0, 0, 0 } } + ---@class rtu + local public = {} + local insert = table.insert local _count_io = function () self.io_count_cache = { #self.discrete_inputs, #self.coils, #self.input_regs, #self.holding_regs } end - -- return : IO count table - local io_count = function () + -- return IO counts + ---@return integer discrete_inputs, integer coils, integer input_regs, integer holding_regs + public.io_count = function () return self.io_count_cache[0], self.io_count_cache[1], self.io_count_cache[2], self.io_count_cache[3] end -- discrete inputs: single bit read-only - -- return : count of discrete inputs - local connect_di = function (f) + -- connect discrete input + ---@param f function + ---@return integer count count of discrete inputs + public.connect_di = function (f) insert(self.discrete_inputs, f) _count_io() return #self.discrete_inputs end - -- return : value, access fault - local read_di = function (di_addr) + -- read discrete input + ---@param di_addr integer + ---@return any value, boolean access_fault + public.read_di = function (di_addr) ppm.clear_fault() local value = self.discrete_inputs[di_addr]() return value, ppm.is_faulted() @@ -57,22 +66,30 @@ rtu.init_unit = function () -- coils: single bit read-write - -- return : count of coils - local connect_coil = function (f_read, f_write) + -- connect coil + ---@param f_read function + ---@param f_write function + ---@return integer count count of coils + public.connect_coil = function (f_read, f_write) insert(self.coils, { read = f_read, write = f_write }) _count_io() return #self.coils end - -- return : value, access fault - local read_coil = function (coil_addr) + -- read coil + ---@param coil_addr integer + ---@return any value, boolean access_fault + public.read_coil = function (coil_addr) ppm.clear_fault() local value = self.coils[coil_addr].read() return value, ppm.is_faulted() end - -- return : access fault - local write_coil = function (coil_addr, value) + -- write coil + ---@param coil_addr integer + ---@param value any + ---@return boolean access_fault + public.write_coil = function (coil_addr, value) ppm.clear_fault() self.coils[coil_addr].write(value) return ppm.is_faulted() @@ -80,15 +97,19 @@ rtu.init_unit = function () -- input registers: multi-bit read-only - -- return : count of input registers - local connect_input_reg = function (f) + -- connect input register + ---@param f function + ---@return integer count count of input registers + public.connect_input_reg = function (f) insert(self.input_regs, f) _count_io() return #self.input_regs end - -- return : value, access fault - local read_input_reg = function (reg_addr) + -- read input register + ---@param reg_addr integer + ---@return any value, boolean access_fault + public.read_input_reg = function (reg_addr) ppm.clear_fault() local value = self.coils[reg_addr]() return value, ppm.is_faulted() @@ -96,42 +117,43 @@ rtu.init_unit = function () -- holding registers: multi-bit read-write - -- return : count of holding registers - local connect_holding_reg = function (f_read, f_write) + -- connect holding register + ---@param f_read function + ---@param f_write function + ---@return integer count count of holding registers + public.connect_holding_reg = function (f_read, f_write) insert(self.holding_regs, { read = f_read, write = f_write }) _count_io() return #self.holding_regs end - -- return : value, access fault - local read_holding_reg = function (reg_addr) + -- read holding register + ---@param reg_addr integer + ---@return any value, boolean access_fault + public.read_holding_reg = function (reg_addr) ppm.clear_fault() local value = self.coils[reg_addr].read() return value, ppm.is_faulted() end - -- return : access fault - local write_holding_reg = function (reg_addr, value) + -- write holding register + ---@param reg_addr integer + ---@param value any + ---@return boolean access_fault + public.write_holding_reg = function (reg_addr, value) ppm.clear_fault() self.coils[reg_addr].write(value) return ppm.is_faulted() end - return { - io_count = io_count, - connect_di = connect_di, - read_di = read_di, - connect_coil = connect_coil, - read_coil = read_coil, - write_coil = write_coil, - connect_input_reg = connect_input_reg, - read_input_reg = read_input_reg, - connect_holding_reg = connect_holding_reg, - read_holding_reg = read_holding_reg, - write_holding_reg = write_holding_reg - } + return public end +-- RTU Communications +---@param modem table +---@param local_port integer +---@param server_port integer +---@param conn_watchdog watchdog rtu.comms = function (modem, local_port, server_port, conn_watchdog) local self = { seq_num = 0, @@ -143,6 +165,9 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) conn_watchdog = conn_watchdog } + ---@class rtu_comms + local public = {} + local insert = table.insert -- open modem @@ -152,6 +177,9 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) -- PRIVATE FUNCTIONS -- + -- send a scada management packet + ---@param msg_type SCADA_MGMT_TYPES + ---@param msg any local _send = function (msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() @@ -164,6 +192,7 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) end -- keep alive ack + ---@param srv_time integer local _send_keep_alive_ack = function (srv_time) _send(SCADA_MGMT_TYPES.KEEP_ALIVE, { srv_time, util.time() }) end @@ -171,7 +200,8 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) -- PUBLIC FUNCTIONS -- -- send a MODBUS TCP packet - local send_modbus = function (m_pkt) + ---@param m_pkt modbus_packet + public.send_modbus = function (m_pkt) local s_pkt = comms.scada_packet() s_pkt.make(self.seq_num, PROTOCOLS.MODBUS_TCP, m_pkt.raw_sendable()) self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) @@ -179,7 +209,9 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) end -- reconnect a newly connected modem - local reconnect_modem = function (modem) + ---@param modem table +---@diagnostic disable-next-line: redefined-local + public.reconnect_modem = function (modem) self.modem = modem -- open modem @@ -189,42 +221,43 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) end -- unlink from the server - local unlink = function (rtu_state) + ---@param rtu_state rtu_state + public.unlink = function (rtu_state) rtu_state.linked = false self.r_seq_num = nil end -- close the connection to the server - local close = function (rtu_state) + ---@param rtu_state rtu_state + public.close = function (rtu_state) self.conn_watchdog.cancel() - unlink(rtu_state) + public.unlink(rtu_state) _send(SCADA_MGMT_TYPES.CLOSE, {}) end -- send capability advertisement - local send_advertisement = function (units) + ---@param units table + public.send_advertisement = function (units) local advertisement = {} for i = 1, #units do - local unit = units[i] + local unit = units[i] ---@type rtu_unit_registry_entry local type = comms.rtu_t_to_advert_type(unit.type) if type ~= nil then + ---@class rtu_advertisement + local advert = { + type = type, ---@type integer + index = unit.index, ---@type integer + reactor = unit.reactor, ---@type integer + rsio = nil ---@type table|nil + } + if type == RTU_ADVERT_TYPES.REDSTONE then - insert(advertisement, { - type = type, - index = unit.index, - reactor = unit.for_reactor, - rsio = unit.device - }) - else - insert(advertisement, { - type = type, - index = unit.index, - reactor = unit.for_reactor, - rsio = nil - }) + advert.rsio = unit.device end + + insert(advertisement, advert) end end @@ -232,7 +265,13 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) end -- parse a MODBUS/SCADA packet - local parse_packet = function(side, sender, reply_to, message, distance) + ---@param side string + ---@param sender integer + ---@param reply_to integer + ---@param message any + ---@param distance integer + ---@return modbus_frame|mgmt_frame|nil packet + public.parse_packet = function(side, sender, reply_to, message, distance) local pkt = nil local s_pkt = comms.scada_packet() @@ -261,10 +300,11 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) end -- handle a MODBUS/SCADA packet - local handle_packet = function(packet, units, rtu_state) + ---@param packet modbus_frame|mgmt_frame + ---@param units table + ---@param rtu_state rtu_state + public.handle_packet = function(packet, units, rtu_state) if packet ~= nil then - local seq_ok = true - -- check sequence number if self.r_seq_num == nil then self.r_seq_num = packet.scada_frame.seq_num() @@ -281,26 +321,27 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) local protocol = packet.scada_frame.protocol() if protocol == PROTOCOLS.MODBUS_TCP then + local return_code = false local reply = modbus.reply__neg_ack(packet) -- handle MODBUS instruction if packet.unit_id <= #units then - local unit = units[packet.unit_id] + local unit = units[packet.unit_id] ---@type rtu_unit_registry_entry if unit.name == "redstone_io" then -- immediately execute redstone RTU requests - local return_code, reply = unit.modbus_io.handle_packet(packet) + return_code, reply = unit.modbus_io.handle_packet(packet) if not return_code then log.warning("requested MODBUS operation failed") end else -- check validity then pass off to unit comms thread - local return_code, reply = unit.modbus_io.check_request(packet) + return_code, reply = unit.modbus_io.check_request(packet) if return_code then -- check if an operation is already in progress for this unit if unit.modbus_busy then reply = unit.modbus_io.reply__srv_device_busy(packet) else - unit.pkt_queue.push(packet) + unit.pkt_queue.push_packet(packet) end else log.warning("cannot perform requested MODBUS operation") @@ -312,7 +353,7 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) log.error("MODBUS packet requesting non-existent unit") end - send_modbus(reply) + public.send_modbus(reply) elseif protocol == PROTOCOLS.SCADA_MGMT then -- SCADA management packet if packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then @@ -334,7 +375,7 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) elseif packet.type == SCADA_MGMT_TYPES.CLOSE then -- close connection self.conn_watchdog.cancel() - unlink(rtu_state) + public.unlink(rtu_state) println_ts("server connection closed by remote host") log.warning("server connection closed by remote host") elseif packet.type == SCADA_MGMT_TYPES.REMOTE_LINKED then @@ -343,7 +384,7 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) self.r_seq_num = nil elseif packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then -- request for capabilities again - send_advertisement(units) + public.send_advertisement(units) else -- not supported log.warning("RTU got unexpected SCADA message type " .. packet.type) @@ -355,15 +396,7 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) end end - return { - send_modbus = send_modbus, - reconnect_modem = reconnect_modem, - parse_packet = parse_packet, - handle_packet = handle_packet, - send_advertisement = send_advertisement, - unlink = unlink, - close = close - } + return public end return rtu diff --git a/rtu/startup.lua b/rtu/startup.lua index ad31460..3b34670 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -22,7 +22,7 @@ local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "alpha-v0.6.2" +local RTU_VERSION = "alpha-v0.6.3" local rtu_t = types.rtu_t @@ -45,8 +45,10 @@ println(">> RTU " .. RTU_VERSION .. " <<") -- mount connected devices ppm.mount_all() +---@class rtu_shared_memory local __shared_memory = { -- RTU system state flags + ---@class rtu_state rtu_state = { linked = false, shutdown = false @@ -59,9 +61,9 @@ local __shared_memory = { -- system objects rtu_sys = { - rtu_comms = nil, - conn_watchdog = nil, - units = {} + rtu_comms = nil, ---@type rtu_comms + conn_watchdog = nil, ---@type watchdog + units = {} ---@type table }, -- message queues @@ -140,7 +142,8 @@ for reactor_idx = 1, #rtu_redstone do end end - table.insert(units, { + ---@class rtu_unit_registry_entry + local unit = { name = "redstone_io", type = rtu_t.redstone, index = 1, @@ -151,7 +154,9 @@ for reactor_idx = 1, #rtu_redstone do modbus_busy = false, pkt_queue = nil, thread = nil - }) + } + + table.insert(units, unit) log.debug("init> initialized RTU unit #" .. #units .. ": redstone_io (redstone) [1] for reactor " .. rtu_redstone[reactor_idx].for_reactor) end @@ -201,6 +206,7 @@ for i = 1, #rtu_devices do end if rtu_iface ~= nil then + ---@class rtu_unit_registry_entry local rtu_unit = { name = rtu_devices[i].name, type = rtu_type, diff --git a/rtu/threads.lua b/rtu/threads.lua index 447178b..27f5c27 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -1,11 +1,9 @@ -local comms = require("scada-common.comms") local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") local ppm = require("scada-common.ppm") local types = require("scada-common.types") local util = require("scada-common.util") -local redstone_rtu = require("rtu.dev.redstone_rtu") local boiler_rtu = require("rtu.dev.boiler_rtu") local boilerv_rtu = require("rtu.dev.boilerv_rtu") local energymachine_rtu = require("rtu.dev.energymachine_rtu") @@ -24,12 +22,11 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts -local psleep = util.psleep - local MAIN_CLOCK = 2 -- (2Hz, 40 ticks) local COMMS_SLEEP = 150 -- (150ms, 3 ticks) -- main thread +---@param smem rtu_shared_memory threads.thread__main = function (smem) -- execute thread local exec = function () @@ -42,7 +39,7 @@ threads.thread__main = function (smem) local rtu_state = smem.rtu_state local rtu_dev = smem.rtu_dev local rtu_comms = smem.rtu_sys.rtu_comms - local conn_watchdog = smem.rtu_sys.conn_watchdog ---@type watchdog + local conn_watchdog = smem.rtu_sys.conn_watchdog local units = smem.rtu_sys.units -- start clock @@ -116,7 +113,7 @@ threads.thread__main = function (smem) else -- relink lost peripheral to correct unit entry for i = 1, #units do - local unit = units[i] + local unit = units[i] ---@type rtu_unit_registry_entry -- find disconnected device to reconnect if unit.name == param1 then @@ -137,7 +134,7 @@ threads.thread__main = function (smem) unit.rtu = imatrix_rtu.new(device) end - unit.modbus_io = modbus.new(unit.rtu) + unit.modbus_io = modbus.new(unit.rtu, true) println_ts("reconnected the " .. unit.type .. " on interface " .. unit.name) end @@ -159,6 +156,7 @@ threads.thread__main = function (smem) end -- communications handler thread +---@param smem rtu_shared_memory threads.thread__comms = function (smem) -- execute thread local exec = function () @@ -179,14 +177,16 @@ threads.thread__comms = function (smem) while comms_queue.ready() and not rtu_state.shutdown do local msg = comms_queue.pop() - if msg.qtype == mqueue.TYPE.COMMAND then - -- received a command - elseif msg.qtype == mqueue.TYPE.DATA then - -- received data - elseif msg.qtype == mqueue.TYPE.PACKET then - -- received a packet - -- handle the packet (rtu_state passed to allow setting link flag) - rtu_comms.handle_packet(msg.message, units, rtu_state) + if msg ~= nil then + if msg.qtype == mqueue.TYPE.COMMAND then + -- received a command + elseif msg.qtype == mqueue.TYPE.DATA then + -- received data + elseif msg.qtype == mqueue.TYPE.PACKET then + -- received a packet + -- handle the packet (rtu_state passed to allow setting link flag) + rtu_comms.handle_packet(msg.message, units, rtu_state) + end end -- quick yield @@ -209,6 +209,8 @@ threads.thread__comms = function (smem) end -- per-unit communications handler thread +---@param smem rtu_shared_memory +---@param unit rtu_unit_registry_entry threads.thread__unit_comms = function (smem, unit) -- execute thread local exec = function () @@ -227,16 +229,18 @@ threads.thread__unit_comms = function (smem, unit) while packet_queue.ready() and not rtu_state.shutdown do local msg = packet_queue.pop() - if msg.qtype == mqueue.TYPE.COMMAND then - -- received a command - elseif msg.qtype == mqueue.TYPE.DATA then - -- received data - elseif msg.qtype == mqueue.TYPE.PACKET then - -- received a packet - unit.modbus_busy = true - local return_code, reply = unit.modbus_io.handle_packet(msg.message) - rtu_comms.send_modbus(reply) - unit.modbus_busy = false + if msg ~= nil then + if msg.qtype == mqueue.TYPE.COMMAND then + -- received a command + elseif msg.qtype == mqueue.TYPE.DATA then + -- received data + elseif msg.qtype == mqueue.TYPE.PACKET then + -- received a packet + unit.modbus_busy = true + local _, reply = unit.modbus_io.handle_packet(msg.message) + rtu_comms.send_modbus(reply) + unit.modbus_busy = false + end end -- quick yield From 95c4d51e67f2b16239f902bb3387292c067c2678 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 11 May 2022 12:09:04 -0400 Subject: [PATCH 177/587] #47 RTU send should be table not any --- rtu/rtu.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index ab47b0a..9a353f9 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -179,7 +179,7 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) -- send a scada management packet ---@param msg_type SCADA_MGMT_TYPES - ---@param msg any + ---@param msg table local _send = function (msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() From 0d090fe9e2f5cca4fd169d76abac704885395bbe Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 11 May 2022 12:31:19 -0400 Subject: [PATCH 178/587] #47 supervisor luadoc, bugfixes --- supervisor/session/plc.lua | 183 +++++++++++++++++------------- supervisor/session/rtu.lua | 2 +- supervisor/session/svsessions.lua | 32 ++++-- supervisor/startup.lua | 5 +- supervisor/supervisor.lua | 35 ++++-- supervisor/unit.lua | 30 +++-- 6 files changed, 176 insertions(+), 111 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index f6a12ca..42bb51c 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -37,6 +37,10 @@ local PERIODICS = { } -- PLC supervisor session +---@param id integer +---@param for_reactor integer +---@param in_queue mqueue +---@param out_queue mqueue plc.new_session = function (id, for_reactor, in_queue, out_queue) local log_header = "plc_session(" .. id .. "): " @@ -78,6 +82,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) rps_reset = true }, -- session database + ---@class reactor_db sDB = { last_status_update = 0, control_state = false, @@ -85,6 +90,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) degraded = false, rps_tripped = false, rps_trip_cause = "ok", + ---@class rps_status rps_status = { dmg_crit = false, ex_hcool = false, @@ -94,45 +100,52 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) no_cool = false, timed_out = false }, + ---@class mek_status mek_status = { - heating_rate = 0, + heating_rate = 0.0, status = false, - burn_rate = 0, - act_burn_rate = 0, - temp = 0, - damage = 0, - boil_eff = 0, - env_loss = 0, + burn_rate = 0.0, + act_burn_rate = 0.0, + temp = 0.0, + damage = 0.0, + boil_eff = 0.0, + env_loss = 0.0, - fuel = 0, - fuel_need = 0, - fuel_fill = 0, - waste = 0, - waste_need = 0, - waste_fill = 0, + fuel = 0.0, + fuel_need = 0.0, + fuel_fill = 0.0, + waste = 0.0, + waste_need = 0.0, + waste_fill = 0.0, cool_type = "?", - cool_amnt = 0, - cool_need = 0, - cool_fill = 0, + cool_amnt = 0.0, + cool_need = 0.0, + cool_fill = 0.0, hcool_type = "?", - hcool_amnt = 0, - hcool_need = 0, - hcool_fill = 0 + hcool_amnt = 0.0, + hcool_need = 0.0, + hcool_fill = 0.0 }, + ---@class mek_struct mek_struct = { - heat_cap = 0, - fuel_asm = 0, - fuel_sa = 0, - fuel_cap = 0, - waste_cap = 0, - cool_cap = 0, - hcool_cap = 0, - max_burn = 0 + heat_cap = 0.0, + fuel_asm = 0.0, + fuel_sa = 0.0, + fuel_cap = 0.0, + waste_cap = 0.0, + cool_cap = 0.0, + hcool_cap = 0.0, + max_burn = 0.0 } } } + ---@class plc_session + local public = {} + + -- copy in the RPS status + ---@param rps_status table local _copy_rps_status = function (rps_status) self.sDB.rps_status.dmg_crit = rps_status[1] self.sDB.rps_status.ex_hcool = rps_status[2] @@ -143,6 +156,8 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) self.sDB.rps_status.timed_out = rps_status[7] end + -- copy in the reactor status + ---@param mek_data table local _copy_status = function (mek_data) -- copy status information self.sDB.mek_status.status = mek_data[1] @@ -174,6 +189,8 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end end + -- copy in the reactor structure + ---@param mek_data table local _copy_struct = function (mek_data) self.sDB.mek_struct.heat_cap = mek_data[1] self.sDB.mek_struct.fuel_asm = mek_data[2] @@ -186,6 +203,8 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- send an RPLC packet + ---@param msg_type RPLC_TYPES + ---@param msg table local _send = function (msg_type, msg) local s_pkt = comms.scada_packet() local r_pkt = comms.rplc_packet() @@ -198,6 +217,8 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- send a SCADA management packet + ---@param msg_type SCADA_MGMT_TYPES + ---@param msg table local _send_mgmt = function (msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() @@ -210,6 +231,8 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- get an ACK status + ---@param pkt rplc_frame + ---@return boolean|nil ack local _get_ack = function (pkt) if pkt.length == 1 then return pkt.data[1] @@ -220,6 +243,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- handle a packet + ---@param pkt rplc_frame local _handle_packet = function (pkt) -- check sequence number if self.r_seq_num == nil then @@ -378,13 +402,13 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) -- PUBLIC FUNCTIONS -- -- get the session ID - local get_id = function () return self.id end + public.get_id = function () return self.id end -- get the session database - local get_db = function () return self.sDB end + public.get_db = function () return self.sDB end -- get the reactor structure - local get_struct = function () + public.get_struct = function () if self.received_struct then return self.sDB.mek_struct else @@ -393,7 +417,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- get the reactor structure - local get_status = function () + public.get_status = function () if self.received_status_cache then return self.sDB.mek_status else @@ -402,12 +426,12 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- check if a timer matches this session's watchdog - local check_wd = function (timer) + public.check_wd = function (timer) return self.plc_conn_watchdog.is_timer(timer) end -- close the connection - local close = function () + public.close = function () self.plc_conn_watchdog.cancel() self.connected = false _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) @@ -416,7 +440,8 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- iterate the session - local iterate = function () + ---@return boolean connected + public.iterate = function () if self.connected then ------------------ -- handle queue -- @@ -428,45 +453,47 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) -- get a new message to process local message = self.in_q.pop() - if message.qtype == mqueue.TYPE.PACKET then - -- handle a packet - _handle_packet(message.message) - elseif message.qtype == mqueue.TYPE.COMMAND then - -- handle instruction - local cmd = message.message - if cmd == PLC_S_CMDS.ENABLE then - -- enable reactor - self.acks.enable = false - self.retry_times.enable_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.RPS_ENABLE, {}) - elseif cmd == PLC_S_CMDS.SCRAM then - -- SCRAM reactor - self.acks.scram = false - self.retry_times.scram_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.RPS_SCRAM, {}) - elseif cmd == PLC_S_CMDS.RPS_RESET then - -- reset RPS - self.acks.rps_reset = false - self.retry_times.rps_reset_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.RPS_RESET, {}) - end - elseif message.qtype == mqueue.TYPE.DATA then - -- instruction with body - local cmd = message.message - if cmd.key == PLC_S_DATA.BURN_RATE then - -- update burn rate - self.commanded_burn_rate = cmd.val - self.ramping_rate = false - self.acks.burn_rate = false - self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) - elseif cmd.key == PLC_S_DATA.RAMP_BURN_RATE then - -- ramp to burn rate - self.commanded_burn_rate = cmd.val - self.ramping_rate = true - self.acks.burn_rate = false - self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + if message ~= nil then + if message.qtype == mqueue.TYPE.PACKET then + -- handle a packet + _handle_packet(message.message) + elseif message.qtype == mqueue.TYPE.COMMAND then + -- handle instruction + local cmd = message.message + if cmd == PLC_S_CMDS.ENABLE then + -- enable reactor + self.acks.enable = false + self.retry_times.enable_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.RPS_ENABLE, {}) + elseif cmd == PLC_S_CMDS.SCRAM then + -- SCRAM reactor + self.acks.scram = false + self.retry_times.scram_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.RPS_SCRAM, {}) + elseif cmd == PLC_S_CMDS.RPS_RESET then + -- reset RPS + self.acks.rps_reset = false + self.retry_times.rps_reset_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.RPS_RESET, {}) + end + elseif message.qtype == mqueue.TYPE.DATA then + -- instruction with body + local cmd = message.message + if cmd.key == PLC_S_DATA.BURN_RATE then + -- update burn rate + self.commanded_burn_rate = cmd.val + self.ramping_rate = false + self.acks.burn_rate = false + self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + elseif cmd.key == PLC_S_DATA.RAMP_BURN_RATE then + -- ramp to burn rate + self.commanded_burn_rate = cmd.val + self.ramping_rate = true + self.acks.burn_rate = false + self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + end end end @@ -567,15 +594,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) return self.connected end - return { - get_id = get_id, - get_db = get_db, - get_struct = get_struct, - get_status = get_status, - check_wd = check_wd, - close = close, - iterate = iterate - } + return public end return plc diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index a360be9..eed5cdc 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -100,7 +100,7 @@ rtu.new_session = function (id, in_queue, out_queue) elseif pkt.type == SCADA_MGMT_TYPES.RTU_ADVERT then -- RTU unit advertisement for i = 1, pkt.length do - local unit = pkt.data[i] + local unit = pkt.data[i] ---@type rtu_advertisement end else log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index e596985..37fc89e 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -31,16 +31,17 @@ local self = { -- PRIVATE FUNCTIONS -- -- iterate all the given sessions +---@param sessions table local function _iterate(sessions) for i = 1, #sessions do - local session = sessions[i] + local session = sessions[i] ---@type plc_session_struct if session.open then local ok = session.instance.iterate() if ok then -- send packets in out queue while session.out_queue.ready() do local msg = session.out_queue.pop() - if msg.qtype == mqueue.TYPE.PACKET then + if msg ~= nil and msg.qtype == mqueue.TYPE.PACKET then self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) end end @@ -52,6 +53,7 @@ local function _iterate(sessions) end -- cleanly close a session +---@param session plc_session_struct local function _shutdown(session) session.open = false session.instance.close() @@ -59,7 +61,7 @@ local function _shutdown(session) -- send packets in out queue (namely the close packet) while session.out_queue.ready() do local msg = session.out_queue.pop() - if msg.qtype == mqueue.TYPE.PACKET then + if msg ~= nil and msg.qtype == mqueue.TYPE.PACKET then self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) end end @@ -68,9 +70,10 @@ local function _shutdown(session) end -- close connections +---@param sessions table local function _close(sessions) for i = 1, #sessions do - local session = sessions[i] + local session = sessions[i] ---@type plc_session_struct if session.open then _shutdown(session) end @@ -78,9 +81,11 @@ local function _close(sessions) end -- check if a watchdog timer event matches that of one of the provided sessions +---@param sessions table +---@param timer_event number local function _check_watchdogs(sessions, timer_event) for i = 1, #sessions do - local session = sessions[i] + local session = sessions[i] ---@type plc_session_struct if session.open then local triggered = session.instance.check_wd(timer_event) if triggered then @@ -92,10 +97,11 @@ local function _check_watchdogs(sessions, timer_event) end -- delete any closed sessions +---@param sessions table local function _free_closed(sessions) local move_to = 1 for i = 1, #sessions do - local session = sessions[i] + local session = sessions[i] ---@type plc_session_struct if session ~= nil then if session.open then if sessions[move_to] == nil then @@ -113,11 +119,15 @@ end -- PUBLIC FUNCTIONS -- +-- link the modem +---@param modem table svsessions.link_modem = function (modem) self.modem = modem end -- find a session by the remote port +---@param remote_port integer +---@return plc_session_struct|nil svsessions.find_session = function (remote_port) -- check RTU sessions for i = 1, #self.rtu_sessions do @@ -144,6 +154,8 @@ svsessions.find_session = function (remote_port) end -- get a session by reactor ID +---@param reactor integer +---@return plc_session_struct session svsessions.get_reactor_session = function (reactor) local session = nil @@ -157,8 +169,13 @@ svsessions.get_reactor_session = function (reactor) end -- establish a new PLC session +---@param local_port integer +---@param remote_port integer +---@param for_reactor integer +---@return integer|false session_id svsessions.establish_plc_session = function (local_port, remote_port, for_reactor) - if svsessions.get_reactor_session(for_reactor) == nil then + if svsessions.get_reactor_session(for_reactor) == nil then + ---@class plc_session_struct local plc_s = { open = true, reactor = for_reactor, @@ -185,6 +202,7 @@ svsessions.establish_plc_session = function (local_port, remote_port, for_reacto end -- attempt to identify which session's watchdog timer fired +---@param timer_event number svsessions.check_all_watchdogs = function (timer_event) -- check RTU session watchdogs _check_watchdogs(self.rtu_sessions, timer_event) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index be9549e..5923887 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -6,15 +6,12 @@ local log = require("scada-common.log") local ppm = require("scada-common.ppm") local util = require("scada-common.util") -local coordinator = require("supervisor.session.coordinator") -local plc = require("supervisor.session.plc") -local rtu = require("supervisor.session.rtu") local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "alpha-v0.3.4" +local SUPERVISOR_VERSION = "alpha-v0.3.5" local print = util.print local println = util.println diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index f9df71e..a534882 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -20,6 +20,10 @@ local print_ts = util.print_ts local println_ts = util.println_ts -- supervisory controller communications +---@param num_reactors integer +---@param modem table +---@param dev_listen integer +---@param coord_listen integer supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) local self = { ln_seq_num = 0, @@ -30,6 +34,9 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) reactor_struct_cache = nil } + ---@class superv_comms + local public = {} + -- PRIVATE FUNCTIONS -- -- open all channels @@ -50,6 +57,8 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) svsessions.link_modem(self.modem) -- send PLC link request responses + ---@param dest integer + ---@param msg table local _send_plc_linking = function (dest, msg) local s_pkt = comms.scada_packet() local r_pkt = comms.rplc_packet() @@ -64,14 +73,22 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) -- PUBLIC FUNCTIONS -- -- reconnect a newly connected modem - local reconnect_modem = function (modem) + ---@param modem table +---@diagnostic disable-next-line: redefined-local + public.reconnect_modem = function (modem) self.modem = modem svsessions.link_modem(self.modem) _open_channels() end -- parse a packet - local parse_packet = function(side, sender, reply_to, message, distance) + ---@param side string + ---@param sender integer + ---@param reply_to integer + ---@param message any + ---@param distance integer + ---@return modbus_frame|rplc_frame|mgmt_frame|coord_frame|nil packet + public.parse_packet = function(side, sender, reply_to, message, distance) local pkt = nil local s_pkt = comms.scada_packet() @@ -111,7 +128,9 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) return pkt end - local handle_packet = function(packet) + -- handle a packet + ---@param packet modbus_frame|rplc_frame|mgmt_frame|coord_frame + public.handle_packet = function(packet) if packet ~= nil then local l_port = packet.scada_frame.local_port() local r_port = packet.scada_frame.remote_port() @@ -126,7 +145,7 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) -- MODBUS response elseif protocol == PROTOCOLS.RPLC then -- reactor PLC packet - if session then + if session ~= nil then if packet.type == RPLC_TYPES.LINK_REQ then -- new device on this port? that's a collision log.debug("PLC_LNK: request from existing connection received on " .. r_port .. ", responding with collision") @@ -162,7 +181,7 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) end elseif protocol == PROTOCOLS.SCADA_MGMT then -- SCADA management packet - if session then + if session ~= nil then -- pass the packet onto the session handler session.in_queue.push_packet(packet) end @@ -184,11 +203,7 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) end end - return { - reconnect_modem = reconnect_modem, - parse_packet = parse_packet, - handle_packet = handle_packet - } + return public end return supervisor diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 95aa5f1..a246ebc 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -1,8 +1,8 @@ local unit = {} +-- create a new reactor unit +---@param for_reactor integer unit.new = function (for_reactor) - local public = {} - local self = { r_id = for_reactor, plc_s = nil, @@ -11,6 +11,7 @@ unit.new = function (for_reactor) energy_storage = {}, redstone = {}, db = { + ---@class annunciator annunciator = { -- RPS -- reactor @@ -37,18 +38,36 @@ unit.new = function (for_reactor) } } + ---@class reactor_unit + local public = {} + + -- PRIVATE FUNCTIONS -- + + -- update the annunciator + local _update_annunciator = function () + self.db.annunciator.PLCOnline = (self.plc_s ~= nil) and (self.plc_s.open) + self.db.annunciator.ReactorTrip = false + end + + -- PUBLIC FUNCTIONS -- + + -- link the PLC + ---@param plc_session plc_session_struct public.link_plc_session = function (plc_session) self.plc_s = plc_session end + -- link a turbine RTU public.add_turbine = function (turbine) table.insert(self.turbines, turbine) end + -- link a boiler RTU public.add_boiler = function (boiler) table.insert(self.boilers, boiler) end + -- link a redstone RTU capability public.add_redstone = function (field, accessor) -- ensure field exists if self.redstone[field] == nil then @@ -59,11 +78,7 @@ unit.new = function (for_reactor) table.insert(self.redstone[field], accessor) end - local _update_annunciator = function () - self.db.annunciator.PLCOnline = (self.plc_s ~= nil) and (self.plc_s.open) - self.db.annunciator.ReactorTrip = false - end - + -- update (iterate) this session public.update = function () -- unlink PLC if session was closed if not self.plc_s.open then @@ -74,6 +89,7 @@ unit.new = function (for_reactor) _update_annunciator() end + -- get the annunciator status public.get_annunciator = function () return self.db.annunciator end return public From b985362757a9ced0a5249882535857d05bc59f7c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 11 May 2022 13:02:21 -0400 Subject: [PATCH 179/587] #8 RTU session for boiler, added transaction controller --- supervisor/session/rtu.lua | 3 + supervisor/session/rtu/boiler.lua | 210 +++++++++++++++++++++++++++++ supervisor/session/rtu/txnctrl.lua | 95 +++++++++++++ 3 files changed, 308 insertions(+) create mode 100644 supervisor/session/rtu/boiler.lua create mode 100644 supervisor/session/rtu/txnctrl.lua diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index eed5cdc..bb86b05 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -3,6 +3,9 @@ local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") local util = require("scada-common.util") +-- supervisor rtu sessions (svrs) +local svrs_boiler = require("supervisor.session.rtu.boiler") + local rtu = {} local PROTOCOLS = comms.PROTOCOLS diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua new file mode 100644 index 0000000..83ecd8b --- /dev/null +++ b/supervisor/session/rtu/boiler.lua @@ -0,0 +1,210 @@ +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") +local util = require("scada-common.util") + +local txnctrl = require("supervisor.session.rtu.txnctrl") + +local boiler = {} + +local PROTOCOLS = comms.PROTOCOLS +local MODBUS_FCODE = types.MODBUS_FCODE + +local rtu_t = types.rtu_t + +local TXN_TYPES = { + BUILD = 0, + STATE = 1, + TANKS = 2 +} + +local PERIODICS = { + BUILD = 1000, + STATE = 500, + TANKS = 1000 +} + +-- create a new boiler rtu session runner +---@param advert rtu_advertisement +---@param out_queue mqueue +boiler.new = function (advert, out_queue) + -- type check + if advert.type ~= rtu_t.boiler then + log.error("attempt to instantiate boiler RTU for non boiler type '" .. advert.type .. "'. this is a bug.") + return nil + end + + local log_tag = "session.rtu.boiler(" .. advert.index .. "): " + + local self = { + uid = advert.index, + reactor = advert.reactor, + out_q = out_queue, + transaction_controller = txnctrl.new(), + has_build = false, + periodics = { + next_build_req = 0, + next_state_req = 0, + next_tanks_req = 0, + }, + ---@class boiler_session_db + db = { + build = { + boil_cap = 0.0, + steam_cap = 0, + water_cap = 0, + hcoolant_cap = 0, + ccoolant_cap = 0, + superheaters = 0, + max_boil_rate = 0.0 + }, + state = { + temperature = 0.0, + boil_rate = 0.0 + }, + tanks = { + steam = 0, + steam_need = 0, + steam_fill = 0.0, + water = 0, + water_need = 0, + water_fill = 0.0, + hcool = 0, + hcool_need = 0, + hcool_fill = 0.0, + ccool = 0, + ccool_need = 0, + ccool_fill = 0.0 + } + } + } + + ---@class rtu_session__boiler + local public = {} + + -- PRIVATE FUNCTIONS -- + + -- query the build of the device + local _request_build = function () + local m_pkt = comms.modbus_packet() + local txn_id = self.transaction_controller.create(TXN_TYPES.BUILD) + + -- read input registers 1 through 7 (start = 1, count = 7) + m_pkt.make(txn_id, self.uid, MODBUS_FCODE.READ_INPUT_REGS, { 1, 7 }) + + self.out_q.push_packet(m_pkt) + end + + -- query the state of the device + local _request_state = function () + local m_pkt = comms.modbus_packet() + local txn_id = self.transaction_controller.create(TXN_TYPES.STATE) + + -- read input registers 8 through 9 (start = 8, count = 2) + m_pkt.make(txn_id, self.uid, MODBUS_FCODE.READ_INPUT_REGS, { 8, 2 }) + + self.out_q.push_packet(m_pkt) + end + + -- query the tanks of the device + local _request_tanks = function () + local m_pkt = comms.modbus_packet() + local txn_id = self.transaction_controller.create(TXN_TYPES.TANKS) + + -- read input registers 10 through 21 (start = 10, count = 12) + m_pkt.make(txn_id, self.uid, MODBUS_FCODE.READ_INPUT_REGS, { 10, 12 }) + + self.out_q.push_packet(m_pkt) + end + + -- PUBLIC FUNCTIONS -- + + -- handle a packet + ---@param m_pkt modbus_frame + public.handle_packet = function (m_pkt) + local success = false + + if m_pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then + if m_pkt.unit_id == self.uid then + local txn_type = self.transaction_controller.resolve(m_pkt.txn_id) + if txn_type == TXN_TYPES.BUILD then + -- build response + if m_pkt.length == 7 then + self.db.build.boil_cap = m_pkt.data[1] + self.db.build.steam_cap = m_pkt.data[2] + self.db.build.water_cap = m_pkt.data[3] + self.db.build.hcoolant_cap = m_pkt.data[4] + self.db.build.ccoolant_cap = m_pkt.data[5] + self.db.build.superheaters = m_pkt.data[6] + self.db.build.max_boil_rate = m_pkt.data[7] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (boiler.build)") + end + elseif txn_type == TXN_TYPES.STATE then + -- state response + if m_pkt.length == 2 then + self.db.state.temperature = m_pkt.data[1] + self.db.state.boil_rate = m_pkt.data[2] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (boiler.state)") + end + elseif txn_type == TXN_TYPES.TANKS then + -- tanks response + if m_pkt.length == 12 then + self.db.tanks.steam = m_pkt.data[1] + self.db.tanks.steam_need = m_pkt.data[2] + self.db.tanks.steam_fill = m_pkt.data[3] + self.db.tanks.water = m_pkt.data[4] + self.db.tanks.water_need = m_pkt.data[5] + self.db.tanks.water_fill = m_pkt.data[6] + self.db.tanks.hcool = m_pkt.data[7] + self.db.tanks.hcool_need = m_pkt.data[8] + self.db.tanks.hcool_fill = m_pkt.data[9] + self.db.tanks.ccool = m_pkt.data[10] + self.db.tanks.ccool_need = m_pkt.data[11] + self.db.tanks.ccool_fill = m_pkt.data[12] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (boiler.tanks)") + end + elseif txn_type == nil then + log.error(log_tag .. "unknown transaction reply") + else + log.error(log_tag .. "unknown transaction type " .. txn_type) + end + else + log.error(log_tag .. "wrong unit ID: " .. m_pkt.unit_id, true) + end + else + log.error(log_tag .. "illegal packet type " .. m_pkt.scada_frame.protocol(), true) + end + + return success + end + + public.get_uid = function () return self.uid end + public.get_reactor = function () return self.reactor end + public.get_db = function () return self.db end + + -- update this runner + ---@param time_now integer milliseconds + public.update = function (time_now) + if not self.has_build and self.next_build_req <= time_now then + _request_build() + self.next_build_req = time_now + PERIODICS.BUILD + end + + if self.next_state_req <= time_now then + _request_state() + self.next_state_req = time_now + PERIODICS.STATE + end + + if self.next_tanks_req <= time_now then + _request_tanks() + self.next_tanks_req = time_now + PERIODICS.TANKS + end + end + + return public +end + +return boiler diff --git a/supervisor/session/rtu/txnctrl.lua b/supervisor/session/rtu/txnctrl.lua new file mode 100644 index 0000000..3d74484 --- /dev/null +++ b/supervisor/session/rtu/txnctrl.lua @@ -0,0 +1,95 @@ +-- +-- MODBUS Transaction Controller +-- + +local util = require("scada-common.util") + +local txnctrl = {} + +local TIMEOUT = 3000 -- 3000ms max wait + +-- create a new transaction controller +txnctrl.new = function () + local self = { + list = {}, + next_id = 0 + } + + ---@class transaction_controller + local public = {} + + local insert = table.insert + + -- get the length of the transaction list + public.length = function () + return #self.list + end + + -- check if there are no active transactions + public.empty = function () + return #self.list == 0 + end + + -- create a new transaction of the given type + ---@param txn_type integer + ---@return integer txn_id + public.create = function (txn_type) + local txn_id = self.next_id + + insert(self.list, { + txn_id = txn_id, + txn_type = txn_type, + expiry = util.time() + TIMEOUT + }) + + self.next_id = self.next_id + 1 + + return txn_id + end + + -- mark a transaction as resolved to get its transaction type + ---@param txn_id integer + ---@return integer txn_type + public.resolve = function (txn_id) + local txn_type = nil + + for i = 1, public.length() do + if self.list[i].txn_id == txn_id then + txn_type = self.list[i].txn_type + self.list[i] = nil + end + end + + return txn_type + end + + -- close timed-out transactions + public.cleanup = function () + local now = util.time() + + local move_to = 1 + for i = 1, public.length() do + local txn = self.list[i] + if txn ~= nil then + if txn.expiry <= now then + self.list[i] = nil + else + if self.list[move_to] == nil then + self.list[move_to] = txn + self.list[i] = nil + end + move_to = move_to + 1 + end + end + end + end + + -- clear the transaction list + public.clear = function () + self.list = {} + end + + return public +end + +return txnctrl From 9695e946081e71d6bff557d00ff62e167051d476 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 11 May 2022 13:05:20 -0400 Subject: [PATCH 180/587] plc session terminology change, changed number/integer types --- supervisor/session/plc.lua | 44 +++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 42bb51c..c36dbda 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -112,30 +112,30 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) boil_eff = 0.0, env_loss = 0.0, - fuel = 0.0, - fuel_need = 0.0, + fuel = 0, + fuel_need = 0, fuel_fill = 0.0, - waste = 0.0, - waste_need = 0.0, + waste = 0, + waste_need = 0, waste_fill = 0.0, - cool_type = "?", - cool_amnt = 0.0, - cool_need = 0.0, - cool_fill = 0.0, + ccool_type = "?", + ccool_amnt = 0, + ccool_need = 0, + ccool_fill = 0.0, hcool_type = "?", - hcool_amnt = 0.0, - hcool_need = 0.0, + hcool_amnt = 0, + hcool_need = 0, hcool_fill = 0.0 }, ---@class mek_struct mek_struct = { - heat_cap = 0.0, - fuel_asm = 0.0, - fuel_sa = 0.0, - fuel_cap = 0.0, - waste_cap = 0.0, - cool_cap = 0.0, - hcool_cap = 0.0, + heat_cap = 0, + fuel_asm = 0, + fuel_sa = 0, + fuel_cap = 0, + waste_cap = 0, + ccool_cap = 0, + hcool_cap = 0, max_burn = 0.0 } } @@ -173,9 +173,9 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) self.sDB.mek_status.fuel_fill = mek_data[9] self.sDB.mek_status.waste = mek_data[10] self.sDB.mek_status.waste_fill = mek_data[11] - self.sDB.mek_status.cool_type = mek_data[12] - self.sDB.mek_status.cool_amnt = mek_data[13] - self.sDB.mek_status.cool_fill = mek_data[14] + self.sDB.mek_status.ccool_type = mek_data[12] + self.sDB.mek_status.ccool_amnt = mek_data[13] + self.sDB.mek_status.ccool_fill = mek_data[14] self.sDB.mek_status.hcool_type = mek_data[15] self.sDB.mek_status.hcool_amnt = mek_data[16] self.sDB.mek_status.hcool_fill = mek_data[17] @@ -184,7 +184,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) if self.received_struct then self.sDB.mek_status.fuel_need = self.sDB.mek_struct.fuel_cap - self.sDB.mek_status.fuel_fill self.sDB.mek_status.waste_need = self.sDB.mek_struct.waste_cap - self.sDB.mek_status.waste_fill - self.sDB.mek_status.cool_need = self.sDB.mek_struct.cool_cap - self.sDB.mek_status.cool_fill + self.sDB.mek_status.cool_need = self.sDB.mek_struct.ccool_cap - self.sDB.mek_status.ccool_fill self.sDB.mek_status.hcool_need = self.sDB.mek_struct.hcool_cap - self.sDB.mek_status.hcool_fill end end @@ -197,7 +197,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) self.sDB.mek_struct.fuel_sa = mek_data[3] self.sDB.mek_struct.fuel_cap = mek_data[4] self.sDB.mek_struct.waste_cap = mek_data[5] - self.sDB.mek_struct.cool_cap = mek_data[6] + self.sDB.mek_struct.ccool_cap = mek_data[6] self.sDB.mek_struct.hcool_cap = mek_data[7] self.sDB.mek_struct.max_burn = mek_data[8] end From 969abca95dfbf97023ec6ebbca7bdc30c56117cd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 12 May 2022 15:36:27 -0400 Subject: [PATCH 181/587] RTU device changes, bugfixes, docs --- rtu/dev/boiler_rtu.lua | 12 +++------ rtu/dev/boilerv_rtu.lua | 18 ++++++-------- rtu/dev/energymachine_rtu.lua | 16 ++++++------ rtu/dev/imatrix_rtu.lua | 17 ++++++------- rtu/dev/redstone_rtu.lua | 47 ++++++++++++++++++++++++----------- rtu/dev/turbine_rtu.lua | 12 +++------ rtu/dev/turbinev_rtu.lua | 19 ++++++-------- rtu/modbus.lua | 2 +- rtu/rtu.lua | 29 ++++++++++++++------- rtu/startup.lua | 10 ++++---- 10 files changed, 98 insertions(+), 84 deletions(-) diff --git a/rtu/dev/boiler_rtu.lua b/rtu/dev/boiler_rtu.lua index 322c511..26b5ebc 100644 --- a/rtu/dev/boiler_rtu.lua +++ b/rtu/dev/boiler_rtu.lua @@ -1,17 +1,15 @@ -local rtu = require("rtu") +local rtu = require("rtu.rtu") local boiler_rtu = {} +-- create new boiler (mek 10.0) device +---@param boiler table boiler_rtu.new = function (boiler) local self = { rtu = rtu.init_unit(), boiler = boiler } - local rtu_interface = function () - return self.rtu - end - -- discrete inputs -- -- none @@ -47,9 +45,7 @@ boiler_rtu.new = function (boiler) -- holding registers -- -- none - return { - rtu_interface = rtu_interface - } + return self.rtu.interface() end return boiler_rtu diff --git a/rtu/dev/boilerv_rtu.lua b/rtu/dev/boilerv_rtu.lua index a609588..fca1f09 100644 --- a/rtu/dev/boilerv_rtu.lua +++ b/rtu/dev/boilerv_rtu.lua @@ -1,29 +1,28 @@ -local rtu = require("rtu") +local rtu = require("rtu.rtu") local boilerv_rtu = {} +-- create new boiler (mek 10.1+) device +---@param boiler table boilerv_rtu.new = function (boiler) local self = { rtu = rtu.init_unit(), boiler = boiler } - local rtu_interface = function () - return self.rtu - end - -- discrete inputs -- - -- none + self.rtu.connect_di(self.boiler.isFormed) -- coils -- -- none -- input registers -- -- multiblock properties - self.rtu.connect_input_reg(self.boiler.isFormed) self.rtu.connect_input_reg(self.boiler.getLength) self.rtu.connect_input_reg(self.boiler.getWidth) self.rtu.connect_input_reg(self.boiler.getHeight) + self.rtu.connect_input_reg(self.boiler.getMinPos) + self.rtu.connect_input_reg(self.boiler.getMaxPos) -- build properties self.rtu.connect_input_reg(self.boiler.getBoilCapacity) self.rtu.connect_input_reg(self.boiler.getSteamCapacity) @@ -32,6 +31,7 @@ boilerv_rtu.new = function (boiler) self.rtu.connect_input_reg(self.boiler.getCooledCoolantCapacity) self.rtu.connect_input_reg(self.boiler.getSuperheaters) self.rtu.connect_input_reg(self.boiler.getMaxBoilRate) + self.rtu.connect_input_reg(self.boiler.getEnvironmentalLoss) -- current state self.rtu.connect_input_reg(self.boiler.getTemperature) self.rtu.connect_input_reg(self.boiler.getBoilRate) @@ -52,9 +52,7 @@ boilerv_rtu.new = function (boiler) -- holding registers -- -- none - return { - rtu_interface = rtu_interface - } + return self.rtu.interface() end return boilerv_rtu diff --git a/rtu/dev/energymachine_rtu.lua b/rtu/dev/energymachine_rtu.lua index d2aee3f..e0e05af 100644 --- a/rtu/dev/energymachine_rtu.lua +++ b/rtu/dev/energymachine_rtu.lua @@ -1,16 +1,20 @@ -local rtu = require("rtu") +local rtu = require("rtu.rtu") local energymachine_rtu = {} +-- create new energy machine device +---@param machine table energymachine_rtu.new = function (machine) local self = { rtu = rtu.init_unit(), machine = machine } - local rtu_interface = function () - return self.rtu - end + ---@class rtu_device + local public = {} + + -- get the RTU interface + public.rtu_interface = function () return self.rtu end -- discrete inputs -- -- none @@ -29,9 +33,7 @@ energymachine_rtu.new = function (machine) -- holding registers -- -- none - return { - rtu_interface = rtu_interface - } + return public end return energymachine_rtu diff --git a/rtu/dev/imatrix_rtu.lua b/rtu/dev/imatrix_rtu.lua index 12fd942..56498e5 100644 --- a/rtu/dev/imatrix_rtu.lua +++ b/rtu/dev/imatrix_rtu.lua @@ -1,29 +1,28 @@ -local rtu = require("rtu") +local rtu = require("rtu.rtu") local imatrix_rtu = {} +-- create new induction matrix (mek 10.1+) device +---@param imatrix table imatrix_rtu.new = function (imatrix) local self = { rtu = rtu.init_unit(), imatrix = imatrix } - local rtu_interface = function () - return self.rtu - end - -- discrete inputs -- - -- none + self.rtu.connect_di(self.boiler.isFormed) -- coils -- -- none -- input registers -- -- multiblock properties - self.rtu.connect_input_reg(self.boiler.isFormed) self.rtu.connect_input_reg(self.boiler.getLength) self.rtu.connect_input_reg(self.boiler.getWidth) self.rtu.connect_input_reg(self.boiler.getHeight) + self.rtu.connect_input_reg(self.boiler.getMinPos) + self.rtu.connect_input_reg(self.boiler.getMaxPos) -- build properties self.rtu.connect_input_reg(self.imatrix.getMaxEnergy) self.rtu.connect_input_reg(self.imatrix.getTransferCap) @@ -40,9 +39,7 @@ imatrix_rtu.new = function (imatrix) -- holding registers -- -- none - return { - rtu_interface = rtu_interface - } + return self.rtu.interface() end return imatrix_rtu diff --git a/rtu/dev/redstone_rtu.lua b/rtu/dev/redstone_rtu.lua index 9683f57..2763e98 100644 --- a/rtu/dev/redstone_rtu.lua +++ b/rtu/dev/redstone_rtu.lua @@ -1,4 +1,4 @@ -local rtu = require("rtu") +local rtu = require("rtu.rtu") local rsio = require("scada-common.rsio") local redstone_rtu = {} @@ -6,16 +6,31 @@ local redstone_rtu = {} local digital_read = rsio.digital_read local digital_is_active = rsio.digital_is_active +-- create new redstone device redstone_rtu.new = function () local self = { rtu = rtu.init_unit() } - local rtu_interface = function () - return self.rtu - end + -- get RTU interface + local interface = self.rtu.interface() - local link_di = function (channel, side, color) + ---@class rtu_rs_device + --- extends rtu_device; fields added manually to please Lua diagnostics + local public = { + io_count = interface.io_count, + read_coil = interface.read_coil, + read_di = interface.read_di, + read_holding_reg = interface.read_holding_reg, + read_input_reg = interface.read_input_reg, + write_coil = interface.write_coil, + write_holding_reg = interface.write_holding_reg + } + + -- link digital input + ---@param side string + ---@param color integer + public.link_di = function (side, color) local f_read = nil if color then @@ -31,7 +46,11 @@ redstone_rtu.new = function () self.rtu.connect_di(f_read) end - local link_do = function (channel, side, color) + -- link digital output + ---@param channel RS_IO + ---@param side string + ---@param color integer + public.link_do = function (channel, side, color) local f_read = nil local f_write = nil @@ -65,7 +84,9 @@ redstone_rtu.new = function () self.rtu.connect_coil(f_read, f_write) end - local link_ai = function (channel, side) + -- link analog input + ---@param side string + public.link_ai = function (side) self.rtu.connect_input_reg( function () return rs.getAnalogInput(side) @@ -73,7 +94,9 @@ redstone_rtu.new = function () ) end - local link_ao = function (channel, side) + -- link analog output + ---@param side string + public.link_ao = function (side) self.rtu.connect_holding_reg( function () return rs.getAnalogOutput(side) @@ -84,13 +107,7 @@ redstone_rtu.new = function () ) end - return { - rtu_interface = rtu_interface, - link_di = link_di, - link_do = link_do, - link_ai = link_ai, - link_ao = link_ao - } + return public end return redstone_rtu diff --git a/rtu/dev/turbine_rtu.lua b/rtu/dev/turbine_rtu.lua index 1f1827f..5ff71a7 100644 --- a/rtu/dev/turbine_rtu.lua +++ b/rtu/dev/turbine_rtu.lua @@ -1,17 +1,15 @@ -local rtu = require("rtu") +local rtu = require("rtu.rtu") local turbine_rtu = {} +-- create new turbine (mek 10.0) device +---@param turbine table turbine_rtu.new = function (turbine) local self = { rtu = rtu.init_unit(), turbine = turbine } - local rtu_interface = function () - return self.rtu - end - -- discrete inputs -- -- none @@ -42,9 +40,7 @@ turbine_rtu.new = function (turbine) -- holding registers -- -- none - return { - rtu_interface = rtu_interface - } + return self.rtu.interface() end return turbine_rtu diff --git a/rtu/dev/turbinev_rtu.lua b/rtu/dev/turbinev_rtu.lua index 2be532b..aa7a108 100644 --- a/rtu/dev/turbinev_rtu.lua +++ b/rtu/dev/turbinev_rtu.lua @@ -1,19 +1,17 @@ -local rtu = require("rtu") +local rtu = require("rtu.rtu") local turbinev_rtu = {} +-- create new turbine (mek 10.1+) device +---@param turbine table turbinev_rtu.new = function (turbine) local self = { rtu = rtu.init_unit(), turbine = turbine } - local rtu_interface = function () - return self.rtu - end - -- discrete inputs -- - -- none + self.rtu.connect_di(self.boiler.isFormed) -- coils -- self.rtu.connect_coil(function () self.turbine.incrementDumpingMode() end, function () end) @@ -21,10 +19,11 @@ turbinev_rtu.new = function (turbine) -- input registers -- -- multiblock properties - self.rtu.connect_input_reg(self.boiler.isFormed) self.rtu.connect_input_reg(self.boiler.getLength) self.rtu.connect_input_reg(self.boiler.getWidth) self.rtu.connect_input_reg(self.boiler.getHeight) + self.rtu.connect_input_reg(self.boiler.getMinPos) + self.rtu.connect_input_reg(self.boiler.getMaxPos) -- build properties self.rtu.connect_input_reg(self.turbine.getBlades) self.rtu.connect_input_reg(self.turbine.getCoils) @@ -50,11 +49,9 @@ turbinev_rtu.new = function (turbine) self.rtu.connect_input_reg(self.turbine.getEnergyFilledPercentage) -- holding registers -- - self.rtu.conenct_holding_reg(self.turbine.setDumpingMode, self.turbine.getDumpingMode) + self.rtu.connect_holding_reg(self.turbine.setDumpingMode, self.turbine.getDumpingMode) - return { - rtu_interface = rtu_interface - } + return self.rtu.interface() end return turbinev_rtu diff --git a/rtu/modbus.lua b/rtu/modbus.lua index 0e5e16c..efc0c84 100644 --- a/rtu/modbus.lua +++ b/rtu/modbus.lua @@ -7,7 +7,7 @@ local MODBUS_FCODE = types.MODBUS_FCODE local MODBUS_EXCODE = types.MODBUS_EXCODE -- new modbus comms handler object ----@param rtu_dev rtu RTU device +---@param rtu_dev rtu_device|rtu_rs_device RTU device ---@param use_parallel_read boolean whether or not to use parallel calls when reading modbus.new = function (rtu_dev, use_parallel_read) local self = { diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 9a353f9..cafb645 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -29,16 +29,20 @@ rtu.init_unit = function () io_count_cache = { 0, 0, 0, 0 } } - ---@class rtu - local public = {} - local insert = table.insert + ---@class rtu_device + local public = {} + + ---@class rtu + local protected = {} + + -- refresh IO count local _count_io = function () self.io_count_cache = { #self.discrete_inputs, #self.coils, #self.input_regs, #self.holding_regs } end - -- return IO counts + -- return IO count ---@return integer discrete_inputs, integer coils, integer input_regs, integer holding_regs public.io_count = function () return self.io_count_cache[0], self.io_count_cache[1], self.io_count_cache[2], self.io_count_cache[3] @@ -49,7 +53,7 @@ rtu.init_unit = function () -- connect discrete input ---@param f function ---@return integer count count of discrete inputs - public.connect_di = function (f) + protected.connect_di = function (f) insert(self.discrete_inputs, f) _count_io() return #self.discrete_inputs @@ -70,7 +74,7 @@ rtu.init_unit = function () ---@param f_read function ---@param f_write function ---@return integer count count of coils - public.connect_coil = function (f_read, f_write) + protected.connect_coil = function (f_read, f_write) insert(self.coils, { read = f_read, write = f_write }) _count_io() return #self.coils @@ -100,7 +104,7 @@ rtu.init_unit = function () -- connect input register ---@param f function ---@return integer count count of input registers - public.connect_input_reg = function (f) + protected.connect_input_reg = function (f) insert(self.input_regs, f) _count_io() return #self.input_regs @@ -121,7 +125,7 @@ rtu.init_unit = function () ---@param f_read function ---@param f_write function ---@return integer count count of holding registers - public.connect_holding_reg = function (f_read, f_write) + protected.connect_holding_reg = function (f_read, f_write) insert(self.holding_regs, { read = f_read, write = f_write }) _count_io() return #self.holding_regs @@ -146,7 +150,14 @@ rtu.init_unit = function () return ppm.is_faulted() end - return public + -- public RTU device access + + -- get the public interface to this RTU + protected.interface = function () + return public + end + + return protected end -- RTU Communications diff --git a/rtu/startup.lua b/rtu/startup.lua index 3b34670..b37e3fd 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -22,7 +22,7 @@ local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "alpha-v0.6.3" +local RTU_VERSION = "alpha-v0.6.4" local rtu_t = types.rtu_t @@ -122,13 +122,13 @@ for reactor_idx = 1, #rtu_redstone do -- link redstone in RTU local mode = rsio.get_io_mode(conf.channel) if mode == rsio.IO_MODE.DIGITAL_IN then - rs_rtu.link_di(conf.channel, conf.side, conf.bundled_color) + rs_rtu.link_di(conf.side, conf.bundled_color) 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 - rs_rtu.link_ai(conf.channel, conf.side) + rs_rtu.link_ai(conf.side) elseif mode == rsio.IO_MODE.ANALOG_OUT then - rs_rtu.link_ao(conf.channel, conf.side) + rs_rtu.link_ao(conf.side) else -- should be unreachable code, we already validated channels log.error("init> fell through if chain attempting to identify IO mode", true) @@ -171,7 +171,7 @@ for i = 1, #rtu_devices do log.warning(message) else local type = ppm.get_type(rtu_devices[i].name) - local rtu_iface = nil + local rtu_iface = nil ---@type rtu_device local rtu_type = "" if type == "boiler" then From e624dd431b60593b547da52af920aff53b6c1c36 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 12 May 2022 15:37:42 -0400 Subject: [PATCH 182/587] tank_fluid and coordinate table types --- scada-common/types.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scada-common/types.lua b/scada-common/types.lua index 372b1d3..95312d3 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -5,6 +5,19 @@ ---@class types local types = {} +---@class tank_fluid +local tank_fluid = { + name = "mekanism:empty_gas", + amount = 0 +} + +---@class coordinate +local coordinate = { + x = 0, + y = 0, + z = 0 +} + ---@alias rtu_t string types.rtu_t = { redstone = "redstone", From 8b43c81fc06569e17aef02a18b441dad945fe332 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 13 May 2022 09:38:10 -0400 Subject: [PATCH 183/587] class definition in only comments --- scada-common/mqueue.lua | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scada-common/mqueue.lua b/scada-common/mqueue.lua index db97df4..30c21e8 100644 --- a/scada-common/mqueue.lua +++ b/scada-common/mqueue.lua @@ -21,10 +21,8 @@ mqueue.new = function () local remove = table.remove ---@class queue_item - local queue_item = { - qtype = 0, ---@type TYPE - message = 0 ---@type any - } + ---@field qtype TYPE + ---@field message any ---@class mqueue local public = {} From 13fcf265b7e40915e3bd5b34eae272577516c2e0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 13 May 2022 09:38:32 -0400 Subject: [PATCH 184/587] updated types, added dumping mode and rtu_advertisement --- scada-common/types.lua | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/scada-common/types.lua b/scada-common/types.lua index 95312d3..b7f115b 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -5,18 +5,24 @@ ---@class types local types = {} +-- CLASSES -- + ---@class tank_fluid -local tank_fluid = { - name = "mekanism:empty_gas", - amount = 0 -} +---@field name string +---@field amount integer ---@class coordinate -local coordinate = { - x = 0, - y = 0, - z = 0 -} +---@field x integer +---@field y integer +---@field z integer + +---@class rtu_advertisement +---@field type integer +---@field index integer +---@field reactor integer +---@field rsio table|nil + +-- STRING TYPES -- ---@alias rtu_t string types.rtu_t = { @@ -43,6 +49,14 @@ types.rps_status_t = { manual = "manual" } +-- turbine steam dumping modes +---@alias DUMPING_MODE string +types.DUMPING_MODE = { + IDLE = "IDLE", + DUMPING = "DUMPING", + DUMPING_EXCESS = "DUMPING_EXCESS" +} + -- MODBUS -- modbus function codes From 635e7b7f59c68a407db49b0ce87b287e443ff267 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 13 May 2022 09:39:28 -0400 Subject: [PATCH 185/587] RTU advertisement sends as basic array, re-ordered input registers on turbine RTU --- rtu/dev/turbine_rtu.lua | 4 ++-- rtu/rtu.lua | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/rtu/dev/turbine_rtu.lua b/rtu/dev/turbine_rtu.lua index 5ff71a7..476a50c 100644 --- a/rtu/dev/turbine_rtu.lua +++ b/rtu/dev/turbine_rtu.lua @@ -23,15 +23,15 @@ turbine_rtu.new = function (turbine) self.rtu.connect_input_reg(self.turbine.getVents) self.rtu.connect_input_reg(self.turbine.getDispersers) self.rtu.connect_input_reg(self.turbine.getCondensers) - self.rtu.connect_input_reg(self.turbine.getDumpingMode) self.rtu.connect_input_reg(self.turbine.getSteamCapacity) self.rtu.connect_input_reg(self.turbine.getMaxFlowRate) - self.rtu.connect_input_reg(self.turbine.getMaxWaterOutput) self.rtu.connect_input_reg(self.turbine.getMaxProduction) + self.rtu.connect_input_reg(self.turbine.getMaxWaterOutput) -- current state self.rtu.connect_input_reg(self.turbine.getFlowRate) self.rtu.connect_input_reg(self.turbine.getProductionRate) self.rtu.connect_input_reg(self.turbine.getLastSteamInputRate) + self.rtu.connect_input_reg(self.turbine.getDumpingMode) -- tanks self.rtu.connect_input_reg(self.turbine.getSteam) self.rtu.connect_input_reg(self.turbine.getSteamNeeded) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index cafb645..637be54 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -256,16 +256,14 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) local type = comms.rtu_t_to_advert_type(unit.type) if type ~= nil then - ---@class rtu_advertisement local advert = { - type = type, ---@type integer - index = unit.index, ---@type integer - reactor = unit.reactor, ---@type integer - rsio = nil ---@type table|nil + type, + unit.index, + unit.reactor } if type == RTU_ADVERT_TYPES.REDSTONE then - advert.rsio = unit.device + insert(advert, unit.device) end insert(advertisement, advert) From fc39588b2e1e5470bec3a2bef07cdfb4c171ea7c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 13 May 2022 09:45:11 -0400 Subject: [PATCH 186/587] #8 RTU session for emachine and turbine, RTU session creation, adjusted sequence number logic in svsessions --- supervisor/session/rtu.lua | 124 +++++++++++++----- supervisor/session/rtu/boiler.lua | 42 +++--- supervisor/session/rtu/emachine.lua | 149 +++++++++++++++++++++ supervisor/session/rtu/turbine.lua | 195 ++++++++++++++++++++++++++++ supervisor/session/svsessions.lua | 31 ++++- supervisor/startup.lua | 2 +- supervisor/supervisor.lua | 48 +++++-- 7 files changed, 525 insertions(+), 66 deletions(-) create mode 100644 supervisor/session/rtu/emachine.lua create mode 100644 supervisor/session/rtu/turbine.lua diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index bb86b05..30844dd 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -5,12 +5,14 @@ local util = require("scada-common.util") -- supervisor rtu sessions (svrs) local svrs_boiler = require("supervisor.session.rtu.boiler") +local svrs_emachine = require("supervisor.session.rtu.emachine") +local svrs_turbine = require("supervisor.session.rtu.turbine") local rtu = {} local PROTOCOLS = comms.PROTOCOLS -local RPLC_TYPES = comms.RPLC_TYPES local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES +local RTU_ADVERT_TYPES = comms.RTU_ADVERT_TYPES local print = util.print local println = util.println @@ -21,27 +23,76 @@ local PERIODICS = { KEEP_ALIVE = 2.0 } -rtu.new_session = function (id, in_queue, out_queue) +-- create a new RTU session +---@param id integer +---@param in_queue mqueue +---@param out_queue mqueue +---@param advertisement table +rtu.new_session = function (id, in_queue, out_queue, advertisement) local log_header = "rtu_session(" .. id .. "): " local self = { id = id, in_q = in_queue, out_q = out_queue, - commanded_state = false, - commanded_burn_rate = 0.0, - ramping_rate = false, + advert = advertisement, -- connection properties seq_num = 0, r_seq_num = nil, connected = true, - received_struct = false, - received_status_cache = false, rtu_conn_watchdog = util.new_watchdog(3), - last_rtt = 0 + last_rtt = 0, + units = {} } + ---@class rtu_session + local public = {} + + -- parse the recorded advertisement + local _parse_advertisement = function () + self.units = {} + for i = 1, #self.advert do + local unit = nil + + ---@type rtu_advertisement + local unit_advert = { + type = self.advert[i][0], + index = self.advert[i][1], + reactor = self.advert[i][2], + rsio = self.advert[i][3] + } + + local u_type = unit_advert.type + + -- create unit by type + if u_type == RTU_ADVERT_TYPES.REDSTONE then + + elseif u_type == RTU_ADVERT_TYPES.BOILER then + unit = svrs_boiler.new(self.id, unit_advert, self.out_q) + elseif u_type == RTU_ADVERT_TYPES.BOILER_VALVE then + -- @todo Mekanism 10.1+ + elseif u_type == RTU_ADVERT_TYPES.TURBINE then + unit = svrs_turbine.new(self.id, unit_advert, self.out_q) + elseif u_type == RTU_ADVERT_TYPES.TURBINE_VALVE then + -- @todo Mekanism 10.1+ + elseif u_type == RTU_ADVERT_TYPES.EMACHINE then + unit = svrs_emachine.new(self.id, unit_advert, self.out_q) + elseif u_type == RTU_ADVERT_TYPES.IMATRIX then + -- @todo Mekanism 10.1+ + end + + if unit ~= nil then + table.insert(self.units, unit) + else + self.units = {} + log.error(log_header .. "bad advertisement; encountered unsupported RTU type") + break + end + end + end + -- send a MODBUS TCP packet + ---@param m_pkt modbus_packet local _send_modbus = function (m_pkt) local s_pkt = comms.scada_packet() s_pkt.make(self.seq_num, PROTOCOLS.MODBUS_TCP, m_pkt.raw_sendable()) @@ -50,6 +101,8 @@ rtu.new_session = function (id, in_queue, out_queue) end -- send a SCADA management packet + ---@param msg_type SCADA_MGMT_TYPES + ---@param msg table local _send_mgmt = function (msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() @@ -62,6 +115,7 @@ rtu.new_session = function (id, in_queue, out_queue) end -- handle a packet + ---@param pkt modbus_frame|mgmt_frame local _handle_packet = function (pkt) -- check sequence number if self.r_seq_num == nil then @@ -78,6 +132,10 @@ rtu.new_session = function (id, in_queue, out_queue) -- process packet if pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then + if self.units[pkt.unit_id] ~= nil then + local unit = self.units[pkt.unit_id] ---@type rtu_session_unit + unit.handle_packet(pkt) + end elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then if pkt.type == SCADA_MGMT_TYPES.KEEP_ALIVE then @@ -102,9 +160,8 @@ rtu.new_session = function (id, in_queue, out_queue) self.connected = false elseif pkt.type == SCADA_MGMT_TYPES.RTU_ADVERT then -- RTU unit advertisement - for i = 1, pkt.length do - local unit = pkt.data[i] ---@type rtu_advertisement - end + self.advert = pkt.data + _parse_advertisement() else log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) end @@ -114,15 +171,16 @@ rtu.new_session = function (id, in_queue, out_queue) -- PUBLIC FUNCTIONS -- -- get the session ID - local get_id = function () return self.id end + public.get_id = function () return self.id end -- check if a timer matches this session's watchdog - local check_wd = function (timer) + ---@param timer number + public.check_wd = function (timer) return self.rtu_conn_watchdog.is_timer(timer) end -- close the connection - local close = function () + public.close = function () self.rtu_conn_watchdog.cancel() self.connected = false _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) @@ -131,8 +189,17 @@ rtu.new_session = function (id, in_queue, out_queue) end -- iterate the session - local iterate = function () + ---@return boolean connected + public.iterate = function () if self.connected then + ------------------ + -- update units -- + ------------------ + + for i = 1, #self.units do + self.units[i].update() + end + ------------------ -- handle queue -- ------------------ @@ -141,17 +208,17 @@ rtu.new_session = function (id, in_queue, out_queue) while self.in_q.ready() and self.connected do -- get a new message to process - local message = self.in_q.pop() + local msg = self.in_q.pop() - if message.qtype == mqueue.TYPE.PACKET then - -- handle a packet - _handle_packet(message.message) - elseif message.qtype == mqueue.TYPE.COMMAND then - -- handle instruction - local cmd = message.message - elseif message.qtype == mqueue.TYPE.DATA then - -- instruction with body - local cmd = message.message + if msg ~= nil then + if msg.qtype == mqueue.TYPE.PACKET then + -- handle a packet + _handle_packet(msg.message) + elseif msg.qtype == mqueue.TYPE.COMMAND then + -- handle instruction + elseif msg.qtype == mqueue.TYPE.DATA then + -- instruction with body + end end -- max 100ms spent processing queue @@ -191,12 +258,7 @@ rtu.new_session = function (id, in_queue, out_queue) return self.connected end - return { - get_id = get_id, - check_wd = check_wd, - close = close, - iterate = iterate - } + return public end return rtu diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua index 83ecd8b..8804bda 100644 --- a/supervisor/session/rtu/boiler.lua +++ b/supervisor/session/rtu/boiler.lua @@ -1,7 +1,6 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local types = require("scada-common.types") -local util = require("scada-common.util") local txnctrl = require("supervisor.session.rtu.txnctrl") @@ -25,16 +24,17 @@ local PERIODICS = { } -- create a new boiler rtu session runner +---@param session_id integer ---@param advert rtu_advertisement ---@param out_queue mqueue -boiler.new = function (advert, out_queue) +boiler.new = function (session_id, advert, out_queue) -- type check if advert.type ~= rtu_t.boiler then - log.error("attempt to instantiate boiler RTU for non boiler type '" .. advert.type .. "'. this is a bug.") + log.error("attempt to instantiate boiler RTU for type '" .. advert.type .. "'. this is a bug.") return nil end - local log_tag = "session.rtu.boiler(" .. advert.index .. "): " + local log_tag = "session.rtu(" .. session_id .. ").boiler(" .. advert.index .. "): " local self = { uid = advert.index, @@ -69,52 +69,46 @@ boiler.new = function (advert, out_queue) water = 0, water_need = 0, water_fill = 0.0, - hcool = 0, + hcool = {}, ---@type tank_fluid hcool_need = 0, hcool_fill = 0.0, - ccool = 0, + ccool = {}, ---@type tank_fluid ccool_need = 0, ccool_fill = 0.0 } } } - ---@class rtu_session__boiler + ---@class rtu_session_unit local public = {} -- PRIVATE FUNCTIONS -- - -- query the build of the device - local _request_build = function () + local _send_request = function (txn_type, f_code, register_range) local m_pkt = comms.modbus_packet() - local txn_id = self.transaction_controller.create(TXN_TYPES.BUILD) + local txn_id = self.transaction_controller.create(txn_type) - -- read input registers 1 through 7 (start = 1, count = 7) - m_pkt.make(txn_id, self.uid, MODBUS_FCODE.READ_INPUT_REGS, { 1, 7 }) + m_pkt.make(txn_id, self.uid, f_code, register_range) self.out_q.push_packet(m_pkt) end + -- query the build of the device + local _request_build = function () + -- read input registers 1 through 7 (start = 1, count = 7) + _send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 7 }) + end + -- query the state of the device local _request_state = function () - local m_pkt = comms.modbus_packet() - local txn_id = self.transaction_controller.create(TXN_TYPES.STATE) - -- read input registers 8 through 9 (start = 8, count = 2) - m_pkt.make(txn_id, self.uid, MODBUS_FCODE.READ_INPUT_REGS, { 8, 2 }) - - self.out_q.push_packet(m_pkt) + _send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 8, 2 }) end -- query the tanks of the device local _request_tanks = function () - local m_pkt = comms.modbus_packet() - local txn_id = self.transaction_controller.create(TXN_TYPES.TANKS) - -- read input registers 10 through 21 (start = 10, count = 12) - m_pkt.make(txn_id, self.uid, MODBUS_FCODE.READ_INPUT_REGS, { 10, 12 }) - - self.out_q.push_packet(m_pkt) + _send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 10, 12 }) end -- PUBLIC FUNCTIONS -- diff --git a/supervisor/session/rtu/emachine.lua b/supervisor/session/rtu/emachine.lua new file mode 100644 index 0000000..340315e --- /dev/null +++ b/supervisor/session/rtu/emachine.lua @@ -0,0 +1,149 @@ +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") + +local txnctrl = require("supervisor.session.rtu.txnctrl") + +local emachine = {} + +local PROTOCOLS = comms.PROTOCOLS +local MODBUS_FCODE = types.MODBUS_FCODE + +local rtu_t = types.rtu_t + +local TXN_TYPES = { + BUILD = 0, + STORAGE = 1 +} + +local PERIODICS = { + BUILD = 1000, + STORAGE = 500 +} + +-- create a new energy machine rtu session runner +---@param session_id integer +---@param advert rtu_advertisement +---@param out_queue mqueue +emachine.new = function (session_id, advert, out_queue) + -- type check + if advert.type ~= rtu_t.energy_machine then + log.error("attempt to instantiate emachine RTU for type '" .. advert.type .. "'. this is a bug.") + return nil + end + + local log_tag = "session.rtu(" .. session_id .. ").emachine(" .. advert.index .. "): " + + local self = { + uid = advert.index, + -- reactor = advert.reactor, + reactor = 0, + out_q = out_queue, + transaction_controller = txnctrl.new(), + has_build = false, + periodics = { + next_build_req = 0, + next_storage_req = 0, + }, + ---@class emachine_session_db + db = { + build = { + max_energy = 0 + }, + storage = { + energy = 0, + energy_need = 0, + energy_fill = 0.0 + } + } + } + + ---@class rtu_session_unit + local public = {} + + -- PRIVATE FUNCTIONS -- + + local _send_request = function (txn_type, f_code, register_range) + local m_pkt = comms.modbus_packet() + local txn_id = self.transaction_controller.create(txn_type) + + m_pkt.make(txn_id, self.uid, f_code, register_range) + + self.out_q.push_packet(m_pkt) + end + + -- query the build of the device + local _request_build = function () + -- read input register 1 (start = 1, count = 1) + _send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 1 }) + end + + -- query the state of the energy storage + local _request_storage = function () + -- read input registers 2 through 4 (start = 2, count = 3) + _send_request(TXN_TYPES.STORAGE, MODBUS_FCODE.READ_INPUT_REGS, { 2, 3 }) + end + + -- PUBLIC FUNCTIONS -- + + -- handle a packet + ---@param m_pkt modbus_frame + public.handle_packet = function (m_pkt) + local success = false + + if m_pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then + if m_pkt.unit_id == self.uid then + local txn_type = self.transaction_controller.resolve(m_pkt.txn_id) + if txn_type == TXN_TYPES.BUILD then + -- build response + if m_pkt.length == 1 then + self.db.build.max_energy = m_pkt.data[1] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (emachine.build)") + end + elseif txn_type == TXN_TYPES.STORAGE then + -- storage response + if m_pkt.length == 3 then + self.db.storage.energy = m_pkt.data[1] + self.db.storage.energy_need = m_pkt.data[2] + self.db.storage.energy_fill = m_pkt.data[3] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (emachine.storage)") + end + elseif txn_type == nil then + log.error(log_tag .. "unknown transaction reply") + else + log.error(log_tag .. "unknown transaction type " .. txn_type) + end + else + log.error(log_tag .. "wrong unit ID: " .. m_pkt.unit_id, true) + end + else + log.error(log_tag .. "illegal packet type " .. m_pkt.scada_frame.protocol(), true) + end + + return success + end + + public.get_uid = function () return self.uid end + public.get_reactor = function () return self.reactor end + public.get_db = function () return self.db end + + -- update this runner + ---@param time_now integer milliseconds + public.update = function (time_now) + if not self.has_build and self.next_build_req <= time_now then + _request_build() + self.next_build_req = time_now + PERIODICS.BUILD + end + + if self.next_storage_req <= time_now then + _request_storage() + self.next_storage_req = time_now + PERIODICS.STORAGE + end + end + + return public +end + +return emachine diff --git a/supervisor/session/rtu/turbine.lua b/supervisor/session/rtu/turbine.lua new file mode 100644 index 0000000..23c3d61 --- /dev/null +++ b/supervisor/session/rtu/turbine.lua @@ -0,0 +1,195 @@ +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") + +local txnctrl = require("supervisor.session.rtu.txnctrl") + +local turbine = {} + +local PROTOCOLS = comms.PROTOCOLS +local DUMPING_MODE = types.DUMPING_MODE +local MODBUS_FCODE = types.MODBUS_FCODE + +local rtu_t = types.rtu_t + +local TXN_TYPES = { + BUILD = 0, + STATE = 1, + TANKS = 2 +} + +local PERIODICS = { + BUILD = 1000, + STATE = 500, + TANKS = 1000 +} + +-- create a new turbine rtu session runner +---@param session_id integer +---@param advert rtu_advertisement +---@param out_queue mqueue +turbine.new = function (session_id, advert, out_queue) + -- type check + if advert.type ~= rtu_t.turbine then + log.error("attempt to instantiate turbine RTU for type '" .. advert.type .. "'. this is a bug.") + return nil + end + + local log_tag = "session.rtu(" .. session_id .. ").turbine(" .. advert.index .. "): " + + local self = { + uid = advert.index, + reactor = advert.reactor, + out_q = out_queue, + transaction_controller = txnctrl.new(), + has_build = false, + periodics = { + next_build_req = 0, + next_state_req = 0, + next_tanks_req = 0, + }, + ---@class turbine_session_db + db = { + build = { + blades = 0, + coils = 0, + vents = 0, + dispersers = 0, + condensers = 0, + steam_cap = 0, + max_flow_rate = 0, + max_production = 0, + max_water_output = 0 + }, + state = { + flow_rate = 0.0, + prod_rate = 0.0, + steam_input_rate = 0.0, + dumping_mode = DUMPING_MODE.IDLE ---@type DUMPING_MODE + }, + tanks = { + steam = 0, + steam_need = 0, + steam_fill = 0.0 + } + } + } + + ---@class rtu_session_unit + local public = {} + + -- PRIVATE FUNCTIONS -- + + local _send_request = function (txn_type, f_code, register_range) + local m_pkt = comms.modbus_packet() + local txn_id = self.transaction_controller.create(txn_type) + + m_pkt.make(txn_id, self.uid, f_code, register_range) + + self.out_q.push_packet(m_pkt) + end + + -- query the build of the device + local _request_build = function () + -- read input registers 1 through 9 (start = 1, count = 9) + _send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 9 }) + end + + -- query the state of the device + local _request_state = function () + -- read input registers 10 through 13 (start = 10, count = 4) + _send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 10, 4 }) + end + + -- query the tanks of the device + local _request_tanks = function () + -- read input registers 14 through 16 (start = 14, count = 3) + _send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 14, 3 }) + end + + -- PUBLIC FUNCTIONS -- + + -- handle a packet + ---@param m_pkt modbus_frame + public.handle_packet = function (m_pkt) + local success = false + + if m_pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then + if m_pkt.unit_id == self.uid then + local txn_type = self.transaction_controller.resolve(m_pkt.txn_id) + if txn_type == TXN_TYPES.BUILD then + -- build response + if m_pkt.length == 9 then + self.db.build.blades = m_pkt.data[1] + self.db.build.coils = m_pkt.data[2] + self.db.build.vents = m_pkt.data[3] + self.db.build.dispersers = m_pkt.data[4] + self.db.build.condensers = m_pkt.data[5] + self.db.build.steam_cap = m_pkt.data[6] + self.db.build.max_flow_rate = m_pkt.data[7] + self.db.build.max_production = m_pkt.data[8] + self.db.build.max_water_output = m_pkt.data[9] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (turbine.build)") + end + elseif txn_type == TXN_TYPES.STATE then + -- state response + if m_pkt.length == 4 then + self.db.state.flow_rate = m_pkt.data[1] + self.db.state.prod_rate = m_pkt.data[2] + self.db.state.steam_input_rate = m_pkt.data[3] + self.db.state.dumping_mode = m_pkt.data[4] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (turbine.state)") + end + elseif txn_type == TXN_TYPES.TANKS then + -- tanks response + if m_pkt.length == 3 then + self.db.tanks.steam = m_pkt.data[1] + self.db.tanks.steam_need = m_pkt.data[2] + self.db.tanks.steam_fill = m_pkt.data[3] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (turbine.tanks)") + end + elseif txn_type == nil then + log.error(log_tag .. "unknown transaction reply") + else + log.error(log_tag .. "unknown transaction type " .. txn_type) + end + else + log.error(log_tag .. "wrong unit ID: " .. m_pkt.unit_id, true) + end + else + log.error(log_tag .. "illegal packet type " .. m_pkt.scada_frame.protocol(), true) + end + + return success + end + + public.get_uid = function () return self.uid end + public.get_reactor = function () return self.reactor end + public.get_db = function () return self.db end + + -- update this runner + ---@param time_now integer milliseconds + public.update = function (time_now) + if not self.has_build and self.next_build_req <= time_now then + _request_build() + self.next_build_req = time_now + PERIODICS.BUILD + end + + if self.next_state_req <= time_now then + _request_state() + self.next_state_req = time_now + PERIODICS.STATE + end + + if self.next_tanks_req <= time_now then + _request_tanks() + self.next_tanks_req = time_now + PERIODICS.TANKS + end + end + + return public +end + +return turbine diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 37fc89e..635e81e 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -53,7 +53,7 @@ local function _iterate(sessions) end -- cleanly close a session ----@param session plc_session_struct +---@param session plc_session_struct|rtu_session_struct local function _shutdown(session) session.open = false session.instance.close() @@ -127,7 +127,7 @@ end -- find a session by the remote port ---@param remote_port integer ----@return plc_session_struct|nil +---@return plc_session_struct|rtu_session_struct|nil svsessions.find_session = function (remote_port) -- check RTU sessions for i = 1, #self.rtu_sessions do @@ -201,6 +201,33 @@ svsessions.establish_plc_session = function (local_port, remote_port, for_reacto end end +-- establish a new RTU session +---@param local_port integer +---@param remote_port integer +---@param advertisement table +---@return integer session_id +svsessions.establish_rtu_session = function (local_port, remote_port, advertisement) + ---@class rtu_session_struct + local rtu_s = { + open = true, + l_port = local_port, + r_port = remote_port, + in_queue = mqueue.new(), + out_queue = mqueue.new(), + instance = nil + } + + rtu_s.instance = rtu.new_session(self.next_rtu_id, rtu_s.in_queue, rtu_s.out_queue, advertisement) + table.insert(self.rtu_sessions, rtu_s) + + log.debug("established new RTU session to " .. remote_port .. " with ID " .. self.next_rtu_id) + + self.next_rtu_id = self.next_rtu_id + 1 + + -- success + return rtu_s.instance.get_id() +end + -- attempt to identify which session's watchdog timer fired ---@param timer_event number svsessions.check_all_watchdogs = function (timer_event) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 5923887..b559bf0 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -11,7 +11,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "alpha-v0.3.5" +local SUPERVISOR_VERSION = "alpha-v0.3.6" local print = util.print local println = util.println diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index a534882..7924e9f 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -26,7 +26,6 @@ local println_ts = util.println_ts ---@param coord_listen integer supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) local self = { - ln_seq_num = 0, num_reactors = num_reactors, modem = modem, dev_listen = dev_listen, @@ -59,15 +58,27 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) -- send PLC link request responses ---@param dest integer ---@param msg table - local _send_plc_linking = function (dest, msg) + local _send_plc_linking = function (seq_id, dest, msg) local s_pkt = comms.scada_packet() local r_pkt = comms.rplc_packet() r_pkt.make(0, RPLC_TYPES.LINK_REQ, msg) - s_pkt.make(self.ln_seq_num, PROTOCOLS.RPLC, r_pkt.raw_sendable()) + s_pkt.make(seq_id, PROTOCOLS.RPLC, r_pkt.raw_sendable()) + + self.modem.transmit(dest, self.dev_listen, s_pkt.raw_sendable()) + end + + -- send RTU advertisement responses + ---@param seq_id integer + ---@param dest integer + local _send_remote_linked = function (seq_id, dest) + local s_pkt = comms.scada_packet() + local m_pkt = comms.mgmt_packet() + + m_pkt.make(SCADA_MGMT_TYPES.REMOTE_LINKED, {}) + s_pkt.make(seq_id, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) self.modem.transmit(dest, self.dev_listen, s_pkt.raw_sendable()) - self.ln_seq_num = self.ln_seq_num + 1 end -- PUBLIC FUNCTIONS -- @@ -143,18 +154,27 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) if protocol == PROTOCOLS.MODBUS_TCP then -- MODBUS response + if session ~= nil then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + else + -- any other packet should be session related, discard it + log.debug("discarding MODBUS_TCP packet without a known session") + end elseif protocol == PROTOCOLS.RPLC then -- reactor PLC packet if session ~= nil then if packet.type == RPLC_TYPES.LINK_REQ then -- new device on this port? that's a collision log.debug("PLC_LNK: request from existing connection received on " .. r_port .. ", responding with collision") - _send_plc_linking(r_port, { RPLC_LINKING.COLLISION }) + _send_plc_linking(packet.scada_frame.seq_num() + 1, r_port, { RPLC_LINKING.COLLISION }) else -- pass the packet onto the session handler session.in_queue.push_packet(packet) end else + local next_seq_id = packet.scada_frame.seq_num() + 1 + -- unknown session, is this a linking request? if packet.type == RPLC_TYPES.LINK_REQ then if packet.length == 1 then @@ -163,12 +183,12 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) if plc_id == false then -- reactor already has a PLC assigned log.debug("PLC_LNK: assignment collision with reactor " .. packet.data[1]) - _send_plc_linking(r_port, { RPLC_LINKING.COLLISION }) + _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.COLLISION }) else -- got an ID; assigned to a reactor successfully println("connected to reactor " .. packet.data[1] .. " PLC (port " .. r_port .. ")") log.debug("PLC_LNK: allowed for device at " .. r_port) - _send_plc_linking(r_port, { RPLC_LINKING.ALLOW }) + _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.ALLOW }) end else log.debug("PLC_LNK: new linking packet length mismatch") @@ -176,7 +196,7 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) else -- force a re-link log.debug("PLC_LNK: no session but not a link, force relink") - _send_plc_linking(r_port, { RPLC_LINKING.DENY }) + _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.DENY }) end end elseif protocol == PROTOCOLS.SCADA_MGMT then @@ -184,6 +204,18 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) if session ~= nil then -- pass the packet onto the session handler session.in_queue.push_packet(packet) + else + -- is this an RTU advertisement? + if packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then + local rtu_id = svsessions.establish_rtu_session(l_port, r_port, packet.data) + + println("connected to RTU (port " .. r_port .. ")") + log.debug("RTU_ADVERT: linked " .. r_port) + _send_remote_linked(packet.scada_frame.seq_num() + 1, r_port) + else + -- any other packet should be session related, discard it + log.debug("discarding SCADA_MGMT packet without a known session") + end end else log.debug("illegal packet type " .. protocol .. " on device listening channel") From 45f58435988fa4fdf37158c8d3e4e96fa7607ba5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 13 May 2022 09:49:24 -0400 Subject: [PATCH 187/587] #8 renamed rtu_session_unit type to unit_session --- supervisor/session/rtu.lua | 4 ++-- supervisor/session/rtu/boiler.lua | 2 +- supervisor/session/rtu/emachine.lua | 2 +- supervisor/session/rtu/turbine.lua | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 30844dd..fd30fd0 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -52,7 +52,7 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) local _parse_advertisement = function () self.units = {} for i = 1, #self.advert do - local unit = nil + local unit = nil ---@type unit_session ---@type rtu_advertisement local unit_advert = { @@ -133,7 +133,7 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) -- process packet if pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then if self.units[pkt.unit_id] ~= nil then - local unit = self.units[pkt.unit_id] ---@type rtu_session_unit + local unit = self.units[pkt.unit_id] ---@type unit_session unit.handle_packet(pkt) end elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua index 8804bda..78931ec 100644 --- a/supervisor/session/rtu/boiler.lua +++ b/supervisor/session/rtu/boiler.lua @@ -79,7 +79,7 @@ boiler.new = function (session_id, advert, out_queue) } } - ---@class rtu_session_unit + ---@class unit_session local public = {} -- PRIVATE FUNCTIONS -- diff --git a/supervisor/session/rtu/emachine.lua b/supervisor/session/rtu/emachine.lua index 340315e..b6c89b6 100644 --- a/supervisor/session/rtu/emachine.lua +++ b/supervisor/session/rtu/emachine.lua @@ -58,7 +58,7 @@ emachine.new = function (session_id, advert, out_queue) } } - ---@class rtu_session_unit + ---@class unit_session local public = {} -- PRIVATE FUNCTIONS -- diff --git a/supervisor/session/rtu/turbine.lua b/supervisor/session/rtu/turbine.lua index 23c3d61..ccca068 100644 --- a/supervisor/session/rtu/turbine.lua +++ b/supervisor/session/rtu/turbine.lua @@ -75,7 +75,7 @@ turbine.new = function (session_id, advert, out_queue) } } - ---@class rtu_session_unit + ---@class unit_session local public = {} -- PRIVATE FUNCTIONS -- From c53ddf16385c8b19a991fa9c266bd3b6d9bf7b71 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 13 May 2022 10:27:57 -0400 Subject: [PATCH 188/587] renamed RTU_ADVERT_TYPES to RTU_UNIT_TYPES --- rtu/rtu.lua | 4 ++-- scada-common/comms.lua | 38 +++++++++++++++++++------------------- supervisor/session/rtu.lua | 16 ++++++++-------- supervisor/supervisor.lua | 2 +- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 637be54..ec963b8 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -12,7 +12,7 @@ local rtu_t = types.rtu_t local PROTOCOLS = comms.PROTOCOLS local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES -local RTU_ADVERT_TYPES = comms.RTU_ADVERT_TYPES +local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES local print = util.print local println = util.println @@ -262,7 +262,7 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) unit.reactor } - if type == RTU_ADVERT_TYPES.REDSTONE then + if type == RTU_UNIT_TYPES.REDSTONE then insert(advert, unit.device) end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 6b4deef..bffd98f 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -48,8 +48,8 @@ local SCADA_MGMT_TYPES = { REMOTE_LINKED = 3 -- remote device linked } ----@alias RTU_ADVERT_TYPES integer -local RTU_ADVERT_TYPES = { +---@alias RTU_UNIT_TYPES integer +local RTU_UNIT_TYPES = { REDSTONE = 0, -- redstone I/O BOILER = 1, -- boiler BOILER_VALVE = 2, -- boiler mekanism 10.1+ @@ -63,7 +63,7 @@ comms.PROTOCOLS = PROTOCOLS comms.RPLC_TYPES = RPLC_TYPES comms.RPLC_LINKING = RPLC_LINKING comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES -comms.RTU_ADVERT_TYPES = RTU_ADVERT_TYPES +comms.RTU_UNIT_TYPES = RTU_UNIT_TYPES -- generic SCADA packet object comms.scada_packet = function () @@ -562,44 +562,44 @@ end -- convert rtu_t to RTU advertisement type ---@param type rtu_t ----@return RTU_ADVERT_TYPES|nil +---@return RTU_UNIT_TYPES|nil comms.rtu_t_to_advert_type = function (type) if type == rtu_t.redstone then - return RTU_ADVERT_TYPES.REDSTONE + return RTU_UNIT_TYPES.REDSTONE elseif type == rtu_t.boiler then - return RTU_ADVERT_TYPES.BOILER + return RTU_UNIT_TYPES.BOILER elseif type == rtu_t.boiler_valve then - return RTU_ADVERT_TYPES.BOILER_VALVE + return RTU_UNIT_TYPES.BOILER_VALVE elseif type == rtu_t.turbine then - return RTU_ADVERT_TYPES.TURBINE + return RTU_UNIT_TYPES.TURBINE elseif type == rtu_t.turbine_valve then - return RTU_ADVERT_TYPES.TURBINE_VALVE + return RTU_UNIT_TYPES.TURBINE_VALVE elseif type == rtu_t.energy_machine then - return RTU_ADVERT_TYPES.EMACHINE + return RTU_UNIT_TYPES.EMACHINE elseif type == rtu_t.induction_matrix then - return RTU_ADVERT_TYPES.IMATRIX + return RTU_UNIT_TYPES.IMATRIX end return nil end -- convert RTU advertisement type to rtu_t ----@param atype RTU_ADVERT_TYPES +---@param atype RTU_UNIT_TYPES ---@return rtu_t|nil comms.advert_type_to_rtu_t = function (atype) - if atype == RTU_ADVERT_TYPES.REDSTONE then + if atype == RTU_UNIT_TYPES.REDSTONE then return rtu_t.redstone - elseif atype == RTU_ADVERT_TYPES.BOILER then + elseif atype == RTU_UNIT_TYPES.BOILER then return rtu_t.boiler - elseif atype == RTU_ADVERT_TYPES.BOILER_VALVE then + elseif atype == RTU_UNIT_TYPES.BOILER_VALVE then return rtu_t.boiler_valve - elseif atype == RTU_ADVERT_TYPES.TURBINE then + elseif atype == RTU_UNIT_TYPES.TURBINE then return rtu_t.turbine - elseif atype == RTU_ADVERT_TYPES.TURBINE_VALVE then + elseif atype == RTU_UNIT_TYPES.TURBINE_VALVE then return rtu_t.turbine_valve - elseif atype == RTU_ADVERT_TYPES.EMACHINE then + elseif atype == RTU_UNIT_TYPES.EMACHINE then return rtu_t.energy_machine - elseif atype == RTU_ADVERT_TYPES.IMATRIX then + elseif atype == RTU_UNIT_TYPES.IMATRIX then return rtu_t.induction_matrix end diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index fd30fd0..5dab40c 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -12,7 +12,7 @@ local rtu = {} local PROTOCOLS = comms.PROTOCOLS local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES -local RTU_ADVERT_TYPES = comms.RTU_ADVERT_TYPES +local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES local print = util.print local println = util.println @@ -65,19 +65,19 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) local u_type = unit_advert.type -- create unit by type - if u_type == RTU_ADVERT_TYPES.REDSTONE then + if u_type == RTU_UNIT_TYPES.REDSTONE then - elseif u_type == RTU_ADVERT_TYPES.BOILER then + elseif u_type == RTU_UNIT_TYPES.BOILER then unit = svrs_boiler.new(self.id, unit_advert, self.out_q) - elseif u_type == RTU_ADVERT_TYPES.BOILER_VALVE then + elseif u_type == RTU_UNIT_TYPES.BOILER_VALVE then -- @todo Mekanism 10.1+ - elseif u_type == RTU_ADVERT_TYPES.TURBINE then + elseif u_type == RTU_UNIT_TYPES.TURBINE then unit = svrs_turbine.new(self.id, unit_advert, self.out_q) - elseif u_type == RTU_ADVERT_TYPES.TURBINE_VALVE then + elseif u_type == RTU_UNIT_TYPES.TURBINE_VALVE then -- @todo Mekanism 10.1+ - elseif u_type == RTU_ADVERT_TYPES.EMACHINE then + elseif u_type == RTU_UNIT_TYPES.EMACHINE then unit = svrs_emachine.new(self.id, unit_advert, self.out_q) - elseif u_type == RTU_ADVERT_TYPES.IMATRIX then + elseif u_type == RTU_UNIT_TYPES.IMATRIX then -- @todo Mekanism 10.1+ end diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 7924e9f..5596cb8 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -10,7 +10,7 @@ local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES local RPLC_LINKING = comms.RPLC_LINKING local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES -local RTU_ADVERT_TYPES = comms.RTU_ADVERT_TYPES +local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES local SESSION_TYPE = svsessions.SESSION_TYPE From bf0e92d6e436a86f19a1ea55de98ced6302cb595 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 13 May 2022 11:08:22 -0400 Subject: [PATCH 189/587] refactoring --- rtu/rtu.lua | 4 ++-- scada-common/comms.lua | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index ec963b8..f46b560 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -252,8 +252,8 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) local advertisement = {} for i = 1, #units do - local unit = units[i] ---@type rtu_unit_registry_entry - local type = comms.rtu_t_to_advert_type(unit.type) + local unit = units[i] --@type rtu_unit_registry_entry + local type = comms.rtu_t_to_unit_type(unit.type) if type ~= nil then local advert = { diff --git a/scada-common/comms.lua b/scada-common/comms.lua index bffd98f..c305b29 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -560,10 +560,10 @@ comms.capi_packet = function () return public end --- convert rtu_t to RTU advertisement type +-- convert rtu_t to RTU unit type ---@param type rtu_t ---@return RTU_UNIT_TYPES|nil -comms.rtu_t_to_advert_type = function (type) +comms.rtu_t_to_unit_type = function (type) if type == rtu_t.redstone then return RTU_UNIT_TYPES.REDSTONE elseif type == rtu_t.boiler then @@ -583,23 +583,23 @@ comms.rtu_t_to_advert_type = function (type) return nil end --- convert RTU advertisement type to rtu_t ----@param atype RTU_UNIT_TYPES +-- convert RTU unit type to rtu_t +---@param utype RTU_UNIT_TYPES ---@return rtu_t|nil -comms.advert_type_to_rtu_t = function (atype) - if atype == RTU_UNIT_TYPES.REDSTONE then +comms.advert_type_to_rtu_t = function (utype) + if utype == RTU_UNIT_TYPES.REDSTONE then return rtu_t.redstone - elseif atype == RTU_UNIT_TYPES.BOILER then + elseif utype == RTU_UNIT_TYPES.BOILER then return rtu_t.boiler - elseif atype == RTU_UNIT_TYPES.BOILER_VALVE then + elseif utype == RTU_UNIT_TYPES.BOILER_VALVE then return rtu_t.boiler_valve - elseif atype == RTU_UNIT_TYPES.TURBINE then + elseif utype == RTU_UNIT_TYPES.TURBINE then return rtu_t.turbine - elseif atype == RTU_UNIT_TYPES.TURBINE_VALVE then + elseif utype == RTU_UNIT_TYPES.TURBINE_VALVE then return rtu_t.turbine_valve - elseif atype == RTU_UNIT_TYPES.EMACHINE then + elseif utype == RTU_UNIT_TYPES.EMACHINE then return rtu_t.energy_machine - elseif atype == RTU_UNIT_TYPES.IMATRIX then + elseif utype == RTU_UNIT_TYPES.IMATRIX then return rtu_t.induction_matrix end From 72da7180158c2c566f78dce38b11323c4fe9ef15 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 13 May 2022 11:38:56 -0400 Subject: [PATCH 190/587] optimized session lookup --- supervisor/session/svsessions.lua | 41 ++++++++++++++++++++++++++++--- supervisor/startup.lua | 2 +- supervisor/supervisor.lua | 15 ++++++++--- 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 635e81e..88c57cd 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -125,10 +125,38 @@ svsessions.link_modem = function (modem) self.modem = modem end --- find a session by the remote port +-- find an RTU session by the remote port +---@param remote_port integer +---@return rtu_session_struct|nil +svsessions.find_rtu_session = function (remote_port) + -- check RTU sessions + for i = 1, #self.rtu_sessions do + if self.rtu_sessions[i].r_port == remote_port then + return self.rtu_sessions[i] + end + end + + return nil +end + +-- find a PLC session by the remote port +---@param remote_port integer +---@return plc_session_struct|nil +svsessions.find_plc_session = function (remote_port) + -- check PLC sessions + for i = 1, #self.plc_sessions do + if self.plc_sessions[i].r_port == remote_port then + return self.plc_sessions[i] + end + end + + return nil +end + +-- find a PLC/RTU session by the remote port ---@param remote_port integer ---@return plc_session_struct|rtu_session_struct|nil -svsessions.find_session = function (remote_port) +svsessions.find_device_session = function (remote_port) -- check RTU sessions for i = 1, #self.rtu_sessions do if self.rtu_sessions[i].r_port == remote_port then @@ -143,6 +171,13 @@ svsessions.find_session = function (remote_port) end end + return nil +end + +-- find a coordinator session by the remote port +---@param remote_port integer +---@return nil +svsessions.find_coord_session = function (remote_port) -- check coordinator sessions for i = 1, #self.coord_sessions do if self.coord_sessions[i].r_port == remote_port then @@ -155,7 +190,7 @@ end -- get a session by reactor ID ---@param reactor integer ----@return plc_session_struct session +---@return plc_session_struct|nil session svsessions.get_reactor_session = function (reactor) local session = nil diff --git a/supervisor/startup.lua b/supervisor/startup.lua index b559bf0..16725b6 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -11,7 +11,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "alpha-v0.3.6" +local SUPERVISOR_VERSION = "alpha-v0.3.7" local print = util.print local println = util.println diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 5596cb8..3a176cb 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -149,10 +149,10 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) -- device (RTU/PLC) listening channel if l_port == self.dev_listen then - -- look for an associated session - local session = svsessions.find_session(r_port) - if protocol == PROTOCOLS.MODBUS_TCP then + -- look for an associated session + local session = svsessions.find_rtu_session(r_port) + -- MODBUS response if session ~= nil then -- pass the packet onto the session handler @@ -162,6 +162,9 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) log.debug("discarding MODBUS_TCP packet without a known session") end elseif protocol == PROTOCOLS.RPLC then + -- look for an associated session + local session = svsessions.find_plc_session(r_port) + -- reactor PLC packet if session ~= nil then if packet.type == RPLC_TYPES.LINK_REQ then @@ -200,6 +203,9 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) end end elseif protocol == PROTOCOLS.SCADA_MGMT then + -- look for an associated session + local session = svsessions.find_device_session(r_port) + -- SCADA management packet if session ~= nil then -- pass the packet onto the session handler @@ -222,6 +228,9 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) end -- coordinator listening channel elseif l_port == self.coord_listen then + -- look for an associated session + local session = svsessions.find_coord_session(r_port) + if protocol == PROTOCOLS.SCADA_MGMT then -- SCADA management packet elseif protocol == PROTOCOLS.COORD_DATA then From 282d3fcd99f022cab2a5c09b37a48807dbe943c2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 13 May 2022 12:23:01 -0400 Subject: [PATCH 191/587] added queue_data class --- scada-common/mqueue.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scada-common/mqueue.lua b/scada-common/mqueue.lua index 30c21e8..8069ecb 100644 --- a/scada-common/mqueue.lua +++ b/scada-common/mqueue.lua @@ -24,6 +24,10 @@ mqueue.new = function () ---@field qtype TYPE ---@field message any + ---@class queue_data + ---@field key any + ---@field val any + ---@class mqueue local public = {} From 6b74db70bd35efc246117d8792080b8523178259 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 13 May 2022 12:23:30 -0400 Subject: [PATCH 192/587] #8 fixed RTU unit type check --- supervisor/session/rtu/boiler.lua | 3 ++- supervisor/session/rtu/emachine.lua | 3 ++- supervisor/session/rtu/turbine.lua | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua index 78931ec..320d7e4 100644 --- a/supervisor/session/rtu/boiler.lua +++ b/supervisor/session/rtu/boiler.lua @@ -7,6 +7,7 @@ local txnctrl = require("supervisor.session.rtu.txnctrl") local boiler = {} local PROTOCOLS = comms.PROTOCOLS +local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES local MODBUS_FCODE = types.MODBUS_FCODE local rtu_t = types.rtu_t @@ -29,7 +30,7 @@ local PERIODICS = { ---@param out_queue mqueue boiler.new = function (session_id, advert, out_queue) -- type check - if advert.type ~= rtu_t.boiler then + if advert.type ~= RTU_UNIT_TYPES.BOILER then log.error("attempt to instantiate boiler RTU for type '" .. advert.type .. "'. this is a bug.") return nil end diff --git a/supervisor/session/rtu/emachine.lua b/supervisor/session/rtu/emachine.lua index b6c89b6..9428806 100644 --- a/supervisor/session/rtu/emachine.lua +++ b/supervisor/session/rtu/emachine.lua @@ -7,6 +7,7 @@ local txnctrl = require("supervisor.session.rtu.txnctrl") local emachine = {} local PROTOCOLS = comms.PROTOCOLS +local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES local MODBUS_FCODE = types.MODBUS_FCODE local rtu_t = types.rtu_t @@ -27,7 +28,7 @@ local PERIODICS = { ---@param out_queue mqueue emachine.new = function (session_id, advert, out_queue) -- type check - if advert.type ~= rtu_t.energy_machine then + if advert.type ~= RTU_UNIT_TYPES.EMACHINE then log.error("attempt to instantiate emachine RTU for type '" .. advert.type .. "'. this is a bug.") return nil end diff --git a/supervisor/session/rtu/turbine.lua b/supervisor/session/rtu/turbine.lua index ccca068..66429f3 100644 --- a/supervisor/session/rtu/turbine.lua +++ b/supervisor/session/rtu/turbine.lua @@ -7,6 +7,7 @@ local txnctrl = require("supervisor.session.rtu.txnctrl") local turbine = {} local PROTOCOLS = comms.PROTOCOLS +local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES local DUMPING_MODE = types.DUMPING_MODE local MODBUS_FCODE = types.MODBUS_FCODE @@ -30,7 +31,7 @@ local PERIODICS = { ---@param out_queue mqueue turbine.new = function (session_id, advert, out_queue) -- type check - if advert.type ~= rtu_t.turbine then + if advert.type ~= RTU_UNIT_TYPES.TURBINE then log.error("attempt to instantiate turbine RTU for type '" .. advert.type .. "'. this is a bug.") return nil end From d3a926b25aa549693f693644ce399a295fd3fee1 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 14 May 2022 13:32:42 -0400 Subject: [PATCH 193/587] fixed require issues caused by using bootloader --- coordinator/startup.lua | 2 ++ initenv.lua | 18 ++++++++++++++++++ pocket/startup.lua | 4 +++- reactor-plc/startup.lua | 2 ++ rtu/startup.lua | 2 ++ startup.lua | 6 ++---- supervisor/startup.lua | 2 ++ 7 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 initenv.lua diff --git a/coordinator/startup.lua b/coordinator/startup.lua index ab90f72..aa65be4 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -2,6 +2,8 @@ -- Nuclear Generation Facility SCADA Coordinator -- +require("/initenv").init_env() + local log = require("scada-common.log") local ppm = require("scada-common.ppm") local util = require("scada-common.util") diff --git a/initenv.lua b/initenv.lua new file mode 100644 index 0000000..257af39 --- /dev/null +++ b/initenv.lua @@ -0,0 +1,18 @@ +-- +-- Initialize the Boot Environment +-- + +-- initialize booted environment +local init_env = function () + local _require = require("cc.require") + local _env = setmetatable({}, { __index = _ENV }) + + -- overwrite require/package globals + require, package = _require.make(_env, "/") + + -- reset terminal + term.clear() + term.setCursorPos(1, 1) +end + +return { init_env = init_env } diff --git a/pocket/startup.lua b/pocket/startup.lua index aeeaef4..40a0777 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -1,3 +1,5 @@ -- -- SCADA Coordinator Access on a Pocket Computer --- \ No newline at end of file +-- + +require("/initenv").init_env() diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 0aaaeba..aad57f7 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -2,6 +2,8 @@ -- Reactor Programmable Logic Controller -- +require("/initenv").init_env() + local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") local ppm = require("scada-common.ppm") diff --git a/rtu/startup.lua b/rtu/startup.lua index b37e3fd..94ae506 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -2,6 +2,8 @@ -- RTU: Remote Terminal Unit -- +require("/initenv").init_env() + local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") local ppm = require("scada-common.ppm") diff --git a/startup.lua b/startup.lua index 0a5cffb..482c919 100644 --- a/startup.lua +++ b/startup.lua @@ -1,6 +1,6 @@ local util = require("scada-common.util") -local BOOTLOADER_VERSION = "0.1" +local BOOTLOADER_VERSION = "0.2" local println = util.println local println_ts = util.println_ts @@ -43,9 +43,7 @@ else return false end -if exit_code then - println_ts("BOOT> APPLICATION EXITED OK") -else +if not exit_code then println_ts("BOOT> APPLICATION CRASHED") end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 16725b6..53f3e1a 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -2,6 +2,8 @@ -- Nuclear Generation Facility SCADA Supervisor -- +require("/initenv").init_env() + local log = require("scada-common.log") local ppm = require("scada-common.ppm") local util = require("scada-common.util") From 533d398f9d1bd558ee2fd5d01202158db106389b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 14 May 2022 13:34:51 -0400 Subject: [PATCH 194/587] comment change --- initenv.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/initenv.lua b/initenv.lua index 257af39..c66e4c3 100644 --- a/initenv.lua +++ b/initenv.lua @@ -1,5 +1,5 @@ -- --- Initialize the Boot Environment +-- Initialize the Post-Boot Module Environment -- -- initialize booted environment From bc6453de2baa1ed6f8b8e5337e0f67a3c5250090 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 14 May 2022 20:07:26 -0400 Subject: [PATCH 195/587] RTU bugfixes, adjusted comms sleep timer --- rtu/rtu.lua | 2 +- rtu/startup.lua | 21 +++++++++++---------- rtu/threads.lua | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index f46b560..de96c66 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -45,7 +45,7 @@ rtu.init_unit = function () -- return IO count ---@return integer discrete_inputs, integer coils, integer input_regs, integer holding_regs public.io_count = function () - return self.io_count_cache[0], self.io_count_cache[1], self.io_count_cache[2], self.io_count_cache[3] + return self.io_count_cache[1], self.io_count_cache[2], self.io_count_cache[3], self.io_count_cache[4] end -- discrete inputs: single bit read-only diff --git a/rtu/startup.lua b/rtu/startup.lua index 94ae506..654f7a4 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -24,7 +24,7 @@ local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "alpha-v0.6.4" +local RTU_VERSION = "alpha-v0.6.5" local rtu_t = types.rtu_t @@ -94,13 +94,14 @@ local rtu_redstone = config.RTU_REDSTONE local rtu_devices = config.RTU_DEVICES -- redstone interfaces -for reactor_idx = 1, #rtu_redstone do +for entry_idx = 1, #rtu_redstone do local rs_rtu = redstone_rtu.new() - local io_table = rtu_redstone[reactor_idx].io + local io_table = rtu_redstone[entry_idx].io + local io_reactor = rtu_redstone[entry_idx].for_reactor local capabilities = {} - log.debug("init> starting redstone RTU I/O linking for reactor " .. rtu_redstone[reactor_idx].for_reactor .. "...") + log.debug("init> starting redstone RTU I/O linking for reactor " .. io_reactor .. "...") for i = 1, #io_table do local valid = false @@ -116,8 +117,8 @@ for reactor_idx = 1, #rtu_redstone do end if not valid then - local message = "init> invalid redstone definition at index " .. i .. " in definition block #" .. reactor_idx .. - " (for reactor " .. rtu_redstone[reactor_idx].for_reactor .. ")" + local message = "init> invalid redstone definition at index " .. i .. " in definition block #" .. entry_idx .. + " (for reactor " .. io_reactor .. ")" println_ts(message) log.warning(message) else @@ -140,7 +141,7 @@ for reactor_idx = 1, #rtu_redstone do table.insert(capabilities, conf.channel) log.debug("init> linked redstone " .. #capabilities .. ": " .. rsio.to_string(conf.channel) .. " (" .. conf.side .. - ") for reactor " .. rtu_redstone[reactor_idx].for_reactor) + ") for reactor " .. io_reactor) end end @@ -148,8 +149,8 @@ for reactor_idx = 1, #rtu_redstone do local unit = { name = "redstone_io", type = rtu_t.redstone, - index = 1, - reactor = rtu_redstone[reactor_idx].for_reactor, + index = entry_idx, + reactor = io_reactor, device = capabilities, -- use device field for redstone channels rtu = rs_rtu, modbus_io = modbus.new(rs_rtu, false), @@ -160,7 +161,7 @@ for reactor_idx = 1, #rtu_redstone do table.insert(units, unit) - log.debug("init> initialized RTU unit #" .. #units .. ": redstone_io (redstone) [1] for reactor " .. rtu_redstone[reactor_idx].for_reactor) + log.debug("init> initialized RTU unit #" .. #units .. ": redstone_io (redstone) [1] for reactor " .. io_reactor) end -- mounted peripherals diff --git a/rtu/threads.lua b/rtu/threads.lua index 27f5c27..617f3d2 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -23,7 +23,7 @@ local print_ts = util.print_ts local println_ts = util.println_ts local MAIN_CLOCK = 2 -- (2Hz, 40 ticks) -local COMMS_SLEEP = 150 -- (150ms, 3 ticks) +local COMMS_SLEEP = 100 -- (100ms, 2 ticks) -- main thread ---@param smem rtu_shared_memory From 94931ef5a216462cfc597c58d65164e6f6a80cd3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 14 May 2022 20:27:06 -0400 Subject: [PATCH 196/587] #8 bugfixes, redstone RTU session --- scada-common/rsio.lua | 3 +- supervisor/session/rtu.lua | 73 ++++++----- supervisor/session/rtu/boiler.lua | 12 +- supervisor/session/rtu/emachine.lua | 8 +- supervisor/session/rtu/redstone.lua | 183 ++++++++++++++++++++++++++++ supervisor/session/rtu/turbine.lua | 12 +- 6 files changed, 246 insertions(+), 45 deletions(-) create mode 100644 supervisor/session/rtu/redstone.lua diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index d71d777..92e1713 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -11,7 +11,8 @@ local rsio = {} ---@alias IO_LVL integer local IO_LVL = { LOW = 0, - HIGH = 1 + HIGH = 1, + DISCONNECT = 2 -- use for RTU session to indicate this RTU is not connected to this channel } ---@alias IO_DIR integer diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 5dab40c..fab3a96 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -6,6 +6,7 @@ local util = require("scada-common.util") -- supervisor rtu sessions (svrs) local svrs_boiler = require("supervisor.session.rtu.boiler") local svrs_emachine = require("supervisor.session.rtu.emachine") +local svrs_redstone = require("supervisor.session.rtu.redstone") local svrs_turbine = require("supervisor.session.rtu.turbine") local rtu = {} @@ -19,10 +20,26 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts +local RTU_S_CMDS = { +} + +local RTU_S_DATA = { + RS_COMMAND = 1, + UNIT_COMMAND = 2 +} + +rtu.RTU_S_CMDS = RTU_S_CMDS +rtu.RTU_S_DATA = RTU_S_DATA + local PERIODICS = { KEEP_ALIVE = 2.0 } +---@class rs_session_command +---@field reactor integer +---@field channel RS_IO +---@field active boolean + -- create a new RTU session ---@param id integer ---@param in_queue mqueue @@ -42,31 +59,32 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) connected = true, rtu_conn_watchdog = util.new_watchdog(3), last_rtt = 0, + rs_io = {}, units = {} } ---@class rtu_session local public = {} - -- parse the recorded advertisement - local _parse_advertisement = function () + -- parse the recorded advertisement and create unit sub-sessions + local _handle_advertisement = function () self.units = {} for i = 1, #self.advert do - local unit = nil ---@type unit_session + local unit = nil ---@type unit_session|nil ---@type rtu_advertisement local unit_advert = { - type = self.advert[i][0], - index = self.advert[i][1], - reactor = self.advert[i][2], - rsio = self.advert[i][3] + type = self.advert[i][1], + index = self.advert[i][2], + reactor = self.advert[i][3], + rsio = self.advert[i][4] } local u_type = unit_advert.type -- create unit by type if u_type == RTU_UNIT_TYPES.REDSTONE then - + unit = svrs_redstone.new(self.id, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.BOILER then unit = svrs_boiler.new(self.id, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.BOILER_VALVE then @@ -79,27 +97,20 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) unit = svrs_emachine.new(self.id, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.IMATRIX then -- @todo Mekanism 10.1+ + else + log.error(log_header .. "bad advertisement: encountered unsupported RTU type") end if unit ~= nil then table.insert(self.units, unit) else self.units = {} - log.error(log_header .. "bad advertisement; encountered unsupported RTU type") + log.error(log_header .. "bad advertisement: error occured while creating a unit") break end end end - -- send a MODBUS TCP packet - ---@param m_pkt modbus_packet - local _send_modbus = function (m_pkt) - local s_pkt = comms.scada_packet() - s_pkt.make(self.seq_num, PROTOCOLS.MODBUS_TCP, m_pkt.raw_sendable()) - self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) - self.seq_num = self.seq_num + 1 - end - -- send a SCADA management packet ---@param msg_type SCADA_MGMT_TYPES ---@param msg table @@ -137,7 +148,7 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) unit.handle_packet(pkt) end elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then - + -- handle management packet if pkt.type == SCADA_MGMT_TYPES.KEEP_ALIVE then -- keep alive reply if pkt.length == 2 then @@ -160,8 +171,9 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) self.connected = false elseif pkt.type == SCADA_MGMT_TYPES.RTU_ADVERT then -- RTU unit advertisement + -- handle advertisement; this will re-create all unit sub-sessions self.advert = pkt.data - _parse_advertisement() + _handle_advertisement() else log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) end @@ -192,14 +204,6 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) ---@return boolean connected public.iterate = function () if self.connected then - ------------------ - -- update units -- - ------------------ - - for i = 1, #self.units do - self.units[i].update() - end - ------------------ -- handle queue -- ------------------ @@ -218,6 +222,11 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) -- handle instruction elseif msg.qtype == mqueue.TYPE.DATA then -- instruction with body + local cmd = msg.message ---@type queue_data + + if cmd.key == RTU_S_DATA.RS_COMMAND then + local rs_cmd = cmd.val ---@type rs_session_command + end end end @@ -236,6 +245,14 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) return self.connected end + ------------------ + -- update units -- + ------------------ + + for i = 1, #self.units do + self.units[i].update() + end + ---------------------- -- update periodics -- ---------------------- diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua index 320d7e4..c0dd0e6 100644 --- a/supervisor/session/rtu/boiler.lua +++ b/supervisor/session/rtu/boiler.lua @@ -183,19 +183,19 @@ boiler.new = function (session_id, advert, out_queue) -- update this runner ---@param time_now integer milliseconds public.update = function (time_now) - if not self.has_build and self.next_build_req <= time_now then + if not self.periodics.has_build and self.next_build_req <= time_now then _request_build() - self.next_build_req = time_now + PERIODICS.BUILD + self.periodics.next_build_req = time_now + PERIODICS.BUILD end - if self.next_state_req <= time_now then + if self.periodics.next_state_req <= time_now then _request_state() - self.next_state_req = time_now + PERIODICS.STATE + self.periodics.next_state_req = time_now + PERIODICS.STATE end - if self.next_tanks_req <= time_now then + if self.periodics.next_tanks_req <= time_now then _request_tanks() - self.next_tanks_req = time_now + PERIODICS.TANKS + self.periodics.next_tanks_req = time_now + PERIODICS.TANKS end end diff --git a/supervisor/session/rtu/emachine.lua b/supervisor/session/rtu/emachine.lua index 9428806..cdbf21e 100644 --- a/supervisor/session/rtu/emachine.lua +++ b/supervisor/session/rtu/emachine.lua @@ -133,14 +133,14 @@ emachine.new = function (session_id, advert, out_queue) -- update this runner ---@param time_now integer milliseconds public.update = function (time_now) - if not self.has_build and self.next_build_req <= time_now then + if not self.has_build and self.periodics.next_build_req <= time_now then _request_build() - self.next_build_req = time_now + PERIODICS.BUILD + self.periodics.next_build_req = time_now + PERIODICS.BUILD end - if self.next_storage_req <= time_now then + if self.periodics.next_storage_req <= time_now then _request_storage() - self.next_storage_req = time_now + PERIODICS.STORAGE + self.periodics.next_storage_req = time_now + PERIODICS.STORAGE end end diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua new file mode 100644 index 0000000..1e1c1b9 --- /dev/null +++ b/supervisor/session/rtu/redstone.lua @@ -0,0 +1,183 @@ +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local rsio = require("scada-common.rsio") +local types = require("scada-common.types") + +local txnctrl = require("supervisor.session.rtu.txnctrl") + +local redstone = {} + +local PROTOCOLS = comms.PROTOCOLS +local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local MODBUS_FCODE = types.MODBUS_FCODE + +local RS_IO = rsio.IO +local IO_LVL = rsio.IO_LVL +local IO_DIR = rsio.IO_DIR +local IO_MODE = rsio.IO_MODE + +local rtu_t = types.rtu_t + +local TXN_TYPES = { + DI_READ = 0, + INPUT_REG_READ = 1 +} + +local PERIODICS = { + INPUT_READ = 200 +} + +-- create a new redstone rtu session runner +---@param session_id integer +---@param advert rtu_advertisement +---@param out_queue mqueue +redstone.new = function (session_id, advert, out_queue) + -- type check + if advert.type ~= RTU_UNIT_TYPES.REDSTONE then + log.error("attempt to instantiate redstone RTU for type '" .. advert.type .. "'. this is a bug.") + return nil + end + + local log_tag = "session.rtu(" .. session_id .. ").redstone(" .. advert.index .. "): " + + local self = { + uid = advert.index, + reactor = advert.reactor, + out_q = out_queue, + transaction_controller = txnctrl.new(), + has_di = false, + has_ai = false, + periodics = { + next_di_req = 0, + next_ir_req = 0, + }, + io_list = { + digital_in = {}, -- discrete inputs + digital_out = {}, -- coils + analog_in = {}, -- input registers + analog_out = {} -- holding registers + }, + db = {} + } + + ---@class unit_session + local public = {} + + -- INITIALIZE -- + + for _ = 1, #RS_IO do + table.insert(self.db, IO_LVL.DISCONNECT) + end + + for i = 1, #advert.rsio do + local channel = advert.rsio[i] + local mode = rsio.get_io_mode(channel) + + if mode == IO_MODE.DIGITAL_IN then + self.has_di = true + table.insert(self.io_list.digital_in, channel) + elseif mode == IO_MODE.DIGITAL_OUT then + table.insert(self.io_list.digital_out, channel) + elseif mode == IO_MODE.ANALOG_IN then + self.has_ai = true + table.insert(self.io_list.analog_in, channel) + elseif mode == IO_MODE.ANALOG_OUT then + table.insert(self.io_list.analog_out, channel) + else + -- should be unreachable code, we already validated channels + log.error(log_tag .. "failed to identify advertisement channel IO mode (" .. channel .. ")", true) + return nil + end + + self.db[channel] = IO_LVL.LOW + end + + + -- PRIVATE FUNCTIONS -- + + local _send_request = function (txn_type, f_code, register_range) + local m_pkt = comms.modbus_packet() + local txn_id = self.transaction_controller.create(txn_type) + + m_pkt.make(txn_id, self.uid, f_code, register_range) + + self.out_q.push_packet(m_pkt) + end + + -- query discrete inputs + local _request_discrete_inputs = function () + _send_request(TXN_TYPES.DI_READ, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, #self.io_list.digital_in }) + end + + -- query input registers + local _request_input_registers = function () + _send_request(TXN_TYPES.INPUT_REG_READ, MODBUS_FCODE.READ_INPUT_REGS, { 1, #self.io_list.analog_in }) + end + + -- PUBLIC FUNCTIONS -- + + -- handle a packet + ---@param m_pkt modbus_frame + public.handle_packet = function (m_pkt) + local success = false + + if m_pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then + if m_pkt.unit_id == self.uid then + local txn_type = self.transaction_controller.resolve(m_pkt.txn_id) + if txn_type == TXN_TYPES.DI_READ then + -- build response + if m_pkt.length == 1 then + self.db.build.max_energy = m_pkt.data[1] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (emachine.build)") + end + elseif txn_type == TXN_TYPES.INPUT_REG_READ then + -- storage response + if m_pkt.length == 3 then + self.db.storage.energy = m_pkt.data[1] + self.db.storage.energy_need = m_pkt.data[2] + self.db.storage.energy_fill = m_pkt.data[3] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (emachine.storage)") + end + elseif txn_type == nil then + log.error(log_tag .. "unknown transaction reply") + else + log.error(log_tag .. "unknown transaction type " .. txn_type) + end + else + log.error(log_tag .. "wrong unit ID: " .. m_pkt.unit_id, true) + end + else + log.error(log_tag .. "illegal packet type " .. m_pkt.scada_frame.protocol(), true) + end + + return success + end + + public.get_uid = function () return self.uid end + public.get_reactor = function () return self.reactor end + public.get_db = function () return self.db end + + -- update this runner + ---@param time_now integer milliseconds + public.update = function (time_now) + if self.has_di then + if self.periodics.next_di_req <= time_now then + _request_discrete_inputs() + self.periodics.next_di_req = time_now + PERIODICS.BUILD + end + end + + if self.has_ai then + if self.periodics.next_ir_req <= time_now then + _request_input_registers() + self.periodics.next_ir_req = time_now + PERIODICS.STORAGE + end + end + end + + return public +end + +return redstone diff --git a/supervisor/session/rtu/turbine.lua b/supervisor/session/rtu/turbine.lua index 66429f3..292ff95 100644 --- a/supervisor/session/rtu/turbine.lua +++ b/supervisor/session/rtu/turbine.lua @@ -174,19 +174,19 @@ turbine.new = function (session_id, advert, out_queue) -- update this runner ---@param time_now integer milliseconds public.update = function (time_now) - if not self.has_build and self.next_build_req <= time_now then + if not self.has_build and self.periodics.next_build_req <= time_now then _request_build() - self.next_build_req = time_now + PERIODICS.BUILD + self.periodics.next_build_req = time_now + PERIODICS.BUILD end - if self.next_state_req <= time_now then + if self.periodics.next_state_req <= time_now then _request_state() - self.next_state_req = time_now + PERIODICS.STATE + self.periodics.next_state_req = time_now + PERIODICS.STATE end - if self.next_tanks_req <= time_now then + if self.periodics.next_tanks_req <= time_now then _request_tanks() - self.next_tanks_req = time_now + PERIODICS.TANKS + self.periodics.next_tanks_req = time_now + PERIODICS.TANKS end end From 374bfb7a19c283e3ab12647d6a1d83ba7df74024 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 14 May 2022 20:42:28 -0400 Subject: [PATCH 197/587] #8 handle redstone RTU MODBUS replies, bugfixes --- supervisor/session/rtu/redstone.lua | 30 +++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index 1e1c1b9..1477773 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -125,20 +125,26 @@ redstone.new = function (session_id, advert, out_queue) if m_pkt.unit_id == self.uid then local txn_type = self.transaction_controller.resolve(m_pkt.txn_id) if txn_type == TXN_TYPES.DI_READ then - -- build response - if m_pkt.length == 1 then - self.db.build.max_energy = m_pkt.data[1] + -- discrete input read response + if m_pkt.length == #self.io_list.digital_in then + for i = 1, m_pkt.length do + local channel = self.io_list.digital_in[i] + local value = m_pkt.data[i] + self.db[channel] = value + end else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (emachine.build)") + log.debug(log_tag .. "MODBUS transaction reply length mismatch (redstone.discrete_input_read)") end elseif txn_type == TXN_TYPES.INPUT_REG_READ then - -- storage response - if m_pkt.length == 3 then - self.db.storage.energy = m_pkt.data[1] - self.db.storage.energy_need = m_pkt.data[2] - self.db.storage.energy_fill = m_pkt.data[3] + -- input register read response + if m_pkt.length == #self.io_list.analog_in then + for i = 1, m_pkt.length do + local channel = self.io_list.analog_in[i] + local value = m_pkt.data[i] + self.db[channel] = value + end else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (emachine.storage)") + log.debug(log_tag .. "MODBUS transaction reply length mismatch (redstone.input_reg_read)") end elseif txn_type == nil then log.error(log_tag .. "unknown transaction reply") @@ -165,14 +171,14 @@ redstone.new = function (session_id, advert, out_queue) if self.has_di then if self.periodics.next_di_req <= time_now then _request_discrete_inputs() - self.periodics.next_di_req = time_now + PERIODICS.BUILD + self.periodics.next_di_req = time_now + PERIODICS.INPUT_READ end end if self.has_ai then if self.periodics.next_ir_req <= time_now then _request_input_registers() - self.periodics.next_ir_req = time_now + PERIODICS.STORAGE + self.periodics.next_ir_req = time_now + PERIODICS.INPUT_READ end end end From 4834dbf7811fc707aa4ec3de4aaa1c828d7e5f71 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 16 May 2022 10:38:47 -0400 Subject: [PATCH 198/587] changed redstone I/O capabilities, added analog read/write scaling functions --- scada-common/rsio.lua | 98 +++++++++++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 37 deletions(-) diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 92e1713..4acc3d7 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -23,10 +23,10 @@ local IO_DIR = { ---@alias IO_MODE integer local IO_MODE = { - DIGITAL_OUT = 0, - DIGITAL_IN = 1, - ANALOG_OUT = 2, - ANALOG_IN = 3 + DIGITAL_IN = 0, + DIGITAL_OUT = 1, + ANALOG_IN = 2, + ANALOG_OUT = 3 } ---@alias RS_IO integer @@ -35,37 +35,35 @@ local RS_IO = { -- facility F_SCRAM = 1, -- active low, facility-wide scram - F_AE2_LIVE = 2, -- active high, indicates whether AE2 network is online (hint: use redstone P2P) -- reactor - R_SCRAM = 3, -- active low, reactor scram - R_ENABLE = 4, -- active high, reactor enable + R_SCRAM = 2, -- active low, reactor scram + R_ENABLE = 3, -- active high, reactor enable -- digital outputs -- + -- facility + F_ALARM = 4, -- active high, facility safety alarm + -- waste WASTE_PO = 5, -- active low, polonium routing WASTE_PU = 6, -- active low, plutonium routing WASTE_AM = 7, -- active low, antimatter routing -- reactor - R_SCRAMMED = 8, -- active high, if the reactor is scrammed - R_AUTO_SCRAM = 9, -- active high, if the reactor was automatically scrammed - R_ACTIVE = 10, -- active high, if the reactor is active - R_AUTO_CTRL = 11, -- active high, if the reactor burn rate is automatic - R_DMG_CRIT = 12, -- active high, if the reactor damage is critical - R_HIGH_TEMP = 13, -- active high, if the reactor is at a high temperature - R_NO_COOLANT = 14, -- active high, if the reactor has no coolant - R_EXCESS_HC = 15, -- active high, if the reactor has excess heated coolant - R_EXCESS_WS = 16, -- active high, if the reactor has excess waste - R_INSUFF_FUEL = 17, -- active high, if the reactor has insufficent fuel - R_PLC_TIMEOUT = 18, -- active high, if the reactor PLC has not been heard from - - -- analog outputs -- - - A_R_BURN_RATE = 19, -- reactor burn rate percentage - A_B_BOIL_RATE = 20, -- boiler boil rate percentage - A_T_FLOW_RATE = 21 -- turbine flow rate percentage + R_ALARM = 8, -- active high, reactor safety alarm + R_SCRAMMED = 9, -- active high, if the reactor is scrammed + R_AUTO_SCRAM = 10, -- active high, if the reactor was automatically scrammed + R_ACTIVE = 11, -- active high, if the reactor is active + R_AUTO_CTRL = 12, -- active high, if the reactor burn rate is automatic + R_DMG_CRIT = 13, -- active high, if the reactor damage is critical + R_HIGH_TEMP = 14, -- active high, if the reactor is at a high temperature + R_NO_COOLANT = 15, -- active high, if the reactor has no coolant + R_EXCESS_HC = 16, -- active high, if the reactor has excess heated coolant + R_EXCESS_WS = 17, -- active high, if the reactor has excess waste + R_INSUFF_FUEL = 18, -- active high, if the reactor has insufficent fuel + R_PLC_FAULT = 19, -- active high, if the reactor PLC reports a device access fault + R_PLC_TIMEOUT = 20 -- active high, if the reactor PLC has not been heard from } rsio.IO_LVL = IO_LVL @@ -82,12 +80,13 @@ rsio.IO = RS_IO rsio.to_string = function (channel) local names = { "F_SCRAM", - "F_AE2_LIVE", "R_SCRAM", "R_ENABLE", + "F_ALARM", "WASTE_PO", "WASTE_PU", "WASTE_AM", + "R_ALARM", "R_SCRAMMED", "R_AUTO_SCRAM", "R_ACTIVE", @@ -98,10 +97,8 @@ rsio.to_string = function (channel) "R_EXCESS_HC", "R_EXCESS_WS", "R_INSUFF_FUEL", - "R_PLC_TIMEOUT", - "A_R_BURN_RATE", - "A_B_BOIL_RATE", - "A_T_FLOW_RATE" + "R_PLC_FAULT", + "R_PLC_TIMEOUT" } if channel > 0 and channel <= #names then @@ -124,18 +121,20 @@ local _DO_ACTIVE_LOW = function (on) return _TRINARY(on, IO_LVL.LOW, IO_LVL.HIGH local RS_DIO_MAP = { -- F_SCRAM { _f = _DI_ACTIVE_LOW, mode = IO_DIR.IN }, - -- F_AE2_LIVE - { _f = _DI_ACTIVE_HIGH, mode = IO_DIR.IN }, -- R_SCRAM { _f = _DI_ACTIVE_LOW, mode = IO_DIR.IN }, -- R_ENABLE { _f = _DI_ACTIVE_HIGH, mode = IO_DIR.IN }, + -- F_ALARM + { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- WASTE_PO { _f = _DO_ACTIVE_LOW, mode = IO_DIR.OUT }, -- WASTE_PU { _f = _DO_ACTIVE_LOW, mode = IO_DIR.OUT }, -- WASTE_AM { _f = _DO_ACTIVE_LOW, mode = IO_DIR.OUT }, + -- R_ALARM + { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_SCRAMMED { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_AUTO_SCRAM @@ -156,6 +155,8 @@ local RS_DIO_MAP = { { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_INSUFF_FUEL { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, + -- R_PLC_FAULT + { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_PLC_TIMEOUT { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT } } @@ -166,12 +167,13 @@ local RS_DIO_MAP = { rsio.get_io_mode = function (channel) local modes = { IO_MODE.DIGITAL_IN, -- F_SCRAM - IO_MODE.DIGITAL_IN, -- F_AE2_LIVE IO_MODE.DIGITAL_IN, -- R_SCRAM IO_MODE.DIGITAL_IN, -- R_ENABLE + IO_MODE.DIGITAL_OUT, -- F_ALARM IO_MODE.DIGITAL_OUT, -- WASTE_PO IO_MODE.DIGITAL_OUT, -- WASTE_PU IO_MODE.DIGITAL_OUT, -- WASTE_AM + IO_MODE.DIGITAL_OUT, -- R_ALARM IO_MODE.DIGITAL_OUT, -- R_SCRAMMED IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM IO_MODE.DIGITAL_OUT, -- R_ACTIVE @@ -182,10 +184,8 @@ rsio.get_io_mode = function (channel) IO_MODE.DIGITAL_OUT, -- R_EXCESS_HC IO_MODE.DIGITAL_OUT, -- R_EXCESS_WS IO_MODE.DIGITAL_OUT, -- R_INSUFF_FUEL - IO_MODE.DIGITAL_OUT, -- R_PLC_TIMEOUT - IO_MODE.ANALOG_OUT, -- A_R_BURN_RATE - IO_MODE.ANALOG_OUT, -- A_B_BOIL_RATE - IO_MODE.ANALOG_OUT -- A_T_FLOW_RATE + IO_MODE.DIGITAL_OUT, -- R_PLC_FAULT + IO_MODE.DIGITAL_OUT -- R_PLC_TIMEOUT } if channel > 0 and channel <= #modes then @@ -205,7 +205,7 @@ local RS_SIDES = rs.getSides() ---@param channel RS_IO ---@return boolean valid rsio.is_valid_channel = function (channel) - return (channel ~= nil) and (channel > 0) and (channel <= RS_IO.A_T_FLOW_RATE) + return (channel ~= nil) and (channel > 0) and (channel <= #RS_IO) end -- check if a side is valid @@ -266,4 +266,28 @@ rsio.digital_is_active = function (channel, level) end end +---------------- +-- ANALOG I/O -- +---------------- + +-- read an analog value scaled from min to max +---@param rs_value number redstone reading (0 to 15) +---@param min number minimum of range +---@param max number maximum of range +---@return number value scaled reading (min to max) +rsio.analog_read = function (rs_value, min, max) + local value = rs_value / 15 + return (value * (max - min)) + min +end + +-- write an analog value from the provided scale range +---@param value number value to write (from min to max range) +---@param min number minimum of range +---@param max number maximum of range +---@return number rs_value scaled redstone reading (0 to 15) +rsio.analog_write = function (value, min, max) + local scaled_value = (value - min) / (max - min) + return scaled_value * 15 +end + return rsio From 530a40b0aad66087ea4b87a9dc4103ba2aadb726 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 16 May 2022 11:52:03 -0400 Subject: [PATCH 199/587] changed DISCONNECT constant value to -1 to not conflict with redstone values --- scada-common/rsio.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 4acc3d7..5b9970a 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -12,7 +12,7 @@ local rsio = {} local IO_LVL = { LOW = 0, HIGH = 1, - DISCONNECT = 2 -- use for RTU session to indicate this RTU is not connected to this channel + DISCONNECT = -1 -- use for RTU session to indicate this RTU is not connected to this channel } ---@alias IO_DIR integer From 11b89b679da7ec731d9167f73918d1534981bb4b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 16 May 2022 11:54:34 -0400 Subject: [PATCH 200/587] #8 redstone RTU output commands --- supervisor/session/rtu.lua | 45 ++++++++++++-- supervisor/session/rtu/redstone.lua | 91 +++++++++++++++++++++++++++-- 2 files changed, 127 insertions(+), 9 deletions(-) diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index fab3a96..59cf0b1 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -1,6 +1,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") +local rsio = require("scada-common.rsio") local util = require("scada-common.util") -- supervisor rtu sessions (svrs) @@ -38,7 +39,7 @@ local PERIODICS = { ---@class rs_session_command ---@field reactor integer ---@field channel RS_IO ----@field active boolean +---@field value integer|boolean -- create a new RTU session ---@param id integer @@ -59,7 +60,7 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) connected = true, rtu_conn_watchdog = util.new_watchdog(3), last_rtt = 0, - rs_io = {}, + rs_io_q = {}, units = {} } @@ -69,8 +70,11 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) -- parse the recorded advertisement and create unit sub-sessions local _handle_advertisement = function () self.units = {} + self.rs_io_q = {} + for i = 1, #self.advert do local unit = nil ---@type unit_session|nil + local rs_in_q = nil ---@type mqueue|nil ---@type rtu_advertisement local unit_advert = { @@ -84,7 +88,7 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) -- create unit by type if u_type == RTU_UNIT_TYPES.REDSTONE then - unit = svrs_redstone.new(self.id, unit_advert, self.out_q) + unit, rs_in_q = svrs_redstone.new(self.id, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.BOILER then unit = svrs_boiler.new(self.id, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.BOILER_VALVE then @@ -103,9 +107,23 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) if unit ~= nil then table.insert(self.units, unit) + + if self.rs_io_q[unit_advert.reactor] == nil then + self.rs_io_q[unit_advert.reactor] = rs_in_q + else + self.units = {} + self.rs_io_q = {} + log.error(log_header .. "bad advertisement: duplicate redstone RTU for reactor " .. unit_advert.reactor) + break + end else self.units = {} - log.error(log_header .. "bad advertisement: error occured while creating a unit") + self.rs_io_q = {} + + local type_string = comms.advert_type_to_rtu_t(u_type) + if type_string == nil then type_string = "unknown" end + + log.error(log_header .. "bad advertisement: error occured while creating a unit (type is " .. type_string .. ")") break end end @@ -226,6 +244,21 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) if cmd.key == RTU_S_DATA.RS_COMMAND then local rs_cmd = cmd.val ---@type rs_session_command + + if rsio.is_valid_channel(rs_cmd.channel) then + cmd.key = svrs_redstone.RS_RTU_S_DATA.RS_COMMAND + if rs_cmd.reactor == nil then + -- for all reactors (facility) + for i = 1, #self.rs_io_q do + local q = self.rs_io.q[i] ---@type mqueue + q.push_data(msg) + end + elseif self.rs_io_q[rs_cmd.reactor] ~= nil then + -- for just one reactor + local q = self.rs_io.q[rs_cmd.reactor] ---@type mqueue + q.push_data(msg) + end + end end end end @@ -249,8 +282,10 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) -- update units -- ------------------ + local time_now = util.time() + for i = 1, #self.units do - self.units[i].update() + self.units[i].update(time_now) end ---------------------- diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index 1477773..4380567 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -1,7 +1,9 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") +local mqueue= require("scada-common.mqueue") local rsio = require("scada-common.rsio") local types = require("scada-common.types") +local util = require("scada-common.util") local txnctrl = require("supervisor.session.rtu.txnctrl") @@ -18,9 +20,21 @@ local IO_MODE = rsio.IO_MODE local rtu_t = types.rtu_t +local RS_RTU_S_CMDS = { +} + +local RS_RTU_S_DATA = { + RS_COMMAND = 1 +} + +redstone.RS_RTU_S_CMDS = RS_RTU_S_CMDS +redstone.RS_RTU_S_DATA = RS_RTU_S_DATA + local TXN_TYPES = { DI_READ = 0, - INPUT_REG_READ = 1 + COIL_WRITE = 1, + INPUT_REG_READ = 2, + HOLD_REG_WRITE = 3 } local PERIODICS = { @@ -43,6 +57,7 @@ redstone.new = function (session_id, advert, out_queue) local self = { uid = advert.index, reactor = advert.reactor, + in_q = mqueue.new(), out_q = out_queue, transaction_controller = txnctrl.new(), has_di = false, @@ -65,10 +80,12 @@ redstone.new = function (session_id, advert, out_queue) -- INITIALIZE -- + -- create all channels as disconnected for _ = 1, #RS_IO do table.insert(self.db, IO_LVL.DISCONNECT) end + -- setup I/O for i = 1, #advert.rsio do local channel = advert.rsio[i] local mode = rsio.get_io_mode(channel) @@ -95,11 +112,11 @@ redstone.new = function (session_id, advert, out_queue) -- PRIVATE FUNCTIONS -- - local _send_request = function (txn_type, f_code, register_range) + local _send_request = function (txn_type, f_code, parameters) local m_pkt = comms.modbus_packet() local txn_id = self.transaction_controller.create(txn_type) - m_pkt.make(txn_id, self.uid, f_code, register_range) + m_pkt.make(txn_id, self.uid, f_code, parameters) self.out_q.push_packet(m_pkt) end @@ -114,6 +131,16 @@ redstone.new = function (session_id, advert, out_queue) _send_request(TXN_TYPES.INPUT_REG_READ, MODBUS_FCODE.READ_INPUT_REGS, { 1, #self.io_list.analog_in }) end + -- write coil output + local _write_coil = function (coil, value) + _send_request(TXN_TYPES.COIL_WRITE, MODBUS_FCODE.WRITE_MUL_COILS, { coil, value }) + end + + -- write holding register output + local _write_holding_register = function (reg, value) + _send_request(TXN_TYPES.HOLD_REG_WRITE, MODBUS_FCODE.WRITE_MUL_HOLD_REGS, { reg, value }) + end + -- PUBLIC FUNCTIONS -- -- handle a packet @@ -168,6 +195,61 @@ redstone.new = function (session_id, advert, out_queue) -- update this runner ---@param time_now integer milliseconds public.update = function (time_now) + -- check command queue + while self.in_q.ready() do + -- get a new message to process + local msg = self.in_q.pop() + + if msg ~= nil then + if msg.qtype == mqueue.TYPE.DATA then + -- instruction with body + local cmd = msg.message ---@type queue_data + if cmd.key == RS_RTU_S_DATA.RS_COMMAND then + local rs_cmd = cmd.val ---@type rs_session_command + + if self.db[rs_cmd.channel] ~= IO_LVL.DISCONNECT then + -- we have this as a connected channel + local mode = rsio.get_io_mode(rs_cmd.channel) + if mode == IO_MODE.DIGITAL_OUT then + -- record the value for retries + self.db[rs_cmd.channel] = rs_cmd.value + + -- find the coil address then write to it + for i = 0, #self.digital_out do + if self.digital_out[i] == rs_cmd.channel then + _write_coil(i, rs_cmd.value) + break + end + end + elseif mode == IO_MODE.ANALOG_OUT then + -- record the value for retries + self.db[rs_cmd.channel] = rs_cmd.value + + -- find the holding register address then write to it + for i = 0, #self.analog_out do + if self.analog_out[i] == rs_cmd.channel then + _write_holding_register(i, rs_cmd.value) + break + end + end + elseif mode ~= nil then + log.debug(log_tag .. "attemted write to non D/O or A/O mode " .. mode) + end + end + end + end + end + + -- max 100ms spent processing queue + if util.time() - time_now > 100 then + log.warning(log_tag .. "exceeded 100ms queue process limit") + break + end + end + + time_now = util.time() + + -- poll digital inputs if self.has_di then if self.periodics.next_di_req <= time_now then _request_discrete_inputs() @@ -175,6 +257,7 @@ redstone.new = function (session_id, advert, out_queue) end end + -- poll analog inputs if self.has_ai then if self.periodics.next_ir_req <= time_now then _request_input_registers() @@ -183,7 +266,7 @@ redstone.new = function (session_id, advert, out_queue) end end - return public + return public, self.in_q end return redstone From bdd8af18730e7b93433f62a693dcdc435105f577 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 16 May 2022 12:50:51 -0400 Subject: [PATCH 201/587] dmesg logging --- scada-common/log.lua | 70 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/scada-common/log.lua b/scada-common/log.lua index 71e4495..3ea4262 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -21,7 +21,8 @@ local LOG_DEBUG = true local _log_sys = { path = "/log.txt", mode = MODE.APPEND, - file = nil + file = nil, + dmesg_out = nil } ---@type function @@ -30,7 +31,8 @@ local free_space = fs.getFreeSpace -- initialize logger ---@param path string file path ---@param write_mode MODE -log.init = function (path, write_mode) +---@param dmesg_redirect? table terminal/window to direct dmesg to +log.init = function (path, write_mode, dmesg_redirect) _log_sys.path = path _log_sys.mode = write_mode @@ -39,6 +41,12 @@ log.init = function (path, write_mode) else _log_sys.file = fs.open(path, "w+") end + + if dmesg_redirect then + _log_sys.dmesg_out = dmesg_redirect + else + _log_sys.dmesg_out = term.current() + end end -- private log write function @@ -76,6 +84,64 @@ local _log = function (msg) end end +-- write a message to the dmesg output +---@param msg string message to write +local _write = function (msg) + local out = _log_sys.dmesg_out + local out_w, out_h = out.getSize() + + local lines = { msg } + + -- wrap if needed + if string.len(msg) > out_w then + local remaining = true + local s_start = 1 + local s_end = out_w + local i = 1 + + lines = {} + + while remaining do + local line = string.sub(msg, s_start, s_end) + + if line == "" then + remaining = false + else + lines[i] = line + + s_start = s_end + 1 + s_end = s_end + out_w + i = i + 1 + end + end + end + + -- output message + for i = 1, #lines do + local cur_x, cur_y = out.getCursorPos() + + if cur_x > 1 then + if cur_y == out_h then + out.scroll(1) + out.setCursorPos(1, cur_y) + else + out.setCursorPos(1, cur_y + 1) + end + end + + out.write(lines[i]) + end +end + +-- dmesg style logging for boot because I like linux-y things +---@param msg string message +---@param show_term? boolean whether or not to show on terminal output +log.dmesg = function (msg, show_term) + local message = string.format("[%10.3f] ", os.clock()) .. msg + if show_term then _write(message) end + _log(message) +end + -- log debug messages ---@param msg string message ---@param trace? boolean include file trace From 136b09d7f2999ddf95bb77f244215fc7f50e4a11 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 16 May 2022 17:11:46 -0400 Subject: [PATCH 202/587] util filter table --- scada-common/util.lua | 27 +++++++++++++++++++++++++++ supervisor/session/rtu/txnctrl.lua | 17 +---------------- supervisor/session/svsessions.lua | 21 +++++---------------- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/scada-common/util.lua b/scada-common/util.lua index 8dffd40..38cfc1f 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -80,6 +80,33 @@ util.adaptive_delay = function (target_timing, last_update) return util.time() end +-- TABLE UTILITIES -- + +-- delete elements from a table if the passed function returns false when passed a table element +-- +-- put briefly: deletes elements that return false, keeps elements that return true +---@param t table table to remove elements from +---@param f function should return false to delete an element when passed the element: f(elem) = true|false +---@param on_delete? function optional function to execute on deletion, passed the table element to be deleted as the parameter +util.filter_table = function (t, f, on_delete) + local move_to = 1 + for i = 1, #t do + local element = t[i] + if element ~= nil then + if f(element) then + if t[move_to] == nil then + t[move_to] = element + t[i] = nil + end + move_to = move_to + 1 + else + if on_delete then on_delete(element) end + t[i] = nil + end + end + end +end + -- MEKANISM POWER -- -- function kFE(fe) return fe / 1000 end diff --git a/supervisor/session/rtu/txnctrl.lua b/supervisor/session/rtu/txnctrl.lua index 3d74484..2d6be2e 100644 --- a/supervisor/session/rtu/txnctrl.lua +++ b/supervisor/session/rtu/txnctrl.lua @@ -66,22 +66,7 @@ txnctrl.new = function () -- close timed-out transactions public.cleanup = function () local now = util.time() - - local move_to = 1 - for i = 1, public.length() do - local txn = self.list[i] - if txn ~= nil then - if txn.expiry <= now then - self.list[i] = nil - else - if self.list[move_to] == nil then - self.list[move_to] = txn - self.list[i] = nil - end - move_to = move_to + 1 - end - end - end + util.filter_table(self.list, function (txn) return txn.expiry > now end) end -- clear the transaction list diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 88c57cd..d0b76cc 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -1,5 +1,6 @@ local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") +local util = require("scada-common.util") local coordinator = require("supervisor.session.coordinator") local plc = require("supervisor.session.plc") @@ -99,22 +100,10 @@ end -- delete any closed sessions ---@param sessions table local function _free_closed(sessions) - local move_to = 1 - for i = 1, #sessions do - local session = sessions[i] ---@type plc_session_struct - if session ~= nil then - if session.open then - if sessions[move_to] == nil then - sessions[move_to] = session - sessions[i] = nil - end - move_to = move_to + 1 - else - log.debug("free'ing closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) - sessions[i] = nil - end - end - end + local f = function (session) return session.open end + local on_delete = function (session) log.debug("free'ing closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) end + + util.filter_table(sessions, f, on_delete) end -- PUBLIC FUNCTIONS -- From 0eff8a3e6ab47f1065d78acbea9e3a87b96b3ea1 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 16 May 2022 17:13:54 -0400 Subject: [PATCH 203/587] #8 cleaned up closing logic, added connected flags to RTU unit sessions --- supervisor/session/plc.lua | 14 +++++++++----- supervisor/session/rtu.lua | 19 ++++++++++++++----- supervisor/session/rtu/boiler.lua | 4 ++++ supervisor/session/rtu/emachine.lua | 4 ++++ supervisor/session/rtu/redstone.lua | 4 ++++ supervisor/session/rtu/turbine.lua | 4 ++++ 6 files changed, 39 insertions(+), 10 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index c36dbda..f799987 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -202,6 +202,12 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) self.sDB.mek_struct.max_burn = mek_data[8] end + -- mark this PLC session as closed, stop watchdog + local _close = function () + self.rtu_conn_watchdog.cancel() + self.connected = false + end + -- send an RPLC packet ---@param msg_type RPLC_TYPES ---@param msg table @@ -392,7 +398,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end elseif pkt.type == SCADA_MGMT_TYPES.CLOSE then -- close the session - self.connected = false + _close() else log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) end @@ -427,13 +433,12 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) -- check if a timer matches this session's watchdog public.check_wd = function (timer) - return self.plc_conn_watchdog.is_timer(timer) + return self.plc_conn_watchdog.is_timer(timer) and self.connected end -- close the connection public.close = function () - self.plc_conn_watchdog.cancel() - self.connected = false + _close() _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) println("connection to reactor " .. self.for_reactor .. " PLC closed by server") log.info(log_header .. "session closed by server") @@ -506,7 +511,6 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) -- exit if connection was closed if not self.connected then - self.plc_conn_watchdog.cancel() println("connection to reactor " .. self.for_reactor .. " PLC closed by remote host") log.info(log_header .. "session closed by remote host") return self.connected diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 59cf0b1..33a66d7 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -129,6 +129,17 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) end end + -- mark this RTU session as closed, stop watchdog + local _close = function () + self.rtu_conn_watchdog.cancel() + self.connected = false + + -- mark all RTU unit sessions as closed so the reactor unit knows + for i = 1, #self.units do + self.units[i].close() + end + end + -- send a SCADA management packet ---@param msg_type SCADA_MGMT_TYPES ---@param msg table @@ -186,7 +197,7 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) end elseif pkt.type == SCADA_MGMT_TYPES.CLOSE then -- close the session - self.connected = false + _close() elseif pkt.type == SCADA_MGMT_TYPES.RTU_ADVERT then -- RTU unit advertisement -- handle advertisement; this will re-create all unit sub-sessions @@ -206,13 +217,12 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) -- check if a timer matches this session's watchdog ---@param timer number public.check_wd = function (timer) - return self.rtu_conn_watchdog.is_timer(timer) + return self.rtu_conn_watchdog.is_timer(timer) and self.connected end -- close the connection public.close = function () - self.rtu_conn_watchdog.cancel() - self.connected = false + _close() _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) println(log_header .. "connection to RTU closed by server") log.info(log_header .. "session closed by server") @@ -272,7 +282,6 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) -- exit if connection was closed if not self.connected then - self.rtu_conn_watchdog.cancel() println(log_header .. "connection to RTU closed by remote host") log.info(log_header .. "session closed by remote host") return self.connected diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua index c0dd0e6..2b8047f 100644 --- a/supervisor/session/rtu/boiler.lua +++ b/supervisor/session/rtu/boiler.lua @@ -42,6 +42,7 @@ boiler.new = function (session_id, advert, out_queue) reactor = advert.reactor, out_q = out_queue, transaction_controller = txnctrl.new(), + connected = true, has_build = false, periodics = { next_build_req = 0, @@ -180,6 +181,9 @@ boiler.new = function (session_id, advert, out_queue) public.get_reactor = function () return self.reactor end public.get_db = function () return self.db end + public.close = function () self.connected = false end + public.is_connected = function () return self.connected end + -- update this runner ---@param time_now integer milliseconds public.update = function (time_now) diff --git a/supervisor/session/rtu/emachine.lua b/supervisor/session/rtu/emachine.lua index cdbf21e..8e2a2e8 100644 --- a/supervisor/session/rtu/emachine.lua +++ b/supervisor/session/rtu/emachine.lua @@ -41,6 +41,7 @@ emachine.new = function (session_id, advert, out_queue) reactor = 0, out_q = out_queue, transaction_controller = txnctrl.new(), + connected = true, has_build = false, periodics = { next_build_req = 0, @@ -130,6 +131,9 @@ emachine.new = function (session_id, advert, out_queue) public.get_reactor = function () return self.reactor end public.get_db = function () return self.db end + public.close = function () self.connected = false end + public.is_connected = function () return self.connected end + -- update this runner ---@param time_now integer milliseconds public.update = function (time_now) diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index 4380567..b5f44ea 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -60,6 +60,7 @@ redstone.new = function (session_id, advert, out_queue) in_q = mqueue.new(), out_q = out_queue, transaction_controller = txnctrl.new(), + connected = true, has_di = false, has_ai = false, periodics = { @@ -192,6 +193,9 @@ redstone.new = function (session_id, advert, out_queue) public.get_reactor = function () return self.reactor end public.get_db = function () return self.db end + public.close = function () self.connected = false end + public.is_connected = function () return self.connected end + -- update this runner ---@param time_now integer milliseconds public.update = function (time_now) diff --git a/supervisor/session/rtu/turbine.lua b/supervisor/session/rtu/turbine.lua index 292ff95..e6d41c2 100644 --- a/supervisor/session/rtu/turbine.lua +++ b/supervisor/session/rtu/turbine.lua @@ -43,6 +43,7 @@ turbine.new = function (session_id, advert, out_queue) reactor = advert.reactor, out_q = out_queue, transaction_controller = txnctrl.new(), + connected = true, has_build = false, periodics = { next_build_req = 0, @@ -171,6 +172,9 @@ turbine.new = function (session_id, advert, out_queue) public.get_reactor = function () return self.reactor end public.get_db = function () return self.db end + public.close = function () self.connected = false end + public.is_connected = function () return self.connected end + -- update this runner ---@param time_now integer milliseconds public.update = function (time_now) From 31ede51c42ff0fc60d94be9f859b68f39479494a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 17 May 2022 10:35:55 -0400 Subject: [PATCH 204/587] still queue packets if RTU is busy, determine busy state by queue length rather than flag --- rtu/rtu.lua | 20 +++++++++++++------- rtu/startup.lua | 4 +--- rtu/threads.lua | 2 -- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index de96c66..d02bc6c 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -336,30 +336,36 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) -- handle MODBUS instruction if packet.unit_id <= #units then local unit = units[packet.unit_id] ---@type rtu_unit_registry_entry + local unit_dbg_tag = " (unit " .. packet.unit_id .. ")" + if unit.name == "redstone_io" then -- immediately execute redstone RTU requests return_code, reply = unit.modbus_io.handle_packet(packet) if not return_code then - log.warning("requested MODBUS operation failed") + log.warning("requested MODBUS operation failed" .. unit_dbg_tag) end else -- check validity then pass off to unit comms thread return_code, reply = unit.modbus_io.check_request(packet) if return_code then - -- check if an operation is already in progress for this unit - if unit.modbus_busy then + -- check if there are more than 3 active transactions + -- still queue the packet, but this may indicate a problem + if unit.pkt_queue.length() > 3 then reply = unit.modbus_io.reply__srv_device_busy(packet) - else - unit.pkt_queue.push_packet(packet) + log.debug("queueing new request with " .. unit.pkt_queue.length() .. + " transactions already in the queue" .. unit_dbg_tag) end + + -- always queue the command even if busy + unit.pkt_queue.push_packet(packet) else - log.warning("cannot perform requested MODBUS operation") + log.warning("cannot perform requested MODBUS operation" .. unit_dbg_tag) end end else -- unit ID out of range? reply = modbus.reply__gw_unavailable(packet) - log.error("MODBUS packet requesting non-existent unit") + log.error("received MODBUS packet for non-existent unit") end public.send_modbus(reply) diff --git a/rtu/startup.lua b/rtu/startup.lua index 654f7a4..8293d39 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -24,7 +24,7 @@ local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "alpha-v0.6.5" +local RTU_VERSION = "alpha-v0.6.6" local rtu_t = types.rtu_t @@ -154,7 +154,6 @@ for entry_idx = 1, #rtu_redstone do device = capabilities, -- use device field for redstone channels rtu = rs_rtu, modbus_io = modbus.new(rs_rtu, false), - modbus_busy = false, pkt_queue = nil, thread = nil } @@ -218,7 +217,6 @@ for i = 1, #rtu_devices do device = device, rtu = rtu_iface, modbus_io = modbus.new(rtu_iface, true), - modbus_busy = false, pkt_queue = mqueue.new(), thread = nil } diff --git a/rtu/threads.lua b/rtu/threads.lua index 617f3d2..a3d19d7 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -236,10 +236,8 @@ threads.thread__unit_comms = function (smem, unit) -- received data elseif msg.qtype == mqueue.TYPE.PACKET then -- received a packet - unit.modbus_busy = true local _, reply = unit.modbus_io.handle_packet(msg.message) rtu_comms.send_modbus(reply) - unit.modbus_busy = false end end From 9c034c366b7bd30c4d8f8c89bd06b92c506f5a8b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 17 May 2022 17:16:04 -0400 Subject: [PATCH 205/587] #8 base class for RTU unit sessions, handle MODBUS error responses --- supervisor/session/rtu/boiler.lua | 152 ++++++++++-------------- supervisor/session/rtu/emachine.lua | 97 ++++++--------- supervisor/session/rtu/redstone.lua | 120 ++++++++----------- supervisor/session/rtu/turbine.lua | 137 +++++++++------------ supervisor/session/rtu/txnctrl.lua | 13 +- supervisor/session/rtu/unit_session.lua | 132 ++++++++++++++++++++ supervisor/startup.lua | 2 +- 7 files changed, 349 insertions(+), 304 deletions(-) create mode 100644 supervisor/session/rtu/unit_session.lua diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua index 2b8047f..c45e22c 100644 --- a/supervisor/session/rtu/boiler.lua +++ b/supervisor/session/rtu/boiler.lua @@ -2,20 +2,23 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local types = require("scada-common.types") -local txnctrl = require("supervisor.session.rtu.txnctrl") +local unit_session = require("supervisor.session.rtu.unit_session") local boiler = {} -local PROTOCOLS = comms.PROTOCOLS local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES local MODBUS_FCODE = types.MODBUS_FCODE -local rtu_t = types.rtu_t - local TXN_TYPES = { - BUILD = 0, - STATE = 1, - TANKS = 2 + BUILD = 1, + STATE = 2, + TANKS = 3 +} + +local TXN_TAGS = { + "boiler.build", + "boiler.state", + "boiler.tanks", } local PERIODICS = { @@ -38,11 +41,7 @@ boiler.new = function (session_id, advert, out_queue) local log_tag = "session.rtu(" .. session_id .. ").boiler(" .. advert.index .. "): " local self = { - uid = advert.index, - reactor = advert.reactor, - out_q = out_queue, - transaction_controller = txnctrl.new(), - connected = true, + session = unit_session.new(log_tag, advert, out_queue, TXN_TAGS), has_build = false, periodics = { next_build_req = 0, @@ -81,36 +80,26 @@ boiler.new = function (session_id, advert, out_queue) } } - ---@class unit_session - local public = {} + local public = self.session.get() -- PRIVATE FUNCTIONS -- - local _send_request = function (txn_type, f_code, register_range) - local m_pkt = comms.modbus_packet() - local txn_id = self.transaction_controller.create(txn_type) - - m_pkt.make(txn_id, self.uid, f_code, register_range) - - self.out_q.push_packet(m_pkt) - end - -- query the build of the device local _request_build = function () -- read input registers 1 through 7 (start = 1, count = 7) - _send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 7 }) + self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 7 }) end -- query the state of the device local _request_state = function () -- read input registers 8 through 9 (start = 8, count = 2) - _send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 8, 2 }) + self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 8, 2 }) end -- query the tanks of the device local _request_tanks = function () -- read input registers 10 through 21 (start = 10, count = 12) - _send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 10, 12 }) + self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 10, 12 }) end -- PUBLIC FUNCTIONS -- @@ -118,76 +107,62 @@ boiler.new = function (session_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame public.handle_packet = function (m_pkt) - local success = false - - if m_pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then - if m_pkt.unit_id == self.uid then - local txn_type = self.transaction_controller.resolve(m_pkt.txn_id) - if txn_type == TXN_TYPES.BUILD then - -- build response - if m_pkt.length == 7 then - self.db.build.boil_cap = m_pkt.data[1] - self.db.build.steam_cap = m_pkt.data[2] - self.db.build.water_cap = m_pkt.data[3] - self.db.build.hcoolant_cap = m_pkt.data[4] - self.db.build.ccoolant_cap = m_pkt.data[5] - self.db.build.superheaters = m_pkt.data[6] - self.db.build.max_boil_rate = m_pkt.data[7] - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (boiler.build)") - end - elseif txn_type == TXN_TYPES.STATE then - -- state response - if m_pkt.length == 2 then - self.db.state.temperature = m_pkt.data[1] - self.db.state.boil_rate = m_pkt.data[2] - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (boiler.state)") - end - elseif txn_type == TXN_TYPES.TANKS then - -- tanks response - if m_pkt.length == 12 then - self.db.tanks.steam = m_pkt.data[1] - self.db.tanks.steam_need = m_pkt.data[2] - self.db.tanks.steam_fill = m_pkt.data[3] - self.db.tanks.water = m_pkt.data[4] - self.db.tanks.water_need = m_pkt.data[5] - self.db.tanks.water_fill = m_pkt.data[6] - self.db.tanks.hcool = m_pkt.data[7] - self.db.tanks.hcool_need = m_pkt.data[8] - self.db.tanks.hcool_fill = m_pkt.data[9] - self.db.tanks.ccool = m_pkt.data[10] - self.db.tanks.ccool_need = m_pkt.data[11] - self.db.tanks.ccool_fill = m_pkt.data[12] - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (boiler.tanks)") - end - elseif txn_type == nil then - log.error(log_tag .. "unknown transaction reply") - else - log.error(log_tag .. "unknown transaction type " .. txn_type) - end + local txn_type = self.session.try_resolve(m_pkt.txn_id) + if txn_type == false then + -- nothing to do + elseif txn_type == TXN_TYPES.BUILD then + -- build response + -- load in data if correct length + if m_pkt.length == 7 then + self.db.build.boil_cap = m_pkt.data[1] + self.db.build.steam_cap = m_pkt.data[2] + self.db.build.water_cap = m_pkt.data[3] + self.db.build.hcoolant_cap = m_pkt.data[4] + self.db.build.ccoolant_cap = m_pkt.data[5] + self.db.build.superheaters = m_pkt.data[6] + self.db.build.max_boil_rate = m_pkt.data[7] else - log.error(log_tag .. "wrong unit ID: " .. m_pkt.unit_id, true) + log.debug(log_tag .. "MODBUS transaction reply length mismatch (boiler.build)") end + elseif txn_type == TXN_TYPES.STATE then + -- state response + -- load in data if correct length + if m_pkt.length == 2 then + self.db.state.temperature = m_pkt.data[1] + self.db.state.boil_rate = m_pkt.data[2] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (boiler.state)") + end + elseif txn_type == TXN_TYPES.TANKS then + -- tanks response + -- load in data if correct length + if m_pkt.length == 12 then + self.db.tanks.steam = m_pkt.data[1] + self.db.tanks.steam_need = m_pkt.data[2] + self.db.tanks.steam_fill = m_pkt.data[3] + self.db.tanks.water = m_pkt.data[4] + self.db.tanks.water_need = m_pkt.data[5] + self.db.tanks.water_fill = m_pkt.data[6] + self.db.tanks.hcool = m_pkt.data[7] + self.db.tanks.hcool_need = m_pkt.data[8] + self.db.tanks.hcool_fill = m_pkt.data[9] + self.db.tanks.ccool = m_pkt.data[10] + self.db.tanks.ccool_need = m_pkt.data[11] + self.db.tanks.ccool_fill = m_pkt.data[12] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (boiler.tanks)") + end + elseif txn_type == nil then + log.error(log_tag .. "unknown transaction reply") else - log.error(log_tag .. "illegal packet type " .. m_pkt.scada_frame.protocol(), true) + log.error(log_tag .. "unknown transaction type " .. txn_type) end - - return success end - public.get_uid = function () return self.uid end - public.get_reactor = function () return self.reactor end - public.get_db = function () return self.db end - - public.close = function () self.connected = false end - public.is_connected = function () return self.connected end - -- update this runner ---@param time_now integer milliseconds public.update = function (time_now) - if not self.periodics.has_build and self.next_build_req <= time_now then + if not self.periodics.has_build and self.periodics.next_build_req <= time_now then _request_build() self.periodics.next_build_req = time_now + PERIODICS.BUILD end @@ -203,6 +178,9 @@ boiler.new = function (session_id, advert, out_queue) end end + -- get the unit session database + public.get_db = function () return self.db end + return public end diff --git a/supervisor/session/rtu/emachine.lua b/supervisor/session/rtu/emachine.lua index 8e2a2e8..c9c6481 100644 --- a/supervisor/session/rtu/emachine.lua +++ b/supervisor/session/rtu/emachine.lua @@ -2,19 +2,21 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local types = require("scada-common.types") -local txnctrl = require("supervisor.session.rtu.txnctrl") +local unit_session = require("supervisor.session.rtu.unit_session") local emachine = {} -local PROTOCOLS = comms.PROTOCOLS local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES local MODBUS_FCODE = types.MODBUS_FCODE -local rtu_t = types.rtu_t - local TXN_TYPES = { - BUILD = 0, - STORAGE = 1 + BUILD = 1, + STORAGE = 2 +} + +local TXN_TAGS = { + "emachine.build", + "emachine.storage" } local PERIODICS = { @@ -36,12 +38,7 @@ emachine.new = function (session_id, advert, out_queue) local log_tag = "session.rtu(" .. session_id .. ").emachine(" .. advert.index .. "): " local self = { - uid = advert.index, - -- reactor = advert.reactor, - reactor = 0, - out_q = out_queue, - transaction_controller = txnctrl.new(), - connected = true, + session = unit_session.new(log_tag, advert, out_queue, TXN_TAGS), has_build = false, periodics = { next_build_req = 0, @@ -60,30 +57,20 @@ emachine.new = function (session_id, advert, out_queue) } } - ---@class unit_session - local public = {} + local public = self.session.get() -- PRIVATE FUNCTIONS -- - local _send_request = function (txn_type, f_code, register_range) - local m_pkt = comms.modbus_packet() - local txn_id = self.transaction_controller.create(txn_type) - - m_pkt.make(txn_id, self.uid, f_code, register_range) - - self.out_q.push_packet(m_pkt) - end - -- query the build of the device local _request_build = function () -- read input register 1 (start = 1, count = 1) - _send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 1 }) + self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 1 }) end -- query the state of the energy storage local _request_storage = function () -- read input registers 2 through 4 (start = 2, count = 3) - _send_request(TXN_TYPES.STORAGE, MODBUS_FCODE.READ_INPUT_REGS, { 2, 3 }) + self.session.send_request(TXN_TYPES.STORAGE, MODBUS_FCODE.READ_INPUT_REGS, { 2, 3 }) end -- PUBLIC FUNCTIONS -- @@ -91,49 +78,32 @@ emachine.new = function (session_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame public.handle_packet = function (m_pkt) - local success = false - - if m_pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then - if m_pkt.unit_id == self.uid then - local txn_type = self.transaction_controller.resolve(m_pkt.txn_id) - if txn_type == TXN_TYPES.BUILD then - -- build response - if m_pkt.length == 1 then - self.db.build.max_energy = m_pkt.data[1] - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (emachine.build)") - end - elseif txn_type == TXN_TYPES.STORAGE then - -- storage response - if m_pkt.length == 3 then - self.db.storage.energy = m_pkt.data[1] - self.db.storage.energy_need = m_pkt.data[2] - self.db.storage.energy_fill = m_pkt.data[3] - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (emachine.storage)") - end - elseif txn_type == nil then - log.error(log_tag .. "unknown transaction reply") - else - log.error(log_tag .. "unknown transaction type " .. txn_type) - end + local txn_type = self.session.try_resolve(m_pkt.txn_id) + if txn_type == false then + -- nothing to do + elseif txn_type == TXN_TYPES.BUILD then + -- build response + if m_pkt.length == 1 then + self.db.build.max_energy = m_pkt.data[1] else - log.error(log_tag .. "wrong unit ID: " .. m_pkt.unit_id, true) + log.debug(log_tag .. "MODBUS transaction reply length mismatch (emachine.build)") end + elseif txn_type == TXN_TYPES.STORAGE then + -- storage response + if m_pkt.length == 3 then + self.db.storage.energy = m_pkt.data[1] + self.db.storage.energy_need = m_pkt.data[2] + self.db.storage.energy_fill = m_pkt.data[3] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (emachine.storage)") + end + elseif txn_type == nil then + log.error(log_tag .. "unknown transaction reply") else - log.error(log_tag .. "illegal packet type " .. m_pkt.scada_frame.protocol(), true) + log.error(log_tag .. "unknown transaction type " .. txn_type) end - - return success end - public.get_uid = function () return self.uid end - public.get_reactor = function () return self.reactor end - public.get_db = function () return self.db end - - public.close = function () self.connected = false end - public.is_connected = function () return self.connected end - -- update this runner ---@param time_now integer milliseconds public.update = function (time_now) @@ -148,6 +118,9 @@ emachine.new = function (session_id, advert, out_queue) end end + -- get the unit session database + public.get_db = function () return self.db end + return public end diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index b5f44ea..57848d9 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -5,11 +5,10 @@ local rsio = require("scada-common.rsio") local types = require("scada-common.types") local util = require("scada-common.util") -local txnctrl = require("supervisor.session.rtu.txnctrl") +local unit_session = require("supervisor.session.rtu.unit_session") local redstone = {} -local PROTOCOLS = comms.PROTOCOLS local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES local MODBUS_FCODE = types.MODBUS_FCODE @@ -18,8 +17,6 @@ local IO_LVL = rsio.IO_LVL local IO_DIR = rsio.IO_DIR local IO_MODE = rsio.IO_MODE -local rtu_t = types.rtu_t - local RS_RTU_S_CMDS = { } @@ -31,10 +28,17 @@ redstone.RS_RTU_S_CMDS = RS_RTU_S_CMDS redstone.RS_RTU_S_DATA = RS_RTU_S_DATA local TXN_TYPES = { - DI_READ = 0, - COIL_WRITE = 1, - INPUT_REG_READ = 2, - HOLD_REG_WRITE = 3 + DI_READ = 1, + COIL_WRITE = 2, + INPUT_REG_READ = 3, + HOLD_REG_WRITE = 4 +} + +local TXN_TAGS = { + "redstone.di_read", + "redstone.coil_write", + "redstone.input_reg_write", + "redstone.hold_reg_write" } local PERIODICS = { @@ -55,12 +59,7 @@ redstone.new = function (session_id, advert, out_queue) local log_tag = "session.rtu(" .. session_id .. ").redstone(" .. advert.index .. "): " local self = { - uid = advert.index, - reactor = advert.reactor, - in_q = mqueue.new(), - out_q = out_queue, - transaction_controller = txnctrl.new(), - connected = true, + session = unit_session.new(log_tag, advert, out_queue, TXN_TAGS), has_di = false, has_ai = false, periodics = { @@ -76,8 +75,7 @@ redstone.new = function (session_id, advert, out_queue) db = {} } - ---@class unit_session - local public = {} + local public = self.session.get() -- INITIALIZE -- @@ -110,36 +108,26 @@ redstone.new = function (session_id, advert, out_queue) self.db[channel] = IO_LVL.LOW end - -- PRIVATE FUNCTIONS -- - local _send_request = function (txn_type, f_code, parameters) - local m_pkt = comms.modbus_packet() - local txn_id = self.transaction_controller.create(txn_type) - - m_pkt.make(txn_id, self.uid, f_code, parameters) - - self.out_q.push_packet(m_pkt) - end - -- query discrete inputs local _request_discrete_inputs = function () - _send_request(TXN_TYPES.DI_READ, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, #self.io_list.digital_in }) + self.session.send_request(TXN_TYPES.DI_READ, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, #self.io_list.digital_in }) end -- query input registers local _request_input_registers = function () - _send_request(TXN_TYPES.INPUT_REG_READ, MODBUS_FCODE.READ_INPUT_REGS, { 1, #self.io_list.analog_in }) + self.session.send_request(TXN_TYPES.INPUT_REG_READ, MODBUS_FCODE.READ_INPUT_REGS, { 1, #self.io_list.analog_in }) end -- write coil output local _write_coil = function (coil, value) - _send_request(TXN_TYPES.COIL_WRITE, MODBUS_FCODE.WRITE_MUL_COILS, { coil, value }) + self.session.send_request(TXN_TYPES.COIL_WRITE, MODBUS_FCODE.WRITE_MUL_COILS, { coil, value }) end -- write holding register output local _write_holding_register = function (reg, value) - _send_request(TXN_TYPES.HOLD_REG_WRITE, MODBUS_FCODE.WRITE_MUL_HOLD_REGS, { reg, value }) + self.session.send_request(TXN_TYPES.HOLD_REG_WRITE, MODBUS_FCODE.WRITE_MUL_HOLD_REGS, { reg, value }) end -- PUBLIC FUNCTIONS -- @@ -147,55 +135,40 @@ redstone.new = function (session_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame public.handle_packet = function (m_pkt) - local success = false - - if m_pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then - if m_pkt.unit_id == self.uid then - local txn_type = self.transaction_controller.resolve(m_pkt.txn_id) - if txn_type == TXN_TYPES.DI_READ then - -- discrete input read response - if m_pkt.length == #self.io_list.digital_in then - for i = 1, m_pkt.length do - local channel = self.io_list.digital_in[i] - local value = m_pkt.data[i] - self.db[channel] = value - end - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (redstone.discrete_input_read)") - end - elseif txn_type == TXN_TYPES.INPUT_REG_READ then - -- input register read response - if m_pkt.length == #self.io_list.analog_in then - for i = 1, m_pkt.length do - local channel = self.io_list.analog_in[i] - local value = m_pkt.data[i] - self.db[channel] = value - end - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (redstone.input_reg_read)") - end - elseif txn_type == nil then - log.error(log_tag .. "unknown transaction reply") - else - log.error(log_tag .. "unknown transaction type " .. txn_type) + local txn_type = self.session.try_resolve(m_pkt.txn_id) + if txn_type == false then + -- nothing to do + elseif txn_type == TXN_TYPES.DI_READ then + -- discrete input read response + if m_pkt.length == #self.io_list.digital_in then + for i = 1, m_pkt.length do + local channel = self.io_list.digital_in[i] + local value = m_pkt.data[i] + self.db[channel] = value end else - log.error(log_tag .. "wrong unit ID: " .. m_pkt.unit_id, true) + log.debug(log_tag .. "MODBUS transaction reply length mismatch (redstone.di_read)") end + elseif txn_type == TXN_TYPES.INPUT_REG_READ then + -- input register read response + if m_pkt.length == #self.io_list.analog_in then + for i = 1, m_pkt.length do + local channel = self.io_list.analog_in[i] + local value = m_pkt.data[i] + self.db[channel] = value + end + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (redstone.input_reg_read)") + end + elseif txn_type == TXN_TYPES.COIL_WRITE or txn_type == TXN_TYPES.HOLD_REG_WRITE then + -- successful acknowledgement + elseif txn_type == nil then + log.error(log_tag .. "unknown transaction reply") else - log.error(log_tag .. "illegal packet type " .. m_pkt.scada_frame.protocol(), true) + log.error(log_tag .. "unknown transaction type " .. txn_type) end - - return success end - public.get_uid = function () return self.uid end - public.get_reactor = function () return self.reactor end - public.get_db = function () return self.db end - - public.close = function () self.connected = false end - public.is_connected = function () return self.connected end - -- update this runner ---@param time_now integer milliseconds public.update = function (time_now) @@ -270,6 +243,9 @@ redstone.new = function (session_id, advert, out_queue) end end + -- get the unit session database + public.get_db = function () return self.db end + return public, self.in_q end diff --git a/supervisor/session/rtu/turbine.lua b/supervisor/session/rtu/turbine.lua index e6d41c2..d45d244 100644 --- a/supervisor/session/rtu/turbine.lua +++ b/supervisor/session/rtu/turbine.lua @@ -2,21 +2,24 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local types = require("scada-common.types") -local txnctrl = require("supervisor.session.rtu.txnctrl") +local unit_session = require("supervisor.session.rtu.unit_session") local turbine = {} -local PROTOCOLS = comms.PROTOCOLS local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES local DUMPING_MODE = types.DUMPING_MODE local MODBUS_FCODE = types.MODBUS_FCODE -local rtu_t = types.rtu_t - local TXN_TYPES = { - BUILD = 0, - STATE = 1, - TANKS = 2 + BUILD = 1, + STATE = 2, + TANKS = 3 +} + +local TXN_TAGS = { + "turbine.build", + "turbine.state", + "turbine.tanks", } local PERIODICS = { @@ -39,11 +42,7 @@ turbine.new = function (session_id, advert, out_queue) local log_tag = "session.rtu(" .. session_id .. ").turbine(" .. advert.index .. "): " local self = { - uid = advert.index, - reactor = advert.reactor, - out_q = out_queue, - transaction_controller = txnctrl.new(), - connected = true, + session = unit_session.new(log_tag, advert, out_queue, TXN_TAGS), has_build = false, periodics = { next_build_req = 0, @@ -77,36 +76,26 @@ turbine.new = function (session_id, advert, out_queue) } } - ---@class unit_session - local public = {} + local public = self.session.get() -- PRIVATE FUNCTIONS -- - local _send_request = function (txn_type, f_code, register_range) - local m_pkt = comms.modbus_packet() - local txn_id = self.transaction_controller.create(txn_type) - - m_pkt.make(txn_id, self.uid, f_code, register_range) - - self.out_q.push_packet(m_pkt) - end - -- query the build of the device local _request_build = function () -- read input registers 1 through 9 (start = 1, count = 9) - _send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 9 }) + self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 9 }) end -- query the state of the device local _request_state = function () -- read input registers 10 through 13 (start = 10, count = 4) - _send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 10, 4 }) + self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 10, 4 }) end -- query the tanks of the device local _request_tanks = function () -- read input registers 14 through 16 (start = 14, count = 3) - _send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 14, 3 }) + self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 14, 3 }) end -- PUBLIC FUNCTIONS -- @@ -114,67 +103,50 @@ turbine.new = function (session_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame public.handle_packet = function (m_pkt) - local success = false - - if m_pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then - if m_pkt.unit_id == self.uid then - local txn_type = self.transaction_controller.resolve(m_pkt.txn_id) - if txn_type == TXN_TYPES.BUILD then - -- build response - if m_pkt.length == 9 then - self.db.build.blades = m_pkt.data[1] - self.db.build.coils = m_pkt.data[2] - self.db.build.vents = m_pkt.data[3] - self.db.build.dispersers = m_pkt.data[4] - self.db.build.condensers = m_pkt.data[5] - self.db.build.steam_cap = m_pkt.data[6] - self.db.build.max_flow_rate = m_pkt.data[7] - self.db.build.max_production = m_pkt.data[8] - self.db.build.max_water_output = m_pkt.data[9] - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (turbine.build)") - end - elseif txn_type == TXN_TYPES.STATE then - -- state response - if m_pkt.length == 4 then - self.db.state.flow_rate = m_pkt.data[1] - self.db.state.prod_rate = m_pkt.data[2] - self.db.state.steam_input_rate = m_pkt.data[3] - self.db.state.dumping_mode = m_pkt.data[4] - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (turbine.state)") - end - elseif txn_type == TXN_TYPES.TANKS then - -- tanks response - if m_pkt.length == 3 then - self.db.tanks.steam = m_pkt.data[1] - self.db.tanks.steam_need = m_pkt.data[2] - self.db.tanks.steam_fill = m_pkt.data[3] - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (turbine.tanks)") - end - elseif txn_type == nil then - log.error(log_tag .. "unknown transaction reply") - else - log.error(log_tag .. "unknown transaction type " .. txn_type) - end + local txn_type = self.session.try_resolve(m_pkt.txn_id) + if txn_type == false then + -- nothing to do + elseif txn_type == TXN_TYPES.BUILD then + -- build response + if m_pkt.length == 9 then + self.db.build.blades = m_pkt.data[1] + self.db.build.coils = m_pkt.data[2] + self.db.build.vents = m_pkt.data[3] + self.db.build.dispersers = m_pkt.data[4] + self.db.build.condensers = m_pkt.data[5] + self.db.build.steam_cap = m_pkt.data[6] + self.db.build.max_flow_rate = m_pkt.data[7] + self.db.build.max_production = m_pkt.data[8] + self.db.build.max_water_output = m_pkt.data[9] else - log.error(log_tag .. "wrong unit ID: " .. m_pkt.unit_id, true) + log.debug(log_tag .. "MODBUS transaction reply length mismatch (turbine.build)") end + elseif txn_type == TXN_TYPES.STATE then + -- state response + if m_pkt.length == 4 then + self.db.state.flow_rate = m_pkt.data[1] + self.db.state.prod_rate = m_pkt.data[2] + self.db.state.steam_input_rate = m_pkt.data[3] + self.db.state.dumping_mode = m_pkt.data[4] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (turbine.state)") + end + elseif txn_type == TXN_TYPES.TANKS then + -- tanks response + if m_pkt.length == 3 then + self.db.tanks.steam = m_pkt.data[1] + self.db.tanks.steam_need = m_pkt.data[2] + self.db.tanks.steam_fill = m_pkt.data[3] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (turbine.tanks)") + end + elseif txn_type == nil then + log.error(log_tag .. "unknown transaction reply") else - log.error(log_tag .. "illegal packet type " .. m_pkt.scada_frame.protocol(), true) + log.error(log_tag .. "unknown transaction type " .. txn_type) end - - return success end - public.get_uid = function () return self.uid end - public.get_reactor = function () return self.reactor end - public.get_db = function () return self.db end - - public.close = function () self.connected = false end - public.is_connected = function () return self.connected end - -- update this runner ---@param time_now integer milliseconds public.update = function (time_now) @@ -194,6 +166,9 @@ turbine.new = function (session_id, advert, out_queue) end end + -- get the unit session database + public.get_db = function () return self.db end + return public end diff --git a/supervisor/session/rtu/txnctrl.lua b/supervisor/session/rtu/txnctrl.lua index 2d6be2e..a19dee6 100644 --- a/supervisor/session/rtu/txnctrl.lua +++ b/supervisor/session/rtu/txnctrl.lua @@ -6,7 +6,7 @@ local util = require("scada-common.util") local txnctrl = {} -local TIMEOUT = 3000 -- 3000ms max wait +local TIMEOUT = 2000 -- 2000ms max wait -- create a new transaction controller txnctrl.new = function () @@ -63,6 +63,17 @@ txnctrl.new = function () return txn_type end + -- renew a transaction by re-inserting it with its ID and type + ---@param txn_id integer + ---@param txn_type integer + public.renew = function (txn_id, txn_type) + insert(self.list, { + txn_id = txn_id, + txn_type = txn_type, + expiry = util.time() + TIMEOUT + }) + end + -- close timed-out transactions public.cleanup = function () local now = util.time() diff --git a/supervisor/session/rtu/unit_session.lua b/supervisor/session/rtu/unit_session.lua new file mode 100644 index 0000000..67f83c0 --- /dev/null +++ b/supervisor/session/rtu/unit_session.lua @@ -0,0 +1,132 @@ +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") + +local txnctrl = require("supervisor.session.rtu.txnctrl") + +local unit_session = {} + +local PROTOCOLS = comms.PROTOCOLS +local MODBUS_FCODE = types.MODBUS_FCODE +local MODBUS_EXCODE = types.MODBUS_EXCODE + +-- create a new unit session runner +---@param log_tag string +---@param advert rtu_advertisement +---@param out_queue mqueue +---@param txn_tags table +unit_session.new = function (log_tag, advert, out_queue, txn_tags) + local self = { + log_tag = log_tag, + txn_tags = txn_tags, + uid = advert.index, + reactor = advert.reactor, + out_q = out_queue, + transaction_controller = txnctrl.new(), + connected = true, + device_fail = false + } + + ---@class _unit_session + local protected = {} + + ---@class unit_session + local public = {} + + -- PROTECTED FUNCTIONS -- + + -- send a MODBUS message, creating a transaction in the process + ---@param txn_type integer transaction type + ---@param f_code MODBUS_FCODE function code + ---@param register_param table register range or register and values + protected.send_request = function (txn_type, f_code, register_param) + local m_pkt = comms.modbus_packet() + local txn_id = self.transaction_controller.create(txn_type) + + m_pkt.make(txn_id, self.uid, f_code, register_param) + + self.out_q.push_packet(m_pkt) + end + + -- try to resolve a MODBUS transaction + ---@param m_pkt modbus_frame MODBUS packet + ---@return integer|false txn_type transaction type or false on error/busy + protected.try_resolve = function (m_pkt) + if m_pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then + if m_pkt.unit_id == self.uid then + local txn_type = self.transaction_controller.resolve(m_pkt.txn_id) + local txn_tag = " (" .. self.txn_tags[txn_type] .. ")" + + if bit.band(m_pkt.func_code, MODBUS_FCODE.ERROR_FLAG) ~= 0 then + -- transaction incomplete or failed + local ex = m_pkt.data[1] + if ex == MODBUS_EXCODE.ILLEGAL_FUNCTION then + log.error(log_tag .. "MODBUS: illegal function" .. txn_tag) + elseif ex == MODBUS_EXCODE.ILLEGAL_DATA_ADDR then + log.error(log_tag .. "MODBUS: illegal data address" .. txn_tag) + elseif ex == MODBUS_EXCODE.SERVER_DEVICE_FAIL then + if self.device_fail then + log.debug(log_tag .. "MODBUS: repeated device failure" .. txn_tag) + else + self.device_fail = true + log.warning(log_tag .. "MODBUS: device failure" .. txn_tag) + end + elseif ex == MODBUS_EXCODE.ACKNOWLEDGE then + -- will have to wait on reply, renew the transaction + self.transaction_controller.renew(m_pkt.txn_id, txn_type) + elseif ex == MODBUS_EXCODE.SERVER_DEVICE_BUSY then + -- will have to wait on reply, renew the transaction + self.transaction_controller.renew(m_pkt.txn_id, txn_type) + log.debug(log_tag .. "MODBUS: device busy" .. txn_tag) + elseif ex == MODBUS_EXCODE.NEG_ACKNOWLEDGE then + -- general failure + log.error(log_tag .. "MODBUS: negative acknowledge (bad request)" .. txn_tag) + elseif ex == MODBUS_EXCODE.GATEWAY_PATH_UNAVAILABLE then + -- RTU gateway has no known unit with the given ID + log.error(log_tag .. "MODBUS: gateway path unavailable (unknown unit)" .. txn_tag) + elseif ex ~= nil then + -- unsupported exception code + log.debug(log_tag .. "MODBUS: unsupported error " .. ex .. txn_tag) + else + -- nil exception code + log.debug(log_tag .. "MODBUS: nil exception code" .. txn_tag) + end + else + -- clear device fail flag + self.device_fail = false + + -- no error, return the transaction type + return txn_type + end + else + log.error(log_tag .. "wrong unit ID: " .. m_pkt.unit_id, true) + end + else + log.error(log_tag .. "illegal packet type " .. m_pkt.scada_frame.protocol(), true) + end + + -- error or transaction in progress, return false + return false + end + + -- get the public interface + protected.get = function () return public end + + -- PUBLIC FUNCTIONS -- + + -- get the unit ID + public.get_uid = function () return self.uid end + -- get the reactor ID + public.get_reactor = function () return self.reactor end + + -- close this unit + public.close = function () self.connected = false end + -- check if this unit is connected + public.is_connected = function () return self.connected end + -- check if this unit is faulted + public.is_faulted = function () return self.device_fail end + + return protected +end + +return unit_session diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 53f3e1a..d647f99 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "alpha-v0.3.7" +local SUPERVISOR_VERSION = "alpha-v0.3.8" local print = util.print local println = util.println From 6184078c3ff0ca4447e0826e28ec316a55c1bb91 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 18 May 2022 13:28:43 -0400 Subject: [PATCH 206/587] #52 work in progress on reactor units --- supervisor/config.lua | 7 ++ supervisor/unit.lua | 150 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 145 insertions(+), 12 deletions(-) diff --git a/supervisor/config.lua b/supervisor/config.lua index 734e820..b98ceb2 100644 --- a/supervisor/config.lua +++ b/supervisor/config.lua @@ -6,6 +6,13 @@ config.SCADA_DEV_LISTEN = 16000 config.SCADA_SV_LISTEN = 16100 -- expected number of reactors config.NUM_REACTORS = 4 +-- expected number of boilers/turbines for each reactor +config.REACTOR_COOLING = { + { BOILERS = 1, TURBINES = 1 }, -- reactor unit 1 + { BOILERS = 1, TURBINES = 1 }, -- reactor unit 2 + { BOILERS = 1, TURBINES = 1 }, -- reactor unit 3 + { BOILERS = 1, TURBINES = 1 } -- reactor unit 4 +} -- log path config.LOG_PATH = "/log.txt" -- log mode diff --git a/supervisor/unit.lua b/supervisor/unit.lua index a246ebc..0afd7e5 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -1,19 +1,35 @@ +local types = require "scada-common.types" +local util = require "scada-common.util" + local unit = {} +---@alias TRI_FAIL integer +local TRI_FAIL = { + OK = 0, + PARTIAL = 1, + FULL = 2 +} + -- create a new reactor unit ----@param for_reactor integer -unit.new = function (for_reactor) +---@param for_reactor integer reactor unit number +---@param num_boilers integer number of boilers expected +---@param num_turbines integer number of turbines expected +unit.new = function (for_reactor, num_boilers, num_turbines) local self = { r_id = for_reactor, - plc_s = nil, + plc_s = nil, ---@class plc_session + counts = { boilers = num_boilers, turbines = num_turbines }, turbines = {}, boilers = {}, energy_storage = {}, redstone = {}, + deltas = { + last_reactor_temp = nil, + last_reactor_temp_time = 0 + }, db = { ---@class annunciator annunciator = { - -- RPS -- reactor PLCOnline = false, ReactorTrip = false, @@ -22,18 +38,18 @@ unit.new = function (for_reactor) RCSFlowLow = false, ReactorTempHigh = false, ReactorHighDeltaT = false, - ReactorOverPower = false, HighStartupRate = false, -- boiler - BoilerOnline = false, + BoilerOnline = TRI_FAIL.OK, HeatingRateLow = false, + BoilRateMismatch = false, CoolantFeedMismatch = false, -- turbine - TurbineOnline = false, + TurbineOnline = TRI_FAIL.OK, SteamFeedMismatch = false, SteamDumpOpen = false, - TurbineTrip = false, - TurbineOverUnderSpeed = false + TurbineOverSpeed = false, + TurbineTrip = false } } } @@ -45,8 +61,110 @@ unit.new = function (for_reactor) -- update the annunciator local _update_annunciator = function () + -- check PLC status self.db.annunciator.PLCOnline = (self.plc_s ~= nil) and (self.plc_s.open) - self.db.annunciator.ReactorTrip = false + + if self.plc_s ~= nil then + ------------- + -- REACTOR -- + ------------- + + local plc_db = self.plc_s.get_db() + + -- compute deltas + local reactor_delta_t = 0 + if self.deltas.last_reactor_temp ~= nil then + reactor_delta_t = (plc_db.mek_status.temp - self.deltas.last_reactor_temp) / (util.time_s() - self.deltas.last_reactor_temp_time) + else + self.deltas.last_reactor_temp = plc_db.mek_status.temp + self.deltas.last_reactor_temp_time = util.time_s() + end + + -- update annunciator + self.db.annunciator.ReactorTrip = plc_db.rps_tripped + self.db.annunciator.ManualReactorTrip = plc_db.rps_trip_cause == types.rps_status_t.manual + self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.no_cool) + self.db.annunciator.RCSFlowLow = plc_db.mek_status.ccool_fill < 0.75 or plc_db.mek_status.hcool_fill > 0.25 + self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > 1000 + self.db.annunciator.ReactorHighDeltaT = reactor_delta_t > 100 + -- @todo this is dependent on setup, i.e. how much coolant is buffered and the turbine setup + self.db.annunciator.HighStartupRate = not plc_db.control_state and plc_db.mek_status.burn_rate > 40 + end + + ------------- + -- BOILERS -- + ------------- + + -- check boiler online status + local connected_boilers = #self.boilers + if connected_boilers == 0 and self.num_boilers > 0 then + self.db.annunciator.BoilerOnline = TRI_FAIL.FULL + elseif connected_boilers > 0 and connected_boilers ~= self.num_boilers then + self.db.annunciator.BoilerOnline = TRI_FAIL.PARTIAL + else + self.db.annunciator.BoilerOnline = TRI_FAIL.OK + end + + local total_boil_rate = 0.0 + local no_boil_count = 0 + for i = 1, #self.boilers do + local boiler = self.boilers[i].get_db() ---@type boiler_session_db + local boil_rate = boiler.state.boil_rate + if boil_rate == 0 then + no_boil_count = no_boil_count + 1 + else + total_boil_rate = total_boil_rate + boiler.state.boil_rate + end + end + + if no_boil_count == 0 and self.num_boilers > 0 then + self.db.annunciator.HeatingRateLow = TRI_FAIL.FULL + elseif no_boil_count > 0 and no_boil_count ~= self.num_boilers then + self.db.annunciator.HeatingRateLow = TRI_FAIL.PARTIAL + else + self.db.annunciator.HeatingRateLow = TRI_FAIL.OK + end + + if self.plc_s ~= nil then + local expected_boil_rate = self.plc_s.get_db().mek_status.heating_rate / 10.0 + self.db.annunciator.BoilRateMismatch = math.abs(expected_boil_rate - total_boil_rate) > 25.0 + else + self.db.annunciator.BoilRateMismatch = false + end + + -------------- + -- TURBINES -- + -------------- + + -- check turbine online status + local connected_turbines = #self.turbines + if connected_turbines == 0 and self.num_turbines > 0 then + self.db.annunciator.TurbineOnline = TRI_FAIL.FULL + elseif connected_turbines > 0 and connected_turbines ~= self.num_turbines then + self.db.annunciator.TurbineOnline = TRI_FAIL.PARTIAL + else + self.db.annunciator.TurbineOnline = TRI_FAIL.OK + end + + --[[ + Turbine Under/Over Speed + ]]-- + + --[[ + Turbine Trip + a turbine trip is when the turbine stops, which means we are no longer receiving water and lose the ability to cool + this can be identified by these conditions: + - the current flow rate is 0 mB/t and it should not be + - it should not be if the boiler or reactor has a non-zero heating rate + - can initially catch this by detecting a 0 flow rate with a non-zero input rate, but eventually the steam will fill up + - can later identified by presence of steam in tank with a 0 flow rate + ]]-- + end + + -- unlink disconnected units + ---@param sessions table + local _unlink_disconnected_units = function (sessions) + util.filter_table(sessions, function (u) return u.is_connected() end) end -- PUBLIC FUNCTIONS -- @@ -55,14 +173,18 @@ unit.new = function (for_reactor) ---@param plc_session plc_session_struct public.link_plc_session = function (plc_session) self.plc_s = plc_session + self.deltas.last_reactor_temp = self.plc_s.get_db().mek_status.temp + self.deltas.last_reactor_temp_time = util.time_s() end - -- link a turbine RTU + -- link a turbine RTU session + ---@param turbine unit_session public.add_turbine = function (turbine) table.insert(self.turbines, turbine) end - -- link a boiler RTU + -- link a boiler RTU session + ---@param boiler unit_session public.add_boiler = function (boiler) table.insert(self.boilers, boiler) end @@ -85,6 +207,10 @@ unit.new = function (for_reactor) self.plc_s = nil end + -- unlink RTU unit sessions if they are closed + _unlink_disconnected_units(self.boilers) + _unlink_disconnected_units(self.turbines) + -- update annunciator logic _update_annunciator() end From cc856d4d801359efd9c94f14fd2335fd205becc8 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 18 May 2022 13:32:44 -0400 Subject: [PATCH 207/587] redundant 'for_reactor' field removed from redstone RTU config --- rtu/config.lua | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/rtu/config.lua b/rtu/config.lua index ec2b047..3d2e889 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -33,20 +33,17 @@ config.RTU_REDSTONE = { { channel = rsio.IO.WASTE_PO, side = "top", - bundled_color = colors.blue, - for_reactor = 1 + bundled_color = colors.blue }, { channel = rsio.IO.WASTE_PU, side = "top", - bundled_color = colors.cyan, - for_reactor = 1 + bundled_color = colors.cyan }, { channel = rsio.IO.WASTE_AM, side = "top", - bundled_color = colors.purple, - for_reactor = 1 + bundled_color = colors.purple } } } From 790571b6fcb0ba7c43582aa34d5e73797f7ae998 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 18 May 2022 13:49:04 -0400 Subject: [PATCH 208/587] #55 correctly use device IDs vs unit IDs --- supervisor/session/rtu.lua | 8 +++--- supervisor/session/rtu/boiler.lua | 5 ++-- supervisor/session/rtu/emachine.lua | 5 ++-- supervisor/session/rtu/redstone.lua | 8 ++++-- supervisor/session/rtu/turbine.lua | 5 ++-- supervisor/session/rtu/unit_session.lua | 38 +++++++++++++++++++------ supervisor/startup.lua | 2 +- 7 files changed, 48 insertions(+), 23 deletions(-) diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 33a66d7..ff95087 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -88,17 +88,17 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) -- create unit by type if u_type == RTU_UNIT_TYPES.REDSTONE then - unit, rs_in_q = svrs_redstone.new(self.id, unit_advert, self.out_q) + unit, rs_in_q = svrs_redstone.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.BOILER then - unit = svrs_boiler.new(self.id, unit_advert, self.out_q) + unit = svrs_boiler.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.BOILER_VALVE then -- @todo Mekanism 10.1+ elseif u_type == RTU_UNIT_TYPES.TURBINE then - unit = svrs_turbine.new(self.id, unit_advert, self.out_q) + unit = svrs_turbine.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.TURBINE_VALVE then -- @todo Mekanism 10.1+ elseif u_type == RTU_UNIT_TYPES.EMACHINE then - unit = svrs_emachine.new(self.id, unit_advert, self.out_q) + unit = svrs_emachine.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.IMATRIX then -- @todo Mekanism 10.1+ else diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua index c45e22c..c3fa28e 100644 --- a/supervisor/session/rtu/boiler.lua +++ b/supervisor/session/rtu/boiler.lua @@ -29,9 +29,10 @@ local PERIODICS = { -- create a new boiler rtu session runner ---@param session_id integer +---@param unit_id integer ---@param advert rtu_advertisement ---@param out_queue mqueue -boiler.new = function (session_id, advert, out_queue) +boiler.new = function (session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPES.BOILER then log.error("attempt to instantiate boiler RTU for type '" .. advert.type .. "'. this is a bug.") @@ -41,7 +42,7 @@ boiler.new = function (session_id, advert, out_queue) local log_tag = "session.rtu(" .. session_id .. ").boiler(" .. advert.index .. "): " local self = { - session = unit_session.new(log_tag, advert, out_queue, TXN_TAGS), + session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), has_build = false, periodics = { next_build_req = 0, diff --git a/supervisor/session/rtu/emachine.lua b/supervisor/session/rtu/emachine.lua index c9c6481..e47293a 100644 --- a/supervisor/session/rtu/emachine.lua +++ b/supervisor/session/rtu/emachine.lua @@ -26,9 +26,10 @@ local PERIODICS = { -- create a new energy machine rtu session runner ---@param session_id integer +---@param unit_id integer ---@param advert rtu_advertisement ---@param out_queue mqueue -emachine.new = function (session_id, advert, out_queue) +emachine.new = function (session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPES.EMACHINE then log.error("attempt to instantiate emachine RTU for type '" .. advert.type .. "'. this is a bug.") @@ -38,7 +39,7 @@ emachine.new = function (session_id, advert, out_queue) local log_tag = "session.rtu(" .. session_id .. ").emachine(" .. advert.index .. "): " local self = { - session = unit_session.new(log_tag, advert, out_queue, TXN_TAGS), + session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), has_build = false, periodics = { next_build_req = 0, diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index 57848d9..b41e223 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -47,19 +47,21 @@ local PERIODICS = { -- create a new redstone rtu session runner ---@param session_id integer +---@param unit_id integer ---@param advert rtu_advertisement ---@param out_queue mqueue -redstone.new = function (session_id, advert, out_queue) +redstone.new = function (session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPES.REDSTONE then log.error("attempt to instantiate redstone RTU for type '" .. advert.type .. "'. this is a bug.") return nil end - local log_tag = "session.rtu(" .. session_id .. ").redstone(" .. advert.index .. "): " + -- for redstone, use unit ID not device index + local log_tag = "session.rtu(" .. session_id .. ").redstone(" .. unit_id .. "): " local self = { - session = unit_session.new(log_tag, advert, out_queue, TXN_TAGS), + session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), has_di = false, has_ai = false, periodics = { diff --git a/supervisor/session/rtu/turbine.lua b/supervisor/session/rtu/turbine.lua index d45d244..d62626e 100644 --- a/supervisor/session/rtu/turbine.lua +++ b/supervisor/session/rtu/turbine.lua @@ -30,9 +30,10 @@ local PERIODICS = { -- create a new turbine rtu session runner ---@param session_id integer +---@param unit_id integer ---@param advert rtu_advertisement ---@param out_queue mqueue -turbine.new = function (session_id, advert, out_queue) +turbine.new = function (session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPES.TURBINE then log.error("attempt to instantiate turbine RTU for type '" .. advert.type .. "'. this is a bug.") @@ -42,7 +43,7 @@ turbine.new = function (session_id, advert, out_queue) local log_tag = "session.rtu(" .. session_id .. ").turbine(" .. advert.index .. "): " local self = { - session = unit_session.new(log_tag, advert, out_queue, TXN_TAGS), + session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), has_build = false, periodics = { next_build_req = 0, diff --git a/supervisor/session/rtu/unit_session.lua b/supervisor/session/rtu/unit_session.lua index 67f83c0..4f50120 100644 --- a/supervisor/session/rtu/unit_session.lua +++ b/supervisor/session/rtu/unit_session.lua @@ -11,15 +11,17 @@ local MODBUS_FCODE = types.MODBUS_FCODE local MODBUS_EXCODE = types.MODBUS_EXCODE -- create a new unit session runner ----@param log_tag string ----@param advert rtu_advertisement ----@param out_queue mqueue ----@param txn_tags table -unit_session.new = function (log_tag, advert, out_queue, txn_tags) +---@param unit_id integer MODBUS unit ID +---@param advert rtu_advertisement RTU advertisement for this unit +---@param out_queue mqueue send queue +---@param log_tag string logging tag +---@param txn_tags table transaction log tags +unit_session.new = function (unit_id, advert, out_queue, log_tag, txn_tags) local self = { log_tag = log_tag, txn_tags = txn_tags, - uid = advert.index, + unit_id = unit_id, + device_index = advert.index, reactor = advert.reactor, out_q = out_queue, transaction_controller = txnctrl.new(), @@ -43,7 +45,7 @@ unit_session.new = function (log_tag, advert, out_queue, txn_tags) local m_pkt = comms.modbus_packet() local txn_id = self.transaction_controller.create(txn_type) - m_pkt.make(txn_id, self.uid, f_code, register_param) + m_pkt.make(txn_id, self.unit_id, f_code, register_param) self.out_q.push_packet(m_pkt) end @@ -53,7 +55,7 @@ unit_session.new = function (log_tag, advert, out_queue, txn_tags) ---@return integer|false txn_type transaction type or false on error/busy protected.try_resolve = function (m_pkt) if m_pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then - if m_pkt.unit_id == self.uid then + if m_pkt.unit_id == self.unit_id then local txn_type = self.transaction_controller.resolve(m_pkt.txn_id) local txn_tag = " (" .. self.txn_tags[txn_type] .. ")" @@ -115,7 +117,9 @@ unit_session.new = function (log_tag, advert, out_queue, txn_tags) -- PUBLIC FUNCTIONS -- -- get the unit ID - public.get_uid = function () return self.uid end + public.get_unit_id = function () return self.unit_id end + -- get the device index + public.get_device_idx = function () return self.device_index end -- get the reactor ID public.get_reactor = function () return self.reactor end @@ -126,6 +130,22 @@ unit_session.new = function (log_tag, advert, out_queue, txn_tags) -- check if this unit is faulted public.is_faulted = function () return self.device_fail end + -- PUBLIC TEMPLATE FUNCTIONS -- + + -- handle a packet + ---@param m_pkt modbus_frame +---@diagnostic disable-next-line: unused-local + public.handle_packet = function (m_pkt) + log.debug("template unit_session.handle_packet() called", true) + end + + -- update this runner + ---@param time_now integer milliseconds +---@diagnostic disable-next-line: unused-local + public.update = function (time_now) + log.debug("template unit_session.update() called", true) + end + return protected end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index d647f99..e6e800a 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "alpha-v0.3.8" +local SUPERVISOR_VERSION = "alpha-v0.3.9" local print = util.print local println = util.println From 62d5490dc823fa73460c0fd6fce10b0a092fa994 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 18 May 2022 14:30:48 -0400 Subject: [PATCH 209/587] #53 RTU redstone parse checks --- rtu/startup.lua | 128 +++++++++++++++++++++++++----------------- scada-common/util.lua | 11 ++++ 2 files changed, 87 insertions(+), 52 deletions(-) diff --git a/rtu/startup.lua b/rtu/startup.lua index 8293d39..2c9b8d8 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -103,64 +103,88 @@ for entry_idx = 1, #rtu_redstone do log.debug("init> starting redstone RTU I/O linking for reactor " .. io_reactor .. "...") - for i = 1, #io_table do - local valid = false - local conf = io_table[i] + local continue = true - -- 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 = "init> invalid redstone definition at index " .. i .. " in definition block #" .. entry_idx .. - " (for reactor " .. io_reactor .. ")" - println_ts(message) - log.warning(message) - else - -- link redstone in RTU - local mode = rsio.get_io_mode(conf.channel) - if mode == rsio.IO_MODE.DIGITAL_IN then - rs_rtu.link_di(conf.side, conf.bundled_color) - 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 - rs_rtu.link_ai(conf.side) - 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("init> fell through if chain attempting to identify IO mode", true) - break - end - - table.insert(capabilities, conf.channel) - - log.debug("init> linked redstone " .. #capabilities .. ": " .. rsio.to_string(conf.channel) .. " (" .. conf.side .. - ") for reactor " .. io_reactor) + for i = 1, #units do + local unit = units[i] ---@type rtu_unit_registry_entry + if unit.reactor == io_reactor and unit.type == rtu_t.redstone then + -- duplicate entry + log.warning("init> skipping definition block #" .. entry_idx .. " for reactor " .. io_reactor .. " with already defined redstone I/O") + continue = false + break 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 - rtu = rs_rtu, - modbus_io = modbus.new(rs_rtu, false), - pkt_queue = nil, - thread = nil - } + if continue then + for i = 1, #io_table do + local valid = false + local conf = io_table[i] - table.insert(units, unit) + -- 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 - log.debug("init> initialized RTU unit #" .. #units .. ": redstone_io (redstone) [1] for reactor " .. io_reactor) + if not valid then + local message = "init> invalid redstone definition at index " .. i .. " in definition block #" .. entry_idx .. + " (for reactor " .. io_reactor .. ")" + println_ts(message) + log.warning(message) + 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 + log.warning("init> skipping duplicate input for channel " .. rsio.to_string(conf.channel) .. " on side " .. conf.side) + 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 + log.warning("init> skipping duplicate input for channel " .. rsio.to_string(conf.channel) .. " on side " .. conf.side) + 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("init> fell through if chain attempting to identify IO mode", true) + break + end + + table.insert(capabilities, conf.channel) + + log.debug("init> 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 + rtu = rs_rtu, + modbus_io = modbus.new(rs_rtu, false), + pkt_queue = nil, + thread = nil + } + + table.insert(units, unit) + + log.debug("init> initialized RTU unit #" .. #units .. ": redstone_io (redstone) [1] for reactor " .. io_reactor) + end end -- mounted peripherals diff --git a/scada-common/util.lua b/scada-common/util.lua index 38cfc1f..9e7a4a7 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -107,6 +107,17 @@ util.filter_table = function (t, f, on_delete) end end +-- check if a table contains the provided element +---@param t table table to check +---@param element any element to check for +util.table_contains = function (t, element) + for i = 1, #t do + if t[i] == element then return true end + end + + return false +end + -- MEKANISM POWER -- -- function kFE(fe) return fe / 1000 end From dd553125d62b8f00cb9443712e86afb22f85e757 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 19 May 2022 09:19:51 -0400 Subject: [PATCH 210/587] #54 don't trip RPS fault on terminate as it ends up being redundant with shutdown sequence --- reactor-plc/plc.lua | 4 +++- reactor-plc/startup.lua | 2 +- scada-common/ppm.lua | 18 +++++++++++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index b54354c..1123399 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -51,7 +51,9 @@ plc.rps_init = function (reactor) -- set reactor access fault flag local _set_fault = function () - self.state[state_keys.fault] = true + if self.reactor.__p_last_fault() ~= "Terminated" then + self.state[state_keys.fault] = true + end end -- clear reactor access fault flag diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index aad57f7..214b167 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 = "alpha-v0.6.6" +local R_PLC_VERSION = "alpha-v0.6.7" local print = util.print local println = util.println diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index c5026ea..6efa33c 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -19,6 +19,7 @@ local _ppm_sys = { mounts = {}, auto_cf = false, faulted = false, + last_fault = "", terminate = false, mute = false } @@ -32,6 +33,7 @@ local _ppm_sys = { local peri_init = function (iface) local self = { faulted = false, + last_fault = "", auto_cf = true, type = peripheral.getType(iface), device = peripheral.wrap(iface) @@ -51,7 +53,10 @@ local peri_init = function (iface) else -- function failed self.faulted = true + self.last_fault = result + _ppm_sys.faulted = true + _ppm_sys.last_fault = result if not _ppm_sys.mute then log.error("PPM: protected " .. key .. "() -> " .. result) @@ -69,6 +74,7 @@ local peri_init = function (iface) -- fault management functions local clear_fault = function () self.faulted = false end + local get_last_fault = function () return self.last_fault end local is_faulted = function () return self.faulted end local is_ok = function () return not self.faulted end @@ -78,6 +84,7 @@ local peri_init = function (iface) -- append to device functions self.device.__p_clear_fault = clear_fault + self.device.__p_last_fault = get_last_fault self.device.__p_is_faulted = is_faulted self.device.__p_is_ok = is_ok self.device.__p_enable_afc = enable_afc @@ -117,14 +124,19 @@ ppm.disable_afc = function () _ppm_sys.auto_cf = false end +-- clear fault flag +ppm.clear_fault = function () + _ppm_sys.faulted = false +end + -- check fault flag ppm.is_faulted = function () return _ppm_sys.faulted end --- clear fault flag -ppm.clear_fault = function () - _ppm_sys.faulted = false +-- get the last fault message +ppm.get_last_fault = function () + return _ppm_sys.last_fault end -- TERMINATION -- From 6a168c884dd2ce765f15dde805521d822fafa3c6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 19 May 2022 10:21:04 -0400 Subject: [PATCH 211/587] #23 device version reporting --- reactor-plc/plc.lua | 6 ++++-- reactor-plc/startup.lua | 5 +++-- rtu/rtu.lua | 6 ++++-- rtu/startup.lua | 4 ++-- supervisor/session/svsessions.lua | 8 +++++++- supervisor/startup.lua | 2 +- supervisor/supervisor.lua | 27 ++++++++++++++++----------- 7 files changed, 37 insertions(+), 21 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 1123399..db04467 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -278,15 +278,17 @@ end -- Reactor PLC Communications ---@param id integer +---@param version string ---@param modem table ---@param local_port integer ---@param server_port integer ---@param reactor table ---@param rps rps ---@param conn_watchdog watchdog -plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_watchdog) +plc.comms = function (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, @@ -499,7 +501,7 @@ plc.comms = function (id, modem, local_port, server_port, reactor, rps, conn_wat -- attempt to establish link with supervisor public.send_link_req = function () - _send(RPLC_TYPES.LINK_REQ, { self.id }) + _send(RPLC_TYPES.LINK_REQ, { self.id, self.version }) end -- send live status information diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 214b167..c1fb666 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 = "alpha-v0.6.7" +local R_PLC_VERSION = "alpha-v0.6.8" local print = util.print local println = util.println @@ -117,7 +117,8 @@ local init = function () log.debug("init> conn watchdog started") -- start comms - smem_sys.plc_comms = plc.comms(config.REACTOR_ID, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog) + 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"); diff --git a/rtu/rtu.lua b/rtu/rtu.lua index d02bc6c..2e2d445 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -161,12 +161,14 @@ rtu.init_unit = function () end -- RTU Communications +---@param version string ---@param modem table ---@param local_port integer ---@param server_port integer ---@param conn_watchdog watchdog -rtu.comms = function (modem, local_port, server_port, conn_watchdog) +rtu.comms = function (version, modem, local_port, server_port, conn_watchdog) local self = { + version = version, seq_num = 0, r_seq_num = nil, txn_id = 0, @@ -249,7 +251,7 @@ rtu.comms = function (modem, local_port, server_port, conn_watchdog) -- send capability advertisement ---@param units table public.send_advertisement = function (units) - local advertisement = {} + local advertisement = { self.version } for i = 1, #units do local unit = units[i] --@type rtu_unit_registry_entry diff --git a/rtu/startup.lua b/rtu/startup.lua index 2c9b8d8..26abaf9 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -24,7 +24,7 @@ local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "alpha-v0.6.6" +local RTU_VERSION = "alpha-v0.6.7" local rtu_t = types.rtu_t @@ -264,7 +264,7 @@ smem_sys.conn_watchdog = util.new_watchdog(5) log.debug("boot> conn watchdog started") -- setup comms -smem_sys.rtu_comms = rtu.comms(smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, smem_sys.conn_watchdog) +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 diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index d0b76cc..b004a38 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -196,13 +196,15 @@ end ---@param local_port integer ---@param remote_port integer ---@param for_reactor integer +---@param version string ---@return integer|false session_id -svsessions.establish_plc_session = function (local_port, remote_port, for_reactor) +svsessions.establish_plc_session = function (local_port, remote_port, for_reactor, version) if svsessions.get_reactor_session(for_reactor) == nil then ---@class plc_session_struct local plc_s = { open = true, reactor = for_reactor, + version = version, l_port = local_port, r_port = remote_port, in_queue = mqueue.new(), @@ -231,9 +233,13 @@ end ---@param advertisement table ---@return integer session_id svsessions.establish_rtu_session = function (local_port, remote_port, advertisement) + -- pull version from advertisement + local version = table.remove(advertisement, 1) + ---@class rtu_session_struct local rtu_s = { open = true, + version = version, l_port = local_port, r_port = remote_port, in_queue = mqueue.new(), diff --git a/supervisor/startup.lua b/supervisor/startup.lua index e6e800a..e175f3a 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -38,7 +38,7 @@ if modem == nil then end -- start comms, open all channels -local superv_comms = supervisor.comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN) +local superv_comms = supervisor.comms(SUPERVISOR_VERSION, config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN) -- base loop clock (6.67Hz, 3 ticks) local MAIN_CLOCK = 0.15 diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 3a176cb..5bc3ce8 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -20,12 +20,14 @@ local print_ts = util.print_ts local println_ts = util.println_ts -- supervisory controller communications +---@param version string ---@param num_reactors integer ---@param modem table ---@param dev_listen integer ---@param coord_listen integer -supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) +supervisor.comms = function (version, num_reactors, modem, dev_listen, coord_listen) local self = { + version = version, num_reactors = num_reactors, modem = modem, dev_listen = dev_listen, @@ -180,16 +182,16 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) -- unknown session, is this a linking request? if packet.type == RPLC_TYPES.LINK_REQ then - if packet.length == 1 then + if packet.length == 2 then -- this is a linking request - local plc_id = svsessions.establish_plc_session(l_port, r_port, packet.data[1]) + local plc_id = svsessions.establish_plc_session(l_port, r_port, packet.data[1], packet.data[2]) if plc_id == false then -- reactor already has a PLC assigned log.debug("PLC_LNK: assignment collision with reactor " .. packet.data[1]) _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.COLLISION }) else -- got an ID; assigned to a reactor successfully - println("connected to reactor " .. packet.data[1] .. " PLC (port " .. r_port .. ")") + println("connected to reactor " .. packet.data[1] .. " PLC v " .. packet.data[2] .. " (port " .. r_port .. ")") log.debug("PLC_LNK: allowed for device at " .. r_port) _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.ALLOW }) end @@ -210,18 +212,21 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) if session ~= nil then -- pass the packet onto the session handler session.in_queue.push_packet(packet) - else - -- is this an RTU advertisement? - if packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then - local rtu_id = svsessions.establish_rtu_session(l_port, r_port, packet.data) + elseif packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then + if packet.length >= 1 then + -- this is an RTU advertisement for a new session + println("connected to RTU v " .. packet.data[1] .. " (port " .. r_port .. ")") + + svsessions.establish_rtu_session(l_port, r_port, packet.data) - println("connected to RTU (port " .. r_port .. ")") log.debug("RTU_ADVERT: linked " .. r_port) _send_remote_linked(packet.scada_frame.seq_num() + 1, r_port) else - -- any other packet should be session related, discard it - log.debug("discarding SCADA_MGMT packet without a known session") + log.debug("RTU_ADVERT: advertisement packet empty") end + else + -- any other packet should be session related, discard it + log.debug("discarding SCADA_MGMT packet without a known session") end else log.debug("illegal packet type " .. protocol .. " on device listening channel") From 44d30ae5839738f1ef181ad44f9672a371d20800 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 19 May 2022 10:35:56 -0400 Subject: [PATCH 212/587] #48 only log every 20 PPM faults (per function) --- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- scada-common/ppm.lua | 18 ++++++++++++++++-- supervisor/startup.lua | 2 +- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index c1fb666..86e7722 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 = "alpha-v0.6.8" +local R_PLC_VERSION = "alpha-v0.6.9" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index 26abaf9..90578ce 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -24,7 +24,7 @@ local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "alpha-v0.6.7" +local RTU_VERSION = "alpha-v0.6.8" local rtu_t = types.rtu_t diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 6efa33c..fd84503 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -15,6 +15,8 @@ ppm.ACCESS_FAULT = ACCESS_FAULT -- PRIVATE DATA/FUNCTIONS -- ---------------------------- +local REPORT_FREQUENCY = 20 -- log every 20 faults per function + local _ppm_sys = { mounts = {}, auto_cf = false, @@ -34,6 +36,7 @@ local peri_init = function (iface) local self = { faulted = false, last_fault = "", + fault_counts = {}, auto_cf = true, type = peripheral.getType(iface), device = peripheral.wrap(iface) @@ -42,6 +45,7 @@ local peri_init = function (iface) -- initialization process (re-map) for key, func in pairs(self.device) do + self.fault_counts[key] = 0 self.device[key] = function (...) local status, result = pcall(func, ...) @@ -49,6 +53,9 @@ local peri_init = function (iface) -- auto fault clear if self.auto_cf then self.faulted = false end if _ppm_sys.auto_cf then _ppm_sys.faulted = false end + + self.fault_counts[key] = 0 + return result else -- function failed @@ -58,10 +65,17 @@ local peri_init = function (iface) _ppm_sys.faulted = true _ppm_sys.last_fault = result - if not _ppm_sys.mute then - log.error("PPM: protected " .. key .. "() -> " .. result) + 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 faults]" + end + + log.error("PPM: protected " .. key .. "() -> " .. result .. count_str) end + self.fault_counts[key] = self.fault_counts[key] + 1 + if result == "Terminated" then _ppm_sys.terminate = true end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index e175f3a..d23436d 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "alpha-v0.3.9" +local SUPERVISOR_VERSION = "alpha-v0.3.10" local print = util.print local println = util.println From 61965f295dfcfc83b6cb5ac92c8f570de15f3223 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 19 May 2022 10:49:17 -0400 Subject: [PATCH 213/587] added #29 to known issues --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 1aa8880..d0a34c1 100644 --- a/README.md +++ b/README.md @@ -54,3 +54,8 @@ TBD, I am planning on AES symmetric encryption for security + HMAC to prevent re This is somewhat important here as otherwise anyone can just control your setup, which is undeseriable. Unlike normal Minecraft PVP chaos, it would be very difficult to identify who is messing with your system, as with an Ender Modem they can do it from effectively anywhere and the server operators would have to check every computer's filesystem to find suspicious code. The only other possible security mitigation for commanding (no effect on monitoring) is to enforce a maximum authorized transmission range (which I will probably also do, or maybe fall back to), as modem message events contain the transmission distance. + +## Known Issues + +GitHub issue \#29: +It appears that with Mekanism 10.0, a boiler peripheral may rapidly disconnect/reconnect constantly while running. This will prevent that RTU from operating correctly while also filling up the log file. This may be due to a very specific version interaction of CC: Tweaked and Mekansim, so you are welcome to try this on Mekanism 10.0 servers, but do be aware it may not work. From 3f4fb630299a89ae1af945fb0836337381a30dc4 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 21 May 2022 12:24:43 -0400 Subject: [PATCH 214/587] #52 basic reactor unit object --- scada-common/types.lua | 9 + supervisor/session/plc.lua | 19 +- supervisor/session/rtu/unit_session.lua | 3 + supervisor/unit.lua | 346 ++++++++++++++++++++---- 4 files changed, 321 insertions(+), 56 deletions(-) diff --git a/scada-common/types.lua b/scada-common/types.lua index b7f115b..b1b5e8c 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -22,6 +22,15 @@ local types = {} ---@field reactor integer ---@field rsio table|nil +-- ENUMERATION TYPES -- + +---@alias TRI_FAIL integer +types.TRI_FAIL = { + OK = 0, + PARTIAL = 1, + FULL = 2 +} + -- STRING TYPES -- ---@alias rtu_t string diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index f799987..bf79f23 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -422,7 +422,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end end - -- get the reactor structure + -- get the reactor status public.get_status = function () if self.received_status_cache then return self.sDB.mek_status @@ -431,6 +431,23 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end end + -- get the reactor RPS status + public.get_rps = function () + return self.sDB.rps_status + end + + -- get the general status information + public.get_general_status = function () + return { + last_status_update = self.sDB.last_status_update, + control_state = self.sDB.control_state, + overridden = self.sDB.overridden, + degraded = self.sDB.degraded, + rps_tripped = self.sDB.rps_tripped, + rps_trip_cause = self.sDB.rps_trip_cause + } + end + -- check if a timer matches this session's watchdog public.check_wd = function (timer) return self.plc_conn_watchdog.is_timer(timer) and self.connected diff --git a/supervisor/session/rtu/unit_session.lua b/supervisor/session/rtu/unit_session.lua index 4f50120..83a0766 100644 --- a/supervisor/session/rtu/unit_session.lua +++ b/supervisor/session/rtu/unit_session.lua @@ -146,6 +146,9 @@ unit_session.new = function (unit_id, advert, out_queue, log_tag, txn_tags) log.debug("template unit_session.update() called", true) end + -- get the unit session database + public.get_db = function () return {} end + return protected end diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 0afd7e5..628de67 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -3,11 +3,20 @@ local util = require "scada-common.util" local unit = {} ----@alias TRI_FAIL integer -local TRI_FAIL = { - OK = 0, - PARTIAL = 1, - FULL = 2 +local TRI_FAIL = types.TRI_FAIL +local DUMPING_MODE = types.DUMPING_MODE + +local DT_KEYS = { + ReactorTemp = "RTP", + ReactorFuel = "RFL", + ReactorWaste = "RWS", + ReactorCCool = "RCC", + ReactorHCool = "RHC", + BoilerWater = "BWR", + BoilerSteam = "BST", + BoilerCCool = "BCC", + BoilerHCool = "BHC", + TurbineSteam = "TST" } -- create a new reactor unit @@ -21,12 +30,8 @@ unit.new = function (for_reactor, num_boilers, num_turbines) counts = { boilers = num_boilers, turbines = num_turbines }, turbines = {}, boilers = {}, - energy_storage = {}, redstone = {}, - deltas = { - last_reactor_temp = nil, - last_reactor_temp_time = 0 - }, + deltas = {}, db = { ---@class annunciator annunciator = { @@ -38,55 +43,137 @@ unit.new = function (for_reactor, num_boilers, num_turbines) RCSFlowLow = false, ReactorTempHigh = false, ReactorHighDeltaT = false, + FuelInputRateLow = false, + WasteLineOcclusion = false, HighStartupRate = false, -- boiler BoilerOnline = TRI_FAIL.OK, - HeatingRateLow = false, + HeatingRateLow = {}, BoilRateMismatch = false, CoolantFeedMismatch = false, -- turbine TurbineOnline = TRI_FAIL.OK, SteamFeedMismatch = false, - SteamDumpOpen = false, - TurbineOverSpeed = false, - TurbineTrip = false + MaxWaterReturnFeed = false, + SteamDumpOpen = {}, + TurbineOverSpeed = {}, + TurbineTrip = {} } } } + -- init boiler table fields + for _ = 1, self.num_boilers do + table.insert(self.db.annunciator.HeatingRateLow, false) + end + + -- init turbine table fields + for _ = 1, self.num_turbines do + table.insert(self.db.annunciator.SteamDumpOpen, TRI_FAIL.OK) + table.insert(self.db.annunciator.TurbineOverSpeed, false) + table.insert(self.db.annunciator.TurbineTrip, false) + end + ---@class reactor_unit local public = {} -- PRIVATE FUNCTIONS -- + -- compute a change with respect to time of the given value + ---@param key string value key + ---@param value number value + local _compute_dt = function (key, value) + if self.deltas[key] then + local data = self.deltas[key] + + data.dt = (value - data.last_v) / (util.time_s() - data.last_t) + + data.last_v = value + data.last_t = util.time_s() + else + self.deltas[key] = { + last_t = util.time_s(), + last_v = value, + dt = 0.0 + } + end + end + + -- clear a delta + ---@param key string value key + local _reset_dt = function (key) + self.deltas[key] = nil + end + + -- get the delta t of a value + ---@param key string value key + ---@return number + local _get_dt = function (key) + if self.deltas[key] then + return self.deltas[key].dt + else + return 0.0 + end + end + + -- update all delta computations + local _dt__compute_all = function () + if self.plc_s ~= nil then + local plc_db = self.plc_s.get_db() + + -- @todo Meknaism 10.1+ will change fuel/waste to need _amnt + _compute_dt(DT_KEYS.ReactorTemp, plc_db.mek_status.temp) + _compute_dt(DT_KEYS.ReactorFuel, plc_db.mek_status.fuel) + _compute_dt(DT_KEYS.ReactorWaste, plc_db.mek_status.waste) + _compute_dt(DT_KEYS.ReactorCCool, plc_db.mek_status.ccool_amnt) + _compute_dt(DT_KEYS.ReactorHCool, plc_db.mek_status.hcool_amnt) + end + + for i = 1, #self.boilers do + local boiler = self.boilers[i] ---@type unit_session + local db = boiler.get_db() ---@type boiler_session_db + + -- @todo Meknaism 10.1+ will change water/steam to need .amount + _compute_dt(DT_KEYS.BoilerWater .. boiler.get_device_idx(), db.tanks.water) + _compute_dt(DT_KEYS.BoilerSteam .. boiler.get_device_idx(), db.tanks.steam) + _compute_dt(DT_KEYS.BoilerCCool .. boiler.get_device_idx(), db.tanks.ccool.amount) + _compute_dt(DT_KEYS.BoilerHCool .. boiler.get_device_idx(), db.tanks.hcool.amount) + end + + for i = 1, #self.turbines do + local turbine = self.turbines[i] ---@type unit_session + local db = turbine.get_db() ---@type turbine_session_db + + _compute_dt(DT_KEYS.TurbineSteam .. turbine.get_device_idx(), db.tanks.steam) + -- @todo Mekanism 10.1+ needed + -- _compute_dt(DT_KEYS.TurbinePower .. turbine.get_device_idx(), db.?) + end + end + -- update the annunciator local _update_annunciator = function () + -- update deltas + _dt__compute_all() + + ------------- + -- REACTOR -- + ------------- + -- check PLC status self.db.annunciator.PLCOnline = (self.plc_s ~= nil) and (self.plc_s.open) if self.plc_s ~= nil then - ------------- - -- REACTOR -- - ------------- - local plc_db = self.plc_s.get_db() - -- compute deltas - local reactor_delta_t = 0 - if self.deltas.last_reactor_temp ~= nil then - reactor_delta_t = (plc_db.mek_status.temp - self.deltas.last_reactor_temp) / (util.time_s() - self.deltas.last_reactor_temp_time) - else - self.deltas.last_reactor_temp = plc_db.mek_status.temp - self.deltas.last_reactor_temp_time = util.time_s() - end - -- update annunciator self.db.annunciator.ReactorTrip = plc_db.rps_tripped self.db.annunciator.ManualReactorTrip = plc_db.rps_trip_cause == types.rps_status_t.manual self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.no_cool) self.db.annunciator.RCSFlowLow = plc_db.mek_status.ccool_fill < 0.75 or plc_db.mek_status.hcool_fill > 0.25 self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > 1000 - self.db.annunciator.ReactorHighDeltaT = reactor_delta_t > 100 + self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > 100 + self.db.annunciator.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < 0.0 or plc_db.mek_status.fuel_fill <= 0.01 + self.db.annunciator.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 0.0 or plc_db.mek_status.waste_fill >= 0.99 -- @todo this is dependent on setup, i.e. how much coolant is buffered and the turbine setup self.db.annunciator.HighStartupRate = not plc_db.control_state and plc_db.mek_status.burn_rate > 40 end @@ -105,33 +192,52 @@ unit.new = function (for_reactor, num_boilers, num_turbines) self.db.annunciator.BoilerOnline = TRI_FAIL.OK end + -- compute aggregated statistics local total_boil_rate = 0.0 - local no_boil_count = 0 + local boiler_steam_dt_sum = 0.0 + local boiler_water_dt_sum = 0.0 for i = 1, #self.boilers do local boiler = self.boilers[i].get_db() ---@type boiler_session_db - local boil_rate = boiler.state.boil_rate - if boil_rate == 0 then - no_boil_count = no_boil_count + 1 - else - total_boil_rate = total_boil_rate + boiler.state.boil_rate - end - end - - if no_boil_count == 0 and self.num_boilers > 0 then - self.db.annunciator.HeatingRateLow = TRI_FAIL.FULL - elseif no_boil_count > 0 and no_boil_count ~= self.num_boilers then - self.db.annunciator.HeatingRateLow = TRI_FAIL.PARTIAL - else - self.db.annunciator.HeatingRateLow = TRI_FAIL.OK + total_boil_rate = total_boil_rate + boiler.state.boil_rate + boiler_steam_dt_sum = _get_dt(DT_KEYS.BoilerSteam .. self.boilers[i].get_device_idx()) + boiler_water_dt_sum = _get_dt(DT_KEYS.BoilerWater .. self.boilers[i].get_device_idx()) end + -- check heating rate low if self.plc_s ~= nil then + -- check for inactive boilers while reactor is active + for i = 1, #self.boilers do + local boiler = self.boilers[i] ---@type unit_session + local idx = boiler.get_device_idx() + local db = boiler.get_db() ---@type boiler_session_db + + if self.plc_s.get_db().mek_status.status then + self.db.annunciator.HeatingRateLow[idx] = db.state.boil_rate == 0 + else + self.db.annunciator.HeatingRateLow[idx] = false + end + end + + -- check for rate mismatch local expected_boil_rate = self.plc_s.get_db().mek_status.heating_rate / 10.0 self.db.annunciator.BoilRateMismatch = math.abs(expected_boil_rate - total_boil_rate) > 25.0 - else - self.db.annunciator.BoilRateMismatch = false end + -- check coolant feed mismatch + local cfmismatch = false + for i = 1, #self.boilers do + local boiler = self.boilers[i] ---@type unit_session + local idx = boiler.get_device_idx() + local db = boiler.get_db() ---@type boiler_session_db + + -- gaining heated coolant + cfmismatch = cfmismatch or _get_dt(DT_KEYS.BoilerHCool .. idx) > 0 or db.tanks.hcool_fill == 1 + -- losing cooled coolant + cfmismatch = cfmismatch or _get_dt(DT_KEYS.BoilerCCool .. idx) < 0 or db.tanks.ccool_fill == 0 + end + + self.db.annunciator.CoolantFeedMismatch = cfmismatch + -------------- -- TURBINES -- -------------- @@ -146,19 +252,62 @@ unit.new = function (for_reactor, num_boilers, num_turbines) self.db.annunciator.TurbineOnline = TRI_FAIL.OK end - --[[ - Turbine Under/Over Speed - ]]-- + -- compute aggregated statistics + local total_flow_rate = 0 + local total_input_rate = 0 + local max_water_return_rate = 0 + for i = 1, #self.turbines do + local turbine = self.turbines[i].get_db() ---@type turbine_session_db + total_flow_rate = total_flow_rate + turbine.state.flow_rate + total_input_rate = total_input_rate + turbine.state.steam_input_rate + max_water_return_rate = max_water_return_rate + turbine.build.max_water_output + end + + -- check for steam feed mismatch and max return rate + local sfmismatch = math.abs(total_flow_rate - total_input_rate) > 10 + sfmismatch = sfmismatch or boiler_steam_dt_sum > 0 or boiler_water_dt_sum < 0 + self.db.annunciator.SteamFeedMismatch = sfmismatch + self.db.annunciator.MaxWaterReturnFeed = max_water_return_rate == total_flow_rate + + -- check if steam dumps are open + for i = 1, #self.turbines do + local turbine = self.turbines[i] ---@type unit_session + local db = turbine.get_db() ---@type turbine_session_db + local idx = turbine.get_device_idx() + + if db.state.dumping_mode == DUMPING_MODE.IDLE then + self.db.annunciator.SteamDumpOpen[idx] = TRI_FAIL.OK + elseif db.state.dumping_mode == DUMPING_MODE.DUMPING_EXCESS then + self.db.annunciator.SteamDumpOpen[idx] = TRI_FAIL.PARTIAL + else + self.db.annunciator.SteamDumpOpen[idx] = TRI_FAIL.FULL + end + end + + -- check if turbines are at max speed but not keeping up + for i = 1, #self.turbines do + local turbine = self.turbines[i] ---@type unit_session + local db = turbine.get_db() ---@type turbine_session_db + local idx = turbine.get_device_idx() + + self.db.annunciator.TurbineOverSpeed[idx] = (db.state.flow_rate == db.build.max_flow_rate) and (_get_dt(DT_KEYS.TurbineSteam .. idx) > 0) + end --[[ Turbine Trip - a turbine trip is when the turbine stops, which means we are no longer receiving water and lose the ability to cool + a turbine trip is when the turbine stops, which means we are no longer receiving water and lose the ability to cool. this can be identified by these conditions: - the current flow rate is 0 mB/t and it should not be - - it should not be if the boiler or reactor has a non-zero heating rate - can initially catch this by detecting a 0 flow rate with a non-zero input rate, but eventually the steam will fill up - can later identified by presence of steam in tank with a 0 flow rate ]]-- + for i = 1, #self.turbines do + local turbine = self.turbines[i] ---@type unit_session + local db = turbine.get_db() ---@type turbine_session_db + + local has_steam = db.state.steam_input_rate > 0 or db.tanks.steam_fill > 0.01 + self.db.annunciator.TurbineTrip[turbine.get_device_idx()] = has_steam and db.state.flow_rate == 0 + end end -- unlink disconnected units @@ -173,20 +322,47 @@ unit.new = function (for_reactor, num_boilers, num_turbines) ---@param plc_session plc_session_struct public.link_plc_session = function (plc_session) self.plc_s = plc_session - self.deltas.last_reactor_temp = self.plc_s.get_db().mek_status.temp - self.deltas.last_reactor_temp_time = util.time_s() + + -- reset deltas + _reset_dt(DT_KEYS.ReactorTemp) + _reset_dt(DT_KEYS.ReactorFuel) + _reset_dt(DT_KEYS.ReactorWaste) + _reset_dt(DT_KEYS.ReactorCCool) + _reset_dt(DT_KEYS.ReactorHCool) end -- link a turbine RTU session ---@param turbine unit_session public.add_turbine = function (turbine) - table.insert(self.turbines, turbine) + if #self.turbines < self.num_turbines and turbine.get_device_idx() <= self.num_turbines then + table.insert(self.turbines, turbine) + + -- reset deltas + _reset_dt(DT_KEYS.TurbineSteam .. turbine.get_device_idx()) + _reset_dt(DT_KEYS.TurbinePower .. turbine.get_device_idx()) + + return true + else + return false + end end -- link a boiler RTU session ---@param boiler unit_session public.add_boiler = function (boiler) - table.insert(self.boilers, boiler) + if #self.boilers < self.num_boilers and boiler.get_device_idx() <= self.num_boilers then + table.insert(self.boilers, boiler) + + -- reset deltas + _reset_dt(DT_KEYS.BoilerWater .. boiler.get_device_idx()) + _reset_dt(DT_KEYS.BoilerSteam .. boiler.get_device_idx()) + _reset_dt(DT_KEYS.BoilerCCool .. boiler.get_device_idx()) + _reset_dt(DT_KEYS.BoilerHCool .. boiler.get_device_idx()) + + return true + else + return false + end end -- link a redstone RTU capability @@ -200,7 +376,7 @@ unit.new = function (for_reactor, num_boilers, num_turbines) table.insert(self.redstone[field], accessor) end - -- update (iterate) this session + -- update (iterate) this unit public.update = function () -- unlink PLC if session was closed if not self.plc_s.open then @@ -215,6 +391,66 @@ unit.new = function (for_reactor, num_boilers, num_turbines) _update_annunciator() end + -- get build properties of all machines + public.get_build = function () + local build = {} + + if self.plc_s ~= nil then + build.reactor = self.plc_s.get_struct() + end + + build.boilers = {} + for i = 1, #self.boilers do + table.insert(build.boilers, self.boilers[i].get_db().build) + end + + build.turbines = {} + for i = 1, #self.turbines do + table.insert(build.turbines, self.turbines[i].get_db().build) + end + + return build + end + + -- get reactor status + public.get_reactor_status = function () + local status = {} + + if self.plc_s ~= nil then + local reactor = self.plc_s + status.mek = reactor.get_status() + status.rps = reactor.get_rps() + status.general = reactor.get_general_status() + end + + return status + end + + -- get RTU statuses + public.get_rtu_statuses = function () + local status = {} + + -- status of boilers (including tanks) + status.boilers = {} + for i = 1, #self.boilers do + table.insert(status.boilers, { + state = self.boilers[i].get_db().state, + tanks = self.boilers[i].get_db().tanks, + }) + end + + -- status of turbines (including tanks) + status.turbines = {} + for i = 1, #self.turbines do + table.insert(status.turbines, { + state = self.turbines[i].get_db().state, + tanks = self.turbines[i].get_db().tanks, + }) + end + + return status + end + -- get the annunciator status public.get_annunciator = function () return self.db.annunciator end From 940ddf0d0083c2bea95721737ced158538b5ac24 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 21 May 2022 12:30:14 -0400 Subject: [PATCH 215/587] function for duplicate session search code --- supervisor/session/svsessions.lua | 49 +++++++++++-------------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index b004a38..409753c 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -106,6 +106,17 @@ local function _free_closed(sessions) util.filter_table(sessions, f, on_delete) end +-- find a session by remote port +---@param list table +---@param port integer +---@return plc_session_struct|rtu_session_struct|nil +local function _find_session(list, port) + for i = 1, #list do + if list[i].r_port == port then return list[i] end + end + return nil +end + -- PUBLIC FUNCTIONS -- -- link the modem @@ -119,13 +130,7 @@ end ---@return rtu_session_struct|nil svsessions.find_rtu_session = function (remote_port) -- check RTU sessions - for i = 1, #self.rtu_sessions do - if self.rtu_sessions[i].r_port == remote_port then - return self.rtu_sessions[i] - end - end - - return nil + return _find_session(self.rtu_sessions, remote_port) end -- find a PLC session by the remote port @@ -133,13 +138,7 @@ end ---@return plc_session_struct|nil svsessions.find_plc_session = function (remote_port) -- check PLC sessions - for i = 1, #self.plc_sessions do - if self.plc_sessions[i].r_port == remote_port then - return self.plc_sessions[i] - end - end - - return nil + return _find_session(self.plc_sessions, remote_port) end -- find a PLC/RTU session by the remote port @@ -147,20 +146,12 @@ end ---@return plc_session_struct|rtu_session_struct|nil svsessions.find_device_session = function (remote_port) -- check RTU sessions - for i = 1, #self.rtu_sessions do - if self.rtu_sessions[i].r_port == remote_port then - return self.rtu_sessions[i] - end - end + local s = _find_session(self.rtu_sessions, remote_port) -- check PLC sessions - for i = 1, #self.plc_sessions do - if self.plc_sessions[i].r_port == remote_port then - return self.plc_sessions[i] - end - end + if s == nil then s = _find_session(self.plc_sessions, remote_port) end - return nil + return s end -- find a coordinator session by the remote port @@ -168,13 +159,7 @@ end ---@return nil svsessions.find_coord_session = function (remote_port) -- check coordinator sessions - for i = 1, #self.coord_sessions do - if self.coord_sessions[i].r_port == remote_port then - return self.coord_sessions[i] - end - end - - return nil + return _find_session(self.coord_sessions, remote_port) end -- get a session by reactor ID From 3b16d783d3e6fa4633ed31a75063c49cd2b974af Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 21 May 2022 13:55:22 -0400 Subject: [PATCH 216/587] fixed bug with RSIO channel valid check --- scada-common/rsio.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 5b9970a..8f981cd 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -205,7 +205,7 @@ local RS_SIDES = rs.getSides() ---@param channel RS_IO ---@return boolean valid rsio.is_valid_channel = function (channel) - return (channel ~= nil) and (channel > 0) and (channel <= #RS_IO) + return (channel ~= nil) and (channel > 0) and (channel <= RS_IO.R_PLC_TIMEOUT) end -- check if a side is valid From 26c6010ce0f32943616647d52dddeab721e62362 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 21 May 2022 13:56:14 -0400 Subject: [PATCH 217/587] #56 pcall threads and restart on crash (unless shutting down) --- reactor-plc/startup.lua | 6 +- reactor-plc/threads.lua | 122 ++++++++++++++++++++++++++++++++++++---- rtu/startup.lua | 6 +- rtu/threads.lua | 69 +++++++++++++++++++++-- 4 files changed, 181 insertions(+), 22 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 86e7722..c401961 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 = "alpha-v0.6.9" +local R_PLC_VERSION = "alpha-v0.7.0" local print = util.print local println = util.println @@ -156,7 +156,7 @@ if __shared_memory.networked then local sp_ctrl_thread = threads.thread__setpoint_control(__shared_memory) -- run threads - parallel.waitForAll(main_thread.exec, rps_thread.exec, comms_thread_tx.exec, comms_thread_rx.exec, sp_ctrl_thread.exec) + 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 @@ -168,7 +168,7 @@ if __shared_memory.networked then end else -- run threads, excluding comms - parallel.waitForAll(main_thread.exec, rps_thread.exec) + parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec) end println_ts("exited") diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 9531792..561f72c 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -31,8 +31,10 @@ local MQ__COMM_CMD = { ---@param smem plc_shared_memory ---@param init function threads.thread__main = function (smem, init) + local public = {} ---@class thread + -- execute thread - local exec = function () + public.exec = function () log.debug("main thread init, clock inactive") -- send status updates at 2Hz (every 10 server ticks) (every loop tick) @@ -183,14 +185,38 @@ threads.thread__main = function (smem, init) end end - return { exec = exec } + -- execute the thread in a protected mode, retrying it on return if not shutting down + public.p_exec = function () + local plc_state = smem.plc_state + + while not plc_state.shutdown do + local status, result = pcall(public.exec) + if status == false then + log.fatal(result) + end + + -- if status is true, then we are probably exiting, so this won't matter + -- if not, we need to restart the clock + -- this thread cannot be slept because it will miss events (namely "terminate" otherwise) + if not plc_state.shutdown then + log.info("main thread restarting now...") + +---@diagnostic disable-next-line: undefined-field + os.queueEvent("clock_start") + end + end + end + + return public end -- RPS operation thread ---@param smem plc_shared_memory threads.thread__rps = function (smem) + local public = {} ---@class thread + -- execute thread - local exec = function () + public.exec = function () log.debug("rps thread start") -- load in from shared memory @@ -301,14 +327,35 @@ threads.thread__rps = function (smem) end end - return { exec = exec } + -- execute the thread in a protected mode, retrying it on return if not shutting down + public.p_exec = function () + local plc_state = smem.plc_state + local rps = smem.plc_sys.rps + + while not plc_state.shutdown do + local status, result = pcall(public.exec) + if status == false then + log.fatal(result) + end + + if not plc_state.shutdown then + if plc_state.init_ok then rps.scram() end + log.info("rps thread restarting in 5 seconds...") + util.psleep(5) + end + end + end + + return public end -- communications sender thread ---@param smem plc_shared_memory threads.thread__comms_tx = function (smem) + local public = {} ---@class thread + -- execute thread - local exec = function () + public.exec = function () log.debug("comms tx thread start") -- load in from shared memory @@ -355,14 +402,33 @@ threads.thread__comms_tx = function (smem) end end - return { exec = exec } + -- execute the thread in a protected mode, retrying it on return if not shutting down + public.p_exec = function () + local plc_state = smem.plc_state + + while not plc_state.shutdown do + local status, result = pcall(public.exec) + if status == false then + log.fatal(result) + end + + if not plc_state.shutdown then + log.info("comms tx thread restarting in 5 seconds...") + util.psleep(5) + end + end + end + + return public end -- communications handler thread ---@param smem plc_shared_memory threads.thread__comms_rx = function (smem) + local public = {} ---@class thread + -- execute thread - local exec = function () + public.exec = function () log.debug("comms rx thread start") -- load in from shared memory @@ -408,14 +474,33 @@ threads.thread__comms_rx = function (smem) end end - return { exec = exec } + -- execute the thread in a protected mode, retrying it on return if not shutting down + public.p_exec = function () + local plc_state = smem.plc_state + + while not plc_state.shutdown do + local status, result = pcall(public.exec) + if status == false then + log.fatal(result) + end + + if not plc_state.shutdown then + log.info("comms rx thread restarting in 5 seconds...") + util.psleep(5) + end + end + end + + return public end -- apply setpoints ---@param smem plc_shared_memory threads.thread__setpoint_control = function (smem) + local public = {} ---@class thread + -- execute thread - local exec = function () + public.exec = function () log.debug("setpoint control thread start") -- load in from shared memory @@ -511,7 +596,24 @@ threads.thread__setpoint_control = function (smem) end end - return { exec = exec } + -- execute the thread in a protected mode, retrying it on return if not shutting down + public.p_exec = function () + local plc_state = smem.plc_state + + while not plc_state.shutdown do + local status, result = pcall(public.exec) + if status == false then + log.fatal(result) + end + + if not plc_state.shutdown then + log.info("setpoint control thread restarting in 5 seconds...") + util.psleep(5) + end + end + end + + return public end return threads diff --git a/rtu/startup.lua b/rtu/startup.lua index 90578ce..003a2d6 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -24,7 +24,7 @@ local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "alpha-v0.6.8" +local RTU_VERSION = "alpha-v0.7.0" local rtu_t = types.rtu_t @@ -272,10 +272,10 @@ local main_thread = threads.thread__main(__shared_memory) local comms_thread = threads.thread__comms(__shared_memory) -- assemble thread list -local _threads = { main_thread.exec, comms_thread.exec } +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.exec) + table.insert(_threads, units[i].thread.p_exec) end end diff --git a/rtu/threads.lua b/rtu/threads.lua index a3d19d7..3d83acf 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -28,8 +28,10 @@ local COMMS_SLEEP = 100 -- (100ms, 2 ticks) -- main thread ---@param smem rtu_shared_memory threads.thread__main = function (smem) + local public = {} ---@class thread + -- execute thread - local exec = function () + public.exec = function () log.debug("main thread start") -- main loop clock @@ -152,14 +154,33 @@ threads.thread__main = function (smem) end end - return { exec = exec } + -- execute the thread in a protected mode, retrying it on return if not shutting down + public.p_exec = function () + local rtu_state = smem.rtu_state + + while not rtu_state.shutdown do + local status, result = pcall(public.exec) + if status == false then + log.fatal(result) + end + + if not rtu_state.shutdown then + log.info("main thread restarting in 5 seconds...") + util.psleep(5) + end + end + end + + return public end -- communications handler thread ---@param smem rtu_shared_memory threads.thread__comms = function (smem) + local public = {} ---@class thread + -- execute thread - local exec = function () + public.exec = function () log.debug("comms thread start") -- load in from shared memory @@ -205,15 +226,34 @@ threads.thread__comms = function (smem) end end - return { exec = exec } + -- execute the thread in a protected mode, retrying it on return if not shutting down + public.p_exec = function () + local rtu_state = smem.rtu_state + + while not rtu_state.shutdown do + local status, result = pcall(public.exec) + if status == false then + log.fatal(result) + end + + if not rtu_state.shutdown then + log.info("comms thread restarting in 5 seconds...") + util.psleep(5) + end + end + end + + return public end -- per-unit communications handler thread ---@param smem rtu_shared_memory ---@param unit rtu_unit_registry_entry threads.thread__unit_comms = function (smem, unit) + local public = {} ---@class thread + -- execute thread - local exec = function () + public.exec = function () log.debug("rtu unit thread start -> " .. unit.name .. "(" .. unit.type .. ")") -- load in from shared memory @@ -256,7 +296,24 @@ threads.thread__unit_comms = function (smem, unit) end end - return { exec = exec } + -- execute the thread in a protected mode, retrying it on return if not shutting down + public.p_exec = function () + local rtu_state = smem.rtu_state + + while not rtu_state.shutdown do + local status, result = pcall(public.exec) + if status == false then + log.fatal(result) + end + + if not rtu_state.shutdown then + log.info("rtu unit thread " .. unit.name .. "(" .. unit.type .. ") restarting in 5 seconds...") + util.psleep(5) + end + end + end + + return public end return threads From a93f0a4452cf796be3172cabd46480dc1d65b131 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 22 May 2022 17:57:24 -0400 Subject: [PATCH 218/587] #57 updates per safety pass, fixed plc_sys fields staying nil on degraded start, fixed repeated SCRAM messages when unlinked --- reactor-plc/plc.lua | 2 +- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 54 +++++++++++++++++++++++------------------ scada-common/log.lua | 6 +++++ scada-common/ppm.lua | 8 +++--- scada-common/util.lua | 6 +++-- 6 files changed, 47 insertions(+), 31 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index db04467..b5d36b5 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -155,7 +155,7 @@ plc.rps_init = function (reactor) -- trip for a PLC comms timeout public.trip_timeout = function () - self.state[state_keys.timed_out] = true + self.state[state_keys.timeout] = true end -- manually SCRAM the reactor diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index c401961..8a2d296 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 = "alpha-v0.7.0" +local R_PLC_VERSION = "alpha-v0.7.1" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 561f72c..f28c775 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -47,12 +47,14 @@ threads.thread__main = function (smem, init) local networked = smem.networked local plc_state = smem.plc_state local plc_dev = smem.plc_dev - local rps = smem.plc_sys.rps - local plc_comms = smem.plc_sys.plc_comms - local conn_watchdog = smem.plc_sys.conn_watchdog -- event loop while true do + -- get plc_sys fields (may have been set late due to degraded boot) + local rps = smem.plc_sys.rps + local plc_comms = smem.plc_sys.plc_comms + local conn_watchdog = smem.plc_sys.conn_watchdog + ---@diagnostic disable-next-line: undefined-field local event, param1, param2, param3, param4, param5 = os.pullEventRaw() @@ -77,14 +79,14 @@ threads.thread__main = function (smem, init) end end end - elseif event == "modem_message" and networked and not plc_state.no_modem then + elseif event == "modem_message" and networked and plc_state.init_ok and not plc_state.no_modem then -- got a packet local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5) if packet ~= nil then -- pass the packet onto the comms message queue smem.q.mq_comms_rx.push_packet(packet) end - elseif event == "timer" and networked and conn_watchdog.is_timer(param1) then + elseif event == "timer" and networked and plc_state.init_ok and conn_watchdog.is_timer(param1) then -- haven't heard from server recently? shutdown reactor plc_comms.unlink() smem.q.mq_rps.push_command(MQ__RPS_CMD.TRIP_TIMEOUT) @@ -128,7 +130,7 @@ threads.thread__main = function (smem, init) smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM) println_ts("reactor reconnected.") - log.info("reactor reconnected.") + log.info("reactor reconnected") plc_state.no_reactor = false if plc_state.init_ok then @@ -139,7 +141,7 @@ threads.thread__main = function (smem, init) end -- determine if we are still in a degraded state - if not networked or ppm.get_device("modem") ~= nil then + if not networked or not plc_state.no_modem then plc_state.degraded = false end elseif networked and type == "modem" then @@ -152,19 +154,20 @@ threads.thread__main = function (smem, init) end println_ts("wireless modem reconnected.") - log.info("comms modem reconnected.") + log.info("comms modem reconnected") plc_state.no_modem = false -- determine if we are still in a degraded state - if ppm.get_device("fissionReactor") ~= nil then + if not plc_state.no_reactor then plc_state.degraded = false end else - log.info("wired modem reconnected.") + log.info("wired modem reconnected") end end end + -- if not init'd and no longer degraded, proceed to init if not plc_state.init_ok and not plc_state.degraded then plc_state.init_ok = true init() @@ -223,8 +226,6 @@ threads.thread__rps = function (smem) local networked = smem.networked local plc_state = smem.plc_state local plc_dev = smem.plc_dev - local rps = smem.plc_sys.rps - local plc_comms = smem.plc_sys.plc_comms local rps_queue = smem.q.mq_rps @@ -233,13 +234,16 @@ threads.thread__rps = function (smem) -- thread loop while true do - local reactor = plc_dev.reactor + -- get plc_sys fields (may have been set late due to degraded boot) + local rps = smem.plc_sys.rps + local plc_comms = smem.plc_sys.plc_comms + -- get reactor, may have changed do to disconnect/reconnect + local reactor = plc_dev.reactor -- RPS checks if plc_state.init_ok then -- SCRAM if no open connection if networked and not plc_comms.is_linked() then - rps.scram() if was_linked then was_linked = false rps.trip_timeout() @@ -264,7 +268,7 @@ threads.thread__rps = function (smem) if not plc_state.no_reactor then local rps_tripped, rps_status_string, rps_first = rps.check() - if rps_first then + if rps_tripped and rps_first then println_ts("[RPS] SCRAM! safety trip: " .. rps_status_string) if networked and not plc_state.no_modem then plc_comms.send_rps_alarm(rps_status_string) @@ -330,7 +334,6 @@ threads.thread__rps = function (smem) -- execute the thread in a protected mode, retrying it on return if not shutting down public.p_exec = function () local plc_state = smem.plc_state - local rps = smem.plc_sys.rps while not plc_state.shutdown do local status, result = pcall(public.exec) @@ -339,7 +342,7 @@ threads.thread__rps = function (smem) end if not plc_state.shutdown then - if plc_state.init_ok then rps.scram() end + if plc_state.init_ok then smem.plc_sys.rps.scram() end log.info("rps thread restarting in 5 seconds...") util.psleep(5) end @@ -360,19 +363,20 @@ threads.thread__comms_tx = function (smem) -- load in from shared memory local plc_state = smem.plc_state - local plc_comms = smem.plc_sys.plc_comms - local comms_queue = smem.q.mq_comms_tx local last_update = util.time() -- thread loop while true do + -- get plc_sys fields (may have been set late due to degraded boot) + local plc_comms = smem.plc_sys.plc_comms + -- check for messages in the message queue while comms_queue.ready() and not plc_state.shutdown do local msg = comms_queue.pop() - if msg ~= nil then + if msg ~= nil and plc_state.init_ok then if msg.qtype == mqueue.TYPE.COMMAND then -- received a command if msg.message == MQ__COMM_CMD.SEND_STATUS then @@ -434,7 +438,6 @@ threads.thread__comms_rx = function (smem) -- load in from shared memory local plc_state = smem.plc_state local setpoints = smem.setpoints - local plc_comms = smem.plc_sys.plc_comms local comms_queue = smem.q.mq_comms_rx @@ -442,11 +445,14 @@ threads.thread__comms_rx = function (smem) -- thread loop while true do + -- get plc_sys fields (may have been set late due to degraded boot) + local plc_comms = smem.plc_sys.plc_comms + -- check for messages in the message queue while comms_queue.ready() and not plc_state.shutdown do local msg = comms_queue.pop() - if msg ~= nil then + if msg ~= nil and plc_state.init_ok then if msg.qtype == mqueue.TYPE.COMMAND then -- received a command elseif msg.qtype == mqueue.TYPE.DATA then @@ -507,7 +513,6 @@ threads.thread__setpoint_control = function (smem) local plc_state = smem.plc_state local setpoints = smem.setpoints local plc_dev = smem.plc_dev - local rps = smem.plc_sys.rps local last_update = util.time() local running = false @@ -520,6 +525,9 @@ threads.thread__setpoint_control = function (smem) -- thread loop while true do + -- get plc_sys fields (may have been set late due to degraded boot) + local rps = smem.plc_sys.rps + -- get reactor, may have changed do to disconnect/reconnect local reactor = plc_dev.reactor if plc_state.init_ok and not plc_state.no_reactor then diff --git a/scada-common/log.lua b/scada-common/log.lua index 3ea4262..d8d245b 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -137,6 +137,7 @@ end ---@param msg string message ---@param show_term? boolean whether or not to show on terminal output log.dmesg = function (msg, show_term) + if msg == nil then return end local message = string.format("[%10.3f] ", os.clock()) .. msg if show_term then _write(message) end _log(message) @@ -146,6 +147,7 @@ end ---@param msg string message ---@param trace? boolean include file trace log.debug = function (msg, trace) + if msg == nil then return end if LOG_DEBUG then local dbg_info = "" @@ -167,12 +169,14 @@ end -- log info messages ---@param msg string message log.info = function (msg) + if msg == nil then return end _log("[INF] " .. msg) end -- log warning messages ---@param msg string message log.warning = function (msg) + if msg == nil then return end _log("[WRN] " .. msg) end @@ -180,6 +184,7 @@ end ---@param msg string message ---@param trace? boolean include file trace log.error = function (msg, trace) + if msg == nil then return end local dbg_info = "" if trace then @@ -199,6 +204,7 @@ end -- log fatal errors ---@param msg string message log.fatal = function (msg) + if msg == nil then return end _log("[FTL] " .. msg) end diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index fd84503..d07e703 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -189,12 +189,12 @@ ppm.mount = function (iface) for i = 1, #ifaces do if iface == ifaces[i] then - log.info("PPM: mount(" .. iface .. ") -> found a " .. peripheral.getType(iface)) - _ppm_sys.mounts[iface] = peri_init(iface) pm_type = _ppm_sys.mounts[iface].type pm_dev = _ppm_sys.mounts[iface].dev + + log.info("PPM: mount(" .. iface .. ") -> found a " .. pm_type) break end end @@ -262,7 +262,7 @@ end ppm.get_all_devices = function (name) local devices = {} - for side, data in pairs(_ppm_sys.mounts) do + for _, data in pairs(_ppm_sys.mounts) do if data.type == name then table.insert(devices, data.dev) end @@ -300,7 +300,7 @@ end ppm.get_wireless_modem = function () local w_modem = nil - for side, device in pairs(_ppm_sys.mounts) do + for _, device in pairs(_ppm_sys.mounts) do if device.type == "modem" and device.dev.isWireless() then w_modem = device.dev break diff --git a/scada-common/util.lua b/scada-common/util.lua index 9e7a4a7..34c10ee 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -19,11 +19,13 @@ end -- timestamped print util.print_ts = function (message) + if message == nil then return end term.write(os.date("[%H:%M:%S] ") .. message) end -- timestamped print line util.println_ts = function (message) + if message == nil then return end print(os.date("[%H:%M:%S] ") .. message) end @@ -37,10 +39,10 @@ util.time_ms = function () end -- current time ----@return integer seconds +---@return number seconds util.time_s = function () ---@diagnostic disable-next-line: undefined-field - return os.epoch('local') / 1000 + return os.epoch('local') / 1000.0 end -- current time From 9fb6b7a8800a67ab9d043908dd773a88245a1bff Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 23 May 2022 17:36:54 -0400 Subject: [PATCH 219/587] #9 rsio test code, fixes per test results --- scada-common/rsio.lua | 12 ++-- test/rstest.lua | 149 ++++++++++++++++++++++++++++++++++++++++++ test/testutils.lua | 65 ++++++++++++++++++ 3 files changed, 220 insertions(+), 6 deletions(-) create mode 100644 test/rstest.lua create mode 100644 test/testutils.lua diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 8f981cd..fc76e0b 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -101,7 +101,7 @@ rsio.to_string = function (channel) "R_PLC_TIMEOUT" } - if channel > 0 and channel <= #names then + if type(channel) == "number" and channel > 0 and channel <= #names then return names[channel] else return "" @@ -188,7 +188,7 @@ rsio.get_io_mode = function (channel) IO_MODE.DIGITAL_OUT -- R_PLC_TIMEOUT } - if channel > 0 and channel <= #modes then + if type(channel) == "number" and channel > 0 and channel <= #modes then return modes[channel] else return IO_MODE.ANALOG_IN @@ -205,7 +205,7 @@ local RS_SIDES = rs.getSides() ---@param channel RS_IO ---@return boolean valid rsio.is_valid_channel = function (channel) - return (channel ~= nil) and (channel > 0) and (channel <= RS_IO.R_PLC_TIMEOUT) + return (type(channel) == "number") and (channel > 0) and (channel <= RS_IO.R_PLC_TIMEOUT) end -- check if a side is valid @@ -224,7 +224,7 @@ end ---@param color integer ---@return boolean valid rsio.is_color = function (color) - return (color > 0) and (_B_AND(color, (color - 1)) == 0); + return (type(color) == "number") and (color > 0) and (_B_AND(color, (color - 1)) == 0); end ----------------- @@ -247,7 +247,7 @@ end ---@param active boolean ---@return IO_LVL rsio.digital_write = function (channel, active) - if channel < RS_IO.WASTE_PO or channel > RS_IO.R_PLC_TIMEOUT then + if type(channel) ~= "number" or channel < RS_IO.F_ALARM or channel > RS_IO.R_PLC_TIMEOUT then return IO_LVL.LOW else return RS_DIO_MAP[channel]._f(active) @@ -259,7 +259,7 @@ end ---@param level IO_LVL ---@return boolean rsio.digital_is_active = function (channel, level) - if channel > RS_IO.R_ENABLE or channel > RS_IO.R_PLC_TIMEOUT then + if type(channel) ~= "number" or channel > RS_IO.R_ENABLE then return false else return RS_DIO_MAP[channel]._f(level) diff --git a/test/rstest.lua b/test/rstest.lua new file mode 100644 index 0000000..629baae --- /dev/null +++ b/test/rstest.lua @@ -0,0 +1,149 @@ +require("/initenv").init_env() + +local rsio = require("scada-common.rsio") +local util = require("scada-common.util") + +local testutils = require("test.testutils") + +local print = util.print +local println = util.println + +local IO = rsio.IO +local IO_LVL = rsio.IO_LVL +local IO_DIR = rsio.IO_DIR +local IO_MODE = rsio.IO_MODE + +println("starting RSIO tester") +println("") + +println(">>> checking valid channels:") + +-- channel function tests +local cid = 0 +local max_value = 1 +for key, value in pairs(IO) do + if value > max_value then max_value = value end + cid = cid + 1 + + local c_name = rsio.to_string(value) + local io_mode = rsio.get_io_mode(value) + local mode = "" + + if io_mode == IO_MODE.DIGITAL_IN then + mode = " (DIGITAL_IN)" + elseif io_mode == IO_MODE.DIGITAL_OUT then + mode = " (DIGITAL_OUT)" + elseif io_mode == IO_MODE.ANALOG_IN then + mode = " (ANALOG_IN)" + elseif io_mode == IO_MODE.ANALOG_OUT then + mode = " (ANALOG_OUT)" + else + error("unknown mode for channel " .. key) + end + + assert(key == c_name, c_name .. " != " .. key .. ": " .. value .. mode) + println(c_name .. ": " .. value .. mode) +end + +assert(max_value == cid, "RS_IO last IDx out-of-sync with count: " .. max_value .. " (count " .. cid .. ")") + +---@diagnostic disable-next-line: undefined-field +os.sleep(1) + +println(">>> checking invalid channels:") + +testutils.test_func("rsio.to_string", rsio.to_string, { -1, 100, false }, "") +testutils.test_func_nil("rsio.to_string", rsio.to_string, "") +testutils.test_func("rsio.get_io_mode", rsio.get_io_mode, { -1, 100, false }, IO_MODE.ANALOG_IN) +testutils.test_func_nil("rsio.get_io_mode", rsio.get_io_mode, IO_MODE.ANALOG_IN) + +---@diagnostic disable-next-line: undefined-field +os.sleep(1) + +println(">>> checking validity checks:") + +local ivc_t_list = { 0, -1, 100 } +testutils.test_func("rsio.is_valid_channel", rsio.is_valid_channel, ivc_t_list, false) +testutils.test_func_nil("rsio.is_valid_channel", rsio.is_valid_channel, false) + +local ivs_t_list = rs.getSides() +testutils.test_func("rsio.is_valid_side", rsio.is_valid_side, ivs_t_list, true) +testutils.test_func("rsio.is_valid_side", rsio.is_valid_side, { "" }, false) +testutils.test_func_nil("rsio.is_valid_side", rsio.is_valid_side, false) + +local ic_t_list = { colors.white, colors.purple, colors.blue, colors.cyan, colors.black } +testutils.test_func("rsio.is_color", rsio.is_color, ic_t_list, true) +testutils.test_func("rsio.is_color", rsio.is_color, { 0, 999999, colors.combine(colors.red, colors.blue, colors.black) }, false) +testutils.test_func_nil("rsio.is_color", rsio.is_color, false) + +---@diagnostic disable-next-line: undefined-field +os.sleep(1) + +println(">>> checking channel-independent I/O wrappers:") + +testutils.test_func("rsio.digital_read", rsio.digital_read, { true, false }, { IO_LVL.HIGH, IO_LVL.LOW }) + +print("rsio.analog_read(): ") +assert(rsio.analog_read(0, 0, 100) == 0, "RS_READ_0_100") +assert(rsio.analog_read(7.5, 0, 100) == 50, "RS_READ_7_5_100") +assert(rsio.analog_read(15, 0, 100) == 100, "RS_READ_15_100") +assert(rsio.analog_read(4, 0, 15) == 4, "RS_READ_4_15") +assert(rsio.analog_read(12, 0, 15) == 12, "RS_READ_12_15") +println("PASS") + +print("rsio.analog_write(): ") +assert(rsio.analog_write(0, 0, 100) == 0, "RS_WRITE_0_100") +assert(rsio.analog_write(100, 0, 100) == 15, "RS_WRITE_100_100") +assert(rsio.analog_write(4, 0, 15) == 4, "RS_WRITE_4_15") +assert(rsio.analog_write(12, 0, 15) == 12, "RS_WRITE_12_15") +println("PASS") + +---@diagnostic disable-next-line: undefined-field +os.sleep(1) + +println(">>> checking channel I/O:") + +print("rsio.digital_is_active(...): ") + +-- check input channels +assert(rsio.digital_is_active(IO.F_SCRAM, IO_LVL.LOW) == true, "IO_F_SCRAM_HIGH") +assert(rsio.digital_is_active(IO.F_SCRAM, IO_LVL.HIGH) == false, "IO_F_SCRAM_LOW") +assert(rsio.digital_is_active(IO.R_SCRAM, IO_LVL.LOW) == true, "IO_R_SCRAM_HIGH") +assert(rsio.digital_is_active(IO.R_SCRAM, IO_LVL.HIGH) == false, "IO_R_SCRAM_LOW") +assert(rsio.digital_is_active(IO.R_ENABLE, IO_LVL.LOW) == false, "IO_R_ENABLE_HIGH") +assert(rsio.digital_is_active(IO.R_ENABLE, IO_LVL.HIGH) == true, "IO_R_ENABLE_LOW") + +-- non-inputs should always return LOW +assert(rsio.digital_is_active(IO.F_ALARM, IO_LVL.LOW) == false, "IO_OUT_READ_LOW") +assert(rsio.digital_is_active(IO.F_ALARM, IO_LVL.HIGH) == false, "IO_OUT_READ_HIGH") + +println("PASS") + +-- check output channels + +print("rsio.digital_write(...): ") + +-- check output channels +assert(rsio.digital_write(IO.F_ALARM, false) == IO_LVL.LOW, "IO_F_ALARM_FALSE") +assert(rsio.digital_write(IO.F_ALARM, true) == IO_LVL.HIGH, "IO_F_ALARM_TRUE") +assert(rsio.digital_write(IO.WASTE_PO, false) == IO_LVL.HIGH, "IO_WASTE_PO_FALSE") +assert(rsio.digital_write(IO.WASTE_PO, true) == IO_LVL.LOW, "IO_WASTE_PO_TRUE") +assert(rsio.digital_write(IO.WASTE_PU, false) == IO_LVL.HIGH, "IO_WASTE_PU_FALSE") +assert(rsio.digital_write(IO.WASTE_PU, true) == IO_LVL.LOW, "IO_WASTE_PU_TRUE") +assert(rsio.digital_write(IO.WASTE_AM, false) == IO_LVL.HIGH, "IO_WASTE_AM_FALSE") +assert(rsio.digital_write(IO.WASTE_AM, true) == IO_LVL.LOW, "IO_WASTE_AM_TRUE") + +-- check all reactor output channels (all are active high) +for i = IO.R_ALARM, (IO.R_PLC_TIMEOUT - IO.R_ALARM + 1) do + assert(rsio.to_string(i) ~= "", "REACTOR_IO_BAD_CHANNEL") + assert(rsio.digital_write(i, false) == IO_LVL.LOW, "IO_" .. rsio.to_string(i) .. "_FALSE") + assert(rsio.digital_write(i, true) == IO_LVL.HIGH, "IO_" .. rsio.to_string(i) .. "_TRUE") +end + +-- non-outputs should always return false +assert(rsio.digital_write(IO.F_SCRAM, true) == IO_LVL.LOW, "IO_IN_WRITE_LOW") +assert(rsio.digital_write(IO.F_SCRAM, false) == IO_LVL.LOW, "IO_IN_WRITE_HIGH") + +println("PASS") + +println("TEST COMPLETE") diff --git a/test/testutils.lua b/test/testutils.lua new file mode 100644 index 0000000..a27875d --- /dev/null +++ b/test/testutils.lua @@ -0,0 +1,65 @@ +local util = require("scada-common.util") + +local print = util.print +local println = util.println + +local testutils = {} + +-- get a value as a string +---@param val any +---@return string value value as string or "%VALSTR_UNKNOWN%" +local function valstr(val) + local t = type(val) + + if t == "nil" then + return "nil" + elseif t == "number" then + return "" .. val + elseif t == "boolean" then + if val then return "true" else return "false" end + elseif t == "string" then + return val + elseif t == "table" or t == "function" then + return val + else + return "%VALSTR_UNKNOWN%" + end +end + +-- test a function +---@param name string function name +---@param f function function +---@param values table input values, one per function call +---@param results any table of values or a single value for all tests +function testutils.test_func(name, f, values, results) + -- if only one value was given, use that for all checks + if type(results) ~= "table" then + local _r = {} + for _ = 1, #values do + table.insert(_r, results) + end + results = _r + end + + assert(#values == #results, "test_func(" .. name .. ") #values ~= #results") + + for i = 1, #values do + local check = values[i] + local expect = results[i] + print(name .. "(" .. valstr(check) .. ") => ") + assert(f(check) == expect, "FAIL") + println("PASS") + end +end + +-- test a function with nil as a parameter +---@param name string function name +---@param f function function +---@param result any expected result +function testutils.test_func_nil(name, f, result) + print(name .. "(" .. valstr(nil) .. ") => ") + assert(f(nil) == result, "FAIL") + println("PASS") +end + +return testutils From 0cf81040fb66d1635366fc310b32714e325633a6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 24 May 2022 22:48:31 -0400 Subject: [PATCH 220/587] moved string value to util and added sprtinf, adjusted prints to use tostring to prevent concatentation errors on some types --- scada-common/util.lua | 46 +++++++++++++++++++++++++++++++++++++------ test/testutils.lua | 25 ++--------------------- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/scada-common/util.lua b/scada-common/util.lua index 34c10ee..c87ff3b 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -8,25 +8,59 @@ local util = {} -- PRINT -- -- print +---@param message any util.print = function (message) - term.write(message) + term.write(tostring(message)) end -- print line +---@param message any util.println = function (message) - print(message) + print(tostring(message)) end -- timestamped print +---@param message any util.print_ts = function (message) - if message == nil then return end - term.write(os.date("[%H:%M:%S] ") .. message) + term.write(os.date("[%H:%M:%S] ") .. tostring(message)) end -- timestamped print line +---@param message any util.println_ts = function (message) - if message == nil then return end - print(os.date("[%H:%M:%S] ") .. message) + print(os.date("[%H:%M:%S] ") .. tostring(message)) +end + +-- STRING TOOLS -- + +-- get a value as a string +---@param val any +---@return string +util.strval = function (val) + local t = type(val) + if t == "table" or t == "function" then + return "[" .. tostring(val) .. "]" + else + return tostring(val) + end +end + +-- concatenation with built-in to string +---@vararg any +---@return string +util.concat = function (...) + local str = "" + for _, v in ipairs(arg) do + str = str .. util.strval(v) + end + return str +end + +-- sprintf implementation +---@param format string +---@vararg any +util.sprintf = function (format, ...) + return string.format(format, table.unpack(arg)) end -- TIME -- diff --git a/test/testutils.lua b/test/testutils.lua index a27875d..12c07f0 100644 --- a/test/testutils.lua +++ b/test/testutils.lua @@ -5,27 +5,6 @@ local println = util.println local testutils = {} --- get a value as a string ----@param val any ----@return string value value as string or "%VALSTR_UNKNOWN%" -local function valstr(val) - local t = type(val) - - if t == "nil" then - return "nil" - elseif t == "number" then - return "" .. val - elseif t == "boolean" then - if val then return "true" else return "false" end - elseif t == "string" then - return val - elseif t == "table" or t == "function" then - return val - else - return "%VALSTR_UNKNOWN%" - end -end - -- test a function ---@param name string function name ---@param f function function @@ -46,7 +25,7 @@ function testutils.test_func(name, f, values, results) for i = 1, #values do local check = values[i] local expect = results[i] - print(name .. "(" .. valstr(check) .. ") => ") + print(name .. "(" .. util.strval(check) .. ") => ") assert(f(check) == expect, "FAIL") println("PASS") end @@ -57,7 +36,7 @@ end ---@param f function function ---@param result any expected result function testutils.test_func_nil(name, f, result) - print(name .. "(" .. valstr(nil) .. ") => ") + print(name .. "(nil) => ") assert(f(nil) == result, "FAIL") println("PASS") end From 4b6a1c59020bc6f222d641f0e8331cc6dbfbab9d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 24 May 2022 22:55:27 -0400 Subject: [PATCH 221/587] fixed incorrect watchdog call --- supervisor/session/plc.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index bf79f23..5279208 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -204,7 +204,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) -- mark this PLC session as closed, stop watchdog local _close = function () - self.rtu_conn_watchdog.cancel() + self.plc_conn_watchdog.cancel() self.connected = false end From ffc997b84ea3724a729f30d762a9e9933458473b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 24 May 2022 22:56:41 -0400 Subject: [PATCH 222/587] removed redundant version tag --- supervisor/supervisor.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 5bc3ce8..5236e0e 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -191,7 +191,7 @@ supervisor.comms = function (version, num_reactors, modem, dev_listen, coord_lis _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.COLLISION }) else -- got an ID; assigned to a reactor successfully - println("connected to reactor " .. packet.data[1] .. " PLC v " .. packet.data[2] .. " (port " .. r_port .. ")") + println("connected to reactor " .. packet.data[1] .. " PLC (" .. packet.data[2] .. ") [port " .. r_port .. "]") log.debug("PLC_LNK: allowed for device at " .. r_port) _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.ALLOW }) end @@ -215,7 +215,7 @@ supervisor.comms = function (version, num_reactors, modem, dev_listen, coord_lis elseif packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then if packet.length >= 1 then -- this is an RTU advertisement for a new session - println("connected to RTU v " .. packet.data[1] .. " (port " .. r_port .. ")") + println("connected to RTU (" .. packet.data[1] .. ") [port " .. r_port .. "]") svsessions.establish_rtu_session(l_port, r_port, packet.data) From 4d7d3be93ba87e27a6f5d7d976302e2a11debfa6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 24 May 2022 22:58:42 -0400 Subject: [PATCH 223/587] power related utility functions put under util table --- scada-common/util.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scada-common/util.lua b/scada-common/util.lua index c87ff3b..7073d38 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -156,10 +156,10 @@ end -- MEKANISM POWER -- --- function kFE(fe) return fe / 1000 end --- function MFE(fe) return fe / 1000000 end --- function GFE(fe) return fe / 1000000000 end --- function TFE(fe) return fe / 1000000000000 end +-- function util.kFE(fe) return fe / 1000.0 end +-- function util.MFE(fe) return fe / 1000000.0 end +-- function util.GFE(fe) return fe / 1000000000.0 end +-- function util.TFE(fe) return fe / 1000000000000.0 end -- -- FLOATING POINT PRINTS -- @@ -177,7 +177,7 @@ end -- return number == math.round(number) -- end --- function power_format(fe) +-- function util.power_format(fe) -- if fe < 1000 then -- return string.format("%.2f FE", fe) -- elseif fe < 1000000 then From 7d7eecaa5e07262cd92b36a16879fa22e5b97e4e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 25 May 2022 23:24:15 -0400 Subject: [PATCH 224/587] log use strval --- scada-common/log.lua | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/scada-common/log.lua b/scada-common/log.lua index d8d245b..8369d22 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -53,7 +53,7 @@ end ---@param msg string local _log = function (msg) local time_stamp = os.date("[%c] ") - local stamped = time_stamp .. msg + local stamped = time_stamp .. util.strval(msg) -- attempt to write log local status, result = pcall(function () @@ -137,8 +137,7 @@ end ---@param msg string message ---@param show_term? boolean whether or not to show on terminal output log.dmesg = function (msg, show_term) - if msg == nil then return end - local message = string.format("[%10.3f] ", os.clock()) .. msg + local message = string.format("[%10.3f] ", os.clock()) .. util.strval(msg) if show_term then _write(message) end _log(message) end @@ -147,7 +146,6 @@ end ---@param msg string message ---@param trace? boolean include file trace log.debug = function (msg, trace) - if msg == nil then return end if LOG_DEBUG then local dbg_info = "" @@ -162,29 +160,26 @@ log.debug = function (msg, trace) dbg_info = info.short_src .. ":" .. name .. info.currentline .. " > " end - _log("[DBG] " .. dbg_info .. msg) + _log("[DBG] " .. dbg_info .. util.strval(msg)) end end -- log info messages ---@param msg string message log.info = function (msg) - if msg == nil then return end - _log("[INF] " .. msg) + _log("[INF] " .. util.strval(msg)) end -- log warning messages ---@param msg string message log.warning = function (msg) - if msg == nil then return end - _log("[WRN] " .. msg) + _log("[WRN] " .. util.strval(msg)) end -- log error messages ---@param msg string message ---@param trace? boolean include file trace log.error = function (msg, trace) - if msg == nil then return end local dbg_info = "" if trace then @@ -198,14 +193,13 @@ log.error = function (msg, trace) dbg_info = info.short_src .. ":" .. name .. info.currentline .. " > " end - _log("[ERR] " .. dbg_info .. msg) + _log("[ERR] " .. dbg_info .. util.strval(msg)) end -- log fatal errors ---@param msg string message log.fatal = function (msg) - if msg == nil then return end - _log("[FTL] " .. msg) + _log("[FTL] " .. util.strval(msg)) end return log From 78ddd4d7823edae733ab5a9a30adc3856d40659a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 26 May 2022 17:49:43 -0400 Subject: [PATCH 225/587] #58 fixed bug with KEEP_ALIVE being sent as a LINK_REQ due to wrong protocol --- reactor-plc/plc.lua | 2 +- reactor-plc/startup.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index b5d36b5..9620636 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -429,7 +429,7 @@ plc.comms = function (id, version, modem, local_port, server_port, reactor, rps, -- keep alive ack ---@param srv_time integer local _send_keep_alive_ack = function (srv_time) - _send(SCADA_MGMT_TYPES.KEEP_ALIVE, { srv_time, util.time() }) + _send_mgmt(SCADA_MGMT_TYPES.KEEP_ALIVE, { srv_time, util.time() }) end -- general ack diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 8a2d296..062db60 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 = "alpha-v0.7.1" +local R_PLC_VERSION = "alpha-v0.7.2" local print = util.print local println = util.println From 214f2d90285b4885c19baf877dc99a896f1f043d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 26 May 2022 17:49:53 -0400 Subject: [PATCH 226/587] fixed supervisor clock not starting --- supervisor/session/svsessions.lua | 2 +- supervisor/startup.lua | 5 ++++- supervisor/supervisor.lua | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 409753c..02bf70b 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -35,7 +35,7 @@ local self = { ---@param sessions table local function _iterate(sessions) for i = 1, #sessions do - local session = sessions[i] ---@type plc_session_struct + local session = sessions[i] ---@type plc_session_struct|rtu_session_struct if session.open then local ok = session.instance.iterate() if ok then diff --git a/supervisor/startup.lua b/supervisor/startup.lua index d23436d..307b8e6 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "alpha-v0.3.10" +local SUPERVISOR_VERSION = "alpha-v0.4.0" local print = util.print local println = util.println @@ -44,6 +44,9 @@ local superv_comms = supervisor.comms(SUPERVISOR_VERSION, config.NUM_REACTORS, m local MAIN_CLOCK = 0.15 local loop_clock = util.new_clock(MAIN_CLOCK) +-- start clock +loop_clock.start() + -- event loop while true do ---@diagnostic disable-next-line: undefined-field diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 5236e0e..ba07d31 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -191,7 +191,7 @@ supervisor.comms = function (version, num_reactors, modem, dev_listen, coord_lis _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.COLLISION }) else -- got an ID; assigned to a reactor successfully - println("connected to reactor " .. packet.data[1] .. " PLC (" .. packet.data[2] .. ") [port " .. r_port .. "]") + println("connected to reactor " .. packet.data[1] .. " PLC (" .. packet.data[2] .. ") [:" .. r_port .. "]") log.debug("PLC_LNK: allowed for device at " .. r_port) _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.ALLOW }) end @@ -215,7 +215,7 @@ supervisor.comms = function (version, num_reactors, modem, dev_listen, coord_lis elseif packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then if packet.length >= 1 then -- this is an RTU advertisement for a new session - println("connected to RTU (" .. packet.data[1] .. ") [port " .. r_port .. "]") + println("connected to RTU (" .. packet.data[1] .. ") [:" .. r_port .. "]") svsessions.establish_rtu_session(l_port, r_port, packet.data) From 51111f707f8a8a9f21defa1784bb0d2d8540f4b7 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 26 May 2022 19:37:19 -0400 Subject: [PATCH 227/587] more descriptive comments --- scada-common/comms.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index c305b29..775127f 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -205,7 +205,7 @@ comms.modbus_packet = function () -- get raw to send public.raw_sendable = function () return self.raw end - -- get this packet + -- get this packet as a frame with an immutable relation to this object public.get = function () ---@class modbus_frame local frame = { @@ -298,7 +298,7 @@ comms.rplc_packet = function () -- get raw to send public.raw_sendable = function () return self.raw end - -- get this packet + -- get this packet as a frame with an immutable relation to this object public.get = function () ---@class rplc_frame local frame = { @@ -382,7 +382,7 @@ comms.mgmt_packet = function () -- get raw to send public.raw_sendable = function () return self.raw end - -- get this packet + -- get this packet as a frame with an immutable relation to this object public.get = function () ---@class mgmt_frame local frame = { @@ -463,7 +463,7 @@ comms.coord_packet = function () -- get raw to send public.raw_sendable = function () return self.raw end - -- get this packet + -- get this packet as a frame with an immutable relation to this object public.get = function () ---@class coord_frame local frame = { @@ -544,7 +544,7 @@ comms.capi_packet = function () -- get raw to send public.raw_sendable = function () return self.raw end - -- get this packet + -- get this packet as a frame with an immutable relation to this object public.get = function () ---@class capi_frame local frame = { From 6df0a1d149c309be722b022014e6175d9e611cfb Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 27 May 2022 18:10:06 -0400 Subject: [PATCH 228/587] #9 MODBUS test code; fixed rtu, modbus, redstone_rtu, and rsio bugs --- rtu/dev/redstone_rtu.lua | 8 +- rtu/modbus.lua | 35 +++--- rtu/rtu.lua | 12 +- rtu/startup.lua | 2 +- scada-common/rsio.lua | 58 +++++----- test/modbustest.lua | 236 +++++++++++++++++++++++++++++++++++++++ test/rstest.lua | 36 +++--- test/testutils.lua | 78 +++++++++++++ 8 files changed, 385 insertions(+), 80 deletions(-) create mode 100644 test/modbustest.lua diff --git a/rtu/dev/redstone_rtu.lua b/rtu/dev/redstone_rtu.lua index 2763e98..563886e 100644 --- a/rtu/dev/redstone_rtu.lua +++ b/rtu/dev/redstone_rtu.lua @@ -4,6 +4,7 @@ local rsio = require("scada-common.rsio") local redstone_rtu = {} local digital_read = rsio.digital_read +local digital_write = rsio.digital_write local digital_is_active = rsio.digital_is_active -- create new redstone device @@ -61,12 +62,11 @@ redstone_rtu.new = function () f_write = function (level) local output = rs.getBundledOutput(side) - local active = digital_is_active(channel, level) - if active then - colors.combine(output, color) + if digital_write(channel, level) then + output = colors.combine(output, color) else - colors.subtract(output, color) + output = colors.subtract(output, color) end rs.setBundledOutput(side, output) diff --git a/rtu/modbus.lua b/rtu/modbus.lua index efc0c84..2654405 100644 --- a/rtu/modbus.lua +++ b/rtu/modbus.lua @@ -28,7 +28,7 @@ modbus.new = function (rtu_dev, use_parallel_read) local readings = {} local access_fault = false local _, coils, _, _ = self.rtu.io_count() - local return_ok = ((c_addr_start + count) <= coils) and (count > 0) + local return_ok = ((c_addr_start + count) <= (coils + 1)) and (count > 0) if return_ok then for i = 1, count do @@ -74,7 +74,7 @@ modbus.new = function (rtu_dev, use_parallel_read) local readings = {} local access_fault = false local discrete_inputs, _, _, _ = self.rtu.io_count() - local return_ok = ((di_addr_start + count) <= discrete_inputs) and (count > 0) + local return_ok = ((di_addr_start + count) <= (discrete_inputs + 1)) and (count > 0) if return_ok then for i = 1, count do @@ -120,7 +120,7 @@ modbus.new = function (rtu_dev, use_parallel_read) local readings = {} local access_fault = false local _, _, _, hold_regs = self.rtu.io_count() - local return_ok = ((hr_addr_start + count) <= hold_regs) and (count > 0) + local return_ok = ((hr_addr_start + count) <= (hold_regs + 1)) and (count > 0) if return_ok then for i = 1, count do @@ -166,7 +166,7 @@ modbus.new = function (rtu_dev, use_parallel_read) local readings = {} local access_fault = false local _, _, input_regs, _ = self.rtu.io_count() - local return_ok = ((ir_addr_start + count) <= input_regs) and (count > 0) + local return_ok = ((ir_addr_start + count) <= (input_regs + 1)) and (count > 0) if return_ok then for i = 1, count do @@ -255,7 +255,7 @@ modbus.new = function (rtu_dev, use_parallel_read) local response = nil local _, coils, _, _ = self.rtu.io_count() local count = #values - local return_ok = ((c_addr_start + count) <= coils) and (count > 0) + local return_ok = ((c_addr_start + count) <= (coils + 1)) and (count > 0) if return_ok then for i = 1, count do @@ -282,12 +282,12 @@ modbus.new = function (rtu_dev, use_parallel_read) local response = nil local _, _, _, hold_regs = self.rtu.io_count() local count = #values - local return_ok = ((hr_addr_start + count) <= hold_regs) and (count > 0) + local return_ok = ((hr_addr_start + count) <= (hold_regs + 1)) and (count > 0) if return_ok then for i = 1, count do local addr = hr_addr_start + i - 1 - local access_fault = self.rtu.write_coil(addr, values[i]) + local access_fault = self.rtu.write_holding_reg(addr, values[i]) if access_fault then return_ok = false @@ -309,12 +309,12 @@ modbus.new = function (rtu_dev, use_parallel_read) local return_code = true local response = { MODBUS_EXCODE.ACKNOWLEDGE } - if #packet.data == 2 then + if packet.length == 2 then -- handle by function code if packet.func_code == MODBUS_FCODE.READ_COILS then elseif packet.func_code == MODBUS_FCODE.READ_DISCRETE_INPUTS then elseif packet.func_code == MODBUS_FCODE.READ_MUL_HOLD_REGS then - elseif packet.func_code == MODBUS_FCODE.READ_INPUT_REGISTERS then + elseif packet.func_code == MODBUS_FCODE.READ_INPUT_REGS then elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_COIL then elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_HOLD_REG then elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_COILS then @@ -351,7 +351,7 @@ modbus.new = function (rtu_dev, use_parallel_read) local return_code = true local response = nil - if #packet.data == 2 then + if packet.length == 2 then -- handle by function code if packet.func_code == MODBUS_FCODE.READ_COILS then return_code, response = _1_read_coils(packet.data[1], packet.data[2]) @@ -359,7 +359,7 @@ modbus.new = function (rtu_dev, use_parallel_read) return_code, response = _2_read_discrete_inputs(packet.data[1], packet.data[2]) elseif packet.func_code == MODBUS_FCODE.READ_MUL_HOLD_REGS then return_code, response = _3_read_multiple_holding_registers(packet.data[1], packet.data[2]) - elseif packet.func_code == MODBUS_FCODE.READ_INPUT_REGISTERS then + elseif packet.func_code == MODBUS_FCODE.READ_INPUT_REGS then return_code, response = _4_read_input_registers(packet.data[1], packet.data[2]) elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_COIL then return_code, response = _5_write_single_coil(packet.data[1], packet.data[2]) @@ -384,14 +384,13 @@ modbus.new = function (rtu_dev, use_parallel_read) if not return_code then -- echo back with error flag func_code = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) + end - if type(response) == "nil" then - response = { } - elseif type(response) == "number" then - response = { response } - elseif type(response) == "table" then - response = response - end + if type(response) == "table" then + elseif type(response) == "nil" then + response = {} + else + response = { response } end -- create reply diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 2e2d445..2309be5 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -54,7 +54,7 @@ rtu.init_unit = function () ---@param f function ---@return integer count count of discrete inputs protected.connect_di = function (f) - insert(self.discrete_inputs, f) + insert(self.discrete_inputs, { read = f }) _count_io() return #self.discrete_inputs end @@ -64,7 +64,7 @@ rtu.init_unit = function () ---@return any value, boolean access_fault public.read_di = function (di_addr) ppm.clear_fault() - local value = self.discrete_inputs[di_addr]() + local value = self.discrete_inputs[di_addr].read() return value, ppm.is_faulted() end @@ -105,7 +105,7 @@ rtu.init_unit = function () ---@param f function ---@return integer count count of input registers protected.connect_input_reg = function (f) - insert(self.input_regs, f) + insert(self.input_regs, { read = f }) _count_io() return #self.input_regs end @@ -115,7 +115,7 @@ rtu.init_unit = function () ---@return any value, boolean access_fault public.read_input_reg = function (reg_addr) ppm.clear_fault() - local value = self.coils[reg_addr]() + local value = self.input_regs[reg_addr].read() return value, ppm.is_faulted() end @@ -136,7 +136,7 @@ rtu.init_unit = function () ---@return any value, boolean access_fault public.read_holding_reg = function (reg_addr) ppm.clear_fault() - local value = self.coils[reg_addr].read() + local value = self.holding_regs[reg_addr].read() return value, ppm.is_faulted() end @@ -146,7 +146,7 @@ rtu.init_unit = function () ---@return boolean access_fault public.write_holding_reg = function (reg_addr, value) ppm.clear_fault() - self.coils[reg_addr].write(value) + self.holding_regs[reg_addr].write(value) return ppm.is_faulted() end diff --git a/rtu/startup.lua b/rtu/startup.lua index 003a2d6..6f5730e 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -24,7 +24,7 @@ local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "alpha-v0.7.0" +local RTU_VERSION = "alpha-v0.7.1" local rtu_t = types.rtu_t diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index fc76e0b..9ba6878 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -110,55 +110,51 @@ end local _B_AND = bit.band -local _TRINARY = function (cond, t, f) if cond then return t else return f end end - -local _DI_ACTIVE_HIGH = function (level) return level == IO_LVL.HIGH end -local _DI_ACTIVE_LOW = function (level) return level == IO_LVL.LOW end -local _DO_ACTIVE_HIGH = function (on) return _TRINARY(on, IO_LVL.HIGH, IO_LVL.LOW) end -local _DO_ACTIVE_LOW = function (on) return _TRINARY(on, IO_LVL.LOW, IO_LVL.HIGH) end +local function _ACTIVE_HIGH(level) return level == IO_LVL.HIGH end +local function _ACTIVE_LOW(level) return level == IO_LVL.LOW end -- I/O mappings to I/O function and I/O mode local RS_DIO_MAP = { -- F_SCRAM - { _f = _DI_ACTIVE_LOW, mode = IO_DIR.IN }, + { _f = _ACTIVE_LOW, mode = IO_DIR.IN }, -- R_SCRAM - { _f = _DI_ACTIVE_LOW, mode = IO_DIR.IN }, + { _f = _ACTIVE_LOW, mode = IO_DIR.IN }, -- R_ENABLE - { _f = _DI_ACTIVE_HIGH, mode = IO_DIR.IN }, + { _f = _ACTIVE_HIGH, mode = IO_DIR.IN }, -- F_ALARM - { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, -- WASTE_PO - { _f = _DO_ACTIVE_LOW, mode = IO_DIR.OUT }, + { _f = _ACTIVE_LOW, mode = IO_DIR.OUT }, -- WASTE_PU - { _f = _DO_ACTIVE_LOW, mode = IO_DIR.OUT }, + { _f = _ACTIVE_LOW, mode = IO_DIR.OUT }, -- WASTE_AM - { _f = _DO_ACTIVE_LOW, mode = IO_DIR.OUT }, + { _f = _ACTIVE_LOW, mode = IO_DIR.OUT }, -- R_ALARM - { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_SCRAMMED - { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_AUTO_SCRAM - { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_ACTIVE - { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_AUTO_CTRL - { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_DMG_CRIT - { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_HIGH_TEMP - { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_NO_COOLANT - { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_EXCESS_HC - { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_EXCESS_WS - { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_INSUFF_FUEL - { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_PLC_FAULT - { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_PLC_TIMEOUT - { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT } + { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT } } -- get the mode of a channel @@ -244,13 +240,13 @@ end -- returns the level corresponding to active ---@param channel RS_IO ----@param active boolean ----@return IO_LVL -rsio.digital_write = function (channel, active) +---@param level IO_LVL +---@return boolean +rsio.digital_write = function (channel, level) if type(channel) ~= "number" or channel < RS_IO.F_ALARM or channel > RS_IO.R_PLC_TIMEOUT then - return IO_LVL.LOW + return false else - return RS_DIO_MAP[channel]._f(active) + return RS_DIO_MAP[channel]._f(level) end end diff --git a/test/modbustest.lua b/test/modbustest.lua new file mode 100644 index 0000000..1d0b9ea --- /dev/null +++ b/test/modbustest.lua @@ -0,0 +1,236 @@ +require("/initenv").init_env() + +local types = require("scada-common.types") +local util = require("scada-common.util") + +local testutils = require("test.testutils") + +local modbus = require("rtu.modbus") +local redstone_rtu = require("rtu.dev.redstone_rtu") + +local rsio = require("scada-common.rsio") + +local print = util.print +local println = util.println + +local MODBUS_FCODE = types.MODBUS_FCODE +local MODBUS_EXCODE = types.MODBUS_EXCODE + +println("starting redstone RTU and MODBUS tester") +println("") + +-- RTU init -- + +print(">>> init redstone RTU: ") + +local rs_rtu = redstone_rtu.new() + +local di, c, ir, hr = rs_rtu.io_count() +assert(di == 0 and c == 0 and ir == 0 and hr == 0, "IOCOUNT_0") + +rs_rtu.link_di("back", colors.black) +rs_rtu.link_di("back", colors.blue) + +rs_rtu.link_do(rsio.IO.F_ALARM, "back", colors.red) +rs_rtu.link_do(rsio.IO.WASTE_AM, "back", colors.purple) + +rs_rtu.link_ai("right") +rs_rtu.link_ao("left") + +di, c, ir, hr = rs_rtu.io_count() +assert(di == 2, "IOCOUNT_DI") +assert(c == 2, "IOCOUNT_C") +assert(ir == 1, "IOCOUNT_IR") +assert(hr == 1, "IOCOUNT_HR") + +println("OK") + +-- MODBUS testing -- + +local rs_modbus = modbus.new(rs_rtu, false) + +local mbt = testutils.modbus_tester(rs_modbus, MODBUS_FCODE.ERROR_FLAG) + +------------------------- +--- CHECKING REQUESTS --- +------------------------- + +println(">>> checking MODBUS requests:") + +print("read c {0}: ") +mbt.pkt_set(MODBUS_FCODE.READ_COILS, {0}) +mbt.test_error__check_request(MODBUS_EXCODE.NEG_ACKNOWLEDGE) +println("PASS") + +print("99 {1,2}: ") +mbt.pkt_set(99, {1, 2}) +mbt.test_error__check_request(MODBUS_EXCODE.ILLEGAL_FUNCTION) +println("PASS") + +print("read c {1,2}: ") +mbt.pkt_set(MODBUS_FCODE.READ_COILS, {1, 2}) +mbt.test_success__check_request(MODBUS_EXCODE.ACKNOWLEDGE) +println("PASS") + +testutils.pause() + +-------------------- +--- BAD REQUESTS --- +-------------------- + +println(">>> trying bad requests:") + +print("read di {1,10}: ") +mbt.pkt_set(MODBUS_FCODE.READ_DISCRETE_INPUTS, {1, 10}) +mbt.test_error__handle_packet(MODBUS_EXCODE.ILLEGAL_DATA_ADDR) +println("PASS") + +print("read di {5,1}: ") +mbt.pkt_set(MODBUS_FCODE.READ_DISCRETE_INPUTS, {5, 1}) +mbt.test_error__handle_packet(MODBUS_EXCODE.ILLEGAL_DATA_ADDR) +println("PASS") + +print("read di {1,0}: ") +mbt.pkt_set(MODBUS_FCODE.READ_DISCRETE_INPUTS, {1, 0}) +mbt.test_error__handle_packet(MODBUS_EXCODE.ILLEGAL_DATA_ADDR) +println("PASS") + +print("read c {5,1}: ") +mbt.pkt_set(MODBUS_FCODE.READ_COILS, {5, 1}) +mbt.test_error__handle_packet(MODBUS_EXCODE.ILLEGAL_DATA_ADDR) +println("PASS") + +print("read c {1,0}: ") +mbt.pkt_set(MODBUS_FCODE.READ_COILS, {1, 0}) +mbt.test_error__handle_packet(MODBUS_EXCODE.ILLEGAL_DATA_ADDR) +println("PASS") + +print("read ir {5,1}: ") +mbt.pkt_set(MODBUS_FCODE.READ_INPUT_REGS, {5, 1}) +mbt.test_error__handle_packet(MODBUS_EXCODE.ILLEGAL_DATA_ADDR) +println("PASS") + +print("read ir {1,0}: ") +mbt.pkt_set(MODBUS_FCODE.READ_INPUT_REGS, {1, 0}) +mbt.test_error__handle_packet(MODBUS_EXCODE.ILLEGAL_DATA_ADDR) +println("PASS") + +print("read hr {5,1}: ") +mbt.pkt_set(MODBUS_FCODE.READ_MUL_HOLD_REGS, {5, 1}) +mbt.test_error__handle_packet(MODBUS_EXCODE.ILLEGAL_DATA_ADDR) +println("PASS") + +print("write c {5,1}: ") +mbt.pkt_set(MODBUS_FCODE.WRITE_SINGLE_COIL, {5, 1}) +mbt.test_error__handle_packet(MODBUS_EXCODE.ILLEGAL_DATA_ADDR) +println("PASS") + +print("write mul c {5,1}: ") +mbt.pkt_set(MODBUS_FCODE.WRITE_SINGLE_COIL, {5, 1}) +mbt.test_error__handle_packet(MODBUS_EXCODE.ILLEGAL_DATA_ADDR) +println("PASS") + +print("write mul c {5,{1}}: ") +mbt.pkt_set(MODBUS_FCODE.WRITE_SINGLE_COIL, {5, {1}}) +mbt.test_error__handle_packet(MODBUS_EXCODE.ILLEGAL_DATA_ADDR) +println("PASS") + +print("write hr {5,1}: ") +mbt.pkt_set(MODBUS_FCODE.WRITE_SINGLE_HOLD_REG, {5, 1}) +mbt.test_error__handle_packet(MODBUS_EXCODE.ILLEGAL_DATA_ADDR) +println("PASS") + +print("write mul hr {5,{1}}: ") +mbt.pkt_set(MODBUS_FCODE.WRITE_SINGLE_HOLD_REG, {5, {1}}) +mbt.test_error__handle_packet(MODBUS_EXCODE.ILLEGAL_DATA_ADDR) +println("PASS") + +testutils.pause() + +---------------------- +--- READING INPUTS --- +---------------------- + +println(">>> reading inputs:") + +print("read di {1,1}: ") +mbt.pkt_set(MODBUS_FCODE.READ_DISCRETE_INPUTS, {1, 1}) +mbt.test_success__handle_packet() + +print("read di {2,1}: ") +mbt.pkt_set(MODBUS_FCODE.READ_DISCRETE_INPUTS, {2, 1}) +mbt.test_success__handle_packet() + +print("read di {1,2}: ") +mbt.pkt_set(MODBUS_FCODE.READ_DISCRETE_INPUTS, {1, 2}) +mbt.test_success__handle_packet() + +print("read ir {1,1}: ") +mbt.pkt_set(MODBUS_FCODE.READ_INPUT_REGS, {1, 1}) +mbt.test_success__handle_packet() + +testutils.pause() + +----------------------- +--- WRITING OUTPUTS --- +----------------------- + +println(">>> writing outputs:") + +print("write mul c {1,{LOW,LOW}}: ") +mbt.pkt_set(MODBUS_FCODE.WRITE_MUL_COILS, {1, {rsio.IO_LVL.LOW, rsio.IO_LVL.LOW}}) +mbt.test_success__handle_packet() + +testutils.pause() + +print("write c {1,HIGH}: ") +mbt.pkt_set(MODBUS_FCODE.WRITE_SINGLE_COIL, {1, rsio.IO_LVL.HIGH}) +mbt.test_success__handle_packet() + +testutils.pause() + +print("write c {2,HIGH}: ") +mbt.pkt_set(MODBUS_FCODE.WRITE_SINGLE_COIL, {2, rsio.IO_LVL.HIGH}) +mbt.test_success__handle_packet() + +testutils.pause() + +print("write hr {1,7}: ") +mbt.pkt_set(MODBUS_FCODE.WRITE_SINGLE_HOLD_REG, {1, 7}) +mbt.test_success__handle_packet() + +testutils.pause() + +print("write mul hr {1,{4}}: ") +mbt.pkt_set(MODBUS_FCODE.WRITE_MUL_HOLD_REGS, {1, {4}}) +mbt.test_success__handle_packet() + +println("PASS") + +testutils.pause() + +----------------------- +--- READING OUTPUTS --- +----------------------- + +println(">>> reading outputs:") + +print("read c {1,1}: ") +mbt.pkt_set(MODBUS_FCODE.READ_COILS, {1, 1}) +mbt.test_success__handle_packet() + +print("read c {2,1}: ") +mbt.pkt_set(MODBUS_FCODE.READ_COILS, {2, 1}) +mbt.test_success__handle_packet() + +print("read c {1,2}: ") +mbt.pkt_set(MODBUS_FCODE.READ_COILS, {1, 2}) +mbt.test_success__handle_packet() + +print("read hr {1,1}: ") +mbt.pkt_set(MODBUS_FCODE.READ_MUL_HOLD_REGS, {1, 1}) +mbt.test_success__handle_packet() + +println("PASS") + +println("TEST COMPLETE") diff --git a/test/rstest.lua b/test/rstest.lua index 629baae..1ed6827 100644 --- a/test/rstest.lua +++ b/test/rstest.lua @@ -47,8 +47,7 @@ end assert(max_value == cid, "RS_IO last IDx out-of-sync with count: " .. max_value .. " (count " .. cid .. ")") ----@diagnostic disable-next-line: undefined-field -os.sleep(1) +testutils.pause() println(">>> checking invalid channels:") @@ -57,8 +56,7 @@ testutils.test_func_nil("rsio.to_string", rsio.to_string, "") testutils.test_func("rsio.get_io_mode", rsio.get_io_mode, { -1, 100, false }, IO_MODE.ANALOG_IN) testutils.test_func_nil("rsio.get_io_mode", rsio.get_io_mode, IO_MODE.ANALOG_IN) ----@diagnostic disable-next-line: undefined-field -os.sleep(1) +testutils.pause() println(">>> checking validity checks:") @@ -76,8 +74,7 @@ testutils.test_func("rsio.is_color", rsio.is_color, ic_t_list, true) testutils.test_func("rsio.is_color", rsio.is_color, { 0, 999999, colors.combine(colors.red, colors.blue, colors.black) }, false) testutils.test_func_nil("rsio.is_color", rsio.is_color, false) ----@diagnostic disable-next-line: undefined-field -os.sleep(1) +testutils.pause() println(">>> checking channel-independent I/O wrappers:") @@ -98,8 +95,7 @@ assert(rsio.analog_write(4, 0, 15) == 4, "RS_WRITE_4_15") assert(rsio.analog_write(12, 0, 15) == 12, "RS_WRITE_12_15") println("PASS") ----@diagnostic disable-next-line: undefined-field -os.sleep(1) +testutils.pause() println(">>> checking channel I/O:") @@ -124,25 +120,25 @@ println("PASS") print("rsio.digital_write(...): ") -- check output channels -assert(rsio.digital_write(IO.F_ALARM, false) == IO_LVL.LOW, "IO_F_ALARM_FALSE") -assert(rsio.digital_write(IO.F_ALARM, true) == IO_LVL.HIGH, "IO_F_ALARM_TRUE") -assert(rsio.digital_write(IO.WASTE_PO, false) == IO_LVL.HIGH, "IO_WASTE_PO_FALSE") -assert(rsio.digital_write(IO.WASTE_PO, true) == IO_LVL.LOW, "IO_WASTE_PO_TRUE") -assert(rsio.digital_write(IO.WASTE_PU, false) == IO_LVL.HIGH, "IO_WASTE_PU_FALSE") -assert(rsio.digital_write(IO.WASTE_PU, true) == IO_LVL.LOW, "IO_WASTE_PU_TRUE") -assert(rsio.digital_write(IO.WASTE_AM, false) == IO_LVL.HIGH, "IO_WASTE_AM_FALSE") -assert(rsio.digital_write(IO.WASTE_AM, true) == IO_LVL.LOW, "IO_WASTE_AM_TRUE") +assert(rsio.digital_write(IO.F_ALARM, IO_LVL.LOW) == false, "IO_F_ALARM_FALSE") +assert(rsio.digital_write(IO.F_ALARM, IO_LVL.HIGH) == true, "IO_F_ALARM_TRUE") +assert(rsio.digital_write(IO.WASTE_PO, IO_LVL.HIGH) == false, "IO_WASTE_PO_FALSE") +assert(rsio.digital_write(IO.WASTE_PO, IO_LVL.LOW) == true, "IO_WASTE_PO_TRUE") +assert(rsio.digital_write(IO.WASTE_PU, IO_LVL.HIGH) == false, "IO_WASTE_PU_FALSE") +assert(rsio.digital_write(IO.WASTE_PU, IO_LVL.LOW) == true, "IO_WASTE_PU_TRUE") +assert(rsio.digital_write(IO.WASTE_AM, IO_LVL.HIGH) == false, "IO_WASTE_AM_FALSE") +assert(rsio.digital_write(IO.WASTE_AM, IO_LVL.LOW) == true, "IO_WASTE_AM_TRUE") -- check all reactor output channels (all are active high) for i = IO.R_ALARM, (IO.R_PLC_TIMEOUT - IO.R_ALARM + 1) do assert(rsio.to_string(i) ~= "", "REACTOR_IO_BAD_CHANNEL") - assert(rsio.digital_write(i, false) == IO_LVL.LOW, "IO_" .. rsio.to_string(i) .. "_FALSE") - assert(rsio.digital_write(i, true) == IO_LVL.HIGH, "IO_" .. rsio.to_string(i) .. "_TRUE") + assert(rsio.digital_write(i, IO_LVL.LOW) == false, "IO_" .. rsio.to_string(i) .. "_FALSE") + assert(rsio.digital_write(i, IO_LVL.HIGH) == true, "IO_" .. rsio.to_string(i) .. "_TRUE") end -- non-outputs should always return false -assert(rsio.digital_write(IO.F_SCRAM, true) == IO_LVL.LOW, "IO_IN_WRITE_LOW") -assert(rsio.digital_write(IO.F_SCRAM, false) == IO_LVL.LOW, "IO_IN_WRITE_HIGH") +assert(rsio.digital_write(IO.F_SCRAM, IO_LVL.LOW) == false, "IO_IN_WRITE_LOW") +assert(rsio.digital_write(IO.F_SCRAM, IO_LVL.LOW) == false, "IO_IN_WRITE_HIGH") println("PASS") diff --git a/test/testutils.lua b/test/testutils.lua index 12c07f0..aa9f45f 100644 --- a/test/testutils.lua +++ b/test/testutils.lua @@ -41,4 +41,82 @@ function testutils.test_func_nil(name, f, result) println("PASS") end +-- get something as a string +---@param result any +---@return string +function testutils.stringify(result) + return textutils.serialize(result, { allow_repetitions = true, compact = true }) +end + +-- pause for 1 second, or the provided seconds +---@param seconds? number +function testutils.pause(seconds) + seconds = seconds or 1.0 +---@diagnostic disable-next-line: undefined-field + os.sleep(seconds) +end + +-- create a new MODBUS tester +---@param modbus modbus modbus object +---@param error_flag MODBUS_FCODE MODBUS_FCODE.ERROR_FLAG +function testutils.modbus_tester(modbus, error_flag) + -- test packet + ---@type modbus_frame + local packet = { + txn_id = 0, + length = 0, + unit_id = 0, + func_code = 0, + data = {}, + scada_frame = nil + } + + ---@class modbus_tester + local public = {} + + -- set the packet function and data for the next test + ---@param func MODBUS_FCODE function code + ---@param data table + function public.pkt_set(func, data) + packet.length = #data + packet.data = data + packet.func_code = func + end + + -- check the current packet, expecting an error + ---@param excode MODBUS_EXCODE exception code to expect + function public.test_error__check_request(excode) + local rcode, reply = modbus.check_request(packet) + assert(rcode == false, "CHECK_NOT_FAIL") + assert(reply.get().func_code == bit.bor(packet.func_code, error_flag), "WRONG_FCODE") + assert(reply.get().data[1] == excode, "EXCODE_MISMATCH") + end + + -- test the current packet, expecting an error + ---@param excode MODBUS_EXCODE exception code to expect + function public.test_error__handle_packet(excode) + local rcode, reply = modbus.handle_packet(packet) + assert(rcode == false, "CHECK_NOT_FAIL") + assert(reply.get().func_code == bit.bor(packet.func_code, error_flag), "WRONG_FCODE") + assert(reply.get().data[1] == excode, "EXCODE_MISMATCH") + end + + -- check the current packet, expecting success + ---@param excode MODBUS_EXCODE exception code to expect + function public.test_success__check_request(excode) + local rcode, reply = modbus.check_request(packet) + assert(rcode, "CHECK_NOT_OK") + assert(reply.get().data[1] == excode, "EXCODE_MISMATCH") + end + + -- test the current packet, expecting success + function public.test_success__handle_packet() + local rcode, reply = modbus.handle_packet(packet) + assert(rcode, "CHECK_NOT_OK") + println(testutils.stringify(reply.get().data)) + end + + return public +end + return testutils From 4d16d64cdcde3a742b9c813f708c89a43e6b1a3b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 27 May 2022 18:17:52 -0400 Subject: [PATCH 229/587] log bugfix --- scada-common/log.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scada-common/log.lua b/scada-common/log.lua index 8369d22..2fa0b32 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -39,7 +39,7 @@ log.init = function (path, write_mode, dmesg_redirect) if _log_sys.mode == MODE.APPEND then _log_sys.file = fs.open(path, "a") else - _log_sys.file = fs.open(path, "w+") + _log_sys.file = fs.open(path, "w") end if dmesg_redirect then From 706bf4d3ba2daa3fe1a731cc6db1f6085e47afa9 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 27 May 2022 18:18:12 -0400 Subject: [PATCH 230/587] #9 turbine RTU tester --- test/turbine_modbustest.lua | 68 +++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 test/turbine_modbustest.lua diff --git a/test/turbine_modbustest.lua b/test/turbine_modbustest.lua new file mode 100644 index 0000000..fe167d7 --- /dev/null +++ b/test/turbine_modbustest.lua @@ -0,0 +1,68 @@ +require("/initenv").init_env() + +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") +local types = require("scada-common.types") +local util = require("scada-common.util") + +local testutils = require("test.testutils") + +local modbus = require("rtu.modbus") +local turbine_rtu = require("rtu.dev.turbine_rtu") + +local print = util.print +local println = util.println + +local MODBUS_FCODE = types.MODBUS_FCODE + +println("starting turbine RTU MODBUS tester") +println("note: use rs_modbustest to fully test RTU/MODBUS") +println(" this only tests a turbine/parallel read") +println("") + +-- RTU init -- + +log.init("/log.txt", log.MODE.NEW) + +print(">>> init turbine RTU: ") + +ppm.mount_all() + +local dev = ppm.get_device("turbine") +assert(dev ~= nil, "NO_TURBINE") + +local t_rtu = turbine_rtu.new(dev) + +local di, c, ir, hr = t_rtu.io_count() +assert(di == 0, "IOCOUNT_DI") +assert(c == 0, "IOCOUNT_C") +assert(ir == 16, "IOCOUNT_IR") +assert(hr == 0, "IOCOUNT_HR") + +println("OK") + +local t_modbus = modbus.new(t_rtu, true) + +local mbt = testutils.modbus_tester(t_modbus, MODBUS_FCODE.ERROR_FLAG) + +---------------------- +--- READING INPUTS --- +---------------------- + +println(">>> reading inputs:") + +print("read ir {1,1}: ") +mbt.pkt_set(MODBUS_FCODE.READ_INPUT_REGS, {1, 1}) +mbt.test_success__handle_packet() + +print("read ir {2,1}: ") +mbt.pkt_set(MODBUS_FCODE.READ_INPUT_REGS, {2, 1}) +mbt.test_success__handle_packet() + +print("read ir {1,16}: ") +mbt.pkt_set(MODBUS_FCODE.READ_INPUT_REGS, {1, 16}) +mbt.test_success__handle_packet() + +println("PASS") + +println("TEST COMPLETE") From ff5b163c1db9549e083636d47e2dbd3a9b4b7eda Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 29 May 2022 14:26:40 -0400 Subject: [PATCH 231/587] ppm patch to support multiple return value functions, changed lack of modem to emit fatal error --- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 4 ++-- scada-common/ppm.lua | 9 +++++++-- supervisor/startup.lua | 4 ++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 062db60..d6e23c0 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 = "alpha-v0.7.2" +local R_PLC_VERSION = "beta-v0.7.3" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index 6f5730e..6700b86 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -24,7 +24,7 @@ local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "alpha-v0.7.1" +local RTU_VERSION = "beta-v0.7.2" local rtu_t = types.rtu_t @@ -80,7 +80,7 @@ local smem_sys = __shared_memory.rtu_sys -- get modem if smem_dev.modem == nil then println("boot> wireless modem not found") - log.warning("no wireless modem on startup") + log.fatal("no wireless modem on startup") return end diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index d07e703..b42731b 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -47,7 +47,10 @@ local peri_init = function (iface) for key, func in pairs(self.device) do self.fault_counts[key] = 0 self.device[key] = function (...) - local status, result = pcall(func, ...) + local return_table = table.pack(pcall(func, ...)) + + local status = return_table[1] + table.remove(return_table, 1) if status then -- auto fault clear @@ -56,8 +59,10 @@ local peri_init = function (iface) self.fault_counts[key] = 0 - return result + return table.unpack(return_table) else + local result = return_table[1] + -- function failed self.faulted = true self.last_fault = result diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 307b8e6..1dcf7a7 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "alpha-v0.4.0" +local SUPERVISOR_VERSION = "beta-v0.4.1" local print = util.print local println = util.println @@ -33,7 +33,7 @@ ppm.mount_all() local modem = ppm.get_wireless_modem() if modem == nil then println("boot> wireless modem not found") - log.warning("no wireless modem on startup") + log.fatal("no wireless modem on startup") return end From e65a1bf6e153c2a45fa13447672340be7cb79639 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 29 May 2022 14:34:09 -0400 Subject: [PATCH 232/587] #61 monitor configuration and init, render engine started, dmesg changes, ppm monitor listing changes --- .vscode/settings.json | 4 +- coordinator/config.lua | 22 ++++++ coordinator/coordinator.lua | 134 +++++++++++++++++++++++++++++++++++- coordinator/renderer.lua | 41 +++++++++++ coordinator/startup.lua | 34 +++++++-- coordinator/util/dialog.lua | 45 ++++++++++++ scada-common/log.lua | 64 +++++++++++++---- scada-common/ppm.lua | 12 +++- 8 files changed, 333 insertions(+), 23 deletions(-) create mode 100644 coordinator/config.lua create mode 100644 coordinator/renderer.lua create mode 100644 coordinator/util/dialog.lua diff --git a/.vscode/settings.json b/.vscode/settings.json index d2812b6..80f7c42 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,8 @@ "parallel", "colors", "textutils", - "shell" + "shell", + "settings", + "window" ] } diff --git a/coordinator/config.lua b/coordinator/config.lua new file mode 100644 index 0000000..57cabf2 --- /dev/null +++ b/coordinator/config.lua @@ -0,0 +1,22 @@ +local config = {} + +-- port of the SCADA supervisor +config.SCADA_SV_PORT = 16100 +-- port to listen to incoming packets from supervisor +config.SCADA_SV_LISTEN = 16101 +-- listen port for SCADA coordinator API access +config.SCADA_API_LISTEN = 16200 +-- expected number of reactor units +config.NUM_UNITS = 4 +-- log path +config.LOG_PATH = "/log.txt" +-- log mode +-- 0 = APPEND (adds to existing file on start) +-- 1 = NEW (replaces existing file on start) +config.LOG_MODE = 0 +-- crypto config +config.SECURE = true +-- must be common between all devices +config.PASSWORD = "testpassword!" + +return config diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index a6bf236..76c9824 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -1,9 +1,141 @@ + local comms = require("scada-common.comms") +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") + +local dialog = require("coordinator.util.dialog") + +local print = util.print +local println = util.println +local print_ts = util.print_ts +local println_ts = util.println_ts local coordinator = {} +local function ask_monitor(names) + println("available monitors:") + for i = 1, #names do + print(" " .. names[i]) + end + println("") + println("select a monitor or type c to cancel") + + local iface = dialog.ask_options(names, "c") + + if iface ~= false and iface ~= nil then + util.filter_table(names, function (x) return x ~= iface end) + end + + return iface +end + +function coordinator.configure_monitors(num_units) + ---@class monitors_struct + local monitors = { + primary = nil, + unit_displays = {} + } + + local monitors_avail = ppm.get_monitor_list() + local names = {} + + -- get all interface names + for iface, _ in pairs(monitors_avail) do + table.insert(names, iface) + end + + -- we need a certain number of monitors (1 per unit + 1 primary display) + if #names ~= num_units + 1 then + println("not enough monitors connected (need " .. num_units + 1 .. ")") + log.warning("insufficient monitors present (need " .. num_units + 1 .. ")") + return false + end + + -- attempt to load settings + settings.load("/coord.settings") + + --------------------- + -- PRIMARY DISPLAY -- + --------------------- + + local iface_primary_display = settings.get("PRIMARY_DISPLAY") + + if not util.table_contains(names, iface_primary_display) then + println("primary display is not connected") + local response = dialog.ask_y_n("would you like to change it", true) + if response == false then return false end + iface_primary_display = nil + end + + while iface_primary_display == nil and #names > 0 do + -- lets get a monitor + iface_primary_display = ask_monitor(names) + end + + if iface_primary_display == false then return false end + + settings.set("PRIMARY_DISPLAY", iface_primary_display) + util.filter_table(names, function (x) return x ~= iface_primary_display end) + + monitors.primary = ppm.get_periph(iface_primary_display) + + ------------------- + -- UNIT DISPLAYS -- + ------------------- + + local unit_displays = settings.get("UNIT_DISPLAYS") + + if unit_displays == nil then + unit_displays = {} + for i = 1, num_units do + local display = nil + + while display == nil and #names > 0 do + -- lets get a monitor + println("please select monitor for unit " .. i) + display = ask_monitor(names) + end + + if display == false then return false end + + unit_displays[i] = display + end + else + -- make sure all displays are connected + for i = 1, num_units do +---@diagnostic disable-next-line: need-check-nil + local display = unit_displays[i] + + if not util.table_contains(names, display) then + local response = dialog.ask_y_n("unit display " .. i .. " is not connected, would you like to change it?", true) + if response == false then return false end + display = nil + end + + while display == nil and #names > 0 do + -- lets get a monitor + display = ask_monitor(names) + end + + if display == false then return false end + + unit_displays[i] = display + end + end + + settings.set("UNIT_DISPLAYS", unit_displays) + settings.save("/coord.settings") + + for i = 1, #unit_displays do + monitors.unit_displays[i] = ppm.get_periph(unit_displays[i]) + end + + return true, monitors +end + -- coordinator communications -coordinator.coord_comms = function () +function coordinator.coord_comms() local self = { reactor_struct_cache = nil } diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua new file mode 100644 index 0000000..57553ca --- /dev/null +++ b/coordinator/renderer.lua @@ -0,0 +1,41 @@ +local log = require("scada-common.log") +local util = require("scada-common.util") + +local renderer = {} + +local engine = { + monitors = nil, + dmesg_window = nil +} + +---@param monitors monitors_struct +function renderer.set_displays(monitors) + engine.monitors = monitors +end + +function renderer.reset() + -- reset primary monitor + engine.monitors.primary.setTextScale(0.5) + engine.monitors.primary.setTextColor(colors.white) + engine.monitors.primary.setBackgroundColor(colors.black) + engine.monitors.primary.clear() + engine.monitors.primary.setCursorPos(1, 1) + + -- reset unit displays + for _, monitor in pairs(engine.monitors.unit_displays) do + monitor.setTextScale(0.5) + monitor.setTextColor(colors.white) + monitor.setBackgroundColor(colors.black) + monitor.clear() + monitor.setCursorPos(1, 1) + end +end + +function renderer.init_dmesg() + local disp_x, disp_y = engine.monitors.primary.getSize() + engine.dmesg_window = window.create(engine.monitors.primary, 1, 1, disp_x, disp_y) + + log.direct_dmesg(engine.dmesg_window) +end + +return renderer diff --git a/coordinator/startup.lua b/coordinator/startup.lua index aa65be4..25a6c5e 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -10,6 +10,7 @@ local util = require("scada-common.util") local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") +local renderer = require("coordinator.renderer") local COORDINATOR_VERSION = "alpha-v0.1.2" @@ -18,7 +19,7 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts -log.init("/log.txt", log.MODE.APPEND) +log.init(config.LOG_PATH, config.LOG_MODE) log.info("========================================") log.info("BOOTING coordinator.startup " .. COORDINATOR_VERSION) @@ -28,10 +29,31 @@ println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<") -- mount connected devices ppm.mount_all() -local modem = ppm.get_wireless_modem() - --- we need a modem -if modem == nil then - println("please connect a wireless modem") +-- setup monitors +local configured, monitors = coordinator.configure_monitors(config.NUM_UNITS) +if not configured then + println("boot> monitor setup failed") + log.fatal("monitor configuration failed") return end + +log.info("monitors ready, dmesg input incoming...") + +-- init renderer +renderer.set_displays(monitors) +renderer.reset() +renderer.init_dmesg() + +log.dmesg("displays connected and reset", "GRAPHICS", colors.green) +log.dmesg("system start on " .. os.date("%c"), "SYSTEM", colors.cyan) +log.dmesg("starting " .. COORDINATOR_VERSION, "BOOT", colors.blue) + +-- get the communications modem +local modem = ppm.get_wireless_modem() +if modem == nil then + println("boot> wireless modem not found") + log.fatal("no wireless modem on startup") + return +end + +log.dmesg("wireless modem connected", "COMMS", colors.purple) diff --git a/coordinator/util/dialog.lua b/coordinator/util/dialog.lua new file mode 100644 index 0000000..ca9a8fe --- /dev/null +++ b/coordinator/util/dialog.lua @@ -0,0 +1,45 @@ +local completion = require("cc.completion") + +local util = require("scada-common.util") + +local print = util.print +local println = util.println +local print_ts = util.print_ts +local println_ts = util.println_ts + +local dialog = {} + +function dialog.ask_y_n(question, default) + print(question) + + if default == true then + print(" (Y/n)? ") + else + print(" (y/N)? ") + end + + local response = read(nil, nil) + + if response == "" then + return default + elseif response == "Y" or response == "y" then + return true + elseif response == "N" or response == "n" then + return false + else + return nil + end +end + +function dialog.ask_options(options, cancel) + print("> ") + local response = read(nil, nil, function(text) return completion.choice(text, options) end) + + if response == cancel then return false end + + if util.table_contains(options, response) then + return response + else return nil end +end + +return dialog diff --git a/scada-common/log.lua b/scada-common/log.lua index 2fa0b32..791b11b 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -49,6 +49,12 @@ log.init = function (path, write_mode, dmesg_redirect) end end +-- direct dmesg output to a monitor/window +---@param window table window or terminal reference +log.direct_dmesg = function (window) + _log_sys.dmesg_out = window +end + -- private log write function ---@param msg string local _log = function (msg) @@ -84,9 +90,16 @@ local _log = function (msg) end end --- write a message to the dmesg output ----@param msg string message to write -local _write = function (msg) +-- dmesg style logging for boot because I like linux-y things +---@param msg string message +---@param tag? string log tag +---@param tag_color? integer log tag color +log.dmesg = function (msg, tag, tag_color) + msg = util.strval(msg) + tag = tag or "" + tag = util.strval(tag) + + local t_stamp = string.format("%12.2f", os.clock()) local out = _log_sys.dmesg_out local out_w, out_h = out.getSize() @@ -116,11 +129,43 @@ local _write = function (msg) end end + -- start output with tag and time, assuming we have enough width for this to be on one line + local cur_x, cur_y = out.getCursorPos() + + if cur_x > 1 then + if cur_y == out_h then + out.scroll(1) + out.setCursorPos(1, cur_y) + else + out.setCursorPos(1, cur_y + 1) + end + end + + -- colored time + local initial_color = out.getTextColor() + out.setTextColor(colors.white) + out.write("[") + out.setTextColor(colors.lightGray) + out.write(t_stamp) + out.setTextColor(colors.white) + out.write("] ") + + -- colored tag + if tag ~= "" then + out.write("[") + out.setTextColor(tag_color) + out.write(tag) + out.setTextColor(colors.white) + out.write("] ") + end + + out.setTextColor(initial_color) + -- output message for i = 1, #lines do - local cur_x, cur_y = out.getCursorPos() + cur_x, cur_y = out.getCursorPos() - if cur_x > 1 then + if i > 1 and cur_x > 1 then if cur_y == out_h then out.scroll(1) out.setCursorPos(1, cur_y) @@ -131,15 +176,8 @@ local _write = function (msg) out.write(lines[i]) end -end --- dmesg style logging for boot because I like linux-y things ----@param msg string message ----@param show_term? boolean whether or not to show on terminal output -log.dmesg = function (msg, show_term) - local message = string.format("[%10.3f] ", os.clock()) .. util.strval(msg) - if show_term then _write(message) end - _log(message) + _log("[" .. t_stamp .. "] " .. tag .. " " .. msg) end -- log debug messages diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index b42731b..5b65f47 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -317,8 +317,16 @@ end -- list all connected monitors ---@return table monitors -ppm.list_monitors = function () - return ppm.get_all_devices("monitor") +ppm.get_monitor_list = function () + local list = {} + + for iface, device in pairs(_ppm_sys.mounts) do + if device.type == "monitor" then + list[iface] = device + end + end + + return list end return ppm From 309ba06f8a964c4b9ef32af3356f6debc0bd4823 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 29 May 2022 15:05:57 -0400 Subject: [PATCH 233/587] #51 crypto system --- lockbox/LICENSE | 22 ++ lockbox/cipher/aes128.lua | 415 +++++++++++++++++++++++++++ lockbox/cipher/aes192.lua | 462 ++++++++++++++++++++++++++++++ lockbox/cipher/aes256.lua | 498 +++++++++++++++++++++++++++++++++ lockbox/cipher/mode/cbc.lua | 164 +++++++++++ lockbox/cipher/mode/cfb.lua | 163 +++++++++++ lockbox/cipher/mode/ctr.lua | 248 ++++++++++++++++ lockbox/cipher/mode/ofb.lua | 164 +++++++++++ lockbox/digest/sha1.lua | 173 ++++++++++++ lockbox/digest/sha2_224.lua | 200 +++++++++++++ lockbox/digest/sha2_256.lua | 203 ++++++++++++++ lockbox/init.lua | 22 ++ lockbox/kdf/pbkdf2.lua | 114 ++++++++ lockbox/mac/hmac.lua | 85 ++++++ lockbox/padding/ansix923.lua | 22 ++ lockbox/padding/isoiec7816.lua | 22 ++ lockbox/padding/pkcs7.lua | 18 ++ lockbox/padding/zero.lua | 19 ++ lockbox/util/array.lua | 211 ++++++++++++++ lockbox/util/bit.lua | 25 ++ lockbox/util/queue.lua | 47 ++++ lockbox/util/stream.lua | 99 +++++++ scada-common/crypto.lua | 246 ++++++++++++++++ test/lockbox-benchmark.lua | 104 +++++++ 24 files changed, 3746 insertions(+) create mode 100644 lockbox/LICENSE create mode 100644 lockbox/cipher/aes128.lua create mode 100644 lockbox/cipher/aes192.lua create mode 100644 lockbox/cipher/aes256.lua create mode 100644 lockbox/cipher/mode/cbc.lua create mode 100644 lockbox/cipher/mode/cfb.lua create mode 100644 lockbox/cipher/mode/ctr.lua create mode 100644 lockbox/cipher/mode/ofb.lua create mode 100644 lockbox/digest/sha1.lua create mode 100644 lockbox/digest/sha2_224.lua create mode 100644 lockbox/digest/sha2_256.lua create mode 100644 lockbox/init.lua create mode 100644 lockbox/kdf/pbkdf2.lua create mode 100644 lockbox/mac/hmac.lua create mode 100644 lockbox/padding/ansix923.lua create mode 100644 lockbox/padding/isoiec7816.lua create mode 100644 lockbox/padding/pkcs7.lua create mode 100644 lockbox/padding/zero.lua create mode 100644 lockbox/util/array.lua create mode 100644 lockbox/util/bit.lua create mode 100644 lockbox/util/queue.lua create mode 100644 lockbox/util/stream.lua create mode 100644 scada-common/crypto.lua create mode 100644 test/lockbox-benchmark.lua diff --git a/lockbox/LICENSE b/lockbox/LICENSE new file mode 100644 index 0000000..9fed1ed --- /dev/null +++ b/lockbox/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 James L. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/lockbox/cipher/aes128.lua b/lockbox/cipher/aes128.lua new file mode 100644 index 0000000..0726ac4 --- /dev/null +++ b/lockbox/cipher/aes128.lua @@ -0,0 +1,415 @@ +local Array = require("lockbox.util.array"); +local Bit = require("lockbox.util.bit"); + +local XOR = Bit.bxor; + +local SBOX = { + [0] = 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, + 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, + 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, + 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, + 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, + 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, + 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, + 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, + 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, + 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, + 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, + 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, + 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, + 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, + 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16}; + +local ISBOX = { + [0] = 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, + 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, + 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, + 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, + 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, + 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, + 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, + 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, + 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, + 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, + 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, + 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, + 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, + 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, + 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D}; + +local ROW_SHIFT = { 1, 6, 11, 16, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, }; +local IROW_SHIFT = { 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3, 16, 13, 10, 7, 4, }; + +local ETABLE = { + [0] = 0x01, 0x03, 0x05, 0x0F, 0x11, 0x33, 0x55, 0xFF, 0x1A, 0x2E, 0x72, 0x96, 0xA1, 0xF8, 0x13, 0x35, + 0x5F, 0xE1, 0x38, 0x48, 0xD8, 0x73, 0x95, 0xA4, 0xF7, 0x02, 0x06, 0x0A, 0x1E, 0x22, 0x66, 0xAA, + 0xE5, 0x34, 0x5C, 0xE4, 0x37, 0x59, 0xEB, 0x26, 0x6A, 0xBE, 0xD9, 0x70, 0x90, 0xAB, 0xE6, 0x31, + 0x53, 0xF5, 0x04, 0x0C, 0x14, 0x3C, 0x44, 0xCC, 0x4F, 0xD1, 0x68, 0xB8, 0xD3, 0x6E, 0xB2, 0xCD, + 0x4C, 0xD4, 0x67, 0xA9, 0xE0, 0x3B, 0x4D, 0xD7, 0x62, 0xA6, 0xF1, 0x08, 0x18, 0x28, 0x78, 0x88, + 0x83, 0x9E, 0xB9, 0xD0, 0x6B, 0xBD, 0xDC, 0x7F, 0x81, 0x98, 0xB3, 0xCE, 0x49, 0xDB, 0x76, 0x9A, + 0xB5, 0xC4, 0x57, 0xF9, 0x10, 0x30, 0x50, 0xF0, 0x0B, 0x1D, 0x27, 0x69, 0xBB, 0xD6, 0x61, 0xA3, + 0xFE, 0x19, 0x2B, 0x7D, 0x87, 0x92, 0xAD, 0xEC, 0x2F, 0x71, 0x93, 0xAE, 0xE9, 0x20, 0x60, 0xA0, + 0xFB, 0x16, 0x3A, 0x4E, 0xD2, 0x6D, 0xB7, 0xC2, 0x5D, 0xE7, 0x32, 0x56, 0xFA, 0x15, 0x3F, 0x41, + 0xC3, 0x5E, 0xE2, 0x3D, 0x47, 0xC9, 0x40, 0xC0, 0x5B, 0xED, 0x2C, 0x74, 0x9C, 0xBF, 0xDA, 0x75, + 0x9F, 0xBA, 0xD5, 0x64, 0xAC, 0xEF, 0x2A, 0x7E, 0x82, 0x9D, 0xBC, 0xDF, 0x7A, 0x8E, 0x89, 0x80, + 0x9B, 0xB6, 0xC1, 0x58, 0xE8, 0x23, 0x65, 0xAF, 0xEA, 0x25, 0x6F, 0xB1, 0xC8, 0x43, 0xC5, 0x54, + 0xFC, 0x1F, 0x21, 0x63, 0xA5, 0xF4, 0x07, 0x09, 0x1B, 0x2D, 0x77, 0x99, 0xB0, 0xCB, 0x46, 0xCA, + 0x45, 0xCF, 0x4A, 0xDE, 0x79, 0x8B, 0x86, 0x91, 0xA8, 0xE3, 0x3E, 0x42, 0xC6, 0x51, 0xF3, 0x0E, + 0x12, 0x36, 0x5A, 0xEE, 0x29, 0x7B, 0x8D, 0x8C, 0x8F, 0x8A, 0x85, 0x94, 0xA7, 0xF2, 0x0D, 0x17, + 0x39, 0x4B, 0xDD, 0x7C, 0x84, 0x97, 0xA2, 0xFD, 0x1C, 0x24, 0x6C, 0xB4, 0xC7, 0x52, 0xF6, 0x01}; + +local LTABLE = { + [0] = 0x00, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1A, 0xC6, 0x4B, 0xC7, 0x1B, 0x68, 0x33, 0xEE, 0xDF, 0x03, + 0x64, 0x04, 0xE0, 0x0E, 0x34, 0x8D, 0x81, 0xEF, 0x4C, 0x71, 0x08, 0xC8, 0xF8, 0x69, 0x1C, 0xC1, + 0x7D, 0xC2, 0x1D, 0xB5, 0xF9, 0xB9, 0x27, 0x6A, 0x4D, 0xE4, 0xA6, 0x72, 0x9A, 0xC9, 0x09, 0x78, + 0x65, 0x2F, 0x8A, 0x05, 0x21, 0x0F, 0xE1, 0x24, 0x12, 0xF0, 0x82, 0x45, 0x35, 0x93, 0xDA, 0x8E, + 0x96, 0x8F, 0xDB, 0xBD, 0x36, 0xD0, 0xCE, 0x94, 0x13, 0x5C, 0xD2, 0xF1, 0x40, 0x46, 0x83, 0x38, + 0x66, 0xDD, 0xFD, 0x30, 0xBF, 0x06, 0x8B, 0x62, 0xB3, 0x25, 0xE2, 0x98, 0x22, 0x88, 0x91, 0x10, + 0x7E, 0x6E, 0x48, 0xC3, 0xA3, 0xB6, 0x1E, 0x42, 0x3A, 0x6B, 0x28, 0x54, 0xFA, 0x85, 0x3D, 0xBA, + 0x2B, 0x79, 0x0A, 0x15, 0x9B, 0x9F, 0x5E, 0xCA, 0x4E, 0xD4, 0xAC, 0xE5, 0xF3, 0x73, 0xA7, 0x57, + 0xAF, 0x58, 0xA8, 0x50, 0xF4, 0xEA, 0xD6, 0x74, 0x4F, 0xAE, 0xE9, 0xD5, 0xE7, 0xE6, 0xAD, 0xE8, + 0x2C, 0xD7, 0x75, 0x7A, 0xEB, 0x16, 0x0B, 0xF5, 0x59, 0xCB, 0x5F, 0xB0, 0x9C, 0xA9, 0x51, 0xA0, + 0x7F, 0x0C, 0xF6, 0x6F, 0x17, 0xC4, 0x49, 0xEC, 0xD8, 0x43, 0x1F, 0x2D, 0xA4, 0x76, 0x7B, 0xB7, + 0xCC, 0xBB, 0x3E, 0x5A, 0xFB, 0x60, 0xB1, 0x86, 0x3B, 0x52, 0xA1, 0x6C, 0xAA, 0x55, 0x29, 0x9D, + 0x97, 0xB2, 0x87, 0x90, 0x61, 0xBE, 0xDC, 0xFC, 0xBC, 0x95, 0xCF, 0xCD, 0x37, 0x3F, 0x5B, 0xD1, + 0x53, 0x39, 0x84, 0x3C, 0x41, 0xA2, 0x6D, 0x47, 0x14, 0x2A, 0x9E, 0x5D, 0x56, 0xF2, 0xD3, 0xAB, + 0x44, 0x11, 0x92, 0xD9, 0x23, 0x20, 0x2E, 0x89, 0xB4, 0x7C, 0xB8, 0x26, 0x77, 0x99, 0xE3, 0xA5, + 0x67, 0x4A, 0xED, 0xDE, 0xC5, 0x31, 0xFE, 0x18, 0x0D, 0x63, 0x8C, 0x80, 0xC0, 0xF7, 0x70, 0x07}; + +local MIXTABLE = { + 0x02, 0x03, 0x01, 0x01, + 0x01, 0x02, 0x03, 0x01, + 0x01, 0x01, 0x02, 0x03, + 0x03, 0x01, 0x01, 0x02}; + +local IMIXTABLE = { + 0x0E, 0x0B, 0x0D, 0x09, + 0x09, 0x0E, 0x0B, 0x0D, + 0x0D, 0x09, 0x0E, 0x0B, + 0x0B, 0x0D, 0x09, 0x0E}; + +local RCON = { +[0] = 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, +0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, +0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, +0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, +0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, +0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, +0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, +0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, +0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, +0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, +0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, +0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, +0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, +0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, +0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, +0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d}; + + +local GMUL = function(A, B) + if(A == 0x01) then return B; end + if(B == 0x01) then return A; end + if(A == 0x00) then return 0; end + if(B == 0x00) then return 0; end + + local LA = LTABLE[A]; + local LB = LTABLE[B]; + + local sum = LA + LB; + if (sum > 0xFF) then sum = sum - 0xFF; end + + return ETABLE[sum]; +end + +local byteSub = Array.substitute; + +local shiftRow = Array.permute; + +local mixCol = function(i, mix) + local out = {}; + + local a, b, c, d; + + a = GMUL(i[ 1], mix[ 1]); + b = GMUL(i[ 2], mix[ 2]); + c = GMUL(i[ 3], mix[ 3]); + d = GMUL(i[ 4], mix[ 4]); + out[ 1] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[ 5]); + b = GMUL(i[ 2], mix[ 6]); + c = GMUL(i[ 3], mix[ 7]); + d = GMUL(i[ 4], mix[ 8]); + out[ 2] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[ 9]); + b = GMUL(i[ 2], mix[10]); + c = GMUL(i[ 3], mix[11]); + d = GMUL(i[ 4], mix[12]); + out[ 3] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[13]); + b = GMUL(i[ 2], mix[14]); + c = GMUL(i[ 3], mix[15]); + d = GMUL(i[ 4], mix[16]); + out[ 4] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[ 5], mix[ 1]); + b = GMUL(i[ 6], mix[ 2]); + c = GMUL(i[ 7], mix[ 3]); + d = GMUL(i[ 8], mix[ 4]); + out[ 5] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[ 5]); + b = GMUL(i[ 6], mix[ 6]); + c = GMUL(i[ 7], mix[ 7]); + d = GMUL(i[ 8], mix[ 8]); + out[ 6] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[ 9]); + b = GMUL(i[ 6], mix[10]); + c = GMUL(i[ 7], mix[11]); + d = GMUL(i[ 8], mix[12]); + out[ 7] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[13]); + b = GMUL(i[ 6], mix[14]); + c = GMUL(i[ 7], mix[15]); + d = GMUL(i[ 8], mix[16]); + out[ 8] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[ 9], mix[ 1]); + b = GMUL(i[10], mix[ 2]); + c = GMUL(i[11], mix[ 3]); + d = GMUL(i[12], mix[ 4]); + out[ 9] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[ 5]); + b = GMUL(i[10], mix[ 6]); + c = GMUL(i[11], mix[ 7]); + d = GMUL(i[12], mix[ 8]); + out[10] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[ 9]); + b = GMUL(i[10], mix[10]); + c = GMUL(i[11], mix[11]); + d = GMUL(i[12], mix[12]); + out[11] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[13]); + b = GMUL(i[10], mix[14]); + c = GMUL(i[11], mix[15]); + d = GMUL(i[12], mix[16]); + out[12] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[13], mix[ 1]); + b = GMUL(i[14], mix[ 2]); + c = GMUL(i[15], mix[ 3]); + d = GMUL(i[16], mix[ 4]); + out[13] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[ 5]); + b = GMUL(i[14], mix[ 6]); + c = GMUL(i[15], mix[ 7]); + d = GMUL(i[16], mix[ 8]); + out[14] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[ 9]); + b = GMUL(i[14], mix[10]); + c = GMUL(i[15], mix[11]); + d = GMUL(i[16], mix[12]); + out[15] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[13]); + b = GMUL(i[14], mix[14]); + c = GMUL(i[15], mix[15]); + d = GMUL(i[16], mix[16]); + out[16] = XOR(XOR(a, b), XOR(c, d)); + + return out; +end + +local keyRound = function(key, round) + local out = {}; + + out[ 1] = XOR(key[ 1], XOR(SBOX[key[14]], RCON[round])); + out[ 2] = XOR(key[ 2], SBOX[key[15]]); + out[ 3] = XOR(key[ 3], SBOX[key[16]]); + out[ 4] = XOR(key[ 4], SBOX[key[13]]); + + out[ 5] = XOR(out[ 1], key[ 5]); + out[ 6] = XOR(out[ 2], key[ 6]); + out[ 7] = XOR(out[ 3], key[ 7]); + out[ 8] = XOR(out[ 4], key[ 8]); + + out[ 9] = XOR(out[ 5], key[ 9]); + out[10] = XOR(out[ 6], key[10]); + out[11] = XOR(out[ 7], key[11]); + out[12] = XOR(out[ 8], key[12]); + + out[13] = XOR(out[ 9], key[13]); + out[14] = XOR(out[10], key[14]); + out[15] = XOR(out[11], key[15]); + out[16] = XOR(out[12], key[16]); + + return out; +end + +local keyExpand = function(key) + local keys = {}; + + local temp = key; + + keys[1] = temp; + + for i = 1, 10 do + temp = keyRound(temp, i); + keys[i + 1] = temp; + end + + return keys; + +end + +local addKey = Array.XOR; + + + +local AES = {}; + +AES.blockSize = 16; + +AES.encrypt = function(_key, block) + + local key = keyExpand(_key); + + --round 0 + block = addKey(block, key[1]); + + --round 1 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[2]); + + --round 2 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[3]); + + --round 3 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[4]); + + --round 4 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[5]); + + --round 5 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[6]); + + --round 6 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[7]); + + --round 7 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[8]); + + --round 8 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[9]); + + --round 9 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[10]); + + --round 10 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = addKey(block, key[11]); + + return block; + +end + +AES.decrypt = function(_key, block) + + local key = keyExpand(_key); + + --round 0 + block = addKey(block, key[11]); + + --round 1 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[10]); + block = mixCol(block, IMIXTABLE); + + --round 2 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[9]); + block = mixCol(block, IMIXTABLE); + + --round 3 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[8]); + block = mixCol(block, IMIXTABLE); + + --round 4 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[7]); + block = mixCol(block, IMIXTABLE); + + --round 5 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[6]); + block = mixCol(block, IMIXTABLE); + + --round 6 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[5]); + block = mixCol(block, IMIXTABLE); + + --round 7 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[4]); + block = mixCol(block, IMIXTABLE); + + --round 8 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[3]); + block = mixCol(block, IMIXTABLE); + + --round 9 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[2]); + block = mixCol(block, IMIXTABLE); + + --round 10 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[1]); + + return block; +end + +return AES; diff --git a/lockbox/cipher/aes192.lua b/lockbox/cipher/aes192.lua new file mode 100644 index 0000000..5f55b0e --- /dev/null +++ b/lockbox/cipher/aes192.lua @@ -0,0 +1,462 @@ + +local Array = require("lockbox.util.array"); +local Bit = require("lockbox.util.bit"); + +local XOR = Bit.bxor; + +local SBOX = { + [0] = 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, + 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, + 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, + 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, + 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, + 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, + 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, + 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, + 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, + 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, + 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, + 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, + 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, + 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, + 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16}; + +local ISBOX = { + [0] = 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, + 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, + 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, + 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, + 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, + 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, + 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, + 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, + 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, + 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, + 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, + 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, + 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, + 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, + 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D}; + +local ROW_SHIFT = { 1, 6, 11, 16, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, }; +local IROW_SHIFT = { 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3, 16, 13, 10, 7, 4, }; + +local ETABLE = { + [0] = 0x01, 0x03, 0x05, 0x0F, 0x11, 0x33, 0x55, 0xFF, 0x1A, 0x2E, 0x72, 0x96, 0xA1, 0xF8, 0x13, 0x35, + 0x5F, 0xE1, 0x38, 0x48, 0xD8, 0x73, 0x95, 0xA4, 0xF7, 0x02, 0x06, 0x0A, 0x1E, 0x22, 0x66, 0xAA, + 0xE5, 0x34, 0x5C, 0xE4, 0x37, 0x59, 0xEB, 0x26, 0x6A, 0xBE, 0xD9, 0x70, 0x90, 0xAB, 0xE6, 0x31, + 0x53, 0xF5, 0x04, 0x0C, 0x14, 0x3C, 0x44, 0xCC, 0x4F, 0xD1, 0x68, 0xB8, 0xD3, 0x6E, 0xB2, 0xCD, + 0x4C, 0xD4, 0x67, 0xA9, 0xE0, 0x3B, 0x4D, 0xD7, 0x62, 0xA6, 0xF1, 0x08, 0x18, 0x28, 0x78, 0x88, + 0x83, 0x9E, 0xB9, 0xD0, 0x6B, 0xBD, 0xDC, 0x7F, 0x81, 0x98, 0xB3, 0xCE, 0x49, 0xDB, 0x76, 0x9A, + 0xB5, 0xC4, 0x57, 0xF9, 0x10, 0x30, 0x50, 0xF0, 0x0B, 0x1D, 0x27, 0x69, 0xBB, 0xD6, 0x61, 0xA3, + 0xFE, 0x19, 0x2B, 0x7D, 0x87, 0x92, 0xAD, 0xEC, 0x2F, 0x71, 0x93, 0xAE, 0xE9, 0x20, 0x60, 0xA0, + 0xFB, 0x16, 0x3A, 0x4E, 0xD2, 0x6D, 0xB7, 0xC2, 0x5D, 0xE7, 0x32, 0x56, 0xFA, 0x15, 0x3F, 0x41, + 0xC3, 0x5E, 0xE2, 0x3D, 0x47, 0xC9, 0x40, 0xC0, 0x5B, 0xED, 0x2C, 0x74, 0x9C, 0xBF, 0xDA, 0x75, + 0x9F, 0xBA, 0xD5, 0x64, 0xAC, 0xEF, 0x2A, 0x7E, 0x82, 0x9D, 0xBC, 0xDF, 0x7A, 0x8E, 0x89, 0x80, + 0x9B, 0xB6, 0xC1, 0x58, 0xE8, 0x23, 0x65, 0xAF, 0xEA, 0x25, 0x6F, 0xB1, 0xC8, 0x43, 0xC5, 0x54, + 0xFC, 0x1F, 0x21, 0x63, 0xA5, 0xF4, 0x07, 0x09, 0x1B, 0x2D, 0x77, 0x99, 0xB0, 0xCB, 0x46, 0xCA, + 0x45, 0xCF, 0x4A, 0xDE, 0x79, 0x8B, 0x86, 0x91, 0xA8, 0xE3, 0x3E, 0x42, 0xC6, 0x51, 0xF3, 0x0E, + 0x12, 0x36, 0x5A, 0xEE, 0x29, 0x7B, 0x8D, 0x8C, 0x8F, 0x8A, 0x85, 0x94, 0xA7, 0xF2, 0x0D, 0x17, + 0x39, 0x4B, 0xDD, 0x7C, 0x84, 0x97, 0xA2, 0xFD, 0x1C, 0x24, 0x6C, 0xB4, 0xC7, 0x52, 0xF6, 0x01}; + +local LTABLE = { + [0] = 0x00, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1A, 0xC6, 0x4B, 0xC7, 0x1B, 0x68, 0x33, 0xEE, 0xDF, 0x03, + 0x64, 0x04, 0xE0, 0x0E, 0x34, 0x8D, 0x81, 0xEF, 0x4C, 0x71, 0x08, 0xC8, 0xF8, 0x69, 0x1C, 0xC1, + 0x7D, 0xC2, 0x1D, 0xB5, 0xF9, 0xB9, 0x27, 0x6A, 0x4D, 0xE4, 0xA6, 0x72, 0x9A, 0xC9, 0x09, 0x78, + 0x65, 0x2F, 0x8A, 0x05, 0x21, 0x0F, 0xE1, 0x24, 0x12, 0xF0, 0x82, 0x45, 0x35, 0x93, 0xDA, 0x8E, + 0x96, 0x8F, 0xDB, 0xBD, 0x36, 0xD0, 0xCE, 0x94, 0x13, 0x5C, 0xD2, 0xF1, 0x40, 0x46, 0x83, 0x38, + 0x66, 0xDD, 0xFD, 0x30, 0xBF, 0x06, 0x8B, 0x62, 0xB3, 0x25, 0xE2, 0x98, 0x22, 0x88, 0x91, 0x10, + 0x7E, 0x6E, 0x48, 0xC3, 0xA3, 0xB6, 0x1E, 0x42, 0x3A, 0x6B, 0x28, 0x54, 0xFA, 0x85, 0x3D, 0xBA, + 0x2B, 0x79, 0x0A, 0x15, 0x9B, 0x9F, 0x5E, 0xCA, 0x4E, 0xD4, 0xAC, 0xE5, 0xF3, 0x73, 0xA7, 0x57, + 0xAF, 0x58, 0xA8, 0x50, 0xF4, 0xEA, 0xD6, 0x74, 0x4F, 0xAE, 0xE9, 0xD5, 0xE7, 0xE6, 0xAD, 0xE8, + 0x2C, 0xD7, 0x75, 0x7A, 0xEB, 0x16, 0x0B, 0xF5, 0x59, 0xCB, 0x5F, 0xB0, 0x9C, 0xA9, 0x51, 0xA0, + 0x7F, 0x0C, 0xF6, 0x6F, 0x17, 0xC4, 0x49, 0xEC, 0xD8, 0x43, 0x1F, 0x2D, 0xA4, 0x76, 0x7B, 0xB7, + 0xCC, 0xBB, 0x3E, 0x5A, 0xFB, 0x60, 0xB1, 0x86, 0x3B, 0x52, 0xA1, 0x6C, 0xAA, 0x55, 0x29, 0x9D, + 0x97, 0xB2, 0x87, 0x90, 0x61, 0xBE, 0xDC, 0xFC, 0xBC, 0x95, 0xCF, 0xCD, 0x37, 0x3F, 0x5B, 0xD1, + 0x53, 0x39, 0x84, 0x3C, 0x41, 0xA2, 0x6D, 0x47, 0x14, 0x2A, 0x9E, 0x5D, 0x56, 0xF2, 0xD3, 0xAB, + 0x44, 0x11, 0x92, 0xD9, 0x23, 0x20, 0x2E, 0x89, 0xB4, 0x7C, 0xB8, 0x26, 0x77, 0x99, 0xE3, 0xA5, + 0x67, 0x4A, 0xED, 0xDE, 0xC5, 0x31, 0xFE, 0x18, 0x0D, 0x63, 0x8C, 0x80, 0xC0, 0xF7, 0x70, 0x07}; + +local MIXTABLE = { + 0x02, 0x03, 0x01, 0x01, + 0x01, 0x02, 0x03, 0x01, + 0x01, 0x01, 0x02, 0x03, + 0x03, 0x01, 0x01, 0x02}; + +local IMIXTABLE = { + 0x0E, 0x0B, 0x0D, 0x09, + 0x09, 0x0E, 0x0B, 0x0D, + 0x0D, 0x09, 0x0E, 0x0B, + 0x0B, 0x0D, 0x09, 0x0E}; + +local RCON = { +[0] = 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, +0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, +0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, +0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, +0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, +0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, +0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, +0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, +0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, +0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, +0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, +0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, +0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, +0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, +0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, +0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d}; + + +local GMUL = function(A, B) + if(A == 0x01) then return B; end + if(B == 0x01) then return A; end + if(A == 0x00) then return 0; end + if(B == 0x00) then return 0; end + + local LA = LTABLE[A]; + local LB = LTABLE[B]; + + local sum = LA + LB; + if (sum > 0xFF) then sum = sum - 0xFF; end + + return ETABLE[sum]; +end + +local byteSub = Array.substitute; + +local shiftRow = Array.permute; + +local mixCol = function(i, mix) + local out = {}; + + local a, b, c, d; + + a = GMUL(i[ 1], mix[ 1]); + b = GMUL(i[ 2], mix[ 2]); + c = GMUL(i[ 3], mix[ 3]); + d = GMUL(i[ 4], mix[ 4]); + out[ 1] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[ 5]); + b = GMUL(i[ 2], mix[ 6]); + c = GMUL(i[ 3], mix[ 7]); + d = GMUL(i[ 4], mix[ 8]); + out[ 2] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[ 9]); + b = GMUL(i[ 2], mix[10]); + c = GMUL(i[ 3], mix[11]); + d = GMUL(i[ 4], mix[12]); + out[ 3] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[13]); + b = GMUL(i[ 2], mix[14]); + c = GMUL(i[ 3], mix[15]); + d = GMUL(i[ 4], mix[16]); + out[ 4] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[ 5], mix[ 1]); + b = GMUL(i[ 6], mix[ 2]); + c = GMUL(i[ 7], mix[ 3]); + d = GMUL(i[ 8], mix[ 4]); + out[ 5] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[ 5]); + b = GMUL(i[ 6], mix[ 6]); + c = GMUL(i[ 7], mix[ 7]); + d = GMUL(i[ 8], mix[ 8]); + out[ 6] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[ 9]); + b = GMUL(i[ 6], mix[10]); + c = GMUL(i[ 7], mix[11]); + d = GMUL(i[ 8], mix[12]); + out[ 7] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[13]); + b = GMUL(i[ 6], mix[14]); + c = GMUL(i[ 7], mix[15]); + d = GMUL(i[ 8], mix[16]); + out[ 8] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[ 9], mix[ 1]); + b = GMUL(i[10], mix[ 2]); + c = GMUL(i[11], mix[ 3]); + d = GMUL(i[12], mix[ 4]); + out[ 9] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[ 5]); + b = GMUL(i[10], mix[ 6]); + c = GMUL(i[11], mix[ 7]); + d = GMUL(i[12], mix[ 8]); + out[10] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[ 9]); + b = GMUL(i[10], mix[10]); + c = GMUL(i[11], mix[11]); + d = GMUL(i[12], mix[12]); + out[11] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[13]); + b = GMUL(i[10], mix[14]); + c = GMUL(i[11], mix[15]); + d = GMUL(i[12], mix[16]); + out[12] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[13], mix[ 1]); + b = GMUL(i[14], mix[ 2]); + c = GMUL(i[15], mix[ 3]); + d = GMUL(i[16], mix[ 4]); + out[13] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[ 5]); + b = GMUL(i[14], mix[ 6]); + c = GMUL(i[15], mix[ 7]); + d = GMUL(i[16], mix[ 8]); + out[14] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[ 9]); + b = GMUL(i[14], mix[10]); + c = GMUL(i[15], mix[11]); + d = GMUL(i[16], mix[12]); + out[15] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[13]); + b = GMUL(i[14], mix[14]); + c = GMUL(i[15], mix[15]); + d = GMUL(i[16], mix[16]); + out[16] = XOR(XOR(a, b), XOR(c, d)); + + return out; +end + +local keyRound = function(key, round) + local i = (round - 1) * 24; + local out = key; + + out[25 + i] = XOR(key[ 1 + i], XOR(SBOX[key[22 + i]], RCON[round])); + out[26 + i] = XOR(key[ 2 + i], SBOX[key[23 + i]]); + out[27 + i] = XOR(key[ 3 + i], SBOX[key[24 + i]]); + out[28 + i] = XOR(key[ 4 + i], SBOX[key[21 + i]]); + + out[29 + i] = XOR(out[25 + i], key[ 5 + i]); + out[30 + i] = XOR(out[26 + i], key[ 6 + i]); + out[31 + i] = XOR(out[27 + i], key[ 7 + i]); + out[32 + i] = XOR(out[28 + i], key[ 8 + i]); + + out[33 + i] = XOR(out[29 + i], key[ 9 + i]); + out[34 + i] = XOR(out[30 + i], key[10 + i]); + out[35 + i] = XOR(out[31 + i], key[11 + i]); + out[36 + i] = XOR(out[32 + i], key[12 + i]); + + out[37 + i] = XOR(out[33 + i], key[13 + i]); + out[38 + i] = XOR(out[34 + i], key[14 + i]); + out[39 + i] = XOR(out[35 + i], key[15 + i]); + out[40 + i] = XOR(out[36 + i], key[16 + i]); + + out[41 + i] = XOR(out[37 + i], key[17 + i]); + out[42 + i] = XOR(out[38 + i], key[18 + i]); + out[43 + i] = XOR(out[39 + i], key[19 + i]); + out[44 + i] = XOR(out[40 + i], key[20 + i]); + + out[45 + i] = XOR(out[41 + i], key[21 + i]); + out[46 + i] = XOR(out[42 + i], key[22 + i]); + out[47 + i] = XOR(out[43 + i], key[23 + i]); + out[48 + i] = XOR(out[44 + i], key[24 + i]); + + return out; +end + +local keyExpand = function(key) + local bytes = Array.copy(key); + + for i = 1, 8 do + keyRound(bytes, i); + end + + local keys = {}; + + keys[ 1] = Array.slice(bytes, 1, 16); + keys[ 2] = Array.slice(bytes, 17, 32); + keys[ 3] = Array.slice(bytes, 33, 48); + keys[ 4] = Array.slice(bytes, 49, 64); + keys[ 5] = Array.slice(bytes, 65, 80); + keys[ 6] = Array.slice(bytes, 81, 96); + keys[ 7] = Array.slice(bytes, 97, 112); + keys[ 8] = Array.slice(bytes, 113, 128); + keys[ 9] = Array.slice(bytes, 129, 144); + keys[10] = Array.slice(bytes, 145, 160); + keys[11] = Array.slice(bytes, 161, 176); + keys[12] = Array.slice(bytes, 177, 192); + keys[13] = Array.slice(bytes, 193, 208); + + return keys; + +end + +local addKey = Array.XOR; + + + +local AES = {}; + +AES.blockSize = 16; + +AES.encrypt = function(_key, block) + + local key = keyExpand(_key); + + --round 0 + block = addKey(block, key[1]); + + --round 1 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[2]); + + --round 2 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[3]); + + --round 3 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[4]); + + --round 4 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[5]); + + --round 5 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[6]); + + --round 6 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[7]); + + --round 7 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[8]); + + --round 8 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[9]); + + --round 9 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[10]); + + --round 10 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[11]); + + --round 11 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[12]); + + --round 12 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = addKey(block, key[13]); + + return block; + +end + +AES.decrypt = function(_key, block) + + local key = keyExpand(_key); + + --round 0 + block = addKey(block, key[13]); + + --round 1 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[12]); + block = mixCol(block, IMIXTABLE); + + --round 2 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[11]); + block = mixCol(block, IMIXTABLE); + + --round 3 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[10]); + block = mixCol(block, IMIXTABLE); + + --round 4 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[9]); + block = mixCol(block, IMIXTABLE); + + --round 5 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[8]); + block = mixCol(block, IMIXTABLE); + + --round 6 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[7]); + block = mixCol(block, IMIXTABLE); + + --round 7 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[6]); + block = mixCol(block, IMIXTABLE); + + --round 8 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[5]); + block = mixCol(block, IMIXTABLE); + + --round 9 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[4]); + block = mixCol(block, IMIXTABLE); + + --round 10 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[3]); + block = mixCol(block, IMIXTABLE); + + --round 11 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[2]); + block = mixCol(block, IMIXTABLE); + + --round 12 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[1]); + + return block; +end + +return AES; diff --git a/lockbox/cipher/aes256.lua b/lockbox/cipher/aes256.lua new file mode 100644 index 0000000..854bae9 --- /dev/null +++ b/lockbox/cipher/aes256.lua @@ -0,0 +1,498 @@ +local Array = require("lockbox.util.array"); +local Bit = require("lockbox.util.bit"); + +local XOR = Bit.bxor; + +local SBOX = { + [0] = 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, + 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, + 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, + 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, + 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, + 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, + 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, + 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, + 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, + 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, + 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, + 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, + 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, + 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, + 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16}; + +local ISBOX = { + [0] = 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, + 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, + 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, + 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, + 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, + 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, + 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, + 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, + 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, + 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, + 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, + 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, + 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, + 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, + 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D}; + +local ROW_SHIFT = { 1, 6, 11, 16, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, }; +local IROW_SHIFT = { 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3, 16, 13, 10, 7, 4, }; + +local ETABLE = { + [0] = 0x01, 0x03, 0x05, 0x0F, 0x11, 0x33, 0x55, 0xFF, 0x1A, 0x2E, 0x72, 0x96, 0xA1, 0xF8, 0x13, 0x35, + 0x5F, 0xE1, 0x38, 0x48, 0xD8, 0x73, 0x95, 0xA4, 0xF7, 0x02, 0x06, 0x0A, 0x1E, 0x22, 0x66, 0xAA, + 0xE5, 0x34, 0x5C, 0xE4, 0x37, 0x59, 0xEB, 0x26, 0x6A, 0xBE, 0xD9, 0x70, 0x90, 0xAB, 0xE6, 0x31, + 0x53, 0xF5, 0x04, 0x0C, 0x14, 0x3C, 0x44, 0xCC, 0x4F, 0xD1, 0x68, 0xB8, 0xD3, 0x6E, 0xB2, 0xCD, + 0x4C, 0xD4, 0x67, 0xA9, 0xE0, 0x3B, 0x4D, 0xD7, 0x62, 0xA6, 0xF1, 0x08, 0x18, 0x28, 0x78, 0x88, + 0x83, 0x9E, 0xB9, 0xD0, 0x6B, 0xBD, 0xDC, 0x7F, 0x81, 0x98, 0xB3, 0xCE, 0x49, 0xDB, 0x76, 0x9A, + 0xB5, 0xC4, 0x57, 0xF9, 0x10, 0x30, 0x50, 0xF0, 0x0B, 0x1D, 0x27, 0x69, 0xBB, 0xD6, 0x61, 0xA3, + 0xFE, 0x19, 0x2B, 0x7D, 0x87, 0x92, 0xAD, 0xEC, 0x2F, 0x71, 0x93, 0xAE, 0xE9, 0x20, 0x60, 0xA0, + 0xFB, 0x16, 0x3A, 0x4E, 0xD2, 0x6D, 0xB7, 0xC2, 0x5D, 0xE7, 0x32, 0x56, 0xFA, 0x15, 0x3F, 0x41, + 0xC3, 0x5E, 0xE2, 0x3D, 0x47, 0xC9, 0x40, 0xC0, 0x5B, 0xED, 0x2C, 0x74, 0x9C, 0xBF, 0xDA, 0x75, + 0x9F, 0xBA, 0xD5, 0x64, 0xAC, 0xEF, 0x2A, 0x7E, 0x82, 0x9D, 0xBC, 0xDF, 0x7A, 0x8E, 0x89, 0x80, + 0x9B, 0xB6, 0xC1, 0x58, 0xE8, 0x23, 0x65, 0xAF, 0xEA, 0x25, 0x6F, 0xB1, 0xC8, 0x43, 0xC5, 0x54, + 0xFC, 0x1F, 0x21, 0x63, 0xA5, 0xF4, 0x07, 0x09, 0x1B, 0x2D, 0x77, 0x99, 0xB0, 0xCB, 0x46, 0xCA, + 0x45, 0xCF, 0x4A, 0xDE, 0x79, 0x8B, 0x86, 0x91, 0xA8, 0xE3, 0x3E, 0x42, 0xC6, 0x51, 0xF3, 0x0E, + 0x12, 0x36, 0x5A, 0xEE, 0x29, 0x7B, 0x8D, 0x8C, 0x8F, 0x8A, 0x85, 0x94, 0xA7, 0xF2, 0x0D, 0x17, + 0x39, 0x4B, 0xDD, 0x7C, 0x84, 0x97, 0xA2, 0xFD, 0x1C, 0x24, 0x6C, 0xB4, 0xC7, 0x52, 0xF6, 0x01}; + +local LTABLE = { + [0] = 0x00, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1A, 0xC6, 0x4B, 0xC7, 0x1B, 0x68, 0x33, 0xEE, 0xDF, 0x03, + 0x64, 0x04, 0xE0, 0x0E, 0x34, 0x8D, 0x81, 0xEF, 0x4C, 0x71, 0x08, 0xC8, 0xF8, 0x69, 0x1C, 0xC1, + 0x7D, 0xC2, 0x1D, 0xB5, 0xF9, 0xB9, 0x27, 0x6A, 0x4D, 0xE4, 0xA6, 0x72, 0x9A, 0xC9, 0x09, 0x78, + 0x65, 0x2F, 0x8A, 0x05, 0x21, 0x0F, 0xE1, 0x24, 0x12, 0xF0, 0x82, 0x45, 0x35, 0x93, 0xDA, 0x8E, + 0x96, 0x8F, 0xDB, 0xBD, 0x36, 0xD0, 0xCE, 0x94, 0x13, 0x5C, 0xD2, 0xF1, 0x40, 0x46, 0x83, 0x38, + 0x66, 0xDD, 0xFD, 0x30, 0xBF, 0x06, 0x8B, 0x62, 0xB3, 0x25, 0xE2, 0x98, 0x22, 0x88, 0x91, 0x10, + 0x7E, 0x6E, 0x48, 0xC3, 0xA3, 0xB6, 0x1E, 0x42, 0x3A, 0x6B, 0x28, 0x54, 0xFA, 0x85, 0x3D, 0xBA, + 0x2B, 0x79, 0x0A, 0x15, 0x9B, 0x9F, 0x5E, 0xCA, 0x4E, 0xD4, 0xAC, 0xE5, 0xF3, 0x73, 0xA7, 0x57, + 0xAF, 0x58, 0xA8, 0x50, 0xF4, 0xEA, 0xD6, 0x74, 0x4F, 0xAE, 0xE9, 0xD5, 0xE7, 0xE6, 0xAD, 0xE8, + 0x2C, 0xD7, 0x75, 0x7A, 0xEB, 0x16, 0x0B, 0xF5, 0x59, 0xCB, 0x5F, 0xB0, 0x9C, 0xA9, 0x51, 0xA0, + 0x7F, 0x0C, 0xF6, 0x6F, 0x17, 0xC4, 0x49, 0xEC, 0xD8, 0x43, 0x1F, 0x2D, 0xA4, 0x76, 0x7B, 0xB7, + 0xCC, 0xBB, 0x3E, 0x5A, 0xFB, 0x60, 0xB1, 0x86, 0x3B, 0x52, 0xA1, 0x6C, 0xAA, 0x55, 0x29, 0x9D, + 0x97, 0xB2, 0x87, 0x90, 0x61, 0xBE, 0xDC, 0xFC, 0xBC, 0x95, 0xCF, 0xCD, 0x37, 0x3F, 0x5B, 0xD1, + 0x53, 0x39, 0x84, 0x3C, 0x41, 0xA2, 0x6D, 0x47, 0x14, 0x2A, 0x9E, 0x5D, 0x56, 0xF2, 0xD3, 0xAB, + 0x44, 0x11, 0x92, 0xD9, 0x23, 0x20, 0x2E, 0x89, 0xB4, 0x7C, 0xB8, 0x26, 0x77, 0x99, 0xE3, 0xA5, + 0x67, 0x4A, 0xED, 0xDE, 0xC5, 0x31, 0xFE, 0x18, 0x0D, 0x63, 0x8C, 0x80, 0xC0, 0xF7, 0x70, 0x07}; + +local MIXTABLE = { + 0x02, 0x03, 0x01, 0x01, + 0x01, 0x02, 0x03, 0x01, + 0x01, 0x01, 0x02, 0x03, + 0x03, 0x01, 0x01, 0x02}; + +local IMIXTABLE = { + 0x0E, 0x0B, 0x0D, 0x09, + 0x09, 0x0E, 0x0B, 0x0D, + 0x0D, 0x09, 0x0E, 0x0B, + 0x0B, 0x0D, 0x09, 0x0E}; + +local RCON = { +[0] = 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, +0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, +0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, +0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, +0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, +0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, +0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, +0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, +0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, +0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, +0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, +0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, +0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, +0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, +0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, +0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d}; + + +local GMUL = function(A, B) + if(A == 0x01) then return B; end + if(B == 0x01) then return A; end + if(A == 0x00) then return 0; end + if(B == 0x00) then return 0; end + + local LA = LTABLE[A]; + local LB = LTABLE[B]; + + local sum = LA + LB; + if (sum > 0xFF) then sum = sum - 0xFF; end + + return ETABLE[sum]; +end + +local byteSub = Array.substitute; + +local shiftRow = Array.permute; + +local mixCol = function(i, mix) + local out = {}; + + local a, b, c, d; + + a = GMUL(i[ 1], mix[ 1]); + b = GMUL(i[ 2], mix[ 2]); + c = GMUL(i[ 3], mix[ 3]); + d = GMUL(i[ 4], mix[ 4]); + out[ 1] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[ 5]); + b = GMUL(i[ 2], mix[ 6]); + c = GMUL(i[ 3], mix[ 7]); + d = GMUL(i[ 4], mix[ 8]); + out[ 2] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[ 9]); + b = GMUL(i[ 2], mix[10]); + c = GMUL(i[ 3], mix[11]); + d = GMUL(i[ 4], mix[12]); + out[ 3] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 1], mix[13]); + b = GMUL(i[ 2], mix[14]); + c = GMUL(i[ 3], mix[15]); + d = GMUL(i[ 4], mix[16]); + out[ 4] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[ 5], mix[ 1]); + b = GMUL(i[ 6], mix[ 2]); + c = GMUL(i[ 7], mix[ 3]); + d = GMUL(i[ 8], mix[ 4]); + out[ 5] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[ 5]); + b = GMUL(i[ 6], mix[ 6]); + c = GMUL(i[ 7], mix[ 7]); + d = GMUL(i[ 8], mix[ 8]); + out[ 6] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[ 9]); + b = GMUL(i[ 6], mix[10]); + c = GMUL(i[ 7], mix[11]); + d = GMUL(i[ 8], mix[12]); + out[ 7] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 5], mix[13]); + b = GMUL(i[ 6], mix[14]); + c = GMUL(i[ 7], mix[15]); + d = GMUL(i[ 8], mix[16]); + out[ 8] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[ 9], mix[ 1]); + b = GMUL(i[10], mix[ 2]); + c = GMUL(i[11], mix[ 3]); + d = GMUL(i[12], mix[ 4]); + out[ 9] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[ 5]); + b = GMUL(i[10], mix[ 6]); + c = GMUL(i[11], mix[ 7]); + d = GMUL(i[12], mix[ 8]); + out[10] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[ 9]); + b = GMUL(i[10], mix[10]); + c = GMUL(i[11], mix[11]); + d = GMUL(i[12], mix[12]); + out[11] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[ 9], mix[13]); + b = GMUL(i[10], mix[14]); + c = GMUL(i[11], mix[15]); + d = GMUL(i[12], mix[16]); + out[12] = XOR(XOR(a, b), XOR(c, d)); + + + a = GMUL(i[13], mix[ 1]); + b = GMUL(i[14], mix[ 2]); + c = GMUL(i[15], mix[ 3]); + d = GMUL(i[16], mix[ 4]); + out[13] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[ 5]); + b = GMUL(i[14], mix[ 6]); + c = GMUL(i[15], mix[ 7]); + d = GMUL(i[16], mix[ 8]); + out[14] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[ 9]); + b = GMUL(i[14], mix[10]); + c = GMUL(i[15], mix[11]); + d = GMUL(i[16], mix[12]); + out[15] = XOR(XOR(a, b), XOR(c, d)); + a = GMUL(i[13], mix[13]); + b = GMUL(i[14], mix[14]); + c = GMUL(i[15], mix[15]); + d = GMUL(i[16], mix[16]); + out[16] = XOR(XOR(a, b), XOR(c, d)); + + return out; +end + +local keyRound = function(key, round) + local i = (round - 1) * 32; + local out = key; + + out[33 + i] = XOR(key[ 1 + i], XOR(SBOX[key[30 + i]], RCON[round])); + out[34 + i] = XOR(key[ 2 + i], SBOX[key[31 + i]]); + out[35 + i] = XOR(key[ 3 + i], SBOX[key[32 + i]]); + out[36 + i] = XOR(key[ 4 + i], SBOX[key[29 + i]]); + + out[37 + i] = XOR(out[33 + i], key[ 5 + i]); + out[38 + i] = XOR(out[34 + i], key[ 6 + i]); + out[39 + i] = XOR(out[35 + i], key[ 7 + i]); + out[40 + i] = XOR(out[36 + i], key[ 8 + i]); + + out[41 + i] = XOR(out[37 + i], key[ 9 + i]); + out[42 + i] = XOR(out[38 + i], key[10 + i]); + out[43 + i] = XOR(out[39 + i], key[11 + i]); + out[44 + i] = XOR(out[40 + i], key[12 + i]); + + out[45 + i] = XOR(out[41 + i], key[13 + i]); + out[46 + i] = XOR(out[42 + i], key[14 + i]); + out[47 + i] = XOR(out[43 + i], key[15 + i]); + out[48 + i] = XOR(out[44 + i], key[16 + i]); + + + out[49 + i] = XOR(SBOX[out[45 + i]], key[17 + i]); + out[50 + i] = XOR(SBOX[out[46 + i]], key[18 + i]); + out[51 + i] = XOR(SBOX[out[47 + i]], key[19 + i]); + out[52 + i] = XOR(SBOX[out[48 + i]], key[20 + i]); + + out[53 + i] = XOR(out[49 + i], key[21 + i]); + out[54 + i] = XOR(out[50 + i], key[22 + i]); + out[55 + i] = XOR(out[51 + i], key[23 + i]); + out[56 + i] = XOR(out[52 + i], key[24 + i]); + + out[57 + i] = XOR(out[53 + i], key[25 + i]); + out[58 + i] = XOR(out[54 + i], key[26 + i]); + out[59 + i] = XOR(out[55 + i], key[27 + i]); + out[60 + i] = XOR(out[56 + i], key[28 + i]); + + out[61 + i] = XOR(out[57 + i], key[29 + i]); + out[62 + i] = XOR(out[58 + i], key[30 + i]); + out[63 + i] = XOR(out[59 + i], key[31 + i]); + out[64 + i] = XOR(out[60 + i], key[32 + i]); + + return out; +end + +local keyExpand = function(key) + local bytes = Array.copy(key); + + for i = 1, 7 do + keyRound(bytes, i); + end + + local keys = {}; + + keys[ 1] = Array.slice(bytes, 1, 16); + keys[ 2] = Array.slice(bytes, 17, 32); + keys[ 3] = Array.slice(bytes, 33, 48); + keys[ 4] = Array.slice(bytes, 49, 64); + keys[ 5] = Array.slice(bytes, 65, 80); + keys[ 6] = Array.slice(bytes, 81, 96); + keys[ 7] = Array.slice(bytes, 97, 112); + keys[ 8] = Array.slice(bytes, 113, 128); + keys[ 9] = Array.slice(bytes, 129, 144); + keys[10] = Array.slice(bytes, 145, 160); + keys[11] = Array.slice(bytes, 161, 176); + keys[12] = Array.slice(bytes, 177, 192); + keys[13] = Array.slice(bytes, 193, 208); + keys[14] = Array.slice(bytes, 209, 224); + keys[15] = Array.slice(bytes, 225, 240); + + return keys; + +end + +local addKey = Array.XOR; + + + +local AES = {}; + +AES.blockSize = 16; + +AES.encrypt = function(_key, block) + + local key = keyExpand(_key); + + --round 0 + block = addKey(block, key[1]); + + --round 1 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[2]); + + --round 2 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[3]); + + --round 3 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[4]); + + --round 4 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[5]); + + --round 5 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[6]); + + --round 6 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[7]); + + --round 7 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[8]); + + --round 8 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[9]); + + --round 9 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[10]); + + --round 10 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[11]); + + --round 11 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[12]); + + --round 12 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[13]); + + --round 13 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = mixCol(block, MIXTABLE); + block = addKey(block, key[14]); + + --round 14 + block = byteSub(block, SBOX); + block = shiftRow(block, ROW_SHIFT); + block = addKey(block, key[15]); + + return block; + +end + +AES.decrypt = function(_key, block) + + local key = keyExpand(_key); + + --round 0 + block = addKey(block, key[15]); + + --round 1 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[14]); + block = mixCol(block, IMIXTABLE); + + --round 2 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[13]); + block = mixCol(block, IMIXTABLE); + + --round 3 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[12]); + block = mixCol(block, IMIXTABLE); + + --round 4 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[11]); + block = mixCol(block, IMIXTABLE); + + --round 5 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[10]); + block = mixCol(block, IMIXTABLE); + + --round 6 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[9]); + block = mixCol(block, IMIXTABLE); + + --round 7 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[8]); + block = mixCol(block, IMIXTABLE); + + --round 8 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[7]); + block = mixCol(block, IMIXTABLE); + + --round 9 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[6]); + block = mixCol(block, IMIXTABLE); + + --round 10 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[5]); + block = mixCol(block, IMIXTABLE); + + --round 11 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[4]); + block = mixCol(block, IMIXTABLE); + + --round 12 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[3]); + block = mixCol(block, IMIXTABLE); + + --round 13 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[2]); + block = mixCol(block, IMIXTABLE); + + --round 14 + block = shiftRow(block, IROW_SHIFT); + block = byteSub(block, ISBOX); + block = addKey(block, key[1]); + + return block; +end + +return AES; diff --git a/lockbox/cipher/mode/cbc.lua b/lockbox/cipher/mode/cbc.lua new file mode 100644 index 0000000..a02ff2e --- /dev/null +++ b/lockbox/cipher/mode/cbc.lua @@ -0,0 +1,164 @@ +local Array = require("lockbox.util.array"); +local Stream = require("lockbox.util.stream"); +local Queue = require("lockbox.util.queue"); + +local CBC = {}; + +CBC.Cipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + local iv; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + iv = nil; + return public; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + if(iv == nil) then + iv = block; + else + local out = Array.XOR(iv, block); + out = blockCipher.encrypt(key, out); + Array.writeToQueue(outputQueue, out); + iv = out; + end + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + return public; + +end + + +CBC.Decipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + local iv; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + iv = nil; + return public; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + if(iv == nil) then + iv = block; + else + local out = block; + out = blockCipher.decrypt(key, out); + out = Array.XOR(iv, out); + Array.writeToQueue(outputQueue, out); + iv = block; + end + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + return public; + +end + +return CBC; + diff --git a/lockbox/cipher/mode/cfb.lua b/lockbox/cipher/mode/cfb.lua new file mode 100644 index 0000000..c736d52 --- /dev/null +++ b/lockbox/cipher/mode/cfb.lua @@ -0,0 +1,163 @@ +local Array = require("lockbox.util.array"); +local Stream = require("lockbox.util.stream"); +local Queue = require("lockbox.util.queue"); + +local CFB = {}; + +CFB.Cipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + local iv; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + iv = nil; + return public; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + if(iv == nil) then + iv = block; + else + local out = iv; + out = blockCipher.encrypt(key, out); + out = Array.XOR(out, block); + Array.writeToQueue(outputQueue, out); + iv = out; + end + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + return public; + +end + +CFB.Decipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + local iv; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + iv = nil; + return public; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + if(iv == nil) then + iv = block; + else + local out = iv; + out = blockCipher.encrypt(key, out); + out = Array.XOR(out, block); + Array.writeToQueue(outputQueue, out); + iv = block; + end + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + return public; + +end + +return CFB; diff --git a/lockbox/cipher/mode/ctr.lua b/lockbox/cipher/mode/ctr.lua new file mode 100644 index 0000000..beb8ef0 --- /dev/null +++ b/lockbox/cipher/mode/ctr.lua @@ -0,0 +1,248 @@ +local Array = require("lockbox.util.array"); +local Stream = require("lockbox.util.stream"); +local Queue = require("lockbox.util.queue"); + +local Bit = require("lockbox.util.bit"); + +local AND = Bit.band; + +local CTR = {}; + +CTR.Cipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + local iv; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + iv = nil; + return public; + end + + local updateIV = function() + iv[16] = iv[16] + 1; + if iv[16] <= 0xFF then return; end + iv[16] = AND(iv[16], 0xFF); + + iv[15] = iv[15] + 1; + if iv[15] <= 0xFF then return; end + iv[15] = AND(iv[15], 0xFF); + + iv[14] = iv[14] + 1; + if iv[14] <= 0xFF then return; end + iv[14] = AND(iv[14], 0xFF); + + iv[13] = iv[13] + 1; + if iv[13] <= 0xFF then return; end + iv[13] = AND(iv[13], 0xFF); + + iv[12] = iv[12] + 1; + if iv[12] <= 0xFF then return; end + iv[12] = AND(iv[12], 0xFF); + + iv[11] = iv[11] + 1; + if iv[11] <= 0xFF then return; end + iv[11] = AND(iv[11], 0xFF); + + iv[10] = iv[10] + 1; + if iv[10] <= 0xFF then return; end + iv[10] = AND(iv[10], 0xFF); + + iv[9] = iv[9] + 1; + if iv[9] <= 0xFF then return; end + iv[9] = AND(iv[9], 0xFF); + + return; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + if(iv == nil) then + iv = block; + else + local out = iv; + out = blockCipher.encrypt(key, out); + + out = Array.XOR(out, block); + Array.writeToQueue(outputQueue, out); + updateIV(); + end + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + return public; + +end + + +CTR.Decipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + local iv; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + iv = nil; + return public; + end + + local updateIV = function() + iv[16] = iv[16] + 1; + if iv[16] <= 0xFF then return; end + iv[16] = AND(iv[16], 0xFF); + + iv[15] = iv[15] + 1; + if iv[15] <= 0xFF then return; end + iv[15] = AND(iv[15], 0xFF); + + iv[14] = iv[14] + 1; + if iv[14] <= 0xFF then return; end + iv[14] = AND(iv[14], 0xFF); + + iv[13] = iv[13] + 1; + if iv[13] <= 0xFF then return; end + iv[13] = AND(iv[13], 0xFF); + + iv[12] = iv[12] + 1; + if iv[12] <= 0xFF then return; end + iv[12] = AND(iv[12], 0xFF); + + iv[11] = iv[11] + 1; + if iv[11] <= 0xFF then return; end + iv[11] = AND(iv[11], 0xFF); + + iv[10] = iv[10] + 1; + if iv[10] <= 0xFF then return; end + iv[10] = AND(iv[10], 0xFF); + + iv[9] = iv[9] + 1; + if iv[9] <= 0xFF then return; end + iv[9] = AND(iv[9], 0xFF); + + return; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + if(iv == nil) then + iv = block; + else + local out = iv; + out = blockCipher.encrypt(key, out); + + out = Array.XOR(out, block); + Array.writeToQueue(outputQueue, out); + updateIV(); + end + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + return public; + +end + + + + +return CTR; + diff --git a/lockbox/cipher/mode/ofb.lua b/lockbox/cipher/mode/ofb.lua new file mode 100644 index 0000000..a824846 --- /dev/null +++ b/lockbox/cipher/mode/ofb.lua @@ -0,0 +1,164 @@ +local Array = require("lockbox.util.array"); +local Stream = require("lockbox.util.stream"); +local Queue = require("lockbox.util.queue"); + +local OFB = {}; + +OFB.Cipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + local iv; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + iv = nil; + return public; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + if(iv == nil) then + iv = block; + else + local out = iv; + out = blockCipher.encrypt(key, out); + iv = out; + out = Array.XOR(out, block); + Array.writeToQueue(outputQueue, out); + end + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + return public; + +end + +OFB.Decipher = function() + + local public = {}; + + local key; + local blockCipher; + local padding; + local inputQueue; + local outputQueue; + local iv; + + public.setKey = function(keyBytes) + key = keyBytes; + return public; + end + + public.setBlockCipher = function(cipher) + blockCipher = cipher; + return public; + end + + public.setPadding = function(paddingMode) + padding = paddingMode; + return public; + end + + public.init = function() + inputQueue = Queue(); + outputQueue = Queue(); + iv = nil; + return public; + end + + public.update = function(messageStream) + local byte = messageStream(); + while (byte ~= nil) do + inputQueue.push(byte); + if(inputQueue.size() >= blockCipher.blockSize) then + local block = Array.readFromQueue(inputQueue, blockCipher.blockSize); + + if(iv == nil) then + iv = block; + else + local out = iv; + out = blockCipher.encrypt(key, out); + iv = out; + out = Array.XOR(out, block); + Array.writeToQueue(outputQueue, out); + end + end + byte = messageStream(); + end + return public; + end + + public.finish = function() + local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead()); + public.update(paddingStream); + + return public; + end + + public.getOutputQueue = function() + return outputQueue; + end + + public.asHex = function() + return Stream.toHex(outputQueue.pop); + end + + public.asBytes = function() + return Stream.toArray(outputQueue.pop); + end + + return public; + +end + + +return OFB; diff --git a/lockbox/digest/sha1.lua b/lockbox/digest/sha1.lua new file mode 100644 index 0000000..fc38866 --- /dev/null +++ b/lockbox/digest/sha1.lua @@ -0,0 +1,173 @@ +require("lockbox").insecure(); + +local Bit = require("lockbox.util.bit"); +local String = require("string"); +local Math = require("math"); +local Queue = require("lockbox.util.queue"); + +local AND = Bit.band; +local OR = Bit.bor; +local XOR = Bit.bxor; +local LROT = Bit.lrotate; +local LSHIFT = Bit.lshift; +local RSHIFT = Bit.rshift; + +--SHA1 is big-endian +local bytes2word = function(b0, b1, b2, b3) + local i = b0; i = LSHIFT(i, 8); + i = OR(i, b1); i = LSHIFT(i, 8); + i = OR(i, b2); i = LSHIFT(i, 8); + i = OR(i, b3); + return i; +end + +local word2bytes = function(word) + local b0, b1, b2, b3; + b3 = AND(word, 0xFF); word = RSHIFT(word, 8); + b2 = AND(word, 0xFF); word = RSHIFT(word, 8); + b1 = AND(word, 0xFF); word = RSHIFT(word, 8); + b0 = AND(word, 0xFF); + return b0, b1, b2, b3; +end + +local dword2bytes = function(i) + local b4, b5, b6, b7 = word2bytes(i); + local b0, b1, b2, b3 = word2bytes(Math.floor(i / 0x100000000)); + return b0, b1, b2, b3, b4, b5, b6, b7; +end + +local F = function(x, y, z) return XOR(z, AND(x, XOR(y, z))); end +local G = function(x, y, z) return XOR(x, XOR(y, z)); end +local H = function(x, y, z) return OR(AND(x, OR(y, z)), AND(y, z)); end +local I = function(x, y, z) return XOR(x, XOR(y, z)); end + +local SHA1 = function() + + local queue = Queue(); + + local h0 = 0x67452301; + local h1 = 0xEFCDAB89; + local h2 = 0x98BADCFE; + local h3 = 0x10325476; + local h4 = 0xC3D2E1F0; + + local public = {}; + + local processBlock = function() + local a = h0; + local b = h1; + local c = h2; + local d = h3; + local e = h4; + local temp; + local k; + + local w = {}; + for i = 0, 15 do + w[i] = bytes2word(queue.pop(), queue.pop(), queue.pop(), queue.pop()); + end + + for i = 16, 79 do + w[i] = LROT((XOR(XOR(w[i - 3], w[i - 8]), XOR(w[i - 14], w[i - 16]))), 1); + end + + for i = 0, 79 do + if (i <= 19) then + temp = F(b, c, d); + k = 0x5A827999; + elseif (i <= 39) then + temp = G(b, c, d); + k = 0x6ED9EBA1; + elseif (i <= 59) then + temp = H(b, c, d); + k = 0x8F1BBCDC; + else + temp = I(b, c, d); + k = 0xCA62C1D6; + end + temp = LROT(a, 5) + temp + e + k + w[i]; + e = d; + d = c; + c = LROT(b, 30); + b = a; + a = temp; + end + + h0 = AND(h0 + a, 0xFFFFFFFF); + h1 = AND(h1 + b, 0xFFFFFFFF); + h2 = AND(h2 + c, 0xFFFFFFFF); + h3 = AND(h3 + d, 0xFFFFFFFF); + h4 = AND(h4 + e, 0xFFFFFFFF); + end + + public.init = function() + queue.reset(); + h0 = 0x67452301; + h1 = 0xEFCDAB89; + h2 = 0x98BADCFE; + h3 = 0x10325476; + h4 = 0xC3D2E1F0; + return public; + end + + + public.update = function(bytes) + for b in bytes do + queue.push(b); + if queue.size() >= 64 then processBlock(); end + end + + return public; + end + + public.finish = function() + local bits = queue.getHead() * 8; + + queue.push(0x80); + while ((queue.size() + 7) % 64) < 63 do + queue.push(0x00); + end + + local b0, b1, b2, b3, b4, b5, b6, b7 = dword2bytes(bits); + + queue.push(b0); + queue.push(b1); + queue.push(b2); + queue.push(b3); + queue.push(b4); + queue.push(b5); + queue.push(b6); + queue.push(b7); + + while queue.size() > 0 do + processBlock(); + end + + return public; + end + + public.asBytes = function() + local b0, b1, b2, b3 = word2bytes(h0); + local b4, b5, b6, b7 = word2bytes(h1); + local b8, b9, b10, b11 = word2bytes(h2); + local b12, b13, b14, b15 = word2bytes(h3); + local b16, b17, b18, b19 = word2bytes(h4); + + return {b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19}; + end + + public.asHex = function() + local b0, b1, b2, b3 = word2bytes(h0); + local b4, b5, b6, b7 = word2bytes(h1); + local b8, b9, b10, b11 = word2bytes(h2); + local b12, b13, b14, b15 = word2bytes(h3); + local b16, b17, b18, b19 = word2bytes(h4); + + return String.format("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19); + end + + return public; +end + +return SHA1; diff --git a/lockbox/digest/sha2_224.lua b/lockbox/digest/sha2_224.lua new file mode 100644 index 0000000..3bb536a --- /dev/null +++ b/lockbox/digest/sha2_224.lua @@ -0,0 +1,200 @@ +local Bit = require("lockbox.util.bit"); +local String = require("string"); +local Math = require("math"); +local Queue = require("lockbox.util.queue"); + +local CONSTANTS = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 }; + +local fmt = "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" .. + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" + +local AND = Bit.band; +local OR = Bit.bor; +local NOT = Bit.bnot; +local XOR = Bit.bxor; +local RROT = Bit.rrotate; +local LSHIFT = Bit.lshift; +local RSHIFT = Bit.rshift; + +--SHA2 is big-endian +local bytes2word = function(b0, b1, b2, b3) + local i = b0; i = LSHIFT(i, 8); + i = OR(i, b1); i = LSHIFT(i, 8); + i = OR(i, b2); i = LSHIFT(i, 8); + i = OR(i, b3); + return i; +end + +local word2bytes = function(word) + local b0, b1, b2, b3; + b3 = AND(word, 0xFF); word = RSHIFT(word, 8); + b2 = AND(word, 0xFF); word = RSHIFT(word, 8); + b1 = AND(word, 0xFF); word = RSHIFT(word, 8); + b0 = AND(word, 0xFF); + return b0, b1, b2, b3; +end + +local dword2bytes = function(i) + local b4, b5, b6, b7 = word2bytes(i); + local b0, b1, b2, b3 = word2bytes(Math.floor(i / 0x100000000)); + return b0, b1, b2, b3, b4, b5, b6, b7; +end + + + + +local SHA2_224 = function() + + local queue = Queue(); + + local h0 = 0xc1059ed8; + local h1 = 0x367cd507; + local h2 = 0x3070dd17; + local h3 = 0xf70e5939; + local h4 = 0xffc00b31; + local h5 = 0x68581511; + local h6 = 0x64f98fa7; + local h7 = 0xbefa4fa4; + + local public = {}; + + local processBlock = function() + local a = h0; + local b = h1; + local c = h2; + local d = h3; + local e = h4; + local f = h5; + local g = h6; + local h = h7; + + local w = {}; + + for i = 0, 15 do + w[i] = bytes2word(queue.pop(), queue.pop(), queue.pop(), queue.pop()); + end + + for i = 16, 63 do + local s0 = XOR(RROT(w[i - 15], 7), XOR(RROT(w[i - 15], 18), RSHIFT(w[i - 15], 3))); + local s1 = XOR(RROT(w[i - 2], 17), XOR(RROT(w[i - 2], 19), RSHIFT(w[i - 2], 10))); + w[i] = AND(w[i - 16] + s0 + w[i - 7] + s1, 0xFFFFFFFF); + end + + for i = 0, 63 do + local s1 = XOR(RROT(e, 6), XOR(RROT(e, 11), RROT(e, 25))); + local ch = XOR(AND(e, f), AND(NOT(e), g)); + local temp1 = h + s1 + ch + CONSTANTS[i + 1] + w[i]; + local s0 = XOR(RROT(a, 2), XOR(RROT(a, 13), RROT(a, 22))); + local maj = XOR(AND(a, b), XOR(AND(a, c), AND(b, c))); + local temp2 = s0 + maj; + + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + end + + h0 = AND(h0 + a, 0xFFFFFFFF); + h1 = AND(h1 + b, 0xFFFFFFFF); + h2 = AND(h2 + c, 0xFFFFFFFF); + h3 = AND(h3 + d, 0xFFFFFFFF); + h4 = AND(h4 + e, 0xFFFFFFFF); + h5 = AND(h5 + f, 0xFFFFFFFF); + h6 = AND(h6 + g, 0xFFFFFFFF); + h7 = AND(h7 + h, 0xFFFFFFFF); + end + + public.init = function() + queue.reset(); + + h0 = 0xc1059ed8; + h1 = 0x367cd507; + h2 = 0x3070dd17; + h3 = 0xf70e5939; + h4 = 0xffc00b31; + h5 = 0x68581511; + h6 = 0x64f98fa7; + h7 = 0xbefa4fa4; + + return public; + end + + public.update = function(bytes) + for b in bytes do + queue.push(b); + if queue.size() >= 64 then processBlock(); end + end + + return public; + end + + public.finish = function() + local bits = queue.getHead() * 8; + + queue.push(0x80); + while ((queue.size() + 7) % 64) < 63 do + queue.push(0x00); + end + + local b0, b1, b2, b3, b4, b5, b6, b7 = dword2bytes(bits); + + queue.push(b0); + queue.push(b1); + queue.push(b2); + queue.push(b3); + queue.push(b4); + queue.push(b5); + queue.push(b6); + queue.push(b7); + + while queue.size() > 0 do + processBlock(); + end + + return public; + end + + public.asBytes = function() + local b0, b1, b2, b3 = word2bytes(h0); + local b4, b5, b6, b7 = word2bytes(h1); + local b8, b9, b10, b11 = word2bytes(h2); + local b12, b13, b14, b15 = word2bytes(h3); + local b16, b17, b18, b19 = word2bytes(h4); + local b20, b21, b22, b23 = word2bytes(h5); + local b24, b25, b26, b27 = word2bytes(h6); + + return { b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 + , b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27}; + end + + public.asHex = function() + local b0, b1, b2, b3 = word2bytes(h0); + local b4, b5, b6, b7 = word2bytes(h1); + local b8, b9, b10, b11 = word2bytes(h2); + local b12, b13, b14, b15 = word2bytes(h3); + local b16, b17, b18, b19 = word2bytes(h4); + local b20, b21, b22, b23 = word2bytes(h5); + local b24, b25, b26, b27 = word2bytes(h6); + + return String.format(fmt, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 + , b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27); + end + + return public; + +end + +return SHA2_224; + diff --git a/lockbox/digest/sha2_256.lua b/lockbox/digest/sha2_256.lua new file mode 100644 index 0000000..1aafa5a --- /dev/null +++ b/lockbox/digest/sha2_256.lua @@ -0,0 +1,203 @@ +local Bit = require("lockbox.util.bit"); +local String = require("string"); +local Math = require("math"); +local Queue = require("lockbox.util.queue"); + +local CONSTANTS = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 }; + +local fmt = "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" .. + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" + +local AND = Bit.band; +local OR = Bit.bor; +local NOT = Bit.bnot; +local XOR = Bit.bxor; +local RROT = Bit.rrotate; +local LSHIFT = Bit.lshift; +local RSHIFT = Bit.rshift; + +--SHA2 is big-endian +local bytes2word = function(b0, b1, b2, b3) + local i = b0; i = LSHIFT(i, 8); + i = OR(i, b1); i = LSHIFT(i, 8); + i = OR(i, b2); i = LSHIFT(i, 8); + i = OR(i, b3); + return i; +end + +local word2bytes = function(word) + local b0, b1, b2, b3; + b3 = AND(word, 0xFF); word = RSHIFT(word, 8); + b2 = AND(word, 0xFF); word = RSHIFT(word, 8); + b1 = AND(word, 0xFF); word = RSHIFT(word, 8); + b0 = AND(word, 0xFF); + return b0, b1, b2, b3; +end + +local dword2bytes = function(i) + local b4, b5, b6, b7 = word2bytes(i); + local b0, b1, b2, b3 = word2bytes(Math.floor(i / 0x100000000)); + return b0, b1, b2, b3, b4, b5, b6, b7; +end + + + + +local SHA2_256 = function() + + local queue = Queue(); + + local h0 = 0x6a09e667; + local h1 = 0xbb67ae85; + local h2 = 0x3c6ef372; + local h3 = 0xa54ff53a; + local h4 = 0x510e527f; + local h5 = 0x9b05688c; + local h6 = 0x1f83d9ab; + local h7 = 0x5be0cd19; + + local public = {}; + + local processBlock = function() + local a = h0; + local b = h1; + local c = h2; + local d = h3; + local e = h4; + local f = h5; + local g = h6; + local h = h7; + + local w = {}; + + for i = 0, 15 do + w[i] = bytes2word(queue.pop(), queue.pop(), queue.pop(), queue.pop()); + end + + for i = 16, 63 do + local s0 = XOR(RROT(w[i - 15], 7), XOR(RROT(w[i - 15], 18), RSHIFT(w[i - 15], 3))); + local s1 = XOR(RROT(w[i - 2], 17), XOR(RROT(w[i - 2], 19), RSHIFT(w[i - 2], 10))); + w[i] = AND(w[i - 16] + s0 + w[i - 7] + s1, 0xFFFFFFFF); + end + + for i = 0, 63 do + local s1 = XOR(RROT(e, 6), XOR(RROT(e, 11), RROT(e, 25))); + local ch = XOR(AND(e, f), AND(NOT(e), g)); + local temp1 = h + s1 + ch + CONSTANTS[i + 1] + w[i]; + local s0 = XOR(RROT(a, 2), XOR(RROT(a, 13), RROT(a, 22))); + local maj = XOR(AND(a, b), XOR(AND(a, c), AND(b, c))); + local temp2 = s0 + maj; + + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + end + + h0 = AND(h0 + a, 0xFFFFFFFF); + h1 = AND(h1 + b, 0xFFFFFFFF); + h2 = AND(h2 + c, 0xFFFFFFFF); + h3 = AND(h3 + d, 0xFFFFFFFF); + h4 = AND(h4 + e, 0xFFFFFFFF); + h5 = AND(h5 + f, 0xFFFFFFFF); + h6 = AND(h6 + g, 0xFFFFFFFF); + h7 = AND(h7 + h, 0xFFFFFFFF); + end + + public.init = function() + queue.reset(); + + h0 = 0x6a09e667; + h1 = 0xbb67ae85; + h2 = 0x3c6ef372; + h3 = 0xa54ff53a; + h4 = 0x510e527f; + h5 = 0x9b05688c; + h6 = 0x1f83d9ab; + h7 = 0x5be0cd19; + + return public; + end + + public.update = function(bytes) + for b in bytes do + queue.push(b); + if queue.size() >= 64 then processBlock(); end + end + + return public; + end + + public.finish = function() + local bits = queue.getHead() * 8; + + queue.push(0x80); + while ((queue.size() + 7) % 64) < 63 do + queue.push(0x00); + end + + local b0, b1, b2, b3, b4, b5, b6, b7 = dword2bytes(bits); + + queue.push(b0); + queue.push(b1); + queue.push(b2); + queue.push(b3); + queue.push(b4); + queue.push(b5); + queue.push(b6); + queue.push(b7); + + while queue.size() > 0 do + processBlock(); + end + + return public; + end + + public.asBytes = function() + local b0, b1, b2, b3 = word2bytes(h0); + local b4, b5, b6, b7 = word2bytes(h1); + local b8, b9, b10, b11 = word2bytes(h2); + local b12, b13, b14, b15 = word2bytes(h3); + local b16, b17, b18, b19 = word2bytes(h4); + local b20, b21, b22, b23 = word2bytes(h5); + local b24, b25, b26, b27 = word2bytes(h6); + local b28, b29, b30, b31 = word2bytes(h7); + + + return { b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 + , b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31}; + end + + public.asHex = function() + local b0, b1, b2, b3 = word2bytes(h0); + local b4, b5, b6, b7 = word2bytes(h1); + local b8, b9, b10, b11 = word2bytes(h2); + local b12, b13, b14, b15 = word2bytes(h3); + local b16, b17, b18, b19 = word2bytes(h4); + local b20, b21, b22, b23 = word2bytes(h5); + local b24, b25, b26, b27 = word2bytes(h6); + local b28, b29, b30, b31 = word2bytes(h7); + + return String.format(fmt, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 + , b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31); + end + + return public; + +end + +return SHA2_256; + diff --git a/lockbox/init.lua b/lockbox/init.lua new file mode 100644 index 0000000..0031a50 --- /dev/null +++ b/lockbox/init.lua @@ -0,0 +1,22 @@ +local Lockbox = {}; + +--[[ +package.path = "./?.lua;" + .. "./cipher/?.lua;" + .. "./digest/?.lua;" + .. "./kdf/?.lua;" + .. "./mac/?.lua;" + .. "./padding/?.lua;" + .. "./test/?.lua;" + .. "./util/?.lua;" + .. package.path; +--]] +Lockbox.ALLOW_INSECURE = true; + +Lockbox.insecure = function() + assert(Lockbox.ALLOW_INSECURE, + "This module is insecure! It should not be used in production." .. + "If you really want to use it, set Lockbox.ALLOW_INSECURE to true before importing it"); +end + +return Lockbox; diff --git a/lockbox/kdf/pbkdf2.lua b/lockbox/kdf/pbkdf2.lua new file mode 100644 index 0000000..a05e42a --- /dev/null +++ b/lockbox/kdf/pbkdf2.lua @@ -0,0 +1,114 @@ +local Bit = require("lockbox.util.bit"); +local Array = require("lockbox.util.array"); +local Stream = require("lockbox.util.stream"); +local Math = require("math"); + +local AND = Bit.band; +local RSHIFT = Bit.rshift; + +local word2bytes = function(word) + local b0, b1, b2, b3; + b3 = AND(word, 0xFF); word = RSHIFT(word, 8); + b2 = AND(word, 0xFF); word = RSHIFT(word, 8); + b1 = AND(word, 0xFF); word = RSHIFT(word, 8); + b0 = AND(word, 0xFF); + return b0, b1, b2, b3; +end + +local PBKDF2 = function() + + local public = {}; + + local blockLen = 16; + local dKeyLen = 256; + local iterations = 4096; + + local salt; + local password; + + + local PRF; + + local dKey; + + + public.setBlockLen = function(len) + blockLen = len; + return public; + end + + public.setDKeyLen = function(len) + dKeyLen = len + return public; + end + + public.setIterations = function(iter) + iterations = iter; + return public; + end + + public.setSalt = function(saltBytes) + salt = saltBytes; + return public; + end + + public.setPassword = function(passwordBytes) + password = passwordBytes; + return public; + end + + public.setPRF = function(prf) + PRF = prf; + return public; + end + + local buildBlock = function(i) + local b0, b1, b2, b3 = word2bytes(i); + local ii = {b0, b1, b2, b3}; + local s = Array.concat(salt, ii); + + local out = {}; + + PRF.setKey(password); + for c = 1, iterations do + PRF.init() + .update(Stream.fromArray(s)); + + s = PRF.finish().asBytes(); + if(c > 1) then + out = Array.XOR(out, s); + else + out = s; + end + end + + return out; + end + + public.finish = function() + local blocks = Math.ceil(dKeyLen / blockLen); + + dKey = {}; + + for b = 1, blocks do + local block = buildBlock(b); + dKey = Array.concat(dKey, block); + end + + if(Array.size(dKey) > dKeyLen) then dKey = Array.truncate(dKey, dKeyLen); end + + return public; + end + + public.asBytes = function() + return dKey; + end + + public.asHex = function() + return Array.toHex(dKey); + end + + return public; +end + +return PBKDF2; diff --git a/lockbox/mac/hmac.lua b/lockbox/mac/hmac.lua new file mode 100644 index 0000000..a10b84c --- /dev/null +++ b/lockbox/mac/hmac.lua @@ -0,0 +1,85 @@ +local Bit = require("lockbox.util.bit"); +local Stream = require("lockbox.util.stream"); +local Array = require("lockbox.util.array"); + +local XOR = Bit.bxor; + +local HMAC = function() + + local public = {}; + local blockSize = 64; + local Digest = nil; + local outerPadding = {}; + local innerPadding = {} + local digest; + + public.setBlockSize = function(bytes) + blockSize = bytes; + return public; + end + + public.setDigest = function(digestModule) + Digest = digestModule; + digest = Digest(); + return public; + end + + public.setKey = function(key) + local keyStream; + + if(Array.size(key) > blockSize) then + keyStream = Stream.fromArray(Digest() + .update(Stream.fromArray(key)) + .finish() + .asBytes()); + else + keyStream = Stream.fromArray(key); + end + + outerPadding = {}; + innerPadding = {}; + + for i = 1, blockSize do + local byte = keyStream(); + if byte == nil then byte = 0x00; end + outerPadding[i] = XOR(0x5C, byte); + innerPadding[i] = XOR(0x36, byte); + end + + return public; + end + + public.init = function() + digest.init() + .update(Stream.fromArray(innerPadding)); + return public; + end + + public.update = function(messageStream) + digest.update(messageStream); + return public; + end + + public.finish = function() + local inner = digest.finish().asBytes(); + digest.init() + .update(Stream.fromArray(outerPadding)) + .update(Stream.fromArray(inner)) + .finish(); + + return public; + end + + public.asBytes = function() + return digest.asBytes(); + end + + public.asHex = function() + return digest.asHex(); + end + + return public; + +end + +return HMAC; diff --git a/lockbox/padding/ansix923.lua b/lockbox/padding/ansix923.lua new file mode 100644 index 0000000..83702c6 --- /dev/null +++ b/lockbox/padding/ansix923.lua @@ -0,0 +1,22 @@ +local ANSIX923Padding = function(blockSize, byteCount) + + local paddingCount = blockSize - (byteCount % blockSize); + local bytesLeft = paddingCount; + + local stream = function() + if bytesLeft > 1 then + bytesLeft = bytesLeft - 1; + return 0x00; + elseif bytesLeft > 0 then + bytesLeft = bytesLeft - 1; + return paddingCount; + else + return nil; + end + end + + return stream; + +end + +return ANSIX923Padding; diff --git a/lockbox/padding/isoiec7816.lua b/lockbox/padding/isoiec7816.lua new file mode 100644 index 0000000..3dc255d --- /dev/null +++ b/lockbox/padding/isoiec7816.lua @@ -0,0 +1,22 @@ +local ISOIEC7816Padding = function(blockSize, byteCount) + + local paddingCount = blockSize - (byteCount % blockSize); + local bytesLeft = paddingCount; + + local stream = function() + if bytesLeft == paddingCount then + bytesLeft = bytesLeft - 1; + return 0x80; + elseif bytesLeft > 0 then + bytesLeft = bytesLeft - 1; + return 0x00; + else + return nil; + end + end + + return stream; + +end + +return ISOIEC7816Padding; diff --git a/lockbox/padding/pkcs7.lua b/lockbox/padding/pkcs7.lua new file mode 100644 index 0000000..3b635ab --- /dev/null +++ b/lockbox/padding/pkcs7.lua @@ -0,0 +1,18 @@ +local PKCS7Padding = function(blockSize, byteCount) + + local paddingCount = blockSize - ((byteCount -1) % blockSize) + 1; + local bytesLeft = paddingCount; + + local stream = function() + if bytesLeft > 0 then + bytesLeft = bytesLeft - 1; + return paddingCount; + else + return nil; + end + end + + return stream; +end + +return PKCS7Padding; diff --git a/lockbox/padding/zero.lua b/lockbox/padding/zero.lua new file mode 100644 index 0000000..d42a9b7 --- /dev/null +++ b/lockbox/padding/zero.lua @@ -0,0 +1,19 @@ +local ZeroPadding = function(blockSize, byteCount) + + local paddingCount = blockSize - ((byteCount -1) % blockSize) + 1; + local bytesLeft = paddingCount; + + local stream = function() + if bytesLeft > 0 then + bytesLeft = bytesLeft - 1; + return 0x00; + else + return nil; + end + end + + return stream; + +end + +return ZeroPadding; diff --git a/lockbox/util/array.lua b/lockbox/util/array.lua new file mode 100644 index 0000000..bd9ed56 --- /dev/null +++ b/lockbox/util/array.lua @@ -0,0 +1,211 @@ + +local String = require("string"); +local Bit = require("lockbox.util.bit"); +local Queue = require("lockbox.util.queue"); + +local XOR = Bit.bxor; + +local Array = {}; + +Array.size = function(array) + return #array; +end + +Array.fromString = function(string) + local bytes = {}; + + local i = 1; + local byte = String.byte(string, i); + while byte ~= nil do + bytes[i] = byte; + i = i + 1; + byte = String.byte(string, i); + end + + return bytes; + +end + +Array.toString = function(bytes) + local chars = {}; + local i = 1; + + local byte = bytes[i]; + while byte ~= nil do + chars[i] = String.char(byte); + i = i + 1; + byte = bytes[i]; + end + + return table.concat(chars, ""); +end + +Array.fromStream = function(stream) + local array = {}; + local i = 1; + + local byte = stream(); + while byte ~= nil do + array[i] = byte; + i = i + 1; + byte = stream(); + end + + return array; +end + +Array.readFromQueue = function(queue, size) + local array = {}; + + for i = 1, size do + array[i] = queue.pop(); + end + + return array; +end + +Array.writeToQueue = function(queue, array) + local size = Array.size(array); + + for i = 1, size do + queue.push(array[i]); + end +end + +Array.toStream = function(array) + local queue = Queue(); + local i = 1; + + local byte = array[i]; + while byte ~= nil do + queue.push(byte); + i = i + 1; + byte = array[i]; + end + + return queue.pop; +end + + +local fromHexTable = {}; +for i = 0, 255 do + fromHexTable[String.format("%02X", i)] = i; + fromHexTable[String.format("%02x", i)] = i; +end + +Array.fromHex = function(hex) + local array = {}; + + for i = 1, String.len(hex) / 2 do + local h = String.sub(hex, i * 2 - 1, i * 2); + array[i] = fromHexTable[h]; + end + + return array; +end + + +local toHexTable = {}; +for i = 0, 255 do + toHexTable[i] = String.format("%02X", i); +end + +Array.toHex = function(array) + local hex = {}; + local i = 1; + + local byte = array[i]; + while byte ~= nil do + hex[i] = toHexTable[byte]; + i = i + 1; + byte = array[i]; + end + + return table.concat(hex, ""); + +end + +Array.concat = function(a, b) + local concat = {}; + local out = 1; + + local i = 1; + local byte = a[i]; + while byte ~= nil do + concat[out] = byte; + i = i + 1; + out = out + 1; + byte = a[i]; + end + + i = 1; + byte = b[i]; + while byte ~= nil do + concat[out] = byte; + i = i + 1; + out = out + 1; + byte = b[i]; + end + + return concat; +end + +Array.truncate = function(a, newSize) + local x = {}; + + for i = 1, newSize do + x[i] = a[i]; + end + + return x; +end + +Array.XOR = function(a, b) + local x = {}; + + for k, v in pairs(a) do + x[k] = XOR(v, b[k]); + end + + return x; +end + +Array.substitute = function(input, sbox) + local out = {}; + + for k, v in pairs(input) do + out[k] = sbox[v]; + end + + return out; +end + +Array.permute = function(input, pbox) + local out = {}; + + for k, v in pairs(pbox) do + out[k] = input[v]; + end + + return out; +end + +Array.copy = function(input) + local out = {}; + + for k, v in pairs(input) do + out[k] = v; + end + return out; +end + +Array.slice = function(input, start, stop) + local out = {}; + + for i = start, stop do + out[i - start + 1] = input[i]; + end + return out; +end + +return Array; diff --git a/lockbox/util/bit.lua b/lockbox/util/bit.lua new file mode 100644 index 0000000..b17238e --- /dev/null +++ b/lockbox/util/bit.lua @@ -0,0 +1,25 @@ +local ok, e +ok = nil +if not ok then + ok, e = pcall(require, "bit") -- the LuaJIT one ? +end +if not ok then + ok, e = pcall(require, "bit32") -- Lua 5.2 +end +if not ok then + ok, e = pcall(require, "bit.numberlua") -- for Lua 5.1, https://github.com/tst2005/lua-bit-numberlua/ +end +if not ok then + error("no bitwise support found", 2) +end +assert(type(e) == "table", "invalid bit module") + +-- Workaround to support Lua 5.2 bit32 API with the LuaJIT bit one +if e.rol and not e.lrotate then + e.lrotate = e.rol +end +if e.ror and not e.rrotate then + e.rrotate = e.ror +end + +return e diff --git a/lockbox/util/queue.lua b/lockbox/util/queue.lua new file mode 100644 index 0000000..4a4a345 --- /dev/null +++ b/lockbox/util/queue.lua @@ -0,0 +1,47 @@ +local Queue = function() + local queue = {}; + local tail = 0; + local head = 0; + + local public = {}; + + public.push = function(obj) + queue[head] = obj; + head = head + 1; + return; + end + + public.pop = function() + if tail < head + then + local obj = queue[tail]; + queue[tail] = nil; + tail = tail + 1; + return obj; + else + return nil; + end + end + + public.size = function() + return head - tail; + end + + public.getHead = function() + return head; + end + + public.getTail = function() + return tail; + end + + public.reset = function() + queue = {}; + head = 0; + tail = 0; + end + + return public; +end + +return Queue; diff --git a/lockbox/util/stream.lua b/lockbox/util/stream.lua new file mode 100644 index 0000000..f81a18c --- /dev/null +++ b/lockbox/util/stream.lua @@ -0,0 +1,99 @@ +local Queue = require("lockbox.util.queue"); +local String = require("string"); + +local Stream = {}; + + +Stream.fromString = function(string) + local i = 0; + return function() + i = i + 1; + return String.byte(string, i); + end +end + + +Stream.toString = function(stream) + local array = {}; + local i = 1; + + local byte = stream(); + while byte ~= nil do + array[i] = String.char(byte); + i = i + 1; + byte = stream(); + end + + return table.concat(array); +end + + +Stream.fromArray = function(array) + local queue = Queue(); + local i = 1; + + local byte = array[i]; + while byte ~= nil do + queue.push(byte); + i = i + 1; + byte = array[i]; + end + + return queue.pop; +end + + +Stream.toArray = function(stream) + local array = {}; + local i = 1; + + local byte = stream(); + while byte ~= nil do + array[i] = byte; + i = i + 1; + byte = stream(); + end + + return array; +end + + +local fromHexTable = {}; +for i = 0, 255 do + fromHexTable[String.format("%02X", i)] = i; + fromHexTable[String.format("%02x", i)] = i; +end + +Stream.fromHex = function(hex) + local queue = Queue(); + + for i = 1, String.len(hex) / 2 do + local h = String.sub(hex, i * 2 - 1, i * 2); + queue.push(fromHexTable[h]); + end + + return queue.pop; +end + + + +local toHexTable = {}; +for i = 0, 255 do + toHexTable[i] = String.format("%02X", i); +end + +Stream.toHex = function(stream) + local hex = {}; + local i = 1; + + local byte = stream(); + while byte ~= nil do + hex[i] = toHexTable[byte]; + i = i + 1; + byte = stream(); + end + + return table.concat(hex); +end + +return Stream; diff --git a/scada-common/crypto.lua b/scada-common/crypto.lua new file mode 100644 index 0000000..19db76b --- /dev/null +++ b/scada-common/crypto.lua @@ -0,0 +1,246 @@ +local aes128 = require("lockbox.cipher.aes128") +local ctr_mode = require("lockbox.cipher.mode.ctr"); + +local sha1 = require("lockbox.digest.sha1"); +local sha2_224 = require("lockbox.digest.sha2_224"); +local sha2_256 = require("lockbox.digest.sha2_256"); + +local pbkdf2 = require("lockbox.kdf.pbkdf2") + +local hmac = require("lockbox.mac.hmac") + +local zero_pad = require("lockbox.padding.zero"); + +local stream = require("lockbox.util.stream") +local array = require("lockbox.util.array") + +local log = require("scada-common.log") +local util = require("scada-common.util") + +local crypto = {} + +local c_eng = { + key = nil, + cipher = nil, + decipher = nil, + hmac = nil +} + +---@alias hex string + +-- initialize cryptographic system +function crypto.init(password, server_port) + local key_deriv = pbkdf2() + + -- setup PBKDF2 + -- the primary goal is to just turn our password into a 16 byte key + key_deriv.setPassword(password) + key_deriv.setSalt("salty_salt_at_" .. server_port) + key_deriv.setIterations(32) + key_deriv.setBlockLen(8) + key_deriv.setDKeyLen(16) + + local start = util.time() + + key_deriv.setPRF(hmac().setBlockSize(64).setDigest(sha2_256)) + key_deriv.finish() + + log.dmesg("pbkdf2: key derivation took " .. (util.time() - start) .. "ms", "CRYPTO", colors.yellow) + + c_eng.key = array.fromHex(key_deriv.asHex()) + + -- initialize cipher + c_eng.cipher = ctr_mode.Cipher() + c_eng.cipher.setKey(c_eng.key) + c_eng.cipher.setBlockCipher(aes128) + c_eng.cipher.setPadding(zero_pad); + + -- initialize decipher + c_eng.decipher = ctr_mode.Decipher() + c_eng.decipher.setKey(c_eng.key) + c_eng.decipher.setBlockCipher(aes128) + c_eng.decipher.setPadding(zero_pad); + + -- initialize HMAC + c_eng.hmac = hmac() + c_eng.hmac.setBlockSize(64) + c_eng.hmac.setDigest(sha1) + c_eng.hmac.setKey(c_eng.key) + + log.dmesg("init: completed in " .. (util.time() - start) .. "ms", "CRYPTO", colors.yellow) +end + +-- encrypt plaintext +---@param plaintext string +---@return string initial_value, string ciphertext +function crypto.encrypt(plaintext) + local start = util.time() + + -- initial value + local iv = { + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255) + } + + log.debug("crypto.random: iv random took " .. (util.time() - start) .. "ms") + + start = util.time() + + c_eng.cipher.init() + c_eng.cipher.update(stream.fromArray(iv)) + c_eng.cipher.update(stream.fromString(plaintext)) + c_eng.cipher.finish() + + local ciphertext = c_eng.cipher.asHex() ---@type hex + + log.debug("crypto.encrypt: aes128-ctr-mode took " .. (util.time() - start) .. "ms") + log.debug("ciphertext: " .. ciphertext) + + return iv, ciphertext +end + +-- decrypt ciphertext +---@param iv string CTR initial value +---@param ciphertext string ciphertext hex +---@return string plaintext +function crypto.decrypt(iv, ciphertext) + local start = util.time() + + c_eng.decipher.init() + c_eng.decipher.update(stream.fromArray(iv)) + c_eng.decipher.update(stream.fromHex(ciphertext)) + c_eng.decipher.finish() + + local plaintext_hex = c_eng.decipher.asHex() ---@type hex + + local plaintext = stream.toString(stream.fromHex(plaintext_hex)) + + log.debug("crypto.decrypt: aes128-ctr-mode took " .. (util.time() - start) .. "ms") + log.debug("plaintext: " .. plaintext) + + return plaintext +end + +-- generate HMAC of message +---@param message_hex string initial value concatenated with ciphertext +function crypto.hmac(message_hex) + local start = util.time() + + c_eng.hmac.init() + c_eng.hmac.update(stream.fromHex(message_hex)) + c_eng.hmac.finish() + + local hash = c_eng.hmac.asHex() ---@type hex + + log.debug("crypto.hmac: hmac-sha1 took " .. (util.time() - start) .. "ms") + log.debug("hmac: " .. hash) + + return hash +end + +-- wrap a modem as a secure modem to send encrypted traffic +---@param modem table modem to wrap +function crypto.secure_modem(modem) + local self = { + modem = modem + } + + ---@class secure_modem + ---@field open function + ---@field isOpen function + ---@field close function + ---@field closeAll function + ---@field isWireless function + ---@field getNamesRemote function + ---@field isPresentRemote function + ---@field getTypeRemote function + ---@field hasTypeRemote function + ---@field getMethodsRemote function + ---@field callRemote function + ---@field getNameLocal function + local public = {} + + -- wrap a modem + ---@param modem table +---@diagnostic disable-next-line: redefined-local + function public.wrap(modem) + self.modem = modem + for key, func in pairs(self.modem) do + public[key] = func + end + end + + -- wrap modem functions, then we replace transmit + public.wrap(self.modem) + + -- send a packet with encryption + ---@param channel integer + ---@param reply_channel integer + ---@param payload table packet raw_sendable + function public.transmit(channel, reply_channel, payload) + local plaintext = textutils.serialize(payload, { allow_repetitions = true, compact = true }) + + local iv, ciphertext = crypto.encrypt(plaintext) +---@diagnostic disable-next-line: redefined-local + local hmac = crypto.hmac(iv .. ciphertext) + + self.modem.transmit(channel, reply_channel, { hmac, iv, ciphertext }) + end + + -- parse in a modem message as a network packet + ---@param side string + ---@param sender integer + ---@param reply_to integer + ---@param message any encrypted packet sent with secure_modem.transmit + ---@param distance integer + ---@return string side, integer sender, integer reply_to, any plaintext_message, integer distance + function public.receive(side, sender, reply_to, message, distance) + local body = "" + + if type(message) == "table" then + if #message == 3 then +---@diagnostic disable-next-line: redefined-local + local hmac = message[1] + local iv = message[2] + local ciphertext = message[3] + + local computed_hmac = crypto.hmac(iv .. ciphertext) + + if hmac == computed_hmac then + -- message intact + local plaintext = crypto.decrypt(iv, ciphertext) + body = textutils.deserialize(plaintext) + + if body == nil then + -- failed decryption + log.debug("crypto.secure_modem: decryption failed") + body = "" + end + else + -- something went wrong + log.debug("crypto.secure_modem: hmac mismatch violation") + end + end + end + + return side, sender, reply_to, body, distance + end + + return public +end + +return crypto diff --git a/test/lockbox-benchmark.lua b/test/lockbox-benchmark.lua new file mode 100644 index 0000000..f6f1ec2 --- /dev/null +++ b/test/lockbox-benchmark.lua @@ -0,0 +1,104 @@ +require("/initenv").init_env() + +local pbkdf2 = require("lockbox.kdf.pbkdf2") +local AES128Cipher = require("lockbox.cipher.aes128") +local HMAC = require("lockbox.mac.hmac") +local SHA1 = require("lockbox.digest.sha1"); +local SHA2_224 = require("lockbox.digest.sha2_224"); +local SHA2_256 = require("lockbox.digest.sha2_256"); +local Stream = require("lockbox.util.stream") +local Array = require("lockbox.util.array") + +local CBCMode = require("lockbox.cipher.mode.cbc"); +local CFBMode = require("lockbox.cipher.mode.cfb"); +local OFBMode = require("lockbox.cipher.mode.ofb"); +local CTRMode = require("lockbox.cipher.mode.ctr"); + +local ZeroPadding = require("lockbox.padding.zero"); + +local comms = require("scada-common.comms") +local util = require("scada-common.util") + +local start = util.time() + +local keyd = pbkdf2() + +keyd.setPassword("mypassword") +keyd.setSalt("no_salt_thanks") +keyd.setIterations(16) +keyd.setBlockLen(4) +keyd.setDKeyLen(16) +keyd.setPRF(HMAC().setBlockSize(64).setDigest(SHA2_256)) +keyd.finish() + +util.println("pbkdf2: took " .. (util.time() - start) .. "ms") +util.println(keyd.asHex()) + +local pkt = comms.modbus_packet() +pkt.make(1, 2, 7, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) +local spkt = comms.scada_packet() +spkt.make(1, 1, pkt.raw_sendable()) + +start = util.time() +local data = textutils.serialize(spkt.raw_sendable(), { allow_repetitions = true, compact = true }) + +util.println("packet serialize: took " .. (util.time() - start) .. "ms") +util.println("message: " .. data) + +start = util.time() +local v = { + cipher = CTRMode.Cipher, + decipher = CTRMode.Decipher, + iv = Array.fromHex("000102030405060708090A0B0C0D0E0F"), + key = Array.fromHex(keyd.asHex()), + padding = ZeroPadding +} +util.println("v init: took " .. (util.time() - start) .. "ms") + +start = util.time() +local cipher = v.cipher() +.setKey(v.key) +.setBlockCipher(AES128Cipher) +.setPadding(v.padding); +util.println("cipher init: took " .. (util.time() - start) .. "ms") + +start = util.time() +local cipherOutput = cipher + .init() + .update(Stream.fromArray(v.iv)) + .update(Stream.fromString(data)) + .asHex(); +util.println("encrypt: took " .. (util.time() - start) .. "ms") +util.println("ciphertext: " .. cipherOutput) + +start = util.time() +local decipher = v.decipher() + .setKey(v.key) + .setBlockCipher(AES128Cipher) + .setPadding(v.padding); +util.println("decipher init: took " .. (util.time() - start) .. "ms") + +start = util.time() +local plainOutput = decipher + .init() + .update(Stream.fromArray(v.iv)) + .update(Stream.fromHex(cipherOutput)) + .asHex(); +util.println("decrypt: took " .. (util.time() - start) .. "ms") +local a = Stream.fromHex(plainOutput) +local b = Stream.toString(a) +util.println("plaintext: " .. b) + +local msg = "000102030405060708090A0B0C0D0E0F" .. cipherOutput + +start = util.time() +local hash = HMAC() + .setBlockSize(64) + .setDigest(SHA1) + .setKey(keyd) + .init() + .update(Stream.fromHex(msg)) + .finish() + .asHex(); +util.println("hmac: took " .. (util.time() - start) .. "ms") +util.println("hash: " .. hash) From 1705d8993eeaf5d25b00f88910ead56aa6a9da27 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 31 May 2022 14:14:17 -0400 Subject: [PATCH 234/587] #64 plc code cleanup --- reactor-plc/plc.lua | 86 ++++++++++++++++++++--------------------- reactor-plc/startup.lua | 16 ++++---- reactor-plc/threads.lua | 36 ++++++++--------- 3 files changed, 69 insertions(+), 69 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 9620636..c7cb76e 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -1,8 +1,8 @@ local comms = require("scada-common.comms") -local log = require("scada-common.log") -local ppm = require("scada-common.ppm") +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") local types = require("scada-common.types") -local util = require("scada-common.util") +local util = require("scada-common.util") local plc = {} @@ -23,7 +23,7 @@ local println_ts = util.println_ts --- identifies dangerous states and SCRAMs reactor if warranted --- --- autonomous from main SCADA supervisor/coordinator control -plc.rps_init = function (reactor) +function plc.rps_init(reactor) local state_keys = { dmg_crit = 1, high_temp = 2, @@ -50,19 +50,19 @@ plc.rps_init = function (reactor) -- PRIVATE FUNCTIONS -- -- set reactor access fault flag - local _set_fault = function () + local function _set_fault() if self.reactor.__p_last_fault() ~= "Terminated" then self.state[state_keys.fault] = true end end -- clear reactor access fault flag - local _clear_fault = function () + local function _clear_fault() self.state[state_keys.fault] = false end -- check for critical damage - local _damage_critical = function () + local function _damage_critical() local damage_percent = self.reactor.getDamagePercent() if damage_percent == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later @@ -75,7 +75,7 @@ plc.rps_init = function (reactor) end -- check if the reactor is at a critically high temperature - local _high_temp = function () + local function _high_temp() -- mekanism: MAX_DAMAGE_TEMPERATURE = 1_200 local temp = self.reactor.getTemperature() if temp == ppm.ACCESS_FAULT then @@ -89,7 +89,7 @@ plc.rps_init = function (reactor) end -- check if there is no coolant (<2% filled) - local _no_coolant = function () + local function _no_coolant() local coolant_filled = self.reactor.getCoolantFilledPercentage() if coolant_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later @@ -102,7 +102,7 @@ plc.rps_init = function (reactor) end -- check for excess waste (>80% filled) - local _excess_waste = function () + local function _excess_waste() local w_filled = self.reactor.getWasteFilledPercentage() if w_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later @@ -115,7 +115,7 @@ plc.rps_init = function (reactor) end -- check for heated coolant backup (>95% filled) - local _excess_heated_coolant = function () + local function _excess_heated_coolant() local hc_filled = self.reactor.getHeatedCoolantFilledPercentage() if hc_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later @@ -128,7 +128,7 @@ plc.rps_init = function (reactor) end -- check if there is no fuel - local _insufficient_fuel = function () + local function _insufficient_fuel() local fuel = self.reactor.getFuel() if fuel == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later @@ -144,28 +144,28 @@ plc.rps_init = function (reactor) -- re-link a reactor after a peripheral re-connect ---@diagnostic disable-next-line: redefined-local - public.reconnect_reactor = function (reactor) + function public.reconnect_reactor(reactor) self.reactor = reactor end -- trip for lost peripheral - public.trip_fault = function () + function public.trip_fault() _set_fault() end -- trip for a PLC comms timeout - public.trip_timeout = function () + function public.trip_timeout() self.state[state_keys.timeout] = true end -- manually SCRAM the reactor - public.trip_manual = function () + function public.trip_manual() self.state[state_keys.manual] = true end -- SCRAM the reactor now ---@return boolean success - public.scram = function () + function public.scram() log.info("RPS: reactor SCRAM") self.reactor.scram() @@ -180,7 +180,7 @@ plc.rps_init = function (reactor) -- start the reactor ---@return boolean success - public.activate = function () + function public.activate() if not self.tripped then log.info("RPS: reactor start") @@ -198,7 +198,7 @@ plc.rps_init = function (reactor) -- check all safety conditions ---@return boolean tripped, rps_status_t trip_status, boolean first_trip - public.check = function () + function public.check() local status = rps_status_t.ok local was_tripped = self.tripped local first_trip = false @@ -259,12 +259,12 @@ plc.rps_init = function (reactor) return self.tripped, status, first_trip end - public.status = function () return self.state end - public.is_tripped = function () return self.tripped end - public.is_active = function () return self.reactor_enabled end + function public.status() return self.state end + function public.is_tripped() return self.tripped end + function public.is_active() return self.reactor_enabled end -- reset the RPS - public.reset = function () + function public.reset() self.tripped = false self.trip_cause = rps_status_t.ok @@ -285,7 +285,7 @@ end ---@param reactor table ---@param rps rps ---@param conn_watchdog watchdog -plc.comms = function (id, version, modem, local_port, server_port, reactor, rps, conn_watchdog) +function plc.comms(id, version, modem, local_port, server_port, reactor, rps, conn_watchdog) local self = { id = id, version = version, @@ -316,7 +316,7 @@ plc.comms = function (id, version, modem, local_port, server_port, reactor, rps, -- send an RPLC packet ---@param msg_type RPLC_TYPES ---@param msg string - local _send = function (msg_type, msg) + local function _send(msg_type, msg) local s_pkt = comms.scada_packet() local r_pkt = comms.rplc_packet() @@ -330,7 +330,7 @@ plc.comms = function (id, version, modem, local_port, server_port, reactor, rps, -- send a SCADA management packet ---@param msg_type SCADA_MGMT_TYPES ---@param msg string - local _send_mgmt = function (msg_type, msg) + local function _send_mgmt(msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() @@ -343,7 +343,7 @@ plc.comms = function (id, version, modem, local_port, server_port, reactor, rps, -- variable reactor status information, excluding heating rate ---@return table data_table, boolean faulted - local _reactor_status = function () + local function _reactor_status() local coolant = nil local hcoolant = nil @@ -402,7 +402,7 @@ plc.comms = function (id, version, modem, local_port, server_port, reactor, rps, -- update the status cache if changed ---@return boolean changed - local _update_status_cache = function () + local function _update_status_cache() local status, faulted = _reactor_status() local changed = false @@ -428,19 +428,19 @@ plc.comms = function (id, version, modem, local_port, server_port, reactor, rps, -- keep alive ack ---@param srv_time integer - local _send_keep_alive_ack = function (srv_time) + local function _send_keep_alive_ack(srv_time) _send_mgmt(SCADA_MGMT_TYPES.KEEP_ALIVE, { srv_time, util.time() }) end -- general ack ---@param msg_type RPLC_TYPES ---@param succeeded boolean - local _send_ack = function (msg_type, succeeded) + local function _send_ack(msg_type, succeeded) _send(msg_type, { succeeded }) end -- send structure properties (these should not change, server will cache these) - local _send_struct = function () + local function _send_struct() local mek_data = { 0, 0, 0, 0, 0, 0, 0, 0 } local tasks = { @@ -468,7 +468,7 @@ plc.comms = function (id, version, modem, local_port, server_port, reactor, rps, -- reconnect a newly connected modem ---@param modem table ---@diagnostic disable-next-line: redefined-local - public.reconnect_modem = function (modem) + function public.reconnect_modem(modem) self.modem = modem -- open modem @@ -480,33 +480,33 @@ plc.comms = function (id, version, modem, local_port, server_port, reactor, rps, -- reconnect a newly connected reactor ---@param reactor table ---@diagnostic disable-next-line: redefined-local - public.reconnect_reactor = function (reactor) + function public.reconnect_reactor(reactor) self.reactor = reactor self.status_cache = nil end -- unlink from the server - public.unlink = function () + function public.unlink() self.linked = false self.r_seq_num = nil self.status_cache = nil end -- close the connection to the server - public.close = function () + function public.close() self.conn_watchdog.cancel() public.unlink() _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) end -- attempt to establish link with supervisor - public.send_link_req = function () + function public.send_link_req() _send(RPLC_TYPES.LINK_REQ, { self.id, self.version }) end -- send live status information ---@param degraded boolean - public.send_status = function (degraded) + function public.send_status(degraded) if self.linked then local mek_data = nil @@ -532,7 +532,7 @@ plc.comms = function (id, version, modem, local_port, server_port, reactor, rps, end -- send reactor protection system status - public.send_rps_status = function () + function public.send_rps_status() if self.linked then _send(RPLC_TYPES.RPS_STATUS, rps.status()) end @@ -540,7 +540,7 @@ plc.comms = function (id, version, modem, local_port, server_port, reactor, rps, -- send reactor protection system alarm ---@param cause rps_status_t - public.send_rps_alarm = function (cause) + function public.send_rps_alarm(cause) if self.linked then local rps_alarm = { cause, @@ -558,7 +558,7 @@ plc.comms = function (id, version, modem, local_port, server_port, reactor, rps, ---@param message any ---@param distance integer ---@return rplc_frame|mgmt_frame|nil packet - public.parse_packet = function(side, sender, reply_to, message, distance) + function public.parse_packet(side, sender, reply_to, message, distance) local pkt = nil local s_pkt = comms.scada_packet() @@ -590,7 +590,7 @@ plc.comms = function (id, version, modem, local_port, server_port, reactor, rps, ---@param packet rplc_frame|mgmt_frame ---@param plc_state plc_state ---@param setpoints setpoints - public.handle_packet = function (packet, plc_state, setpoints) + function public.handle_packet(packet, plc_state, setpoints) if packet ~= nil then -- check sequence number if self.r_seq_num == nil then @@ -760,8 +760,8 @@ plc.comms = function (id, version, modem, local_port, server_port, reactor, rps, end end - public.is_scrammed = function () return self.scrammed end - public.is_linked = function () return self.linked end + function public.is_scrammed() return self.scrammed end + function public.is_linked() return self.linked end return public end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index d6e23c0..e0aeeba 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -4,16 +4,16 @@ require("/initenv").init_env() -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local ppm = require("scada-common.ppm") -local util = require("scada-common.util") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") -local config = require("reactor-plc.config") -local plc = require("reactor-plc.plc") +local config = require("reactor-plc.config") +local plc = require("reactor-plc.plc") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "beta-v0.7.3" +local R_PLC_VERSION = "beta-v0.7.4" local print = util.print local println = util.println @@ -102,7 +102,7 @@ if __shared_memory.networked and smem_dev.modem == nil then end -- PLC init -local init = function () +local function init() if plc_state.init_ok then -- just booting up, no fission allowed (neutrons stay put thanks) smem_dev.reactor.scram() diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index f28c775..45a8201 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -1,7 +1,7 @@ -local log = require("scada-common.log") +local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") -local ppm = require("scada-common.ppm") -local util = require("scada-common.util") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") local threads = {} @@ -30,11 +30,11 @@ local MQ__COMM_CMD = { -- main thread ---@param smem plc_shared_memory ---@param init function -threads.thread__main = function (smem, init) +function threads.thread__main(smem, init) local public = {} ---@class thread -- execute thread - public.exec = function () + function public.exec() log.debug("main thread init, clock inactive") -- send status updates at 2Hz (every 10 server ticks) (every loop tick) @@ -189,7 +189,7 @@ threads.thread__main = function (smem, init) end -- execute the thread in a protected mode, retrying it on return if not shutting down - public.p_exec = function () + function public.p_exec() local plc_state = smem.plc_state while not plc_state.shutdown do @@ -215,11 +215,11 @@ end -- RPS operation thread ---@param smem plc_shared_memory -threads.thread__rps = function (smem) +function threads.thread__rps(smem) local public = {} ---@class thread -- execute thread - public.exec = function () + function public.exec() log.debug("rps thread start") -- load in from shared memory @@ -332,7 +332,7 @@ threads.thread__rps = function (smem) end -- execute the thread in a protected mode, retrying it on return if not shutting down - public.p_exec = function () + function public.p_exec() local plc_state = smem.plc_state while not plc_state.shutdown do @@ -354,11 +354,11 @@ end -- communications sender thread ---@param smem plc_shared_memory -threads.thread__comms_tx = function (smem) +function threads.thread__comms_tx(smem) local public = {} ---@class thread -- execute thread - public.exec = function () + function public.exec() log.debug("comms tx thread start") -- load in from shared memory @@ -407,7 +407,7 @@ threads.thread__comms_tx = function (smem) end -- execute the thread in a protected mode, retrying it on return if not shutting down - public.p_exec = function () + function public.p_exec() local plc_state = smem.plc_state while not plc_state.shutdown do @@ -428,11 +428,11 @@ end -- communications handler thread ---@param smem plc_shared_memory -threads.thread__comms_rx = function (smem) +function threads.thread__comms_rx(smem) local public = {} ---@class thread -- execute thread - public.exec = function () + function public.exec() log.debug("comms rx thread start") -- load in from shared memory @@ -481,7 +481,7 @@ threads.thread__comms_rx = function (smem) end -- execute the thread in a protected mode, retrying it on return if not shutting down - public.p_exec = function () + function public.p_exec() local plc_state = smem.plc_state while not plc_state.shutdown do @@ -502,11 +502,11 @@ end -- apply setpoints ---@param smem plc_shared_memory -threads.thread__setpoint_control = function (smem) +function threads.thread__setpoint_control(smem) local public = {} ---@class thread -- execute thread - public.exec = function () + function public.exec() log.debug("setpoint control thread start") -- load in from shared memory @@ -605,7 +605,7 @@ threads.thread__setpoint_control = function (smem) end -- execute the thread in a protected mode, retrying it on return if not shutting down - public.p_exec = function () + function public.p_exec() local plc_state = smem.plc_state while not plc_state.shutdown do From 4ec07ca053f41ed310636a1f15a074e009439c41 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 31 May 2022 14:54:55 -0400 Subject: [PATCH 235/587] #64 rtu code cleanup and device bugfixes --- rtu/dev/boiler_rtu.lua | 51 +++++++++++++------------- rtu/dev/boilerv_rtu.lua | 65 ++++++++++++++++----------------- rtu/dev/energymachine_rtu.lua | 23 ++++-------- rtu/dev/imatrix_rtu.lua | 39 ++++++++++---------- rtu/dev/redstone_rtu.lua | 26 +++++++------- rtu/dev/turbine_rtu.lua | 41 ++++++++++----------- rtu/dev/turbinev_rtu.lua | 67 +++++++++++++++++------------------ rtu/modbus.lua | 28 +++++++-------- rtu/rtu.lua | 48 ++++++++++++------------- rtu/startup.lua | 30 ++++++++-------- rtu/threads.lua | 18 +++++----- 11 files changed, 205 insertions(+), 231 deletions(-) diff --git a/rtu/dev/boiler_rtu.lua b/rtu/dev/boiler_rtu.lua index 26b5ebc..74924af 100644 --- a/rtu/dev/boiler_rtu.lua +++ b/rtu/dev/boiler_rtu.lua @@ -4,11 +4,8 @@ local boiler_rtu = {} -- create new boiler (mek 10.0) device ---@param boiler table -boiler_rtu.new = function (boiler) - local self = { - rtu = rtu.init_unit(), - boiler = boiler - } +function boiler_rtu.new(boiler) + local unit = rtu.init_unit() -- discrete inputs -- -- none @@ -18,34 +15,34 @@ boiler_rtu.new = function (boiler) -- input registers -- -- build properties - self.rtu.connect_input_reg(self.boiler.getBoilCapacity) - self.rtu.connect_input_reg(self.boiler.getSteamCapacity) - self.rtu.connect_input_reg(self.boiler.getWaterCapacity) - self.rtu.connect_input_reg(self.boiler.getHeatedCoolantCapacity) - self.rtu.connect_input_reg(self.boiler.getCooledCoolantCapacity) - self.rtu.connect_input_reg(self.boiler.getSuperheaters) - self.rtu.connect_input_reg(self.boiler.getMaxBoilRate) + unit.connect_input_reg(boiler.getBoilCapacity) + unit.connect_input_reg(boiler.getSteamCapacity) + unit.connect_input_reg(boiler.getWaterCapacity) + unit.connect_input_reg(boiler.getHeatedCoolantCapacity) + unit.connect_input_reg(boiler.getCooledCoolantCapacity) + unit.connect_input_reg(boiler.getSuperheaters) + unit.connect_input_reg(boiler.getMaxBoilRate) -- current state - self.rtu.connect_input_reg(self.boiler.getTemperature) - self.rtu.connect_input_reg(self.boiler.getBoilRate) + unit.connect_input_reg(boiler.getTemperature) + unit.connect_input_reg(boiler.getBoilRate) -- tanks - self.rtu.connect_input_reg(self.boiler.getSteam) - self.rtu.connect_input_reg(self.boiler.getSteamNeeded) - self.rtu.connect_input_reg(self.boiler.getSteamFilledPercentage) - self.rtu.connect_input_reg(self.boiler.getWater) - self.rtu.connect_input_reg(self.boiler.getWaterNeeded) - self.rtu.connect_input_reg(self.boiler.getWaterFilledPercentage) - self.rtu.connect_input_reg(self.boiler.getHeatedCoolant) - self.rtu.connect_input_reg(self.boiler.getHeatedCoolantNeeded) - self.rtu.connect_input_reg(self.boiler.getHeatedCoolantFilledPercentage) - self.rtu.connect_input_reg(self.boiler.getCooledCoolant) - self.rtu.connect_input_reg(self.boiler.getCooledCoolantNeeded) - self.rtu.connect_input_reg(self.boiler.getCooledCoolantFilledPercentage) + unit.connect_input_reg(boiler.getSteam) + unit.connect_input_reg(boiler.getSteamNeeded) + unit.connect_input_reg(boiler.getSteamFilledPercentage) + unit.connect_input_reg(boiler.getWater) + unit.connect_input_reg(boiler.getWaterNeeded) + unit.connect_input_reg(boiler.getWaterFilledPercentage) + unit.connect_input_reg(boiler.getHeatedCoolant) + unit.connect_input_reg(boiler.getHeatedCoolantNeeded) + unit.connect_input_reg(boiler.getHeatedCoolantFilledPercentage) + unit.connect_input_reg(boiler.getCooledCoolant) + unit.connect_input_reg(boiler.getCooledCoolantNeeded) + unit.connect_input_reg(boiler.getCooledCoolantFilledPercentage) -- holding registers -- -- none - return self.rtu.interface() + return unit.interface() end return boiler_rtu diff --git a/rtu/dev/boilerv_rtu.lua b/rtu/dev/boilerv_rtu.lua index fca1f09..782332f 100644 --- a/rtu/dev/boilerv_rtu.lua +++ b/rtu/dev/boilerv_rtu.lua @@ -4,55 +4,52 @@ local boilerv_rtu = {} -- create new boiler (mek 10.1+) device ---@param boiler table -boilerv_rtu.new = function (boiler) - local self = { - rtu = rtu.init_unit(), - boiler = boiler - } +function boilerv_rtu.new(boiler) + local unit = rtu.init_unit() -- discrete inputs -- - self.rtu.connect_di(self.boiler.isFormed) + unit.connect_di(boiler.isFormed) -- coils -- -- none -- input registers -- -- multiblock properties - self.rtu.connect_input_reg(self.boiler.getLength) - self.rtu.connect_input_reg(self.boiler.getWidth) - self.rtu.connect_input_reg(self.boiler.getHeight) - self.rtu.connect_input_reg(self.boiler.getMinPos) - self.rtu.connect_input_reg(self.boiler.getMaxPos) + unit.connect_input_reg(boiler.getLength) + unit.connect_input_reg(boiler.getWidth) + unit.connect_input_reg(boiler.getHeight) + unit.connect_input_reg(boiler.getMinPos) + unit.connect_input_reg(boiler.getMaxPos) -- build properties - self.rtu.connect_input_reg(self.boiler.getBoilCapacity) - self.rtu.connect_input_reg(self.boiler.getSteamCapacity) - self.rtu.connect_input_reg(self.boiler.getWaterCapacity) - self.rtu.connect_input_reg(self.boiler.getHeatedCoolantCapacity) - self.rtu.connect_input_reg(self.boiler.getCooledCoolantCapacity) - self.rtu.connect_input_reg(self.boiler.getSuperheaters) - self.rtu.connect_input_reg(self.boiler.getMaxBoilRate) - self.rtu.connect_input_reg(self.boiler.getEnvironmentalLoss) + unit.connect_input_reg(boiler.getBoilCapacity) + unit.connect_input_reg(boiler.getSteamCapacity) + unit.connect_input_reg(boiler.getWaterCapacity) + unit.connect_input_reg(boiler.getHeatedCoolantCapacity) + unit.connect_input_reg(boiler.getCooledCoolantCapacity) + unit.connect_input_reg(boiler.getSuperheaters) + unit.connect_input_reg(boiler.getMaxBoilRate) + unit.connect_input_reg(boiler.getEnvironmentalLoss) -- current state - self.rtu.connect_input_reg(self.boiler.getTemperature) - self.rtu.connect_input_reg(self.boiler.getBoilRate) + unit.connect_input_reg(boiler.getTemperature) + unit.connect_input_reg(boiler.getBoilRate) -- tanks - self.rtu.connect_input_reg(self.boiler.getSteam) - self.rtu.connect_input_reg(self.boiler.getSteamNeeded) - self.rtu.connect_input_reg(self.boiler.getSteamFilledPercentage) - self.rtu.connect_input_reg(self.boiler.getWater) - self.rtu.connect_input_reg(self.boiler.getWaterNeeded) - self.rtu.connect_input_reg(self.boiler.getWaterFilledPercentage) - self.rtu.connect_input_reg(self.boiler.getHeatedCoolant) - self.rtu.connect_input_reg(self.boiler.getHeatedCoolantNeeded) - self.rtu.connect_input_reg(self.boiler.getHeatedCoolantFilledPercentage) - self.rtu.connect_input_reg(self.boiler.getCooledCoolant) - self.rtu.connect_input_reg(self.boiler.getCooledCoolantNeeded) - self.rtu.connect_input_reg(self.boiler.getCooledCoolantFilledPercentage) + unit.connect_input_reg(boiler.getSteam) + unit.connect_input_reg(boiler.getSteamNeeded) + unit.connect_input_reg(boiler.getSteamFilledPercentage) + unit.connect_input_reg(boiler.getWater) + unit.connect_input_reg(boiler.getWaterNeeded) + unit.connect_input_reg(boiler.getWaterFilledPercentage) + unit.connect_input_reg(boiler.getHeatedCoolant) + unit.connect_input_reg(boiler.getHeatedCoolantNeeded) + unit.connect_input_reg(boiler.getHeatedCoolantFilledPercentage) + unit.connect_input_reg(boiler.getCooledCoolant) + unit.connect_input_reg(boiler.getCooledCoolantNeeded) + unit.connect_input_reg(boiler.getCooledCoolantFilledPercentage) -- holding registers -- -- none - return self.rtu.interface() + return unit.interface() end return boilerv_rtu diff --git a/rtu/dev/energymachine_rtu.lua b/rtu/dev/energymachine_rtu.lua index e0e05af..e08abb8 100644 --- a/rtu/dev/energymachine_rtu.lua +++ b/rtu/dev/energymachine_rtu.lua @@ -4,17 +4,8 @@ local energymachine_rtu = {} -- create new energy machine device ---@param machine table -energymachine_rtu.new = function (machine) - local self = { - rtu = rtu.init_unit(), - machine = machine - } - - ---@class rtu_device - local public = {} - - -- get the RTU interface - public.rtu_interface = function () return self.rtu end +function energymachine_rtu.new(machine) + local unit = rtu.init_unit() -- discrete inputs -- -- none @@ -24,16 +15,16 @@ energymachine_rtu.new = function (machine) -- input registers -- -- build properties - self.rtu.connect_input_reg(self.machine.getTotalMaxEnergy) + unit.connect_input_reg(machine.getTotalMaxEnergy) -- containers - self.rtu.connect_input_reg(self.machine.getTotalEnergy) - self.rtu.connect_input_reg(self.machine.getTotalEnergyNeeded) - self.rtu.connect_input_reg(self.machine.getTotalEnergyFilledPercentage) + unit.connect_input_reg(machine.getTotalEnergy) + unit.connect_input_reg(machine.getTotalEnergyNeeded) + unit.connect_input_reg(machine.getTotalEnergyFilledPercentage) -- holding registers -- -- none - return public + return unit.interface() end return energymachine_rtu diff --git a/rtu/dev/imatrix_rtu.lua b/rtu/dev/imatrix_rtu.lua index 56498e5..3ac3acd 100644 --- a/rtu/dev/imatrix_rtu.lua +++ b/rtu/dev/imatrix_rtu.lua @@ -4,42 +4,39 @@ local imatrix_rtu = {} -- create new induction matrix (mek 10.1+) device ---@param imatrix table -imatrix_rtu.new = function (imatrix) - local self = { - rtu = rtu.init_unit(), - imatrix = imatrix - } +function imatrix_rtu.new(imatrix) + local unit = rtu.init_unit() -- discrete inputs -- - self.rtu.connect_di(self.boiler.isFormed) + unit.connect_di(imatrix.isFormed) -- coils -- -- none -- input registers -- -- multiblock properties - self.rtu.connect_input_reg(self.boiler.getLength) - self.rtu.connect_input_reg(self.boiler.getWidth) - self.rtu.connect_input_reg(self.boiler.getHeight) - self.rtu.connect_input_reg(self.boiler.getMinPos) - self.rtu.connect_input_reg(self.boiler.getMaxPos) + unit.connect_input_reg(imatrix.getLength) + unit.connect_input_reg(imatrix.getWidth) + unit.connect_input_reg(imatrix.getHeight) + unit.connect_input_reg(imatrix.getMinPos) + unit.connect_input_reg(imatrix.getMaxPos) -- build properties - self.rtu.connect_input_reg(self.imatrix.getMaxEnergy) - self.rtu.connect_input_reg(self.imatrix.getTransferCap) - self.rtu.connect_input_reg(self.imatrix.getInstalledCells) - self.rtu.connect_input_reg(self.imatrix.getInstalledProviders) + unit.connect_input_reg(imatrix.getMaxEnergy) + unit.connect_input_reg(imatrix.getTransferCap) + unit.connect_input_reg(imatrix.getInstalledCells) + unit.connect_input_reg(imatrix.getInstalledProviders) -- containers - self.rtu.connect_input_reg(self.imatrix.getEnergy) - self.rtu.connect_input_reg(self.imatrix.getEnergyNeeded) - self.rtu.connect_input_reg(self.imatrix.getEnergyFilledPercentage) + unit.connect_input_reg(imatrix.getEnergy) + unit.connect_input_reg(imatrix.getEnergyNeeded) + unit.connect_input_reg(imatrix.getEnergyFilledPercentage) -- I/O rates - self.rtu.connect_input_reg(self.imatrix.getLastInput) - self.rtu.connect_input_reg(self.imatrix.getLastOutput) + unit.connect_input_reg(imatrix.getLastInput) + unit.connect_input_reg(imatrix.getLastOutput) -- holding registers -- -- none - return self.rtu.interface() + return unit.interface() end return imatrix_rtu diff --git a/rtu/dev/redstone_rtu.lua b/rtu/dev/redstone_rtu.lua index 563886e..5865552 100644 --- a/rtu/dev/redstone_rtu.lua +++ b/rtu/dev/redstone_rtu.lua @@ -1,4 +1,4 @@ -local rtu = require("rtu.rtu") +local rtu = require("rtu.rtu") local rsio = require("scada-common.rsio") local redstone_rtu = {} @@ -8,13 +8,11 @@ local digital_write = rsio.digital_write local digital_is_active = rsio.digital_is_active -- create new redstone device -redstone_rtu.new = function () - local self = { - rtu = rtu.init_unit() - } +function redstone_rtu.new() + local unit = rtu.init_unit() -- get RTU interface - local interface = self.rtu.interface() + local interface = unit.interface() ---@class rtu_rs_device --- extends rtu_device; fields added manually to please Lua diagnostics @@ -31,7 +29,7 @@ redstone_rtu.new = function () -- link digital input ---@param side string ---@param color integer - public.link_di = function (side, color) + function public.link_di(side, color) local f_read = nil if color then @@ -44,14 +42,14 @@ redstone_rtu.new = function () end end - self.rtu.connect_di(f_read) + unit.connect_di(f_read) end -- link digital output ---@param channel RS_IO ---@param side string ---@param color integer - public.link_do = function (channel, side, color) + function public.link_do(channel, side, color) local f_read = nil local f_write = nil @@ -81,13 +79,13 @@ redstone_rtu.new = function () end end - self.rtu.connect_coil(f_read, f_write) + unit.connect_coil(f_read, f_write) end -- link analog input ---@param side string - public.link_ai = function (side) - self.rtu.connect_input_reg( + function public.link_ai(side) + unit.connect_input_reg( function () return rs.getAnalogInput(side) end @@ -96,8 +94,8 @@ redstone_rtu.new = function () -- link analog output ---@param side string - public.link_ao = function (side) - self.rtu.connect_holding_reg( + function public.link_ao(side) + unit.connect_holding_reg( function () return rs.getAnalogOutput(side) end, diff --git a/rtu/dev/turbine_rtu.lua b/rtu/dev/turbine_rtu.lua index 476a50c..530080f 100644 --- a/rtu/dev/turbine_rtu.lua +++ b/rtu/dev/turbine_rtu.lua @@ -4,11 +4,8 @@ local turbine_rtu = {} -- create new turbine (mek 10.0) device ---@param turbine table -turbine_rtu.new = function (turbine) - local self = { - rtu = rtu.init_unit(), - turbine = turbine - } +function turbine_rtu.new(turbine) + local unit = rtu.init_unit() -- discrete inputs -- -- none @@ -18,29 +15,29 @@ turbine_rtu.new = function (turbine) -- input registers -- -- build properties - self.rtu.connect_input_reg(self.turbine.getBlades) - self.rtu.connect_input_reg(self.turbine.getCoils) - self.rtu.connect_input_reg(self.turbine.getVents) - self.rtu.connect_input_reg(self.turbine.getDispersers) - self.rtu.connect_input_reg(self.turbine.getCondensers) - self.rtu.connect_input_reg(self.turbine.getSteamCapacity) - self.rtu.connect_input_reg(self.turbine.getMaxFlowRate) - self.rtu.connect_input_reg(self.turbine.getMaxProduction) - self.rtu.connect_input_reg(self.turbine.getMaxWaterOutput) + unit.connect_input_reg(turbine.getBlades) + unit.connect_input_reg(turbine.getCoils) + unit.connect_input_reg(turbine.getVents) + unit.connect_input_reg(turbine.getDispersers) + unit.connect_input_reg(turbine.getCondensers) + unit.connect_input_reg(turbine.getSteamCapacity) + unit.connect_input_reg(turbine.getMaxFlowRate) + unit.connect_input_reg(turbine.getMaxProduction) + unit.connect_input_reg(turbine.getMaxWaterOutput) -- current state - self.rtu.connect_input_reg(self.turbine.getFlowRate) - self.rtu.connect_input_reg(self.turbine.getProductionRate) - self.rtu.connect_input_reg(self.turbine.getLastSteamInputRate) - self.rtu.connect_input_reg(self.turbine.getDumpingMode) + unit.connect_input_reg(turbine.getFlowRate) + unit.connect_input_reg(turbine.getProductionRate) + unit.connect_input_reg(turbine.getLastSteamInputRate) + unit.connect_input_reg(turbine.getDumpingMode) -- tanks - self.rtu.connect_input_reg(self.turbine.getSteam) - self.rtu.connect_input_reg(self.turbine.getSteamNeeded) - self.rtu.connect_input_reg(self.turbine.getSteamFilledPercentage) + unit.connect_input_reg(turbine.getSteam) + unit.connect_input_reg(turbine.getSteamNeeded) + unit.connect_input_reg(turbine.getSteamFilledPercentage) -- holding registers -- -- none - return self.rtu.interface() + return unit.interface() end return turbine_rtu diff --git a/rtu/dev/turbinev_rtu.lua b/rtu/dev/turbinev_rtu.lua index aa7a108..be90f8e 100644 --- a/rtu/dev/turbinev_rtu.lua +++ b/rtu/dev/turbinev_rtu.lua @@ -4,54 +4,51 @@ local turbinev_rtu = {} -- create new turbine (mek 10.1+) device ---@param turbine table -turbinev_rtu.new = function (turbine) - local self = { - rtu = rtu.init_unit(), - turbine = turbine - } +function turbinev_rtu.new(turbine) + local unit = rtu.init_unit() -- discrete inputs -- - self.rtu.connect_di(self.boiler.isFormed) + unit.connect_di(turbine.isFormed) -- coils -- - self.rtu.connect_coil(function () self.turbine.incrementDumpingMode() end, function () end) - self.rtu.connect_coil(function () self.turbine.decrementDumpingMode() end, function () end) + unit.connect_coil(function () turbine.incrementDumpingMode() end, function () end) + unit.connect_coil(function () turbine.decrementDumpingMode() end, function () end) -- input registers -- -- multiblock properties - self.rtu.connect_input_reg(self.boiler.getLength) - self.rtu.connect_input_reg(self.boiler.getWidth) - self.rtu.connect_input_reg(self.boiler.getHeight) - self.rtu.connect_input_reg(self.boiler.getMinPos) - self.rtu.connect_input_reg(self.boiler.getMaxPos) + unit.connect_input_reg(turbine.getLength) + unit.connect_input_reg(turbine.getWidth) + unit.connect_input_reg(turbine.getHeight) + unit.connect_input_reg(turbine.getMinPos) + unit.connect_input_reg(turbine.getMaxPos) -- build properties - self.rtu.connect_input_reg(self.turbine.getBlades) - self.rtu.connect_input_reg(self.turbine.getCoils) - self.rtu.connect_input_reg(self.turbine.getVents) - self.rtu.connect_input_reg(self.turbine.getDispersers) - self.rtu.connect_input_reg(self.turbine.getCondensers) - self.rtu.connect_input_reg(self.turbine.getDumpingMode) - self.rtu.connect_input_reg(self.turbine.getSteamCapacity) - self.rtu.connect_input_reg(self.turbine.getMaxEnergy) - self.rtu.connect_input_reg(self.turbine.getMaxFlowRate) - self.rtu.connect_input_reg(self.turbine.getMaxWaterOutput) - self.rtu.connect_input_reg(self.turbine.getMaxProduction) + unit.connect_input_reg(turbine.getBlades) + unit.connect_input_reg(turbine.getCoils) + unit.connect_input_reg(turbine.getVents) + unit.connect_input_reg(turbine.getDispersers) + unit.connect_input_reg(turbine.getCondensers) + unit.connect_input_reg(turbine.getDumpingMode) + unit.connect_input_reg(turbine.getSteamCapacity) + unit.connect_input_reg(turbine.getMaxEnergy) + unit.connect_input_reg(turbine.getMaxFlowRate) + unit.connect_input_reg(turbine.getMaxWaterOutput) + unit.connect_input_reg(turbine.getMaxProduction) -- current state - self.rtu.connect_input_reg(self.turbine.getFlowRate) - self.rtu.connect_input_reg(self.turbine.getProductionRate) - self.rtu.connect_input_reg(self.turbine.getLastSteamInputRate) + unit.connect_input_reg(turbine.getFlowRate) + unit.connect_input_reg(turbine.getProductionRate) + unit.connect_input_reg(turbine.getLastSteamInputRate) -- tanks/containers - self.rtu.connect_input_reg(self.turbine.getSteam) - self.rtu.connect_input_reg(self.turbine.getSteamNeeded) - self.rtu.connect_input_reg(self.turbine.getSteamFilledPercentage) - self.rtu.connect_input_reg(self.turbine.getEnergy) - self.rtu.connect_input_reg(self.turbine.getEnergyNeeded) - self.rtu.connect_input_reg(self.turbine.getEnergyFilledPercentage) + unit.connect_input_reg(turbine.getSteam) + unit.connect_input_reg(turbine.getSteamNeeded) + unit.connect_input_reg(turbine.getSteamFilledPercentage) + unit.connect_input_reg(turbine.getEnergy) + unit.connect_input_reg(turbine.getEnergyNeeded) + unit.connect_input_reg(turbine.getEnergyFilledPercentage) -- holding registers -- - self.rtu.connect_holding_reg(self.turbine.setDumpingMode, self.turbine.getDumpingMode) + unit.connect_holding_reg(turbine.setDumpingMode, turbine.getDumpingMode) - return self.rtu.interface() + return unit.interface() end return turbinev_rtu diff --git a/rtu/modbus.lua b/rtu/modbus.lua index 2654405..498dd19 100644 --- a/rtu/modbus.lua +++ b/rtu/modbus.lua @@ -9,7 +9,7 @@ local MODBUS_EXCODE = types.MODBUS_EXCODE -- new modbus comms handler object ---@param rtu_dev rtu_device|rtu_rs_device RTU device ---@param use_parallel_read boolean whether or not to use parallel calls when reading -modbus.new = function (rtu_dev, use_parallel_read) +function modbus.new(rtu_dev, use_parallel_read) local self = { rtu = rtu_dev, use_parallel = use_parallel_read @@ -23,7 +23,7 @@ modbus.new = function (rtu_dev, use_parallel_read) ---@param c_addr_start integer ---@param count integer ---@return boolean ok, table readings - local _1_read_coils = function (c_addr_start, count) + local function _1_read_coils(c_addr_start, count) local tasks = {} local readings = {} local access_fault = false @@ -69,7 +69,7 @@ modbus.new = function (rtu_dev, use_parallel_read) ---@param di_addr_start integer ---@param count integer ---@return boolean ok, table readings - local _2_read_discrete_inputs = function (di_addr_start, count) + local function _2_read_discrete_inputs(di_addr_start, count) local tasks = {} local readings = {} local access_fault = false @@ -115,7 +115,7 @@ modbus.new = function (rtu_dev, use_parallel_read) ---@param hr_addr_start integer ---@param count integer ---@return boolean ok, table readings - local _3_read_multiple_holding_registers = function (hr_addr_start, count) + local function _3_read_multiple_holding_registers(hr_addr_start, count) local tasks = {} local readings = {} local access_fault = false @@ -161,7 +161,7 @@ modbus.new = function (rtu_dev, use_parallel_read) ---@param ir_addr_start integer ---@param count integer ---@return boolean ok, table readings - local _4_read_input_registers = function (ir_addr_start, count) + local function _4_read_input_registers(ir_addr_start, count) local tasks = {} local readings = {} local access_fault = false @@ -207,7 +207,7 @@ modbus.new = function (rtu_dev, use_parallel_read) ---@param c_addr integer ---@param value any ---@return boolean ok, MODBUS_EXCODE|nil - local _5_write_single_coil = function (c_addr, value) + local function _5_write_single_coil(c_addr, value) local response = nil local _, coils, _, _ = self.rtu.io_count() local return_ok = c_addr <= coils @@ -229,7 +229,7 @@ modbus.new = function (rtu_dev, use_parallel_read) ---@param hr_addr integer ---@param value any ---@return boolean ok, MODBUS_EXCODE|nil - local _6_write_single_holding_register = function (hr_addr, value) + local function _6_write_single_holding_register(hr_addr, value) local response = nil local _, _, _, hold_regs = self.rtu.io_count() local return_ok = hr_addr <= hold_regs @@ -251,7 +251,7 @@ modbus.new = function (rtu_dev, use_parallel_read) ---@param c_addr_start integer ---@param values any ---@return boolean ok, MODBUS_EXCODE|nil - local _15_write_multiple_coils = function (c_addr_start, values) + local function _15_write_multiple_coils(c_addr_start, values) local response = nil local _, coils, _, _ = self.rtu.io_count() local count = #values @@ -278,7 +278,7 @@ modbus.new = function (rtu_dev, use_parallel_read) ---@param hr_addr_start integer ---@param values any ---@return boolean ok, MODBUS_EXCODE|nil - local _16_write_multiple_holding_registers = function (hr_addr_start, values) + local function _16_write_multiple_holding_registers(hr_addr_start, values) local response = nil local _, _, _, hold_regs = self.rtu.io_count() local count = #values @@ -305,7 +305,7 @@ modbus.new = function (rtu_dev, use_parallel_read) -- validate a request without actually executing it ---@param packet modbus_frame ---@return boolean return_code, modbus_packet reply - public.check_request = function (packet) + function public.check_request(packet) local return_code = true local response = { MODBUS_EXCODE.ACKNOWLEDGE } @@ -347,7 +347,7 @@ modbus.new = function (rtu_dev, use_parallel_read) -- handle a MODBUS TCP packet and generate a reply ---@param packet modbus_frame ---@return boolean return_code, modbus_packet reply - public.handle_packet = function (packet) + function public.handle_packet(packet) local return_code = true local response = nil @@ -402,7 +402,7 @@ modbus.new = function (rtu_dev, use_parallel_read) -- return a SERVER_DEVICE_BUSY error reply ---@return modbus_packet reply - public.reply__srv_device_busy = function (packet) + function public.reply__srv_device_busy(packet) -- reply back with error flag and exception code local reply = comms.modbus_packet() local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) @@ -413,7 +413,7 @@ modbus.new = function (rtu_dev, use_parallel_read) -- return a NEG_ACKNOWLEDGE error reply ---@return modbus_packet reply - public.reply__neg_ack = function (packet) + function public.reply__neg_ack(packet) -- reply back with error flag and exception code local reply = comms.modbus_packet() local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) @@ -424,7 +424,7 @@ modbus.new = function (rtu_dev, use_parallel_read) -- return a GATEWAY_PATH_UNAVAILABLE error reply ---@return modbus_packet reply - public.reply__gw_unavailable = function (packet) + function public.reply__gw_unavailable(packet) -- reply back with error flag and exception code local reply = comms.modbus_packet() local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 2309be5..c0d57fe 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -20,7 +20,7 @@ local print_ts = util.print_ts local println_ts = util.println_ts -- create a new RTU -rtu.init_unit = function () +function rtu.init_unit() local self = { discrete_inputs = {}, coils = {}, @@ -38,13 +38,13 @@ rtu.init_unit = function () local protected = {} -- refresh IO count - local _count_io = function () + local function _count_io() self.io_count_cache = { #self.discrete_inputs, #self.coils, #self.input_regs, #self.holding_regs } end -- return IO count ---@return integer discrete_inputs, integer coils, integer input_regs, integer holding_regs - public.io_count = function () + function public.io_count() return self.io_count_cache[1], self.io_count_cache[2], self.io_count_cache[3], self.io_count_cache[4] end @@ -53,7 +53,7 @@ rtu.init_unit = function () -- connect discrete input ---@param f function ---@return integer count count of discrete inputs - protected.connect_di = function (f) + function protected.connect_di(f) insert(self.discrete_inputs, { read = f }) _count_io() return #self.discrete_inputs @@ -62,7 +62,7 @@ rtu.init_unit = function () -- read discrete input ---@param di_addr integer ---@return any value, boolean access_fault - public.read_di = function (di_addr) + function public.read_di(di_addr) ppm.clear_fault() local value = self.discrete_inputs[di_addr].read() return value, ppm.is_faulted() @@ -74,7 +74,7 @@ rtu.init_unit = function () ---@param f_read function ---@param f_write function ---@return integer count count of coils - protected.connect_coil = function (f_read, f_write) + function protected.connect_coil(f_read, f_write) insert(self.coils, { read = f_read, write = f_write }) _count_io() return #self.coils @@ -83,7 +83,7 @@ rtu.init_unit = function () -- read coil ---@param coil_addr integer ---@return any value, boolean access_fault - public.read_coil = function (coil_addr) + function public.read_coil(coil_addr) ppm.clear_fault() local value = self.coils[coil_addr].read() return value, ppm.is_faulted() @@ -93,7 +93,7 @@ rtu.init_unit = function () ---@param coil_addr integer ---@param value any ---@return boolean access_fault - public.write_coil = function (coil_addr, value) + function public.write_coil(coil_addr, value) ppm.clear_fault() self.coils[coil_addr].write(value) return ppm.is_faulted() @@ -104,7 +104,7 @@ rtu.init_unit = function () -- connect input register ---@param f function ---@return integer count count of input registers - protected.connect_input_reg = function (f) + function protected.connect_input_reg(f) insert(self.input_regs, { read = f }) _count_io() return #self.input_regs @@ -113,7 +113,7 @@ rtu.init_unit = function () -- read input register ---@param reg_addr integer ---@return any value, boolean access_fault - public.read_input_reg = function (reg_addr) + function public.read_input_reg(reg_addr) ppm.clear_fault() local value = self.input_regs[reg_addr].read() return value, ppm.is_faulted() @@ -125,7 +125,7 @@ rtu.init_unit = function () ---@param f_read function ---@param f_write function ---@return integer count count of holding registers - protected.connect_holding_reg = function (f_read, f_write) + function protected.connect_holding_reg(f_read, f_write) insert(self.holding_regs, { read = f_read, write = f_write }) _count_io() return #self.holding_regs @@ -134,7 +134,7 @@ rtu.init_unit = function () -- read holding register ---@param reg_addr integer ---@return any value, boolean access_fault - public.read_holding_reg = function (reg_addr) + function public.read_holding_reg(reg_addr) ppm.clear_fault() local value = self.holding_regs[reg_addr].read() return value, ppm.is_faulted() @@ -144,7 +144,7 @@ rtu.init_unit = function () ---@param reg_addr integer ---@param value any ---@return boolean access_fault - public.write_holding_reg = function (reg_addr, value) + function public.write_holding_reg(reg_addr, value) ppm.clear_fault() self.holding_regs[reg_addr].write(value) return ppm.is_faulted() @@ -153,7 +153,7 @@ rtu.init_unit = function () -- public RTU device access -- get the public interface to this RTU - protected.interface = function () + function protected.interface() return public end @@ -166,7 +166,7 @@ end ---@param local_port integer ---@param server_port integer ---@param conn_watchdog watchdog -rtu.comms = function (version, modem, local_port, server_port, conn_watchdog) +function rtu.comms(version, modem, local_port, server_port, conn_watchdog) local self = { version = version, seq_num = 0, @@ -193,7 +193,7 @@ rtu.comms = function (version, modem, local_port, server_port, conn_watchdog) -- send a scada management packet ---@param msg_type SCADA_MGMT_TYPES ---@param msg table - local _send = function (msg_type, msg) + local function _send(msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() @@ -206,7 +206,7 @@ rtu.comms = function (version, modem, local_port, server_port, conn_watchdog) -- keep alive ack ---@param srv_time integer - local _send_keep_alive_ack = function (srv_time) + local function _send_keep_alive_ack(srv_time) _send(SCADA_MGMT_TYPES.KEEP_ALIVE, { srv_time, util.time() }) end @@ -214,7 +214,7 @@ rtu.comms = function (version, modem, local_port, server_port, conn_watchdog) -- send a MODBUS TCP packet ---@param m_pkt modbus_packet - public.send_modbus = function (m_pkt) + function public.send_modbus(m_pkt) local s_pkt = comms.scada_packet() s_pkt.make(self.seq_num, PROTOCOLS.MODBUS_TCP, m_pkt.raw_sendable()) self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) @@ -224,7 +224,7 @@ rtu.comms = function (version, modem, local_port, server_port, conn_watchdog) -- reconnect a newly connected modem ---@param modem table ---@diagnostic disable-next-line: redefined-local - public.reconnect_modem = function (modem) + function public.reconnect_modem(modem) self.modem = modem -- open modem @@ -235,14 +235,14 @@ rtu.comms = function (version, modem, local_port, server_port, conn_watchdog) -- unlink from the server ---@param rtu_state rtu_state - public.unlink = function (rtu_state) + function public.unlink(rtu_state) rtu_state.linked = false self.r_seq_num = nil end -- close the connection to the server ---@param rtu_state rtu_state - public.close = function (rtu_state) + function public.close(rtu_state) self.conn_watchdog.cancel() public.unlink(rtu_state) _send(SCADA_MGMT_TYPES.CLOSE, {}) @@ -250,7 +250,7 @@ rtu.comms = function (version, modem, local_port, server_port, conn_watchdog) -- send capability advertisement ---@param units table - public.send_advertisement = function (units) + function public.send_advertisement(units) local advertisement = { self.version } for i = 1, #units do @@ -282,7 +282,7 @@ rtu.comms = function (version, modem, local_port, server_port, conn_watchdog) ---@param message any ---@param distance integer ---@return modbus_frame|mgmt_frame|nil packet - public.parse_packet = function(side, sender, reply_to, message, distance) + function public.parse_packet(side, sender, reply_to, message, distance) local pkt = nil local s_pkt = comms.scada_packet() @@ -314,7 +314,7 @@ rtu.comms = function (version, modem, local_port, server_port, conn_watchdog) ---@param packet modbus_frame|mgmt_frame ---@param units table ---@param rtu_state rtu_state - public.handle_packet = function(packet, units, rtu_state) + function public.handle_packet(packet, units, rtu_state) if packet ~= nil then -- check sequence number if self.r_seq_num == nil then diff --git a/rtu/startup.lua b/rtu/startup.lua index 6700b86..d10fe54 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -4,27 +4,27 @@ require("/initenv").init_env() -local log = require("scada-common.log") +local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") -local ppm = require("scada-common.ppm") -local rsio = require("scada-common.rsio") -local types = require("scada-common.types") -local util = require("scada-common.util") +local ppm = require("scada-common.ppm") +local rsio = require("scada-common.rsio") +local types = require("scada-common.types") +local util = require("scada-common.util") -local config = require("rtu.config") -local modbus = require("rtu.modbus") -local rtu = require("rtu.rtu") +local config = require("rtu.config") +local modbus = require("rtu.modbus") +local rtu = require("rtu.rtu") local threads = require("rtu.threads") -local redstone_rtu = require("rtu.dev.redstone_rtu") -local boiler_rtu = require("rtu.dev.boiler_rtu") -local boilerv_rtu = require("rtu.dev.boilerv_rtu") +local redstone_rtu = require("rtu.dev.redstone_rtu") +local boiler_rtu = require("rtu.dev.boiler_rtu") +local boilerv_rtu = require("rtu.dev.boilerv_rtu") local energymachine_rtu = require("rtu.dev.energymachine_rtu") -local imatrix_rtu = require("rtu.dev.imatrix_rtu") -local turbine_rtu = require("rtu.dev.turbine_rtu") -local turbinev_rtu = require("rtu.dev.turbinev_rtu") +local imatrix_rtu = require("rtu.dev.imatrix_rtu") +local turbine_rtu = require("rtu.dev.turbine_rtu") +local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.7.2" +local RTU_VERSION = "beta-v0.7.3" local rtu_t = types.rtu_t diff --git a/rtu/threads.lua b/rtu/threads.lua index 3d83acf..ffe0f65 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -27,11 +27,11 @@ local COMMS_SLEEP = 100 -- (100ms, 2 ticks) -- main thread ---@param smem rtu_shared_memory -threads.thread__main = function (smem) +function threads.thread__main(smem) local public = {} ---@class thread -- execute thread - public.exec = function () + function public.exec() log.debug("main thread start") -- main loop clock @@ -155,7 +155,7 @@ threads.thread__main = function (smem) end -- execute the thread in a protected mode, retrying it on return if not shutting down - public.p_exec = function () + function public.p_exec() local rtu_state = smem.rtu_state while not rtu_state.shutdown do @@ -176,11 +176,11 @@ end -- communications handler thread ---@param smem rtu_shared_memory -threads.thread__comms = function (smem) +function threads.thread__comms(smem) local public = {} ---@class thread -- execute thread - public.exec = function () + function public.exec() log.debug("comms thread start") -- load in from shared memory @@ -227,7 +227,7 @@ threads.thread__comms = function (smem) end -- execute the thread in a protected mode, retrying it on return if not shutting down - public.p_exec = function () + function public.p_exec() local rtu_state = smem.rtu_state while not rtu_state.shutdown do @@ -249,11 +249,11 @@ end -- per-unit communications handler thread ---@param smem rtu_shared_memory ---@param unit rtu_unit_registry_entry -threads.thread__unit_comms = function (smem, unit) +function threads.thread__unit_comms(smem, unit) local public = {} ---@class thread -- execute thread - public.exec = function () + function public.exec() log.debug("rtu unit thread start -> " .. unit.name .. "(" .. unit.type .. ")") -- load in from shared memory @@ -297,7 +297,7 @@ threads.thread__unit_comms = function (smem, unit) end -- execute the thread in a protected mode, retrying it on return if not shutting down - public.p_exec = function () + function public.p_exec() local rtu_state = smem.rtu_state while not rtu_state.shutdown do From 43d5c0f8addaa2b37e4e24693e86a3e80a227b2a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 31 May 2022 15:36:17 -0400 Subject: [PATCH 236/587] #64 supervisor code cleanup --- supervisor/session/plc.lua | 42 ++++++++++++------------- supervisor/session/rtu.lua | 30 +++++++++--------- supervisor/session/rtu/boiler.lua | 16 +++++----- supervisor/session/rtu/emachine.lua | 14 ++++----- supervisor/session/rtu/redstone.lua | 28 ++++++++--------- supervisor/session/rtu/turbine.lua | 16 +++++----- supervisor/session/rtu/txnctrl.lua | 16 +++++----- supervisor/session/rtu/unit_session.lua | 28 ++++++++--------- supervisor/session/svsessions.lua | 32 +++++++++---------- supervisor/startup.lua | 8 ++--- supervisor/supervisor.lua | 18 +++++------ supervisor/unit.lua | 32 +++++++++---------- 12 files changed, 140 insertions(+), 140 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 5279208..b4e7291 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -1,7 +1,7 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") +local comms = require("scada-common.comms") +local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") -local util = require("scada-common.util") +local util = require("scada-common.util") local plc = {} @@ -41,7 +41,7 @@ local PERIODICS = { ---@param for_reactor integer ---@param in_queue mqueue ---@param out_queue mqueue -plc.new_session = function (id, for_reactor, in_queue, out_queue) +function plc.new_session(id, for_reactor, in_queue, out_queue) local log_header = "plc_session(" .. id .. "): " local self = { @@ -146,7 +146,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) -- copy in the RPS status ---@param rps_status table - local _copy_rps_status = function (rps_status) + local function _copy_rps_status(rps_status) self.sDB.rps_status.dmg_crit = rps_status[1] self.sDB.rps_status.ex_hcool = rps_status[2] self.sDB.rps_status.ex_waste = rps_status[3] @@ -158,7 +158,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) -- copy in the reactor status ---@param mek_data table - local _copy_status = function (mek_data) + local function _copy_status(mek_data) -- copy status information self.sDB.mek_status.status = mek_data[1] self.sDB.mek_status.burn_rate = mek_data[2] @@ -191,7 +191,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) -- copy in the reactor structure ---@param mek_data table - local _copy_struct = function (mek_data) + local function _copy_struct(mek_data) self.sDB.mek_struct.heat_cap = mek_data[1] self.sDB.mek_struct.fuel_asm = mek_data[2] self.sDB.mek_struct.fuel_sa = mek_data[3] @@ -203,7 +203,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- mark this PLC session as closed, stop watchdog - local _close = function () + local function _close() self.plc_conn_watchdog.cancel() self.connected = false end @@ -211,7 +211,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) -- send an RPLC packet ---@param msg_type RPLC_TYPES ---@param msg table - local _send = function (msg_type, msg) + local function _send(msg_type, msg) local s_pkt = comms.scada_packet() local r_pkt = comms.rplc_packet() @@ -225,7 +225,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) -- send a SCADA management packet ---@param msg_type SCADA_MGMT_TYPES ---@param msg table - local _send_mgmt = function (msg_type, msg) + local function _send_mgmt(msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() @@ -239,7 +239,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) -- get an ACK status ---@param pkt rplc_frame ---@return boolean|nil ack - local _get_ack = function (pkt) + local function _get_ack(pkt) if pkt.length == 1 then return pkt.data[1] else @@ -250,7 +250,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) -- handle a packet ---@param pkt rplc_frame - local _handle_packet = function (pkt) + local function _handle_packet(pkt) -- check sequence number if self.r_seq_num == nil then self.r_seq_num = pkt.scada_frame.seq_num() @@ -408,13 +408,13 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) -- PUBLIC FUNCTIONS -- -- get the session ID - public.get_id = function () return self.id end + function public.get_id() return self.id end -- get the session database - public.get_db = function () return self.sDB end + function public.get_db() return self.sDB end -- get the reactor structure - public.get_struct = function () + function public.get_struct() if self.received_struct then return self.sDB.mek_struct else @@ -423,7 +423,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- get the reactor status - public.get_status = function () + function public.get_status() if self.received_status_cache then return self.sDB.mek_status else @@ -432,12 +432,12 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- get the reactor RPS status - public.get_rps = function () + function public.get_rps() return self.sDB.rps_status end -- get the general status information - public.get_general_status = function () + function public.get_general_status() return { last_status_update = self.sDB.last_status_update, control_state = self.sDB.control_state, @@ -449,12 +449,12 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- check if a timer matches this session's watchdog - public.check_wd = function (timer) + function public.check_wd(timer) return self.plc_conn_watchdog.is_timer(timer) and self.connected end -- close the connection - public.close = function () + function public.close() _close() _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) println("connection to reactor " .. self.for_reactor .. " PLC closed by server") @@ -463,7 +463,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) -- iterate the session ---@return boolean connected - public.iterate = function () + function public.iterate() if self.connected then ------------------ -- handle queue -- diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index ff95087..5454917 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -1,14 +1,14 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") +local comms = require("scada-common.comms") +local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") -local rsio = require("scada-common.rsio") -local util = require("scada-common.util") +local rsio = require("scada-common.rsio") +local util = require("scada-common.util") -- supervisor rtu sessions (svrs) -local svrs_boiler = require("supervisor.session.rtu.boiler") +local svrs_boiler = require("supervisor.session.rtu.boiler") local svrs_emachine = require("supervisor.session.rtu.emachine") local svrs_redstone = require("supervisor.session.rtu.redstone") -local svrs_turbine = require("supervisor.session.rtu.turbine") +local svrs_turbine = require("supervisor.session.rtu.turbine") local rtu = {} @@ -46,7 +46,7 @@ local PERIODICS = { ---@param in_queue mqueue ---@param out_queue mqueue ---@param advertisement table -rtu.new_session = function (id, in_queue, out_queue, advertisement) +function rtu.new_session(id, in_queue, out_queue, advertisement) local log_header = "rtu_session(" .. id .. "): " local self = { @@ -68,7 +68,7 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) local public = {} -- parse the recorded advertisement and create unit sub-sessions - local _handle_advertisement = function () + local function _handle_advertisement() self.units = {} self.rs_io_q = {} @@ -130,7 +130,7 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) end -- mark this RTU session as closed, stop watchdog - local _close = function () + local function _close() self.rtu_conn_watchdog.cancel() self.connected = false @@ -143,7 +143,7 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) -- send a SCADA management packet ---@param msg_type SCADA_MGMT_TYPES ---@param msg table - local _send_mgmt = function (msg_type, msg) + local function _send_mgmt(msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() @@ -156,7 +156,7 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) -- handle a packet ---@param pkt modbus_frame|mgmt_frame - local _handle_packet = function (pkt) + local function _handle_packet(pkt) -- check sequence number if self.r_seq_num == nil then self.r_seq_num = pkt.scada_frame.seq_num() @@ -212,16 +212,16 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) -- PUBLIC FUNCTIONS -- -- get the session ID - public.get_id = function () return self.id end + function public.get_id() return self.id end -- check if a timer matches this session's watchdog ---@param timer number - public.check_wd = function (timer) + function public.check_wd(timer) return self.rtu_conn_watchdog.is_timer(timer) and self.connected end -- close the connection - public.close = function () + function public.close() _close() _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) println(log_header .. "connection to RTU closed by server") @@ -230,7 +230,7 @@ rtu.new_session = function (id, in_queue, out_queue, advertisement) -- iterate the session ---@return boolean connected - public.iterate = function () + function public.iterate() if self.connected then ------------------ -- handle queue -- diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua index c3fa28e..552b3e2 100644 --- a/supervisor/session/rtu/boiler.lua +++ b/supervisor/session/rtu/boiler.lua @@ -1,5 +1,5 @@ local comms = require("scada-common.comms") -local log = require("scada-common.log") +local log = require("scada-common.log") local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") @@ -32,7 +32,7 @@ local PERIODICS = { ---@param unit_id integer ---@param advert rtu_advertisement ---@param out_queue mqueue -boiler.new = function (session_id, unit_id, advert, out_queue) +function boiler.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPES.BOILER then log.error("attempt to instantiate boiler RTU for type '" .. advert.type .. "'. this is a bug.") @@ -86,19 +86,19 @@ boiler.new = function (session_id, unit_id, advert, out_queue) -- PRIVATE FUNCTIONS -- -- query the build of the device - local _request_build = function () + local function _request_build() -- read input registers 1 through 7 (start = 1, count = 7) self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 7 }) end -- query the state of the device - local _request_state = function () + local function _request_state() -- read input registers 8 through 9 (start = 8, count = 2) self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 8, 2 }) end -- query the tanks of the device - local _request_tanks = function () + local function _request_tanks() -- read input registers 10 through 21 (start = 10, count = 12) self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 10, 12 }) end @@ -107,7 +107,7 @@ boiler.new = function (session_id, unit_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame - public.handle_packet = function (m_pkt) + function public.handle_packet(m_pkt) local txn_type = self.session.try_resolve(m_pkt.txn_id) if txn_type == false then -- nothing to do @@ -162,7 +162,7 @@ boiler.new = function (session_id, unit_id, advert, out_queue) -- update this runner ---@param time_now integer milliseconds - public.update = function (time_now) + function public.update(time_now) if not self.periodics.has_build and self.periodics.next_build_req <= time_now then _request_build() self.periodics.next_build_req = time_now + PERIODICS.BUILD @@ -180,7 +180,7 @@ boiler.new = function (session_id, unit_id, advert, out_queue) end -- get the unit session database - public.get_db = function () return self.db end + function public.get_db() return self.db end return public end diff --git a/supervisor/session/rtu/emachine.lua b/supervisor/session/rtu/emachine.lua index e47293a..525fa07 100644 --- a/supervisor/session/rtu/emachine.lua +++ b/supervisor/session/rtu/emachine.lua @@ -1,5 +1,5 @@ local comms = require("scada-common.comms") -local log = require("scada-common.log") +local log = require("scada-common.log") local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") @@ -29,7 +29,7 @@ local PERIODICS = { ---@param unit_id integer ---@param advert rtu_advertisement ---@param out_queue mqueue -emachine.new = function (session_id, unit_id, advert, out_queue) +function emachine.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPES.EMACHINE then log.error("attempt to instantiate emachine RTU for type '" .. advert.type .. "'. this is a bug.") @@ -63,13 +63,13 @@ emachine.new = function (session_id, unit_id, advert, out_queue) -- PRIVATE FUNCTIONS -- -- query the build of the device - local _request_build = function () + local function _request_build() -- read input register 1 (start = 1, count = 1) self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 1 }) end -- query the state of the energy storage - local _request_storage = function () + local function _request_storage() -- read input registers 2 through 4 (start = 2, count = 3) self.session.send_request(TXN_TYPES.STORAGE, MODBUS_FCODE.READ_INPUT_REGS, { 2, 3 }) end @@ -78,7 +78,7 @@ emachine.new = function (session_id, unit_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame - public.handle_packet = function (m_pkt) + function public.handle_packet(m_pkt) local txn_type = self.session.try_resolve(m_pkt.txn_id) if txn_type == false then -- nothing to do @@ -107,7 +107,7 @@ emachine.new = function (session_id, unit_id, advert, out_queue) -- update this runner ---@param time_now integer milliseconds - public.update = function (time_now) + function public.update(time_now) if not self.has_build and self.periodics.next_build_req <= time_now then _request_build() self.periodics.next_build_req = time_now + PERIODICS.BUILD @@ -120,7 +120,7 @@ emachine.new = function (session_id, unit_id, advert, out_queue) end -- get the unit session database - public.get_db = function () return self.db end + function public.get_db() return self.db end return public end diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index b41e223..e870201 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -1,9 +1,9 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local mqueue= require("scada-common.mqueue") -local rsio = require("scada-common.rsio") -local types = require("scada-common.types") -local util = require("scada-common.util") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local rsio = require("scada-common.rsio") +local types = require("scada-common.types") +local util = require("scada-common.util") local unit_session = require("supervisor.session.rtu.unit_session") @@ -50,7 +50,7 @@ local PERIODICS = { ---@param unit_id integer ---@param advert rtu_advertisement ---@param out_queue mqueue -redstone.new = function (session_id, unit_id, advert, out_queue) +function redstone.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPES.REDSTONE then log.error("attempt to instantiate redstone RTU for type '" .. advert.type .. "'. this is a bug.") @@ -113,22 +113,22 @@ redstone.new = function (session_id, unit_id, advert, out_queue) -- PRIVATE FUNCTIONS -- -- query discrete inputs - local _request_discrete_inputs = function () + local function _request_discrete_inputs() self.session.send_request(TXN_TYPES.DI_READ, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, #self.io_list.digital_in }) end -- query input registers - local _request_input_registers = function () + local function _request_input_registers() self.session.send_request(TXN_TYPES.INPUT_REG_READ, MODBUS_FCODE.READ_INPUT_REGS, { 1, #self.io_list.analog_in }) end -- write coil output - local _write_coil = function (coil, value) + local function _write_coil(coil, value) self.session.send_request(TXN_TYPES.COIL_WRITE, MODBUS_FCODE.WRITE_MUL_COILS, { coil, value }) end -- write holding register output - local _write_holding_register = function (reg, value) + local function _write_holding_register(reg, value) self.session.send_request(TXN_TYPES.HOLD_REG_WRITE, MODBUS_FCODE.WRITE_MUL_HOLD_REGS, { reg, value }) end @@ -136,7 +136,7 @@ redstone.new = function (session_id, unit_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame - public.handle_packet = function (m_pkt) + function public.handle_packet(m_pkt) local txn_type = self.session.try_resolve(m_pkt.txn_id) if txn_type == false then -- nothing to do @@ -173,7 +173,7 @@ redstone.new = function (session_id, unit_id, advert, out_queue) -- update this runner ---@param time_now integer milliseconds - public.update = function (time_now) + function public.update(time_now) -- check command queue while self.in_q.ready() do -- get a new message to process @@ -246,7 +246,7 @@ redstone.new = function (session_id, unit_id, advert, out_queue) end -- get the unit session database - public.get_db = function () return self.db end + function public.get_db() return self.db end return public, self.in_q end diff --git a/supervisor/session/rtu/turbine.lua b/supervisor/session/rtu/turbine.lua index d62626e..913f007 100644 --- a/supervisor/session/rtu/turbine.lua +++ b/supervisor/session/rtu/turbine.lua @@ -1,5 +1,5 @@ local comms = require("scada-common.comms") -local log = require("scada-common.log") +local log = require("scada-common.log") local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") @@ -33,7 +33,7 @@ local PERIODICS = { ---@param unit_id integer ---@param advert rtu_advertisement ---@param out_queue mqueue -turbine.new = function (session_id, unit_id, advert, out_queue) +function turbine.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPES.TURBINE then log.error("attempt to instantiate turbine RTU for type '" .. advert.type .. "'. this is a bug.") @@ -82,19 +82,19 @@ turbine.new = function (session_id, unit_id, advert, out_queue) -- PRIVATE FUNCTIONS -- -- query the build of the device - local _request_build = function () + local function _request_build() -- read input registers 1 through 9 (start = 1, count = 9) self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 9 }) end -- query the state of the device - local _request_state = function () + local function _request_state() -- read input registers 10 through 13 (start = 10, count = 4) self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 10, 4 }) end -- query the tanks of the device - local _request_tanks = function () + local function _request_tanks() -- read input registers 14 through 16 (start = 14, count = 3) self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 14, 3 }) end @@ -103,7 +103,7 @@ turbine.new = function (session_id, unit_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame - public.handle_packet = function (m_pkt) + function public.handle_packet(m_pkt) local txn_type = self.session.try_resolve(m_pkt.txn_id) if txn_type == false then -- nothing to do @@ -150,7 +150,7 @@ turbine.new = function (session_id, unit_id, advert, out_queue) -- update this runner ---@param time_now integer milliseconds - public.update = function (time_now) + function public.update(time_now) if not self.has_build and self.periodics.next_build_req <= time_now then _request_build() self.periodics.next_build_req = time_now + PERIODICS.BUILD @@ -168,7 +168,7 @@ turbine.new = function (session_id, unit_id, advert, out_queue) end -- get the unit session database - public.get_db = function () return self.db end + function public.get_db() return self.db end return public end diff --git a/supervisor/session/rtu/txnctrl.lua b/supervisor/session/rtu/txnctrl.lua index a19dee6..2ebb526 100644 --- a/supervisor/session/rtu/txnctrl.lua +++ b/supervisor/session/rtu/txnctrl.lua @@ -9,7 +9,7 @@ local txnctrl = {} local TIMEOUT = 2000 -- 2000ms max wait -- create a new transaction controller -txnctrl.new = function () +function txnctrl.new() local self = { list = {}, next_id = 0 @@ -21,19 +21,19 @@ txnctrl.new = function () local insert = table.insert -- get the length of the transaction list - public.length = function () + function public.length() return #self.list end -- check if there are no active transactions - public.empty = function () + function public.empty() return #self.list == 0 end -- create a new transaction of the given type ---@param txn_type integer ---@return integer txn_id - public.create = function (txn_type) + function public.create(txn_type) local txn_id = self.next_id insert(self.list, { @@ -50,7 +50,7 @@ txnctrl.new = function () -- mark a transaction as resolved to get its transaction type ---@param txn_id integer ---@return integer txn_type - public.resolve = function (txn_id) + function public.resolve(txn_id) local txn_type = nil for i = 1, public.length() do @@ -66,7 +66,7 @@ txnctrl.new = function () -- renew a transaction by re-inserting it with its ID and type ---@param txn_id integer ---@param txn_type integer - public.renew = function (txn_id, txn_type) + function public.renew(txn_id, txn_type) insert(self.list, { txn_id = txn_id, txn_type = txn_type, @@ -75,13 +75,13 @@ txnctrl.new = function () end -- close timed-out transactions - public.cleanup = function () + function public.cleanup() local now = util.time() util.filter_table(self.list, function (txn) return txn.expiry > now end) end -- clear the transaction list - public.clear = function () + function public.clear() self.list = {} end diff --git a/supervisor/session/rtu/unit_session.lua b/supervisor/session/rtu/unit_session.lua index 83a0766..ee7a217 100644 --- a/supervisor/session/rtu/unit_session.lua +++ b/supervisor/session/rtu/unit_session.lua @@ -1,5 +1,5 @@ local comms = require("scada-common.comms") -local log = require("scada-common.log") +local log = require("scada-common.log") local types = require("scada-common.types") local txnctrl = require("supervisor.session.rtu.txnctrl") @@ -16,7 +16,7 @@ local MODBUS_EXCODE = types.MODBUS_EXCODE ---@param out_queue mqueue send queue ---@param log_tag string logging tag ---@param txn_tags table transaction log tags -unit_session.new = function (unit_id, advert, out_queue, log_tag, txn_tags) +function unit_session.new(unit_id, advert, out_queue, log_tag, txn_tags) local self = { log_tag = log_tag, txn_tags = txn_tags, @@ -41,7 +41,7 @@ unit_session.new = function (unit_id, advert, out_queue, log_tag, txn_tags) ---@param txn_type integer transaction type ---@param f_code MODBUS_FCODE function code ---@param register_param table register range or register and values - protected.send_request = function (txn_type, f_code, register_param) + function protected.send_request(txn_type, f_code, register_param) local m_pkt = comms.modbus_packet() local txn_id = self.transaction_controller.create(txn_type) @@ -53,7 +53,7 @@ unit_session.new = function (unit_id, advert, out_queue, log_tag, txn_tags) -- try to resolve a MODBUS transaction ---@param m_pkt modbus_frame MODBUS packet ---@return integer|false txn_type transaction type or false on error/busy - protected.try_resolve = function (m_pkt) + function protected.try_resolve(m_pkt) if m_pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then if m_pkt.unit_id == self.unit_id then local txn_type = self.transaction_controller.resolve(m_pkt.txn_id) @@ -112,42 +112,42 @@ unit_session.new = function (unit_id, advert, out_queue, log_tag, txn_tags) end -- get the public interface - protected.get = function () return public end + function protected.get() return public end -- PUBLIC FUNCTIONS -- -- get the unit ID - public.get_unit_id = function () return self.unit_id end + function public.get_unit_id() return self.unit_id end -- get the device index - public.get_device_idx = function () return self.device_index end + function public.get_device_idx() return self.device_index end -- get the reactor ID - public.get_reactor = function () return self.reactor end + function public.get_reactor() return self.reactor end -- close this unit - public.close = function () self.connected = false end + function public.close() self.connected = false end -- check if this unit is connected - public.is_connected = function () return self.connected end + function public.is_connected() return self.connected end -- check if this unit is faulted - public.is_faulted = function () return self.device_fail end + function public.is_faulted() return self.device_fail end -- PUBLIC TEMPLATE FUNCTIONS -- -- handle a packet ---@param m_pkt modbus_frame ---@diagnostic disable-next-line: unused-local - public.handle_packet = function (m_pkt) + function public.handle_packet(m_pkt) log.debug("template unit_session.handle_packet() called", true) end -- update this runner ---@param time_now integer milliseconds ---@diagnostic disable-next-line: unused-local - public.update = function (time_now) + function public.update(time_now) log.debug("template unit_session.update() called", true) end -- get the unit session database - public.get_db = function () return {} end + function public.get_db() return {} end return protected end diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 02bf70b..1077661 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -1,10 +1,10 @@ -local log = require("scada-common.log") +local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") -local util = require("scada-common.util") +local util = require("scada-common.util") local coordinator = require("supervisor.session.coordinator") -local plc = require("supervisor.session.plc") -local rtu = require("supervisor.session.rtu") +local plc = require("supervisor.session.plc") +local rtu = require("supervisor.session.rtu") -- Supervisor Sessions Handler @@ -121,14 +121,14 @@ end -- link the modem ---@param modem table -svsessions.link_modem = function (modem) +function svsessions.link_modem(modem) self.modem = modem end -- find an RTU session by the remote port ---@param remote_port integer ---@return rtu_session_struct|nil -svsessions.find_rtu_session = function (remote_port) +function svsessions.find_rtu_session(remote_port) -- check RTU sessions return _find_session(self.rtu_sessions, remote_port) end @@ -136,7 +136,7 @@ end -- find a PLC session by the remote port ---@param remote_port integer ---@return plc_session_struct|nil -svsessions.find_plc_session = function (remote_port) +function svsessions.find_plc_session(remote_port) -- check PLC sessions return _find_session(self.plc_sessions, remote_port) end @@ -144,7 +144,7 @@ end -- find a PLC/RTU session by the remote port ---@param remote_port integer ---@return plc_session_struct|rtu_session_struct|nil -svsessions.find_device_session = function (remote_port) +function svsessions.find_device_session(remote_port) -- check RTU sessions local s = _find_session(self.rtu_sessions, remote_port) @@ -157,7 +157,7 @@ end -- find a coordinator session by the remote port ---@param remote_port integer ---@return nil -svsessions.find_coord_session = function (remote_port) +function svsessions.find_coord_session(remote_port) -- check coordinator sessions return _find_session(self.coord_sessions, remote_port) end @@ -165,7 +165,7 @@ end -- get a session by reactor ID ---@param reactor integer ---@return plc_session_struct|nil session -svsessions.get_reactor_session = function (reactor) +function svsessions.get_reactor_session(reactor) local session = nil for i = 1, #self.plc_sessions do @@ -183,7 +183,7 @@ end ---@param for_reactor integer ---@param version string ---@return integer|false session_id -svsessions.establish_plc_session = function (local_port, remote_port, for_reactor, version) +function svsessions.establish_plc_session(local_port, remote_port, for_reactor, version) if svsessions.get_reactor_session(for_reactor) == nil then ---@class plc_session_struct local plc_s = { @@ -217,7 +217,7 @@ end ---@param remote_port integer ---@param advertisement table ---@return integer session_id -svsessions.establish_rtu_session = function (local_port, remote_port, advertisement) +function svsessions.establish_rtu_session(local_port, remote_port, advertisement) -- pull version from advertisement local version = table.remove(advertisement, 1) @@ -245,7 +245,7 @@ end -- attempt to identify which session's watchdog timer fired ---@param timer_event number -svsessions.check_all_watchdogs = function (timer_event) +function svsessions.check_all_watchdogs(timer_event) -- check RTU session watchdogs _check_watchdogs(self.rtu_sessions, timer_event) @@ -257,7 +257,7 @@ svsessions.check_all_watchdogs = function (timer_event) end -- iterate all sessions -svsessions.iterate_all = function () +function svsessions.iterate_all() -- iterate RTU sessions _iterate(self.rtu_sessions) @@ -269,7 +269,7 @@ svsessions.iterate_all = function () end -- delete all closed sessions -svsessions.free_all_closed = function () +function svsessions.free_all_closed() -- free closed RTU sessions _free_closed(self.rtu_sessions) @@ -281,7 +281,7 @@ svsessions.free_all_closed = function () end -- close all open connections -svsessions.close_all = function () +function svsessions.close_all() -- close sessions _close(self.rtu_sessions) _close(self.plc_sessions) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 1dcf7a7..8bffd4f 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -4,16 +4,16 @@ require("/initenv").init_env() -local log = require("scada-common.log") -local ppm = require("scada-common.ppm") +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") local util = require("scada-common.util") local svsessions = require("supervisor.session.svsessions") -local config = require("supervisor.config") +local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.4.1" +local SUPERVISOR_VERSION = "beta-v0.4.2" local print = util.print local println = util.println diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index ba07d31..23c4a9e 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -1,6 +1,6 @@ local comms = require("scada-common.comms") -local log = require("scada-common.log") -local util = require("scada-common.util") +local log = require("scada-common.log") +local util = require("scada-common.util") local svsessions = require("supervisor.session.svsessions") @@ -25,7 +25,7 @@ local println_ts = util.println_ts ---@param modem table ---@param dev_listen integer ---@param coord_listen integer -supervisor.comms = function (version, num_reactors, modem, dev_listen, coord_listen) +function supervisor.comms(version, num_reactors, modem, dev_listen, coord_listen) local self = { version = version, num_reactors = num_reactors, @@ -41,7 +41,7 @@ supervisor.comms = function (version, num_reactors, modem, dev_listen, coord_lis -- PRIVATE FUNCTIONS -- -- open all channels - local _open_channels = function () + local function _open_channels() if not self.modem.isOpen(self.dev_listen) then self.modem.open(self.dev_listen) end @@ -60,7 +60,7 @@ supervisor.comms = function (version, num_reactors, modem, dev_listen, coord_lis -- send PLC link request responses ---@param dest integer ---@param msg table - local _send_plc_linking = function (seq_id, dest, msg) + local function _send_plc_linking(seq_id, dest, msg) local s_pkt = comms.scada_packet() local r_pkt = comms.rplc_packet() @@ -73,7 +73,7 @@ supervisor.comms = function (version, num_reactors, modem, dev_listen, coord_lis -- send RTU advertisement responses ---@param seq_id integer ---@param dest integer - local _send_remote_linked = function (seq_id, dest) + local function _send_remote_linked(seq_id, dest) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() @@ -88,7 +88,7 @@ supervisor.comms = function (version, num_reactors, modem, dev_listen, coord_lis -- reconnect a newly connected modem ---@param modem table ---@diagnostic disable-next-line: redefined-local - public.reconnect_modem = function (modem) + function public.reconnect_modem(modem) self.modem = modem svsessions.link_modem(self.modem) _open_channels() @@ -101,7 +101,7 @@ supervisor.comms = function (version, num_reactors, modem, dev_listen, coord_lis ---@param message any ---@param distance integer ---@return modbus_frame|rplc_frame|mgmt_frame|coord_frame|nil packet - public.parse_packet = function(side, sender, reply_to, message, distance) + function public.parse_packet(side, sender, reply_to, message, distance) local pkt = nil local s_pkt = comms.scada_packet() @@ -143,7 +143,7 @@ supervisor.comms = function (version, num_reactors, modem, dev_listen, coord_lis -- handle a packet ---@param packet modbus_frame|rplc_frame|mgmt_frame|coord_frame - public.handle_packet = function(packet) + function public.handle_packet(packet) if packet ~= nil then local l_port = packet.scada_frame.local_port() local r_port = packet.scada_frame.remote_port() diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 628de67..33f5853 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -23,7 +23,7 @@ local DT_KEYS = { ---@param for_reactor integer reactor unit number ---@param num_boilers integer number of boilers expected ---@param num_turbines integer number of turbines expected -unit.new = function (for_reactor, num_boilers, num_turbines) +function unit.new(for_reactor, num_boilers, num_turbines) local self = { r_id = for_reactor, plc_s = nil, ---@class plc_session @@ -82,7 +82,7 @@ unit.new = function (for_reactor, num_boilers, num_turbines) -- compute a change with respect to time of the given value ---@param key string value key ---@param value number value - local _compute_dt = function (key, value) + local function _compute_dt(key, value) if self.deltas[key] then local data = self.deltas[key] @@ -101,14 +101,14 @@ unit.new = function (for_reactor, num_boilers, num_turbines) -- clear a delta ---@param key string value key - local _reset_dt = function (key) + local function _reset_dt(key) self.deltas[key] = nil end -- get the delta t of a value ---@param key string value key ---@return number - local _get_dt = function (key) + local function _get_dt(key) if self.deltas[key] then return self.deltas[key].dt else @@ -117,7 +117,7 @@ unit.new = function (for_reactor, num_boilers, num_turbines) end -- update all delta computations - local _dt__compute_all = function () + local function _dt__compute_all() if self.plc_s ~= nil then local plc_db = self.plc_s.get_db() @@ -151,7 +151,7 @@ unit.new = function (for_reactor, num_boilers, num_turbines) end -- update the annunciator - local _update_annunciator = function () + local function _update_annunciator() -- update deltas _dt__compute_all() @@ -312,7 +312,7 @@ unit.new = function (for_reactor, num_boilers, num_turbines) -- unlink disconnected units ---@param sessions table - local _unlink_disconnected_units = function (sessions) + local function _unlink_disconnected_units(sessions) util.filter_table(sessions, function (u) return u.is_connected() end) end @@ -320,7 +320,7 @@ unit.new = function (for_reactor, num_boilers, num_turbines) -- link the PLC ---@param plc_session plc_session_struct - public.link_plc_session = function (plc_session) + function public.link_plc_session(plc_session) self.plc_s = plc_session -- reset deltas @@ -333,7 +333,7 @@ unit.new = function (for_reactor, num_boilers, num_turbines) -- link a turbine RTU session ---@param turbine unit_session - public.add_turbine = function (turbine) + function public.add_turbine(turbine) if #self.turbines < self.num_turbines and turbine.get_device_idx() <= self.num_turbines then table.insert(self.turbines, turbine) @@ -349,7 +349,7 @@ unit.new = function (for_reactor, num_boilers, num_turbines) -- link a boiler RTU session ---@param boiler unit_session - public.add_boiler = function (boiler) + function public.add_boiler(boiler) if #self.boilers < self.num_boilers and boiler.get_device_idx() <= self.num_boilers then table.insert(self.boilers, boiler) @@ -366,7 +366,7 @@ unit.new = function (for_reactor, num_boilers, num_turbines) end -- link a redstone RTU capability - public.add_redstone = function (field, accessor) + function public.add_redstone(field, accessor) -- ensure field exists if self.redstone[field] == nil then self.redstone[field] = {} @@ -377,7 +377,7 @@ unit.new = function (for_reactor, num_boilers, num_turbines) end -- update (iterate) this unit - public.update = function () + function public.update() -- unlink PLC if session was closed if not self.plc_s.open then self.plc_s = nil @@ -392,7 +392,7 @@ unit.new = function (for_reactor, num_boilers, num_turbines) end -- get build properties of all machines - public.get_build = function () + function public.get_build() local build = {} if self.plc_s ~= nil then @@ -413,7 +413,7 @@ unit.new = function (for_reactor, num_boilers, num_turbines) end -- get reactor status - public.get_reactor_status = function () + function public.get_reactor_status() local status = {} if self.plc_s ~= nil then @@ -427,7 +427,7 @@ unit.new = function (for_reactor, num_boilers, num_turbines) end -- get RTU statuses - public.get_rtu_statuses = function () + function public.get_rtu_statuses() local status = {} -- status of boilers (including tanks) @@ -452,7 +452,7 @@ unit.new = function (for_reactor, num_boilers, num_turbines) end -- get the annunciator status - public.get_annunciator = function () return self.db.annunciator end + function public.get_annunciator() return self.db.annunciator end return public end From e52b76aa246d715a274f78fc314d83de104ce372 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 31 May 2022 15:40:17 -0400 Subject: [PATCH 237/587] supervisor unit sessions now actually call txnctrl.cleanup --- supervisor/session/rtu/boiler.lua | 2 ++ supervisor/session/rtu/emachine.lua | 2 ++ supervisor/session/rtu/redstone.lua | 2 ++ supervisor/session/rtu/turbine.lua | 2 ++ supervisor/session/rtu/unit_session.lua | 5 +++++ supervisor/startup.lua | 2 +- 6 files changed, 14 insertions(+), 1 deletion(-) diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua index 552b3e2..f34dbc3 100644 --- a/supervisor/session/rtu/boiler.lua +++ b/supervisor/session/rtu/boiler.lua @@ -177,6 +177,8 @@ function boiler.new(session_id, unit_id, advert, out_queue) _request_tanks() self.periodics.next_tanks_req = time_now + PERIODICS.TANKS end + + self.session.post_update() end -- get the unit session database diff --git a/supervisor/session/rtu/emachine.lua b/supervisor/session/rtu/emachine.lua index 525fa07..fa34d21 100644 --- a/supervisor/session/rtu/emachine.lua +++ b/supervisor/session/rtu/emachine.lua @@ -117,6 +117,8 @@ function emachine.new(session_id, unit_id, advert, out_queue) _request_storage() self.periodics.next_storage_req = time_now + PERIODICS.STORAGE end + + self.session.post_update() end -- get the unit session database diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index e870201..3200eef 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -243,6 +243,8 @@ function redstone.new(session_id, unit_id, advert, out_queue) self.periodics.next_ir_req = time_now + PERIODICS.INPUT_READ end end + + self.session.post_update() end -- get the unit session database diff --git a/supervisor/session/rtu/turbine.lua b/supervisor/session/rtu/turbine.lua index 913f007..c6578fa 100644 --- a/supervisor/session/rtu/turbine.lua +++ b/supervisor/session/rtu/turbine.lua @@ -165,6 +165,8 @@ function turbine.new(session_id, unit_id, advert, out_queue) _request_tanks() self.periodics.next_tanks_req = time_now + PERIODICS.TANKS end + + self.session.post_update() end -- get the unit session database diff --git a/supervisor/session/rtu/unit_session.lua b/supervisor/session/rtu/unit_session.lua index ee7a217..ba2e988 100644 --- a/supervisor/session/rtu/unit_session.lua +++ b/supervisor/session/rtu/unit_session.lua @@ -111,6 +111,11 @@ function unit_session.new(unit_id, advert, out_queue, log_tag, txn_tags) return false end + -- post update tasks + function protected.post_update() + self.transaction_controller.cleanup() + end + -- get the public interface function protected.get() return public end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 8bffd4f..1e77fa9 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.4.2" +local SUPERVISOR_VERSION = "beta-v0.4.3" local print = util.print local println = util.println From ccc5220ca87662449df61f49e66e689e36d04deb Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 31 May 2022 15:55:40 -0400 Subject: [PATCH 238/587] util round and trinary --- scada-common/util.lua | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/scada-common/util.lua b/scada-common/util.lua index 7073d38..5bbe2b8 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -5,6 +5,17 @@ ---@class util local util = {} +-- OPERATORS -- + +-- trinary operator +---@param cond boolean condition +---@param a any return if true +---@param b any return if false +---@return any value +util.trinary = function (cond, a, b) + if cond then return a else return b end +end + -- PRINT -- -- print @@ -56,6 +67,9 @@ util.concat = function (...) return str end +-- alias +util.c = util.concat + -- sprintf implementation ---@param format string ---@vararg any @@ -63,6 +77,14 @@ util.sprintf = function (format, ...) return string.format(format, table.unpack(arg)) end +-- MATH -- + +-- round a number to an integer +---@return integer rounded +util.round = function (x) + return math.floor(x + 0.5) +end + -- TIME -- -- current time From 341df1a7392c8b2a8b5194e0185f72db4dbe0c08 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 31 May 2022 16:05:05 -0400 Subject: [PATCH 239/587] simplification of initenv file --- initenv.lua | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/initenv.lua b/initenv.lua index c66e4c3..fffb705 100644 --- a/initenv.lua +++ b/initenv.lua @@ -2,17 +2,17 @@ -- Initialize the Post-Boot Module Environment -- --- initialize booted environment -local init_env = function () - local _require = require("cc.require") - local _env = setmetatable({}, { __index = _ENV }) +return { + -- initialize booted environment + init_env = function () + local _require = require("cc.require") + local _env = setmetatable({}, { __index = _ENV }) - -- overwrite require/package globals - require, package = _require.make(_env, "/") + -- overwrite require/package globals + require, package = _require.make(_env, "/") - -- reset terminal - term.clear() - term.setCursorPos(1, 1) -end - -return { init_env = init_env } + -- reset terminal + term.clear() + term.setCursorPos(1, 1) + end +} From 3bb95eb441b7506138b603d17232a1daff3211b7 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 31 May 2022 16:09:06 -0400 Subject: [PATCH 240/587] #64 util code cleanup --- coordinator/startup.lua | 2 +- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- scada-common/alarm.lua | 10 ++--- scada-common/comms.lua | 88 ++++++++++++++++++++--------------------- scada-common/crypto.lua | 23 ++++++----- scada-common/log.lua | 22 +++++------ scada-common/mqueue.lua | 18 ++++----- scada-common/ppm.lua | 58 +++++++++++++-------------- scada-common/rsio.lua | 20 +++++----- scada-common/util.lua | 48 +++++++++++----------- supervisor/startup.lua | 2 +- 12 files changed, 147 insertions(+), 148 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 25a6c5e..66a58ea 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -12,7 +12,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.1.2" +local COORDINATOR_VERSION = "alpha-v0.1.3" local print = util.print local println = util.println diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index e0aeeba..48dad46 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.7.4" +local R_PLC_VERSION = "beta-v0.7.5" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index d10fe54..3c01df3 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -24,7 +24,7 @@ local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.7.3" +local RTU_VERSION = "beta-v0.7.4" local rtu_t = types.rtu_t diff --git a/scada-common/alarm.lua b/scada-common/alarm.lua index 2fcaa19..0a3038c 100644 --- a/scada-common/alarm.lua +++ b/scada-common/alarm.lua @@ -17,7 +17,7 @@ alarm.SEVERITY = SEVERITY -- severity integer to string ---@param severity SEVERITY -alarm.severity_to_string = function (severity) +function alarm.severity_to_string(severity) if severity == SEVERITY.INFO then return "INFO" elseif severity == SEVERITY.WARNING then @@ -39,7 +39,7 @@ end ---@param severity SEVERITY ---@param device string ---@param message string -alarm.scada_alarm = function (severity, device, message) +function alarm.scada_alarm(severity, device, message) local self = { time = util.time(), ts_string = os.date("[%H:%M:%S]"), @@ -53,12 +53,12 @@ alarm.scada_alarm = function (severity, device, message) -- format the alarm as a string ---@return string message - public.format = function () - return self.ts_string .. " [" .. alarm.severity_to_string(self.severity) .. "] (" .. self.device ") >> " .. self.message + function public.format() + return self.ts_string .. " [" .. alarm.severity_to_string(self.severity) .. "] (" .. self.device .. ") >> " .. self.message end -- get alarm properties - public.properties = function () + function public.properties() return { time = self.time, severity = self.severity, diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 775127f..30cadcf 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -2,7 +2,7 @@ -- Communications -- -local log = require("scada-common.log") +local log = require("scada-common.log") local types = require("scada-common.types") ---@class comms @@ -66,7 +66,7 @@ comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES comms.RTU_UNIT_TYPES = RTU_UNIT_TYPES -- generic SCADA packet object -comms.scada_packet = function () +function comms.scada_packet() local self = { modem_msg_in = nil, valid = false, @@ -84,7 +84,7 @@ comms.scada_packet = function () ---@param seq_num integer ---@param protocol PROTOCOLS ---@param payload table - public.make = function (seq_num, protocol, payload) + function public.make(seq_num, protocol, payload) self.valid = true self.seq_num = seq_num self.protocol = protocol @@ -99,7 +99,7 @@ comms.scada_packet = function () ---@param reply_to integer ---@param message any ---@param distance integer - public.receive = function (side, sender, reply_to, message, distance) + function public.receive(side, sender, reply_to, message, distance) self.modem_msg_in = { iface = side, s_port = sender, @@ -125,25 +125,25 @@ comms.scada_packet = function () -- public accessors -- - public.modem_event = function () return self.modem_msg_in end - public.raw_sendable = function () return self.raw end + function public.modem_event() return self.modem_msg_in end + function public.raw_sendable() return self.raw end - public.local_port = function () return self.modem_msg_in.s_port end - public.remote_port = function () return self.modem_msg_in.r_port end + function public.local_port() return self.modem_msg_in.s_port end + function public.remote_port() return self.modem_msg_in.r_port end - public.is_valid = function () return self.valid end + function public.is_valid() return self.valid end - public.seq_num = function () return self.seq_num end - public.protocol = function () return self.protocol end - public.length = function () return self.length end - public.data = function () return self.payload end + function public.seq_num() return self.seq_num end + function public.protocol() return self.protocol end + function public.length() return self.length end + function public.data() return self.payload end return public end -- MODBUS packet -- modeled after MODBUS TCP packet -comms.modbus_packet = function () +function comms.modbus_packet() local self = { frame = nil, raw = nil, @@ -162,7 +162,7 @@ comms.modbus_packet = function () ---@param unit_id integer ---@param func_code MODBUS_FCODE ---@param data table - public.make = function (txn_id, unit_id, func_code, data) + function public.make(txn_id, unit_id, func_code, data) self.txn_id = txn_id self.length = #data self.unit_id = unit_id @@ -179,7 +179,7 @@ comms.modbus_packet = function () -- decode a MODBUS packet from a SCADA frame ---@param frame scada_packet ---@return boolean success - public.decode = function (frame) + function public.decode(frame) if frame then self.frame = frame @@ -203,10 +203,10 @@ comms.modbus_packet = function () end -- get raw to send - public.raw_sendable = function () return self.raw end + function public.raw_sendable() return self.raw end -- get this packet as a frame with an immutable relation to this object - public.get = function () + function public.get() ---@class modbus_frame local frame = { scada_frame = self.frame, @@ -224,7 +224,7 @@ comms.modbus_packet = function () end -- reactor PLC packet -comms.rplc_packet = function () +function comms.rplc_packet() local self = { frame = nil, raw = nil, @@ -238,7 +238,7 @@ comms.rplc_packet = function () local public = {} -- check that type is known - local _rplc_type_valid = function () + local function _rplc_type_valid() return self.type == RPLC_TYPES.LINK_REQ or self.type == RPLC_TYPES.STATUS or self.type == RPLC_TYPES.MEK_STRUCT or @@ -254,7 +254,7 @@ comms.rplc_packet = function () ---@param id integer ---@param packet_type RPLC_TYPES ---@param data table - public.make = function (id, packet_type, data) + function public.make(id, packet_type, data) -- packet accessor properties self.id = id self.type = packet_type @@ -271,7 +271,7 @@ comms.rplc_packet = function () -- decode an RPLC packet from a SCADA frame ---@param frame scada_packet ---@return boolean success - public.decode = function (frame) + function public.decode(frame) if frame then self.frame = frame @@ -296,10 +296,10 @@ comms.rplc_packet = function () end -- get raw to send - public.raw_sendable = function () return self.raw end + function public.raw_sendable() return self.raw end -- get this packet as a frame with an immutable relation to this object - public.get = function () + function public.get() ---@class rplc_frame local frame = { scada_frame = self.frame, @@ -316,7 +316,7 @@ comms.rplc_packet = function () end -- SCADA management packet -comms.mgmt_packet = function () +function comms.mgmt_packet() local self = { frame = nil, raw = nil, @@ -329,7 +329,7 @@ comms.mgmt_packet = function () local public = {} -- check that type is known - local _scada_type_valid = function () + local function _scada_type_valid() return self.type == SCADA_MGMT_TYPES.KEEP_ALIVE or self.type == SCADA_MGMT_TYPES.CLOSE or self.type == SCADA_MGMT_TYPES.REMOTE_LINKED or @@ -339,7 +339,7 @@ comms.mgmt_packet = function () -- make a SCADA management packet ---@param packet_type SCADA_MGMT_TYPES ---@param data table - public.make = function (packet_type, data) + function public.make(packet_type, data) -- packet accessor properties self.type = packet_type self.length = #data @@ -355,7 +355,7 @@ comms.mgmt_packet = function () -- decode a SCADA management packet from a SCADA frame ---@param frame scada_packet ---@return boolean success - public.decode = function (frame) + function public.decode(frame) if frame then self.frame = frame @@ -380,10 +380,10 @@ comms.mgmt_packet = function () end -- get raw to send - public.raw_sendable = function () return self.raw end + function public.raw_sendable() return self.raw end -- get this packet as a frame with an immutable relation to this object - public.get = function () + function public.get() ---@class mgmt_frame local frame = { scada_frame = self.frame, @@ -400,7 +400,7 @@ end -- SCADA coordinator packet -- @todo -comms.coord_packet = function () +function comms.coord_packet() local self = { frame = nil, raw = nil, @@ -412,7 +412,7 @@ comms.coord_packet = function () ---@class coord_packet local public = {} - local _coord_type_valid = function () + local function _coord_type_valid() -- @todo return false end @@ -420,7 +420,7 @@ comms.coord_packet = function () -- make a coordinator packet ---@param packet_type any ---@param data table - public.make = function (packet_type, data) + function public.make(packet_type, data) -- packet accessor properties self.type = packet_type self.length = #data @@ -436,7 +436,7 @@ comms.coord_packet = function () -- decode a coordinator packet from a SCADA frame ---@param frame scada_packet ---@return boolean success - public.decode = function (frame) + function public.decode(frame) if frame then self.frame = frame @@ -461,10 +461,10 @@ comms.coord_packet = function () end -- get raw to send - public.raw_sendable = function () return self.raw end + function public.raw_sendable() return self.raw end -- get this packet as a frame with an immutable relation to this object - public.get = function () + function public.get() ---@class coord_frame local frame = { scada_frame = self.frame, @@ -481,7 +481,7 @@ end -- coordinator API (CAPI) packet -- @todo -comms.capi_packet = function () +function comms.capi_packet() local self = { frame = nil, raw = nil, @@ -493,7 +493,7 @@ comms.capi_packet = function () ---@class capi_packet local public = {} - local _coord_type_valid = function () + local function _coord_type_valid() -- @todo return false end @@ -501,7 +501,7 @@ comms.capi_packet = function () -- make a coordinator API packet ---@param packet_type any ---@param data table - public.make = function (packet_type, data) + function public.make(packet_type, data) -- packet accessor properties self.type = packet_type self.length = #data @@ -517,7 +517,7 @@ comms.capi_packet = function () -- decode a coordinator API packet from a SCADA frame ---@param frame scada_packet ---@return boolean success - public.decode = function (frame) + function public.decode(frame) if frame then self.frame = frame @@ -542,10 +542,10 @@ comms.capi_packet = function () end -- get raw to send - public.raw_sendable = function () return self.raw end + function public.raw_sendable() return self.raw end -- get this packet as a frame with an immutable relation to this object - public.get = function () + function public.get() ---@class capi_frame local frame = { scada_frame = self.frame, @@ -563,7 +563,7 @@ end -- convert rtu_t to RTU unit type ---@param type rtu_t ---@return RTU_UNIT_TYPES|nil -comms.rtu_t_to_unit_type = function (type) +function comms.rtu_t_to_unit_type(type) if type == rtu_t.redstone then return RTU_UNIT_TYPES.REDSTONE elseif type == rtu_t.boiler then @@ -586,7 +586,7 @@ end -- convert RTU unit type to rtu_t ---@param utype RTU_UNIT_TYPES ---@return rtu_t|nil -comms.advert_type_to_rtu_t = function (utype) +function comms.advert_type_to_rtu_t(utype) if utype == RTU_UNIT_TYPES.REDSTONE then return rtu_t.redstone elseif utype == RTU_UNIT_TYPES.BOILER then diff --git a/scada-common/crypto.lua b/scada-common/crypto.lua index 19db76b..d16bf84 100644 --- a/scada-common/crypto.lua +++ b/scada-common/crypto.lua @@ -1,20 +1,19 @@ -local aes128 = require("lockbox.cipher.aes128") -local ctr_mode = require("lockbox.cipher.mode.ctr"); +-- +-- Cryptographic Communications Engine +-- -local sha1 = require("lockbox.digest.sha1"); +local aes128 = require("lockbox.cipher.aes128") +local ctr_mode = require("lockbox.cipher.mode.ctr"); +local sha1 = require("lockbox.digest.sha1"); local sha2_224 = require("lockbox.digest.sha2_224"); local sha2_256 = require("lockbox.digest.sha2_256"); - -local pbkdf2 = require("lockbox.kdf.pbkdf2") - -local hmac = require("lockbox.mac.hmac") - +local pbkdf2 = require("lockbox.kdf.pbkdf2") +local hmac = require("lockbox.mac.hmac") local zero_pad = require("lockbox.padding.zero"); +local stream = require("lockbox.util.stream") +local array = require("lockbox.util.array") -local stream = require("lockbox.util.stream") -local array = require("lockbox.util.array") - -local log = require("scada-common.log") +local log = require("scada-common.log") local util = require("scada-common.util") local crypto = {} diff --git a/scada-common/log.lua b/scada-common/log.lua index 791b11b..f923c0c 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -1,9 +1,9 @@ -local util = require("scada-common.util") - -- -- File System Logger -- +local util = require("scada-common.util") + ---@class log local log = {} @@ -32,7 +32,7 @@ local free_space = fs.getFreeSpace ---@param path string file path ---@param write_mode MODE ---@param dmesg_redirect? table terminal/window to direct dmesg to -log.init = function (path, write_mode, dmesg_redirect) +function log.init(path, write_mode, dmesg_redirect) _log_sys.path = path _log_sys.mode = write_mode @@ -51,13 +51,13 @@ end -- direct dmesg output to a monitor/window ---@param window table window or terminal reference -log.direct_dmesg = function (window) +function log.direct_dmesg(window) _log_sys.dmesg_out = window end -- private log write function ---@param msg string -local _log = function (msg) +local function _log(msg) local time_stamp = os.date("[%c] ") local stamped = time_stamp .. util.strval(msg) @@ -94,7 +94,7 @@ end ---@param msg string message ---@param tag? string log tag ---@param tag_color? integer log tag color -log.dmesg = function (msg, tag, tag_color) +function log.dmesg(msg, tag, tag_color) msg = util.strval(msg) tag = tag or "" tag = util.strval(tag) @@ -183,7 +183,7 @@ end -- log debug messages ---@param msg string message ---@param trace? boolean include file trace -log.debug = function (msg, trace) +function log.debug(msg, trace) if LOG_DEBUG then local dbg_info = "" @@ -204,20 +204,20 @@ end -- log info messages ---@param msg string message -log.info = function (msg) +function log.info(msg) _log("[INF] " .. util.strval(msg)) end -- log warning messages ---@param msg string message -log.warning = function (msg) +function log.warning(msg) _log("[WRN] " .. util.strval(msg)) end -- log error messages ---@param msg string message ---@param trace? boolean include file trace -log.error = function (msg, trace) +function log.error(msg, trace) local dbg_info = "" if trace then @@ -236,7 +236,7 @@ end -- log fatal errors ---@param msg string message -log.fatal = function (msg) +function log.fatal(msg) _log("[FTL] " .. util.strval(msg)) end diff --git a/scada-common/mqueue.lua b/scada-common/mqueue.lua index 8069ecb..fd80cfa 100644 --- a/scada-common/mqueue.lua +++ b/scada-common/mqueue.lua @@ -14,7 +14,7 @@ local TYPE = { mqueue.TYPE = TYPE -- create a new message queue -mqueue.new = function () +function mqueue.new() local queue = {} local insert = table.insert @@ -32,44 +32,44 @@ mqueue.new = function () local public = {} -- get queue length - public.length = function () return #queue end + function public.length() return #queue end -- check if queue is empty ---@return boolean is_empty - public.empty = function () return #queue == 0 end + function public.empty() return #queue == 0 end -- check if queue has contents - public.ready = function () return #queue ~= 0 end + function public.ready() return #queue ~= 0 end -- push a new item onto the queue ---@param qtype TYPE ---@param message string - local _push = function (qtype, message) + local function _push(qtype, message) insert(queue, { qtype = qtype, message = message }) end -- push a command onto the queue ---@param message any - public.push_command = function (message) + function public.push_command(message) _push(TYPE.COMMAND, message) end -- push data onto the queue ---@param key any ---@param value any - public.push_data = function (key, value) + function public.push_data(key, value) _push(TYPE.DATA, { key = key, val = value }) end -- push a packet onto the queue ---@param packet scada_packet|modbus_packet|rplc_packet|coord_packet|capi_packet - public.push_packet = function (packet) + function public.push_packet(packet) _push(TYPE.PACKET, packet) end -- get an item off the queue ---@return queue_item|nil - public.pop = function () + function public.pop() if #queue > 0 then return remove(queue, 1) else diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 5b65f47..740dc2b 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -1,9 +1,9 @@ -local log = require("scada-common.log") - -- -- Protected Peripheral Manager -- +local log = require("scada-common.log") + ---@class ppm local ppm = {} @@ -32,7 +32,7 @@ local _ppm_sys = { --- ---assumes iface is a valid peripheral ---@param iface string CC peripheral interface -local peri_init = function (iface) +local function peri_init(iface) local self = { faulted = false, last_fault = "", @@ -92,13 +92,13 @@ local peri_init = function (iface) -- fault management functions - local clear_fault = function () self.faulted = false end - local get_last_fault = function () return self.last_fault end - local is_faulted = function () return self.faulted end - local is_ok = function () return not self.faulted end + local function clear_fault() self.faulted = false end + local function get_last_fault() return self.last_fault end + local function is_faulted() return self.faulted end + local function is_ok() return not self.faulted end - local enable_afc = function () self.auto_cf = true end - local disable_afc = function () self.auto_cf = false end + local function enable_afc() self.auto_cf = true end + local function disable_afc() self.auto_cf = false end -- append to device functions @@ -122,53 +122,53 @@ end -- REPORTING -- -- silence error prints -ppm.disable_reporting = function () +function ppm.disable_reporting() _ppm_sys.mute = true end -- allow error prints -ppm.enable_reporting = function () +function ppm.enable_reporting() _ppm_sys.mute = false end -- FAULT MEMORY -- -- enable automatically clearing fault flag -ppm.enable_afc = function () +function ppm.enable_afc() _ppm_sys.auto_cf = true end -- disable automatically clearing fault flag -ppm.disable_afc = function () +function ppm.disable_afc() _ppm_sys.auto_cf = false end -- clear fault flag -ppm.clear_fault = function () +function ppm.clear_fault() _ppm_sys.faulted = false end -- check fault flag -ppm.is_faulted = function () +function ppm.is_faulted() return _ppm_sys.faulted end -- get the last fault message -ppm.get_last_fault = function () +function ppm.get_last_fault() return _ppm_sys.last_fault end -- TERMINATION -- -- if a caught error was a termination request -ppm.should_terminate = function () +function ppm.should_terminate() return _ppm_sys.terminate end -- MOUNTING -- -- mount all available peripherals (clears mounts first) -ppm.mount_all = function () +function ppm.mount_all() local ifaces = peripheral.getNames() _ppm_sys.mounts = {} @@ -187,7 +187,7 @@ end -- mount a particular device ---@param iface string CC peripheral interface ---@return string|nil type, table|nil device -ppm.mount = function (iface) +function ppm.mount(iface) local ifaces = peripheral.getNames() local pm_dev = nil local pm_type = nil @@ -210,7 +210,7 @@ end -- handle peripheral_detach event ---@param iface string CC peripheral interface ---@return string|nil type, table|nil device -ppm.handle_unmount = function (iface) +function ppm.handle_unmount(iface) local pm_dev = nil local pm_type = nil @@ -233,20 +233,20 @@ end -- list all available peripherals ---@return table names -ppm.list_avail = function () +function ppm.list_avail() return peripheral.getNames() end -- list mounted peripherals ---@return table mounts -ppm.list_mounts = function () +function ppm.list_mounts() return _ppm_sys.mounts end -- get a mounted peripheral by side/interface ---@param iface string CC peripheral interface ---@return table|nil device function table -ppm.get_periph = function (iface) +function ppm.get_periph(iface) if _ppm_sys.mounts[iface] then return _ppm_sys.mounts[iface].dev else return nil end @@ -255,7 +255,7 @@ end -- get a mounted peripheral type by side/interface ---@param iface string CC peripheral interface ---@return string|nil type -ppm.get_type = function (iface) +function ppm.get_type(iface) if _ppm_sys.mounts[iface] then return _ppm_sys.mounts[iface].type else return nil end @@ -264,7 +264,7 @@ end -- get all mounted peripherals by type ---@param name string type name ---@return table devices device function tables -ppm.get_all_devices = function (name) +function ppm.get_all_devices(name) local devices = {} for _, data in pairs(_ppm_sys.mounts) do @@ -279,7 +279,7 @@ end -- get a mounted peripheral by type (if multiple, returns the first) ---@param name string type name ---@return table|nil device function table -ppm.get_device = function (name) +function ppm.get_device(name) local device = nil for side, data in pairs(_ppm_sys.mounts) do @@ -296,13 +296,13 @@ end -- get the fission reactor (if multiple, returns the first) ---@return table|nil reactor function table -ppm.get_fission_reactor = function () +function ppm.get_fission_reactor() return ppm.get_device("fissionReactor") end -- get the wireless modem (if multiple, returns the first) ---@return table|nil modem function table -ppm.get_wireless_modem = function () +function ppm.get_wireless_modem() local w_modem = nil for _, device in pairs(_ppm_sys.mounts) do @@ -317,7 +317,7 @@ end -- list all connected monitors ---@return table monitors -ppm.get_monitor_list = function () +function ppm.get_monitor_list() local list = {} for iface, device in pairs(_ppm_sys.mounts) do diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 9ba6878..05f078c 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -77,7 +77,7 @@ rsio.IO = RS_IO -- channel to string ---@param channel RS_IO -rsio.to_string = function (channel) +function rsio.to_string(channel) local names = { "F_SCRAM", "R_SCRAM", @@ -160,7 +160,7 @@ local RS_DIO_MAP = { -- get the mode of a channel ---@param channel RS_IO ---@return IO_MODE -rsio.get_io_mode = function (channel) +function rsio.get_io_mode(channel) local modes = { IO_MODE.DIGITAL_IN, -- F_SCRAM IO_MODE.DIGITAL_IN, -- R_SCRAM @@ -200,14 +200,14 @@ local RS_SIDES = rs.getSides() -- check if a channel is valid ---@param channel RS_IO ---@return boolean valid -rsio.is_valid_channel = function (channel) +function rsio.is_valid_channel(channel) return (type(channel) == "number") and (channel > 0) and (channel <= RS_IO.R_PLC_TIMEOUT) end -- check if a side is valid ---@param side string ---@return boolean valid -rsio.is_valid_side = function (side) +function rsio.is_valid_side(side) if side ~= nil then for i = 0, #RS_SIDES do if RS_SIDES[i] == side then return true end @@ -219,7 +219,7 @@ end -- check if a color is a valid single color ---@param color integer ---@return boolean valid -rsio.is_color = function (color) +function rsio.is_color(color) return (type(color) == "number") and (color > 0) and (_B_AND(color, (color - 1)) == 0); end @@ -230,7 +230,7 @@ end -- get digital IO level reading ---@param rs_value boolean ---@return IO_LVL -rsio.digital_read = function (rs_value) +function rsio.digital_read(rs_value) if rs_value then return IO_LVL.HIGH else @@ -242,7 +242,7 @@ end ---@param channel RS_IO ---@param level IO_LVL ---@return boolean -rsio.digital_write = function (channel, level) +function rsio.digital_write(channel, level) if type(channel) ~= "number" or channel < RS_IO.F_ALARM or channel > RS_IO.R_PLC_TIMEOUT then return false else @@ -254,7 +254,7 @@ end ---@param channel RS_IO ---@param level IO_LVL ---@return boolean -rsio.digital_is_active = function (channel, level) +function rsio.digital_is_active(channel, level) if type(channel) ~= "number" or channel > RS_IO.R_ENABLE then return false else @@ -271,7 +271,7 @@ end ---@param min number minimum of range ---@param max number maximum of range ---@return number value scaled reading (min to max) -rsio.analog_read = function (rs_value, min, max) +function rsio.analog_read(rs_value, min, max) local value = rs_value / 15 return (value * (max - min)) + min end @@ -281,7 +281,7 @@ end ---@param min number minimum of range ---@param max number maximum of range ---@return number rs_value scaled redstone reading (0 to 15) -rsio.analog_write = function (value, min, max) +function rsio.analog_write(value, min, max) local scaled_value = (value - min) / (max - min) return scaled_value * 15 end diff --git a/scada-common/util.lua b/scada-common/util.lua index 5bbe2b8..bfba8d9 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -12,7 +12,7 @@ local util = {} ---@param a any return if true ---@param b any return if false ---@return any value -util.trinary = function (cond, a, b) +function util.trinary(cond, a, b) if cond then return a else return b end end @@ -20,25 +20,25 @@ end -- print ---@param message any -util.print = function (message) +function util.print(message) term.write(tostring(message)) end -- print line ---@param message any -util.println = function (message) +function util.println(message) print(tostring(message)) end -- timestamped print ---@param message any -util.print_ts = function (message) +function util.print_ts(message) term.write(os.date("[%H:%M:%S] ") .. tostring(message)) end -- timestamped print line ---@param message any -util.println_ts = function (message) +function util.println_ts(message) print(os.date("[%H:%M:%S] ") .. tostring(message)) end @@ -47,7 +47,7 @@ end -- get a value as a string ---@param val any ---@return string -util.strval = function (val) +function util.strval(val) local t = type(val) if t == "table" or t == "function" then return "[" .. tostring(val) .. "]" @@ -59,7 +59,7 @@ end -- concatenation with built-in to string ---@vararg any ---@return string -util.concat = function (...) +function util.concat(...) local str = "" for _, v in ipairs(arg) do str = str .. util.strval(v) @@ -73,7 +73,7 @@ util.c = util.concat -- sprintf implementation ---@param format string ---@vararg any -util.sprintf = function (format, ...) +function util.sprintf(format, ...) return string.format(format, table.unpack(arg)) end @@ -81,7 +81,7 @@ end -- round a number to an integer ---@return integer rounded -util.round = function (x) +function util.round(x) return math.floor(x + 0.5) end @@ -89,21 +89,21 @@ end -- current time ---@return integer milliseconds -util.time_ms = function () +function util.time_ms() ---@diagnostic disable-next-line: undefined-field return os.epoch('local') end -- current time ---@return number seconds -util.time_s = function () +function util.time_s() ---@diagnostic disable-next-line: undefined-field return os.epoch('local') / 1000.0 end -- current time ---@return integer milliseconds -util.time = function () +function util.time() return util.time_ms() end @@ -112,7 +112,7 @@ end -- protected sleep call so we still are in charge of catching termination ---@param t integer seconds --- EVENT_CONSUMER: this function consumes events -util.psleep = function (t) +function util.psleep(t) ---@diagnostic disable-next-line: undefined-field pcall(os.sleep, t) end @@ -120,7 +120,7 @@ end -- no-op to provide a brief pause (1 tick) to yield --- --- EVENT_CONSUMER: this function consumes events -util.nop = function () +function util.nop() util.psleep(0.05) end @@ -129,7 +129,7 @@ end ---@param last_update integer millisecond time of last update ---@return integer time_now -- EVENT_CONSUMER: this function consumes events -util.adaptive_delay = function (target_timing, last_update) +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 if sleep_for >= 50 then @@ -146,7 +146,7 @@ end ---@param t table table to remove elements from ---@param f function should return false to delete an element when passed the element: f(elem) = true|false ---@param on_delete? function optional function to execute on deletion, passed the table element to be deleted as the parameter -util.filter_table = function (t, f, on_delete) +function util.filter_table(t, f, on_delete) local move_to = 1 for i = 1, #t do local element = t[i] @@ -168,7 +168,7 @@ end -- check if a table contains the provided element ---@param t table table to check ---@param element any element to check for -util.table_contains = function (t, element) +function util.table_contains(t, element) for i = 1, #t do if t[i] == element then return true end end @@ -213,7 +213,7 @@ end ---@param timeout number timeout duration --- --- triggers a timer event if not fed within 'timeout' seconds -util.new_watchdog = function (timeout) +function util.new_watchdog(timeout) ---@diagnostic disable-next-line: undefined-field local start_timer = os.startTimer ---@diagnostic disable-next-line: undefined-field @@ -228,12 +228,12 @@ util.new_watchdog = function (timeout) local public = {} ---@param timer number timer event timer ID - public.is_timer = function (timer) + function public.is_timer(timer) return self.wd_timer == timer end -- satiate the beast - public.feed = function () + function public.feed() if self.wd_timer ~= nil then cancel_timer(self.wd_timer) end @@ -241,7 +241,7 @@ util.new_watchdog = function (timeout) end -- cancel the watchdog - public.cancel = function () + function public.cancel() if self.wd_timer ~= nil then cancel_timer(self.wd_timer) end @@ -256,7 +256,7 @@ end ---@param period number clock period --- --- fires a timer event at the specified period, does not start at construct time -util.new_clock = function (period) +function util.new_clock(period) ---@diagnostic disable-next-line: undefined-field local start_timer = os.startTimer @@ -269,12 +269,12 @@ util.new_clock = function (period) local public = {} ---@param timer number timer event timer ID - public.is_clock = function (timer) + function public.is_clock(timer) return self.timer == timer end -- start the clock - public.start = function () + function public.start() self.timer = start_timer(self.period) end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 1e77fa9..bf17a08 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.4.3" +local SUPERVISOR_VERSION = "beta-v0.4.4" local print = util.print local println = util.println From fc7b83a18a6109b508b702358bec807dbca80ef3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Jun 2022 10:49:36 -0400 Subject: [PATCH 241/587] #28 #66 #59 new RTUs --- rtu/dev/envd_rtu.lua | 26 +++++++++++++++++++++ rtu/dev/sna_rtu.lua | 37 ++++++++++++++++++++++++++++++ rtu/dev/sps_rtu.lua | 47 ++++++++++++++++++++++++++++++++++++++ scada-common/comms.lua | 5 +++- scada-common/types.lua | 5 +++- supervisor/session/rtu.lua | 5 ++++ 6 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 rtu/dev/envd_rtu.lua create mode 100644 rtu/dev/sna_rtu.lua create mode 100644 rtu/dev/sps_rtu.lua diff --git a/rtu/dev/envd_rtu.lua b/rtu/dev/envd_rtu.lua new file mode 100644 index 0000000..c09ee0c --- /dev/null +++ b/rtu/dev/envd_rtu.lua @@ -0,0 +1,26 @@ +local rtu = require("rtu.rtu") + +local envd_rtu = {} + +-- create new environment detector device +---@param envd table +function envd_rtu.new(envd) + local unit = rtu.init_unit() + + -- discrete inputs -- + -- none + + -- coils -- + -- none + + -- input registers -- + unit.connect_input_reg(envd.getRadiation) + unit.connect_input_reg(envd.getRadiationRaw) + + -- holding registers -- + -- none + + return unit.interface() +end + +return envd_rtu diff --git a/rtu/dev/sna_rtu.lua b/rtu/dev/sna_rtu.lua new file mode 100644 index 0000000..a4c250f --- /dev/null +++ b/rtu/dev/sna_rtu.lua @@ -0,0 +1,37 @@ +local rtu = require("rtu.rtu") + +local sna_rtu = {} + +-- create new solar neutron activator (sna) device +---@param sna table +function sna_rtu.new(sna) + local unit = rtu.init_unit() + + -- discrete inputs -- + -- none + + -- coils -- + -- none + + -- input registers -- + -- build properties + unit.connect_input_reg(sna.getInputCapacity) + unit.connect_input_reg(sna.getOutputCapacity) + -- current state + unit.connect_input_reg(sna.getProductionRate) + unit.connect_input_reg(sna.getPeakProductionRate) + -- tanks + unit.connect_input_reg(sna.getInput) + unit.connect_input_reg(sna.getInputNeeded) + unit.connect_input_reg(sna.getInputFilledPercentage) + unit.connect_input_reg(sna.getOutput) + unit.connect_input_reg(sna.getOutputNeeded) + unit.connect_input_reg(sna.getOutputFilledPercentage) + + -- holding registers -- + -- none + + return unit.interface() +end + +return sna_rtu diff --git a/rtu/dev/sps_rtu.lua b/rtu/dev/sps_rtu.lua new file mode 100644 index 0000000..3b7fdf1 --- /dev/null +++ b/rtu/dev/sps_rtu.lua @@ -0,0 +1,47 @@ +local rtu = require("rtu.rtu") + +local sps_rtu = {} + +-- create new super-critical phase shifter (sps) device +---@param sps table +function sps_rtu.new(sps) + local unit = rtu.init_unit() + + -- discrete inputs -- + unit.connect_di(sps.isFormed) + + -- coils -- + -- none + + -- input registers -- + -- multiblock properties + unit.connect_input_reg(sps.getLength) + unit.connect_input_reg(sps.getWidth) + unit.connect_input_reg(sps.getHeight) + unit.connect_input_reg(sps.getMinPos) + unit.connect_input_reg(sps.getMaxPos) + -- build properties + unit.connect_input_reg(sps.getCoils) + unit.connect_input_reg(sps.getInputCapacity) + unit.connect_input_reg(sps.getOutputCapacity) + unit.connect_input_reg(sps.getMaxEnergy) + -- current state + unit.connect_input_reg(sps.getProcessRate) + -- tanks + unit.connect_input_reg(sps.getInput) + unit.connect_input_reg(sps.getInputNeeded) + unit.connect_input_reg(sps.getInputFilledPercentage) + unit.connect_input_reg(sps.getOutput) + unit.connect_input_reg(sps.getOutputNeeded) + unit.connect_input_reg(sps.getOutputFilledPercentage) + unit.connect_input_reg(sps.getEnergy) + unit.connect_input_reg(sps.getEnergyNeeded) + unit.connect_input_reg(sps.getEnergyFilledPercentage) + + -- holding registers -- + -- none + + return unit.interface() +end + +return sps_rtu diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 30cadcf..61a0836 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -56,7 +56,10 @@ local RTU_UNIT_TYPES = { TURBINE = 3, -- turbine TURBINE_VALVE = 4, -- turbine, mekanism 10.1+ EMACHINE = 5, -- energy machine - IMATRIX = 6 -- induction matrix + IMATRIX = 6, -- induction matrix + SPS = 7, -- SPS + SNA = 8, -- SNA + ENV_DETECTOR = 9 -- environment detector } comms.PROTOCOLS = PROTOCOLS diff --git a/scada-common/types.lua b/scada-common/types.lua index b1b5e8c..65aa19f 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -41,7 +41,10 @@ types.rtu_t = { turbine = "turbine", turbine_valve = "turbine_valve", energy_machine = "emachine", - induction_matrix = "induction_matrix" + induction_matrix = "induction_matrix", + sps = "sps", + sna = "sna", + env_detector = "environment_detector" } ---@alias rps_status_t string diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 5454917..5598e82 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -101,6 +101,11 @@ function rtu.new_session(id, in_queue, out_queue, advertisement) unit = svrs_emachine.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.IMATRIX then -- @todo Mekanism 10.1+ + elseif u_type == RTU_UNIT_TYPES.SPS then + -- @todo Mekanism 10.1+ + elseif u_type == RTU_UNIT_TYPES.SNA then + -- @todo Mekanism 10.1+ + elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then else log.error(log_header .. "bad advertisement: encountered unsupported RTU type") end From 5cba8ff9f13155960a56532f3216a922c2367fbd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Jun 2022 11:11:35 -0400 Subject: [PATCH 242/587] #59 environment detector RTU --- rtu/startup.lua | 7 ++- supervisor/session/rtu.lua | 2 + supervisor/session/rtu/envd.lua | 100 ++++++++++++++++++++++++++++++++ supervisor/startup.lua | 2 +- 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 supervisor/session/rtu/envd.lua diff --git a/rtu/startup.lua b/rtu/startup.lua index 3c01df3..9257f08 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -20,11 +20,12 @@ local redstone_rtu = require("rtu.dev.redstone_rtu") local boiler_rtu = require("rtu.dev.boiler_rtu") local boilerv_rtu = require("rtu.dev.boilerv_rtu") local energymachine_rtu = require("rtu.dev.energymachine_rtu") +local envd_rtu = require("rtu.dev.envd_rtu") local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.7.4" +local RTU_VERSION = "beta-v0.7.5" local rtu_t = types.rtu_t @@ -225,6 +226,10 @@ for i = 1, #rtu_devices do -- induction matrix multiblock (10.1+) rtu_type = rtu_t.induction_matrix rtu_iface = imatrix_rtu.new(device) + elseif type == "environmentDetector" then + -- advanced peripherals environment detector + rtu_type = rtu_t.env_detector + rtu_iface = envd_rtu.new(device) else local message = "init> device '" .. rtu_devices[i].name .. "' is not a known type (" .. type .. ")" println_ts(message) diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 5598e82..c45ea39 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -7,6 +7,7 @@ local util = require("scada-common.util") -- supervisor rtu sessions (svrs) local svrs_boiler = require("supervisor.session.rtu.boiler") local svrs_emachine = require("supervisor.session.rtu.emachine") +local svrs_envd = require("supervisor.session.rtu.envd") local svrs_redstone = require("supervisor.session.rtu.redstone") local svrs_turbine = require("supervisor.session.rtu.turbine") @@ -106,6 +107,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement) elseif u_type == RTU_UNIT_TYPES.SNA then -- @todo Mekanism 10.1+ elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then + unit = svrs_envd.new(self.id, i, unit_advert, self.out_q) else log.error(log_header .. "bad advertisement: encountered unsupported RTU type") end diff --git a/supervisor/session/rtu/envd.lua b/supervisor/session/rtu/envd.lua new file mode 100644 index 0000000..ce398e4 --- /dev/null +++ b/supervisor/session/rtu/envd.lua @@ -0,0 +1,100 @@ +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") + +local unit_session = require("supervisor.session.rtu.unit_session") + +local envd = {} + +local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local MODBUS_FCODE = types.MODBUS_FCODE + +local TXN_TYPES = { + RAD = 1 +} + +local TXN_TAGS = { + "envd.radiation" +} + +local PERIODICS = { + RAD = 500 +} + +-- create a new environment detector rtu session runner +---@param session_id integer +---@param unit_id integer +---@param advert rtu_advertisement +---@param out_queue mqueue +function envd.new(session_id, unit_id, advert, out_queue) + -- type check + if advert.type ~= RTU_UNIT_TYPES.ENV_DETECTOR then + log.error("attempt to instantiate envd RTU for type '" .. advert.type .. "'. this is a bug.") + return nil + end + + local log_tag = "session.rtu(" .. session_id .. ").envd(" .. advert.index .. "): " + + local self = { + session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), + periodics = { + next_rad_req = 0 + }, + ---@class envd_session_db + db = { + radiation = {}, + radiation_raw = 0 + } + } + + local public = self.session.get() + + -- PRIVATE FUNCTIONS -- + + -- query the radiation readings of the device + local function _request_radiation() + -- read input registers 1 and 2 (start = 1, count = 2) + self.session.send_request(TXN_TYPES.RAD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 2 }) + end + + -- PUBLIC FUNCTIONS -- + + -- handle a packet + ---@param m_pkt modbus_frame + function public.handle_packet(m_pkt) + local txn_type = self.session.try_resolve(m_pkt.txn_id) + if txn_type == false then + -- nothing to do + elseif txn_type == TXN_TYPES.RAD then + -- radiation status response + if m_pkt.length == 2 then + self.db.radiation = m_pkt.data[1] + self.db.radiation_raw = m_pkt.data[2] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (envd.radiation)") + end + elseif txn_type == nil then + log.error(log_tag .. "unknown transaction reply") + else + log.error(log_tag .. "unknown transaction type " .. txn_type) + end + end + + -- update this runner + ---@param time_now integer milliseconds + function public.update(time_now) + if not self.has_build and self.periodics.next_rad_req <= time_now then + _request_radiation() + self.periodics.next_rad_req = time_now + PERIODICS.RAD + end + + self.session.post_update() + end + + -- get the unit session database + function public.get_db() return self.db end + + return public +end + +return envd diff --git a/supervisor/startup.lua b/supervisor/startup.lua index bf17a08..32cc86e 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.4.4" +local SUPERVISOR_VERSION = "beta-v0.4.5" local print = util.print local println = util.println From 1242c5a81cc1101a300fb340d158662c206605c7 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Jun 2022 11:17:54 -0400 Subject: [PATCH 243/587] use TXN_TAGS for consistency --- supervisor/session/rtu/boiler.lua | 4 ++-- supervisor/session/rtu/emachine.lua | 4 ++-- supervisor/session/rtu/envd.lua | 2 +- supervisor/session/rtu/redstone.lua | 4 ++-- supervisor/session/rtu/turbine.lua | 6 +++--- supervisor/startup.lua | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua index f34dbc3..afb31f8 100644 --- a/supervisor/session/rtu/boiler.lua +++ b/supervisor/session/rtu/boiler.lua @@ -123,7 +123,7 @@ function boiler.new(session_id, unit_id, advert, out_queue) self.db.build.superheaters = m_pkt.data[6] self.db.build.max_boil_rate = m_pkt.data[7] else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (boiler.build)") + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end elseif txn_type == TXN_TYPES.STATE then -- state response @@ -132,7 +132,7 @@ function boiler.new(session_id, unit_id, advert, out_queue) self.db.state.temperature = m_pkt.data[1] self.db.state.boil_rate = m_pkt.data[2] else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (boiler.state)") + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end elseif txn_type == TXN_TYPES.TANKS then -- tanks response diff --git a/supervisor/session/rtu/emachine.lua b/supervisor/session/rtu/emachine.lua index fa34d21..b054965 100644 --- a/supervisor/session/rtu/emachine.lua +++ b/supervisor/session/rtu/emachine.lua @@ -87,7 +87,7 @@ function emachine.new(session_id, unit_id, advert, out_queue) if m_pkt.length == 1 then self.db.build.max_energy = m_pkt.data[1] else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (emachine.build)") + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end elseif txn_type == TXN_TYPES.STORAGE then -- storage response @@ -96,7 +96,7 @@ function emachine.new(session_id, unit_id, advert, out_queue) self.db.storage.energy_need = m_pkt.data[2] self.db.storage.energy_fill = m_pkt.data[3] else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (emachine.storage)") + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end elseif txn_type == nil then log.error(log_tag .. "unknown transaction reply") diff --git a/supervisor/session/rtu/envd.lua b/supervisor/session/rtu/envd.lua index ce398e4..269d8fa 100644 --- a/supervisor/session/rtu/envd.lua +++ b/supervisor/session/rtu/envd.lua @@ -71,7 +71,7 @@ function envd.new(session_id, unit_id, advert, out_queue) self.db.radiation = m_pkt.data[1] self.db.radiation_raw = m_pkt.data[2] else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (envd.radiation)") + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end elseif txn_type == nil then log.error(log_tag .. "unknown transaction reply") diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index 3200eef..81be32f 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -149,7 +149,7 @@ function redstone.new(session_id, unit_id, advert, out_queue) self.db[channel] = value end else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (redstone.di_read)") + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end elseif txn_type == TXN_TYPES.INPUT_REG_READ then -- input register read response @@ -160,7 +160,7 @@ function redstone.new(session_id, unit_id, advert, out_queue) self.db[channel] = value end else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (redstone.input_reg_read)") + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end elseif txn_type == TXN_TYPES.COIL_WRITE or txn_type == TXN_TYPES.HOLD_REG_WRITE then -- successful acknowledgement diff --git a/supervisor/session/rtu/turbine.lua b/supervisor/session/rtu/turbine.lua index c6578fa..352f57e 100644 --- a/supervisor/session/rtu/turbine.lua +++ b/supervisor/session/rtu/turbine.lua @@ -120,7 +120,7 @@ function turbine.new(session_id, unit_id, advert, out_queue) self.db.build.max_production = m_pkt.data[8] self.db.build.max_water_output = m_pkt.data[9] else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (turbine.build)") + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end elseif txn_type == TXN_TYPES.STATE then -- state response @@ -130,7 +130,7 @@ function turbine.new(session_id, unit_id, advert, out_queue) self.db.state.steam_input_rate = m_pkt.data[3] self.db.state.dumping_mode = m_pkt.data[4] else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (turbine.state)") + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end elseif txn_type == TXN_TYPES.TANKS then -- tanks response @@ -139,7 +139,7 @@ function turbine.new(session_id, unit_id, advert, out_queue) self.db.tanks.steam_need = m_pkt.data[2] self.db.tanks.steam_fill = m_pkt.data[3] else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (turbine.tanks)") + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end elseif txn_type == nil then log.error(log_tag .. "unknown transaction reply") diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 32cc86e..bfdd925 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.4.5" +local SUPERVISOR_VERSION = "beta-v0.4.6" local print = util.print local println = util.println From dcb517d1cb7792ab5015942debf6a349a763eabd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Jun 2022 11:23:06 -0400 Subject: [PATCH 244/587] trailing case of not using TXN_TAGS --- supervisor/session/rtu/boiler.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua index afb31f8..56ed590 100644 --- a/supervisor/session/rtu/boiler.lua +++ b/supervisor/session/rtu/boiler.lua @@ -151,7 +151,7 @@ function boiler.new(session_id, unit_id, advert, out_queue) self.db.tanks.ccool_need = m_pkt.data[11] self.db.tanks.ccool_fill = m_pkt.data[12] else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (boiler.tanks)") + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end elseif txn_type == nil then log.error(log_tag .. "unknown transaction reply") From 07574aa116bf6a7a88437c15c56fd394d8d34ce3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Jun 2022 15:00:50 -0400 Subject: [PATCH 245/587] alignment and fixed has_build bugs --- supervisor/session/rtu/boiler.lua | 25 +++++++++++++------------ supervisor/session/rtu/emachine.lua | 3 ++- supervisor/session/rtu/envd.lua | 2 +- supervisor/session/rtu/turbine.lua | 25 +++++++++++++------------ 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua index 56ed590..54c1080 100644 --- a/supervisor/session/rtu/boiler.lua +++ b/supervisor/session/rtu/boiler.lua @@ -115,13 +115,14 @@ function boiler.new(session_id, unit_id, advert, out_queue) -- build response -- load in data if correct length if m_pkt.length == 7 then - self.db.build.boil_cap = m_pkt.data[1] - self.db.build.steam_cap = m_pkt.data[2] - self.db.build.water_cap = m_pkt.data[3] - self.db.build.hcoolant_cap = m_pkt.data[4] - self.db.build.ccoolant_cap = m_pkt.data[5] - self.db.build.superheaters = m_pkt.data[6] + self.db.build.boil_cap = m_pkt.data[1] + self.db.build.steam_cap = m_pkt.data[2] + self.db.build.water_cap = m_pkt.data[3] + self.db.build.hcoolant_cap = m_pkt.data[4] + self.db.build.ccoolant_cap = m_pkt.data[5] + self.db.build.superheaters = m_pkt.data[6] self.db.build.max_boil_rate = m_pkt.data[7] + self.has_build = true else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end @@ -130,7 +131,7 @@ function boiler.new(session_id, unit_id, advert, out_queue) -- load in data if correct length if m_pkt.length == 2 then self.db.state.temperature = m_pkt.data[1] - self.db.state.boil_rate = m_pkt.data[2] + self.db.state.boil_rate = m_pkt.data[2] else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end @@ -138,16 +139,16 @@ function boiler.new(session_id, unit_id, advert, out_queue) -- tanks response -- load in data if correct length if m_pkt.length == 12 then - self.db.tanks.steam = m_pkt.data[1] + self.db.tanks.steam = m_pkt.data[1] self.db.tanks.steam_need = m_pkt.data[2] self.db.tanks.steam_fill = m_pkt.data[3] - self.db.tanks.water = m_pkt.data[4] + self.db.tanks.water = m_pkt.data[4] self.db.tanks.water_need = m_pkt.data[5] self.db.tanks.water_fill = m_pkt.data[6] - self.db.tanks.hcool = m_pkt.data[7] + self.db.tanks.hcool = m_pkt.data[7] self.db.tanks.hcool_need = m_pkt.data[8] self.db.tanks.hcool_fill = m_pkt.data[9] - self.db.tanks.ccool = m_pkt.data[10] + self.db.tanks.ccool = m_pkt.data[10] self.db.tanks.ccool_need = m_pkt.data[11] self.db.tanks.ccool_fill = m_pkt.data[12] else @@ -163,7 +164,7 @@ function boiler.new(session_id, unit_id, advert, out_queue) -- update this runner ---@param time_now integer milliseconds function public.update(time_now) - if not self.periodics.has_build and self.periodics.next_build_req <= time_now then + if not self.has_build and self.periodics.next_build_req <= time_now then _request_build() self.periodics.next_build_req = time_now + PERIODICS.BUILD end diff --git a/supervisor/session/rtu/emachine.lua b/supervisor/session/rtu/emachine.lua index b054965..703330d 100644 --- a/supervisor/session/rtu/emachine.lua +++ b/supervisor/session/rtu/emachine.lua @@ -86,13 +86,14 @@ function emachine.new(session_id, unit_id, advert, out_queue) -- build response if m_pkt.length == 1 then self.db.build.max_energy = m_pkt.data[1] + self.has_build = true else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end elseif txn_type == TXN_TYPES.STORAGE then -- storage response if m_pkt.length == 3 then - self.db.storage.energy = m_pkt.data[1] + self.db.storage.energy = m_pkt.data[1] self.db.storage.energy_need = m_pkt.data[2] self.db.storage.energy_fill = m_pkt.data[3] else diff --git a/supervisor/session/rtu/envd.lua b/supervisor/session/rtu/envd.lua index 269d8fa..ca4a1af 100644 --- a/supervisor/session/rtu/envd.lua +++ b/supervisor/session/rtu/envd.lua @@ -83,7 +83,7 @@ function envd.new(session_id, unit_id, advert, out_queue) -- update this runner ---@param time_now integer milliseconds function public.update(time_now) - if not self.has_build and self.periodics.next_rad_req <= time_now then + if self.periodics.next_rad_req <= time_now then _request_radiation() self.periodics.next_rad_req = time_now + PERIODICS.RAD end diff --git a/supervisor/session/rtu/turbine.lua b/supervisor/session/rtu/turbine.lua index 352f57e..34e6cd0 100644 --- a/supervisor/session/rtu/turbine.lua +++ b/supervisor/session/rtu/turbine.lua @@ -110,32 +110,33 @@ function turbine.new(session_id, unit_id, advert, out_queue) elseif txn_type == TXN_TYPES.BUILD then -- build response if m_pkt.length == 9 then - self.db.build.blades = m_pkt.data[1] - self.db.build.coils = m_pkt.data[2] - self.db.build.vents = m_pkt.data[3] - self.db.build.dispersers = m_pkt.data[4] - self.db.build.condensers = m_pkt.data[5] - self.db.build.steam_cap = m_pkt.data[6] - self.db.build.max_flow_rate = m_pkt.data[7] - self.db.build.max_production = m_pkt.data[8] + self.db.build.blades = m_pkt.data[1] + self.db.build.coils = m_pkt.data[2] + self.db.build.vents = m_pkt.data[3] + self.db.build.dispersers = m_pkt.data[4] + self.db.build.condensers = m_pkt.data[5] + self.db.build.steam_cap = m_pkt.data[6] + self.db.build.max_flow_rate = m_pkt.data[7] + self.db.build.max_production = m_pkt.data[8] self.db.build.max_water_output = m_pkt.data[9] + self.has_build = true else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end elseif txn_type == TXN_TYPES.STATE then -- state response if m_pkt.length == 4 then - self.db.state.flow_rate = m_pkt.data[1] - self.db.state.prod_rate = m_pkt.data[2] + self.db.state.flow_rate = m_pkt.data[1] + self.db.state.prod_rate = m_pkt.data[2] self.db.state.steam_input_rate = m_pkt.data[3] - self.db.state.dumping_mode = m_pkt.data[4] + self.db.state.dumping_mode = m_pkt.data[4] else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end elseif txn_type == TXN_TYPES.TANKS then -- tanks response if m_pkt.length == 3 then - self.db.tanks.steam = m_pkt.data[1] + self.db.tanks.steam = m_pkt.data[1] self.db.tanks.steam_need = m_pkt.data[2] self.db.tanks.steam_fill = m_pkt.data[3] else From 27a86cc893f265f3aa5b7c217f7f8a8f7cdad598 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Jun 2022 15:33:04 -0400 Subject: [PATCH 246/587] #28 SPS RTU supervisor session --- supervisor/session/rtu.lua | 3 +- supervisor/session/rtu/sps.lua | 213 +++++++++++++++++++++++++++++++++ supervisor/startup.lua | 2 +- 3 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 supervisor/session/rtu/sps.lua diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index c45ea39..00dc81b 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -9,6 +9,7 @@ local svrs_boiler = require("supervisor.session.rtu.boiler") local svrs_emachine = require("supervisor.session.rtu.emachine") local svrs_envd = require("supervisor.session.rtu.envd") local svrs_redstone = require("supervisor.session.rtu.redstone") +local svrs_sps = require("supervisor.session.rtu.sps") local svrs_turbine = require("supervisor.session.rtu.turbine") local rtu = {} @@ -103,7 +104,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement) elseif u_type == RTU_UNIT_TYPES.IMATRIX then -- @todo Mekanism 10.1+ elseif u_type == RTU_UNIT_TYPES.SPS then - -- @todo Mekanism 10.1+ + unit = svrs_sps.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.SNA then -- @todo Mekanism 10.1+ elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua new file mode 100644 index 0000000..47f25ac --- /dev/null +++ b/supervisor/session/rtu/sps.lua @@ -0,0 +1,213 @@ +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") + +local unit_session = require("supervisor.session.rtu.unit_session") + +local sps = {} + +local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local MODBUS_FCODE = types.MODBUS_FCODE + +local TXN_TYPES = { + FORMED = 1, + BUILD = 2, + STATE = 3, + TANKS = 4 +} + +local TXN_TAGS = { + "sps.formed", + "sps.build", + "sps.state", + "sps.tanks", +} + +local PERIODICS = { + FORMED = 2000, + BUILD = 1000, + STATE = 500, + TANKS = 1000 +} + +-- create a new sps rtu session runner +---@param session_id integer +---@param unit_id integer +---@param advert rtu_advertisement +---@param out_queue mqueue +function sps.new(session_id, unit_id, advert, out_queue) + -- type check + if advert.type ~= RTU_UNIT_TYPES.SPS then + log.error("attempt to instantiate sps RTU for type '" .. advert.type .. "'. this is a bug.") + return nil + end + + local log_tag = "session.rtu(" .. session_id .. ").sps(" .. advert.index .. "): " + + local self = { + session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), + has_build = false, + periodics = { + next_formed_req = 0, + next_build_req = 0, + next_state_req = 0, + next_tanks_req = 0, + }, + ---@class sps_session_db + db = { + formed = false, + build = { + length = 0, + width = 0, + height = 0, + min_pos = 0, + max_pos = 0, + coils = 0, + input_cap = 0, + output_cap = 0, + max_energy = 0 + }, + state = { + process_rate = 0.0 + }, + tanks = { + input = {}, ---@type tank_fluid + input_need = 0, + input_fill = 0.0, + output = {}, ---@type tank_fluid + output_need = 0, + output_fill = 0.0, + energy = 0, + energy_need = 0, + energy_fill = 0.0 + } + } + } + + local public = self.session.get() + + -- PRIVATE FUNCTIONS -- + + -- query if the build is formed + local function _request_formed() + -- read discrete input 1 (start = 1, count = 1) + self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) + end + + -- query the build of the device + local function _request_build() + -- read input registers 1 through 7 (start = 1, count = 7) + self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 7 }) + end + + -- query the state of the device + local function _request_state() + -- read input registers 8 through 9 (start = 8, count = 2) + self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 8, 2 }) + end + + -- query the tanks of the device + local function _request_tanks() + -- read input registers 10 through 21 (start = 10, count = 12) + self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 10, 12 }) + end + + -- PUBLIC FUNCTIONS -- + + -- handle a packet + ---@param m_pkt modbus_frame + function public.handle_packet(m_pkt) + local txn_type = self.session.try_resolve(m_pkt.txn_id) + if txn_type == false then + -- nothing to do + elseif txn_type == TXN_TYPES.FORMED then + -- formed response + -- load in data if correct length + if m_pkt.length == 1 then + self.db.formed = m_pkt.data[1] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == TXN_TYPES.BUILD then + -- build response + -- load in data if correct length + if m_pkt.length == 9 then + self.db.build.length = m_pkt.data[1] + self.db.build.width = m_pkt.data[2] + self.db.build.height = m_pkt.data[3] + self.db.build.min_pos = m_pkt.data[4] + self.db.build.max_pos = m_pkt.data[5] + self.db.build.coils = m_pkt.data[6] + self.db.build.input_cap = m_pkt.data[7] + self.db.build.output_cap = m_pkt.data[8] + self.db.build.max_energy = m_pkt.data[9] + self.has_build = true + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == TXN_TYPES.STATE then + -- state response + -- load in data if correct length + if m_pkt.length == 1 then + self.db.state.process_rate = m_pkt.data[1] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == TXN_TYPES.TANKS then + -- tanks response + -- load in data if correct length + if m_pkt.length == 9 then + self.db.tanks.input = m_pkt.data[1] + self.db.tanks.input_need = m_pkt.data[2] + self.db.tanks.input_fill = m_pkt.data[3] + self.db.tanks.output = m_pkt.data[4] + self.db.tanks.output_need = m_pkt.data[5] + self.db.tanks.output_fill = m_pkt.data[6] + self.db.tanks.energy = m_pkt.data[7] + self.db.tanks.energy_need = m_pkt.data[8] + self.db.tanks.energy_fill = m_pkt.data[9] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == nil then + log.error(log_tag .. "unknown transaction reply") + else + log.error(log_tag .. "unknown transaction type " .. txn_type) + end + end + + -- update this runner + ---@param time_now integer milliseconds + function public.update(time_now) + if self.periodics.next_formed_req <= time_now then + _request_formed() + self.periodics.next_formed_req = time_now + PERIODICS.FORMED + end + + if self.db.formed then + if not self.has_build and self.periodics.next_build_req <= time_now then + _request_build() + self.periodics.next_build_req = time_now + PERIODICS.BUILD + end + + if self.periodics.next_state_req <= time_now then + _request_state() + self.periodics.next_state_req = time_now + PERIODICS.STATE + end + + if self.periodics.next_tanks_req <= time_now then + _request_tanks() + self.periodics.next_tanks_req = time_now + PERIODICS.TANKS + end + end + + self.session.post_update() + end + + -- get the unit session database + function public.get_db() return self.db end + + return public +end + +return sps diff --git a/supervisor/startup.lua b/supervisor/startup.lua index bfdd925..456d9d8 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.4.6" +local SUPERVISOR_VERSION = "beta-v0.4.7" local print = util.print local println = util.println From 0f7e77b0cb8afb232743be47c6e2afeb2797f98a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Jun 2022 15:36:47 -0400 Subject: [PATCH 247/587] #28 fixed addresses for RTU session --- supervisor/session/rtu/sps.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua index 47f25ac..84f3995 100644 --- a/supervisor/session/rtu/sps.lua +++ b/supervisor/session/rtu/sps.lua @@ -96,19 +96,19 @@ function sps.new(session_id, unit_id, advert, out_queue) -- query the build of the device local function _request_build() - -- read input registers 1 through 7 (start = 1, count = 7) - self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 7 }) + -- read input registers 1 through 9 (start = 1, count = 9) + self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 9 }) end -- query the state of the device local function _request_state() - -- read input registers 8 through 9 (start = 8, count = 2) - self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 8, 2 }) + -- read input register 10 (start = 10, count = 1) + self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 10, 1 }) end -- query the tanks of the device local function _request_tanks() - -- read input registers 10 through 21 (start = 10, count = 12) + -- read input registers 11 through 19 (start = 11, count = 9) self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 10, 12 }) end From e443beec19b547e683b323fd475533e1871f528d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Jun 2022 16:25:23 -0400 Subject: [PATCH 248/587] #66 SNA RTU supervisor session --- supervisor/session/rtu.lua | 3 +- supervisor/session/rtu/sna.lua | 169 +++++++++++++++++++++++++++++++++ supervisor/startup.lua | 2 +- 3 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 supervisor/session/rtu/sna.lua diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 00dc81b..e4827d9 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -9,6 +9,7 @@ local svrs_boiler = require("supervisor.session.rtu.boiler") local svrs_emachine = require("supervisor.session.rtu.emachine") local svrs_envd = require("supervisor.session.rtu.envd") local svrs_redstone = require("supervisor.session.rtu.redstone") +local svrs_sna = require("supervisor.session.rtu.sna") local svrs_sps = require("supervisor.session.rtu.sps") local svrs_turbine = require("supervisor.session.rtu.turbine") @@ -106,7 +107,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement) elseif u_type == RTU_UNIT_TYPES.SPS then unit = svrs_sps.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.SNA then - -- @todo Mekanism 10.1+ + unit = svrs_sna.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then unit = svrs_envd.new(self.id, i, unit_advert, self.out_q) else diff --git a/supervisor/session/rtu/sna.lua b/supervisor/session/rtu/sna.lua new file mode 100644 index 0000000..27f2868 --- /dev/null +++ b/supervisor/session/rtu/sna.lua @@ -0,0 +1,169 @@ +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") + +local unit_session = require("supervisor.session.rtu.unit_session") + +local sna = {} + +local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local MODBUS_FCODE = types.MODBUS_FCODE + +local TXN_TYPES = { + BUILD = 1, + STATE = 2, + TANKS = 3 +} + +local TXN_TAGS = { + "sna.build", + "sna.state", + "sna.tanks", +} + +local PERIODICS = { + BUILD = 1000, + STATE = 500, + TANKS = 1000 +} + +-- create a new sna rtu session runner +---@param session_id integer +---@param unit_id integer +---@param advert rtu_advertisement +---@param out_queue mqueue +function sna.new(session_id, unit_id, advert, out_queue) + -- type check + if advert.type ~= RTU_UNIT_TYPES.SNA then + log.error("attempt to instantiate sna RTU for type '" .. advert.type .. "'. this is a bug.") + return nil + end + + local log_tag = "session.rtu(" .. session_id .. ").sna(" .. advert.index .. "): " + + local self = { + session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), + has_build = false, + periodics = { + next_build_req = 0, + next_state_req = 0, + next_tanks_req = 0, + }, + ---@class sna_session_db + db = { + build = { + input_cap = 0, + output_cap = 0 + }, + state = { + production_rate = 0.0, + peak_production = 0.0 + }, + tanks = { + input = {}, ---@type tank_fluid + input_need = 0, + input_fill = 0.0, + output = {}, ---@type tank_fluid + output_need = 0, + output_fill = 0.0 + } + } + } + + local public = self.session.get() + + -- PRIVATE FUNCTIONS -- + + -- query the build of the device + local function _request_build() + -- read input registers 1 through 2 (start = 1, count = 2) + self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 2 }) + end + + -- query the state of the device + local function _request_state() + -- read input registers 3 through 4 (start = 3, count = 2) + self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 3, 2 }) + end + + -- query the tanks of the device + local function _request_tanks() + -- read input registers 5 through 10 (start = 5, count = 6) + self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 5, 6 }) + end + + -- PUBLIC FUNCTIONS -- + + -- handle a packet + ---@param m_pkt modbus_frame + function public.handle_packet(m_pkt) + local txn_type = self.session.try_resolve(m_pkt.txn_id) + if txn_type == false then + -- nothing to do + elseif txn_type == TXN_TYPES.BUILD then + -- build response + -- load in data if correct length + if m_pkt.length == 2 then + self.db.build.input_cap = m_pkt.data[1] + self.db.build.output_cap = m_pkt.data[2] + self.has_build = true + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == TXN_TYPES.STATE then + -- state response + -- load in data if correct length + if m_pkt.length == 2 then + self.db.state.production_rate = m_pkt.data[1] + self.db.state.peak_production = m_pkt.data[2] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == TXN_TYPES.TANKS then + -- tanks response + -- load in data if correct length + if m_pkt.length == 6 then + self.db.tanks.input = m_pkt.data[1] + self.db.tanks.input_need = m_pkt.data[2] + self.db.tanks.input_fill = m_pkt.data[3] + self.db.tanks.output = m_pkt.data[4] + self.db.tanks.output_need = m_pkt.data[5] + self.db.tanks.output_fill = m_pkt.data[6] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == nil then + log.error(log_tag .. "unknown transaction reply") + else + log.error(log_tag .. "unknown transaction type " .. txn_type) + end + end + + -- update this runner + ---@param time_now integer milliseconds + function public.update(time_now) + if not self.has_build and self.periodics.next_build_req <= time_now then + _request_build() + self.periodics.next_build_req = time_now + PERIODICS.BUILD + end + + if self.periodics.next_state_req <= time_now then + _request_state() + self.periodics.next_state_req = time_now + PERIODICS.STATE + end + + if self.periodics.next_tanks_req <= time_now then + _request_tanks() + self.periodics.next_tanks_req = time_now + PERIODICS.TANKS + end + + self.session.post_update() + end + + -- get the unit session database + function public.get_db() return self.db end + + return public +end + +return sna diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 456d9d8..c71804f 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.4.7" +local SUPERVISOR_VERSION = "beta-v0.4.8" local print = util.print local println = util.println From 6d97d45227dd6cda1a69b1a266944744dedb8a7c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Jun 2022 17:45:52 -0400 Subject: [PATCH 249/587] #67 imatrix RTU supervisor session --- rtu/dev/imatrix_rtu.lua | 8 +- supervisor/session/rtu.lua | 13 +- supervisor/session/rtu/imatrix.lua | 203 +++++++++++++++++++++++++++++ supervisor/startup.lua | 2 +- 4 files changed, 220 insertions(+), 6 deletions(-) create mode 100644 supervisor/session/rtu/imatrix.lua diff --git a/rtu/dev/imatrix_rtu.lua b/rtu/dev/imatrix_rtu.lua index 3ac3acd..6e99453 100644 --- a/rtu/dev/imatrix_rtu.lua +++ b/rtu/dev/imatrix_rtu.lua @@ -25,13 +25,13 @@ function imatrix_rtu.new(imatrix) unit.connect_input_reg(imatrix.getTransferCap) unit.connect_input_reg(imatrix.getInstalledCells) unit.connect_input_reg(imatrix.getInstalledProviders) - -- containers - unit.connect_input_reg(imatrix.getEnergy) - unit.connect_input_reg(imatrix.getEnergyNeeded) - unit.connect_input_reg(imatrix.getEnergyFilledPercentage) -- I/O rates unit.connect_input_reg(imatrix.getLastInput) unit.connect_input_reg(imatrix.getLastOutput) + -- tanks + unit.connect_input_reg(imatrix.getEnergy) + unit.connect_input_reg(imatrix.getEnergyNeeded) + unit.connect_input_reg(imatrix.getEnergyFilledPercentage) -- holding registers -- -- none diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index e4827d9..abdfc61 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -8,6 +8,7 @@ local util = require("scada-common.util") local svrs_boiler = require("supervisor.session.rtu.boiler") local svrs_emachine = require("supervisor.session.rtu.emachine") local svrs_envd = require("supervisor.session.rtu.envd") +local svrs_imatrix = require("supervisor.session.rtu.imatrix") local svrs_redstone = require("supervisor.session.rtu.redstone") local svrs_sna = require("supervisor.session.rtu.sna") local svrs_sps = require("supervisor.session.rtu.sps") @@ -91,24 +92,34 @@ function rtu.new_session(id, in_queue, out_queue, advertisement) -- create unit by type if u_type == RTU_UNIT_TYPES.REDSTONE then + -- redstone unit, rs_in_q = svrs_redstone.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.BOILER then + -- boiler unit = svrs_boiler.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.BOILER_VALVE then + -- boiler (Mekanism 10.1+) -- @todo Mekanism 10.1+ elseif u_type == RTU_UNIT_TYPES.TURBINE then + -- turbine unit = svrs_turbine.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.TURBINE_VALVE then + -- turbine (Mekanism 10.1+) -- @todo Mekanism 10.1+ elseif u_type == RTU_UNIT_TYPES.EMACHINE then + -- mekanism [energy] machine unit = svrs_emachine.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.IMATRIX then - -- @todo Mekanism 10.1+ + -- induction matrix + unit = svrs_imatrix.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.SPS then + -- super-critical phase shifter unit = svrs_sps.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.SNA then + -- solar neutron activator unit = svrs_sna.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then + -- environment detector unit = svrs_envd.new(self.id, i, unit_advert, self.out_q) else log.error(log_header .. "bad advertisement: encountered unsupported RTU type") diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua new file mode 100644 index 0000000..92c32f4 --- /dev/null +++ b/supervisor/session/rtu/imatrix.lua @@ -0,0 +1,203 @@ +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") + +local unit_session = require("supervisor.session.rtu.unit_session") + +local imatrix = {} + +local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local MODBUS_FCODE = types.MODBUS_FCODE + +local TXN_TYPES = { + FORMED = 1, + BUILD = 2, + STATE = 3, + TANKS = 4 +} + +local TXN_TAGS = { + "imatrix.formed", + "imatrix.build", + "imatrix.state", + "imatrix.tanks", +} + +local PERIODICS = { + FORMED = 2000, + BUILD = 1000, + STATE = 500, + TANKS = 1000 +} + +-- create a new imatrix rtu session runner +---@param session_id integer +---@param unit_id integer +---@param advert rtu_advertisement +---@param out_queue mqueue +function imatrix.new(session_id, unit_id, advert, out_queue) + -- type check + if advert.type ~= RTU_UNIT_TYPES.IMATRIX then + log.error("attempt to instantiate imatrix RTU for type '" .. advert.type .. "'. this is a bug.") + return nil + end + + local log_tag = "session.rtu(" .. session_id .. ").imatrix(" .. advert.index .. "): " + + local self = { + session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), + has_build = false, + periodics = { + next_formed_req = 0, + next_build_req = 0, + next_state_req = 0, + next_tanks_req = 0, + }, + ---@class imatrix_session_db + db = { + formed = false, + build = { + length = 0, + width = 0, + height = 0, + min_pos = 0, + max_pos = 0, + max_energy = 0, + transfer_cap = 0, + cells = 0, + providers = 0 + }, + state = { + last_input = 0, + last_output = 0 + }, + tanks = { + energy = 0, + energy_need = 0, + energy_fill = 0.0 + } + } + } + + local public = self.session.get() + + -- PRIVATE FUNCTIONS -- + + -- query if the build is formed + local function _request_formed() + -- read discrete input 1 (start = 1, count = 1) + self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) + end + + -- query the build of the device + local function _request_build() + -- read input registers 1 through 9 (start = 1, count = 9) + self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 9 }) + end + + -- query the state of the device + local function _request_state() + -- read input register 10 through 11 (start = 10, count = 2) + self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 10, 2 }) + end + + -- query the tanks of the device + local function _request_tanks() + -- read input registers 12 through 15 (start = 12, count = 3) + self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 12, 3 }) + end + + -- PUBLIC FUNCTIONS -- + + -- handle a packet + ---@param m_pkt modbus_frame + function public.handle_packet(m_pkt) + local txn_type = self.session.try_resolve(m_pkt.txn_id) + if txn_type == false then + -- nothing to do + elseif txn_type == TXN_TYPES.FORMED then + -- formed response + -- load in data if correct length + if m_pkt.length == 1 then + self.db.formed = m_pkt.data[1] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == TXN_TYPES.BUILD then + -- build response + -- load in data if correct length + if m_pkt.length == 9 then + self.db.build.length = m_pkt.data[1] + self.db.build.width = m_pkt.data[2] + self.db.build.height = m_pkt.data[3] + self.db.build.min_pos = m_pkt.data[4] + self.db.build.max_pos = m_pkt.data[5] + self.db.build.max_energy = m_pkt.data[6] + self.db.build.transfer_cap = m_pkt.data[7] + self.db.build.cells = m_pkt.data[8] + self.db.build.providers = m_pkt.data[9] + self.has_build = true + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == TXN_TYPES.STATE then + -- state response + -- load in data if correct length + if m_pkt.length == 2 then + self.db.state.last_input = m_pkt.data[1] + self.db.state.last_output = m_pkt.data[2] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == TXN_TYPES.TANKS then + -- tanks response + -- load in data if correct length + if m_pkt.length == 3 then + self.db.tanks.energy = m_pkt.data[1] + self.db.tanks.energy_need = m_pkt.data[2] + self.db.tanks.energy_fill = m_pkt.data[3] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == nil then + log.error(log_tag .. "unknown transaction reply") + else + log.error(log_tag .. "unknown transaction type " .. txn_type) + end + end + + -- update this runner + ---@param time_now integer milliseconds + function public.update(time_now) + if self.periodics.next_formed_req <= time_now then + _request_formed() + self.periodics.next_formed_req = time_now + PERIODICS.FORMED + end + + if self.db.formed then + if not self.has_build and self.periodics.next_build_req <= time_now then + _request_build() + self.periodics.next_build_req = time_now + PERIODICS.BUILD + end + + if self.periodics.next_state_req <= time_now then + _request_state() + self.periodics.next_state_req = time_now + PERIODICS.STATE + end + + if self.periodics.next_tanks_req <= time_now then + _request_tanks() + self.periodics.next_tanks_req = time_now + PERIODICS.TANKS + end + end + + self.session.post_update() + end + + -- get the unit session database + function public.get_db() return self.db end + + return public +end + +return imatrix diff --git a/supervisor/startup.lua b/supervisor/startup.lua index c71804f..82c38e6 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.4.8" +local SUPERVISOR_VERSION = "beta-v0.4.9" local print = util.print local println = util.println From c764506999207a5d95dc1ac37b96c9b09de3e05a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Jun 2022 17:59:24 -0400 Subject: [PATCH 250/587] #67 boilerv RTU supervisor session, supervisor session cleanup --- supervisor/session/rtu.lua | 3 +- supervisor/session/rtu/boiler.lua | 4 +- supervisor/session/rtu/boilerv.lua | 229 ++++++++++++++++++++++++++++ supervisor/session/rtu/emachine.lua | 2 +- supervisor/session/rtu/imatrix.lua | 6 +- supervisor/session/rtu/redstone.lua | 2 +- supervisor/session/rtu/sna.lua | 4 +- supervisor/session/rtu/sps.lua | 6 +- supervisor/session/rtu/turbine.lua | 4 +- supervisor/startup.lua | 2 +- 10 files changed, 246 insertions(+), 16 deletions(-) create mode 100644 supervisor/session/rtu/boilerv.lua diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index abdfc61..cbe1c9f 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -6,6 +6,7 @@ local util = require("scada-common.util") -- supervisor rtu sessions (svrs) local svrs_boiler = require("supervisor.session.rtu.boiler") +local svrs_boilerv = require("supervisor.session.rtu.boilerv") local svrs_emachine = require("supervisor.session.rtu.emachine") local svrs_envd = require("supervisor.session.rtu.envd") local svrs_imatrix = require("supervisor.session.rtu.imatrix") @@ -99,7 +100,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement) unit = svrs_boiler.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.BOILER_VALVE then -- boiler (Mekanism 10.1+) - -- @todo Mekanism 10.1+ + unit = svrs_boilerv.new(self.id, 1, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.TURBINE then -- turbine unit = svrs_turbine.new(self.id, i, unit_advert, self.out_q) diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua index 54c1080..4aecb28 100644 --- a/supervisor/session/rtu/boiler.lua +++ b/supervisor/session/rtu/boiler.lua @@ -18,7 +18,7 @@ local TXN_TYPES = { local TXN_TAGS = { "boiler.build", "boiler.state", - "boiler.tanks", + "boiler.tanks" } local PERIODICS = { @@ -47,7 +47,7 @@ function boiler.new(session_id, unit_id, advert, out_queue) periodics = { next_build_req = 0, next_state_req = 0, - next_tanks_req = 0, + next_tanks_req = 0 }, ---@class boiler_session_db db = { diff --git a/supervisor/session/rtu/boilerv.lua b/supervisor/session/rtu/boilerv.lua new file mode 100644 index 0000000..1845668 --- /dev/null +++ b/supervisor/session/rtu/boilerv.lua @@ -0,0 +1,229 @@ +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") + +local unit_session = require("supervisor.session.rtu.unit_session") + +local boilerv = {} + +local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local MODBUS_FCODE = types.MODBUS_FCODE + +local TXN_TYPES = { + FORMED = 1, + BUILD = 2, + STATE = 3, + TANKS = 4 +} + +local TXN_TAGS = { + "boilerv.formed", + "boilerv.build", + "boilerv.state", + "boilerv.tanks" +} + +local PERIODICS = { + FORMED = 2000, + BUILD = 1000, + STATE = 500, + TANKS = 1000 +} + +-- create a new boilerv rtu session runner +---@param session_id integer +---@param unit_id integer +---@param advert rtu_advertisement +---@param out_queue mqueue +function boilerv.new(session_id, unit_id, advert, out_queue) + -- type check + if advert.type ~= RTU_UNIT_TYPES.BOILER_VALVE then + log.error("attempt to instantiate boilerv RTU for type '" .. advert.type .. "'. this is a bug.") + return nil + end + + local log_tag = "session.rtu(" .. session_id .. ").boilerv(" .. advert.index .. "): " + + local self = { + session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), + has_build = false, + periodics = { + next_formed_req = 0, + next_build_req = 0, + next_state_req = 0, + next_tanks_req = 0 + }, + ---@class boilerv_session_db + db = { + formed = false, + build = { + length = 0, + width = 0, + height = 0, + min_pos = 0, + max_pos = 0, + boil_cap = 0.0, + steam_cap = 0, + water_cap = 0, + hcoolant_cap = 0, + ccoolant_cap = 0, + superheaters = 0, + max_boil_rate = 0.0, + env_loss = 0.0 + }, + state = { + temperature = 0.0, + boil_rate = 0.0 + }, + tanks = { + steam = {}, ---@type tank_fluid + steam_need = 0, + steam_fill = 0.0, + water = {}, ---@type tank_fluid + water_need = 0, + water_fill = 0.0, + hcool = {}, ---@type tank_fluid + hcool_need = 0, + hcool_fill = 0.0, + ccool = {}, ---@type tank_fluid + ccool_need = 0, + ccool_fill = 0.0 + } + } + } + + local public = self.session.get() + + -- PRIVATE FUNCTIONS -- + + -- query if the multiblock is formed + local function _request_formed() + -- read discrete input 1 (start = 1, count = 1) + self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) + end + + -- query the build of the device + local function _request_build() + -- read input registers 1 through 13 (start = 1, count = 13) + self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 7 }) + end + + -- query the state of the device + local function _request_state() + -- read input registers 14 through 16 (start = 14, count = 2) + self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 14, 2 }) + end + + -- query the tanks of the device + local function _request_tanks() + -- read input registers 17 through 29 (start = 17, count = 12) + self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 17, 12 }) + end + + -- PUBLIC FUNCTIONS -- + + -- handle a packet + ---@param m_pkt modbus_frame + function public.handle_packet(m_pkt) + local txn_type = self.session.try_resolve(m_pkt.txn_id) + if txn_type == false then + -- nothing to do + elseif txn_type == TXN_TYPES.FORMED then + -- formed response + -- load in data if correct length + if m_pkt.length == 1 then + self.db.formed = m_pkt.data[1] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == TXN_TYPES.BUILD then + -- build response + -- load in data if correct length + if m_pkt.length == 13 then + self.db.build.length = m_pkt.data[1] + self.db.build.width = m_pkt.data[2] + self.db.build.height = m_pkt.data[3] + self.db.build.min_pos = m_pkt.data[4] + self.db.build.max_pos = m_pkt.data[5] + self.db.build.boil_cap = m_pkt.data[6] + self.db.build.steam_cap = m_pkt.data[7] + self.db.build.water_cap = m_pkt.data[8] + self.db.build.hcoolant_cap = m_pkt.data[9] + self.db.build.ccoolant_cap = m_pkt.data[10] + self.db.build.superheaters = m_pkt.data[11] + self.db.build.max_boil_rate = m_pkt.data[12] + self.db.build.env_loss = m_pkt.data[13] + self.has_build = true + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == TXN_TYPES.STATE then + -- state response + -- load in data if correct length + if m_pkt.length == 2 then + self.db.state.temperature = m_pkt.data[1] + self.db.state.boil_rate = m_pkt.data[2] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == TXN_TYPES.TANKS then + -- tanks response + -- load in data if correct length + if m_pkt.length == 12 then + self.db.tanks.steam = m_pkt.data[1] + self.db.tanks.steam_need = m_pkt.data[2] + self.db.tanks.steam_fill = m_pkt.data[3] + self.db.tanks.water = m_pkt.data[4] + self.db.tanks.water_need = m_pkt.data[5] + self.db.tanks.water_fill = m_pkt.data[6] + self.db.tanks.hcool = m_pkt.data[7] + self.db.tanks.hcool_need = m_pkt.data[8] + self.db.tanks.hcool_fill = m_pkt.data[9] + self.db.tanks.ccool = m_pkt.data[10] + self.db.tanks.ccool_need = m_pkt.data[11] + self.db.tanks.ccool_fill = m_pkt.data[12] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == nil then + log.error(log_tag .. "unknown transaction reply") + else + log.error(log_tag .. "unknown transaction type " .. txn_type) + end + end + + -- update this runner + ---@param time_now integer milliseconds + function public.update(time_now) + if self.periodics.next_formed_req <= time_now then + _request_formed() + self.periodics.next_formed_req = time_now + PERIODICS.FORMED + end + + if self.db.formed then + if not self.has_build and self.periodics.next_build_req <= time_now then + _request_build() + self.periodics.next_build_req = time_now + PERIODICS.BUILD + end + + if self.periodics.next_state_req <= time_now then + _request_state() + self.periodics.next_state_req = time_now + PERIODICS.STATE + end + + if self.periodics.next_tanks_req <= time_now then + _request_tanks() + self.periodics.next_tanks_req = time_now + PERIODICS.TANKS + end + end + + self.session.post_update() + end + + -- get the unit session database + function public.get_db() return self.db end + + return public +end + +return boilerv diff --git a/supervisor/session/rtu/emachine.lua b/supervisor/session/rtu/emachine.lua index 703330d..d6ec2c7 100644 --- a/supervisor/session/rtu/emachine.lua +++ b/supervisor/session/rtu/emachine.lua @@ -43,7 +43,7 @@ function emachine.new(session_id, unit_id, advert, out_queue) has_build = false, periodics = { next_build_req = 0, - next_storage_req = 0, + next_storage_req = 0 }, ---@class emachine_session_db db = { diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua index 92c32f4..72fdde6 100644 --- a/supervisor/session/rtu/imatrix.lua +++ b/supervisor/session/rtu/imatrix.lua @@ -20,7 +20,7 @@ local TXN_TAGS = { "imatrix.formed", "imatrix.build", "imatrix.state", - "imatrix.tanks", + "imatrix.tanks" } local PERIODICS = { @@ -51,7 +51,7 @@ function imatrix.new(session_id, unit_id, advert, out_queue) next_formed_req = 0, next_build_req = 0, next_state_req = 0, - next_tanks_req = 0, + next_tanks_req = 0 }, ---@class imatrix_session_db db = { @@ -83,7 +83,7 @@ function imatrix.new(session_id, unit_id, advert, out_queue) -- PRIVATE FUNCTIONS -- - -- query if the build is formed + -- query if the multiblock is formed local function _request_formed() -- read discrete input 1 (start = 1, count = 1) self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index 81be32f..b728cdb 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -66,7 +66,7 @@ function redstone.new(session_id, unit_id, advert, out_queue) has_ai = false, periodics = { next_di_req = 0, - next_ir_req = 0, + next_ir_req = 0 }, io_list = { digital_in = {}, -- discrete inputs diff --git a/supervisor/session/rtu/sna.lua b/supervisor/session/rtu/sna.lua index 27f2868..539b98e 100644 --- a/supervisor/session/rtu/sna.lua +++ b/supervisor/session/rtu/sna.lua @@ -18,7 +18,7 @@ local TXN_TYPES = { local TXN_TAGS = { "sna.build", "sna.state", - "sna.tanks", + "sna.tanks" } local PERIODICS = { @@ -47,7 +47,7 @@ function sna.new(session_id, unit_id, advert, out_queue) periodics = { next_build_req = 0, next_state_req = 0, - next_tanks_req = 0, + next_tanks_req = 0 }, ---@class sna_session_db db = { diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua index 84f3995..72c3f13 100644 --- a/supervisor/session/rtu/sps.lua +++ b/supervisor/session/rtu/sps.lua @@ -20,7 +20,7 @@ local TXN_TAGS = { "sps.formed", "sps.build", "sps.state", - "sps.tanks", + "sps.tanks" } local PERIODICS = { @@ -51,7 +51,7 @@ function sps.new(session_id, unit_id, advert, out_queue) next_formed_req = 0, next_build_req = 0, next_state_req = 0, - next_tanks_req = 0, + next_tanks_req = 0 }, ---@class sps_session_db db = { @@ -88,7 +88,7 @@ function sps.new(session_id, unit_id, advert, out_queue) -- PRIVATE FUNCTIONS -- - -- query if the build is formed + -- query if the multiblock is formed local function _request_formed() -- read discrete input 1 (start = 1, count = 1) self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) diff --git a/supervisor/session/rtu/turbine.lua b/supervisor/session/rtu/turbine.lua index 34e6cd0..17a4ee0 100644 --- a/supervisor/session/rtu/turbine.lua +++ b/supervisor/session/rtu/turbine.lua @@ -19,7 +19,7 @@ local TXN_TYPES = { local TXN_TAGS = { "turbine.build", "turbine.state", - "turbine.tanks", + "turbine.tanks" } local PERIODICS = { @@ -48,7 +48,7 @@ function turbine.new(session_id, unit_id, advert, out_queue) periodics = { next_build_req = 0, next_state_req = 0, - next_tanks_req = 0, + next_tanks_req = 0 }, ---@class turbine_session_db db = { diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 82c38e6..d873a36 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.4.9" +local SUPERVISOR_VERSION = "beta-v0.4.10" local print = util.print local println = util.println From 5068e475901fd3168198fe7c33ced630e0788d40 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 5 Jun 2022 09:30:56 -0400 Subject: [PATCH 251/587] #67 turbine valve RTU supervisor session, bugfixes with redstone RTU session --- rtu/dev/turbinev_rtu.lua | 4 +- supervisor/session/rtu.lua | 56 ++++-- supervisor/session/rtu/redstone.lua | 1 + supervisor/session/rtu/turbine.lua | 6 +- supervisor/session/rtu/turbinev.lua | 295 ++++++++++++++++++++++++++++ supervisor/startup.lua | 2 +- 6 files changed, 342 insertions(+), 22 deletions(-) create mode 100644 supervisor/session/rtu/turbinev.lua diff --git a/rtu/dev/turbinev_rtu.lua b/rtu/dev/turbinev_rtu.lua index be90f8e..191427d 100644 --- a/rtu/dev/turbinev_rtu.lua +++ b/rtu/dev/turbinev_rtu.lua @@ -27,16 +27,16 @@ function turbinev_rtu.new(turbine) unit.connect_input_reg(turbine.getVents) unit.connect_input_reg(turbine.getDispersers) unit.connect_input_reg(turbine.getCondensers) - unit.connect_input_reg(turbine.getDumpingMode) unit.connect_input_reg(turbine.getSteamCapacity) unit.connect_input_reg(turbine.getMaxEnergy) unit.connect_input_reg(turbine.getMaxFlowRate) - unit.connect_input_reg(turbine.getMaxWaterOutput) unit.connect_input_reg(turbine.getMaxProduction) + unit.connect_input_reg(turbine.getMaxWaterOutput) -- current state unit.connect_input_reg(turbine.getFlowRate) unit.connect_input_reg(turbine.getProductionRate) unit.connect_input_reg(turbine.getLastSteamInputRate) + unit.connect_input_reg(turbine.getDumpingMode) -- tanks/containers unit.connect_input_reg(turbine.getSteam) unit.connect_input_reg(turbine.getSteamNeeded) diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index cbe1c9f..7644f41 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -14,6 +14,7 @@ local svrs_redstone = require("supervisor.session.rtu.redstone") local svrs_sna = require("supervisor.session.rtu.sna") local svrs_sps = require("supervisor.session.rtu.sps") local svrs_turbine = require("supervisor.session.rtu.turbine") +local svrs_turbinev = require("supervisor.session.rtu.turbinev") local rtu = {} @@ -66,20 +67,30 @@ function rtu.new_session(id, in_queue, out_queue, advertisement) rtu_conn_watchdog = util.new_watchdog(3), last_rtt = 0, rs_io_q = {}, + turbine_cmd_q = {}, + turbine_cmd_capable = false, units = {} } ---@class rtu_session local public = {} + local function _reset_config() + self.units = {} + self.rs_io_q = {} + self.turbine_cmd_q = {} + self.turbine_cmd_capable = false + end + -- parse the recorded advertisement and create unit sub-sessions local function _handle_advertisement() self.units = {} self.rs_io_q = {} for i = 1, #self.advert do - local unit = nil ---@type unit_session|nil - local rs_in_q = nil ---@type mqueue|nil + local unit = nil ---@type unit_session|nil + local rs_in_q = nil ---@type mqueue|nil + local tbv_in_q = nil ---@type mqueue|nil ---@type rtu_advertisement local unit_advert = { @@ -106,7 +117,8 @@ function rtu.new_session(id, in_queue, out_queue, advertisement) unit = svrs_turbine.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.TURBINE_VALVE then -- turbine (Mekanism 10.1+) - -- @todo Mekanism 10.1+ + unit, tbv_in_q = svrs_turbinev.new(self.id, i, unit_advert, self.out_q) + self.turbine_cmd_capable = true elseif u_type == RTU_UNIT_TYPES.EMACHINE then -- mekanism [energy] machine unit = svrs_emachine.new(self.id, i, unit_advert, self.out_q) @@ -129,21 +141,33 @@ function rtu.new_session(id, in_queue, out_queue, advertisement) if unit ~= nil then table.insert(self.units, unit) - if self.rs_io_q[unit_advert.reactor] == nil then - self.rs_io_q[unit_advert.reactor] = rs_in_q - else - self.units = {} - self.rs_io_q = {} - log.error(log_header .. "bad advertisement: duplicate redstone RTU for reactor " .. unit_advert.reactor) - break + if u_type == RTU_UNIT_TYPES.REDSTONE then + if self.rs_io_q[unit_advert.reactor] == nil then + self.rs_io_q[unit_advert.reactor] = rs_in_q + else + _reset_config() + log.error(log_header .. util.c("bad advertisement: duplicate redstone RTU for reactor " .. unit_advert.reactor)) + break + end + elseif u_type == RTU_UNIT_TYPES.TURBINE_VALVE then + if self.turbine_cmd_q[unit_advert.reactor] == nil then + self.turbine_cmd_q[unit_advert.reactor] = {} + end + + local queues = self.turbine_cmd_q[unit_advert.reactor] + + if queues[unit_advert.index] == nil then + queues[unit_advert.index] = tbv_in_q + else + _reset_config() + log.error(log_header .. util.c("bad advertisement: duplicate turbine RTU (same index of ", + unit_advert.index, ") for reactor ", unit_advert.reactor)) + break + end end else - self.units = {} - self.rs_io_q = {} - - local type_string = comms.advert_type_to_rtu_t(u_type) - if type_string == nil then type_string = "unknown" end - + _reset_config() + local type_string = util.strval(comms.advert_type_to_rtu_t(u_type)) log.error(log_header .. "bad advertisement: error occured while creating a unit (type is " .. type_string .. ")") break end diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index b728cdb..a505d86 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -62,6 +62,7 @@ function redstone.new(session_id, unit_id, advert, out_queue) local self = { session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), + in_q = mqueue.new(), has_di = false, has_ai = false, periodics = { diff --git a/supervisor/session/rtu/turbine.lua b/supervisor/session/rtu/turbine.lua index 17a4ee0..c2dd282 100644 --- a/supervisor/session/rtu/turbine.lua +++ b/supervisor/session/rtu/turbine.lua @@ -64,9 +64,9 @@ function turbine.new(session_id, unit_id, advert, out_queue) max_water_output = 0 }, state = { - flow_rate = 0.0, - prod_rate = 0.0, - steam_input_rate = 0.0, + flow_rate = 0, + prod_rate = 0, + steam_input_rate = 0, dumping_mode = DUMPING_MODE.IDLE ---@type DUMPING_MODE }, tanks = { diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua new file mode 100644 index 0000000..59668f7 --- /dev/null +++ b/supervisor/session/rtu/turbinev.lua @@ -0,0 +1,295 @@ +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local types = require("scada-common.types") +local util = require("scada-common.util") + +local unit_session = require("supervisor.session.rtu.unit_session") + +local turbinev = {} + +local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local DUMPING_MODE = types.DUMPING_MODE +local MODBUS_FCODE = types.MODBUS_FCODE + +local TBV_RTU_S_CMDS = { + INC_DUMP_MODE = 1, + DEC_DUMP_MODE = 2 +} + +local TBV_RTU_S_DATA = { + SET_DUMP_MODE = 1 +} + +turbinev.RS_RTU_S_CMDS = TBV_RTU_S_CMDS +turbinev.RS_RTU_S_DATA = TBV_RTU_S_DATA + +local TXN_TYPES = { + FORMED = 1, + BUILD = 2, + STATE = 3, + TANKS = 4, + INC_DUMP = 5, + DEC_DUMP = 6, + SET_DUMP = 7 +} + +local TXN_TAGS = { + "turbinev.formed", + "turbinev.build", + "turbinev.state", + "turbinev.tanks", + "turbinev.inc_dump", + "turbinev.dec_dump", + "turbinev.set_dump" +} + +local PERIODICS = { + FORMED = 2000, + BUILD = 1000, + STATE = 500, + TANKS = 1000 +} + +-- create a new turbinev rtu session runner +---@param session_id integer +---@param unit_id integer +---@param advert rtu_advertisement +---@param out_queue mqueue +function turbinev.new(session_id, unit_id, advert, out_queue) + -- type check + if advert.type ~= RTU_UNIT_TYPES.TURBINE_VALVE then + log.error("attempt to instantiate turbinev RTU for type '" .. advert.type .. "'. this is a bug.") + return nil + end + + local log_tag = "session.rtu(" .. session_id .. ").turbinev(" .. advert.index .. "): " + + local self = { + session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), + in_q = mqueue.new(), + has_build = false, + periodics = { + next_build_req = 0, + next_state_req = 0, + next_tanks_req = 0 + }, + ---@class turbinev_session_db + db = { + formed = false, + build = { + length = 0, + width = 0, + height = 0, + min_pos = 0, + max_pos = 0, + blades = 0, + coils = 0, + vents = 0, + dispersers = 0, + condensers = 0, + steam_cap = 0, + max_energy = 0, + max_flow_rate = 0, + max_production = 0, + max_water_output = 0 + }, + state = { + flow_rate = 0, + prod_rate = 0, + steam_input_rate = 0, + dumping_mode = DUMPING_MODE.IDLE ---@type DUMPING_MODE + }, + tanks = { + steam = 0, + steam_need = 0, + steam_fill = 0.0, + energy = 0, + energy_need = 0, + energy_fill = 0.0 + } + } + } + + local public = self.session.get() + + -- PRIVATE FUNCTIONS -- + + -- increment the dumping mode + local function _inc_dump_mode() + -- write coil 1 with unused value 0 + self.session.send_request(TXN_TYPES.INC_DUMP, MODBUS_FCODE.WRITE_SINGLE_COIL, { 1, 0 }) + end + + -- decrement the dumping mode + local function _dec_dump_mode() + -- write coil 2 with unused value 0 + self.session.send_request(TXN_TYPES.DEC_DUMP, MODBUS_FCODE.WRITE_SINGLE_COIL, { 2, 0 }) + end + + -- set the dumping mode + ---@param mode DUMPING_MODE + local function _set_dump_mode(mode) + -- write holding register 1 + self.session.send_request(TXN_TYPES.SET_DUMP, MODBUS_FCODE.WRITE_SINGLE_HOLD_REG, { 1, mode }) + end + + -- query if the multiblock is formed + local function _request_formed() + -- read discrete input 1 (start = 1, count = 1) + self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) + end + + -- query the build of the device + local function _request_build() + -- read input registers 1 through 15 (start = 1, count = 15) + self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 15 }) + end + + -- query the state of the device + local function _request_state() + -- read input registers 16 through 19 (start = 16, count = 4) + self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 16, 4 }) + end + + -- query the tanks of the device + local function _request_tanks() + -- read input registers 20 through 25 (start = 20, count = 6) + self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 20, 6 }) + end + + -- PUBLIC FUNCTIONS -- + + -- handle a packet + ---@param m_pkt modbus_frame + function public.handle_packet(m_pkt) + local txn_type = self.session.try_resolve(m_pkt.txn_id) + if txn_type == false then + -- nothing to do + elseif txn_type == TXN_TYPES.BUILD then + -- build response + if m_pkt.length == 15 then + self.db.build.length = m_pkt.data[1] + self.db.build.width = m_pkt.data[2] + self.db.build.height = m_pkt.data[3] + self.db.build.min_pos = m_pkt.data[4] + self.db.build.max_pos = m_pkt.data[5] + self.db.build.blades = m_pkt.data[6] + self.db.build.coils = m_pkt.data[7] + self.db.build.vents = m_pkt.data[8] + self.db.build.dispersers = m_pkt.data[9] + self.db.build.condensers = m_pkt.data[10] + self.db.build.steam_cap = m_pkt.data[11] + self.db.build.max_energy = m_pkt.data[12] + self.db.build.max_flow_rate = m_pkt.data[13] + self.db.build.max_production = m_pkt.data[14] + self.db.build.max_water_output = m_pkt.data[15] + self.has_build = true + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == TXN_TYPES.STATE then + -- state response + if m_pkt.length == 4 then + self.db.state.flow_rate = m_pkt.data[1] + self.db.state.prod_rate = m_pkt.data[2] + self.db.state.steam_input_rate = m_pkt.data[3] + self.db.state.dumping_mode = m_pkt.data[4] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == TXN_TYPES.TANKS then + -- tanks response + if m_pkt.length == 6 then + self.db.tanks.steam = m_pkt.data[1] + self.db.tanks.steam_need = m_pkt.data[2] + self.db.tanks.steam_fill = m_pkt.data[3] + self.db.tanks.energy = m_pkt.data[4] + self.db.tanks.energy_need = m_pkt.data[5] + self.db.tanks.energy_fill = m_pkt.data[6] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == TXN_TYPES.INC_DUMP or txn_type == TXN_TYPES.DEC_DUMP or txn_type == TXN_TYPES.SET_DUMP then + -- successful acknowledgement + elseif txn_type == nil then + log.error(log_tag .. "unknown transaction reply") + else + log.error(log_tag .. "unknown transaction type " .. txn_type) + end + end + + -- update this runner + ---@param time_now integer milliseconds + function public.update(time_now) + -- check command queue + while self.in_q.ready() do + -- get a new message to process + local msg = self.in_q.pop() + + if msg ~= nil then + if msg.qtype == mqueue.TYPE.COMMAND then + -- instruction + local cmd = msg.message + + if cmd == TBV_RTU_S_CMDS.INC_DUMP_MODE then + _inc_dump_mode() + elseif cmd == TBV_RTU_S_CMDS.DEC_DUMP_MODE then + _dec_dump_mode() + else + log.debug(log_tag .. "unrecognized in_q command " .. util.strval(cmd)) + end + elseif msg.qtype == mqueue.TYPE.DATA then + -- instruction with body + local cmd = msg.message ---@type queue_data + if cmd.key == TBV_RTU_S_DATA.SET_DUMP_MODE then + _set_dump_mode(cmd.val) + else + log.debug(log_tag .. "unrecognized in_q data " .. util.strval(cmd.key)) + end + end + end + + -- max 100ms spent processing queue + if util.time() - time_now > 100 then + log.warning(log_tag .. "exceeded 100ms queue process limit") + break + end + end + + time_now = util.time() + + -- handle periodics + + if self.periodics.next_formed_req <= time_now then + _request_formed() + self.periodics.next_formed_req = time_now + PERIODICS.FORMED + end + + if self.db.formed then + if not self.has_build and self.periodics.next_build_req <= time_now then + _request_build() + self.periodics.next_build_req = time_now + PERIODICS.BUILD + end + + if self.periodics.next_state_req <= time_now then + _request_state() + self.periodics.next_state_req = time_now + PERIODICS.STATE + end + + if self.periodics.next_tanks_req <= time_now then + _request_tanks() + self.periodics.next_tanks_req = time_now + PERIODICS.TANKS + end + end + + self.session.post_update() + end + + -- get the unit session database + function public.get_db() return self.db end + + return public, self.in_q +end + +return turbinev diff --git a/supervisor/startup.lua b/supervisor/startup.lua index d873a36..845daa8 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.4.10" +local SUPERVISOR_VERSION = "beta-v0.4.11" local print = util.print local println = util.println From f0c97e8b705e71638be0517c439281af853d1cd4 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 5 Jun 2022 11:16:25 -0400 Subject: [PATCH 252/587] #65 safe concat where appropriate --- reactor-plc/plc.lua | 2 +- rtu/rtu.lua | 2 +- rtu/startup.lua | 26 +++++++++++++------------- rtu/threads.lua | 2 +- scada-common/crypto.lua | 6 +++--- scada-common/log.lua | 2 +- scada-common/ppm.lua | 13 +++++++------ supervisor/session/plc.lua | 4 ++-- supervisor/session/rtu.lua | 4 ++-- supervisor/session/rtu/redstone.lua | 6 +++--- supervisor/session/rtu/turbinev.lua | 4 ++-- supervisor/supervisor.lua | 8 ++++---- 12 files changed, 40 insertions(+), 39 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index c7cb76e..590ce71 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -738,7 +738,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co log.warning("PLC KEEP_ALIVE trip time > 500ms (" .. trip_time .. "ms)") end - -- log.debug("RPLC RTT = ".. trip_time .. "ms") + -- log.debug("RPLC RTT = " .. trip_time .. "ms") _send_keep_alive_ack(timestamp) else diff --git a/rtu/rtu.lua b/rtu/rtu.lua index c0d57fe..d59649d 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -383,7 +383,7 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog) log.warning("RTU KEEP_ALIVE trip time > 500ms (" .. trip_time .. "ms)") end - -- log.debug("RTU RTT = ".. trip_time .. "ms") + -- log.debug("RTU RTT = " .. trip_time .. "ms") _send_keep_alive_ack(timestamp) else diff --git a/rtu/startup.lua b/rtu/startup.lua index 9257f08..a3abcb4 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -102,7 +102,7 @@ for entry_idx = 1, #rtu_redstone do local capabilities = {} - log.debug("init> starting redstone RTU I/O linking for reactor " .. io_reactor .. "...") + log.debug(util.c("init> starting redstone RTU I/O linking for reactor ", io_reactor, "...")) local continue = true @@ -110,7 +110,7 @@ for entry_idx = 1, #rtu_redstone do local unit = units[i] ---@type rtu_unit_registry_entry if unit.reactor == io_reactor and unit.type == rtu_t.redstone then -- duplicate entry - log.warning("init> skipping definition block #" .. entry_idx .. " for reactor " .. io_reactor .. " with already defined redstone I/O") + log.warning(util.c("init> skipping definition block #", entry_idx, " for reactor ", io_reactor, " with already defined redstone I/O")) continue = false break end @@ -131,8 +131,8 @@ for entry_idx = 1, #rtu_redstone do end if not valid then - local message = "init> invalid redstone definition at index " .. i .. " in definition block #" .. entry_idx .. - " (for reactor " .. io_reactor .. ")" + local message = util.c("init> invalid redstone definition at index ", i, " in definition block #", entry_idx, + " (for reactor ", io_reactor, ")") println_ts(message) log.warning(message) else @@ -141,7 +141,7 @@ for entry_idx = 1, #rtu_redstone do if mode == rsio.IO_MODE.DIGITAL_IN then -- can't have duplicate inputs if util.table_contains(capabilities, conf.channel) then - log.warning("init> skipping duplicate input for channel " .. rsio.to_string(conf.channel) .. " on side " .. conf.side) + log.warning(util.c("init> skipping duplicate input for channel ", rsio.to_string(conf.channel), " on side ", conf.side)) else rs_rtu.link_di(conf.side, conf.bundled_color) end @@ -150,7 +150,7 @@ for entry_idx = 1, #rtu_redstone do elseif mode == rsio.IO_MODE.ANALOG_IN then -- can't have duplicate inputs if util.table_contains(capabilities, conf.channel) then - log.warning("init> skipping duplicate input for channel " .. rsio.to_string(conf.channel) .. " on side " .. conf.side) + log.warning(util.c("init> skipping duplicate input for channel ", rsio.to_string(conf.channel), " on side ", conf.side)) else rs_rtu.link_ai(conf.side) end @@ -164,8 +164,8 @@ for entry_idx = 1, #rtu_redstone do table.insert(capabilities, conf.channel) - log.debug("init> linked redstone " .. #capabilities .. ": " .. rsio.to_string(conf.channel) .. " (" .. conf.side .. - ") for reactor " .. io_reactor) + log.debug(util.c("init> linked redstone ", #capabilities, ": ", rsio.to_string(conf.channel), " (", conf.side, ") for reactor ", + io_reactor)) end end @@ -184,7 +184,7 @@ for entry_idx = 1, #rtu_redstone do table.insert(units, unit) - log.debug("init> initialized RTU unit #" .. #units .. ": redstone_io (redstone) [1] for reactor " .. io_reactor) + log.debug(util.c("init> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for reactor ", io_reactor)) end end @@ -193,7 +193,7 @@ for i = 1, #rtu_devices do local device = ppm.get_periph(rtu_devices[i].name) if device == nil then - local message = "init> '" .. rtu_devices[i].name .. "' not found" + local message = util.c("init> '", rtu_devices[i].name, "' not found") println_ts(message) log.warning(message) else @@ -231,7 +231,7 @@ for i = 1, #rtu_devices do rtu_type = rtu_t.env_detector rtu_iface = envd_rtu.new(device) else - local message = "init> device '" .. rtu_devices[i].name .. "' is not a known type (" .. type .. ")" + local message = util.c("init> device '", rtu_devices[i].name, "' is not a known type (", type, ")") println_ts(message) log.warning(message) end @@ -254,8 +254,8 @@ for i = 1, #rtu_devices do table.insert(units, rtu_unit) - log.debug("init> initialized RTU unit #" .. #units .. ": " .. rtu_devices[i].name .. " (" .. rtu_type .. ") [" .. - rtu_devices[i].index .. "] for reactor " .. rtu_devices[i].for_reactor) + log.debug(util.c("init> initialized RTU unit #", #units, ": ", rtu_devices[i].name, " (", rtu_type, ") [", + rtu_devices[i].index, "] for reactor ", rtu_devices[i].for_reactor)) end end end diff --git a/rtu/threads.lua b/rtu/threads.lua index ffe0f65..4e812de 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -307,7 +307,7 @@ function threads.thread__unit_comms(smem, unit) end if not rtu_state.shutdown then - log.info("rtu unit thread " .. unit.name .. "(" .. unit.type .. ") restarting in 5 seconds...") + log.info(util.c("rtu unit thread ", unit.name, "(", unit.type, ") restarting in 5 seconds...")) util.psleep(5) end end diff --git a/scada-common/crypto.lua b/scada-common/crypto.lua index d16bf84..4f8de1c 100644 --- a/scada-common/crypto.lua +++ b/scada-common/crypto.lua @@ -107,7 +107,7 @@ function crypto.encrypt(plaintext) local ciphertext = c_eng.cipher.asHex() ---@type hex log.debug("crypto.encrypt: aes128-ctr-mode took " .. (util.time() - start) .. "ms") - log.debug("ciphertext: " .. ciphertext) + log.debug("ciphertext: " .. util.strval(ciphertext)) return iv, ciphertext end @@ -129,7 +129,7 @@ function crypto.decrypt(iv, ciphertext) local plaintext = stream.toString(stream.fromHex(plaintext_hex)) log.debug("crypto.decrypt: aes128-ctr-mode took " .. (util.time() - start) .. "ms") - log.debug("plaintext: " .. plaintext) + log.debug("plaintext: " .. util.strval(plaintext)) return plaintext end @@ -146,7 +146,7 @@ function crypto.hmac(message_hex) local hash = c_eng.hmac.asHex() ---@type hex log.debug("crypto.hmac: hmac-sha1 took " .. (util.time() - start) .. "ms") - log.debug("hmac: " .. hash) + log.debug("hmac: " .. util.strval(hash)) return hash end diff --git a/scada-common/log.lua b/scada-common/log.lua index f923c0c..0c6ae89 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -177,7 +177,7 @@ function log.dmesg(msg, tag, tag_color) out.write(lines[i]) end - _log("[" .. t_stamp .. "] " .. tag .. " " .. msg) + _log(util.c("[", t_stamp, "] ", tag, " ", msg)) end -- log debug messages diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 740dc2b..bd417aa 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -2,7 +2,8 @@ -- Protected Peripheral Manager -- -local log = require("scada-common.log") +local log = require("scada-common.log") +local util = require("scada-common.util") ---@class ppm local ppm = {} @@ -76,7 +77,7 @@ local function peri_init(iface) count_str = " [" .. self.fault_counts[key] .. " total faults]" end - log.error("PPM: protected " .. key .. "() -> " .. result .. count_str) + log.error(util.c("PPM: protected ", key, "() -> ", result, count_str)) end self.fault_counts[key] = self.fault_counts[key] + 1 @@ -176,7 +177,7 @@ function ppm.mount_all() for i = 1, #ifaces do _ppm_sys.mounts[ifaces[i]] = peri_init(ifaces[i]) - log.info("PPM: found a " .. _ppm_sys.mounts[ifaces[i]].type .. " (" .. ifaces[i] .. ")") + log.info(util.c("PPM: found a ", _ppm_sys.mounts[ifaces[i]].type, " (", ifaces[i], ")")) end if #ifaces == 0 then @@ -199,7 +200,7 @@ function ppm.mount(iface) pm_type = _ppm_sys.mounts[iface].type pm_dev = _ppm_sys.mounts[iface].dev - log.info("PPM: mount(" .. iface .. ") -> found a " .. pm_type) + log.info(util.c("PPM: mount(", iface, ") -> found a ", pm_type)) break end end @@ -221,9 +222,9 @@ function ppm.handle_unmount(iface) pm_type = lost_dev.type pm_dev = lost_dev.dev - log.warning("PPM: lost device " .. pm_type .. " mounted to " .. iface) + log.warning(util.c("PPM: lost device ", pm_type, " mounted to ", iface)) else - log.error("PPM: lost device unknown to the PPM mounted to " .. iface) + log.error(util.c("PPM: lost device unknown to the PPM mounted to ", iface)) end return pm_type, pm_dev diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index b4e7291..0039bc3 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -391,8 +391,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) log.warning(log_header .. "PLC KEEP_ALIVE round trip time > 500ms (" .. self.last_rtt .. "ms)") end - -- log.debug(log_header .. "PLC RTT = ".. self.last_rtt .. "ms") - -- log.debug(log_header .. "PLC TT = ".. (srv_now - plc_send) .. "ms") + -- log.debug(log_header .. "PLC RTT = " .. self.last_rtt .. "ms") + -- log.debug(log_header .. "PLC TT = " .. (srv_now - plc_send) .. "ms") else log.debug(log_header .. "SCADA keep alive packet length mismatch") end diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 7644f41..9468255 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -235,8 +235,8 @@ function rtu.new_session(id, in_queue, out_queue, advertisement) log.warning(log_header .. "RTU KEEP_ALIVE round trip time > 500ms (" .. self.last_rtt .. "ms)") end - -- log.debug(log_header .. "RTU RTT = ".. self.last_rtt .. "ms") - -- log.debug(log_header .. "RTU TT = ".. (srv_now - rtu_send) .. "ms") + -- log.debug(log_header .. "RTU RTT = " .. self.last_rtt .. "ms") + -- log.debug(log_header .. "RTU TT = " .. (srv_now - rtu_send) .. "ms") else log.debug(log_header .. "SCADA keep alive packet length mismatch") end diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index a505d86..6e6f7b5 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -104,7 +104,7 @@ function redstone.new(session_id, unit_id, advert, out_queue) table.insert(self.io_list.analog_out, channel) else -- should be unreachable code, we already validated channels - log.error(log_tag .. "failed to identify advertisement channel IO mode (" .. channel .. ")", true) + log.error(util.c(log_tag, "failed to identify advertisement channel IO mode (", channel, ")"), true) return nil end @@ -212,8 +212,8 @@ function redstone.new(session_id, unit_id, advert, out_queue) break end end - elseif mode ~= nil then - log.debug(log_tag .. "attemted write to non D/O or A/O mode " .. mode) + else + log.debug(util.c(log_tag, "attemted write to non D/O or A/O mode ", mode)) end end end diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua index 59668f7..f8bf9da 100644 --- a/supervisor/session/rtu/turbinev.lua +++ b/supervisor/session/rtu/turbinev.lua @@ -237,7 +237,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue) elseif cmd == TBV_RTU_S_CMDS.DEC_DUMP_MODE then _dec_dump_mode() else - log.debug(log_tag .. "unrecognized in_q command " .. util.strval(cmd)) + log.debug(util.c(log_tag, "unrecognized in_q command ", cmd)) end elseif msg.qtype == mqueue.TYPE.DATA then -- instruction with body @@ -245,7 +245,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue) if cmd.key == TBV_RTU_S_DATA.SET_DUMP_MODE then _set_dump_mode(cmd.val) else - log.debug(log_tag .. "unrecognized in_q data " .. util.strval(cmd.key)) + log.debug(util.c(log_tag, "unrecognized in_q data ", cmd.key)) end end end diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 23c4a9e..1256dc6 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -187,11 +187,11 @@ function supervisor.comms(version, num_reactors, modem, dev_listen, coord_listen local plc_id = svsessions.establish_plc_session(l_port, r_port, packet.data[1], packet.data[2]) if plc_id == false then -- reactor already has a PLC assigned - log.debug("PLC_LNK: assignment collision with reactor " .. packet.data[1]) + log.debug(util.c("PLC_LNK: assignment collision with reactor ", packet.data[1])) _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.COLLISION }) else -- got an ID; assigned to a reactor successfully - println("connected to reactor " .. packet.data[1] .. " PLC (" .. packet.data[2] .. ") [:" .. r_port .. "]") + println(util.c("connected to reactor ", packet.data[1], " PLC (", packet.data[2], ") [:", r_port, "]")) log.debug("PLC_LNK: allowed for device at " .. r_port) _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.ALLOW }) end @@ -215,7 +215,7 @@ function supervisor.comms(version, num_reactors, modem, dev_listen, coord_listen elseif packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then if packet.length >= 1 then -- this is an RTU advertisement for a new session - println("connected to RTU (" .. packet.data[1] .. ") [:" .. r_port .. "]") + println(util.c("connected to RTU (", packet.data[1], ") [:", r_port, "]")) svsessions.establish_rtu_session(l_port, r_port, packet.data) @@ -244,7 +244,7 @@ function supervisor.comms(version, num_reactors, modem, dev_listen, coord_listen log.debug("illegal packet type " .. protocol .. " on coordinator listening channel") end else - log.error("received packet on unused channel " .. l_port, true) + log.warning("received packet on unused channel " .. l_port) end end end From 81345f5325f8d0559763421d7bff4cdd37afe201 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 5 Jun 2022 13:21:02 -0400 Subject: [PATCH 253/587] #71 validate frame data types --- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- scada-common/comms.lua | 119 +++++++++++++++++++++++++--------------- supervisor/startup.lua | 2 +- 4 files changed, 77 insertions(+), 48 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 48dad46..616bd6c 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.7.5" +local R_PLC_VERSION = "beta-v0.7.6" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index a3abcb4..716b58e 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.7.5" +local RTU_VERSION = "beta-v0.7.6" local rtu_t = types.rtu_t diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 61a0836..8422bd4 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -115,12 +115,15 @@ function comms.scada_packet() if type(self.raw) == "table" then if #self.raw >= 3 then - self.valid = true self.seq_num = self.raw[1] self.protocol = self.raw[2] self.length = #self.raw[3] self.payload = self.raw[3] end + + self.valid = type(self.seq_num) == "number" and + type(self.protocol) == "number" and + type(self.payload) == "table" end return self.valid @@ -166,16 +169,20 @@ function comms.modbus_packet() ---@param func_code MODBUS_FCODE ---@param data table function public.make(txn_id, unit_id, func_code, data) - self.txn_id = txn_id - self.length = #data - self.unit_id = unit_id - self.func_code = func_code - self.data = data + if type(data) == "table" then + self.txn_id = txn_id + self.length = #data + self.unit_id = unit_id + self.func_code = func_code + self.data = data - -- populate raw array - self.raw = { self.txn_id, self.unit_id, self.func_code } - for i = 1, self.length do - insert(self.raw, data[i]) + -- populate raw array + self.raw = { self.txn_id, self.unit_id, self.func_code } + for i = 1, self.length do + insert(self.raw, data[i]) + end + else + log.error("comms.modbus_packet.make(): data not table") end end @@ -194,7 +201,11 @@ function comms.modbus_packet() public.make(data[1], data[2], data[3], { table.unpack(data, 4, #data) }) end - return size_ok + local valid = type(self.txn_id) == "number" and + type(self.unit_id) == "number" and + type(self.func_code) == "number" + + return size_ok and valid else log.debug("attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true) return false @@ -258,16 +269,20 @@ function comms.rplc_packet() ---@param packet_type RPLC_TYPES ---@param data table function public.make(id, packet_type, data) - -- packet accessor properties - self.id = id - self.type = packet_type - self.length = #data - self.data = data + if type(data) == "table" then + -- packet accessor properties + self.id = id + self.type = packet_type + self.length = #data + self.data = data - -- populate raw array - self.raw = { self.id, self.type } - for i = 1, #data do - insert(self.raw, data[i]) + -- populate raw array + self.raw = { self.id, self.type } + for i = 1, #data do + insert(self.raw, data[i]) + end + else + log.error("comms.rplc_packet.make(): data not table") end end @@ -287,6 +302,8 @@ function comms.rplc_packet() ok = _rplc_type_valid() end + ok = ok and type(self.id) == "number" + return ok else log.debug("attempted RPLC parse of incorrect protocol " .. frame.protocol(), true) @@ -343,15 +360,19 @@ function comms.mgmt_packet() ---@param packet_type SCADA_MGMT_TYPES ---@param data table function public.make(packet_type, data) - -- packet accessor properties - self.type = packet_type - self.length = #data - self.data = data + if type(data) == "table" then + -- packet accessor properties + self.type = packet_type + self.length = #data + self.data = data - -- populate raw array - self.raw = { self.type } - for i = 1, #data do - insert(self.raw, data[i]) + -- populate raw array + self.raw = { self.type } + for i = 1, #data do + insert(self.raw, data[i]) + end + else + log.error("comms.mgmt_packet.make(): data not table") end end @@ -374,7 +395,7 @@ function comms.mgmt_packet() return ok else log.debug("attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true) - return false + return false end else log.debug("nil frame encountered", true) @@ -424,15 +445,19 @@ function comms.coord_packet() ---@param packet_type any ---@param data table function public.make(packet_type, data) - -- packet accessor properties - self.type = packet_type - self.length = #data - self.data = data + if type(data) == "table" then + -- packet accessor properties + self.type = packet_type + self.length = #data + self.data = data - -- populate raw array - self.raw = { self.type } - for i = 1, #data do - insert(self.raw, data[i]) + -- populate raw array + self.raw = { self.type } + for i = 1, #data do + insert(self.raw, data[i]) + end + else + log.error("comms.coord_packet.make(): data not table") end end @@ -505,15 +530,19 @@ function comms.capi_packet() ---@param packet_type any ---@param data table function public.make(packet_type, data) - -- packet accessor properties - self.type = packet_type - self.length = #data - self.data = data + if type(data) == "table" then + -- packet accessor properties + self.type = packet_type + self.length = #data + self.data = data - -- populate raw array - self.raw = { self.type } - for i = 1, #data do - insert(self.raw, data[i]) + -- populate raw array + self.raw = { self.type } + for i = 1, #data do + insert(self.raw, data[i]) + end + else + log.error("comms.capi_packet.make(): data not table") end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 845daa8..a123492 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.4.11" +local SUPERVISOR_VERSION = "beta-v0.4.12" local print = util.print local println = util.println From d6c8eb4d56db483276a145cf3efda3d718cedcd0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 5 Jun 2022 14:49:50 -0400 Subject: [PATCH 254/587] #68 check RTU unit configs while parsing --- rtu/startup.lua | 382 ++++++++++++++++++++++++++++-------------------- 1 file changed, 220 insertions(+), 162 deletions(-) diff --git a/rtu/startup.lua b/rtu/startup.lua index 716b58e..0348358 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.7.6" +local RTU_VERSION = "beta-v0.7.7" local rtu_t = types.rtu_t @@ -39,7 +39,7 @@ log.init(config.LOG_PATH, config.LOG_MODE) log.info("========================================") log.info("BOOTING rtu.startup " .. RTU_VERSION) log.info("========================================") -println(">> RTU " .. RTU_VERSION .. " <<") +println(">> RTU GATEWAY " .. RTU_VERSION .. " <<") ---------------------------------------- -- startup @@ -94,198 +94,256 @@ local units = __shared_memory.rtu_sys.units local rtu_redstone = config.RTU_REDSTONE local rtu_devices = config.RTU_DEVICES --- redstone interfaces -for entry_idx = 1, #rtu_redstone do - local rs_rtu = redstone_rtu.new() - local io_table = rtu_redstone[entry_idx].io - local io_reactor = rtu_redstone[entry_idx].for_reactor +-- configure RTU gateway based on config file definitions +local function configure() + -- redstone interfaces + for entry_idx = 1, #rtu_redstone do + local rs_rtu = redstone_rtu.new() + local io_table = rtu_redstone[entry_idx].io + local io_reactor = rtu_redstone[entry_idx].for_reactor - local capabilities = {} - - log.debug(util.c("init> starting redstone RTU I/O linking for reactor ", io_reactor, "...")) - - local continue = true - - for i = 1, #units do - local unit = units[i] ---@type rtu_unit_registry_entry - if unit.reactor == io_reactor and unit.type == rtu_t.redstone then - -- duplicate entry - log.warning(util.c("init> skipping definition block #", entry_idx, " for reactor ", io_reactor, " with already defined redstone I/O")) - continue = false - break + -- CHECK: reactor ID must be >= to 1 + if type(io_reactor) ~= "number" or io_reactor <= 0 or io_reactor ~= math.floor(io_reactor) then + println(util.c("configure> redstone entry #", entry_idx, " : ", io_reactor, " isn't an integer >= 1")) + return false end - end - if continue then - for i = 1, #io_table do - local valid = false - local conf = io_table[i] + -- CHECK: io table exists + if type(io_table) ~= "table" then + println(util.c("configure> redstone entry #", entry_idx, " no IO table found")) + return false + end - -- 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 + local capabilities = {} - if not valid then - local message = util.c("init> invalid redstone definition at index ", i, " in definition block #", entry_idx, - " (for reactor ", io_reactor, ")") - println_ts(message) + log.debug(util.c("configure> starting redstone RTU I/O linking for reactor ", io_reactor, "...")) + + local continue = true + + -- check for duplicate entries + for i = 1, #units do + local unit = units[i] ---@type rtu_unit_registry_entry + if unit.reactor == io_reactor and unit.type == rtu_t.redstone then + -- duplicate entry + local message = util.c("configure> skipping definition block #", entry_idx, " for reactor ", io_reactor, + " with already defined redstone I/O") + println(message) log.warning(message) - 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 - log.warning(util.c("init> skipping duplicate input for channel ", rsio.to_string(conf.channel), " on side ", conf.side)) - 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 - log.warning(util.c("init> skipping duplicate input for channel ", rsio.to_string(conf.channel), " on side ", conf.side)) - 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("init> fell through if chain attempting to identify IO mode", true) - break - end - - table.insert(capabilities, conf.channel) - - log.debug(util.c("init> linked redstone ", #capabilities, ": ", rsio.to_string(conf.channel), " (", conf.side, ") for reactor ", - io_reactor)) + continue = false + break 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 - rtu = rs_rtu, - modbus_io = modbus.new(rs_rtu, false), - pkt_queue = nil, - thread = nil - } + -- not a duplicate + if continue then + for i = 1, #io_table do + local valid = false + local conf = io_table[i] - table.insert(units, unit) + -- 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 - log.debug(util.c("init> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for reactor ", io_reactor)) - 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 --- mounted peripherals -for i = 1, #rtu_devices do - local device = ppm.get_periph(rtu_devices[i].name) + table.insert(capabilities, conf.channel) - if device == nil then - local message = util.c("init> '", rtu_devices[i].name, "' not found") - println_ts(message) - log.warning(message) - else - local type = ppm.get_type(rtu_devices[i].name) - local rtu_iface = nil ---@type rtu_device - local rtu_type = "" + log.debug(util.c("configure> linked redstone ", #capabilities, ": ", rsio.to_string(conf.channel), + " (", conf.side, ") for reactor ", io_reactor)) + end + end - if type == "boiler" then - -- boiler multiblock - rtu_type = rtu_t.boiler - rtu_iface = boiler_rtu.new(device) - elseif type == "boilerValve" then - -- boiler multiblock (10.1+) - rtu_type = rtu_t.boiler_valve - rtu_iface = boilerv_rtu.new(device) - elseif type == "turbine" then - -- turbine multiblock - rtu_type = rtu_t.turbine - rtu_iface = turbine_rtu.new(device) - elseif type == "turbineValve" then - -- turbine multiblock (10.1+) - rtu_type = rtu_t.turbine_valve - rtu_iface = turbinev_rtu.new(device) - elseif type == "mekanismMachine" then - -- assumed to be an induction matrix multiblock, pre Mekanism 10.1 - -- also works with energy cubes - rtu_type = rtu_t.energy_machine - rtu_iface = energymachine_rtu.new(device) - elseif type == "inductionPort" then - -- induction matrix multiblock (10.1+) - rtu_type = rtu_t.induction_matrix - rtu_iface = imatrix_rtu.new(device) - elseif type == "environmentDetector" then - -- advanced peripherals environment detector - rtu_type = rtu_t.env_detector - rtu_iface = envd_rtu.new(device) - else - local message = util.c("init> device '", rtu_devices[i].name, "' is not a known type (", type, ")") - println_ts(message) - log.warning(message) - end - - if rtu_iface ~= nil then ---@class rtu_unit_registry_entry - local rtu_unit = { - name = rtu_devices[i].name, - type = rtu_type, - index = rtu_devices[i].index, - reactor = rtu_devices[i].for_reactor, - device = device, - rtu = rtu_iface, - modbus_io = modbus.new(rtu_iface, true), - pkt_queue = mqueue.new(), + local unit = { + name = "redstone_io", + type = rtu_t.redstone, + index = entry_idx, + reactor = io_reactor, + device = capabilities, -- use device field for redstone channels + rtu = rs_rtu, + modbus_io = modbus.new(rs_rtu, false), + pkt_queue = nil, thread = nil } - rtu_unit.thread = threads.thread__unit_comms(__shared_memory, rtu_unit) + table.insert(units, unit) - table.insert(units, rtu_unit) - - log.debug(util.c("init> initialized RTU unit #", #units, ": ", rtu_devices[i].name, " (", rtu_type, ") [", - rtu_devices[i].index, "] for reactor ", rtu_devices[i].for_reactor)) + log.debug(util.c("init> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for reactor ", io_reactor)) end end + + -- mounted peripherals + for i = 1, #rtu_devices do + 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 + 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 type(index) ~= "number" or index <= 0 or index ~= math.floor(index) then + println(util.c("configure> device entry #", i, ": index ", index, " isn't an integer >= 1")) + return false + end + + -- CHECK: reactor is an integer >= 1 + if type(for_reactor) ~= "number" or for_reactor <= 0 or for_reactor ~= math.floor(for_reactor) 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) + + if device == nil then + local message = util.c("configure> '", name, "' not found") + println(message) + log.fatal(message) + return false + else + local type = ppm.get_type(name) + local rtu_iface = nil ---@type rtu_device + local rtu_type = "" + + if type == "boiler" then + -- boiler multiblock + rtu_type = rtu_t.boiler + rtu_iface = boiler_rtu.new(device) + elseif type == "boilerValve" then + -- boiler multiblock (10.1+) + rtu_type = rtu_t.boiler_valve + rtu_iface = boilerv_rtu.new(device) + elseif type == "turbine" then + -- turbine multiblock + rtu_type = rtu_t.turbine + rtu_iface = turbine_rtu.new(device) + elseif type == "turbineValve" then + -- turbine multiblock (10.1+) + rtu_type = rtu_t.turbine_valve + rtu_iface = turbinev_rtu.new(device) + elseif type == "mekanismMachine" then + -- assumed to be an induction matrix multiblock, pre Mekanism 10.1 + -- also works with energy cubes + rtu_type = rtu_t.energy_machine + rtu_iface = energymachine_rtu.new(device) + elseif type == "inductionPort" then + -- induction matrix multiblock (10.1+) + rtu_type = rtu_t.induction_matrix + rtu_iface = imatrix_rtu.new(device) + elseif type == "environmentDetector" then + -- advanced peripherals environment detector + rtu_type = rtu_t.env_detector + rtu_iface = envd_rtu.new(device) + else + local message = util.c("configure> device '", name, "' is not a known type (", type, ")") + println_ts(message) + log.fatal(message) + return false + end + + if rtu_iface ~= nil then + ---@class rtu_unit_registry_entry + local rtu_unit = { + name = name, + type = rtu_type, + index = index, + reactor = for_reactor, + device = device, + rtu = rtu_iface, + modbus_io = modbus.new(rtu_iface, true), + pkt_queue = mqueue.new(), + 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 + end + + -- we made it through all that trusting-user-to-write-a-config-file chaos + return true end ---------------------------------------- -- start system ---------------------------------------- --- start connection watchdog -smem_sys.conn_watchdog = util.new_watchdog(5) -log.debug("boot> conn watchdog started") +log.debug("boot> running configure()") --- 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") +if configure() then + -- start connection watchdog + smem_sys.conn_watchdog = util.new_watchdog(5) + log.debug("boot> conn watchdog started") --- init threads -local main_thread = threads.thread__main(__shared_memory) -local comms_thread = threads.thread__comms(__shared_memory) + -- 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") --- 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) + -- 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 -end --- run threads -parallel.waitForAll(table.unpack(_threads)) + -- run threads + parallel.waitForAll(table.unpack(_threads)) +else + println("configuration failed, exiting...") +end println_ts("exited") log.info("exited") From 1c819779c7185a3446f74aba365e3e77f2fe9e69 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 5 Jun 2022 15:09:02 -0400 Subject: [PATCH 255/587] #69 config file validation --- coordinator/startup.lua | 34 +++++++++++++++++++++++++++++----- reactor-plc/startup.lua | 24 +++++++++++++++++++++++- rtu/startup.lua | 20 +++++++++++++++++++- scada-common/util.lua | 30 ++++++++++++++++++++++++++++++ supervisor/startup.lua | 36 +++++++++++++++++++++++++++++++++++- 5 files changed, 136 insertions(+), 8 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 66a58ea..5775168 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -4,21 +4,41 @@ require("/initenv").init_env() -local log = require("scada-common.log") -local ppm = require("scada-common.ppm") +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") local util = require("scada-common.util") -local config = require("coordinator.config") +local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") -local renderer = require("coordinator.renderer") +local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.1.3" +local COORDINATOR_VERSION = "alpha-v0.1.4" local print = util.print local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts +---------------------------------------- +-- config validation +---------------------------------------- + +local cfv = util.new_validator() + +cfv.assert_port(config.SCADA_SV_PORT) +cfv.assert_port(config.SCADA_SV_LISTEN) +cfv.assert_port(config.SCADA_API_LISTEN) +cfv.assert_type_int(config.NUM_UNITS) +cfv.assert_type_str(config.LOG_PATH) +cfv.assert_type_int(config.LOG_MODE) +cfv.assert_type_bool(config.SECURE) +cfv.assert_type_str(config.PASSWORD) +assert(cfv.valid(), "bad config file: missing/invalid fields") + +---------------------------------------- +-- log init +---------------------------------------- + log.init(config.LOG_PATH, config.LOG_MODE) log.info("========================================") @@ -26,6 +46,10 @@ log.info("BOOTING coordinator.startup " .. COORDINATOR_VERSION) log.info("========================================") println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<") +---------------------------------------- +-- startup +---------------------------------------- + -- mount connected devices ppm.mount_all() diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 616bd6c..73f4964 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -13,13 +13,31 @@ local config = require("reactor-plc.config") local plc = require("reactor-plc.plc") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "beta-v0.7.6" +local R_PLC_VERSION = "beta-v0.7.7" local print = util.print local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts +---------------------------------------- +-- config validation +---------------------------------------- + +local cfv = util.new_validator() + +cfv.assert_type_bool(config.NETWORKED) +cfv.assert_type_int(config.REACTOR_ID) +cfv.assert_port(config.SERVER_PORT) +cfv.assert_port(config.LISTEN_PORT) +cfv.assert_type_str(config.LOG_PATH) +cfv.assert_type_int(config.LOG_MODE) +assert(cfv.valid(), "bad config file: missing/invalid fields") + +---------------------------------------- +-- log init +---------------------------------------- + log.init(config.LOG_PATH, config.LOG_MODE) log.info("========================================") @@ -27,6 +45,10 @@ log.info("BOOTING reactor-plc.startup " .. R_PLC_VERSION) log.info("========================================") println(">> Reactor PLC " .. R_PLC_VERSION .. " <<") +---------------------------------------- +-- startup +---------------------------------------- + -- mount connected devices ppm.mount_all() diff --git a/rtu/startup.lua b/rtu/startup.lua index 0348358..209fed6 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.7.7" +local RTU_VERSION = "beta-v0.7.8" local rtu_t = types.rtu_t @@ -34,6 +34,24 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts +---------------------------------------- +-- config validation +---------------------------------------- + +local cfv = util.new_validator() + +cfv.assert_port(config.SERVER_PORT) +cfv.assert_port(config.LISTEN_PORT) +cfv.assert_type_str(config.LOG_PATH) +cfv.assert_type_int(config.LOG_MODE) +cfv.assert_type_table(config.RTU_DEVICES) +cfv.assert_type_table(config.RTU_REDSTONE) +assert(cfv.valid(), "bad config file: missing/invalid fields") + +---------------------------------------- +-- log init +---------------------------------------- + log.init(config.LOG_PATH, config.LOG_MODE) log.info("========================================") diff --git a/scada-common/util.lua b/scada-common/util.lua index bfba8d9..ba417ff 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -281,4 +281,34 @@ function util.new_clock(period) return public end +-- create a new type validator +-- +-- can execute sequential checks and check valid() to see if it is still valid +function util.new_validator() + local valid = true + + ---@class validator + local public = {} + + function public.assert_type_bool(value) valid = valid and type(value) == "boolean" end + function public.assert_type_num(value) valid = valid and type(value) == "number" end + function public.assert_type_int(value) valid = valid and type(value) == "number" and value == math.floor(value) end + function public.assert_type_str(value) valid = valid and type(value) == "string" end + function public.assert_type_table(value) valid = valid and type(value) == "table" end + + function public.assert_eq(check, expect) valid = valid and check == expect end + function public.assert_min(check, min) valid = valid and check >= min end + function public.assert_min_ex(check, min) valid = valid and check > min end + function public.assert_max(check, max) valid = valid and check <= max end + function public.assert_max_ex(check, max) valid = valid and check < max end + function public.assert_range(check, min, max) valid = valid and check >= min and check <= max end + function public.assert_range_ex(check, min, max) valid = valid and check > min and check < max end + + function public.assert_port(port) valid = valid and type(port) == "number" and port >= 0 and port <= 65535 end + + function public.valid() return valid end + + return public +end + return util diff --git a/supervisor/startup.lua b/supervisor/startup.lua index a123492..46ad6b4 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,13 +13,43 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.4.12" +local SUPERVISOR_VERSION = "beta-v0.4.13" local print = util.print local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts +---------------------------------------- +-- config validation +---------------------------------------- + +local cfv = util.new_validator() + +cfv.assert_port(config.SCADA_DEV_LISTEN) +cfv.assert_port(config.SCADA_SV_LISTEN) +cfv.assert_type_int(config.NUM_REACTORS) +cfv.assert_type_table(config.REACTOR_COOLING) +cfv.assert_type_str(config.LOG_PATH) +cfv.assert_type_int(config.LOG_MODE) + +assert(cfv.valid(), "bad config file: missing/invalid fields") + +for i = 1, config.NUM_REACTORS do + cfv.assert_type_table(config.REACTOR_COOLING[i]) + assert(cfv.valid(), "missing cooling entry for reactor " .. i) + cfv.assert_type_int(config.REACTOR_COOLING[i].BOILERS) + cfv.assert_type_int(config.REACTOR_COOLING[i].TURBINES) + assert(cfv.valid(), "missing boilers/turbines for reactor " .. i) + cfv.assert_min(config.REACTOR_COOLING[i].BOILERS, 0) + cfv.assert_min(config.REACTOR_COOLING[i].TURBINES, 1) + assert(cfv.valid(), "bad number of boilers/turbines for reactor " .. i) +end + +---------------------------------------- +-- log init +---------------------------------------- + log.init(config.LOG_PATH, config.LOG_MODE) log.info("========================================") @@ -27,6 +57,10 @@ log.info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION) log.info("========================================") println(">> SCADA Supervisor " .. SUPERVISOR_VERSION .. " <<") +---------------------------------------- +-- startup +---------------------------------------- + -- mount connected devices ppm.mount_all() From 0bc0decbf2688a73c348e5c41a5fb6863819ed1f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 5 Jun 2022 16:51:38 -0400 Subject: [PATCH 256/587] util.is_int --- rtu/startup.lua | 8 ++++---- scada-common/rsio.lua | 14 ++++++++------ scada-common/util.lua | 7 +++++++ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/rtu/startup.lua b/rtu/startup.lua index 209fed6..ed39c9a 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.7.8" +local RTU_VERSION = "beta-v0.7.9" local rtu_t = types.rtu_t @@ -121,7 +121,7 @@ local function configure() local io_reactor = rtu_redstone[entry_idx].for_reactor -- CHECK: reactor ID must be >= to 1 - if type(io_reactor) ~= "number" or io_reactor <= 0 or io_reactor ~= math.floor(io_reactor) 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")) return false end @@ -244,13 +244,13 @@ local function configure() end -- CHECK: index is an integer >= 1 - if type(index) ~= "number" or index <= 0 or index ~= math.floor(index) then + if (not util.is_int(index)) or (index <= 0) then println(util.c("configure> device entry #", i, ": index ", index, " isn't an integer >= 1")) return false end -- CHECK: reactor is an integer >= 1 - if type(for_reactor) ~= "number" or for_reactor <= 0 or for_reactor ~= math.floor(for_reactor) then + 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 diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 05f078c..04ab23c 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -2,6 +2,8 @@ -- Redstone I/O -- +local util = require("scada-common.util") + local rsio = {} ---------------------- @@ -101,7 +103,7 @@ function rsio.to_string(channel) "R_PLC_TIMEOUT" } - if type(channel) == "number" and channel > 0 and channel <= #names then + if util.is_int(channel) and channel > 0 and channel <= #names then return names[channel] else return "" @@ -184,7 +186,7 @@ function rsio.get_io_mode(channel) IO_MODE.DIGITAL_OUT -- R_PLC_TIMEOUT } - if type(channel) == "number" and channel > 0 and channel <= #modes then + if util.is_int(channel) and channel > 0 and channel <= #modes then return modes[channel] else return IO_MODE.ANALOG_IN @@ -201,7 +203,7 @@ local RS_SIDES = rs.getSides() ---@param channel RS_IO ---@return boolean valid function rsio.is_valid_channel(channel) - return (type(channel) == "number") and (channel > 0) and (channel <= RS_IO.R_PLC_TIMEOUT) + return util.is_int(channel) and (channel > 0) and (channel <= RS_IO.R_PLC_TIMEOUT) end -- check if a side is valid @@ -220,7 +222,7 @@ end ---@param color integer ---@return boolean valid function rsio.is_color(color) - return (type(color) == "number") and (color > 0) and (_B_AND(color, (color - 1)) == 0); + return util.is_int(color) and (color > 0) and (_B_AND(color, (color - 1)) == 0); end ----------------- @@ -243,7 +245,7 @@ end ---@param level IO_LVL ---@return boolean function rsio.digital_write(channel, level) - if type(channel) ~= "number" or channel < RS_IO.F_ALARM or channel > RS_IO.R_PLC_TIMEOUT then + if (not util.is_int(channel)) or (channel < RS_IO.F_ALARM) or (channel > RS_IO.R_PLC_TIMEOUT) then return false else return RS_DIO_MAP[channel]._f(level) @@ -255,7 +257,7 @@ end ---@param level IO_LVL ---@return boolean function rsio.digital_is_active(channel, level) - if type(channel) ~= "number" or channel > RS_IO.R_ENABLE then + if (not util.is_int(channel)) or (channel > RS_IO.R_ENABLE) then return false else return RS_DIO_MAP[channel]._f(level) diff --git a/scada-common/util.lua b/scada-common/util.lua index ba417ff..c155922 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -79,6 +79,13 @@ end -- MATH -- +-- is a value an integer +---@param x any value +---@return boolean if the number is an integer +function util.is_int(x) + return type(x) == "number" and x == math.floor(x) +end + -- round a number to an integer ---@return integer rounded function util.round(x) From ebcc911b81ce349de80d22bdc92180658469b8c7 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 5 Jun 2022 16:53:36 -0400 Subject: [PATCH 257/587] #70 validate RTU advertisements on the supervisor --- supervisor/session/rtu.lua | 23 +++++++++++++++++- supervisor/session/rtu/redstone.lua | 36 +++++++++++++++++------------ supervisor/startup.lua | 2 +- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 9468255..ca4e467 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -102,8 +102,29 @@ function rtu.new_session(id, in_queue, out_queue, advertisement) local u_type = unit_advert.type - -- create unit by type + -- validate unit advertisement + + local advert_validator = util.new_validator() + advert_validator.assert_type_int(unit_advert.index) + advert_validator.assert_type_int(unit_advert.reactor) + if u_type == RTU_UNIT_TYPES.REDSTONE then + advert_validator.assert_type_table(unit_advert.rsio) + end + + if advert_validator.valid() then + advert_validator.assert_min(unit_advert.index, 1) + advert_validator.assert_min(unit_advert.reactor, 1) + if not advert_validator.valid() then u_type = false end + else + u_type = false + end + + -- create unit by type + + if u_type == false then + -- validation fail + elseif u_type == RTU_UNIT_TYPES.REDSTONE then -- redstone unit, rs_in_q = svrs_redstone.new(self.id, i, unit_advert, self.out_q) elseif u_type == RTU_UNIT_TYPES.BOILER then diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index 6e6f7b5..2447094 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -90,25 +90,31 @@ function redstone.new(session_id, unit_id, advert, out_queue) -- setup I/O for i = 1, #advert.rsio do local channel = advert.rsio[i] - local mode = rsio.get_io_mode(channel) - if mode == IO_MODE.DIGITAL_IN then - self.has_di = true - table.insert(self.io_list.digital_in, channel) - elseif mode == IO_MODE.DIGITAL_OUT then - table.insert(self.io_list.digital_out, channel) - elseif mode == IO_MODE.ANALOG_IN then - self.has_ai = true - table.insert(self.io_list.analog_in, channel) - elseif mode == IO_MODE.ANALOG_OUT then - table.insert(self.io_list.analog_out, channel) + if rsio.is_valid_channel(channel) then + local mode = rsio.get_io_mode(channel) + + if mode == IO_MODE.DIGITAL_IN then + self.has_di = true + table.insert(self.io_list.digital_in, channel) + elseif mode == IO_MODE.DIGITAL_OUT then + table.insert(self.io_list.digital_out, channel) + elseif mode == IO_MODE.ANALOG_IN then + self.has_ai = true + table.insert(self.io_list.analog_in, channel) + elseif mode == IO_MODE.ANALOG_OUT then + table.insert(self.io_list.analog_out, channel) + else + -- should be unreachable code, we already validated channels + log.error(util.c(log_tag, "failed to identify advertisement channel IO mode (", channel, ")"), true) + return nil + end + + self.db[channel] = IO_LVL.LOW else - -- should be unreachable code, we already validated channels - log.error(util.c(log_tag, "failed to identify advertisement channel IO mode (", channel, ")"), true) + log.error(util.c(log_tag, "invalid advertisement channel (", channel, ")"), true) return nil end - - self.db[channel] = IO_LVL.LOW end -- PRIVATE FUNCTIONS -- diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 46ad6b4..54f4f54 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.4.13" +local SUPERVISOR_VERSION = "beta-v0.4.14" local print = util.print local println = util.println From b75d482f4a49809d97526766f143268aa2e99b75 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 5 Jun 2022 16:54:34 -0400 Subject: [PATCH 258/587] use is_int in validator --- scada-common/util.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scada-common/util.lua b/scada-common/util.lua index c155922..b860b28 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -299,7 +299,7 @@ function util.new_validator() function public.assert_type_bool(value) valid = valid and type(value) == "boolean" end function public.assert_type_num(value) valid = valid and type(value) == "number" end - function public.assert_type_int(value) valid = valid and type(value) == "number" and value == math.floor(value) end + function public.assert_type_int(value) valid = valid and util.is_int(value) end function public.assert_type_str(value) valid = valid and type(value) == "string" end function public.assert_type_table(value) valid = valid and type(value) == "table" end From 8b307ea030a4b617c92fb2d9ecbea1c00bb3e19a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 5 Jun 2022 23:24:18 -0400 Subject: [PATCH 259/587] alias for color type and added read() to globals --- .vscode/settings.json | 3 ++- scada-common/types.lua | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 80f7c42..3974dbe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,6 +10,7 @@ "textutils", "shell", "settings", - "window" + "window", + "read" ] } diff --git a/scada-common/types.lua b/scada-common/types.lua index 65aa19f..7e31e58 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -22,6 +22,10 @@ local types = {} ---@field reactor integer ---@field rsio table|nil +-- ALIASES -- + +---@alias color integer + -- ENUMERATION TYPES -- ---@alias TRI_FAIL integer From 285026c1fa20775de74e7c2846ad3693ad15a58a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 6 Jun 2022 15:40:08 -0400 Subject: [PATCH 260/587] docs cleanup --- scada-common/util.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scada-common/util.lua b/scada-common/util.lua index b860b28..fc194ca 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -81,7 +81,7 @@ end -- is a value an integer ---@param x any value ----@return boolean if the number is an integer +---@return boolean is_integer if the number is an integer function util.is_int(x) return type(x) == "number" and x == math.floor(x) end From 8ea75b9501a5838d19d91ea2d9a4eb8fa58f452a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 6 Jun 2022 15:42:39 -0400 Subject: [PATCH 261/587] #62, #63 graphics primatives and added display boxes to renderer --- coordinator/renderer.lua | 62 +++++++++++++--- coordinator/startup.lua | 11 ++- graphics/core.lua | 73 ++++++++++++++++++ graphics/element.lua | 124 +++++++++++++++++++++++++++++++ graphics/elements/displaybox.lua | 21 ++++++ graphics/elements/textbox.lua | 74 ++++++++++++++++++ 6 files changed, 353 insertions(+), 12 deletions(-) create mode 100644 graphics/core.lua create mode 100644 graphics/element.lua create mode 100644 graphics/elements/displaybox.lua create mode 100644 graphics/elements/textbox.lua diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 57553ca..4ed6358 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -1,36 +1,58 @@ -local log = require("scada-common.log") +local log = require("scada-common.log") local util = require("scada-common.util") +local displaybox = require("graphics.elements.displaybox") +local structs = require("graphics.structs") + local renderer = {} +local gconf = { + -- root boxes + root = { + fgd = colors.black, + bkg = colors.lightGray + } +} + +-- render engine local engine = { monitors = nil, dmesg_window = nil } +-- UI elements +local ui = { + main_box = nil, + unit_boxes = {} +} + +-- reset a display to the "default", but set text scale to 0.5 +local function _reset_display(monitor) + monitor.setTextScale(0.5) + monitor.setTextColor(colors.white) + monitor.setBackgroundColor(colors.black) + monitor.clear() + monitor.setCursorPos(1, 1) +end + +-- link to the monitor peripherals ---@param monitors monitors_struct function renderer.set_displays(monitors) engine.monitors = monitors end +-- reset all displays in use by the renderer function renderer.reset() -- reset primary monitor - engine.monitors.primary.setTextScale(0.5) - engine.monitors.primary.setTextColor(colors.white) - engine.monitors.primary.setBackgroundColor(colors.black) - engine.monitors.primary.clear() - engine.monitors.primary.setCursorPos(1, 1) + _reset_display(engine.monitors.primary) -- reset unit displays for _, monitor in pairs(engine.monitors.unit_displays) do - monitor.setTextScale(0.5) - monitor.setTextColor(colors.white) - monitor.setBackgroundColor(colors.black) - monitor.clear() - monitor.setCursorPos(1, 1) + _reset_display(monitor) end end +-- initialize the dmesg output window function renderer.init_dmesg() local disp_x, disp_y = engine.monitors.primary.getSize() engine.dmesg_window = window.create(engine.monitors.primary, 1, 1, disp_x, disp_y) @@ -38,4 +60,22 @@ function renderer.init_dmesg() log.direct_dmesg(engine.dmesg_window) end +-- start the coordinator GUI +function renderer.start_ui() + local palette = structs.graphics.cpair(gconf.root.fgd, gconf.root.bkg) + + ui.main_box = displaybox{window = engine.monitors.primary, fg_bg = palette} + + for _, monitor in pairs(engine.monitors.unit_displays) do + table.insert(ui.unit_boxes, displaybox{window = engine.monitors.primary, fg_bg = palette}) + end +end + +-- close out the UI +function renderer.close_ui() + -- clear root UI elements + ui.main_box = nil + ui.unit_boxes = {} +end + return renderer diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 5775168..8b79745 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -12,7 +12,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.1.4" +local COORDINATOR_VERSION = "alpha-v0.1.5" local print = util.print local println = util.println @@ -81,3 +81,12 @@ if modem == nil then end log.dmesg("wireless modem connected", "COMMS", colors.purple) + +log.dmesg("starting UI...", "GRAPHICS", colors.green) +util.psleep(3) + +local ui_ok = pcall(renderer.start_ui) +if not ui_ok then + renderer.close_ui() + log.dmesg("UI draw failed", "GRAPHICS", colors.green) +end diff --git a/graphics/core.lua b/graphics/core.lua new file mode 100644 index 0000000..3d23dfc --- /dev/null +++ b/graphics/core.lua @@ -0,0 +1,73 @@ +local core = {} + +local events = {} + +---@class monitor_touch +---@field monitor string +---@field x integer +---@field y integer + +---@param monitor string +---@param x integer +---@param y integer +---@return monitor_touch +function events.touch(monitor, x, y) + return { + monitor = monitor, + x = x, + y = y + } +end + +core.events = events + +local graphics = {} + +---@alias TEXT_ALIGN integer +graphics.TEXT_ALIGN = { + LEFT = 1, + CENTER = 2, + RIGHT = 3 +} + +---@class graphics_frame +---@field x integer +---@field y integer +---@field w integer +---@field h integer + +---@param x integer +---@param y integer +---@param w integer +---@param h integer +---@return graphics_frame +function graphics.gframe(x, y, w, h) + return { + x = x, + y = y, + w = w, + h = h + } +end + +---@class cpair +---@field fgd color +---@field bkg color +---@field blit_fgd string +---@field blit_bkg string + +---@param foreground color +---@param background color +---@return cpair +function graphics.cpair(foreground, background) + return { + fgd = foreground, + bkg = background, + blit_fgd = colors.toBlit(foreground), + blit_bkg = colors.toBlit(background) + } +end + +core.graphics = graphics + +return core diff --git a/graphics/element.lua b/graphics/element.lua new file mode 100644 index 0000000..7078bc9 --- /dev/null +++ b/graphics/element.lua @@ -0,0 +1,124 @@ +local core = require("graphics.core") + +local element = {} + +---@class graphics_args_generic +---@field window? table +---@field parent? graphics_element +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field width? integer parent width if omitted +---@field height? integer parent height if omitted +---@field gframe? graphics_frame frame instead of x/y/width/height +---@field fg_bg? cpair foreground/background colors + +-- a base graphics element, should not be created on its own +---@param args graphics_args_generic arguments +function element.new(args) + local self = { + p_window = nil, ---@type table + position = { x = 1, y = 1 }, + bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1} + } + + local protected = { + window = nil, ---@type table + frame = core.graphics.gframe(1, 1, 1, 1) + } + + -- SETUP -- + + -- get the parent window + self.p_window = args.window or args.parent.window() + + assert(self.p_window, "graphics.element: no parent window provided") + + -- get frame coordinates/size + if args.gframe ~= nil then + protected.frame.x = args.gframe.x + protected.frame.y = args.gframe.y + protected.frame.w = args.gframe.w + protected.frame.h = args.gframe.h + else + local w, h = self.p_window.getSize() + protected.frame.x = args.x or 1 + protected.frame.y = args.y or 1 + protected.frame.w = args.width or w + protected.frame.h = args.height or h + end + + -- create window + local f = protected.frame + protected.window = window.create(self.p_window, f.x, f.y, f.w, f.h, true) + + -- init display box + if args.fg_bg ~= nil then + protected.window.setBackgroundColor(args.fg_bg.bkg) + protected.window.setTextColor(args.fg_bg.fgd) + protected.window.clear() + end + + -- record position + self.position.x, self.position.y = protected.window.getPosition() + + -- calculate bounds + self.bounds.x1 = self.position.x + self.bounds.x2 = self.position.x + f.w - 1 + self.bounds.y1 = self.position.y + self.bounds.y2 = self.position.y + f.h - 1 + + -- PROTECTED FUNCTIONS -- + + -- handle a touch event + ---@param event table monitor_touch event + function protected.handle_touch(event) + end + + -- handle data value changes + function protected.on_update(...) + end + + ---@class graphics_element + local public = {} + + -- get public interface + function protected.get() return public end + + -- PUBLIC FUNCTIONS -- + + -- get the window object + function public.window() return protected.window end + + -- handle a monitor touch + ---@param event monitor_touch monitor touch event + function public.handle_touch(event) + local in_x = event.x >= self.bounds.x1 and event.x <= self.bounds.x2 + local in_y = event.y >= self.bounds.y1 and event.y <= self.bounds.y2 + + if in_x and in_y then + -- handle the touch event, transformed into the window frame + protected.handle_touch(core.events.touch(event.monitor, + (event.x - self.position.x) + 1, + (event.y - self.position.y) + 1)) + end + end + + -- draw the element given new data + function public.draw(...) + protected.on_update(...) + end + + -- show the element + function public.show() + protected.window.setVisible(true) + end + + -- hide the element + function public.hide() + protected.window.setVisible(false) + end + + return protected +end + +return element diff --git a/graphics/elements/displaybox.lua b/graphics/elements/displaybox.lua new file mode 100644 index 0000000..0d7fd47 --- /dev/null +++ b/graphics/elements/displaybox.lua @@ -0,0 +1,21 @@ +-- Root Display Box Graphics Element + +local element = require("graphics.element") + +---@class displaybox_args +---@field window table +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field width? integer parent width if omitted +---@field height? integer parent height if omitted +---@field gframe? graphics_frame frame instead of x/y/width/height +---@field fg_bg? cpair foreground/background colors + +-- new root display box +---@param args displaybox_args +local function displaybox(args) + -- create new graphics element base object + return element.new(args).get() +end + +return displaybox diff --git a/graphics/elements/textbox.lua b/graphics/elements/textbox.lua new file mode 100644 index 0000000..435df30 --- /dev/null +++ b/graphics/elements/textbox.lua @@ -0,0 +1,74 @@ +-- Text Box Graphics Element + +local element = require("graphics.element") + +---@class textbox_args +---@field text string text to show +---@field parent graphics_element +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field width? integer parent width if omitted +---@field height? integer parent height if omitted +---@field gframe? graphics_frame frame instead of x/y/width/height +---@field fg_bg? cpair foreground/background colors + +-- new text box +---@param args textbox_args +local function textbox(args) + assert(args.text ~= nil, "graphics.elements.textbox: empty text box") + + -- create new graphics element base object + local e = element.new(args) + + -- write text + + local text = args.text + local lines = { text } + + local w = e.frame.w + local h = e.frame.h + + -- wrap if needed + if string.len(text) > w then + local remaining = true + local s_start = 1 + local s_end = w + local i = 1 + + lines = {} + + while remaining do + local line = string.sub(text, s_start, s_end) + + if line == "" then + remaining = false + else + lines[i] = line + + s_start = s_end + 1 + s_end = s_end + w + i = i + 1 + end + end + end + + -- output message + for i = 1, #lines do + local cur_x, cur_y = e.window.getCursorPos() + + if i > 1 and cur_x > 1 then + if cur_y == h then + e.window.scroll(1) + e.window.setCursorPos(1, cur_y) + else + e.window.setCursorPos(1, cur_y + 1) + end + end + + e.window.write(lines[i]) + end + + return e.get() +end + +return textbox From ce227a175a8d87c727c21964e18923f807c18295 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 8 Jun 2022 12:27:28 -0400 Subject: [PATCH 262/587] #63 rectangle element --- graphics/core.lua | 14 ++++++++ graphics/element.lua | 2 ++ graphics/elements/rectangle.lua | 64 +++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 graphics/elements/rectangle.lua diff --git a/graphics/core.lua b/graphics/core.lua index 3d23dfc..adb9806 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -30,6 +30,20 @@ graphics.TEXT_ALIGN = { RIGHT = 3 } +---@class graphics_border +---@field width integer +---@field color color + +---@param width integer +---@param color color +---@return graphics_border +function graphics.border(width, color) + return { + width = width, + color = color + } +end + ---@class graphics_frame ---@field x integer ---@field y integer diff --git a/graphics/element.lua b/graphics/element.lua index 7078bc9..97b88f7 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -23,6 +23,7 @@ function element.new(args) local protected = { window = nil, ---@type table + fg_bg = core.graphics.cpair(colors.white, colors.black), frame = core.graphics.gframe(1, 1, 1, 1) } @@ -56,6 +57,7 @@ function element.new(args) protected.window.setBackgroundColor(args.fg_bg.bkg) protected.window.setTextColor(args.fg_bg.fgd) protected.window.clear() + protected.fg_bg = args.fg_bg end -- record position diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua new file mode 100644 index 0000000..90c0a4d --- /dev/null +++ b/graphics/elements/rectangle.lua @@ -0,0 +1,64 @@ +-- Rectangle Graphics Element + +local element = require("graphics.element") + +---@class rectangle_args +---@field border? graphics_border +---@field parent graphics_element +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field width? integer parent width if omitted +---@field height? integer parent height if omitted +---@field gframe? graphics_frame frame instead of x/y/width/height +---@field fg_bg cpair foreground/background colors + +-- new rectangle +---@param args rectangle_args +local function rectangle(args) + -- create new graphics element rectangle object + local e = element.new(args) + + -- draw bordered box if requested + -- element constructor will have drawn basic colored rectangle regardless + if args.border ~= nil then + e.setCursorPos(1, 1) + + local border_width = args.border.width + local border_blit = colors.toBlit(args.border.color) + local spaces = "" + local blit_fg = "" + local blit_bg_top_bot = "" + local blit_bg_sides = "" + + -- form the basic and top/bottom blit strings + for _ = 1, e.frame.w do + spaces = spaces .. " " + blit_fg = blit_fg .. e.fg_bg.blit_fgd + blit_bg_top_bot = blit_bg_top_bot .. border_blit + end + + -- form the body blit strings (sides are border, inside is normal) + for x = 1, e.frame.w do + -- edges get border color, center gets normal + if x <= border_width or x > (e.frame.w - border_width) then + blit_bg_sides = blit_bg_sides .. border_blit + else + blit_bg_sides = blit_bg_sides .. e.fg_bg.blit_bkg + end + end + + -- draw rectangle with borders + for y = 1, e.frame.h do + e.setCursorPos(y, 1) + if y <= border_width or y > (e.frame.h - border_width) then + e.blit(spaces, blit_fg, blit_bg_top_bot) + else + e.blit(spaces, blit_fg, blit_bg_sides) + end + end + end + + return e.get() +end + +return rectangle From 8002698dd0766068cff3ab1f5406d7dffd704764 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 8 Jun 2022 12:29:53 -0400 Subject: [PATCH 263/587] #63 rectangle construct asserts --- graphics/elements/rectangle.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua index 90c0a4d..ba659b2 100644 --- a/graphics/elements/rectangle.lua +++ b/graphics/elements/rectangle.lua @@ -21,6 +21,9 @@ local function rectangle(args) -- draw bordered box if requested -- element constructor will have drawn basic colored rectangle regardless if args.border ~= nil then + assert(args.border.width * 2 <= e.frame.w, "graphics.elements.rectangle: border too thick for width") + assert(args.border.width * 2 <= e.frame.h, "graphics.elements.rectangle: border too thick for height") + e.setCursorPos(1, 1) local border_width = args.border.width From 29c4c39d235c0cd1aeffe9bd4af6f927f829c918 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 8 Jun 2022 13:08:48 -0400 Subject: [PATCH 264/587] #62 uneven border support because rectangular pixels --- graphics/core.lua | 39 +++++++++++++++++++++++---------- graphics/elements/rectangle.lua | 16 ++++++++------ 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/graphics/core.lua b/graphics/core.lua index adb9806..91e7915 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -7,6 +7,7 @@ local events = {} ---@field x integer ---@field y integer +-- create a new touch event definition ---@param monitor string ---@param x integer ---@param y integer @@ -33,14 +34,18 @@ graphics.TEXT_ALIGN = { ---@class graphics_border ---@field width integer ---@field color color +---@field even boolean ----@param width integer ----@param color color +-- create a new border definition +---@param width integer border width +---@param color color border color +---@param even boolean whether to pad width extra to account for rectangular pixels ---@return graphics_border -function graphics.border(width, color) +function graphics.border(width, color, even) return { width = width, - color = color + color = color, + even = even } end @@ -50,6 +55,7 @@ end ---@field w integer ---@field h integer +-- create a new graphics frame definition ---@param x integer ---@param y integer ---@param w integer @@ -65,20 +71,31 @@ function graphics.gframe(x, y, w, h) end ---@class cpair +---@field color_a color +---@field color_b color +---@field blit_a string +---@field blit_b string ---@field fgd color ---@field bkg color ---@field blit_fgd string ---@field blit_bkg string ----@param foreground color ----@param background color +-- create a new color pair definition +---@param a color +---@param b color ---@return cpair -function graphics.cpair(foreground, background) +function graphics.cpair(a, b) return { - fgd = foreground, - bkg = background, - blit_fgd = colors.toBlit(foreground), - blit_bkg = colors.toBlit(background) + -- color pairs + color_a = a, + color_b = b, + blit_a = a, + blit_b = b, + -- aliases + fgd = a, + bkg = b, + blit_fgd = colors.toBlit(a), + blit_bkg = colors.toBlit(b) } end diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua index ba659b2..724eb7a 100644 --- a/graphics/elements/rectangle.lua +++ b/graphics/elements/rectangle.lua @@ -1,6 +1,7 @@ -- Rectangle Graphics Element local element = require("graphics.element") +local util = require("scada-common.util") ---@class rectangle_args ---@field border? graphics_border @@ -15,24 +16,25 @@ local element = require("graphics.element") -- new rectangle ---@param args rectangle_args local function rectangle(args) - -- create new graphics element rectangle object + -- create new graphics element base object local e = element.new(args) -- draw bordered box if requested -- element constructor will have drawn basic colored rectangle regardless if args.border ~= nil then - assert(args.border.width * 2 <= e.frame.w, "graphics.elements.rectangle: border too thick for width") - assert(args.border.width * 2 <= e.frame.h, "graphics.elements.rectangle: border too thick for height") - e.setCursorPos(1, 1) - local border_width = args.border.width + local border_width_v = args.border.width + local border_width_h = util.trinary(args.border.even, args.border.width * 2, args.border.width) local border_blit = colors.toBlit(args.border.color) local spaces = "" local blit_fg = "" local blit_bg_top_bot = "" local blit_bg_sides = "" + assert(border_width_v * 2 <= e.frame.w, "graphics.elements.rectangle: border too thick for width") + assert(border_width_h * 2 <= e.frame.h, "graphics.elements.rectangle: border too thick for height") + -- form the basic and top/bottom blit strings for _ = 1, e.frame.w do spaces = spaces .. " " @@ -43,7 +45,7 @@ local function rectangle(args) -- form the body blit strings (sides are border, inside is normal) for x = 1, e.frame.w do -- edges get border color, center gets normal - if x <= border_width or x > (e.frame.w - border_width) then + if x <= border_width_h or x > (e.frame.w - border_width_h) then blit_bg_sides = blit_bg_sides .. border_blit else blit_bg_sides = blit_bg_sides .. e.fg_bg.blit_bkg @@ -53,7 +55,7 @@ local function rectangle(args) -- draw rectangle with borders for y = 1, e.frame.h do e.setCursorPos(y, 1) - if y <= border_width or y > (e.frame.h - border_width) then + if y <= border_width_v or y > (e.frame.h - border_width_v) then e.blit(spaces, blit_fg, blit_bg_top_bot) else e.blit(spaces, blit_fg, blit_bg_sides) From 2ac9bab92e38727aedaab20337873d9e5d55771b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 8 Jun 2022 13:18:14 -0400 Subject: [PATCH 265/587] #63 basketweave tiling pattern element --- graphics/elements/rectangle.lua | 6 ++- graphics/elements/tiling.lua | 81 +++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 graphics/elements/tiling.lua diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua index 724eb7a..f1b177d 100644 --- a/graphics/elements/rectangle.lua +++ b/graphics/elements/rectangle.lua @@ -1,8 +1,9 @@ -- Rectangle Graphics Element -local element = require("graphics.element") local util = require("scada-common.util") +local element = require("graphics.element") + ---@class rectangle_args ---@field border? graphics_border ---@field parent graphics_element @@ -32,6 +33,7 @@ local function rectangle(args) local blit_bg_top_bot = "" local blit_bg_sides = "" + -- check dimensions assert(border_width_v * 2 <= e.frame.w, "graphics.elements.rectangle: border too thick for width") assert(border_width_h * 2 <= e.frame.h, "graphics.elements.rectangle: border too thick for height") @@ -54,7 +56,7 @@ local function rectangle(args) -- draw rectangle with borders for y = 1, e.frame.h do - e.setCursorPos(y, 1) + e.setCursorPos(1, y) if y <= border_width_v or y > (e.frame.h - border_width_v) then e.blit(spaces, blit_fg, blit_bg_top_bot) else diff --git a/graphics/elements/tiling.lua b/graphics/elements/tiling.lua new file mode 100644 index 0000000..8395068 --- /dev/null +++ b/graphics/elements/tiling.lua @@ -0,0 +1,81 @@ +-- "Basketweave" Tiling Graphics Element + +local util = require("scada-common.util") + +local element = require("graphics.element") + +---@class tiling_args +---@field fill_c cpair colors to fill with +---@field even? boolean whether to account for rectangular pixels +---@field border? graphics_border +---@field parent graphics_element +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field width? integer parent width if omitted +---@field height? integer parent height if omitted +---@field gframe? graphics_frame frame instead of x/y/width/height +---@field fg_bg cpair foreground/background colors + +-- new tiling box +---@param args tiling_args +local function tiling(args) + -- create new graphics element base object + local e = element.new(args) + + -- draw tiling box + + local fill_a = args.fill_c.blit_a + local fill_b = args.fill_c.blit_b + + local even = args.even == true + + local start_x = 1 + local start_y = 1 + local width = e.frame.w + local height = e.frame.h + local alternator = true + + -- border + if args.border ~= nil then + e.window.setBackgroundColor(args.border.color) + e.window.clear() + + start_x = 1 + util.trinary(args.border.even, args.border.width * 2, args.border.width) + start_y = 1 + args.border.width + + width = width - (2 * util.trinary(args.border.even, args.border.width * 2, args.border.width)) + height = height - (2 * args.border.width) + end + + -- check dimensions + assert(start_x <= width, "graphics.elements.tiling: start_x > width") + assert(start_y <= height, "graphics.elements.tiling: start_y > height") + assert(width > 0, "graphics.elements.tiling: width <= 0") + assert(height > 0, "graphics.elements.tiling: height <= 0") + + -- create pattern + for y = start_y, height do + e.window.setCursorPos(1, y) + for _ = start_x, width do + if alternator then + if even then + e.window.blit(" ", "00", fill_a .. fill_a) + else + e.window.blit(" ", "0", fill_a) + end + else + if even then + e.window.blit(" ", "00", fill_b .. fill_b) + else + e.window.blit(" ", "0", fill_b) + end + end + + alternator = not alternator + end + end + + return e.get() +end + +return tiling From b99f57e4801ce765b3dc8c47f1853bcec4663f74 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 8 Jun 2022 14:15:34 -0400 Subject: [PATCH 266/587] #62 redrawing --- graphics/element.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/graphics/element.lua b/graphics/element.lua index 97b88f7..e151227 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -106,7 +106,7 @@ function element.new(args) end -- draw the element given new data - function public.draw(...) + function public.update(...) protected.on_update(...) end @@ -120,6 +120,11 @@ function element.new(args) protected.window.setVisible(false) end + -- re-draw the element + function public.redraw() + protected.window.redraw() + end + return protected end From 9d107da8d9b9380ec91d9a4509b7f19107f2a53b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 8 Jun 2022 14:16:05 -0400 Subject: [PATCH 267/587] #63 horizontal fill bar indicator --- graphics/elements/hbar.lua | 84 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 graphics/elements/hbar.lua diff --git a/graphics/elements/hbar.lua b/graphics/elements/hbar.lua new file mode 100644 index 0000000..f200bee --- /dev/null +++ b/graphics/elements/hbar.lua @@ -0,0 +1,84 @@ +-- Horizontal Bar Graphics Element + +local util = require("scada-common.util") + +local element = require("graphics.element") + +---@class hbar_args +---@field bar_fg_bg cpair bar foreground/background colors +---@field border? graphics_border +---@field parent graphics_element +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field width? integer parent width if omitted +---@field fg_bg cpair foreground/background colors + +-- new horizontal bar +---@param args hbar_args +local function hbar(args) + local bkg = "" + local last_num_bars = -1 + + -- create new graphics element base object + local e = element.new(args) + + -- bar width is width - 5 characters for " 100%" + local bar_width = e.frame.w - 5 + + assert(bar_width > 0, "graphics.elements.hbar: too small for bar") + + -- set background blit string + for _ = 1, bar_width do + bkg = bkg .. args.bar_fg_bg.blit_bkg + end + + -- handle data changes + function e.on_update(fraction) + -- enforce minimum and maximum + if fraction < 0 then + fraction = 0.0 + elseif fraction > 1 then + fraction = 1.0 + end + + -- compute number of bars + local num_bars = util.round((fraction * 100) / (bar_width * 2)) + + -- redraw bar if changed + if num_bars ~= last_num_bars then + last_num_bars = num_bars + + local bar = "" + local spaces = "" + + -- fill percentage + for _ = 1, num_bars / 2 do + spaces = spaces .. " " + bar = bar .. args.bar_fg_bg.blit_fgd + end + + -- add fractional bar if needed + if num_bars % 2 == 1 then + spaces = spaces .. "\x95" + bar = bar .. args.bar_fg_bg.blit_fgd + end + + -- pad background + for _ = 1, bar_width - ((num_bars / 2) + num_bars % 2) do + spaces = spaces .. " " + bar = bar .. args.bar_fg_bg.blit_bkg + end + + e.window.setCursorPos(1, 1) + e.window.blit(spaces, bar, bkg) + end + + -- update percentage + e.window.setCursorPos(bar_width + 1, 1) + e.window.write(util.sprintf("%3.0f%%", fraction * 100)) + end + + return e.get() +end + +return hbar From 254e85f3ed42cce06bd15a2f12585e30fb4920d9 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 8 Jun 2022 14:47:45 -0400 Subject: [PATCH 268/587] timer callback dispatcher --- scada-common/tcallbackdsp.lua | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 scada-common/tcallbackdsp.lua diff --git a/scada-common/tcallbackdsp.lua b/scada-common/tcallbackdsp.lua new file mode 100644 index 0000000..5cb24e3 --- /dev/null +++ b/scada-common/tcallbackdsp.lua @@ -0,0 +1,31 @@ +-- +-- Timer Callback Dispatcher +-- + +local log = require("scada-common.log") +local util = require("scada-common.util") + +local tcallbackdsp = {} + +local registry = {} + +-- request a function to be called after the specified time +---@param time number seconds +---@param f function callback function +function tcallbackdsp.dispatch(time, f) + log.debug(util.c("TCD: dispatching ", f, " for call in ", time, " seconds")) +---@diagnostic disable-next-line: undefined-field + registry[os.startTimer(time)] = { callback = f } +end + +-- lookup a timer event and execute the callback if found +---@param event integer timer event timer ID +function tcallbackdsp.handle(event) + if registry[event] ~= nil then + log.debug(util.c("TCD: executing callback ", registry[event].callback, " for timer ", event)) + registry[event].callback() + registry[event] = nil + end +end + +return tcallbackdsp From 15bc816d7e6ac0be9825c187667a375337f9a18b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 8 Jun 2022 14:48:17 -0400 Subject: [PATCH 269/587] #63 button control element --- graphics/elements/button.lua | 58 ++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 graphics/elements/button.lua diff --git a/graphics/elements/button.lua b/graphics/elements/button.lua new file mode 100644 index 0000000..5d818ff --- /dev/null +++ b/graphics/elements/button.lua @@ -0,0 +1,58 @@ +-- Button Graphics Element + +local tcd = require("scada-common.tcallbackdsp") + +local element = require("graphics.element") + +---@class button_args +---@field text string button text +---@field callback function function to call on touch +---@field min_width? integer text length + 2 if omitted +---@field active_fg_bg? cpair foreground/background colors when pressed +---@field parent graphics_element +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field height? integer parent height if omitted +---@field gframe? graphics_frame frame instead of x/y/width/height +---@field fg_bg cpair foreground/background colors + +-- new button +---@param args button_args +local function button(args) + local text_width = string.len(args.text) + args.width = math.max(text_width + 2, args.min_width) + + -- create new graphics element base object + local e = element.new(args) + + local h_pad = math.floor((e.frame.w - text_width) / 2) + local v_pad = math.floor(e.frame.h / 2) + 1 + + -- write the button text + e.window.setCursorPos(h_pad, v_pad) + e.write(args.text) + + -- handle touch + function e.handle_touch(event) + if args.active_fg_bg ~= nil then + -- show as pressed + e.window.setTextColor(args.active_fg_bg.fgd) + e.window.setBackgroundColor(args.active_fg_bg.bkg) + e.window.redraw() + + -- show as unpressed in 0.5 seconds + tcd.dispatch(0.5, function () + e.window.setTextColor(e.fg_bg.fgd) + e.window.setBackgroundColor(e.fg_bg.bkg) + e.window.redraw() + end) + end + + -- call the touch callback + args.callback() + end + + return e.get() +end + +return button From ac607f9dc6da08c82bc59e7b0a706983f949686d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 8 Jun 2022 16:21:49 -0400 Subject: [PATCH 270/587] #63 latching button in addition to pushbutton --- .../elements/{button.lua => button_push.lua} | 10 +-- graphics/elements/button_switch.lua | 69 +++++++++++++++++++ 2 files changed, 74 insertions(+), 5 deletions(-) rename graphics/elements/{button.lua => button_push.lua} (92%) create mode 100644 graphics/elements/button_switch.lua diff --git a/graphics/elements/button.lua b/graphics/elements/button_push.lua similarity index 92% rename from graphics/elements/button.lua rename to graphics/elements/button_push.lua index 5d818ff..f74ad19 100644 --- a/graphics/elements/button.lua +++ b/graphics/elements/button_push.lua @@ -4,7 +4,7 @@ local tcd = require("scada-common.tcallbackdsp") local element = require("graphics.element") ----@class button_args +---@class push_button_args ---@field text string button text ---@field callback function function to call on touch ---@field min_width? integer text length + 2 if omitted @@ -16,9 +16,9 @@ local element = require("graphics.element") ---@field gframe? graphics_frame frame instead of x/y/width/height ---@field fg_bg cpair foreground/background colors --- new button ----@param args button_args -local function button(args) +-- new push button +---@param args push_button_args +local function push_button(args) local text_width = string.len(args.text) args.width = math.max(text_width + 2, args.min_width) @@ -55,4 +55,4 @@ local function button(args) return e.get() end -return button +return push_button diff --git a/graphics/elements/button_switch.lua b/graphics/elements/button_switch.lua new file mode 100644 index 0000000..3d75bcc --- /dev/null +++ b/graphics/elements/button_switch.lua @@ -0,0 +1,69 @@ +-- Button Graphics Element + +local element = require("graphics.element") + +---@class switch_button_args +---@field text string button text +---@field callback function function to call on touch +---@field default? boolean default state, defaults to off (false) +---@field min_width? integer text length + 2 if omitted +---@field active_fg_bg cpair foreground/background colors when pressed +---@field parent graphics_element +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field height? integer parent height if omitted +---@field gframe? graphics_frame frame instead of x/y/width/height +---@field fg_bg cpair foreground/background colors + +-- new switch button (latch high/low) +---@param args switch_button_args +local function switch_button(args) + -- button state (convert nil to false if missing) + local state = args.default or false + + -- determine widths + local text_width = string.len(args.text) + args.width = math.max(text_width + 2, args.min_width) + + -- create new graphics element base object + local e = element.new(args) + + local h_pad = math.floor((e.frame.w - text_width) / 2) + local v_pad = math.floor(e.frame.h / 2) + 1 + + -- write the button text + e.window.setCursorPos(h_pad, v_pad) + e.write(args.text) + + -- show the button state + local function draw_state() + if state then + -- show as pressed + e.window.setTextColor(args.active_fg_bg.fgd) + e.window.setBackgroundColor(args.active_fg_bg.bkg) + else + -- show as unpressed + e.window.setTextColor(e.fg_bg.fgd) + e.window.setBackgroundColor(e.fg_bg.bkg) + end + + e.window.redraw() + end + + -- initial draw + draw_state() + + -- handle touch + function e.handle_touch(event) + -- toggle state + state = not state + draw_state() + + -- call the touch callback with state + args.callback(state) + end + + return e.get() +end + +return switch_button From 6f645579f802bfda58ac73258cf629e522a09abe Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 8 Jun 2022 16:52:41 -0400 Subject: [PATCH 271/587] #63 removed gframe as an argument to buttons --- graphics/elements/button_push.lua | 1 - graphics/elements/button_switch.lua | 1 - 2 files changed, 2 deletions(-) diff --git a/graphics/elements/button_push.lua b/graphics/elements/button_push.lua index f74ad19..e81740a 100644 --- a/graphics/elements/button_push.lua +++ b/graphics/elements/button_push.lua @@ -13,7 +13,6 @@ local element = require("graphics.element") ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field height? integer parent height if omitted ----@field gframe? graphics_frame frame instead of x/y/width/height ---@field fg_bg cpair foreground/background colors -- new push button diff --git a/graphics/elements/button_switch.lua b/graphics/elements/button_switch.lua index 3d75bcc..6c14483 100644 --- a/graphics/elements/button_switch.lua +++ b/graphics/elements/button_switch.lua @@ -12,7 +12,6 @@ local element = require("graphics.element") ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field height? integer parent height if omitted ----@field gframe? graphics_frame frame instead of x/y/width/height ---@field fg_bg cpair foreground/background colors -- new switch button (latch high/low) From d8bbe4b459251cf3acd63fa8fb9e26f8b80e8921 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 8 Jun 2022 17:16:53 -0400 Subject: [PATCH 272/587] #63 added indicator icon/light, added util.strrep string repeater --- graphics/elements/indicator_icon.lua | 61 +++++++++++++++++++++++++++ graphics/elements/indicator_light.lua | 49 +++++++++++++++++++++ scada-common/util.lua | 12 ++++++ 3 files changed, 122 insertions(+) create mode 100644 graphics/elements/indicator_icon.lua create mode 100644 graphics/elements/indicator_light.lua diff --git a/graphics/elements/indicator_icon.lua b/graphics/elements/indicator_icon.lua new file mode 100644 index 0000000..d36dbe7 --- /dev/null +++ b/graphics/elements/indicator_icon.lua @@ -0,0 +1,61 @@ +-- Icon Indicator Graphics Element + +local util = require("scada-common.util") + +local element = require("graphics.element") + +---@class icon_sym_color +---@field color cpair +---@field symbol string + +---@class icon_indicator_args +---@field text string indicator text +---@field states table state color and symbol table +---@field default? integer default state, defaults to 1 +---@field min_text_width? integer text length if omitted +---@field parent graphics_element +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field height? integer parent height if omitted +---@field fg_bg cpair foreground/background colors + +-- new icon indicator +---@param args icon_indicator_args +local function icon_indicator(args) + -- determine width + args.width = (args.min_text_width or string.len(args.text)) + 4 + + -- create new graphics element base object + local e = element.new(args) + + -- state blit strings + local state_blit_cmds = {} + for i = 1, #args.states do + local sym_color = args.states[i] ---@type icon_sym_color + + table.insert(state_blit_cmds, { + text = " " .. sym_color.symbol .. " ", + fgd = util.strrep(sym_color.color.blit_fgd, 3), + bkg = util.strrep(sym_color.color.blit_bkg, 3) + }) + end + + -- write text and initial indicator light + e.setCursorPos(5, 1) + e.window.write(args.text) + + -- on state change + ---@param new_state integer indicator state + function e.on_update(new_state) + local blit_cmd = state_blit_cmds[new_state] + e.window.setCursorPos(1, 1) + e.window.blit(blit_cmd.text, blit_cmd.fgd, blit_cmd.bkg) + end + + -- initial icon draw + e.on_update(args.default or 1) + + return e.get() +end + +return icon_indicator diff --git a/graphics/elements/indicator_light.lua b/graphics/elements/indicator_light.lua new file mode 100644 index 0000000..b3357e8 --- /dev/null +++ b/graphics/elements/indicator_light.lua @@ -0,0 +1,49 @@ +-- Indicator Light Graphics Element + +local util = require("scada-common.util") + +local element = require("graphics.element") + +---@class indicator_light_args +---@field text string indicator text +---@field colors cpair on/off colors (a/b respectively) +---@field min_text_width? integer text length if omitted +---@field parent graphics_element +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field height? integer parent height if omitted +---@field fg_bg cpair foreground/background colors + +-- new indicator light +---@param args indicator_light_args +local function indicator_light(args) + -- determine width + args.width = (args.min_text_width or string.len(args.text)) + 3 + + -- create new graphics element base object + local e = element.new(args) + + -- on/off blit strings + local on_blit = util.strrep(args.colors.blit_a, 2) + local off_blit = util.strrep(args.colors.blit_b, 2) + + -- write text and initial indicator light + e.setCursorPos(1, 1) + e.window.blit(" ", "000", off_blit .. e.fg_bg.blit_bkg) + e.window.write(args.text) + + -- on state change + ---@param new_state boolean indicator state + function e.on_update(new_state) + e.window.setCursorPos(1, 1) + if new_state then + e.window.blit(" ", "00", on_blit) + else + e.window.blit(" ", "00", off_blit) + end + end + + return e.get() +end + +return indicator_light diff --git a/scada-common/util.lua b/scada-common/util.lua index fc194ca..30498d6 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -56,6 +56,18 @@ function util.strval(val) end end +-- repeat a string n times +---@param str string +---@param n integer +---@return string +function util.strrep(str, n) + local repeated = "" + for _ = 1, n do + repeated = repeated .. str + end + return repeated +end + -- concatenation with built-in to string ---@vararg any ---@return string From bc844d21bd05753f22c68eae2d3299bfc83ae6a5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 8 Jun 2022 17:22:20 -0400 Subject: [PATCH 273/587] #63 use util.strrep where appropriate --- graphics/elements/hbar.lua | 4 +--- graphics/elements/rectangle.lua | 8 +++----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/graphics/elements/hbar.lua b/graphics/elements/hbar.lua index f200bee..37e1550 100644 --- a/graphics/elements/hbar.lua +++ b/graphics/elements/hbar.lua @@ -28,9 +28,7 @@ local function hbar(args) assert(bar_width > 0, "graphics.elements.hbar: too small for bar") -- set background blit string - for _ = 1, bar_width do - bkg = bkg .. args.bar_fg_bg.blit_bkg - end + bkg = util.strrep(args.bar_fg_bg.blit_bkg, bar_width) -- handle data changes function e.on_update(fraction) diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua index f1b177d..069ef2b 100644 --- a/graphics/elements/rectangle.lua +++ b/graphics/elements/rectangle.lua @@ -38,11 +38,9 @@ local function rectangle(args) assert(border_width_h * 2 <= e.frame.h, "graphics.elements.rectangle: border too thick for height") -- form the basic and top/bottom blit strings - for _ = 1, e.frame.w do - spaces = spaces .. " " - blit_fg = blit_fg .. e.fg_bg.blit_fgd - blit_bg_top_bot = blit_bg_top_bot .. border_blit - end + spaces = util.strrep(" ", e.frame.w) + blit_fg = util.strrep(e.fg_bg.blit_fgd, e.frame.w) + blit_bg_top_bot = util.strrep(border_blit, e.frame.w) -- form the body blit strings (sides are border, inside is normal) for x = 1, e.frame.w do From 1dad4bcf7788c829a0117f529331f13c37491df4 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 8 Jun 2022 18:48:20 -0400 Subject: [PATCH 274/587] util string wrap function --- scada-common/util.lua | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/scada-common/util.lua b/scada-common/util.lua index 30498d6..dea1621 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -68,6 +68,47 @@ function util.strrep(str, n) return repeated end +-- wrap a string into a table of lines, supporting single dash splits +---@param str string +---@param limit integer line limit +---@return table lines +function util.strwrap(str, limit) + local lines = {} + local ln_start = 1 + + lines[1] = string.sub(str, 1, str:find("([%-%s]+)") - 1) + +---@diagnostic disable-next-line: discard-returns + str:gsub("(%s+)()(%S+)()", + function(space, start, word, stop) + -- support splitting SINGLE DASH words + word:gsub("(%S+)(%-)()(%S+)()", + function (pre, dash, d_start, post, d_stop) + if (stop + d_stop) - ln_start <= limit then + -- do nothing, it will entirely fit + elseif ((start + d_start) + 1) - ln_start <= limit then + -- we can fit including the dash + lines[#lines] = lines[#lines] .. space .. pre .. dash + -- drop the space and replace the word with the post + space = "" + word = post + -- force a wrap + stop = limit + 1 + ln_start + -- change start position for new line start + start = start + d_start - 1 + end + end) + -- can we append this or do we have to start a new line? + if stop - ln_start > limit then + -- starting new line + ln_start = start + lines[#lines + 1] = word + else lines[#lines] = lines[#lines] .. space .. word end + end) + + return lines +end + -- concatenation with built-in to string ---@vararg any ---@return string From 307883e6e7571965a5c0259db45afb5a8c623ccd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 8 Jun 2022 18:53:24 -0400 Subject: [PATCH 275/587] #63 use util string wrap and support text alignment --- graphics/elements/textbox.lua | 58 ++++++++++++----------------------- 1 file changed, 20 insertions(+), 38 deletions(-) diff --git a/graphics/elements/textbox.lua b/graphics/elements/textbox.lua index 435df30..cd4a313 100644 --- a/graphics/elements/textbox.lua +++ b/graphics/elements/textbox.lua @@ -1,10 +1,16 @@ -- Text Box Graphics Element +local util = require("scada-common.util") + +local core = require("graphics.core") local element = require("graphics.element") +local TEXT_ALIGN = core.graphics.TEXT_ALIGN + ---@class textbox_args ---@field text string text to show ---@field parent graphics_element +---@field alignment? TEXT_ALIGN text alignment, left by default ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field width? integer parent width if omitted @@ -20,49 +26,25 @@ local function textbox(args) -- create new graphics element base object local e = element.new(args) - -- write text + local alignment = args.alignment or TEXT_ALIGN.LEFT + + -- draw textbox local text = args.text - local lines = { text } + local lines = util.strwrap(text, e.frame.w) - local w = e.frame.w - local h = e.frame.h - - -- wrap if needed - if string.len(text) > w then - local remaining = true - local s_start = 1 - local s_end = w - local i = 1 - - lines = {} - - while remaining do - local line = string.sub(text, s_start, s_end) - - if line == "" then - remaining = false - else - lines[i] = line - - s_start = s_end + 1 - s_end = s_end + w - i = i + 1 - end - end - end - - -- output message for i = 1, #lines do - local cur_x, cur_y = e.window.getCursorPos() + if i > e.frame.h then break end - if i > 1 and cur_x > 1 then - if cur_y == h then - e.window.scroll(1) - e.window.setCursorPos(1, cur_y) - else - e.window.setCursorPos(1, cur_y + 1) - end + local len = string.len(lines[i]) + + -- use cursor position to align this line + if alignment == TEXT_ALIGN.CENTER then + e.window.setCursorPos(math.floor((e.frame.w - len) / 2), i) + elseif alignment == TEXT_ALIGN.RIGHT then + e.window.setCursorPos(e.frame.w - len, i) + else + e.window.setCursorPos(1, i) end e.window.write(lines[i]) From 11e4d89b1da85788412dd7e5b1945b5119d062c2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 9 Jun 2022 10:18:37 -0400 Subject: [PATCH 276/587] #63 vertical fill bar indicator --- graphics/element.lua | 7 ++++ graphics/elements/hbar.lua | 1 - graphics/elements/vbar.lua | 82 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 graphics/elements/vbar.lua diff --git a/graphics/element.lua b/graphics/element.lua index e151227..da296cb 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -32,6 +32,7 @@ function element.new(args) -- get the parent window self.p_window = args.window or args.parent.window() + -- check window assert(self.p_window, "graphics.element: no parent window provided") -- get frame coordinates/size @@ -48,6 +49,12 @@ function element.new(args) protected.frame.h = args.height or h end + -- check frame + assert(protected.frame.x >= 1, "graphics.element: frame x not >= 1") + assert(protected.frame.y >= 1, "graphics.element: frame y not >= 1") + assert(protected.frame.w >= 1, "graphics.element: frame width not >= 1") + assert(protected.frame.h >= 1, "graphics.element: frame height not >= 1") + -- create window local f = protected.frame protected.window = window.create(self.p_window, f.x, f.y, f.w, f.h, true) diff --git a/graphics/elements/hbar.lua b/graphics/elements/hbar.lua index 37e1550..189a429 100644 --- a/graphics/elements/hbar.lua +++ b/graphics/elements/hbar.lua @@ -6,7 +6,6 @@ local element = require("graphics.element") ---@class hbar_args ---@field bar_fg_bg cpair bar foreground/background colors ----@field border? graphics_border ---@field parent graphics_element ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted diff --git a/graphics/elements/vbar.lua b/graphics/elements/vbar.lua new file mode 100644 index 0000000..d626575 --- /dev/null +++ b/graphics/elements/vbar.lua @@ -0,0 +1,82 @@ +-- Horizontal Bar Graphics Element + +local util = require("scada-common.util") + +local element = require("graphics.element") + +---@class vbar_args +---@field parent graphics_element +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field width? integer parent width if omitted +---@field height? integer parent height if omitted +---@field gframe? graphics_frame frame instead of x/y/width/height +---@field fg_bg cpair foreground/background colors + +-- new vertical bar +---@param args vbar_args +local function vbar(args) + -- last state + local last_num_bars = -1 + + -- create new graphics element base object + local e = element.new(args) + + -- blit strings + local fgd = util.strrep(e.fg_bg.blit_fgd, e.frame.w) + local bkg = util.strrep(e.fg_bg.blit_bkg, e.frame.w) + local spaces = util.strrep(" ", e.frame.w) + local one_third = util.strrep("\x8f", e.frame.w) + local two_thirds = util.strrep("\x83", e.frame.w) + + -- handle data changes + function e.on_update(fraction) + -- enforce minimum and maximum + if fraction < 0 then + fraction = 0.0 + elseif fraction > 1 then + fraction = 1.0 + end + + -- compute number of bars + local num_bars = util.round((fraction * 100) / (e.frame.h * 3)) + + -- redraw only if number of bars has changed + if num_bars ~= last_num_bars then + last_num_bars = num_bars + + -- start bottom up + local y = e.window.h + + -- start at base of vertical bar + e.window.setCursorPos(1, y) + + -- fill percentage + for _ = 1, num_bars / 3 do + e.window.blit(spaces, bkg, fgd) + y = y - 1 + e.window.setCursorPos(1, y) + end + + -- add fractional bar if needed + if num_bars % 3 == 1 then + e.window.blit(one_third, bkg, fgd) + y = y - 1 + elseif num_bars % 3 == 2 then + e.window.blit(two_thirds, bkg, fgd) + y = y - 1 + end + + -- fill the rest blank + while y > 0 do + e.window.setCursorPos(1, y) + e.window.blit(spaces, fgd, bkg) + y = y - 1 + end + end + end + + return e.get() +end + +return vbar From 1fa87132d63e053b8c5d240c7a96f4fae6115fb8 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 9 Jun 2022 11:59:55 -0400 Subject: [PATCH 277/587] #63 allow hbar to have variable height, other bar improvement --- graphics/elements/hbar.lua | 35 ++++++++++++++++++++++++----------- graphics/elements/vbar.lua | 4 ++-- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/graphics/elements/hbar.lua b/graphics/elements/hbar.lua index 189a429..bba4eb6 100644 --- a/graphics/elements/hbar.lua +++ b/graphics/elements/hbar.lua @@ -5,27 +5,35 @@ local util = require("scada-common.util") local element = require("graphics.element") ---@class hbar_args ----@field bar_fg_bg cpair bar foreground/background colors +---@field show_percent? boolean whether or not to show the percent +---@field bar_fg_bg? cpair bar foreground/background colors if showing percent ---@field parent graphics_element ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field width? integer parent width if omitted +---@field height? integer parent height if omitted +---@field gframe? graphics_frame frame instead of x/y/width/height ---@field fg_bg cpair foreground/background colors -- new horizontal bar ---@param args hbar_args local function hbar(args) + -- properties/state local bkg = "" local last_num_bars = -1 -- create new graphics element base object local e = element.new(args) - -- bar width is width - 5 characters for " 100%" - local bar_width = e.frame.w - 5 + -- bar width is width - 5 characters for " 100%" if showing percent + local bar_width = util.trinary(args.show_percent, e.frame.w - 5, e.frame.w) assert(bar_width > 0, "graphics.elements.hbar: too small for bar") + -- determine bar colors + local bar_bkg = util.trinary(args.bar_fg_bg == nil, e.fg_bg.blit_bkg, args.bar_fg_bg.blit_bkg) + local bar_fgd = util.trinary(args.bar_fg_bg == nil, e.fg_bg.blit_fgd, args.bar_fg_bg.blit_fgd) + -- set background blit string bkg = util.strrep(args.bar_fg_bg.blit_bkg, bar_width) @@ -45,34 +53,39 @@ local function hbar(args) if num_bars ~= last_num_bars then last_num_bars = num_bars - local bar = "" + local fgd = "" local spaces = "" -- fill percentage for _ = 1, num_bars / 2 do spaces = spaces .. " " - bar = bar .. args.bar_fg_bg.blit_fgd + fgd = fgd .. bar_fgd end -- add fractional bar if needed if num_bars % 2 == 1 then spaces = spaces .. "\x95" - bar = bar .. args.bar_fg_bg.blit_fgd + fgd = fgd .. bar_fgd end -- pad background for _ = 1, bar_width - ((num_bars / 2) + num_bars % 2) do spaces = spaces .. " " - bar = bar .. args.bar_fg_bg.blit_bkg + fgd = fgd .. bar_bkg end - e.window.setCursorPos(1, 1) - e.window.blit(spaces, bar, bkg) + -- draw bar + for y = 1, e.frame.h do + e.window.setCursorPos(1, y) + e.window.blit(spaces, fgd, bkg) + end end -- update percentage - e.window.setCursorPos(bar_width + 1, 1) - e.window.write(util.sprintf("%3.0f%%", fraction * 100)) + if args.show_percent then + e.window.setCursorPos(bar_width + 1, math.max(1, math.ceil(e.frame.h / 2))) + e.window.write(util.sprintf("%3.0f%%", fraction * 100)) + end end return e.get() diff --git a/graphics/elements/vbar.lua b/graphics/elements/vbar.lua index d626575..333af74 100644 --- a/graphics/elements/vbar.lua +++ b/graphics/elements/vbar.lua @@ -1,4 +1,4 @@ --- Horizontal Bar Graphics Element +-- Vertical Bar Graphics Element local util = require("scada-common.util") @@ -16,7 +16,7 @@ local element = require("graphics.element") -- new vertical bar ---@param args vbar_args local function vbar(args) - -- last state + -- properties/state local last_num_bars = -1 -- create new graphics element base object From dc867095fd072a87aba0b1407ae088dc65db13fa Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 11 Jun 2022 12:20:49 -0400 Subject: [PATCH 278/587] util spaces function --- graphics/elements/rectangle.lua | 2 +- graphics/elements/vbar.lua | 2 +- scada-common/util.lua | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua index 069ef2b..a8c65b1 100644 --- a/graphics/elements/rectangle.lua +++ b/graphics/elements/rectangle.lua @@ -38,7 +38,7 @@ local function rectangle(args) assert(border_width_h * 2 <= e.frame.h, "graphics.elements.rectangle: border too thick for height") -- form the basic and top/bottom blit strings - spaces = util.strrep(" ", e.frame.w) + spaces = util.spaces(e.frame.w) blit_fg = util.strrep(e.fg_bg.blit_fgd, e.frame.w) blit_bg_top_bot = util.strrep(border_blit, e.frame.w) diff --git a/graphics/elements/vbar.lua b/graphics/elements/vbar.lua index 333af74..c140c2e 100644 --- a/graphics/elements/vbar.lua +++ b/graphics/elements/vbar.lua @@ -25,7 +25,7 @@ local function vbar(args) -- blit strings local fgd = util.strrep(e.fg_bg.blit_fgd, e.frame.w) local bkg = util.strrep(e.fg_bg.blit_bkg, e.frame.w) - local spaces = util.strrep(" ", e.frame.w) + local spaces = util.spaces(e.frame.w) local one_third = util.strrep("\x8f", e.frame.w) local two_thirds = util.strrep("\x83", e.frame.w) diff --git a/scada-common/util.lua b/scada-common/util.lua index dea1621..1a36bc0 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -68,6 +68,13 @@ function util.strrep(str, n) return repeated end +-- repeat a space n times +---@param n integer +---@return string +function util.spaces(n) + return util.strrep(" ", n) +end + -- wrap a string into a table of lines, supporting single dash splits ---@param str string ---@param limit integer line limit From 0950fc045dd39fbb3611d8340bc48bcd99bc1a1a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 11 Jun 2022 12:21:14 -0400 Subject: [PATCH 279/587] #63 new indicators and fixed up old ones --- graphics/elements/indicator_data.lua | 61 +++++++++++++++++++++++ graphics/elements/indicator_icon.lua | 11 ++-- graphics/elements/indicator_light.lua | 11 ++-- graphics/elements/indicator_state.lua | 72 +++++++++++++++++++++++++++ 4 files changed, 143 insertions(+), 12 deletions(-) create mode 100644 graphics/elements/indicator_data.lua create mode 100644 graphics/elements/indicator_state.lua diff --git a/graphics/elements/indicator_data.lua b/graphics/elements/indicator_data.lua new file mode 100644 index 0000000..a9677c5 --- /dev/null +++ b/graphics/elements/indicator_data.lua @@ -0,0 +1,61 @@ +-- Data Indicator Graphics Element + +local util = require("scada-common.util") + +local element = require("graphics.element") + +---@class data_indicator_args +---@field label string indicator label +---@field unit? string indicator unit +---@field format string data format (lua string format) +---@field label_unit_colors? cpair label foreground color (a), unit foreground color (b) +---@field default any default value +---@field parent graphics_element +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field width integer length +---@field fg_bg cpair foreground/background colors + +-- new data indicator +---@param args data_indicator_args +local function data_indicator(args) + -- create new graphics element base object + local e = element.new(args) + + -- label color + if args.label_unit_colors ~= nil then + e.window.setForegroundColor(args.label_unit_colors.color_a) + end + + -- write label + e.setCursorPos(1, 1) + e.window.write(args.label) + + local data_start = string.len(args.label) + 2 + + -- on state change + ---@param value any new value + function e.on_update(value) + local data_str = util.sprintf(args.format, value) + + -- write data + e.window.setCursorPos(data_start, 1) + e.window.setForegroundColor(e.fg_bg.fgd) + e.window.write(data_str) + + -- write label + if args.unit ~= nil then + if args.label_unit_colors ~= nil then + e.window.setForegroundColor(args.label_unit_colors.color_b) + end + e.window.write(" " .. args.unit) + end + end + + -- initial value draw + e.on_update(args.default) + + return e.get() +end + +return data_indicator diff --git a/graphics/elements/indicator_icon.lua b/graphics/elements/indicator_icon.lua index d36dbe7..fc06a29 100644 --- a/graphics/elements/indicator_icon.lua +++ b/graphics/elements/indicator_icon.lua @@ -9,21 +9,20 @@ local element = require("graphics.element") ---@field symbol string ---@class icon_indicator_args ----@field text string indicator text +---@field label string indicator label ---@field states table state color and symbol table ---@field default? integer default state, defaults to 1 ----@field min_text_width? integer text length if omitted +---@field min_label_width? integer label length if omitted ---@field parent graphics_element ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ----@field height? integer parent height if omitted ---@field fg_bg cpair foreground/background colors -- new icon indicator ---@param args icon_indicator_args local function icon_indicator(args) -- determine width - args.width = (args.min_text_width or string.len(args.text)) + 4 + args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 4 -- create new graphics element base object local e = element.new(args) @@ -40,9 +39,9 @@ local function icon_indicator(args) }) end - -- write text and initial indicator light + -- write label and initial indicator light e.setCursorPos(5, 1) - e.window.write(args.text) + e.window.write(args.label) -- on state change ---@param new_state integer indicator state diff --git a/graphics/elements/indicator_light.lua b/graphics/elements/indicator_light.lua index b3357e8..9192099 100644 --- a/graphics/elements/indicator_light.lua +++ b/graphics/elements/indicator_light.lua @@ -5,20 +5,19 @@ local util = require("scada-common.util") local element = require("graphics.element") ---@class indicator_light_args ----@field text string indicator text +---@field label string indicator label ---@field colors cpair on/off colors (a/b respectively) ----@field min_text_width? integer text length if omitted +---@field min_label_width? integer label length if omitted ---@field parent graphics_element ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ----@field height? integer parent height if omitted ---@field fg_bg cpair foreground/background colors -- new indicator light ---@param args indicator_light_args local function indicator_light(args) -- determine width - args.width = (args.min_text_width or string.len(args.text)) + 3 + args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 3 -- create new graphics element base object local e = element.new(args) @@ -27,10 +26,10 @@ local function indicator_light(args) local on_blit = util.strrep(args.colors.blit_a, 2) local off_blit = util.strrep(args.colors.blit_b, 2) - -- write text and initial indicator light + -- write label and initial indicator light e.setCursorPos(1, 1) e.window.blit(" ", "000", off_blit .. e.fg_bg.blit_bkg) - e.window.write(args.text) + e.window.write(args.label) -- on state change ---@param new_state boolean indicator state diff --git a/graphics/elements/indicator_state.lua b/graphics/elements/indicator_state.lua new file mode 100644 index 0000000..422259f --- /dev/null +++ b/graphics/elements/indicator_state.lua @@ -0,0 +1,72 @@ +-- State (Text) Indicator Graphics Element + +local util = require("scada-common.util") + +local element = require("graphics.element") + +---@class state_text_color +---@field color cpair +---@field text string + +---@class state_indicator_args +---@field states table state color and text table +---@field default? integer default state, defaults to 1 +---@field min_width? integer max state text length if omitted +---@field parent graphics_element +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field height? integer 1 if omitted, must be an odd number +---@field fg_bg cpair foreground/background colors + +-- new state indicator +---@param args state_indicator_args +local function state_indicator(args) + -- determine height + if util.is_int(args.height) then + assert(args.height % 2 == 1, "graphics.elements.indicator_state: height should be an odd number") + else + args.height = 1 + end + + -- initial guess at width + args.width = args.min_width or 1 + + -- state blit strings + local state_blit_cmds = {} + for i = 1, #args.states do + local state_def = args.states[i] ---@type state_text_color + + -- re-determine width + if string.len(state_def.text) > args.width then + args.width = string.len(state_def.text) + end + + local len = string.len(state_def.text) + local lpad = math.floor((args.width - len) / 2) + local rpad = len - lpad + + table.insert(state_blit_cmds, { + text = util.spaces(lpad) .. state_def.text .. util.spaces(rpad), + fgd = util.strrep(state_def.color.blit_fgd, 3), + bkg = util.strrep(state_def.color.blit_bkg, 3) + }) + end + + -- create new graphics element base object + local e = element.new(args) + + -- on state change + ---@param new_state integer indicator state + function e.on_update(new_state) + local blit_cmd = state_blit_cmds[new_state] + e.window.setCursorPos(1, 1) + e.window.blit(blit_cmd.text, blit_cmd.fgd, blit_cmd.bkg) + end + + -- initial draw + e.on_update(args.default or 1) + + return e.get() +end + +return state_indicator From 3004902ce54fdab9e979ac0c0fdeb026ca673041 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 11 Jun 2022 16:38:15 -0400 Subject: [PATCH 280/587] #63 bugfixes --- graphics/elements/button_push.lua | 4 +++- graphics/elements/button_switch.lua | 4 +++- graphics/elements/indicator_data.lua | 6 +++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/graphics/elements/button_push.lua b/graphics/elements/button_push.lua index e81740a..a51e8f3 100644 --- a/graphics/elements/button_push.lua +++ b/graphics/elements/button_push.lua @@ -29,9 +29,11 @@ local function push_button(args) -- write the button text e.window.setCursorPos(h_pad, v_pad) - e.write(args.text) + e.window.write(args.text) -- handle touch + ---@param event monitor_touch monitor touch event +---@diagnostic disable-next-line: unused-local function e.handle_touch(event) if args.active_fg_bg ~= nil then -- show as pressed diff --git a/graphics/elements/button_switch.lua b/graphics/elements/button_switch.lua index 6c14483..c17e553 100644 --- a/graphics/elements/button_switch.lua +++ b/graphics/elements/button_switch.lua @@ -32,7 +32,7 @@ local function switch_button(args) -- write the button text e.window.setCursorPos(h_pad, v_pad) - e.write(args.text) + e.window.write(args.text) -- show the button state local function draw_state() @@ -53,6 +53,8 @@ local function switch_button(args) draw_state() -- handle touch + ---@param event monitor_touch monitor touch event +---@diagnostic disable-next-line: unused-local function e.handle_touch(event) -- toggle state state = not state diff --git a/graphics/elements/indicator_data.lua b/graphics/elements/indicator_data.lua index a9677c5..4f0655a 100644 --- a/graphics/elements/indicator_data.lua +++ b/graphics/elements/indicator_data.lua @@ -24,7 +24,7 @@ local function data_indicator(args) -- label color if args.label_unit_colors ~= nil then - e.window.setForegroundColor(args.label_unit_colors.color_a) + e.window.setTextColor(args.label_unit_colors.color_a) end -- write label @@ -40,13 +40,13 @@ local function data_indicator(args) -- write data e.window.setCursorPos(data_start, 1) - e.window.setForegroundColor(e.fg_bg.fgd) + e.window.setTextColor(e.fg_bg.fgd) e.window.write(data_str) -- write label if args.unit ~= nil then if args.label_unit_colors ~= nil then - e.window.setForegroundColor(args.label_unit_colors.color_b) + e.window.setTextColor(args.label_unit_colors.color_b) end e.window.write(" " .. args.unit) end From 4488a0594f915b393944467d3c990c6e15b4812d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 11 Jun 2022 16:44:31 -0400 Subject: [PATCH 281/587] #63 numeric spinbox element --- graphics/element.lua | 10 +++ graphics/elements/spinbox_numeric.lua | 90 +++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 graphics/elements/spinbox_numeric.lua diff --git a/graphics/element.lua b/graphics/element.lua index da296cb..1ed7afd 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -87,6 +87,11 @@ function element.new(args) function protected.on_update(...) end + -- get control value + function protected.get_value() + return nil + end + ---@class graphics_element local public = {} @@ -117,6 +122,11 @@ function element.new(args) protected.on_update(...) end + -- get the control value reading + function public.get_value() + return protected.get_value() + end + -- show the element function public.show() protected.window.setVisible(true) diff --git a/graphics/elements/spinbox_numeric.lua b/graphics/elements/spinbox_numeric.lua new file mode 100644 index 0000000..d77c3ea --- /dev/null +++ b/graphics/elements/spinbox_numeric.lua @@ -0,0 +1,90 @@ +-- Spinbox Numeric Graphics Element + +local element = require("graphics.element") +local util = require("scada-common.util") + +---@class spinbox_args +---@field default? number default value, defaults to 0.0 +---@field whole_num_precision integer number of whole number digits +---@field fractional_precision integer number of fractional digits +---@field arrow_fg_bg cpair arrow foreground/background colors +---@field parent graphics_element +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field fg_bg cpair foreground/background colors + +-- new spinbox control +---@param args spinbox_args +local function spinbox(args) + -- properties + local value = args.default or 0.0 + local digits = {} + local wn_prec = args.whole_num_precision + local fr_prec = args.fractional_precision + local dec_point_x = args.whole_num_precision + 1 + + assert(util.is_int(wn_prec), "graphics.element.spinbox_numeric: whole number precision must be an integer") + assert(util.is_int(fr_prec), "graphics.element.spinbox_numeric: fractional precision must be an integer") + + local initial_str = util.sprintf("%" .. wn_prec .. "." .. fr_prec .. "f", value) + +---@diagnostic disable-next-line: discard-returns + initial_str:gsub("%d", function(char) table.insert(digits, char) end) + + -- determine widths + args.width = wn_prec + fr_prec + util.trinary(fr_prec > 0, 1, 0) + args.height = 3 + + -- create new graphics element base object + local e = element.new(args) + + -- draw the arrows + e.window.setBackgroundColor(args.arrow_fg_bg.bkg) + e.window.setTextColor(args.arrow_fg_bg.fgd) + e.window.setCursorPos(1, 1) + e.window.write(util.strrep("\x1e", wn_prec)) + e.window.setCursorPos(1, 3) + e.window.write(util.strrep("\x1f", wn_prec)) + if fr_prec > 0 then + e.window.setCursorPos(1, 1) + e.window.write(" " .. util.strrep("\x1e", fr_prec)) + e.window.setCursorPos(1, 3) + e.window.write(" " .. util.strrep("\x1f", fr_prec)) + end + + -- handle touch + ---@param event monitor_touch monitor touch event + function e.handle_touch(event) + -- only handle if on an increment or decrement arrow + if event.x ~= dec_point_x then + local idx = util.trinary(event.x > dec_point_x, event.x - 1, event.x) + if event.y == 1 then + -- increment + digits[idx] = digits[idx] + 1 + elseif event.y == 3 then + -- decrement + digits[idx] = digits[idx] - 1 + end + + -- update value + value = 0 + for i = 1, #digits do + if i <= wn_prec then + local pow = wn_prec - i + value = value + (digits[i] * (10 ^ pow)) + else + local pow = i - wn_prec + value = value + (digits[i] * (10 ^ -pow)) + end + end + end + end + + -- get current value + ---@return number|integer + function e.get_value() return value end + + return e.get() +end + +return spinbox From 89437b2be939238578a41f27bd5a30f9d3b0a47d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 11 Jun 2022 17:06:32 -0400 Subject: [PATCH 282/587] #63 cleanup and assertions --- graphics/elements/button_push.lua | 5 ++++- graphics/elements/button_switch.lua | 6 +++++- graphics/elements/hbar.lua | 2 +- graphics/elements/indicator_data.lua | 13 +++++++++---- graphics/elements/indicator_icon.lua | 7 +++++-- graphics/elements/indicator_light.lua | 7 +++++-- graphics/elements/indicator_state.lua | 4 +++- graphics/elements/rectangle.lua | 10 +++++----- graphics/elements/spinbox_numeric.lua | 7 ++++--- graphics/elements/textbox.lua | 2 +- graphics/elements/tiling.lua | 4 +++- graphics/elements/vbar.lua | 2 +- 12 files changed, 46 insertions(+), 23 deletions(-) diff --git a/graphics/elements/button_push.lua b/graphics/elements/button_push.lua index a51e8f3..321e5a6 100644 --- a/graphics/elements/button_push.lua +++ b/graphics/elements/button_push.lua @@ -13,11 +13,14 @@ local element = require("graphics.element") ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field height? integer parent height if omitted ----@field fg_bg cpair foreground/background colors +---@field fg_bg? cpair foreground/background colors -- new push button ---@param args push_button_args local function push_button(args) + assert(type(args.text) == "string", "graphics.elements.button_push: text is a required field") + assert(type(args.callback) == "function", "graphics.elements.button_push: callback is a required field") + local text_width = string.len(args.text) args.width = math.max(text_width + 2, args.min_width) diff --git a/graphics/elements/button_switch.lua b/graphics/elements/button_switch.lua index c17e553..6a06348 100644 --- a/graphics/elements/button_switch.lua +++ b/graphics/elements/button_switch.lua @@ -12,11 +12,15 @@ local element = require("graphics.element") ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field height? integer parent height if omitted ----@field fg_bg cpair foreground/background colors +---@field fg_bg? cpair foreground/background colors -- new switch button (latch high/low) ---@param args switch_button_args local function switch_button(args) + assert(type(args.text) == "string", "graphics.elements.button_switch: text is a required field") + assert(type(args.callback) == "function", "graphics.elements.button_switch: callback is a required field") + assert(type(args.active_fg_bg) == "table", "graphics.elements.button_switch: active_fg_bg is a required field") + -- button state (convert nil to false if missing) local state = args.default or false diff --git a/graphics/elements/hbar.lua b/graphics/elements/hbar.lua index bba4eb6..fad6a50 100644 --- a/graphics/elements/hbar.lua +++ b/graphics/elements/hbar.lua @@ -13,7 +13,7 @@ local element = require("graphics.element") ---@field width? integer parent width if omitted ---@field height? integer parent height if omitted ---@field gframe? graphics_frame frame instead of x/y/width/height ----@field fg_bg cpair foreground/background colors +---@field fg_bg? cpair foreground/background colors -- new horizontal bar ---@param args hbar_args diff --git a/graphics/elements/indicator_data.lua b/graphics/elements/indicator_data.lua index 4f0655a..97e2667 100644 --- a/graphics/elements/indicator_data.lua +++ b/graphics/elements/indicator_data.lua @@ -9,16 +9,21 @@ local element = require("graphics.element") ---@field unit? string indicator unit ---@field format string data format (lua string format) ---@field label_unit_colors? cpair label foreground color (a), unit foreground color (b) ----@field default any default value +---@field initial_value any default value ---@field parent graphics_element ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field width integer length ----@field fg_bg cpair foreground/background colors +---@field fg_bg? cpair foreground/background colors -- new data indicator ---@param args data_indicator_args local function data_indicator(args) + assert(type(args.label) == "string", "graphics.elements.indicator_data: label is a required field") + assert(type(args.format) == "string", "graphics.elements.indicator_data: format is a required field") + assert(args.initial_value ~= nil, "graphics.elements.indicator_data: initial_value is a required field") + assert(util.is_int(args.width), "graphics.elements.indicator_data: width is a required field") + -- create new graphics element base object local e = element.new(args) @@ -28,7 +33,7 @@ local function data_indicator(args) end -- write label - e.setCursorPos(1, 1) + e.window.setCursorPos(1, 1) e.window.write(args.label) local data_start = string.len(args.label) + 2 @@ -53,7 +58,7 @@ local function data_indicator(args) end -- initial value draw - e.on_update(args.default) + e.on_update(args.initial_value) return e.get() end diff --git a/graphics/elements/indicator_icon.lua b/graphics/elements/indicator_icon.lua index fc06a29..6bcf326 100644 --- a/graphics/elements/indicator_icon.lua +++ b/graphics/elements/indicator_icon.lua @@ -16,11 +16,14 @@ local element = require("graphics.element") ---@field parent graphics_element ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ----@field fg_bg cpair foreground/background colors +---@field fg_bg? cpair foreground/background colors -- new icon indicator ---@param args icon_indicator_args local function icon_indicator(args) + assert(type(args.label) == "string", "graphics.elements.indicator_icon: label is a required field") + assert(type(args.states) == "table", "graphics.elements.indicator_icon: states is a required field") + -- determine width args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 4 @@ -40,7 +43,7 @@ local function icon_indicator(args) end -- write label and initial indicator light - e.setCursorPos(5, 1) + e.window.setCursorPos(5, 1) e.window.write(args.label) -- on state change diff --git a/graphics/elements/indicator_light.lua b/graphics/elements/indicator_light.lua index 9192099..8cf7d14 100644 --- a/graphics/elements/indicator_light.lua +++ b/graphics/elements/indicator_light.lua @@ -11,11 +11,14 @@ local element = require("graphics.element") ---@field parent graphics_element ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ----@field fg_bg cpair foreground/background colors +---@field fg_bg? cpair foreground/background colors -- new indicator light ---@param args indicator_light_args local function indicator_light(args) + assert(type(args.label) == "string", "graphics.elements.indicator_light: label is a required field") + assert(type(args.colors) == "table", "graphics.elements.indicator_light: colors is a required field") + -- determine width args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 3 @@ -27,7 +30,7 @@ local function indicator_light(args) local off_blit = util.strrep(args.colors.blit_b, 2) -- write label and initial indicator light - e.setCursorPos(1, 1) + e.window.setCursorPos(1, 1) e.window.blit(" ", "000", off_blit .. e.fg_bg.blit_bkg) e.window.write(args.label) diff --git a/graphics/elements/indicator_state.lua b/graphics/elements/indicator_state.lua index 422259f..722f633 100644 --- a/graphics/elements/indicator_state.lua +++ b/graphics/elements/indicator_state.lua @@ -16,11 +16,13 @@ local element = require("graphics.element") ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field height? integer 1 if omitted, must be an odd number ----@field fg_bg cpair foreground/background colors +---@field fg_bg? cpair foreground/background colors -- new state indicator ---@param args state_indicator_args local function state_indicator(args) + assert(type(args.states) == "table", "graphics.elements.indicator_state: states is a required field") + -- determine height if util.is_int(args.height) then assert(args.height % 2 == 1, "graphics.elements.indicator_state: height should be an odd number") diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua index a8c65b1..2ea84ea 100644 --- a/graphics/elements/rectangle.lua +++ b/graphics/elements/rectangle.lua @@ -12,7 +12,7 @@ local element = require("graphics.element") ---@field width? integer parent width if omitted ---@field height? integer parent height if omitted ---@field gframe? graphics_frame frame instead of x/y/width/height ----@field fg_bg cpair foreground/background colors +---@field fg_bg? cpair foreground/background colors -- new rectangle ---@param args rectangle_args @@ -23,7 +23,7 @@ local function rectangle(args) -- draw bordered box if requested -- element constructor will have drawn basic colored rectangle regardless if args.border ~= nil then - e.setCursorPos(1, 1) + e.window.setCursorPos(1, 1) local border_width_v = args.border.width local border_width_h = util.trinary(args.border.even, args.border.width * 2, args.border.width) @@ -54,11 +54,11 @@ local function rectangle(args) -- draw rectangle with borders for y = 1, e.frame.h do - e.setCursorPos(1, y) + e.window.setCursorPos(1, y) if y <= border_width_v or y > (e.frame.h - border_width_v) then - e.blit(spaces, blit_fg, blit_bg_top_bot) + e.window.blit(spaces, blit_fg, blit_bg_top_bot) else - e.blit(spaces, blit_fg, blit_bg_sides) + e.window.blit(spaces, blit_fg, blit_bg_sides) end end end diff --git a/graphics/elements/spinbox_numeric.lua b/graphics/elements/spinbox_numeric.lua index d77c3ea..841bf1f 100644 --- a/graphics/elements/spinbox_numeric.lua +++ b/graphics/elements/spinbox_numeric.lua @@ -11,7 +11,7 @@ local util = require("scada-common.util") ---@field parent graphics_element ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ----@field fg_bg cpair foreground/background colors +---@field fg_bg? cpair foreground/background colors -- new spinbox control ---@param args spinbox_args @@ -26,6 +26,8 @@ local function spinbox(args) assert(util.is_int(wn_prec), "graphics.element.spinbox_numeric: whole number precision must be an integer") assert(util.is_int(fr_prec), "graphics.element.spinbox_numeric: fractional precision must be an integer") + assert(type(args.arrow_fg_bg) == "table", "graphics.element.spinbox_numeric: arrow_fg_bg is a required field") + local initial_str = util.sprintf("%" .. wn_prec .. "." .. fr_prec .. "f", value) ---@diagnostic disable-next-line: discard-returns @@ -69,11 +71,10 @@ local function spinbox(args) -- update value value = 0 for i = 1, #digits do + local pow = math.abs(wn_prec - i) if i <= wn_prec then - local pow = wn_prec - i value = value + (digits[i] * (10 ^ pow)) else - local pow = i - wn_prec value = value + (digits[i] * (10 ^ -pow)) end end diff --git a/graphics/elements/textbox.lua b/graphics/elements/textbox.lua index cd4a313..d5694a4 100644 --- a/graphics/elements/textbox.lua +++ b/graphics/elements/textbox.lua @@ -21,7 +21,7 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN -- new text box ---@param args textbox_args local function textbox(args) - assert(args.text ~= nil, "graphics.elements.textbox: empty text box") + assert(type(args.text) == "string", "graphics.elements.textbox: text is a required field") -- create new graphics element base object local e = element.new(args) diff --git a/graphics/elements/tiling.lua b/graphics/elements/tiling.lua index 8395068..c046670 100644 --- a/graphics/elements/tiling.lua +++ b/graphics/elements/tiling.lua @@ -14,11 +14,13 @@ local element = require("graphics.element") ---@field width? integer parent width if omitted ---@field height? integer parent height if omitted ---@field gframe? graphics_frame frame instead of x/y/width/height ----@field fg_bg cpair foreground/background colors +---@field fg_bg? cpair foreground/background colors -- new tiling box ---@param args tiling_args local function tiling(args) + assert(type(args.fill_c) == "table", "graphics.elements.tiling: fill_c is a required field") + -- create new graphics element base object local e = element.new(args) diff --git a/graphics/elements/vbar.lua b/graphics/elements/vbar.lua index c140c2e..ccd259a 100644 --- a/graphics/elements/vbar.lua +++ b/graphics/elements/vbar.lua @@ -11,7 +11,7 @@ local element = require("graphics.element") ---@field width? integer parent width if omitted ---@field height? integer parent height if omitted ---@field gframe? graphics_frame frame instead of x/y/width/height ----@field fg_bg cpair foreground/background colors +---@field fg_bg? cpair foreground/background colors -- new vertical bar ---@param args vbar_args From 7dbc5594b05ec8acf6829ff43635602de2ca90e4 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 11 Jun 2022 17:09:14 -0400 Subject: [PATCH 283/587] #63 div graphics element --- graphics/elements/div.lua | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 graphics/elements/div.lua diff --git a/graphics/elements/div.lua b/graphics/elements/div.lua new file mode 100644 index 0000000..cb56aa0 --- /dev/null +++ b/graphics/elements/div.lua @@ -0,0 +1,21 @@ +-- Div (Division, like in HTML) Graphics Element + +local element = require("graphics.element") + +---@class div_args +---@field parent graphics_element +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field width? integer parent width if omitted +---@field height? integer parent height if omitted +---@field gframe? graphics_frame frame instead of x/y/width/height +---@field fg_bg? cpair foreground/background colors + +-- new div element +---@param args div_args +local function div(args) + -- create new graphics element base object + return element.new(args).get() +end + +return div From 3593493c9843259a2608cbdb5842321cd4f7407f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 11 Jun 2022 17:58:29 -0400 Subject: [PATCH 284/587] #62 basic start of the UI --- coordinator/renderer.lua | 18 ++++++++++++++---- coordinator/startup.lua | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 4ed6358..9c29c4b 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -1,8 +1,10 @@ local log = require("scada-common.log") local util = require("scada-common.util") +local core = require("graphics.core") + local displaybox = require("graphics.elements.displaybox") -local structs = require("graphics.structs") +local textbox = require("graphics.elements.textbox") local renderer = {} @@ -62,12 +64,14 @@ end -- start the coordinator GUI function renderer.start_ui() - local palette = structs.graphics.cpair(gconf.root.fgd, gconf.root.bkg) + local palette = core.graphics.cpair(gconf.root.fgd, gconf.root.bkg) - ui.main_box = displaybox{window = engine.monitors.primary, fg_bg = palette} + ui.main_box = displaybox{window=engine.monitors.primary,fg_bg=palette} + + textbox{parent=ui.main_box,text="Nuclear Generation Facility SCADA Coordinator",alignment=core.graphics.TEXT_ALIGN.CENTER,height=1,fg_bg=core.graphics.cpair(colors.white,colors.gray)} for _, monitor in pairs(engine.monitors.unit_displays) do - table.insert(ui.unit_boxes, displaybox{window = engine.monitors.primary, fg_bg = palette}) + table.insert(ui.unit_boxes, displaybox{window=monitor,fg_bg=palette}) end end @@ -76,6 +80,12 @@ function renderer.close_ui() -- clear root UI elements ui.main_box = nil ui.unit_boxes = {} + + -- reset displays + renderer.reset() + + -- re-draw dmesg + engine.dmesg_window.redraw() end return renderer diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 8b79745..3b67dea 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -12,7 +12,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.1.5" +local COORDINATOR_VERSION = "alpha-v0.1.6" local print = util.print local println = util.println From 13513a9ce6bf01672074d4b189f980bf58d3675f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 14 Jun 2022 12:02:42 -0400 Subject: [PATCH 285/587] #62 graphics layouts --- coordinator/renderer.lua | 32 ++++--------- coordinator/{util => ui}/dialog.lua | 0 coordinator/ui/main_layout.lua | 21 +++++++++ coordinator/ui/style.lua | 11 +++++ coordinator/ui/unit_layout.lua | 21 +++++++++ graphics/core.lua | 4 ++ graphics/layout.lua | 71 +++++++++++++++++++++++++++++ 7 files changed, 138 insertions(+), 22 deletions(-) rename coordinator/{util => ui}/dialog.lua (100%) create mode 100644 coordinator/ui/main_layout.lua create mode 100644 coordinator/ui/style.lua create mode 100644 coordinator/ui/unit_layout.lua create mode 100644 graphics/layout.lua diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 9c29c4b..0948e67 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -3,29 +3,21 @@ local util = require("scada-common.util") local core = require("graphics.core") -local displaybox = require("graphics.elements.displaybox") -local textbox = require("graphics.elements.textbox") +local main_layout = require("coordinator.ui.main_layout") +local unit_layout = require("coordinator.ui.unit_layout") local renderer = {} -local gconf = { - -- root boxes - root = { - fgd = colors.black, - bkg = colors.lightGray - } -} - -- render engine local engine = { monitors = nil, dmesg_window = nil } --- UI elements +-- UI layouts local ui = { - main_box = nil, - unit_boxes = {} + main_layout = nil, + unit_layouts = {} } -- reset a display to the "default", but set text scale to 0.5 @@ -64,22 +56,18 @@ end -- start the coordinator GUI function renderer.start_ui() - local palette = core.graphics.cpair(gconf.root.fgd, gconf.root.bkg) + ui.main_layout = main_layout(engine.monitors.primary) - ui.main_box = displaybox{window=engine.monitors.primary,fg_bg=palette} - - textbox{parent=ui.main_box,text="Nuclear Generation Facility SCADA Coordinator",alignment=core.graphics.TEXT_ALIGN.CENTER,height=1,fg_bg=core.graphics.cpair(colors.white,colors.gray)} - - for _, monitor in pairs(engine.monitors.unit_displays) do - table.insert(ui.unit_boxes, displaybox{window=monitor,fg_bg=palette}) + for id, monitor in pairs(engine.monitors.unit_displays) do + table.insert(ui.unit_layouts, unit_layout(monitor, id)) end end -- close out the UI function renderer.close_ui() -- clear root UI elements - ui.main_box = nil - ui.unit_boxes = {} + ui.main_layout = nil + ui.unit_layouts = {} -- reset displays renderer.reset() diff --git a/coordinator/util/dialog.lua b/coordinator/ui/dialog.lua similarity index 100% rename from coordinator/util/dialog.lua rename to coordinator/ui/dialog.lua diff --git a/coordinator/ui/main_layout.lua b/coordinator/ui/main_layout.lua new file mode 100644 index 0000000..96d0a45 --- /dev/null +++ b/coordinator/ui/main_layout.lua @@ -0,0 +1,21 @@ +-- +-- Main SCADA Coordinator GUI +-- + +local core = require("graphics.core") +local layout = require("graphics.layout") + +local style = require("coordinator.ui.style") + +local displaybox = require("graphics.elements.displaybox") +local textbox = require("graphics.elements.textbox") + +local function init(monitor) + local main = layout.create(monitor, displaybox{window=monitor,fg_bg=style.root}) + + textbox{parent=main,text="Nuclear Generation Facility SCADA Coordinator",alignment=core.graphics.TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + + return main +end + +return init diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua new file mode 100644 index 0000000..a148130 --- /dev/null +++ b/coordinator/ui/style.lua @@ -0,0 +1,11 @@ + +local core = require("graphics.core") + +local style = {} + +-- MAIN LAYOUT -- + +style.root = core.graphics.cpair(colors.black, colors.lightGray) +style.header = core.graphics.cpair(colors.white,colors.gray) + +return style diff --git a/coordinator/ui/unit_layout.lua b/coordinator/ui/unit_layout.lua new file mode 100644 index 0000000..45884ca --- /dev/null +++ b/coordinator/ui/unit_layout.lua @@ -0,0 +1,21 @@ +-- +-- Reactor Unit SCADA Coordinator GUI +-- + +local core = require("graphics.core") +local layout = require("graphics.layout") + +local style = require("coordinator.ui.style") + +local displaybox = require("graphics.elements.displaybox") +local textbox = require("graphics.elements.textbox") + +local function init(monitor, id) + local main = layout.create(monitor, displaybox{window=monitor,fg_bg=style.root}) + + textbox{parent=main,text="Reactor Unit #" .. id,alignment=core.graphics.TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + + return main +end + +return init diff --git a/graphics/core.lua b/graphics/core.lua index 91e7915..4bd0d9e 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -1,3 +1,7 @@ +-- +-- Graphics Core Functions and Objects +-- + local core = {} local events = {} diff --git a/graphics/layout.lua b/graphics/layout.lua new file mode 100644 index 0000000..fe91cdd --- /dev/null +++ b/graphics/layout.lua @@ -0,0 +1,71 @@ +local core = require("graphics.core") +local util = require("scada-common.util") + +local displaybox = require("graphics.elements.displaybox") + +local layout = {} + +---@class stem +---@field element graphics_element +---@field children table + +function layout.create(window, default_fg_bg) + local self = { + root = displaybox{window=window,fg_bg=default_fg_bg}, + tree = {} + } + + -- recursive function to search layout tree for an element + ---@param id string element ID to look for + ---@param tree table tree to search in + ---@return stem|nil + local function lookup(id, tree) + for key, stem in pairs(tree) do + if key == id then + return stem + else + stem = lookup(id, stem.children) + if stem ~= nil then return stem end + end + end + + return nil + end + + ---@class layout + local public = {} + + -- insert a new element + ---@param parent_id string|nil parent or nil for root + ---@param id string element ID + ---@param element graphics_element + function public.insert_at(parent_id, id, element) + if parent_id == nil then + self.tree[id] = { element = element, children = {} } + else + local parent = lookup(parent_id, self.tree) + if parent ~= nil then + parent.children[id] = { element = element, children = {} } + end + end + end + + -- get an element by ID + ---@param id string element ID + ---@return graphics_element|nil + function public.get_element_by_id(id) + local elem = lookup(id, self.tree) +---@diagnostic disable-next-line: need-check-nil + return util.trinary(elem == nil, nil, elem.element) + end + + -- get the root element + ---@return graphics_element + function public.get_root() + return self.root + end + + return public +end + +return layout From 2e4a533148b63d655a5cd03a8c86a107c2551b76 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 14 Jun 2022 12:05:49 -0400 Subject: [PATCH 286/587] comments --- graphics/element.lua | 4 ++++ graphics/layout.lua | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/graphics/element.lua b/graphics/element.lua index 1ed7afd..cfaba05 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -1,3 +1,7 @@ +-- +-- Generic Graphics Element +-- + local core = require("graphics.core") local element = {} diff --git a/graphics/layout.lua b/graphics/layout.lua index fe91cdd..ebbcfd3 100644 --- a/graphics/layout.lua +++ b/graphics/layout.lua @@ -1,3 +1,7 @@ +-- +-- Graphics View Layout +-- + local core = require("graphics.core") local util = require("scada-common.util") From b628472d8145106fdaf9683e4e65c35b5d1f9822 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 15 Jun 2022 15:35:34 -0400 Subject: [PATCH 287/587] #74 work on coordinator comms --- coordinator/coordinator.lua | 166 +++++++++++++++++++++++++++++++++++- scada-common/comms.lua | 18 +++- 2 files changed, 179 insertions(+), 5 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 76c9824..5dbb39e 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -6,13 +6,19 @@ local util = require("scada-common.util") local dialog = require("coordinator.util.dialog") +local coordinator = {} + local print = util.print local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts -local coordinator = {} +local PROTOCOLS = comms.PROTOCOLS +local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES +local COORD_TYPES = comms.COORD_TYPES +-- request the user to select a monitor +---@param names table available monitors local function ask_monitor(names) println("available monitors:") for i = 1, #names do @@ -30,6 +36,8 @@ local function ask_monitor(names) return iface end +-- configure monitor layout +---@param num_units integer number of units expected function coordinator.configure_monitors(num_units) ---@class monitors_struct local monitors = { @@ -135,10 +143,162 @@ function coordinator.configure_monitors(num_units) end -- coordinator communications -function coordinator.coord_comms() +---@param conn_watchdog watchdog +function coordinator.coord_comms(version, num_reactors, modem, sv_port, sv_listen, api_listen, conn_watchdog) local self = { - reactor_struct_cache = nil + seq_num = 0, + r_seq_num = nil, + modem = modem, + connected = false } + + ---@class coord_comms + local public = {} + + -- PRIVATE FUNCTIONS -- + + -- open all channels + local function _open_channels() + if not self.modem.isOpen(sv_listen) then + self.modem.open(sv_listen) + end + + if not self.modem.isOpen(api_listen) then + self.modem.open(api_listen) + end + end + + -- open at construct time + _open_channels() + + -- send a coordinator packet + ---@param msg_type COORD_TYPES + ---@param msg string + local function _send(msg_type, msg) + local s_pkt = comms.scada_packet() + local c_pkt = comms.coord_packet() + + c_pkt.make(msg_type, msg) + s_pkt.make(self.seq_num, PROTOCOLS.COORD_DATA, c_pkt.raw_sendable()) + + self.modem.transmit(sv_port, sv_listen, s_pkt.raw_sendable()) + self.seq_num = self.seq_num + 1 + end + + -- send a SCADA management packet + ---@param msg_type SCADA_MGMT_TYPES + ---@param msg string + local function _send_mgmt(msg_type, msg) + local s_pkt = comms.scada_packet() + local m_pkt = comms.mgmt_packet() + + m_pkt.make(msg_type, msg) + s_pkt.make(self.seq_num, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) + + self.modem.transmit(sv_port, sv_listen, s_pkt.raw_sendable()) + self.seq_num = self.seq_num + 1 + end + + -- PUBLIC FUNCTIONS -- + + -- reconnect a newly connected modem + ---@param modem table +---@diagnostic disable-next-line: redefined-local + function public.reconnect_modem(modem) + self.modem = modem + _open_channels() + end + + -- parse a packet + ---@param side string + ---@param sender integer + ---@param reply_to integer + ---@param message any + ---@param distance integer + ---@return mgmt_frame|coord_frame|capi_frame|nil packet + function public.parse_packet(side, sender, reply_to, message, distance) + local pkt = nil + local s_pkt = comms.scada_packet() + + -- parse packet as generic SCADA packet + s_pkt.receive(side, sender, reply_to, message, distance) + + if s_pkt.is_valid() then + -- get as SCADA management packet + if s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then + local mgmt_pkt = comms.mgmt_packet() + if mgmt_pkt.decode(s_pkt) then + pkt = mgmt_pkt.get() + end + -- get as coordinator packet + elseif s_pkt.protocol() == PROTOCOLS.COORD_DATA then + local coord_pkt = comms.coord_packet() + if coord_pkt.decode(s_pkt) then + pkt = coord_pkt.get() + end + -- get as coordinator API packet + elseif s_pkt.protocol() == PROTOCOLS.COORD_API then + local capi_pkt = comms.capi_packet() + if capi_pkt.decode(s_pkt) then + pkt = capi_pkt.get() + end + else + log.debug("attempted parse of illegal packet type " .. s_pkt.protocol(), true) + end + end + + return pkt + end + + -- handle a packet + ---@param packet mgmt_frame|coord_frame|capi_frame + function public.handle_packet(packet) + if packet ~= nil then + -- check sequence number + if self.r_seq_num == nil then + self.r_seq_num = packet.scada_frame.seq_num() + elseif self.connected and self.r_seq_num >= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + return + else + self.r_seq_num = packet.scada_frame.seq_num() + end + + -- feed watchdog on valid sequence number + conn_watchdog.feed() + + local protocol = packet.scada_frame.protocol() + + -- handle packet + if protocol == PROTOCOLS.COORD_DATA then + if packet.type == COORD_TYPES.ESTABLISH then + elseif packet.type == COORD_TYPES.QUERY_UNIT then + elseif packet.type == COORD_TYPES.QUERY_FACILITY then + elseif packet.type == COORD_TYPES.COMMAND_UNIT then + elseif packet.type == COORD_TYPES.ALARM then + else + log.warning("received unknown coordinator data packet type " .. packet.type) + end + elseif protocol == PROTOCOLS.COORD_API then + elseif protocol == PROTOCOLS.SCADA_MGMT then + if packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + -- keep alive response received + elseif packet.type == SCADA_MGMT_TYPES.CLOSE then + -- handle session close + conn_watchdog.cancel() + println_ts("server connection closed by remote host") + log.warning("server connection closed by remote host") + else + log.warning("received unknown SCADA_MGMT packet type " .. packet.type) + end + else + -- should be unreachable assuming packet is from parse_packet() + log.error("illegal packet type " .. protocol, true) + end + end + end + + return public end return coordinator diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 8422bd4..e834d1e 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -48,6 +48,15 @@ local SCADA_MGMT_TYPES = { REMOTE_LINKED = 3 -- remote device linked } +---@alias COORD_TYPES integer +local COORD_TYPES = { + ESTABLISH = 0, -- initial greeting + QUERY_UNIT = 1, -- query the state of a unit + QUERY_FACILITY = 2, -- query general facility status + COMMAND_UNIT = 3, -- command a reactor unit + ALARM = 4 -- alarm signaling +} + ---@alias RTU_UNIT_TYPES integer local RTU_UNIT_TYPES = { REDSTONE = 0, -- redstone I/O @@ -66,6 +75,7 @@ comms.PROTOCOLS = PROTOCOLS comms.RPLC_TYPES = RPLC_TYPES comms.RPLC_LINKING = RPLC_LINKING comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES +comms.COORD_TYPES = COORD_TYPES comms.RTU_UNIT_TYPES = RTU_UNIT_TYPES -- generic SCADA packet object @@ -436,9 +446,13 @@ function comms.coord_packet() ---@class coord_packet local public = {} + -- check that type is known local function _coord_type_valid() - -- @todo - return false + return self.type == COORD_TYPES.ESTABLISH or + self.type == COORD_TYPES.QUERY_UNIT or + self.type == COORD_TYPES.QUERY_FACILITY or + self.type == COORD_TYPES.COMMAND_UNIT or + self.type == COORD_TYPES.ALARM end -- make a coordinator packet From 971657c3d2c5356116860cc1b82aeac00b005a56 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 16 Jun 2022 11:19:32 -0400 Subject: [PATCH 288/587] graphics library refactoring and bugfixes --- graphics/element.lua | 26 +++++++++++++---- .../push_button.lua} | 7 +++-- .../{ => controls}/spinbox_numeric.lua | 4 +-- .../switch_button.lua} | 9 ++++-- .../data.lua} | 29 ++++++++++--------- graphics/elements/{ => indicators}/hbar.lua | 19 +++++++----- .../icon.lua} | 15 ++++++---- .../light.lua} | 7 +++-- .../state.lua} | 18 +++++++----- graphics/elements/{ => indicators}/vbar.lua | 0 10 files changed, 85 insertions(+), 49 deletions(-) rename graphics/elements/{button_push.lua => controls/push_button.lua} (90%) rename graphics/elements/{ => controls}/spinbox_numeric.lua (92%) rename graphics/elements/{button_switch.lua => controls/switch_button.lua} (88%) rename graphics/elements/{indicator_data.lua => indicators/data.lua} (68%) rename graphics/elements/{ => indicators}/hbar.lua (82%) rename graphics/elements/{indicator_icon.lua => indicators/icon.lua} (87%) rename graphics/elements/{indicator_light.lua => indicators/light.lua} (92%) rename graphics/elements/{indicator_state.lua => indicators/state.lua} (79%) rename graphics/elements/{ => indicators}/vbar.lua (100%) diff --git a/graphics/element.lua b/graphics/element.lua index cfaba05..9e78faf 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -3,6 +3,8 @@ -- local core = require("graphics.core") +local log = require("scada-common.log") +local util = require("scada-common.util") local element = {} @@ -20,11 +22,15 @@ local element = {} ---@param args graphics_args_generic arguments function element.new(args) local self = { + elem_type = debug.getinfo(2).name, p_window = nil, ---@type table position = { x = 1, y = 1 }, bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1} } + ---@fixme remove debug + log.dmesg("new " .. self.elem_type) + local protected = { window = nil, ---@type table fg_bg = core.graphics.cpair(colors.white, colors.black), @@ -34,7 +40,10 @@ function element.new(args) -- SETUP -- -- get the parent window - self.p_window = args.window or args.parent.window() + self.p_window = args.window + if self.p_window == nil and args.parent ~= nil then + self.p_window = args.parent.window() + end -- check window assert(self.p_window, "graphics.element: no parent window provided") @@ -63,14 +72,18 @@ function element.new(args) local f = protected.frame protected.window = window.create(self.p_window, f.x, f.y, f.w, f.h, true) - -- init display box + -- init colors if args.fg_bg ~= nil then - protected.window.setBackgroundColor(args.fg_bg.bkg) - protected.window.setTextColor(args.fg_bg.fgd) - protected.window.clear() protected.fg_bg = args.fg_bg + elseif args.parent ~= nil then + protected.fg_bg = args.parent.get_fg_bg() end + -- set colors + protected.window.setBackgroundColor(protected.fg_bg.bkg) + protected.window.setTextColor(protected.fg_bg.fgd) + protected.window.clear() + -- record position self.position.x, self.position.y = protected.window.getPosition() @@ -107,6 +120,9 @@ function element.new(args) -- get the window object function public.window() return protected.window end + -- get the foreground/background colors + function public.get_fg_bg() return protected.fg_bg end + -- handle a monitor touch ---@param event monitor_touch monitor touch event function public.handle_touch(event) diff --git a/graphics/elements/button_push.lua b/graphics/elements/controls/push_button.lua similarity index 90% rename from graphics/elements/button_push.lua rename to graphics/elements/controls/push_button.lua index 321e5a6..9968240 100644 --- a/graphics/elements/button_push.lua +++ b/graphics/elements/controls/push_button.lua @@ -18,8 +18,11 @@ local element = require("graphics.element") -- new push button ---@param args push_button_args local function push_button(args) - assert(type(args.text) == "string", "graphics.elements.button_push: text is a required field") - assert(type(args.callback) == "function", "graphics.elements.button_push: callback is a required field") + assert(type(args.text) == "string", "graphics.elements.controls.push_button: text is a required field") + assert(type(args.callback) == "function", "graphics.elements.controls.push_button: callback is a required field") + + -- single line + args.height = 1 local text_width = string.len(args.text) args.width = math.max(text_width + 2, args.min_width) diff --git a/graphics/elements/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua similarity index 92% rename from graphics/elements/spinbox_numeric.lua rename to graphics/elements/controls/spinbox_numeric.lua index 841bf1f..92946d8 100644 --- a/graphics/elements/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -23,8 +23,8 @@ local function spinbox(args) local fr_prec = args.fractional_precision local dec_point_x = args.whole_num_precision + 1 - assert(util.is_int(wn_prec), "graphics.element.spinbox_numeric: whole number precision must be an integer") - assert(util.is_int(fr_prec), "graphics.element.spinbox_numeric: fractional precision must be an integer") + assert(util.is_int(wn_prec), "graphics.element.controls.spinbox_numeric: whole number precision must be an integer") + assert(util.is_int(fr_prec), "graphics.element.controls.spinbox_numeric: fractional precision must be an integer") assert(type(args.arrow_fg_bg) == "table", "graphics.element.spinbox_numeric: arrow_fg_bg is a required field") diff --git a/graphics/elements/button_switch.lua b/graphics/elements/controls/switch_button.lua similarity index 88% rename from graphics/elements/button_switch.lua rename to graphics/elements/controls/switch_button.lua index 6a06348..03c25bb 100644 --- a/graphics/elements/button_switch.lua +++ b/graphics/elements/controls/switch_button.lua @@ -17,9 +17,12 @@ local element = require("graphics.element") -- new switch button (latch high/low) ---@param args switch_button_args local function switch_button(args) - assert(type(args.text) == "string", "graphics.elements.button_switch: text is a required field") - assert(type(args.callback) == "function", "graphics.elements.button_switch: callback is a required field") - assert(type(args.active_fg_bg) == "table", "graphics.elements.button_switch: active_fg_bg is a required field") + assert(type(args.text) == "string", "graphics.elements.controls.switch_button: text is a required field") + assert(type(args.callback) == "function", "graphics.elements.controls.switch_button: callback is a required field") + assert(type(args.active_fg_bg) == "table", "graphics.elements.controls.switch_button: active_fg_bg is a required field") + + -- single line + args.height = 1 -- button state (convert nil to false if missing) local state = args.default or false diff --git a/graphics/elements/indicator_data.lua b/graphics/elements/indicators/data.lua similarity index 68% rename from graphics/elements/indicator_data.lua rename to graphics/elements/indicators/data.lua index 97e2667..d33cb69 100644 --- a/graphics/elements/indicator_data.lua +++ b/graphics/elements/indicators/data.lua @@ -8,8 +8,8 @@ local element = require("graphics.element") ---@field label string indicator label ---@field unit? string indicator unit ---@field format string data format (lua string format) ----@field label_unit_colors? cpair label foreground color (a), unit foreground color (b) ----@field initial_value any default value +---@field lu_colors? cpair label foreground color (a), unit foreground color (b) +---@field value any default value ---@field parent graphics_element ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted @@ -18,18 +18,21 @@ local element = require("graphics.element") -- new data indicator ---@param args data_indicator_args -local function data_indicator(args) - assert(type(args.label) == "string", "graphics.elements.indicator_data: label is a required field") - assert(type(args.format) == "string", "graphics.elements.indicator_data: format is a required field") - assert(args.initial_value ~= nil, "graphics.elements.indicator_data: initial_value is a required field") - assert(util.is_int(args.width), "graphics.elements.indicator_data: width is a required field") +local function data(args) + assert(type(args.label) == "string", "graphics.elements.indicators.data: label is a required field") + assert(type(args.format) == "string", "graphics.elements.indicators.data: format is a required field") + assert(args.value ~= nil, "graphics.elements.indicators.data: value is a required field") + assert(util.is_int(args.width), "graphics.elements.indicators.data: width is a required field") + + -- single line + args.height = 1 -- create new graphics element base object local e = element.new(args) -- label color - if args.label_unit_colors ~= nil then - e.window.setTextColor(args.label_unit_colors.color_a) + if args.lu_colors ~= nil then + e.window.setTextColor(args.lu_colors.color_a) end -- write label @@ -50,17 +53,17 @@ local function data_indicator(args) -- write label if args.unit ~= nil then - if args.label_unit_colors ~= nil then - e.window.setTextColor(args.label_unit_colors.color_b) + if args.lu_colors ~= nil then + e.window.setTextColor(args.lu_colors.color_b) end e.window.write(" " .. args.unit) end end -- initial value draw - e.on_update(args.initial_value) + e.on_update(args.value) return e.get() end -return data_indicator +return data diff --git a/graphics/elements/hbar.lua b/graphics/elements/indicators/hbar.lua similarity index 82% rename from graphics/elements/hbar.lua rename to graphics/elements/indicators/hbar.lua index fad6a50..d620df0 100644 --- a/graphics/elements/hbar.lua +++ b/graphics/elements/indicators/hbar.lua @@ -19,7 +19,6 @@ local element = require("graphics.element") ---@param args hbar_args local function hbar(args) -- properties/state - local bkg = "" local last_num_bars = -1 -- create new graphics element base object @@ -31,12 +30,10 @@ local function hbar(args) assert(bar_width > 0, "graphics.elements.hbar: too small for bar") -- determine bar colors + ---@fixme this doesnt work as intended local bar_bkg = util.trinary(args.bar_fg_bg == nil, e.fg_bg.blit_bkg, args.bar_fg_bg.blit_bkg) local bar_fgd = util.trinary(args.bar_fg_bg == nil, e.fg_bg.blit_fgd, args.bar_fg_bg.blit_fgd) - -- set background blit string - bkg = util.strrep(args.bar_fg_bg.blit_bkg, bar_width) - -- handle data changes function e.on_update(fraction) -- enforce minimum and maximum @@ -47,37 +44,43 @@ local function hbar(args) end -- compute number of bars - local num_bars = util.round((fraction * 100) / (bar_width * 2)) + local num_bars = util.round(fraction * (bar_width * 2)) + util.print(num_bars) -- redraw bar if changed if num_bars ~= last_num_bars then last_num_bars = num_bars local fgd = "" + local bkg = "" local spaces = "" -- fill percentage for _ = 1, num_bars / 2 do spaces = spaces .. " " fgd = fgd .. bar_fgd + bkg = bkg .. bar_bkg end -- add fractional bar if needed if num_bars % 2 == 1 then spaces = spaces .. "\x95" - fgd = fgd .. bar_fgd + fgd = fgd .. bar_bkg + bkg = bkg .. bar_fgd end -- pad background - for _ = 1, bar_width - ((num_bars / 2) + num_bars % 2) do + for _ = 1, ((bar_width * 2) - num_bars) / 2 do spaces = spaces .. " " fgd = fgd .. bar_bkg + bkg = bkg .. bar_bkg end -- draw bar for y = 1, e.frame.h do e.window.setCursorPos(1, y) - e.window.blit(spaces, fgd, bkg) + -- intentionally swapped fgd/bkg since we use spaces as fill, but they are the opposite + e.window.blit(spaces, bkg, fgd) end end diff --git a/graphics/elements/indicator_icon.lua b/graphics/elements/indicators/icon.lua similarity index 87% rename from graphics/elements/indicator_icon.lua rename to graphics/elements/indicators/icon.lua index 6bcf326..d116f2b 100644 --- a/graphics/elements/indicator_icon.lua +++ b/graphics/elements/indicators/icon.lua @@ -11,7 +11,7 @@ local element = require("graphics.element") ---@class icon_indicator_args ---@field label string indicator label ---@field states table state color and symbol table ----@field default? integer default state, defaults to 1 +---@field value? integer default state, defaults to 1 ---@field min_label_width? integer label length if omitted ---@field parent graphics_element ---@field x? integer 1 if omitted @@ -20,9 +20,12 @@ local element = require("graphics.element") -- new icon indicator ---@param args icon_indicator_args -local function icon_indicator(args) - assert(type(args.label) == "string", "graphics.elements.indicator_icon: label is a required field") - assert(type(args.states) == "table", "graphics.elements.indicator_icon: states is a required field") +local function icon(args) + assert(type(args.label) == "string", "graphics.elements.indicators.icon: label is a required field") + assert(type(args.states) == "table", "graphics.elements.indicators.icon: states is a required field") + + -- single line + args.height = 1 -- determine width args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 4 @@ -55,9 +58,9 @@ local function icon_indicator(args) end -- initial icon draw - e.on_update(args.default or 1) + e.on_update(args.value or 1) return e.get() end -return icon_indicator +return icon diff --git a/graphics/elements/indicator_light.lua b/graphics/elements/indicators/light.lua similarity index 92% rename from graphics/elements/indicator_light.lua rename to graphics/elements/indicators/light.lua index 8cf7d14..6db28db 100644 --- a/graphics/elements/indicator_light.lua +++ b/graphics/elements/indicators/light.lua @@ -16,8 +16,11 @@ local element = require("graphics.element") -- new indicator light ---@param args indicator_light_args local function indicator_light(args) - assert(type(args.label) == "string", "graphics.elements.indicator_light: label is a required field") - assert(type(args.colors) == "table", "graphics.elements.indicator_light: colors is a required field") + assert(type(args.label) == "string", "graphics.elements.indicators.light: label is a required field") + assert(type(args.colors) == "table", "graphics.elements.indicators.light: colors is a required field") + + -- single line + args.height = 1 -- determine width args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 3 diff --git a/graphics/elements/indicator_state.lua b/graphics/elements/indicators/state.lua similarity index 79% rename from graphics/elements/indicator_state.lua rename to graphics/elements/indicators/state.lua index 722f633..0383fd8 100644 --- a/graphics/elements/indicator_state.lua +++ b/graphics/elements/indicators/state.lua @@ -10,7 +10,7 @@ local element = require("graphics.element") ---@class state_indicator_args ---@field states table state color and text table ----@field default? integer default state, defaults to 1 +---@field value? integer default state, defaults to 1 ---@field min_width? integer max state text length if omitted ---@field parent graphics_element ---@field x? integer 1 if omitted @@ -21,11 +21,11 @@ local element = require("graphics.element") -- new state indicator ---@param args state_indicator_args local function state_indicator(args) - assert(type(args.states) == "table", "graphics.elements.indicator_state: states is a required field") + assert(type(args.states) == "table", "graphics.elements.indicators.state: states is a required field") -- determine height if util.is_int(args.height) then - assert(args.height % 2 == 1, "graphics.elements.indicator_state: height should be an odd number") + assert(args.height % 2 == 1, "graphics.elements.indicators.state: height should be an odd number") else args.height = 1 end @@ -45,12 +45,14 @@ local function state_indicator(args) local len = string.len(state_def.text) local lpad = math.floor((args.width - len) / 2) - local rpad = len - lpad + local rpad = args.width - lpad + + local text = util.spaces(lpad) .. state_def.text .. util.spaces(rpad) table.insert(state_blit_cmds, { - text = util.spaces(lpad) .. state_def.text .. util.spaces(rpad), - fgd = util.strrep(state_def.color.blit_fgd, 3), - bkg = util.strrep(state_def.color.blit_bkg, 3) + text = text, + fgd = util.strrep(state_def.color.blit_fgd, string.len(text)), + bkg = util.strrep(state_def.color.blit_bkg, string.len(text)) }) end @@ -66,7 +68,7 @@ local function state_indicator(args) end -- initial draw - e.on_update(args.default or 1) + e.on_update(args.value or 1) return e.get() end diff --git a/graphics/elements/vbar.lua b/graphics/elements/indicators/vbar.lua similarity index 100% rename from graphics/elements/vbar.lua rename to graphics/elements/indicators/vbar.lua From 7f007e032dd72a14fe86a3a43ea15292412013af Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 16 Jun 2022 11:24:35 -0400 Subject: [PATCH 289/587] #62, #72 work on main layout, not using layout class, refactoring and bugfixes --- coordinator/renderer.lua | 14 ++-- coordinator/startup.lua | 6 +- coordinator/ui/components/unit_overview.lua | 43 ++++++++++++ coordinator/ui/layout/main_view.lua | 29 ++++++++ coordinator/ui/layout/unit_view.lua | 22 ++++++ coordinator/ui/main_layout.lua | 21 ------ coordinator/ui/style.lua | 28 +++++++- coordinator/ui/unit_layout.lua | 21 ------ graphics/layout.lua | 75 --------------------- 9 files changed, 133 insertions(+), 126 deletions(-) create mode 100644 coordinator/ui/components/unit_overview.lua create mode 100644 coordinator/ui/layout/main_view.lua create mode 100644 coordinator/ui/layout/unit_view.lua delete mode 100644 coordinator/ui/main_layout.lua delete mode 100644 coordinator/ui/unit_layout.lua delete mode 100644 graphics/layout.lua diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 0948e67..8a3df7b 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -3,8 +3,8 @@ local util = require("scada-common.util") local core = require("graphics.core") -local main_layout = require("coordinator.ui.main_layout") -local unit_layout = require("coordinator.ui.unit_layout") +local main_view = require("coordinator.ui.layout.main_view") +local unit_view = require("coordinator.ui.layout.unit_view") local renderer = {} @@ -56,10 +56,15 @@ end -- start the coordinator GUI function renderer.start_ui() - ui.main_layout = main_layout(engine.monitors.primary) + -- hide dmesg + engine.dmesg_window.setVisible(false) + -- show main view on main monitor + ui.main_layout = main_view(engine.monitors.primary) + + -- show unit views on unit displays for id, monitor in pairs(engine.monitors.unit_displays) do - table.insert(ui.unit_layouts, unit_layout(monitor, id)) + table.insert(ui.unit_layouts, unit_view(monitor, id)) end end @@ -73,6 +78,7 @@ function renderer.close_ui() renderer.reset() -- re-draw dmesg + engine.dmesg_window.setVisible(true) engine.dmesg_window.redraw() end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 3b67dea..6dee519 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -12,7 +12,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.1.6" +local COORDINATOR_VERSION = "alpha-v0.2.0" local print = util.print local println = util.println @@ -85,8 +85,8 @@ log.dmesg("wireless modem connected", "COMMS", colors.purple) log.dmesg("starting UI...", "GRAPHICS", colors.green) util.psleep(3) -local ui_ok = pcall(renderer.start_ui) +local ui_ok, message = pcall(renderer.start_ui) if not ui_ok then renderer.close_ui() - log.dmesg("UI draw failed", "GRAPHICS", colors.green) + log.dmesg("UI draw failed: " .. message, "GRAPHICS", colors.green) end diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua new file mode 100644 index 0000000..413a97a --- /dev/null +++ b/coordinator/ui/components/unit_overview.lua @@ -0,0 +1,43 @@ +local core = require("graphics.core") + +local style = require("coordinator.ui.style") + +local Div = require("graphics.elements.div") +local HorizontalBar = require("graphics.elements.indicators.hbar") +local DataIndicator = require("graphics.elements.indicators.data") +local StateIndicator = require("graphics.elements.indicators.state") +local Rectangle = require("graphics.elements.rectangle") +local TextBox = require("graphics.elements.textbox") + +local TEXT_ALIGN = core.graphics.TEXT_ALIGN + +local cpair = core.graphics.cpair +local border = core.graphics.border + +---@param parent graphics_element +local function make(parent, x, y, unit_id) + -- bounding box div + local root = Div{parent=parent,x=x,y=y,width=75,height=50} + + -- unit header message + TextBox{parent=root,text="Unit #" .. unit_id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + + -- reactor + local reactor = Rectangle{parent=root,border=border(1, colors.gray, false),width=30,height=10,x=1,y=3} + + local text_fg_bg = cpair(colors.black, colors.lightGray) + local lu_col = cpair(colors.gray, colors.gray) + + local status = StateIndicator{parent=reactor,x=9,y=2,states=style.reactor.states,value=1,min_width=14} + local core_temp = DataIndicator{parent=reactor,x=3,y=4,lu_colors=lu_col,label="Core: ",unit="K",format="%7.0f",value=295,width=26,fg_bg=text_fg_bg} + local heating_r = DataIndicator{parent=reactor,x=3,y=5,lu_colors=lu_col,label="Heating:",unit="mB/t",format="%7.0f",value=359999,width=26,fg_bg=text_fg_bg} + local burn_r = DataIndicator{parent=reactor,x=3,y=6,lu_colors=lu_col,label="Burn: ",unit="mB/t",format="%7.1f",value=40.1,width=26,fg_bg=text_fg_bg} + + local fuel = HorizontalBar{parent=root,x=34,y=4,show_percent=true,bar_fg_bg=cpair(colors.brown,colors.white),height=1,width=14} + local coolant = HorizontalBar{parent=root,x=34,y=5,show_percent=true,bar_fg_bg=cpair(colors.lightBlue,colors.white),height=1,width=14} + + fuel.update(0.85) + coolant.update(0.75) +end + +return make diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua new file mode 100644 index 0000000..7ebb1e4 --- /dev/null +++ b/coordinator/ui/layout/main_view.lua @@ -0,0 +1,29 @@ +-- +-- Main SCADA Coordinator GUI +-- + +local core = require("graphics.core") +local log = require("scada-common.log") + +local style = require("coordinator.ui.style") + +local DisplayBox = require("graphics.elements.displaybox") +local TextBox = require("graphics.elements.textbox") + +local unit_overview = require("coordinator.ui.components.unit_overview") + +local TEXT_ALIGN = core.graphics.TEXT_ALIGN + +local function init(monitor) + local main = DisplayBox{window=monitor,fg_bg=style.root} + + -- window header message + TextBox{parent=main,text="Nuclear Generation Facility SCADA Coordinator",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + + -- unit overviews + unit_overview(main, 5, 5, 1) + + return main +end + +return init diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua new file mode 100644 index 0000000..e82657b --- /dev/null +++ b/coordinator/ui/layout/unit_view.lua @@ -0,0 +1,22 @@ +-- +-- Reactor Unit SCADA Coordinator GUI +-- + +local core = require("graphics.core") + +local style = require("coordinator.ui.style") + +local DisplayBox = require("graphics.elements.displaybox") +local TextBox = require("graphics.elements.textbox") + +local TEXT_ALIGN = core.graphics.TEXT_ALIGN + +local function init(monitor, id) + local main = DisplayBox{window=monitor,fg_bg=style.root} + + TextBox{parent=main,text="Reactor Unit #" .. id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + + return main +end + +return init diff --git a/coordinator/ui/main_layout.lua b/coordinator/ui/main_layout.lua deleted file mode 100644 index 96d0a45..0000000 --- a/coordinator/ui/main_layout.lua +++ /dev/null @@ -1,21 +0,0 @@ --- --- Main SCADA Coordinator GUI --- - -local core = require("graphics.core") -local layout = require("graphics.layout") - -local style = require("coordinator.ui.style") - -local displaybox = require("graphics.elements.displaybox") -local textbox = require("graphics.elements.textbox") - -local function init(monitor) - local main = layout.create(monitor, displaybox{window=monitor,fg_bg=style.root}) - - textbox{parent=main,text="Nuclear Generation Facility SCADA Coordinator",alignment=core.graphics.TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} - - return main -end - -return init diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index a148130..04be985 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -3,9 +3,33 @@ local core = require("graphics.core") local style = {} +local cpair = core.graphics.cpair + -- MAIN LAYOUT -- -style.root = core.graphics.cpair(colors.black, colors.lightGray) -style.header = core.graphics.cpair(colors.white,colors.gray) +style.root = cpair(colors.black, colors.lightGray) +style.header = cpair(colors.white, colors.gray) + +style.reactor = { + -- reactor states + states = { + { + color = cpair(colors.black, colors.yellow), + text = "DISCONNECTED" + }, + { + color = cpair(colors.white, colors.gray), + text = "DISABLED" + }, + { + color = cpair(colors.black, colors.green), + text = "ACTIVE" + }, + { + color = cpair(colors.black, colors.red), + text = "SCRAM!" + } + } +} return style diff --git a/coordinator/ui/unit_layout.lua b/coordinator/ui/unit_layout.lua deleted file mode 100644 index 45884ca..0000000 --- a/coordinator/ui/unit_layout.lua +++ /dev/null @@ -1,21 +0,0 @@ --- --- Reactor Unit SCADA Coordinator GUI --- - -local core = require("graphics.core") -local layout = require("graphics.layout") - -local style = require("coordinator.ui.style") - -local displaybox = require("graphics.elements.displaybox") -local textbox = require("graphics.elements.textbox") - -local function init(monitor, id) - local main = layout.create(monitor, displaybox{window=monitor,fg_bg=style.root}) - - textbox{parent=main,text="Reactor Unit #" .. id,alignment=core.graphics.TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} - - return main -end - -return init diff --git a/graphics/layout.lua b/graphics/layout.lua deleted file mode 100644 index ebbcfd3..0000000 --- a/graphics/layout.lua +++ /dev/null @@ -1,75 +0,0 @@ --- --- Graphics View Layout --- - -local core = require("graphics.core") -local util = require("scada-common.util") - -local displaybox = require("graphics.elements.displaybox") - -local layout = {} - ----@class stem ----@field element graphics_element ----@field children table - -function layout.create(window, default_fg_bg) - local self = { - root = displaybox{window=window,fg_bg=default_fg_bg}, - tree = {} - } - - -- recursive function to search layout tree for an element - ---@param id string element ID to look for - ---@param tree table tree to search in - ---@return stem|nil - local function lookup(id, tree) - for key, stem in pairs(tree) do - if key == id then - return stem - else - stem = lookup(id, stem.children) - if stem ~= nil then return stem end - end - end - - return nil - end - - ---@class layout - local public = {} - - -- insert a new element - ---@param parent_id string|nil parent or nil for root - ---@param id string element ID - ---@param element graphics_element - function public.insert_at(parent_id, id, element) - if parent_id == nil then - self.tree[id] = { element = element, children = {} } - else - local parent = lookup(parent_id, self.tree) - if parent ~= nil then - parent.children[id] = { element = element, children = {} } - end - end - end - - -- get an element by ID - ---@param id string element ID - ---@return graphics_element|nil - function public.get_element_by_id(id) - local elem = lookup(id, self.tree) ----@diagnostic disable-next-line: need-check-nil - return util.trinary(elem == nil, nil, elem.element) - end - - -- get the root element - ---@return graphics_element - function public.get_root() - return self.root - end - - return public -end - -return layout From ea9e9288f7b7a77746755f6fa6645c194c2b4e89 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 16 Jun 2022 11:29:47 -0400 Subject: [PATCH 290/587] bugfix to hbar --- graphics/elements/indicators/hbar.lua | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/graphics/elements/indicators/hbar.lua b/graphics/elements/indicators/hbar.lua index d620df0..31edb14 100644 --- a/graphics/elements/indicators/hbar.lua +++ b/graphics/elements/indicators/hbar.lua @@ -27,12 +27,15 @@ local function hbar(args) -- bar width is width - 5 characters for " 100%" if showing percent local bar_width = util.trinary(args.show_percent, e.frame.w - 5, e.frame.w) - assert(bar_width > 0, "graphics.elements.hbar: too small for bar") + assert(bar_width > 0, "graphics.elements.indicators.hbar: too small for bar") -- determine bar colors - ---@fixme this doesnt work as intended - local bar_bkg = util.trinary(args.bar_fg_bg == nil, e.fg_bg.blit_bkg, args.bar_fg_bg.blit_bkg) - local bar_fgd = util.trinary(args.bar_fg_bg == nil, e.fg_bg.blit_fgd, args.bar_fg_bg.blit_fgd) + local bar_bkg = e.fg_bg.blit_bkg + local bar_fgd = e.fg_bg.blit_fgd + if args.show_percent and args.bar_fg_bg ~= nil then + bar_bkg = args.bar_fg_bg.blit_bkg + bar_fgd = args.bar_fg_bg.blit_fgd + end -- handle data changes function e.on_update(fraction) From 6980e7365845fa80b74bb9429b8b802da9016439 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 16 Jun 2022 11:31:52 -0400 Subject: [PATCH 291/587] default to not even border --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_overview.lua | 2 +- graphics/core.lua | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 6dee519..2668f5a 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -12,7 +12,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.2.0" +local COORDINATOR_VERSION = "alpha-v0.2.1" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 413a97a..b00c380 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -23,7 +23,7 @@ local function make(parent, x, y, unit_id) TextBox{parent=root,text="Unit #" .. unit_id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} -- reactor - local reactor = Rectangle{parent=root,border=border(1, colors.gray, false),width=30,height=10,x=1,y=3} + local reactor = Rectangle{parent=root,border=border(1, colors.gray),width=30,height=10,x=1,y=3} local text_fg_bg = cpair(colors.black, colors.lightGray) local lu_col = cpair(colors.gray, colors.gray) diff --git a/graphics/core.lua b/graphics/core.lua index 4bd0d9e..9e7ae4b 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -43,13 +43,13 @@ graphics.TEXT_ALIGN = { -- create a new border definition ---@param width integer border width ---@param color color border color ----@param even boolean whether to pad width extra to account for rectangular pixels +---@param even? boolean whether to pad width extra to account for rectangular pixels, defaults to false ---@return graphics_border function graphics.border(width, color, even) return { width = width, color = color, - even = even + even = even or false -- convert nil to false } end From 27038f64f793f10c4694b78d0cd2bb011eb01ce9 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 16 Jun 2022 12:17:41 -0400 Subject: [PATCH 292/587] SCRAM button graphics element --- graphics/elements/controls/scram_button.lua | 71 +++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 graphics/elements/controls/scram_button.lua diff --git a/graphics/elements/controls/scram_button.lua b/graphics/elements/controls/scram_button.lua new file mode 100644 index 0000000..3cd6074 --- /dev/null +++ b/graphics/elements/controls/scram_button.lua @@ -0,0 +1,71 @@ +-- SCRAM Button Graphics Element + +local tcd = require("scada-common.tcallbackdsp") + +local core = require("graphics.core") + +local element = require("graphics.element") + +---@class scram_button_args +---@field callback function function to call on touch +---@field parent graphics_element +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted + +-- new scram button +---@param args scram_button_args +local function scram_button(args) + assert(type(args.callback) == "function", "graphics.elements.controls.scram_button: callback is a required field") + + -- static dimensions + args.height = 3 + args.width = 9 + + -- static colors + args.fg_bg = core.graphics.cpair(colors.white, colors.black) + + -- create new graphics element base object + local e = element.new(args) + + -- write the button text + e.window.setCursorPos(3, 2) + e.window.write("SCRAM") + + -- draw border + + -- top + e.window.setTextColor(colors.yellow) + e.window.setBackgroundColor(colors.black) + e.window.setCursorPos(1, 1) + e.window.write("\x99\x89\x89\x89\x89\x89\x89\x89\x99") + + -- center left + e.window.setCursorPos(1, 2) + e.window.setTextColor(colors.black) + e.window.setBackgroundColor(colors.yellow) + e.window.write("\x99") + + -- center right + e.window.setTextColor(colors.black) + e.window.setBackgroundColor(colors.yellow) + e.window.setCursorPos(9, 2) + e.window.write("\x99") + + -- bottom + e.window.setTextColor(colors.yellow) + e.window.setBackgroundColor(colors.black) + e.window.setCursorPos(1, 3) + e.window.write("\x99\x98\x98\x98\x98\x98\x98\x98\x99") + + -- handle touch + ---@param event monitor_touch monitor touch event +---@diagnostic disable-next-line: unused-local + function e.handle_touch(event) + -- call the touch callback + args.callback() + end + + return e.get() +end + +return scram_button From 9bd2229e27094265b011ea61045a4247ce29545b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 18 Jun 2022 01:33:45 -0400 Subject: [PATCH 293/587] improvements to rectangle graphics element even rendering --- graphics/elements/indicators/hbar.lua | 1 - graphics/elements/rectangle.lua | 62 ++++++++++++++++++++------- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/graphics/elements/indicators/hbar.lua b/graphics/elements/indicators/hbar.lua index 31edb14..91f2b4b 100644 --- a/graphics/elements/indicators/hbar.lua +++ b/graphics/elements/indicators/hbar.lua @@ -48,7 +48,6 @@ local function hbar(args) -- compute number of bars local num_bars = util.round(fraction * (bar_width * 2)) - util.print(num_bars) -- redraw bar if changed if num_bars ~= last_num_bars then diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua index 2ea84ea..3622380 100644 --- a/graphics/elements/rectangle.lua +++ b/graphics/elements/rectangle.lua @@ -25,27 +25,33 @@ local function rectangle(args) if args.border ~= nil then e.window.setCursorPos(1, 1) - local border_width_v = args.border.width - local border_width_h = util.trinary(args.border.even, args.border.width * 2, args.border.width) + local border_width = args.border.width local border_blit = colors.toBlit(args.border.color) - local spaces = "" - local blit_fg = "" - local blit_bg_top_bot = "" - local blit_bg_sides = "" + local width_x2 = border_width * 2 + local inner_width = e.frame.w - width_x2 -- check dimensions - assert(border_width_v * 2 <= e.frame.w, "graphics.elements.rectangle: border too thick for width") - assert(border_width_h * 2 <= e.frame.h, "graphics.elements.rectangle: border too thick for height") + assert(width_x2 <= e.frame.w, "graphics.elements.rectangle: border too thick for width") + assert(width_x2 <= e.frame.h, "graphics.elements.rectangle: border too thick for height") - -- form the basic and top/bottom blit strings - spaces = util.spaces(e.frame.w) - blit_fg = util.strrep(e.fg_bg.blit_fgd, e.frame.w) - blit_bg_top_bot = util.strrep(border_blit, e.frame.w) + -- form the basic line strings and top/bottom blit strings + local spaces = util.spaces(e.frame.w) + local blit_fg = util.strrep(e.fg_bg.blit_fgd, e.frame.w) + local blit_bg_sides = "" + local blit_bg_top_bot = util.strrep(border_blit, e.frame.w) + + -- partial bars + local p_a = util.spaces(border_width) .. util.strrep("\x8f", inner_width) .. util.spaces(border_width) + local p_b = util.spaces(border_width) .. util.strrep("\x83", inner_width) .. util.spaces(border_width) + local p_inv_fg = util.strrep(border_blit, border_width) .. util.strrep(e.fg_bg.blit_bkg, inner_width) .. + util.strrep(border_blit, border_width) + local p_inv_bg = util.strrep(e.fg_bg.blit_bkg, border_width) .. util.strrep(border_blit, inner_width) .. + util.strrep(e.fg_bg.blit_bkg, border_width) -- form the body blit strings (sides are border, inside is normal) for x = 1, e.frame.w do -- edges get border color, center gets normal - if x <= border_width_h or x > (e.frame.w - border_width_h) then + if x <= border_width or x > (e.frame.w - border_width) then blit_bg_sides = blit_bg_sides .. border_blit else blit_bg_sides = blit_bg_sides .. e.fg_bg.blit_bkg @@ -55,8 +61,34 @@ local function rectangle(args) -- draw rectangle with borders for y = 1, e.frame.h do e.window.setCursorPos(1, y) - if y <= border_width_v or y > (e.frame.h - border_width_v) then - e.window.blit(spaces, blit_fg, blit_bg_top_bot) + if y <= border_width then + -- partial pixel fill + if args.border.even and y == border_width then + if width_x2 % 3 == 1 then + e.window.blit(p_b, p_inv_bg, p_inv_fg) + elseif width_x2 % 3 == 2 then + e.window.blit(p_a, p_inv_bg, p_inv_fg) + else + -- skip line + e.window.blit(spaces, blit_fg, blit_bg_sides) + end + else + e.window.blit(spaces, blit_fg, blit_bg_top_bot) + end + elseif y > (e.frame.h - border_width) then + -- partial pixel fill + if args.border.even and y == ((e.frame.h - border_width) + 1) then + if width_x2 % 3 == 1 then + e.window.blit(p_a, p_inv_fg, blit_bg_top_bot) + elseif width_x2 % 3 == 2 then + e.window.blit(p_b, p_inv_fg, blit_bg_top_bot) + else + -- skip line + e.window.blit(spaces, blit_fg, blit_bg_sides) + end + else + e.window.blit(spaces, blit_fg, blit_bg_top_bot) + end else e.window.blit(spaces, blit_fg, blit_bg_sides) end From e4b7f807fead9a9557cf176a7826e7d3cb8c2051 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 18 Jun 2022 02:14:48 -0400 Subject: [PATCH 294/587] commas in data indicators --- graphics/elements/indicators/data.lua | 30 ++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/graphics/elements/indicators/data.lua b/graphics/elements/indicators/data.lua index d33cb69..07930c5 100644 --- a/graphics/elements/indicators/data.lua +++ b/graphics/elements/indicators/data.lua @@ -4,10 +4,34 @@ local util = require("scada-common.util") local element = require("graphics.element") +-- format a number string with commas as the thousands separator +-- +-- subtracts from spaces at the start if present for each comma used +---@param num string number string +---@return string +local function comma_format(num) + local formatted = num + local commas = 0 + local i = 1 + + while i > 0 do + formatted, i = formatted:gsub("^(%s-%d+)(%d%d%d)", '%1,%2') + if i > 0 then commas = commas + 1 end + end + + local _, num_spaces = formatted:gsub(" %s-", "") + local remove = math.min(num_spaces, commas) + + formatted = string.sub(formatted, remove + 1) + + return formatted +end + ---@class data_indicator_args ---@field label string indicator label ---@field unit? string indicator unit ---@field format string data format (lua string format) +---@field commas boolean whether to use commas if a number is given (default to false) ---@field lu_colors? cpair label foreground color (a), unit foreground color (b) ---@field value any default value ---@field parent graphics_element @@ -49,7 +73,11 @@ local function data(args) -- write data e.window.setCursorPos(data_start, 1) e.window.setTextColor(e.fg_bg.fgd) - e.window.write(data_str) + if args.commas then + e.window.write(comma_format(data_str)) + else + e.window.write(data_str) + end -- write label if args.unit ~= nil then From 5a3897572dcf8decfe7b4c9434bc08a69625e4b8 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 18 Jun 2022 02:15:03 -0400 Subject: [PATCH 295/587] fixed bug with single word strings in strwrap --- scada-common/util.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scada-common/util.lua b/scada-common/util.lua index 1a36bc0..54a820c 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -83,7 +83,13 @@ function util.strwrap(str, limit) local lines = {} local ln_start = 1 - lines[1] = string.sub(str, 1, str:find("([%-%s]+)") - 1) + local first_break = str:find("([%-%s]+)") + + if first_break ~= nil then + lines[1] = string.sub(str, 1, first_break - 1) + else + lines[1] = str + end ---@diagnostic disable-next-line: discard-returns str:gsub("(%s+)()(%S+)()", From 15595ca81b16baaa704b66fc06a67a4e2cb29fef Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Jun 2022 11:20:09 -0400 Subject: [PATCH 296/587] #75 offset children of rectangles with borders --- graphics/element.lua | 24 +++++++++++++++++++++++- graphics/elements/rectangle.lua | 12 ++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/graphics/element.lua b/graphics/element.lua index 9e78faf..23cee8a 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -13,6 +13,8 @@ local element = {} ---@field parent? graphics_element ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted +---@field inner_x? integer 0 if omitted +---@field inner_y? integer 0 if omitted ---@field width? integer parent width if omitted ---@field height? integer parent height if omitted ---@field gframe? graphics_frame frame instead of x/y/width/height @@ -25,6 +27,7 @@ function element.new(args) elem_type = debug.getinfo(2).name, p_window = nil, ---@type table position = { x = 1, y = 1 }, + child_offset = { x = 0, y = 0 }, bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1} } @@ -62,6 +65,10 @@ function element.new(args) protected.frame.h = args.height or h end + -- inner offsets + if args.inner_x ~= nil then self.child_offset.x = args.inner_x end + if args.inner_y ~= nil then self.child_offset.y = args.inner_y end + -- check frame assert(protected.frame.x >= 1, "graphics.element: frame x not >= 1") assert(protected.frame.y >= 1, "graphics.element: frame y not >= 1") @@ -70,7 +77,16 @@ function element.new(args) -- create window local f = protected.frame - protected.window = window.create(self.p_window, f.x, f.y, f.w, f.h, true) + local x = f.x + local y = f.y + + if args.parent ~= nil then + local offset_x, offset_y = args.parent.get_offset() + x = x + offset_x + y = y + offset_y + end + + protected.window = window.create(self.p_window, x, y, f.w, f.h, true) -- init colors if args.fg_bg ~= nil then @@ -123,6 +139,12 @@ function element.new(args) -- get the foreground/background colors function public.get_fg_bg() return protected.fg_bg end + -- get offset from this element's frame + ---@return integer x, integer y + function public.get_offset() + return self.child_offset.x, self.child_offset.y + end + -- handle a monitor touch ---@param event monitor_touch monitor touch event function public.handle_touch(event) diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua index 3622380..69452b5 100644 --- a/graphics/elements/rectangle.lua +++ b/graphics/elements/rectangle.lua @@ -17,6 +17,18 @@ local element = require("graphics.element") -- new rectangle ---@param args rectangle_args local function rectangle(args) + -- offset children + if args.border ~= nil then + args.inner_x = args.border.width + args.inner_y = args.border.width + + -- slightly different y offset if the border is set to even + if args.border.even then + local width_x2 = (2 * args.border.width) + args.inner_y = (width_x2 // 3) + util.trinary(width_x2 % 3 > 0, 1, 0) + end + end + -- create new graphics element base object local e = element.new(args) From d3f28a68820d364417b85767a879518dd93669da Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Jun 2022 11:35:17 -0400 Subject: [PATCH 297/587] #75 handle edge case on rectangle border width, renamed inner_* to offset_* --- graphics/element.lua | 8 ++++---- graphics/elements/rectangle.lua | 13 +++++++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/graphics/element.lua b/graphics/element.lua index 23cee8a..662570d 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -13,8 +13,8 @@ local element = {} ---@field parent? graphics_element ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ----@field inner_x? integer 0 if omitted ----@field inner_y? integer 0 if omitted +---@field offset_x? integer 0 if omitted +---@field offset_y? integer 0 if omitted ---@field width? integer parent width if omitted ---@field height? integer parent height if omitted ---@field gframe? graphics_frame frame instead of x/y/width/height @@ -66,8 +66,8 @@ function element.new(args) end -- inner offsets - if args.inner_x ~= nil then self.child_offset.x = args.inner_x end - if args.inner_y ~= nil then self.child_offset.y = args.inner_y end + if args.offset_x ~= nil then self.child_offset.x = args.offset_x end + if args.offset_y ~= nil then self.child_offset.y = args.offset_y end -- check frame assert(protected.frame.x >= 1, "graphics.element: frame x not >= 1") diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua index 69452b5..9e8c3ec 100644 --- a/graphics/elements/rectangle.lua +++ b/graphics/elements/rectangle.lua @@ -19,13 +19,13 @@ local element = require("graphics.element") local function rectangle(args) -- offset children if args.border ~= nil then - args.inner_x = args.border.width - args.inner_y = args.border.width + args.offsetx = args.border.width + args.offsety = args.border.width -- slightly different y offset if the border is set to even if args.border.even then local width_x2 = (2 * args.border.width) - args.inner_y = (width_x2 // 3) + util.trinary(width_x2 % 3 > 0, 1, 0) + args.offsety = (width_x2 // 3) + util.trinary(width_x2 % 3 > 0, 1, 0) end end @@ -37,7 +37,8 @@ local function rectangle(args) if args.border ~= nil then e.window.setCursorPos(1, 1) - local border_width = args.border.width + local border_width = args.offsetx + local border_height = args.offsety local border_blit = colors.toBlit(args.border.color) local width_x2 = border_width * 2 local inner_width = e.frame.w - width_x2 @@ -73,9 +74,9 @@ local function rectangle(args) -- draw rectangle with borders for y = 1, e.frame.h do e.window.setCursorPos(1, y) - if y <= border_width then + if y <= border_height then -- partial pixel fill - if args.border.even and y == border_width then + if args.border.even and y == border_height then if width_x2 % 3 == 1 then e.window.blit(p_b, p_inv_bg, p_inv_fg) elseif width_x2 % 3 == 2 then From cf6f0e315383a6916f9a4493e3f1816c23a25221 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 25 Jun 2022 13:38:31 -0400 Subject: [PATCH 298/587] publisher-subscriber interconnect layer --- scada-common/psil.lua | 48 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 scada-common/psil.lua diff --git a/scada-common/psil.lua b/scada-common/psil.lua new file mode 100644 index 0000000..9901e97 --- /dev/null +++ b/scada-common/psil.lua @@ -0,0 +1,48 @@ +-- +-- Publisher-Subscriber Interconnect Layer +-- + +local psil = {} + +-- instantiate a new PSI layer +function psil.create() + local self = { + ic = {} + } + + -- allocate a new interconnect field + ---@key string data key + local function alloc(key) + self.ic[key] = { subscribers = {}, value = 0 } + end + + ---@class psil + local public = {} + + -- subscribe to a data object in the interconnect + ---@param key string data key + ---@param func function function to call on change + function public.subscribe(key, func) + if self.ic[key] == nil then alloc(key) end + table.insert(self.ic[key].subscribers, { notify = func }) + end + + -- publish data to a given key, passing it to all subscribers if it has changed + ---@param key string data key + ---@param value any data value + function public.publish(key, value) + if self.ic[key] == nil then alloc(key) end + + if self.ic[key].value ~= value then + for i = 1, #self.ic[key].subscribers do + self.ic[key].subscribers[i].notify(value) + end + end + + self.ic[key].value = value + end + + return public +end + +return psil From e54d5b3d85c44addf41314c2d0e54d4a293d021b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 25 Jun 2022 13:39:47 -0400 Subject: [PATCH 299/587] #74 coordinator comms and work on database --- coordinator/apisessions.lua | 7 ++ coordinator/coordinator.lua | 166 ++++++++++++++++++++++-------------- scada-common/comms.lua | 10 ++- 3 files changed, 114 insertions(+), 69 deletions(-) create mode 100644 coordinator/apisessions.lua diff --git a/coordinator/apisessions.lua b/coordinator/apisessions.lua new file mode 100644 index 0000000..2141e77 --- /dev/null +++ b/coordinator/apisessions.lua @@ -0,0 +1,7 @@ +local apisessions = {} + +---@param packet capi_frame +function apisessions.handle_packet(packet) +end + +return apisessions diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 5dbb39e..c917681 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -1,13 +1,19 @@ local comms = require("scada-common.comms") -local log = require("scada-common.log") -local ppm = require("scada-common.ppm") -local util = require("scada-common.util") +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 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 @@ -142,12 +148,43 @@ 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 + -- coordinator communications ----@param conn_watchdog watchdog -function coordinator.coord_comms(version, num_reactors, modem, sv_port, sv_listen, api_listen, conn_watchdog) +---@param version string +---@param modem table +---@param sv_port integer +---@param sv_listen integer +---@param api_listen integer +---@param sv_watchdog watchdog +function coordinator.coord_comms(version, modem, sv_port, sv_listen, api_listen, sv_watchdog) local self = { - seq_num = 0, - r_seq_num = nil, + sv_seq_num = 0, + sv_r_seq_num = nil, modem = modem, connected = false } @@ -171,32 +208,26 @@ function coordinator.coord_comms(version, num_reactors, modem, sv_port, sv_liste -- open at construct time _open_channels() - -- send a coordinator packet - ---@param msg_type COORD_TYPES - ---@param msg string - local function _send(msg_type, msg) + -- send a packet to the supervisor + ---@param msg_type SCADA_MGMT_TYPES|COORD_TYPES + ---@param msg table + local function _send_sv(protocol, msg_type, msg) local s_pkt = comms.scada_packet() - local c_pkt = comms.coord_packet() + local pkt = nil ---@type mgmt_packet|coord_packet - c_pkt.make(msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOLS.COORD_DATA, c_pkt.raw_sendable()) + if protocol == PROTOCOLS.SCADA_MGMT then + pkt = comms.mgmt_packet() + elseif protocol == PROTOCOLS.COORD_DATA then + pkt = comms.coord_packet() + else + return + end + + pkt.make(msg_type, msg) + s_pkt.make(self.sv_seq_num, protocol, pkt.raw_sendable()) self.modem.transmit(sv_port, sv_listen, s_pkt.raw_sendable()) - self.seq_num = self.seq_num + 1 - end - - -- send a SCADA management packet - ---@param msg_type SCADA_MGMT_TYPES - ---@param msg string - local function _send_mgmt(msg_type, msg) - local s_pkt = comms.scada_packet() - local m_pkt = comms.mgmt_packet() - - m_pkt.make(msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) - - self.modem.transmit(sv_port, sv_listen, s_pkt.raw_sendable()) - self.seq_num = self.seq_num + 1 + self.sv_seq_num = self.sv_seq_num + 1 end -- PUBLIC FUNCTIONS -- @@ -254,46 +285,49 @@ function coordinator.coord_comms(version, num_reactors, modem, sv_port, sv_liste ---@param packet mgmt_frame|coord_frame|capi_frame function public.handle_packet(packet) if packet ~= nil then - -- check sequence number - if self.r_seq_num == nil then - self.r_seq_num = packet.scada_frame.seq_num() - elseif self.connected and self.r_seq_num >= packet.scada_frame.seq_num() then - log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) - return - else - self.r_seq_num = packet.scada_frame.seq_num() - end - - -- feed watchdog on valid sequence number - conn_watchdog.feed() - local protocol = packet.scada_frame.protocol() - -- handle packet - if protocol == PROTOCOLS.COORD_DATA then - if packet.type == COORD_TYPES.ESTABLISH then - elseif packet.type == COORD_TYPES.QUERY_UNIT then - elseif packet.type == COORD_TYPES.QUERY_FACILITY then - elseif packet.type == COORD_TYPES.COMMAND_UNIT then - elseif packet.type == COORD_TYPES.ALARM then - else - log.warning("received unknown coordinator data packet type " .. packet.type) - end - elseif protocol == PROTOCOLS.COORD_API then - elseif protocol == PROTOCOLS.SCADA_MGMT then - if packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then - -- keep alive response received - elseif packet.type == SCADA_MGMT_TYPES.CLOSE then - -- handle session close - conn_watchdog.cancel() - println_ts("server connection closed by remote host") - log.warning("server connection closed by remote host") - else - log.warning("received unknown SCADA_MGMT packet type " .. packet.type) - end + if protocol == PROTOCOLS.COORD_API then + apisessions.handle_packet(packet) else - -- should be unreachable assuming packet is from parse_packet() - log.error("illegal packet type " .. protocol, true) + -- check sequence number + if self.sv_r_seq_num == nil then + self.sv_r_seq_num = packet.scada_frame.seq_num() + elseif self.connected and self.sv_r_seq_num >= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order: last = " .. self.sv_r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + return + else + self.sv_r_seq_num = packet.scada_frame.seq_num() + end + + -- feed watchdog on valid sequence number + sv_watchdog.feed() + + -- handle packet + if protocol == PROTOCOLS.COORD_DATA then + if packet.type == COORD_TYPES.ESTABLISH then + elseif packet.type == COORD_TYPES.QUERY_UNIT then + elseif packet.type == COORD_TYPES.QUERY_FACILITY then + elseif packet.type == COORD_TYPES.COMMAND_UNIT then + elseif packet.type == COORD_TYPES.ALARM then + else + log.warning("received unknown COORD_DATA packet type " .. packet.type) + end + elseif protocol == PROTOCOLS.SCADA_MGMT then + if packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + -- keep alive response received + elseif packet.type == SCADA_MGMT_TYPES.CLOSE then + -- handle session close + sv_watchdog.cancel() + println_ts("server connection closed by remote host") + log.warning("server connection closed by remote host") + else + log.warning("received unknown SCADA_MGMT packet type " .. packet.type) + end + else + -- should be unreachable assuming packet is from parse_packet() + log.error("illegal packet type " .. protocol, true) + end end end end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index e834d1e..1943baa 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -57,6 +57,11 @@ local COORD_TYPES = { ALARM = 4 -- alarm signaling } +---@alias CAPI_TYPES integer +local CAPI_TYPES = { + ESTABLISH = 0 -- initial greeting +} + ---@alias RTU_UNIT_TYPES integer local RTU_UNIT_TYPES = { REDSTONE = 0, -- redstone I/O @@ -433,7 +438,6 @@ function comms.mgmt_packet() end -- SCADA coordinator packet --- @todo function comms.coord_packet() local self = { frame = nil, @@ -456,7 +460,7 @@ function comms.coord_packet() end -- make a coordinator packet - ---@param packet_type any + ---@param packet_type COORD_TYPES ---@param data table function public.make(packet_type, data) if type(data) == "table" then @@ -541,7 +545,7 @@ function comms.capi_packet() end -- make a coordinator API packet - ---@param packet_type any + ---@param packet_type CAPI_TYPES ---@param data table function public.make(packet_type, data) if type(data) == "table" then From 47599b8ff6b2bbaba8d50dba3d31368edc17ed5f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 25 Jun 2022 14:27:15 -0400 Subject: [PATCH 300/587] fixes to offsets and width calculations, init hbar to 0 --- graphics/element.lua | 22 +++++++++++++++------- graphics/elements/indicators/hbar.lua | 3 +++ graphics/elements/rectangle.lua | 10 +++++----- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/graphics/element.lua b/graphics/element.lua index 662570d..dea041d 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -69,23 +69,31 @@ function element.new(args) if args.offset_x ~= nil then self.child_offset.x = args.offset_x end if args.offset_y ~= nil then self.child_offset.y = args.offset_y end - -- check frame - assert(protected.frame.x >= 1, "graphics.element: frame x not >= 1") - assert(protected.frame.y >= 1, "graphics.element: frame y not >= 1") - assert(protected.frame.w >= 1, "graphics.element: frame width not >= 1") - assert(protected.frame.h >= 1, "graphics.element: frame height not >= 1") - - -- create window + -- adjust window frame if applicable local f = protected.frame local x = f.x local y = f.y + -- apply offsets if args.parent ~= nil then + -- offset x/y local offset_x, offset_y = args.parent.get_offset() x = x + offset_x y = y + offset_y + + -- constrain to parent inner width/height + local w, h = self.p_window.getSize() + f.w = math.min(f.w, w - (2 * offset_x) - f.x) + f.y = math.min(f.h, h - (2 * offset_y) - f.y) end + -- check frame + assert(f.x >= 1, "graphics.element: frame x not >= 1") + assert(f.y >= 1, "graphics.element: frame y not >= 1") + assert(f.w >= 1, "graphics.element: frame width not >= 1") + assert(f.h >= 1, "graphics.element: frame height not >= 1") + + -- create window protected.window = window.create(self.p_window, x, y, f.w, f.h, true) -- init colors diff --git a/graphics/elements/indicators/hbar.lua b/graphics/elements/indicators/hbar.lua index 91f2b4b..a769423 100644 --- a/graphics/elements/indicators/hbar.lua +++ b/graphics/elements/indicators/hbar.lua @@ -93,6 +93,9 @@ local function hbar(args) end end + -- initialize to 0 + e.on_update(0) + return e.get() end diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua index 9e8c3ec..933bcd3 100644 --- a/graphics/elements/rectangle.lua +++ b/graphics/elements/rectangle.lua @@ -19,13 +19,13 @@ local element = require("graphics.element") local function rectangle(args) -- offset children if args.border ~= nil then - args.offsetx = args.border.width - args.offsety = args.border.width + args.offset_x = args.border.width + args.offset_y = args.border.width -- slightly different y offset if the border is set to even if args.border.even then local width_x2 = (2 * args.border.width) - args.offsety = (width_x2 // 3) + util.trinary(width_x2 % 3 > 0, 1, 0) + args.offset_y = math.floor(width_x2 / 3) + util.trinary(width_x2 % 3 > 0, 1, 0) end end @@ -37,8 +37,8 @@ local function rectangle(args) if args.border ~= nil then e.window.setCursorPos(1, 1) - local border_width = args.offsetx - local border_height = args.offsety + local border_width = args.offset_x + local border_height = args.offset_y local border_blit = colors.toBlit(args.border.color) local width_x2 = border_width * 2 local inner_width = e.frame.w - width_x2 From 6397f29d4feb529208fa3909499065faa79d2c3e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 25 Jun 2022 14:51:38 -0400 Subject: [PATCH 301/587] fixed offsets/inner width for real this time --- graphics/element.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/graphics/element.lua b/graphics/element.lua index dea041d..268dcb7 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -49,7 +49,7 @@ function element.new(args) end -- check window - assert(self.p_window, "graphics.element: no parent window provided") + assert(self.p_window, "graphics.element{" .. self.elem_type .. "}: no parent window provided") -- get frame coordinates/size if args.gframe ~= nil then @@ -83,15 +83,15 @@ function element.new(args) -- constrain to parent inner width/height local w, h = self.p_window.getSize() - f.w = math.min(f.w, w - (2 * offset_x) - f.x) - f.y = math.min(f.h, h - (2 * offset_y) - f.y) + f.w = math.min(f.w, w - ((2 * offset_x) + (f.x - 1))) + f.h = math.min(f.h, h - ((2 * offset_y) + (f.y - 1))) end -- check frame - assert(f.x >= 1, "graphics.element: frame x not >= 1") - assert(f.y >= 1, "graphics.element: frame y not >= 1") - assert(f.w >= 1, "graphics.element: frame width not >= 1") - assert(f.h >= 1, "graphics.element: frame height not >= 1") + assert(f.x >= 1, "graphics.element{" .. self.elem_type .. "}: frame x not >= 1") + assert(f.y >= 1, "graphics.element{" .. self.elem_type .. "}: frame y not >= 1") + assert(f.w >= 1, "graphics.element{" .. self.elem_type .. "}: frame width not >= 1") + assert(f.h >= 1, "graphics.element{" .. self.elem_type .. "}: frame height not >= 1") -- create window protected.window = window.create(self.p_window, x, y, f.w, f.h, true) From 316b255a04aa307ebc074d8c823306b817eebe08 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 25 Jun 2022 14:51:59 -0400 Subject: [PATCH 302/587] fixed hbar percentage position --- graphics/elements/indicators/hbar.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/elements/indicators/hbar.lua b/graphics/elements/indicators/hbar.lua index a769423..c886a63 100644 --- a/graphics/elements/indicators/hbar.lua +++ b/graphics/elements/indicators/hbar.lua @@ -88,7 +88,7 @@ local function hbar(args) -- update percentage if args.show_percent then - e.window.setCursorPos(bar_width + 1, math.max(1, math.ceil(e.frame.h / 2))) + e.window.setCursorPos(bar_width + 2, math.max(1, math.ceil(e.frame.h / 2))) e.window.write(util.sprintf("%3.0f%%", fraction * 100)) end end From e137953f93238e3628e28150f103cb1ad796af6f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 25 Jun 2022 16:20:58 -0400 Subject: [PATCH 303/587] fixed vbar bugs --- graphics/elements/indicators/vbar.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphics/elements/indicators/vbar.lua b/graphics/elements/indicators/vbar.lua index ccd259a..e1016aa 100644 --- a/graphics/elements/indicators/vbar.lua +++ b/graphics/elements/indicators/vbar.lua @@ -39,14 +39,14 @@ local function vbar(args) end -- compute number of bars - local num_bars = util.round((fraction * 100) / (e.frame.h * 3)) + local num_bars = util.round(fraction * (e.frame.h * 3)) -- redraw only if number of bars has changed if num_bars ~= last_num_bars then last_num_bars = num_bars -- start bottom up - local y = e.window.h + local y = e.frame.h -- start at base of vertical bar e.window.setCursorPos(1, y) From 1188d2f7df7ee417fa5a7b9fe6ecf037dca738aa Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 25 Jun 2022 16:21:57 -0400 Subject: [PATCH 304/587] #72 work on main layout, reactor and boiler views exist now --- coordinator/startup.lua | 14 ++++++- coordinator/ui/components/boiler.lua | 43 +++++++++++++++++++ coordinator/ui/components/reactor.lua | 46 +++++++++++++++++++++ coordinator/ui/components/unit_overview.lua | 24 +++++------ coordinator/ui/layout/main_view.lua | 2 +- coordinator/ui/style.lua | 18 ++++++++ 6 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 coordinator/ui/components/boiler.lua create mode 100644 coordinator/ui/components/reactor.lua diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 2668f5a..0f21c1f 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -82,11 +82,21 @@ end log.dmesg("wireless modem connected", "COMMS", colors.purple) +-- start the UI + log.dmesg("starting UI...", "GRAPHICS", colors.green) -util.psleep(3) +-- util.psleep(3) local ui_ok, message = pcall(renderer.start_ui) if not ui_ok then renderer.close_ui() - log.dmesg("UI draw failed: " .. message, "GRAPHICS", colors.green) + println_ts("UI crashed") + log.dmesg(util.c("UI crashed: ", message), "GRAPHICS", colors.green) + log.fatal(util.c("ui crashed with error ", message)) end + +-- renderer.close_ui() +-- log.dmesg("system shutdown", "SYSTEM", colors.cyan) + +println_ts("exited") +log.info("exited") diff --git a/coordinator/ui/components/boiler.lua b/coordinator/ui/components/boiler.lua new file mode 100644 index 0000000..e1e35d9 --- /dev/null +++ b/coordinator/ui/components/boiler.lua @@ -0,0 +1,43 @@ +local core = require("graphics.core") + +local style = require("coordinator.ui.style") + +local Div = require("graphics.elements.div") +local DataIndicator = require("graphics.elements.indicators.data") +local StateIndicator = require("graphics.elements.indicators.state") +local Rectangle = require("graphics.elements.rectangle") +local TextBox = require("graphics.elements.textbox") +local VerticalBar = require("graphics.elements.indicators.vbar") + +local TEXT_ALIGN = core.graphics.TEXT_ALIGN + +local cpair = core.graphics.cpair +local border = core.graphics.border + +local function new_view(root, x, y) + local boiler = Rectangle{parent=root,border=border(1, colors.gray, true),width=31,height=8,x=x,y=y} + + local text_fg_bg = cpair(colors.black, colors.lightGray) + local lu_col = cpair(colors.gray, colors.gray) + + local status = StateIndicator{parent=boiler,x=10,y=1,states=style.boiler.states,value=3,min_width=10} + local temp = DataIndicator{parent=boiler,x=5,y=3,lu_colors=lu_col,label="Temp:",unit="K",format="%10.2f",value=1900,width=22,fg_bg=text_fg_bg} + local boil_r = DataIndicator{parent=boiler,x=5,y=4,lu_colors=lu_col,label="Boil:",unit="mB/t",format="%10.0f",value=801523,commas=true,width=22,fg_bg=text_fg_bg} + + TextBox{parent=boiler,text="H",x=2,y=6,height=1,width=1,fg_bg=text_fg_bg} + TextBox{parent=boiler,text="W",x=3,y=6,height=1,width=1,fg_bg=text_fg_bg} + TextBox{parent=boiler,text="S",x=27,y=6,height=1,width=1,fg_bg=text_fg_bg} + TextBox{parent=boiler,text="C",x=28,y=6,height=1,width=1,fg_bg=text_fg_bg} + + local hcool = VerticalBar{parent=boiler,x=2,y=1,fg_bg=cpair(colors.orange,colors.gray),height=4,width=1} + local water = VerticalBar{parent=boiler,x=3,y=1,fg_bg=cpair(colors.blue,colors.gray),height=4,width=1} + local steam = VerticalBar{parent=boiler,x=27,y=1,fg_bg=cpair(colors.white,colors.gray),height=4,width=1} + local cool = VerticalBar{parent=boiler,x=28,y=1,fg_bg=cpair(colors.lightBlue,colors.gray),height=4,width=1} + + hcool.update(0.22) + water.update(1) + steam.update(0.05) + cool.update(0.13) +end + +return new_view diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua new file mode 100644 index 0000000..6f01d66 --- /dev/null +++ b/coordinator/ui/components/reactor.lua @@ -0,0 +1,46 @@ +local core = require("graphics.core") + +local style = require("coordinator.ui.style") + +local Div = require("graphics.elements.div") +local HorizontalBar = require("graphics.elements.indicators.hbar") +local DataIndicator = require("graphics.elements.indicators.data") +local StateIndicator = require("graphics.elements.indicators.state") +local Rectangle = require("graphics.elements.rectangle") +local TextBox = require("graphics.elements.textbox") + +local TEXT_ALIGN = core.graphics.TEXT_ALIGN + +local cpair = core.graphics.cpair +local border = core.graphics.border + +local function new_view(root, x, y) + local reactor = Rectangle{parent=root,border=border(1, colors.gray, true),width=30,height=7,x=x,y=y} + + local text_fg_bg = cpair(colors.black, colors.lightGray) + local lu_col = cpair(colors.gray, colors.gray) + + local status = StateIndicator{parent=reactor,x=8,y=1,states=style.reactor.states,value=3,min_width=14} + local core_temp = DataIndicator{parent=reactor,x=2,y=3,lu_colors=lu_col,label="Core: ",unit="K",format="%12.2f",value=451.12,width=26,fg_bg=text_fg_bg} + local burn_r = DataIndicator{parent=reactor,x=2,y=4,lu_colors=lu_col,label="Burn: ",unit="mB/t",format="%12.1f",value=40.1,width=26,fg_bg=text_fg_bg} + local heating_r = DataIndicator{parent=reactor,x=2,y=5,lu_colors=lu_col,label="Heating:",unit="mB/t",format="%12.0f",value=8015342,commas=true,width=26,fg_bg=text_fg_bg} + + local reactor_fills = Rectangle{parent=root,border=border(1, colors.gray, true),width=24,height=7,x=(x + 29),y=y} + + TextBox{parent=reactor_fills,text="FUEL",x=2,y=1,height=1,fg_bg=text_fg_bg} + TextBox{parent=reactor_fills,text="COOL",x=2,y=2,height=1,fg_bg=text_fg_bg} + TextBox{parent=reactor_fills,text="HCOOL",x=2,y=4,height=1,fg_bg=text_fg_bg} + TextBox{parent=reactor_fills,text="WASTE",x=2,y=5,height=1,fg_bg=text_fg_bg} + + local fuel = HorizontalBar{parent=reactor_fills,x=8,y=1,show_percent=true,bar_fg_bg=cpair(colors.black,colors.gray),height=1,width=14} + local cool = HorizontalBar{parent=reactor_fills,x=8,y=2,show_percent=true,bar_fg_bg=cpair(colors.lightBlue,colors.gray),height=1,width=14} + local hcool = HorizontalBar{parent=reactor_fills,x=8,y=4,show_percent=true,bar_fg_bg=cpair(colors.orange,colors.gray),height=1,width=14} + local waste = HorizontalBar{parent=reactor_fills,x=8,y=5,show_percent=true,bar_fg_bg=cpair(colors.brown,colors.gray),height=1,width=14} + + fuel.update(1) + cool.update(0.85) + hcool.update(0.08) + waste.update(0.32) +end + +return new_view diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index b00c380..6ec388f 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -2,6 +2,9 @@ local core = require("graphics.core") local style = require("coordinator.ui.style") +local reactor_view = require("coordinator.ui.components.reactor") +local boiler_view = require("coordinator.ui.components.boiler") + local Div = require("graphics.elements.div") local HorizontalBar = require("graphics.elements.indicators.hbar") local DataIndicator = require("graphics.elements.indicators.data") @@ -22,22 +25,19 @@ local function make(parent, x, y, unit_id) -- unit header message TextBox{parent=root,text="Unit #" .. unit_id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} - -- reactor - local reactor = Rectangle{parent=root,border=border(1, colors.gray),width=30,height=10,x=1,y=3} + ------------- + -- REACTOR -- + ------------- - local text_fg_bg = cpair(colors.black, colors.lightGray) - local lu_col = cpair(colors.gray, colors.gray) + reactor_view(root, 1, 3) - local status = StateIndicator{parent=reactor,x=9,y=2,states=style.reactor.states,value=1,min_width=14} - local core_temp = DataIndicator{parent=reactor,x=3,y=4,lu_colors=lu_col,label="Core: ",unit="K",format="%7.0f",value=295,width=26,fg_bg=text_fg_bg} - local heating_r = DataIndicator{parent=reactor,x=3,y=5,lu_colors=lu_col,label="Heating:",unit="mB/t",format="%7.0f",value=359999,width=26,fg_bg=text_fg_bg} - local burn_r = DataIndicator{parent=reactor,x=3,y=6,lu_colors=lu_col,label="Burn: ",unit="mB/t",format="%7.1f",value=40.1,width=26,fg_bg=text_fg_bg} + ------------- + -- BOILERS -- + ------------- - local fuel = HorizontalBar{parent=root,x=34,y=4,show_percent=true,bar_fg_bg=cpair(colors.brown,colors.white),height=1,width=14} - local coolant = HorizontalBar{parent=root,x=34,y=5,show_percent=true,bar_fg_bg=cpair(colors.lightBlue,colors.white),height=1,width=14} + boiler_view(root, 23, 11) + boiler_view(root, 23, 20) - fuel.update(0.85) - coolant.update(0.75) end return make diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index 7ebb1e4..aa46efa 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -21,7 +21,7 @@ local function init(monitor) TextBox{parent=main,text="Nuclear Generation Facility SCADA Coordinator",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} -- unit overviews - unit_overview(main, 5, 5, 1) + unit_overview(main, 3, 3, 1) return main end diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index 04be985..363d758 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -32,4 +32,22 @@ style.reactor = { } } +style.boiler = { + -- boiler states + states = { + { + color = cpair(colors.black, colors.yellow), + text = "OFF-LINE" + }, + { + color = cpair(colors.white, colors.gray), + text = "IDLE" + }, + { + color = cpair(colors.black, colors.green), + text = "ACTIVE" + } + } +} + return style From f32cdf556356fbb99051eb07efdaf7b110f66391 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 25 Jun 2022 18:39:21 -0400 Subject: [PATCH 305/587] ticked version and fixed wording --- coordinator/startup.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 0f21c1f..f647f62 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -12,7 +12,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.2.1" +local COORDINATOR_VERSION = "alpha-v0.2.2" local print = util.print local println = util.println @@ -61,7 +61,7 @@ if not configured then return end -log.info("monitors ready, dmesg input incoming...") +log.info("monitors ready, dmesg output incoming...") -- init renderer renderer.set_displays(monitors) From 01caf3d914cd8b553e3c249fd9d2402d26f0ae79 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Jun 2022 16:36:21 -0400 Subject: [PATCH 306/587] pipe indicator graphics element --- graphics/elements/indicators/pipe.lua | 110 ++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 graphics/elements/indicators/pipe.lua diff --git a/graphics/elements/indicators/pipe.lua b/graphics/elements/indicators/pipe.lua new file mode 100644 index 0000000..fbd77e8 --- /dev/null +++ b/graphics/elements/indicators/pipe.lua @@ -0,0 +1,110 @@ +-- Pipe Graphics Element + +local util = require("scada-common.util") + +local element = require("graphics.element") + +---@class pipe_args +---@field parent graphics_element +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field end_x integer end of pipe +---@field end_y integer end of pipe +---@field thin boolean true for 1 subpixels, false (default) for 2 +---@field align_tr? boolean false to align bottom left (default), true to align top right +---@field fg_bg? cpair foreground/background colors + +-- new pipe +---@param args pipe_args +local function pipe(args) + assert(util.is_int(args.end_x), "graphics.elements.indicators.pipe: end_x is a required field") + assert(util.is_int(args.end_y), "graphics.elements.indicators.pipe: end_y is a required field") + + args.x = args.x or 1 + args.y = args.y or 1 + args.width = args.end_x - args.x + args.height = args.end_y - args.y + + -- create new graphics element base object + local e = element.new(args) + + -- draw pipe + + local align_tr = args.align_tr or false + + local x = 1 + local y = 1 + + if align_tr then + -- cross width then height + for i = 1, args.width do + if args.thin then + if i == args.width then + -- corner + e.window.blit("\x93", e.fg_bg.blit_bkg, e.fg_bg.blit_fgd) + else + e.window.blit("\x8c", e.fg_bg.blit_fgd, e.fg_bg.blit_bkg) + end + else + if i == args.width then + -- corner + e.window.blit(" ", e.fg_bg.blit_bkg, e.fg_bg.blit_fgd) + else + e.window.blit("\x8f", e.fg_bg.blit_fgd, e.fg_bg.blit_bkg) + end + end + + x = x + 1 + e.window.setCursorPos(x, y) + end + + -- back up one + x = x - 1 + + for _ = 1, args.height do + y = y + 1 + e.window.setCursorPos(x, y) + + if args.thin then + e.window.blit("\x95", e.fg_bg.blit_bkg, e.fg_bg.blit_fgd) + else + e.window.blit(" ", e.fg_bg.blit_bkg, e.fg_bg.blit_fgd) + end + end + else + -- cross height then width + for i = 1, args.height do + if args.thin then + if i == args.height then + -- corner + e.window.blit("\x8d", e.fg_bg.blit_fgd, e.fg_bg.blit_bkg) + else + e.window.blit("\x95", e.fg_bg.blit_fgd, e.fg_bg.blit_bkg) + end + else + e.window.blit(" ", e.fg_bg.blit_bkg, e.fg_bg.blit_fgd) + end + + y = y + 1 + e.window.setCursorPos(x, y) + end + + -- back up one + y = y - 1 + + for _ = 1, args.width do + x = x + 1 + e.window.setCursorPos(x, y) + + if args.thin then + e.window.blit("\x8c", e.fg_bg.blit_fgd, e.fg_bg.blit_bkg) + else + e.window.blit("\x83", e.fg_bg.blit_bkg, e.fg_bg.blit_fgd) + end + end + end + + return e.get() +end + +return pipe From ef73c524179211e3cfd5338d7304e5746759e0af Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 29 Jun 2022 17:40:08 -0400 Subject: [PATCH 307/587] pipenet indicator instead of pipe indicator --- graphics/core.lua | 36 +++++++ graphics/elements/indicators/pipe.lua | 110 ------------------- graphics/elements/indicators/pipenet.lua | 129 +++++++++++++++++++++++ 3 files changed, 165 insertions(+), 110 deletions(-) delete mode 100644 graphics/elements/indicators/pipe.lua create mode 100644 graphics/elements/indicators/pipenet.lua diff --git a/graphics/core.lua b/graphics/core.lua index 9e7ae4b..6e4eb92 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -103,6 +103,42 @@ function graphics.cpair(a, b) } end +---@class pipe +---@field x1 integer starting x, origin is 0 +---@field y1 integer starting y, origin is 0 +---@field x2 integer ending x, origin is 0 +---@field y2 integer ending y, origin is 0 +---@field w integer width +---@field h integer height +---@field color color pipe color +---@field thin boolean true for 1 subpixels, false (default) for 2 +---@field align_tr boolean false to align bottom left (default), true to align top right + +-- create a new pipe +-- +-- note: pipe coordinate origin is (0, 0) +---@param x1 integer starting x, origin is 0 +---@param y1 integer starting y, origin is 0 +---@param x2 integer ending x, origin is 0 +---@param y2 integer ending y, origin is 0 +---@param color color pipe color +---@param thin? boolean true for 1 subpixels, false (default) for 2 +---@param align_tr? boolean false to align bottom left (default), true to align top right +---@return pipe +function graphics.pipe(x1, y1, x2, y2, color, thin, align_tr) + return { + x1 = x1, + y1 = y1, + x2 = x2, + y2 = y2, + w = x2 - x1, + h = y2 - y1, + color = color, + thin = thin or false, + align_tr = align_tr or false + } +end + core.graphics = graphics return core diff --git a/graphics/elements/indicators/pipe.lua b/graphics/elements/indicators/pipe.lua deleted file mode 100644 index fbd77e8..0000000 --- a/graphics/elements/indicators/pipe.lua +++ /dev/null @@ -1,110 +0,0 @@ --- Pipe Graphics Element - -local util = require("scada-common.util") - -local element = require("graphics.element") - ----@class pipe_args ----@field parent graphics_element ----@field x? integer 1 if omitted ----@field y? integer 1 if omitted ----@field end_x integer end of pipe ----@field end_y integer end of pipe ----@field thin boolean true for 1 subpixels, false (default) for 2 ----@field align_tr? boolean false to align bottom left (default), true to align top right ----@field fg_bg? cpair foreground/background colors - --- new pipe ----@param args pipe_args -local function pipe(args) - assert(util.is_int(args.end_x), "graphics.elements.indicators.pipe: end_x is a required field") - assert(util.is_int(args.end_y), "graphics.elements.indicators.pipe: end_y is a required field") - - args.x = args.x or 1 - args.y = args.y or 1 - args.width = args.end_x - args.x - args.height = args.end_y - args.y - - -- create new graphics element base object - local e = element.new(args) - - -- draw pipe - - local align_tr = args.align_tr or false - - local x = 1 - local y = 1 - - if align_tr then - -- cross width then height - for i = 1, args.width do - if args.thin then - if i == args.width then - -- corner - e.window.blit("\x93", e.fg_bg.blit_bkg, e.fg_bg.blit_fgd) - else - e.window.blit("\x8c", e.fg_bg.blit_fgd, e.fg_bg.blit_bkg) - end - else - if i == args.width then - -- corner - e.window.blit(" ", e.fg_bg.blit_bkg, e.fg_bg.blit_fgd) - else - e.window.blit("\x8f", e.fg_bg.blit_fgd, e.fg_bg.blit_bkg) - end - end - - x = x + 1 - e.window.setCursorPos(x, y) - end - - -- back up one - x = x - 1 - - for _ = 1, args.height do - y = y + 1 - e.window.setCursorPos(x, y) - - if args.thin then - e.window.blit("\x95", e.fg_bg.blit_bkg, e.fg_bg.blit_fgd) - else - e.window.blit(" ", e.fg_bg.blit_bkg, e.fg_bg.blit_fgd) - end - end - else - -- cross height then width - for i = 1, args.height do - if args.thin then - if i == args.height then - -- corner - e.window.blit("\x8d", e.fg_bg.blit_fgd, e.fg_bg.blit_bkg) - else - e.window.blit("\x95", e.fg_bg.blit_fgd, e.fg_bg.blit_bkg) - end - else - e.window.blit(" ", e.fg_bg.blit_bkg, e.fg_bg.blit_fgd) - end - - y = y + 1 - e.window.setCursorPos(x, y) - end - - -- back up one - y = y - 1 - - for _ = 1, args.width do - x = x + 1 - e.window.setCursorPos(x, y) - - if args.thin then - e.window.blit("\x8c", e.fg_bg.blit_fgd, e.fg_bg.blit_bkg) - else - e.window.blit("\x83", e.fg_bg.blit_bkg, e.fg_bg.blit_fgd) - end - end - end - - return e.get() -end - -return pipe diff --git a/graphics/elements/indicators/pipenet.lua b/graphics/elements/indicators/pipenet.lua new file mode 100644 index 0000000..c375b3a --- /dev/null +++ b/graphics/elements/indicators/pipenet.lua @@ -0,0 +1,129 @@ +-- Pipe Graphics Element + +local util = require("scada-common.util") + +local core = require("graphics.core") +local element = require("graphics.element") + +---@class pipenet_args +---@field parent graphics_element +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field pipes table pipe list +---@field bg? color background color + +-- new pipe network +---@param args pipenet_args +local function pipenet(args) + assert(type(args.pipes) == "table", "graphics.elements.indicators.pipenet: pipes is a required field") + + args.width = 0 + args.height = 0 + + -- determine width/height + for i = 1, #args.pipes do + local pipe = args.pipes[i] ---@type pipe + + local true_w = pipe.w + pipe.x1 + local true_h = pipe.h + pipe.y1 + + if true_w > args.width then args.width = true_w end + if true_h > args.height then args.height = true_h end + end + + args.x = args.x or 1 + args.y = args.y or 1 + + if args.bg ~= nil then + args.fg_bg = core.graphics.cpair(args.bg, args.bg) + end + + -- create new graphics element base object + local e = element.new(args) + + -- draw all pipes + for p = 1, #args.pipes do + local pipe = args.pipes[p] ---@type pipe + + local x = 1 + pipe.x1 + local y = 1 + pipe.y1 + + e.window.setCursorPos(x, y) + + local c = core.graphics.cpair(pipe.color, e.fg_bg.bkg) + + if pipe.align_tr then + -- cross width then height + for i = 1, pipe.w do + if pipe.thin then + if i == pipe.w then + -- corner + e.window.blit("\x93", c.blit_bkg, c.blit_fgd) + else + e.window.blit("\x8c", c.blit_fgd, c.blit_bkg) + end + else + if i == pipe.w then + -- corner + e.window.blit(" ", c.blit_bkg, c.blit_fgd) + else + e.window.blit("\x8f", c.blit_fgd, c.blit_bkg) + end + end + + x = x + 1 + e.window.setCursorPos(x, y) + end + + -- back up one + x = x - 1 + + for _ = 1, pipe.h do + y = y + 1 + e.window.setCursorPos(x, y) + + if pipe.thin then + e.window.blit("\x95", c.blit_bkg, c.blit_fgd) + else + e.window.blit(" ", c.blit_bkg, c.blit_fgd) + end + end + else + -- cross height then width + for i = 1, pipe.h do + if pipe.thin then + if i == pipe.h then + -- corner + e.window.blit("\x8d", c.blit_fgd, c.blit_bkg) + else + e.window.blit("\x95", c.blit_fgd, c.blit_bkg) + end + else + e.window.blit(" ", c.blit_bkg, c.blit_fgd) + end + + y = y + 1 + e.window.setCursorPos(x, y) + end + + -- back up one + y = y - 1 + + for _ = 1, pipe.w do + x = x + 1 + e.window.setCursorPos(x, y) + + if pipe.thin then + e.window.blit("\x8c", c.blit_fgd, c.blit_bkg) + else + e.window.blit("\x83", c.blit_bkg, c.blit_fgd) + end + end + end + + end + + return e.get() +end + +return pipenet From 20a1fab611689e1647296e26f10dc68f7c9c711d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 29 Jun 2022 17:40:46 -0400 Subject: [PATCH 308/587] #72 added pipes to main overview, changed text of reactor overview --- coordinator/ui/components/reactor.lua | 4 ++-- coordinator/ui/components/unit_overview.lua | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua index 6f01d66..acd1ba7 100644 --- a/coordinator/ui/components/reactor.lua +++ b/coordinator/ui/components/reactor.lua @@ -21,8 +21,8 @@ local function new_view(root, x, y) local lu_col = cpair(colors.gray, colors.gray) local status = StateIndicator{parent=reactor,x=8,y=1,states=style.reactor.states,value=3,min_width=14} - local core_temp = DataIndicator{parent=reactor,x=2,y=3,lu_colors=lu_col,label="Core: ",unit="K",format="%12.2f",value=451.12,width=26,fg_bg=text_fg_bg} - local burn_r = DataIndicator{parent=reactor,x=2,y=4,lu_colors=lu_col,label="Burn: ",unit="mB/t",format="%12.1f",value=40.1,width=26,fg_bg=text_fg_bg} + local core_temp = DataIndicator{parent=reactor,x=2,y=3,lu_colors=lu_col,label="Core Temp:",unit="K",format="%10.2f",value=451.12,width=26,fg_bg=text_fg_bg} + local burn_r = DataIndicator{parent=reactor,x=2,y=4,lu_colors=lu_col,label="Burn Rate:",unit="mB/t",format="%10.1f",value=40.1,width=26,fg_bg=text_fg_bg} local heating_r = DataIndicator{parent=reactor,x=2,y=5,lu_colors=lu_col,label="Heating:",unit="mB/t",format="%12.0f",value=8015342,commas=true,width=26,fg_bg=text_fg_bg} local reactor_fills = Rectangle{parent=root,border=border(1, colors.gray, true),width=24,height=7,x=(x + 29),y=y} diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 6ec388f..fff0aad 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -6,16 +6,19 @@ local reactor_view = require("coordinator.ui.components.reactor") local boiler_view = require("coordinator.ui.components.boiler") local Div = require("graphics.elements.div") -local HorizontalBar = require("graphics.elements.indicators.hbar") -local DataIndicator = require("graphics.elements.indicators.data") -local StateIndicator = require("graphics.elements.indicators.state") local Rectangle = require("graphics.elements.rectangle") local TextBox = require("graphics.elements.textbox") +local HorizontalBar = require("graphics.elements.indicators.hbar") +local DataIndicator = require("graphics.elements.indicators.data") +local PipeNetwork = require("graphics.elements.indicators.pipenet") +local StateIndicator = require("graphics.elements.indicators.state") + local TEXT_ALIGN = core.graphics.TEXT_ALIGN local cpair = core.graphics.cpair local border = core.graphics.border +local pipe = core.graphics.pipe ---@param parent graphics_element local function make(parent, x, y, unit_id) @@ -31,6 +34,15 @@ local function make(parent, x, y, unit_id) reactor_view(root, 1, 3) + local pipes = { + pipe(0, 0, 13, 12, colors.lightBlue), + pipe(0, 0, 13, 3, colors.lightBlue), + pipe(2, 0, 11, 2, colors.orange), + pipe(2, 0, 11, 11, colors.orange) + } + + PipeNetwork{parent=root,x=12,y=10,pipes=pipes,bg=colors.lightGray} + ------------- -- BOILERS -- ------------- From 35c408883a304c4dd65524849c4a3567d62b42a2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 2 Jul 2022 15:08:24 -0400 Subject: [PATCH 309/587] fixes to pipes/pipenet --- graphics/core.lua | 4 +-- graphics/elements/indicators/pipenet.lua | 44 ++++++++++++++++-------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/graphics/core.lua b/graphics/core.lua index 6e4eb92..c600010 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -131,8 +131,8 @@ function graphics.pipe(x1, y1, x2, y2, color, thin, align_tr) y1 = y1, x2 = x2, y2 = y2, - w = x2 - x1, - h = y2 - y1, + w = math.abs(x2 - x1) + 1, + h = math.abs(y2 - y1) + 1, color = color, thin = thin or false, align_tr = align_tr or false diff --git a/graphics/elements/indicators/pipenet.lua b/graphics/elements/indicators/pipenet.lua index c375b3a..b9436c3 100644 --- a/graphics/elements/indicators/pipenet.lua +++ b/graphics/elements/indicators/pipenet.lua @@ -24,8 +24,8 @@ local function pipenet(args) for i = 1, #args.pipes do local pipe = args.pipes[i] ---@type pipe - local true_w = pipe.w + pipe.x1 - local true_h = pipe.h + pipe.y1 + local true_w = pipe.w + math.min(pipe.x1, pipe.x2) + local true_h = pipe.h + math.min(pipe.y1, pipe.y2) if true_w > args.width then args.width = true_w end if true_h > args.height then args.height = true_h end @@ -48,6 +48,9 @@ local function pipenet(args) local x = 1 + pipe.x1 local y = 1 + pipe.y1 + local x_step = util.trinary(pipe.x1 >= pipe.x2, -1, 1) + local y_step = util.trinary(pipe.y1 >= pipe.y2, -1, 1) + e.window.setCursorPos(x, y) local c = core.graphics.cpair(pipe.color, e.fg_bg.bkg) @@ -58,12 +61,16 @@ local function pipenet(args) if pipe.thin then if i == pipe.w then -- corner - e.window.blit("\x93", c.blit_bkg, c.blit_fgd) + if y_step > 0 then + e.window.blit("\x93", c.blit_bkg, c.blit_fgd) + else + e.window.blit("\x8e", c.blit_fgd, c.blit_bkg) + end else e.window.blit("\x8c", c.blit_fgd, c.blit_bkg) end else - if i == pipe.w then + if i == pipe.w and y_step > 0 then -- corner e.window.blit(" ", c.blit_bkg, c.blit_fgd) else @@ -71,15 +78,15 @@ local function pipenet(args) end end - x = x + 1 + x = x + x_step e.window.setCursorPos(x, y) end -- back up one - x = x - 1 + x = x - x_step - for _ = 1, pipe.h do - y = y + 1 + for _ = 1, pipe.h - 1 do + y = y + y_step e.window.setCursorPos(x, y) if pipe.thin then @@ -94,23 +101,32 @@ local function pipenet(args) if pipe.thin then if i == pipe.h then -- corner - e.window.blit("\x8d", c.blit_fgd, c.blit_bkg) + if y_step < 0 then + e.window.blit("\x97", c.blit_bkg, c.blit_fgd) + else + e.window.blit("\x8d", c.blit_fgd, c.blit_bkg) + end else e.window.blit("\x95", c.blit_fgd, c.blit_bkg) end else - e.window.blit(" ", c.blit_bkg, c.blit_fgd) + if i == pipe.h and y_step < 0 then + -- corner + e.window.blit("\x83", c.blit_bkg, c.blit_fgd) + else + e.window.blit(" ", c.blit_bkg, c.blit_fgd) + end end - y = y + 1 + y = y + y_step e.window.setCursorPos(x, y) end -- back up one - y = y - 1 + y = y - y_step - for _ = 1, pipe.w do - x = x + 1 + for _ = 1, pipe.w - 1 do + x = x + x_step e.window.setCursorPos(x, y) if pipe.thin then From 3048fbed8be4894508682a3162d97aae35b79622 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 2 Jul 2022 15:09:35 -0400 Subject: [PATCH 310/587] moved pipenet to be basic element not an indicator --- coordinator/ui/components/unit_overview.lua | 2 +- graphics/elements/{indicators => }/pipenet.lua | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename graphics/elements/{indicators => }/pipenet.lua (100%) diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index fff0aad..c6ff25e 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -6,12 +6,12 @@ local reactor_view = require("coordinator.ui.components.reactor") local boiler_view = require("coordinator.ui.components.boiler") local Div = require("graphics.elements.div") +local PipeNetwork = require("graphics.elements.pipenet") local Rectangle = require("graphics.elements.rectangle") local TextBox = require("graphics.elements.textbox") local HorizontalBar = require("graphics.elements.indicators.hbar") local DataIndicator = require("graphics.elements.indicators.data") -local PipeNetwork = require("graphics.elements.indicators.pipenet") local StateIndicator = require("graphics.elements.indicators.state") local TEXT_ALIGN = core.graphics.TEXT_ALIGN diff --git a/graphics/elements/indicators/pipenet.lua b/graphics/elements/pipenet.lua similarity index 100% rename from graphics/elements/indicators/pipenet.lua rename to graphics/elements/pipenet.lua From 7ad115bc038fd366b1b75bfce2e2622817ffd384 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 2 Jul 2022 17:24:52 -0400 Subject: [PATCH 311/587] #72 unit overview layout completed --- coordinator/startup.lua | 2 +- coordinator/ui/components/boiler.lua | 10 ++-- coordinator/ui/components/turbine.lua | 32 +++++++++++++ coordinator/ui/components/unit_overview.lua | 51 ++++++++++++++++++--- coordinator/ui/layout/main_view.lua | 5 +- coordinator/ui/style.lua | 22 +++++++++ 6 files changed, 108 insertions(+), 14 deletions(-) create mode 100644 coordinator/ui/components/turbine.lua diff --git a/coordinator/startup.lua b/coordinator/startup.lua index f647f62..513a326 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -12,7 +12,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.2.2" +local COORDINATOR_VERSION = "alpha-v0.2.3" local print = util.print local println = util.println diff --git a/coordinator/ui/components/boiler.lua b/coordinator/ui/components/boiler.lua index e1e35d9..204c317 100644 --- a/coordinator/ui/components/boiler.lua +++ b/coordinator/ui/components/boiler.lua @@ -15,7 +15,7 @@ local cpair = core.graphics.cpair local border = core.graphics.border local function new_view(root, x, y) - local boiler = Rectangle{parent=root,border=border(1, colors.gray, true),width=31,height=8,x=x,y=y} + local boiler = Rectangle{parent=root,border=border(1, colors.gray, true),width=31,height=7,x=x,y=y} local text_fg_bg = cpair(colors.black, colors.lightGray) local lu_col = cpair(colors.gray, colors.gray) @@ -24,10 +24,10 @@ local function new_view(root, x, y) local temp = DataIndicator{parent=boiler,x=5,y=3,lu_colors=lu_col,label="Temp:",unit="K",format="%10.2f",value=1900,width=22,fg_bg=text_fg_bg} local boil_r = DataIndicator{parent=boiler,x=5,y=4,lu_colors=lu_col,label="Boil:",unit="mB/t",format="%10.0f",value=801523,commas=true,width=22,fg_bg=text_fg_bg} - TextBox{parent=boiler,text="H",x=2,y=6,height=1,width=1,fg_bg=text_fg_bg} - TextBox{parent=boiler,text="W",x=3,y=6,height=1,width=1,fg_bg=text_fg_bg} - TextBox{parent=boiler,text="S",x=27,y=6,height=1,width=1,fg_bg=text_fg_bg} - TextBox{parent=boiler,text="C",x=28,y=6,height=1,width=1,fg_bg=text_fg_bg} + TextBox{parent=boiler,text="H",x=2,y=5,height=1,width=1,fg_bg=text_fg_bg} + TextBox{parent=boiler,text="W",x=3,y=5,height=1,width=1,fg_bg=text_fg_bg} + TextBox{parent=boiler,text="S",x=27,y=5,height=1,width=1,fg_bg=text_fg_bg} + TextBox{parent=boiler,text="C",x=28,y=5,height=1,width=1,fg_bg=text_fg_bg} local hcool = VerticalBar{parent=boiler,x=2,y=1,fg_bg=cpair(colors.orange,colors.gray),height=4,width=1} local water = VerticalBar{parent=boiler,x=3,y=1,fg_bg=cpair(colors.blue,colors.gray),height=4,width=1} diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua new file mode 100644 index 0000000..b4ad063 --- /dev/null +++ b/coordinator/ui/components/turbine.lua @@ -0,0 +1,32 @@ +local core = require("graphics.core") + +local style = require("coordinator.ui.style") + +local Div = require("graphics.elements.div") +local DataIndicator = require("graphics.elements.indicators.data") +local StateIndicator = require("graphics.elements.indicators.state") +local Rectangle = require("graphics.elements.rectangle") +local TextBox = require("graphics.elements.textbox") +local VerticalBar = require("graphics.elements.indicators.vbar") + +local TEXT_ALIGN = core.graphics.TEXT_ALIGN + +local cpair = core.graphics.cpair +local border = core.graphics.border + +local function new_view(root, x, y) + local turbine = Rectangle{parent=root,border=border(1, colors.gray, true),width=23,height=7,x=x,y=y} + + local text_fg_bg = cpair(colors.black, colors.lightGray) + local lu_col = cpair(colors.gray, colors.gray) + + local status = StateIndicator{parent=turbine,x=8,y=1,states=style.turbine.states,value=3,min_width=10} + local production = DataIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",unit="MFE",format="%10.2f",value=3.2,width=16,fg_bg=text_fg_bg} + local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=801523,commas=true,width=16,fg_bg=text_fg_bg} + + local steam = VerticalBar{parent=turbine,x=2,y=1,fg_bg=cpair(colors.white,colors.gray),height=5,width=2} + + steam.update(0.12) +end + +return new_view diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index c6ff25e..6de0502 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -4,6 +4,7 @@ local style = require("coordinator.ui.style") local reactor_view = require("coordinator.ui.components.reactor") local boiler_view = require("coordinator.ui.components.boiler") +local turbine_view = require("coordinator.ui.components.turbine") local Div = require("graphics.elements.div") local PipeNetwork = require("graphics.elements.pipenet") @@ -23,7 +24,7 @@ local pipe = core.graphics.pipe ---@param parent graphics_element local function make(parent, x, y, unit_id) -- bounding box div - local root = Div{parent=parent,x=x,y=y,width=75,height=50} + local root = Div{parent=parent,x=x,y=y,width=80,height=27}--,fg_bg=cpair(colors.white,colors.black)} -- unit header message TextBox{parent=root,text="Unit #" .. unit_id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} @@ -34,21 +35,57 @@ local function make(parent, x, y, unit_id) reactor_view(root, 1, 3) - local pipes = { - pipe(0, 0, 13, 12, colors.lightBlue), - pipe(0, 0, 13, 3, colors.lightBlue), + local coolant_pipes = { + pipe(0, 0, 12, 12, colors.lightBlue), + pipe(0, 0, 12, 3, colors.lightBlue), pipe(2, 0, 11, 2, colors.orange), pipe(2, 0, 11, 11, colors.orange) } - PipeNetwork{parent=root,x=12,y=10,pipes=pipes,bg=colors.lightGray} + PipeNetwork{parent=root,x=4,y=10,pipes=coolant_pipes,bg=colors.lightGray} ------------- -- BOILERS -- ------------- - boiler_view(root, 23, 11) - boiler_view(root, 23, 20) + boiler_view(root, 16, 11) + boiler_view(root, 16, 20) + + -------------- + -- TURBINES -- + -------------- + + turbine_view(root, 58, 3) + turbine_view(root, 58, 11) + turbine_view(root, 58, 20) + + local steam_pipes_a = { + -- boiler 1 + pipe(0, 1, 6, 1, colors.white, false, true), + pipe(0, 2, 6, 2, colors.blue, false, true), + -- boiler 2 + pipe(0, 10, 6, 10, colors.white, false, true), + pipe(0, 11, 6, 11, colors.blue, false, true) + } + + local steam_pipes_b = { + -- turbines 1 & 2, pipes from boiler 1 + pipe(0, 9, 1, 2, colors.white, false, true), + pipe(1, 1, 3, 1, colors.white, false, false), + pipe(0, 9, 3, 9, colors.white, false, true), + pipe(0, 10, 2, 3, colors.blue, false, true), + pipe(2, 2, 3, 2, colors.blue, false, false), + pipe(0, 10, 3, 10, colors.blue, false, true), + -- turbine 3, pipes from boiler 2 + pipe(0, 18, 1, 9, colors.white, false, true), + pipe(1, 1, 3, 1, colors.white, false, false), + pipe(0, 18, 3, 18, colors.white, false, true), + pipe(0, 19, 2, 10, colors.blue, false, true), + pipe(0, 19, 3, 19, colors.blue, false, true), + } + + PipeNetwork{parent=root,x=47,y=11,pipes=steam_pipes_a,bg=colors.lightGray} + PipeNetwork{parent=root,x=54,y=3,pipes=steam_pipes_b,bg=colors.lightGray} end diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index aa46efa..e383f3d 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -21,7 +21,10 @@ local function init(monitor) TextBox{parent=main,text="Nuclear Generation Facility SCADA Coordinator",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} -- unit overviews - unit_overview(main, 3, 3, 1) + unit_overview(main, 2, 3, 1) + unit_overview(main, 84, 3, 2) + unit_overview(main, 2, 32, 3) + unit_overview(main, 84, 32, 4) return main end diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index 363d758..772a0a7 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -50,4 +50,26 @@ style.boiler = { } } +style.turbine = { + -- turbine states + states = { + { + color = cpair(colors.black, colors.yellow), + text = "OFF-LINE" + }, + { + color = cpair(colors.white, colors.gray), + text = "IDLE" + }, + { + color = cpair(colors.black, colors.green), + text = "ACTIVE" + }, + { + color = cpair(colors.black, colors.red), + text = "TRIP" + } + } +} + return style From ed0982a8325c9cf80489216847a312269564890f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Jul 2022 11:18:07 -0400 Subject: [PATCH 312/587] handle nil tag color --- scada-common/log.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scada-common/log.lua b/scada-common/log.lua index 0c6ae89..7689340 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -150,10 +150,10 @@ function log.dmesg(msg, tag, tag_color) out.setTextColor(colors.white) out.write("] ") - -- colored tag + -- print optionally colored tag if tag ~= "" then out.write("[") - out.setTextColor(tag_color) + if tag_color then out.setTextColor(tag_color) end out.write(tag) out.setTextColor(colors.white) out.write("] ") From f6708ca98822caaf9771a0288c4002c1d5cae7ce Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Jul 2022 11:18:26 -0400 Subject: [PATCH 313/587] coordinator dmesg wrapper functions --- coordinator/coordinator.lua | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index c917681..42fd39e 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -174,6 +174,25 @@ function coordinator.init_database(num_units) end end +-- dmesg print wrapper +---@param message string message +---@param dmesg_tag string tag +local function log_dmesg(message, dmesg_tag) + local colors = { + GRAPHICS = colors.green, + SYSTEM = colors.cyan, + BOOT = colors.blue, + COMMS = colors.purple + } + + log.dmesg(message, dmesg_tag, colors[dmesg_tag]) +end + +function coordinator.log_graphics(message) log_dmesg(message, "GRAPHICS") end +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 + -- coordinator communications ---@param version string ---@param modem table @@ -181,7 +200,7 @@ end ---@param sv_listen integer ---@param api_listen integer ---@param sv_watchdog watchdog -function coordinator.coord_comms(version, modem, sv_port, sv_listen, api_listen, sv_watchdog) +function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_watchdog) local self = { sv_seq_num = 0, sv_r_seq_num = nil, From bd332405159811aa73ec97426540715b4b1f6d52 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Jul 2022 12:46:31 -0400 Subject: [PATCH 314/587] #62 modifing color palette --- coordinator/renderer.lua | 32 ++++++++++++++++++++++++++------ coordinator/ui/style.lua | 15 ++++++++++++++- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 8a3df7b..cc4f0df 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -5,6 +5,7 @@ local core = require("graphics.core") local main_view = require("coordinator.ui.layout.main_view") local unit_view = require("coordinator.ui.layout.unit_view") +local style = require("coordinator.ui.style") local renderer = {} @@ -21,12 +22,29 @@ local ui = { } -- reset a display to the "default", but set text scale to 0.5 -local function _reset_display(monitor) +---@param monitor table monitor +---@param recolor? boolean override default color palette +local function _reset_display(monitor, recolor) monitor.setTextScale(0.5) monitor.setTextColor(colors.white) monitor.setBackgroundColor(colors.black) monitor.clear() monitor.setCursorPos(1, 1) + + if recolor then + -- set overridden colors + for i = 1, #style.colors do + monitor.setPaletteColor(style.colors[i].c, style.colors[i].hex) + end + else + -- reset all colors + for _, val in colors do + -- colors api has constants and functions, just get color constants + if type(val) == "number" then + monitor.setPaletteColor(val, term.nativePaletteColor(val)) + end + end + end end -- link to the monitor peripherals @@ -36,13 +54,14 @@ function renderer.set_displays(monitors) end -- reset all displays in use by the renderer -function renderer.reset() +---@param recolor? boolean true to use color palette from style +function renderer.reset(recolor) -- reset primary monitor - _reset_display(engine.monitors.primary) + _reset_display(engine.monitors.primary, recolor) -- reset unit displays for _, monitor in pairs(engine.monitors.unit_displays) do - _reset_display(monitor) + _reset_display(monitor, recolor) end end @@ -69,13 +88,14 @@ function renderer.start_ui() end -- close out the UI -function renderer.close_ui() +---@param recolor? boolean true to restore to color palette from style +function renderer.close_ui(recolor) -- clear root UI elements ui.main_layout = nil ui.unit_layouts = {} -- reset displays - renderer.reset() + renderer.reset(recolor) -- re-draw dmesg engine.dmesg_window.setVisible(true) diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index 772a0a7..400c6db 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -5,11 +5,24 @@ local style = {} local cpair = core.graphics.cpair --- MAIN LAYOUT -- +-- GLOBAL -- style.root = cpair(colors.black, colors.lightGray) style.header = cpair(colors.white, colors.gray) +style.colors = { + { + c = colors.green, + hex = 0x7ed788 + }, + { + c = colors.lightGray, + hex = 0xcacaca + } +} + +-- MAIN LAYOUT -- + style.reactor = { -- reactor states states = { From 33159bc6779bb04349ead82526b1f357a944d9e3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Jul 2022 12:47:02 -0400 Subject: [PATCH 315/587] main loop and work on #74 comms --- coordinator/config.lua | 2 + coordinator/startup.lua | 139 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 130 insertions(+), 11 deletions(-) diff --git a/coordinator/config.lua b/coordinator/config.lua index 57cabf2..12dbeab 100644 --- a/coordinator/config.lua +++ b/coordinator/config.lua @@ -8,6 +8,8 @@ config.SCADA_SV_LISTEN = 16101 config.SCADA_API_LISTEN = 16200 -- expected number of reactor units config.NUM_UNITS = 4 +-- graphics color +config.RECOLOR = true -- log path config.LOG_PATH = "/log.txt" -- log mode diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 513a326..ee0dda4 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -8,17 +8,23 @@ local log = require("scada-common.log") local ppm = require("scada-common.ppm") local util = require("scada-common.util") +local apisessions = require("coordinator.apisessions") local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.2.3" +local COORDINATOR_VERSION = "alpha-v0.2.4" local print = util.print local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts +local log_graphics = coordinator.log_graphics +local log_sys = coordinator.log_sys +local log_boot = coordinator.log_boot +local log_comms = coordinator.log_comms + ---------------------------------------- -- config validation ---------------------------------------- @@ -29,6 +35,7 @@ cfv.assert_port(config.SCADA_SV_PORT) cfv.assert_port(config.SCADA_SV_LISTEN) cfv.assert_port(config.SCADA_API_LISTEN) cfv.assert_type_int(config.NUM_UNITS) +cfv.assert_type_bool(config.RECOLOR) cfv.assert_type_str(config.LOG_PATH) cfv.assert_type_int(config.LOG_MODE) cfv.assert_type_bool(config.SECURE) @@ -65,38 +72,148 @@ log.info("monitors ready, dmesg output incoming...") -- init renderer renderer.set_displays(monitors) -renderer.reset() +renderer.reset(config.RECOLOR) renderer.init_dmesg() -log.dmesg("displays connected and reset", "GRAPHICS", colors.green) -log.dmesg("system start on " .. os.date("%c"), "SYSTEM", colors.cyan) -log.dmesg("starting " .. COORDINATOR_VERSION, "BOOT", colors.blue) +log_graphics("displays connected and reset") +log_sys("system start on " .. os.date("%c")) +log_boot("starting " .. COORDINATOR_VERSION) -- get the communications modem local modem = ppm.get_wireless_modem() if modem == nil then + 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 -log.dmesg("wireless modem connected", "COMMS", colors.purple) +-- create connection watchdog +local conn_watchdog = util.new_watchdog(5) +conn_watchdog.cancel() +log.debug("boot> conn watchdog created") +-- 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 (6.67Hz, 3 ticks) +local MAIN_CLOCK = 0.15 +local loop_clock = util.new_clock(MAIN_CLOCK) + +---------------------------------------- -- start the UI +---------------------------------------- -log.dmesg("starting UI...", "GRAPHICS", colors.green) +log_graphics("starting UI...") -- util.psleep(3) +local draw_start = util.time_ms() + local ui_ok, message = pcall(renderer.start_ui) if not ui_ok then - renderer.close_ui() + renderer.close_ui(config.RECOLOR) + log_graphics(util.c("UI crashed: ", message)) println_ts("UI crashed") - log.dmesg(util.c("UI crashed: ", message), "GRAPHICS", colors.green) log.fatal(util.c("ui crashed with error ", message)) +else + log_graphics("first UI draw took " .. (util.time_ms() - draw_start) .. "ms") + + -- start clock + loop_clock.start() end --- renderer.close_ui() --- log.dmesg("system shutdown", "SYSTEM", colors.cyan) +---------------------------------------- +-- main event loop +---------------------------------------- + +-- start connection watchdog +conn_watchdog.feed() +log.debug("boot> conn watchdog started") + +-- event loop +-- ui_ok will never change in this loop, same as while true or exit if UI start failed +while ui_ok do +---@diagnostic disable-next-line: undefined-field + local event, param1, param2, param3, param4, param5 = os.pullEventRaw() + + -- 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 + log_sys("comms modem disconnected") + println_ts("wireless modem disconnected!") + log.error("comms modem disconnected!") + else + log_sys("non-comms modem disconnected") + log.warning("non-comms modem disconnected") + end + elseif type == "monitor" then + -- @todo: handle monitor loss + 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 + coord_comms.reconnect_modem(modem) + + log_sys("comms modem reconnected") + println_ts("wireless modem reconnected.") + else + log_sys("wired modem reconnected") + end + elseif type == "monitor" then + -- @todo: handle monitor reconnect + 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) + else + -- a non-clock/main watchdog timer event, check API watchdogs + --apisessions.check_all_watchdogs(param1) + 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) + end + + -- check for termination request + if event == "terminate" or ppm.should_terminate() then + log_comms("terminate requested, closing sessions...") + println_ts("closing sessions...") + apisessions.close_all() + log_comms("api sessions closed") + break + end +end + +renderer.close_ui(config.RECOLOR) +log_sys("system shutdown") println_ts("exited") log.info("exited") From 9bd220cbb2ea826beface1c9d613dbe2249544dd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Jul 2022 12:48:21 -0400 Subject: [PATCH 316/587] removed unused requires --- coordinator/renderer.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index cc4f0df..59fafcf 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -1,7 +1,4 @@ local log = require("scada-common.log") -local util = require("scada-common.util") - -local core = require("graphics.core") local main_view = require("coordinator.ui.layout.main_view") local unit_view = require("coordinator.ui.layout.unit_view") From 335e0f5ee9c7140f0e93bb6943ebe11876d20331 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Jul 2022 12:49:46 -0400 Subject: [PATCH 317/587] gitignore for notes directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..200fed8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +_notes/ From 409e8083a702801d5282732d5aca9be30ad53916 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Jul 2022 23:47:13 -0400 Subject: [PATCH 318/587] dmesg working status animation --- scada-common/log.lua | 70 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/scada-common/log.lua b/scada-common/log.lua index 7689340..1e91bb6 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -94,7 +94,11 @@ end ---@param msg string message ---@param tag? string log tag ---@param tag_color? integer log tag color +---@return dmesg_ts_coord coordinates line area to place working indicator function log.dmesg(msg, tag, tag_color) + ---@class dmesg_ts_coord + local ts_coord = { x1 = 2, x2 = 3, y = 1 } + msg = util.strval(msg) tag = tag or "" tag = util.strval(tag) @@ -147,6 +151,8 @@ function log.dmesg(msg, tag, tag_color) out.write("[") out.setTextColor(colors.lightGray) out.write(t_stamp) + ts_coord.x2, ts_coord.y = out.getCursorPos() + ts_coord.x2 = ts_coord.x2 - 1 out.setTextColor(colors.white) out.write("] ") @@ -178,6 +184,70 @@ function log.dmesg(msg, tag, tag_color) end _log(util.c("[", t_stamp, "] ", tag, " ", msg)) + + return ts_coord +end + +-- print a dmesg message, but then show remaining seconds instead of timestamp +---@param msg string message +---@param tag? string log tag +---@param tag_color? integer log tag color +---@return function update, function done +function log.dmesg_working(msg, tag, tag_color) + local ts_coord = log.dmesg(msg, tag, tag_color) + + local out = _log_sys.dmesg_out + local width = (ts_coord.x2 - ts_coord.x1) + 1 + + local initial_color = out.getTextColor() + + local counter = 0 + + local function update(sec_remaining) + local time = util.sprintf("%ds", sec_remaining) + local available = width - (string.len(time) + 2) + local progress = "" + + out.setCursorPos(ts_coord.x1, ts_coord.y) + out.write(" ") + + if counter % 4 == 0 then + progress = "|" + elseif counter % 4 == 1 then + progress = "/" + elseif counter % 4 == 2 then + progress = "-" + elseif counter % 4 == 3 then + progress = "\\" + end + + out.setTextColor(colors.blue) + out.write(progress) + out.setTextColor(colors.lightGray) + out.write(util.spaces(available) .. time) + out.setTextColor(initial_color) + + counter = counter + 1 + end + + local function done(ok) + local lpad = math.max(math.floor((width - 4) / 2), 0) + local rpad = (width - 4) - lpad + + out.setCursorPos(ts_coord.x1, ts_coord.y) + + if ok or ok == nil then + out.setTextColor(colors.green) + out.write(util.spaces(lpad) .. "DONE" .. util.spaces(rpad)) + else + out.setTextColor(colors.red) + out.write(util.spaces(lpad) .. "FAIL" .. util.spaces(rpad)) + end + + out.setTextColor(initial_color) + end + + return update, done end -- log debug messages From 1444008479148c37197db24fa30e92eaefdc27f0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Jul 2022 23:48:01 -0400 Subject: [PATCH 319/587] #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 From 39672fedb43e07509228eb2beff00a654b354d5e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 5 Jul 2022 23:49:48 -0400 Subject: [PATCH 320/587] code cleanup --- coordinator/coordinator.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 16aa3e3..4954323 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -272,14 +272,14 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa 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) + task_done(self.sv_linked) + + if terminated then + coordinator.log_comms("supervisor connection attempt cancelled by user") end return self.sv_linked From ea17ba41febb7c1288a6494fa75ed5cb2f2529a6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 7 Jul 2022 00:34:42 -0400 Subject: [PATCH 321/587] #74 supervisor-coordinator comms establish --- coordinator/apisessions.lua | 9 ++ coordinator/coordinator.lua | 71 +++++++--- coordinator/startup.lua | 2 +- graphics/element.lua | 5 - scada-common/comms.lua | 40 +++--- scada-common/log.lua | 2 +- scada-common/mqueue.lua | 2 +- supervisor/session/coordinator.lua | 207 +++++++++++++++++++++++++++++ supervisor/session/svsessions.lua | 28 ++++ supervisor/startup.lua | 5 +- supervisor/supervisor.lua | 72 ++++++++-- 11 files changed, 379 insertions(+), 64 deletions(-) diff --git a/coordinator/apisessions.lua b/coordinator/apisessions.lua index 2141e77..3c14c08 100644 --- a/coordinator/apisessions.lua +++ b/coordinator/apisessions.lua @@ -4,4 +4,13 @@ local apisessions = {} function apisessions.handle_packet(packet) end +function apisessions.check_all_watchdogs() +end + +function apisessions.close_all() +end + +function apisessions.free_all_closed() +end + return apisessions diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 4954323..78e6a1d 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -17,7 +17,7 @@ local println_ts = util.println_ts local PROTOCOLS = comms.PROTOCOLS local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES -local COORD_TYPES = comms.COORD_TYPES +local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES -- request the user to select a monitor ---@param names table available monitors @@ -209,16 +209,16 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa _open_channels() -- send a packet to the supervisor - ---@param msg_type SCADA_MGMT_TYPES|COORD_TYPES + ---@param msg_type SCADA_MGMT_TYPES|SCADA_CRDN_TYPES ---@param msg table local function _send_sv(protocol, msg_type, msg) local s_pkt = comms.scada_packet() - local pkt = nil ---@type mgmt_packet|coord_packet + local pkt = nil ---@type mgmt_packet|crdn_packet if protocol == PROTOCOLS.SCADA_MGMT then pkt = comms.mgmt_packet() - elseif protocol == PROTOCOLS.COORD_DATA then - pkt = comms.coord_packet() + elseif protocol == PROTOCOLS.SCADA_CRDN then + pkt = comms.crdn_packet() else return end @@ -230,6 +230,17 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa self.sv_seq_num = self.sv_seq_num + 1 end + -- attempt connection establishment + local function _send_establish() + _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.ESTABLISH, { version }) + end + + -- keep alive ack + ---@param srv_time integer + local function _send_keep_alive_ack(srv_time) + _send_sv(PROTOCOLS.SCADA_MGMT, SCADA_MGMT_TYPES.KEEP_ALIVE, { srv_time, util.time() }) + end + -- PUBLIC FUNCTIONS -- -- reconnect a newly connected modem @@ -251,7 +262,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa local start = util.time_s() local terminated = false - _send_sv(PROTOCOLS.COORD_DATA, COORD_TYPES.ESTABLISH, {}) + _send_establish() clock.start() @@ -262,12 +273,12 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa 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, {}) + _send_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 + if packet ~= nil and packet.type == SCADA_CRDN_TYPES.ESTABLISH then public.handle_packet(packet) end elseif event == "terminate" then @@ -291,7 +302,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa ---@param reply_to integer ---@param message any ---@param distance integer - ---@return mgmt_frame|coord_frame|capi_frame|nil packet + ---@return mgmt_frame|crdn_frame|capi_frame|nil packet function public.parse_packet(side, sender, reply_to, message, distance) local pkt = nil local s_pkt = comms.scada_packet() @@ -307,10 +318,10 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa pkt = mgmt_pkt.get() end -- get as coordinator packet - elseif s_pkt.protocol() == PROTOCOLS.COORD_DATA then - local coord_pkt = comms.coord_packet() - if coord_pkt.decode(s_pkt) then - pkt = coord_pkt.get() + elseif s_pkt.protocol() == PROTOCOLS.SCADA_CRDN then + local crdn_pkt = comms.crdn_packet() + if crdn_pkt.decode(s_pkt) then + pkt = crdn_pkt.get() end -- get as coordinator API packet elseif s_pkt.protocol() == PROTOCOLS.COORD_API then @@ -327,7 +338,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa end -- handle a packet - ---@param packet mgmt_frame|coord_frame|capi_frame + ---@param packet mgmt_frame|crdn_frame|capi_frame function public.handle_packet(packet) if packet ~= nil then local protocol = packet.scada_frame.protocol() @@ -349,8 +360,8 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa sv_watchdog.feed() -- handle packet - if protocol == PROTOCOLS.COORD_DATA then - if packet.type == COORD_TYPES.ESTABLISH then + if protocol == PROTOCOLS.SCADA_CRDN then + if packet.type == SCADA_CRDN_TYPES.ESTABLISH then -- connection with supervisor established if packet.length > 1 then -- get configuration @@ -369,22 +380,38 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa -- init database structure database.init(conf) + + self.sv_linked = true 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 - elseif packet.type == COORD_TYPES.ALARM then + elseif packet.type == SCADA_CRDN_TYPES.QUERY_UNIT then + elseif packet.type == SCADA_CRDN_TYPES.QUERY_FACILITY then + elseif packet.type == SCADA_CRDN_TYPES.COMMAND_UNIT then + elseif packet.type == SCADA_CRDN_TYPES.ALARM then else - log.warning("received unknown COORD_DATA packet type " .. packet.type) + log.warning("received unknown SCADA_CRDN packet type " .. packet.type) end elseif protocol == PROTOCOLS.SCADA_MGMT then if packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then - -- keep alive response received + -- keep alive request received, echo back + if packet.length == 1 then + local timestamp = packet.data[1] + local trip_time = util.time() - timestamp + + if trip_time > 500 then + log.warning("coord KEEP_ALIVE trip time > 500ms (" .. trip_time .. "ms)") + end + + -- log.debug("coord RTT = " .. trip_time .. "ms") + + _send_keep_alive_ack(timestamp) + else + log.debug("SCADA keep alive packet length mismatch") + end elseif packet.type == SCADA_MGMT_TYPES.CLOSE then -- handle session close sv_watchdog.cancel() diff --git a/coordinator/startup.lua b/coordinator/startup.lua index de36d5b..e6cadc9 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.3.0" +local COORDINATOR_VERSION = "alpha-v0.3.2" local print = util.print local println = util.println diff --git a/graphics/element.lua b/graphics/element.lua index 268dcb7..5e58ec1 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -3,8 +3,6 @@ -- local core = require("graphics.core") -local log = require("scada-common.log") -local util = require("scada-common.util") local element = {} @@ -31,9 +29,6 @@ function element.new(args) bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1} } - ---@fixme remove debug - log.dmesg("new " .. self.elem_type) - local protected = { window = nil, ---@type table fg_bg = core.graphics.cpair(colors.white, colors.black), diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 1943baa..3d18d7f 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -16,7 +16,7 @@ local PROTOCOLS = { MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol RPLC = 1, -- reactor PLC protocol SCADA_MGMT = 2, -- SCADA supervisor management, device advertisements, etc - COORD_DATA = 3, -- data/control packets for coordinators to/from supervisory controllers + SCADA_CRDN = 3, -- data/control packets for coordinators to/from supervisory controllers COORD_API = 4 -- data/control packets for pocket computers to/from coordinators } @@ -48,8 +48,8 @@ local SCADA_MGMT_TYPES = { REMOTE_LINKED = 3 -- remote device linked } ----@alias COORD_TYPES integer -local COORD_TYPES = { +---@alias SCADA_CRDN_TYPES integer +local SCADA_CRDN_TYPES = { ESTABLISH = 0, -- initial greeting QUERY_UNIT = 1, -- query the state of a unit QUERY_FACILITY = 2, -- query general facility status @@ -80,7 +80,7 @@ comms.PROTOCOLS = PROTOCOLS comms.RPLC_TYPES = RPLC_TYPES comms.RPLC_LINKING = RPLC_LINKING comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES -comms.COORD_TYPES = COORD_TYPES +comms.SCADA_CRDN_TYPES = SCADA_CRDN_TYPES comms.RTU_UNIT_TYPES = RTU_UNIT_TYPES -- generic SCADA packet object @@ -438,7 +438,7 @@ function comms.mgmt_packet() end -- SCADA coordinator packet -function comms.coord_packet() +function comms.crdn_packet() local self = { frame = nil, raw = nil, @@ -447,20 +447,20 @@ function comms.coord_packet() data = nil } - ---@class coord_packet + ---@class crdn_packet local public = {} -- check that type is known - local function _coord_type_valid() - return self.type == COORD_TYPES.ESTABLISH or - self.type == COORD_TYPES.QUERY_UNIT or - self.type == COORD_TYPES.QUERY_FACILITY or - self.type == COORD_TYPES.COMMAND_UNIT or - self.type == COORD_TYPES.ALARM + local function _crdn_type_valid() + return self.type == SCADA_CRDN_TYPES.ESTABLISH or + self.type == SCADA_CRDN_TYPES.QUERY_UNIT or + self.type == SCADA_CRDN_TYPES.QUERY_FACILITY or + self.type == SCADA_CRDN_TYPES.COMMAND_UNIT or + self.type == SCADA_CRDN_TYPES.ALARM end -- make a coordinator packet - ---@param packet_type COORD_TYPES + ---@param packet_type SCADA_CRDN_TYPES ---@param data table function public.make(packet_type, data) if type(data) == "table" then @@ -475,7 +475,7 @@ function comms.coord_packet() insert(self.raw, data[i]) end else - log.error("comms.coord_packet.make(): data not table") + log.error("comms.crdn_packet.make(): data not table") end end @@ -486,18 +486,18 @@ function comms.coord_packet() if frame then self.frame = frame - if frame.protocol() == PROTOCOLS.COORD_DATA then + if frame.protocol() == PROTOCOLS.SCADA_CRDN then local ok = frame.length() >= 1 if ok then local data = frame.data() public.make(data[1], { table.unpack(data, 2, #data) }) - ok = _coord_type_valid() + ok = _crdn_type_valid() end return ok else - log.debug("attempted COORD_DATA parse of incorrect protocol " .. frame.protocol(), true) + log.debug("attempted SCADA_CRDN parse of incorrect protocol " .. frame.protocol(), true) return false end else @@ -511,7 +511,7 @@ function comms.coord_packet() -- get this packet as a frame with an immutable relation to this object function public.get() - ---@class coord_frame + ---@class crdn_frame local frame = { scada_frame = self.frame, type = self.type, @@ -539,7 +539,7 @@ function comms.capi_packet() ---@class capi_packet local public = {} - local function _coord_type_valid() + local function _capi_type_valid() -- @todo return false end @@ -577,7 +577,7 @@ function comms.capi_packet() if ok then local data = frame.data() public.make(data[1], { table.unpack(data, 2, #data) }) - ok = _coord_type_valid() + ok = _capi_type_valid() end return ok diff --git a/scada-common/log.lua b/scada-common/log.lua index 1e91bb6..08aa8c8 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -183,7 +183,7 @@ function log.dmesg(msg, tag, tag_color) out.write(lines[i]) end - _log(util.c("[", t_stamp, "] ", tag, " ", msg)) + _log(util.c("[", t_stamp, "] [", tag, "] ", msg)) return ts_coord end diff --git a/scada-common/mqueue.lua b/scada-common/mqueue.lua index fd80cfa..ed22535 100644 --- a/scada-common/mqueue.lua +++ b/scada-common/mqueue.lua @@ -62,7 +62,7 @@ function mqueue.new() end -- push a packet onto the queue - ---@param packet scada_packet|modbus_packet|rplc_packet|coord_packet|capi_packet + ---@param packet scada_packet|modbus_packet|rplc_packet|crdn_packet|capi_packet function public.push_packet(packet) _push(TYPE.PACKET, packet) end diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index afa28c0..4920b55 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -1,3 +1,210 @@ +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local util = require("scada-common.util") + local coordinator = {} +local PROTOCOLS = comms.PROTOCOLS +local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES +local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES + +local print = util.print +local println = util.println +local print_ts = util.print_ts +local println_ts = util.println_ts + +local PERIODICS = { + KEEP_ALIVE = 2.0 +} + +-- coordinator supervisor session +---@param id integer +---@param in_queue mqueue +---@param out_queue mqueue +function coordinator.new_session(id, in_queue, out_queue) + local log_header = "crdn_session(" .. id .. "): " + + local self = { + id = id, + in_q = in_queue, + out_q = out_queue, + -- connection properties + seq_num = 0, + r_seq_num = nil, + connected = true, + conn_watchdog = util.new_watchdog(3), + last_rtt = 0, + -- periodic messages + periodics = { + last_update = 0, + keep_alive = 0 + } + } + + -- mark this coordinator session as closed, stop watchdog + local function _close() + self.conn_watchdog.cancel() + self.connected = false + end + + -- send a CRDN packet + ---@param msg_type SCADA_CRDN_TYPES + ---@param msg table + local function _send(msg_type, msg) + local s_pkt = comms.scada_packet() + local c_pkt = comms.crdn_packet() + + c_pkt.make(msg_type, msg) + s_pkt.make(self.seq_num, PROTOCOLS.SCADA_CRDN, c_pkt.raw_sendable()) + + self.out_q.push_packet(s_pkt) + self.seq_num = self.seq_num + 1 + end + + -- send a SCADA management packet + ---@param msg_type SCADA_MGMT_TYPES + ---@param msg table + local function _send_mgmt(msg_type, msg) + local s_pkt = comms.scada_packet() + local m_pkt = comms.mgmt_packet() + + m_pkt.make(msg_type, msg) + s_pkt.make(self.seq_num, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) + + self.out_q.push_packet(s_pkt) + self.seq_num = self.seq_num + 1 + end + + -- handle a packet + ---@param pkt crdn_frame + local function _handle_packet(pkt) + -- check sequence number + if self.r_seq_num == nil then + self.r_seq_num = pkt.scada_frame.seq_num() + elseif self.r_seq_num >= pkt.scada_frame.seq_num() then + log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + return + else + self.r_seq_num = pkt.scada_frame.seq_num() + end + + -- feed watchdog + self.conn_watchdog.feed() + + -- process packet + if pkt.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then + if pkt.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + -- keep alive reply + if pkt.length == 2 then + local srv_start = pkt.data[1] + local coord_send = pkt.data[2] + local srv_now = util.time() + self.last_rtt = srv_now - srv_start + + if self.last_rtt > 500 then + log.warning(log_header .. "COORD KEEP_ALIVE round trip time > 500ms (" .. self.last_rtt .. "ms)") + end + + log.debug(log_header .. "COORD RTT = " .. self.last_rtt .. "ms") + log.debug(log_header .. "COORD TT = " .. (srv_now - coord_send) .. "ms") + else + log.debug(log_header .. "SCADA keep alive packet length mismatch") + end + elseif pkt.type == SCADA_MGMT_TYPES.CLOSE then + -- close the session + _close() + else + log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) + end + elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_CRDN then + if pkt.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + else + end + end + end + + ---@class coord_session + local public = {} + + -- get the session ID + function public.get_id() return self.id end + + -- check if a timer matches this session's watchdog + function public.check_wd(timer) + return self.conn_watchdog.is_timer(timer) and self.connected + end + + -- close the connection + function public.close() + _close() + _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) + println("connection to coordinator #" .. self.id .. " closed by server") + log.info(log_header .. "session closed by server") + end + + -- iterate the session + ---@return boolean connected + function public.iterate() + if self.connected then + ------------------ + -- handle queue -- + ------------------ + + local handle_start = util.time() + + while self.in_q.ready() and self.connected do + -- get a new message to process + local message = self.in_q.pop() + + if message ~= nil then + if message.qtype == mqueue.TYPE.PACKET then + -- handle a packet + _handle_packet(message.message) + elseif message.qtype == mqueue.TYPE.COMMAND then + -- handle instruction + elseif message.qtype == mqueue.TYPE.DATA then + -- instruction with body + end + end + + -- max 100ms spent processing queue + if util.time() - handle_start > 100 then + log.warning(log_header .. "exceeded 100ms queue process limit") + break + end + end + + -- exit if connection was closed + if not self.connected then + println("connection to coordinator " .. self.id .. " closed by remote host") + log.info(log_header .. "session closed by remote host") + return self.connected + end + + ---------------------- + -- update periodics -- + ---------------------- + + local elapsed = util.time() - self.periodics.last_update + + local periodics = self.periodics + + -- keep alive + + periodics.keep_alive = periodics.keep_alive + elapsed + if periodics.keep_alive >= PERIODICS.KEEP_ALIVE then + _send_mgmt(SCADA_MGMT_TYPES.KEEP_ALIVE, { util.time() }) + periodics.keep_alive = 0 + end + + self.periodics.last_update = util.time() + end + + return self.connected + end + + return public +end + return coordinator diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 1077661..3a1ac4b 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -243,6 +243,34 @@ function svsessions.establish_rtu_session(local_port, remote_port, advertisement return rtu_s.instance.get_id() end +-- establish a new coordinator session +---@param local_port integer +---@param remote_port integer +---@param version string +---@return integer|false session_id +function svsessions.establish_coord_session(local_port, remote_port, version) + ---@class coord_session_struct + local coord_s = { + open = true, + version = version, + l_port = local_port, + r_port = remote_port, + in_queue = mqueue.new(), + out_queue = mqueue.new(), + instance = nil + } + + coord_s.instance = coordinator.new_session(self.next_coord_id, coord_s.in_queue, coord_s.out_queue) + table.insert(self.coord_sessions, coord_s) + + log.debug("established new coordinator session to " .. remote_port .. " with ID " .. self.next_coord_id) + + self.next_coord_id = self.next_coord_id + 1 + + -- success + return coord_s.instance.get_id() +end + -- attempt to identify which session's watchdog timer fired ---@param timer_event number function svsessions.check_all_watchdogs(timer_event) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 54f4f54..cdf3426 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.4.14" +local SUPERVISOR_VERSION = "beta-v0.5.1" local print = util.print local println = util.println @@ -72,7 +72,8 @@ if modem == nil then end -- start comms, open all channels -local superv_comms = supervisor.comms(SUPERVISOR_VERSION, config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN) +local superv_comms = supervisor.comms(SUPERVISOR_VERSION, config.NUM_REACTORS, config.REACTOR_COOLING, modem, + config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN) -- base loop clock (6.67Hz, 3 ticks) local MAIN_CLOCK = 0.15 diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 1256dc6..617e996 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -9,8 +9,9 @@ local supervisor = {} local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES local RPLC_LINKING = comms.RPLC_LINKING -local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES +local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES local SESSION_TYPE = svsessions.SESSION_TYPE @@ -22,10 +23,11 @@ local println_ts = util.println_ts -- supervisory controller communications ---@param version string ---@param num_reactors integer +---@param cooling_conf table ---@param modem table ---@param dev_listen integer ---@param coord_listen integer -function supervisor.comms(version, num_reactors, modem, dev_listen, coord_listen) +function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen, coord_listen) local self = { version = version, num_reactors = num_reactors, @@ -57,7 +59,7 @@ function supervisor.comms(version, num_reactors, modem, dev_listen, coord_listen -- link modem to svsessions svsessions.link_modem(self.modem) - -- send PLC link request responses + -- send PLC link request response ---@param dest integer ---@param msg table local function _send_plc_linking(seq_id, dest, msg) @@ -70,7 +72,7 @@ function supervisor.comms(version, num_reactors, modem, dev_listen, coord_listen self.modem.transmit(dest, self.dev_listen, s_pkt.raw_sendable()) end - -- send RTU advertisement responses + -- send RTU advertisement response ---@param seq_id integer ---@param dest integer local function _send_remote_linked(seq_id, dest) @@ -83,6 +85,26 @@ function supervisor.comms(version, num_reactors, modem, dev_listen, coord_listen self.modem.transmit(dest, self.dev_listen, s_pkt.raw_sendable()) end + -- send coordinator connection establish response + ---@param seq_id integer + ---@param dest integer + local function _send_crdn_establish(seq_id, dest) + local s_pkt = comms.scada_packet() + local c_pkt = comms.crdn_packet() + + local config = { self.num_reactors } + + for i = 1, #cooling_conf do + table.insert(config, cooling_conf[i].BOILERS) + table.insert(config, cooling_conf[i].TURBINES) + end + + c_pkt.make(SCADA_CRDN_TYPES.ESTABLISH, config) + s_pkt.make(seq_id, PROTOCOLS.SCADA_CRDN, c_pkt.raw_sendable()) + + self.modem.transmit(dest, self.coord_listen, s_pkt.raw_sendable()) + end + -- PUBLIC FUNCTIONS -- -- reconnect a newly connected modem @@ -100,7 +122,7 @@ function supervisor.comms(version, num_reactors, modem, dev_listen, coord_listen ---@param reply_to integer ---@param message any ---@param distance integer - ---@return modbus_frame|rplc_frame|mgmt_frame|coord_frame|nil packet + ---@return modbus_frame|rplc_frame|mgmt_frame|crdn_frame|nil packet function public.parse_packet(side, sender, reply_to, message, distance) local pkt = nil local s_pkt = comms.scada_packet() @@ -128,10 +150,10 @@ function supervisor.comms(version, num_reactors, modem, dev_listen, coord_listen pkt = mgmt_pkt.get() end -- get as coordinator packet - elseif s_pkt.protocol() == PROTOCOLS.COORD_DATA then - local coord_pkt = comms.coord_packet() - if coord_pkt.decode(s_pkt) then - pkt = coord_pkt.get() + elseif s_pkt.protocol() == PROTOCOLS.SCADA_CRDN then + local crdn_pkt = comms.crdn_packet() + if crdn_pkt.decode(s_pkt) then + pkt = crdn_pkt.get() end else log.debug("attempted parse of illegal packet type " .. s_pkt.protocol(), true) @@ -142,7 +164,7 @@ function supervisor.comms(version, num_reactors, modem, dev_listen, coord_listen end -- handle a packet - ---@param packet modbus_frame|rplc_frame|mgmt_frame|coord_frame + ---@param packet modbus_frame|rplc_frame|mgmt_frame|crdn_frame function public.handle_packet(packet) if packet ~= nil then local l_port = packet.scada_frame.local_port() @@ -226,7 +248,7 @@ function supervisor.comms(version, num_reactors, modem, dev_listen, coord_listen end else -- any other packet should be session related, discard it - log.debug("discarding SCADA_MGMT packet without a known session") + log.debug(util.c(r_port, "->", l_port, ": discarding SCADA_MGMT packet without a known session")) end else log.debug("illegal packet type " .. protocol .. " on device listening channel") @@ -238,8 +260,34 @@ function supervisor.comms(version, num_reactors, modem, dev_listen, coord_listen if protocol == PROTOCOLS.SCADA_MGMT then -- SCADA management packet - elseif protocol == PROTOCOLS.COORD_DATA then + if session ~= nil then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + else + -- any other packet should be session related, discard it + log.debug(util.c(r_port, "->", l_port, ": discarding SCADA_MGMT packet without a known session")) + end + elseif protocol == PROTOCOLS.SCADA_CRDN then -- coordinator packet + if session ~= nil then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + elseif packet.type == SCADA_CRDN_TYPES.ESTABLISH then + if packet.length == 1 then + -- this is an attempt to establish a new session + println(util.c("connected to coordinator [:", r_port, "]")) + + svsessions.establish_coord_session(l_port, r_port, packet.data[1]) + + log.debug("CRDN_ESTABLISH: connected to " .. r_port) + _send_crdn_establish(packet.scada_frame.seq_num() + 1, r_port) + else + log.debug("CRDN_ESTABLISH: establish packet length mismatch") + end + else + -- any other packet should be session related, discard it + log.debug(util.c(r_port, "->", l_port, ": discarding SCADA_CRDN packet without a known session")) + end else log.debug("illegal packet type " .. protocol .. " on coordinator listening channel") end From 4b60c038f47119aa3649480344799209d665b9ec Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 7 Jul 2022 00:37:58 -0400 Subject: [PATCH 322/587] removed debug prints --- supervisor/session/coordinator.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 4920b55..429e69a 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -106,8 +106,8 @@ function coordinator.new_session(id, in_queue, out_queue) log.warning(log_header .. "COORD KEEP_ALIVE round trip time > 500ms (" .. self.last_rtt .. "ms)") end - log.debug(log_header .. "COORD RTT = " .. self.last_rtt .. "ms") - log.debug(log_header .. "COORD TT = " .. (srv_now - coord_send) .. "ms") + -- log.debug(log_header .. "COORD RTT = " .. self.last_rtt .. "ms") + -- log.debug(log_header .. "COORD TT = " .. (srv_now - coord_send) .. "ms") else log.debug(log_header .. "SCADA keep alive packet length mismatch") end From b25ebdf959b7e766e6a22e0f35ae8952025dc95b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 7 Jul 2022 13:18:10 -0400 Subject: [PATCH 323/587] fixed supervisor keep alive periodics timing --- supervisor/session/coordinator.lua | 2 +- supervisor/session/plc.lua | 2 +- supervisor/session/rtu.lua | 2 +- supervisor/startup.lua | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 429e69a..3ffa89f 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -15,7 +15,7 @@ local print_ts = util.print_ts local println_ts = util.println_ts local PERIODICS = { - KEEP_ALIVE = 2.0 + KEEP_ALIVE = 2000 } -- coordinator supervisor session diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 0039bc3..5229b70 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -33,7 +33,7 @@ plc.PLC_S_CMDS = PLC_S_CMDS plc.PLC_S_DATA = PLC_S_DATA local PERIODICS = { - KEEP_ALIVE = 2.0 + KEEP_ALIVE = 2000 } -- PLC supervisor session diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index ca4e467..51f7e21 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -39,7 +39,7 @@ rtu.RTU_S_CMDS = RTU_S_CMDS rtu.RTU_S_DATA = RTU_S_DATA local PERIODICS = { - KEEP_ALIVE = 2.0 + KEEP_ALIVE = 2000 } ---@class rs_session_command diff --git a/supervisor/startup.lua b/supervisor/startup.lua index cdf3426..5533d74 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.5.1" +local SUPERVISOR_VERSION = "beta-v0.5.2" local print = util.print local println = util.println From 5a96818c978f24e912b4cdb45a04090d7cbf63ee Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 9 Jul 2022 13:43:38 -0400 Subject: [PATCH 324/587] #72 ui formatting --- coordinator/ui/components/unit_overview.lua | 31 +++++++++------------ coordinator/ui/layout/main_view.lua | 4 +-- coordinator/ui/style.lua | 24 ++++++++++------ 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 6de0502..e4c6b3a 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -6,14 +6,9 @@ local reactor_view = require("coordinator.ui.components.reactor") local boiler_view = require("coordinator.ui.components.boiler") local turbine_view = require("coordinator.ui.components.turbine") -local Div = require("graphics.elements.div") -local PipeNetwork = require("graphics.elements.pipenet") -local Rectangle = require("graphics.elements.rectangle") -local TextBox = require("graphics.elements.textbox") - -local HorizontalBar = require("graphics.elements.indicators.hbar") -local DataIndicator = require("graphics.elements.indicators.data") -local StateIndicator = require("graphics.elements.indicators.state") +local Div = require("graphics.elements.div") +local PipeNetwork = require("graphics.elements.pipenet") +local TextBox = require("graphics.elements.textbox") local TEXT_ALIGN = core.graphics.TEXT_ALIGN @@ -24,7 +19,7 @@ local pipe = core.graphics.pipe ---@param parent graphics_element local function make(parent, x, y, unit_id) -- bounding box div - local root = Div{parent=parent,x=x,y=y,width=80,height=27}--,fg_bg=cpair(colors.white,colors.black)} + local root = Div{parent=parent,x=x,y=y,width=80,height=25} -- unit header message TextBox{parent=root,text="Unit #" .. unit_id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} @@ -36,8 +31,8 @@ local function make(parent, x, y, unit_id) reactor_view(root, 1, 3) local coolant_pipes = { - pipe(0, 0, 12, 12, colors.lightBlue), - pipe(0, 0, 12, 3, colors.lightBlue), + pipe(0, 0, 11, 12, colors.lightBlue), + pipe(0, 0, 11, 3, colors.lightBlue), pipe(2, 0, 11, 2, colors.orange), pipe(2, 0, 11, 11, colors.orange) } @@ -49,7 +44,7 @@ local function make(parent, x, y, unit_id) ------------- boiler_view(root, 16, 11) - boiler_view(root, 16, 20) + boiler_view(root, 16, 19) -------------- -- TURBINES -- @@ -57,15 +52,15 @@ local function make(parent, x, y, unit_id) turbine_view(root, 58, 3) turbine_view(root, 58, 11) - turbine_view(root, 58, 20) + turbine_view(root, 58, 19) local steam_pipes_a = { -- boiler 1 pipe(0, 1, 6, 1, colors.white, false, true), pipe(0, 2, 6, 2, colors.blue, false, true), -- boiler 2 - pipe(0, 10, 6, 10, colors.white, false, true), - pipe(0, 11, 6, 11, colors.blue, false, true) + pipe(0, 9, 6, 9, colors.white, false, true), + pipe(0, 10, 6, 10, colors.blue, false, true) } local steam_pipes_b = { @@ -79,9 +74,9 @@ local function make(parent, x, y, unit_id) -- turbine 3, pipes from boiler 2 pipe(0, 18, 1, 9, colors.white, false, true), pipe(1, 1, 3, 1, colors.white, false, false), - pipe(0, 18, 3, 18, colors.white, false, true), - pipe(0, 19, 2, 10, colors.blue, false, true), - pipe(0, 19, 3, 19, colors.blue, false, true), + pipe(0, 17, 3, 17, colors.white, false, true), + pipe(0, 18, 2, 10, colors.blue, false, true), + pipe(0, 18, 3, 18, colors.blue, false, true), } PipeNetwork{parent=root,x=47,y=11,pipes=steam_pipes_a,bg=colors.lightGray} diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index e383f3d..da51586 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -23,8 +23,8 @@ local function init(monitor) -- unit overviews unit_overview(main, 2, 3, 1) unit_overview(main, 84, 3, 2) - unit_overview(main, 2, 32, 3) - unit_overview(main, 84, 32, 4) + unit_overview(main, 2, 29, 3) + unit_overview(main, 84, 29, 4) return main end diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index 400c6db..9e3e265 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -11,14 +11,22 @@ style.root = cpair(colors.black, colors.lightGray) style.header = cpair(colors.white, colors.gray) style.colors = { - { - c = colors.green, - hex = 0x7ed788 - }, - { - c = colors.lightGray, - hex = 0xcacaca - } + { c = colors.red, hex = 0xdf4949 }, + { c = colors.orange, hex = 0xffb659 }, + { c = colors.yellow, hex = 0xfffc79 }, + { c = colors.lime, hex = 0x64dd20 }, + { c = colors.green, hex = 0x4aee8a }, + { c = colors.cyan, hex = 0x34bac8 }, + { c = colors.lightBlue, hex = 0x6cc0f2 }, + { c = colors.blue, hex = 0x0096ff }, + { c = colors.purple, hex = 0xb156ee }, + { c = colors.pink, hex = 0xf26ba2 }, + { c = colors.magenta, hex = 0xf9488a }, + -- { c = colors.white, hex = 0xf0f0f0 }, + { c = colors.lightGray, hex = 0xcacaca }, + { c = colors.gray, hex = 0x575757 }, + -- { c = colors.black, hex = 0x191919 }, + -- { c = colors.brown, hex = 0x7f664c } } -- MAIN LAYOUT -- From 6f61203db3b2b68d02ee3541f92d03ad91ba5917 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 10 Jul 2022 16:15:30 -0400 Subject: [PATCH 325/587] #72, #78 updated main view to adapt to facility configuration, initial use of pub/sub for main view --- coordinator/database.lua | 3 + coordinator/ui/components/boiler.lua | 20 +++- coordinator/ui/components/reactor.lua | 20 +++- coordinator/ui/components/turbine.lua | 15 ++- coordinator/ui/components/unit_overview.lua | 119 ++++++++++++++------ coordinator/ui/layout/main_view.lua | 18 +-- 6 files changed, 145 insertions(+), 50 deletions(-) diff --git a/coordinator/database.lua b/coordinator/database.lua index b15bf05..3cee4ac 100644 --- a/coordinator/database.lua +++ b/coordinator/database.lua @@ -51,4 +51,7 @@ function database.init(conf) end end +-- get the database +function database.get() return db end + return database diff --git a/coordinator/ui/components/boiler.lua b/coordinator/ui/components/boiler.lua index 204c317..f45585b 100644 --- a/coordinator/ui/components/boiler.lua +++ b/coordinator/ui/components/boiler.lua @@ -14,7 +14,12 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN local cpair = core.graphics.cpair local border = core.graphics.border -local function new_view(root, x, y) +-- new boiler view +---@param root graphics_element +---@param x integer +---@param y integer +---@param ps psil +local function new_view(root, x, y, ps) local boiler = Rectangle{parent=root,border=border(1, colors.gray, true),width=31,height=7,x=x,y=y} local text_fg_bg = cpair(colors.black, colors.lightGray) @@ -24,6 +29,10 @@ local function new_view(root, x, y) local temp = DataIndicator{parent=boiler,x=5,y=3,lu_colors=lu_col,label="Temp:",unit="K",format="%10.2f",value=1900,width=22,fg_bg=text_fg_bg} local boil_r = DataIndicator{parent=boiler,x=5,y=4,lu_colors=lu_col,label="Boil:",unit="mB/t",format="%10.0f",value=801523,commas=true,width=22,fg_bg=text_fg_bg} + ps.subscribe("status", status.update) + ps.subscribe("temp", temp.update) + ps.subscribe("boil_rate", boil_r.update) + TextBox{parent=boiler,text="H",x=2,y=5,height=1,width=1,fg_bg=text_fg_bg} TextBox{parent=boiler,text="W",x=3,y=5,height=1,width=1,fg_bg=text_fg_bg} TextBox{parent=boiler,text="S",x=27,y=5,height=1,width=1,fg_bg=text_fg_bg} @@ -32,12 +41,17 @@ local function new_view(root, x, y) local hcool = VerticalBar{parent=boiler,x=2,y=1,fg_bg=cpair(colors.orange,colors.gray),height=4,width=1} local water = VerticalBar{parent=boiler,x=3,y=1,fg_bg=cpair(colors.blue,colors.gray),height=4,width=1} local steam = VerticalBar{parent=boiler,x=27,y=1,fg_bg=cpair(colors.white,colors.gray),height=4,width=1} - local cool = VerticalBar{parent=boiler,x=28,y=1,fg_bg=cpair(colors.lightBlue,colors.gray),height=4,width=1} + local ccool = VerticalBar{parent=boiler,x=28,y=1,fg_bg=cpair(colors.lightBlue,colors.gray),height=4,width=1} + + ps.subscribe("hcool", hcool.update) + ps.subscribe("water", water.update) + ps.subscribe("steam", steam.update) + ps.subscribe("ccool", ccool.update) hcool.update(0.22) water.update(1) steam.update(0.05) - cool.update(0.13) + ccool.update(0.13) end return new_view diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua index acd1ba7..05d27dd 100644 --- a/coordinator/ui/components/reactor.lua +++ b/coordinator/ui/components/reactor.lua @@ -14,7 +14,11 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN local cpair = core.graphics.cpair local border = core.graphics.border -local function new_view(root, x, y) +---@param root graphics_element +---@param x integer +---@param y integer +---@param ps psil +local function new_view(root, x, y, ps) local reactor = Rectangle{parent=root,border=border(1, colors.gray, true),width=30,height=7,x=x,y=y} local text_fg_bg = cpair(colors.black, colors.lightGray) @@ -25,6 +29,11 @@ local function new_view(root, x, y) local burn_r = DataIndicator{parent=reactor,x=2,y=4,lu_colors=lu_col,label="Burn Rate:",unit="mB/t",format="%10.1f",value=40.1,width=26,fg_bg=text_fg_bg} local heating_r = DataIndicator{parent=reactor,x=2,y=5,lu_colors=lu_col,label="Heating:",unit="mB/t",format="%12.0f",value=8015342,commas=true,width=26,fg_bg=text_fg_bg} + ps.subscribe("status", status.update) + ps.subscribe("temp", core_temp.update) + ps.subscribe("burn_rate", burn_r.update) + ps.subscribe("heating_rate", heating_r.update) + local reactor_fills = Rectangle{parent=root,border=border(1, colors.gray, true),width=24,height=7,x=(x + 29),y=y} TextBox{parent=reactor_fills,text="FUEL",x=2,y=1,height=1,fg_bg=text_fg_bg} @@ -33,12 +42,17 @@ local function new_view(root, x, y) TextBox{parent=reactor_fills,text="WASTE",x=2,y=5,height=1,fg_bg=text_fg_bg} local fuel = HorizontalBar{parent=reactor_fills,x=8,y=1,show_percent=true,bar_fg_bg=cpair(colors.black,colors.gray),height=1,width=14} - local cool = HorizontalBar{parent=reactor_fills,x=8,y=2,show_percent=true,bar_fg_bg=cpair(colors.lightBlue,colors.gray),height=1,width=14} + local ccool = HorizontalBar{parent=reactor_fills,x=8,y=2,show_percent=true,bar_fg_bg=cpair(colors.lightBlue,colors.gray),height=1,width=14} local hcool = HorizontalBar{parent=reactor_fills,x=8,y=4,show_percent=true,bar_fg_bg=cpair(colors.orange,colors.gray),height=1,width=14} local waste = HorizontalBar{parent=reactor_fills,x=8,y=5,show_percent=true,bar_fg_bg=cpair(colors.brown,colors.gray),height=1,width=14} + ps.subscribe("fuel", fuel.update) + ps.subscribe("ccool", ccool.update) + ps.subscribe("hcool", hcool.update) + ps.subscribe("waste", waste.update) + fuel.update(1) - cool.update(0.85) + ccool.update(0.85) hcool.update(0.08) waste.update(0.32) end diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua index b4ad063..9498524 100644 --- a/coordinator/ui/components/turbine.lua +++ b/coordinator/ui/components/turbine.lua @@ -14,18 +14,29 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN local cpair = core.graphics.cpair local border = core.graphics.border -local function new_view(root, x, y) +-- new turbine view +---@param root graphics_element +---@param x integer +---@param y integer +---@param ps psil +local function new_view(root, x, y, ps) local turbine = Rectangle{parent=root,border=border(1, colors.gray, true),width=23,height=7,x=x,y=y} local text_fg_bg = cpair(colors.black, colors.lightGray) local lu_col = cpair(colors.gray, colors.gray) local status = StateIndicator{parent=turbine,x=8,y=1,states=style.turbine.states,value=3,min_width=10} - local production = DataIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",unit="MFE",format="%10.2f",value=3.2,width=16,fg_bg=text_fg_bg} + local prod_rate = DataIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",unit="MFE",format="%10.2f",value=3.2,width=16,fg_bg=text_fg_bg} local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=801523,commas=true,width=16,fg_bg=text_fg_bg} + ps.subscribe("status", status.update) + ps.subscribe("prod_rate", prod_rate.update) + ps.subscribe("flow_rate", flow_rate.update) + local steam = VerticalBar{parent=turbine,x=2,y=1,fg_bg=cpair(colors.white,colors.gray),height=5,width=2} + ps.subscribe("steam", steam.update) + steam.update(0.12) end diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index e4c6b3a..6819930 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -17,25 +17,37 @@ local border = core.graphics.border local pipe = core.graphics.pipe ---@param parent graphics_element -local function make(parent, x, y, unit_id) +---@param x integer +---@param y integer +---@param unit coord_db_entry +local function make(parent, x, y, unit) -- bounding box div local root = Div{parent=parent,x=x,y=y,width=80,height=25} -- unit header message - TextBox{parent=root,text="Unit #" .. unit_id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + TextBox{parent=root,text="Unit #" .. unit.unit_id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + + local num_boilers = #unit.boiler_data_tbl + local num_turbines = #unit.turbine_data_tbl ------------- -- REACTOR -- ------------- - reactor_view(root, 1, 3) + reactor_view(root, 1, 3, unit.reactor_ps) - local coolant_pipes = { - pipe(0, 0, 11, 12, colors.lightBlue), - pipe(0, 0, 11, 3, colors.lightBlue), - pipe(2, 0, 11, 2, colors.orange), - pipe(2, 0, 11, 11, colors.orange) - } + local coolant_pipes = {} + + if num_boilers == 2 then + table.insert(coolant_pipes, pipe(0, 0, 11, 12, colors.lightBlue)) + end + + table.insert(coolant_pipes, pipe(0, 0, 11, 3, colors.lightBlue)) + table.insert(coolant_pipes, pipe(2, 0, 11, 2, colors.orange)) + + if num_boilers == 2 then + table.insert(coolant_pipes, pipe(2, 0, 11, 11, colors.orange)) + end PipeNetwork{parent=root,x=4,y=10,pipes=coolant_pipes,bg=colors.lightGray} @@ -43,41 +55,78 @@ local function make(parent, x, y, unit_id) -- BOILERS -- ------------- - boiler_view(root, 16, 11) - boiler_view(root, 16, 19) + boiler_view(root, 16, 11, unit.boiler_ps_tbl[1]) + if num_boilers == 2 then boiler_view(root, 16, 19, unit.boiler_ps_tbl[2]) end -------------- -- TURBINES -- -------------- - turbine_view(root, 58, 3) - turbine_view(root, 58, 11) - turbine_view(root, 58, 19) + local t_idx = 1 + + if num_turbines == 3 then + turbine_view(root, 58, 3, unit.turbine_ps_tbl[t_idx]) + t_idx = t_idx + 1 + end + + if num_turbines >= 1 then + turbine_view(root, 58, 11, unit.turbine_ps_tbl[t_idx]) + t_idx = t_idx + 1 + end + + if num_turbines >= 2 then turbine_view(root, 58, 19, unit.turbine_ps_tbl[t_idx]) end local steam_pipes_a = { - -- boiler 1 - pipe(0, 1, 6, 1, colors.white, false, true), - pipe(0, 2, 6, 2, colors.blue, false, true), - -- boiler 2 - pipe(0, 9, 6, 9, colors.white, false, true), - pipe(0, 10, 6, 10, colors.blue, false, true) + -- boiler 1 steam/water pipes + pipe(0, 1, 6, 1, colors.white, false, true), -- steam boiler 1 to turbine junction + pipe(0, 2, 6, 2, colors.blue, false, true) -- water boiler 1 to turbine junction } - local steam_pipes_b = { - -- turbines 1 & 2, pipes from boiler 1 - pipe(0, 9, 1, 2, colors.white, false, true), - pipe(1, 1, 3, 1, colors.white, false, false), - pipe(0, 9, 3, 9, colors.white, false, true), - pipe(0, 10, 2, 3, colors.blue, false, true), - pipe(2, 2, 3, 2, colors.blue, false, false), - pipe(0, 10, 3, 10, colors.blue, false, true), - -- turbine 3, pipes from boiler 2 - pipe(0, 18, 1, 9, colors.white, false, true), - pipe(1, 1, 3, 1, colors.white, false, false), - pipe(0, 17, 3, 17, colors.white, false, true), - pipe(0, 18, 2, 10, colors.blue, false, true), - pipe(0, 18, 3, 18, colors.blue, false, true), - } + if num_boilers == 2 then + -- boiler 2 steam/water pipes + table.insert(steam_pipes_a, pipe(0, 9, 6, 9, colors.white, false, true)) -- steam boiler 2 to turbine junction + table.insert(steam_pipes_a, pipe(0, 10, 6, 10, colors.blue, false, true)) -- water boiler 2 to turbine junction + end + + local steam_pipes_b = {} + + if num_turbines == 3 then + table.insert(steam_pipes_b, pipe(0, 9, 1, 2, colors.white, false, true)) -- steam boiler 1 to turbine 1 junction start + table.insert(steam_pipes_b, pipe(1, 1, 3, 1, colors.white, false, false)) -- steam boiler 1 to turbine 1 junction end + end + + table.insert(steam_pipes_b, pipe(0, 9, 3, 9, colors.white, false, true)) -- steam boiler 1 to turbine 2 + + if num_turbines == 3 then + table.insert(steam_pipes_b, pipe(0, 10, 2, 3, colors.blue, false, true)) -- water boiler 1 to turbine 1 junction start + table.insert(steam_pipes_b, pipe(2, 2, 3, 2, colors.blue, false, false)) -- water boiler 1 to turbine 1 junction end + end + + table.insert(steam_pipes_b, pipe(0, 10, 3, 10, colors.blue, false, true)) -- water boiler 1 to turbine 2 + + if num_turbines >= 2 then + if num_boilers == 2 then + table.insert(steam_pipes_b, pipe(0, 17, 1, 9, colors.white, false, true)) -- steam boiler 2 to turbine 2 junction + table.insert(steam_pipes_b, pipe(0, 17, 3, 17, colors.white, false, true)) -- steam boiler 2 to turbine 3 + else + table.insert(steam_pipes_b, pipe(1, 17, 1, 9, colors.white, false, true)) -- steam boiler 2 to turbine 2 junction + table.insert(steam_pipes_b, pipe(1, 17, 3, 17, colors.white, false, true)) -- steam boiler 2 to turbine 3 + end + + if num_boilers == 2 then + table.insert(steam_pipes_b, pipe(0, 18, 2, 10, colors.blue, false, true)) -- water boiler 2 to turbine 3 + table.insert(steam_pipes_b, pipe(0, 18, 3, 18, colors.blue, false, true)) -- water boiler 2 to turbine 2 junction + else + table.insert(steam_pipes_b, pipe(2, 18, 2, 10, colors.blue, false, true)) -- water boiler 2 to turbine 3 + table.insert(steam_pipes_b, pipe(2, 18, 3, 18, colors.blue, false, true)) -- water boiler 2 to turbine 2 junction + end + elseif num_turbines == 1 and num_boilers == 2 then + table.insert(steam_pipes_b, pipe(0, 17, 1, 9, colors.white, false, true)) -- steam boiler 2 to turbine 2 junction + table.insert(steam_pipes_b, pipe(0, 17, 1, 17, colors.white, false, true)) -- steam boiler 2 to turbine 3 + + table.insert(steam_pipes_b, pipe(0, 18, 2, 10, colors.blue, false, true)) -- water boiler 2 to turbine 3 + table.insert(steam_pipes_b, pipe(0, 18, 2, 18, colors.blue, false, true)) -- water boiler 2 to turbine 2 junction + end PipeNetwork{parent=root,x=47,y=11,pipes=steam_pipes_a,bg=colors.lightGray} PipeNetwork{parent=root,x=54,y=3,pipes=steam_pipes_b,bg=colors.lightGray} diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index da51586..f45413c 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -2,10 +2,12 @@ -- Main SCADA Coordinator GUI -- -local core = require("graphics.core") -local log = require("scada-common.log") +local log = require("scada-common.log") -local style = require("coordinator.ui.style") +local database = require("coordinator.database") +local style = require("coordinator.ui.style") + +local core = require("graphics.core") local DisplayBox = require("graphics.elements.displaybox") local TextBox = require("graphics.elements.textbox") @@ -20,11 +22,13 @@ local function init(monitor) -- window header message TextBox{parent=main,text="Nuclear Generation Facility SCADA Coordinator",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + local db = database.get() + -- unit overviews - unit_overview(main, 2, 3, 1) - unit_overview(main, 84, 3, 2) - unit_overview(main, 2, 29, 3) - unit_overview(main, 84, 29, 4) + if db.facility.num_units >= 1 then unit_overview(main, 2, 3, db.units[1]) end + if db.facility.num_units >= 2 then unit_overview(main, 84, 3, db.units[2]) end + if db.facility.num_units >= 3 then unit_overview(main, 2, 29, db.units[3]) end + if db.facility.num_units == 4 then unit_overview(main, 84, 29, db.units[4]) end return main end From 8704d845bdfda7eede8a6ec9dbe8cfd238a5d5a3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 14 Jul 2022 13:45:40 -0400 Subject: [PATCH 326/587] fixed bug with cpair blit_a/blit_b colors --- graphics/core.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphics/core.lua b/graphics/core.lua index c600010..4894b3a 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -93,8 +93,8 @@ function graphics.cpair(a, b) -- color pairs color_a = a, color_b = b, - blit_a = a, - blit_b = b, + blit_a = colors.toBlit(a), + blit_b = colors.toBlit(b), -- aliases fgd = a, bkg = b, From bd1ab116860f7bbcd9fbcf90ae632ef630cd9d6b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 14 Jul 2022 13:47:39 -0400 Subject: [PATCH 327/587] #79 water cooling only support, dynamic height, changed 2 turbine 1 boiler layout --- coordinator/startup.lua | 2 +- coordinator/ui/components/boiler.lua | 1 - coordinator/ui/components/reactor.lua | 1 - coordinator/ui/components/turbine.lua | 1 - coordinator/ui/components/unit_overview.lua | 168 ++++++++++++-------- coordinator/ui/layout/main_view.lua | 20 ++- graphics/element.lua | 10 ++ 7 files changed, 129 insertions(+), 74 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index e6cadc9..0ef6814 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.3.2" +local COORDINATOR_VERSION = "alpha-v0.3.3" local print = util.print local println = util.println diff --git a/coordinator/ui/components/boiler.lua b/coordinator/ui/components/boiler.lua index f45585b..8deace3 100644 --- a/coordinator/ui/components/boiler.lua +++ b/coordinator/ui/components/boiler.lua @@ -2,7 +2,6 @@ local core = require("graphics.core") local style = require("coordinator.ui.style") -local Div = require("graphics.elements.div") local DataIndicator = require("graphics.elements.indicators.data") local StateIndicator = require("graphics.elements.indicators.state") local Rectangle = require("graphics.elements.rectangle") diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua index 05d27dd..d7d35f5 100644 --- a/coordinator/ui/components/reactor.lua +++ b/coordinator/ui/components/reactor.lua @@ -2,7 +2,6 @@ local core = require("graphics.core") local style = require("coordinator.ui.style") -local Div = require("graphics.elements.div") local HorizontalBar = require("graphics.elements.indicators.hbar") local DataIndicator = require("graphics.elements.indicators.data") local StateIndicator = require("graphics.elements.indicators.state") diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua index 9498524..f1ffea0 100644 --- a/coordinator/ui/components/turbine.lua +++ b/coordinator/ui/components/turbine.lua @@ -2,7 +2,6 @@ local core = require("graphics.core") local style = require("coordinator.ui.style") -local Div = require("graphics.elements.div") local DataIndicator = require("graphics.elements.indicators.data") local StateIndicator = require("graphics.elements.indicators.state") local Rectangle = require("graphics.elements.rectangle") diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 6819930..2ddedc7 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -1,3 +1,7 @@ +-- +-- Basic Unit Overview +-- + local core = require("graphics.core") local style = require("coordinator.ui.style") @@ -21,116 +25,152 @@ local pipe = core.graphics.pipe ---@param y integer ---@param unit coord_db_entry local function make(parent, x, y, unit) + local height = 0 + local num_boilers = #unit.boiler_data_tbl + local num_turbines = #unit.turbine_data_tbl + + assert(num_boilers >= 0 and num_boilers <= 2, "minimum 0 boilers, maximum 2 boilers") + assert(num_turbines >= 1 and num_turbines <= 3, "minimum 1 turbine, maximum 3 turbines") + + if num_boilers == 0 and num_turbines == 1 then + height = 9 + elseif num_boilers == 1 and num_turbines <= 2 then + height = 17 + else + height = 25 + end + -- bounding box div - local root = Div{parent=parent,x=x,y=y,width=80,height=25} + local root = Div{parent=parent,x=x,y=y,width=80,height=height,fg_bg=cpair(colors.black,colors.black)} -- unit header message TextBox{parent=root,text="Unit #" .. unit.unit_id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} - local num_boilers = #unit.boiler_data_tbl - local num_turbines = #unit.turbine_data_tbl - ------------- -- REACTOR -- ------------- reactor_view(root, 1, 3, unit.reactor_ps) - local coolant_pipes = {} + if num_boilers > 0 then + local coolant_pipes = {} - if num_boilers == 2 then - table.insert(coolant_pipes, pipe(0, 0, 11, 12, colors.lightBlue)) + if num_boilers >= 2 then + table.insert(coolant_pipes, pipe(0, 0, 11, 12, colors.lightBlue)) + end + + table.insert(coolant_pipes, pipe(0, 0, 11, 3, colors.lightBlue)) + table.insert(coolant_pipes, pipe(2, 0, 11, 2, colors.orange)) + + if num_boilers >= 2 then + table.insert(coolant_pipes, pipe(2, 0, 11, 11, colors.orange)) + end + + PipeNetwork{parent=root,x=4,y=10,pipes=coolant_pipes,bg=colors.lightGray} end - table.insert(coolant_pipes, pipe(0, 0, 11, 3, colors.lightBlue)) - table.insert(coolant_pipes, pipe(2, 0, 11, 2, colors.orange)) - - if num_boilers == 2 then - table.insert(coolant_pipes, pipe(2, 0, 11, 11, colors.orange)) - end - - PipeNetwork{parent=root,x=4,y=10,pipes=coolant_pipes,bg=colors.lightGray} - ------------- -- BOILERS -- ------------- - boiler_view(root, 16, 11, unit.boiler_ps_tbl[1]) - if num_boilers == 2 then boiler_view(root, 16, 19, unit.boiler_ps_tbl[2]) end + if num_boilers >= 1 then boiler_view(root, 16, 11, unit.boiler_ps_tbl[1]) end + if num_boilers >= 2 then boiler_view(root, 16, 19, unit.boiler_ps_tbl[2]) end -------------- -- TURBINES -- -------------- local t_idx = 1 + local no_boilers = num_boilers == 0 - if num_turbines == 3 then + if (num_turbines >= 3) or no_boilers or (num_boilers == 1 and num_turbines >= 2) then turbine_view(root, 58, 3, unit.turbine_ps_tbl[t_idx]) t_idx = t_idx + 1 end - if num_turbines >= 1 then + if (num_turbines >= 1 and not no_boilers) or num_turbines >= 2 then turbine_view(root, 58, 11, unit.turbine_ps_tbl[t_idx]) t_idx = t_idx + 1 end - if num_turbines >= 2 then turbine_view(root, 58, 19, unit.turbine_ps_tbl[t_idx]) end - - local steam_pipes_a = { - -- boiler 1 steam/water pipes - pipe(0, 1, 6, 1, colors.white, false, true), -- steam boiler 1 to turbine junction - pipe(0, 2, 6, 2, colors.blue, false, true) -- water boiler 1 to turbine junction - } - - if num_boilers == 2 then - -- boiler 2 steam/water pipes - table.insert(steam_pipes_a, pipe(0, 9, 6, 9, colors.white, false, true)) -- steam boiler 2 to turbine junction - table.insert(steam_pipes_a, pipe(0, 10, 6, 10, colors.blue, false, true)) -- water boiler 2 to turbine junction + if (num_turbines >= 2 and num_boilers >= 2) or num_turbines >= 3 then + turbine_view(root, 58, 19, unit.turbine_ps_tbl[t_idx]) end local steam_pipes_b = {} - if num_turbines == 3 then - table.insert(steam_pipes_b, pipe(0, 9, 1, 2, colors.white, false, true)) -- steam boiler 1 to turbine 1 junction start - table.insert(steam_pipes_b, pipe(1, 1, 3, 1, colors.white, false, false)) -- steam boiler 1 to turbine 1 junction end - end + if no_boilers then + table.insert(steam_pipes_b, pipe(0, 1, 3, 1, colors.white)) -- steam to turbine 1 + table.insert(steam_pipes_b, pipe(0, 2, 3, 2, colors.blue)) -- water to turbine 1 - table.insert(steam_pipes_b, pipe(0, 9, 3, 9, colors.white, false, true)) -- steam boiler 1 to turbine 2 - - if num_turbines == 3 then - table.insert(steam_pipes_b, pipe(0, 10, 2, 3, colors.blue, false, true)) -- water boiler 1 to turbine 1 junction start - table.insert(steam_pipes_b, pipe(2, 2, 3, 2, colors.blue, false, false)) -- water boiler 1 to turbine 1 junction end - end - - table.insert(steam_pipes_b, pipe(0, 10, 3, 10, colors.blue, false, true)) -- water boiler 1 to turbine 2 - - if num_turbines >= 2 then - if num_boilers == 2 then - table.insert(steam_pipes_b, pipe(0, 17, 1, 9, colors.white, false, true)) -- steam boiler 2 to turbine 2 junction - table.insert(steam_pipes_b, pipe(0, 17, 3, 17, colors.white, false, true)) -- steam boiler 2 to turbine 3 - else - table.insert(steam_pipes_b, pipe(1, 17, 1, 9, colors.white, false, true)) -- steam boiler 2 to turbine 2 junction - table.insert(steam_pipes_b, pipe(1, 17, 3, 17, colors.white, false, true)) -- steam boiler 2 to turbine 3 + if num_turbines >= 2 then + table.insert(steam_pipes_b, pipe(1, 2, 3, 9, colors.white)) -- steam to turbine 2 + table.insert(steam_pipes_b, pipe(2, 3, 3, 10, colors.blue)) -- water to turbine 2 end - if num_boilers == 2 then - table.insert(steam_pipes_b, pipe(0, 18, 2, 10, colors.blue, false, true)) -- water boiler 2 to turbine 3 - table.insert(steam_pipes_b, pipe(0, 18, 3, 18, colors.blue, false, true)) -- water boiler 2 to turbine 2 junction - else - table.insert(steam_pipes_b, pipe(2, 18, 2, 10, colors.blue, false, true)) -- water boiler 2 to turbine 3 - table.insert(steam_pipes_b, pipe(2, 18, 3, 18, colors.blue, false, true)) -- water boiler 2 to turbine 2 junction + if num_turbines >= 3 then + table.insert(steam_pipes_b, pipe(1, 9, 3, 17, colors.white)) -- steam boiler 1 to turbine 1 junction end + table.insert(steam_pipes_b, pipe(2, 10, 3, 18, colors.blue)) -- water boiler 1 to turbine 1 junction start end - elseif num_turbines == 1 and num_boilers == 2 then - table.insert(steam_pipes_b, pipe(0, 17, 1, 9, colors.white, false, true)) -- steam boiler 2 to turbine 2 junction - table.insert(steam_pipes_b, pipe(0, 17, 1, 17, colors.white, false, true)) -- steam boiler 2 to turbine 3 + else + -- boiler side pipes - table.insert(steam_pipes_b, pipe(0, 18, 2, 10, colors.blue, false, true)) -- water boiler 2 to turbine 3 - table.insert(steam_pipes_b, pipe(0, 18, 2, 18, colors.blue, false, true)) -- water boiler 2 to turbine 2 junction + local steam_pipes_a = { + -- boiler 1 steam/water pipes + pipe(0, 1, 6, 1, colors.white, false, true), -- steam boiler 1 to turbine junction + pipe(0, 2, 6, 2, colors.blue, false, true) -- water boiler 1 to turbine junction + } + + if num_boilers >= 2 then + -- boiler 2 steam/water pipes + table.insert(steam_pipes_a, pipe(0, 9, 6, 9, colors.white, false, true)) -- steam boiler 2 to turbine junction + table.insert(steam_pipes_a, pipe(0, 10, 6, 10, colors.blue, false, true)) -- water boiler 2 to turbine junction + end + + -- turbine side pipes + + if num_turbines >= 3 or (num_boilers == 1 and num_turbines == 2) then + table.insert(steam_pipes_b, pipe(0, 9, 1, 2, colors.white, false, true)) -- steam boiler 1 to turbine 1 junction start + table.insert(steam_pipes_b, pipe(1, 1, 3, 1, colors.white, false, false)) -- steam boiler 1 to turbine 1 junction end + end + + table.insert(steam_pipes_b, pipe(0, 9, 3, 9, colors.white, false, true)) -- steam boiler 1 to turbine 2 + + if num_turbines >= 3 or (num_boilers == 1 and num_turbines == 2) then + table.insert(steam_pipes_b, pipe(0, 10, 2, 3, colors.blue, false, true)) -- water boiler 1 to turbine 1 junction start + table.insert(steam_pipes_b, pipe(2, 2, 3, 2, colors.blue, false, false)) -- water boiler 1 to turbine 1 junction end + end + + table.insert(steam_pipes_b, pipe(0, 10, 3, 10, colors.blue, false, true)) -- water boiler 1 to turbine 2 + + if num_turbines >= 3 or (num_turbines >= 2 and num_boilers >= 2) then + if num_boilers >= 2 then + table.insert(steam_pipes_b, pipe(0, 17, 1, 9, colors.white, false, true)) -- steam boiler 2 to turbine 2 junction + table.insert(steam_pipes_b, pipe(0, 17, 3, 17, colors.white, false, true)) -- steam boiler 2 to turbine 3 + + table.insert(steam_pipes_b, pipe(0, 18, 2, 10, colors.blue, false, true)) -- water boiler 2 to turbine 3 + table.insert(steam_pipes_b, pipe(0, 18, 3, 18, colors.blue, false, true)) -- water boiler 2 to turbine 2 junction + else + table.insert(steam_pipes_b, pipe(1, 17, 1, 9, colors.white, false, true)) -- steam boiler 2 to turbine 2 junction + table.insert(steam_pipes_b, pipe(1, 17, 3, 17, colors.white, false, true)) -- steam boiler 2 to turbine 3 + + table.insert(steam_pipes_b, pipe(2, 18, 2, 10, colors.blue, false, true)) -- water boiler 2 to turbine 3 + table.insert(steam_pipes_b, pipe(2, 18, 3, 18, colors.blue, false, true)) -- water boiler 2 to turbine 2 junction + end + elseif num_turbines == 1 and num_boilers >= 2 then + table.insert(steam_pipes_b, pipe(0, 17, 1, 9, colors.white, false, true)) -- steam boiler 2 to turbine 2 junction + table.insert(steam_pipes_b, pipe(0, 17, 1, 17, colors.white, false, true)) -- steam boiler 2 to turbine 3 + + table.insert(steam_pipes_b, pipe(0, 18, 2, 10, colors.blue, false, true)) -- water boiler 2 to turbine 3 + table.insert(steam_pipes_b, pipe(0, 18, 2, 18, colors.blue, false, true)) -- water boiler 2 to turbine 2 junction + end + + PipeNetwork{parent=root,x=47,y=11,pipes=steam_pipes_a,bg=colors.lightGray} end - PipeNetwork{parent=root,x=47,y=11,pipes=steam_pipes_a,bg=colors.lightGray} PipeNetwork{parent=root,x=54,y=3,pipes=steam_pipes_b,bg=colors.lightGray} + return root end return make diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index f45413c..ee096ce 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -2,8 +2,6 @@ -- Main SCADA Coordinator GUI -- -local log = require("scada-common.log") - local database = require("coordinator.database") local style = require("coordinator.ui.style") @@ -24,11 +22,21 @@ local function init(monitor) local db = database.get() + local uo_1, uo_2, uo_3, uo_4 ---@type graphics_element + -- unit overviews - if db.facility.num_units >= 1 then unit_overview(main, 2, 3, db.units[1]) end - if db.facility.num_units >= 2 then unit_overview(main, 84, 3, db.units[2]) end - if db.facility.num_units >= 3 then unit_overview(main, 2, 29, db.units[3]) end - if db.facility.num_units == 4 then unit_overview(main, 84, 29, db.units[4]) end + if db.facility.num_units >= 1 then uo_1 = unit_overview(main, 2, 3, db.units[1]) end + if db.facility.num_units >= 2 then uo_2 = unit_overview(main, 84, 3, db.units[2]) end + + if db.facility.num_units >= 3 then + -- base offset 3, spacing 1, max height of units 1 and 2 + local row_2_offset = 3 + 1 + math.max(uo_1.height(), uo_2.height()) + + uo_3 = unit_overview(main, 2, row_2_offset, db.units[3]) + if db.facility.num_units == 4 then uo_4 = unit_overview(main, 84, row_2_offset, db.units[4]) end + end + + -- command & control return main end diff --git a/graphics/element.lua b/graphics/element.lua index 5e58ec1..274681e 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -148,6 +148,16 @@ function element.new(args) return self.child_offset.x, self.child_offset.y end + -- get element width + function public.width() + return protected.frame.w + end + + -- get element height + function public.height() + return protected.frame.h + end + -- handle a monitor touch ---@param event monitor_touch monitor touch event function public.handle_touch(event) From 6643c7e6ed326e15c7d0d4f9660c2c5fd4898422 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 14 Jul 2022 14:29:48 -0400 Subject: [PATCH 328/587] removed debug fg_bg set --- coordinator/ui/components/unit_overview.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 2ddedc7..30f887b 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -41,7 +41,7 @@ local function make(parent, x, y, unit) end -- bounding box div - local root = Div{parent=parent,x=x,y=y,width=80,height=height,fg_bg=cpair(colors.black,colors.black)} + local root = Div{parent=parent,x=x,y=y,width=80,height=height} -- unit header message TextBox{parent=root,text="Unit #" .. unit.unit_id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} From 88bf4d56532c2ce2169e4f156d415040ac5d2ffb Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 15 Jul 2022 09:58:04 -0400 Subject: [PATCH 329/587] #80 mek 10.1+ support for reactor plc --- reactor-plc/plc.lua | 18 ++++++++++++++++-- reactor-plc/startup.lua | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 590ce71..d2009f1 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -344,6 +344,8 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co -- variable reactor status information, excluding heating rate ---@return table data_table, boolean faulted local function _reactor_status() + local fuel = nil + local waste = nil local coolant = nil local hcoolant = nil @@ -375,9 +377,9 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co function () data_table[5] = self.reactor.getDamagePercent() end, function () data_table[6] = self.reactor.getBoilEfficiency() end, function () data_table[7] = self.reactor.getEnvironmentalLoss() end, - function () data_table[8] = self.reactor.getFuel() end, + function () fuel = self.reactor.getFuel() end, function () data_table[9] = self.reactor.getFuelFilledPercentage() end, - function () data_table[10] = self.reactor.getWaste() end, + function () waste = self.reactor.getWaste() end, function () data_table[11] = self.reactor.getWasteFilledPercentage() end, function () coolant = self.reactor.getCoolant() end, function () data_table[14] = self.reactor.getCoolantFilledPercentage() end, @@ -387,6 +389,18 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co parallel.waitForAll(table.unpack(tasks)) + if type(fuel) == "table" then + data_table[8] = fuel.amount + elseif type(fuel) == "number" then + data_table[8] = fuel + end + + if type(waste) == "table" then + data_table[10] = waste.amount + elseif type(waste) == "number" then + data_table[10] = waste + end + if coolant ~= nil then data_table[12] = coolant.name data_table[13] = coolant.amount diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 73f4964..b4b932b 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.7.7" +local R_PLC_VERSION = "beta-v0.8.0" local print = util.print local println = util.println From 525dedb83021eff1179153bc5d0832e2d76696c4 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 16 Jul 2022 12:54:02 -0400 Subject: [PATCH 330/587] added missing RPS fields to supervisor session --- supervisor/session/plc.lua | 8 ++++++-- supervisor/startup.lua | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 5229b70..b9e0c56 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -98,7 +98,9 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) high_temp = false, no_fuel = false, no_cool = false, - timed_out = false + fault = false, + timeout = false, + manual = false }, ---@class mek_status mek_status = { @@ -153,7 +155,9 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) self.sDB.rps_status.high_temp = rps_status[4] self.sDB.rps_status.no_fuel = rps_status[5] self.sDB.rps_status.no_cool = rps_status[6] - self.sDB.rps_status.timed_out = rps_status[7] + self.sDB.rps_status.fault = rps_status[7] + self.sDB.rps_status.timeout = rps_status[8] + self.sDB.rps_status.manual = rps_status[9] end -- copy in the reactor status diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 5533d74..be4f366 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.5.2" +local SUPERVISOR_VERSION = "beta-v0.5.3" local print = util.print local println = util.println From c3d6d900a1391fc7d6e27862609194d508908a7b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 16 Jul 2022 13:25:07 -0400 Subject: [PATCH 331/587] bugfixes to graphics elements --- graphics/elements/controls/push_button.lua | 4 +- graphics/elements/controls/scram_button.lua | 12 +++--- .../elements/controls/spinbox_numeric.lua | 20 ++++++++-- graphics/elements/indicators/light.lua | 19 ++++------ graphics/elements/tiling.lua | 38 +++++++++---------- 5 files changed, 51 insertions(+), 42 deletions(-) diff --git a/graphics/elements/controls/push_button.lua b/graphics/elements/controls/push_button.lua index 9968240..82aa081 100644 --- a/graphics/elements/controls/push_button.lua +++ b/graphics/elements/controls/push_button.lua @@ -24,13 +24,15 @@ local function push_button(args) -- single line args.height = 1 + args.min_width = args.min_width or 0 + local text_width = string.len(args.text) args.width = math.max(text_width + 2, args.min_width) -- create new graphics element base object local e = element.new(args) - local h_pad = math.floor((e.frame.w - text_width) / 2) + local h_pad = math.floor((e.frame.w - text_width) / 2) + 1 local v_pad = math.floor(e.frame.h / 2) + 1 -- write the button text diff --git a/graphics/elements/controls/scram_button.lua b/graphics/elements/controls/scram_button.lua index 3cd6074..1694969 100644 --- a/graphics/elements/controls/scram_button.lua +++ b/graphics/elements/controls/scram_button.lua @@ -11,6 +11,7 @@ local element = require("graphics.element") ---@field parent graphics_element ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted +---@field fg_bg? cpair foreground/background colors -- new scram button ---@param args scram_button_args @@ -21,9 +22,6 @@ local function scram_button(args) args.height = 3 args.width = 9 - -- static colors - args.fg_bg = core.graphics.cpair(colors.white, colors.black) - -- create new graphics element base object local e = element.new(args) @@ -35,25 +33,25 @@ local function scram_button(args) -- top e.window.setTextColor(colors.yellow) - e.window.setBackgroundColor(colors.black) + e.window.setBackgroundColor(args.fg_bg.bkg) e.window.setCursorPos(1, 1) e.window.write("\x99\x89\x89\x89\x89\x89\x89\x89\x99") -- center left e.window.setCursorPos(1, 2) - e.window.setTextColor(colors.black) + e.window.setTextColor(args.fg_bg.bkg) e.window.setBackgroundColor(colors.yellow) e.window.write("\x99") -- center right - e.window.setTextColor(colors.black) + e.window.setTextColor(args.fg_bg.bkg) e.window.setBackgroundColor(colors.yellow) e.window.setCursorPos(9, 2) e.window.write("\x99") -- bottom e.window.setTextColor(colors.yellow) - e.window.setBackgroundColor(colors.black) + e.window.setBackgroundColor(args.fg_bg.bkg) e.window.setCursorPos(1, 3) e.window.write("\x99\x98\x98\x98\x98\x98\x98\x98\x99") diff --git a/graphics/elements/controls/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua index 92946d8..589d1ef 100644 --- a/graphics/elements/controls/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -21,11 +21,12 @@ local function spinbox(args) local digits = {} local wn_prec = args.whole_num_precision local fr_prec = args.fractional_precision - local dec_point_x = args.whole_num_precision + 1 assert(util.is_int(wn_prec), "graphics.element.controls.spinbox_numeric: whole number precision must be an integer") assert(util.is_int(fr_prec), "graphics.element.controls.spinbox_numeric: fractional precision must be an integer") + local dec_point_x = args.whole_num_precision + 1 + assert(type(args.arrow_fg_bg) == "table", "graphics.element.spinbox_numeric: arrow_fg_bg is a required field") local initial_str = util.sprintf("%" .. wn_prec .. "." .. fr_prec .. "f", value) @@ -48,12 +49,23 @@ local function spinbox(args) e.window.setCursorPos(1, 3) e.window.write(util.strrep("\x1f", wn_prec)) if fr_prec > 0 then - e.window.setCursorPos(1, 1) + e.window.setCursorPos(1 + wn_prec, 1) e.window.write(" " .. util.strrep("\x1e", fr_prec)) - e.window.setCursorPos(1, 3) + e.window.setCursorPos(1 + wn_prec, 3) e.window.write(" " .. util.strrep("\x1f", fr_prec)) end + -- print out the current value + local function show_num() + e.window.setBackgroundColor(e.fg_bg.bkg) + e.window.setTextColor(e.fg_bg.fgd) + e.window.setCursorPos(1, 2) + e.window.write(util.sprintf("%" .. wn_prec + fr_prec + 1 .. "." .. fr_prec .. "f", value)) + end + + -- init with the default value + show_num() + -- handle touch ---@param event monitor_touch monitor touch event function e.handle_touch(event) @@ -78,6 +90,8 @@ local function spinbox(args) value = value + (digits[i] * (10 ^ -pow)) end end + + show_num() end end diff --git a/graphics/elements/indicators/light.lua b/graphics/elements/indicators/light.lua index 6db28db..3d733df 100644 --- a/graphics/elements/indicators/light.lua +++ b/graphics/elements/indicators/light.lua @@ -23,31 +23,26 @@ local function indicator_light(args) args.height = 1 -- determine width - args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 3 + args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2 -- create new graphics element base object local e = element.new(args) - -- on/off blit strings - local on_blit = util.strrep(args.colors.blit_a, 2) - local off_blit = util.strrep(args.colors.blit_b, 2) - - -- write label and initial indicator light - e.window.setCursorPos(1, 1) - e.window.blit(" ", "000", off_blit .. e.fg_bg.blit_bkg) - e.window.write(args.label) - -- on state change ---@param new_state boolean indicator state function e.on_update(new_state) e.window.setCursorPos(1, 1) if new_state then - e.window.blit(" ", "00", on_blit) + e.window.blit(" \x95", "0" .. args.colors.blit_a, args.colors.blit_a .. e.fg_bg.blit_bkg) else - e.window.blit(" ", "00", off_blit) + e.window.blit(" \x95", "0" .. args.colors.blit_b, args.colors.blit_b .. e.fg_bg.blit_bkg) end end + -- write label and initial indicator light + e.on_update(false) + e.window.write(args.label) + return e.get() end diff --git a/graphics/elements/tiling.lua b/graphics/elements/tiling.lua index c046670..47367b4 100644 --- a/graphics/elements/tiling.lua +++ b/graphics/elements/tiling.lua @@ -7,7 +7,7 @@ local element = require("graphics.element") ---@class tiling_args ---@field fill_c cpair colors to fill with ---@field even? boolean whether to account for rectangular pixels ----@field border? graphics_border +---@field border_c? color optional frame color ---@field parent graphics_element ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted @@ -33,37 +33,37 @@ local function tiling(args) local start_x = 1 local start_y = 1 - local width = e.frame.w - local height = e.frame.h + local inner_width = math.floor(e.frame.w / util.trinary(even, 2, 1)) + local inner_height = e.frame.h local alternator = true -- border - if args.border ~= nil then - e.window.setBackgroundColor(args.border.color) + if args.border_c ~= nil then + e.window.setBackgroundColor(args.border_c) e.window.clear() - start_x = 1 + util.trinary(args.border.even, args.border.width * 2, args.border.width) - start_y = 1 + args.border.width + start_x = 1 + util.trinary(even, 2, 1) + start_y = 2 - width = width - (2 * util.trinary(args.border.even, args.border.width * 2, args.border.width)) - height = height - (2 * args.border.width) + inner_width = math.floor((e.frame.w - 2 * util.trinary(even, 2, 1)) / 2) + inner_height = e.frame.h - 2 end -- check dimensions - assert(start_x <= width, "graphics.elements.tiling: start_x > width") - assert(start_y <= height, "graphics.elements.tiling: start_y > height") - assert(width > 0, "graphics.elements.tiling: width <= 0") - assert(height > 0, "graphics.elements.tiling: height <= 0") + assert(inner_width > 0, "graphics.elements.tiling: inner_width <= 0") + assert(inner_height > 0, "graphics.elements.tiling: inner_height <= 0") + assert(start_x <= inner_width, "graphics.elements.tiling: start_x > inner_width") + assert(start_y <= inner_height, "graphics.elements.tiling: start_y > inner_height") -- create pattern - for y = start_y, height do - e.window.setCursorPos(1, y) - for _ = start_x, width do + for y = start_y, inner_height + (start_y - 1) do + e.window.setCursorPos(start_x, y) + for x = 1, inner_width do if alternator then if even then - e.window.blit(" ", "00", fill_a .. fill_a) + e.window.blit("NF", "00", fill_a .. fill_a) else - e.window.blit(" ", "0", fill_a) + e.window.blit("F", "0", fill_a) end else if even then @@ -73,7 +73,7 @@ local function tiling(args) end end - alternator = not alternator + if x ~= inner_width then alternator = not alternator end end end From 2aedc015c82e395a836dfe7f29ce03741b08a329 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 17 Jul 2022 15:05:27 -0400 Subject: [PATCH 332/587] correctly find mek 10.1+ fission reactors --- reactor-plc/startup.lua | 2 +- scada-common/ppm.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index b4b932b..c718f19 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.8.0" +local R_PLC_VERSION = "beta-v0.8.1" local print = util.print local println = util.println diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index bd417aa..b383e5c 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -298,7 +298,7 @@ end -- get the fission reactor (if multiple, returns the first) ---@return table|nil reactor function table function ppm.get_fission_reactor() - return ppm.get_device("fissionReactor") + return ppm.get_device("fissionReactor") or ppm.get_device("fissionReactorLogicAdapter") end -- get the wireless modem (if multiple, returns the first) From 41cc6b9accebea99e894834b4f37dbf36803f1ea Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 19 Jul 2022 14:02:20 -0400 Subject: [PATCH 333/587] support for craftos-pc env by supporting modems instead of wireless modems for comms --- .vscode/settings.json | 3 ++- scada-common/ppm.lua | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3974dbe..1a28f3c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,6 +11,7 @@ "shell", "settings", "window", - "read" + "read", + "periphemu" ] } diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index b383e5c..3ca4921 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -302,12 +302,15 @@ function ppm.get_fission_reactor() end -- get the wireless modem (if multiple, returns the first) +-- +-- if this is in a CraftOS emulated environment, wired modems will be used instead ---@return table|nil modem function table function ppm.get_wireless_modem() local w_modem = nil + local emulated_env = periphemu ~= nil for _, device in pairs(_ppm_sys.mounts) do - if device.type == "modem" and device.dev.isWireless() then + if device.type == "modem" and (emulated_env or device.dev.isWireless()) then w_modem = device.dev break end From d6a201a45f4d89e71e9c3a36a7b8d01f07e786bf Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 19 Jul 2022 14:03:02 -0400 Subject: [PATCH 334/587] #73 initial unit view --- coordinator/coordinator.lua | 2 +- coordinator/startup.lua | 2 +- coordinator/ui/layout/unit_view.lua | 69 +++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 78e6a1d..3a304f6 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -6,7 +6,7 @@ local util = require("scada-common.util") local apisessions = require("coordinator.apisessions") local database = require("coordinator.database") -local dialog = require("coordinator.util.dialog") +local dialog = require("coordinator.ui.dialog") local coordinator = {} diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 0ef6814..efcd82c 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.3.3" +local COORDINATOR_VERSION = "alpha-v0.3.4" local print = util.print local println = util.println diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index e82657b..f84d216 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -7,15 +7,84 @@ local core = require("graphics.core") local style = require("coordinator.ui.style") local DisplayBox = require("graphics.elements.displaybox") +local Div = require("graphics.elements.div") local TextBox = require("graphics.elements.textbox") +local Tiling = require("graphics.elements.tiling") + +local DataIndicator = require("graphics.elements.indicators.data") +local HorizontalBar = require("graphics.elements.indicators.hbar") +local IndicatorLight = require("graphics.elements.indicators.light") +local StateIndicator = require("graphics.elements.indicators.state") + +local PushButton = require("graphics.elements.controls.push_button") +local SCRAMButton = require("graphics.elements.controls.scram_button") +local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") local TEXT_ALIGN = core.graphics.TEXT_ALIGN +local cpair = core.graphics.cpair +local border = core.graphics.border + local function init(monitor, id) local main = DisplayBox{window=monitor,fg_bg=style.root} TextBox{parent=main,text="Reactor Unit #" .. id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + local reactor_width = 18 + local core_width = ((reactor_width - 2) * 2) + 4 + local core_height = reactor_width + + local scram_fg_bg = core.graphics.cpair(colors.white, colors.gray) + + local reactor_top_view = Tiling{parent=main,x=2,y=3,width=core_width,height=core_height,fill_c=cpair(colors.lightGray,colors.lightBlue),even=true,border_c=colors.gray} + + local f = function () print("scram!") end + local scram = SCRAMButton{parent=main,x=2,y=core_height+4,callback=f,fg_bg=scram_fg_bg} + + local burn_control = Div{parent=main,x=13,y=core_height+4,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} + + local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} + local set_burn = function () print("set burn to " .. burn_rate.get_value()) end + + TextBox{parent=burn_control,x=9,y=2,text="mB/t"} + PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),callback=set_burn} + + local annunciator = Div{parent=main,x=34,y=core_height+4} + + -- annunciator colors per IAEA-TECDOC-812 recommendations + + -- connectivity/basic state + local plc_online = IndicatorLight{parent=annunciator,x=1,y=1,label="PLC Online",colors=cpair(colors.green,colors.red)} + local r_active = IndicatorLight{parent=annunciator,x=1,y=2,label="Active",colors=cpair(colors.green,colors.gray)} + local r_auto = IndicatorLight{parent=annunciator,x=1,y=3,label="Auto Control",colors=cpair(colors.blue,colors.gray)} + + -- annunciator fields + local r_trip = IndicatorLight{parent=annunciator,x=1,y=5,label="Reactor Trip",colors=cpair(colors.red,colors.gray)} + local r_mtrp = IndicatorLight{parent=annunciator,x=1,y=6,label="Manual Reactor Trip",colors=cpair(colors.red,colors.gray)} + local r_rtrp = IndicatorLight{parent=annunciator,x=1,y=7,label="RCP Trip",colors=cpair(colors.red,colors.gray)} + local r_cflo = IndicatorLight{parent=annunciator,x=1,y=8,label="RCS Flow Low",colors=cpair(colors.yellow,colors.gray)} + local r_temp = IndicatorLight{parent=annunciator,x=1,y=9,label="Reactor Temp. High",colors=cpair(colors.red,colors.gray)} + local r_rhdt = IndicatorLight{parent=annunciator,x=1,y=10,label="Reactor High Delta T",colors=cpair(colors.yellow,colors.gray)} + local r_firl = IndicatorLight{parent=annunciator,x=1,y=11,label="Fuel Input Rate Low",colors=cpair(colors.yellow,colors.gray)} + local r_wloc = IndicatorLight{parent=annunciator,x=1,y=12,label="Waste Line Occlusion",colors=cpair(colors.yellow,colors.gray)} + local r_hsrt = IndicatorLight{parent=annunciator,x=1,y=13,label="High Startup Rate",colors=cpair(colors.yellow,colors.gray)} + + -- RPS + local rps_trp = IndicatorLight{parent=annunciator,x=1,y=15,label="RPS Trip",colors=cpair(colors.red,colors.gray)} + local rps_dmg = IndicatorLight{parent=annunciator,x=1,y=16,label="Damage Critical",colors=cpair(colors.yellow,colors.gray)} + local rps_exh = IndicatorLight{parent=annunciator,x=1,y=17,label="Excess Heated Coolant",colors=cpair(colors.yellow,colors.gray)} + local rps_exc = IndicatorLight{parent=annunciator,x=1,y=18,label="Excess Waste",colors=cpair(colors.yellow,colors.gray)} + local rps_tmp = IndicatorLight{parent=annunciator,x=1,y=19,label="High Core Temp",colors=cpair(colors.yellow,colors.gray)} + local rps_nof = IndicatorLight{parent=annunciator,x=1,y=20,label="No Fuel",colors=cpair(colors.yellow,colors.gray)} + local rps_noc = IndicatorLight{parent=annunciator,x=1,y=21,label="No Coolant",colors=cpair(colors.yellow,colors.gray)} + local rps_flt = IndicatorLight{parent=annunciator,x=1,y=22,label="PPM Fault",colors=cpair(colors.yellow,colors.gray)} + local rps_tmo = IndicatorLight{parent=annunciator,x=1,y=23,label="Timeout",colors=cpair(colors.yellow,colors.gray)} + + r_auto.update(true) + r_trip.update(true) + r_mtrp.update(true) + rps_trp.update(true) + return main end From 1afafba5014308e4c405c1d3afb2ad7084e52a42 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 19 Jul 2022 15:18:11 -0400 Subject: [PATCH 335/587] wrap os.pullEventRaw to have return types --- coordinator/coordinator.lua | 3 +-- coordinator/startup.lua | 5 ++--- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 3 +-- rtu/startup.lua | 2 +- rtu/threads.lua | 3 +-- scada-common/types.lua | 34 ++++++++++++++++++++++++++++++++++ scada-common/util.lua | 10 ++++++++++ supervisor/startup.lua | 5 ++--- 9 files changed, 53 insertions(+), 14 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 3a304f6..9a72cdb 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -267,8 +267,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa 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() + local event, p1, p2, p3, p4, p5 = util.pull_event() if event == "timer" and clock.is_clock(p1) then -- timed out attempt, try again diff --git a/coordinator/startup.lua b/coordinator/startup.lua index efcd82c..d0320c7 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.3.4" +local COORDINATOR_VERSION = "alpha-v0.3.5" local print = util.print local println = util.println @@ -149,8 +149,7 @@ log.debug("boot> conn watchdog started") -- event loop -- ui_ok will never change in this loop, same as while true or exit if UI start failed while ui_ok do ----@diagnostic disable-next-line: undefined-field - local event, param1, param2, param3, param4, param5 = os.pullEventRaw() + local event, param1, param2, param3, param4, param5 = util.pull_event() -- handle event if event == "peripheral_detach" then diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index c718f19..62c3efe 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.8.1" +local R_PLC_VERSION = "beta-v0.8.2" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 45a8201..2ee2906 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -55,8 +55,7 @@ function threads.thread__main(smem, init) local plc_comms = smem.plc_sys.plc_comms local conn_watchdog = smem.plc_sys.conn_watchdog ----@diagnostic disable-next-line: undefined-field - local event, param1, param2, param3, param4, param5 = os.pullEventRaw() + local event, param1, param2, param3, param4, param5 = util.pull_event() -- handle event if event == "timer" and loop_clock.is_clock(param1) then diff --git a/rtu/startup.lua b/rtu/startup.lua index ed39c9a..2c00791 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.7.9" +local RTU_VERSION = "beta-v0.7.10" local rtu_t = types.rtu_t diff --git a/rtu/threads.lua b/rtu/threads.lua index 4e812de..42ea746 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -49,8 +49,7 @@ function threads.thread__main(smem) -- event loop while true do ----@diagnostic disable-next-line: undefined-field - local event, param1, param2, param3, param4, param5 = os.pullEventRaw() + local event, param1, param2, param3, param4, param5 = util.pull_event() if event == "timer" and loop_clock.is_clock(param1) then -- start next clock timer diff --git a/scada-common/types.lua b/scada-common/types.lua index 7e31e58..75f381a 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -37,6 +37,40 @@ types.TRI_FAIL = { -- STRING TYPES -- +---@alias os_event +---| "alarm" +---| "char" +---| "computer_command" +---| "disk" +---| "disk_eject" +---| "http_check" +---| "http_failure" +---| "http_success" +---| "key" +---| "key_up" +---| "modem_message" +---| "monitor_resize" +---| "monitor_touch" +---| "mouse_click" +---| "mouse_drag" +---| "mouse_scroll" +---| "mouse_up" +---| "paste" +---| "peripheral" +---| "peripheral_detach" +---| "rednet_message" +---| "redstone" +---| "speaker_audio_empty" +---| "task_complete" +---| "term_resize" +---| "terminate" +---| "timer" +---| "turtle_inventory" +---| "websocket_closed" +---| "websocket_failure" +---| "websocket_message" +---| "websocket_success" + ---@alias rtu_t string types.rtu_t = { redstone = "redstone", diff --git a/scada-common/util.lua b/scada-common/util.lua index 2bd1903..ccf9ff4 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -180,6 +180,16 @@ function util.time() return util.time_ms() end +-- OS -- + +-- OS pull event raw wrapper with types +---@param target_event? string event to wait for +---@return os_event event, any param1, any param2, any param3, any param4, any param5 +function util.pull_event(target_event) +---@diagnostic disable-next-line: undefined-field + return os.pullEventRaw(target_event) +end + -- PARALLELIZATION -- -- protected sleep call so we still are in charge of catching termination diff --git a/supervisor/startup.lua b/supervisor/startup.lua index be4f366..49348ed 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.5.3" +local SUPERVISOR_VERSION = "beta-v0.5.4" local print = util.print local println = util.println @@ -84,8 +84,7 @@ loop_clock.start() -- event loop while true do ----@diagnostic disable-next-line: undefined-field - local event, param1, param2, param3, param4, param5 = os.pullEventRaw() + local event, param1, param2, param3, param4, param5 = util.pull_event() -- handle event if event == "peripheral_detach" then From 9b21a971fef408445530c11e7f7a808334c1103d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 20 Jul 2022 13:28:58 -0400 Subject: [PATCH 336/587] #74 close supervisor connection on exit, start of touch event handling --- coordinator/coordinator.lua | 12 +++++++++++- coordinator/renderer.lua | 17 ++++++++++++++++- coordinator/startup.lua | 13 ++++++++++--- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 9a72cdb..5972bfc 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -44,7 +44,9 @@ function coordinator.configure_monitors(num_units) ---@class monitors_struct local monitors = { primary = nil, - unit_displays = {} + primary_name = "", + unit_displays = {}, + unit_name_map = {} } local monitors_avail = ppm.get_monitor_list() @@ -89,6 +91,7 @@ function coordinator.configure_monitors(num_units) util.filter_table(names, function (x) return x ~= iface_primary_display end) monitors.primary = ppm.get_periph(iface_primary_display) + monitors.primary_name = iface_primary_display ------------------- -- UNIT DISPLAYS -- @@ -139,6 +142,7 @@ function coordinator.configure_monitors(num_units) for i = 1, #unit_displays do monitors.unit_displays[i] = ppm.get_periph(unit_displays[i]) + monitors.unit_name_map[i] = unit_displays[i] end return true, monitors @@ -251,6 +255,12 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa _open_channels() end + -- close the connection to the server + function public.close() + sv_watchdog.cancel() + _send_sv(PROTOCOLS.SCADA_MGMT, SCADA_MGMT_TYPES.CLOSE, {}) + end + -- attempt to connect to the subervisor ---@param timeout_s number timeout in seconds ---@param tick_dmesg_waiting function callback to tick dmesg waiting diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 59fafcf..2e49026 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -1,4 +1,4 @@ -local log = require("scada-common.log") +local log = require("scada-common.log") local main_view = require("coordinator.ui.layout.main_view") local unit_view = require("coordinator.ui.layout.unit_view") @@ -99,4 +99,19 @@ function renderer.close_ui(recolor) engine.dmesg_window.redraw() end +-- handle a touch event +---@param event monitor_touch +function renderer.handle_touch(event) + if event.monitor == engine.monitors.primary_name then + ui.main_layout.handle_touch(event) + else + for id, monitor in pairs(engine.monitors.unit_name_map) do + if event.monitor == monitor then + local layout = ui.unit_layouts[id] ---@type graphics_element + layout.handle_touch(event) + end + end + end +end + return renderer diff --git a/coordinator/startup.lua b/coordinator/startup.lua index d0320c7..b6728f7 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -8,12 +8,14 @@ local log = require("scada-common.log") local ppm = require("scada-common.ppm") local util = require("scada-common.util") +local core = require("graphics.core") + local apisessions = require("coordinator.apisessions") local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.3.5" +local COORDINATOR_VERSION = "alpha-v0.3.6" local print = util.print local println = util.println @@ -211,12 +213,17 @@ while ui_ok do -- got a packet local packet = coord_comms.parse_packet(param1, param2, param3, param4, param5) coord_comms.handle_packet(packet) + elseif event == "monitor_touch" then + -- handle a monitor touch event + renderer.handle_touch(core.events.touch(param1, param2, param3)) end -- check for termination request if event == "terminate" or ppm.should_terminate() then - log_comms("terminate requested, closing sessions...") - println_ts("closing sessions...") + log_comms("terminate requested, closing supervisor connection") + coord_comms.close() + log_comms("closing api sessions...") + println_ts("closing api sessions...") apisessions.close_all() log_comms("api sessions closed") break From fc141413219511b984d42071a63efb6fb098d4cb Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 23 Jul 2022 20:08:37 -0400 Subject: [PATCH 337/587] #73 unit overview parent/child setup, fixed touch events by setting up children for elements --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_overview.lua | 1 - coordinator/ui/layout/unit_view.lua | 6 ++- graphics/element.lua | 60 +++++++++++++++++++-- 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index b6728f7..e685797 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -15,7 +15,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.3.6" +local COORDINATOR_VERSION = "alpha-v0.3.7" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 30f887b..0f70ad8 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -114,7 +114,6 @@ local function make(parent, x, y, unit) end else -- boiler side pipes - local steam_pipes_a = { -- boiler 1 steam/water pipes pipe(0, 1, 6, 1, colors.white, false, true), -- steam boiler 1 to turbine junction diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index f84d216..45ce5ed 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -43,11 +43,15 @@ local function init(monitor, id) local burn_control = Div{parent=main,x=13,y=core_height+4,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} + main(scram, burn_control) + local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} local set_burn = function () print("set burn to " .. burn_rate.get_value()) end + burn_control(burn_rate) + TextBox{parent=burn_control,x=9,y=2,text="mB/t"} - PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),callback=set_burn} + burn_control(PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),callback=set_burn}) local annunciator = Div{parent=main,x=34,y=core_height+4} diff --git a/graphics/element.lua b/graphics/element.lua index 274681e..ee4b633 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -26,7 +26,9 @@ function element.new(args) p_window = nil, ---@type table position = { x = 1, y = 1 }, child_offset = { x = 0, y = 0 }, - bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1} + bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1}, + children = {}, + mt = {} } local protected = { @@ -35,6 +37,38 @@ function element.new(args) frame = core.graphics.gframe(1, 1, 1, 1) } + -- append a child element without a tag + local function add_child(child) + table.insert(self.children, child) + return #self.children + end + + -- add a child element without a tag + function self.mt.__add(_, child) return add_child(child) end + function self.mt.__lt(_, child) return add_child(child) end + function self.mt.__le(_, child) return add_child(child) end + + -- add a child element without a tag + ---@param _ table ignored (self) + ---@vararg table children + ---@return integer|table id/ids + function self.mt.__call(_, ...) + local children = { ... } + + if #children == 1 then + return add_child(children[1]) + else + local ids = {} + for _, v in ipairs(children) do table.insert(ids, add_child(v)) end + return ids + end + end + + -- element as string + function self.mt.__tostring() + return "graphics.element{" .. self.elem_type .. "}"-- @ " .. tostring(self) + end + -- SETUP -- -- get the parent window @@ -131,6 +165,8 @@ function element.new(args) ---@class graphics_element local public = {} + setmetatable(public, self.mt) + -- get public interface function protected.get() return public end @@ -139,6 +175,19 @@ function element.new(args) -- get the window object function public.window() return protected.window end + -- add a child element + ---@param key string id + ---@param child graphics_element + function public.add_child(key, child) self.children[key] = child end + + -- get a child element + ---@return graphics_element + function public.get_child(key) return self.children[key] end + + -- remove child + ---@param key string|integer + function public.remove(key) self.children[key] = nil end + -- get the foreground/background colors function public.get_fg_bg() return protected.fg_bg end @@ -165,10 +214,13 @@ function element.new(args) local in_y = event.y >= self.bounds.y1 and event.y <= self.bounds.y2 if in_x and in_y then + local event_T = core.events.touch(event.monitor, (event.x - self.position.x) + 1, (event.y - self.position.y) + 1) + -- handle the touch event, transformed into the window frame - protected.handle_touch(core.events.touch(event.monitor, - (event.x - self.position.x) + 1, - (event.y - self.position.y) + 1)) + protected.handle_touch(event_T) + + -- pass on touch event to children + for _, val in pairs(self.children) do val.handle_touch(event_T) end end end From 01a364b5cf2e2d18e4cb707f09aa96fa2425b314 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 23 Jul 2022 20:08:52 -0400 Subject: [PATCH 338/587] fixed bug with spinbox --- graphics/elements/controls/spinbox_numeric.lua | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/graphics/elements/controls/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua index 589d1ef..37a40c2 100644 --- a/graphics/elements/controls/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -13,7 +13,7 @@ local util = require("scada-common.util") ---@field y? integer 1 if omitted ---@field fg_bg? cpair foreground/background colors --- new spinbox control +-- new spinbox control (minimum value is 0) ---@param args spinbox_args local function spinbox(args) -- properties @@ -25,11 +25,13 @@ local function spinbox(args) assert(util.is_int(wn_prec), "graphics.element.controls.spinbox_numeric: whole number precision must be an integer") assert(util.is_int(fr_prec), "graphics.element.controls.spinbox_numeric: fractional precision must be an integer") + local fmt = "%" .. (wn_prec + fr_prec + 1) .. "." .. fr_prec .. "f" + local fmt_init = "%0" .. (wn_prec + fr_prec + 1) .. "." .. fr_prec .. "f" local dec_point_x = args.whole_num_precision + 1 assert(type(args.arrow_fg_bg) == "table", "graphics.element.spinbox_numeric: arrow_fg_bg is a required field") - local initial_str = util.sprintf("%" .. wn_prec .. "." .. fr_prec .. "f", value) + local initial_str = util.sprintf(fmt_init, value) ---@diagnostic disable-next-line: discard-returns initial_str:gsub("%d", function(char) table.insert(digits, char) end) @@ -55,12 +57,18 @@ local function spinbox(args) e.window.write(" " .. util.strrep("\x1f", fr_prec)) end + -- zero the value + local function zero() + for i = 1, #digits do digits[i] = 0 end + value = 0 + end + -- print out the current value local function show_num() e.window.setBackgroundColor(e.fg_bg.bkg) e.window.setTextColor(e.fg_bg.fgd) e.window.setCursorPos(1, 2) - e.window.write(util.sprintf("%" .. wn_prec + fr_prec + 1 .. "." .. fr_prec .. "f", value)) + e.window.write(util.sprintf(fmt, value)) end -- init with the default value @@ -91,6 +99,9 @@ local function spinbox(args) end end + -- min 0 + if value < 0 then zero() end + show_num() end end From f1a50990f25e5798bb0a603e2effb6f4785daf6c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 28 Jul 2022 10:09:34 -0400 Subject: [PATCH 339/587] #84 improved element creation process for adding children --- graphics/core.lua | 6 +- graphics/element.lua | 67 ++++++++++--------- graphics/elements/controls/push_button.lua | 4 +- graphics/elements/controls/scram_button.lua | 4 +- .../elements/controls/spinbox_numeric.lua | 4 +- graphics/elements/controls/switch_button.lua | 4 +- graphics/elements/displaybox.lua | 2 +- graphics/elements/div.lua | 4 +- graphics/elements/indicators/data.lua | 4 +- graphics/elements/indicators/hbar.lua | 5 +- graphics/elements/indicators/icon.lua | 4 +- graphics/elements/indicators/light.lua | 4 +- graphics/elements/indicators/state.lua | 4 +- graphics/elements/indicators/vbar.lua | 4 +- graphics/elements/pipenet.lua | 10 +-- graphics/elements/rectangle.lua | 4 +- graphics/elements/textbox.lua | 6 +- graphics/elements/tiling.lua | 4 +- 18 files changed, 90 insertions(+), 54 deletions(-) diff --git a/graphics/core.lua b/graphics/core.lua index 4894b3a..f4d86fd 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -40,6 +40,8 @@ graphics.TEXT_ALIGN = { ---@field color color ---@field even boolean +---@alias element_id string|integer + -- create a new border definition ---@param width integer border width ---@param color color border color @@ -111,7 +113,7 @@ end ---@field w integer width ---@field h integer height ---@field color color pipe color ----@field thin boolean true for 1 subpixels, false (default) for 2 +---@field thin boolean true for 1 subpixel, false (default) for 2 ---@field align_tr boolean false to align bottom left (default), true to align top right -- create a new pipe @@ -122,7 +124,7 @@ end ---@param x2 integer ending x, origin is 0 ---@param y2 integer ending y, origin is 0 ---@param color color pipe color ----@param thin? boolean true for 1 subpixels, false (default) for 2 +---@param thin? boolean true for 1 subpixel, false (default) for 2 ---@param align_tr? boolean false to align bottom left (default), true to align top right ---@return pipe function graphics.pipe(x1, y1, x2, y2, color, thin, align_tr) diff --git a/graphics/element.lua b/graphics/element.lua index ee4b633..64e1029 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -9,6 +9,7 @@ local element = {} ---@class graphics_args_generic ---@field window? table ---@field parent? graphics_element +---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field offset_x? integer 0 if omitted @@ -22,7 +23,9 @@ local element = {} ---@param args graphics_args_generic arguments function element.new(args) local self = { + id = -1, elem_type = debug.getinfo(2).name, + define_completed = false, p_window = nil, ---@type table position = { x = 1, y = 1 }, child_offset = { x = 0, y = 0 }, @@ -31,42 +34,16 @@ function element.new(args) mt = {} } + ---@class graphics_template local protected = { window = nil, ---@type table fg_bg = core.graphics.cpair(colors.white, colors.black), frame = core.graphics.gframe(1, 1, 1, 1) } - -- append a child element without a tag - local function add_child(child) - table.insert(self.children, child) - return #self.children - end - - -- add a child element without a tag - function self.mt.__add(_, child) return add_child(child) end - function self.mt.__lt(_, child) return add_child(child) end - function self.mt.__le(_, child) return add_child(child) end - - -- add a child element without a tag - ---@param _ table ignored (self) - ---@vararg table children - ---@return integer|table id/ids - function self.mt.__call(_, ...) - local children = { ... } - - if #children == 1 then - return add_child(children[1]) - else - local ids = {} - for _, v in ipairs(children) do table.insert(ids, add_child(v)) end - return ids - end - end - -- element as string function self.mt.__tostring() - return "graphics.element{" .. self.elem_type .. "}"-- @ " .. tostring(self) + return "graphics.element{" .. self.elem_type .. "} @ " .. tostring(self) end -- SETUP -- @@ -167,8 +144,21 @@ function element.new(args) setmetatable(public, self.mt) - -- get public interface - function protected.get() return public end + -- get public interface and wrap up element creation + ---@return graphics_element element, element_id id + function protected.complete() + if not self.define_completed then + self.define_completed = true + + if args.parent then + self.id = args.parent.__add_child(args.id, public) + end + + return public, self.id + else + assert("graphics.element{" .. self.elem_type .. "}: illegal duplicate call to complete()") + end + end -- PUBLIC FUNCTIONS -- @@ -176,9 +166,18 @@ function element.new(args) function public.window() return protected.window end -- add a child element - ---@param key string id + ---@param key string|nil id ---@param child graphics_element - function public.add_child(key, child) self.children[key] = child end + ---@return graphics_element element, integer|string key + function public.__add_child(key, child) + if key == nil then + table.insert(self.children, child) + return child, #self.children + else + self.children[key] = child + return child, key + end + end -- get a child element ---@return graphics_element @@ -188,6 +187,10 @@ function element.new(args) ---@param key string|integer function public.remove(key) self.children[key] = nil end + ---@param id element_id + function public.get_element_by_id(id) + end + -- get the foreground/background colors function public.get_fg_bg() return protected.fg_bg end diff --git a/graphics/elements/controls/push_button.lua b/graphics/elements/controls/push_button.lua index 82aa081..35e70a6 100644 --- a/graphics/elements/controls/push_button.lua +++ b/graphics/elements/controls/push_button.lua @@ -10,6 +10,7 @@ local element = require("graphics.element") ---@field min_width? integer text length + 2 if omitted ---@field active_fg_bg? cpair foreground/background colors when pressed ---@field parent graphics_element +---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field height? integer parent height if omitted @@ -17,6 +18,7 @@ local element = require("graphics.element") -- new push button ---@param args push_button_args +---@return graphics_element element, element_id id local function push_button(args) assert(type(args.text) == "string", "graphics.elements.controls.push_button: text is a required field") assert(type(args.callback) == "function", "graphics.elements.controls.push_button: callback is a required field") @@ -61,7 +63,7 @@ local function push_button(args) args.callback() end - return e.get() + return e.complete() end return push_button diff --git a/graphics/elements/controls/scram_button.lua b/graphics/elements/controls/scram_button.lua index 1694969..e916fbe 100644 --- a/graphics/elements/controls/scram_button.lua +++ b/graphics/elements/controls/scram_button.lua @@ -9,12 +9,14 @@ local element = require("graphics.element") ---@class scram_button_args ---@field callback function function to call on touch ---@field parent graphics_element +---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field fg_bg? cpair foreground/background colors -- new scram button ---@param args scram_button_args +---@return graphics_element element, element_id id local function scram_button(args) assert(type(args.callback) == "function", "graphics.elements.controls.scram_button: callback is a required field") @@ -63,7 +65,7 @@ local function scram_button(args) args.callback() end - return e.get() + return e.complete() end return scram_button diff --git a/graphics/elements/controls/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua index 37a40c2..16237d6 100644 --- a/graphics/elements/controls/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -9,12 +9,14 @@ local util = require("scada-common.util") ---@field fractional_precision integer number of fractional digits ---@field arrow_fg_bg cpair arrow foreground/background colors ---@field parent graphics_element +---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field fg_bg? cpair foreground/background colors -- new spinbox control (minimum value is 0) ---@param args spinbox_args +---@return graphics_element element, element_id id local function spinbox(args) -- properties local value = args.default or 0.0 @@ -110,7 +112,7 @@ local function spinbox(args) ---@return number|integer function e.get_value() return value end - return e.get() + return e.complete() end return spinbox diff --git a/graphics/elements/controls/switch_button.lua b/graphics/elements/controls/switch_button.lua index 03c25bb..aae443c 100644 --- a/graphics/elements/controls/switch_button.lua +++ b/graphics/elements/controls/switch_button.lua @@ -9,6 +9,7 @@ local element = require("graphics.element") ---@field min_width? integer text length + 2 if omitted ---@field active_fg_bg cpair foreground/background colors when pressed ---@field parent graphics_element +---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field height? integer parent height if omitted @@ -16,6 +17,7 @@ local element = require("graphics.element") -- new switch button (latch high/low) ---@param args switch_button_args +---@return graphics_element element, element_id id local function switch_button(args) assert(type(args.text) == "string", "graphics.elements.controls.switch_button: text is a required field") assert(type(args.callback) == "function", "graphics.elements.controls.switch_button: callback is a required field") @@ -71,7 +73,7 @@ local function switch_button(args) args.callback(state) end - return e.get() + return e.complete() end return switch_button diff --git a/graphics/elements/displaybox.lua b/graphics/elements/displaybox.lua index 0d7fd47..218f599 100644 --- a/graphics/elements/displaybox.lua +++ b/graphics/elements/displaybox.lua @@ -15,7 +15,7 @@ local element = require("graphics.element") ---@param args displaybox_args local function displaybox(args) -- create new graphics element base object - return element.new(args).get() + return element.new(args).complete() end return displaybox diff --git a/graphics/elements/div.lua b/graphics/elements/div.lua index cb56aa0..0864b36 100644 --- a/graphics/elements/div.lua +++ b/graphics/elements/div.lua @@ -4,6 +4,7 @@ local element = require("graphics.element") ---@class div_args ---@field parent graphics_element +---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field width? integer parent width if omitted @@ -13,9 +14,10 @@ local element = require("graphics.element") -- new div element ---@param args div_args +---@return graphics_element element, element_id id local function div(args) -- create new graphics element base object - return element.new(args).get() + return element.new(args).complete() end return div diff --git a/graphics/elements/indicators/data.lua b/graphics/elements/indicators/data.lua index 07930c5..1022551 100644 --- a/graphics/elements/indicators/data.lua +++ b/graphics/elements/indicators/data.lua @@ -35,6 +35,7 @@ end ---@field lu_colors? cpair label foreground color (a), unit foreground color (b) ---@field value any default value ---@field parent graphics_element +---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field width integer length @@ -42,6 +43,7 @@ end -- new data indicator ---@param args data_indicator_args +---@return graphics_element element, element_id id local function data(args) assert(type(args.label) == "string", "graphics.elements.indicators.data: label is a required field") assert(type(args.format) == "string", "graphics.elements.indicators.data: format is a required field") @@ -91,7 +93,7 @@ local function data(args) -- initial value draw e.on_update(args.value) - return e.get() + return e.complete() end return data diff --git a/graphics/elements/indicators/hbar.lua b/graphics/elements/indicators/hbar.lua index c886a63..53862aa 100644 --- a/graphics/elements/indicators/hbar.lua +++ b/graphics/elements/indicators/hbar.lua @@ -8,6 +8,7 @@ local element = require("graphics.element") ---@field show_percent? boolean whether or not to show the percent ---@field bar_fg_bg? cpair bar foreground/background colors if showing percent ---@field parent graphics_element +---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field width? integer parent width if omitted @@ -17,6 +18,8 @@ local element = require("graphics.element") -- new horizontal bar ---@param args hbar_args +---@return graphics_element element, element_id id +---@return graphics_element element, element_id id local function hbar(args) -- properties/state local last_num_bars = -1 @@ -96,7 +99,7 @@ local function hbar(args) -- initialize to 0 e.on_update(0) - return e.get() + return e.complete() end return hbar diff --git a/graphics/elements/indicators/icon.lua b/graphics/elements/indicators/icon.lua index d116f2b..a33711a 100644 --- a/graphics/elements/indicators/icon.lua +++ b/graphics/elements/indicators/icon.lua @@ -14,12 +14,14 @@ local element = require("graphics.element") ---@field value? integer default state, defaults to 1 ---@field min_label_width? integer label length if omitted ---@field parent graphics_element +---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field fg_bg? cpair foreground/background colors -- new icon indicator ---@param args icon_indicator_args +---@return graphics_element element, element_id id local function icon(args) assert(type(args.label) == "string", "graphics.elements.indicators.icon: label is a required field") assert(type(args.states) == "table", "graphics.elements.indicators.icon: states is a required field") @@ -60,7 +62,7 @@ local function icon(args) -- initial icon draw e.on_update(args.value or 1) - return e.get() + return e.complete() end return icon diff --git a/graphics/elements/indicators/light.lua b/graphics/elements/indicators/light.lua index 3d733df..f902bfb 100644 --- a/graphics/elements/indicators/light.lua +++ b/graphics/elements/indicators/light.lua @@ -9,12 +9,14 @@ local element = require("graphics.element") ---@field colors cpair on/off colors (a/b respectively) ---@field min_label_width? integer label length if omitted ---@field parent graphics_element +---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field fg_bg? cpair foreground/background colors -- new indicator light ---@param args indicator_light_args +---@return graphics_element element, element_id id local function indicator_light(args) assert(type(args.label) == "string", "graphics.elements.indicators.light: label is a required field") assert(type(args.colors) == "table", "graphics.elements.indicators.light: colors is a required field") @@ -43,7 +45,7 @@ local function indicator_light(args) e.on_update(false) e.window.write(args.label) - return e.get() + return e.complete() end return indicator_light diff --git a/graphics/elements/indicators/state.lua b/graphics/elements/indicators/state.lua index 0383fd8..2311d3d 100644 --- a/graphics/elements/indicators/state.lua +++ b/graphics/elements/indicators/state.lua @@ -13,6 +13,7 @@ local element = require("graphics.element") ---@field value? integer default state, defaults to 1 ---@field min_width? integer max state text length if omitted ---@field parent graphics_element +---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field height? integer 1 if omitted, must be an odd number @@ -20,6 +21,7 @@ local element = require("graphics.element") -- new state indicator ---@param args state_indicator_args +---@return graphics_element element, element_id id local function state_indicator(args) assert(type(args.states) == "table", "graphics.elements.indicators.state: states is a required field") @@ -70,7 +72,7 @@ local function state_indicator(args) -- initial draw e.on_update(args.value or 1) - return e.get() + return e.complete() end return state_indicator diff --git a/graphics/elements/indicators/vbar.lua b/graphics/elements/indicators/vbar.lua index e1016aa..8b3d9ba 100644 --- a/graphics/elements/indicators/vbar.lua +++ b/graphics/elements/indicators/vbar.lua @@ -6,6 +6,7 @@ local element = require("graphics.element") ---@class vbar_args ---@field parent graphics_element +---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field width? integer parent width if omitted @@ -15,6 +16,7 @@ local element = require("graphics.element") -- new vertical bar ---@param args vbar_args +---@return graphics_element element, element_id id local function vbar(args) -- properties/state local last_num_bars = -1 @@ -76,7 +78,7 @@ local function vbar(args) end end - return e.get() + return e.complete() end return vbar diff --git a/graphics/elements/pipenet.lua b/graphics/elements/pipenet.lua index b9436c3..81e7b0a 100644 --- a/graphics/elements/pipenet.lua +++ b/graphics/elements/pipenet.lua @@ -6,14 +6,16 @@ local core = require("graphics.core") local element = require("graphics.element") ---@class pipenet_args ----@field parent graphics_element ----@field x? integer 1 if omitted ----@field y? integer 1 if omitted ---@field pipes table pipe list ---@field bg? color background color +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted -- new pipe network ---@param args pipenet_args +---@return graphics_element element, element_id id local function pipenet(args) assert(type(args.pipes) == "table", "graphics.elements.indicators.pipenet: pipes is a required field") @@ -139,7 +141,7 @@ local function pipenet(args) end - return e.get() + return e.complete() end return pipenet diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua index 933bcd3..12dacac 100644 --- a/graphics/elements/rectangle.lua +++ b/graphics/elements/rectangle.lua @@ -7,6 +7,7 @@ local element = require("graphics.element") ---@class rectangle_args ---@field border? graphics_border ---@field parent graphics_element +---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field width? integer parent width if omitted @@ -16,6 +17,7 @@ local element = require("graphics.element") -- new rectangle ---@param args rectangle_args +---@return graphics_element element, element_id id local function rectangle(args) -- offset children if args.border ~= nil then @@ -108,7 +110,7 @@ local function rectangle(args) end end - return e.get() + return e.complete() end return rectangle diff --git a/graphics/elements/textbox.lua b/graphics/elements/textbox.lua index d5694a4..1d76f4e 100644 --- a/graphics/elements/textbox.lua +++ b/graphics/elements/textbox.lua @@ -9,8 +9,9 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN ---@class textbox_args ---@field text string text to show ----@field parent graphics_element ---@field alignment? TEXT_ALIGN text alignment, left by default +---@field parent graphics_element +---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field width? integer parent width if omitted @@ -20,6 +21,7 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN -- new text box ---@param args textbox_args +---@return graphics_element element, element_id id local function textbox(args) assert(type(args.text) == "string", "graphics.elements.textbox: text is a required field") @@ -50,7 +52,7 @@ local function textbox(args) e.window.write(lines[i]) end - return e.get() + return e.complete() end return textbox diff --git a/graphics/elements/tiling.lua b/graphics/elements/tiling.lua index 47367b4..9f94d97 100644 --- a/graphics/elements/tiling.lua +++ b/graphics/elements/tiling.lua @@ -9,6 +9,7 @@ local element = require("graphics.element") ---@field even? boolean whether to account for rectangular pixels ---@field border_c? color optional frame color ---@field parent graphics_element +---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field width? integer parent width if omitted @@ -18,6 +19,7 @@ local element = require("graphics.element") -- new tiling box ---@param args tiling_args +---@return graphics_element element, element_id id local function tiling(args) assert(type(args.fill_c) == "table", "graphics.elements.tiling: fill_c is a required field") @@ -77,7 +79,7 @@ local function tiling(args) end end - return e.get() + return e.complete() end return tiling From f4f36b020b6d1ea2bba9962947ef9fc0d36320f7 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 28 Jul 2022 10:15:12 -0400 Subject: [PATCH 340/587] #84 recursive get element by id --- graphics/element.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/graphics/element.lua b/graphics/element.lua index 64e1029..40b3f66 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -187,8 +187,20 @@ function element.new(args) ---@param key string|integer function public.remove(key) self.children[key] = nil end + -- attempt to get a child element by ID (does not include this element itself) ---@param id element_id + ---@return graphics_element|nil element function public.get_element_by_id(id) + if self.children[id] == nil then + for _, child in pairs(self.children) do + local elem = child.get_element_by_id(id) + if elem ~= nil then return elem end + end + else + return self.children[id] + end + + return nil end -- get the foreground/background colors From 14b24678f9793d5beabd5db5d0df4dc41668e9d9 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 28 Jul 2022 11:17:34 -0400 Subject: [PATCH 341/587] #84 auto-incrementing x with line break function, removed need for get_offset by having parent prepare child template --- graphics/element.lua | 236 ++++++++++-------- graphics/elements/controls/push_button.lua | 2 +- graphics/elements/controls/scram_button.lua | 2 +- .../elements/controls/spinbox_numeric.lua | 2 +- graphics/elements/controls/switch_button.lua | 2 +- graphics/elements/displaybox.lua | 2 +- graphics/elements/div.lua | 2 +- graphics/elements/indicators/data.lua | 2 +- graphics/elements/indicators/hbar.lua | 2 +- graphics/elements/indicators/icon.lua | 2 +- graphics/elements/indicators/light.lua | 2 +- graphics/elements/indicators/state.lua | 2 +- graphics/elements/indicators/vbar.lua | 2 +- graphics/elements/pipenet.lua | 2 +- graphics/elements/rectangle.lua | 2 +- graphics/elements/textbox.lua | 2 +- graphics/elements/tiling.lua | 2 +- 17 files changed, 144 insertions(+), 124 deletions(-) diff --git a/graphics/element.lua b/graphics/element.lua index 40b3f66..bad2a80 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -11,7 +11,7 @@ local element = {} ---@field parent? graphics_element ---@field id? string element id ---@field x? integer 1 if omitted ----@field y? integer 1 if omitted +---@field y? integer next line if omitted ---@field offset_x? integer 0 if omitted ---@field offset_y? integer 0 if omitted ---@field width? integer parent width if omitted @@ -30,6 +30,7 @@ function element.new(args) position = { x = 1, y = 1 }, child_offset = { x = 0, y = 0 }, bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1}, + next_y = 1, children = {}, mt = {} } @@ -46,84 +47,85 @@ function element.new(args) return "graphics.element{" .. self.elem_type .. "} @ " .. tostring(self) end - -- SETUP -- + ---@class graphics_element + local public = {} - -- get the parent window - self.p_window = args.window - if self.p_window == nil and args.parent ~= nil then - self.p_window = args.parent.window() - end - - -- check window - assert(self.p_window, "graphics.element{" .. self.elem_type .. "}: no parent window provided") - - -- get frame coordinates/size - if args.gframe ~= nil then - protected.frame.x = args.gframe.x - protected.frame.y = args.gframe.y - protected.frame.w = args.gframe.w - protected.frame.h = args.gframe.h - else - local w, h = self.p_window.getSize() - protected.frame.x = args.x or 1 - protected.frame.y = args.y or 1 - protected.frame.w = args.width or w - protected.frame.h = args.height or h - end - - -- inner offsets - if args.offset_x ~= nil then self.child_offset.x = args.offset_x end - if args.offset_y ~= nil then self.child_offset.y = args.offset_y end - - -- adjust window frame if applicable - local f = protected.frame - local x = f.x - local y = f.y - - -- apply offsets - if args.parent ~= nil then - -- offset x/y - local offset_x, offset_y = args.parent.get_offset() - x = x + offset_x - y = y + offset_y - - -- constrain to parent inner width/height - local w, h = self.p_window.getSize() - f.w = math.min(f.w, w - ((2 * offset_x) + (f.x - 1))) - f.h = math.min(f.h, h - ((2 * offset_y) + (f.y - 1))) - end - - -- check frame - assert(f.x >= 1, "graphics.element{" .. self.elem_type .. "}: frame x not >= 1") - assert(f.y >= 1, "graphics.element{" .. self.elem_type .. "}: frame y not >= 1") - assert(f.w >= 1, "graphics.element{" .. self.elem_type .. "}: frame width not >= 1") - assert(f.h >= 1, "graphics.element{" .. self.elem_type .. "}: frame height not >= 1") - - -- create window - protected.window = window.create(self.p_window, x, y, f.w, f.h, true) - - -- init colors - if args.fg_bg ~= nil then - protected.fg_bg = args.fg_bg - elseif args.parent ~= nil then - protected.fg_bg = args.parent.get_fg_bg() - end - - -- set colors - protected.window.setBackgroundColor(protected.fg_bg.bkg) - protected.window.setTextColor(protected.fg_bg.fgd) - protected.window.clear() - - -- record position - self.position.x, self.position.y = protected.window.getPosition() - - -- calculate bounds - self.bounds.x1 = self.position.x - self.bounds.x2 = self.position.x + f.w - 1 - self.bounds.y1 = self.position.y - self.bounds.y2 = self.position.y + f.h - 1 + setmetatable(public, self.mt) + ------------------------- -- PROTECTED FUNCTIONS -- + ------------------------- + + -- prepare the template + ---@param offset_x integer x offset + ---@param offset_y integer y offset + ---@param next_y integer next line if no y was provided + function protected.prepare_template(offset_x, offset_y, next_y) + -- get frame coordinates/size + if args.gframe ~= nil then + protected.frame.x = args.gframe.x + protected.frame.y = args.gframe.y + protected.frame.w = args.gframe.w + protected.frame.h = args.gframe.h + else + local w, h = self.p_window.getSize() + protected.frame.x = args.x or 1 + protected.frame.y = args.y or next_y + protected.frame.w = args.width or w + protected.frame.h = args.height or h + end + + -- inner offsets + if args.offset_x ~= nil then self.child_offset.x = args.offset_x end + if args.offset_y ~= nil then self.child_offset.y = args.offset_y end + + -- adjust window frame if applicable + local f = protected.frame + local x = f.x + local y = f.y + + -- apply offsets + if args.parent ~= nil then + -- constrain to parent inner width/height + local w, h = self.p_window.getSize() + f.w = math.min(f.w, w - ((2 * offset_x) + (f.x - 1))) + f.h = math.min(f.h, h - ((2 * offset_y) + (f.y - 1))) + + -- offset x/y + f.x = x + offset_x + f.y = y + offset_y + end + + -- check frame + assert(f.x >= 1, "graphics.element{" .. self.elem_type .. "}: frame x not >= 1") + assert(f.y >= 1, "graphics.element{" .. self.elem_type .. "}: frame y not >= 1") + assert(f.w >= 1, "graphics.element{" .. self.elem_type .. "}: frame width not >= 1") + assert(f.h >= 1, "graphics.element{" .. self.elem_type .. "}: frame height not >= 1") + + -- create window + protected.window = window.create(self.p_window, f.x, f.y, f.w, f.h, true) + + -- init colors + if args.fg_bg ~= nil then + protected.fg_bg = args.fg_bg + elseif args.parent ~= nil then + protected.fg_bg = args.parent.get_fg_bg() + end + + -- set colors + protected.window.setBackgroundColor(protected.fg_bg.bkg) + protected.window.setTextColor(protected.fg_bg.fgd) + protected.window.clear() + + -- record position + self.position.x, self.position.y = protected.window.getPosition() + + -- calculate bounds + self.bounds.x1 = self.position.x + self.bounds.x2 = self.position.x + f.w - 1 + self.bounds.y1 = self.position.y + self.bounds.y2 = self.position.y + f.h - 1 + end -- handle a touch event ---@param event table monitor_touch event @@ -139,43 +141,56 @@ function element.new(args) return nil end - ---@class graphics_element - local public = {} - - setmetatable(public, self.mt) - - -- get public interface and wrap up element creation + -- get public interface ---@return graphics_element element, element_id id - function protected.complete() - if not self.define_completed then - self.define_completed = true + function protected.get() return public, self.id end - if args.parent then - self.id = args.parent.__add_child(args.id, public) - end + ----------- + -- SETUP -- + ----------- - return public, self.id - else - assert("graphics.element{" .. self.elem_type .. "}: illegal duplicate call to complete()") - end + -- get the parent window + self.p_window = args.window + if self.p_window == nil and args.parent ~= nil then + self.p_window = args.parent.window() end + -- check window + assert(self.p_window, "graphics.element{" .. self.elem_type .. "}: no parent window provided") + + -- prepare the template + if args.parent == nil then + protected.prepare_template(0, 0, 1) + else + self.id = args.parent.__add_child(args.id, protected) + end + + ---------------------- -- PUBLIC FUNCTIONS -- + ---------------------- -- get the window object function public.window() return protected.window end + -- CHILD ELEMENTS -- + -- add a child element ---@param key string|nil id - ---@param child graphics_element - ---@return graphics_element element, integer|string key + ---@param child graphics_template + ---@return integer|string key function public.__add_child(key, child) + child.prepare_template(self.child_offset.x, self.child_offset.y, self.next_y) + + self.next_y = child.frame.y + child.frame.h + + local child_element = child.get() + if key == nil then - table.insert(self.children, child) - return child, #self.children + table.insert(self.children, child_element) + return #self.children else - self.children[key] = child - return child, key + self.children[key] = child_element + return key end end @@ -203,15 +218,16 @@ function element.new(args) return nil end + -- AUTO-PLACEMENT -- + + -- skip a line for automatically placed elements + function public.line_break() self.next_y = self.next_y + 1 end + + -- PROPERTIES -- + -- get the foreground/background colors function public.get_fg_bg() return protected.fg_bg end - -- get offset from this element's frame - ---@return integer x, integer y - function public.get_offset() - return self.child_offset.x, self.child_offset.y - end - -- get element width function public.width() return protected.frame.w @@ -222,6 +238,13 @@ function element.new(args) return protected.frame.h end + -- get the control value reading + function public.get_value() + return protected.get_value() + end + + -- FUNCTION CALLBACKS -- + -- handle a monitor touch ---@param event monitor_touch monitor touch event function public.handle_touch(event) @@ -244,10 +267,7 @@ function element.new(args) protected.on_update(...) end - -- get the control value reading - function public.get_value() - return protected.get_value() - end + -- VISIBILITY -- -- show the element function public.show() diff --git a/graphics/elements/controls/push_button.lua b/graphics/elements/controls/push_button.lua index 35e70a6..5e2bf4d 100644 --- a/graphics/elements/controls/push_button.lua +++ b/graphics/elements/controls/push_button.lua @@ -63,7 +63,7 @@ local function push_button(args) args.callback() end - return e.complete() + return e.get() end return push_button diff --git a/graphics/elements/controls/scram_button.lua b/graphics/elements/controls/scram_button.lua index e916fbe..bac3045 100644 --- a/graphics/elements/controls/scram_button.lua +++ b/graphics/elements/controls/scram_button.lua @@ -65,7 +65,7 @@ local function scram_button(args) args.callback() end - return e.complete() + return e.get() end return scram_button diff --git a/graphics/elements/controls/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua index 16237d6..55b80ce 100644 --- a/graphics/elements/controls/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -112,7 +112,7 @@ local function spinbox(args) ---@return number|integer function e.get_value() return value end - return e.complete() + return e.get() end return spinbox diff --git a/graphics/elements/controls/switch_button.lua b/graphics/elements/controls/switch_button.lua index aae443c..964eada 100644 --- a/graphics/elements/controls/switch_button.lua +++ b/graphics/elements/controls/switch_button.lua @@ -73,7 +73,7 @@ local function switch_button(args) args.callback(state) end - return e.complete() + return e.get() end return switch_button diff --git a/graphics/elements/displaybox.lua b/graphics/elements/displaybox.lua index 218f599..0d7fd47 100644 --- a/graphics/elements/displaybox.lua +++ b/graphics/elements/displaybox.lua @@ -15,7 +15,7 @@ local element = require("graphics.element") ---@param args displaybox_args local function displaybox(args) -- create new graphics element base object - return element.new(args).complete() + return element.new(args).get() end return displaybox diff --git a/graphics/elements/div.lua b/graphics/elements/div.lua index 0864b36..59e3a1e 100644 --- a/graphics/elements/div.lua +++ b/graphics/elements/div.lua @@ -17,7 +17,7 @@ local element = require("graphics.element") ---@return graphics_element element, element_id id local function div(args) -- create new graphics element base object - return element.new(args).complete() + return element.new(args).get() end return div diff --git a/graphics/elements/indicators/data.lua b/graphics/elements/indicators/data.lua index 1022551..86e248b 100644 --- a/graphics/elements/indicators/data.lua +++ b/graphics/elements/indicators/data.lua @@ -93,7 +93,7 @@ local function data(args) -- initial value draw e.on_update(args.value) - return e.complete() + return e.get() end return data diff --git a/graphics/elements/indicators/hbar.lua b/graphics/elements/indicators/hbar.lua index 53862aa..a45eb91 100644 --- a/graphics/elements/indicators/hbar.lua +++ b/graphics/elements/indicators/hbar.lua @@ -99,7 +99,7 @@ local function hbar(args) -- initialize to 0 e.on_update(0) - return e.complete() + return e.get() end return hbar diff --git a/graphics/elements/indicators/icon.lua b/graphics/elements/indicators/icon.lua index a33711a..cdbcf9e 100644 --- a/graphics/elements/indicators/icon.lua +++ b/graphics/elements/indicators/icon.lua @@ -62,7 +62,7 @@ local function icon(args) -- initial icon draw e.on_update(args.value or 1) - return e.complete() + return e.get() end return icon diff --git a/graphics/elements/indicators/light.lua b/graphics/elements/indicators/light.lua index f902bfb..e16a68a 100644 --- a/graphics/elements/indicators/light.lua +++ b/graphics/elements/indicators/light.lua @@ -45,7 +45,7 @@ local function indicator_light(args) e.on_update(false) e.window.write(args.label) - return e.complete() + return e.get() end return indicator_light diff --git a/graphics/elements/indicators/state.lua b/graphics/elements/indicators/state.lua index 2311d3d..a3d5854 100644 --- a/graphics/elements/indicators/state.lua +++ b/graphics/elements/indicators/state.lua @@ -72,7 +72,7 @@ local function state_indicator(args) -- initial draw e.on_update(args.value or 1) - return e.complete() + return e.get() end return state_indicator diff --git a/graphics/elements/indicators/vbar.lua b/graphics/elements/indicators/vbar.lua index 8b3d9ba..114fb77 100644 --- a/graphics/elements/indicators/vbar.lua +++ b/graphics/elements/indicators/vbar.lua @@ -78,7 +78,7 @@ local function vbar(args) end end - return e.complete() + return e.get() end return vbar diff --git a/graphics/elements/pipenet.lua b/graphics/elements/pipenet.lua index 81e7b0a..71ee9fd 100644 --- a/graphics/elements/pipenet.lua +++ b/graphics/elements/pipenet.lua @@ -141,7 +141,7 @@ local function pipenet(args) end - return e.complete() + return e.get() end return pipenet diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua index 12dacac..b7ece93 100644 --- a/graphics/elements/rectangle.lua +++ b/graphics/elements/rectangle.lua @@ -110,7 +110,7 @@ local function rectangle(args) end end - return e.complete() + return e.get() end return rectangle diff --git a/graphics/elements/textbox.lua b/graphics/elements/textbox.lua index 1d76f4e..14b5591 100644 --- a/graphics/elements/textbox.lua +++ b/graphics/elements/textbox.lua @@ -52,7 +52,7 @@ local function textbox(args) e.window.write(lines[i]) end - return e.complete() + return e.get() end return textbox diff --git a/graphics/elements/tiling.lua b/graphics/elements/tiling.lua index 9f94d97..4d20025 100644 --- a/graphics/elements/tiling.lua +++ b/graphics/elements/tiling.lua @@ -79,7 +79,7 @@ local function tiling(args) end end - return e.complete() + return e.get() end return tiling From 291860832670804d32dc8346d4b42c8bdeeb4d1a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 28 Jul 2022 11:17:58 -0400 Subject: [PATCH 342/587] #73 updated unit layout for graphics library changes --- coordinator/ui/layout/unit_view.lua | 55 +++++++++++++++-------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index 45ce5ed..6016128 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -43,51 +43,54 @@ local function init(monitor, id) local burn_control = Div{parent=main,x=13,y=core_height+4,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} - main(scram, burn_control) - local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} local set_burn = function () print("set burn to " .. burn_rate.get_value()) end - burn_control(burn_rate) - TextBox{parent=burn_control,x=9,y=2,text="mB/t"} - burn_control(PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),callback=set_burn}) + PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),callback=set_burn} local annunciator = Div{parent=main,x=34,y=core_height+4} -- annunciator colors per IAEA-TECDOC-812 recommendations -- connectivity/basic state - local plc_online = IndicatorLight{parent=annunciator,x=1,y=1,label="PLC Online",colors=cpair(colors.green,colors.red)} - local r_active = IndicatorLight{parent=annunciator,x=1,y=2,label="Active",colors=cpair(colors.green,colors.gray)} - local r_auto = IndicatorLight{parent=annunciator,x=1,y=3,label="Auto Control",colors=cpair(colors.blue,colors.gray)} + local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(colors.green,colors.red)} + local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=cpair(colors.white,colors.red)} + local r_active = IndicatorLight{parent=annunciator,label="Active",colors=cpair(colors.green,colors.gray)} + local r_auto = IndicatorLight{parent=annunciator,label="Auto Control",colors=cpair(colors.blue,colors.gray)} + + annunciator.line_break() -- annunciator fields - local r_trip = IndicatorLight{parent=annunciator,x=1,y=5,label="Reactor Trip",colors=cpair(colors.red,colors.gray)} - local r_mtrp = IndicatorLight{parent=annunciator,x=1,y=6,label="Manual Reactor Trip",colors=cpair(colors.red,colors.gray)} - local r_rtrp = IndicatorLight{parent=annunciator,x=1,y=7,label="RCP Trip",colors=cpair(colors.red,colors.gray)} - local r_cflo = IndicatorLight{parent=annunciator,x=1,y=8,label="RCS Flow Low",colors=cpair(colors.yellow,colors.gray)} - local r_temp = IndicatorLight{parent=annunciator,x=1,y=9,label="Reactor Temp. High",colors=cpair(colors.red,colors.gray)} - local r_rhdt = IndicatorLight{parent=annunciator,x=1,y=10,label="Reactor High Delta T",colors=cpair(colors.yellow,colors.gray)} - local r_firl = IndicatorLight{parent=annunciator,x=1,y=11,label="Fuel Input Rate Low",colors=cpair(colors.yellow,colors.gray)} - local r_wloc = IndicatorLight{parent=annunciator,x=1,y=12,label="Waste Line Occlusion",colors=cpair(colors.yellow,colors.gray)} - local r_hsrt = IndicatorLight{parent=annunciator,x=1,y=13,label="High Startup Rate",colors=cpair(colors.yellow,colors.gray)} + local r_trip = IndicatorLight{parent=annunciator,label="Reactor Trip",colors=cpair(colors.red,colors.gray)} + local r_mtrp = IndicatorLight{parent=annunciator,label="Manual Reactor Trip",colors=cpair(colors.red,colors.gray)} + local r_rtrp = IndicatorLight{parent=annunciator,label="RCP Trip",colors=cpair(colors.red,colors.gray)} + local r_cflo = IndicatorLight{parent=annunciator,label="RCS Flow Low",colors=cpair(colors.yellow,colors.gray)} + local r_temp = IndicatorLight{parent=annunciator,label="Reactor Temp. High",colors=cpair(colors.red,colors.gray)} + local r_rhdt = IndicatorLight{parent=annunciator,label="Reactor High Delta T",colors=cpair(colors.yellow,colors.gray)} + local r_firl = IndicatorLight{parent=annunciator,label="Fuel Input Rate Low",colors=cpair(colors.yellow,colors.gray)} + local r_wloc = IndicatorLight{parent=annunciator,label="Waste Line Occlusion",colors=cpair(colors.yellow,colors.gray)} + local r_hsrt = IndicatorLight{parent=annunciator,label="High Startup Rate",colors=cpair(colors.yellow,colors.gray)} + + annunciator.line_break() -- RPS - local rps_trp = IndicatorLight{parent=annunciator,x=1,y=15,label="RPS Trip",colors=cpair(colors.red,colors.gray)} - local rps_dmg = IndicatorLight{parent=annunciator,x=1,y=16,label="Damage Critical",colors=cpair(colors.yellow,colors.gray)} - local rps_exh = IndicatorLight{parent=annunciator,x=1,y=17,label="Excess Heated Coolant",colors=cpair(colors.yellow,colors.gray)} - local rps_exc = IndicatorLight{parent=annunciator,x=1,y=18,label="Excess Waste",colors=cpair(colors.yellow,colors.gray)} - local rps_tmp = IndicatorLight{parent=annunciator,x=1,y=19,label="High Core Temp",colors=cpair(colors.yellow,colors.gray)} - local rps_nof = IndicatorLight{parent=annunciator,x=1,y=20,label="No Fuel",colors=cpair(colors.yellow,colors.gray)} - local rps_noc = IndicatorLight{parent=annunciator,x=1,y=21,label="No Coolant",colors=cpair(colors.yellow,colors.gray)} - local rps_flt = IndicatorLight{parent=annunciator,x=1,y=22,label="PPM Fault",colors=cpair(colors.yellow,colors.gray)} - local rps_tmo = IndicatorLight{parent=annunciator,x=1,y=23,label="Timeout",colors=cpair(colors.yellow,colors.gray)} + local rps_trp = IndicatorLight{parent=annunciator,label="RPS Trip",colors=cpair(colors.red,colors.gray)} + local rps_dmg = IndicatorLight{parent=annunciator,label="Damage Critical",colors=cpair(colors.yellow,colors.gray)} + local rps_exh = IndicatorLight{parent=annunciator,label="Excess Heated Coolant",colors=cpair(colors.yellow,colors.gray)} + local rps_exc = IndicatorLight{parent=annunciator,label="Excess Waste",colors=cpair(colors.yellow,colors.gray)} + local rps_tmp = IndicatorLight{parent=annunciator,label="High Core Temp",colors=cpair(colors.yellow,colors.gray)} + local rps_nof = IndicatorLight{parent=annunciator,label="No Fuel",colors=cpair(colors.yellow,colors.gray)} + local rps_noc = IndicatorLight{parent=annunciator,label="No Coolant",colors=cpair(colors.yellow,colors.gray)} + local rps_flt = IndicatorLight{parent=annunciator,label="PPM Fault",colors=cpair(colors.yellow,colors.gray)} + local rps_tmo = IndicatorLight{parent=annunciator,label="Timeout",colors=cpair(colors.yellow,colors.gray)} + plc_hbeat.update(true) r_auto.update(true) r_trip.update(true) r_mtrp.update(true) rps_trp.update(true) + rps_nof.update(true) return main end From f5c703a8b352d2eb7bb7f8d53a862dd54861033a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 28 Jul 2022 11:42:22 -0400 Subject: [PATCH 343/587] fixed push button touch redraw --- graphics/elements/controls/push_button.lua | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/graphics/elements/controls/push_button.lua b/graphics/elements/controls/push_button.lua index 5e2bf4d..227b8e0 100644 --- a/graphics/elements/controls/push_button.lua +++ b/graphics/elements/controls/push_button.lua @@ -37,9 +37,14 @@ local function push_button(args) local h_pad = math.floor((e.frame.w - text_width) / 2) + 1 local v_pad = math.floor(e.frame.h / 2) + 1 - -- write the button text - e.window.setCursorPos(h_pad, v_pad) - e.window.write(args.text) + -- draw the button + local function draw() + e.window.clear() + + -- write the button text + e.window.setCursorPos(h_pad, v_pad) + e.window.write(args.text) + end -- handle touch ---@param event monitor_touch monitor touch event @@ -49,13 +54,13 @@ local function push_button(args) -- show as pressed e.window.setTextColor(args.active_fg_bg.fgd) e.window.setBackgroundColor(args.active_fg_bg.bkg) - e.window.redraw() + draw() - -- show as unpressed in 0.5 seconds - tcd.dispatch(0.5, function () + -- show as unpressed in 0.25 seconds + tcd.dispatch(0.25, function () e.window.setTextColor(e.fg_bg.fgd) e.window.setBackgroundColor(e.fg_bg.bkg) - e.window.redraw() + draw() end) end @@ -63,6 +68,9 @@ local function push_button(args) args.callback() end + -- initial draw + draw() + return e.get() end From 42c2b1bda1b59886c107e72b336ed22b034ee936 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 28 Jul 2022 12:10:52 -0400 Subject: [PATCH 344/587] coordinator use tcallbackdsp, #73 burn rate set button click effect, test blinks of lights --- coordinator/startup.lua | 10 ++++++++-- coordinator/ui/layout/unit_view.lua | 22 ++++++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index e685797..b817063 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -3,6 +3,7 @@ -- require("/initenv").init_env() +local tcallbackdsp = require("scada-common.tcallbackdsp") local log = require("scada-common.log") local ppm = require("scada-common.ppm") @@ -15,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.3.7" +local COORDINATOR_VERSION = "alpha-v0.3.8" local print = util.print local println = util.println @@ -206,8 +207,13 @@ while ui_ok do println_ts(msg) log.warning(msg) else - -- a non-clock/main watchdog timer event, check API watchdogs + -- a non-clock/main watchdog timer event + + --check API watchdogs --apisessions.check_all_watchdogs(param1) + + -- notify timer callback dispatcher + tcallbackdsp.handle(param1) end elseif event == "modem_message" then -- got a packet diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index 6016128..839ef12 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -3,6 +3,7 @@ -- local core = require("graphics.core") +local tcallbackdsp = require("scada-common.tcallbackdsp") local style = require("coordinator.ui.style") @@ -47,7 +48,7 @@ local function init(monitor, id) local set_burn = function () print("set burn to " .. burn_rate.get_value()) end TextBox{parent=burn_control,x=9,y=2,text="mB/t"} - PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),callback=set_burn} + PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} local annunciator = Div{parent=main,x=34,y=core_height+4} @@ -55,7 +56,7 @@ local function init(monitor, id) -- connectivity/basic state local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(colors.green,colors.red)} - local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=cpair(colors.white,colors.red)} + local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=cpair(colors.white,colors.gray)} local r_active = IndicatorLight{parent=annunciator,label="Active",colors=cpair(colors.green,colors.gray)} local r_auto = IndicatorLight{parent=annunciator,label="Auto Control",colors=cpair(colors.blue,colors.gray)} @@ -92,6 +93,23 @@ local function init(monitor, id) rps_trp.update(true) rps_nof.update(true) + local heartbeat = true + local function _test_toggle() + plc_hbeat.update(heartbeat) + heartbeat = not heartbeat + tcallbackdsp.dispatch(1, _test_toggle) + end + + local rps = true + local function _test_toggle1() + rps_nof.update(rps) + rps = not rps + tcallbackdsp.dispatch(0.25, _test_toggle1) + end + + tcallbackdsp.dispatch(1, _test_toggle) + tcallbackdsp.dispatch(0.25, _test_toggle1) + return main end From 17dd35e6de4dffa6e32adac4981ba02f417b405b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 1 Aug 2022 10:30:53 -0300 Subject: [PATCH 345/587] bugfixes to tiling element --- graphics/elements/tiling.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/graphics/elements/tiling.lua b/graphics/elements/tiling.lua index 4d20025..86af96d 100644 --- a/graphics/elements/tiling.lua +++ b/graphics/elements/tiling.lua @@ -47,7 +47,7 @@ local function tiling(args) start_x = 1 + util.trinary(even, 2, 1) start_y = 2 - inner_width = math.floor((e.frame.w - 2 * util.trinary(even, 2, 1)) / 2) + inner_width = math.floor((e.frame.w - 2 * util.trinary(even, 2, 1)) / util.trinary(even, 2, 1)) inner_height = e.frame.h - 2 end @@ -63,9 +63,9 @@ local function tiling(args) for x = 1, inner_width do if alternator then if even then - e.window.blit("NF", "00", fill_a .. fill_a) + e.window.blit(" ", "00", fill_a .. fill_a) else - e.window.blit("F", "0", fill_a) + e.window.blit(" ", "0", fill_a) end else if even then @@ -75,8 +75,10 @@ local function tiling(args) end end - if x ~= inner_width then alternator = not alternator end + alternator = not alternator end + + if inner_width % 2 == 0 then alternator = not alternator end end return e.get() From 826114e5bf0efc650d3b2e84d8dbbc552fd6f7a7 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 1 Aug 2022 13:05:39 -0300 Subject: [PATCH 346/587] #73 core map and bugfixes --- coordinator/startup.lua | 2 +- coordinator/ui/components/boiler.lua | 1 + coordinator/ui/components/reactor.lua | 1 + coordinator/ui/components/turbine.lua | 1 + coordinator/ui/layout/unit_view.lua | 50 ++++++++--- graphics/elements/colormap.lua | 33 +++++++ graphics/elements/indicators/coremap.lua | 106 +++++++++++++++++++++++ 7 files changed, 182 insertions(+), 12 deletions(-) create mode 100644 graphics/elements/colormap.lua create mode 100644 graphics/elements/indicators/coremap.lua diff --git a/coordinator/startup.lua b/coordinator/startup.lua index b817063..77b1514 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.3.8" +local COORDINATOR_VERSION = "alpha-v0.3.9" local print = util.print local println = util.println diff --git a/coordinator/ui/components/boiler.lua b/coordinator/ui/components/boiler.lua index 8deace3..761366a 100644 --- a/coordinator/ui/components/boiler.lua +++ b/coordinator/ui/components/boiler.lua @@ -47,6 +47,7 @@ local function new_view(root, x, y, ps) ps.subscribe("steam", steam.update) ps.subscribe("ccool", ccool.update) + ---@fixme test code hcool.update(0.22) water.update(1) steam.update(0.05) diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua index d7d35f5..e7cda02 100644 --- a/coordinator/ui/components/reactor.lua +++ b/coordinator/ui/components/reactor.lua @@ -50,6 +50,7 @@ local function new_view(root, x, y, ps) ps.subscribe("hcool", hcool.update) ps.subscribe("waste", waste.update) + ---@fixme test code fuel.update(1) ccool.update(0.85) hcool.update(0.08) diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua index f1ffea0..7609e3c 100644 --- a/coordinator/ui/components/turbine.lua +++ b/coordinator/ui/components/turbine.lua @@ -36,6 +36,7 @@ local function new_view(root, x, y, ps) ps.subscribe("steam", steam.update) + ---@fixme test code steam.update(0.12) end diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index 839ef12..067a68a 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -10,8 +10,9 @@ local style = require("coordinator.ui.style") local DisplayBox = require("graphics.elements.displaybox") local Div = require("graphics.elements.div") local TextBox = require("graphics.elements.textbox") -local Tiling = require("graphics.elements.tiling") +local ColorMap = require("graphics.elements.colormap") +local CoreMap = require("graphics.elements.indicators.coremap") local DataIndicator = require("graphics.elements.indicators.data") local HorizontalBar = require("graphics.elements.indicators.hbar") local IndicatorLight = require("graphics.elements.indicators.light") @@ -31,18 +32,28 @@ local function init(monitor, id) TextBox{parent=main,text="Reactor Unit #" .. id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} - local reactor_width = 18 - local core_width = ((reactor_width - 2) * 2) + 4 - local core_height = reactor_width - local scram_fg_bg = core.graphics.cpair(colors.white, colors.gray) - local reactor_top_view = Tiling{parent=main,x=2,y=3,width=core_width,height=core_height,fill_c=cpair(colors.lightGray,colors.lightBlue),even=true,border_c=colors.gray} + ---@fixme test code + local t = 300 + if id == 1 then + t = 340 + elseif id == 2 then + t = 340 + elseif id == 3 then + t = 300 + elseif id == 4 then + t = 300 + end + + local core_view = CoreMap{parent=main,x=2,y=3} + core_view.update(t) + local core_shift = core_view.height() local f = function () print("scram!") end - local scram = SCRAMButton{parent=main,x=2,y=core_height+4,callback=f,fg_bg=scram_fg_bg} + local scram = SCRAMButton{parent=main,x=2,y=core_shift+4,callback=f,fg_bg=scram_fg_bg} - local burn_control = Div{parent=main,x=13,y=core_height+4,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} + local burn_control = Div{parent=main,x=13,y=core_shift+4,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} local set_burn = function () print("set burn to " .. burn_rate.get_value()) end @@ -50,7 +61,11 @@ local function init(monitor, id) TextBox{parent=burn_control,x=9,y=2,text="mB/t"} PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} - local annunciator = Div{parent=main,x=34,y=core_height+4} + ---@fixme test code + main.line_break() + ColorMap{parent=main} + + local annunciator = Div{parent=main,x=34,y=3} -- annunciator colors per IAEA-TECDOC-812 recommendations @@ -63,8 +78,8 @@ local function init(monitor, id) annunciator.line_break() -- annunciator fields - local r_trip = IndicatorLight{parent=annunciator,label="Reactor Trip",colors=cpair(colors.red,colors.gray)} - local r_mtrp = IndicatorLight{parent=annunciator,label="Manual Reactor Trip",colors=cpair(colors.red,colors.gray)} + local r_trip = IndicatorLight{parent=annunciator,label="Reactor SCRAM",colors=cpair(colors.red,colors.gray)} + local r_mtrp = IndicatorLight{parent=annunciator,label="Manual Reactor SCRAM",colors=cpair(colors.red,colors.gray)} local r_rtrp = IndicatorLight{parent=annunciator,label="RCP Trip",colors=cpair(colors.red,colors.gray)} local r_cflo = IndicatorLight{parent=annunciator,label="RCS Flow Low",colors=cpair(colors.yellow,colors.gray)} local r_temp = IndicatorLight{parent=annunciator,label="Reactor Temp. High",colors=cpair(colors.red,colors.gray)} @@ -86,6 +101,16 @@ local function init(monitor, id) local rps_flt = IndicatorLight{parent=annunciator,label="PPM Fault",colors=cpair(colors.yellow,colors.gray)} local rps_tmo = IndicatorLight{parent=annunciator,label="Timeout",colors=cpair(colors.yellow,colors.gray)} + annunciator.line_break() + + -- cooling + local c_brm = IndicatorLight{parent=annunciator,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_cfm = IndicatorLight{parent=annunciator,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_sfm = IndicatorLight{parent=annunciator,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_mwrf = IndicatorLight{parent=annunciator,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} + local c_tbnt = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + + ---@fixme test code plc_hbeat.update(true) r_auto.update(true) r_trip.update(true) @@ -93,6 +118,7 @@ local function init(monitor, id) rps_trp.update(true) rps_nof.update(true) + ---@fixme test code local heartbeat = true local function _test_toggle() plc_hbeat.update(heartbeat) @@ -100,6 +126,7 @@ local function init(monitor, id) tcallbackdsp.dispatch(1, _test_toggle) end + ---@fixme test code local rps = true local function _test_toggle1() rps_nof.update(rps) @@ -107,6 +134,7 @@ local function init(monitor, id) tcallbackdsp.dispatch(0.25, _test_toggle1) end + ---@fixme test code tcallbackdsp.dispatch(1, _test_toggle) tcallbackdsp.dispatch(0.25, _test_toggle1) diff --git a/graphics/elements/colormap.lua b/graphics/elements/colormap.lua new file mode 100644 index 0000000..4c7ba94 --- /dev/null +++ b/graphics/elements/colormap.lua @@ -0,0 +1,33 @@ +-- Color Map Graphics Element + +local util = require("scada-common.util") + +local element = require("graphics.element") + +---@class colormap_args +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted + +-- new color map +---@param args colormap_args +---@return graphics_element element, element_id id +local function colormap(args) + local bkg = "008877FFCCEE114455DD9933BBAA2266" + local spaces = util.spaces(32) + + args.width = 32 + args.height = 1 + + -- create new graphics element base object + local e = element.new(args) + + -- draw color map + e.window.setCursorPos(1, 1) + e.window.blit(spaces, bkg, bkg) + + return e.get() +end + +return colormap diff --git a/graphics/elements/indicators/coremap.lua b/graphics/elements/indicators/coremap.lua new file mode 100644 index 0000000..0f7d7f1 --- /dev/null +++ b/graphics/elements/indicators/coremap.lua @@ -0,0 +1,106 @@ +-- Reactor Core View Graphics Element + +local util = require("scada-common.util") + +local core = require("graphics.core") +local element = require("graphics.element") + +---@class core_map_args +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field fg_bg? cpair foreground/background colors + +-- new core map box +---@param args core_map_args +---@return graphics_element element, element_id id +local function core_map(args) + args.width = 30 + args.height = 18 + + -- arbitrary foreground color, gray reactor frame background + args.fg_bg = core.graphics.cpair(colors.white, colors.gray) + + -- create new graphics element base object + local e = element.new(args) + + -- draw core map box + + local start_x = 2 + local start_y = 2 + + local inner_width = math.floor((e.frame.w - 2) / 2) + local inner_height = e.frame.h - 2 + local alternator = true + + -- check dimensions + assert(inner_width > 0, "graphics.elements.indicators.coremap: inner_width <= 0") + assert(inner_height > 0, "graphics.elements.indicators.coremap: inner_height <= 0") + assert(start_x <= inner_width, "graphics.elements.indicators.coremap: start_x > inner_width") + assert(start_y <= inner_height, "graphics.elements.indicators.coremap: start_y > inner_height") + + -- draw the core + local function draw(t) + local i = 1 + local back_c = "FF" + local text_c = "FF" + + -- determine fuel assembly coloring + if t <= 300 then + -- gray + back_c = "88" + elseif t <= 350 then + -- blue + back_c = "33" + elseif t < 600 then + -- green + back_c = "DD" + elseif t < 1000 then + -- yellow + back_c = "44" + elseif t < 1200 then + -- orange + back_c = "11" + elseif t < 1300 then + -- red + back_c = "EE" + text_c = "00" + else + -- pink + back_c = "22" + text_c = "00" + end + + -- draw pattern + for y = start_y, inner_height + (start_y - 1) do + e.window.setCursorPos(start_x, y) + for x = 1, inner_width do + local str = util.sprintf("%02X", i) + + if alternator then + i = i + 1 + e.window.blit(str, text_c, back_c) + else + e.window.blit(" ", "00", "00") + end + + alternator = not alternator + end + + if inner_width % 2 == 0 then alternator = not alternator end + end + end + + draw(300) + + -- on state change + ---@param temperature integer temperature in Kelvin + function e.on_update(temperature) + draw(temperature) + end + + return e.get() +end + +return core_map From 6b23a3274491aeb07a5cc111fc233be6e6591416 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 1 Aug 2022 13:11:20 -0300 Subject: [PATCH 347/587] renamed core_view to core_map --- coordinator/ui/layout/unit_view.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index 067a68a..09f3417 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -46,9 +46,9 @@ local function init(monitor, id) t = 300 end - local core_view = CoreMap{parent=main,x=2,y=3} - core_view.update(t) - local core_shift = core_view.height() + local core_map = CoreMap{parent=main,x=2,y=3} + core_map.update(t) + local core_shift = core_map.height() local f = function () print("scram!") end local scram = SCRAMButton{parent=main,x=2,y=core_shift+4,callback=f,fg_bg=scram_fg_bg} From 252c48a02c7d2a6166e5b93f94954d20de172d52 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 2 Aug 2022 11:46:21 -0300 Subject: [PATCH 348/587] #73 core map changes --- coordinator/startup.lua | 2 +- coordinator/ui/layout/unit_view.lua | 2 +- graphics/elements/indicators/coremap.lua | 69 ++++++++++++++++-------- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 77b1514..778a957 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.3.9" +local COORDINATOR_VERSION = "alpha-v0.3.10" local print = util.print local println = util.println diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index 09f3417..26df34b 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -46,7 +46,7 @@ local function init(monitor, id) t = 300 end - local core_map = CoreMap{parent=main,x=2,y=3} + local core_map = CoreMap{parent=main,x=2,y=3,reactor_l=18,reactor_w=18} core_map.update(t) local core_shift = core_map.height() diff --git a/graphics/elements/indicators/coremap.lua b/graphics/elements/indicators/coremap.lua index 0f7d7f1..6adb3c9 100644 --- a/graphics/elements/indicators/coremap.lua +++ b/graphics/elements/indicators/coremap.lua @@ -6,31 +6,33 @@ local core = require("graphics.core") local element = require("graphics.element") ---@class core_map_args +---@field reactor_l integer reactor length +---@field reactor_w integer reactor width ---@field parent graphics_element ---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ----@field fg_bg? cpair foreground/background colors -- new core map box ---@param args core_map_args ---@return graphics_element element, element_id id local function core_map(args) - args.width = 30 - args.height = 18 + assert(util.is_int(args.reactor_l), "graphics.elements.indicators.coremap: reactor_l is a required field") + assert(util.is_int(args.reactor_w), "graphics.elements.indicators.coremap: reactor_w is a required field") - -- arbitrary foreground color, gray reactor frame background - args.fg_bg = core.graphics.cpair(colors.white, colors.gray) + args.width = args.reactor_l + args.height = args.reactor_w + + -- inherit only foreground color + args.fg_bg = core.graphics.cpair(args.parent.get_fg_bg().fgd, colors.gray) -- create new graphics element base object local e = element.new(args) - -- draw core map box - local start_x = 2 local start_y = 2 - local inner_width = math.floor((e.frame.w - 2) / 2) + local inner_width = e.frame.w - 2 local inner_height = e.frame.h - 2 local alternator = true @@ -40,36 +42,58 @@ local function core_map(args) assert(start_x <= inner_width, "graphics.elements.indicators.coremap: start_x > inner_width") assert(start_y <= inner_height, "graphics.elements.indicators.coremap: start_y > inner_height") + -- label coordinates + + e.window.setTextColor(colors.white) + + for x = 0, (inner_width - 1) do + e.window.setCursorPos(x + start_x, 1) + e.window.write(util.sprintf("%X", x)) + end + + for y = 0, (inner_height - 1) do + e.window.setCursorPos(1, y + start_y) + e.window.write(util.sprintf("%X", y)) + end + + -- even out bottom edge + e.window.setTextColor(e.fg_bg.bkg) + e.window.setBackgroundColor(args.parent.get_fg_bg().bkg) + e.window.setCursorPos(1, e.frame.h) + e.window.write(util.strrep("\x8f", e.frame.w)) + e.window.setTextColor(e.fg_bg.fgd) + e.window.setBackgroundColor(e.fg_bg.bkg) + -- draw the core + ---@param t number temperature in K local function draw(t) local i = 1 - local back_c = "FF" - local text_c = "FF" + local back_c = "F" + local text_c = "8" -- determine fuel assembly coloring if t <= 300 then -- gray - back_c = "88" + text_c = "8" elseif t <= 350 then -- blue - back_c = "33" + text_c = "3" elseif t < 600 then -- green - back_c = "DD" + text_c = "D" elseif t < 1000 then -- yellow - back_c = "44" + text_c = "4" + -- back_c = "8" elseif t < 1200 then -- orange - back_c = "11" + text_c = "1" elseif t < 1300 then -- red - back_c = "EE" - text_c = "00" + text_c = "E" else -- pink - back_c = "22" - text_c = "00" + text_c = "2" end -- draw pattern @@ -80,9 +104,9 @@ local function core_map(args) if alternator then i = i + 1 - e.window.blit(str, text_c, back_c) + e.window.blit("\x07", text_c, back_c) else - e.window.blit(" ", "00", "00") + e.window.blit("\x07", "7", "8") end alternator = not alternator @@ -92,10 +116,11 @@ local function core_map(args) end end + -- initial draw at base temp draw(300) -- on state change - ---@param temperature integer temperature in Kelvin + ---@param temperature number temperature in Kelvin function e.on_update(temperature) draw(temperature) end From 02c3c5c53ceb60b97418095fc54138c0ea0bff14 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 9 Aug 2022 00:40:02 -0400 Subject: [PATCH 349/587] fixed bug with textbox alignment --- graphics/elements/textbox.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphics/elements/textbox.lua b/graphics/elements/textbox.lua index 14b5591..45eb92f 100644 --- a/graphics/elements/textbox.lua +++ b/graphics/elements/textbox.lua @@ -42,9 +42,9 @@ local function textbox(args) -- use cursor position to align this line if alignment == TEXT_ALIGN.CENTER then - e.window.setCursorPos(math.floor((e.frame.w - len) / 2), i) + e.window.setCursorPos(math.floor((e.frame.w - len) / 2) + 1, i) elseif alignment == TEXT_ALIGN.RIGHT then - e.window.setCursorPos(e.frame.w - len, i) + e.window.setCursorPos((e.frame.w - len) + 1, i) else e.window.setCursorPos(1, i) end From 3c2f6314518b9ac50b533fc642e0b436d018c96d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 9 Aug 2022 00:40:50 -0400 Subject: [PATCH 350/587] #73 additional indicators next to core map --- coordinator/startup.lua | 20 ++++++------- coordinator/ui/layout/unit_view.lua | 42 +++++++++++++++++++++++++-- coordinator/ui/style.lua | 1 + graphics/elements/indicators/hbar.lua | 2 +- 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 778a957..afab9d3 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -3,20 +3,20 @@ -- require("/initenv").init_env() + +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") local tcallbackdsp = require("scada-common.tcallbackdsp") +local util = require("scada-common.util") -local log = require("scada-common.log") -local ppm = require("scada-common.ppm") -local util = require("scada-common.util") +local core = require("graphics.core") -local core = require("graphics.core") +local apisessions = require("coordinator.apisessions") +local config = require("coordinator.config") +local coordinator = require("coordinator.coordinator") +local renderer = require("coordinator.renderer") -local apisessions = require("coordinator.apisessions") -local config = require("coordinator.config") -local coordinator = require("coordinator.coordinator") -local renderer = require("coordinator.renderer") - -local COORDINATOR_VERSION = "alpha-v0.3.10" +local COORDINATOR_VERSION = "alpha-v0.3.11" local print = util.print local println = util.println diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index 26df34b..a5edc05 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -12,11 +12,12 @@ local Div = require("graphics.elements.div") local TextBox = require("graphics.elements.textbox") local ColorMap = require("graphics.elements.colormap") -local CoreMap = require("graphics.elements.indicators.coremap") +local CoreMap = require("graphics.elements.indicators.coremap") local DataIndicator = require("graphics.elements.indicators.data") local HorizontalBar = require("graphics.elements.indicators.hbar") local IndicatorLight = require("graphics.elements.indicators.light") local StateIndicator = require("graphics.elements.indicators.state") +local VerticalBar = require("graphics.elements.indicators.vbar") local PushButton = require("graphics.elements.controls.push_button") local SCRAMButton = require("graphics.elements.controls.scram_button") @@ -32,7 +33,8 @@ local function init(monitor, id) TextBox{parent=main,text="Reactor Unit #" .. id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} - local scram_fg_bg = core.graphics.cpair(colors.white, colors.gray) + local scram_fg_bg = cpair(colors.white, colors.gray) + local lu_cpair = cpair(colors.gray, colors.gray) ---@fixme test code local t = 300 @@ -50,6 +52,40 @@ local function init(monitor, id) core_map.update(t) local core_shift = core_map.height() + local stat_fg_bg = cpair(colors.black,colors.white) + + TextBox{parent=main,x=21,y=3,text="Core Temp",height=1,fg_bg=style.label} + DataIndicator{parent=main,x=21,label="",format="%9.2f",value=300,unit="K",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + main.line_break() + + TextBox{parent=main,x=21,text="Burn Rate",height=1,width=12,fg_bg=style.label} + DataIndicator{parent=main,x=21,label="",format="%6.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + main.line_break() + + TextBox{parent=main,x=21,text="Heating Rate",height=1,width=12,fg_bg=style.label} + DataIndicator{parent=main,x=21,label="",format="%11.0f",value=0,unit="",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + main.line_break() + + TextBox{parent=main,text="CR",x=21,y=12,height=1,width=3,fg_bg=style.label} + local ctrl_rods = HorizontalBar{parent=main,x=24,y=12,show_percent=false,bar_fg_bg=cpair(colors.black,colors.gray),height=1,width=9} + ctrl_rods.update(0.75) + + TextBox{parent=main,text="FL",x=21,y=20,height=1,width=2,fg_bg=style.label} + TextBox{parent=main,text="WS",x=24,y=20,height=1,width=2,fg_bg=style.label} + TextBox{parent=main,text="CL",x=28,y=20,height=1,width=2,fg_bg=style.label} + TextBox{parent=main,text="HC",x=31,y=20,height=1,width=2,fg_bg=style.label} + + local fuel = VerticalBar{parent=main,x=21,y=14,fg_bg=cpair(colors.black,colors.gray),height=5,width=2} + local waste = VerticalBar{parent=main,x=24,y=14,fg_bg=cpair(colors.brown,colors.gray),height=5,width=2} + local ccool = VerticalBar{parent=main,x=28,y=14,fg_bg=cpair(colors.lightBlue,colors.gray),height=5,width=2} + local hcool = VerticalBar{parent=main,x=31,y=14,fg_bg=cpair(colors.orange,colors.gray),height=5,width=2} + + ---@fixme test code + fuel.update(1) + ccool.update(0.85) + hcool.update(0.08) + waste.update(0.32) + local f = function () print("scram!") end local scram = SCRAMButton{parent=main,x=2,y=core_shift+4,callback=f,fg_bg=scram_fg_bg} @@ -63,7 +99,7 @@ local function init(monitor, id) ---@fixme test code main.line_break() - ColorMap{parent=main} + ColorMap{parent=main,x=2,y=51} local annunciator = Div{parent=main,x=34,y=3} diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index 9e3e265..897372f 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -9,6 +9,7 @@ local cpair = core.graphics.cpair style.root = cpair(colors.black, colors.lightGray) style.header = cpair(colors.white, colors.gray) +style.label = cpair(colors.gray, colors.lightGray) style.colors = { { c = colors.red, hex = 0xdf4949 }, diff --git a/graphics/elements/indicators/hbar.lua b/graphics/elements/indicators/hbar.lua index a45eb91..f433b91 100644 --- a/graphics/elements/indicators/hbar.lua +++ b/graphics/elements/indicators/hbar.lua @@ -35,7 +35,7 @@ local function hbar(args) -- determine bar colors local bar_bkg = e.fg_bg.blit_bkg local bar_fgd = e.fg_bg.blit_fgd - if args.show_percent and args.bar_fg_bg ~= nil then + if args.bar_fg_bg ~= nil then bar_bkg = args.bar_fg_bg.blit_bkg bar_fgd = args.bar_fg_bg.blit_fgd end From 7f011369c40cab835a5b5609bd9eb195676a3cef Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 16 Aug 2022 11:22:06 -0400 Subject: [PATCH 351/587] util pad function --- graphics/elements/indicators/state.lua | 6 +----- scada-common/log.lua | 7 ++----- scada-common/util.lua | 12 ++++++++++++ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/graphics/elements/indicators/state.lua b/graphics/elements/indicators/state.lua index a3d5854..5dd39ee 100644 --- a/graphics/elements/indicators/state.lua +++ b/graphics/elements/indicators/state.lua @@ -45,11 +45,7 @@ local function state_indicator(args) args.width = string.len(state_def.text) end - local len = string.len(state_def.text) - local lpad = math.floor((args.width - len) / 2) - local rpad = args.width - lpad - - local text = util.spaces(lpad) .. state_def.text .. util.spaces(rpad) + local text = util.pad(state_def.text, args.width) table.insert(state_blit_cmds, { text = text, diff --git a/scada-common/log.lua b/scada-common/log.lua index 08aa8c8..dadba40 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -231,17 +231,14 @@ function log.dmesg_working(msg, tag, tag_color) end local function done(ok) - local lpad = math.max(math.floor((width - 4) / 2), 0) - local rpad = (width - 4) - lpad - out.setCursorPos(ts_coord.x1, ts_coord.y) if ok or ok == nil then out.setTextColor(colors.green) - out.write(util.spaces(lpad) .. "DONE" .. util.spaces(rpad)) + out.write(util.pad("DONE", width)) else out.setTextColor(colors.red) - out.write(util.spaces(lpad) .. "FAIL" .. util.spaces(rpad)) + out.write(util.pad("FAIL", width)) end out.setTextColor(initial_color) diff --git a/scada-common/util.lua b/scada-common/util.lua index ccf9ff4..68957f9 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -75,6 +75,18 @@ function util.spaces(n) return util.strrep(" ", n) end +-- pad text to a minimum width +---@param str string text +---@param n integer minimum width +---@return string +function util.pad(str, n) + local len = string.len(str) + local lpad = math.floor((n - len) / 2) + local rpad = (n - len) - lpad + + return util.spaces(lpad) .. str .. util.spaces(rpad) +end + -- wrap a string into a table of lines, supporting single dash splits ---@param str string ---@param limit integer line limit From 8dac59fba48a4b82ff9d4e03279a18ece1a6c43d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 16 Aug 2022 11:22:58 -0400 Subject: [PATCH 352/587] #73 waste selection --- coordinator/startup.lua | 2 +- coordinator/ui/layout/unit_view.lua | 50 ++++++--- graphics/elements/controls/multi_button.lua | 112 +++++++++++++++++++ graphics/elements/controls/switch_button.lua | 8 +- 4 files changed, 153 insertions(+), 19 deletions(-) create mode 100644 graphics/elements/controls/multi_button.lua diff --git a/coordinator/startup.lua b/coordinator/startup.lua index afab9d3..b31307f 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.3.11" +local COORDINATOR_VERSION = "alpha-v0.3.12" local print = util.print local println = util.println diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index a5edc05..1dcc8c7 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -19,6 +19,7 @@ local IndicatorLight = require("graphics.elements.indicators.light") local StateIndicator = require("graphics.elements.indicators.state") local VerticalBar = require("graphics.elements.indicators.vbar") +local MultiButton = require("graphics.elements.controls.multi_button") local PushButton = require("graphics.elements.controls.push_button") local SCRAMButton = require("graphics.elements.controls.scram_button") local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") @@ -66,19 +67,15 @@ local function init(monitor, id) DataIndicator{parent=main,x=21,label="",format="%11.0f",value=0,unit="",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} main.line_break() - TextBox{parent=main,text="CR",x=21,y=12,height=1,width=3,fg_bg=style.label} - local ctrl_rods = HorizontalBar{parent=main,x=24,y=12,show_percent=false,bar_fg_bg=cpair(colors.black,colors.gray),height=1,width=9} - ctrl_rods.update(0.75) + TextBox{parent=main,text="FL",x=21,y=19,height=1,width=2,fg_bg=style.label} + TextBox{parent=main,text="WS",x=24,y=19,height=1,width=2,fg_bg=style.label} + TextBox{parent=main,text="CL",x=28,y=19,height=1,width=2,fg_bg=style.label} + TextBox{parent=main,text="HC",x=31,y=19,height=1,width=2,fg_bg=style.label} - TextBox{parent=main,text="FL",x=21,y=20,height=1,width=2,fg_bg=style.label} - TextBox{parent=main,text="WS",x=24,y=20,height=1,width=2,fg_bg=style.label} - TextBox{parent=main,text="CL",x=28,y=20,height=1,width=2,fg_bg=style.label} - TextBox{parent=main,text="HC",x=31,y=20,height=1,width=2,fg_bg=style.label} - - local fuel = VerticalBar{parent=main,x=21,y=14,fg_bg=cpair(colors.black,colors.gray),height=5,width=2} - local waste = VerticalBar{parent=main,x=24,y=14,fg_bg=cpair(colors.brown,colors.gray),height=5,width=2} - local ccool = VerticalBar{parent=main,x=28,y=14,fg_bg=cpair(colors.lightBlue,colors.gray),height=5,width=2} - local hcool = VerticalBar{parent=main,x=31,y=14,fg_bg=cpair(colors.orange,colors.gray),height=5,width=2} + local fuel = VerticalBar{parent=main,x=21,y=12,fg_bg=cpair(colors.black,colors.gray),height=6,width=2} + local waste = VerticalBar{parent=main,x=24,y=12,fg_bg=cpair(colors.brown,colors.gray),height=6,width=2} + local ccool = VerticalBar{parent=main,x=28,y=12,fg_bg=cpair(colors.lightBlue,colors.gray),height=6,width=2} + local hcool = VerticalBar{parent=main,x=31,y=12,fg_bg=cpair(colors.orange,colors.gray),height=6,width=2} ---@fixme test code fuel.update(1) @@ -89,7 +86,7 @@ local function init(monitor, id) local f = function () print("scram!") end local scram = SCRAMButton{parent=main,x=2,y=core_shift+4,callback=f,fg_bg=scram_fg_bg} - local burn_control = Div{parent=main,x=13,y=core_shift+4,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} + local burn_control = Div{parent=main,x=14,y=core_shift+4,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} local set_burn = function () print("set burn to " .. burn_rate.get_value()) end @@ -97,6 +94,33 @@ local function init(monitor, id) TextBox{parent=burn_control,x=9,y=2,text="mB/t"} PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} + local opts = { + { + text = "Auto", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.white, colors.gray) + }, + { + text = "Pu", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.black, colors.lime) + }, + { + text = "Po", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.black, colors.cyan) + }, + { + text = "AM", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.black, colors.purple) + } + } + + local waste_sel_f = function (s) print("waste: " .. s) end + local waste_sel = Div{parent=main,x=2,y=core_shift+8,width=31,height=3,fg_bg=cpair(colors.black, colors.white)} + MultiButton{parent=waste_sel,x=2,y=1,options=opts,callback=waste_sel_f,min_width=6,fg_bg=cpair(colors.black, colors.white)} + ---@fixme test code main.line_break() ColorMap{parent=main,x=2,y=51} diff --git a/graphics/elements/controls/multi_button.lua b/graphics/elements/controls/multi_button.lua new file mode 100644 index 0000000..16ee85e --- /dev/null +++ b/graphics/elements/controls/multi_button.lua @@ -0,0 +1,112 @@ +-- Button Graphics Element + +local element = require("graphics.element") +local util = require("scada-common.util") + +---@class button_option +---@field text string +---@field fg_bg cpair +---@field active_fg_bg cpair +---@field _lpad integer automatically calculated left pad +---@field _start_x integer starting touch x range (inclusive) +---@field _end_x integer ending touch x range (inclusive) + +---@class multi_button_args +---@field options table button options +---@field callback function function to call on touch +---@field default? boolean default state, defaults to options[1] +---@field min_width? integer text length + 2 if omitted +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field height? integer parent height if omitted +---@field fg_bg? cpair foreground/background colors + +-- new multi button (latch selection, exclusively one button at a time) +---@param args multi_button_args +---@return graphics_element element, element_id id +local function multi_button(args) + assert(type(args.options) == "table", "graphics.elements.controls.multi_button: options is a required field") + assert(type(args.callback) == "function", "graphics.elements.controls.multi_button: callback is a required field") + + -- single line + args.height = 3 + + -- button state (convert nil to 1 if missing) + local state = args.default or 1 + + -- determine widths + local max_width = 1 + for i = 1, #args.options do + local opt = args.options[i] ---@type button_option + if string.len(opt.text) > max_width then + max_width = string.len(opt.text) + end + end + + local button_width = math.max(max_width, args.min_width or 1) + + args.width = (button_width * #args.options) + #args.options + 1 + + -- create new graphics element base object + local e = element.new(args) + + -- calculate required button information + local next_x = 2 + for i = 1, #args.options do + local opt = args.options[i] ---@type button_option + local w = string.len(opt.text) + + opt._lpad = math.floor((e.frame.w - w) / 2) + opt._start_x = next_x + opt._end_x = next_x + button_width - 1 + + next_x = next_x + (button_width + 1) + end + + -- show the button state + local function draw() + for i = 1, #args.options do + local opt = args.options[i] ---@type button_option + + e.window.setCursorPos(opt._start_x, 2) + + if state == i then + -- show as pressed + e.window.setTextColor(opt.active_fg_bg.fgd) + e.window.setBackgroundColor(opt.active_fg_bg.bkg) + else + -- show as unpressed + e.window.setTextColor(opt.fg_bg.fgd) + e.window.setBackgroundColor(opt.fg_bg.bkg) + end + + e.window.write(util.pad(opt.text, button_width)) + end + end + + -- initial draw + draw() + + -- handle touch + ---@param event monitor_touch monitor touch event + function e.handle_touch(event) + -- determine what was pressed + if event.y == 2 then + for i = 1, #args.options do + local opt = args.options[i] ---@type button_option + + if event.x >= opt._start_x and event.x <= opt._end_x then + state = i + draw() + args.callback(state) + end + end + end + end + + return e.get() +end + +return multi_button diff --git a/graphics/elements/controls/switch_button.lua b/graphics/elements/controls/switch_button.lua index 964eada..f7a304f 100644 --- a/graphics/elements/controls/switch_button.lua +++ b/graphics/elements/controls/switch_button.lua @@ -39,10 +39,6 @@ local function switch_button(args) local h_pad = math.floor((e.frame.w - text_width) / 2) local v_pad = math.floor(e.frame.h / 2) + 1 - -- write the button text - e.window.setCursorPos(h_pad, v_pad) - e.window.write(args.text) - -- show the button state local function draw_state() if state then @@ -55,7 +51,9 @@ local function switch_button(args) e.window.setBackgroundColor(e.fg_bg.bkg) end - e.window.redraw() + -- write the button text + e.window.setCursorPos(h_pad, v_pad) + e.window.write(args.text) end -- initial draw From 395c1ff9ce34297eae8e92b3484808dd1e3ad7a5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 16 Aug 2022 13:04:02 -0400 Subject: [PATCH 353/587] #73 add indicators for radiation monitor and boilers/turbines --- coordinator/ui/layout/unit_view.lua | 79 ++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 17 deletions(-) diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index 1dcc8c7..06ac4cf 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -51,7 +51,6 @@ local function init(monitor, id) local core_map = CoreMap{parent=main,x=2,y=3,reactor_l=18,reactor_w=18} core_map.update(t) - local core_shift = core_map.height() local stat_fg_bg = cpair(colors.black,colors.white) @@ -63,30 +62,38 @@ local function init(monitor, id) DataIndicator{parent=main,x=21,label="",format="%6.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} main.line_break() + TextBox{parent=main,x=21,text="Commanded Burn Rate",height=2,width=12,fg_bg=style.label} + DataIndicator{parent=main,x=21,label="",format="%6.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + main.line_break() + TextBox{parent=main,x=21,text="Heating Rate",height=1,width=12,fg_bg=style.label} DataIndicator{parent=main,x=21,label="",format="%11.0f",value=0,unit="",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} main.line_break() - TextBox{parent=main,text="FL",x=21,y=19,height=1,width=2,fg_bg=style.label} - TextBox{parent=main,text="WS",x=24,y=19,height=1,width=2,fg_bg=style.label} - TextBox{parent=main,text="CL",x=28,y=19,height=1,width=2,fg_bg=style.label} - TextBox{parent=main,text="HC",x=31,y=19,height=1,width=2,fg_bg=style.label} + TextBox{parent=main,x=21,text="Containment Integrity",height=2,width=12,fg_bg=style.label} + DataIndicator{parent=main,x=21,label="",format="%9.0f",value=100,unit="%",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + main.line_break() - local fuel = VerticalBar{parent=main,x=21,y=12,fg_bg=cpair(colors.black,colors.gray),height=6,width=2} - local waste = VerticalBar{parent=main,x=24,y=12,fg_bg=cpair(colors.brown,colors.gray),height=6,width=2} - local ccool = VerticalBar{parent=main,x=28,y=12,fg_bg=cpair(colors.lightBlue,colors.gray),height=6,width=2} - local hcool = VerticalBar{parent=main,x=31,y=12,fg_bg=cpair(colors.orange,colors.gray),height=6,width=2} + -- TextBox{parent=main,text="FL",x=21,y=19,height=1,width=2,fg_bg=style.label} + -- TextBox{parent=main,text="WS",x=24,y=19,height=1,width=2,fg_bg=style.label} + -- TextBox{parent=main,text="CL",x=28,y=19,height=1,width=2,fg_bg=style.label} + -- TextBox{parent=main,text="HC",x=31,y=19,height=1,width=2,fg_bg=style.label} - ---@fixme test code - fuel.update(1) - ccool.update(0.85) - hcool.update(0.08) - waste.update(0.32) + -- local fuel = VerticalBar{parent=main,x=21,y=12,fg_bg=cpair(colors.black,colors.gray),height=6,width=2} + -- local waste = VerticalBar{parent=main,x=24,y=12,fg_bg=cpair(colors.brown,colors.gray),height=6,width=2} + -- local ccool = VerticalBar{parent=main,x=28,y=12,fg_bg=cpair(colors.lightBlue,colors.gray),height=6,width=2} + -- local hcool = VerticalBar{parent=main,x=31,y=12,fg_bg=cpair(colors.orange,colors.gray),height=6,width=2} + + -- ---@fixme test code + -- fuel.update(1) + -- ccool.update(0.85) + -- hcool.update(0.08) + -- waste.update(0.32) local f = function () print("scram!") end - local scram = SCRAMButton{parent=main,x=2,y=core_shift+4,callback=f,fg_bg=scram_fg_bg} + local scram = SCRAMButton{parent=main,x=2,y=22,callback=f,fg_bg=scram_fg_bg} - local burn_control = Div{parent=main,x=14,y=core_shift+4,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} + local burn_control = Div{parent=main,x=14,y=22,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} local set_burn = function () print("set burn to " .. burn_rate.get_value()) end @@ -118,7 +125,7 @@ local function init(monitor, id) } local waste_sel_f = function (s) print("waste: " .. s) end - local waste_sel = Div{parent=main,x=2,y=core_shift+8,width=31,height=3,fg_bg=cpair(colors.black, colors.white)} + local waste_sel = Div{parent=main,x=2,y=26,width=31,height=3,fg_bg=cpair(colors.black, colors.white)} MultiButton{parent=waste_sel,x=2,y=1,options=opts,callback=waste_sel_f,min_width=6,fg_bg=cpair(colors.black, colors.white)} ---@fixme test code @@ -170,6 +177,44 @@ local function init(monitor, id) local c_mwrf = IndicatorLight{parent=annunciator,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} local c_tbnt = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + annunciator.line_break() + + -- machine-specific indicators + TextBox{parent=main,x=32,y=34,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} + TextBox{parent=main,x=32,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} + main.line_break() + annunciator.line_break() + TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + IndicatorLight{parent=annunciator,label="Steam Dump Open",colors=cpair(colors.yellow,colors.gray)} + TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} + TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + main.line_break() + annunciator.line_break() + TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + IndicatorLight{parent=annunciator,label="Steam Dump Open",colors=cpair(colors.yellow,colors.gray)} + TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} + TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + main.line_break() + annunciator.line_break() + TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + IndicatorLight{parent=annunciator,label="Steam Dump Open",colors=cpair(colors.yellow,colors.gray)} + TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} + TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + + annunciator.line_break() + IndicatorLight{parent=annunciator,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)} + IndicatorLight{parent=annunciator,label="Radiation Alarm",colors=cpair(colors.red,colors.gray)} + + DataIndicator{parent=main,x=34,y=51,label="",format="%10.1f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=18,fg_bg=stat_fg_bg} + ---@fixme test code plc_hbeat.update(true) r_auto.update(true) From c80d861b28d20ef0a37aa645ef9619141963ff4e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 16 Aug 2022 13:56:42 -0400 Subject: [PATCH 354/587] #73 unit view reorganization --- coordinator/ui/layout/unit_view.lua | 86 +++++++++++++++-------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index 06ac4cf..eff91ae 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -90,48 +90,6 @@ local function init(monitor, id) -- hcool.update(0.08) -- waste.update(0.32) - local f = function () print("scram!") end - local scram = SCRAMButton{parent=main,x=2,y=22,callback=f,fg_bg=scram_fg_bg} - - local burn_control = Div{parent=main,x=14,y=22,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} - - local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} - local set_burn = function () print("set burn to " .. burn_rate.get_value()) end - - TextBox{parent=burn_control,x=9,y=2,text="mB/t"} - PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} - - local opts = { - { - text = "Auto", - fg_bg = cpair(colors.black, colors.lightGray), - active_fg_bg = cpair(colors.white, colors.gray) - }, - { - text = "Pu", - fg_bg = cpair(colors.black, colors.lightGray), - active_fg_bg = cpair(colors.black, colors.lime) - }, - { - text = "Po", - fg_bg = cpair(colors.black, colors.lightGray), - active_fg_bg = cpair(colors.black, colors.cyan) - }, - { - text = "AM", - fg_bg = cpair(colors.black, colors.lightGray), - active_fg_bg = cpair(colors.black, colors.purple) - } - } - - local waste_sel_f = function (s) print("waste: " .. s) end - local waste_sel = Div{parent=main,x=2,y=26,width=31,height=3,fg_bg=cpair(colors.black, colors.white)} - MultiButton{parent=waste_sel,x=2,y=1,options=opts,callback=waste_sel_f,min_width=6,fg_bg=cpair(colors.black, colors.white)} - - ---@fixme test code - main.line_break() - ColorMap{parent=main,x=2,y=51} - local annunciator = Div{parent=main,x=34,y=3} -- annunciator colors per IAEA-TECDOC-812 recommendations @@ -215,6 +173,50 @@ local function init(monitor, id) DataIndicator{parent=main,x=34,y=51,label="",format="%10.1f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=18,fg_bg=stat_fg_bg} + local f = function () print("scram!") end + local scram = SCRAMButton{parent=main,x=12,y=44,callback=f,fg_bg=scram_fg_bg} + local start = SCRAMButton{parent=main,x=22,y=44,callback=f,fg_bg=scram_fg_bg} + + local burn_control = Div{parent=main,x=12,y=40,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} + + local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} + local set_burn = function () print("set burn to " .. burn_rate.get_value()) end + + TextBox{parent=burn_control,x=9,y=2,text="mB/t"} + PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} + + local opts = { + { + text = "Auto", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.white, colors.gray) + }, + { + text = "Pu", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.black, colors.lime) + }, + { + text = "Po", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.black, colors.cyan) + }, + { + text = "AM", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.black, colors.purple) + } + } + + local waste_sel_f = function (s) print("waste: " .. s) end + local waste_sel = Div{parent=main,x=2,y=48,width=29,height=2,fg_bg=cpair(colors.black, colors.white)} + MultiButton{parent=waste_sel,x=1,y=1,options=opts,callback=waste_sel_f,min_width=6,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=waste_sel,text="Waste Processing",alignment=TEXT_ALIGN.CENTER,x=1,y=1,height=1} + + ---@fixme test code + main.line_break() + ColorMap{parent=main,x=2,y=51} + ---@fixme test code plc_hbeat.update(true) r_auto.update(true) From c985e90ec3d9985d8ef67e74e4bb96849a3460f6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 28 Aug 2022 11:52:43 -0400 Subject: [PATCH 355/587] #73 test unit view completed, additional features held for after data integration is set --- coordinator/startup.lua | 2 +- coordinator/ui/layout/unit_view.lua | 29 +++++---- graphics/elements/controls/start_button.lua | 71 +++++++++++++++++++++ 3 files changed, 90 insertions(+), 12 deletions(-) create mode 100644 graphics/elements/controls/start_button.lua diff --git a/coordinator/startup.lua b/coordinator/startup.lua index b31307f..9b23416 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.3.12" +local COORDINATOR_VERSION = "alpha-v0.3.14" local print = util.print local println = util.println diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index eff91ae..cb11953 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -2,15 +2,15 @@ -- Reactor Unit SCADA Coordinator GUI -- -local core = require("graphics.core") -local tcallbackdsp = require("scada-common.tcallbackdsp") +local core = require("graphics.core") +local tcallbackdsp = require("scada-common.tcallbackdsp") -local style = require("coordinator.ui.style") +local style = require("coordinator.ui.style") -local DisplayBox = require("graphics.elements.displaybox") -local Div = require("graphics.elements.div") -local TextBox = require("graphics.elements.textbox") -local ColorMap = require("graphics.elements.colormap") +local DisplayBox = require("graphics.elements.displaybox") +local Div = require("graphics.elements.div") +local TextBox = require("graphics.elements.textbox") +local ColorMap = require("graphics.elements.colormap") local CoreMap = require("graphics.elements.indicators.coremap") local DataIndicator = require("graphics.elements.indicators.data") @@ -22,6 +22,7 @@ local VerticalBar = require("graphics.elements.indicators.vbar") local MultiButton = require("graphics.elements.controls.multi_button") local PushButton = require("graphics.elements.controls.push_button") local SCRAMButton = require("graphics.elements.controls.scram_button") +local StartButton = require("graphics.elements.controls.start_button") local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") local TEXT_ALIGN = core.graphics.TEXT_ALIGN @@ -173,14 +174,17 @@ local function init(monitor, id) DataIndicator{parent=main,x=34,y=51,label="",format="%10.1f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=18,fg_bg=stat_fg_bg} - local f = function () print("scram!") end - local scram = SCRAMButton{parent=main,x=12,y=44,callback=f,fg_bg=scram_fg_bg} - local start = SCRAMButton{parent=main,x=22,y=44,callback=f,fg_bg=scram_fg_bg} + ---@fixme debug code + local f = function () print("unit " .. id .. " scram!") end + local fs = function () print("unit " .. id .. " start!") end + + local start = StartButton{parent=main,x=12,y=44,callback=fs,fg_bg=scram_fg_bg} + local scram = SCRAMButton{parent=main,x=22,y=44,callback=f,fg_bg=scram_fg_bg} local burn_control = Div{parent=main,x=12,y=40,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} - local set_burn = function () print("set burn to " .. burn_rate.get_value()) end + local set_burn = function () print("unit " .. id .. " set burn to " .. burn_rate.get_value()) end TextBox{parent=burn_control,x=9,y=2,text="mB/t"} PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} @@ -208,8 +212,11 @@ local function init(monitor, id) } } + ---@fixme debug code local waste_sel_f = function (s) print("waste: " .. s) end + local waste_sel = Div{parent=main,x=2,y=48,width=29,height=2,fg_bg=cpair(colors.black, colors.white)} + MultiButton{parent=waste_sel,x=1,y=1,options=opts,callback=waste_sel_f,min_width=6,fg_bg=cpair(colors.black, colors.white)} TextBox{parent=waste_sel,text="Waste Processing",alignment=TEXT_ALIGN.CENTER,x=1,y=1,height=1} diff --git a/graphics/elements/controls/start_button.lua b/graphics/elements/controls/start_button.lua new file mode 100644 index 0000000..45aba40 --- /dev/null +++ b/graphics/elements/controls/start_button.lua @@ -0,0 +1,71 @@ +-- SCRAM Button Graphics Element + +local tcd = require("scada-common.tcallbackdsp") + +local core = require("graphics.core") + +local element = require("graphics.element") + +---@class start_button_args +---@field callback function function to call on touch +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field fg_bg? cpair foreground/background colors + +-- new start button +---@param args start_button_args +---@return graphics_element element, element_id id +local function start_button(args) + assert(type(args.callback) == "function", "graphics.elements.controls.start_button: callback is a required field") + + -- static dimensions + args.height = 3 + args.width = 9 + + -- create new graphics element base object + local e = element.new(args) + + -- write the button text + e.window.setCursorPos(3, 2) + e.window.write("START") + + -- draw border + + -- top + e.window.setTextColor(colors.orange) + e.window.setBackgroundColor(args.fg_bg.bkg) + e.window.setCursorPos(1, 1) + e.window.write("\x99\x89\x89\x89\x89\x89\x89\x89\x99") + + -- center left + e.window.setCursorPos(1, 2) + e.window.setTextColor(args.fg_bg.bkg) + e.window.setBackgroundColor(colors.orange) + e.window.write("\x99") + + -- center right + e.window.setTextColor(args.fg_bg.bkg) + e.window.setBackgroundColor(colors.orange) + e.window.setCursorPos(9, 2) + e.window.write("\x99") + + -- bottom + e.window.setTextColor(colors.orange) + e.window.setBackgroundColor(args.fg_bg.bkg) + e.window.setCursorPos(1, 3) + e.window.write("\x99\x98\x98\x98\x98\x98\x98\x98\x99") + + -- handle touch + ---@param event monitor_touch monitor touch event +---@diagnostic disable-next-line: unused-local + function e.handle_touch(event) + -- call the touch callback + args.callback() + end + + return e.get() +end + +return start_button From eadf5c488a7124c9ac5c8543374a6e6f33111720 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 28 Aug 2022 12:12:30 -0400 Subject: [PATCH 356/587] #86 improvements to supervisor units, code cleanup --- supervisor/session/coordinator.lua | 10 ++- supervisor/session/rtu.lua | 19 ++++-- supervisor/session/rtu/boiler.lua | 6 +- supervisor/session/rtu/boilerv.lua | 6 +- supervisor/session/rtu/emachine.lua | 6 +- supervisor/session/rtu/envd.lua | 6 +- supervisor/session/rtu/imatrix.lua | 6 +- supervisor/session/rtu/redstone.lua | 12 ++-- supervisor/session/rtu/sna.lua | 6 +- supervisor/session/rtu/sps.lua | 6 +- supervisor/session/rtu/turbine.lua | 6 +- supervisor/session/rtu/turbinev.lua | 10 +-- supervisor/session/rtu/unit_session.lua | 6 +- supervisor/session/svsessions.lua | 39 +++++++++-- supervisor/{ => session}/unit.lua | 90 ++++++++++++------------- supervisor/startup.lua | 8 +-- supervisor/supervisor.lua | 10 +-- 17 files changed, 144 insertions(+), 108 deletions(-) rename supervisor/{ => session}/unit.lua (85%) diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 3ffa89f..95507d2 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -22,13 +22,15 @@ local PERIODICS = { ---@param id integer ---@param in_queue mqueue ---@param out_queue mqueue -function coordinator.new_session(id, in_queue, out_queue) +---@param facility_units table +function coordinator.new_session(id, in_queue, out_queue, facility_units) local log_header = "crdn_session(" .. id .. "): " local self = { id = id, in_q = in_queue, out_q = out_queue, + units = facility_units, -- connection properties seq_num = 0, r_seq_num = nil, @@ -118,7 +120,9 @@ function coordinator.new_session(id, in_queue, out_queue) log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) end elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_CRDN then - if pkt.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + if pkt.type == SCADA_CRDN_TYPES.QUERY_UNIT then + -- return unit statuses + else end end @@ -139,7 +143,7 @@ function coordinator.new_session(id, in_queue, out_queue) function public.close() _close() _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) - println("connection to coordinator #" .. self.id .. " closed by server") + println("connection to coordinator " .. self.id .. " closed by server") log.info(log_header .. "session closed by server") end diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 51f7e21..30e9432 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -1,8 +1,8 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local rsio = require("scada-common.rsio") -local util = require("scada-common.util") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local rsio = require("scada-common.rsio") +local util = require("scada-common.util") -- supervisor rtu sessions (svrs) local svrs_boiler = require("supervisor.session.rtu.boiler") @@ -52,13 +52,15 @@ local PERIODICS = { ---@param in_queue mqueue ---@param out_queue mqueue ---@param advertisement table -function rtu.new_session(id, in_queue, out_queue, advertisement) +---@param facility_units table +function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) local log_header = "rtu_session(" .. id .. "): " local self = { id = id, in_q = in_queue, out_q = out_queue, + f_units = facility_units, advert = advertisement, -- connection properties seq_num = 0, @@ -66,6 +68,11 @@ function rtu.new_session(id, in_queue, out_queue, advertisement) connected = true, rtu_conn_watchdog = util.new_watchdog(3), last_rtt = 0, + -- periodic messages + periodics = { + last_update = 0, + keep_alive = 0 + }, rs_io_q = {}, turbine_cmd_q = {}, turbine_cmd_capable = false, diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua index 4aecb28..5daef5e 100644 --- a/supervisor/session/rtu/boiler.lua +++ b/supervisor/session/rtu/boiler.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/boilerv.lua b/supervisor/session/rtu/boilerv.lua index 1845668..16125f0 100644 --- a/supervisor/session/rtu/boilerv.lua +++ b/supervisor/session/rtu/boilerv.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/emachine.lua b/supervisor/session/rtu/emachine.lua index d6ec2c7..db8a17b 100644 --- a/supervisor/session/rtu/emachine.lua +++ b/supervisor/session/rtu/emachine.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/envd.lua b/supervisor/session/rtu/envd.lua index ca4a1af..f502e17 100644 --- a/supervisor/session/rtu/envd.lua +++ b/supervisor/session/rtu/envd.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua index 72fdde6..965b57b 100644 --- a/supervisor/session/rtu/imatrix.lua +++ b/supervisor/session/rtu/imatrix.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index 2447094..ba01400 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -1,9 +1,9 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local rsio = require("scada-common.rsio") -local types = require("scada-common.types") -local util = require("scada-common.util") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local rsio = require("scada-common.rsio") +local types = require("scada-common.types") +local util = require("scada-common.util") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/sna.lua b/supervisor/session/rtu/sna.lua index 539b98e..ccfac69 100644 --- a/supervisor/session/rtu/sna.lua +++ b/supervisor/session/rtu/sna.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua index 72c3f13..c2180d9 100644 --- a/supervisor/session/rtu/sps.lua +++ b/supervisor/session/rtu/sps.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/turbine.lua b/supervisor/session/rtu/turbine.lua index c2dd282..ab63fed 100644 --- a/supervisor/session/rtu/turbine.lua +++ b/supervisor/session/rtu/turbine.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua index f8bf9da..b31cb7c 100644 --- a/supervisor/session/rtu/turbinev.lua +++ b/supervisor/session/rtu/turbinev.lua @@ -1,8 +1,8 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local types = require("scada-common.types") -local util = require("scada-common.util") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local types = require("scada-common.types") +local util = require("scada-common.util") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/unit_session.lua b/supervisor/session/rtu/unit_session.lua index ba2e988..ed2f027 100644 --- a/supervisor/session/rtu/unit_session.lua +++ b/supervisor/session/rtu/unit_session.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local txnctrl = require("supervisor.session.rtu.txnctrl") diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 3a1ac4b..f01a2f8 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -1,6 +1,8 @@ -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local util = require("scada-common.util") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local util = require("scada-common.util") + +local unit = require("supervisor.session.unit") local coordinator = require("supervisor.session.coordinator") local plc = require("supervisor.session.plc") @@ -21,6 +23,7 @@ svsessions.SESSION_TYPE = SESSION_TYPE local self = { modem = nil, num_reactors = 0, + facility_units = {}, rtu_sessions = {}, plc_sessions = {}, coord_sessions = {}, @@ -119,9 +122,23 @@ end -- PUBLIC FUNCTIONS -- --- link the modem +-- initialize svsessions ---@param modem table -function svsessions.link_modem(modem) +---@param num_reactors integer +---@param cooling_conf table +function svsessions.init(modem, num_reactors, cooling_conf) + self.modem = modem + self.num_reactors = num_reactors + self.facility_units = {} + + for i = 1, self.num_reactors do + table.insert(self.facility_units, unit.new(i, cooling_conf[i].BOILERS, cooling_conf[i].TURBINES)) + end +end + +-- re-link the modem +---@param modem table +function svsessions.relink_modem(modem) self.modem = modem end @@ -200,6 +217,8 @@ function svsessions.establish_plc_session(local_port, remote_port, for_reactor, plc_s.instance = plc.new_session(self.next_plc_id, for_reactor, plc_s.in_queue, plc_s.out_queue) table.insert(self.plc_sessions, plc_s) + self.facility_units[for_reactor].link_plc_session(plc_s) + log.debug("established new PLC session to " .. remote_port .. " with ID " .. self.next_plc_id) self.next_plc_id = self.next_plc_id + 1 @@ -232,7 +251,7 @@ function svsessions.establish_rtu_session(local_port, remote_port, advertisement instance = nil } - rtu_s.instance = rtu.new_session(self.next_rtu_id, rtu_s.in_queue, rtu_s.out_queue, advertisement) + rtu_s.instance = rtu.new_session(self.next_rtu_id, rtu_s.in_queue, rtu_s.out_queue, advertisement, self.facility_units) table.insert(self.rtu_sessions, rtu_s) log.debug("established new RTU session to " .. remote_port .. " with ID " .. self.next_rtu_id) @@ -260,7 +279,7 @@ function svsessions.establish_coord_session(local_port, remote_port, version) instance = nil } - coord_s.instance = coordinator.new_session(self.next_coord_id, coord_s.in_queue, coord_s.out_queue) + coord_s.instance = coordinator.new_session(self.next_coord_id, coord_s.in_queue, coord_s.out_queue, self.facility_units) table.insert(self.coord_sessions, coord_s) log.debug("established new coordinator session to " .. remote_port .. " with ID " .. self.next_coord_id) @@ -294,6 +313,12 @@ function svsessions.iterate_all() -- iterate coordinator sessions _iterate(self.coord_sessions) + + -- iterate units + for i = 1, #self.facility_units do + local u = self.facility_units[i] ---@type reactor_unit + u.update() + end end -- delete all closed sessions diff --git a/supervisor/unit.lua b/supervisor/session/unit.lua similarity index 85% rename from supervisor/unit.lua rename to supervisor/session/unit.lua index 33f5853..0ca2637 100644 --- a/supervisor/unit.lua +++ b/supervisor/session/unit.lua @@ -7,15 +7,15 @@ local TRI_FAIL = types.TRI_FAIL local DUMPING_MODE = types.DUMPING_MODE local DT_KEYS = { - ReactorTemp = "RTP", - ReactorFuel = "RFL", + ReactorTemp = "RTP", + ReactorFuel = "RFL", ReactorWaste = "RWS", ReactorCCool = "RCC", ReactorHCool = "RHC", - BoilerWater = "BWR", - BoilerSteam = "BST", - BoilerCCool = "BCC", - BoilerHCool = "BHC", + BoilerWater = "BWR", + BoilerSteam = "BST", + BoilerCCool = "BCC", + BoilerHCool = "BHC", TurbineSteam = "TST" } @@ -37,6 +37,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) annunciator = { -- reactor PLCOnline = false, + PLCHeartbeat = false, -- alternate true/false to blink, each time there is a keep_alive ReactorTrip = false, ManualReactorTrip = false, RCPTrip = false, @@ -47,12 +48,12 @@ function unit.new(for_reactor, num_boilers, num_turbines) WasteLineOcclusion = false, HighStartupRate = false, -- boiler - BoilerOnline = TRI_FAIL.OK, + BoilerOnline = {}, HeatingRateLow = {}, BoilRateMismatch = false, CoolantFeedMismatch = false, -- turbine - TurbineOnline = TRI_FAIL.OK, + TurbineOnline = {}, SteamFeedMismatch = false, MaxWaterReturnFeed = false, SteamDumpOpen = {}, @@ -63,12 +64,14 @@ function unit.new(for_reactor, num_boilers, num_turbines) } -- init boiler table fields - for _ = 1, self.num_boilers do + for _ = 1, num_boilers do + table.insert(self.db.annunciator.BoilerOnline, false) table.insert(self.db.annunciator.HeatingRateLow, false) end -- init turbine table fields - for _ = 1, self.num_turbines do + for _ = 1, num_turbines do + table.insert(self.db.annunciator.TurbineOnline, false) table.insert(self.db.annunciator.SteamDumpOpen, TRI_FAIL.OK) table.insert(self.db.annunciator.TurbineOverSpeed, false) table.insert(self.db.annunciator.TurbineTrip, false) @@ -173,6 +176,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > 1000 self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > 100 self.db.annunciator.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < 0.0 or plc_db.mek_status.fuel_fill <= 0.01 + -- @todo this is catagorized as not urgent, but the >= 0.99 is extremely urgent, revist this (RPS will kick in though) self.db.annunciator.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 0.0 or plc_db.mek_status.waste_fill >= 0.99 -- @todo this is dependent on setup, i.e. how much coolant is buffered and the turbine setup self.db.annunciator.HighStartupRate = not plc_db.control_state and plc_db.mek_status.burn_rate > 40 @@ -182,25 +186,24 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- BOILERS -- ------------- - -- check boiler online status - local connected_boilers = #self.boilers - if connected_boilers == 0 and self.num_boilers > 0 then - self.db.annunciator.BoilerOnline = TRI_FAIL.FULL - elseif connected_boilers > 0 and connected_boilers ~= self.num_boilers then - self.db.annunciator.BoilerOnline = TRI_FAIL.PARTIAL - else - self.db.annunciator.BoilerOnline = TRI_FAIL.OK - end + -- clear boiler online flags + for i = 1, self.counts.boilers do self.db.annunciator.BoilerOnline[i] = false end - -- compute aggregated statistics + -- aggregated statistics local total_boil_rate = 0.0 local boiler_steam_dt_sum = 0.0 local boiler_water_dt_sum = 0.0 + + -- go through boilers for stats and online for i = 1, #self.boilers do - local boiler = self.boilers[i].get_db() ---@type boiler_session_db + local session = self.boilers[i] ---@type unit_session + local boiler = session.get_db() ---@type boiler_session_db + total_boil_rate = total_boil_rate + boiler.state.boil_rate boiler_steam_dt_sum = _get_dt(DT_KEYS.BoilerSteam .. self.boilers[i].get_device_idx()) boiler_water_dt_sum = _get_dt(DT_KEYS.BoilerWater .. self.boilers[i].get_device_idx()) + + self.db.annunciator.BoilerOnline[session.get_device_idx()] = true end -- check heating rate low @@ -242,25 +245,24 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- TURBINES -- -------------- - -- check turbine online status - local connected_turbines = #self.turbines - if connected_turbines == 0 and self.num_turbines > 0 then - self.db.annunciator.TurbineOnline = TRI_FAIL.FULL - elseif connected_turbines > 0 and connected_turbines ~= self.num_turbines then - self.db.annunciator.TurbineOnline = TRI_FAIL.PARTIAL - else - self.db.annunciator.TurbineOnline = TRI_FAIL.OK - end + -- clear turbine online flags + for i = 1, self.counts.turbines do self.db.annunciator.TurbineOnline[i] = false end - -- compute aggregated statistics + -- aggregated statistics local total_flow_rate = 0 local total_input_rate = 0 local max_water_return_rate = 0 + + -- go through turbines for stats and online for i = 1, #self.turbines do - local turbine = self.turbines[i].get_db() ---@type turbine_session_db + local session = self.turbine[i] ---@type unit_session + local turbine = session.get_db() ---@type turbine_session_db + total_flow_rate = total_flow_rate + turbine.state.flow_rate total_input_rate = total_input_rate + turbine.state.steam_input_rate max_water_return_rate = max_water_return_rate + turbine.build.max_water_output + + self.db.annunciator.TurbineOnline[session.get_device_idx()] = true end -- check for steam feed mismatch and max return rate @@ -334,7 +336,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- link a turbine RTU session ---@param turbine unit_session function public.add_turbine(turbine) - if #self.turbines < self.num_turbines and turbine.get_device_idx() <= self.num_turbines then + if #self.turbines < num_turbines and turbine.get_device_idx() <= num_turbines then table.insert(self.turbines, turbine) -- reset deltas @@ -350,7 +352,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- link a boiler RTU session ---@param boiler unit_session function public.add_boiler(boiler) - if #self.boilers < self.num_boilers and boiler.get_device_idx() <= self.num_boilers then + if #self.boilers < num_boilers and boiler.get_device_idx() <= num_boilers then table.insert(self.boilers, boiler) -- reset deltas @@ -379,7 +381,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- update (iterate) this unit function public.update() -- unlink PLC if session was closed - if not self.plc_s.open then + if self.plc_s ~= nil and not self.plc_s.open then self.plc_s = nil end @@ -401,12 +403,14 @@ function unit.new(for_reactor, num_boilers, num_turbines) build.boilers = {} for i = 1, #self.boilers do - table.insert(build.boilers, self.boilers[i].get_db().build) + local boiler = self.boilers[i] ---@type unit_session + build.boilers[boiler.get_device_idx()] = { formed = boiler.get_db().formed, build = boiler.get_db().build } end build.turbines = {} for i = 1, #self.turbines do - table.insert(build.turbines, self.turbines[i].get_db().build) + local turbine = self.turbines[i] ---@type unit_session + build.turbines[turbine.get_device_idx()] = { formed = turbine.get_db().formed, build = turbine.get_db().build } end return build @@ -433,19 +437,15 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- status of boilers (including tanks) status.boilers = {} for i = 1, #self.boilers do - table.insert(status.boilers, { - state = self.boilers[i].get_db().state, - tanks = self.boilers[i].get_db().tanks, - }) + local boiler = self.boilers[i] ---@type unit_session + status.boilers[boiler.get_device_idx()] = { state = boiler.get_db().state, tanks = boiler.get_db().tanks } end -- status of turbines (including tanks) status.turbines = {} for i = 1, #self.turbines do - table.insert(status.turbines, { - state = self.turbines[i].get_db().state, - tanks = self.turbines[i].get_db().tanks, - }) + local turbine = self.turbines[i] ---@type unit_session + status.turbines[turbine.get_device_idx()] = { state = turbine.get_db().state, tanks = turbine.get_db().tanks } end return status diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 49348ed..acc80bb 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -4,16 +4,16 @@ require("/initenv").init_env() -local log = require("scada-common.log") -local ppm = require("scada-common.ppm") -local util = require("scada-common.util") +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.5.4" +local SUPERVISOR_VERSION = "beta-v0.5.5" local print = util.print local println = util.println diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 617e996..3aa9f9c 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local util = require("scada-common.util") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local util = require("scada-common.util") local svsessions = require("supervisor.session.svsessions") @@ -57,7 +57,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen _open_channels() -- link modem to svsessions - svsessions.link_modem(self.modem) + svsessions.init(self.modem, num_reactors, cooling_conf) -- send PLC link request response ---@param dest integer @@ -112,7 +112,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen ---@diagnostic disable-next-line: redefined-local function public.reconnect_modem(modem) self.modem = modem - svsessions.link_modem(self.modem) + svsessions.relink_modem(self.modem) _open_channels() end From d38e5ca5ec5a93ccd70952efccdb393093714f76 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 28 Aug 2022 12:57:36 -0400 Subject: [PATCH 357/587] #86 send builds and statuses periodically --- scada-common/comms.lua | 8 ++-- supervisor/session/coordinator.lua | 76 ++++++++++++++++++++++++++++-- supervisor/session/unit.lua | 11 +++-- supervisor/startup.lua | 2 +- 4 files changed, 83 insertions(+), 14 deletions(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 3d18d7f..f0d18c1 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -51,8 +51,8 @@ local SCADA_MGMT_TYPES = { ---@alias SCADA_CRDN_TYPES integer local SCADA_CRDN_TYPES = { ESTABLISH = 0, -- initial greeting - QUERY_UNIT = 1, -- query the state of a unit - QUERY_FACILITY = 2, -- query general facility status + STRUCT_BUILDS = 1, -- mekanism structure builds + UNIT_STATUSES = 2, -- state of reactor units COMMAND_UNIT = 3, -- command a reactor unit ALARM = 4 -- alarm signaling } @@ -453,8 +453,8 @@ function comms.crdn_packet() -- check that type is known local function _crdn_type_valid() return self.type == SCADA_CRDN_TYPES.ESTABLISH or - self.type == SCADA_CRDN_TYPES.QUERY_UNIT or - self.type == SCADA_CRDN_TYPES.QUERY_FACILITY or + self.type == SCADA_CRDN_TYPES.STRUCT_BUILDS or + self.type == SCADA_CRDN_TYPES.UNIT_STATUSES or self.type == SCADA_CRDN_TYPES.COMMAND_UNIT or self.type == SCADA_CRDN_TYPES.ALARM end diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 95507d2..c875e05 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -14,8 +14,13 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts +-- retry time constants in ms +local INITIAL_WAIT = 1500 +local RETRY_PERIOD = 1000 + local PERIODICS = { - KEEP_ALIVE = 2000 + KEEP_ALIVE = 2000, + STATUS = 500 } -- coordinator supervisor session @@ -40,7 +45,16 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) -- periodic messages periodics = { last_update = 0, - keep_alive = 0 + keep_alive = 0, + status_packet = 0 + }, + -- when to next retry one of these messages + retry_times = { + builds_packet = (util.time() + 500) + }, + -- message acknowledgements + acks = { + builds = true } } @@ -78,6 +92,34 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) self.seq_num = self.seq_num + 1 end + -- send unit builds + local function _send_builds() + self.acks.builds = false + + local builds = {} + + for i = 1, #self.units do + local unit = self.units[i] ---@type reactor_unit + builds[unit.get_id()] = unit.get_build() + end + + _send(SCADA_CRDN_TYPES.STRUCT_BUILDS, builds) + end + + -- send unit statuses + local function _send_status() + self.acks.builds = false + + local status = {} + + for i = 1, #self.units do + local unit = self.units[i] ---@type reactor_unit + status[unit.get_id()] = { unit.get_reactor_status(), unit.get_rtu_statuses() } + end + + _send(SCADA_CRDN_TYPES.UNIT_STATUSES, status) + end + -- handle a packet ---@param pkt crdn_frame local function _handle_packet(pkt) @@ -120,10 +162,11 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) end elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_CRDN then - if pkt.type == SCADA_CRDN_TYPES.QUERY_UNIT then - -- return unit statuses - + if pkt.type == SCADA_CRDN_TYPES.STRUCT_BUILDS then + -- acknowledgement to coordinator receiving builds + self.acks.builds = true else + log.debug(log_header .. "handler received unexpected SCADA_CRDN packet type " .. pkt.type) end end end @@ -202,7 +245,30 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) periodics.keep_alive = 0 end + -- unit statuses to coordinator + + periodics.status_packet = periodics.status_packet + elapsed + if periodics.status_packet >= PERIODICS.STATUS then + _send_status() + periodics.status_packet = 0 + end + self.periodics.last_update = util.time() + + --------------------- + -- attempt retries -- + --------------------- + + local rtimes = self.retry_times + + -- builds packet retry + + if not self.acks.builds then + if rtimes.builds_packet - util.time() <= 0 then + _send_builds() + rtimes.builds_packet = util.time() + RETRY_PERIOD + end + end end return self.connected diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 0ca2637..051cb15 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -404,13 +404,13 @@ function unit.new(for_reactor, num_boilers, num_turbines) build.boilers = {} for i = 1, #self.boilers do local boiler = self.boilers[i] ---@type unit_session - build.boilers[boiler.get_device_idx()] = { formed = boiler.get_db().formed, build = boiler.get_db().build } + build.boilers[boiler.get_device_idx()] = { boiler.get_db().formed, boiler.get_db().build } end build.turbines = {} for i = 1, #self.turbines do local turbine = self.turbines[i] ---@type unit_session - build.turbines[turbine.get_device_idx()] = { formed = turbine.get_db().formed, build = turbine.get_db().build } + build.turbines[turbine.get_device_idx()] = { turbine.get_db().formed, turbine.get_db().build } end return build @@ -438,14 +438,14 @@ function unit.new(for_reactor, num_boilers, num_turbines) status.boilers = {} for i = 1, #self.boilers do local boiler = self.boilers[i] ---@type unit_session - status.boilers[boiler.get_device_idx()] = { state = boiler.get_db().state, tanks = boiler.get_db().tanks } + status.boilers[boiler.get_device_idx()] = { boiler.get_db().state, boiler.get_db().tanks } end -- status of turbines (including tanks) status.turbines = {} for i = 1, #self.turbines do local turbine = self.turbines[i] ---@type unit_session - status.turbines[turbine.get_device_idx()] = { state = turbine.get_db().state, tanks = turbine.get_db().tanks } + status.turbines[turbine.get_device_idx()] = { turbine.get_db().state, turbine.get_db().tanks } end return status @@ -454,6 +454,9 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- get the annunciator status function public.get_annunciator() return self.db.annunciator end + -- get the reactor ID + function public.get_id() return self.r_id end + return public end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index acc80bb..52922ff 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.5.5" +local SUPERVISOR_VERSION = "beta-v0.5.6" local print = util.print local println = util.println From c3f740768957f597f4b5a79515087f0cb68a459f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 3 Sep 2022 10:50:14 -0400 Subject: [PATCH 358/587] #86 work on supervisor/coordinator comms --- coordinator/coordinator.lua | 15 +++++- coordinator/database.lua | 104 ++++++++++++++++++++++++++++++++++++ coordinator/renderer.lua | 20 ++++++- coordinator/startup.lua | 2 +- supervisor/session/plc.lua | 12 ++--- supervisor/session/unit.lua | 10 ++-- supervisor/startup.lua | 2 +- 7 files changed, 148 insertions(+), 17 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 5972bfc..0b62ed2 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -397,8 +397,19 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa else log.debug("supervisor conn establish packet length mismatch") end - elseif packet.type == SCADA_CRDN_TYPES.QUERY_UNIT then - elseif packet.type == SCADA_CRDN_TYPES.QUERY_FACILITY then + elseif packet.type == SCADA_CRDN_TYPES.STRUCT_BUILDS then + -- record builds + if database.populate_builds(packet.data) then + -- acknowledge receipt of builds + _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.STRUCT_BUILDS, {}) + else + log.error("supervisor build packet invalid") + end + elseif packet.type == SCADA_CRDN_TYPES.UNIT_STATUSES then + -- update statuses + if not database.update_statuses(packet.data) then + log.error("supervisor unit status packet invalid") + end elseif packet.type == SCADA_CRDN_TYPES.COMMAND_UNIT then elseif packet.type == SCADA_CRDN_TYPES.ALARM then else diff --git a/coordinator/database.lua b/coordinator/database.lua index 3cee4ac..1edda7b 100644 --- a/coordinator/database.lua +++ b/coordinator/database.lua @@ -1,4 +1,5 @@ local psil = require("scada-common.psil") +local log = require("scada-common.log") local database = {} @@ -7,6 +8,10 @@ database.WASTE = { Pu = 0, Po = 1, AntiMatter = 2 } ---@class coord_db local db = {} +-- @todo +function database.purge_subscribers() +end + -- initialize the coordinator database ---@param conf facility_conf configuration function database.init(conf) @@ -51,6 +56,105 @@ function database.init(conf) end end +-- populate structure builds +---@param builds table +---@return boolean valid +function database.populate_builds(builds) + if #builds ~= #db.units then + log.error("number of provided unit builds does not match expected number of units") + return false + else + for i = 1, #builds do + local unit = db.units[i] ---@type coord_db_entry + local build = builds[i] + + -- reactor build + unit.reactor_data.mek_struct = build.reactor + for key, val in pairs(unit.reactor_data.mek_struct) do + unit.reactor_ps.publish(key, val) + end + + -- boiler builds + for id, boiler in pairs(build.boilers) do + unit.boiler_data_tbl[id] = { + formed = boiler[2], ---@type boolean|nil + build = boiler[1] ---@type table + } + + unit.boiler_ps_tbl[id].publish("formed", boiler[2]) + + local key_prefix = "unit_" .. i .. "_boiler_" .. id .. "_" + + for key, val in pairs(unit.boiler_data_tbl[id].build) do + unit.boiler_ps_tbl[id].publish(key_prefix .. key, val) + end + end + + -- turbine builds + for id, turbine in pairs(build.turbines) do + unit.turbine_data_tbl[id] = { + formed = turbine[2], ---@type boolean|nil + build = turbine[1] ---@type table + } + + unit.turbine_ps_tbl[id].publish("formed", turbine[2]) + + local key_prefix = "unit_" .. i .. "_turbine_" .. id .. "_" + + for key, val in pairs(unit.turbine_data_tbl[id].build) do + unit.turbine_ps_tbl[id].publish(key_prefix .. key, val) + end + end + end + end + + return true +end + +-- update unit statuses +---@param statuses table +---@return boolean valid +function database.update_statuses(statuses) + if #statuses ~= #db.units then + log.error("number of provided unit statuses does not match expected number of units") + return false + else + for i = 1, #statuses do + local unit = db.units[i] ---@type coord_db_entry + local status = statuses[i] + + -- reactor status + + local reactor_status = status[1] + local mek_status = reactor_status[1] + local rps_status = reactor_status[2] + local gen_status = reactor_status[3] + + unit.reactor_data.last_status_update = gen_status[1] + unit.reactor_data.control_state = gen_status[2] + unit.reactor_data.overridden = gen_status[3] + unit.reactor_data.degraded = gen_status[4] + unit.reactor_data.rps_tripped = gen_status[5] + unit.reactor_data.rps_trip_cause = gen_status[6] + + unit.reactor_data.rps_status = rps_status ---@type rps_status + unit.reactor_data.mek_status = mek_status ---@type mek_status + + for key, val in pairs(unit.reactor_data) do + if key ~= "mek_struct" then + unit.reactor_ps.publish(key, val) + end + end + + -- boiler statuses + + -- turbine statuses + end + end + + return true +end + -- get the database function database.get() return db end diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 2e49026..9683f0d 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -1,4 +1,6 @@ -local log = require("scada-common.log") +local log = require("scada-common.log") + +local database = require("coordinator.database") local main_view = require("coordinator.ui.layout.main_view") local unit_view = require("coordinator.ui.layout.unit_view") @@ -9,7 +11,8 @@ local renderer = {} -- render engine local engine = { monitors = nil, - dmesg_window = nil + dmesg_window = nil, + ui_ready = false } -- UI layouts @@ -82,11 +85,20 @@ function renderer.start_ui() for id, monitor in pairs(engine.monitors.unit_displays) do table.insert(ui.unit_layouts, unit_view(monitor, id)) end + + -- report ui as ready + engine.ui_ready = true end -- close out the UI ---@param recolor? boolean true to restore to color palette from style function renderer.close_ui(recolor) + -- delete all subscribers + database.purge_subscribers() + + -- report ui as not ready + engine.ui_ready = false + -- clear root UI elements ui.main_layout = nil ui.unit_layouts = {} @@ -99,6 +111,10 @@ function renderer.close_ui(recolor) engine.dmesg_window.redraw() end +-- is the UI ready? +---@return boolean ready +function renderer.ui_ready() return engine.ui_ready end + -- handle a touch event ---@param event monitor_touch function renderer.handle_touch(event) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 9b23416..7b72f25 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.3.14" +local COORDINATOR_VERSION = "alpha-v0.4.0" local print = util.print local println = util.println diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index b9e0c56..a310f52 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -443,12 +443,12 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) -- get the general status information function public.get_general_status() return { - last_status_update = self.sDB.last_status_update, - control_state = self.sDB.control_state, - overridden = self.sDB.overridden, - degraded = self.sDB.degraded, - rps_tripped = self.sDB.rps_tripped, - rps_trip_cause = self.sDB.rps_trip_cause + self.sDB.last_status_update, + self.sDB.control_state, + self.sDB.overridden, + self.sDB.degraded, + self.sDB.rps_tripped, + self.sDB.rps_trip_cause } end diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 051cb15..5008d6a 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -404,13 +404,13 @@ function unit.new(for_reactor, num_boilers, num_turbines) build.boilers = {} for i = 1, #self.boilers do local boiler = self.boilers[i] ---@type unit_session - build.boilers[boiler.get_device_idx()] = { boiler.get_db().formed, boiler.get_db().build } + build.boilers[boiler.get_device_idx()] = { boiler.get_db().build, boiler.get_db().formed } end build.turbines = {} for i = 1, #self.turbines do local turbine = self.turbines[i] ---@type unit_session - build.turbines[turbine.get_device_idx()] = { turbine.get_db().formed, turbine.get_db().build } + build.turbines[turbine.get_device_idx()] = { turbine.get_db().build, turbine.get_db().formed } end return build @@ -422,9 +422,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) if self.plc_s ~= nil then local reactor = self.plc_s - status.mek = reactor.get_status() - status.rps = reactor.get_rps() - status.general = reactor.get_general_status() + status = { reactor.get_status(), reactor.get_rps(), reactor.get_general_status() } end return status @@ -448,6 +446,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) status.turbines[turbine.get_device_idx()] = { turbine.get_db().state, turbine.get_db().tanks } end + ---@todo other RTU statuses + return status end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 52922ff..fdeedc1 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.5.6" +local SUPERVISOR_VERSION = "beta-v0.5.7" local print = util.print local println = util.println From 5a8bba51080d773225587986b5629125714fab86 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 3 Sep 2022 11:51:27 -0400 Subject: [PATCH 359/587] #85 handle loss of supervisor conn or comms modem --- coordinator/coordinator.lua | 8 +++-- coordinator/database.lua | 4 --- coordinator/renderer.lua | 3 -- coordinator/startup.lua | 70 ++++++++++++++++++++++++++----------- 4 files changed, 55 insertions(+), 30 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 0b62ed2..7ce510f 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -403,12 +403,12 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa -- acknowledge receipt of builds _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.STRUCT_BUILDS, {}) else - log.error("supervisor build packet invalid") + log.error("received invalid build packet") end elseif packet.type == SCADA_CRDN_TYPES.UNIT_STATUSES then -- update statuses if not database.update_statuses(packet.data) then - log.error("supervisor unit status packet invalid") + log.error("received invalid unit statuses packet") end elseif packet.type == SCADA_CRDN_TYPES.COMMAND_UNIT then elseif packet.type == SCADA_CRDN_TYPES.ALARM then @@ -435,6 +435,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa elseif packet.type == SCADA_MGMT_TYPES.CLOSE then -- handle session close sv_watchdog.cancel() + self.sv_linked = false println_ts("server connection closed by remote host") log.warning("server connection closed by remote host") else @@ -448,6 +449,9 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa end end + -- check if the coordinator is still linked to the supervisor + function public.is_linked() return self.sv_linked end + return public end diff --git a/coordinator/database.lua b/coordinator/database.lua index 1edda7b..ecb72b3 100644 --- a/coordinator/database.lua +++ b/coordinator/database.lua @@ -8,10 +8,6 @@ database.WASTE = { Pu = 0, Po = 1, AntiMatter = 2 } ---@class coord_db local db = {} --- @todo -function database.purge_subscribers() -end - -- initialize the coordinator database ---@param conf facility_conf configuration function database.init(conf) diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 9683f0d..986c0e0 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -93,9 +93,6 @@ end -- close out the UI ---@param recolor? boolean true to restore to color palette from style function renderer.close_ui(recolor) - -- delete all subscribers - database.purge_subscribers() - -- report ui as not ready engine.ui_ready = false diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 7b72f25..150547c 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -108,39 +108,52 @@ log_comms("comms initialized") 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 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") - println("boot> failed to connect to supervisor") - log.fatal("failed to connect to supervisor") - log_sys("system shutdown") - return + -- 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 end +init_connect_sv() + ---------------------------------------- -- start the UI ---------------------------------------- -log_graphics("starting UI...") --- util.psleep(3) +-- start up the UI +---@return boolean ui_ok started ok +local function init_start_ui() + log_graphics("starting UI...") + -- util.psleep(3) -local draw_start = util.time_ms() + local draw_start = util.time_ms() -local ui_ok, message = pcall(renderer.start_ui) -if not ui_ok then - renderer.close_ui(config.RECOLOR) - log_graphics(util.c("UI crashed: ", message)) - println_ts("UI crashed") - log.fatal(util.c("ui crashed with error ", message)) -else - log_graphics("first UI draw took " .. (util.time_ms() - draw_start) .. "ms") + local ui_ok, message = pcall(renderer.start_ui) + if not ui_ok then + renderer.close_ui(config.RECOLOR) + log_graphics(util.c("UI crashed: ", message)) + println_ts("UI crashed") + log.fatal(util.c("ui crashed with error ", message)) + else + log_graphics("first UI draw took " .. (util.time_ms() - draw_start) .. "ms") - -- start clock - loop_clock.start() + -- start clock + loop_clock.start() + end + + return ui_ok end +local ui_ok = init_start_ui() + ---------------------------------------- -- main event loop ---------------------------------------- @@ -165,6 +178,9 @@ while ui_ok do log_sys("comms modem disconnected") println_ts("wireless modem disconnected!") log.error("comms modem disconnected!") + + -- close out UI + renderer.close_ui() else log_sys("non-comms modem disconnected") log.warning("non-comms modem disconnected") @@ -185,6 +201,10 @@ while ui_ok do log_sys("comms modem reconnected") println_ts("wireless modem reconnected.") + + -- re-init system + init_connect_sv() + ui_ok = init_start_ui() else log_sys("wired modem reconnected") end @@ -206,6 +226,14 @@ while ui_ok do log_comms(msg) println_ts(msg) log.warning(msg) + + -- close connection and UI + coord_comms.close() + renderer.close_ui() + + -- try to re-connect to the supervisor + init_connect_sv() + ui_ok = init_start_ui() else -- a non-clock/main watchdog timer event From f36b0c7e37c58878d0cea77152347c36e1fad951 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 3 Sep 2022 11:54:34 -0400 Subject: [PATCH 360/587] #85 version for reconnecting --- coordinator/startup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 150547c..544cec8 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.4.0" +local COORDINATOR_VERSION = "alpha-v0.4.1" local print = util.print local println = util.println From 17fce01ff537a85972cea168782c94fb2f845627 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 3 Sep 2022 13:10:09 -0400 Subject: [PATCH 361/587] added rps_trip_cause type --- reactor-plc/plc.lua | 2 +- scada-common/types.lua | 12 ++++++++++++ supervisor/session/plc.lua | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index d2009f1..691525f 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -41,7 +41,7 @@ function plc.rps_init(reactor) state = { false, false, false, false, false, false, false, false, false }, reactor_enabled = false, tripped = false, - trip_cause = "" + trip_cause = "" ---@type rps_trip_cause } ---@class rps diff --git a/scada-common/types.lua b/scada-common/types.lua index 75f381a..089af7c 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -71,6 +71,18 @@ types.TRI_FAIL = { ---| "websocket_message" ---| "websocket_success" +---@alias rps_trip_cause +---| "ok" +---| "dmg_crit" +---| "high_temp" +---| "no_coolant" +---| "full_waste" +---| "heated_coolant_backup" +---| "no_fuel" +---| "fault" +---| "timeout" +---| "manual" + ---@alias rtu_t string types.rtu_t = { redstone = "redstone", diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index a310f52..9784600 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -89,7 +89,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) overridden = false, degraded = false, rps_tripped = false, - rps_trip_cause = "ok", + rps_trip_cause = "ok", ---@type rps_trip_cause ---@class rps_status rps_status = { dmg_crit = false, From 564b89d19ce41346c2590e334872ac48b3c559ad Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 3 Sep 2022 13:10:51 -0400 Subject: [PATCH 362/587] #78 linked up unit overview using psil --- coordinator/database.lua | 113 +++++++++++++++++--- coordinator/startup.lua | 2 +- coordinator/ui/components/boiler.lua | 39 +++---- coordinator/ui/components/reactor.lua | 43 ++++---- coordinator/ui/components/turbine.lua | 27 ++--- coordinator/ui/components/unit_overview.lua | 12 +-- coordinator/ui/style.lua | 4 + 7 files changed, 169 insertions(+), 71 deletions(-) diff --git a/coordinator/database.lua b/coordinator/database.lua index ecb72b3..0238429 100644 --- a/coordinator/database.lua +++ b/coordinator/database.lua @@ -119,32 +119,115 @@ function database.update_statuses(statuses) local unit = db.units[i] ---@type coord_db_entry local status = statuses[i] - -- reactor status + -- reactor PLC status local reactor_status = status[1] - local mek_status = reactor_status[1] - local rps_status = reactor_status[2] - local gen_status = reactor_status[3] - unit.reactor_data.last_status_update = gen_status[1] - unit.reactor_data.control_state = gen_status[2] - unit.reactor_data.overridden = gen_status[3] - unit.reactor_data.degraded = gen_status[4] - unit.reactor_data.rps_tripped = gen_status[5] - unit.reactor_data.rps_trip_cause = gen_status[6] + if #reactor_status == 0 then + unit.reactor_ps.publish("computed_status", 1) -- disconnected + else + local mek_status = reactor_status[1] + local rps_status = reactor_status[2] + local gen_status = reactor_status[3] - unit.reactor_data.rps_status = rps_status ---@type rps_status - unit.reactor_data.mek_status = mek_status ---@type mek_status + unit.reactor_data.last_status_update = gen_status[1] + unit.reactor_data.control_state = gen_status[2] + unit.reactor_data.overridden = gen_status[3] + unit.reactor_data.degraded = gen_status[4] + unit.reactor_data.rps_tripped = gen_status[5] + unit.reactor_data.rps_trip_cause = gen_status[6] - for key, val in pairs(unit.reactor_data) do - if key ~= "mek_struct" then - unit.reactor_ps.publish(key, val) + unit.reactor_data.rps_status = rps_status ---@type rps_status + unit.reactor_data.mek_status = mek_status ---@type mek_status + + if unit.reactor_data.mek_status.status then + unit.reactor_ps.publish("computed_status", 3) -- running + else + if unit.reactor_data.degraded then + unit.reactor_ps.publish("computed_status", 5) -- faulted + elseif unit.reactor_data.rps_tripped and unit.reactor_data.rps_trip_cause ~= "manual" then + unit.reactor_ps.publish("computed_status", 4) -- SCRAM + else + unit.reactor_ps.publish("computed_status", 2) -- disabled + end + end + + for key, val in pairs(unit.reactor_data) do + if key ~= "mek_struct" then + unit.reactor_ps.publish(key, val) + end end end + -- RTU statuses + + local rtu_statuses = status[2] + -- boiler statuses + for id = 1, #unit.boiler_data_tbl do + if rtu_statuses.boilers[i] == nil then + -- disconnected + unit.boiler_ps_tbl[id].publish(id .. "_computed_status", 1) + end + end + + for id, boiler in pairs(rtu_statuses.boilers) do + unit.boiler_data_tbl[id].state = boiler[1] ---@type table + unit.boiler_data_tbl[id].tanks = boiler[2] ---@type table + + local key_prefix = id .. "_" + + local data = unit.boiler_data_tbl[id] ---@type boiler_session_db|boilerv_session_db + + if data.state.boil_rate > 0 then + unit.boiler_ps_tbl[id].publish(id .. "_computed_status", 3) -- active + else + unit.boiler_ps_tbl[id].publish(id .. "_computed_status", 2) -- idle + end + + for key, val in pairs(unit.boiler_data_tbl[id].state) do + unit.boiler_ps_tbl[id].publish(key_prefix .. key, val) + end + + for key, val in pairs(unit.boiler_data_tbl[id].tanks) do + unit.boiler_ps_tbl[id].publish(key_prefix .. key, val) + end + end + -- turbine statuses + + for id = 1, #unit.turbine_ps_tbl do + if rtu_statuses.turbines[i] == nil then + -- disconnected + unit.turbine_ps_tbl[id].publish(id .. "_computed_status", 1) + end + end + + for id, turbine in pairs(rtu_statuses.turbines) do + unit.turbine_data_tbl[id].state = turbine[1] ---@type table + unit.turbine_data_tbl[id].tanks = turbine[2] ---@type table + + local key_prefix = id .. "_" + + local data = unit.turbine_data_tbl[id] ---@type turbine_session_db|turbinev_session_db + + if data.tanks.steam_fill >= 0.99 then + unit.turbine_ps_tbl[id].publish(id .. "_computed_status", 4) -- trip + elseif data.state.flow_rate < 100 then + unit.turbine_ps_tbl[id].publish(id .. "_computed_status", 2) -- idle + else + unit.turbine_ps_tbl[id].publish(id .. "_computed_status", 3) -- active + end + + for key, val in pairs(unit.turbine_data_tbl[id].state) do + unit.turbine_ps_tbl[id].publish(key_prefix .. key, val) + end + + for key, val in pairs(unit.turbine_data_tbl[id].tanks) do + unit.turbine_ps_tbl[id].publish(key_prefix .. key, val) + end + end end end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 544cec8..ef3555c 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.4.1" +local COORDINATOR_VERSION = "alpha-v0.4.2" local print = util.print local println = util.println diff --git a/coordinator/ui/components/boiler.lua b/coordinator/ui/components/boiler.lua index 761366a..0d36a2f 100644 --- a/coordinator/ui/components/boiler.lua +++ b/coordinator/ui/components/boiler.lua @@ -1,6 +1,6 @@ -local core = require("graphics.core") +local core = require("graphics.core") -local style = require("coordinator.ui.style") +local style = require("coordinator.ui.style") local DataIndicator = require("graphics.elements.indicators.data") local StateIndicator = require("graphics.elements.indicators.state") @@ -17,20 +17,24 @@ local border = core.graphics.border ---@param root graphics_element ---@param x integer ---@param y integer +---@param id integer +---@param data boiler_session_db|boilerv_session_db ---@param ps psil -local function new_view(root, x, y, ps) +local function new_view(root, x, y, id, data, ps) + local tag = id .. "_" + local boiler = Rectangle{parent=root,border=border(1, colors.gray, true),width=31,height=7,x=x,y=y} local text_fg_bg = cpair(colors.black, colors.lightGray) local lu_col = cpair(colors.gray, colors.gray) - local status = StateIndicator{parent=boiler,x=10,y=1,states=style.boiler.states,value=3,min_width=10} - local temp = DataIndicator{parent=boiler,x=5,y=3,lu_colors=lu_col,label="Temp:",unit="K",format="%10.2f",value=1900,width=22,fg_bg=text_fg_bg} - local boil_r = DataIndicator{parent=boiler,x=5,y=4,lu_colors=lu_col,label="Boil:",unit="mB/t",format="%10.0f",value=801523,commas=true,width=22,fg_bg=text_fg_bg} + local status = StateIndicator{parent=boiler,x=10,y=1,states=style.boiler.states,value=1,min_width=10} + local temp = DataIndicator{parent=boiler,x=5,y=3,lu_colors=lu_col,label="Temp:",unit="K",format="%10.2f",value=data.state.temperature,width=22,fg_bg=text_fg_bg} + local boil_r = DataIndicator{parent=boiler,x=5,y=4,lu_colors=lu_col,label="Boil:",unit="mB/t",format="%10.0f",value=data.state.boil_rate,commas=true,width=22,fg_bg=text_fg_bg} - ps.subscribe("status", status.update) - ps.subscribe("temp", temp.update) - ps.subscribe("boil_rate", boil_r.update) + ps.subscribe(tag .. "computed_status", status.update) + ps.subscribe(tag .. "temperature", temp.update) + ps.subscribe(tag .. "boil_rate", boil_r.update) TextBox{parent=boiler,text="H",x=2,y=5,height=1,width=1,fg_bg=text_fg_bg} TextBox{parent=boiler,text="W",x=3,y=5,height=1,width=1,fg_bg=text_fg_bg} @@ -42,16 +46,15 @@ local function new_view(root, x, y, ps) local steam = VerticalBar{parent=boiler,x=27,y=1,fg_bg=cpair(colors.white,colors.gray),height=4,width=1} local ccool = VerticalBar{parent=boiler,x=28,y=1,fg_bg=cpair(colors.lightBlue,colors.gray),height=4,width=1} - ps.subscribe("hcool", hcool.update) - ps.subscribe("water", water.update) - ps.subscribe("steam", steam.update) - ps.subscribe("ccool", ccool.update) + ps.subscribe(tag .. "hcool_fill", hcool.update) + ps.subscribe(tag .. "water_fill", water.update) + ps.subscribe(tag .. "steam_fill", steam.update) + ps.subscribe(tag .. "ccool_fill", ccool.update) - ---@fixme test code - hcool.update(0.22) - water.update(1) - steam.update(0.05) - ccool.update(0.13) + hcool.update(data.tanks.hcool_fill) + water.update(data.tanks.water_fill) + steam.update(data.tanks.steam_fill) + ccool.update(data.tanks.ccool_fill) end return new_view diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua index e7cda02..523a949 100644 --- a/coordinator/ui/components/reactor.lua +++ b/coordinator/ui/components/reactor.lua @@ -1,6 +1,8 @@ -local core = require("graphics.core") +local util = require("scada-common.util") -local style = require("coordinator.ui.style") +local core = require("graphics.core") + +local style = require("coordinator.ui.style") local HorizontalBar = require("graphics.elements.indicators.hbar") local DataIndicator = require("graphics.elements.indicators.data") @@ -16,19 +18,20 @@ local border = core.graphics.border ---@param root graphics_element ---@param x integer ---@param y integer +---@param data reactor_db ---@param ps psil -local function new_view(root, x, y, ps) +local function new_view(root, x, y, data, ps) local reactor = Rectangle{parent=root,border=border(1, colors.gray, true),width=30,height=7,x=x,y=y} local text_fg_bg = cpair(colors.black, colors.lightGray) local lu_col = cpair(colors.gray, colors.gray) - local status = StateIndicator{parent=reactor,x=8,y=1,states=style.reactor.states,value=3,min_width=14} - local core_temp = DataIndicator{parent=reactor,x=2,y=3,lu_colors=lu_col,label="Core Temp:",unit="K",format="%10.2f",value=451.12,width=26,fg_bg=text_fg_bg} - local burn_r = DataIndicator{parent=reactor,x=2,y=4,lu_colors=lu_col,label="Burn Rate:",unit="mB/t",format="%10.1f",value=40.1,width=26,fg_bg=text_fg_bg} - local heating_r = DataIndicator{parent=reactor,x=2,y=5,lu_colors=lu_col,label="Heating:",unit="mB/t",format="%12.0f",value=8015342,commas=true,width=26,fg_bg=text_fg_bg} + local status = StateIndicator{parent=reactor,x=8,y=1,states=style.reactor.states,value=1,min_width=14} + local core_temp = DataIndicator{parent=reactor,x=2,y=3,lu_colors=lu_col,label="Core Temp:",unit="K",format="%10.2f",value=data.mek_status.temp,width=26,fg_bg=text_fg_bg} + local burn_r = DataIndicator{parent=reactor,x=2,y=4,lu_colors=lu_col,label="Burn Rate:",unit="mB/t",format="%10.1f",value=data.mek_status.act_burn_rate,width=26,fg_bg=text_fg_bg} + local heating_r = DataIndicator{parent=reactor,x=2,y=5,lu_colors=lu_col,label="Heating:",unit="mB/t",format="%12.0f",value=data.mek_status.heating_rate,commas=true,width=26,fg_bg=text_fg_bg} - ps.subscribe("status", status.update) + ps.subscribe("computed_status", status.update) ps.subscribe("temp", core_temp.update) ps.subscribe("burn_rate", burn_r.update) ps.subscribe("heating_rate", heating_r.update) @@ -40,21 +43,23 @@ local function new_view(root, x, y, ps) TextBox{parent=reactor_fills,text="HCOOL",x=2,y=4,height=1,fg_bg=text_fg_bg} TextBox{parent=reactor_fills,text="WASTE",x=2,y=5,height=1,fg_bg=text_fg_bg} + local ccool_color = util.trinary(data.mek_status.ccool_type == "sodium", cpair(colors.lightBlue,colors.gray), cpair(colors.blue,colors.gray)) + local hcool_color = util.trinary(data.mek_status.hcool_type == "superheated_sodium", cpair(colors.orange,colors.gray), cpair(colors.white,colors.gray)) + local fuel = HorizontalBar{parent=reactor_fills,x=8,y=1,show_percent=true,bar_fg_bg=cpair(colors.black,colors.gray),height=1,width=14} - local ccool = HorizontalBar{parent=reactor_fills,x=8,y=2,show_percent=true,bar_fg_bg=cpair(colors.lightBlue,colors.gray),height=1,width=14} - local hcool = HorizontalBar{parent=reactor_fills,x=8,y=4,show_percent=true,bar_fg_bg=cpair(colors.orange,colors.gray),height=1,width=14} + local ccool = HorizontalBar{parent=reactor_fills,x=8,y=2,show_percent=true,bar_fg_bg=ccool_color,height=1,width=14} + local hcool = HorizontalBar{parent=reactor_fills,x=8,y=4,show_percent=true,bar_fg_bg=hcool_color,height=1,width=14} local waste = HorizontalBar{parent=reactor_fills,x=8,y=5,show_percent=true,bar_fg_bg=cpair(colors.brown,colors.gray),height=1,width=14} - ps.subscribe("fuel", fuel.update) - ps.subscribe("ccool", ccool.update) - ps.subscribe("hcool", hcool.update) - ps.subscribe("waste", waste.update) + ps.subscribe("fuel_fill", fuel.update) + ps.subscribe("ccool_fill", ccool.update) + ps.subscribe("hcool_fill", hcool.update) + ps.subscribe("waste_fill", waste.update) - ---@fixme test code - fuel.update(1) - ccool.update(0.85) - hcool.update(0.08) - waste.update(0.32) + fuel.update(data.mek_status.fuel_fill) + ccool.update(data.mek_status.ccool_fill) + hcool.update(data.mek_status.hcool_fill) + waste.update(data.mek_status.waste_fill) end return new_view diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua index 7609e3c..705d2ea 100644 --- a/coordinator/ui/components/turbine.lua +++ b/coordinator/ui/components/turbine.lua @@ -1,6 +1,6 @@ -local core = require("graphics.core") +local core = require("graphics.core") -local style = require("coordinator.ui.style") +local style = require("coordinator.ui.style") local DataIndicator = require("graphics.elements.indicators.data") local StateIndicator = require("graphics.elements.indicators.state") @@ -17,27 +17,30 @@ local border = core.graphics.border ---@param root graphics_element ---@param x integer ---@param y integer +---@param id integer +---@param data turbine_session_db|turbinev_session_db ---@param ps psil -local function new_view(root, x, y, ps) +local function new_view(root, x, y, id, data, ps) + local tag = id .. "_" + local turbine = Rectangle{parent=root,border=border(1, colors.gray, true),width=23,height=7,x=x,y=y} local text_fg_bg = cpair(colors.black, colors.lightGray) local lu_col = cpair(colors.gray, colors.gray) - local status = StateIndicator{parent=turbine,x=8,y=1,states=style.turbine.states,value=3,min_width=10} - local prod_rate = DataIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",unit="MFE",format="%10.2f",value=3.2,width=16,fg_bg=text_fg_bg} - local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=801523,commas=true,width=16,fg_bg=text_fg_bg} + local status = StateIndicator{parent=turbine,x=8,y=1,states=style.turbine.states,value=1,min_width=10} + local prod_rate = DataIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",unit="MFE",format="%10.2f",value=data.state.prod_rate,width=16,fg_bg=text_fg_bg} + local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=data.state.flow_rate,commas=true,width=16,fg_bg=text_fg_bg} - ps.subscribe("status", status.update) - ps.subscribe("prod_rate", prod_rate.update) - ps.subscribe("flow_rate", flow_rate.update) + ps.subscribe(tag .. "computed_status", status.update) + ps.subscribe(tag .. "prod_rate", prod_rate.update) + ps.subscribe(tag .. "flow_rate", flow_rate.update) local steam = VerticalBar{parent=turbine,x=2,y=1,fg_bg=cpair(colors.white,colors.gray),height=5,width=2} - ps.subscribe("steam", steam.update) + ps.subscribe(tag .. "steam_fill", steam.update) - ---@fixme test code - steam.update(0.12) + steam.update(data.tanks.steam_fill) end return new_view diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 0f70ad8..12ed1de 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -50,7 +50,7 @@ local function make(parent, x, y, unit) -- REACTOR -- ------------- - reactor_view(root, 1, 3, unit.reactor_ps) + reactor_view(root, 1, 3, unit.reactor_data, unit.reactor_ps) if num_boilers > 0 then local coolant_pipes = {} @@ -73,8 +73,8 @@ local function make(parent, x, y, unit) -- BOILERS -- ------------- - if num_boilers >= 1 then boiler_view(root, 16, 11, unit.boiler_ps_tbl[1]) end - if num_boilers >= 2 then boiler_view(root, 16, 19, unit.boiler_ps_tbl[2]) end + if num_boilers >= 1 then boiler_view(root, 16, 11, 1, unit.boiler_data_tbl[1], unit.boiler_ps_tbl[1]) end + if num_boilers >= 2 then boiler_view(root, 16, 19, 2, unit.boiler_data_tbl[2], unit.boiler_ps_tbl[2]) end -------------- -- TURBINES -- @@ -84,17 +84,17 @@ local function make(parent, x, y, unit) local no_boilers = num_boilers == 0 if (num_turbines >= 3) or no_boilers or (num_boilers == 1 and num_turbines >= 2) then - turbine_view(root, 58, 3, unit.turbine_ps_tbl[t_idx]) + turbine_view(root, 58, 3, t_idx, unit.turbine_data_tbl[t_idx], unit.turbine_ps_tbl[t_idx]) t_idx = t_idx + 1 end if (num_turbines >= 1 and not no_boilers) or num_turbines >= 2 then - turbine_view(root, 58, 11, unit.turbine_ps_tbl[t_idx]) + turbine_view(root, 58, 11, t_idx, unit.turbine_data_tbl[t_idx], unit.turbine_ps_tbl[t_idx]) t_idx = t_idx + 1 end if (num_turbines >= 2 and num_boilers >= 2) or num_turbines >= 3 then - turbine_view(root, 58, 19, unit.turbine_ps_tbl[t_idx]) + turbine_view(root, 58, 19, t_idx, unit.turbine_data_tbl[t_idx], unit.turbine_ps_tbl[t_idx]) end local steam_pipes_b = {} diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index 897372f..08b0b9b 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -50,6 +50,10 @@ style.reactor = { { color = cpair(colors.black, colors.red), text = "SCRAM!" + }, + { + color = cpair(colors.black, colors.orange), + text = "PLC FAULT!" } } } From 621adbbcbc36fcfaa5fc75886df69f6d3aa0b47e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 5 Sep 2022 11:49:23 -0400 Subject: [PATCH 363/587] #86 type bug fix --- supervisor/session/unit.lua | 22 ++++++++++++---------- supervisor/startup.lua | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 5008d6a..e919592 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -26,7 +26,8 @@ local DT_KEYS = { function unit.new(for_reactor, num_boilers, num_turbines) local self = { r_id = for_reactor, - plc_s = nil, ---@class plc_session + plc_s = nil, ---@class plc_session_struct + plc_i = nil, ---@class plc_session counts = { boilers = num_boilers, turbines = num_turbines }, turbines = {}, boilers = {}, @@ -104,9 +105,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- clear a delta ---@param key string value key - local function _reset_dt(key) - self.deltas[key] = nil - end + local function _reset_dt(key) self.deltas[key] = nil end -- get the delta t of a value ---@param key string value key @@ -122,7 +121,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- update all delta computations local function _dt__compute_all() if self.plc_s ~= nil then - local plc_db = self.plc_s.get_db() + local plc_db = self.plc_i.get_db() -- @todo Meknaism 10.1+ will change fuel/waste to need _amnt _compute_dt(DT_KEYS.ReactorTemp, plc_db.mek_status.temp) @@ -166,7 +165,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) self.db.annunciator.PLCOnline = (self.plc_s ~= nil) and (self.plc_s.open) if self.plc_s ~= nil then - local plc_db = self.plc_s.get_db() + local plc_db = self.plc_i.get_db() -- update annunciator self.db.annunciator.ReactorTrip = plc_db.rps_tripped @@ -208,13 +207,15 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- check heating rate low if self.plc_s ~= nil then + local r_db = self.plc_i.get_db() + -- check for inactive boilers while reactor is active for i = 1, #self.boilers do local boiler = self.boilers[i] ---@type unit_session local idx = boiler.get_device_idx() local db = boiler.get_db() ---@type boiler_session_db - if self.plc_s.get_db().mek_status.status then + if r_db.mek_status.status then self.db.annunciator.HeatingRateLow[idx] = db.state.boil_rate == 0 else self.db.annunciator.HeatingRateLow[idx] = false @@ -222,7 +223,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) end -- check for rate mismatch - local expected_boil_rate = self.plc_s.get_db().mek_status.heating_rate / 10.0 + local expected_boil_rate = r_db.mek_status.heating_rate / 10.0 self.db.annunciator.BoilRateMismatch = math.abs(expected_boil_rate - total_boil_rate) > 25.0 end @@ -324,6 +325,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) ---@param plc_session plc_session_struct function public.link_plc_session(plc_session) self.plc_s = plc_session + self.plc_i = plc_session.instance -- reset deltas _reset_dt(DT_KEYS.ReactorTemp) @@ -398,7 +400,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) local build = {} if self.plc_s ~= nil then - build.reactor = self.plc_s.get_struct() + build.reactor = self.plc_i.get_struct() end build.boilers = {} @@ -421,7 +423,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) local status = {} if self.plc_s ~= nil then - local reactor = self.plc_s + local reactor = self.plc_i status = { reactor.get_status(), reactor.get_rps(), reactor.get_general_status() } end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index fdeedc1..f809780 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.5.7" +local SUPERVISOR_VERSION = "beta-v0.5.8" local print = util.print local println = util.println From 473763fd2753866bc4a750fc22a6f05485435626 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 5 Sep 2022 16:04:32 -0400 Subject: [PATCH 364/587] #78 removed use of data in graphics layouts since we don't have data at construct time --- coordinator/startup.lua | 2 +- coordinator/ui/components/boiler.lua | 12 +++--------- coordinator/ui/components/reactor.lua | 17 +++++++---------- coordinator/ui/components/turbine.lua | 9 +++------ coordinator/ui/components/unit_overview.lua | 10 +++++----- coordinator/ui/layout/main_view.lua | 13 +++++++------ 6 files changed, 26 insertions(+), 37 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index ef3555c..1b38bd8 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.4.2" +local COORDINATOR_VERSION = "alpha-v0.4.3" local print = util.print local println = util.println diff --git a/coordinator/ui/components/boiler.lua b/coordinator/ui/components/boiler.lua index 0d36a2f..d97d93e 100644 --- a/coordinator/ui/components/boiler.lua +++ b/coordinator/ui/components/boiler.lua @@ -18,9 +18,8 @@ local border = core.graphics.border ---@param x integer ---@param y integer ---@param id integer ----@param data boiler_session_db|boilerv_session_db ---@param ps psil -local function new_view(root, x, y, id, data, ps) +local function new_view(root, x, y, id, ps) local tag = id .. "_" local boiler = Rectangle{parent=root,border=border(1, colors.gray, true),width=31,height=7,x=x,y=y} @@ -29,8 +28,8 @@ local function new_view(root, x, y, id, data, ps) local lu_col = cpair(colors.gray, colors.gray) local status = StateIndicator{parent=boiler,x=10,y=1,states=style.boiler.states,value=1,min_width=10} - local temp = DataIndicator{parent=boiler,x=5,y=3,lu_colors=lu_col,label="Temp:",unit="K",format="%10.2f",value=data.state.temperature,width=22,fg_bg=text_fg_bg} - local boil_r = DataIndicator{parent=boiler,x=5,y=4,lu_colors=lu_col,label="Boil:",unit="mB/t",format="%10.0f",value=data.state.boil_rate,commas=true,width=22,fg_bg=text_fg_bg} + local temp = DataIndicator{parent=boiler,x=5,y=3,lu_colors=lu_col,label="Temp:",unit="K",format="%10.2f",value=0,width=22,fg_bg=text_fg_bg} + local boil_r = DataIndicator{parent=boiler,x=5,y=4,lu_colors=lu_col,label="Boil:",unit="mB/t",format="%10.0f",value=0,commas=true,width=22,fg_bg=text_fg_bg} ps.subscribe(tag .. "computed_status", status.update) ps.subscribe(tag .. "temperature", temp.update) @@ -50,11 +49,6 @@ local function new_view(root, x, y, id, data, ps) ps.subscribe(tag .. "water_fill", water.update) ps.subscribe(tag .. "steam_fill", steam.update) ps.subscribe(tag .. "ccool_fill", ccool.update) - - hcool.update(data.tanks.hcool_fill) - water.update(data.tanks.water_fill) - steam.update(data.tanks.steam_fill) - ccool.update(data.tanks.ccool_fill) end return new_view diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua index 523a949..4faf9a0 100644 --- a/coordinator/ui/components/reactor.lua +++ b/coordinator/ui/components/reactor.lua @@ -27,9 +27,9 @@ local function new_view(root, x, y, data, ps) local lu_col = cpair(colors.gray, colors.gray) local status = StateIndicator{parent=reactor,x=8,y=1,states=style.reactor.states,value=1,min_width=14} - local core_temp = DataIndicator{parent=reactor,x=2,y=3,lu_colors=lu_col,label="Core Temp:",unit="K",format="%10.2f",value=data.mek_status.temp,width=26,fg_bg=text_fg_bg} - local burn_r = DataIndicator{parent=reactor,x=2,y=4,lu_colors=lu_col,label="Burn Rate:",unit="mB/t",format="%10.1f",value=data.mek_status.act_burn_rate,width=26,fg_bg=text_fg_bg} - local heating_r = DataIndicator{parent=reactor,x=2,y=5,lu_colors=lu_col,label="Heating:",unit="mB/t",format="%12.0f",value=data.mek_status.heating_rate,commas=true,width=26,fg_bg=text_fg_bg} + local core_temp = DataIndicator{parent=reactor,x=2,y=3,lu_colors=lu_col,label="Core Temp:",unit="K",format="%10.2f",value=0,width=26,fg_bg=text_fg_bg} + local burn_r = DataIndicator{parent=reactor,x=2,y=4,lu_colors=lu_col,label="Burn Rate:",unit="mB/t",format="%10.1f",value=0,width=26,fg_bg=text_fg_bg} + local heating_r = DataIndicator{parent=reactor,x=2,y=5,lu_colors=lu_col,label="Heating:",unit="mB/t",format="%12.0f",value=0,commas=true,width=26,fg_bg=text_fg_bg} ps.subscribe("computed_status", status.update) ps.subscribe("temp", core_temp.update) @@ -43,8 +43,10 @@ local function new_view(root, x, y, data, ps) TextBox{parent=reactor_fills,text="HCOOL",x=2,y=4,height=1,fg_bg=text_fg_bg} TextBox{parent=reactor_fills,text="WASTE",x=2,y=5,height=1,fg_bg=text_fg_bg} - local ccool_color = util.trinary(data.mek_status.ccool_type == "sodium", cpair(colors.lightBlue,colors.gray), cpair(colors.blue,colors.gray)) - local hcool_color = util.trinary(data.mek_status.hcool_type == "superheated_sodium", cpair(colors.orange,colors.gray), cpair(colors.white,colors.gray)) + -- local ccool_color = util.trinary(data.mek_status.ccool_type == "sodium", cpair(colors.lightBlue,colors.gray), cpair(colors.blue,colors.gray)) + -- local hcool_color = util.trinary(data.mek_status.hcool_type == "superheated_sodium", cpair(colors.orange,colors.gray), cpair(colors.white,colors.gray)) + local ccool_color = util.trinary(true, cpair(colors.lightBlue,colors.gray), cpair(colors.blue,colors.gray)) + local hcool_color = util.trinary(true, cpair(colors.orange,colors.gray), cpair(colors.white,colors.gray)) local fuel = HorizontalBar{parent=reactor_fills,x=8,y=1,show_percent=true,bar_fg_bg=cpair(colors.black,colors.gray),height=1,width=14} local ccool = HorizontalBar{parent=reactor_fills,x=8,y=2,show_percent=true,bar_fg_bg=ccool_color,height=1,width=14} @@ -55,11 +57,6 @@ local function new_view(root, x, y, data, ps) ps.subscribe("ccool_fill", ccool.update) ps.subscribe("hcool_fill", hcool.update) ps.subscribe("waste_fill", waste.update) - - fuel.update(data.mek_status.fuel_fill) - ccool.update(data.mek_status.ccool_fill) - hcool.update(data.mek_status.hcool_fill) - waste.update(data.mek_status.waste_fill) end return new_view diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua index 705d2ea..5d0f5b3 100644 --- a/coordinator/ui/components/turbine.lua +++ b/coordinator/ui/components/turbine.lua @@ -18,9 +18,8 @@ local border = core.graphics.border ---@param x integer ---@param y integer ---@param id integer ----@param data turbine_session_db|turbinev_session_db ---@param ps psil -local function new_view(root, x, y, id, data, ps) +local function new_view(root, x, y, id, ps) local tag = id .. "_" local turbine = Rectangle{parent=root,border=border(1, colors.gray, true),width=23,height=7,x=x,y=y} @@ -29,8 +28,8 @@ local function new_view(root, x, y, id, data, ps) local lu_col = cpair(colors.gray, colors.gray) local status = StateIndicator{parent=turbine,x=8,y=1,states=style.turbine.states,value=1,min_width=10} - local prod_rate = DataIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",unit="MFE",format="%10.2f",value=data.state.prod_rate,width=16,fg_bg=text_fg_bg} - local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=data.state.flow_rate,commas=true,width=16,fg_bg=text_fg_bg} + local prod_rate = DataIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",unit="MFE",format="%10.2f",value=0,width=16,fg_bg=text_fg_bg} + local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=0,commas=true,width=16,fg_bg=text_fg_bg} ps.subscribe(tag .. "computed_status", status.update) ps.subscribe(tag .. "prod_rate", prod_rate.update) @@ -39,8 +38,6 @@ local function new_view(root, x, y, id, data, ps) local steam = VerticalBar{parent=turbine,x=2,y=1,fg_bg=cpair(colors.white,colors.gray),height=5,width=2} ps.subscribe(tag .. "steam_fill", steam.update) - - steam.update(data.tanks.steam_fill) end return new_view diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 12ed1de..322943a 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -73,8 +73,8 @@ local function make(parent, x, y, unit) -- BOILERS -- ------------- - if num_boilers >= 1 then boiler_view(root, 16, 11, 1, unit.boiler_data_tbl[1], unit.boiler_ps_tbl[1]) end - if num_boilers >= 2 then boiler_view(root, 16, 19, 2, unit.boiler_data_tbl[2], unit.boiler_ps_tbl[2]) end + if num_boilers >= 1 then boiler_view(root, 16, 11, 1, unit.boiler_ps_tbl[1]) end + if num_boilers >= 2 then boiler_view(root, 16, 19, 2, unit.boiler_ps_tbl[2]) end -------------- -- TURBINES -- @@ -84,17 +84,17 @@ local function make(parent, x, y, unit) local no_boilers = num_boilers == 0 if (num_turbines >= 3) or no_boilers or (num_boilers == 1 and num_turbines >= 2) then - turbine_view(root, 58, 3, t_idx, unit.turbine_data_tbl[t_idx], unit.turbine_ps_tbl[t_idx]) + turbine_view(root, 58, 3, t_idx, unit.turbine_ps_tbl[t_idx]) t_idx = t_idx + 1 end if (num_turbines >= 1 and not no_boilers) or num_turbines >= 2 then - turbine_view(root, 58, 11, t_idx, unit.turbine_data_tbl[t_idx], unit.turbine_ps_tbl[t_idx]) + turbine_view(root, 58, 11, t_idx, unit.turbine_ps_tbl[t_idx]) t_idx = t_idx + 1 end if (num_turbines >= 2 and num_boilers >= 2) or num_turbines >= 3 then - turbine_view(root, 58, 19, t_idx, unit.turbine_data_tbl[t_idx], unit.turbine_ps_tbl[t_idx]) + turbine_view(root, 58, 19, t_idx, unit.turbine_ps_tbl[t_idx]) end local steam_pipes_b = {} diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index ee096ce..048e4b7 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -2,16 +2,17 @@ -- Main SCADA Coordinator GUI -- -local database = require("coordinator.database") -local style = require("coordinator.ui.style") +local database = require("coordinator.database") -local core = require("graphics.core") - -local DisplayBox = require("graphics.elements.displaybox") -local TextBox = require("graphics.elements.textbox") +local style = require("coordinator.ui.style") local unit_overview = require("coordinator.ui.components.unit_overview") +local core = require("graphics.core") + +local DisplayBox = require("graphics.elements.displaybox") +local TextBox = require("graphics.elements.textbox") + local TEXT_ALIGN = core.graphics.TEXT_ALIGN local function init(monitor) From 4359cc3e6375867545943381bce20e140e3a321d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 5 Sep 2022 16:21:59 -0400 Subject: [PATCH 365/587] formatting --- reactor-plc/config.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactor-plc/config.lua b/reactor-plc/config.lua index 99edc92..2e0eed3 100644 --- a/reactor-plc/config.lua +++ b/reactor-plc/config.lua @@ -3,7 +3,7 @@ local config = {} -- set to false to run in offline mode (safety regulation only) config.NETWORKED = true -- unique reactor ID -config.REACTOR_ID = 1 +config.REACTOR_ID = 1 -- port to send packets TO server config.SERVER_PORT = 16000 -- port to listen to incoming packets FROM server From e456d344685fb4b7f7b64aa79d543052636f2267 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 5 Sep 2022 16:23:03 -0400 Subject: [PATCH 366/587] svsessions bugfixes --- supervisor/session/coordinator.lua | 2 -- supervisor/session/plc.lua | 2 +- supervisor/startup.lua | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index c875e05..f36414e 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -108,8 +108,6 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) -- send unit statuses local function _send_status() - self.acks.builds = false - local status = {} for i = 1, #self.units do diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 9784600..ffffeb9 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -342,7 +342,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) end elseif pkt.type == RPLC_TYPES.RPS_STATUS then -- RPS status packet received, copy data - if pkt.length == 7 then + if pkt.length == 9 then local status = pcall(_copy_rps_status, pkt.data) if status then -- copied in RPS status data OK diff --git a/supervisor/startup.lua b/supervisor/startup.lua index f809780..51fa010 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.5.8" +local SUPERVISOR_VERSION = "beta-v0.5.9" local print = util.print local println = util.println From 397e311f1bceea76fae244fe4a00d0533702f830 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 5 Sep 2022 16:24:57 -0400 Subject: [PATCH 367/587] #85 handling supervisor disconnected, bugfix with renderer --- coordinator/renderer.lua | 2 +- coordinator/startup.lua | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 986c0e0..d0f48d6 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -38,7 +38,7 @@ local function _reset_display(monitor, recolor) end else -- reset all colors - for _, val in colors do + for _, val in pairs(colors) do -- colors api has constants and functions, just get color constants if type(val) == "number" then monitor.setPaletteColor(val, term.nativePaletteColor(val)) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 1b38bd8..cf76ff5 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.4.3" +local COORDINATOR_VERSION = "alpha-v0.4.4" local print = util.print local println = util.println @@ -247,6 +247,17 @@ while ui_ok do -- 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 + -- close connection and UI + coord_comms.close() + renderer.close_ui() + + -- try to re-connect to the supervisor + init_connect_sv() + ui_ok = init_start_ui() + end elseif event == "monitor_touch" then -- handle a monitor touch event renderer.handle_touch(core.events.touch(param1, param2, param3)) From 117784500a2cc19aac8445f4a4dfe788b3e5c77b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 5 Sep 2022 19:40:20 -0400 Subject: [PATCH 368/587] #78 functional reactor stats on main view --- coordinator/database.lua | 10 +++++++++- coordinator/startup.lua | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/coordinator/database.lua b/coordinator/database.lua index 0238429..858f864 100644 --- a/coordinator/database.lua +++ b/coordinator/database.lua @@ -153,10 +153,18 @@ function database.update_statuses(statuses) end for key, val in pairs(unit.reactor_data) do - if key ~= "mek_struct" then + if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then unit.reactor_ps.publish(key, val) end end + + for key, val in pairs(unit.reactor_data.rps_status) do + unit.reactor_ps.publish(key, val) + end + + for key, val in pairs(unit.reactor_data.mek_status) do + unit.reactor_ps.publish(key, val) + end end -- RTU statuses diff --git a/coordinator/startup.lua b/coordinator/startup.lua index cf76ff5..f7f2b74 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.4.4" +local COORDINATOR_VERSION = "alpha-v0.4.5" local print = util.print local println = util.println From b53d2d66942944de580e27570003dec24c66c478 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 6 Sep 2022 22:38:27 -0400 Subject: [PATCH 369/587] code cleanup and work on #78 linking for annunciator --- coordinator/coordinator.lua | 10 +- coordinator/{database.lua => iocontrol.lua} | 78 ++++++++---- coordinator/renderer.lua | 2 +- coordinator/startup.lua | 2 +- coordinator/ui/components/boiler.lua | 10 +- coordinator/ui/components/reactor.lua | 11 +- coordinator/ui/components/turbine.lua | 10 +- coordinator/ui/components/unit_overview.lua | 9 +- coordinator/ui/layout/main_view.lua | 6 +- coordinator/ui/layout/unit_view.lua | 134 +++++++++++--------- 10 files changed, 162 insertions(+), 110 deletions(-) rename coordinator/{database.lua => iocontrol.lua} (79%) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 7ce510f..18ec157 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -4,7 +4,7 @@ local ppm = require("scada-common.ppm") local util = require("scada-common.util") local apisessions = require("coordinator.apisessions") -local database = require("coordinator.database") +local iocontrol = require("coordinator.iocontrol") local dialog = require("coordinator.ui.dialog") @@ -387,8 +387,8 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa table.insert(conf.defs, packet.data[i]) end - -- init database structure - database.init(conf) + -- init io controller + iocontrol.init(conf) self.sv_linked = true else @@ -399,7 +399,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa end elseif packet.type == SCADA_CRDN_TYPES.STRUCT_BUILDS then -- record builds - if database.populate_builds(packet.data) then + if iocontrol.populate_builds(packet.data) then -- acknowledge receipt of builds _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.STRUCT_BUILDS, {}) else @@ -407,7 +407,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa end elseif packet.type == SCADA_CRDN_TYPES.UNIT_STATUSES then -- update statuses - if not database.update_statuses(packet.data) then + if not iocontrol.update_statuses(packet.data) then log.error("received invalid unit statuses packet") end elseif packet.type == SCADA_CRDN_TYPES.COMMAND_UNIT then diff --git a/coordinator/database.lua b/coordinator/iocontrol.lua similarity index 79% rename from coordinator/database.lua rename to coordinator/iocontrol.lua index 858f864..b5878b9 100644 --- a/coordinator/database.lua +++ b/coordinator/iocontrol.lua @@ -1,30 +1,35 @@ local psil = require("scada-common.psil") local log = require("scada-common.log") -local database = {} +local iocontrol = {} -database.WASTE = { Pu = 0, Po = 1, AntiMatter = 2 } +---@class ioctl +local io = {} ----@class coord_db -local db = {} - --- initialize the coordinator database +-- initialize the coordinator IO controller ---@param conf facility_conf configuration -function database.init(conf) - db.facility = { +function iocontrol.init(conf) + io.facility = { scram = false, num_units = conf.num_units, ps = psil.create() } - db.units = {} + io.units = {} for i = 1, conf.num_units do - ---@class coord_db_entry + ---@class ioctl_entry local entry = { unit_id = i, ---@type integer initialized = false, - waste_mode = 0, + control_state = false, + burn_rate_cmd = 0.0, + waste_control = 0, + + ---@fixme debug stubs to be linked into comms later? + start = function () print("UNIT " .. i .. ": start") end, + scram = function () print("UNIT " .. i .. ": SCRAM") end, + set_burn = function (rate) print("UNIT " .. i .. ": set burn rate to " .. rate) end, reactor_ps = psil.create(), reactor_data = {}, ---@type reactor_db @@ -48,20 +53,20 @@ function database.init(conf) table.insert(entry.turbine_data_tbl, data) end - table.insert(db.units, entry) + table.insert(io.units, entry) end end -- populate structure builds ---@param builds table ---@return boolean valid -function database.populate_builds(builds) - if #builds ~= #db.units then +function iocontrol.populate_builds(builds) + if #builds ~= #io.units then log.error("number of provided unit builds does not match expected number of units") return false else for i = 1, #builds do - local unit = db.units[i] ---@type coord_db_entry + local unit = io.units[i] ---@type ioctl_entry local build = builds[i] -- reactor build @@ -110,13 +115,13 @@ end -- update unit statuses ---@param statuses table ---@return boolean valid -function database.update_statuses(statuses) - if #statuses ~= #db.units then +function iocontrol.update_statuses(statuses) + if #statuses ~= #io.units then log.error("number of provided unit statuses does not match expected number of units") return false else for i = 1, #statuses do - local unit = db.units[i] ---@type coord_db_entry + local unit = io.units[i] ---@type ioctl_entry local status = statuses[i] -- reactor PLC status @@ -124,8 +129,11 @@ function database.update_statuses(statuses) local reactor_status = status[1] if #reactor_status == 0 then + unit.reactor_ps.publish("online", false) unit.reactor_ps.publish("computed_status", 1) -- disconnected else + unit.reactor_ps.publish("online", true) + local mek_status = reactor_status[1] local rps_status = reactor_status[2] local gen_status = reactor_status[3] @@ -167,20 +175,43 @@ function database.update_statuses(statuses) end end + -- annunciator + + local annunciator = status[2] ---@type annunciator + + for key, val in pairs(annunciator) do + if key == "TurbineTrip" then + local trips = val + local any = false + + for x = 1, #trips do + any = any or trips[x] + unit.turbine_ps_tbl[x].publish(x .. "_TurbineTrip", trips[x]) + end + + unit.reactor_ps.publish("TurbineTrip", any) + else + unit.reactor_ps.publish(key, val) + end + end + -- RTU statuses - local rtu_statuses = status[2] + local rtu_statuses = status[3] -- boiler statuses for id = 1, #unit.boiler_data_tbl do if rtu_statuses.boilers[i] == nil then -- disconnected + unit.boiler_ps_tbl[id].publish(id .. "_online", false) unit.boiler_ps_tbl[id].publish(id .. "_computed_status", 1) end end for id, boiler in pairs(rtu_statuses.boilers) do + unit.boiler_ps_tbl[id].publish(id .. "_online", true) + unit.boiler_data_tbl[id].state = boiler[1] ---@type table unit.boiler_data_tbl[id].tanks = boiler[2] ---@type table @@ -208,11 +239,14 @@ function database.update_statuses(statuses) for id = 1, #unit.turbine_ps_tbl do if rtu_statuses.turbines[i] == nil then -- disconnected + unit.turbine_ps_tbl[id].publish(id .. "_online", false) unit.turbine_ps_tbl[id].publish(id .. "_computed_status", 1) end end for id, turbine in pairs(rtu_statuses.turbines) do + unit.turbine_ps_tbl[id].publish(id .. "_online", true) + unit.turbine_data_tbl[id].state = turbine[1] ---@type table unit.turbine_data_tbl[id].tanks = turbine[2] ---@type table @@ -242,7 +276,7 @@ function database.update_statuses(statuses) return true end --- get the database -function database.get() return db end +-- get the IO controller database +function iocontrol.get_db() return io end -return database +return iocontrol diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index d0f48d6..db874ae 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -1,6 +1,6 @@ local log = require("scada-common.log") -local database = require("coordinator.database") +local iocontrol = require("coordinator.iocontrol") local main_view = require("coordinator.ui.layout.main_view") local unit_view = require("coordinator.ui.layout.unit_view") diff --git a/coordinator/startup.lua b/coordinator/startup.lua index f7f2b74..379e2fc 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.4.5" +local COORDINATOR_VERSION = "alpha-v0.4.6" local print = util.print local println = util.println diff --git a/coordinator/ui/components/boiler.lua b/coordinator/ui/components/boiler.lua index d97d93e..702cbed 100644 --- a/coordinator/ui/components/boiler.lua +++ b/coordinator/ui/components/boiler.lua @@ -14,11 +14,11 @@ local cpair = core.graphics.cpair local border = core.graphics.border -- new boiler view ----@param root graphics_element ----@param x integer ----@param y integer ----@param id integer ----@param ps psil +---@param root graphics_element parent +---@param x integer top left x +---@param y integer top left y +---@param id integer device index +---@param ps psil ps interface local function new_view(root, x, y, id, ps) local tag = id .. "_" diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua index 4faf9a0..09f82f9 100644 --- a/coordinator/ui/components/reactor.lua +++ b/coordinator/ui/components/reactor.lua @@ -15,11 +15,12 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN local cpair = core.graphics.cpair local border = core.graphics.border ----@param root graphics_element ----@param x integer ----@param y integer ----@param data reactor_db ----@param ps psil +-- create new reactor view +---@param root graphics_element parent +---@param x integer top left x +---@param y integer top left y +---@param data reactor_db reactor data +---@param ps psil ps interface local function new_view(root, x, y, data, ps) local reactor = Rectangle{parent=root,border=border(1, colors.gray, true),width=30,height=7,x=x,y=y} diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua index 5d0f5b3..9fa855f 100644 --- a/coordinator/ui/components/turbine.lua +++ b/coordinator/ui/components/turbine.lua @@ -14,11 +14,11 @@ local cpair = core.graphics.cpair local border = core.graphics.border -- new turbine view ----@param root graphics_element ----@param x integer ----@param y integer ----@param id integer ----@param ps psil +---@param root graphics_element parent +---@param x integer top left x +---@param y integer top left y +---@param id integer device index +---@param ps psil ps interface local function new_view(root, x, y, id, ps) local tag = id .. "_" diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 322943a..81ea0e4 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -20,10 +20,11 @@ local cpair = core.graphics.cpair local border = core.graphics.border local pipe = core.graphics.pipe ----@param parent graphics_element ----@param x integer ----@param y integer ----@param unit coord_db_entry +-- make a new unit overview window +---@param parent graphics_element parent +---@param x integer top left x +---@param y integer top left y +---@param unit ioctl_entry unit database entry local function make(parent, x, y, unit) local height = 0 local num_boilers = #unit.boiler_data_tbl diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index 048e4b7..b9145a3 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -2,7 +2,7 @@ -- Main SCADA Coordinator GUI -- -local database = require("coordinator.database") +local iocontrol = require("coordinator.iocontrol") local style = require("coordinator.ui.style") @@ -15,13 +15,15 @@ local TextBox = require("graphics.elements.textbox") local TEXT_ALIGN = core.graphics.TEXT_ALIGN +-- create new main view +---@param monitor table main viewscreen local function init(monitor) local main = DisplayBox{window=monitor,fg_bg=style.root} -- window header message TextBox{parent=main,text="Nuclear Generation Facility SCADA Coordinator",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} - local db = database.get() + local db = iocontrol.get_db() local uo_1, uo_2, uo_3, uo_4 ---@type graphics_element diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index cb11953..06c5074 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -2,11 +2,14 @@ -- Reactor Unit SCADA Coordinator GUI -- -local core = require("graphics.core") local tcallbackdsp = require("scada-common.tcallbackdsp") +local iocontrol = require("coordinator.iocontrol") + local style = require("coordinator.ui.style") +local core = require("graphics.core") + local DisplayBox = require("graphics.elements.displaybox") local Div = require("graphics.elements.div") local TextBox = require("graphics.elements.textbox") @@ -30,7 +33,14 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN local cpair = core.graphics.cpair local border = core.graphics.border +-- create a unit view +---@param monitor table +---@param id integer local function init(monitor, id) + local unit = iocontrol.get_db().units[id] ---@type ioctl_entry + local r_ps = unit.reactor_ps + local r_data = unit.reactor_data + local main = DisplayBox{window=monitor,fg_bg=style.root} TextBox{parent=main,text="Reactor Unit #" .. id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} @@ -38,41 +48,37 @@ local function init(monitor, id) local scram_fg_bg = cpair(colors.white, colors.gray) local lu_cpair = cpair(colors.gray, colors.gray) - ---@fixme test code - local t = 300 - if id == 1 then - t = 340 - elseif id == 2 then - t = 340 - elseif id == 3 then - t = 300 - elseif id == 4 then - t = 300 - end + -- main stats and core map -- + --@todo need to be checking actual reactor dimensions somehow local core_map = CoreMap{parent=main,x=2,y=3,reactor_l=18,reactor_w=18} - core_map.update(t) + r_ps.subscribe("temp", core_map.update) local stat_fg_bg = cpair(colors.black,colors.white) TextBox{parent=main,x=21,y=3,text="Core Temp",height=1,fg_bg=style.label} - DataIndicator{parent=main,x=21,label="",format="%9.2f",value=300,unit="K",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + local core_temp = DataIndicator{parent=main,x=21,label="",format="%9.2f",value=0,unit="K",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + r_ps.subscribe("temp", core_temp.update) main.line_break() TextBox{parent=main,x=21,text="Burn Rate",height=1,width=12,fg_bg=style.label} - DataIndicator{parent=main,x=21,label="",format="%6.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + local act_burn_r = DataIndicator{parent=main,x=21,label="",format="%6.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + r_ps.subscribe("act_burn_rate", act_burn_r.update) main.line_break() TextBox{parent=main,x=21,text="Commanded Burn Rate",height=2,width=12,fg_bg=style.label} - DataIndicator{parent=main,x=21,label="",format="%6.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + local burn_r = DataIndicator{parent=main,x=21,label="",format="%6.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + r_ps.subscribe("burn_rate", burn_r.update) main.line_break() TextBox{parent=main,x=21,text="Heating Rate",height=1,width=12,fg_bg=style.label} - DataIndicator{parent=main,x=21,label="",format="%11.0f",value=0,unit="",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + local heating_r = DataIndicator{parent=main,x=21,label="",format="%11.0f",value=0,unit="",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + r_ps.subscribe("heating_rate", heating_r.update) main.line_break() TextBox{parent=main,x=21,text="Containment Integrity",height=2,width=12,fg_bg=style.label} - DataIndicator{parent=main,x=21,label="",format="%9.0f",value=100,unit="%",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + local integ = DataIndicator{parent=main,x=21,label="",format="%9.0f",value=100,unit="%",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + r_ps.subscribe("damage", function (x) integ.update(1.0 - (x / 100.0)) end) main.line_break() -- TextBox{parent=main,text="FL",x=21,y=19,height=1,width=2,fg_bg=style.label} @@ -85,11 +91,7 @@ local function init(monitor, id) -- local ccool = VerticalBar{parent=main,x=28,y=12,fg_bg=cpair(colors.lightBlue,colors.gray),height=6,width=2} -- local hcool = VerticalBar{parent=main,x=31,y=12,fg_bg=cpair(colors.orange,colors.gray),height=6,width=2} - -- ---@fixme test code - -- fuel.update(1) - -- ccool.update(0.85) - -- hcool.update(0.08) - -- waste.update(0.32) + -- annunciator -- local annunciator = Div{parent=main,x=34,y=3} @@ -99,20 +101,35 @@ local function init(monitor, id) local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(colors.green,colors.red)} local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=cpair(colors.white,colors.gray)} local r_active = IndicatorLight{parent=annunciator,label="Active",colors=cpair(colors.green,colors.gray)} + --@todo auto control as info sent here local r_auto = IndicatorLight{parent=annunciator,label="Auto Control",colors=cpair(colors.blue,colors.gray)} + r_ps.subscribe("PLCOnline", plc_online.update) + r_ps.subscribe("PLCHeartbeat", plc_hbeat.update) + r_ps.subscribe("status", r_active.update) + annunciator.line_break() -- annunciator fields - local r_trip = IndicatorLight{parent=annunciator,label="Reactor SCRAM",colors=cpair(colors.red,colors.gray)} - local r_mtrp = IndicatorLight{parent=annunciator,label="Manual Reactor SCRAM",colors=cpair(colors.red,colors.gray)} - local r_rtrp = IndicatorLight{parent=annunciator,label="RCP Trip",colors=cpair(colors.red,colors.gray)} - local r_cflo = IndicatorLight{parent=annunciator,label="RCS Flow Low",colors=cpair(colors.yellow,colors.gray)} - local r_temp = IndicatorLight{parent=annunciator,label="Reactor Temp. High",colors=cpair(colors.red,colors.gray)} - local r_rhdt = IndicatorLight{parent=annunciator,label="Reactor High Delta T",colors=cpair(colors.yellow,colors.gray)} - local r_firl = IndicatorLight{parent=annunciator,label="Fuel Input Rate Low",colors=cpair(colors.yellow,colors.gray)} - local r_wloc = IndicatorLight{parent=annunciator,label="Waste Line Occlusion",colors=cpair(colors.yellow,colors.gray)} - local r_hsrt = IndicatorLight{parent=annunciator,label="High Startup Rate",colors=cpair(colors.yellow,colors.gray)} + local r_scram = IndicatorLight{parent=annunciator,label="Reactor SCRAM",colors=cpair(colors.red,colors.gray)} + local r_mscrm = IndicatorLight{parent=annunciator,label="Manual Reactor SCRAM",colors=cpair(colors.red,colors.gray)} + local r_rtrip = IndicatorLight{parent=annunciator,label="RCP Trip",colors=cpair(colors.red,colors.gray)} + local r_cflow = IndicatorLight{parent=annunciator,label="RCS Flow Low",colors=cpair(colors.yellow,colors.gray)} + local r_temp = IndicatorLight{parent=annunciator,label="Reactor Temp. High",colors=cpair(colors.red,colors.gray)} + local r_rhdt = IndicatorLight{parent=annunciator,label="Reactor High Delta T",colors=cpair(colors.yellow,colors.gray)} + local r_firl = IndicatorLight{parent=annunciator,label="Fuel Input Rate Low",colors=cpair(colors.yellow,colors.gray)} + local r_wloc = IndicatorLight{parent=annunciator,label="Waste Line Occlusion",colors=cpair(colors.yellow,colors.gray)} + local r_hsrt = IndicatorLight{parent=annunciator,label="High Startup Rate",colors=cpair(colors.yellow,colors.gray)} + + r_ps.subscribe("ReactorSCRAM", r_scram.update) + r_ps.subscribe("ManualReactorSCRAM", r_mscrm.update) + r_ps.subscribe("RCPTrip", r_rtrip.update) + r_ps.subscribe("RCSFlowLow", r_cflow.update) + r_ps.subscribe("ReactorTempHigh", r_temp.update) + r_ps.subscribe("ReactorHighDeltaT", r_rhdt.update) + r_ps.subscribe("FuelInputRateLow", r_firl.update) + r_ps.subscribe("WasteLineOcclusion", r_wloc.update) + r_ps.subscribe("HighStartupRate", r_hsrt.update) annunciator.line_break() @@ -120,13 +137,23 @@ local function init(monitor, id) local rps_trp = IndicatorLight{parent=annunciator,label="RPS Trip",colors=cpair(colors.red,colors.gray)} local rps_dmg = IndicatorLight{parent=annunciator,label="Damage Critical",colors=cpair(colors.yellow,colors.gray)} local rps_exh = IndicatorLight{parent=annunciator,label="Excess Heated Coolant",colors=cpair(colors.yellow,colors.gray)} - local rps_exc = IndicatorLight{parent=annunciator,label="Excess Waste",colors=cpair(colors.yellow,colors.gray)} + local rps_exw = IndicatorLight{parent=annunciator,label="Excess Waste",colors=cpair(colors.yellow,colors.gray)} local rps_tmp = IndicatorLight{parent=annunciator,label="High Core Temp",colors=cpair(colors.yellow,colors.gray)} local rps_nof = IndicatorLight{parent=annunciator,label="No Fuel",colors=cpair(colors.yellow,colors.gray)} local rps_noc = IndicatorLight{parent=annunciator,label="No Coolant",colors=cpair(colors.yellow,colors.gray)} local rps_flt = IndicatorLight{parent=annunciator,label="PPM Fault",colors=cpair(colors.yellow,colors.gray)} local rps_tmo = IndicatorLight{parent=annunciator,label="Timeout",colors=cpair(colors.yellow,colors.gray)} + r_ps.subscribe("rps_tripped", rps_trp.update) + r_ps.subscribe("dmg_crit", rps_dmg.update) + r_ps.subscribe("ex_hcool", rps_exh.update) + r_ps.subscribe("ex_waste", rps_exw.update) + r_ps.subscribe("high_temp", rps_tmp.update) + r_ps.subscribe("no_fuel", rps_nof.update) + r_ps.subscribe("no_cool", rps_noc.update) + r_ps.subscribe("fault", rps_flt.update) + r_ps.subscribe("timeout", rps_tmo.update) + annunciator.line_break() -- cooling @@ -136,6 +163,12 @@ local function init(monitor, id) local c_mwrf = IndicatorLight{parent=annunciator,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} local c_tbnt = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + r_ps.subscribe("BoilRateMismatch", c_brm.update) + r_ps.subscribe("CoolantFeedMismatch", c_cfm.update) + r_ps.subscribe("SteamFeedMismatch", c_sfm.update) + r_ps.subscribe("MaxWaterReturnFeed", c_mwrf.update) + r_ps.subscribe("TurbineTrip", c_tbnt.update) + annunciator.line_break() -- machine-specific indicators @@ -174,19 +207,17 @@ local function init(monitor, id) DataIndicator{parent=main,x=34,y=51,label="",format="%10.1f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=18,fg_bg=stat_fg_bg} - ---@fixme debug code - local f = function () print("unit " .. id .. " scram!") end - local fs = function () print("unit " .. id .. " start!") end + -- reactor controls -- - local start = StartButton{parent=main,x=12,y=44,callback=fs,fg_bg=scram_fg_bg} - local scram = SCRAMButton{parent=main,x=22,y=44,callback=f,fg_bg=scram_fg_bg} + StartButton{parent=main,x=12,y=44,callback=unit.start,fg_bg=scram_fg_bg} + SCRAMButton{parent=main,x=22,y=44,callback=unit.scram,fg_bg=scram_fg_bg} local burn_control = Div{parent=main,x=12,y=40,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} - local set_burn = function () print("unit " .. id .. " set burn to " .. burn_rate.get_value()) end - TextBox{parent=burn_control,x=9,y=2,text="mB/t"} + + local set_burn = function () unit.set_burn(burn_rate.get_value()) end PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} local opts = { @@ -224,33 +255,16 @@ local function init(monitor, id) main.line_break() ColorMap{parent=main,x=2,y=51} - ---@fixme test code - plc_hbeat.update(true) - r_auto.update(true) - r_trip.update(true) - r_mtrp.update(true) - rps_trp.update(true) - rps_nof.update(true) - - ---@fixme test code - local heartbeat = true - local function _test_toggle() - plc_hbeat.update(heartbeat) - heartbeat = not heartbeat - tcallbackdsp.dispatch(1, _test_toggle) - end - ---@fixme test code local rps = true - local function _test_toggle1() + local function _test_toggle() rps_nof.update(rps) rps = not rps - tcallbackdsp.dispatch(0.25, _test_toggle1) + tcallbackdsp.dispatch(0.25, _test_toggle) end ---@fixme test code - tcallbackdsp.dispatch(1, _test_toggle) - tcallbackdsp.dispatch(0.25, _test_toggle1) + tcallbackdsp.dispatch(0.25, _test_toggle) return main end From c2ac7fc973916b08a564ff5b6dac0edba5b25a99 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 7 Sep 2022 10:25:22 -0400 Subject: [PATCH 370/587] #78 removed redundant device index from boiler/turbine ps keys --- coordinator/coordinator.lua | 2 +- coordinator/iocontrol.lua | 66 ++++++++++----------- coordinator/ui/components/boiler.lua | 21 +++---- coordinator/ui/components/turbine.lua | 22 +++---- coordinator/ui/components/unit_overview.lua | 10 ++-- 5 files changed, 54 insertions(+), 67 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 18ec157..e7a9ebd 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -399,7 +399,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa end elseif packet.type == SCADA_CRDN_TYPES.STRUCT_BUILDS then -- record builds - if iocontrol.populate_builds(packet.data) then + if iocontrol.record_builds(packet.data) then -- acknowledge receipt of builds _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.STRUCT_BUILDS, {}) else diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index b5878b9..1d20ddb 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -60,7 +60,7 @@ end -- populate structure builds ---@param builds table ---@return boolean valid -function iocontrol.populate_builds(builds) +function iocontrol.record_builds(builds) if #builds ~= #io.units then log.error("number of provided unit builds does not match expected number of units") return false @@ -84,10 +84,8 @@ function iocontrol.populate_builds(builds) unit.boiler_ps_tbl[id].publish("formed", boiler[2]) - local key_prefix = "unit_" .. i .. "_boiler_" .. id .. "_" - for key, val in pairs(unit.boiler_data_tbl[id].build) do - unit.boiler_ps_tbl[id].publish(key_prefix .. key, val) + unit.boiler_ps_tbl[id].publish(key, val) end end @@ -100,10 +98,8 @@ function iocontrol.populate_builds(builds) unit.turbine_ps_tbl[id].publish("formed", turbine[2]) - local key_prefix = "unit_" .. i .. "_turbine_" .. id .. "_" - for key, val in pairs(unit.turbine_data_tbl[id].build) do - unit.turbine_ps_tbl[id].publish(key_prefix .. key, val) + unit.turbine_ps_tbl[id].publish(key, val) end end end @@ -129,11 +125,8 @@ function iocontrol.update_statuses(statuses) local reactor_status = status[1] if #reactor_status == 0 then - unit.reactor_ps.publish("online", false) unit.reactor_ps.publish("computed_status", 1) -- disconnected else - unit.reactor_ps.publish("online", true) - local mek_status = reactor_status[1] local rps_status = reactor_status[2] local gen_status = reactor_status[3] @@ -181,16 +174,31 @@ function iocontrol.update_statuses(statuses) for key, val in pairs(annunciator) do if key == "TurbineTrip" then + -- split up turbine trip table for all turbines and a general OR combination local trips = val local any = false - for x = 1, #trips do - any = any or trips[x] - unit.turbine_ps_tbl[x].publish(x .. "_TurbineTrip", trips[x]) + for id = 1, #trips do + any = any or trips[id] + unit.turbine_ps_tbl[id].publish(key, trips[id]) end unit.reactor_ps.publish("TurbineTrip", any) + elseif key == "BoilerOnline" or key == "HeatingRateLow" then + -- split up array for all boilers + for id = 1, #val do + unit.boiler_ps_tbl[id].publish(key, val[id]) + end + elseif key == "TurbineOnline" or key == "SteamDumpOpen" or key == "TurbineOverSpeed" then + -- split up array for all turbines + for id = 1, #val do + unit.turbine_ps_tbl[id].publish(key, val[id]) + end + elseif type(val) == "table" then + -- we missed one of the tables? + log.error("unrecognized table found in annunciator list, this is a bug", true) else + -- non-table fields unit.reactor_ps.publish(key, val) end end @@ -204,33 +212,28 @@ function iocontrol.update_statuses(statuses) for id = 1, #unit.boiler_data_tbl do if rtu_statuses.boilers[i] == nil then -- disconnected - unit.boiler_ps_tbl[id].publish(id .. "_online", false) - unit.boiler_ps_tbl[id].publish(id .. "_computed_status", 1) + unit.boiler_ps_tbl[id].publish("computed_status", 1) end end for id, boiler in pairs(rtu_statuses.boilers) do - unit.boiler_ps_tbl[id].publish(id .. "_online", true) - unit.boiler_data_tbl[id].state = boiler[1] ---@type table unit.boiler_data_tbl[id].tanks = boiler[2] ---@type table - local key_prefix = id .. "_" - local data = unit.boiler_data_tbl[id] ---@type boiler_session_db|boilerv_session_db if data.state.boil_rate > 0 then - unit.boiler_ps_tbl[id].publish(id .. "_computed_status", 3) -- active + unit.boiler_ps_tbl[id].publish("computed_status", 3) -- active else - unit.boiler_ps_tbl[id].publish(id .. "_computed_status", 2) -- idle + unit.boiler_ps_tbl[id].publish("computed_status", 2) -- idle end for key, val in pairs(unit.boiler_data_tbl[id].state) do - unit.boiler_ps_tbl[id].publish(key_prefix .. key, val) + unit.boiler_ps_tbl[id].publish(key, val) end for key, val in pairs(unit.boiler_data_tbl[id].tanks) do - unit.boiler_ps_tbl[id].publish(key_prefix .. key, val) + unit.boiler_ps_tbl[id].publish(key, val) end end @@ -239,35 +242,30 @@ function iocontrol.update_statuses(statuses) for id = 1, #unit.turbine_ps_tbl do if rtu_statuses.turbines[i] == nil then -- disconnected - unit.turbine_ps_tbl[id].publish(id .. "_online", false) - unit.turbine_ps_tbl[id].publish(id .. "_computed_status", 1) + unit.turbine_ps_tbl[id].publish("computed_status", 1) end end for id, turbine in pairs(rtu_statuses.turbines) do - unit.turbine_ps_tbl[id].publish(id .. "_online", true) - unit.turbine_data_tbl[id].state = turbine[1] ---@type table unit.turbine_data_tbl[id].tanks = turbine[2] ---@type table - local key_prefix = id .. "_" - local data = unit.turbine_data_tbl[id] ---@type turbine_session_db|turbinev_session_db if data.tanks.steam_fill >= 0.99 then - unit.turbine_ps_tbl[id].publish(id .. "_computed_status", 4) -- trip + unit.turbine_ps_tbl[id].publish("computed_status", 4) -- trip elseif data.state.flow_rate < 100 then - unit.turbine_ps_tbl[id].publish(id .. "_computed_status", 2) -- idle + unit.turbine_ps_tbl[id].publish("computed_status", 2) -- idle else - unit.turbine_ps_tbl[id].publish(id .. "_computed_status", 3) -- active + unit.turbine_ps_tbl[id].publish("computed_status", 3) -- active end for key, val in pairs(unit.turbine_data_tbl[id].state) do - unit.turbine_ps_tbl[id].publish(key_prefix .. key, val) + unit.turbine_ps_tbl[id].publish(key, val) end for key, val in pairs(unit.turbine_data_tbl[id].tanks) do - unit.turbine_ps_tbl[id].publish(key_prefix .. key, val) + unit.turbine_ps_tbl[id].publish(key, val) end end end diff --git a/coordinator/ui/components/boiler.lua b/coordinator/ui/components/boiler.lua index 702cbed..08cab0a 100644 --- a/coordinator/ui/components/boiler.lua +++ b/coordinator/ui/components/boiler.lua @@ -8,8 +8,6 @@ local Rectangle = require("graphics.elements.rectangle") local TextBox = require("graphics.elements.textbox") local VerticalBar = require("graphics.elements.indicators.vbar") -local TEXT_ALIGN = core.graphics.TEXT_ALIGN - local cpair = core.graphics.cpair local border = core.graphics.border @@ -17,11 +15,8 @@ local border = core.graphics.border ---@param root graphics_element parent ---@param x integer top left x ---@param y integer top left y ----@param id integer device index ---@param ps psil ps interface -local function new_view(root, x, y, id, ps) - local tag = id .. "_" - +local function new_view(root, x, y, ps) local boiler = Rectangle{parent=root,border=border(1, colors.gray, true),width=31,height=7,x=x,y=y} local text_fg_bg = cpair(colors.black, colors.lightGray) @@ -31,9 +26,9 @@ local function new_view(root, x, y, id, ps) local temp = DataIndicator{parent=boiler,x=5,y=3,lu_colors=lu_col,label="Temp:",unit="K",format="%10.2f",value=0,width=22,fg_bg=text_fg_bg} local boil_r = DataIndicator{parent=boiler,x=5,y=4,lu_colors=lu_col,label="Boil:",unit="mB/t",format="%10.0f",value=0,commas=true,width=22,fg_bg=text_fg_bg} - ps.subscribe(tag .. "computed_status", status.update) - ps.subscribe(tag .. "temperature", temp.update) - ps.subscribe(tag .. "boil_rate", boil_r.update) + ps.subscribe("computed_status", status.update) + ps.subscribe("temperature", temp.update) + ps.subscribe("boil_rate", boil_r.update) TextBox{parent=boiler,text="H",x=2,y=5,height=1,width=1,fg_bg=text_fg_bg} TextBox{parent=boiler,text="W",x=3,y=5,height=1,width=1,fg_bg=text_fg_bg} @@ -45,10 +40,10 @@ local function new_view(root, x, y, id, ps) local steam = VerticalBar{parent=boiler,x=27,y=1,fg_bg=cpair(colors.white,colors.gray),height=4,width=1} local ccool = VerticalBar{parent=boiler,x=28,y=1,fg_bg=cpair(colors.lightBlue,colors.gray),height=4,width=1} - ps.subscribe(tag .. "hcool_fill", hcool.update) - ps.subscribe(tag .. "water_fill", water.update) - ps.subscribe(tag .. "steam_fill", steam.update) - ps.subscribe(tag .. "ccool_fill", ccool.update) + ps.subscribe("hcool_fill", hcool.update) + ps.subscribe("water_fill", water.update) + ps.subscribe("steam_fill", steam.update) + ps.subscribe("ccool_fill", ccool.update) end return new_view diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua index 9fa855f..b885966 100644 --- a/coordinator/ui/components/turbine.lua +++ b/coordinator/ui/components/turbine.lua @@ -5,11 +5,8 @@ local style = require("coordinator.ui.style") local DataIndicator = require("graphics.elements.indicators.data") local StateIndicator = require("graphics.elements.indicators.state") local Rectangle = require("graphics.elements.rectangle") -local TextBox = require("graphics.elements.textbox") local VerticalBar = require("graphics.elements.indicators.vbar") -local TEXT_ALIGN = core.graphics.TEXT_ALIGN - local cpair = core.graphics.cpair local border = core.graphics.border @@ -17,27 +14,24 @@ local border = core.graphics.border ---@param root graphics_element parent ---@param x integer top left x ---@param y integer top left y ----@param id integer device index ---@param ps psil ps interface -local function new_view(root, x, y, id, ps) - local tag = id .. "_" - +local function new_view(root, x, y, ps) local turbine = Rectangle{parent=root,border=border(1, colors.gray, true),width=23,height=7,x=x,y=y} local text_fg_bg = cpair(colors.black, colors.lightGray) local lu_col = cpair(colors.gray, colors.gray) - local status = StateIndicator{parent=turbine,x=8,y=1,states=style.turbine.states,value=1,min_width=10} - local prod_rate = DataIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",unit="MFE",format="%10.2f",value=0,width=16,fg_bg=text_fg_bg} - local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=0,commas=true,width=16,fg_bg=text_fg_bg} + local status = StateIndicator{parent=turbine,x=8,y=1,states=style.turbine.states,value=1,min_width=10} + local prod_rate = DataIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",unit="MFE",format="%10.2f",value=0,width=16,fg_bg=text_fg_bg} + local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=0,commas=true,width=16,fg_bg=text_fg_bg} - ps.subscribe(tag .. "computed_status", status.update) - ps.subscribe(tag .. "prod_rate", prod_rate.update) - ps.subscribe(tag .. "flow_rate", flow_rate.update) + ps.subscribe("computed_status", status.update) + ps.subscribe("prod_rate", prod_rate.update) + ps.subscribe("flow_rate", flow_rate.update) local steam = VerticalBar{parent=turbine,x=2,y=1,fg_bg=cpair(colors.white,colors.gray),height=5,width=2} - ps.subscribe(tag .. "steam_fill", steam.update) + ps.subscribe("steam_fill", steam.update) end return new_view diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 81ea0e4..7e24396 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -74,8 +74,8 @@ local function make(parent, x, y, unit) -- BOILERS -- ------------- - if num_boilers >= 1 then boiler_view(root, 16, 11, 1, unit.boiler_ps_tbl[1]) end - if num_boilers >= 2 then boiler_view(root, 16, 19, 2, unit.boiler_ps_tbl[2]) end + if num_boilers >= 1 then boiler_view(root, 16, 11, unit.boiler_ps_tbl[1]) end + if num_boilers >= 2 then boiler_view(root, 16, 19, unit.boiler_ps_tbl[2]) end -------------- -- TURBINES -- @@ -85,17 +85,17 @@ local function make(parent, x, y, unit) local no_boilers = num_boilers == 0 if (num_turbines >= 3) or no_boilers or (num_boilers == 1 and num_turbines >= 2) then - turbine_view(root, 58, 3, t_idx, unit.turbine_ps_tbl[t_idx]) + turbine_view(root, 58, 3, unit.turbine_ps_tbl[t_idx]) t_idx = t_idx + 1 end if (num_turbines >= 1 and not no_boilers) or num_turbines >= 2 then - turbine_view(root, 58, 11, t_idx, unit.turbine_ps_tbl[t_idx]) + turbine_view(root, 58, 11, unit.turbine_ps_tbl[t_idx]) t_idx = t_idx + 1 end if (num_turbines >= 2 and num_boilers >= 2) or num_turbines >= 3 then - turbine_view(root, 58, 19, t_idx, unit.turbine_ps_tbl[t_idx]) + turbine_view(root, 58, 19, unit.turbine_ps_tbl[t_idx]) end local steam_pipes_b = {} From 0f6b3fdd9878d0f4f1145f3f669a11a1dae2b490 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 7 Sep 2022 10:25:48 -0400 Subject: [PATCH 371/587] fixed incorrect comment --- reactor-plc/threads.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 2ee2906..57726e1 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -37,7 +37,7 @@ function threads.thread__main(smem, init) function public.exec() log.debug("main thread init, clock inactive") - -- send status updates at 2Hz (every 10 server ticks) (every loop tick) + -- send status updates at 1Hz (every 20 server ticks) (every loop tick) -- send link requests at 0.5Hz (every 40 server ticks) (every 4 loop ticks) local LINK_TICKS = 4 local ticks_to_update = 0 From 49605e5966c03f14a1568a75849e1acd3702116b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 7 Sep 2022 10:39:51 -0400 Subject: [PATCH 372/587] added tri-state indicator light --- graphics/elements/indicators/light.lua | 2 - graphics/elements/indicators/trilight.lua | 65 +++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 graphics/elements/indicators/trilight.lua diff --git a/graphics/elements/indicators/light.lua b/graphics/elements/indicators/light.lua index e16a68a..411982d 100644 --- a/graphics/elements/indicators/light.lua +++ b/graphics/elements/indicators/light.lua @@ -1,7 +1,5 @@ -- Indicator Light Graphics Element -local util = require("scada-common.util") - local element = require("graphics.element") ---@class indicator_light_args diff --git a/graphics/elements/indicators/trilight.lua b/graphics/elements/indicators/trilight.lua new file mode 100644 index 0000000..d73516f --- /dev/null +++ b/graphics/elements/indicators/trilight.lua @@ -0,0 +1,65 @@ +-- Tri-State Indicator Light Graphics Element + +local element = require("graphics.element") + +---@class tristate_indicator_light_args +---@field label string indicator label +---@field c_off color color for off +---@field c1 color color for state 1 +---@field c2 color color for state 2 +---@field c3 color color for state 3 +---@field min_label_width? integer label length if omitted +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field fg_bg? cpair foreground/background colors + +-- new indicator light +---@param args tristate_indicator_light_args +---@return graphics_element element, element_id id +local function tristate_indicator_light(args) + assert(type(args.label) == "string", "graphics.elements.indicators.trilight: label is a required field") + assert(type(args.c_off) == "integer", "graphics.elements.indicators.trilight: c_off is a required field") + assert(type(args.c1) == "integer", "graphics.elements.indicators.trilight: c1 is a required field") + assert(type(args.c2) == "integer", "graphics.elements.indicators.trilight: c2 is a required field") + assert(type(args.c3) == "integer", "graphics.elements.indicators.trilight: c3 is a required field") + + -- single line + args.height = 1 + + -- determine width + args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2 + + -- blit translations + local c_off colors.toBlit(args.c_off) + local c1 colors.toBlit(args.c1) + local c2 colors.toBlit(args.c2) + local c3 colors.toBlit(args.c3) + + -- create new graphics element base object + local e = element.new(args) + + -- on state change + ---@param new_state integer indicator state + function e.on_update(new_state) + e.window.setCursorPos(1, 1) + if new_state == 1 then + e.window.blit(" \x95", "0" .. c1, c1 .. e.fg_bg.blit_bkg) + elseif new_state == 2 then + e.window.blit(" \x95", "0" .. c2, c2 .. e.fg_bg.blit_bkg) + elseif new_state == 3 then + e.window.blit(" \x95", "0" .. c3, c3 .. e.fg_bg.blit_bkg) + else + e.window.blit(" \x95", "0" ..c_off, c_off .. e.fg_bg.blit_bkg) + end + end + + -- write label and initial indicator light + e.on_update(0) + e.window.write(args.label) + + return e.get() +end + +return tristate_indicator_light From e084ae1eeaccbbb60cc51d5ecdad4e0cca4bc655 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 7 Sep 2022 10:42:12 -0400 Subject: [PATCH 373/587] removed redundant c_off from trilight --- graphics/elements/indicators/trilight.lua | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/graphics/elements/indicators/trilight.lua b/graphics/elements/indicators/trilight.lua index d73516f..df09e51 100644 --- a/graphics/elements/indicators/trilight.lua +++ b/graphics/elements/indicators/trilight.lua @@ -4,7 +4,6 @@ local element = require("graphics.element") ---@class tristate_indicator_light_args ---@field label string indicator label ----@field c_off color color for off ---@field c1 color color for state 1 ---@field c2 color color for state 2 ---@field c3 color color for state 3 @@ -20,7 +19,6 @@ local element = require("graphics.element") ---@return graphics_element element, element_id id local function tristate_indicator_light(args) assert(type(args.label) == "string", "graphics.elements.indicators.trilight: label is a required field") - assert(type(args.c_off) == "integer", "graphics.elements.indicators.trilight: c_off is a required field") assert(type(args.c1) == "integer", "graphics.elements.indicators.trilight: c1 is a required field") assert(type(args.c2) == "integer", "graphics.elements.indicators.trilight: c2 is a required field") assert(type(args.c3) == "integer", "graphics.elements.indicators.trilight: c3 is a required field") @@ -32,7 +30,6 @@ local function tristate_indicator_light(args) args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2 -- blit translations - local c_off colors.toBlit(args.c_off) local c1 colors.toBlit(args.c1) local c2 colors.toBlit(args.c2) local c3 colors.toBlit(args.c3) @@ -44,14 +41,12 @@ local function tristate_indicator_light(args) ---@param new_state integer indicator state function e.on_update(new_state) e.window.setCursorPos(1, 1) - if new_state == 1 then - e.window.blit(" \x95", "0" .. c1, c1 .. e.fg_bg.blit_bkg) - elseif new_state == 2 then + if new_state == 2 then e.window.blit(" \x95", "0" .. c2, c2 .. e.fg_bg.blit_bkg) elseif new_state == 3 then e.window.blit(" \x95", "0" .. c3, c3 .. e.fg_bg.blit_bkg) else - e.window.blit(" \x95", "0" ..c_off, c_off .. e.fg_bg.blit_bkg) + e.window.blit(" \x95", "0" .. c1, c1 .. e.fg_bg.blit_bkg) end end From 3621f53c451fd966c508f2256738d619a18462b0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 7 Sep 2022 11:10:20 -0400 Subject: [PATCH 374/587] #78 linked up the rest of the fields that we currently have, holding off on a few that are still WIP features --- coordinator/iocontrol.lua | 6 ++ coordinator/startup.lua | 6 +- coordinator/ui/layout/unit_view.lua | 143 +++++++++++++++++----------- 3 files changed, 97 insertions(+), 58 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 1d20ddb..6feaf5a 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -22,6 +22,9 @@ function iocontrol.init(conf) unit_id = i, ---@type integer initialized = false, + num_boilers = 0, + num_turbines = 0, + control_state = false, burn_rate_cmd = 0.0, waste_control = 0, @@ -53,6 +56,9 @@ function iocontrol.init(conf) table.insert(entry.turbine_data_tbl, data) end + entry.num_boilers = #entry.boiler_data_tbl + entry.num_turbines = #entry.turbine_data_tbl + table.insert(io.units, entry) end end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 379e2fc..6283cb0 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.4.6" +local COORDINATOR_VERSION = "alpha-v0.4.7" local print = util.print local println = util.println @@ -186,7 +186,7 @@ while ui_ok do log.warning("non-comms modem disconnected") end elseif type == "monitor" then - -- @todo: handle monitor loss + ---@todo: handle monitor loss end end elseif event == "peripheral" then @@ -209,7 +209,7 @@ while ui_ok do log_sys("wired modem reconnected") end elseif type == "monitor" then - -- @todo: handle monitor reconnect + ---@todo: handle monitor reconnect end end elseif event == "timer" then diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index 06c5074..412680e 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -2,31 +2,32 @@ -- Reactor Unit SCADA Coordinator GUI -- -local tcallbackdsp = require("scada-common.tcallbackdsp") +local tcallbackdsp = require("scada-common.tcallbackdsp") -local iocontrol = require("coordinator.iocontrol") +local iocontrol = require("coordinator.iocontrol") -local style = require("coordinator.ui.style") +local style = require("coordinator.ui.style") -local core = require("graphics.core") +local core = require("graphics.core") -local DisplayBox = require("graphics.elements.displaybox") -local Div = require("graphics.elements.div") -local TextBox = require("graphics.elements.textbox") -local ColorMap = require("graphics.elements.colormap") +local DisplayBox = require("graphics.elements.displaybox") +local Div = require("graphics.elements.div") +local TextBox = require("graphics.elements.textbox") +local ColorMap = require("graphics.elements.colormap") -local CoreMap = require("graphics.elements.indicators.coremap") -local DataIndicator = require("graphics.elements.indicators.data") -local HorizontalBar = require("graphics.elements.indicators.hbar") -local IndicatorLight = require("graphics.elements.indicators.light") -local StateIndicator = require("graphics.elements.indicators.state") -local VerticalBar = require("graphics.elements.indicators.vbar") +local CoreMap = require("graphics.elements.indicators.coremap") +local DataIndicator = require("graphics.elements.indicators.data") +local HorizontalBar = require("graphics.elements.indicators.hbar") +local IndicatorLight = require("graphics.elements.indicators.light") +local StateIndicator = require("graphics.elements.indicators.state") +local TriIndicatorLight = require("graphics.elements.indicators.trilight") +local VerticalBar = require("graphics.elements.indicators.vbar") -local MultiButton = require("graphics.elements.controls.multi_button") -local PushButton = require("graphics.elements.controls.push_button") -local SCRAMButton = require("graphics.elements.controls.scram_button") -local StartButton = require("graphics.elements.controls.start_button") -local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") +local MultiButton = require("graphics.elements.controls.multi_button") +local PushButton = require("graphics.elements.controls.push_button") +local SCRAMButton = require("graphics.elements.controls.scram_button") +local StartButton = require("graphics.elements.controls.start_button") +local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") local TEXT_ALIGN = core.graphics.TEXT_ALIGN @@ -39,7 +40,8 @@ local border = core.graphics.border local function init(monitor, id) local unit = iocontrol.get_db().units[id] ---@type ioctl_entry local r_ps = unit.reactor_ps - local r_data = unit.reactor_data + local b_ps = unit.boiler_ps_tbl + local t_ps = unit.turbine_ps_tbl local main = DisplayBox{window=monitor,fg_bg=style.root} @@ -50,7 +52,7 @@ local function init(monitor, id) -- main stats and core map -- - --@todo need to be checking actual reactor dimensions somehow + ---@todo need to be checking actual reactor dimensions somehow local core_map = CoreMap{parent=main,x=2,y=3,reactor_l=18,reactor_w=18} r_ps.subscribe("temp", core_map.update) @@ -101,7 +103,7 @@ local function init(monitor, id) local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(colors.green,colors.red)} local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=cpair(colors.white,colors.gray)} local r_active = IndicatorLight{parent=annunciator,label="Active",colors=cpair(colors.green,colors.gray)} - --@todo auto control as info sent here + ---@todo auto control as info sent here local r_auto = IndicatorLight{parent=annunciator,label="Auto Control",colors=cpair(colors.blue,colors.gray)} r_ps.subscribe("PLCOnline", plc_online.update) @@ -172,39 +174,73 @@ local function init(monitor, id) annunciator.line_break() -- machine-specific indicators - TextBox{parent=main,x=32,y=34,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} - TextBox{parent=main,x=32,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} - main.line_break() - annunciator.line_break() - TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - IndicatorLight{parent=annunciator,label="Steam Dump Open",colors=cpair(colors.yellow,colors.gray)} - TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} - TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} - main.line_break() - annunciator.line_break() - TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - IndicatorLight{parent=annunciator,label="Steam Dump Open",colors=cpair(colors.yellow,colors.gray)} - TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} - TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} - main.line_break() - annunciator.line_break() - TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - IndicatorLight{parent=annunciator,label="Steam Dump Open",colors=cpair(colors.yellow,colors.gray)} - TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} - TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + if unit.num_boilers > 0 then + TextBox{parent=main,x=32,y=34,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local b1_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} + b_ps[1].subscribe("HeatingRateLow", b1_hr.update) + end + if unit.num_boilers > 1 then + TextBox{parent=main,x=32,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local b2_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} + b_ps[2].subscribe("HeatingRateLow", b2_hr.update) + end + if unit.num_boilers > 0 then + main.line_break() + annunciator.line_break() + end + + TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t1_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} + t_ps[1].subscribe("SteamDumpOpen", t1_sdo.update) + + TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t1_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} + t_ps[1].subscribe("TurbineOverSpeed", t1_tos.update) + + TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t1_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + t_ps[1].subscribe("TurbineTrip", t1_trp.update) + + main.line_break() annunciator.line_break() + + if unit.num_turbines > 1 then + TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t2_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} + t_ps[2].subscribe("SteamDumpOpen", t2_sdo.update) + + TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t2_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} + t_ps[2].subscribe("TurbineOverSpeed", t2_tos.update) + + TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t2_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + t_ps[2].subscribe("TurbineTrip", t2_trp.update) + + main.line_break() + annunciator.line_break() + end + + if unit.num_turbines > 2 then + TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t3_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} + t_ps[3].subscribe("SteamDumpOpen", t3_sdo.update) + + TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t3_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} + t_ps[3].subscribe("TurbineOverSpeed", t3_tos.update) + + TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t3_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + t_ps[3].subscribe("TurbineTrip", t3_trp.update) + + annunciator.line_break() + end + + ---@todo radiation monitor IndicatorLight{parent=annunciator,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)} IndicatorLight{parent=annunciator,label="Radiation Alarm",colors=cpair(colors.red,colors.gray)} - DataIndicator{parent=main,x=34,y=51,label="",format="%10.1f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=18,fg_bg=stat_fg_bg} -- reactor controls -- @@ -213,10 +249,8 @@ local function init(monitor, id) SCRAMButton{parent=main,x=22,y=44,callback=unit.scram,fg_bg=scram_fg_bg} local burn_control = Div{parent=main,x=12,y=40,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} - local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} TextBox{parent=burn_control,x=9,y=2,text="mB/t"} - local set_burn = function () unit.set_burn(burn_rate.get_value()) end PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} @@ -243,9 +277,8 @@ local function init(monitor, id) } } - ---@fixme debug code + ---@todo waste selection local waste_sel_f = function (s) print("waste: " .. s) end - local waste_sel = Div{parent=main,x=2,y=48,width=29,height=2,fg_bg=cpair(colors.black, colors.white)} MultiButton{parent=waste_sel,x=1,y=1,options=opts,callback=waste_sel_f,min_width=6,fg_bg=cpair(colors.black, colors.white)} From c5ba95449feba8a3607628f8b3efae8d9c0cd306 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 8 Sep 2022 10:22:11 -0400 Subject: [PATCH 375/587] bugfix to trilight, change to test code in unit view --- coordinator/startup.lua | 2 +- coordinator/ui/layout/unit_view.lua | 2 +- graphics/elements/indicators/trilight.lua | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 6283cb0..6c26267 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.4.7" +local COORDINATOR_VERSION = "alpha-v0.4.8" local print = util.print local println = util.println diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index 412680e..52fcbce 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -291,7 +291,7 @@ local function init(monitor, id) ---@fixme test code local rps = true local function _test_toggle() - rps_nof.update(rps) + rps_trp.update(rps) rps = not rps tcallbackdsp.dispatch(0.25, _test_toggle) end diff --git a/graphics/elements/indicators/trilight.lua b/graphics/elements/indicators/trilight.lua index df09e51..9d8731e 100644 --- a/graphics/elements/indicators/trilight.lua +++ b/graphics/elements/indicators/trilight.lua @@ -19,9 +19,9 @@ local element = require("graphics.element") ---@return graphics_element element, element_id id local function tristate_indicator_light(args) assert(type(args.label) == "string", "graphics.elements.indicators.trilight: label is a required field") - assert(type(args.c1) == "integer", "graphics.elements.indicators.trilight: c1 is a required field") - assert(type(args.c2) == "integer", "graphics.elements.indicators.trilight: c2 is a required field") - assert(type(args.c3) == "integer", "graphics.elements.indicators.trilight: c3 is a required field") + assert(type(args.c1) == "number", "graphics.elements.indicators.trilight: c1 is a required field") + assert(type(args.c2) == "number", "graphics.elements.indicators.trilight: c2 is a required field") + assert(type(args.c3) == "number", "graphics.elements.indicators.trilight: c3 is a required field") -- single line args.height = 1 @@ -30,9 +30,9 @@ local function tristate_indicator_light(args) args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2 -- blit translations - local c1 colors.toBlit(args.c1) - local c2 colors.toBlit(args.c2) - local c3 colors.toBlit(args.c3) + local c1 = colors.toBlit(args.c1) + local c2 = colors.toBlit(args.c2) + local c3 = colors.toBlit(args.c3) -- create new graphics element base object local e = element.new(args) From 17954ef3d02c5b4b4df6e54f736f01ecb5f1f07f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 8 Sep 2022 10:25:00 -0400 Subject: [PATCH 376/587] #86 supervisor fixes and changes for annunciator/units; send annunciator, fixed heartbeat, change to max return flow detection --- supervisor/session/coordinator.lua | 2 +- supervisor/session/plc.lua | 2 ++ supervisor/session/unit.lua | 20 ++++++++++++++------ supervisor/startup.lua | 2 +- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index f36414e..00f3a33 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -112,7 +112,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) for i = 1, #self.units do local unit = self.units[i] ---@type reactor_unit - status[unit.get_id()] = { unit.get_reactor_status(), unit.get_rtu_statuses() } + status[unit.get_id()] = { unit.get_reactor_status(), unit.get_annunciator(), unit.get_rtu_statuses() } end _send(SCADA_CRDN_TYPES.UNIT_STATUSES, status) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index ffffeb9..ffc851b 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -285,6 +285,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) self.sDB.overridden = pkt.data[3] self.sDB.degraded = pkt.data[4] self.sDB.mek_status.heating_rate = pkt.data[5] + ---@todo rps_tripped is redundant with overridden, rename overridden to rps_tripped globally + self.sDB.rps_tripped = pkt.data[4] -- attempt to read mek_data table if pkt.data[6] ~= nil then diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index e919592..88c512b 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -1,5 +1,6 @@ local types = require "scada-common.types" local util = require "scada-common.util" +local log = require "scada-common.log" local unit = {} @@ -33,14 +34,15 @@ function unit.new(for_reactor, num_boilers, num_turbines) boilers = {}, redstone = {}, deltas = {}, + last_heartbeat = 0, db = { ---@class annunciator annunciator = { -- reactor PLCOnline = false, PLCHeartbeat = false, -- alternate true/false to blink, each time there is a keep_alive - ReactorTrip = false, - ManualReactorTrip = false, + ReactorSCRAM = false, + ManualReactorSCRAM = false, RCPTrip = false, RCSFlowLow = false, ReactorTempHigh = false, @@ -167,9 +169,15 @@ function unit.new(for_reactor, num_boilers, num_turbines) if self.plc_s ~= nil then local plc_db = self.plc_i.get_db() - -- update annunciator - self.db.annunciator.ReactorTrip = plc_db.rps_tripped - self.db.annunciator.ManualReactorTrip = plc_db.rps_trip_cause == types.rps_status_t.manual + -- heartbeat blink about every second + if self.last_heartbeat + 1000 < plc_db.last_status_update then + self.db.annunciator.PLCHeartbeat = not self.db.annunciator.PLCHeartbeat + self.last_heartbeat = plc_db.last_status_update + end + + -- update other annunciator fields + self.db.annunciator.ReactorSCRAM = plc_db.overridden + self.db.annunciator.ManualReactorSCRAM = plc_db.rps_trip_cause == types.rps_status_t.manual self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.no_cool) self.db.annunciator.RCSFlowLow = plc_db.mek_status.ccool_fill < 0.75 or plc_db.mek_status.hcool_fill > 0.25 self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > 1000 @@ -270,7 +278,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) local sfmismatch = math.abs(total_flow_rate - total_input_rate) > 10 sfmismatch = sfmismatch or boiler_steam_dt_sum > 0 or boiler_water_dt_sum < 0 self.db.annunciator.SteamFeedMismatch = sfmismatch - self.db.annunciator.MaxWaterReturnFeed = max_water_return_rate == total_flow_rate + self.db.annunciator.MaxWaterReturnFeed = max_water_return_rate == total_flow_rate and total_flow_rate ~= 0 -- check if steam dumps are open for i = 1, #self.turbines do diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 51fa010..ea313a4 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.5.9" +local SUPERVISOR_VERSION = "beta-v0.5.10" local print = util.print local println = util.println From 350370a084aa92f97a4a566f10c009a9456d9643 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 8 Sep 2022 12:19:19 -0400 Subject: [PATCH 377/587] notify subscriber right away if there is already a value present --- scada-common/psil.lua | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scada-common/psil.lua b/scada-common/psil.lua index 9901e97..ddadf36 100644 --- a/scada-common/psil.lua +++ b/scada-common/psil.lua @@ -13,17 +13,26 @@ function psil.create() -- allocate a new interconnect field ---@key string data key local function alloc(key) - self.ic[key] = { subscribers = {}, value = 0 } + self.ic[key] = { subscribers = {}, value = nil } end ---@class psil local public = {} -- subscribe to a data object in the interconnect + -- + -- will call func() right away if a value is already avaliable ---@param key string data key ---@param func function function to call on change function public.subscribe(key, func) - if self.ic[key] == nil then alloc(key) end + -- allocate new key if not found or notify if value is found + if self.ic[key] == nil then + alloc(key) + elseif self.ic[key].value ~= nil then + func(self.ic[key].value) + end + + -- subscribe to key table.insert(self.ic[key].subscribers, { notify = func }) end From 33695b2ed6332c064a0bd51b74fbf818c1879dfb Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 8 Sep 2022 14:49:01 -0400 Subject: [PATCH 378/587] #74 #86 removed redundant overridden field (use rps_tripped) --- coordinator/iocontrol.lua | 7 +++---- reactor-plc/plc.lua | 2 +- supervisor/session/plc.lua | 13 ++++--------- supervisor/session/unit.lua | 12 ++++++------ 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 6feaf5a..2d99905 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -139,10 +139,9 @@ function iocontrol.update_statuses(statuses) unit.reactor_data.last_status_update = gen_status[1] unit.reactor_data.control_state = gen_status[2] - unit.reactor_data.overridden = gen_status[3] - unit.reactor_data.degraded = gen_status[4] - unit.reactor_data.rps_tripped = gen_status[5] - unit.reactor_data.rps_trip_cause = gen_status[6] + unit.reactor_data.rps_tripped = gen_status[3] + unit.reactor_data.rps_trip_cause = gen_status[4] + unit.reactor_data.degraded = gen_status[5] unit.reactor_data.rps_status = rps_status ---@type rps_status unit.reactor_data.mek_status = mek_status ---@type mek_status diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 691525f..5983c8a 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -531,7 +531,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co local sys_status = { util.time(), -- timestamp (not self.scrammed), -- requested control state - rps.is_tripped(), -- overridden + rps.is_tripped(), -- rps_tripped degraded, -- degraded self.reactor.getHeatingRate(), -- heating rate mek_data -- mekanism status data diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index ffc851b..d682fdb 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -86,7 +86,6 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) sDB = { last_status_update = 0, control_state = false, - overridden = false, degraded = false, rps_tripped = false, rps_trip_cause = "ok", ---@type rps_trip_cause @@ -282,11 +281,9 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) if pkt.length >= 5 then self.sDB.last_status_update = pkt.data[1] self.sDB.control_state = pkt.data[2] - self.sDB.overridden = pkt.data[3] + self.sDB.rps_tripped = pkt.data[3] self.sDB.degraded = pkt.data[4] self.sDB.mek_status.heating_rate = pkt.data[5] - ---@todo rps_tripped is redundant with overridden, rename overridden to rps_tripped globally - self.sDB.rps_tripped = pkt.data[4] -- attempt to read mek_data table if pkt.data[6] ~= nil then @@ -357,8 +354,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) end elseif pkt.type == RPLC_TYPES.RPS_ALARM then -- RPS alarm - self.sDB.overridden = true - if pkt.length == 8 then + if pkt.length == 10 then self.sDB.rps_tripped = true self.sDB.rps_trip_cause = pkt.data[1] local status = pcall(_copy_rps_status, { table.unpack(pkt.data, 2, #pkt.length) }) @@ -447,10 +443,9 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) return { self.sDB.last_status_update, self.sDB.control_state, - self.sDB.overridden, - self.sDB.degraded, self.sDB.rps_tripped, - self.sDB.rps_trip_cause + self.sDB.rps_trip_cause, + self.sDB.degraded } end diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 88c512b..6cd3a5a 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -125,7 +125,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) if self.plc_s ~= nil then local plc_db = self.plc_i.get_db() - -- @todo Meknaism 10.1+ will change fuel/waste to need _amnt + ---@todo Mekanism 10.1+ will change fuel/waste to need _amnt _compute_dt(DT_KEYS.ReactorTemp, plc_db.mek_status.temp) _compute_dt(DT_KEYS.ReactorFuel, plc_db.mek_status.fuel) _compute_dt(DT_KEYS.ReactorWaste, plc_db.mek_status.waste) @@ -137,7 +137,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) local boiler = self.boilers[i] ---@type unit_session local db = boiler.get_db() ---@type boiler_session_db - -- @todo Meknaism 10.1+ will change water/steam to need .amount + ---@todo Mekanism 10.1+ will change water/steam to need .amount _compute_dt(DT_KEYS.BoilerWater .. boiler.get_device_idx(), db.tanks.water) _compute_dt(DT_KEYS.BoilerSteam .. boiler.get_device_idx(), db.tanks.steam) _compute_dt(DT_KEYS.BoilerCCool .. boiler.get_device_idx(), db.tanks.ccool.amount) @@ -149,7 +149,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) local db = turbine.get_db() ---@type turbine_session_db _compute_dt(DT_KEYS.TurbineSteam .. turbine.get_device_idx(), db.tanks.steam) - -- @todo Mekanism 10.1+ needed + ---@todo Mekanism 10.1+ needed -- _compute_dt(DT_KEYS.TurbinePower .. turbine.get_device_idx(), db.?) end end @@ -176,16 +176,16 @@ function unit.new(for_reactor, num_boilers, num_turbines) end -- update other annunciator fields - self.db.annunciator.ReactorSCRAM = plc_db.overridden + self.db.annunciator.ReactorSCRAM = plc_db.rps_tripped self.db.annunciator.ManualReactorSCRAM = plc_db.rps_trip_cause == types.rps_status_t.manual self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.no_cool) self.db.annunciator.RCSFlowLow = plc_db.mek_status.ccool_fill < 0.75 or plc_db.mek_status.hcool_fill > 0.25 self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > 1000 self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > 100 self.db.annunciator.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < 0.0 or plc_db.mek_status.fuel_fill <= 0.01 - -- @todo this is catagorized as not urgent, but the >= 0.99 is extremely urgent, revist this (RPS will kick in though) + ---@todo this is catagorized as not urgent, but the >= 0.99 is extremely urgent, revist this (RPS will kick in though) self.db.annunciator.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 0.0 or plc_db.mek_status.waste_fill >= 0.99 - -- @todo this is dependent on setup, i.e. how much coolant is buffered and the turbine setup + ---@todo this is dependent on setup, i.e. how much coolant is buffered and the turbine setup self.db.annunciator.HighStartupRate = not plc_db.control_state and plc_db.mek_status.burn_rate > 40 end From 6f3405949d6127e9320e8fbaaaf18cb58eb8dc9f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 10 Sep 2022 10:42:56 -0400 Subject: [PATCH 379/587] #88 hold on rendering unit detail view until we get a status, added waiting animation --- coordinator/renderer.lua | 3 +- coordinator/ui/components/unit_detail.lua | 298 +++++++++++++++++++++ coordinator/ui/components/unit_waiting.lua | 33 +++ coordinator/ui/layout/unit_view.lua | 295 ++------------------ graphics/elements/animations/waiting.lua | 94 +++++++ scada-common/tcallbackdsp.lua | 5 - 6 files changed, 444 insertions(+), 284 deletions(-) create mode 100644 coordinator/ui/components/unit_detail.lua create mode 100644 coordinator/ui/components/unit_waiting.lua create mode 100644 graphics/elements/animations/waiting.lua diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index db874ae..11ffa1e 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -2,9 +2,10 @@ local log = require("scada-common.log") local iocontrol = require("coordinator.iocontrol") +local style = require("coordinator.ui.style") + local main_view = require("coordinator.ui.layout.main_view") local unit_view = require("coordinator.ui.layout.unit_view") -local style = require("coordinator.ui.style") local renderer = {} diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua new file mode 100644 index 0000000..1572880 --- /dev/null +++ b/coordinator/ui/components/unit_detail.lua @@ -0,0 +1,298 @@ +-- +-- Reactor Unit SCADA Coordinator GUI +-- + +local tcallbackdsp = require("scada-common.tcallbackdsp") + +local iocontrol = require("coordinator.iocontrol") + +local style = require("coordinator.ui.style") + +local core = require("graphics.core") + +local Div = require("graphics.elements.div") +local TextBox = require("graphics.elements.textbox") +local ColorMap = require("graphics.elements.colormap") + +local CoreMap = require("graphics.elements.indicators.coremap") +local DataIndicator = require("graphics.elements.indicators.data") +local IndicatorLight = require("graphics.elements.indicators.light") +local TriIndicatorLight = require("graphics.elements.indicators.trilight") + +local MultiButton = require("graphics.elements.controls.multi_button") +local PushButton = require("graphics.elements.controls.push_button") +local SCRAMButton = require("graphics.elements.controls.scram_button") +local StartButton = require("graphics.elements.controls.start_button") +local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") + +local TEXT_ALIGN = core.graphics.TEXT_ALIGN + +local cpair = core.graphics.cpair + +-- create a unit view +---@param parent graphics_element parent +---@param id integer +local function init(parent, id) + local unit = iocontrol.get_db().units[id] ---@type ioctl_entry + local r_ps = unit.reactor_ps + local b_ps = unit.boiler_ps_tbl + local t_ps = unit.turbine_ps_tbl + + TextBox{parent=parent,text="Reactor Unit #" .. id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + + local scram_fg_bg = cpair(colors.white, colors.gray) + local lu_cpair = cpair(colors.gray, colors.gray) + + -- main stats and core map -- + + ---@todo need to be checking actual reactor dimensions somehow + local core_map = CoreMap{parent=parent,x=2,y=3,reactor_l=18,reactor_w=18} + r_ps.subscribe("temp", core_map.update) + + local stat_fg_bg = cpair(colors.black,colors.white) + + TextBox{parent=parent,x=21,y=3,text="Core Temp",height=1,fg_bg=style.label} + local core_temp = DataIndicator{parent=parent,x=21,label="",format="%9.2f",value=0,unit="K",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + r_ps.subscribe("temp", core_temp.update) + parent.line_break() + + TextBox{parent=parent,x=21,text="Burn Rate",height=1,width=12,fg_bg=style.label} + local act_burn_r = DataIndicator{parent=parent,x=21,label="",format="%6.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + r_ps.subscribe("act_burn_rate", act_burn_r.update) + parent.line_break() + + TextBox{parent=parent,x=21,text="Commanded Burn Rate",height=2,width=12,fg_bg=style.label} + local burn_r = DataIndicator{parent=parent,x=21,label="",format="%6.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + r_ps.subscribe("burn_rate", burn_r.update) + parent.line_break() + + TextBox{parent=parent,x=21,text="Heating Rate",height=1,width=12,fg_bg=style.label} + local heating_r = DataIndicator{parent=parent,x=21,label="",format="%11.0f",value=0,unit="",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + r_ps.subscribe("heating_rate", heating_r.update) + parent.line_break() + + TextBox{parent=parent,x=21,text="Containment Integrity",height=2,width=12,fg_bg=style.label} + local integ = DataIndicator{parent=parent,x=21,label="",format="%9.0f",value=100,unit="%",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + r_ps.subscribe("damage", function (x) integ.update(1.0 - (x / 100.0)) end) + parent.line_break() + + -- TextBox{parent=main,text="FL",x=21,y=19,height=1,width=2,fg_bg=style.label} + -- TextBox{parent=main,text="WS",x=24,y=19,height=1,width=2,fg_bg=style.label} + -- TextBox{parent=main,text="CL",x=28,y=19,height=1,width=2,fg_bg=style.label} + -- TextBox{parent=main,text="HC",x=31,y=19,height=1,width=2,fg_bg=style.label} + + -- local fuel = VerticalBar{parent=main,x=21,y=12,fg_bg=cpair(colors.black,colors.gray),height=6,width=2} + -- local waste = VerticalBar{parent=main,x=24,y=12,fg_bg=cpair(colors.brown,colors.gray),height=6,width=2} + -- local ccool = VerticalBar{parent=main,x=28,y=12,fg_bg=cpair(colors.lightBlue,colors.gray),height=6,width=2} + -- local hcool = VerticalBar{parent=main,x=31,y=12,fg_bg=cpair(colors.orange,colors.gray),height=6,width=2} + + -- annunciator -- + + local annunciator = Div{parent=parent,x=34,y=3} + + -- annunciator colors per IAEA-TECDOC-812 recommendations + + -- connectivity/basic state + local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(colors.green,colors.red)} + local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=cpair(colors.white,colors.gray)} + local r_active = IndicatorLight{parent=annunciator,label="Active",colors=cpair(colors.green,colors.gray)} + ---@todo auto control as info sent here + local r_auto = IndicatorLight{parent=annunciator,label="Auto Control",colors=cpair(colors.blue,colors.gray)} + + r_ps.subscribe("PLCOnline", plc_online.update) + r_ps.subscribe("PLCHeartbeat", plc_hbeat.update) + r_ps.subscribe("status", r_active.update) + + annunciator.line_break() + + -- annunciator fields + local r_scram = IndicatorLight{parent=annunciator,label="Reactor SCRAM",colors=cpair(colors.red,colors.gray)} + local r_mscrm = IndicatorLight{parent=annunciator,label="Manual Reactor SCRAM",colors=cpair(colors.red,colors.gray)} + local r_rtrip = IndicatorLight{parent=annunciator,label="RCP Trip",colors=cpair(colors.red,colors.gray)} + local r_cflow = IndicatorLight{parent=annunciator,label="RCS Flow Low",colors=cpair(colors.yellow,colors.gray)} + local r_temp = IndicatorLight{parent=annunciator,label="Reactor Temp. High",colors=cpair(colors.red,colors.gray)} + local r_rhdt = IndicatorLight{parent=annunciator,label="Reactor High Delta T",colors=cpair(colors.yellow,colors.gray)} + local r_firl = IndicatorLight{parent=annunciator,label="Fuel Input Rate Low",colors=cpair(colors.yellow,colors.gray)} + local r_wloc = IndicatorLight{parent=annunciator,label="Waste Line Occlusion",colors=cpair(colors.yellow,colors.gray)} + local r_hsrt = IndicatorLight{parent=annunciator,label="High Startup Rate",colors=cpair(colors.yellow,colors.gray)} + + r_ps.subscribe("ReactorSCRAM", r_scram.update) + r_ps.subscribe("ManualReactorSCRAM", r_mscrm.update) + r_ps.subscribe("RCPTrip", r_rtrip.update) + r_ps.subscribe("RCSFlowLow", r_cflow.update) + r_ps.subscribe("ReactorTempHigh", r_temp.update) + r_ps.subscribe("ReactorHighDeltaT", r_rhdt.update) + r_ps.subscribe("FuelInputRateLow", r_firl.update) + r_ps.subscribe("WasteLineOcclusion", r_wloc.update) + r_ps.subscribe("HighStartupRate", r_hsrt.update) + + annunciator.line_break() + + -- RPS + local rps_trp = IndicatorLight{parent=annunciator,label="RPS Trip",colors=cpair(colors.red,colors.gray)} + local rps_dmg = IndicatorLight{parent=annunciator,label="Damage Critical",colors=cpair(colors.yellow,colors.gray)} + local rps_exh = IndicatorLight{parent=annunciator,label="Excess Heated Coolant",colors=cpair(colors.yellow,colors.gray)} + local rps_exw = IndicatorLight{parent=annunciator,label="Excess Waste",colors=cpair(colors.yellow,colors.gray)} + local rps_tmp = IndicatorLight{parent=annunciator,label="High Core Temp",colors=cpair(colors.yellow,colors.gray)} + local rps_nof = IndicatorLight{parent=annunciator,label="No Fuel",colors=cpair(colors.yellow,colors.gray)} + local rps_noc = IndicatorLight{parent=annunciator,label="No Coolant",colors=cpair(colors.yellow,colors.gray)} + local rps_flt = IndicatorLight{parent=annunciator,label="PPM Fault",colors=cpair(colors.yellow,colors.gray)} + local rps_tmo = IndicatorLight{parent=annunciator,label="Timeout",colors=cpair(colors.yellow,colors.gray)} + + r_ps.subscribe("rps_tripped", rps_trp.update) + r_ps.subscribe("dmg_crit", rps_dmg.update) + r_ps.subscribe("ex_hcool", rps_exh.update) + r_ps.subscribe("ex_waste", rps_exw.update) + r_ps.subscribe("high_temp", rps_tmp.update) + r_ps.subscribe("no_fuel", rps_nof.update) + r_ps.subscribe("no_cool", rps_noc.update) + r_ps.subscribe("fault", rps_flt.update) + r_ps.subscribe("timeout", rps_tmo.update) + + annunciator.line_break() + + -- cooling + local c_brm = IndicatorLight{parent=annunciator,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_cfm = IndicatorLight{parent=annunciator,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_sfm = IndicatorLight{parent=annunciator,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_mwrf = IndicatorLight{parent=annunciator,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} + local c_tbnt = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + + r_ps.subscribe("BoilRateMismatch", c_brm.update) + r_ps.subscribe("CoolantFeedMismatch", c_cfm.update) + r_ps.subscribe("SteamFeedMismatch", c_sfm.update) + r_ps.subscribe("MaxWaterReturnFeed", c_mwrf.update) + r_ps.subscribe("TurbineTrip", c_tbnt.update) + + annunciator.line_break() + + -- machine-specific indicators + if unit.num_boilers > 0 then + TextBox{parent=parent,x=32,y=34,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local b1_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} + b_ps[1].subscribe("HeatingRateLow", b1_hr.update) + end + if unit.num_boilers > 1 then + TextBox{parent=parent,x=32,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local b2_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} + b_ps[2].subscribe("HeatingRateLow", b2_hr.update) + end + + if unit.num_boilers > 0 then + parent.line_break() + annunciator.line_break() + end + + TextBox{parent=parent,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t1_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} + t_ps[1].subscribe("SteamDumpOpen", t1_sdo.update) + + TextBox{parent=parent,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t1_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} + t_ps[1].subscribe("TurbineOverSpeed", t1_tos.update) + + TextBox{parent=parent,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t1_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + t_ps[1].subscribe("TurbineTrip", t1_trp.update) + + parent.line_break() + annunciator.line_break() + + if unit.num_turbines > 1 then + TextBox{parent=parent,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t2_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} + t_ps[2].subscribe("SteamDumpOpen", t2_sdo.update) + + TextBox{parent=parent,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t2_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} + t_ps[2].subscribe("TurbineOverSpeed", t2_tos.update) + + TextBox{parent=parent,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t2_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + t_ps[2].subscribe("TurbineTrip", t2_trp.update) + + parent.line_break() + annunciator.line_break() + end + + if unit.num_turbines > 2 then + TextBox{parent=parent,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t3_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} + t_ps[3].subscribe("SteamDumpOpen", t3_sdo.update) + + TextBox{parent=parent,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t3_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} + t_ps[3].subscribe("TurbineOverSpeed", t3_tos.update) + + TextBox{parent=parent,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t3_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + t_ps[3].subscribe("TurbineTrip", t3_trp.update) + + annunciator.line_break() + end + + ---@todo radiation monitor + IndicatorLight{parent=annunciator,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)} + IndicatorLight{parent=annunciator,label="Radiation Alarm",colors=cpair(colors.red,colors.gray)} + DataIndicator{parent=parent,x=34,y=51,label="",format="%10.1f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=18,fg_bg=stat_fg_bg} + + -- reactor controls -- + + StartButton{parent=parent,x=12,y=44,callback=unit.start,fg_bg=scram_fg_bg} + SCRAMButton{parent=parent,x=22,y=44,callback=unit.scram,fg_bg=scram_fg_bg} + + local burn_control = Div{parent=parent,x=12,y=40,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} + local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} + TextBox{parent=burn_control,x=9,y=2,text="mB/t"} + local set_burn = function () unit.set_burn(burn_rate.get_value()) end + PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} + + local opts = { + { + text = "Auto", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.white, colors.gray) + }, + { + text = "Pu", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.black, colors.lime) + }, + { + text = "Po", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.black, colors.cyan) + }, + { + text = "AM", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.black, colors.purple) + } + } + + ---@todo waste selection + local waste_sel_f = function (s) print("waste: " .. s) end + local waste_sel = Div{parent=parent,x=2,y=48,width=29,height=2,fg_bg=cpair(colors.black, colors.white)} + + MultiButton{parent=waste_sel,x=1,y=1,options=opts,callback=waste_sel_f,min_width=6,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=waste_sel,text="Waste Processing",alignment=TEXT_ALIGN.CENTER,x=1,y=1,height=1} + + ---@fixme test code + parent.line_break() + ColorMap{parent=parent,x=2,y=51} + + ---@fixme test code + local rps = true + local function _test_toggle() + rps_trp.update(rps) + rps = not rps + tcallbackdsp.dispatch(0.25, _test_toggle) + end + + ---@fixme test code + tcallbackdsp.dispatch(0.25, _test_toggle) + + return parent +end + +return init diff --git a/coordinator/ui/components/unit_waiting.lua b/coordinator/ui/components/unit_waiting.lua new file mode 100644 index 0000000..f7fd7ec --- /dev/null +++ b/coordinator/ui/components/unit_waiting.lua @@ -0,0 +1,33 @@ +-- +-- Reactor Unit SCADA Coordinator GUI +-- + +local style = require("coordinator.ui.style") + +local core = require("graphics.core") + +local Div = require("graphics.elements.div") +local TextBox = require("graphics.elements.textbox") + +local WaitingAnim = require("graphics.elements.animations.waiting") + +local TEXT_ALIGN = core.graphics.TEXT_ALIGN + +local cpair = core.graphics.cpair + +-- create a unit waiting view +---@param parent graphics_element parent +---@param y integer y offset +local function init(parent, y) + -- bounding box div + local root = Div{parent=parent,x=1,y=y,height=5} + + local waiting_x = math.floor(parent.width() / 2) - 2 + + TextBox{parent=root,text="Waiting for status...",alignment=TEXT_ALIGN.CENTER,y=1,height=1,fg_bg=cpair(colors.black,style.root.bkg)} + WaitingAnim{parent=root,x=waiting_x,y=3,fg_bg=cpair(colors.blue,style.root.bkg)} + + return root +end + +return init diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index 52fcbce..c2ae072 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -2,34 +2,18 @@ -- Reactor Unit SCADA Coordinator GUI -- -local tcallbackdsp = require("scada-common.tcallbackdsp") +local tcallbackdsp = require("scada-common.tcallbackdsp") -local iocontrol = require("coordinator.iocontrol") +local iocontrol = require("coordinator.iocontrol") -local style = require("coordinator.ui.style") +local style = require("coordinator.ui.style") -local core = require("graphics.core") +local unit_wait = require("coordinator.ui.components.unit_waiting") +local unit_detail = require("coordinator.ui.components.unit_detail") -local DisplayBox = require("graphics.elements.displaybox") -local Div = require("graphics.elements.div") -local TextBox = require("graphics.elements.textbox") -local ColorMap = require("graphics.elements.colormap") +local core = require("graphics.core") -local CoreMap = require("graphics.elements.indicators.coremap") -local DataIndicator = require("graphics.elements.indicators.data") -local HorizontalBar = require("graphics.elements.indicators.hbar") -local IndicatorLight = require("graphics.elements.indicators.light") -local StateIndicator = require("graphics.elements.indicators.state") -local TriIndicatorLight = require("graphics.elements.indicators.trilight") -local VerticalBar = require("graphics.elements.indicators.vbar") - -local MultiButton = require("graphics.elements.controls.multi_button") -local PushButton = require("graphics.elements.controls.push_button") -local SCRAMButton = require("graphics.elements.controls.scram_button") -local StartButton = require("graphics.elements.controls.start_button") -local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") - -local TEXT_ALIGN = core.graphics.TEXT_ALIGN +local DisplayBox = require("graphics.elements.displaybox") local cpair = core.graphics.cpair local border = core.graphics.border @@ -38,266 +22,21 @@ local border = core.graphics.border ---@param monitor table ---@param id integer local function init(monitor, id) - local unit = iocontrol.get_db().units[id] ---@type ioctl_entry - local r_ps = unit.reactor_ps - local b_ps = unit.boiler_ps_tbl - local t_ps = unit.turbine_ps_tbl - local main = DisplayBox{window=monitor,fg_bg=style.root} - TextBox{parent=main,text="Reactor Unit #" .. id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + unit_wait(main, 20) - local scram_fg_bg = cpair(colors.white, colors.gray) - local lu_cpair = cpair(colors.gray, colors.gray) - - -- main stats and core map -- - - ---@todo need to be checking actual reactor dimensions somehow - local core_map = CoreMap{parent=main,x=2,y=3,reactor_l=18,reactor_w=18} - r_ps.subscribe("temp", core_map.update) - - local stat_fg_bg = cpair(colors.black,colors.white) - - TextBox{parent=main,x=21,y=3,text="Core Temp",height=1,fg_bg=style.label} - local core_temp = DataIndicator{parent=main,x=21,label="",format="%9.2f",value=0,unit="K",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} - r_ps.subscribe("temp", core_temp.update) - main.line_break() - - TextBox{parent=main,x=21,text="Burn Rate",height=1,width=12,fg_bg=style.label} - local act_burn_r = DataIndicator{parent=main,x=21,label="",format="%6.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} - r_ps.subscribe("act_burn_rate", act_burn_r.update) - main.line_break() - - TextBox{parent=main,x=21,text="Commanded Burn Rate",height=2,width=12,fg_bg=style.label} - local burn_r = DataIndicator{parent=main,x=21,label="",format="%6.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} - r_ps.subscribe("burn_rate", burn_r.update) - main.line_break() - - TextBox{parent=main,x=21,text="Heating Rate",height=1,width=12,fg_bg=style.label} - local heating_r = DataIndicator{parent=main,x=21,label="",format="%11.0f",value=0,unit="",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} - r_ps.subscribe("heating_rate", heating_r.update) - main.line_break() - - TextBox{parent=main,x=21,text="Containment Integrity",height=2,width=12,fg_bg=style.label} - local integ = DataIndicator{parent=main,x=21,label="",format="%9.0f",value=100,unit="%",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} - r_ps.subscribe("damage", function (x) integ.update(1.0 - (x / 100.0)) end) - main.line_break() - - -- TextBox{parent=main,text="FL",x=21,y=19,height=1,width=2,fg_bg=style.label} - -- TextBox{parent=main,text="WS",x=24,y=19,height=1,width=2,fg_bg=style.label} - -- TextBox{parent=main,text="CL",x=28,y=19,height=1,width=2,fg_bg=style.label} - -- TextBox{parent=main,text="HC",x=31,y=19,height=1,width=2,fg_bg=style.label} - - -- local fuel = VerticalBar{parent=main,x=21,y=12,fg_bg=cpair(colors.black,colors.gray),height=6,width=2} - -- local waste = VerticalBar{parent=main,x=24,y=12,fg_bg=cpair(colors.brown,colors.gray),height=6,width=2} - -- local ccool = VerticalBar{parent=main,x=28,y=12,fg_bg=cpair(colors.lightBlue,colors.gray),height=6,width=2} - -- local hcool = VerticalBar{parent=main,x=31,y=12,fg_bg=cpair(colors.orange,colors.gray),height=6,width=2} - - -- annunciator -- - - local annunciator = Div{parent=main,x=34,y=3} - - -- annunciator colors per IAEA-TECDOC-812 recommendations - - -- connectivity/basic state - local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(colors.green,colors.red)} - local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=cpair(colors.white,colors.gray)} - local r_active = IndicatorLight{parent=annunciator,label="Active",colors=cpair(colors.green,colors.gray)} - ---@todo auto control as info sent here - local r_auto = IndicatorLight{parent=annunciator,label="Auto Control",colors=cpair(colors.blue,colors.gray)} - - r_ps.subscribe("PLCOnline", plc_online.update) - r_ps.subscribe("PLCHeartbeat", plc_hbeat.update) - r_ps.subscribe("status", r_active.update) - - annunciator.line_break() - - -- annunciator fields - local r_scram = IndicatorLight{parent=annunciator,label="Reactor SCRAM",colors=cpair(colors.red,colors.gray)} - local r_mscrm = IndicatorLight{parent=annunciator,label="Manual Reactor SCRAM",colors=cpair(colors.red,colors.gray)} - local r_rtrip = IndicatorLight{parent=annunciator,label="RCP Trip",colors=cpair(colors.red,colors.gray)} - local r_cflow = IndicatorLight{parent=annunciator,label="RCS Flow Low",colors=cpair(colors.yellow,colors.gray)} - local r_temp = IndicatorLight{parent=annunciator,label="Reactor Temp. High",colors=cpair(colors.red,colors.gray)} - local r_rhdt = IndicatorLight{parent=annunciator,label="Reactor High Delta T",colors=cpair(colors.yellow,colors.gray)} - local r_firl = IndicatorLight{parent=annunciator,label="Fuel Input Rate Low",colors=cpair(colors.yellow,colors.gray)} - local r_wloc = IndicatorLight{parent=annunciator,label="Waste Line Occlusion",colors=cpair(colors.yellow,colors.gray)} - local r_hsrt = IndicatorLight{parent=annunciator,label="High Startup Rate",colors=cpair(colors.yellow,colors.gray)} - - r_ps.subscribe("ReactorSCRAM", r_scram.update) - r_ps.subscribe("ManualReactorSCRAM", r_mscrm.update) - r_ps.subscribe("RCPTrip", r_rtrip.update) - r_ps.subscribe("RCSFlowLow", r_cflow.update) - r_ps.subscribe("ReactorTempHigh", r_temp.update) - r_ps.subscribe("ReactorHighDeltaT", r_rhdt.update) - r_ps.subscribe("FuelInputRateLow", r_firl.update) - r_ps.subscribe("WasteLineOcclusion", r_wloc.update) - r_ps.subscribe("HighStartupRate", r_hsrt.update) - - annunciator.line_break() - - -- RPS - local rps_trp = IndicatorLight{parent=annunciator,label="RPS Trip",colors=cpair(colors.red,colors.gray)} - local rps_dmg = IndicatorLight{parent=annunciator,label="Damage Critical",colors=cpair(colors.yellow,colors.gray)} - local rps_exh = IndicatorLight{parent=annunciator,label="Excess Heated Coolant",colors=cpair(colors.yellow,colors.gray)} - local rps_exw = IndicatorLight{parent=annunciator,label="Excess Waste",colors=cpair(colors.yellow,colors.gray)} - local rps_tmp = IndicatorLight{parent=annunciator,label="High Core Temp",colors=cpair(colors.yellow,colors.gray)} - local rps_nof = IndicatorLight{parent=annunciator,label="No Fuel",colors=cpair(colors.yellow,colors.gray)} - local rps_noc = IndicatorLight{parent=annunciator,label="No Coolant",colors=cpair(colors.yellow,colors.gray)} - local rps_flt = IndicatorLight{parent=annunciator,label="PPM Fault",colors=cpair(colors.yellow,colors.gray)} - local rps_tmo = IndicatorLight{parent=annunciator,label="Timeout",colors=cpair(colors.yellow,colors.gray)} - - r_ps.subscribe("rps_tripped", rps_trp.update) - r_ps.subscribe("dmg_crit", rps_dmg.update) - r_ps.subscribe("ex_hcool", rps_exh.update) - r_ps.subscribe("ex_waste", rps_exw.update) - r_ps.subscribe("high_temp", rps_tmp.update) - r_ps.subscribe("no_fuel", rps_nof.update) - r_ps.subscribe("no_cool", rps_noc.update) - r_ps.subscribe("fault", rps_flt.update) - r_ps.subscribe("timeout", rps_tmo.update) - - annunciator.line_break() - - -- cooling - local c_brm = IndicatorLight{parent=annunciator,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)} - local c_cfm = IndicatorLight{parent=annunciator,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} - local c_sfm = IndicatorLight{parent=annunciator,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} - local c_mwrf = IndicatorLight{parent=annunciator,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} - local c_tbnt = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} - - r_ps.subscribe("BoilRateMismatch", c_brm.update) - r_ps.subscribe("CoolantFeedMismatch", c_cfm.update) - r_ps.subscribe("SteamFeedMismatch", c_sfm.update) - r_ps.subscribe("MaxWaterReturnFeed", c_mwrf.update) - r_ps.subscribe("TurbineTrip", c_tbnt.update) - - annunciator.line_break() - - -- machine-specific indicators - if unit.num_boilers > 0 then - TextBox{parent=main,x=32,y=34,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local b1_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} - b_ps[1].subscribe("HeatingRateLow", b1_hr.update) - end - if unit.num_boilers > 1 then - TextBox{parent=main,x=32,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local b2_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} - b_ps[2].subscribe("HeatingRateLow", b2_hr.update) + -- block waiting for initial status + local function show_view() + local unit = iocontrol.get_db().units[id] ---@type ioctl_entry + if unit.reactor_data.last_status_update ~= nil then + unit_detail(main, id) + else + tcallbackdsp.dispatch(1, show_view) + end end - if unit.num_boilers > 0 then - main.line_break() - annunciator.line_break() - end - - TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t1_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} - t_ps[1].subscribe("SteamDumpOpen", t1_sdo.update) - - TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t1_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} - t_ps[1].subscribe("TurbineOverSpeed", t1_tos.update) - - TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t1_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} - t_ps[1].subscribe("TurbineTrip", t1_trp.update) - - main.line_break() - annunciator.line_break() - - if unit.num_turbines > 1 then - TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t2_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} - t_ps[2].subscribe("SteamDumpOpen", t2_sdo.update) - - TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t2_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} - t_ps[2].subscribe("TurbineOverSpeed", t2_tos.update) - - TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t2_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} - t_ps[2].subscribe("TurbineTrip", t2_trp.update) - - main.line_break() - annunciator.line_break() - end - - if unit.num_turbines > 2 then - TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t3_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} - t_ps[3].subscribe("SteamDumpOpen", t3_sdo.update) - - TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t3_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} - t_ps[3].subscribe("TurbineOverSpeed", t3_tos.update) - - TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t3_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} - t_ps[3].subscribe("TurbineTrip", t3_trp.update) - - annunciator.line_break() - end - - ---@todo radiation monitor - IndicatorLight{parent=annunciator,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)} - IndicatorLight{parent=annunciator,label="Radiation Alarm",colors=cpair(colors.red,colors.gray)} - DataIndicator{parent=main,x=34,y=51,label="",format="%10.1f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=18,fg_bg=stat_fg_bg} - - -- reactor controls -- - - StartButton{parent=main,x=12,y=44,callback=unit.start,fg_bg=scram_fg_bg} - SCRAMButton{parent=main,x=22,y=44,callback=unit.scram,fg_bg=scram_fg_bg} - - local burn_control = Div{parent=main,x=12,y=40,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} - local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} - TextBox{parent=burn_control,x=9,y=2,text="mB/t"} - local set_burn = function () unit.set_burn(burn_rate.get_value()) end - PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} - - local opts = { - { - text = "Auto", - fg_bg = cpair(colors.black, colors.lightGray), - active_fg_bg = cpair(colors.white, colors.gray) - }, - { - text = "Pu", - fg_bg = cpair(colors.black, colors.lightGray), - active_fg_bg = cpair(colors.black, colors.lime) - }, - { - text = "Po", - fg_bg = cpair(colors.black, colors.lightGray), - active_fg_bg = cpair(colors.black, colors.cyan) - }, - { - text = "AM", - fg_bg = cpair(colors.black, colors.lightGray), - active_fg_bg = cpair(colors.black, colors.purple) - } - } - - ---@todo waste selection - local waste_sel_f = function (s) print("waste: " .. s) end - local waste_sel = Div{parent=main,x=2,y=48,width=29,height=2,fg_bg=cpair(colors.black, colors.white)} - - MultiButton{parent=waste_sel,x=1,y=1,options=opts,callback=waste_sel_f,min_width=6,fg_bg=cpair(colors.black, colors.white)} - TextBox{parent=waste_sel,text="Waste Processing",alignment=TEXT_ALIGN.CENTER,x=1,y=1,height=1} - - ---@fixme test code - main.line_break() - ColorMap{parent=main,x=2,y=51} - - ---@fixme test code - local rps = true - local function _test_toggle() - rps_trp.update(rps) - rps = not rps - tcallbackdsp.dispatch(0.25, _test_toggle) - end - - ---@fixme test code - tcallbackdsp.dispatch(0.25, _test_toggle) + tcallbackdsp.dispatch(1, show_view) return main end diff --git a/graphics/elements/animations/waiting.lua b/graphics/elements/animations/waiting.lua new file mode 100644 index 0000000..938d43b --- /dev/null +++ b/graphics/elements/animations/waiting.lua @@ -0,0 +1,94 @@ +-- Loading/Waiting Animation Graphics Element + +local tcd = require("scada-common.tcallbackdsp") + +local element = require("graphics.element") + +---@class waiting_args +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field fg_bg? cpair foreground/background colors + +-- new waiting animation element +---@param args waiting_args +---@return graphics_element element, element_id id +local function waiting(args) + local state = 0 + + args.width = 4 + args.height = 3 + + -- create new graphics element base object + local e = element.new(args) + + local blit_fg = e.fg_bg.blit_fgd + local blit_bg = e.fg_bg.blit_bkg + local blit_fg_2x = e.fg_bg.blit_fgd .. e.fg_bg.blit_fgd + local blit_bg_2x = e.fg_bg.blit_bkg .. e.fg_bg.blit_bkg + + local function update() + print("updated waiting") + e.window.clear() + + if state >= 0 and state < 7 then + -- top + e.window.setCursorPos(1 + math.floor(state / 2), 1) + if state % 2 == 0 then + e.window.blit("\x8f", blit_fg, blit_bg) + else + e.window.blit("\x8a\x85", blit_fg_2x, blit_bg_2x) + end + + -- bottom + e.window.setCursorPos(4 - math.ceil(state / 2), 3) + if state % 2 == 0 then + e.window.blit("\x8f", blit_fg, blit_bg) + else + e.window.blit("\x8a\x85", blit_fg_2x, blit_bg_2x) + end + else + local st = state - 7 + + -- right + if st % 3 == 0 then + e.window.setCursorPos(4, 1 + math.floor(st / 3)) + e.window.blit("\x83", blit_bg, blit_fg) + elseif st % 3 == 1 then + e.window.setCursorPos(4, 1 + math.floor(st / 3)) + e.window.blit("\x8f", blit_bg, blit_fg) + e.window.setCursorPos(4, 2 + math.floor(st / 3)) + e.window.blit("\x83", blit_fg, blit_bg) + else + e.window.setCursorPos(4, 2 + math.floor(st / 3)) + e.window.blit("\x8f", blit_fg, blit_bg) + end + + -- left + if st % 3 == 0 then + e.window.setCursorPos(1, 3 - math.floor(st / 3)) + e.window.blit("\x83", blit_fg, blit_bg) + e.window.setCursorPos(1, 2 - math.floor(st / 3)) + e.window.blit("\x8f", blit_bg, blit_fg) + elseif st % 3 == 1 then + e.window.setCursorPos(1, 2 - math.floor(st / 3)) + e.window.blit("\x83", blit_bg, blit_fg) + else + e.window.setCursorPos(1, 2 - math.floor(st / 3)) + e.window.blit("\x8f", blit_fg, blit_bg) + end + end + + state = state + 1 + if state >= 12 then state = 0 end + + tcd.dispatch(0.5, update) + end + + update() + + return e.get() +end + +return waiting diff --git a/scada-common/tcallbackdsp.lua b/scada-common/tcallbackdsp.lua index 5cb24e3..65b8ec9 100644 --- a/scada-common/tcallbackdsp.lua +++ b/scada-common/tcallbackdsp.lua @@ -2,9 +2,6 @@ -- Timer Callback Dispatcher -- -local log = require("scada-common.log") -local util = require("scada-common.util") - local tcallbackdsp = {} local registry = {} @@ -13,7 +10,6 @@ local registry = {} ---@param time number seconds ---@param f function callback function function tcallbackdsp.dispatch(time, f) - log.debug(util.c("TCD: dispatching ", f, " for call in ", time, " seconds")) ---@diagnostic disable-next-line: undefined-field registry[os.startTimer(time)] = { callback = f } end @@ -22,7 +18,6 @@ end ---@param event integer timer event timer ID function tcallbackdsp.handle(event) if registry[event] ~= nil then - log.debug(util.c("TCD: executing callback ", registry[event].callback, " for timer ", event)) registry[event].callback() registry[event] = nil end From dcf275784c0a68a92c2db454f2bb230b9b479950 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 10 Sep 2022 10:43:48 -0400 Subject: [PATCH 380/587] removed debug print --- graphics/elements/animations/waiting.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/graphics/elements/animations/waiting.lua b/graphics/elements/animations/waiting.lua index 938d43b..031b0da 100644 --- a/graphics/elements/animations/waiting.lua +++ b/graphics/elements/animations/waiting.lua @@ -29,7 +29,6 @@ local function waiting(args) local blit_bg_2x = e.fg_bg.blit_bkg .. e.fg_bg.blit_bkg local function update() - print("updated waiting") e.window.clear() if state >= 0 and state < 7 then From 98c826e762b13c862d58e05442232542ac9d34bf Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 10 Sep 2022 15:14:48 -0400 Subject: [PATCH 381/587] start/stop animations with show/hide and pass show/hide down children --- graphics/element.lua | 18 ++++++++++++++++++ graphics/elements/animations/waiting.lua | 21 ++++++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/graphics/element.lua b/graphics/element.lua index bad2a80..6ed3434 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -141,6 +141,14 @@ function element.new(args) return nil end + -- start animations + function protected.start_anim() + end + + -- stop animations + function protected.stop_anim() + end + -- get public interface ---@return graphics_element element, element_id id function protected.get() return public, self.id end @@ -272,10 +280,20 @@ function element.new(args) -- show the element function public.show() protected.window.setVisible(true) + protected.start_anim() + + for i = 1, #self.children do + self.children[i].show() + end end -- hide the element function public.hide() + protected.stop_anim() + for i = 1, #self.children do + self.children[i].hide() + end + protected.window.setVisible(false) end diff --git a/graphics/elements/animations/waiting.lua b/graphics/elements/animations/waiting.lua index 031b0da..5c03d09 100644 --- a/graphics/elements/animations/waiting.lua +++ b/graphics/elements/animations/waiting.lua @@ -16,6 +16,7 @@ local element = require("graphics.element") ---@return graphics_element element, element_id id local function waiting(args) local state = 0 + local run_animation = false args.width = 4 args.height = 3 @@ -28,7 +29,8 @@ local function waiting(args) local blit_fg_2x = e.fg_bg.blit_fgd .. e.fg_bg.blit_fgd local blit_bg_2x = e.fg_bg.blit_bkg .. e.fg_bg.blit_bkg - local function update() + -- tick the animation + local function animate() e.window.clear() if state >= 0 and state < 7 then @@ -82,10 +84,23 @@ local function waiting(args) state = state + 1 if state >= 12 then state = 0 end - tcd.dispatch(0.5, update) + if run_animation then + tcd.dispatch(0.5, animate) + end end - update() + -- start the animation + function e.start_anim() + run_animation = true + animate() + end + + -- stop the animation + function e.stop_anim() + run_animation = false + end + + e.start_anim() return e.get() end From 4275c9d4088a684108d60dee9baaeeb789cefbc5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 10 Sep 2022 15:15:24 -0400 Subject: [PATCH 382/587] unit detail view in div and hide waiting indicator on connect --- coordinator/ui/components/unit_detail.lua | 82 ++++++++++++----------- coordinator/ui/layout/unit_view.lua | 3 +- 2 files changed, 44 insertions(+), 41 deletions(-) diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 1572880..16ae5ca 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -38,7 +38,9 @@ local function init(parent, id) local b_ps = unit.boiler_ps_tbl local t_ps = unit.turbine_ps_tbl - TextBox{parent=parent,text="Reactor Unit #" .. id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + local main = Div{parent=parent,x=1,y=1} + + TextBox{parent=main,text="Reactor Unit #" .. id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} local scram_fg_bg = cpair(colors.white, colors.gray) local lu_cpair = cpair(colors.gray, colors.gray) @@ -46,35 +48,35 @@ local function init(parent, id) -- main stats and core map -- ---@todo need to be checking actual reactor dimensions somehow - local core_map = CoreMap{parent=parent,x=2,y=3,reactor_l=18,reactor_w=18} + local core_map = CoreMap{parent=main,x=2,y=3,reactor_l=18,reactor_w=18} r_ps.subscribe("temp", core_map.update) local stat_fg_bg = cpair(colors.black,colors.white) - TextBox{parent=parent,x=21,y=3,text="Core Temp",height=1,fg_bg=style.label} - local core_temp = DataIndicator{parent=parent,x=21,label="",format="%9.2f",value=0,unit="K",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + TextBox{parent=main,x=21,y=3,text="Core Temp",height=1,fg_bg=style.label} + local core_temp = DataIndicator{parent=main,x=21,label="",format="%9.2f",value=0,unit="K",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} r_ps.subscribe("temp", core_temp.update) - parent.line_break() + main.line_break() - TextBox{parent=parent,x=21,text="Burn Rate",height=1,width=12,fg_bg=style.label} - local act_burn_r = DataIndicator{parent=parent,x=21,label="",format="%6.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + TextBox{parent=main,x=21,text="Burn Rate",height=1,width=12,fg_bg=style.label} + local act_burn_r = DataIndicator{parent=main,x=21,label="",format="%6.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} r_ps.subscribe("act_burn_rate", act_burn_r.update) - parent.line_break() + main.line_break() - TextBox{parent=parent,x=21,text="Commanded Burn Rate",height=2,width=12,fg_bg=style.label} - local burn_r = DataIndicator{parent=parent,x=21,label="",format="%6.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + TextBox{parent=main,x=21,text="Commanded Burn Rate",height=2,width=12,fg_bg=style.label} + local burn_r = DataIndicator{parent=main,x=21,label="",format="%6.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} r_ps.subscribe("burn_rate", burn_r.update) - parent.line_break() + main.line_break() - TextBox{parent=parent,x=21,text="Heating Rate",height=1,width=12,fg_bg=style.label} - local heating_r = DataIndicator{parent=parent,x=21,label="",format="%11.0f",value=0,unit="",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + TextBox{parent=main,x=21,text="Heating Rate",height=1,width=12,fg_bg=style.label} + local heating_r = DataIndicator{parent=main,x=21,label="",format="%11.0f",value=0,unit="",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} r_ps.subscribe("heating_rate", heating_r.update) - parent.line_break() + main.line_break() - TextBox{parent=parent,x=21,text="Containment Integrity",height=2,width=12,fg_bg=style.label} - local integ = DataIndicator{parent=parent,x=21,label="",format="%9.0f",value=100,unit="%",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + TextBox{parent=main,x=21,text="Containment Integrity",height=2,width=12,fg_bg=style.label} + local integ = DataIndicator{parent=main,x=21,label="",format="%9.0f",value=100,unit="%",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} r_ps.subscribe("damage", function (x) integ.update(1.0 - (x / 100.0)) end) - parent.line_break() + main.line_break() -- TextBox{parent=main,text="FL",x=21,y=19,height=1,width=2,fg_bg=style.label} -- TextBox{parent=main,text="WS",x=24,y=19,height=1,width=2,fg_bg=style.label} @@ -88,7 +90,7 @@ local function init(parent, id) -- annunciator -- - local annunciator = Div{parent=parent,x=34,y=3} + local annunciator = Div{parent=main,x=34,y=3} -- annunciator colors per IAEA-TECDOC-812 recommendations @@ -168,63 +170,63 @@ local function init(parent, id) -- machine-specific indicators if unit.num_boilers > 0 then - TextBox{parent=parent,x=32,y=34,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=main,x=32,y=34,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local b1_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} b_ps[1].subscribe("HeatingRateLow", b1_hr.update) end if unit.num_boilers > 1 then - TextBox{parent=parent,x=32,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=main,x=32,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local b2_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} b_ps[2].subscribe("HeatingRateLow", b2_hr.update) end if unit.num_boilers > 0 then - parent.line_break() + main.line_break() annunciator.line_break() end - TextBox{parent=parent,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local t1_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} t_ps[1].subscribe("SteamDumpOpen", t1_sdo.update) - TextBox{parent=parent,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local t1_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} t_ps[1].subscribe("TurbineOverSpeed", t1_tos.update) - TextBox{parent=parent,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local t1_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} t_ps[1].subscribe("TurbineTrip", t1_trp.update) - parent.line_break() + main.line_break() annunciator.line_break() if unit.num_turbines > 1 then - TextBox{parent=parent,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local t2_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} t_ps[2].subscribe("SteamDumpOpen", t2_sdo.update) - TextBox{parent=parent,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local t2_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} t_ps[2].subscribe("TurbineOverSpeed", t2_tos.update) - TextBox{parent=parent,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local t2_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} t_ps[2].subscribe("TurbineTrip", t2_trp.update) - parent.line_break() + main.line_break() annunciator.line_break() end if unit.num_turbines > 2 then - TextBox{parent=parent,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local t3_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} t_ps[3].subscribe("SteamDumpOpen", t3_sdo.update) - TextBox{parent=parent,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local t3_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} t_ps[3].subscribe("TurbineOverSpeed", t3_tos.update) - TextBox{parent=parent,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local t3_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} t_ps[3].subscribe("TurbineTrip", t3_trp.update) @@ -234,14 +236,14 @@ local function init(parent, id) ---@todo radiation monitor IndicatorLight{parent=annunciator,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)} IndicatorLight{parent=annunciator,label="Radiation Alarm",colors=cpair(colors.red,colors.gray)} - DataIndicator{parent=parent,x=34,y=51,label="",format="%10.1f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=18,fg_bg=stat_fg_bg} + DataIndicator{parent=main,x=34,y=51,label="",format="%10.1f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=18,fg_bg=stat_fg_bg} -- reactor controls -- - StartButton{parent=parent,x=12,y=44,callback=unit.start,fg_bg=scram_fg_bg} - SCRAMButton{parent=parent,x=22,y=44,callback=unit.scram,fg_bg=scram_fg_bg} + StartButton{parent=main,x=12,y=44,callback=unit.start,fg_bg=scram_fg_bg} + SCRAMButton{parent=main,x=22,y=44,callback=unit.scram,fg_bg=scram_fg_bg} - local burn_control = Div{parent=parent,x=12,y=40,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} + local burn_control = Div{parent=main,x=12,y=40,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} TextBox{parent=burn_control,x=9,y=2,text="mB/t"} local set_burn = function () unit.set_burn(burn_rate.get_value()) end @@ -272,14 +274,14 @@ local function init(parent, id) ---@todo waste selection local waste_sel_f = function (s) print("waste: " .. s) end - local waste_sel = Div{parent=parent,x=2,y=48,width=29,height=2,fg_bg=cpair(colors.black, colors.white)} + local waste_sel = Div{parent=main,x=2,y=48,width=29,height=2,fg_bg=cpair(colors.black, colors.white)} MultiButton{parent=waste_sel,x=1,y=1,options=opts,callback=waste_sel_f,min_width=6,fg_bg=cpair(colors.black, colors.white)} TextBox{parent=waste_sel,text="Waste Processing",alignment=TEXT_ALIGN.CENTER,x=1,y=1,height=1} ---@fixme test code - parent.line_break() - ColorMap{parent=parent,x=2,y=51} + main.line_break() + ColorMap{parent=main,x=2,y=51} ---@fixme test code local rps = true @@ -292,7 +294,7 @@ local function init(parent, id) ---@fixme test code tcallbackdsp.dispatch(0.25, _test_toggle) - return parent + return main end return init diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index c2ae072..4584fb5 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -24,12 +24,13 @@ local border = core.graphics.border local function init(monitor, id) local main = DisplayBox{window=monitor,fg_bg=style.root} - unit_wait(main, 20) + local waiting = unit_wait(main, 20) -- block waiting for initial status local function show_view() local unit = iocontrol.get_db().units[id] ---@type ioctl_entry if unit.reactor_data.last_status_update ~= nil then + waiting.hide() unit_detail(main, id) else tcallbackdsp.dispatch(1, show_view) From c14fc048a1b515684d071a6e9432f26a7150245b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 10 Sep 2022 15:26:52 -0400 Subject: [PATCH 383/587] #88 not going to actually hold UI since that hides the PLC offline state and other offline indicators, instead should expose property update capability --- coordinator/ui/layout/unit_view.lua | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index 4584fb5..bdf0537 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -8,36 +8,17 @@ local iocontrol = require("coordinator.iocontrol") local style = require("coordinator.ui.style") -local unit_wait = require("coordinator.ui.components.unit_waiting") local unit_detail = require("coordinator.ui.components.unit_detail") -local core = require("graphics.core") - local DisplayBox = require("graphics.elements.displaybox") -local cpair = core.graphics.cpair -local border = core.graphics.border - -- create a unit view ---@param monitor table ---@param id integer local function init(monitor, id) local main = DisplayBox{window=monitor,fg_bg=style.root} - local waiting = unit_wait(main, 20) - - -- block waiting for initial status - local function show_view() - local unit = iocontrol.get_db().units[id] ---@type ioctl_entry - if unit.reactor_data.last_status_update ~= nil then - waiting.hide() - unit_detail(main, id) - else - tcallbackdsp.dispatch(1, show_view) - end - end - - tcallbackdsp.dispatch(1, show_view) + unit_detail(main, id) return main end From d9be5ccb47cda1518c5bddde5e133a66940f3098 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 10 Sep 2022 22:08:29 -0400 Subject: [PATCH 384/587] #89 fixed up ui closing to be cleaner on restart --- coordinator/renderer.lua | 13 ++++++++----- coordinator/startup.lua | 6 +++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 11ffa1e..f76c0b3 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -92,18 +92,21 @@ function renderer.start_ui() end -- close out the UI ----@param recolor? boolean true to restore to color palette from style -function renderer.close_ui(recolor) +function renderer.close_ui() -- report ui as not ready engine.ui_ready = false + -- hide to stop animation callbacks + ui.main_layout.hide() + for i = 1, #ui.unit_layouts do + ui.unit_layouts[i].hide() + engine.monitors.unit_displays[i].clear() + end + -- clear root UI elements ui.main_layout = nil ui.unit_layouts = {} - -- reset displays - renderer.reset(recolor) - -- re-draw dmesg engine.dmesg_window.setVisible(true) engine.dmesg_window.redraw() diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 6c26267..4df19bf 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.4.8" +local COORDINATOR_VERSION = "alpha-v0.4.9" local print = util.print local println = util.println @@ -138,7 +138,7 @@ local function init_start_ui() local ui_ok, message = pcall(renderer.start_ui) if not ui_ok then - renderer.close_ui(config.RECOLOR) + renderer.close_ui() log_graphics(util.c("UI crashed: ", message)) println_ts("UI crashed") log.fatal(util.c("ui crashed with error ", message)) @@ -275,7 +275,7 @@ while ui_ok do end end -renderer.close_ui(config.RECOLOR) +renderer.close_ui() log_sys("system shutdown") println_ts("exited") From 10c53ac4b30846ecf4ebd9efdb3cd436a46426c2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 12 Sep 2022 12:59:28 -0400 Subject: [PATCH 385/587] #91 get and set values for all controls/indicators and textbox --- graphics/element.lua | 19 ++++- graphics/elements/controls/multi_button.lua | 27 ++++--- graphics/elements/controls/push_button.lua | 9 +++ graphics/elements/controls/scram_button.lua | 7 +- .../elements/controls/spinbox_numeric.lua | 79 +++++++++++++------ graphics/elements/controls/start_button.lua | 7 +- graphics/elements/controls/switch_button.lua | 23 ++++-- graphics/elements/indicators/coremap.lua | 9 ++- graphics/elements/indicators/data.lua | 4 + graphics/elements/indicators/hbar.lua | 14 ++++ graphics/elements/indicators/icon.lua | 3 + graphics/elements/indicators/light.lua | 3 + graphics/elements/indicators/state.lua | 3 + graphics/elements/indicators/trilight.lua | 3 + graphics/elements/indicators/vbar.lua | 4 + graphics/elements/textbox.lua | 38 ++++++--- 16 files changed, 192 insertions(+), 60 deletions(-) diff --git a/graphics/element.lua b/graphics/element.lua index 6ed3434..d776c7f 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -37,6 +37,7 @@ function element.new(args) ---@class graphics_template local protected = { + value = nil, ---@type any window = nil, ---@type table fg_bg = core.graphics.cpair(colors.white, colors.black), frame = core.graphics.gframe(1, 1, 1, 1) @@ -136,11 +137,27 @@ function element.new(args) function protected.on_update(...) end - -- get control value + -- get value function protected.get_value() + return protected.value + end + + -- set value + ---@param value any value to set + function protected.set_value(value) return nil end + -- custom recolor command, varies by element if implemented + ---@vararg cpair|color color(s) + function protected.recolor(...) + end + + -- custom resize command, varies by element if implemented + ---@vararg integer sizing + function protected.resize(...) + end + -- start animations function protected.start_anim() end diff --git a/graphics/elements/controls/multi_button.lua b/graphics/elements/controls/multi_button.lua index 16ee85e..84aa65b 100644 --- a/graphics/elements/controls/multi_button.lua +++ b/graphics/elements/controls/multi_button.lua @@ -1,6 +1,7 @@ -- Button Graphics Element local element = require("graphics.element") + local util = require("scada-common.util") ---@class button_option @@ -33,9 +34,6 @@ local function multi_button(args) -- single line args.height = 3 - -- button state (convert nil to 1 if missing) - local state = args.default or 1 - -- determine widths local max_width = 1 for i = 1, #args.options do @@ -52,6 +50,9 @@ local function multi_button(args) -- create new graphics element base object local e = element.new(args) + -- button state (convert nil to 1 if missing) + e.value = args.default or 1 + -- calculate required button information local next_x = 2 for i = 1, #args.options do @@ -72,7 +73,7 @@ local function multi_button(args) e.window.setCursorPos(opt._start_x, 2) - if state == i then + if e.value == i then -- show as pressed e.window.setTextColor(opt.active_fg_bg.fgd) e.window.setBackgroundColor(opt.active_fg_bg.bkg) @@ -86,9 +87,6 @@ local function multi_button(args) end end - -- initial draw - draw() - -- handle touch ---@param event monitor_touch monitor touch event function e.handle_touch(event) @@ -98,14 +96,25 @@ local function multi_button(args) local opt = args.options[i] ---@type button_option if event.x >= opt._start_x and event.x <= opt._end_x then - state = i + e.value = i draw() - args.callback(state) + args.callback(e.value) end end end end + -- set the value + ---@param val integer new value + function e.set_value(val) + e.value = val + draw() + args.callback(e.value) + end + + -- initial draw + draw() + return e.get() end diff --git a/graphics/elements/controls/push_button.lua b/graphics/elements/controls/push_button.lua index 227b8e0..d8a1755 100644 --- a/graphics/elements/controls/push_button.lua +++ b/graphics/elements/controls/push_button.lua @@ -2,6 +2,7 @@ local tcd = require("scada-common.tcallbackdsp") +local core = require("graphics.core") local element = require("graphics.element") ---@class push_button_args @@ -52,12 +53,14 @@ local function push_button(args) function e.handle_touch(event) if args.active_fg_bg ~= nil then -- show as pressed + e.value = true e.window.setTextColor(args.active_fg_bg.fgd) e.window.setBackgroundColor(args.active_fg_bg.bkg) draw() -- show as unpressed in 0.25 seconds tcd.dispatch(0.25, function () + e.value = false e.window.setTextColor(e.fg_bg.fgd) e.window.setBackgroundColor(e.fg_bg.bkg) draw() @@ -68,6 +71,12 @@ local function push_button(args) args.callback() end + -- set the value + ---@param val boolean new value + function e.set_value(val) + if val then e.handle_touch(core.events.touch("", 1, 1)) end + end + -- initial draw draw() diff --git a/graphics/elements/controls/scram_button.lua b/graphics/elements/controls/scram_button.lua index bac3045..fc12d67 100644 --- a/graphics/elements/controls/scram_button.lua +++ b/graphics/elements/controls/scram_button.lua @@ -3,7 +3,6 @@ local tcd = require("scada-common.tcallbackdsp") local core = require("graphics.core") - local element = require("graphics.element") ---@class scram_button_args @@ -65,6 +64,12 @@ local function scram_button(args) args.callback() end + -- set the value + ---@param val boolean new value + function e.set_value(val) + if val then e.handle_touch(core.events.touch("", 1, 1)) end + end + return e.get() end diff --git a/graphics/elements/controls/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua index 55b80ce..ac363b2 100644 --- a/graphics/elements/controls/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -1,6 +1,7 @@ -- Spinbox Numeric Graphics Element local element = require("graphics.element") + local util = require("scada-common.util") ---@class spinbox_args @@ -19,7 +20,6 @@ local util = require("scada-common.util") ---@return graphics_element element, element_id id local function spinbox(args) -- properties - local value = args.default or 0.0 local digits = {} local wn_prec = args.whole_num_precision local fr_prec = args.fractional_precision @@ -33,11 +33,6 @@ local function spinbox(args) assert(type(args.arrow_fg_bg) == "table", "graphics.element.spinbox_numeric: arrow_fg_bg is a required field") - local initial_str = util.sprintf(fmt_init, value) - ----@diagnostic disable-next-line: discard-returns - initial_str:gsub("%d", function(char) table.insert(digits, char) end) - -- determine widths args.width = wn_prec + fr_prec + util.trinary(fr_prec > 0, 1, 0) args.height = 3 @@ -45,6 +40,14 @@ local function spinbox(args) -- create new graphics element base object local e = element.new(args) + -- set initial value + e.value = args.default or 0.0 + + local initial_str = util.sprintf(fmt_init, e.value) + +---@diagnostic disable-next-line: discard-returns + initial_str:gsub("%d", function (char) table.insert(digits, char) end) + -- draw the arrows e.window.setBackgroundColor(args.arrow_fg_bg.bkg) e.window.setTextColor(args.arrow_fg_bg.fgd) @@ -62,7 +65,7 @@ local function spinbox(args) -- zero the value local function zero() for i = 1, #digits do digits[i] = 0 end - value = 0 + e.value = 0 end -- print out the current value @@ -70,7 +73,42 @@ local function spinbox(args) e.window.setBackgroundColor(e.fg_bg.bkg) e.window.setTextColor(e.fg_bg.fgd) e.window.setCursorPos(1, 2) - e.window.write(util.sprintf(fmt, value)) + e.window.write(util.sprintf(fmt, e.value)) + end + + -- update the value per digits table + local function update_value() + e.value = 0 + for i = 1, #digits do + local pow = math.abs(wn_prec - i) + if i <= wn_prec then + e.value = e.value + (digits[i] * (10 ^ pow)) + else + e.value = e.value + (digits[i] * (10 ^ -pow)) + end + end + end + + -- enforce numeric limits + local function enforce_limits() + -- min 0 + if e.value < 0 then + zero() + -- max printable + elseif string.len(util.sprintf(fmt, e.value)) > args.width then + -- max out + for i = 1, #digits do digits[i] = 9 end + + -- re-update value + update_value() + end + end + + -- update value and show + local function parse_and_show() + update_value() + enforce_limits() + show_num() end -- init with the default value @@ -90,27 +128,16 @@ local function spinbox(args) digits[idx] = digits[idx] - 1 end - -- update value - value = 0 - for i = 1, #digits do - local pow = math.abs(wn_prec - i) - if i <= wn_prec then - value = value + (digits[i] * (10 ^ pow)) - else - value = value + (digits[i] * (10 ^ -pow)) - end - end - - -- min 0 - if value < 0 then zero() end - - show_num() + parse_and_show() end end - -- get current value - ---@return number|integer - function e.get_value() return value end + -- set the value + ---@param val number number to show + function e.set_value(val) + e.value = val + parse_and_show() + end return e.get() end diff --git a/graphics/elements/controls/start_button.lua b/graphics/elements/controls/start_button.lua index 45aba40..59c8580 100644 --- a/graphics/elements/controls/start_button.lua +++ b/graphics/elements/controls/start_button.lua @@ -3,7 +3,6 @@ local tcd = require("scada-common.tcallbackdsp") local core = require("graphics.core") - local element = require("graphics.element") ---@class start_button_args @@ -65,6 +64,12 @@ local function start_button(args) args.callback() end + -- set the value + ---@param val boolean new value + function e.set_value(val) + if val then e.handle_touch(core.events.touch("", 1, 1)) end + end + return e.get() end diff --git a/graphics/elements/controls/switch_button.lua b/graphics/elements/controls/switch_button.lua index f7a304f..2863747 100644 --- a/graphics/elements/controls/switch_button.lua +++ b/graphics/elements/controls/switch_button.lua @@ -26,9 +26,6 @@ local function switch_button(args) -- single line args.height = 1 - -- button state (convert nil to false if missing) - local state = args.default or false - -- determine widths local text_width = string.len(args.text) args.width = math.max(text_width + 2, args.min_width) @@ -36,12 +33,15 @@ local function switch_button(args) -- create new graphics element base object local e = element.new(args) + -- button state (convert nil to false if missing) + e.value = args.default or false + local h_pad = math.floor((e.frame.w - text_width) / 2) local v_pad = math.floor(e.frame.h / 2) + 1 -- show the button state local function draw_state() - if state then + if e.value then -- show as pressed e.window.setTextColor(args.active_fg_bg.fgd) e.window.setBackgroundColor(args.active_fg_bg.bkg) @@ -64,11 +64,22 @@ local function switch_button(args) ---@diagnostic disable-next-line: unused-local function e.handle_touch(event) -- toggle state - state = not state + e.value = not e.value draw_state() -- call the touch callback with state - args.callback(state) + args.callback(e.value) + end + + -- set the value + ---@param val boolean new value + function e.set_value(val) + -- set state + e.value = val + draw_state() + + -- call the touch callback with state + args.callback(e.value) end return e.get() diff --git a/graphics/elements/indicators/coremap.lua b/graphics/elements/indicators/coremap.lua index 6adb3c9..393575d 100644 --- a/graphics/elements/indicators/coremap.lua +++ b/graphics/elements/indicators/coremap.lua @@ -116,15 +116,18 @@ local function core_map(args) end end - -- initial draw at base temp - draw(300) - -- on state change ---@param temperature number temperature in Kelvin function e.on_update(temperature) + e.value = temperature draw(temperature) end + function e.set_value(val) e.on_update(val) end + + -- initial draw at base temp + e.on_update(300) + return e.get() end diff --git a/graphics/elements/indicators/data.lua b/graphics/elements/indicators/data.lua index 86e248b..64e2a23 100644 --- a/graphics/elements/indicators/data.lua +++ b/graphics/elements/indicators/data.lua @@ -70,6 +70,8 @@ local function data(args) -- on state change ---@param value any new value function e.on_update(value) + e.value = value + local data_str = util.sprintf(args.format, value) -- write data @@ -90,6 +92,8 @@ local function data(args) end end + function e.set_value(val) e.on_update(val) end + -- initial value draw e.on_update(args.value) diff --git a/graphics/elements/indicators/hbar.lua b/graphics/elements/indicators/hbar.lua index f433b91..4fc8532 100644 --- a/graphics/elements/indicators/hbar.lua +++ b/graphics/elements/indicators/hbar.lua @@ -42,6 +42,8 @@ local function hbar(args) -- handle data changes function e.on_update(fraction) + e.value = fraction + -- enforce minimum and maximum if fraction < 0 then fraction = 0.0 @@ -96,6 +98,18 @@ local function hbar(args) end end + ---@param bar_fg_bg cpair new bar colors + function e.recolor(bar_fg_bg) + bar_bkg = bar_fg_bg.blit_bkg + bar_fgd = bar_fg_bg.blit_fgd + + -- re-draw + last_num_bars = 0 + e.on_update(e.value) + end + + function e.set_value(val) e.on_update(val) end + -- initialize to 0 e.on_update(0) diff --git a/graphics/elements/indicators/icon.lua b/graphics/elements/indicators/icon.lua index cdbcf9e..728bfea 100644 --- a/graphics/elements/indicators/icon.lua +++ b/graphics/elements/indicators/icon.lua @@ -55,10 +55,13 @@ local function icon(args) ---@param new_state integer indicator state function e.on_update(new_state) local blit_cmd = state_blit_cmds[new_state] + e.value = new_state e.window.setCursorPos(1, 1) e.window.blit(blit_cmd.text, blit_cmd.fgd, blit_cmd.bkg) end + function e.set_value(val) e.on_update(val) end + -- initial icon draw e.on_update(args.value or 1) diff --git a/graphics/elements/indicators/light.lua b/graphics/elements/indicators/light.lua index 411982d..a1cc85b 100644 --- a/graphics/elements/indicators/light.lua +++ b/graphics/elements/indicators/light.lua @@ -31,6 +31,7 @@ local function indicator_light(args) -- on state change ---@param new_state boolean indicator state function e.on_update(new_state) + e.value = new_state e.window.setCursorPos(1, 1) if new_state then e.window.blit(" \x95", "0" .. args.colors.blit_a, args.colors.blit_a .. e.fg_bg.blit_bkg) @@ -39,6 +40,8 @@ local function indicator_light(args) end end + function e.set_value(val) e.on_update(val) end + -- write label and initial indicator light e.on_update(false) e.window.write(args.label) diff --git a/graphics/elements/indicators/state.lua b/graphics/elements/indicators/state.lua index 5dd39ee..4631a40 100644 --- a/graphics/elements/indicators/state.lua +++ b/graphics/elements/indicators/state.lua @@ -61,10 +61,13 @@ local function state_indicator(args) ---@param new_state integer indicator state function e.on_update(new_state) local blit_cmd = state_blit_cmds[new_state] + e.value = new_state e.window.setCursorPos(1, 1) e.window.blit(blit_cmd.text, blit_cmd.fgd, blit_cmd.bkg) end + function e.set_value(val) e.on_update(val) end + -- initial draw e.on_update(args.value or 1) diff --git a/graphics/elements/indicators/trilight.lua b/graphics/elements/indicators/trilight.lua index 9d8731e..435af84 100644 --- a/graphics/elements/indicators/trilight.lua +++ b/graphics/elements/indicators/trilight.lua @@ -40,6 +40,7 @@ local function tristate_indicator_light(args) -- on state change ---@param new_state integer indicator state function e.on_update(new_state) + e.value = new_state e.window.setCursorPos(1, 1) if new_state == 2 then e.window.blit(" \x95", "0" .. c2, c2 .. e.fg_bg.blit_bkg) @@ -50,6 +51,8 @@ local function tristate_indicator_light(args) end end + function e.set_value(val) e.on_update(val) end + -- write label and initial indicator light e.on_update(0) e.window.write(args.label) diff --git a/graphics/elements/indicators/vbar.lua b/graphics/elements/indicators/vbar.lua index 114fb77..7888033 100644 --- a/graphics/elements/indicators/vbar.lua +++ b/graphics/elements/indicators/vbar.lua @@ -33,6 +33,8 @@ local function vbar(args) -- handle data changes function e.on_update(fraction) + e.value = fraction + -- enforce minimum and maximum if fraction < 0 then fraction = 0.0 @@ -78,6 +80,8 @@ local function vbar(args) end end + function e.set_value(val) e.on_update(val) end + return e.get() end diff --git a/graphics/elements/textbox.lua b/graphics/elements/textbox.lua index 45eb92f..c911677 100644 --- a/graphics/elements/textbox.lua +++ b/graphics/elements/textbox.lua @@ -32,24 +32,36 @@ local function textbox(args) -- draw textbox - local text = args.text - local lines = util.strwrap(text, e.frame.w) + local function display_text(text) + e.value = text - for i = 1, #lines do - if i > e.frame.h then break end + local lines = util.strwrap(text, e.frame.w) - local len = string.len(lines[i]) + for i = 1, #lines do + if i > e.frame.h then break end - -- use cursor position to align this line - if alignment == TEXT_ALIGN.CENTER then - e.window.setCursorPos(math.floor((e.frame.w - len) / 2) + 1, i) - elseif alignment == TEXT_ALIGN.RIGHT then - e.window.setCursorPos((e.frame.w - len) + 1, i) - else - e.window.setCursorPos(1, i) + local len = string.len(lines[i]) + + -- use cursor position to align this line + if alignment == TEXT_ALIGN.CENTER then + e.window.setCursorPos(math.floor((e.frame.w - len) / 2) + 1, i) + elseif alignment == TEXT_ALIGN.RIGHT then + e.window.setCursorPos((e.frame.w - len) + 1, i) + else + e.window.setCursorPos(1, i) + end + + e.window.write(lines[i]) end + end - e.window.write(lines[i]) + display_text(args.text) + + -- set the string value and re-draw the text + ---@param val string value + function e.set_value(val) + e.window.clear() + display_text(val) end return e.get() From e0ab2ade892e7c226938dc122c8182fcf2052f72 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 12 Sep 2022 13:53:39 -0400 Subject: [PATCH 386/587] #91 support resizing core map per reactor dimension updates --- graphics/element.lua | 19 ++++- graphics/elements/indicators/coremap.lua | 100 ++++++++++++++--------- 2 files changed, 81 insertions(+), 38 deletions(-) diff --git a/graphics/element.lua b/graphics/element.lua index d776c7f..9759ce5 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -251,23 +251,39 @@ function element.new(args) -- PROPERTIES -- -- get the foreground/background colors + ---@return cpair fg_bg function public.get_fg_bg() return protected.fg_bg end -- get element width + ---@return integer width function public.width() return protected.frame.w end -- get element height + ---@return integer height function public.height() return protected.frame.h end - -- get the control value reading + -- get the element value + ---@return any value function public.get_value() return protected.get_value() end + -- set the element value + ---@param value any new value + function public.set_value(value) + protected.set_value(value) + end + + -- resize attributes of the element value if supported + ---@vararg number dimensions (element specific) + function public.resize(...) + protected.resize(...) + end + -- FUNCTION CALLBACKS -- -- handle a monitor touch @@ -288,6 +304,7 @@ function element.new(args) end -- draw the element given new data + ---@vararg any new data function public.update(...) protected.on_update(...) end diff --git a/graphics/elements/indicators/coremap.lua b/graphics/elements/indicators/coremap.lua index 393575d..8d60da5 100644 --- a/graphics/elements/indicators/coremap.lua +++ b/graphics/elements/indicators/coremap.lua @@ -20,8 +20,9 @@ local function core_map(args) assert(util.is_int(args.reactor_l), "graphics.elements.indicators.coremap: reactor_l is a required field") assert(util.is_int(args.reactor_w), "graphics.elements.indicators.coremap: reactor_w is a required field") - args.width = args.reactor_l - args.height = args.reactor_w + -- require max dimensions + args.width = 18 + args.height = 18 -- inherit only foreground color args.fg_bg = core.graphics.cpair(args.parent.get_fg_bg().fgd, colors.gray) @@ -29,44 +30,43 @@ local function core_map(args) -- create new graphics element base object local e = element.new(args) - local start_x = 2 - local start_y = 2 - - local inner_width = e.frame.w - 2 - local inner_height = e.frame.h - 2 local alternator = true - -- check dimensions - assert(inner_width > 0, "graphics.elements.indicators.coremap: inner_width <= 0") - assert(inner_height > 0, "graphics.elements.indicators.coremap: inner_height <= 0") - assert(start_x <= inner_width, "graphics.elements.indicators.coremap: start_x > inner_width") - assert(start_y <= inner_height, "graphics.elements.indicators.coremap: start_y > inner_height") + local shift_x = 0 + local shift_y = 0 - -- label coordinates + local start_x = 2 + shift_x + local start_y = 2 + shift_y - e.window.setTextColor(colors.white) + local inner_width = e.frame.w - start_x + local inner_height = e.frame.h - start_y - for x = 0, (inner_width - 1) do - e.window.setCursorPos(x + start_x, 1) - e.window.write(util.sprintf("%X", x)) + -- create coordinate grid and frame + local function draw_frame() + e.window.setTextColor(colors.white) + + for x = 0, (inner_width - 1) do + e.window.setCursorPos(x + start_x, 1) + e.window.write(util.sprintf("%X", x)) + end + + for y = 0, (inner_height - 1) do + e.window.setCursorPos(1, y + start_y) + e.window.write(util.sprintf("%X", y)) + end + + -- even out bottom edge + e.window.setTextColor(e.fg_bg.bkg) + e.window.setBackgroundColor(args.parent.get_fg_bg().bkg) + e.window.setCursorPos(1, e.frame.h) + e.window.write(util.strrep("\x8f", e.frame.w)) + e.window.setTextColor(e.fg_bg.fgd) + e.window.setBackgroundColor(e.fg_bg.bkg) end - for y = 0, (inner_height - 1) do - e.window.setCursorPos(1, y + start_y) - e.window.write(util.sprintf("%X", y)) - end - - -- even out bottom edge - e.window.setTextColor(e.fg_bg.bkg) - e.window.setBackgroundColor(args.parent.get_fg_bg().bkg) - e.window.setCursorPos(1, e.frame.h) - e.window.write(util.strrep("\x8f", e.frame.w)) - e.window.setTextColor(e.fg_bg.fgd) - e.window.setBackgroundColor(e.fg_bg.bkg) - -- draw the core ---@param t number temperature in K - local function draw(t) + local function draw_core(t) local i = 1 local back_c = "F" local text_c = "8" @@ -99,9 +99,7 @@ local function core_map(args) -- draw pattern for y = start_y, inner_height + (start_y - 1) do e.window.setCursorPos(start_x, y) - for x = 1, inner_width do - local str = util.sprintf("%02X", i) - + for _ = 1, inner_width do if alternator then i = i + 1 e.window.blit("\x07", text_c, back_c) @@ -120,13 +118,41 @@ local function core_map(args) ---@param temperature number temperature in Kelvin function e.on_update(temperature) e.value = temperature - draw(temperature) + draw_core(e.value) end + -- set temperature to display + ---@param val number degrees K function e.set_value(val) e.on_update(val) end - -- initial draw at base temp - e.on_update(300) + -- resize reactor dimensions + ---@param reactor_l integer reactor length (rendered in 2D top-down as width) + ---@param reactor_w integer reactor width (rendered in 2D top-down as height) + function e.resize(reactor_l, reactor_w) + -- enforce possible dimensions + if reactor_l > 16 then reactor_l = 16 elseif reactor_l < 3 then reactor_l = 3 end + if reactor_w > 16 then reactor_w = 16 elseif reactor_w < 3 then reactor_w = 3 end + + -- update dimensions + shift_x = 8 - math.floor(reactor_l / 2) + shift_y = 8 - math.floor(reactor_w / 2) + start_x = 2 + shift_x + start_y = 2 + shift_y + inner_width = reactor_l + inner_height = reactor_w + + e.window.clear() + + -- re-draw + draw_frame() + e.on_update(e.value) + end + + -- initial (one-time except for resize()) frame draw + draw_frame() + + -- initial draw + e.on_update(0) return e.get() end From cd6bb7376db5afdeffe80e837862aa71511d1f3c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 12 Sep 2022 14:38:48 -0400 Subject: [PATCH 387/587] #91 adjusted resizing logic for core map --- graphics/elements/indicators/coremap.lua | 25 ++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/graphics/elements/indicators/coremap.lua b/graphics/elements/indicators/coremap.lua index 8d60da5..09a467c 100644 --- a/graphics/elements/indicators/coremap.lua +++ b/graphics/elements/indicators/coremap.lua @@ -32,14 +32,17 @@ local function core_map(args) local alternator = true - local shift_x = 0 - local shift_y = 0 + local core_l = args.reactor_l - 2 + local core_w = args.reactor_w - 2 + + local shift_x = 8 - math.floor(core_l / 2) + local shift_y = 8 - math.floor(core_w / 2) local start_x = 2 + shift_x local start_y = 2 + shift_y - local inner_width = e.frame.w - start_x - local inner_height = e.frame.h - start_y + local inner_width = core_l + local inner_height = core_w -- create coordinate grid and frame local function draw_frame() @@ -130,16 +133,18 @@ local function core_map(args) ---@param reactor_w integer reactor width (rendered in 2D top-down as height) function e.resize(reactor_l, reactor_w) -- enforce possible dimensions - if reactor_l > 16 then reactor_l = 16 elseif reactor_l < 3 then reactor_l = 3 end - if reactor_w > 16 then reactor_w = 16 elseif reactor_w < 3 then reactor_w = 3 end + if reactor_l > 18 then reactor_l = 18 elseif reactor_l < 3 then reactor_l = 3 end + if reactor_w > 18 then reactor_w = 18 elseif reactor_w < 3 then reactor_w = 3 end -- update dimensions - shift_x = 8 - math.floor(reactor_l / 2) - shift_y = 8 - math.floor(reactor_w / 2) + core_l = reactor_l - 2 + core_w = reactor_w - 2 + shift_x = 8 - math.floor(core_l / 2) + shift_y = 8 - math.floor(core_w / 2) start_x = 2 + shift_x start_y = 2 + shift_y - inner_width = reactor_l - inner_height = reactor_w + inner_width = core_l + inner_height = core_w e.window.clear() From 1bf21564f9417a18d2b4ed4a069d8c974ac37031 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 12 Sep 2022 14:43:01 -0400 Subject: [PATCH 388/587] #91 recoloring of horizontal and vertical bar indicators --- graphics/elements/indicators/hbar.lua | 1 + graphics/elements/indicators/vbar.lua | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/graphics/elements/indicators/hbar.lua b/graphics/elements/indicators/hbar.lua index 4fc8532..4a291ff 100644 --- a/graphics/elements/indicators/hbar.lua +++ b/graphics/elements/indicators/hbar.lua @@ -98,6 +98,7 @@ local function hbar(args) end end + -- change bar color ---@param bar_fg_bg cpair new bar colors function e.recolor(bar_fg_bg) bar_bkg = bar_fg_bg.blit_bkg diff --git a/graphics/elements/indicators/vbar.lua b/graphics/elements/indicators/vbar.lua index 7888033..38eb023 100644 --- a/graphics/elements/indicators/vbar.lua +++ b/graphics/elements/indicators/vbar.lua @@ -80,6 +80,17 @@ local function vbar(args) end end + -- change bar color + ---@param fg_bg cpair new bar colors + function e.recolor(fg_bg) + fgd = util.strrep(fg_bg.blit_fgd, e.frame.w) + bkg = util.strrep(fg_bg.blit_bkg, e.frame.w) + + -- re-draw + last_num_bars = 0 + e.on_update(e.value) + end + function e.set_value(val) e.on_update(val) end return e.get() From 70d9da847eeca59805210f4e0483be188fa27aa7 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 12 Sep 2022 15:58:43 -0400 Subject: [PATCH 389/587] graphics elements comments --- graphics/elements/indicators/data.lua | 2 ++ graphics/elements/indicators/hbar.lua | 3 +++ graphics/elements/indicators/icon.lua | 2 ++ graphics/elements/indicators/light.lua | 2 ++ graphics/elements/indicators/state.lua | 2 ++ graphics/elements/indicators/trilight.lua | 2 ++ graphics/elements/indicators/vbar.lua | 3 +++ 7 files changed, 16 insertions(+) diff --git a/graphics/elements/indicators/data.lua b/graphics/elements/indicators/data.lua index 64e2a23..c566791 100644 --- a/graphics/elements/indicators/data.lua +++ b/graphics/elements/indicators/data.lua @@ -92,6 +92,8 @@ local function data(args) end end + -- set the value + ---@param val any new value function e.set_value(val) e.on_update(val) end -- initial value draw diff --git a/graphics/elements/indicators/hbar.lua b/graphics/elements/indicators/hbar.lua index 4a291ff..9794736 100644 --- a/graphics/elements/indicators/hbar.lua +++ b/graphics/elements/indicators/hbar.lua @@ -41,6 +41,7 @@ local function hbar(args) end -- handle data changes + ---@param fraction number 0.0 to 1.0 function e.on_update(fraction) e.value = fraction @@ -109,6 +110,8 @@ local function hbar(args) e.on_update(e.value) end + -- set the percentage value + ---@param val number 0.0 to 1.0 function e.set_value(val) e.on_update(val) end -- initialize to 0 diff --git a/graphics/elements/indicators/icon.lua b/graphics/elements/indicators/icon.lua index 728bfea..0c71d29 100644 --- a/graphics/elements/indicators/icon.lua +++ b/graphics/elements/indicators/icon.lua @@ -60,6 +60,8 @@ local function icon(args) e.window.blit(blit_cmd.text, blit_cmd.fgd, blit_cmd.bkg) end + -- set indicator state + ---@param val integer indicator state function e.set_value(val) e.on_update(val) end -- initial icon draw diff --git a/graphics/elements/indicators/light.lua b/graphics/elements/indicators/light.lua index a1cc85b..5d38a7c 100644 --- a/graphics/elements/indicators/light.lua +++ b/graphics/elements/indicators/light.lua @@ -40,6 +40,8 @@ local function indicator_light(args) end end + -- set indicator state + ---@param val boolean indicator state function e.set_value(val) e.on_update(val) end -- write label and initial indicator light diff --git a/graphics/elements/indicators/state.lua b/graphics/elements/indicators/state.lua index 4631a40..386910c 100644 --- a/graphics/elements/indicators/state.lua +++ b/graphics/elements/indicators/state.lua @@ -66,6 +66,8 @@ local function state_indicator(args) e.window.blit(blit_cmd.text, blit_cmd.fgd, blit_cmd.bkg) end + -- set indicator state + ---@param val integer indicator state function e.set_value(val) e.on_update(val) end -- initial draw diff --git a/graphics/elements/indicators/trilight.lua b/graphics/elements/indicators/trilight.lua index 435af84..83aef37 100644 --- a/graphics/elements/indicators/trilight.lua +++ b/graphics/elements/indicators/trilight.lua @@ -51,6 +51,8 @@ local function tristate_indicator_light(args) end end + -- set indicator state + ---@param val integer indicator state function e.set_value(val) e.on_update(val) end -- write label and initial indicator light diff --git a/graphics/elements/indicators/vbar.lua b/graphics/elements/indicators/vbar.lua index 38eb023..f56c60c 100644 --- a/graphics/elements/indicators/vbar.lua +++ b/graphics/elements/indicators/vbar.lua @@ -32,6 +32,7 @@ local function vbar(args) local two_thirds = util.strrep("\x83", e.frame.w) -- handle data changes + ---@param fraction number 0.0 to 1.0 function e.on_update(fraction) e.value = fraction @@ -91,6 +92,8 @@ local function vbar(args) e.on_update(e.value) end + -- set the percentage value + ---@param val number 0.0 to 1.0 function e.set_value(val) e.on_update(val) end return e.get() From 265368f9b200e04a62526692abf842757fdcc527 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 12 Sep 2022 16:01:18 -0400 Subject: [PATCH 390/587] fixed integrity % and changed to actual burn rate on main screen --- coordinator/startup.lua | 2 +- coordinator/ui/components/reactor.lua | 2 +- coordinator/ui/components/unit_detail.lua | 13 +------------ 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 4df19bf..b008b59 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.4.9" +local COORDINATOR_VERSION = "alpha-v0.4.10" local print = util.print local println = util.println diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua index 09f82f9..e04a34e 100644 --- a/coordinator/ui/components/reactor.lua +++ b/coordinator/ui/components/reactor.lua @@ -34,7 +34,7 @@ local function new_view(root, x, y, data, ps) ps.subscribe("computed_status", status.update) ps.subscribe("temp", core_temp.update) - ps.subscribe("burn_rate", burn_r.update) + ps.subscribe("act_burn_rate", burn_r.update) ps.subscribe("heating_rate", heating_r.update) local reactor_fills = Rectangle{parent=root,border=border(1, colors.gray, true),width=24,height=7,x=(x + 29),y=y} diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 16ae5ca..2ef2dbe 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -75,7 +75,7 @@ local function init(parent, id) TextBox{parent=main,x=21,text="Containment Integrity",height=2,width=12,fg_bg=style.label} local integ = DataIndicator{parent=main,x=21,label="",format="%9.0f",value=100,unit="%",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} - r_ps.subscribe("damage", function (x) integ.update(1.0 - (x / 100.0)) end) + r_ps.subscribe("damage", function (x) integ.update(100.0 - x) end) main.line_break() -- TextBox{parent=main,text="FL",x=21,y=19,height=1,width=2,fg_bg=style.label} @@ -283,17 +283,6 @@ local function init(parent, id) main.line_break() ColorMap{parent=main,x=2,y=51} - ---@fixme test code - local rps = true - local function _test_toggle() - rps_trp.update(rps) - rps = not rps - tcallbackdsp.dispatch(0.25, _test_toggle) - end - - ---@fixme test code - tcallbackdsp.dispatch(0.25, _test_toggle) - return main end From c47e0044b174dec5bc1380f754a2ec323a32be10 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 13 Sep 2022 16:07:21 -0400 Subject: [PATCH 391/587] addresed monitor disconnect to-do, changed monitor requirement to minimum, fixed up connect/reconnect for #92 --- coordinator/coordinator.lua | 3 +- coordinator/renderer.lua | 73 ++++++++++++++++++++++++------------- coordinator/startup.lua | 70 ++++++++++++++++++++++++++--------- 3 files changed, 103 insertions(+), 43 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index e7a9ebd..d759a93 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -58,7 +58,7 @@ function coordinator.configure_monitors(num_units) end -- we need a certain number of monitors (1 per unit + 1 primary display) - if #names ~= num_units + 1 then + if #names < num_units + 1 then println("not enough monitors connected (need " .. num_units + 1 .. ")") log.warning("insufficient monitors present (need " .. num_units + 1 .. ")") return false @@ -258,6 +258,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa -- close the connection to the server function public.close() sv_watchdog.cancel() + self.sv_linked = false _send_sv(PROTOCOLS.SCADA_MGMT, SCADA_MGMT_TYPES.CLOSE, {}) end diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index f76c0b3..bbbd90e 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -54,6 +54,25 @@ function renderer.set_displays(monitors) engine.monitors = monitors end +-- check if the renderer is configured to use a given monitor peripheral +---@param periph table peripheral +---@return boolean is_used +function renderer.is_monitor_used(periph) + if engine.monitors ~= nil then + if engine.monitors.primary == periph then + return true + else + for i = 1, #engine.monitors.unit_displays do + if engine.monitors.unit_displays[i] == periph then + return true + end + end + end + end + + return false +end + -- reset all displays in use by the renderer ---@param recolor? boolean true to use color palette from style function renderer.reset(recolor) @@ -76,40 +95,44 @@ end -- start the coordinator GUI function renderer.start_ui() - -- hide dmesg - engine.dmesg_window.setVisible(false) + if not engine.ui_ready then + -- hide dmesg + engine.dmesg_window.setVisible(false) - -- show main view on main monitor - ui.main_layout = main_view(engine.monitors.primary) + -- show main view on main monitor + ui.main_layout = main_view(engine.monitors.primary) - -- show unit views on unit displays - for id, monitor in pairs(engine.monitors.unit_displays) do - table.insert(ui.unit_layouts, unit_view(monitor, id)) + -- show unit views on unit displays + for id, monitor in pairs(engine.monitors.unit_displays) do + table.insert(ui.unit_layouts, unit_view(monitor, id)) + end + + -- report ui as ready + engine.ui_ready = true end - - -- report ui as ready - engine.ui_ready = true end -- close out the UI function renderer.close_ui() - -- report ui as not ready - engine.ui_ready = false + if engine.ui_ready then + -- report ui as not ready + engine.ui_ready = false - -- hide to stop animation callbacks - ui.main_layout.hide() - for i = 1, #ui.unit_layouts do - ui.unit_layouts[i].hide() - engine.monitors.unit_displays[i].clear() + -- hide to stop animation callbacks + ui.main_layout.hide() + for i = 1, #ui.unit_layouts do + ui.unit_layouts[i].hide() + engine.monitors.unit_displays[i].clear() + end + + -- clear root UI elements + ui.main_layout = nil + ui.unit_layouts = {} + + -- re-draw dmesg + engine.dmesg_window.setVisible(true) + engine.dmesg_window.redraw() end - - -- clear root UI elements - ui.main_layout = nil - ui.unit_layouts = {} - - -- re-draw dmesg - engine.dmesg_window.setVisible(true) - engine.dmesg_window.redraw() end -- is the UI ready? diff --git a/coordinator/startup.lua b/coordinator/startup.lua index b008b59..4c30435 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.4.10" +local COORDINATOR_VERSION = "alpha-v0.4.11" local print = util.print local println = util.println @@ -58,7 +58,7 @@ log.info("========================================") println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<") ---------------------------------------- --- startup +-- system startup ---------------------------------------- -- mount connected devices @@ -83,6 +83,10 @@ log_graphics("displays connected and reset") log_sys("system start on " .. os.date("%c")) log_boot("starting " .. COORDINATOR_VERSION) +---------------------------------------- +-- setup communications +---------------------------------------- + -- get the communications modem local modem = ppm.get_wireless_modem() if modem == nil then @@ -108,6 +112,10 @@ log_comms("comms initialized") 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) @@ -115,14 +123,20 @@ local function init_connect_sv() -- 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 + return false end + + return true end -init_connect_sv() +if not init_connect_sv() then + println("boot> failed to connect to supervisor") + log_sys("system shutdown") + return +else + log_sys("supervisor connected, proceeding to UI start") +end ---------------------------------------- -- start the UI @@ -158,10 +172,14 @@ local ui_ok = init_start_ui() -- main event loop ---------------------------------------- +local no_modem = false + -- start connection watchdog conn_watchdog.feed() log.debug("boot> conn watchdog started") +log_sys("system started successfully") + -- event loop -- ui_ok will never change in this loop, same as while true or exit if UI start failed while ui_ok do @@ -175,18 +193,28 @@ while ui_ok do 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 - ---@todo: handle monitor loss + 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 elseif event == "peripheral" then @@ -196,6 +224,7 @@ while ui_ok do if type == "modem" then if device.isWireless() then -- reconnected modem + no_modem = false modem = device coord_comms.reconnect_modem(modem) @@ -203,13 +232,13 @@ while ui_ok do println_ts("wireless modem reconnected.") -- re-init system - init_connect_sv() + if not init_connect_sv() then break end ui_ok = init_start_ui() else log_sys("wired modem reconnected") end elseif type == "monitor" then - ---@todo: handle monitor reconnect + -- not supported, system will exit on loss of in-use monitors end end elseif event == "timer" then @@ -231,9 +260,11 @@ while ui_ok do coord_comms.close() renderer.close_ui() - -- try to re-connect to the supervisor - init_connect_sv() - ui_ok = init_start_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 else -- a non-clock/main watchdog timer event @@ -250,13 +281,17 @@ while ui_ok do -- 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() - -- try to re-connect to the supervisor - init_connect_sv() - ui_ok = init_start_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 @@ -265,10 +300,11 @@ while ui_ok do -- check for termination request if event == "terminate" or ppm.should_terminate() then - log_comms("terminate requested, closing supervisor connection") + println_ts("terminate requested, closing connections...") + log_comms("terminate requested, closing supervisor connection...") coord_comms.close() + log_comms("supervisor connection closed") log_comms("closing api sessions...") - println_ts("closing api sessions...") apisessions.close_all() log_comms("api sessions closed") break From 6686d8ea627098da3f3c916a4d55324aa6a9337b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 13 Sep 2022 16:08:11 -0400 Subject: [PATCH 392/587] changed reactor status message text on main view --- coordinator/startup.lua | 2 +- coordinator/ui/style.lua | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 4c30435..2d9bf43 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.4.11" +local COORDINATOR_VERSION = "alpha-v0.4.12" local print = util.print local println = util.println diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index 08b0b9b..ec0d20d 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -37,7 +37,7 @@ style.reactor = { states = { { color = cpair(colors.black, colors.yellow), - text = "DISCONNECTED" + text = "PLC OFF-LINE" }, { color = cpair(colors.white, colors.gray), @@ -49,11 +49,11 @@ style.reactor = { }, { color = cpair(colors.black, colors.red), - text = "SCRAM!" + text = "SCRAMMED" }, { color = cpair(colors.black, colors.orange), - text = "PLC FAULT!" + text = "PLC FAULT" } } } From 3267e7ff13b7a6a6d3af363d868a634529b2d1e3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 17 Sep 2022 17:04:57 -0400 Subject: [PATCH 393/587] #96 RTU starts unlinked now on main thread start --- rtu/config.lua | 4 ++-- rtu/startup.lua | 2 +- rtu/threads.lua | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/rtu/config.lua b/rtu/config.lua index 3d2e889..c97fda1 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -15,12 +15,12 @@ config.LOG_MODE = 0 -- RTU peripheral devices (named: side/network device name) config.RTU_DEVICES = { { - name = "boiler_1", + name = "boilerValve_0", index = 1, for_reactor = 1 }, { - name = "turbine_1", + name = "turbineValve_0", index = 1, for_reactor = 1 } diff --git a/rtu/startup.lua b/rtu/startup.lua index 2c00791..9f9ee16 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.7.10" +local RTU_VERSION = "beta-v0.7.11" local rtu_t = types.rtu_t diff --git a/rtu/threads.lua b/rtu/threads.lua index 42ea746..37ce9bc 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -44,6 +44,9 @@ function threads.thread__main(smem) local conn_watchdog = smem.rtu_sys.conn_watchdog local units = smem.rtu_sys.units + -- start unlinked (in case of restart) + rtu_comms.unlink(rtu_state) + -- start clock loop_clock.start() From 88c34d8bca0b2db23b022a8002a1cc662d660073 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 18 Sep 2022 22:02:17 -0400 Subject: [PATCH 394/587] fixed acknowledge packets to use error flag, fixed 'static'-like function scope of modbus functions --- rtu/modbus.lua | 73 ++++++++++++++++++++++++------------------------- rtu/rtu.lua | 2 +- rtu/startup.lua | 2 +- rtu/threads.lua | 6 ++-- 4 files changed, 41 insertions(+), 42 deletions(-) diff --git a/rtu/modbus.lua b/rtu/modbus.lua index 498dd19..c011ede 100644 --- a/rtu/modbus.lua +++ b/rtu/modbus.lua @@ -332,10 +332,9 @@ function modbus.new(rtu_dev, use_parallel_read) -- default is to echo back local func_code = packet.func_code - if not return_code then - -- echo back with error flag - func_code = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) - end + + -- echo back with error flag, on success the "error" will be acknowledgement + func_code = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) -- create reply local reply = comms.modbus_packet() @@ -400,40 +399,40 @@ function modbus.new(rtu_dev, use_parallel_read) return return_code, reply end - -- return a SERVER_DEVICE_BUSY error reply - ---@return modbus_packet reply - function public.reply__srv_device_busy(packet) - -- reply back with error flag and exception code - local reply = comms.modbus_packet() - local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) - local data = { MODBUS_EXCODE.SERVER_DEVICE_BUSY } - reply.make(packet.txn_id, packet.unit_id, fcode, data) - return reply - end - - -- return a NEG_ACKNOWLEDGE error reply - ---@return modbus_packet reply - function public.reply__neg_ack(packet) - -- reply back with error flag and exception code - local reply = comms.modbus_packet() - local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) - local data = { MODBUS_EXCODE.NEG_ACKNOWLEDGE } - reply.make(packet.txn_id, packet.unit_id, fcode, data) - return reply - end - - -- return a GATEWAY_PATH_UNAVAILABLE error reply - ---@return modbus_packet reply - function public.reply__gw_unavailable(packet) - -- reply back with error flag and exception code - local reply = comms.modbus_packet() - local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) - local data = { MODBUS_EXCODE.GATEWAY_PATH_UNAVAILABLE } - reply.make(packet.txn_id, packet.unit_id, fcode, data) - return reply - end - return public end +-- return a SERVER_DEVICE_BUSY error reply +---@return modbus_packet reply +function modbus.reply__srv_device_busy(packet) + -- reply back with error flag and exception code + local reply = comms.modbus_packet() + local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) + local data = { MODBUS_EXCODE.SERVER_DEVICE_BUSY } + reply.make(packet.txn_id, packet.unit_id, fcode, data) + return reply +end + +-- return a NEG_ACKNOWLEDGE error reply +---@return modbus_packet reply +function modbus.reply__neg_ack(packet) + -- reply back with error flag and exception code + local reply = comms.modbus_packet() + local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) + local data = { MODBUS_EXCODE.NEG_ACKNOWLEDGE } + reply.make(packet.txn_id, packet.unit_id, fcode, data) + return reply +end + +-- return a GATEWAY_PATH_UNAVAILABLE error reply +---@return modbus_packet reply +function modbus.reply__gw_unavailable(packet) + -- reply back with error flag and exception code + local reply = comms.modbus_packet() + local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG) + local data = { MODBUS_EXCODE.GATEWAY_PATH_UNAVAILABLE } + reply.make(packet.txn_id, packet.unit_id, fcode, data) + return reply +end + return modbus diff --git a/rtu/rtu.lua b/rtu/rtu.lua index d59649d..cf69b58 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -353,7 +353,7 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog) -- check if there are more than 3 active transactions -- still queue the packet, but this may indicate a problem if unit.pkt_queue.length() > 3 then - reply = unit.modbus_io.reply__srv_device_busy(packet) + reply = modbus.reply__srv_device_busy(packet) log.debug("queueing new request with " .. unit.pkt_queue.length() .. " transactions already in the queue" .. unit_dbg_tag) end diff --git a/rtu/startup.lua b/rtu/startup.lua index 9f9ee16..758ee84 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local imatrix_rtu = require("rtu.dev.imatrix_rtu") local turbine_rtu = require("rtu.dev.turbine_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.7.11" +local RTU_VERSION = "beta-v0.7.12" local rtu_t = types.rtu_t diff --git a/rtu/threads.lua b/rtu/threads.lua index 37ce9bc..e20c337 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -256,7 +256,7 @@ function threads.thread__unit_comms(smem, unit) -- execute thread function public.exec() - log.debug("rtu unit thread start -> " .. unit.name .. "(" .. unit.type .. ")") + log.debug("rtu unit thread start -> " .. unit.type .. "(" .. unit.name .. ")") -- load in from shared memory local rtu_state = smem.rtu_state @@ -289,7 +289,7 @@ function threads.thread__unit_comms(smem, unit) -- check for termination request if rtu_state.shutdown then - log.info("rtu unit thread exiting -> " .. unit.name .. "(" .. unit.type .. ")") + log.info("rtu unit thread exiting -> " .. unit.type .. "(" .. unit.name .. ")") break end @@ -309,7 +309,7 @@ function threads.thread__unit_comms(smem, unit) end if not rtu_state.shutdown then - log.info(util.c("rtu unit thread ", unit.name, "(", unit.type, ") restarting in 5 seconds...")) + log.info(util.c("rtu unit thread ", unit.type, "(", unit.name, ") restarting in 5 seconds...")) util.psleep(5) end end From d0d20b12999edc8460cbae5b8dd8f311f16a06a6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 18 Sep 2022 22:25:59 -0400 Subject: [PATCH 395/587] #95 added boiler/turbine RTUs to supervisor, tons of RTU/MODBUS related bugfixes, adjusted annunciator conditions --- supervisor/session/rtu.lua | 56 ++++++++++++++++++++----- supervisor/session/rtu/boiler.lua | 2 +- supervisor/session/rtu/boilerv.lua | 23 +++++----- supervisor/session/rtu/emachine.lua | 2 +- supervisor/session/rtu/envd.lua | 2 +- supervisor/session/rtu/imatrix.lua | 2 +- supervisor/session/rtu/redstone.lua | 2 +- supervisor/session/rtu/sna.lua | 2 +- supervisor/session/rtu/sps.lua | 2 +- supervisor/session/rtu/turbine.lua | 2 +- supervisor/session/rtu/turbinev.lua | 17 ++++++-- supervisor/session/rtu/txnctrl.lua | 6 ++- supervisor/session/rtu/unit_session.lua | 3 +- supervisor/session/unit.lua | 40 ++++++++++++------ supervisor/startup.lua | 2 +- 15 files changed, 114 insertions(+), 49 deletions(-) diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 30e9432..f8cd3f4 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -60,6 +60,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) id = id, in_q = in_queue, out_q = out_queue, + modbus_q = mqueue.new(), f_units = facility_units, advert = advertisement, -- connection properties @@ -107,6 +108,8 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) rsio = self.advert[i][4] } + local target_unit = self.f_units[unit_advert.reactor] ---@type reactor_unit + local u_type = unit_advert.type -- validate unit advertisement @@ -133,35 +136,39 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) -- validation fail elseif u_type == RTU_UNIT_TYPES.REDSTONE then -- redstone - unit, rs_in_q = svrs_redstone.new(self.id, i, unit_advert, self.out_q) + unit, rs_in_q = svrs_redstone.new(self.id, i, unit_advert, self.modbus_q) elseif u_type == RTU_UNIT_TYPES.BOILER then -- boiler - unit = svrs_boiler.new(self.id, i, unit_advert, self.out_q) + unit = svrs_boiler.new(self.id, i, unit_advert, self.modbus_q) + target_unit.add_boiler(unit) elseif u_type == RTU_UNIT_TYPES.BOILER_VALVE then -- boiler (Mekanism 10.1+) - unit = svrs_boilerv.new(self.id, 1, unit_advert, self.out_q) + unit = svrs_boilerv.new(self.id, i, unit_advert, self.modbus_q) + target_unit.add_boiler(unit) elseif u_type == RTU_UNIT_TYPES.TURBINE then -- turbine - unit = svrs_turbine.new(self.id, i, unit_advert, self.out_q) + unit = svrs_turbine.new(self.id, i, unit_advert, self.modbus_q) + target_unit.add_turbine(unit) elseif u_type == RTU_UNIT_TYPES.TURBINE_VALVE then -- turbine (Mekanism 10.1+) - unit, tbv_in_q = svrs_turbinev.new(self.id, i, unit_advert, self.out_q) + unit, tbv_in_q = svrs_turbinev.new(self.id, i, unit_advert, self.modbus_q) + target_unit.add_turbine(unit) self.turbine_cmd_capable = true elseif u_type == RTU_UNIT_TYPES.EMACHINE then -- mekanism [energy] machine - unit = svrs_emachine.new(self.id, i, unit_advert, self.out_q) + unit = svrs_emachine.new(self.id, i, unit_advert, self.modbus_q) elseif u_type == RTU_UNIT_TYPES.IMATRIX then -- induction matrix - unit = svrs_imatrix.new(self.id, i, unit_advert, self.out_q) + unit = svrs_imatrix.new(self.id, i, unit_advert, self.modbus_q) elseif u_type == RTU_UNIT_TYPES.SPS then -- super-critical phase shifter - unit = svrs_sps.new(self.id, i, unit_advert, self.out_q) + unit = svrs_sps.new(self.id, i, unit_advert, self.modbus_q) elseif u_type == RTU_UNIT_TYPES.SNA then -- solar neutron activator - unit = svrs_sna.new(self.id, i, unit_advert, self.out_q) + unit = svrs_sna.new(self.id, i, unit_advert, self.modbus_q) elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then -- environment detector - unit = svrs_envd.new(self.id, i, unit_advert, self.out_q) + unit = svrs_envd.new(self.id, i, unit_advert, self.modbus_q) else log.error(log_header .. "bad advertisement: encountered unsupported RTU type") end @@ -213,6 +220,17 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) end end + -- send a MODBUS packet + ---@param m_pkt modbus_packet MODBUS packet + local function _send_modbus(m_pkt) + local s_pkt = comms.scada_packet() + + s_pkt.make(self.seq_num, PROTOCOLS.MODBUS_TCP, m_pkt.raw_sendable()) + + self.out_q.push_packet(s_pkt) + self.seq_num = self.seq_num + 1 + end + -- send a SCADA management packet ---@param msg_type SCADA_MGMT_TYPES ---@param msg table @@ -387,11 +405,29 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) end self.periodics.last_update = util.time() + + ---------------------------------------------- + -- pass MODBUS packets on to main out queue -- + ---------------------------------------------- + + for _ = 1, self.modbus_q.length() do + -- get the next message + local msg = self.modbus_q.pop() + + if msg ~= nil then + if msg.qtype == mqueue.TYPE.PACKET then + _send_modbus(msg.message) + end + end + end end return self.connected end + -- handle initial advertisement + _handle_advertisement() + return public end diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua index 5daef5e..7aa25f0 100644 --- a/supervisor/session/rtu/boiler.lua +++ b/supervisor/session/rtu/boiler.lua @@ -108,7 +108,7 @@ function boiler.new(session_id, unit_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame function public.handle_packet(m_pkt) - local txn_type = self.session.try_resolve(m_pkt.txn_id) + local txn_type = self.session.try_resolve(m_pkt) if txn_type == false then -- nothing to do elseif txn_type == TXN_TYPES.BUILD then diff --git a/supervisor/session/rtu/boilerv.lua b/supervisor/session/rtu/boilerv.lua index 16125f0..bea740a 100644 --- a/supervisor/session/rtu/boilerv.lua +++ b/supervisor/session/rtu/boilerv.lua @@ -1,6 +1,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local types = require("scada-common.types") +local util = require("scada-common.util") local unit_session = require("supervisor.session.rtu.unit_session") @@ -60,8 +61,8 @@ function boilerv.new(session_id, unit_id, advert, out_queue) length = 0, width = 0, height = 0, - min_pos = 0, - max_pos = 0, + min_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate + max_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate boil_cap = 0.0, steam_cap = 0, water_cap = 0, @@ -76,16 +77,16 @@ function boilerv.new(session_id, unit_id, advert, out_queue) boil_rate = 0.0 }, tanks = { - steam = {}, ---@type tank_fluid + steam = { type = "mekanism:empty_gas", amount = 0 }, ---@type tank_fluid steam_need = 0, steam_fill = 0.0, - water = {}, ---@type tank_fluid + water = { type = "mekanism:empty_gas", amount = 0 }, ---@type tank_fluid water_need = 0, water_fill = 0.0, - hcool = {}, ---@type tank_fluid + hcool = { type = "mekanism:empty_gas", amount = 0 }, ---@type tank_fluid hcool_need = 0, hcool_fill = 0.0, - ccool = {}, ---@type tank_fluid + ccool = { type = "mekanism:empty_gas", amount = 0 }, ---@type tank_fluid ccool_need = 0, ccool_fill = 0.0 } @@ -105,19 +106,19 @@ function boilerv.new(session_id, unit_id, advert, out_queue) -- query the build of the device local function _request_build() -- read input registers 1 through 13 (start = 1, count = 13) - self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 7 }) + self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 13 }) end -- query the state of the device local function _request_state() - -- read input registers 14 through 16 (start = 14, count = 2) + -- read input registers 14 through 15 (start = 14, count = 2) self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 14, 2 }) end -- query the tanks of the device local function _request_tanks() - -- read input registers 17 through 29 (start = 17, count = 12) - self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 17, 12 }) + -- read input registers 16 through 27 (start = 16, count = 12) + self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 16, 12 }) end -- PUBLIC FUNCTIONS -- @@ -125,7 +126,7 @@ function boilerv.new(session_id, unit_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame function public.handle_packet(m_pkt) - local txn_type = self.session.try_resolve(m_pkt.txn_id) + local txn_type = self.session.try_resolve(m_pkt) if txn_type == false then -- nothing to do elseif txn_type == TXN_TYPES.FORMED then diff --git a/supervisor/session/rtu/emachine.lua b/supervisor/session/rtu/emachine.lua index db8a17b..e31c2af 100644 --- a/supervisor/session/rtu/emachine.lua +++ b/supervisor/session/rtu/emachine.lua @@ -79,7 +79,7 @@ function emachine.new(session_id, unit_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame function public.handle_packet(m_pkt) - local txn_type = self.session.try_resolve(m_pkt.txn_id) + local txn_type = self.session.try_resolve(m_pkt) if txn_type == false then -- nothing to do elseif txn_type == TXN_TYPES.BUILD then diff --git a/supervisor/session/rtu/envd.lua b/supervisor/session/rtu/envd.lua index f502e17..93968a9 100644 --- a/supervisor/session/rtu/envd.lua +++ b/supervisor/session/rtu/envd.lua @@ -62,7 +62,7 @@ function envd.new(session_id, unit_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame function public.handle_packet(m_pkt) - local txn_type = self.session.try_resolve(m_pkt.txn_id) + local txn_type = self.session.try_resolve(m_pkt) if txn_type == false then -- nothing to do elseif txn_type == TXN_TYPES.RAD then diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua index 965b57b..e6a9513 100644 --- a/supervisor/session/rtu/imatrix.lua +++ b/supervisor/session/rtu/imatrix.lua @@ -112,7 +112,7 @@ function imatrix.new(session_id, unit_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame function public.handle_packet(m_pkt) - local txn_type = self.session.try_resolve(m_pkt.txn_id) + local txn_type = self.session.try_resolve(m_pkt) if txn_type == false then -- nothing to do elseif txn_type == TXN_TYPES.FORMED then diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index ba01400..7ebd28b 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -144,7 +144,7 @@ function redstone.new(session_id, unit_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame function public.handle_packet(m_pkt) - local txn_type = self.session.try_resolve(m_pkt.txn_id) + local txn_type = self.session.try_resolve(m_pkt) if txn_type == false then -- nothing to do elseif txn_type == TXN_TYPES.DI_READ then diff --git a/supervisor/session/rtu/sna.lua b/supervisor/session/rtu/sna.lua index ccfac69..2d7f885 100644 --- a/supervisor/session/rtu/sna.lua +++ b/supervisor/session/rtu/sna.lua @@ -97,7 +97,7 @@ function sna.new(session_id, unit_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame function public.handle_packet(m_pkt) - local txn_type = self.session.try_resolve(m_pkt.txn_id) + local txn_type = self.session.try_resolve(m_pkt) if txn_type == false then -- nothing to do elseif txn_type == TXN_TYPES.BUILD then diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua index c2180d9..18ad727 100644 --- a/supervisor/session/rtu/sps.lua +++ b/supervisor/session/rtu/sps.lua @@ -117,7 +117,7 @@ function sps.new(session_id, unit_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame function public.handle_packet(m_pkt) - local txn_type = self.session.try_resolve(m_pkt.txn_id) + local txn_type = self.session.try_resolve(m_pkt) if txn_type == false then -- nothing to do elseif txn_type == TXN_TYPES.FORMED then diff --git a/supervisor/session/rtu/turbine.lua b/supervisor/session/rtu/turbine.lua index ab63fed..7fc58f8 100644 --- a/supervisor/session/rtu/turbine.lua +++ b/supervisor/session/rtu/turbine.lua @@ -104,7 +104,7 @@ function turbine.new(session_id, unit_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame function public.handle_packet(m_pkt) - local txn_type = self.session.try_resolve(m_pkt.txn_id) + local txn_type = self.session.try_resolve(m_pkt) if txn_type == false then -- nothing to do elseif txn_type == TXN_TYPES.BUILD then diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua index b31cb7c..56d43ef 100644 --- a/supervisor/session/rtu/turbinev.lua +++ b/supervisor/session/rtu/turbinev.lua @@ -70,6 +70,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue) in_q = mqueue.new(), has_build = false, periodics = { + next_formed_req = 0, next_build_req = 0, next_state_req = 0, next_tanks_req = 0 @@ -81,8 +82,8 @@ function turbinev.new(session_id, unit_id, advert, out_queue) length = 0, width = 0, height = 0, - min_pos = 0, - max_pos = 0, + min_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate + max_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate blades = 0, coils = 0, vents = 0, @@ -101,7 +102,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue) dumping_mode = DUMPING_MODE.IDLE ---@type DUMPING_MODE }, tanks = { - steam = 0, + steam = { type = "mekanism:empty_gas", amount = 0 }, ---@type tank_fluid steam_need = 0, steam_fill = 0.0, energy = 0, @@ -163,9 +164,17 @@ function turbinev.new(session_id, unit_id, advert, out_queue) -- handle a packet ---@param m_pkt modbus_frame function public.handle_packet(m_pkt) - local txn_type = self.session.try_resolve(m_pkt.txn_id) + local txn_type = self.session.try_resolve(m_pkt) if txn_type == false then -- nothing to do + elseif txn_type == TXN_TYPES.FORMED then + -- formed response + -- load in data if correct length + if m_pkt.length == 1 then + self.db.formed = m_pkt.data[1] + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end elseif txn_type == TXN_TYPES.BUILD then -- build response if m_pkt.length == 15 then diff --git a/supervisor/session/rtu/txnctrl.lua b/supervisor/session/rtu/txnctrl.lua index 2ebb526..a1f3b4b 100644 --- a/supervisor/session/rtu/txnctrl.lua +++ b/supervisor/session/rtu/txnctrl.lua @@ -19,6 +19,7 @@ function txnctrl.new() local public = {} local insert = table.insert + local remove = table.remove -- get the length of the transaction list function public.length() @@ -55,8 +56,9 @@ function txnctrl.new() for i = 1, public.length() do if self.list[i].txn_id == txn_id then - txn_type = self.list[i].txn_type - self.list[i] = nil + local entry = remove(self.list, i) + txn_type = entry.txn_type + break end end diff --git a/supervisor/session/rtu/unit_session.lua b/supervisor/session/rtu/unit_session.lua index ed2f027..3a70f26 100644 --- a/supervisor/session/rtu/unit_session.lua +++ b/supervisor/session/rtu/unit_session.lua @@ -1,6 +1,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local types = require("scada-common.types") +local util = require("scada-common.util") local txnctrl = require("supervisor.session.rtu.txnctrl") @@ -57,7 +58,7 @@ function unit_session.new(unit_id, advert, out_queue, log_tag, txn_tags) if m_pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then if m_pkt.unit_id == self.unit_id then local txn_type = self.transaction_controller.resolve(m_pkt.txn_id) - local txn_tag = " (" .. self.txn_tags[txn_type] .. ")" + local txn_tag = " (" .. util.strval(self.txn_tags[txn_type]) .. ")" if bit.band(m_pkt.func_code, MODBUS_FCODE.ERROR_FLAG) ~= 0 then -- transaction incomplete or failed diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 6cd3a5a..95fa035 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -17,7 +17,8 @@ local DT_KEYS = { BoilerSteam = "BST", BoilerCCool = "BCC", BoilerHCool = "BHC", - TurbineSteam = "TST" + TurbineSteam = "TST", + TurbinePower = "TPR" } -- create a new reactor unit @@ -135,22 +136,21 @@ function unit.new(for_reactor, num_boilers, num_turbines) for i = 1, #self.boilers do local boiler = self.boilers[i] ---@type unit_session - local db = boiler.get_db() ---@type boiler_session_db + local db = boiler.get_db() ---@type boilerv_session_db - ---@todo Mekanism 10.1+ will change water/steam to need .amount - _compute_dt(DT_KEYS.BoilerWater .. boiler.get_device_idx(), db.tanks.water) - _compute_dt(DT_KEYS.BoilerSteam .. boiler.get_device_idx(), db.tanks.steam) + _compute_dt(DT_KEYS.BoilerWater .. boiler.get_device_idx(), db.tanks.water.amount) + _compute_dt(DT_KEYS.BoilerSteam .. boiler.get_device_idx(), db.tanks.steam.amount) _compute_dt(DT_KEYS.BoilerCCool .. boiler.get_device_idx(), db.tanks.ccool.amount) _compute_dt(DT_KEYS.BoilerHCool .. boiler.get_device_idx(), db.tanks.hcool.amount) end for i = 1, #self.turbines do local turbine = self.turbines[i] ---@type unit_session - local db = turbine.get_db() ---@type turbine_session_db + local db = turbine.get_db() ---@type turbinev_session_db - _compute_dt(DT_KEYS.TurbineSteam .. turbine.get_device_idx(), db.tanks.steam) - ---@todo Mekanism 10.1+ needed - -- _compute_dt(DT_KEYS.TurbinePower .. turbine.get_device_idx(), db.?) + _compute_dt(DT_KEYS.TurbineSteam .. turbine.get_device_idx(), db.tanks.steam.amount) + ---@todo unused currently? + _compute_dt(DT_KEYS.TurbinePower .. turbine.get_device_idx(), db.tanks.energy) end end @@ -242,10 +242,12 @@ function unit.new(for_reactor, num_boilers, num_turbines) local idx = boiler.get_device_idx() local db = boiler.get_db() ---@type boiler_session_db + local gaining_hc = _get_dt(DT_KEYS.BoilerHCool .. idx) > 0 or db.tanks.hcool_fill == 1 + -- gaining heated coolant - cfmismatch = cfmismatch or _get_dt(DT_KEYS.BoilerHCool .. idx) > 0 or db.tanks.hcool_fill == 1 + cfmismatch = cfmismatch or gaining_hc -- losing cooled coolant - cfmismatch = cfmismatch or _get_dt(DT_KEYS.BoilerCCool .. idx) < 0 or db.tanks.ccool_fill == 0 + cfmismatch = cfmismatch or _get_dt(DT_KEYS.BoilerCCool .. idx) < 0 or (gaining_hc and db.tanks.ccool_fill == 0) end self.db.annunciator.CoolantFeedMismatch = cfmismatch @@ -264,7 +266,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- go through turbines for stats and online for i = 1, #self.turbines do - local session = self.turbine[i] ---@type unit_session + local session = self.turbines[i] ---@type unit_session local turbine = session.get_db() ---@type turbine_session_db total_flow_rate = total_flow_rate + turbine.state.flow_rate @@ -329,6 +331,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- PUBLIC FUNCTIONS -- + -- ADD/LINK DEVICES -- + -- link the PLC ---@param plc_session plc_session_struct function public.link_plc_session(plc_session) @@ -388,6 +392,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) table.insert(self.redstone[field], accessor) end + -- UPDATE SESSION -- + -- update (iterate) this unit function public.update() -- unlink PLC if session was closed @@ -403,6 +409,16 @@ function unit.new(for_reactor, num_boilers, num_turbines) _update_annunciator() end + -- COMMAND UNIT -- + + -- SCRAM reactor + function public.scram() + if self.plc_s ~= nil then + end + end + + -- READ STATES/PROPERTIES -- + -- get build properties of all machines function public.get_build() local build = {} diff --git a/supervisor/startup.lua b/supervisor/startup.lua index ea313a4..c7d4072 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.5.10" +local SUPERVISOR_VERSION = "beta-v0.5.11" local print = util.print local println = util.println From 36557fc34551d9fad3499c178af9116f9f5476bf Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 21 Sep 2022 15:53:51 -0400 Subject: [PATCH 396/587] code cleanup, type hints, bugfixes, and #98 removal of support for mek 10.0 RTU peripherals --- coordinator/coordinator.lua | 23 +++- coordinator/iocontrol.lua | 37 +++-- coordinator/startup.lua | 4 +- coordinator/ui/dialog.lua | 10 +- graphics/element.lua | 26 +++- graphics/elements/indicators/hbar.lua | 1 - reactor-plc/plc.lua | 4 +- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 12 +- rtu/dev/boiler_rtu.lua | 48 ------- rtu/dev/energymachine_rtu.lua | 30 ---- rtu/dev/redstone_rtu.lua | 1 + rtu/dev/turbine_rtu.lua | 43 ------ rtu/modbus.lua | 43 ++++-- rtu/rtu.lua | 16 ++- rtu/startup.lua | 72 +++++----- rtu/threads.lua | 38 +++-- scada-common/comms.lua | 41 +++--- scada-common/crypto.lua | 6 +- scada-common/mqueue.lua | 10 +- scada-common/types.lua | 3 - supervisor/session/coordinator.lua | 19 +++ supervisor/session/rtu.lua | 30 ++-- supervisor/session/rtu/boiler.lua | 191 -------------------------- supervisor/session/rtu/boilerv.lua | 1 - supervisor/session/rtu/emachine.lua | 131 ------------------ supervisor/session/rtu/turbine.lua | 179 ------------------------ supervisor/session/svsessions.lua | 3 + supervisor/session/unit.lua | 20 +-- supervisor/startup.lua | 2 +- supervisor/supervisor.lua | 2 +- 31 files changed, 250 insertions(+), 798 deletions(-) delete mode 100644 rtu/dev/boiler_rtu.lua delete mode 100644 rtu/dev/energymachine_rtu.lua delete mode 100644 rtu/dev/turbine_rtu.lua delete mode 100644 supervisor/session/rtu/boiler.lua delete mode 100644 supervisor/session/rtu/emachine.lua delete mode 100644 supervisor/session/rtu/turbine.lua diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index d759a93..3414753 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -21,6 +21,7 @@ local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES -- request the user to select a monitor ---@param names table available monitors +---@return boolean|string|nil local function ask_monitor(names) println("available monitors:") for i = 1, #names do @@ -71,7 +72,7 @@ function coordinator.configure_monitors(num_units) -- PRIMARY DISPLAY -- --------------------- - local iface_primary_display = settings.get("PRIMARY_DISPLAY") + local iface_primary_display = settings.get("PRIMARY_DISPLAY") ---@type boolean|string|nil if not util.table_contains(names, iface_primary_display) then println("primary display is not connected") @@ -85,7 +86,7 @@ function coordinator.configure_monitors(num_units) iface_primary_display = ask_monitor(names) end - if iface_primary_display == false then return false end + if type(iface_primary_display) ~= "string" then return false end settings.set("PRIMARY_DISPLAY", iface_primary_display) util.filter_table(names, function (x) return x ~= iface_primary_display end) @@ -175,7 +176,10 @@ 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 +function coordinator.log_comms_connecting(message) +---@diagnostic disable-next-line: return-type-mismatch + return log_dmesg(message, "COMMS", true) +end -- coordinator communications ---@param version string @@ -306,6 +310,14 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa return self.sv_linked end + -- send a unit command + ---@param unit integer unit ID + ---@param cmd CRDN_COMMANDS command + ---@param option any? optional options (like burn rate) + function public.send_command(unit, cmd, option) + _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.COMMAND_UNIT, { unit, cmd, option }) + end + -- parse a packet ---@param side string ---@param sender integer @@ -348,12 +360,13 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa end -- handle a packet - ---@param packet mgmt_frame|crdn_frame|capi_frame + ---@param packet mgmt_frame|crdn_frame|capi_frame|nil function public.handle_packet(packet) if packet ~= nil then local protocol = packet.scada_frame.protocol() if protocol == PROTOCOLS.COORD_API then +---@diagnostic disable-next-line: param-type-mismatch apisessions.handle_packet(packet) else -- check sequence number @@ -389,7 +402,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa end -- init io controller - iocontrol.init(conf) + iocontrol.init(conf, public) self.sv_linked = true else diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 2d99905..b947f8c 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -1,5 +1,9 @@ -local psil = require("scada-common.psil") -local log = require("scada-common.log") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local psil = require("scada-common.psil") +local util = require("scada-common.util") + +local CRDN_COMMANDS = comms.CRDN_COMMANDS local iocontrol = {} @@ -8,7 +12,8 @@ local io = {} -- initialize the coordinator IO controller ---@param conf facility_conf configuration -function iocontrol.init(conf) +---@param comms coord_comms comms reference +function iocontrol.init(conf, comms) io.facility = { scram = false, num_units = conf.num_units, @@ -29,10 +34,20 @@ function iocontrol.init(conf) burn_rate_cmd = 0.0, waste_control = 0, - ---@fixme debug stubs to be linked into comms later? - start = function () print("UNIT " .. i .. ": start") end, - scram = function () print("UNIT " .. i .. ": SCRAM") end, - set_burn = function (rate) print("UNIT " .. i .. ": set burn rate to " .. rate) end, + start = function () + comms.send_command(i, CRDN_COMMANDS.START) + log.debug(util.c("sent unit ", i, ": START")) + end, + + scram = function () + comms.send_command(i, CRDN_COMMANDS.SCRAM) + log.debug(util.c("sent unit ", i, ": SCRAM")) + end, + + set_burn = function (rate) + comms.send_command(i, CRDN_COMMANDS.SET_BURN, rate) + log.debug(util.c("sent unit ", i, ": SET_BURN = ", rate)) + end, reactor_ps = psil.create(), reactor_data = {}, ---@type reactor_db @@ -45,13 +60,13 @@ function iocontrol.init(conf) } for _ = 1, conf.defs[(i * 2) - 1] do - local data = {} ---@type boiler_session_db|boilerv_session_db + local data = {} ---@type 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 + local data = {} ---@type turbinev_session_db table.insert(entry.turbine_ps_tbl, psil.create()) table.insert(entry.turbine_data_tbl, data) end @@ -225,7 +240,7 @@ function iocontrol.update_statuses(statuses) unit.boiler_data_tbl[id].state = boiler[1] ---@type table unit.boiler_data_tbl[id].tanks = boiler[2] ---@type table - local data = unit.boiler_data_tbl[id] ---@type boiler_session_db|boilerv_session_db + local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db if data.state.boil_rate > 0 then unit.boiler_ps_tbl[id].publish("computed_status", 3) -- active @@ -255,7 +270,7 @@ function iocontrol.update_statuses(statuses) unit.turbine_data_tbl[id].state = turbine[1] ---@type table unit.turbine_data_tbl[id].tanks = turbine[2] ---@type table - local data = unit.turbine_data_tbl[id] ---@type turbine_session_db|turbinev_session_db + local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db if data.tanks.steam_fill >= 0.99 then unit.turbine_ps_tbl[id].publish("computed_status", 4) -- trip diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 2d9bf43..10c0ede 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.4.12" +local COORDINATOR_VERSION = "alpha-v0.4.13" local print = util.print local println = util.println @@ -66,7 +66,7 @@ ppm.mount_all() -- setup monitors local configured, monitors = coordinator.configure_monitors(config.NUM_UNITS) -if not configured then +if not configured or monitors == nil then println("boot> monitor setup failed") log.fatal("monitor configuration failed") return diff --git a/coordinator/ui/dialog.lua b/coordinator/ui/dialog.lua index ca9a8fe..4c2a522 100644 --- a/coordinator/ui/dialog.lua +++ b/coordinator/ui/dialog.lua @@ -1,6 +1,6 @@ local completion = require("cc.completion") -local util = require("scada-common.util") +local util = require("scada-common.util") local print = util.print local println = util.println @@ -9,6 +9,10 @@ local println_ts = util.println_ts local dialog = {} +-- ask the user yes or no +---@param question string +---@param default boolean +---@return boolean|nil function dialog.ask_y_n(question, default) print(question) @@ -31,6 +35,10 @@ function dialog.ask_y_n(question, default) end end +-- ask the user for an input within a set of options +---@param options table +---@param cancel string +---@return boolean|string|nil function dialog.ask_options(options, cancel) print("> ") local response = read(nil, nil, function(text) return completion.choice(text, options) end) diff --git a/graphics/element.lua b/graphics/element.lua index 9759ce5..2e4c906 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -19,8 +19,32 @@ local element = {} ---@field gframe? graphics_frame frame instead of x/y/width/height ---@field fg_bg? cpair foreground/background colors +---@alias graphics_args graphics_args_generic +---|waiting_args +---|multi_button_args +---|push_button_args +---|scram_button_args +---|spinbox_args +---|start_button_args +---|switch_button_args +---|core_map_args +---|data_indicator_args +---|hbar_args +---|icon_indicator_args +---|indicator_light_args +---|state_indicator_args +---|tristate_indicator_light_args +---|vbar_args +---|colormap_args +---|displaybox_args +---|div_args +---|pipenet_args +---|rectangle_args +---|textbox_args +---|tiling_args + -- a base graphics element, should not be created on its own ----@param args graphics_args_generic arguments +---@param args graphics_args arguments function element.new(args) local self = { id = -1, diff --git a/graphics/elements/indicators/hbar.lua b/graphics/elements/indicators/hbar.lua index 9794736..092d88e 100644 --- a/graphics/elements/indicators/hbar.lua +++ b/graphics/elements/indicators/hbar.lua @@ -19,7 +19,6 @@ local element = require("graphics.element") -- new horizontal bar ---@param args hbar_args ---@return graphics_element element, element_id id ----@return graphics_element element, element_id id local function hbar(args) -- properties/state local last_num_bars = -1 diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 5983c8a..db84d0b 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -315,7 +315,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co -- send an RPLC packet ---@param msg_type RPLC_TYPES - ---@param msg string + ---@param msg table local function _send(msg_type, msg) local s_pkt = comms.scada_packet() local r_pkt = comms.rplc_packet() @@ -329,7 +329,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co -- send a SCADA management packet ---@param msg_type SCADA_MGMT_TYPES - ---@param msg string + ---@param msg table local function _send_mgmt(msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 62c3efe..f04f0d3 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.8.2" +local R_PLC_VERSION = "beta-v0.8.3" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 57726e1..3b4cf49 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -194,7 +194,7 @@ function threads.thread__main(smem, init) while not plc_state.shutdown do local status, result = pcall(public.exec) if status == false then - log.fatal(result) + log.fatal(util.strval(result)) end -- if status is true, then we are probably exiting, so this won't matter @@ -337,7 +337,7 @@ function threads.thread__rps(smem) while not plc_state.shutdown do local status, result = pcall(public.exec) if status == false then - log.fatal(result) + log.fatal(util.strval(result)) end if not plc_state.shutdown then @@ -412,7 +412,7 @@ function threads.thread__comms_tx(smem) while not plc_state.shutdown do local status, result = pcall(public.exec) if status == false then - log.fatal(result) + log.fatal(util.strval(result)) end if not plc_state.shutdown then @@ -460,7 +460,7 @@ function threads.thread__comms_rx(smem) -- received a packet -- handle the packet (setpoints passed to update burn rate setpoint) -- (plc_state passed to check if degraded) - plc_comms.handle_packet(msg.message, setpoints, plc_state) + plc_comms.handle_packet(msg.message, plc_state, setpoints) end end @@ -486,7 +486,7 @@ function threads.thread__comms_rx(smem) while not plc_state.shutdown do local status, result = pcall(public.exec) if status == false then - log.fatal(result) + log.fatal(util.strval(result)) end if not plc_state.shutdown then @@ -610,7 +610,7 @@ function threads.thread__setpoint_control(smem) while not plc_state.shutdown do local status, result = pcall(public.exec) if status == false then - log.fatal(result) + log.fatal(util.strval(result)) end if not plc_state.shutdown then diff --git a/rtu/dev/boiler_rtu.lua b/rtu/dev/boiler_rtu.lua deleted file mode 100644 index 74924af..0000000 --- a/rtu/dev/boiler_rtu.lua +++ /dev/null @@ -1,48 +0,0 @@ -local rtu = require("rtu.rtu") - -local boiler_rtu = {} - --- create new boiler (mek 10.0) device ----@param boiler table -function boiler_rtu.new(boiler) - local unit = rtu.init_unit() - - -- discrete inputs -- - -- none - - -- coils -- - -- none - - -- input registers -- - -- build properties - unit.connect_input_reg(boiler.getBoilCapacity) - unit.connect_input_reg(boiler.getSteamCapacity) - unit.connect_input_reg(boiler.getWaterCapacity) - unit.connect_input_reg(boiler.getHeatedCoolantCapacity) - unit.connect_input_reg(boiler.getCooledCoolantCapacity) - unit.connect_input_reg(boiler.getSuperheaters) - unit.connect_input_reg(boiler.getMaxBoilRate) - -- current state - unit.connect_input_reg(boiler.getTemperature) - unit.connect_input_reg(boiler.getBoilRate) - -- tanks - unit.connect_input_reg(boiler.getSteam) - unit.connect_input_reg(boiler.getSteamNeeded) - unit.connect_input_reg(boiler.getSteamFilledPercentage) - unit.connect_input_reg(boiler.getWater) - unit.connect_input_reg(boiler.getWaterNeeded) - unit.connect_input_reg(boiler.getWaterFilledPercentage) - unit.connect_input_reg(boiler.getHeatedCoolant) - unit.connect_input_reg(boiler.getHeatedCoolantNeeded) - unit.connect_input_reg(boiler.getHeatedCoolantFilledPercentage) - unit.connect_input_reg(boiler.getCooledCoolant) - unit.connect_input_reg(boiler.getCooledCoolantNeeded) - unit.connect_input_reg(boiler.getCooledCoolantFilledPercentage) - - -- holding registers -- - -- none - - return unit.interface() -end - -return boiler_rtu diff --git a/rtu/dev/energymachine_rtu.lua b/rtu/dev/energymachine_rtu.lua deleted file mode 100644 index e08abb8..0000000 --- a/rtu/dev/energymachine_rtu.lua +++ /dev/null @@ -1,30 +0,0 @@ -local rtu = require("rtu.rtu") - -local energymachine_rtu = {} - --- create new energy machine device ----@param machine table -function energymachine_rtu.new(machine) - local unit = rtu.init_unit() - - -- discrete inputs -- - -- none - - -- coils -- - -- none - - -- input registers -- - -- build properties - unit.connect_input_reg(machine.getTotalMaxEnergy) - -- containers - unit.connect_input_reg(machine.getTotalEnergy) - unit.connect_input_reg(machine.getTotalEnergyNeeded) - unit.connect_input_reg(machine.getTotalEnergyFilledPercentage) - - -- holding registers -- - -- none - - return unit.interface() -end - -return energymachine_rtu diff --git a/rtu/dev/redstone_rtu.lua b/rtu/dev/redstone_rtu.lua index 5865552..f461fdf 100644 --- a/rtu/dev/redstone_rtu.lua +++ b/rtu/dev/redstone_rtu.lua @@ -1,4 +1,5 @@ local rtu = require("rtu.rtu") + local rsio = require("scada-common.rsio") local redstone_rtu = {} diff --git a/rtu/dev/turbine_rtu.lua b/rtu/dev/turbine_rtu.lua deleted file mode 100644 index 530080f..0000000 --- a/rtu/dev/turbine_rtu.lua +++ /dev/null @@ -1,43 +0,0 @@ -local rtu = require("rtu.rtu") - -local turbine_rtu = {} - --- create new turbine (mek 10.0) device ----@param turbine table -function turbine_rtu.new(turbine) - local unit = rtu.init_unit() - - -- discrete inputs -- - -- none - - -- coils -- - -- none - - -- input registers -- - -- build properties - unit.connect_input_reg(turbine.getBlades) - unit.connect_input_reg(turbine.getCoils) - unit.connect_input_reg(turbine.getVents) - unit.connect_input_reg(turbine.getDispersers) - unit.connect_input_reg(turbine.getCondensers) - unit.connect_input_reg(turbine.getSteamCapacity) - unit.connect_input_reg(turbine.getMaxFlowRate) - unit.connect_input_reg(turbine.getMaxProduction) - unit.connect_input_reg(turbine.getMaxWaterOutput) - -- current state - unit.connect_input_reg(turbine.getFlowRate) - unit.connect_input_reg(turbine.getProductionRate) - unit.connect_input_reg(turbine.getLastSteamInputRate) - unit.connect_input_reg(turbine.getDumpingMode) - -- tanks - unit.connect_input_reg(turbine.getSteam) - unit.connect_input_reg(turbine.getSteamNeeded) - unit.connect_input_reg(turbine.getSteamFilledPercentage) - - -- holding registers -- - -- none - - return unit.interface() -end - -return turbine_rtu diff --git a/rtu/modbus.lua b/rtu/modbus.lua index c011ede..802c2dc 100644 --- a/rtu/modbus.lua +++ b/rtu/modbus.lua @@ -20,12 +20,15 @@ function modbus.new(rtu_dev, use_parallel_read) local insert = table.insert + -- read a span of coils (digital outputs) + -- + -- returns a table of readings or a MODBUS_EXCODE error code ---@param c_addr_start integer ---@param count integer - ---@return boolean ok, table readings + ---@return boolean ok, table|MODBUS_EXCODE readings local function _1_read_coils(c_addr_start, count) local tasks = {} - local readings = {} + local readings = {} ---@type table|MODBUS_EXCODE local access_fault = false local _, coils, _, _ = self.rtu.io_count() local return_ok = ((c_addr_start + count) <= (coils + 1)) and (count > 0) @@ -66,12 +69,15 @@ function modbus.new(rtu_dev, use_parallel_read) return return_ok, readings end + -- read a span of discrete inputs (digital inputs) + -- + -- returns a table of readings or a MODBUS_EXCODE error code ---@param di_addr_start integer ---@param count integer - ---@return boolean ok, table readings + ---@return boolean ok, table|MODBUS_EXCODE readings local function _2_read_discrete_inputs(di_addr_start, count) local tasks = {} - local readings = {} + local readings = {} ---@type table|MODBUS_EXCODE local access_fault = false local discrete_inputs, _, _, _ = self.rtu.io_count() local return_ok = ((di_addr_start + count) <= (discrete_inputs + 1)) and (count > 0) @@ -112,12 +118,15 @@ function modbus.new(rtu_dev, use_parallel_read) return return_ok, readings end + -- read a span of holding registers (analog outputs) + -- + -- returns a table of readings or a MODBUS_EXCODE error code ---@param hr_addr_start integer ---@param count integer - ---@return boolean ok, table readings + ---@return boolean ok, table|MODBUS_EXCODE readings local function _3_read_multiple_holding_registers(hr_addr_start, count) local tasks = {} - local readings = {} + local readings = {} ---@type table|MODBUS_EXCODE local access_fault = false local _, _, _, hold_regs = self.rtu.io_count() local return_ok = ((hr_addr_start + count) <= (hold_regs + 1)) and (count > 0) @@ -158,12 +167,15 @@ function modbus.new(rtu_dev, use_parallel_read) return return_ok, readings end + -- read a span of input registers (analog inputs) + -- + -- returns a table of readings or a MODBUS_EXCODE error code ---@param ir_addr_start integer ---@param count integer - ---@return boolean ok, table readings + ---@return boolean ok, table|MODBUS_EXCODE readings local function _4_read_input_registers(ir_addr_start, count) local tasks = {} - local readings = {} + local readings = {} ---@type table|MODBUS_EXCODE local access_fault = false local _, _, input_regs, _ = self.rtu.io_count() local return_ok = ((ir_addr_start + count) <= (input_regs + 1)) and (count > 0) @@ -204,9 +216,10 @@ function modbus.new(rtu_dev, use_parallel_read) return return_ok, readings end + -- write a single coil (digital output) ---@param c_addr integer ---@param value any - ---@return boolean ok, MODBUS_EXCODE|nil + ---@return boolean ok, MODBUS_EXCODE local function _5_write_single_coil(c_addr, value) local response = nil local _, coils, _, _ = self.rtu.io_count() @@ -226,9 +239,10 @@ function modbus.new(rtu_dev, use_parallel_read) return return_ok, response end + -- write a single holding register (analog output) ---@param hr_addr integer ---@param value any - ---@return boolean ok, MODBUS_EXCODE|nil + ---@return boolean ok, MODBUS_EXCODE local function _6_write_single_holding_register(hr_addr, value) local response = nil local _, _, _, hold_regs = self.rtu.io_count() @@ -248,9 +262,10 @@ function modbus.new(rtu_dev, use_parallel_read) return return_ok, response end + -- write multiple coils (digital outputs) ---@param c_addr_start integer ---@param values any - ---@return boolean ok, MODBUS_EXCODE|nil + ---@return boolean ok, MODBUS_EXCODE local function _15_write_multiple_coils(c_addr_start, values) local response = nil local _, coils, _, _ = self.rtu.io_count() @@ -275,9 +290,10 @@ function modbus.new(rtu_dev, use_parallel_read) return return_ok, response end + -- write multiple holding registers (analog outputs) ---@param hr_addr_start integer ---@param values any - ---@return boolean ok, MODBUS_EXCODE|nil + ---@return boolean ok, MODBUS_EXCODE local function _16_write_multiple_holding_registers(hr_addr_start, values) local response = nil local _, _, _, hold_regs = self.rtu.io_count() @@ -403,6 +419,7 @@ function modbus.new(rtu_dev, use_parallel_read) end -- return a SERVER_DEVICE_BUSY error reply +---@param packet modbus_frame MODBUS packet frame ---@return modbus_packet reply function modbus.reply__srv_device_busy(packet) -- reply back with error flag and exception code @@ -414,6 +431,7 @@ function modbus.reply__srv_device_busy(packet) end -- return a NEG_ACKNOWLEDGE error reply +---@param packet modbus_frame MODBUS packet frame ---@return modbus_packet reply function modbus.reply__neg_ack(packet) -- reply back with error flag and exception code @@ -425,6 +443,7 @@ function modbus.reply__neg_ack(packet) end -- return a GATEWAY_PATH_UNAVAILABLE error reply +---@param packet modbus_frame MODBUS packet frame ---@return modbus_packet reply function modbus.reply__gw_unavailable(packet) -- reply back with error flag and exception code diff --git a/rtu/rtu.lua b/rtu/rtu.lua index cf69b58..4380e84 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -1,15 +1,12 @@ -local comms = require("scada-common.comms") -local ppm = require("scada-common.ppm") -local log = require("scada-common.log") -local types = require("scada-common.types") -local util = require("scada-common.util") +local comms = require("scada-common.comms") +local ppm = require("scada-common.ppm") +local log = require("scada-common.log") +local util = require("scada-common.util") local modbus = require("rtu.modbus") local rtu = {} -local rtu_t = types.rtu_t - local PROTOCOLS = comms.PROTOCOLS local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES @@ -333,6 +330,7 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog) if protocol == PROTOCOLS.MODBUS_TCP then local return_code = false +---@diagnostic disable-next-line: param-type-mismatch local reply = modbus.reply__neg_ack(packet) -- handle MODBUS instruction @@ -342,17 +340,20 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog) if unit.name == "redstone_io" then -- immediately execute redstone RTU requests +---@diagnostic disable-next-line: param-type-mismatch return_code, reply = unit.modbus_io.handle_packet(packet) if not return_code then log.warning("requested MODBUS operation failed" .. unit_dbg_tag) end else -- check validity then pass off to unit comms thread +---@diagnostic disable-next-line: param-type-mismatch return_code, reply = unit.modbus_io.check_request(packet) if return_code then -- check if there are more than 3 active transactions -- still queue the packet, but this may indicate a problem if unit.pkt_queue.length() > 3 then +---@diagnostic disable-next-line: param-type-mismatch reply = modbus.reply__srv_device_busy(packet) log.debug("queueing new request with " .. unit.pkt_queue.length() .. " transactions already in the queue" .. unit_dbg_tag) @@ -366,6 +367,7 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog) end else -- unit ID out of range? +---@diagnostic disable-next-line: param-type-mismatch reply = modbus.reply__gw_unavailable(packet) log.error("received MODBUS packet for non-existent unit") end diff --git a/rtu/startup.lua b/rtu/startup.lua index 758ee84..342cb19 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -4,28 +4,27 @@ require("/initenv").init_env() -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local ppm = require("scada-common.ppm") -local rsio = require("scada-common.rsio") -local types = require("scada-common.types") -local util = require("scada-common.util") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local ppm = require("scada-common.ppm") +local rsio = require("scada-common.rsio") +local types = require("scada-common.types") +local util = require("scada-common.util") -local config = require("rtu.config") -local modbus = require("rtu.modbus") -local rtu = require("rtu.rtu") -local threads = require("rtu.threads") +local config = require("rtu.config") +local modbus = require("rtu.modbus") +local rtu = require("rtu.rtu") +local threads = require("rtu.threads") -local redstone_rtu = require("rtu.dev.redstone_rtu") -local boiler_rtu = require("rtu.dev.boiler_rtu") -local boilerv_rtu = require("rtu.dev.boilerv_rtu") -local energymachine_rtu = require("rtu.dev.energymachine_rtu") -local envd_rtu = require("rtu.dev.envd_rtu") -local imatrix_rtu = require("rtu.dev.imatrix_rtu") -local turbine_rtu = require("rtu.dev.turbine_rtu") -local turbinev_rtu = require("rtu.dev.turbinev_rtu") +local boilerv_rtu = require("rtu.dev.boilerv_rtu") +local envd_rtu = require("rtu.dev.envd_rtu") +local imatrix_rtu = require("rtu.dev.imatrix_rtu") +local redstone_rtu = require("rtu.dev.redstone_rtu") +local sna_rtu = require("rtu.dev.sna_rtu") +local sps_rtu = require("rtu.dev.sps_rtu") +local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.7.12" +local RTU_VERSION = "beta-v0.8.0" local rtu_t = types.rtu_t @@ -219,9 +218,9 @@ local function configure() index = entry_idx, reactor = io_reactor, device = capabilities, -- use device field for redstone channels - rtu = rs_rtu, + rtu = rs_rtu, ---@type rtu_device|rtu_rs_device modbus_io = modbus.new(rs_rtu, false), - pkt_queue = nil, + pkt_queue = nil, ---@type mqueue|nil thread = nil } @@ -267,31 +266,26 @@ local function configure() local rtu_iface = nil ---@type rtu_device local rtu_type = "" - if type == "boiler" then + if type == "boilerValve" then -- boiler multiblock - rtu_type = rtu_t.boiler - rtu_iface = boiler_rtu.new(device) - elseif type == "boilerValve" then - -- boiler multiblock (10.1+) rtu_type = rtu_t.boiler_valve rtu_iface = boilerv_rtu.new(device) - elseif type == "turbine" then - -- turbine multiblock - rtu_type = rtu_t.turbine - rtu_iface = turbine_rtu.new(device) elseif type == "turbineValve" then - -- turbine multiblock (10.1+) + -- turbine multiblock rtu_type = rtu_t.turbine_valve rtu_iface = turbinev_rtu.new(device) - elseif type == "mekanismMachine" then - -- assumed to be an induction matrix multiblock, pre Mekanism 10.1 - -- also works with energy cubes - rtu_type = rtu_t.energy_machine - rtu_iface = energymachine_rtu.new(device) elseif type == "inductionPort" then - -- induction matrix multiblock (10.1+) + -- induction matrix multiblock rtu_type = rtu_t.induction_matrix rtu_iface = imatrix_rtu.new(device) + elseif type == "spsPort" then + -- SPS multiblock + rtu_type = rtu_t.sps + rtu_iface = sps_rtu.new(device) + elseif type == "solarNeutronActivator" then + -- SNA + rtu_type = rtu_t.sps + rtu_iface = sna_rtu.new(device) elseif type == "environmentDetector" then -- advanced peripherals environment detector rtu_type = rtu_t.env_detector @@ -311,9 +305,9 @@ local function configure() index = index, reactor = for_reactor, device = device, - rtu = rtu_iface, + rtu = rtu_iface, ---@type rtu_device|rtu_rs_device modbus_io = modbus.new(rtu_iface, true), - pkt_queue = mqueue.new(), + pkt_queue = mqueue.new(), ---@type mqueue|nil thread = nil } diff --git a/rtu/threads.lua b/rtu/threads.lua index e20c337..0cfcd7a 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -1,15 +1,12 @@ -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local ppm = require("scada-common.ppm") -local types = require("scada-common.types") -local util = require("scada-common.util") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local ppm = require("scada-common.ppm") +local types = require("scada-common.types") +local util = require("scada-common.util") -local boiler_rtu = require("rtu.dev.boiler_rtu") -local boilerv_rtu = require("rtu.dev.boilerv_rtu") -local energymachine_rtu = require("rtu.dev.energymachine_rtu") -local imatrix_rtu = require("rtu.dev.imatrix_rtu") -local turbine_rtu = require("rtu.dev.turbine_rtu") -local turbinev_rtu = require("rtu.dev.turbinev_rtu") +local boilerv_rtu = require("rtu.dev.boilerv_rtu") +local imatrix_rtu = require("rtu.dev.imatrix_rtu") +local turbinev_rtu = require("rtu.dev.turbinev_rtu") local modbus = require("rtu.modbus") @@ -124,16 +121,10 @@ function threads.thread__main(smem) -- found, re-link unit.device = device - if unit.type == rtu_t.boiler then - unit.rtu = boiler_rtu.new(device) - elseif unit.type == rtu_t.boiler_valve then + if unit.type == rtu_t.boiler_valve then unit.rtu = boilerv_rtu.new(device) - elseif unit.type == rtu_t.turbine then - unit.rtu = turbine_rtu.new(device) elseif unit.type == rtu_t.turbine_valve then unit.rtu = turbinev_rtu.new(device) - elseif unit.type == rtu_t.energy_machine then - unit.rtu = energymachine_rtu.new(device) elseif unit.type == rtu_t.induction_matrix then unit.rtu = imatrix_rtu.new(device) end @@ -163,7 +154,7 @@ function threads.thread__main(smem) while not rtu_state.shutdown do local status, result = pcall(public.exec) if status == false then - log.fatal(result) + log.fatal(util.strval(result)) end if not rtu_state.shutdown then @@ -235,7 +226,7 @@ function threads.thread__comms(smem) while not rtu_state.shutdown do local status, result = pcall(public.exec) if status == false then - log.fatal(result) + log.fatal(util.strval(result)) end if not rtu_state.shutdown then @@ -265,6 +256,11 @@ function threads.thread__unit_comms(smem, unit) local last_update = util.time() + if packet_queue == nil then + log.error("rtu unit thread created without a message queue, exiting...", true) + return + end + -- thread loop while true do -- check for messages in the message queue @@ -305,7 +301,7 @@ function threads.thread__unit_comms(smem, unit) while not rtu_state.shutdown do local status, result = pcall(public.exec) if status == false then - log.fatal(result) + log.fatal(util.strval(result)) end if not rtu_state.shutdown then diff --git a/scada-common/comms.lua b/scada-common/comms.lua index f0d18c1..bb5d3cb 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -57,6 +57,15 @@ local SCADA_CRDN_TYPES = { ALARM = 4 -- alarm signaling } +---@alias CRDN_COMMANDS integer +local CRDN_COMMANDS = { + SCRAM = 0, -- SCRAM the reactor + START = 1, -- start the reactor + RESET_RPS = 2, -- reset the RPS + SET_BURN = 3, -- set the burn rate + SET_WASTE = 4 -- set the waste processing mode +} + ---@alias CAPI_TYPES integer local CAPI_TYPES = { ESTABLISH = 0 -- initial greeting @@ -65,15 +74,12 @@ local CAPI_TYPES = { ---@alias RTU_UNIT_TYPES integer local RTU_UNIT_TYPES = { REDSTONE = 0, -- redstone I/O - BOILER = 1, -- boiler - BOILER_VALVE = 2, -- boiler mekanism 10.1+ - TURBINE = 3, -- turbine - TURBINE_VALVE = 4, -- turbine, mekanism 10.1+ - EMACHINE = 5, -- energy machine - IMATRIX = 6, -- induction matrix - SPS = 7, -- SPS - SNA = 8, -- SNA - ENV_DETECTOR = 9 -- environment detector + BOILER_VALVE = 1, -- boiler mekanism 10.1+ + TURBINE_VALVE = 2, -- turbine, mekanism 10.1+ + IMATRIX = 3, -- induction matrix + SPS = 4, -- SPS + SNA = 5, -- SNA + ENV_DETECTOR = 6 -- environment detector } comms.PROTOCOLS = PROTOCOLS @@ -81,8 +87,13 @@ comms.RPLC_TYPES = RPLC_TYPES comms.RPLC_LINKING = RPLC_LINKING comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES comms.SCADA_CRDN_TYPES = SCADA_CRDN_TYPES +comms.CRDN_COMMANDS = CRDN_COMMANDS +comms.CAPI_TYPES = CAPI_TYPES comms.RTU_UNIT_TYPES = RTU_UNIT_TYPES +---@alias packet scada_packet|modbus_packet|rplc_packet|mgmt_packet|crdn_packet|capi_packet +---@alias frame modbus_frame|rplc_frame|mgmt_frame|crdn_frame|capi_frame + -- generic SCADA packet object function comms.scada_packet() local self = { @@ -616,16 +627,10 @@ end function comms.rtu_t_to_unit_type(type) if type == rtu_t.redstone then return RTU_UNIT_TYPES.REDSTONE - elseif type == rtu_t.boiler then - return RTU_UNIT_TYPES.BOILER elseif type == rtu_t.boiler_valve then return RTU_UNIT_TYPES.BOILER_VALVE - elseif type == rtu_t.turbine then - return RTU_UNIT_TYPES.TURBINE elseif type == rtu_t.turbine_valve then return RTU_UNIT_TYPES.TURBINE_VALVE - elseif type == rtu_t.energy_machine then - return RTU_UNIT_TYPES.EMACHINE elseif type == rtu_t.induction_matrix then return RTU_UNIT_TYPES.IMATRIX end @@ -639,16 +644,10 @@ end function comms.advert_type_to_rtu_t(utype) if utype == RTU_UNIT_TYPES.REDSTONE then return rtu_t.redstone - elseif utype == RTU_UNIT_TYPES.BOILER then - return rtu_t.boiler elseif utype == RTU_UNIT_TYPES.BOILER_VALVE then return rtu_t.boiler_valve - elseif utype == RTU_UNIT_TYPES.TURBINE then - return rtu_t.turbine elseif utype == RTU_UNIT_TYPES.TURBINE_VALVE then return rtu_t.turbine_valve - elseif utype == RTU_UNIT_TYPES.EMACHINE then - return rtu_t.energy_machine elseif utype == RTU_UNIT_TYPES.IMATRIX then return rtu_t.induction_matrix end diff --git a/scada-common/crypto.lua b/scada-common/crypto.lua index 4f8de1c..ed75f94 100644 --- a/scada-common/crypto.lua +++ b/scada-common/crypto.lua @@ -13,8 +13,8 @@ local zero_pad = require("lockbox.padding.zero"); local stream = require("lockbox.util.stream") local array = require("lockbox.util.array") -local log = require("scada-common.log") -local util = require("scada-common.util") +local log = require("scada-common.log") +local util = require("scada-common.util") local crypto = {} @@ -71,7 +71,7 @@ end -- encrypt plaintext ---@param plaintext string ----@return string initial_value, string ciphertext +---@return table initial_value, string ciphertext function crypto.encrypt(plaintext) local start = util.time() diff --git a/scada-common/mqueue.lua b/scada-common/mqueue.lua index ed22535..22bae5d 100644 --- a/scada-common/mqueue.lua +++ b/scada-common/mqueue.lua @@ -4,7 +4,7 @@ local mqueue = {} ----@alias TYPE integer +---@alias MQ_TYPE integer local TYPE = { COMMAND = 0, DATA = 1, @@ -21,7 +21,7 @@ function mqueue.new() local remove = table.remove ---@class queue_item - ---@field qtype TYPE + ---@field qtype MQ_TYPE ---@field message any ---@class queue_data @@ -42,8 +42,8 @@ function mqueue.new() function public.ready() return #queue ~= 0 end -- push a new item onto the queue - ---@param qtype TYPE - ---@param message string + ---@param qtype MQ_TYPE + ---@param message any local function _push(qtype, message) insert(queue, { qtype = qtype, message = message }) end @@ -62,7 +62,7 @@ function mqueue.new() end -- push a packet onto the queue - ---@param packet scada_packet|modbus_packet|rplc_packet|crdn_packet|capi_packet + ---@param packet packet|frame function public.push_packet(packet) _push(TYPE.PACKET, packet) end diff --git a/scada-common/types.lua b/scada-common/types.lua index 089af7c..54b4455 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -86,11 +86,8 @@ types.TRI_FAIL = { ---@alias rtu_t string types.rtu_t = { redstone = "redstone", - boiler = "boiler", boiler_valve = "boiler_valve", - turbine = "turbine", turbine_valve = "turbine_valve", - energy_machine = "emachine", induction_matrix = "induction_matrix", sps = "sps", sna = "sna", diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 00f3a33..80fb9d5 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -8,6 +8,7 @@ local coordinator = {} local PROTOCOLS = comms.PROTOCOLS local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES +local CRDN_COMMANDS = comms.CRDN_COMMANDS local print = util.print local println = util.println @@ -163,6 +164,24 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) if pkt.type == SCADA_CRDN_TYPES.STRUCT_BUILDS then -- acknowledgement to coordinator receiving builds self.acks.builds = true + elseif pkt.type == SCADA_CRDN_TYPES.COMMAND_UNIT then + if pkt.length > 2 then + -- get command and unit id + local cmd = pkt.data[1] + local uid = pkt.data[2] + + -- continue if valid unit id + if util.is_int(uid) and uid > 0 and uid <= #self.units then + local unit = self.units[pkt.data[2]] ---@type reactor_unit + if cmd == CRDN_COMMANDS.SCRAM then + unit.scram() + end + else + log.debug(log_header .. "CRDN command unit invalid") + end + else + log.debug(log_header .. "CRDN command unit packet length mismatch") + end else log.debug(log_header .. "handler received unexpected SCADA_CRDN packet type " .. pkt.type) end diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index f8cd3f4..312c9a3 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -5,15 +5,12 @@ local rsio = require("scada-common.rsio") local util = require("scada-common.util") -- supervisor rtu sessions (svrs) -local svrs_boiler = require("supervisor.session.rtu.boiler") local svrs_boilerv = require("supervisor.session.rtu.boilerv") -local svrs_emachine = require("supervisor.session.rtu.emachine") local svrs_envd = require("supervisor.session.rtu.envd") local svrs_imatrix = require("supervisor.session.rtu.imatrix") local svrs_redstone = require("supervisor.session.rtu.redstone") local svrs_sna = require("supervisor.session.rtu.sna") local svrs_sps = require("supervisor.session.rtu.sps") -local svrs_turbine = require("supervisor.session.rtu.turbine") local svrs_turbinev = require("supervisor.session.rtu.turbinev") local rtu = {} @@ -76,7 +73,6 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) }, rs_io_q = {}, turbine_cmd_q = {}, - turbine_cmd_capable = false, units = {} } @@ -87,7 +83,6 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) self.units = {} self.rs_io_q = {} self.turbine_cmd_q = {} - self.turbine_cmd_capable = false end -- parse the recorded advertisement and create unit sub-sessions @@ -110,7 +105,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) local target_unit = self.f_units[unit_advert.reactor] ---@type reactor_unit - local u_type = unit_advert.type + local u_type = unit_advert.type ---@type integer|boolean -- validate unit advertisement @@ -137,26 +132,14 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) elseif u_type == RTU_UNIT_TYPES.REDSTONE then -- redstone unit, rs_in_q = svrs_redstone.new(self.id, i, unit_advert, self.modbus_q) - elseif u_type == RTU_UNIT_TYPES.BOILER then - -- boiler - unit = svrs_boiler.new(self.id, i, unit_advert, self.modbus_q) - target_unit.add_boiler(unit) elseif u_type == RTU_UNIT_TYPES.BOILER_VALVE then -- boiler (Mekanism 10.1+) unit = svrs_boilerv.new(self.id, i, unit_advert, self.modbus_q) - target_unit.add_boiler(unit) - elseif u_type == RTU_UNIT_TYPES.TURBINE then - -- turbine - unit = svrs_turbine.new(self.id, i, unit_advert, self.modbus_q) - target_unit.add_turbine(unit) + if type(unit) ~= "nil" then target_unit.add_boiler(unit) end elseif u_type == RTU_UNIT_TYPES.TURBINE_VALVE then -- turbine (Mekanism 10.1+) unit, tbv_in_q = svrs_turbinev.new(self.id, i, unit_advert, self.modbus_q) - target_unit.add_turbine(unit) - self.turbine_cmd_capable = true - elseif u_type == RTU_UNIT_TYPES.EMACHINE then - -- mekanism [energy] machine - unit = svrs_emachine.new(self.id, i, unit_advert, self.modbus_q) + if type(unit) ~= "nil" then target_unit.add_turbine(unit) end elseif u_type == RTU_UNIT_TYPES.IMATRIX then -- induction matrix unit = svrs_imatrix.new(self.id, i, unit_advert, self.modbus_q) @@ -202,8 +185,10 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) end else _reset_config() - local type_string = util.strval(comms.advert_type_to_rtu_t(u_type)) - log.error(log_header .. "bad advertisement: error occured while creating a unit (type is " .. type_string .. ")") + if type(u_type) == "number" then + local type_string = util.strval(comms.advert_type_to_rtu_t(u_type)) + log.error(log_header .. "bad advertisement: error occured while creating a unit (type is " .. type_string .. ")") + end break end end @@ -265,6 +250,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) if pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then if self.units[pkt.unit_id] ~= nil then local unit = self.units[pkt.unit_id] ---@type unit_session +---@diagnostic disable-next-line: param-type-mismatch unit.handle_packet(pkt) end elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua deleted file mode 100644 index 7aa25f0..0000000 --- a/supervisor/session/rtu/boiler.lua +++ /dev/null @@ -1,191 +0,0 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") - -local unit_session = require("supervisor.session.rtu.unit_session") - -local boiler = {} - -local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES -local MODBUS_FCODE = types.MODBUS_FCODE - -local TXN_TYPES = { - BUILD = 1, - STATE = 2, - TANKS = 3 -} - -local TXN_TAGS = { - "boiler.build", - "boiler.state", - "boiler.tanks" -} - -local PERIODICS = { - BUILD = 1000, - STATE = 500, - TANKS = 1000 -} - --- create a new boiler rtu session runner ----@param session_id integer ----@param unit_id integer ----@param advert rtu_advertisement ----@param out_queue mqueue -function boiler.new(session_id, unit_id, advert, out_queue) - -- type check - if advert.type ~= RTU_UNIT_TYPES.BOILER then - log.error("attempt to instantiate boiler RTU for type '" .. advert.type .. "'. this is a bug.") - return nil - end - - local log_tag = "session.rtu(" .. session_id .. ").boiler(" .. advert.index .. "): " - - local self = { - session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), - has_build = false, - periodics = { - next_build_req = 0, - next_state_req = 0, - next_tanks_req = 0 - }, - ---@class boiler_session_db - db = { - build = { - boil_cap = 0.0, - steam_cap = 0, - water_cap = 0, - hcoolant_cap = 0, - ccoolant_cap = 0, - superheaters = 0, - max_boil_rate = 0.0 - }, - state = { - temperature = 0.0, - boil_rate = 0.0 - }, - tanks = { - steam = 0, - steam_need = 0, - steam_fill = 0.0, - water = 0, - water_need = 0, - water_fill = 0.0, - hcool = {}, ---@type tank_fluid - hcool_need = 0, - hcool_fill = 0.0, - ccool = {}, ---@type tank_fluid - ccool_need = 0, - ccool_fill = 0.0 - } - } - } - - local public = self.session.get() - - -- PRIVATE FUNCTIONS -- - - -- query the build of the device - local function _request_build() - -- read input registers 1 through 7 (start = 1, count = 7) - self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 7 }) - end - - -- query the state of the device - local function _request_state() - -- read input registers 8 through 9 (start = 8, count = 2) - self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 8, 2 }) - end - - -- query the tanks of the device - local function _request_tanks() - -- read input registers 10 through 21 (start = 10, count = 12) - self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 10, 12 }) - end - - -- PUBLIC FUNCTIONS -- - - -- handle a packet - ---@param m_pkt modbus_frame - function public.handle_packet(m_pkt) - local txn_type = self.session.try_resolve(m_pkt) - if txn_type == false then - -- nothing to do - elseif txn_type == TXN_TYPES.BUILD then - -- build response - -- load in data if correct length - if m_pkt.length == 7 then - self.db.build.boil_cap = m_pkt.data[1] - self.db.build.steam_cap = m_pkt.data[2] - self.db.build.water_cap = m_pkt.data[3] - self.db.build.hcoolant_cap = m_pkt.data[4] - self.db.build.ccoolant_cap = m_pkt.data[5] - self.db.build.superheaters = m_pkt.data[6] - self.db.build.max_boil_rate = m_pkt.data[7] - self.has_build = true - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") - end - elseif txn_type == TXN_TYPES.STATE then - -- state response - -- load in data if correct length - if m_pkt.length == 2 then - self.db.state.temperature = m_pkt.data[1] - self.db.state.boil_rate = m_pkt.data[2] - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") - end - elseif txn_type == TXN_TYPES.TANKS then - -- tanks response - -- load in data if correct length - if m_pkt.length == 12 then - self.db.tanks.steam = m_pkt.data[1] - self.db.tanks.steam_need = m_pkt.data[2] - self.db.tanks.steam_fill = m_pkt.data[3] - self.db.tanks.water = m_pkt.data[4] - self.db.tanks.water_need = m_pkt.data[5] - self.db.tanks.water_fill = m_pkt.data[6] - self.db.tanks.hcool = m_pkt.data[7] - self.db.tanks.hcool_need = m_pkt.data[8] - self.db.tanks.hcool_fill = m_pkt.data[9] - self.db.tanks.ccool = m_pkt.data[10] - self.db.tanks.ccool_need = m_pkt.data[11] - self.db.tanks.ccool_fill = m_pkt.data[12] - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") - end - elseif txn_type == nil then - log.error(log_tag .. "unknown transaction reply") - else - log.error(log_tag .. "unknown transaction type " .. txn_type) - end - end - - -- update this runner - ---@param time_now integer milliseconds - function public.update(time_now) - if not self.has_build and self.periodics.next_build_req <= time_now then - _request_build() - self.periodics.next_build_req = time_now + PERIODICS.BUILD - end - - if self.periodics.next_state_req <= time_now then - _request_state() - self.periodics.next_state_req = time_now + PERIODICS.STATE - end - - if self.periodics.next_tanks_req <= time_now then - _request_tanks() - self.periodics.next_tanks_req = time_now + PERIODICS.TANKS - end - - self.session.post_update() - end - - -- get the unit session database - function public.get_db() return self.db end - - return public -end - -return boiler diff --git a/supervisor/session/rtu/boilerv.lua b/supervisor/session/rtu/boilerv.lua index bea740a..a7cea55 100644 --- a/supervisor/session/rtu/boilerv.lua +++ b/supervisor/session/rtu/boilerv.lua @@ -1,7 +1,6 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local types = require("scada-common.types") -local util = require("scada-common.util") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/emachine.lua b/supervisor/session/rtu/emachine.lua deleted file mode 100644 index e31c2af..0000000 --- a/supervisor/session/rtu/emachine.lua +++ /dev/null @@ -1,131 +0,0 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") - -local unit_session = require("supervisor.session.rtu.unit_session") - -local emachine = {} - -local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES -local MODBUS_FCODE = types.MODBUS_FCODE - -local TXN_TYPES = { - BUILD = 1, - STORAGE = 2 -} - -local TXN_TAGS = { - "emachine.build", - "emachine.storage" -} - -local PERIODICS = { - BUILD = 1000, - STORAGE = 500 -} - --- create a new energy machine rtu session runner ----@param session_id integer ----@param unit_id integer ----@param advert rtu_advertisement ----@param out_queue mqueue -function emachine.new(session_id, unit_id, advert, out_queue) - -- type check - if advert.type ~= RTU_UNIT_TYPES.EMACHINE then - log.error("attempt to instantiate emachine RTU for type '" .. advert.type .. "'. this is a bug.") - return nil - end - - local log_tag = "session.rtu(" .. session_id .. ").emachine(" .. advert.index .. "): " - - local self = { - session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), - has_build = false, - periodics = { - next_build_req = 0, - next_storage_req = 0 - }, - ---@class emachine_session_db - db = { - build = { - max_energy = 0 - }, - storage = { - energy = 0, - energy_need = 0, - energy_fill = 0.0 - } - } - } - - local public = self.session.get() - - -- PRIVATE FUNCTIONS -- - - -- query the build of the device - local function _request_build() - -- read input register 1 (start = 1, count = 1) - self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 1 }) - end - - -- query the state of the energy storage - local function _request_storage() - -- read input registers 2 through 4 (start = 2, count = 3) - self.session.send_request(TXN_TYPES.STORAGE, MODBUS_FCODE.READ_INPUT_REGS, { 2, 3 }) - end - - -- PUBLIC FUNCTIONS -- - - -- handle a packet - ---@param m_pkt modbus_frame - function public.handle_packet(m_pkt) - local txn_type = self.session.try_resolve(m_pkt) - if txn_type == false then - -- nothing to do - elseif txn_type == TXN_TYPES.BUILD then - -- build response - if m_pkt.length == 1 then - self.db.build.max_energy = m_pkt.data[1] - self.has_build = true - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") - end - elseif txn_type == TXN_TYPES.STORAGE then - -- storage response - if m_pkt.length == 3 then - self.db.storage.energy = m_pkt.data[1] - self.db.storage.energy_need = m_pkt.data[2] - self.db.storage.energy_fill = m_pkt.data[3] - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") - end - elseif txn_type == nil then - log.error(log_tag .. "unknown transaction reply") - else - log.error(log_tag .. "unknown transaction type " .. txn_type) - end - end - - -- update this runner - ---@param time_now integer milliseconds - function public.update(time_now) - if not self.has_build and self.periodics.next_build_req <= time_now then - _request_build() - self.periodics.next_build_req = time_now + PERIODICS.BUILD - end - - if self.periodics.next_storage_req <= time_now then - _request_storage() - self.periodics.next_storage_req = time_now + PERIODICS.STORAGE - end - - self.session.post_update() - end - - -- get the unit session database - function public.get_db() return self.db end - - return public -end - -return emachine diff --git a/supervisor/session/rtu/turbine.lua b/supervisor/session/rtu/turbine.lua deleted file mode 100644 index 7fc58f8..0000000 --- a/supervisor/session/rtu/turbine.lua +++ /dev/null @@ -1,179 +0,0 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") - -local unit_session = require("supervisor.session.rtu.unit_session") - -local turbine = {} - -local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES -local DUMPING_MODE = types.DUMPING_MODE -local MODBUS_FCODE = types.MODBUS_FCODE - -local TXN_TYPES = { - BUILD = 1, - STATE = 2, - TANKS = 3 -} - -local TXN_TAGS = { - "turbine.build", - "turbine.state", - "turbine.tanks" -} - -local PERIODICS = { - BUILD = 1000, - STATE = 500, - TANKS = 1000 -} - --- create a new turbine rtu session runner ----@param session_id integer ----@param unit_id integer ----@param advert rtu_advertisement ----@param out_queue mqueue -function turbine.new(session_id, unit_id, advert, out_queue) - -- type check - if advert.type ~= RTU_UNIT_TYPES.TURBINE then - log.error("attempt to instantiate turbine RTU for type '" .. advert.type .. "'. this is a bug.") - return nil - end - - local log_tag = "session.rtu(" .. session_id .. ").turbine(" .. advert.index .. "): " - - local self = { - session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), - has_build = false, - periodics = { - next_build_req = 0, - next_state_req = 0, - next_tanks_req = 0 - }, - ---@class turbine_session_db - db = { - build = { - blades = 0, - coils = 0, - vents = 0, - dispersers = 0, - condensers = 0, - steam_cap = 0, - max_flow_rate = 0, - max_production = 0, - max_water_output = 0 - }, - state = { - flow_rate = 0, - prod_rate = 0, - steam_input_rate = 0, - dumping_mode = DUMPING_MODE.IDLE ---@type DUMPING_MODE - }, - tanks = { - steam = 0, - steam_need = 0, - steam_fill = 0.0 - } - } - } - - local public = self.session.get() - - -- PRIVATE FUNCTIONS -- - - -- query the build of the device - local function _request_build() - -- read input registers 1 through 9 (start = 1, count = 9) - self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 9 }) - end - - -- query the state of the device - local function _request_state() - -- read input registers 10 through 13 (start = 10, count = 4) - self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 10, 4 }) - end - - -- query the tanks of the device - local function _request_tanks() - -- read input registers 14 through 16 (start = 14, count = 3) - self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 14, 3 }) - end - - -- PUBLIC FUNCTIONS -- - - -- handle a packet - ---@param m_pkt modbus_frame - function public.handle_packet(m_pkt) - local txn_type = self.session.try_resolve(m_pkt) - if txn_type == false then - -- nothing to do - elseif txn_type == TXN_TYPES.BUILD then - -- build response - if m_pkt.length == 9 then - self.db.build.blades = m_pkt.data[1] - self.db.build.coils = m_pkt.data[2] - self.db.build.vents = m_pkt.data[3] - self.db.build.dispersers = m_pkt.data[4] - self.db.build.condensers = m_pkt.data[5] - self.db.build.steam_cap = m_pkt.data[6] - self.db.build.max_flow_rate = m_pkt.data[7] - self.db.build.max_production = m_pkt.data[8] - self.db.build.max_water_output = m_pkt.data[9] - self.has_build = true - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") - end - elseif txn_type == TXN_TYPES.STATE then - -- state response - if m_pkt.length == 4 then - self.db.state.flow_rate = m_pkt.data[1] - self.db.state.prod_rate = m_pkt.data[2] - self.db.state.steam_input_rate = m_pkt.data[3] - self.db.state.dumping_mode = m_pkt.data[4] - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") - end - elseif txn_type == TXN_TYPES.TANKS then - -- tanks response - if m_pkt.length == 3 then - self.db.tanks.steam = m_pkt.data[1] - self.db.tanks.steam_need = m_pkt.data[2] - self.db.tanks.steam_fill = m_pkt.data[3] - else - log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") - end - elseif txn_type == nil then - log.error(log_tag .. "unknown transaction reply") - else - log.error(log_tag .. "unknown transaction type " .. txn_type) - end - end - - -- update this runner - ---@param time_now integer milliseconds - function public.update(time_now) - if not self.has_build and self.periodics.next_build_req <= time_now then - _request_build() - self.periodics.next_build_req = time_now + PERIODICS.BUILD - end - - if self.periodics.next_state_req <= time_now then - _request_state() - self.periodics.next_state_req = time_now + PERIODICS.STATE - end - - if self.periodics.next_tanks_req <= time_now then - _request_tanks() - self.periodics.next_tanks_req = time_now + PERIODICS.TANKS - end - - self.session.post_update() - end - - -- get the unit session database - function public.get_db() return self.db end - - return public -end - -return turbine diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index f01a2f8..764e5bd 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -147,6 +147,7 @@ end ---@return rtu_session_struct|nil function svsessions.find_rtu_session(remote_port) -- check RTU sessions +---@diagnostic disable-next-line: return-type-mismatch return _find_session(self.rtu_sessions, remote_port) end @@ -155,6 +156,7 @@ end ---@return plc_session_struct|nil function svsessions.find_plc_session(remote_port) -- check PLC sessions +---@diagnostic disable-next-line: return-type-mismatch return _find_session(self.plc_sessions, remote_port) end @@ -176,6 +178,7 @@ end ---@return nil function svsessions.find_coord_session(remote_port) -- check coordinator sessions +---@diagnostic disable-next-line: return-type-mismatch return _find_session(self.coord_sessions, remote_port) end diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 95fa035..bc1d544 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -1,6 +1,6 @@ -local types = require "scada-common.types" -local util = require "scada-common.util" -local log = require "scada-common.log" +local types = require("scada-common.types") +local util = require("scada-common.util") +local log = require("scada-common.log") local unit = {} @@ -204,7 +204,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- go through boilers for stats and online for i = 1, #self.boilers do local session = self.boilers[i] ---@type unit_session - local boiler = session.get_db() ---@type boiler_session_db + local boiler = session.get_db() ---@type boilerv_session_db total_boil_rate = total_boil_rate + boiler.state.boil_rate boiler_steam_dt_sum = _get_dt(DT_KEYS.BoilerSteam .. self.boilers[i].get_device_idx()) @@ -221,7 +221,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) for i = 1, #self.boilers do local boiler = self.boilers[i] ---@type unit_session local idx = boiler.get_device_idx() - local db = boiler.get_db() ---@type boiler_session_db + local db = boiler.get_db() ---@type boilerv_session_db if r_db.mek_status.status then self.db.annunciator.HeatingRateLow[idx] = db.state.boil_rate == 0 @@ -240,7 +240,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) for i = 1, #self.boilers do local boiler = self.boilers[i] ---@type unit_session local idx = boiler.get_device_idx() - local db = boiler.get_db() ---@type boiler_session_db + local db = boiler.get_db() ---@type boilerv_session_db local gaining_hc = _get_dt(DT_KEYS.BoilerHCool .. idx) > 0 or db.tanks.hcool_fill == 1 @@ -267,7 +267,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- go through turbines for stats and online for i = 1, #self.turbines do local session = self.turbines[i] ---@type unit_session - local turbine = session.get_db() ---@type turbine_session_db + local turbine = session.get_db() ---@type turbinev_session_db total_flow_rate = total_flow_rate + turbine.state.flow_rate total_input_rate = total_input_rate + turbine.state.steam_input_rate @@ -285,7 +285,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- check if steam dumps are open for i = 1, #self.turbines do local turbine = self.turbines[i] ---@type unit_session - local db = turbine.get_db() ---@type turbine_session_db + local db = turbine.get_db() ---@type turbinev_session_db local idx = turbine.get_device_idx() if db.state.dumping_mode == DUMPING_MODE.IDLE then @@ -300,7 +300,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- check if turbines are at max speed but not keeping up for i = 1, #self.turbines do local turbine = self.turbines[i] ---@type unit_session - local db = turbine.get_db() ---@type turbine_session_db + local db = turbine.get_db() ---@type turbinev_session_db local idx = turbine.get_device_idx() self.db.annunciator.TurbineOverSpeed[idx] = (db.state.flow_rate == db.build.max_flow_rate) and (_get_dt(DT_KEYS.TurbineSteam .. idx) > 0) @@ -316,7 +316,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) ]]-- for i = 1, #self.turbines do local turbine = self.turbines[i] ---@type unit_session - local db = turbine.get_db() ---@type turbine_session_db + local db = turbine.get_db() ---@type turbinev_session_db local has_steam = db.state.steam_input_rate > 0 or db.tanks.steam_fill > 0.01 self.db.annunciator.TurbineTrip[turbine.get_device_idx()] = has_steam and db.state.flow_rate == 0 diff --git a/supervisor/startup.lua b/supervisor/startup.lua index c7d4072..5378f83 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.5.11" +local SUPERVISOR_VERSION = "beta-v0.5.12" local print = util.print local println = util.println diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 3aa9f9c..50ead6d 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -164,7 +164,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen end -- handle a packet - ---@param packet modbus_frame|rplc_frame|mgmt_frame|crdn_frame + ---@param packet modbus_frame|rplc_frame|mgmt_frame|crdn_frame|nil function public.handle_packet(packet) if packet ~= nil then local l_port = packet.scada_frame.local_port() From a87e557d2d53d3ac0f597133f6702fea20c9efbf Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 21 Sep 2022 17:30:20 -0400 Subject: [PATCH 397/587] updated readme, removed #29 from known issues due to updating to requiring 10.1+ --- README.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d0a34c1..db4ee4d 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,22 @@ # cc-mek-scada Configurable ComputerCraft SCADA system for multi-reactor control of Mekanism fission reactors with a GUI, automatic safety features, waste processing control, and more! -This requires CC: Tweaked and Mekanism v10.0+ (10.1 recommended for full feature set). +Mod Requirements: +- CC: Tweaked +- Mekanism v10.1+ + +Mod Recommendations: +- Advanced Peripherals (adds the capability to detect environmental radiation levels) + +v10.1+ is required due the complete support of CC:Tweaked added in Mekanism v10.1 + +There was also an apparent bug with boilers disconnecting and reconnecting when active in my test world on 10.0.24, so it may not even have been an option to fully implement this with support for 10.0. ## [SCADA](https://en.wikipedia.org/wiki/SCADA) > Supervisory control and data acquisition (SCADA) is a control system architecture comprising computers, networked data communications and graphical user interfaces for high-level supervision of machines and processes. It also covers sensors and other devices, such as programmable logic controllers, which interface with process plant or machinery. -This project implements concepts of a SCADA system in ComputerCraft (because why not? ..okay don't answer that). I recommend reviewing that linked wikipedia page on SCADA if you want to understand the concepts used here. +This project implements concepts of a SCADA system in ComputerCraft (because why not? ..okay don't answer that). I recommend reviewing that linked wikipedia page on SCADA if you *want* to understand the concepts used here. ![Architecture](https://upload.wikimedia.org/wikipedia/commons/thumb/1/10/Functional_levels_of_a_Distributed_Control_System.svg/1000px-Functional_levels_of_a_Distributed_Control_System.svg.png) @@ -35,7 +44,7 @@ The RTU control code is relatively unique, as instead of having instructions be ### PLCs -PLCs are advanced devices that allow for both reporting and control to/from the SCADA system in addition to programed behaviors independent of the SCADA system. Currently there is only one type of PLC, and that is the reactor PLC. This is responsible for reporting on and controlling the reactor as a part of the SCADA system, and independently regulating the safety of the reactor. It checks the status for multiple hazard scenarios and shuts down the reactor if any condition is satisfied. +PLCs are advanced devices that allow for both reporting and control to/from the SCADA system in addition to programed behaviors independent of the SCADA system. Currently there is only one type of PLC, and that is the reactor PLC. This is responsible for reporting on and controlling the reactor as a part of the SCADA system, and independently regulating the safety of the reactor. It checks the status for multiple hazard scenarios and shuts down the reactor if any condition is met. There can and should only be one of these per reactor. A single Advanced Computer will act as the PLC, with either a direct connection (physical contact) or a wired modem connection to the reactor logic port. @@ -57,5 +66,4 @@ The only other possible security mitigation for commanding (no effect on monitor ## Known Issues -GitHub issue \#29: -It appears that with Mekanism 10.0, a boiler peripheral may rapidly disconnect/reconnect constantly while running. This will prevent that RTU from operating correctly while also filling up the log file. This may be due to a very specific version interaction of CC: Tweaked and Mekansim, so you are welcome to try this on Mekanism 10.0 servers, but do be aware it may not work. +None yet since the switch to requiring 10.1+! From 50be7f9ca2975e6328d645c9e291a95f11f0cef2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 22 Sep 2022 20:42:06 -0400 Subject: [PATCH 398/587] #97 fixed issue where traffic on other channels gets processed if channels are left open --- coordinator/coordinator.lua | 35 ++++++++++++++++++----------------- coordinator/startup.lua | 2 +- reactor-plc/plc.lua | 21 ++++++++++----------- reactor-plc/startup.lua | 2 +- rtu/rtu.lua | 21 ++++++++++----------- rtu/startup.lua | 2 +- supervisor/startup.lua | 2 +- supervisor/supervisor.lua | 24 ++++++++---------------- 8 files changed, 50 insertions(+), 59 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 3414753..22e79d9 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -202,19 +202,14 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa -- PRIVATE FUNCTIONS -- - -- open all channels - local function _open_channels() - if not self.modem.isOpen(sv_listen) then - self.modem.open(sv_listen) - end - - if not self.modem.isOpen(api_listen) then - self.modem.open(api_listen) - end + -- configure modem channels + local function _conf_channels() + self.modem.closeAll() + self.modem.open(sv_listen) + self.modem.open(api_listen) end - -- open at construct time - _open_channels() + _conf_channels() -- send a packet to the supervisor ---@param msg_type SCADA_MGMT_TYPES|SCADA_CRDN_TYPES @@ -256,7 +251,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa ---@diagnostic disable-next-line: redefined-local function public.reconnect_modem(modem) self.modem = modem - _open_channels() + _conf_channels() end -- close the connection to the server @@ -364,11 +359,16 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa function public.handle_packet(packet) if packet ~= nil then local protocol = packet.scada_frame.protocol() + local l_port = packet.scada_frame.local_port() - if protocol == PROTOCOLS.COORD_API then + if l_port == api_listen then + if protocol == PROTOCOLS.COORD_API then ---@diagnostic disable-next-line: param-type-mismatch - apisessions.handle_packet(packet) - else + apisessions.handle_packet(packet) + else + log.debug("illegal packet type " .. protocol .. " on api listening channel", true) + end + elseif l_port == sv_listen then -- check sequence number if self.sv_r_seq_num == nil then self.sv_r_seq_num = packet.scada_frame.seq_num() @@ -456,9 +456,10 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa log.warning("received unknown SCADA_MGMT packet type " .. packet.type) end else - -- should be unreachable assuming packet is from parse_packet() - log.error("illegal packet type " .. protocol, true) + log.debug("illegal packet type " .. protocol .. " on supervisor listening channel", true) end + else + log.debug("received packet on unconfigured channel " .. l_port, true) end end end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 10c0ede..3590a39 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.4.13" +local COORDINATOR_VERSION = "alpha-v0.4.14" local print = util.print local println = util.println diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index db84d0b..b56fd4c 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -303,14 +303,17 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co max_burn_rate = nil } - ---@class plc_comms - local public = {} - - -- open modem - if not self.modem.isOpen(self.l_port) then + -- configure modem channels + local function _conf_channels() + self.modem.closeAll() self.modem.open(self.l_port) end + _conf_channels() + + ---@class plc_comms + local public = {} + -- PRIVATE FUNCTIONS -- -- send an RPLC packet @@ -484,11 +487,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co ---@diagnostic disable-next-line: redefined-local function public.reconnect_modem(modem) self.modem = modem - - -- open modem - if not self.modem.isOpen(self.l_port) then - self.modem.open(self.l_port) - end + _conf_channels() end -- reconnect a newly connected reactor @@ -605,7 +604,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co ---@param plc_state plc_state ---@param setpoints setpoints function public.handle_packet(packet, plc_state, setpoints) - if packet ~= nil then + if packet ~= nil and packet.scada_frame.local_port() == self.l_port then -- check sequence number if self.r_seq_num == nil then self.r_seq_num = packet.scada_frame.seq_num() diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index f04f0d3..4cc2658 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.8.3" +local R_PLC_VERSION = "beta-v0.8.4" local print = util.print local println = util.println diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 4380e84..c427f54 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -175,16 +175,19 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog) conn_watchdog = conn_watchdog } + -- configure modem channels + local function _conf_channels() + self.modem.closeAll() + self.modem.open(self.l_port) + end + + _conf_channels() + ---@class rtu_comms local public = {} local insert = table.insert - -- open modem - if not self.modem.isOpen(self.l_port) then - self.modem.open(self.l_port) - end - -- PRIVATE FUNCTIONS -- -- send a scada management packet @@ -223,11 +226,7 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog) ---@diagnostic disable-next-line: redefined-local function public.reconnect_modem(modem) self.modem = modem - - -- open modem - if not self.modem.isOpen(self.l_port) then - self.modem.open(self.l_port) - end + _conf_channels() end -- unlink from the server @@ -312,7 +311,7 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog) ---@param units table ---@param rtu_state rtu_state function public.handle_packet(packet, units, rtu_state) - if packet ~= nil then + if packet ~= nil and packet.scada_frame.local_port() == self.l_port then -- check sequence number if self.r_seq_num == nil then self.r_seq_num = packet.scada_frame.seq_num() diff --git a/rtu/startup.lua b/rtu/startup.lua index 342cb19..422ee5e 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -24,7 +24,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.8.0" +local RTU_VERSION = "beta-v0.8.1" local rtu_t = types.rtu_t diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 5378f83..0acef53 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.5.12" +local SUPERVISOR_VERSION = "beta-v0.5.13" local print = util.print local println = util.println diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 50ead6d..873c3e1 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -9,12 +9,9 @@ local supervisor = {} local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES local RPLC_LINKING = comms.RPLC_LINKING -local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES -local SESSION_TYPE = svsessions.SESSION_TYPE - local print = util.print local println = util.println local print_ts = util.print_ts @@ -42,19 +39,14 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen -- PRIVATE FUNCTIONS -- - -- open all channels - local function _open_channels() - if not self.modem.isOpen(self.dev_listen) then - self.modem.open(self.dev_listen) - end - - if not self.modem.isOpen(self.coord_listen) then - self.modem.open(self.coord_listen) - end + -- configure modem channels + local function _conf_channels() + self.modem.closeAll() + self.modem.open(self.dev_listen) + self.modem.open(self.coord_listen) end - -- open at construct time - _open_channels() + _conf_channels() -- link modem to svsessions svsessions.init(self.modem, num_reactors, cooling_conf) @@ -113,7 +105,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen function public.reconnect_modem(modem) self.modem = modem svsessions.relink_modem(self.modem) - _open_channels() + _conf_channels() end -- parse a packet @@ -292,7 +284,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen log.debug("illegal packet type " .. protocol .. " on coordinator listening channel") end else - log.warning("received packet on unused channel " .. l_port) + log.warning("received packet on unconfigured channel " .. l_port) end end end From 4f7775ccb61cd5e35d750a61db844624f3144b6b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 22 Sep 2022 21:31:07 -0400 Subject: [PATCH 399/587] check for table type before checking length, added power conversion/formatting helpers --- .vscode/settings.json | 3 +- coordinator/startup.lua | 2 +- coordinator/ui/components/turbine.lua | 5 ++- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- scada-common/comms.lua | 8 +++- scada-common/util.lua | 53 ++++++++++++++------------- supervisor/startup.lua | 2 +- 8 files changed, 43 insertions(+), 34 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1a28f3c..46b3a86 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,6 +12,7 @@ "settings", "window", "read", - "periphemu" + "periphemu", + "mekanismEnergyHelper" ] } diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 3590a39..47c1dc8 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.4.14" +local COORDINATOR_VERSION = "alpha-v0.4.15" local print = util.print local println = util.println diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua index b885966..119caf2 100644 --- a/coordinator/ui/components/turbine.lua +++ b/coordinator/ui/components/turbine.lua @@ -1,4 +1,5 @@ local core = require("graphics.core") +local util = require("scada-common.util") local style = require("coordinator.ui.style") @@ -22,11 +23,11 @@ local function new_view(root, x, y, ps) local lu_col = cpair(colors.gray, colors.gray) local status = StateIndicator{parent=turbine,x=8,y=1,states=style.turbine.states,value=1,min_width=10} - local prod_rate = DataIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",unit="MFE",format="%10.2f",value=0,width=16,fg_bg=text_fg_bg} + local prod_rate = DataIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",unit="FE",format="%10.2f",value=0,width=16,fg_bg=text_fg_bg} local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=0,commas=true,width=16,fg_bg=text_fg_bg} ps.subscribe("computed_status", status.update) - ps.subscribe("prod_rate", prod_rate.update) + ps.subscribe("prod_rate", function (val) prod_rate.update(util.joules_to_fe(val)) end) ps.subscribe("flow_rate", flow_rate.update) local steam = VerticalBar{parent=turbine,x=2,y=1,fg_bg=cpair(colors.white,colors.gray),height=5,width=2} diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 4cc2658..5d0b6e5 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.8.4" +local R_PLC_VERSION = "beta-v0.8.5" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index 422ee5e..7e6978d 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -24,7 +24,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.8.1" +local RTU_VERSION = "beta-v0.8.2" local rtu_t = types.rtu_t diff --git a/scada-common/comms.lua b/scada-common/comms.lua index bb5d3cb..352e77f 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -143,8 +143,12 @@ function comms.scada_packet() if #self.raw >= 3 then self.seq_num = self.raw[1] self.protocol = self.raw[2] - self.length = #self.raw[3] - self.payload = self.raw[3] + + -- element 3 must be a table + if type(self.raw[3]) == "table" then + self.length = #self.raw[3] + self.payload = self.raw[3] + end end self.valid = type(self.seq_num) == "number" and diff --git a/scada-common/util.lua b/scada-common/util.lua index 68957f9..aa1e385 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -273,34 +273,37 @@ end -- MEKANISM POWER -- --- function util.kFE(fe) return fe / 1000.0 end --- function util.MFE(fe) return fe / 1000000.0 end --- function util.GFE(fe) return fe / 1000000000.0 end --- function util.TFE(fe) return fe / 1000000000000.0 end +-- convert Joules to FE +---@param J number Joules +---@return number FE Forge Energy +function util.joules_to_fe(J) return mekanismEnergyHelper.joulesToFE(J) end --- -- FLOATING POINT PRINTS -- +-- convert FE to Joules +---@param FE number Forge Energy +---@return number J Joules +function util.fe_to_joules(FE) return mekanismEnergyHelper.feToJoules(FE) end --- local function fractional_1s(number) --- return number == math.round(number) --- end +local function kFE(fe) return fe / 1000.0 end +local function MFE(fe) return fe / 1000000.0 end +local function GFE(fe) return fe / 1000000000.0 end +local function TFE(fe) return fe / 1000000000000.0 end --- local function fractional_10ths(number) --- number = number * 10 --- return number == math.round(number) --- end - --- local function fractional_100ths(number) --- number = number * 100 --- return number == math.round(number) --- end - --- function util.power_format(fe) --- if fe < 1000 then --- return string.format("%.2f FE", fe) --- elseif fe < 1000000 then --- return string.format("%.3f kFE", kFE(fe)) --- end --- end +-- format a power value into XXX.XX UNIT format (FE, kFE, MFE, GFE, TFE) +---@param fe number forge energy value +---@return string str formatted string +function util.power_format(fe) + if fe < 1000 then + return string.format("%.2f FE", fe) + elseif fe < 1000000 then + return string.format("%.2f kFE", kFE(fe)) + elseif fe < 1000000000 then + return string.format("%.2f MFE", MFE(fe)) + elseif fe < 1000000000000 then + return string.format("%.2f GFE", GFE(fe)) + else + return string.format("%.2f TFE", TFE(fe)) + end +end -- WATCHDOG -- diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 0acef53..398266e 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.5.13" +local SUPERVISOR_VERSION = "beta-v0.5.14" local print = util.print local println = util.println From 7a90ea7e4ebc7ea5a3d35c2ca707201bbe8caf4f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 29 Sep 2022 11:02:03 -0400 Subject: [PATCH 400/587] #87 check if the reactor is active on startup/reconnect before scram'ing, rps now ignores scram errors if the error is due to the reactor being inactive --- reactor-plc/plc.lua | 6 +++++- reactor-plc/startup.lua | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index b56fd4c..6eea5c9 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -18,6 +18,10 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts +-- I sure hope the devs don't change this error message, not that it would have safety implications +-- I wish they didn't change it to error on SCRAM calls if the reactor was already inactive +local PCALL_SCRAM_MSG = "pcall: Scram requires the reactor to be active." + --- RPS: Reactor Protection System --- --- identifies dangerous states and SCRAMs reactor if warranted @@ -169,7 +173,7 @@ function plc.rps_init(reactor) log.info("RPS: reactor SCRAM") self.reactor.scram() - if self.reactor.__p_is_faulted() then + if self.reactor.__p_is_faulted() and (self.reactor.__p_last_fault() ~= PCALL_SCRAM_MSG) then log.error("RPS: failed reactor SCRAM") return false else diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 5d0b6e5..0562f39 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.8.5" +local R_PLC_VERSION = "beta-v0.8.6" local print = util.print local println = util.println @@ -114,7 +114,7 @@ if __shared_memory.networked and smem_dev.modem == nil then println("boot> wireless modem not found") log.warning("no wireless modem on startup") - if smem_dev.reactor ~= nil then + if smem_dev.reactor ~= nil and smem_dev.reactor.getStatus() then smem_dev.reactor.scram() end @@ -127,7 +127,7 @@ end local function init() if plc_state.init_ok then -- just booting up, no fission allowed (neutrons stay put thanks) - smem_dev.reactor.scram() + if smem_dev.reactor.getStatus() then smem_dev.reactor.scram() end -- init reactor protection system smem_sys.rps = plc.rps_init(smem_dev.reactor) From 1b553ad495ca286e97589334f3a673117d8a1187 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 30 Sep 2022 17:33:35 -0400 Subject: [PATCH 401/587] #83 additional reactor structure fields, bugfix to rps alarm on sv, removed spam-prone rps error messages --- reactor-plc/plc.lua | 65 ++++++++++++++++++++++---------------- reactor-plc/startup.lua | 2 +- supervisor/session/plc.lua | 32 +++++++++++++------ supervisor/startup.lua | 2 +- 4 files changed, 61 insertions(+), 40 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 6eea5c9..78045af 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -22,6 +22,16 @@ local println_ts = util.println_ts -- I wish they didn't change it to error on SCRAM calls if the reactor was already inactive local PCALL_SCRAM_MSG = "pcall: Scram requires the reactor to be active." +-- RPS SAFETY CONSTANTS + +local MAX_DAMAGE_PERCENT = 90 +local MAX_DAMAGE_TEMPERATURE = 1200 +local MIN_COOLANT_FILL = 0.02 +local MAX_WASTE_FILL = 0.8 +local MAX_HEATED_COLLANT_FILL = 0.95 + +-- END RPS SAFETY CONSTANTS + --- RPS: Reactor Protection System --- --- identifies dangerous states and SCRAMs reactor if warranted @@ -70,11 +80,10 @@ function plc.rps_init(reactor) local damage_percent = self.reactor.getDamagePercent() if damage_percent == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log.error("RPS: failed to check reactor damage") _set_fault() self.state[state_keys.dmg_crit] = false else - self.state[state_keys.dmg_crit] = damage_percent >= 100 + self.state[state_keys.dmg_crit] = damage_percent >= MAX_DAMAGE_PERCENT end end @@ -84,11 +93,10 @@ function plc.rps_init(reactor) local temp = self.reactor.getTemperature() if temp == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log.error("RPS: failed to check reactor temperature") _set_fault() self.state[state_keys.high_temp] = false else - self.state[state_keys.high_temp] = temp >= 1200 + self.state[state_keys.high_temp] = temp >= MAX_DAMAGE_TEMPERATURE end end @@ -97,11 +105,10 @@ function plc.rps_init(reactor) local coolant_filled = self.reactor.getCoolantFilledPercentage() if coolant_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log.error("RPS: failed to check reactor coolant level") _set_fault() self.state[state_keys.no_coolant] = false else - self.state[state_keys.no_coolant] = coolant_filled < 0.02 + self.state[state_keys.no_coolant] = coolant_filled < MIN_COOLANT_FILL end end @@ -110,11 +117,10 @@ function plc.rps_init(reactor) local w_filled = self.reactor.getWasteFilledPercentage() if w_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log.error("RPS: failed to check reactor waste level") _set_fault() self.state[state_keys.ex_waste] = false else - self.state[state_keys.ex_waste] = w_filled > 0.8 + self.state[state_keys.ex_waste] = w_filled > MAX_WASTE_FILL end end @@ -123,11 +129,10 @@ function plc.rps_init(reactor) local hc_filled = self.reactor.getHeatedCoolantFilledPercentage() if hc_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log.error("RPS: failed to check reactor heated coolant level") _set_fault() self.state[state_keys.ex_hcoolant] = false else - self.state[state_keys.ex_hcoolant] = hc_filled > 0.95 + self.state[state_keys.ex_hcoolant] = hc_filled > MAX_HEATED_COLLANT_FILL end end @@ -136,7 +141,6 @@ function plc.rps_init(reactor) local fuel = self.reactor.getFuel() if fuel == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later - log.error("RPS: failed to check reactor fuel") _set_fault() self.state[state_keys.no_fuel] = false else @@ -364,9 +368,9 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co 0, -- getDamagePercent 0, -- getBoilEfficiency 0, -- getEnvironmentalLoss - 0, -- getFuel + 0, -- fuel_amnt 0, -- getFuelFilledPercentage - 0, -- getWaste + 0, -- waste_amnt 0, -- getWasteFilledPercentage "", -- coolant_name 0, -- coolant_amnt @@ -396,16 +400,12 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co parallel.waitForAll(table.unpack(tasks)) - if type(fuel) == "table" then + if fuel ~= nil then data_table[8] = fuel.amount - elseif type(fuel) == "number" then - data_table[8] = fuel end - if type(waste) == "table" then + if waste ~= nil then data_table[10] = waste.amount - elseif type(waste) == "number" then - data_table[10] = waste end if coolant ~= nil then @@ -462,17 +462,26 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co -- send structure properties (these should not change, server will cache these) local function _send_struct() - local mek_data = { 0, 0, 0, 0, 0, 0, 0, 0 } + local min_pos = { x = 0, y = 0, z = 0 } + local max_pos = { x = 0, y = 0, z = 0 } + + local mek_data = { false, 0, 0, 0, min_pos, max_pos, 0, 0, 0, 0, 0, 0, 0, 0 } local tasks = { - function () mek_data[1] = self.reactor.getHeatCapacity() end, - function () mek_data[2] = self.reactor.getFuelAssemblies() end, - function () mek_data[3] = self.reactor.getFuelSurfaceArea() end, - function () mek_data[4] = self.reactor.getFuelCapacity() end, - function () mek_data[5] = self.reactor.getWasteCapacity() end, - function () mek_data[6] = self.reactor.getCoolantCapacity() end, - function () mek_data[7] = self.reactor.getHeatedCoolantCapacity() end, - function () mek_data[8] = self.reactor.getMaxBurnRate() end + function () mek_data[1] = self.reactor.isFormed() end, + function () mek_data[2] = self.reactor.getLength() end, + function () mek_data[3] = self.reactor.getWidth() end, + function () mek_data[4] = self.reactor.getHeight() end, + function () mek_data[5] = self.reactor.getMinPos() end, + function () mek_data[6] = self.reactor.getMaxPos() end, + function () mek_data[7] = self.reactor.getHeatCapacity() end, + function () mek_data[8] = self.reactor.getFuelAssemblies() end, + function () mek_data[9] = self.reactor.getFuelSurfaceArea() end, + function () mek_data[10] = self.reactor.getFuelCapacity() end, + function () mek_data[11] = self.reactor.getWasteCapacity() end, + function () mek_data[12] = self.reactor.getCoolantCapacity() end, + function () mek_data[13] = self.reactor.getHeatedCoolantCapacity() end, + function () mek_data[14] = self.reactor.getMaxBurnRate() end } parallel.waitForAll(table.unpack(tasks)) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 0562f39..ed6378e 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.8.6" +local R_PLC_VERSION = "beta-v0.8.7" local print = util.print local println = util.println diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index d682fdb..d80b4b1 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -130,6 +130,12 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) }, ---@class mek_struct mek_struct = { + formed = false, + length = 0, + width = 0, + height = 0, + min_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate + max_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate heat_cap = 0, fuel_asm = 0, fuel_sa = 0, @@ -195,14 +201,20 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) -- copy in the reactor structure ---@param mek_data table local function _copy_struct(mek_data) - self.sDB.mek_struct.heat_cap = mek_data[1] - self.sDB.mek_struct.fuel_asm = mek_data[2] - self.sDB.mek_struct.fuel_sa = mek_data[3] - self.sDB.mek_struct.fuel_cap = mek_data[4] - self.sDB.mek_struct.waste_cap = mek_data[5] - self.sDB.mek_struct.ccool_cap = mek_data[6] - self.sDB.mek_struct.hcool_cap = mek_data[7] - self.sDB.mek_struct.max_burn = mek_data[8] + self.sDB.mek_struct.formed = mek_data[1] + self.sDB.mek_struct.length = mek_data[2] + self.sDB.mek_struct.width = mek_data[3] + self.sDB.mek_struct.height = mek_data[4] + self.sDB.mek_struct.min_pos = mek_data[5] + self.sDB.mek_struct.max_pos = mek_data[6] + self.sDB.mek_struct.heat_cap = mek_data[7] + self.sDB.mek_struct.fuel_asm = mek_data[8] + self.sDB.mek_struct.fuel_sa = mek_data[9] + self.sDB.mek_struct.fuel_cap = mek_data[10] + self.sDB.mek_struct.waste_cap = mek_data[11] + self.sDB.mek_struct.ccool_cap = mek_data[12] + self.sDB.mek_struct.hcool_cap = mek_data[13] + self.sDB.mek_struct.max_burn = mek_data[14] end -- mark this PLC session as closed, stop watchdog @@ -301,7 +313,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) end elseif pkt.type == RPLC_TYPES.MEK_STRUCT then -- received reactor structure, record it - if pkt.length == 8 then + if pkt.length == 14 then local status = pcall(_copy_struct, pkt.data) if status then -- copied in structure data OK @@ -357,7 +369,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) if pkt.length == 10 then self.sDB.rps_tripped = true self.sDB.rps_trip_cause = pkt.data[1] - local status = pcall(_copy_rps_status, { table.unpack(pkt.data, 2, #pkt.length) }) + local status = pcall(_copy_rps_status, { table.unpack(pkt.data, 2, pkt.length) }) if status then -- copied in RPS status data OK else diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 398266e..b4878b1 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.5.14" +local SUPERVISOR_VERSION = "beta-v0.5.15" local print = util.print local println = util.println From c02479b52ed58035ed6faae5ee4304f823f8ef25 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 2 Oct 2022 21:17:13 -0400 Subject: [PATCH 402/587] #99 updating/sending builds --- coordinator/iocontrol.lua | 49 +++++++++++++-------- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 2 +- graphics/elements/controls/scram_button.lua | 10 +++-- graphics/elements/controls/start_button.lua | 10 +++-- graphics/elements/indicators/coremap.lua | 3 ++ rtu/config.lua | 40 ++++++++--------- supervisor/session/coordinator.lua | 21 ++++++++- supervisor/session/plc.lua | 19 ++++---- supervisor/session/rtu.lua | 5 +++ supervisor/session/rtu/imatrix.lua | 4 +- supervisor/session/rtu/sps.lua | 4 +- supervisor/session/svqtypes.lua | 13 ++++++ supervisor/session/svsessions.lua | 39 +++++++++++----- supervisor/startup.lua | 2 +- 15 files changed, 148 insertions(+), 75 deletions(-) create mode 100644 supervisor/session/svqtypes.lua diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index b947f8c..f34cfab 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -86,41 +86,52 @@ function iocontrol.record_builds(builds) log.error("number of provided unit builds does not match expected number of units") return false else + -- note: if not all units and RTUs are connected, some will be nil for i = 1, #builds do local unit = io.units[i] ---@type ioctl_entry local build = builds[i] -- reactor build - unit.reactor_data.mek_struct = build.reactor - for key, val in pairs(unit.reactor_data.mek_struct) do - unit.reactor_ps.publish(key, val) + if type(build.reactor) == "table" then + unit.reactor_data.mek_struct = build.reactor + for key, val in pairs(unit.reactor_data.mek_struct) do + unit.reactor_ps.publish(key, val) + end + + if unit.reactor_data.mek_struct.length ~= 0 and unit.reactor_data.mek_struct.width ~= 0 then + unit.reactor_ps.publish("size", { unit.reactor_data.mek_struct.length, unit.reactor_data.mek_struct.width }) + end end -- boiler builds - for id, boiler in pairs(build.boilers) do - unit.boiler_data_tbl[id] = { - formed = boiler[2], ---@type boolean|nil - build = boiler[1] ---@type table - } + if type(build.boilers) == "table" then + for id, boiler in pairs(build.boilers) do + unit.boiler_data_tbl[id] = { + formed = boiler[2], ---@type boolean|nil + build = boiler[1] ---@type table + } - unit.boiler_ps_tbl[id].publish("formed", boiler[2]) + unit.boiler_ps_tbl[id].publish("formed", boiler[2]) - for key, val in pairs(unit.boiler_data_tbl[id].build) do - unit.boiler_ps_tbl[id].publish(key, val) + for key, val in pairs(unit.boiler_data_tbl[id].build) do + unit.boiler_ps_tbl[id].publish(key, val) + end end end -- turbine builds - for id, turbine in pairs(build.turbines) do - unit.turbine_data_tbl[id] = { - formed = turbine[2], ---@type boolean|nil - build = turbine[1] ---@type table - } + if type(build.turbines) == "table" then + for id, turbine in pairs(build.turbines) do + unit.turbine_data_tbl[id] = { + formed = turbine[2], ---@type boolean|nil + build = turbine[1] ---@type table + } - unit.turbine_ps_tbl[id].publish("formed", turbine[2]) + unit.turbine_ps_tbl[id].publish("formed", turbine[2]) - for key, val in pairs(unit.turbine_data_tbl[id].build) do - unit.turbine_ps_tbl[id].publish(key, val) + for key, val in pairs(unit.turbine_data_tbl[id].build) do + unit.turbine_ps_tbl[id].publish(key, val) + end end end end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 47c1dc8..396a116 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.4.15" +local COORDINATOR_VERSION = "alpha-v0.5.0" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 2ef2dbe..2244fd3 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -47,9 +47,9 @@ local function init(parent, id) -- main stats and core map -- - ---@todo need to be checking actual reactor dimensions somehow local core_map = CoreMap{parent=main,x=2,y=3,reactor_l=18,reactor_w=18} r_ps.subscribe("temp", core_map.update) + r_ps.subscribe("size", function (s) core_map.resize(s[1], s[2]) end) local stat_fg_bg = cpair(colors.black,colors.white) diff --git a/graphics/elements/controls/scram_button.lua b/graphics/elements/controls/scram_button.lua index fc12d67..1abe8a1 100644 --- a/graphics/elements/controls/scram_button.lua +++ b/graphics/elements/controls/scram_button.lua @@ -5,6 +5,8 @@ local tcd = require("scada-common.tcallbackdsp") local core = require("graphics.core") local element = require("graphics.element") +local accent = colors.yellow + ---@class scram_button_args ---@field callback function function to call on touch ---@field parent graphics_element @@ -33,7 +35,7 @@ local function scram_button(args) -- draw border -- top - e.window.setTextColor(colors.yellow) + e.window.setTextColor(accent) e.window.setBackgroundColor(args.fg_bg.bkg) e.window.setCursorPos(1, 1) e.window.write("\x99\x89\x89\x89\x89\x89\x89\x89\x99") @@ -41,17 +43,17 @@ local function scram_button(args) -- center left e.window.setCursorPos(1, 2) e.window.setTextColor(args.fg_bg.bkg) - e.window.setBackgroundColor(colors.yellow) + e.window.setBackgroundColor(accent) e.window.write("\x99") -- center right e.window.setTextColor(args.fg_bg.bkg) - e.window.setBackgroundColor(colors.yellow) + e.window.setBackgroundColor(accent) e.window.setCursorPos(9, 2) e.window.write("\x99") -- bottom - e.window.setTextColor(colors.yellow) + e.window.setTextColor(accent) e.window.setBackgroundColor(args.fg_bg.bkg) e.window.setCursorPos(1, 3) e.window.write("\x99\x98\x98\x98\x98\x98\x98\x98\x99") diff --git a/graphics/elements/controls/start_button.lua b/graphics/elements/controls/start_button.lua index 59c8580..e512c31 100644 --- a/graphics/elements/controls/start_button.lua +++ b/graphics/elements/controls/start_button.lua @@ -5,6 +5,8 @@ local tcd = require("scada-common.tcallbackdsp") local core = require("graphics.core") local element = require("graphics.element") +local accent = colors.lightBlue + ---@class start_button_args ---@field callback function function to call on touch ---@field parent graphics_element @@ -33,7 +35,7 @@ local function start_button(args) -- draw border -- top - e.window.setTextColor(colors.orange) + e.window.setTextColor(accent) e.window.setBackgroundColor(args.fg_bg.bkg) e.window.setCursorPos(1, 1) e.window.write("\x99\x89\x89\x89\x89\x89\x89\x89\x99") @@ -41,17 +43,17 @@ local function start_button(args) -- center left e.window.setCursorPos(1, 2) e.window.setTextColor(args.fg_bg.bkg) - e.window.setBackgroundColor(colors.orange) + e.window.setBackgroundColor(accent) e.window.write("\x99") -- center right e.window.setTextColor(args.fg_bg.bkg) - e.window.setBackgroundColor(colors.orange) + e.window.setBackgroundColor(accent) e.window.setCursorPos(9, 2) e.window.write("\x99") -- bottom - e.window.setTextColor(colors.orange) + e.window.setTextColor(accent) e.window.setBackgroundColor(args.fg_bg.bkg) e.window.setCursorPos(1, 3) e.window.write("\x99\x98\x98\x98\x98\x98\x98\x98\x99") diff --git a/graphics/elements/indicators/coremap.lua b/graphics/elements/indicators/coremap.lua index 09a467c..0ca72e1 100644 --- a/graphics/elements/indicators/coremap.lua +++ b/graphics/elements/indicators/coremap.lua @@ -115,6 +115,9 @@ local function core_map(args) if inner_width % 2 == 0 then alternator = not alternator end end + + -- reset alternator + alternator = true end -- on state change diff --git a/rtu/config.lua b/rtu/config.lua index c97fda1..55968e1 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -27,26 +27,26 @@ config.RTU_DEVICES = { } -- RTU redstone interface definitions config.RTU_REDSTONE = { - { - for_reactor = 1, - io = { - { - channel = rsio.IO.WASTE_PO, - side = "top", - bundled_color = colors.blue - }, - { - channel = rsio.IO.WASTE_PU, - side = "top", - bundled_color = colors.cyan - }, - { - channel = rsio.IO.WASTE_AM, - side = "top", - bundled_color = colors.purple - } - } - } + -- { + -- for_reactor = 1, + -- io = { + -- { + -- channel = rsio.IO.WASTE_PO, + -- side = "top", + -- bundled_color = colors.blue + -- }, + -- { + -- channel = rsio.IO.WASTE_PU, + -- side = "top", + -- bundled_color = colors.cyan + -- }, + -- { + -- channel = rsio.IO.WASTE_AM, + -- side = "top", + -- bundled_color = colors.purple + -- } + -- } + -- } } return config diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 80fb9d5..4860942 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -19,6 +19,16 @@ local println_ts = util.println_ts local INITIAL_WAIT = 1500 local RETRY_PERIOD = 1000 +local CRD_S_CMDS = { + RESEND_BUILDS = 1 +} + +local CRD_S_DATA = { +} + +coordinator.CRD_S_CMDS = CRD_S_CMDS +coordinator.CRD_S_DATA = CRD_S_DATA + local PERIODICS = { KEEP_ALIVE = 2000, STATUS = 500 @@ -51,11 +61,11 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) }, -- when to next retry one of these messages retry_times = { - builds_packet = (util.time() + 500) + builds_packet = 0 }, -- message acknowledgements acks = { - builds = true + builds = false } } @@ -227,6 +237,13 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) _handle_packet(message.message) elseif message.qtype == mqueue.TYPE.COMMAND then -- handle instruction + local cmd = message.message + if cmd == CRD_S_CMDS.RESEND_BUILDS then + -- re-send builds + self.acks.builds = false + self.retry_times.builds_packet = util.time() + RETRY_PERIOD + _send_builds() + end elseif message.qtype == mqueue.TYPE.DATA then -- instruction with body end diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index d80b4b1..9f01ec8 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -1,7 +1,9 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local util = require("scada-common.util") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local util = require("scada-common.util") + +local svqtypes = require("supervisor.session.svqtypes") local plc = {} @@ -19,9 +21,9 @@ local INITIAL_WAIT = 1500 local RETRY_PERIOD = 1000 local PLC_S_CMDS = { - SCRAM = 0, - ENABLE = 1, - RPS_RESET = 2 + SCRAM = 1, + ENABLE = 2, + RPS_RESET = 3 } local PLC_S_DATA = { @@ -193,7 +195,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) if self.received_struct then self.sDB.mek_status.fuel_need = self.sDB.mek_struct.fuel_cap - self.sDB.mek_status.fuel_fill self.sDB.mek_status.waste_need = self.sDB.mek_struct.waste_cap - self.sDB.mek_status.waste_fill - self.sDB.mek_status.cool_need = self.sDB.mek_struct.ccool_cap - self.sDB.mek_status.ccool_fill + self.sDB.mek_status.cool_need = self.sDB.mek_struct.ccool_cap - self.sDB.mek_status.ccool_fill self.sDB.mek_status.hcool_need = self.sDB.mek_struct.hcool_cap - self.sDB.mek_status.hcool_fill end end @@ -318,6 +320,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) if status then -- copied in structure data OK self.received_struct = true + self.out_q.push_command(svqtypes.SV_Q_CMDS.BUILD_CHANGED) else -- error copying structure data log.error(log_header .. "failed to parse struct packet data") diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 312c9a3..e844a9f 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -4,6 +4,8 @@ local mqueue = require("scada-common.mqueue") local rsio = require("scada-common.rsio") local util = require("scada-common.util") +local svqtypes = require("supervisor.session.svqtypes") + -- supervisor rtu sessions (svrs) local svrs_boilerv = require("supervisor.session.rtu.boilerv") local svrs_envd = require("supervisor.session.rtu.envd") @@ -192,6 +194,9 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) break end end + + -- report build changed + self.out_q.push_command(svqtypes.SV_Q_CMDS.BUILD_CHANGED) end -- mark this RTU session as closed, stop watchdog diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua index e6a9513..d56be02 100644 --- a/supervisor/session/rtu/imatrix.lua +++ b/supervisor/session/rtu/imatrix.lua @@ -60,8 +60,8 @@ function imatrix.new(session_id, unit_id, advert, out_queue) length = 0, width = 0, height = 0, - min_pos = 0, - max_pos = 0, + min_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate + max_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate max_energy = 0, transfer_cap = 0, cells = 0, diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua index 18ad727..e7a2a58 100644 --- a/supervisor/session/rtu/sps.lua +++ b/supervisor/session/rtu/sps.lua @@ -60,8 +60,8 @@ function sps.new(session_id, unit_id, advert, out_queue) length = 0, width = 0, height = 0, - min_pos = 0, - max_pos = 0, + min_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate + max_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate coils = 0, input_cap = 0, output_cap = 0, diff --git a/supervisor/session/svqtypes.lua b/supervisor/session/svqtypes.lua new file mode 100644 index 0000000..9814608 --- /dev/null +++ b/supervisor/session/svqtypes.lua @@ -0,0 +1,13 @@ +local svqtypes = {} + +local SV_Q_CMDS = { + BUILD_CHANGED = 0 +} + +local SV_Q_DATA = { +} + +svqtypes.SV_Q_CMDS = SV_Q_CMDS +svqtypes.SV_Q_DATA = SV_Q_DATA + +return svqtypes diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 764e5bd..3ad05f4 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -2,6 +2,7 @@ local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") local util = require("scada-common.util") +local svqtypes = require("supervisor.session.svqtypes") local unit = require("supervisor.session.unit") local coordinator = require("supervisor.session.coordinator") @@ -10,6 +11,9 @@ local rtu = require("supervisor.session.rtu") -- Supervisor Sessions Handler +local SV_Q_CMDS = svqtypes.SV_Q_CMDS +local CRD_S_CMDS = coordinator.CRD_S_CMDS + local svsessions = {} local SESSION_TYPE = { @@ -38,20 +42,30 @@ local self = { ---@param sessions table local function _iterate(sessions) for i = 1, #sessions do - local session = sessions[i] ---@type plc_session_struct|rtu_session_struct - if session.open then - local ok = session.instance.iterate() - if ok then - -- send packets in out queue - while session.out_queue.ready() do - local msg = session.out_queue.pop() - if msg ~= nil and msg.qtype == mqueue.TYPE.PACKET then + local session = sessions[i] ---@type plc_session_struct|rtu_session_struct|coord_session_struct + if session.open and session.instance.iterate() then + -- process output queues + while session.out_queue.ready() do + local msg = session.out_queue.pop() + if msg ~= nil then + if msg.qtype == mqueue.TYPE.PACKET then + -- packet to be sent self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) + elseif msg.qtype == mqueue.TYPE.COMMAND then + -- notification + local cmd = msg.message + if cmd == SV_Q_CMDS.BUILD_CHANGED then + -- notify coordinator(s) that a build has changed + for j = 1, #self.coord_sessions do + local s = self.coord_sessions[j] ---@type coord_session_struct + s.in_queue.push_command(CRD_S_CMDS.RESEND_BUILDS) + end + end end end - else - session.open = false end + else + session.open = false end end end @@ -104,7 +118,10 @@ end ---@param sessions table local function _free_closed(sessions) local f = function (session) return session.open end - local on_delete = function (session) log.debug("free'ing closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) end + + local on_delete = function (session) + log.debug("free'ing closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) + end util.filter_table(sessions, f, on_delete) end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index b4878b1..1756a2a 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.5.15" +local SUPERVISOR_VERSION = "beta-v0.6.0" local print = util.print local println = util.println From 62ac993dae541d04bb3a723db0509482a0727847 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 6 Oct 2022 13:54:52 -0400 Subject: [PATCH 403/587] #93, #94, unit commands and range/type checks on unit IDs on PLC/RTU connections --- supervisor/session/coordinator.lua | 38 +++++++++--- supervisor/session/plc.lua | 2 +- supervisor/session/rtu.lua | 56 +++++++++--------- supervisor/session/svqtypes.lua | 7 ++- supervisor/session/svsessions.lua | 94 +++++++++++++++++++++++------- supervisor/session/unit.lua | 9 --- supervisor/startup.lua | 2 +- supervisor/supervisor.lua | 26 +++++---- 8 files changed, 155 insertions(+), 79 deletions(-) diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 4860942..a4b2e2d 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -1,7 +1,9 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local util = require("scada-common.util") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local util = require("scada-common.util") + +local svqtypes = require("supervisor.session.svqtypes") local coordinator = {} @@ -10,6 +12,9 @@ local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES local CRDN_COMMANDS = comms.CRDN_COMMANDS +local SV_Q_CMDS = svqtypes.SV_Q_CMDS +local SV_Q_DATA = svqtypes.SV_Q_DATA + local print = util.print local println = util.println local print_ts = util.print_ts @@ -175,16 +180,33 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) -- acknowledgement to coordinator receiving builds self.acks.builds = true elseif pkt.type == SCADA_CRDN_TYPES.COMMAND_UNIT then - if pkt.length > 2 then + if pkt.length >= 2 then -- get command and unit id local cmd = pkt.data[1] local uid = pkt.data[2] -- continue if valid unit id if util.is_int(uid) and uid > 0 and uid <= #self.units then - local unit = self.units[pkt.data[2]] ---@type reactor_unit - if cmd == CRDN_COMMANDS.SCRAM then - unit.scram() + if cmd == CRDN_COMMANDS.START then + self.out_q.push_data(SV_Q_DATA.START, uid) + elseif cmd == CRDN_COMMANDS.SCRAM then + self.out_q.push_data(SV_Q_DATA.SCRAM, uid) + elseif cmd == CRDN_COMMANDS.RESET_RPS then + self.out_q.push_data(SV_Q_DATA.RESET_RPS, uid) + elseif cmd == CRDN_COMMANDS.SET_BURN then + if pkt.length == 3 then + self.out_q.push_data(SV_Q_DATA.SET_BURN, { uid, pkt.data[3] }) + else + log.debug(log_header .. "CRDN command unit burn rate missing option") + end + elseif cmd == CRDN_COMMANDS.SET_WASTE then + if pkt.length == 3 then + self.out_q.push_data(SV_Q_DATA.SET_WASTE, { uid, pkt.data[3] }) + else + log.debug(log_header .. "CRDN command unit set waste missing option") + end + else + log.debug(log_header .. "CRDN command unknown") end else log.debug(log_header .. "CRDN command unit invalid") diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 9f01ec8..68898f3 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -516,7 +516,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) end elseif message.qtype == mqueue.TYPE.DATA then -- instruction with body - local cmd = message.message + local cmd = message.message ---@type queue_data if cmd.key == PLC_S_DATA.BURN_RATE then -- update burn rate self.commanded_burn_rate = cmd.val diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index e844a9f..1469863 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -105,8 +105,6 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) rsio = self.advert[i][4] } - local target_unit = self.f_units[unit_advert.reactor] ---@type reactor_unit - local u_type = unit_advert.type ---@type integer|boolean -- validate unit advertisement @@ -122,6 +120,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) if advert_validator.valid() then advert_validator.assert_min(unit_advert.index, 1) advert_validator.assert_min(unit_advert.reactor, 1) + advert_validator.assert_max(unit_advert.reactor, #self.f_units) if not advert_validator.valid() then u_type = false end else u_type = false @@ -131,31 +130,35 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) if u_type == false then -- validation fail - elseif u_type == RTU_UNIT_TYPES.REDSTONE then - -- redstone - unit, rs_in_q = svrs_redstone.new(self.id, i, unit_advert, self.modbus_q) - elseif u_type == RTU_UNIT_TYPES.BOILER_VALVE then - -- boiler (Mekanism 10.1+) - unit = svrs_boilerv.new(self.id, i, unit_advert, self.modbus_q) - if type(unit) ~= "nil" then target_unit.add_boiler(unit) end - elseif u_type == RTU_UNIT_TYPES.TURBINE_VALVE then - -- turbine (Mekanism 10.1+) - unit, tbv_in_q = svrs_turbinev.new(self.id, i, unit_advert, self.modbus_q) - if type(unit) ~= "nil" then target_unit.add_turbine(unit) end - elseif u_type == RTU_UNIT_TYPES.IMATRIX then - -- induction matrix - unit = svrs_imatrix.new(self.id, i, unit_advert, self.modbus_q) - elseif u_type == RTU_UNIT_TYPES.SPS then - -- super-critical phase shifter - unit = svrs_sps.new(self.id, i, unit_advert, self.modbus_q) - elseif u_type == RTU_UNIT_TYPES.SNA then - -- solar neutron activator - unit = svrs_sna.new(self.id, i, unit_advert, self.modbus_q) - elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then - -- environment detector - unit = svrs_envd.new(self.id, i, unit_advert, self.modbus_q) else - log.error(log_header .. "bad advertisement: encountered unsupported RTU type") + local target_unit = self.f_units[unit_advert.reactor] ---@type reactor_unit + + if u_type == RTU_UNIT_TYPES.REDSTONE then + -- redstone + unit, rs_in_q = svrs_redstone.new(self.id, i, unit_advert, self.modbus_q) + elseif u_type == RTU_UNIT_TYPES.BOILER_VALVE then + -- boiler (Mekanism 10.1+) + unit = svrs_boilerv.new(self.id, i, unit_advert, self.modbus_q) + if type(unit) ~= "nil" then target_unit.add_boiler(unit) end + elseif u_type == RTU_UNIT_TYPES.TURBINE_VALVE then + -- turbine (Mekanism 10.1+) + unit, tbv_in_q = svrs_turbinev.new(self.id, i, unit_advert, self.modbus_q) + if type(unit) ~= "nil" then target_unit.add_turbine(unit) end + elseif u_type == RTU_UNIT_TYPES.IMATRIX then + -- induction matrix + unit = svrs_imatrix.new(self.id, i, unit_advert, self.modbus_q) + elseif u_type == RTU_UNIT_TYPES.SPS then + -- super-critical phase shifter + unit = svrs_sps.new(self.id, i, unit_advert, self.modbus_q) + elseif u_type == RTU_UNIT_TYPES.SNA then + -- solar neutron activator + unit = svrs_sna.new(self.id, i, unit_advert, self.modbus_q) + elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then + -- environment detector + unit = svrs_envd.new(self.id, i, unit_advert, self.modbus_q) + else + log.error(log_header .. "bad advertisement: encountered unsupported RTU type") + end end if unit ~= nil then @@ -333,7 +336,6 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) elseif msg.qtype == mqueue.TYPE.DATA then -- instruction with body local cmd = msg.message ---@type queue_data - if cmd.key == RTU_S_DATA.RS_COMMAND then local rs_cmd = cmd.val ---@type rs_session_command diff --git a/supervisor/session/svqtypes.lua b/supervisor/session/svqtypes.lua index 9814608..175f98b 100644 --- a/supervisor/session/svqtypes.lua +++ b/supervisor/session/svqtypes.lua @@ -1,10 +1,15 @@ local svqtypes = {} local SV_Q_CMDS = { - BUILD_CHANGED = 0 + BUILD_CHANGED = 1 } local SV_Q_DATA = { + START = 1, + SCRAM = 2, + RESET_RPS = 3, + SET_BURN = 4, + SET_WASTE = 5 } svqtypes.SV_Q_CMDS = SV_Q_CMDS diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 3ad05f4..2c42f91 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -12,6 +12,10 @@ local rtu = require("supervisor.session.rtu") -- Supervisor Sessions Handler local SV_Q_CMDS = svqtypes.SV_Q_CMDS +local SV_Q_DATA = svqtypes.SV_Q_DATA + +local PLC_S_CMDS = plc.PLC_S_CMDS +local PLC_S_DATA = plc.PLC_S_DATA local CRD_S_CMDS = coordinator.CRD_S_CMDS local svsessions = {} @@ -38,32 +42,75 @@ local self = { -- PRIVATE FUNCTIONS -- +-- handle a session output queue +---@param session plc_session_struct|rtu_session_struct|coord_session_struct +local function _sv_handle_outq(session) + -- record handler start time + local handle_start = util.time() + + -- process output queue + while session.out_queue.ready() do + -- get a new message to process + local msg = session.out_queue.pop() + + if msg ~= nil then + if msg.qtype == mqueue.TYPE.PACKET then + -- handle a packet to be sent + self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) + elseif msg.qtype == mqueue.TYPE.COMMAND then + -- handle instruction/notification + local cmd = msg.message + if cmd == SV_Q_CMDS.BUILD_CHANGED then + -- notify coordinator(s) that a build has changed + for j = 1, #self.coord_sessions do + local s = self.coord_sessions[j] ---@type coord_session_struct + s.in_queue.push_command(CRD_S_CMDS.RESEND_BUILDS) + end + end + elseif msg.qtype == mqueue.TYPE.DATA then + -- instruction/notification with body + local cmd = msg.message ---@type queue_data + local plc_s = nil + + if type(cmd.val) == "table" then + plc_s = svsessions.get_reactor_session(cmd.val[1]) + elseif type(cmd.val) == "number" then + plc_s = svsessions.get_reactor_session(cmd.val) + end + + if plc_s ~= nil then + if cmd.key == SV_Q_DATA.START then + plc_s.in_queue.push_command(PLC_S_CMDS.ENABLE) + elseif cmd.key == SV_Q_DATA.SCRAM then + plc_s.in_queue.push_command(PLC_S_CMDS.SCRAM) + elseif cmd.key == SV_Q_DATA.RESET_RPS then + plc_s.in_queue.push_command(PLC_S_CMDS.RPS_RESET) + elseif cmd.key == SV_Q_DATA.SET_BURN and type(cmd.val) == "table" and #cmd.val == 2 then + plc_s.in_queue.push_data(PLC_S_DATA.BURN_RATE, cmd.val[2]) + elseif cmd.key == SV_Q_DATA.SET_WASTE and type(cmd.val) == "table" and #cmd.val == 2 then + ---@todo set waste + end + end + end + end + + -- max 100ms spent processing queue + if util.time() - handle_start > 100 then + log.warning("supervisor out queue handler exceeded 100ms queue process limit") + log.warning(util.c("offending session: port ", session.r_port, " type '", session.s_type, "'")) + break + end + end +end + -- iterate all the given sessions ---@param sessions table local function _iterate(sessions) for i = 1, #sessions do local session = sessions[i] ---@type plc_session_struct|rtu_session_struct|coord_session_struct + if session.open and session.instance.iterate() then - -- process output queues - while session.out_queue.ready() do - local msg = session.out_queue.pop() - if msg ~= nil then - if msg.qtype == mqueue.TYPE.PACKET then - -- packet to be sent - self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) - elseif msg.qtype == mqueue.TYPE.COMMAND then - -- notification - local cmd = msg.message - if cmd == SV_Q_CMDS.BUILD_CHANGED then - -- notify coordinator(s) that a build has changed - for j = 1, #self.coord_sessions do - local s = self.coord_sessions[j] ---@type coord_session_struct - s.in_queue.push_command(CRD_S_CMDS.RESEND_BUILDS) - end - end - end - end - end + _sv_handle_outq(session) else session.open = false end @@ -221,9 +268,10 @@ end ---@param version string ---@return integer|false session_id function svsessions.establish_plc_session(local_port, remote_port, for_reactor, version) - if svsessions.get_reactor_session(for_reactor) == nil then + if svsessions.get_reactor_session(for_reactor) == nil and for_reactor >= 1 and for_reactor <= self.num_reactors then ---@class plc_session_struct local plc_s = { + s_type = "plc", open = true, reactor = for_reactor, version = version, @@ -246,7 +294,7 @@ function svsessions.establish_plc_session(local_port, remote_port, for_reactor, -- success return plc_s.instance.get_id() else - -- reactor already assigned to a PLC + -- reactor already assigned to a PLC or ID out of range return false end end @@ -262,6 +310,7 @@ function svsessions.establish_rtu_session(local_port, remote_port, advertisement ---@class rtu_session_struct local rtu_s = { + s_type = "rtu", open = true, version = version, l_port = local_port, @@ -290,6 +339,7 @@ end function svsessions.establish_coord_session(local_port, remote_port, version) ---@class coord_session_struct local coord_s = { + s_type = "crd", open = true, version = version, l_port = local_port, diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index bc1d544..c51586e 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -126,7 +126,6 @@ function unit.new(for_reactor, num_boilers, num_turbines) if self.plc_s ~= nil then local plc_db = self.plc_i.get_db() - ---@todo Mekanism 10.1+ will change fuel/waste to need _amnt _compute_dt(DT_KEYS.ReactorTemp, plc_db.mek_status.temp) _compute_dt(DT_KEYS.ReactorFuel, plc_db.mek_status.fuel) _compute_dt(DT_KEYS.ReactorWaste, plc_db.mek_status.waste) @@ -409,14 +408,6 @@ function unit.new(for_reactor, num_boilers, num_turbines) _update_annunciator() end - -- COMMAND UNIT -- - - -- SCRAM reactor - function public.scram() - if self.plc_s ~= nil then - end - end - -- READ STATES/PROPERTIES -- -- get build properties of all machines diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 1756a2a..d2ad774 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.6.0" +local SUPERVISOR_VERSION = "beta-v0.6.1" local print = util.print local println = util.println diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 873c3e1..4cf0eef 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -198,19 +198,25 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen if packet.type == RPLC_TYPES.LINK_REQ then if packet.length == 2 then -- this is a linking request - local plc_id = svsessions.establish_plc_session(l_port, r_port, packet.data[1], packet.data[2]) - if plc_id == false then - -- reactor already has a PLC assigned - log.debug(util.c("PLC_LNK: assignment collision with reactor ", packet.data[1])) - _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.COLLISION }) + if type(packet.data[1]) == "number" and type(packet.data[2]) == "string" then + local plc_id = svsessions.establish_plc_session(l_port, r_port, packet.data[1], packet.data[2]) + if plc_id == false then + -- reactor already has a PLC assigned + log.debug(util.c("PLC_LNK: assignment collision with reactor ", packet.data[1])) + _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.COLLISION }) + else + -- got an ID; assigned to a reactor successfully + println(util.c("connected to reactor ", packet.data[1], " PLC (", packet.data[2], ") [:", r_port, "]")) + log.debug("PLC_LNK: allowed for device at " .. r_port) + _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.ALLOW }) + end else - -- got an ID; assigned to a reactor successfully - println(util.c("connected to reactor ", packet.data[1], " PLC (", packet.data[2], ") [:", r_port, "]")) - log.debug("PLC_LNK: allowed for device at " .. r_port) - _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.ALLOW }) + log.debug("PLC_LNK: bad parameter types") + _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.DENY }) end else log.debug("PLC_LNK: new linking packet length mismatch") + _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.DENY }) end else -- force a re-link @@ -267,7 +273,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen elseif packet.type == SCADA_CRDN_TYPES.ESTABLISH then if packet.length == 1 then -- this is an attempt to establish a new session - println(util.c("connected to coordinator [:", r_port, "]")) + println(util.c("connected to coordinator (", packet.data[1], ") [:", r_port, "]")) svsessions.establish_coord_session(l_port, r_port, packet.data[1]) From 9d60777223f7d02036b43f353176ed0065ff9b5a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 7 Oct 2022 10:19:37 -0400 Subject: [PATCH 404/587] #93 added reset RPS command to iocontrol/gui --- coordinator/iocontrol.lua | 5 ++ coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 8 +- .../{scram_button.lua => hazard_button.lua} | 22 +++--- graphics/elements/controls/start_button.lua | 78 ------------------- 5 files changed, 22 insertions(+), 93 deletions(-) rename graphics/elements/controls/{scram_button.lua => hazard_button.lua} (73%) delete mode 100644 graphics/elements/controls/start_button.lua diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index f34cfab..bc93f05 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -44,6 +44,11 @@ function iocontrol.init(conf, comms) log.debug(util.c("sent unit ", i, ": SCRAM")) end, + reset_rps = function () + comms.send_command(i, CRDN_COMMANDS.RESET_RPS) + log.debug(util.c("sent unit ", i, ": RESET_RPS")) + end, + set_burn = function (rate) comms.send_command(i, CRDN_COMMANDS.SET_BURN, rate) log.debug(util.c("sent unit ", i, ": SET_BURN = ", rate)) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 396a116..708c3d3 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.5.0" +local COORDINATOR_VERSION = "alpha-v0.5.1" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 2244fd3..36291cd 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -19,10 +19,9 @@ local DataIndicator = require("graphics.elements.indicators.data") local IndicatorLight = require("graphics.elements.indicators.light") local TriIndicatorLight = require("graphics.elements.indicators.trilight") +local HazardButton = require("graphics.elements.controls.hazard_button") local MultiButton = require("graphics.elements.controls.multi_button") local PushButton = require("graphics.elements.controls.push_button") -local SCRAMButton = require("graphics.elements.controls.scram_button") -local StartButton = require("graphics.elements.controls.start_button") local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") local TEXT_ALIGN = core.graphics.TEXT_ALIGN @@ -240,8 +239,9 @@ local function init(parent, id) -- reactor controls -- - StartButton{parent=main,x=12,y=44,callback=unit.start,fg_bg=scram_fg_bg} - SCRAMButton{parent=main,x=22,y=44,callback=unit.scram,fg_bg=scram_fg_bg} + HazardButton{parent=main,x=2,y=44,text="START",accent=colors.lightBlue,callback=unit.start,fg_bg=scram_fg_bg} + HazardButton{parent=main,x=12,y=44,text="SCRAM",accent=colors.yellow,callback=unit.scram,fg_bg=scram_fg_bg} + HazardButton{parent=main,x=22,y=44,text="RESET",accent=colors.red,callback=unit.reset_rps,fg_bg=scram_fg_bg} local burn_control = Div{parent=main,x=12,y=40,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} diff --git a/graphics/elements/controls/scram_button.lua b/graphics/elements/controls/hazard_button.lua similarity index 73% rename from graphics/elements/controls/scram_button.lua rename to graphics/elements/controls/hazard_button.lua index 1abe8a1..90a2118 100644 --- a/graphics/elements/controls/scram_button.lua +++ b/graphics/elements/controls/hazard_button.lua @@ -1,13 +1,13 @@ --- SCRAM Button Graphics Element +-- Hazard-bordered Button Graphics Element local tcd = require("scada-common.tcallbackdsp") local core = require("graphics.core") local element = require("graphics.element") -local accent = colors.yellow - ---@class scram_button_args +---@field text string text to show on button +---@field accent color accent color for hazard border ---@field callback function function to call on touch ---@field parent graphics_element ---@field id? string element id @@ -19,23 +19,25 @@ local accent = colors.yellow ---@param args scram_button_args ---@return graphics_element element, element_id id local function scram_button(args) - assert(type(args.callback) == "function", "graphics.elements.controls.scram_button: callback is a required field") + assert(type(args.text) == "string", "graphics.elements.controls.hazard_button: text is a required field") + assert(type(args.accent) == "number", "graphics.elements.controls.hazard_button: accent is a required field") + assert(type(args.callback) == "function", "graphics.elements.controls.hazard_button: callback is a required field") -- static dimensions args.height = 3 - args.width = 9 + args.width = string.len(args.text) + 4 -- create new graphics element base object local e = element.new(args) -- write the button text e.window.setCursorPos(3, 2) - e.window.write("SCRAM") + e.window.write(args.text) -- draw border -- top - e.window.setTextColor(accent) + e.window.setTextColor(args.accent) e.window.setBackgroundColor(args.fg_bg.bkg) e.window.setCursorPos(1, 1) e.window.write("\x99\x89\x89\x89\x89\x89\x89\x89\x99") @@ -43,17 +45,17 @@ local function scram_button(args) -- center left e.window.setCursorPos(1, 2) e.window.setTextColor(args.fg_bg.bkg) - e.window.setBackgroundColor(accent) + e.window.setBackgroundColor(args.accent) e.window.write("\x99") -- center right e.window.setTextColor(args.fg_bg.bkg) - e.window.setBackgroundColor(accent) + e.window.setBackgroundColor(args.accent) e.window.setCursorPos(9, 2) e.window.write("\x99") -- bottom - e.window.setTextColor(accent) + e.window.setTextColor(args.accent) e.window.setBackgroundColor(args.fg_bg.bkg) e.window.setCursorPos(1, 3) e.window.write("\x99\x98\x98\x98\x98\x98\x98\x98\x99") diff --git a/graphics/elements/controls/start_button.lua b/graphics/elements/controls/start_button.lua deleted file mode 100644 index e512c31..0000000 --- a/graphics/elements/controls/start_button.lua +++ /dev/null @@ -1,78 +0,0 @@ --- SCRAM Button Graphics Element - -local tcd = require("scada-common.tcallbackdsp") - -local core = require("graphics.core") -local element = require("graphics.element") - -local accent = colors.lightBlue - ----@class start_button_args ----@field callback function function to call on touch ----@field parent graphics_element ----@field id? string element id ----@field x? integer 1 if omitted ----@field y? integer 1 if omitted ----@field fg_bg? cpair foreground/background colors - --- new start button ----@param args start_button_args ----@return graphics_element element, element_id id -local function start_button(args) - assert(type(args.callback) == "function", "graphics.elements.controls.start_button: callback is a required field") - - -- static dimensions - args.height = 3 - args.width = 9 - - -- create new graphics element base object - local e = element.new(args) - - -- write the button text - e.window.setCursorPos(3, 2) - e.window.write("START") - - -- draw border - - -- top - e.window.setTextColor(accent) - e.window.setBackgroundColor(args.fg_bg.bkg) - e.window.setCursorPos(1, 1) - e.window.write("\x99\x89\x89\x89\x89\x89\x89\x89\x99") - - -- center left - e.window.setCursorPos(1, 2) - e.window.setTextColor(args.fg_bg.bkg) - e.window.setBackgroundColor(accent) - e.window.write("\x99") - - -- center right - e.window.setTextColor(args.fg_bg.bkg) - e.window.setBackgroundColor(accent) - e.window.setCursorPos(9, 2) - e.window.write("\x99") - - -- bottom - e.window.setTextColor(accent) - e.window.setBackgroundColor(args.fg_bg.bkg) - e.window.setCursorPos(1, 3) - e.window.write("\x99\x98\x98\x98\x98\x98\x98\x98\x99") - - -- handle touch - ---@param event monitor_touch monitor touch event ----@diagnostic disable-next-line: unused-local - function e.handle_touch(event) - -- call the touch callback - args.callback() - end - - -- set the value - ---@param val boolean new value - function e.set_value(val) - if val then e.handle_touch(core.events.touch("", 1, 1)) end - end - - return e.get() -end - -return start_button From d4da6a7f3a53f497fbbb0daa4f7c26734732a0d2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 7 Oct 2022 10:28:46 -0400 Subject: [PATCH 405/587] fixed up types/names for hazard button --- coordinator/startup.lua | 2 +- graphics/element.lua | 3 +-- graphics/elements/controls/hazard_button.lua | 10 +++++----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 708c3d3..2fbe51d 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.5.1" +local COORDINATOR_VERSION = "alpha-v0.5.2" local print = util.print local println = util.println diff --git a/graphics/element.lua b/graphics/element.lua index 2e4c906..907cc8e 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -21,11 +21,10 @@ local element = {} ---@alias graphics_args graphics_args_generic ---|waiting_args +---|hazard_button_args ---|multi_button_args ---|push_button_args ----|scram_button_args ---|spinbox_args ----|start_button_args ---|switch_button_args ---|core_map_args ---|data_indicator_args diff --git a/graphics/elements/controls/hazard_button.lua b/graphics/elements/controls/hazard_button.lua index 90a2118..c931f19 100644 --- a/graphics/elements/controls/hazard_button.lua +++ b/graphics/elements/controls/hazard_button.lua @@ -5,7 +5,7 @@ local tcd = require("scada-common.tcallbackdsp") local core = require("graphics.core") local element = require("graphics.element") ----@class scram_button_args +---@class hazard_button_args ---@field text string text to show on button ---@field accent color accent color for hazard border ---@field callback function function to call on touch @@ -15,10 +15,10 @@ local element = require("graphics.element") ---@field y? integer 1 if omitted ---@field fg_bg? cpair foreground/background colors --- new scram button ----@param args scram_button_args +-- new hazard button +---@param args hazard_button_args ---@return graphics_element element, element_id id -local function scram_button(args) +local function hazard_button(args) assert(type(args.text) == "string", "graphics.elements.controls.hazard_button: text is a required field") assert(type(args.accent) == "number", "graphics.elements.controls.hazard_button: accent is a required field") assert(type(args.callback) == "function", "graphics.elements.controls.hazard_button: callback is a required field") @@ -77,4 +77,4 @@ local function scram_button(args) return e.get() end -return scram_button +return hazard_button From 573c263548e60f10b3b29c8e46b46dfe43c032cc Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 7 Oct 2022 10:29:25 -0400 Subject: [PATCH 406/587] same ppm fault check as with scram for enabling an enabled reactor --- reactor-plc/plc.lua | 5 +++-- reactor-plc/startup.lua | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 78045af..4a69c5e 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -19,8 +19,9 @@ local print_ts = util.print_ts local println_ts = util.println_ts -- I sure hope the devs don't change this error message, not that it would have safety implications --- I wish they didn't change it to error on SCRAM calls if the reactor was already inactive +-- I wish they didn't change it to be like this local PCALL_SCRAM_MSG = "pcall: Scram requires the reactor to be active." +local PCALL_START_MSG = "pcall: Reactor is already active." -- RPS SAFETY CONSTANTS @@ -193,7 +194,7 @@ function plc.rps_init(reactor) log.info("RPS: reactor start") self.reactor.activate() - if self.reactor.__p_is_faulted() then + if self.reactor.__p_is_faulted() and (self.reactor.__p_last_fault() ~= PCALL_START_MSG) then log.error("RPS: failed reactor start") else self.reactor_enabled = true diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index ed6378e..4909737 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.8.7" +local R_PLC_VERSION = "beta-v0.8.8" local print = util.print local println = util.println From 529951f9988c21167db07b564289851ac663c90a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 7 Oct 2022 11:21:17 -0400 Subject: [PATCH 407/587] automatically show current burn rate in burn rate spinbox --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 3 ++ .../elements/controls/spinbox_numeric.lua | 47 ++++++++----------- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 2fbe51d..766b7ff 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.5.2" +local COORDINATOR_VERSION = "alpha-v0.5.3" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 36291cd..899d50e 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -246,9 +246,12 @@ local function init(parent, id) local burn_control = Div{parent=main,x=12,y=40,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} TextBox{parent=burn_control,x=9,y=2,text="mB/t"} + local set_burn = function () unit.set_burn(burn_rate.get_value()) end PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} + r_ps.subscribe("burn_rate", function (v) burn_rate.set_value(v) end) + local opts = { { text = "Auto", diff --git a/graphics/elements/controls/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua index ac363b2..1f61a5b 100644 --- a/graphics/elements/controls/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -43,11 +43,6 @@ local function spinbox(args) -- set initial value e.value = args.default or 0.0 - local initial_str = util.sprintf(fmt_init, e.value) - ----@diagnostic disable-next-line: discard-returns - initial_str:gsub("%d", function (char) table.insert(digits, char) end) - -- draw the arrows e.window.setBackgroundColor(args.arrow_fg_bg.bkg) e.window.setTextColor(args.arrow_fg_bg.fgd) @@ -62,18 +57,12 @@ local function spinbox(args) e.window.write(" " .. util.strrep("\x1f", fr_prec)) end - -- zero the value - local function zero() - for i = 1, #digits do digits[i] = 0 end - e.value = 0 - end + -- populate digits from current value + local function set_digits() + local initial_str = util.sprintf(fmt_init, e.value) - -- print out the current value - local function show_num() - e.window.setBackgroundColor(e.fg_bg.bkg) - e.window.setTextColor(e.fg_bg.fgd) - e.window.setCursorPos(1, 2) - e.window.write(util.sprintf(fmt, e.value)) +---@diagnostic disable-next-line: discard-returns + initial_str:gsub("%d", function (char) table.insert(digits, char) end) end -- update the value per digits table @@ -89,11 +78,13 @@ local function spinbox(args) end end - -- enforce numeric limits - local function enforce_limits() + -- print out the current value + local function show_num() + -- enforce limits -- min 0 if e.value < 0 then - zero() + for i = 1, #digits do digits[i] = 0 end + e.value = 0 -- max printable elseif string.len(util.sprintf(fmt, e.value)) > args.width then -- max out @@ -102,13 +93,12 @@ local function spinbox(args) -- re-update value update_value() end - end - -- update value and show - local function parse_and_show() - update_value() - enforce_limits() - show_num() + -- draw + e.window.setBackgroundColor(e.fg_bg.bkg) + e.window.setTextColor(e.fg_bg.fgd) + e.window.setCursorPos(1, 2) + e.window.write(util.sprintf(fmt, e.value)) end -- init with the default value @@ -128,7 +118,8 @@ local function spinbox(args) digits[idx] = digits[idx] - 1 end - parse_and_show() + update_value() + show_num() end end @@ -136,7 +127,9 @@ local function spinbox(args) ---@param val number number to show function e.set_value(val) e.value = val - parse_and_show() + + set_digits() + show_num() end return e.get() From 5dfbe650c6deb2714a01a69fbda065dc827098eb Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 7 Oct 2022 11:28:56 -0400 Subject: [PATCH 408/587] #93 don't send out-of-range burn rates (won't get a good ack), fixed unit command packet ordering --- supervisor/session/coordinator.lua | 4 ++-- supervisor/session/plc.lua | 26 ++++++++++++++++---------- supervisor/startup.lua | 2 +- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index a4b2e2d..e3c80f7 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -182,8 +182,8 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) elseif pkt.type == SCADA_CRDN_TYPES.COMMAND_UNIT then if pkt.length >= 2 then -- get command and unit id - local cmd = pkt.data[1] - local uid = pkt.data[2] + local uid = pkt.data[1] + local cmd = pkt.data[2] -- continue if valid unit id if util.is_int(uid) and uid > 0 and uid <= #self.units then diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 68898f3..4e7cdb5 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -519,18 +519,24 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) local cmd = message.message ---@type queue_data if cmd.key == PLC_S_DATA.BURN_RATE then -- update burn rate - self.commanded_burn_rate = cmd.val - self.ramping_rate = false - self.acks.burn_rate = false - self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + cmd.val = math.floor(cmd.val * 10) / 10 -- round to 10ths place + if cmd.val > 0 and cmd.val <= self.sDB.mek_struct.max_burn then + self.commanded_burn_rate = cmd.val + self.ramping_rate = false + self.acks.burn_rate = false + self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + end elseif cmd.key == PLC_S_DATA.RAMP_BURN_RATE then -- ramp to burn rate - self.commanded_burn_rate = cmd.val - self.ramping_rate = true - self.acks.burn_rate = false - self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + cmd.val = math.floor(cmd.val * 10) / 10 -- round to 10ths place + if cmd.val > 0 and cmd.val <= self.sDB.mek_struct.max_burn then + self.commanded_burn_rate = cmd.val + self.ramping_rate = true + self.acks.burn_rate = false + self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + end end end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index d2ad774..69cb1fe 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.6.1" +local SUPERVISOR_VERSION = "beta-v0.6.2" local print = util.print local println = util.println From 77dc7ec0c934cecdc24a9ec47cc2342ba67c6620 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 7 Oct 2022 11:43:18 -0400 Subject: [PATCH 409/587] fixed rps reset infinte retry, improved time delta calculations, added last_update to rtu device databases --- supervisor/session/plc.lua | 2 +- supervisor/session/rtu/boilerv.lua | 31 +++++++++++-------- supervisor/session/rtu/envd.lua | 5 +++- supervisor/session/rtu/imatrix.lua | 7 +++++ supervisor/session/rtu/sna.lua | 11 +++++-- supervisor/session/rtu/sps.lua | 25 ++++++++++------ supervisor/session/rtu/turbinev.lua | 6 ++++ supervisor/session/unit.lua | 46 +++++++++++++++++------------ supervisor/startup.lua | 2 +- 9 files changed, 90 insertions(+), 45 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 4e7cdb5..a233387 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -386,7 +386,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) -- RPS reset acknowledgement local ack = _get_ack(pkt) if ack then - self.acks.rps_tripped = true + self.acks.rps_reset = true self.sDB.rps_tripped = false self.sDB.rps_trip_cause = "ok" elseif ack == false then diff --git a/supervisor/session/rtu/boilerv.lua b/supervisor/session/rtu/boilerv.lua index a7cea55..bd9aaad 100644 --- a/supervisor/session/rtu/boilerv.lua +++ b/supervisor/session/rtu/boilerv.lua @@ -1,6 +1,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local types = require("scada-common.types") +local util = require("scada-common.util") local unit_session = require("supervisor.session.rtu.unit_session") @@ -57,6 +58,7 @@ function boilerv.new(session_id, unit_id, advert, out_queue) db = { formed = false, build = { + last_update = 0, length = 0, width = 0, height = 0, @@ -72,10 +74,12 @@ function boilerv.new(session_id, unit_id, advert, out_queue) env_loss = 0.0 }, state = { + last_update = 0, temperature = 0.0, boil_rate = 0.0 }, tanks = { + last_update = 0, steam = { type = "mekanism:empty_gas", amount = 0 }, ---@type tank_fluid steam_need = 0, steam_fill = 0.0, @@ -140,6 +144,7 @@ function boilerv.new(session_id, unit_id, advert, out_queue) -- build response -- load in data if correct length if m_pkt.length == 13 then + self.db.build.last_update = util.time_ms() self.db.build.length = m_pkt.data[1] self.db.build.width = m_pkt.data[2] self.db.build.height = m_pkt.data[3] @@ -161,6 +166,7 @@ function boilerv.new(session_id, unit_id, advert, out_queue) -- state response -- load in data if correct length if m_pkt.length == 2 then + self.db.state.last_update = util.time_ms() self.db.state.temperature = m_pkt.data[1] self.db.state.boil_rate = m_pkt.data[2] else @@ -170,18 +176,19 @@ function boilerv.new(session_id, unit_id, advert, out_queue) -- tanks response -- load in data if correct length if m_pkt.length == 12 then - self.db.tanks.steam = m_pkt.data[1] - self.db.tanks.steam_need = m_pkt.data[2] - self.db.tanks.steam_fill = m_pkt.data[3] - self.db.tanks.water = m_pkt.data[4] - self.db.tanks.water_need = m_pkt.data[5] - self.db.tanks.water_fill = m_pkt.data[6] - self.db.tanks.hcool = m_pkt.data[7] - self.db.tanks.hcool_need = m_pkt.data[8] - self.db.tanks.hcool_fill = m_pkt.data[9] - self.db.tanks.ccool = m_pkt.data[10] - self.db.tanks.ccool_need = m_pkt.data[11] - self.db.tanks.ccool_fill = m_pkt.data[12] + self.db.tanks.last_update = util.time_ms() + self.db.tanks.steam = m_pkt.data[1] + self.db.tanks.steam_need = m_pkt.data[2] + self.db.tanks.steam_fill = m_pkt.data[3] + self.db.tanks.water = m_pkt.data[4] + self.db.tanks.water_need = m_pkt.data[5] + self.db.tanks.water_fill = m_pkt.data[6] + self.db.tanks.hcool = m_pkt.data[7] + self.db.tanks.hcool_need = m_pkt.data[8] + self.db.tanks.hcool_fill = m_pkt.data[9] + self.db.tanks.ccool = m_pkt.data[10] + self.db.tanks.ccool_need = m_pkt.data[11] + self.db.tanks.ccool_fill = m_pkt.data[12] else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end diff --git a/supervisor/session/rtu/envd.lua b/supervisor/session/rtu/envd.lua index 93968a9..3050835 100644 --- a/supervisor/session/rtu/envd.lua +++ b/supervisor/session/rtu/envd.lua @@ -1,6 +1,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local types = require("scada-common.types") +local util = require("scada-common.util") local unit_session = require("supervisor.session.rtu.unit_session") @@ -42,6 +43,7 @@ function envd.new(session_id, unit_id, advert, out_queue) }, ---@class envd_session_db db = { + last_update = 0, radiation = {}, radiation_raw = 0 } @@ -68,7 +70,8 @@ function envd.new(session_id, unit_id, advert, out_queue) elseif txn_type == TXN_TYPES.RAD then -- radiation status response if m_pkt.length == 2 then - self.db.radiation = m_pkt.data[1] + self.db.last_update = util.time_ms() + self.db.radiation = m_pkt.data[1] self.db.radiation_raw = m_pkt.data[2] else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua index d56be02..fc64b6d 100644 --- a/supervisor/session/rtu/imatrix.lua +++ b/supervisor/session/rtu/imatrix.lua @@ -1,6 +1,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local types = require("scada-common.types") +local util = require("scada-common.util") local unit_session = require("supervisor.session.rtu.unit_session") @@ -57,6 +58,7 @@ function imatrix.new(session_id, unit_id, advert, out_queue) db = { formed = false, build = { + last_update = 0, length = 0, width = 0, height = 0, @@ -68,10 +70,12 @@ function imatrix.new(session_id, unit_id, advert, out_queue) providers = 0 }, state = { + last_update = 0, last_input = 0, last_output = 0 }, tanks = { + last_update = 0, energy = 0, energy_need = 0, energy_fill = 0.0 @@ -127,6 +131,7 @@ function imatrix.new(session_id, unit_id, advert, out_queue) -- build response -- load in data if correct length if m_pkt.length == 9 then + self.db.build.last_update = util.time_ms() self.db.build.length = m_pkt.data[1] self.db.build.width = m_pkt.data[2] self.db.build.height = m_pkt.data[3] @@ -144,6 +149,7 @@ function imatrix.new(session_id, unit_id, advert, out_queue) -- state response -- load in data if correct length if m_pkt.length == 2 then + self.db.state.last_update = util.time_ms() self.db.state.last_input = m_pkt.data[1] self.db.state.last_output = m_pkt.data[2] else @@ -153,6 +159,7 @@ function imatrix.new(session_id, unit_id, advert, out_queue) -- tanks response -- load in data if correct length if m_pkt.length == 3 then + self.db.tanks.last_update = util.time_ms() self.db.tanks.energy = m_pkt.data[1] self.db.tanks.energy_need = m_pkt.data[2] self.db.tanks.energy_fill = m_pkt.data[3] diff --git a/supervisor/session/rtu/sna.lua b/supervisor/session/rtu/sna.lua index 2d7f885..68f38ae 100644 --- a/supervisor/session/rtu/sna.lua +++ b/supervisor/session/rtu/sna.lua @@ -1,6 +1,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local types = require("scada-common.types") +local util = require("scada-common.util") local unit_session = require("supervisor.session.rtu.unit_session") @@ -52,14 +53,17 @@ function sna.new(session_id, unit_id, advert, out_queue) ---@class sna_session_db db = { build = { + last_update = 0, input_cap = 0, output_cap = 0 }, state = { + last_update = 0, production_rate = 0.0, peak_production = 0.0 }, tanks = { + last_update = 0, input = {}, ---@type tank_fluid input_need = 0, input_fill = 0.0, @@ -104,8 +108,9 @@ function sna.new(session_id, unit_id, advert, out_queue) -- build response -- load in data if correct length if m_pkt.length == 2 then - self.db.build.input_cap = m_pkt.data[1] - self.db.build.output_cap = m_pkt.data[2] + self.db.build.last_update = util.time_ms() + self.db.build.input_cap = m_pkt.data[1] + self.db.build.output_cap = m_pkt.data[2] self.has_build = true else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") @@ -114,6 +119,7 @@ function sna.new(session_id, unit_id, advert, out_queue) -- state response -- load in data if correct length if m_pkt.length == 2 then + self.db.state.last_update = util.time_ms() self.db.state.production_rate = m_pkt.data[1] self.db.state.peak_production = m_pkt.data[2] else @@ -123,6 +129,7 @@ function sna.new(session_id, unit_id, advert, out_queue) -- tanks response -- load in data if correct length if m_pkt.length == 6 then + self.db.tanks.last_update = util.time_ms() self.db.tanks.input = m_pkt.data[1] self.db.tanks.input_need = m_pkt.data[2] self.db.tanks.input_fill = m_pkt.data[3] diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua index e7a2a58..badad45 100644 --- a/supervisor/session/rtu/sps.lua +++ b/supervisor/session/rtu/sps.lua @@ -1,6 +1,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local types = require("scada-common.types") +local util = require("scada-common.util") local unit_session = require("supervisor.session.rtu.unit_session") @@ -57,6 +58,7 @@ function sps.new(session_id, unit_id, advert, out_queue) db = { formed = false, build = { + last_update = 0, length = 0, width = 0, height = 0, @@ -68,9 +70,11 @@ function sps.new(session_id, unit_id, advert, out_queue) max_energy = 0 }, state = { + last_update = 0, process_rate = 0.0 }, tanks = { + last_update = 0, input = {}, ---@type tank_fluid input_need = 0, input_fill = 0.0, @@ -132,15 +136,16 @@ function sps.new(session_id, unit_id, advert, out_queue) -- build response -- load in data if correct length if m_pkt.length == 9 then - self.db.build.length = m_pkt.data[1] - self.db.build.width = m_pkt.data[2] - self.db.build.height = m_pkt.data[3] - self.db.build.min_pos = m_pkt.data[4] - self.db.build.max_pos = m_pkt.data[5] - self.db.build.coils = m_pkt.data[6] - self.db.build.input_cap = m_pkt.data[7] - self.db.build.output_cap = m_pkt.data[8] - self.db.build.max_energy = m_pkt.data[9] + self.db.build.last_update = util.time_ms() + self.db.build.length = m_pkt.data[1] + self.db.build.width = m_pkt.data[2] + self.db.build.height = m_pkt.data[3] + self.db.build.min_pos = m_pkt.data[4] + self.db.build.max_pos = m_pkt.data[5] + self.db.build.coils = m_pkt.data[6] + self.db.build.input_cap = m_pkt.data[7] + self.db.build.output_cap = m_pkt.data[8] + self.db.build.max_energy = m_pkt.data[9] self.has_build = true else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") @@ -149,6 +154,7 @@ function sps.new(session_id, unit_id, advert, out_queue) -- state response -- load in data if correct length if m_pkt.length == 1 then + self.db.state.last_update = util.time_ms() self.db.state.process_rate = m_pkt.data[1] else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") @@ -157,6 +163,7 @@ function sps.new(session_id, unit_id, advert, out_queue) -- tanks response -- load in data if correct length if m_pkt.length == 9 then + self.db.tanks.last_update = util.time_ms() self.db.tanks.input = m_pkt.data[1] self.db.tanks.input_need = m_pkt.data[2] self.db.tanks.input_fill = m_pkt.data[3] diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua index 56d43ef..d30733c 100644 --- a/supervisor/session/rtu/turbinev.lua +++ b/supervisor/session/rtu/turbinev.lua @@ -79,6 +79,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue) db = { formed = false, build = { + last_update = 0, length = 0, width = 0, height = 0, @@ -96,12 +97,14 @@ function turbinev.new(session_id, unit_id, advert, out_queue) max_water_output = 0 }, state = { + last_update = 0, flow_rate = 0, prod_rate = 0, steam_input_rate = 0, dumping_mode = DUMPING_MODE.IDLE ---@type DUMPING_MODE }, tanks = { + last_update = 0, steam = { type = "mekanism:empty_gas", amount = 0 }, ---@type tank_fluid steam_need = 0, steam_fill = 0.0, @@ -178,6 +181,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue) elseif txn_type == TXN_TYPES.BUILD then -- build response if m_pkt.length == 15 then + self.db.build.last_update = util.time_ms() self.db.build.length = m_pkt.data[1] self.db.build.width = m_pkt.data[2] self.db.build.height = m_pkt.data[3] @@ -200,6 +204,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue) elseif txn_type == TXN_TYPES.STATE then -- state response if m_pkt.length == 4 then + self.db.state.last_update = util.time_ms() self.db.state.flow_rate = m_pkt.data[1] self.db.state.prod_rate = m_pkt.data[2] self.db.state.steam_input_rate = m_pkt.data[3] @@ -210,6 +215,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue) elseif txn_type == TXN_TYPES.TANKS then -- tanks response if m_pkt.length == 6 then + self.db.tanks.last_update = util.time_ms() self.db.tanks.steam = m_pkt.data[1] self.db.tanks.steam_need = m_pkt.data[2] self.db.tanks.steam_fill = m_pkt.data[3] diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index c51586e..b8dcc03 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -89,17 +89,20 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- compute a change with respect to time of the given value ---@param key string value key ---@param value number value - local function _compute_dt(key, value) + ---@param time number timestamp for value + local function _compute_dt(key, value, time) if self.deltas[key] then local data = self.deltas[key] - data.dt = (value - data.last_v) / (util.time_s() - data.last_t) + if time ~= data.last_t then + data.dt = (value - data.last_v) / (time - data.last_t) - data.last_v = value - data.last_t = util.time_s() + data.last_v = value + data.last_t = time + end else self.deltas[key] = { - last_t = util.time_s(), + last_t = time, last_v = value, dt = 0.0 } @@ -126,30 +129,36 @@ function unit.new(for_reactor, num_boilers, num_turbines) if self.plc_s ~= nil then local plc_db = self.plc_i.get_db() - _compute_dt(DT_KEYS.ReactorTemp, plc_db.mek_status.temp) - _compute_dt(DT_KEYS.ReactorFuel, plc_db.mek_status.fuel) - _compute_dt(DT_KEYS.ReactorWaste, plc_db.mek_status.waste) - _compute_dt(DT_KEYS.ReactorCCool, plc_db.mek_status.ccool_amnt) - _compute_dt(DT_KEYS.ReactorHCool, plc_db.mek_status.hcool_amnt) + local last_update_s = plc_db.last_status_update / 1000.0 + + _compute_dt(DT_KEYS.ReactorTemp, plc_db.mek_status.temp, last_update_s) + _compute_dt(DT_KEYS.ReactorFuel, plc_db.mek_status.fuel, last_update_s) + _compute_dt(DT_KEYS.ReactorWaste, plc_db.mek_status.waste, last_update_s) + _compute_dt(DT_KEYS.ReactorCCool, plc_db.mek_status.ccool_amnt, last_update_s) + _compute_dt(DT_KEYS.ReactorHCool, plc_db.mek_status.hcool_amnt, last_update_s) end for i = 1, #self.boilers do local boiler = self.boilers[i] ---@type unit_session local db = boiler.get_db() ---@type boilerv_session_db - _compute_dt(DT_KEYS.BoilerWater .. boiler.get_device_idx(), db.tanks.water.amount) - _compute_dt(DT_KEYS.BoilerSteam .. boiler.get_device_idx(), db.tanks.steam.amount) - _compute_dt(DT_KEYS.BoilerCCool .. boiler.get_device_idx(), db.tanks.ccool.amount) - _compute_dt(DT_KEYS.BoilerHCool .. boiler.get_device_idx(), db.tanks.hcool.amount) + local last_update_s = db.tanks.last_update / 1000.0 + + _compute_dt(DT_KEYS.BoilerWater .. boiler.get_device_idx(), db.tanks.water.amount, last_update_s) + _compute_dt(DT_KEYS.BoilerSteam .. boiler.get_device_idx(), db.tanks.steam.amount, last_update_s) + _compute_dt(DT_KEYS.BoilerCCool .. boiler.get_device_idx(), db.tanks.ccool.amount, last_update_s) + _compute_dt(DT_KEYS.BoilerHCool .. boiler.get_device_idx(), db.tanks.hcool.amount, last_update_s) end for i = 1, #self.turbines do local turbine = self.turbines[i] ---@type unit_session local db = turbine.get_db() ---@type turbinev_session_db - _compute_dt(DT_KEYS.TurbineSteam .. turbine.get_device_idx(), db.tanks.steam.amount) + local last_update_s = db.tanks.last_update / 1000.0 + + _compute_dt(DT_KEYS.TurbineSteam .. turbine.get_device_idx(), db.tanks.steam.amount, last_update_s) ---@todo unused currently? - _compute_dt(DT_KEYS.TurbinePower .. turbine.get_device_idx(), db.tanks.energy) + _compute_dt(DT_KEYS.TurbinePower .. turbine.get_device_idx(), db.tanks.energy, last_update_s) end end @@ -182,10 +191,9 @@ function unit.new(for_reactor, num_boilers, num_turbines) self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > 1000 self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > 100 self.db.annunciator.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < 0.0 or plc_db.mek_status.fuel_fill <= 0.01 - ---@todo this is catagorized as not urgent, but the >= 0.99 is extremely urgent, revist this (RPS will kick in though) - self.db.annunciator.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 0.0 or plc_db.mek_status.waste_fill >= 0.99 + self.db.annunciator.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 0.0 or plc_db.mek_status.waste_fill >= 0.85 ---@todo this is dependent on setup, i.e. how much coolant is buffered and the turbine setup - self.db.annunciator.HighStartupRate = not plc_db.control_state and plc_db.mek_status.burn_rate > 40 + self.db.annunciator.HighStartupRate = not plc_db.mek_status.status and plc_db.mek_status.burn_rate > 40 end ------------- diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 69cb1fe..7d1fae1 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.6.2" +local SUPERVISOR_VERSION = "beta-v0.6.3" local print = util.print local println = util.println From bfa87815facce5a2d084e3969fcf76a6863810ff Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 12 Oct 2022 16:37:11 -0400 Subject: [PATCH 410/587] #90 flashing GUI indicator lights --- coordinator/renderer.lua | 34 +++++++--- coordinator/startup.lua | 12 ++-- coordinator/ui/components/unit_detail.lua | 22 +++--- graphics/core.lua | 4 ++ graphics/elements/indicators/light.lua | 54 +++++++++++++-- graphics/flasher.lua | 82 +++++++++++++++++++++++ 6 files changed, 176 insertions(+), 32 deletions(-) create mode 100644 graphics/flasher.lua diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index bbbd90e..94071d4 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -1,4 +1,5 @@ local log = require("scada-common.log") +local flasher = require("graphics.flasher") local iocontrol = require("coordinator.iocontrol") @@ -107,6 +108,9 @@ function renderer.start_ui() table.insert(ui.unit_layouts, unit_view(monitor, id)) end + -- start flasher callback task + flasher.init() + -- report ui as ready engine.ui_ready = true end @@ -114,25 +118,33 @@ end -- close out the UI function renderer.close_ui() - if engine.ui_ready then - -- report ui as not ready - engine.ui_ready = false + -- report ui as not ready + engine.ui_ready = false + -- stop blinking indicators + flasher.clear() + + if engine.ui_ready then -- hide to stop animation callbacks ui.main_layout.hide() for i = 1, #ui.unit_layouts do ui.unit_layouts[i].hide() engine.monitors.unit_displays[i].clear() end - - -- clear root UI elements - ui.main_layout = nil - ui.unit_layouts = {} - - -- re-draw dmesg - engine.dmesg_window.setVisible(true) - engine.dmesg_window.redraw() + else + -- clear unit displays + for i = 1, #ui.unit_layouts do + engine.monitors.unit_displays[i].clear() + end end + + -- clear root UI elements + ui.main_layout = nil + ui.unit_layouts = {} + + -- re-draw dmesg + engine.dmesg_window.setVisible(true) + engine.dmesg_window.redraw() end -- is the UI ready? diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 766b7ff..aed8ac0 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.5.3" +local COORDINATOR_VERSION = "alpha-v0.5.4" local print = util.print local println = util.println @@ -174,11 +174,13 @@ local ui_ok = init_start_ui() local no_modem = false --- start connection watchdog -conn_watchdog.feed() -log.debug("boot> conn watchdog started") +if ui_ok then + -- start connection watchdog + conn_watchdog.feed() + log.debug("boot> conn watchdog started") -log_sys("system started successfully") + log_sys("system started successfully") +end -- event loop -- ui_ok will never change in this loop, same as while true or exit if UI start failed diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 899d50e..df79a00 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -28,6 +28,8 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN local cpair = core.graphics.cpair +local period = core.flasher.PERIOD + -- create a unit view ---@param parent graphics_element parent ---@param id integer @@ -130,15 +132,15 @@ local function init(parent, id) annunciator.line_break() -- RPS - local rps_trp = IndicatorLight{parent=annunciator,label="RPS Trip",colors=cpair(colors.red,colors.gray)} - local rps_dmg = IndicatorLight{parent=annunciator,label="Damage Critical",colors=cpair(colors.yellow,colors.gray)} + local rps_trp = IndicatorLight{parent=annunciator,label="RPS Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local rps_dmg = IndicatorLight{parent=annunciator,label="Damage Critical",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} local rps_exh = IndicatorLight{parent=annunciator,label="Excess Heated Coolant",colors=cpair(colors.yellow,colors.gray)} local rps_exw = IndicatorLight{parent=annunciator,label="Excess Waste",colors=cpair(colors.yellow,colors.gray)} - local rps_tmp = IndicatorLight{parent=annunciator,label="High Core Temp",colors=cpair(colors.yellow,colors.gray)} + local rps_tmp = IndicatorLight{parent=annunciator,label="High Core Temp",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} local rps_nof = IndicatorLight{parent=annunciator,label="No Fuel",colors=cpair(colors.yellow,colors.gray)} local rps_noc = IndicatorLight{parent=annunciator,label="No Coolant",colors=cpair(colors.yellow,colors.gray)} - local rps_flt = IndicatorLight{parent=annunciator,label="PPM Fault",colors=cpair(colors.yellow,colors.gray)} - local rps_tmo = IndicatorLight{parent=annunciator,label="Timeout",colors=cpair(colors.yellow,colors.gray)} + local rps_flt = IndicatorLight{parent=annunciator,label="PPM Fault",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} + local rps_tmo = IndicatorLight{parent=annunciator,label="Timeout",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} r_ps.subscribe("rps_tripped", rps_trp.update) r_ps.subscribe("dmg_crit", rps_dmg.update) @@ -157,7 +159,7 @@ local function init(parent, id) local c_cfm = IndicatorLight{parent=annunciator,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} local c_sfm = IndicatorLight{parent=annunciator,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} local c_mwrf = IndicatorLight{parent=annunciator,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} - local c_tbnt = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + local c_tbnt = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} r_ps.subscribe("BoilRateMismatch", c_brm.update) r_ps.subscribe("CoolantFeedMismatch", c_cfm.update) @@ -193,7 +195,7 @@ local function init(parent, id) t_ps[1].subscribe("TurbineOverSpeed", t1_tos.update) TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t1_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + local t1_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} t_ps[1].subscribe("TurbineTrip", t1_trp.update) main.line_break() @@ -209,7 +211,7 @@ local function init(parent, id) t_ps[2].subscribe("TurbineOverSpeed", t2_tos.update) TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t2_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + local t2_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} t_ps[2].subscribe("TurbineTrip", t2_trp.update) main.line_break() @@ -226,7 +228,7 @@ local function init(parent, id) t_ps[3].subscribe("TurbineOverSpeed", t3_tos.update) TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t3_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray)} + local t3_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} t_ps[3].subscribe("TurbineTrip", t3_trp.update) annunciator.line_break() @@ -234,7 +236,7 @@ local function init(parent, id) ---@todo radiation monitor IndicatorLight{parent=annunciator,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)} - IndicatorLight{parent=annunciator,label="Radiation Alarm",colors=cpair(colors.red,colors.gray)} + IndicatorLight{parent=annunciator,label="Radiation Alarm",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} DataIndicator{parent=main,x=34,y=51,label="",format="%10.1f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=18,fg_bg=stat_fg_bg} -- reactor controls -- diff --git a/graphics/core.lua b/graphics/core.lua index f4d86fd..510e31d 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -4,6 +4,10 @@ local core = {} +local flasher = require("graphics.flasher") + +core.flasher = flasher + local events = {} ---@class monitor_touch diff --git a/graphics/elements/indicators/light.lua b/graphics/elements/indicators/light.lua index 5d38a7c..6106ebe 100644 --- a/graphics/elements/indicators/light.lua +++ b/graphics/elements/indicators/light.lua @@ -1,11 +1,15 @@ -- Indicator Light Graphics Element local element = require("graphics.element") +local flasher = require("graphics.flasher") +local util = require("scada-common.util") ---@class indicator_light_args ---@field label string indicator label ---@field colors cpair on/off colors (a/b respectively) ---@field min_label_width? integer label length if omitted +---@field flash? boolean whether to flash on true rather than stay on +---@field period? PERIOD flash period ---@field parent graphics_element ---@field id? string element id ---@field x? integer 1 if omitted @@ -19,25 +23,62 @@ local function indicator_light(args) assert(type(args.label) == "string", "graphics.elements.indicators.light: label is a required field") assert(type(args.colors) == "table", "graphics.elements.indicators.light: colors is a required field") + if args.flash then + assert(util.is_int(args.period), "graphics.elements.indicators.light: period is a required field if flash is enabled") + end + -- single line args.height = 1 -- determine width args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2 + -- flasher state + local flash_on = true + -- create new graphics element base object local e = element.new(args) + -- called by flasher when enabled + local function flash_callback() + e.window.setCursorPos(1, 1) + + if flash_on then + e.window.blit(" \x95", "0" .. args.colors.blit_a, args.colors.blit_a .. e.fg_bg.blit_bkg) + else + e.window.blit(" \x95", "0" .. args.colors.blit_b, args.colors.blit_b .. e.fg_bg.blit_bkg) + end + + flash_on = not flash_on + end + + -- enable light or start flashing + local function enable() + if args.flash then + flash_on = true + flasher.start(flash_callback, args.period) + else + e.window.setCursorPos(1, 1) + e.window.blit(" \x95", "0" .. args.colors.blit_a, args.colors.blit_a .. e.fg_bg.blit_bkg) + end + end + + -- disable light or stop flashing + local function disable() + if args.flash then + flash_on = false + flasher.stop(flash_callback) + end + + e.window.setCursorPos(1, 1) + e.window.blit(" \x95", "0" .. args.colors.blit_b, args.colors.blit_b .. e.fg_bg.blit_bkg) + end + -- on state change ---@param new_state boolean indicator state function e.on_update(new_state) e.value = new_state - e.window.setCursorPos(1, 1) - if new_state then - e.window.blit(" \x95", "0" .. args.colors.blit_a, args.colors.blit_a .. e.fg_bg.blit_bkg) - else - e.window.blit(" \x95", "0" .. args.colors.blit_b, args.colors.blit_b .. e.fg_bg.blit_bkg) - end + if new_state then enable() else disable() end end -- set indicator state @@ -46,6 +87,7 @@ local function indicator_light(args) -- write label and initial indicator light e.on_update(false) + e.window.setCursorPos(3, 1) e.window.write(args.label) return e.get() diff --git a/graphics/flasher.lua b/graphics/flasher.lua new file mode 100644 index 0000000..689fab6 --- /dev/null +++ b/graphics/flasher.lua @@ -0,0 +1,82 @@ +-- +-- Indicator Light Flasher +-- + +local tcd = require("scada-common.tcallbackdsp") + +local flasher = {} + +-- note: no additional call needs to be made in a main loop as this class automatically uses the TCD to operate + +---@alias PERIOD integer +local PERIOD = { + BLINK_250_MS = 1, + BLINK_500_MS = 2, + BLINK_1000_MS = 3 +} + +flasher.PERIOD = PERIOD + +local active = false +local registry = { {}, {}, {} } -- one registry table per period +local callback_counter = 0 + +-- start the flasher task +function flasher.init() + active = true + registry = { {}, {}, {} } + flasher.callback_250ms() +end + +-- clear all blinking indicators and stop the flasher task +function flasher.clear() + active = false + registry = { {}, {}, {} } +end + +-- register a function to be called on the selected blink period +-- +-- times are not strictly enforced, but all with a given period will be set at the same time +---@param f function function to call each period +---@param period PERIOD time period option (1, 2, or 3) +function flasher.start(f, period) + if type(registry[period]) == "table" then + table.insert(registry[period], f) + end +end + +-- stop a function from being called at the blink period +---@param f function function callback registered +function flasher.stop(f) + for i = 1, #registry do + for j = 1, #registry[i] do + if registry[i][j] == f then + registry[i][j] = nil + break + end + end + end +end + +-- blink registered indicators +-- +-- this assumes it is called every 250ms, it does no checking of time on its own +function flasher.callback_250ms() + if active then + for _, f in pairs(registry[PERIOD.BLINK_250_MS]) do f() end + + if callback_counter % 2 == 0 then + for _, f in pairs(registry[PERIOD.BLINK_500_MS]) do f() end + end + + if callback_counter % 4 == 0 then + for _, f in pairs(registry[PERIOD.BLINK_1000_MS]) do f() end + end + + callback_counter = callback_counter + 1 + + tcd.dispatch(0.25, flasher.callback_250ms) + end +end + +return flasher From ab757e14a73fb7afa594d36e1c18722274fae422 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 20 Oct 2022 12:22:03 -0400 Subject: [PATCH 411/587] #100 work in progress on command acks for reactive buttons --- coordinator/coordinator.lua | 23 +++++++++++++-- coordinator/iocontrol.lua | 47 ++++++++++++++++++------------ coordinator/startup.lua | 2 +- supervisor/session/coordinator.lua | 17 ++++++----- supervisor/session/plc.lua | 30 +++++++++++++++++++ supervisor/session/svqtypes.lua | 4 ++- supervisor/session/svsessions.lua | 45 +++++++++++++++------------- supervisor/startup.lua | 2 +- 8 files changed, 118 insertions(+), 52 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 22e79d9..655ce58 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -18,6 +18,7 @@ local println_ts = util.println_ts local PROTOCOLS = comms.PROTOCOLS local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES +local CRDN_COMMANDS = comms.CRDN_COMMANDS -- request the user to select a monitor ---@param names table available monitors @@ -306,11 +307,11 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa end -- send a unit command - ---@param unit integer unit ID ---@param cmd CRDN_COMMANDS command + ---@param unit integer unit ID ---@param option any? optional options (like burn rate) - function public.send_command(unit, cmd, option) - _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.COMMAND_UNIT, { unit, cmd, option }) + function public.send_command(cmd, unit, option) + _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.COMMAND_UNIT, { cmd, unit, option }) end -- parse a packet @@ -425,6 +426,22 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa log.error("received invalid unit statuses packet") end elseif packet.type == SCADA_CRDN_TYPES.COMMAND_UNIT then + -- unit command acknowledgement + if packet.length == 3 then + local cmd = packet.data[1] + local unit = packet.data[2] + local ack = packet.data[3] + + if cmd == CRDN_COMMANDS.SCRAM then + elseif cmd == CRDN_COMMANDS.START then + elseif cmd == CRDN_COMMANDS.RESET_RPS then + elseif cmd == CRDN_COMMANDS.SET_BURN then + elseif cmd == CRDN_COMMANDS.SET_WASTE then + else + end + else + log.debug("unit command ack packet length mismatch") + end elseif packet.type == SCADA_CRDN_TYPES.ALARM then else log.warning("received unknown SCADA_CRDN packet type " .. packet.type) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index bc93f05..339ad63 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -34,25 +34,10 @@ function iocontrol.init(conf, comms) burn_rate_cmd = 0.0, waste_control = 0, - start = function () - comms.send_command(i, CRDN_COMMANDS.START) - log.debug(util.c("sent unit ", i, ": START")) - end, - - scram = function () - comms.send_command(i, CRDN_COMMANDS.SCRAM) - log.debug(util.c("sent unit ", i, ": SCRAM")) - end, - - reset_rps = function () - comms.send_command(i, CRDN_COMMANDS.RESET_RPS) - log.debug(util.c("sent unit ", i, ": RESET_RPS")) - end, - - set_burn = function (rate) - comms.send_command(i, CRDN_COMMANDS.SET_BURN, rate) - log.debug(util.c("sent unit ", i, ": SET_BURN = ", rate)) - end, + start = function () end, + scram = function () end, + reset_rps = function () end, + set_burn = function (rate) end, reactor_ps = psil.create(), reactor_data = {}, ---@type reactor_db @@ -64,12 +49,36 @@ function iocontrol.init(conf, comms) turbine_data_tbl = {} } + function entry.start() + entry.control_state = true + comms.send_command(CRDN_COMMANDS.START, i) + log.debug(util.c("UNIT[", i, "]: START")) + end + + function entry.scram() + entry.control_state = false + comms.send_command(CRDN_COMMANDS.SCRAM, i) + log.debug(util.c("UNIT[", i, "]: SCRAM")) + end + + function entry.reset_rps() + comms.send_command(CRDN_COMMANDS.RESET_RPS, i) + log.debug(util.c("UNIT[", i, "]: RESET_RPS")) + end + + function entry.set_burn(rate) + comms.send_command(CRDN_COMMANDS.SET_BURN, i, rate) + log.debug(util.c("UNIT[", i, "]: SET_BURN = ", rate)) + end + + -- create boiler tables for _ = 1, conf.defs[(i * 2) - 1] do local data = {} ---@type boilerv_session_db table.insert(entry.boiler_ps_tbl, psil.create()) table.insert(entry.boiler_data_tbl, data) end + -- create turbine tables for _ = 1, conf.defs[i * 2] do local data = {} ---@type turbinev_session_db table.insert(entry.turbine_ps_tbl, psil.create()) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index aed8ac0..915cb26 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.5.4" +local COORDINATOR_VERSION = "alpha-v0.5.5" local print = util.print local println = util.println diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index e3c80f7..3db1da7 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -182,26 +182,29 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) elseif pkt.type == SCADA_CRDN_TYPES.COMMAND_UNIT then if pkt.length >= 2 then -- get command and unit id - local uid = pkt.data[1] - local cmd = pkt.data[2] + local cmd = pkt.data[1] + local uid = pkt.data[2] + + -- pkt.data[3] will be nil except for some commands + local data = { uid, pkt.data[3] } -- continue if valid unit id if util.is_int(uid) and uid > 0 and uid <= #self.units then if cmd == CRDN_COMMANDS.START then - self.out_q.push_data(SV_Q_DATA.START, uid) + self.out_q.push_data(SV_Q_DATA.START, data) elseif cmd == CRDN_COMMANDS.SCRAM then - self.out_q.push_data(SV_Q_DATA.SCRAM, uid) + self.out_q.push_data(SV_Q_DATA.SCRAM, data) elseif cmd == CRDN_COMMANDS.RESET_RPS then - self.out_q.push_data(SV_Q_DATA.RESET_RPS, uid) + self.out_q.push_data(SV_Q_DATA.RESET_RPS, data) elseif cmd == CRDN_COMMANDS.SET_BURN then if pkt.length == 3 then - self.out_q.push_data(SV_Q_DATA.SET_BURN, { uid, pkt.data[3] }) + self.out_q.push_data(SV_Q_DATA.SET_BURN, data) else log.debug(log_header .. "CRDN command unit burn rate missing option") end elseif cmd == CRDN_COMMANDS.SET_WASTE then if pkt.length == 3 then - self.out_q.push_data(SV_Q_DATA.SET_WASTE, { uid, pkt.data[3] }) + self.out_q.push_data(SV_Q_DATA.SET_WASTE, data) else log.debug(log_header .. "CRDN command unit set waste missing option") end diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index a233387..514cd60 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -11,6 +11,8 @@ local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES +local CRDN_COMMANDS = comms.CRDN_COMMANDS + local print = util.print local println = util.println local print_ts = util.print_ts @@ -336,6 +338,13 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) elseif ack == false then log.debug(log_header .. "burn rate update failed!") end + + -- send acknowledgement to coordinator + self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { + unit = self.for_reactor, + cmd = CRDN_COMMANDS.SET_BURN, + ack = ack + }) elseif pkt.type == RPLC_TYPES.RPS_ENABLE then -- enable acknowledgement local ack = _get_ack(pkt) @@ -345,6 +354,13 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) elseif ack == false then log.debug(log_header .. "enable failed!") end + + -- send acknowledgement to coordinator + self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { + unit = self.for_reactor, + cmd = CRDN_COMMANDS.START, + ack = ack + }) elseif pkt.type == RPLC_TYPES.RPS_SCRAM then -- SCRAM acknowledgement local ack = _get_ack(pkt) @@ -354,6 +370,13 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) elseif ack == false then log.debug(log_header .. "SCRAM failed!") end + + -- send acknowledgement to coordinator + self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { + unit = self.for_reactor, + cmd = CRDN_COMMANDS.SCRAM, + ack = ack + }) elseif pkt.type == RPLC_TYPES.RPS_STATUS then -- RPS status packet received, copy data if pkt.length == 9 then @@ -392,6 +415,13 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) elseif ack == false then log.debug(log_header .. "RPS reset failed") end + + -- send acknowledgement to coordinator + self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { + unit = self.for_reactor, + cmd = CRDN_COMMANDS.RESET_RPS, + ack = ack + }) else log.debug(log_header .. "handler received unsupported RPLC packet type " .. pkt.type) end diff --git a/supervisor/session/svqtypes.lua b/supervisor/session/svqtypes.lua index 175f98b..19f9d54 100644 --- a/supervisor/session/svqtypes.lua +++ b/supervisor/session/svqtypes.lua @@ -9,7 +9,9 @@ local SV_Q_DATA = { SCRAM = 2, RESET_RPS = 3, SET_BURN = 4, - SET_WASTE = 5 + SET_WASTE = 5, + __END_PLC_CMDS__ = 6, + CRDN_ACK = 7 } svqtypes.SV_Q_CMDS = SV_Q_CMDS diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 2c42f91..d2ccad0 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -70,25 +70,30 @@ local function _sv_handle_outq(session) elseif msg.qtype == mqueue.TYPE.DATA then -- instruction/notification with body local cmd = msg.message ---@type queue_data - local plc_s = nil - if type(cmd.val) == "table" then - plc_s = svsessions.get_reactor_session(cmd.val[1]) - elseif type(cmd.val) == "number" then - plc_s = svsessions.get_reactor_session(cmd.val) - end + if cmd.key < SV_Q_DATA.__END_PLC_CMDS__ then + -- PLC commands from coordinator + local crdn_sid = session.instance.get_id() + local plc_s = svsessions.get_reactor_session(cmd.val[1]) - if plc_s ~= nil then - if cmd.key == SV_Q_DATA.START then - plc_s.in_queue.push_command(PLC_S_CMDS.ENABLE) - elseif cmd.key == SV_Q_DATA.SCRAM then - plc_s.in_queue.push_command(PLC_S_CMDS.SCRAM) - elseif cmd.key == SV_Q_DATA.RESET_RPS then - plc_s.in_queue.push_command(PLC_S_CMDS.RPS_RESET) - elseif cmd.key == SV_Q_DATA.SET_BURN and type(cmd.val) == "table" and #cmd.val == 2 then - plc_s.in_queue.push_data(PLC_S_DATA.BURN_RATE, cmd.val[2]) - elseif cmd.key == SV_Q_DATA.SET_WASTE and type(cmd.val) == "table" and #cmd.val == 2 then - ---@todo set waste + if plc_s ~= nil then + if cmd.key == SV_Q_DATA.START then + plc_s.in_queue.push_command(PLC_S_CMDS.ENABLE) + elseif cmd.key == SV_Q_DATA.SCRAM then + plc_s.in_queue.push_command(PLC_S_CMDS.SCRAM) + elseif cmd.key == SV_Q_DATA.RESET_RPS then + plc_s.in_queue.push_command(PLC_S_CMDS.RPS_RESET) + elseif cmd.key == SV_Q_DATA.SET_BURN and type(cmd.val) == "table" and #cmd.val == 2 then + plc_s.in_queue.push_data(PLC_S_DATA.BURN_RATE, cmd.val[2]) + elseif cmd.key == SV_Q_DATA.SET_WASTE and type(cmd.val) == "table" and #cmd.val == 2 then + ---@todo set waste + else + log.debug(util.c("unknown PLC SV queue command ", cmd.key)) + end + end + else + if cmd.key == SV_Q_DATA.CRDN_ACK then + ---@todo ack to be sent to coordinator end end end @@ -279,7 +284,7 @@ function svsessions.establish_plc_session(local_port, remote_port, for_reactor, r_port = remote_port, in_queue = mqueue.new(), out_queue = mqueue.new(), - instance = nil + instance = nil ---@type plc_session } plc_s.instance = plc.new_session(self.next_plc_id, for_reactor, plc_s.in_queue, plc_s.out_queue) @@ -317,7 +322,7 @@ function svsessions.establish_rtu_session(local_port, remote_port, advertisement r_port = remote_port, in_queue = mqueue.new(), out_queue = mqueue.new(), - instance = nil + instance = nil ---@type rtu_session } rtu_s.instance = rtu.new_session(self.next_rtu_id, rtu_s.in_queue, rtu_s.out_queue, advertisement, self.facility_units) @@ -346,7 +351,7 @@ function svsessions.establish_coord_session(local_port, remote_port, version) r_port = remote_port, in_queue = mqueue.new(), out_queue = mqueue.new(), - instance = nil + instance = nil ---@type coord_session } coord_s.instance = coordinator.new_session(self.next_coord_id, coord_s.in_queue, coord_s.out_queue, self.facility_units) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 7d1fae1..b7acdfb 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.6.3" +local SUPERVISOR_VERSION = "beta-v0.6.4" local print = util.print local println = util.println From 6d5af983101e46be49a2e71890ffae62df109b7a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 20 Oct 2022 12:22:45 -0400 Subject: [PATCH 412/587] graphics element enable/disable, click indication on hazard buttons --- graphics/element.lua | 23 ++++++- graphics/elements/controls/hazard_button.lua | 62 ++++++++++++------- graphics/elements/controls/multi_button.lua | 2 +- graphics/elements/controls/push_button.lua | 34 +++++----- .../elements/controls/spinbox_numeric.lua | 2 +- graphics/elements/controls/switch_button.lua | 12 ++-- 6 files changed, 88 insertions(+), 47 deletions(-) diff --git a/graphics/element.lua b/graphics/element.lua index 907cc8e..70b784c 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -60,6 +60,7 @@ function element.new(args) ---@class graphics_template local protected = { + enabled = true, value = nil, ---@type any window = nil, ---@type table fg_bg = core.graphics.cpair(colors.white, colors.black), @@ -157,6 +158,7 @@ function element.new(args) end -- handle data value changes + ---@vararg any value(s) function protected.on_update(...) end @@ -168,7 +170,14 @@ function element.new(args) -- set value ---@param value any value to set function protected.set_value(value) - return nil + end + + -- enable the control + function protected.enable() + end + + -- disable the control + function protected.disable() end -- custom recolor command, varies by element if implemented @@ -301,6 +310,18 @@ function element.new(args) protected.set_value(value) end + -- enable the element + function public.enable() + protected.enabled = true + protected.enable() + end + + -- disable the element + function public.disable() + protected.enabled = false + protected.disable() + end + -- resize attributes of the element value if supported ---@vararg number dimensions (element specific) function public.resize(...) diff --git a/graphics/elements/controls/hazard_button.lua b/graphics/elements/controls/hazard_button.lua index c931f19..c0395b9 100644 --- a/graphics/elements/controls/hazard_button.lua +++ b/graphics/elements/controls/hazard_button.lua @@ -35,37 +35,53 @@ local function hazard_button(args) e.window.write(args.text) -- draw border + ---@param accent color accent color + local function draw_border(accent) + -- top + e.window.setTextColor(args.accent) + e.window.setBackgroundColor(args.fg_bg.bkg) + e.window.setCursorPos(1, 1) + e.window.write("\x99\x89\x89\x89\x89\x89\x89\x89\x99") - -- top - e.window.setTextColor(args.accent) - e.window.setBackgroundColor(args.fg_bg.bkg) - e.window.setCursorPos(1, 1) - e.window.write("\x99\x89\x89\x89\x89\x89\x89\x89\x99") + -- center left + e.window.setCursorPos(1, 2) + e.window.setTextColor(args.fg_bg.bkg) + e.window.setBackgroundColor(args.accent) + e.window.write("\x99") - -- center left - e.window.setCursorPos(1, 2) - e.window.setTextColor(args.fg_bg.bkg) - e.window.setBackgroundColor(args.accent) - e.window.write("\x99") + -- center right + e.window.setTextColor(args.fg_bg.bkg) + e.window.setBackgroundColor(args.accent) + e.window.setCursorPos(9, 2) + e.window.write("\x99") - -- center right - e.window.setTextColor(args.fg_bg.bkg) - e.window.setBackgroundColor(args.accent) - e.window.setCursorPos(9, 2) - e.window.write("\x99") - - -- bottom - e.window.setTextColor(args.accent) - e.window.setBackgroundColor(args.fg_bg.bkg) - e.window.setCursorPos(1, 3) - e.window.write("\x99\x98\x98\x98\x98\x98\x98\x98\x99") + -- bottom + e.window.setTextColor(args.accent) + e.window.setBackgroundColor(args.fg_bg.bkg) + e.window.setCursorPos(1, 3) + e.window.write("\x99\x98\x98\x98\x98\x98\x98\x98\x99") + end -- handle touch ---@param event monitor_touch monitor touch event ---@diagnostic disable-next-line: unused-local function e.handle_touch(event) - -- call the touch callback - args.callback() + if e.enabled then + -- call the touch callback + args.callback() + + -- change text color to indicate clicked + e.window.setTextColor(args.accent) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + + -- restore text color after 1 second + tcd.dispatch(1, function () + e.window.setTextColor(args.fg_bg.fgd) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + end) + end end -- set the value diff --git a/graphics/elements/controls/multi_button.lua b/graphics/elements/controls/multi_button.lua index 84aa65b..1dd39b4 100644 --- a/graphics/elements/controls/multi_button.lua +++ b/graphics/elements/controls/multi_button.lua @@ -91,7 +91,7 @@ local function multi_button(args) ---@param event monitor_touch monitor touch event function e.handle_touch(event) -- determine what was pressed - if event.y == 2 then + if e.enabled and event.y == 2 then for i = 1, #args.options do local opt = args.options[i] ---@type button_option diff --git a/graphics/elements/controls/push_button.lua b/graphics/elements/controls/push_button.lua index d8a1755..d4fbcaa 100644 --- a/graphics/elements/controls/push_button.lua +++ b/graphics/elements/controls/push_button.lua @@ -51,24 +51,26 @@ local function push_button(args) ---@param event monitor_touch monitor touch event ---@diagnostic disable-next-line: unused-local function e.handle_touch(event) - if args.active_fg_bg ~= nil then - -- show as pressed - e.value = true - e.window.setTextColor(args.active_fg_bg.fgd) - e.window.setBackgroundColor(args.active_fg_bg.bkg) - draw() - - -- show as unpressed in 0.25 seconds - tcd.dispatch(0.25, function () - e.value = false - e.window.setTextColor(e.fg_bg.fgd) - e.window.setBackgroundColor(e.fg_bg.bkg) + if e.enabled then + if args.active_fg_bg ~= nil then + -- show as pressed + e.value = true + e.window.setTextColor(args.active_fg_bg.fgd) + e.window.setBackgroundColor(args.active_fg_bg.bkg) draw() - end) - end - -- call the touch callback - args.callback() + -- show as unpressed in 0.25 seconds + tcd.dispatch(0.25, function () + e.value = false + e.window.setTextColor(e.fg_bg.fgd) + e.window.setBackgroundColor(e.fg_bg.bkg) + draw() + end) + end + + -- call the touch callback + args.callback() + end end -- set the value diff --git a/graphics/elements/controls/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua index 1f61a5b..23065e2 100644 --- a/graphics/elements/controls/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -108,7 +108,7 @@ local function spinbox(args) ---@param event monitor_touch monitor touch event function e.handle_touch(event) -- only handle if on an increment or decrement arrow - if event.x ~= dec_point_x then + if e.enabled and event.x ~= dec_point_x then local idx = util.trinary(event.x > dec_point_x, event.x - 1, event.x) if event.y == 1 then -- increment diff --git a/graphics/elements/controls/switch_button.lua b/graphics/elements/controls/switch_button.lua index 2863747..cb003cb 100644 --- a/graphics/elements/controls/switch_button.lua +++ b/graphics/elements/controls/switch_button.lua @@ -63,12 +63,14 @@ local function switch_button(args) ---@param event monitor_touch monitor touch event ---@diagnostic disable-next-line: unused-local function e.handle_touch(event) - -- toggle state - e.value = not e.value - draw_state() + if e.enabled then + -- toggle state + e.value = not e.value + draw_state() - -- call the touch callback with state - args.callback(e.value) + -- call the touch callback with state + args.callback(e.value) + end end -- set the value From 1bf8fe557c29bcdde0858287047de3532ef5e57f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 20 Oct 2022 12:23:00 -0400 Subject: [PATCH 413/587] flasher callback now private function --- graphics/flasher.lua | 48 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/graphics/flasher.lua b/graphics/flasher.lua index 689fab6..97afd83 100644 --- a/graphics/flasher.lua +++ b/graphics/flasher.lua @@ -21,14 +21,35 @@ local active = false local registry = { {}, {}, {} } -- one registry table per period local callback_counter = 0 --- start the flasher task +-- blink registered indicators +-- +-- this assumes it is called every 250ms, it does no checking of time on its own +local function callback_250ms() + if active then + for _, f in pairs(registry[PERIOD.BLINK_250_MS]) do f() end + + if callback_counter % 2 == 0 then + for _, f in pairs(registry[PERIOD.BLINK_500_MS]) do f() end + end + + if callback_counter % 4 == 0 then + for _, f in pairs(registry[PERIOD.BLINK_1000_MS]) do f() end + end + + callback_counter = callback_counter + 1 + + tcd.dispatch(0.25, callback_250ms) + end +end + +-- start the flasher periodic function flasher.init() active = true registry = { {}, {}, {} } - flasher.callback_250ms() + callback_250ms() end --- clear all blinking indicators and stop the flasher task +-- clear all blinking indicators and stop the flasher periodic function flasher.clear() active = false registry = { {}, {}, {} } @@ -58,25 +79,4 @@ function flasher.stop(f) end end --- blink registered indicators --- --- this assumes it is called every 250ms, it does no checking of time on its own -function flasher.callback_250ms() - if active then - for _, f in pairs(registry[PERIOD.BLINK_250_MS]) do f() end - - if callback_counter % 2 == 0 then - for _, f in pairs(registry[PERIOD.BLINK_500_MS]) do f() end - end - - if callback_counter % 4 == 0 then - for _, f in pairs(registry[PERIOD.BLINK_1000_MS]) do f() end - end - - callback_counter = callback_counter + 1 - - tcd.dispatch(0.25, flasher.callback_250ms) - end -end - return flasher From 2f55ad76f29a1f9a6da3ba2153a986b4e02f9ef0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 20 Oct 2022 13:27:33 -0400 Subject: [PATCH 414/587] round burn rate to prevent weird floating point issues, added debug prints --- reactor-plc/plc.lua | 8 +++++++- reactor-plc/startup.lua | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 4a69c5e..5e04ed1 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -200,6 +200,8 @@ function plc.rps_init(reactor) self.reactor_enabled = true return true end + else + log.debug(util.c("RPS: failed start, RPS tripped: ", self.trip_cause)) end return false @@ -280,6 +282,8 @@ function plc.rps_init(reactor) for i = 1, #self.state do self.state[i] = false end + + log.info("RPS: reset") end return public @@ -677,7 +681,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co -- set the burn rate if packet.length == 2 then local success = false - local burn_rate = packet.data[1] + local burn_rate = math.floor(packet.data[1] * 10) / 10 local ramp = packet.data[2] -- if no known max burn rate, check again @@ -696,6 +700,8 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co self.reactor.setBurnRate(burn_rate) success = not self.reactor.__p_is_faulted() end + else + log.debug(burn_rate .. " rate outside of 0 < x <= " .. self.max_burn_rate) end end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 4909737..81338ee 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.8.8" +local R_PLC_VERSION = "beta-v0.8.9" local print = util.print local println = util.println @@ -143,14 +143,14 @@ local function init() smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog) log.debug("init> comms init") else - println("boot> starting in offline mode"); + println("boot> starting in offline mode") log.debug("init> running without networking") end ---@diagnostic disable-next-line: undefined-field os.queueEvent("clock_start") - println("boot> completed"); + println("boot> completed") log.debug("init> boot completed") else println("boot> system in degraded state, awaiting devices...") From 788fae44aa2f46c9c25f5e40baa5f3a090602b83 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 20 Oct 2022 13:53:39 -0400 Subject: [PATCH 415/587] #105 single coordinator configuration --- coordinator/coordinator.lua | 4 +- coordinator/startup.lua | 2 +- graphics/elements/controls/hazard_button.lua | 12 +++-- supervisor/session/svsessions.lua | 56 ++++++++++++-------- supervisor/startup.lua | 2 +- supervisor/supervisor.lua | 35 +++++++----- 6 files changed, 69 insertions(+), 42 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 655ce58..13ec9a4 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -409,6 +409,8 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa else log.debug("supervisor conn establish packet length mismatch") end + elseif packet.length == 1 and packet.data[1] == false then + log.debug("supervisor connection denied") else log.debug("supervisor conn establish packet length mismatch") end @@ -431,7 +433,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa local cmd = packet.data[1] local unit = packet.data[2] local ack = packet.data[3] - + if cmd == CRDN_COMMANDS.SCRAM then elseif cmd == CRDN_COMMANDS.START then elseif cmd == CRDN_COMMANDS.RESET_RPS then diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 915cb26..fac7ab8 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.5.5" +local COORDINATOR_VERSION = "alpha-v0.5.6" local print = util.print local println = util.println diff --git a/graphics/elements/controls/hazard_button.lua b/graphics/elements/controls/hazard_button.lua index c0395b9..5fca7fe 100644 --- a/graphics/elements/controls/hazard_button.lua +++ b/graphics/elements/controls/hazard_button.lua @@ -38,7 +38,7 @@ local function hazard_button(args) ---@param accent color accent color local function draw_border(accent) -- top - e.window.setTextColor(args.accent) + e.window.setTextColor(accent) e.window.setBackgroundColor(args.fg_bg.bkg) e.window.setCursorPos(1, 1) e.window.write("\x99\x89\x89\x89\x89\x89\x89\x89\x99") @@ -46,17 +46,17 @@ local function hazard_button(args) -- center left e.window.setCursorPos(1, 2) e.window.setTextColor(args.fg_bg.bkg) - e.window.setBackgroundColor(args.accent) + e.window.setBackgroundColor(accent) e.window.write("\x99") -- center right e.window.setTextColor(args.fg_bg.bkg) - e.window.setBackgroundColor(args.accent) + e.window.setBackgroundColor(accent) e.window.setCursorPos(9, 2) e.window.write("\x99") -- bottom - e.window.setTextColor(args.accent) + e.window.setTextColor(accent) e.window.setBackgroundColor(args.fg_bg.bkg) e.window.setCursorPos(1, 3) e.window.write("\x99\x98\x98\x98\x98\x98\x98\x98\x99") @@ -90,6 +90,10 @@ local function hazard_button(args) if val then e.handle_touch(core.events.touch("", 1, 1)) end end + -- initial draw of border + ---@todo disabling will change border + draw_border(args.accent) + return e.get() end diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index d2ccad0..de54493 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -60,12 +60,9 @@ local function _sv_handle_outq(session) elseif msg.qtype == mqueue.TYPE.COMMAND then -- handle instruction/notification local cmd = msg.message - if cmd == SV_Q_CMDS.BUILD_CHANGED then - -- notify coordinator(s) that a build has changed - for j = 1, #self.coord_sessions do - local s = self.coord_sessions[j] ---@type coord_session_struct - s.in_queue.push_command(CRD_S_CMDS.RESEND_BUILDS) - end + if (cmd == SV_Q_CMDS.BUILD_CHANGED) and (svsessions.get_coord_session() ~= nil) then + -- notify coordinator that a build has changed + svsessions.get_coord_session().in_queue.push_command(CRD_S_CMDS.RESEND_BUILDS) end elseif msg.qtype == mqueue.TYPE.DATA then -- instruction/notification with body @@ -243,6 +240,8 @@ function svsessions.find_device_session(remote_port) end -- find a coordinator session by the remote port +-- +-- only one coordinator is allowed, but this is kept to be consistent with all other session tables ---@param remote_port integer ---@return nil function svsessions.find_coord_session(remote_port) @@ -251,6 +250,12 @@ function svsessions.find_coord_session(remote_port) return _find_session(self.coord_sessions, remote_port) end +-- get the a coordinator session if exists +---@return coord_session_struct|nil +function svsessions.get_coord_session() + return self.coord_sessions[1] +end + -- get a session by reactor ID ---@param reactor integer ---@return plc_session_struct|nil session @@ -342,27 +347,32 @@ end ---@param version string ---@return integer|false session_id function svsessions.establish_coord_session(local_port, remote_port, version) - ---@class coord_session_struct - local coord_s = { - s_type = "crd", - open = true, - version = version, - l_port = local_port, - r_port = remote_port, - in_queue = mqueue.new(), - out_queue = mqueue.new(), - instance = nil ---@type coord_session - } + if svsessions.get_coord_session() == nil then + ---@class coord_session_struct + local coord_s = { + s_type = "crd", + open = true, + version = version, + l_port = local_port, + r_port = remote_port, + in_queue = mqueue.new(), + out_queue = mqueue.new(), + instance = nil ---@type coord_session + } - coord_s.instance = coordinator.new_session(self.next_coord_id, coord_s.in_queue, coord_s.out_queue, self.facility_units) - table.insert(self.coord_sessions, coord_s) + coord_s.instance = coordinator.new_session(self.next_coord_id, coord_s.in_queue, coord_s.out_queue, self.facility_units) + table.insert(self.coord_sessions, coord_s) - log.debug("established new coordinator session to " .. remote_port .. " with ID " .. self.next_coord_id) + log.debug("established new coordinator session to " .. remote_port .. " with ID " .. self.next_coord_id) - self.next_coord_id = self.next_coord_id + 1 + self.next_coord_id = self.next_coord_id + 1 - -- success - return coord_s.instance.get_id() + -- success + return coord_s.instance.get_id() + else + -- we already have a coordinator linked + return false + end end -- attempt to identify which session's watchdog timer fired diff --git a/supervisor/startup.lua b/supervisor/startup.lua index b7acdfb..1e3148b 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.6.4" +local SUPERVISOR_VERSION = "beta-v0.6.5" local print = util.print local println = util.println diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 4cf0eef..ec85623 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -80,15 +80,19 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen -- send coordinator connection establish response ---@param seq_id integer ---@param dest integer - local function _send_crdn_establish(seq_id, dest) + ---@param allow boolean + local function _send_crdn_establish(seq_id, dest, allow) local s_pkt = comms.scada_packet() local c_pkt = comms.crdn_packet() - local config = { self.num_reactors } + local config = { false } - for i = 1, #cooling_conf do - table.insert(config, cooling_conf[i].BOILERS) - table.insert(config, cooling_conf[i].TURBINES) + if allow then + config = { self.num_reactors } + for i = 1, #cooling_conf do + table.insert(config, cooling_conf[i].BOILERS) + table.insert(config, cooling_conf[i].TURBINES) + end end c_pkt.make(SCADA_CRDN_TYPES.ESTABLISH, config) @@ -235,11 +239,14 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen elseif packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then if packet.length >= 1 then -- this is an RTU advertisement for a new session - println(util.c("connected to RTU (", packet.data[1], ") [:", r_port, "]")) + local rtu_version = packet.data[1] + -- note: this function mutates packet.data svsessions.establish_rtu_session(l_port, r_port, packet.data) + println(util.c("connected to RTU (", rtu_version, ") [:", r_port, "]")) log.debug("RTU_ADVERT: linked " .. r_port) + _send_remote_linked(packet.scada_frame.seq_num() + 1, r_port) else log.debug("RTU_ADVERT: advertisement packet empty") @@ -263,7 +270,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen session.in_queue.push_packet(packet) else -- any other packet should be session related, discard it - log.debug(util.c(r_port, "->", l_port, ": discarding SCADA_MGMT packet without a known session")) + log.debug(r_port .. "->" .. l_port .. ": discarding SCADA_MGMT packet without a known session") end elseif protocol == PROTOCOLS.SCADA_CRDN then -- coordinator packet @@ -273,18 +280,22 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen elseif packet.type == SCADA_CRDN_TYPES.ESTABLISH then if packet.length == 1 then -- this is an attempt to establish a new session - println(util.c("connected to coordinator (", packet.data[1], ") [:", r_port, "]")) + local s_id = svsessions.establish_coord_session(l_port, r_port, packet.data[1]) - svsessions.establish_coord_session(l_port, r_port, packet.data[1]) + if s_id ~= false then + println(util.c("connected to coordinator (", packet.data[1], ") [:", r_port, "]")) + log.debug("CRDN_ESTABLISH: connected to " .. r_port) + else + log.debug("CRDN_ESTABLISH: denied new coordinator due to already being connected to another coordinator") + end - log.debug("CRDN_ESTABLISH: connected to " .. r_port) - _send_crdn_establish(packet.scada_frame.seq_num() + 1, r_port) + _send_crdn_establish(packet.scada_frame.seq_num() + 1, r_port, (s_id ~= false)) else log.debug("CRDN_ESTABLISH: establish packet length mismatch") end else -- any other packet should be session related, discard it - log.debug(util.c(r_port, "->", l_port, ": discarding SCADA_CRDN packet without a known session")) + log.debug(r_port .. "->" .. l_port .. ": discarding SCADA_CRDN packet without a known session") end else log.debug("illegal packet type " .. protocol .. " on coordinator listening channel") From 93286174d413662498f751092695e1eb24523ec5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 20 Oct 2022 13:59:35 -0400 Subject: [PATCH 416/587] some sneaky semicolons --- scada-common/crypto.lua | 16 ++++++++-------- scada-common/rsio.lua | 2 +- test/lockbox-benchmark.lua | 16 ++++++++-------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/scada-common/crypto.lua b/scada-common/crypto.lua index ed75f94..6424bcb 100644 --- a/scada-common/crypto.lua +++ b/scada-common/crypto.lua @@ -3,13 +3,13 @@ -- local aes128 = require("lockbox.cipher.aes128") -local ctr_mode = require("lockbox.cipher.mode.ctr"); -local sha1 = require("lockbox.digest.sha1"); -local sha2_224 = require("lockbox.digest.sha2_224"); -local sha2_256 = require("lockbox.digest.sha2_256"); +local ctr_mode = require("lockbox.cipher.mode.ctr") +local sha1 = require("lockbox.digest.sha1") +local sha2_224 = require("lockbox.digest.sha2_224") +local sha2_256 = require("lockbox.digest.sha2_256") local pbkdf2 = require("lockbox.kdf.pbkdf2") local hmac = require("lockbox.mac.hmac") -local zero_pad = require("lockbox.padding.zero"); +local zero_pad = require("lockbox.padding.zero") local stream = require("lockbox.util.stream") local array = require("lockbox.util.array") @@ -52,13 +52,13 @@ function crypto.init(password, server_port) c_eng.cipher = ctr_mode.Cipher() c_eng.cipher.setKey(c_eng.key) c_eng.cipher.setBlockCipher(aes128) - c_eng.cipher.setPadding(zero_pad); + c_eng.cipher.setPadding(zero_pad) -- initialize decipher c_eng.decipher = ctr_mode.Decipher() c_eng.decipher.setKey(c_eng.key) c_eng.decipher.setBlockCipher(aes128) - c_eng.decipher.setPadding(zero_pad); + c_eng.decipher.setPadding(zero_pad) -- initialize HMAC c_eng.hmac = hmac() @@ -222,7 +222,7 @@ function crypto.secure_modem(modem) if hmac == computed_hmac then -- message intact local plaintext = crypto.decrypt(iv, ciphertext) - body = textutils.deserialize(plaintext) + body = textutils.unserialize(plaintext) if body == nil then -- failed decryption diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 04ab23c..8598e85 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -222,7 +222,7 @@ end ---@param color integer ---@return boolean valid function rsio.is_color(color) - return util.is_int(color) and (color > 0) and (_B_AND(color, (color - 1)) == 0); + return util.is_int(color) and (color > 0) and (_B_AND(color, (color - 1)) == 0) end ----------------- diff --git a/test/lockbox-benchmark.lua b/test/lockbox-benchmark.lua index f6f1ec2..0191c2a 100644 --- a/test/lockbox-benchmark.lua +++ b/test/lockbox-benchmark.lua @@ -3,18 +3,18 @@ require("/initenv").init_env() local pbkdf2 = require("lockbox.kdf.pbkdf2") local AES128Cipher = require("lockbox.cipher.aes128") local HMAC = require("lockbox.mac.hmac") -local SHA1 = require("lockbox.digest.sha1"); -local SHA2_224 = require("lockbox.digest.sha2_224"); -local SHA2_256 = require("lockbox.digest.sha2_256"); +local SHA1 = require("lockbox.digest.sha1") +local SHA2_224 = require("lockbox.digest.sha2_224") +local SHA2_256 = require("lockbox.digest.sha2_256") local Stream = require("lockbox.util.stream") local Array = require("lockbox.util.array") -local CBCMode = require("lockbox.cipher.mode.cbc"); -local CFBMode = require("lockbox.cipher.mode.cfb"); -local OFBMode = require("lockbox.cipher.mode.ofb"); -local CTRMode = require("lockbox.cipher.mode.ctr"); +local CBCMode = require("lockbox.cipher.mode.cbc") +local CFBMode = require("lockbox.cipher.mode.cfb") +local OFBMode = require("lockbox.cipher.mode.ofb") +local CTRMode = require("lockbox.cipher.mode.ctr") -local ZeroPadding = require("lockbox.padding.zero"); +local ZeroPadding = require("lockbox.padding.zero") local comms = require("scada-common.comms") local util = require("scada-common.util") From d202a490113a968b50669912bd5a871dda94c268 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 21 Oct 2022 15:15:56 -0400 Subject: [PATCH 417/587] #108 resolved TCD race condition --- coordinator/startup.lua | 3 +-- graphics/elements/animations/waiting.lua | 2 +- graphics/flasher.lua | 3 ++- scada-common/tcallbackdsp.lua | 24 ++++++++++++++++++++++++ 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index fac7ab8..0c4e579 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.5.6" +local COORDINATOR_VERSION = "alpha-v0.5.7" local print = util.print local println = util.println @@ -146,7 +146,6 @@ end ---@return boolean ui_ok started ok local function init_start_ui() log_graphics("starting UI...") - -- util.psleep(3) local draw_start = util.time_ms() diff --git a/graphics/elements/animations/waiting.lua b/graphics/elements/animations/waiting.lua index 5c03d09..2b08092 100644 --- a/graphics/elements/animations/waiting.lua +++ b/graphics/elements/animations/waiting.lua @@ -85,7 +85,7 @@ local function waiting(args) if state >= 12 then state = 0 end if run_animation then - tcd.dispatch(0.5, animate) + tcd.dispatch_unique(0.5, animate) end end diff --git a/graphics/flasher.lua b/graphics/flasher.lua index 97afd83..02f0dcd 100644 --- a/graphics/flasher.lua +++ b/graphics/flasher.lua @@ -38,13 +38,14 @@ local function callback_250ms() callback_counter = callback_counter + 1 - tcd.dispatch(0.25, callback_250ms) + tcd.dispatch_unique(0.25, callback_250ms) end end -- start the flasher periodic function flasher.init() active = true + callback_counter = 0 registry = { {}, {}, {} } callback_250ms() end diff --git a/scada-common/tcallbackdsp.lua b/scada-common/tcallbackdsp.lua index 65b8ec9..3371c98 100644 --- a/scada-common/tcallbackdsp.lua +++ b/scada-common/tcallbackdsp.lua @@ -2,6 +2,9 @@ -- Timer Callback Dispatcher -- +local log = require("scada-common.log") +local util = require("scada-common.util") + local tcallbackdsp = {} local registry = {} @@ -14,6 +17,27 @@ function tcallbackdsp.dispatch(time, f) registry[os.startTimer(time)] = { callback = f } end +-- request a function to be called after the specified time, aborting any registered instances of that function reference +---@param time number seconds +---@param f function callback function +function tcallbackdsp.dispatch_unique(time, f) + -- ignore if already registered + for timer, entry in pairs(registry) do + if entry.callback == f then + -- found an instance of this function reference, abort it + log.debug(util.c("TCD: aborting duplicate timer callback (timer: ", timer, ", function: ", f, ")")) + + -- cancel event and remove from registry (even if it fires it won't call) +---@diagnostic disable-next-line: undefined-field + os.cancelTimer(timer) + registry[timer] = nil + end + end + +---@diagnostic disable-next-line: undefined-field + registry[os.startTimer(time)] = { callback = f } +end + -- lookup a timer event and execute the callback if found ---@param event integer timer event timer ID function tcallbackdsp.handle(event) From 307bf6e2c86dc82e46275b9d1fe477671f762b3c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 23 Oct 2022 01:41:02 -0400 Subject: [PATCH 418/587] added util timer functions, tweaks to flasher and some debug prints for #110 --- coordinator/renderer.lua | 2 +- graphics/flasher.lua | 7 +++--- scada-common/tcallbackdsp.lua | 41 ++++++++++++++++++++++++------- scada-common/util.lua | 45 +++++++++++++++++++++++++---------- 4 files changed, 69 insertions(+), 26 deletions(-) diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 94071d4..fe81f5e 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -109,7 +109,7 @@ function renderer.start_ui() end -- start flasher callback task - flasher.init() + flasher.run() -- report ui as ready engine.ui_ready = true diff --git a/graphics/flasher.lua b/graphics/flasher.lua index 02f0dcd..5fa856a 100644 --- a/graphics/flasher.lua +++ b/graphics/flasher.lua @@ -42,17 +42,16 @@ local function callback_250ms() end end --- start the flasher periodic -function flasher.init() +-- start/resume the flasher periodic +function flasher.run() active = true - callback_counter = 0 - registry = { {}, {}, {} } callback_250ms() end -- clear all blinking indicators and stop the flasher periodic function flasher.clear() active = false + callback_counter = 0 registry = { {}, {}, {} } end diff --git a/scada-common/tcallbackdsp.lua b/scada-common/tcallbackdsp.lua index 3371c98..cdce1c0 100644 --- a/scada-common/tcallbackdsp.lua +++ b/scada-common/tcallbackdsp.lua @@ -13,8 +13,14 @@ local registry = {} ---@param time number seconds ---@param f function callback function function tcallbackdsp.dispatch(time, f) ----@diagnostic disable-next-line: undefined-field - registry[os.startTimer(time)] = { callback = f } + local timer = util.start_timer(time) + registry[timer] = { + callback = f, + duration = time, + expiry = time + util.time_s() + } + + log.debug("queued callback for " .. util.strval(f) .. " timer #" .. timer) end -- request a function to be called after the specified time, aborting any registered instances of that function reference @@ -25,25 +31,44 @@ function tcallbackdsp.dispatch_unique(time, f) for timer, entry in pairs(registry) do if entry.callback == f then -- found an instance of this function reference, abort it - log.debug(util.c("TCD: aborting duplicate timer callback (timer: ", timer, ", function: ", f, ")")) + log.debug(util.c("TCD: aborting duplicate timer callback (timer: ", timer, ", ", f, ")")) -- cancel event and remove from registry (even if it fires it won't call) ----@diagnostic disable-next-line: undefined-field - os.cancelTimer(timer) + util.cancel_timer(timer) registry[timer] = nil end end ----@diagnostic disable-next-line: undefined-field - registry[os.startTimer(time)] = { callback = f } + local timer = util.start_timer(time) + registry[timer] = { + callback = f, + duration = time, + expiry = time + util.time_s() + } + + log.debug("queued callback for " .. util.strval(f) .. " timer #" .. timer) end -- lookup a timer event and execute the callback if found ---@param event integer timer event timer ID function tcallbackdsp.handle(event) if registry[event] ~= nil then - registry[event].callback() + local callback = registry[event].callback + -- clear first so that dispatch_unique call from inside callback won't throw a debug message registry[event] = nil + callback() + end +end + +-- identify any overdo callbacks +-- +-- prints to log debug output +function tcallbackdsp.diagnostics() + for timer, entry in pairs(registry) do + if entry.expiry >= util.time_s() then + local overtime = util.time_s() - entry.expiry + log.debug(util.c("TCD: unserviced timer ", timer, " for callback ", entry.callback, " is at least ", overtime, "s late")) + end end end diff --git a/scada-common/util.lua b/scada-common/util.lua index aa1e385..cb8cdcb 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -202,6 +202,33 @@ function util.pull_event(target_event) return os.pullEventRaw(target_event) end +-- OS queue event raw wrapper with types +---@param event os_event +---@param param1 any +---@param param2 any +---@param param3 any +---@param param4 any +---@param param5 any +function util.push_event(event, param1, param2, param3, param4, param5) +---@diagnostic disable-next-line: undefined-field + return os.queueEvent(event, param1, param2, param3, param4, param5) +end + +-- start an OS timer +---@param t number timer duration in seconds +---@return integer timer ID +function util.start_timer(t) +---@diagnostic disable-next-line: undefined-field + return os.startTimer(t) +end + +-- cancel an OS timer +---@param timer integer timer ID +function util.cancel_timer(timer) +---@diagnostic disable-next-line: undefined-field + os.cancelTimer(timer) +end + -- PARALLELIZATION -- -- protected sleep call so we still are in charge of catching termination @@ -312,14 +339,9 @@ end --- --- triggers a timer event if not fed within 'timeout' seconds function util.new_watchdog(timeout) ----@diagnostic disable-next-line: undefined-field - local start_timer = os.startTimer ----@diagnostic disable-next-line: undefined-field - local cancel_timer = os.cancelTimer - local self = { timeout = timeout, - wd_timer = start_timer(timeout) + wd_timer = util.start_timer(timeout) } ---@class watchdog @@ -333,15 +355,15 @@ function util.new_watchdog(timeout) -- satiate the beast function public.feed() if self.wd_timer ~= nil then - cancel_timer(self.wd_timer) + util.cancel_timer(self.wd_timer) end - self.wd_timer = start_timer(self.timeout) + self.wd_timer = util.start_timer(self.timeout) end -- cancel the watchdog function public.cancel() if self.wd_timer ~= nil then - cancel_timer(self.wd_timer) + util.cancel_timer(self.wd_timer) end end @@ -355,9 +377,6 @@ end --- --- fires a timer event at the specified period, does not start at construct time function util.new_clock(period) ----@diagnostic disable-next-line: undefined-field - local start_timer = os.startTimer - local self = { period = period, timer = nil @@ -373,7 +392,7 @@ function util.new_clock(period) -- start the clock function public.start() - self.timer = start_timer(self.period) + self.timer = util.start_timer(self.period) end return public From a02fb6f6919ad89b80c6460cbd1c4de83b15cd67 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 23 Oct 2022 12:21:17 -0400 Subject: [PATCH 419/587] #110 periodically call unserviced TCD callbacks --- coordinator/startup.lua | 8 ++++-- graphics/elements/indicators/light.lua | 2 +- scada-common/tcallbackdsp.lua | 40 +++++++++++++++++++++++--- scada-common/util.lua | 5 ++++ 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 0c4e579..c612921 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.5.7" +local COORDINATOR_VERSION = "alpha-v0.5.9" local print = util.print local println = util.println @@ -181,8 +181,7 @@ if ui_ok then log_sys("system started successfully") end --- event loop --- ui_ok will never change in this loop, same as while true or exit if UI start failed +-- main event loop while ui_ok do local event, param1, param2, param3, param4, param5 = util.pull_event() @@ -299,6 +298,9 @@ while ui_ok do renderer.handle_touch(core.events.touch(param1, param2, param3)) end + -- call unserviced TCD callbacks + tcallbackdsp.call_unserviced() + -- check for termination request if event == "terminate" or ppm.should_terminate() then println_ts("terminate requested, closing connections...") diff --git a/graphics/elements/indicators/light.lua b/graphics/elements/indicators/light.lua index 6106ebe..0c4c340 100644 --- a/graphics/elements/indicators/light.lua +++ b/graphics/elements/indicators/light.lua @@ -42,7 +42,7 @@ local function indicator_light(args) -- called by flasher when enabled local function flash_callback() e.window.setCursorPos(1, 1) - + if flash_on then e.window.blit(" \x95", "0" .. args.colors.blit_a, args.colors.blit_a .. e.fg_bg.blit_bkg) else diff --git a/scada-common/tcallbackdsp.lua b/scada-common/tcallbackdsp.lua index cdce1c0..339d1b5 100644 --- a/scada-common/tcallbackdsp.lua +++ b/scada-common/tcallbackdsp.lua @@ -9,6 +9,8 @@ local tcallbackdsp = {} local registry = {} +local UNSERVICED_CALL_DELAY = util.TICK_TIME_S + -- request a function to be called after the specified time ---@param time number seconds ---@param f function callback function @@ -20,7 +22,7 @@ function tcallbackdsp.dispatch(time, f) expiry = time + util.time_s() } - log.debug("queued callback for " .. util.strval(f) .. " timer #" .. timer) + -- log.debug(util.c("TCD: queued callback for ", f, " [timer: ", timer, "]")) end -- request a function to be called after the specified time, aborting any registered instances of that function reference @@ -31,7 +33,7 @@ function tcallbackdsp.dispatch_unique(time, f) for timer, entry in pairs(registry) do if entry.callback == f then -- found an instance of this function reference, abort it - log.debug(util.c("TCD: aborting duplicate timer callback (timer: ", timer, ", ", f, ")")) + log.debug(util.c("TCD: aborting duplicate timer callback [timer: ", timer, ", ", f, "]")) -- cancel event and remove from registry (even if it fires it won't call) util.cancel_timer(timer) @@ -46,7 +48,7 @@ function tcallbackdsp.dispatch_unique(time, f) expiry = time + util.time_s() } - log.debug("queued callback for " .. util.strval(f) .. " timer #" .. timer) + -- log.debug(util.c("TCD: queued callback for ", f, " [timer: ", timer, "]")) end -- lookup a timer event and execute the callback if found @@ -60,14 +62,44 @@ function tcallbackdsp.handle(event) end end +-- execute any callbacks that are overdo their time and have not been serviced +-- +-- this can be called periodically to prevent loss of any callbacks do to timer events that are lost (see github issue #110) +function tcallbackdsp.call_unserviced() + local found_unserviced = true + + while found_unserviced do + found_unserviced = false + + -- go through registry, restart if unserviced entries were found due to mutating registry table + for timer, entry in pairs(registry) do + found_unserviced = util.time_s() > (entry.expiry + UNSERVICED_CALL_DELAY) + if found_unserviced then + local overtime = util.time_s() - entry.expiry + local callback = entry.callback + + log.warning(util.c("TCD: executing unserviced callback ", entry.callback, " (", overtime, "s late) [timer: ", timer, "]")) + + -- clear first so that dispatch_unique call from inside callback won't see it as a conflict + registry[timer] = nil + callback() + break + end + end + end +end + -- identify any overdo callbacks -- -- prints to log debug output function tcallbackdsp.diagnostics() for timer, entry in pairs(registry) do - if entry.expiry >= util.time_s() then + if entry.expiry < util.time_s() then local overtime = util.time_s() - entry.expiry log.debug(util.c("TCD: unserviced timer ", timer, " for callback ", entry.callback, " is at least ", overtime, "s late")) + else + local time = entry.expiry - util.time_s() + log.debug(util.c("TCD: pending timer ", timer, " for callback ", entry.callback, " (call after ", entry.duration, "s, expires ", time, ")")) end end end diff --git a/scada-common/util.lua b/scada-common/util.lua index cb8cdcb..440b9e0 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -5,6 +5,11 @@ ---@class util local util = {} +-- ENVIRONMENT CONSTANTS -- + +util.TICK_TIME_S = 0.05 +util.TICK_TIME_MS = 50 + -- OPERATORS -- -- trinary operator From b2be3ef5fc6a47e86650f329a8a42d0b84235081 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 25 Oct 2022 13:29:57 -0400 Subject: [PATCH 420/587] #106 reactor formed support and remounting --- coordinator/iocontrol.lua | 135 +++++++++++++++++++--------------- coordinator/startup.lua | 2 +- coordinator/ui/style.lua | 4 ++ reactor-plc/plc.lua | 134 ++++++++++++++++++++-------------- reactor-plc/startup.lua | 27 ++++--- reactor-plc/threads.lua | 65 ++++++++++++++--- scada-common/ppm.lua | 68 +++++++++++++++++- supervisor/session/plc.lua | 143 ++++++++++++++++++++----------------- supervisor/startup.lua | 2 +- 9 files changed, 383 insertions(+), 197 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 339ad63..0a42038 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -172,16 +172,21 @@ function iocontrol.update_statuses(statuses) if #reactor_status == 0 then unit.reactor_ps.publish("computed_status", 1) -- disconnected - else + elseif #reactor_status == 3 then local mek_status = reactor_status[1] local rps_status = reactor_status[2] local gen_status = reactor_status[3] - unit.reactor_data.last_status_update = gen_status[1] - unit.reactor_data.control_state = gen_status[2] - unit.reactor_data.rps_tripped = gen_status[3] - unit.reactor_data.rps_trip_cause = gen_status[4] - unit.reactor_data.degraded = gen_status[5] + if #gen_status == 6 then + unit.reactor_data.last_status_update = gen_status[1] + unit.reactor_data.control_state = gen_status[2] + unit.reactor_data.rps_tripped = gen_status[3] + unit.reactor_data.rps_trip_cause = gen_status[4] + unit.reactor_data.no_reactor = gen_status[5] + unit.reactor_data.formed = gen_status[6] + else + log.debug("reactor general status length mismatch") + end unit.reactor_data.rps_status = rps_status ---@type rps_status unit.reactor_data.mek_status = mek_status ---@type mek_status @@ -189,8 +194,10 @@ function iocontrol.update_statuses(statuses) if unit.reactor_data.mek_status.status then unit.reactor_ps.publish("computed_status", 3) -- running else - if unit.reactor_data.degraded then + if unit.reactor_data.no_reactor then unit.reactor_ps.publish("computed_status", 5) -- faulted + elseif not unit.reactor_data.formed then + unit.reactor_ps.publish("computed_status", 6) -- multiblock not formed elseif unit.reactor_data.rps_tripped and unit.reactor_data.rps_trip_cause ~= "manual" then unit.reactor_ps.publish("computed_status", 4) -- SCRAM else @@ -204,13 +211,19 @@ function iocontrol.update_statuses(statuses) end end - for key, val in pairs(unit.reactor_data.rps_status) do - unit.reactor_ps.publish(key, val) + if type(unit.reactor_data.rps_status) == "table" then + for key, val in pairs(unit.reactor_data.rps_status) do + unit.reactor_ps.publish(key, val) + end end - for key, val in pairs(unit.reactor_data.mek_status) do - unit.reactor_ps.publish(key, val) + if type(unit.reactor_data.mek_status) == "table" then + for key, val in pairs(unit.reactor_data.mek_status) do + unit.reactor_ps.publish(key, val) + end end + else + log.debug("reactor status length mismatch") end -- annunciator @@ -252,65 +265,71 @@ function iocontrol.update_statuses(statuses) local rtu_statuses = status[3] - -- boiler statuses + if type(rtu_statuses) == "table" then + if type(rtu_statuses.boilers) == "table" then + -- boiler statuses - for id = 1, #unit.boiler_data_tbl do - if rtu_statuses.boilers[i] == nil then - -- disconnected - unit.boiler_ps_tbl[id].publish("computed_status", 1) - end - end + for id = 1, #unit.boiler_data_tbl do + if rtu_statuses.boilers[i] == nil then + -- disconnected + unit.boiler_ps_tbl[id].publish("computed_status", 1) + end + end - for id, boiler in pairs(rtu_statuses.boilers) do - unit.boiler_data_tbl[id].state = boiler[1] ---@type table - unit.boiler_data_tbl[id].tanks = boiler[2] ---@type table + for id, boiler in pairs(rtu_statuses.boilers) do + unit.boiler_data_tbl[id].state = boiler[1] ---@type table + unit.boiler_data_tbl[id].tanks = boiler[2] ---@type table - local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db + local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db - if data.state.boil_rate > 0 then - unit.boiler_ps_tbl[id].publish("computed_status", 3) -- active - else - unit.boiler_ps_tbl[id].publish("computed_status", 2) -- idle + if data.state.boil_rate > 0 then + unit.boiler_ps_tbl[id].publish("computed_status", 3) -- active + else + unit.boiler_ps_tbl[id].publish("computed_status", 2) -- idle + end + + for key, val in pairs(unit.boiler_data_tbl[id].state) do + unit.boiler_ps_tbl[id].publish(key, val) + end + + for key, val in pairs(unit.boiler_data_tbl[id].tanks) do + unit.boiler_ps_tbl[id].publish(key, val) + end + end end - for key, val in pairs(unit.boiler_data_tbl[id].state) do - unit.boiler_ps_tbl[id].publish(key, val) - end + if type(rtu_statuses.turbines) == "table" then + -- turbine statuses - for key, val in pairs(unit.boiler_data_tbl[id].tanks) do - unit.boiler_ps_tbl[id].publish(key, val) - end - end + for id = 1, #unit.turbine_ps_tbl do + if rtu_statuses.turbines[i] == nil then + -- disconnected + unit.turbine_ps_tbl[id].publish("computed_status", 1) + end + end - -- turbine statuses + for id, turbine in pairs(rtu_statuses.turbines) do + unit.turbine_data_tbl[id].state = turbine[1] ---@type table + unit.turbine_data_tbl[id].tanks = turbine[2] ---@type table - for id = 1, #unit.turbine_ps_tbl do - if rtu_statuses.turbines[i] == nil then - -- disconnected - unit.turbine_ps_tbl[id].publish("computed_status", 1) - end - end + local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db - for id, turbine in pairs(rtu_statuses.turbines) do - unit.turbine_data_tbl[id].state = turbine[1] ---@type table - unit.turbine_data_tbl[id].tanks = turbine[2] ---@type table + if data.tanks.steam_fill >= 0.99 then + unit.turbine_ps_tbl[id].publish("computed_status", 4) -- trip + elseif data.state.flow_rate < 100 then + unit.turbine_ps_tbl[id].publish("computed_status", 2) -- idle + else + unit.turbine_ps_tbl[id].publish("computed_status", 3) -- active + end - local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db + for key, val in pairs(unit.turbine_data_tbl[id].state) do + unit.turbine_ps_tbl[id].publish(key, val) + end - if data.tanks.steam_fill >= 0.99 then - unit.turbine_ps_tbl[id].publish("computed_status", 4) -- trip - elseif data.state.flow_rate < 100 then - unit.turbine_ps_tbl[id].publish("computed_status", 2) -- idle - else - unit.turbine_ps_tbl[id].publish("computed_status", 3) -- active - end - - for key, val in pairs(unit.turbine_data_tbl[id].state) do - unit.turbine_ps_tbl[id].publish(key, val) - end - - for key, val in pairs(unit.turbine_data_tbl[id].tanks) do - unit.turbine_ps_tbl[id].publish(key, val) + for key, val in pairs(unit.turbine_data_tbl[id].tanks) do + unit.turbine_ps_tbl[id].publish(key, val) + end + end end end end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index c612921..e8717c4 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.5.9" +local COORDINATOR_VERSION = "alpha-v0.5.10" local print = util.print local println = util.println diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index ec0d20d..395e6f2 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -54,6 +54,10 @@ style.reactor = { { color = cpair(colors.black, colors.orange), text = "PLC FAULT" + }, + { + color = cpair(colors.black, colors.orange), + text = "NOT FORMED" } } } diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 5e04ed1..f8cfc76 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -38,7 +38,9 @@ local MAX_HEATED_COLLANT_FILL = 0.95 --- identifies dangerous states and SCRAMs reactor if warranted --- --- autonomous from main SCADA supervisor/coordinator control -function plc.rps_init(reactor) +---@param reactor table +---@param is_formed boolean +function plc.rps_init(reactor, is_formed) local state_keys = { dmg_crit = 1, high_temp = 2, @@ -48,13 +50,15 @@ function plc.rps_init(reactor) no_fuel = 6, fault = 7, timeout = 8, - manual = 9 + manual = 9, + sys_fail = 10 } local self = { reactor = reactor, - state = { false, false, false, false, false, false, false, false, false }, + state = { false, false, false, false, false, false, false, false, false, false }, reactor_enabled = false, + formed = is_formed, tripped = false, trip_cause = "" ---@type rps_trip_cause } @@ -76,14 +80,25 @@ function plc.rps_init(reactor) self.state[state_keys.fault] = false end + -- check if the reactor is formed + local function _is_formed() + local is_formed = self.reactor.isFormed() + if is_formed == ppm.ACCESS_FAULT then + -- lost the peripheral or terminated, handled later + _set_fault() + elseif not self.state[state_keys.sys_fail] then + self.formed = is_formed + self.state[state_keys.sys_fail] = not is_formed + end + end + -- check for critical damage local function _damage_critical() local damage_percent = self.reactor.getDamagePercent() if damage_percent == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() - self.state[state_keys.dmg_crit] = false - else + elseif not self.state[state_keys.dmg_crit] then self.state[state_keys.dmg_crit] = damage_percent >= MAX_DAMAGE_PERCENT end end @@ -95,8 +110,7 @@ function plc.rps_init(reactor) if temp == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() - self.state[state_keys.high_temp] = false - else + elseif not self.state[state_keys.high_temp] then self.state[state_keys.high_temp] = temp >= MAX_DAMAGE_TEMPERATURE end end @@ -107,8 +121,7 @@ function plc.rps_init(reactor) if coolant_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() - self.state[state_keys.no_coolant] = false - else + elseif not self.state[state_keys.no_coolant] then self.state[state_keys.no_coolant] = coolant_filled < MIN_COOLANT_FILL end end @@ -119,8 +132,7 @@ function plc.rps_init(reactor) if w_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() - self.state[state_keys.ex_waste] = false - else + elseif not self.state[state_keys.ex_waste] then self.state[state_keys.ex_waste] = w_filled > MAX_WASTE_FILL end end @@ -131,8 +143,7 @@ function plc.rps_init(reactor) if hc_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() - self.state[state_keys.ex_hcoolant] = false - else + elseif not self.state[state_keys.ex_hcoolant] then self.state[state_keys.ex_hcoolant] = hc_filled > MAX_HEATED_COLLANT_FILL end end @@ -143,8 +154,7 @@ function plc.rps_init(reactor) if fuel == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() - self.state[state_keys.no_fuel] = false - else + elseif not self.state[state_keys.no_fuel] then self.state[state_keys.no_fuel] = fuel == 0 end end @@ -169,7 +179,13 @@ function plc.rps_init(reactor) -- manually SCRAM the reactor function public.trip_manual() - self.state[state_keys.manual] = true + self.state[state_keys.manual] = true + end + + -- trip for unformed reactor + function public.trip_sys_fail() + self.state[state_keys.fault] = true + self.state[state_keys.sys_fail] = true end -- SCRAM the reactor now @@ -216,6 +232,7 @@ function plc.rps_init(reactor) -- update state parallel.waitForAll( + _is_formed, _damage_critical, _high_temp, _no_coolant, @@ -227,6 +244,9 @@ function plc.rps_init(reactor) -- check system states in order of severity if self.tripped then status = self.trip_cause + elseif self.state[state_keys.sys_fail] then + log.warning("RPS: system failure, reactor not formed") + status = rps_status_t.sys_fail elseif self.state[state_keys.dmg_crit] then log.warning("RPS: damage critical") status = rps_status_t.dmg_crit @@ -273,9 +293,11 @@ function plc.rps_init(reactor) function public.status() return self.state end function public.is_tripped() return self.tripped end function public.is_active() return self.reactor_enabled end + function public.is_formed() return self.formed end -- reset the RPS - function public.reset() + ---@param quiet? boolean true to suppress the info log message + function public.reset(quiet) self.tripped = false self.trip_cause = rps_status_t.ok @@ -283,7 +305,7 @@ function plc.rps_init(reactor) self.state[i] = false end - log.info("RPS: reset") + if not quiet then log.info("RPS: reset") end end return public @@ -473,20 +495,19 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co local mek_data = { false, 0, 0, 0, min_pos, max_pos, 0, 0, 0, 0, 0, 0, 0, 0 } local tasks = { - function () mek_data[1] = self.reactor.isFormed() end, - function () mek_data[2] = self.reactor.getLength() end, - function () mek_data[3] = self.reactor.getWidth() end, - function () mek_data[4] = self.reactor.getHeight() end, - function () mek_data[5] = self.reactor.getMinPos() end, - function () mek_data[6] = self.reactor.getMaxPos() end, - function () mek_data[7] = self.reactor.getHeatCapacity() end, - function () mek_data[8] = self.reactor.getFuelAssemblies() end, - function () mek_data[9] = self.reactor.getFuelSurfaceArea() end, - function () mek_data[10] = self.reactor.getFuelCapacity() end, - function () mek_data[11] = self.reactor.getWasteCapacity() end, - function () mek_data[12] = self.reactor.getCoolantCapacity() end, - function () mek_data[13] = self.reactor.getHeatedCoolantCapacity() end, - function () mek_data[14] = self.reactor.getMaxBurnRate() end + function () mek_data[1] = self.reactor.getLength() end, + function () mek_data[2] = self.reactor.getWidth() end, + function () mek_data[3] = self.reactor.getHeight() end, + function () mek_data[4] = self.reactor.getMinPos() end, + function () mek_data[5] = self.reactor.getMaxPos() end, + function () mek_data[6] = self.reactor.getHeatCapacity() end, + function () mek_data[7] = self.reactor.getFuelAssemblies() end, + function () mek_data[8] = self.reactor.getFuelSurfaceArea() end, + function () mek_data[9] = self.reactor.getFuelCapacity() end, + function () mek_data[10] = self.reactor.getWasteCapacity() end, + function () mek_data[11] = self.reactor.getCoolantCapacity() end, + function () mek_data[12] = self.reactor.getHeatedCoolantCapacity() end, + function () mek_data[13] = self.reactor.getMaxBurnRate() end } parallel.waitForAll(table.unpack(tasks)) @@ -536,29 +557,35 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co end -- send live status information - ---@param degraded boolean - function public.send_status(degraded) + ---@param no_reactor boolean PLC lost reactor connection + ---@param formed boolean reactor formed + function public.send_status(no_reactor, formed) if self.linked then - local mek_data = nil + local mek_data = nil ---@type table + local heating_rate = nil ---@type number - if _update_status_cache() then - mek_data = self.status_cache + if (not no_reactor) and 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() end local sys_status = { util.time(), -- timestamp (not self.scrammed), -- requested control state rps.is_tripped(), -- rps_tripped - degraded, -- degraded - self.reactor.getHeatingRate(), -- heating rate + no_reactor, -- no reactor peripheral connected + formed, -- reactor formed + heating_rate, -- heating rate mek_data -- mekanism status data } - if not self.reactor.__p_is_faulted() then - _send(RPLC_TYPES.STATUS, sys_status) - else - log.error("failed to send status: PPM fault") - end + _send(RPLC_TYPES.STATUS, sys_status) end end @@ -570,7 +597,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co end -- send reactor protection system alarm - ---@param cause rps_status_t + ---@param cause rps_status_t reactor protection system status function public.send_rps_alarm(cause) if self.linked then local rps_alarm = { @@ -618,9 +645,9 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co end -- handle an RPLC packet - ---@param packet rplc_frame|mgmt_frame - ---@param plc_state plc_state - ---@param setpoints setpoints + ---@param packet rplc_frame|mgmt_frame packet frame + ---@param plc_state plc_state PLC state + ---@param setpoints setpoints setpoint control table function public.handle_packet(packet, plc_state, setpoints) if packet ~= nil and packet.scada_frame.local_port() == self.l_port then -- check sequence number @@ -651,7 +678,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co if link_ack == RPLC_LINKING.ALLOW then self.status_cache = nil _send_struct() - public.send_status(plc_state.degraded) + public.send_status(plc_state.no_reactor, plc_state.reactor_formed) log.debug("re-sent initial status data") elseif link_ack == RPLC_LINKING.DENY then println_ts("received unsolicited link denial, unlinking") @@ -671,7 +698,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co elseif packet.type == RPLC_TYPES.STATUS then -- request of full status, clear cache first self.status_cache = nil - public.send_status(plc_state.degraded) + public.send_status(plc_state.no_reactor, plc_state.reactor_formed) log.debug("sent out status cache again, did supervisor miss it?") elseif packet.type == RPLC_TYPES.MEK_STRUCT then -- request for physical structure @@ -738,8 +765,11 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co self.r_seq_num = nil self.status_cache = nil - _send_struct() - public.send_status(plc_state.degraded) + if plc_state.reactor_formed then + _send_struct() + end + + public.send_status(plc_state.no_reactor, plc_state.reactor_formed) log.debug("sent initial status data") elseif link_ack == RPLC_LINKING.DENY then diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 81338ee..22dd79a 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.8.9" +local R_PLC_VERSION = "beta-v0.9.0" local print = util.print local println = util.println @@ -64,6 +64,7 @@ local __shared_memory = { init_ok = true, shutdown = false, degraded = false, + reactor_formed = true, no_reactor = false, no_modem = false }, @@ -101,7 +102,7 @@ local smem_sys = __shared_memory.plc_sys local plc_state = __shared_memory.plc_state --- we need a reactor and a modem +-- we need a reactor, can at least do some things even if it isn't formed though if smem_dev.reactor == nil then println("boot> fission reactor not found"); log.warning("no reactor on startup") @@ -109,12 +110,21 @@ if smem_dev.reactor == nil then plc_state.init_ok = false plc_state.degraded = true plc_state.no_reactor = true +elseif not smem_dev.reactor.isFormed() then + println("boot> fission reactor not formed"); + log.warning("reactor logic adapter present, but reactor is not formed") + + plc_state.degraded = true + 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") - if smem_dev.reactor ~= nil and smem_dev.reactor.getStatus() then + -- 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 @@ -127,10 +137,12 @@ end local function init() if plc_state.init_ok then -- just booting up, no fission allowed (neutrons stay put thanks) - if smem_dev.reactor.getStatus() then smem_dev.reactor.scram() end + if plc_state.reactor_formed and smem_dev.reactor.getStatus() then + smem_dev.reactor.scram() + end -- init reactor protection system - smem_sys.rps = plc.rps_init(smem_dev.reactor) + smem_sys.rps = plc.rps_init(smem_dev.reactor, plc_state.reactor_formed) log.debug("init> rps init") if __shared_memory.networked then @@ -147,8 +159,7 @@ local function init() log.debug("init> running without networking") end ----@diagnostic disable-next-line: undefined-field - os.queueEvent("clock_start") + util.push_event("clock_start") println("boot> completed") log.debug("init> boot completed") @@ -182,7 +193,7 @@ if __shared_memory.networked then if plc_state.init_ok then -- send status one last time after RPS shutdown - smem_sys.plc_comms.send_status(plc_state.degraded) + smem_sys.plc_comms.send_status(plc_state.no_reactor, plc_state.reactor_formed) smem_sys.plc_comms.send_rps_status() -- close connection diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 3b4cf49..b700fbd 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -78,6 +78,51 @@ function threads.thread__main(smem, init) end end end + + -- are we now formed after waiting to be formed? + if not plc_state.reactor_formed and rps.is_formed() then + -- push a connect event and unmount it from the PPM + local iface = ppm.get_iface(plc_dev.reactor) + if iface then + ppm.unmount(plc_dev.reactor) + + local type, device = ppm.mount(iface) + + 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) + 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 + else + -- fully lost the reactor now :( + println_ts("reactor lost (failed reconnect)!") + log.error("reactor lost (failed reconnect!") + + plc_state.no_reactor = true + plc_state.degraded = true + end + else + log.error("failed to get interface of previously connected reactor", true) + end + elseif not rps.is_formed() then + -- reactor no longer formed + plc_state.reactor_formed = false + end elseif event == "modem_message" and networked and plc_state.init_ok and not plc_state.no_modem then -- got a packet local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5) @@ -94,16 +139,18 @@ function threads.thread__main(smem, init) local type, device = ppm.handle_unmount(param1) if type ~= nil and device ~= nil then - if type == "fissionReactor" then + if type == "fissionReactorLogicAdapter" then println_ts("reactor disconnected!") log.error("reactor disconnected!") + plc_state.no_reactor = true plc_state.degraded = true elseif networked and type == "modem" then -- we only care if this is our wireless modem if device == plc_dev.modem then - println_ts("wireless modem disconnected!") + println_ts("comms modem disconnected!") log.error("comms modem disconnected!") + plc_state.no_modem = true if plc_state.init_ok then @@ -122,7 +169,7 @@ function threads.thread__main(smem, init) local type, device = ppm.mount(param1) if type ~= nil and device ~= nil then - if type == "fissionReactor" then + if type == "fissionReactorLogicAdapter" then -- reconnected reactor plc_dev.reactor = device @@ -130,7 +177,9 @@ function threads.thread__main(smem, init) println_ts("reactor reconnected.") log.info("reactor reconnected") + plc_state.no_reactor = false + plc_state.reactor_formed = device.isFormed() if plc_state.init_ok then rps.reconnect_reactor(plc_dev.reactor) @@ -140,7 +189,7 @@ function threads.thread__main(smem, init) end -- determine if we are still in a degraded state - if not networked or not plc_state.no_modem then + 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 @@ -202,9 +251,7 @@ function threads.thread__main(smem, init) -- this thread cannot be slept because it will miss events (namely "terminate" otherwise) if not plc_state.shutdown then log.info("main thread restarting now...") - ----@diagnostic disable-next-line: undefined-field - os.queueEvent("clock_start") + util.push_event("clock_start") end end end @@ -261,7 +308,7 @@ function threads.thread__rps(smem) -- if we are in standalone mode, continuously reset RPS -- RPS will trip again if there are faults, but if it isn't cleared, the user can't re-enable - if not networked then rps.reset() end + if not networked then rps.reset(true) end -- check safety (SCRAM occurs if tripped) if not plc_state.no_reactor then @@ -380,7 +427,7 @@ function threads.thread__comms_tx(smem) -- received a command if msg.message == MQ__COMM_CMD.SEND_STATUS then -- send PLC/RPS status - plc_comms.send_status(plc_state.degraded) + plc_comms.send_status(plc_state.no_reactor, plc_state.reactor_formed) plc_comms.send_rps_status() end elseif msg.qtype == mqueue.TYPE.DATA then diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 3ca4921..e536ca6 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -10,7 +10,10 @@ local ppm = {} local ACCESS_FAULT = nil ---@type nil +local UNDEFINED_FIELD = "undefined field" + ppm.ACCESS_FAULT = ACCESS_FAULT +ppm.UNDEFINED_FIELD = UNDEFINED_FIELD ---------------------------- -- PRIVATE DATA/FUNCTIONS -- @@ -110,6 +113,40 @@ local function peri_init(iface) self.device.__p_enable_afc = enable_afc self.device.__p_disable_afc = disable_afc + -- 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]" + end + + log.error(util.c("PPM: caught undefined function ", key, "()", count_str)) + end + + self.fault_counts[key] = self.fault_counts[key] + 1 + + return ACCESS_FAULT + end + + setmetatable(self.device, mt) + return { type = self.type, dev = self.device @@ -208,6 +245,20 @@ function ppm.mount(iface) return pm_type, pm_dev end +-- manually unmount a peripheral from the PPM +---@param device table device table +function ppm.unmount(device) + if device then + for side, data in pairs(_ppm_sys.mounts) do + if data.dev == device then + log.warning(util.c("PPM: manually unmounted ", data.type, " mounted to ", side)) + _ppm_sys.mounts[side] = nil + break + end + end + end +end + -- handle peripheral_detach event ---@param iface string CC peripheral interface ---@return string|nil type, table|nil device @@ -227,6 +278,8 @@ function ppm.handle_unmount(iface) log.error(util.c("PPM: lost device unknown to the PPM mounted to ", iface)) end + _ppm_sys.mounts[iface] = nil + return pm_type, pm_dev end @@ -244,6 +297,19 @@ function ppm.list_mounts() return _ppm_sys.mounts end +-- get a mounted peripheral side/interface by device table +---@param device table device table +---@return string|nil iface CC peripheral interface +function ppm.get_iface(device) + if device then + for side, data in pairs(_ppm_sys.mounts) do + if data.dev == device then return side end + end + end + + return nil +end + -- get a mounted peripheral by side/interface ---@param iface string CC peripheral interface ---@return table|nil device function table @@ -298,7 +364,7 @@ end -- get the fission reactor (if multiple, returns the first) ---@return table|nil reactor function table function ppm.get_fission_reactor() - return ppm.get_device("fissionReactor") or ppm.get_device("fissionReactorLogicAdapter") + return ppm.get_device("fissionReactorLogicAdapter") end -- get the wireless modem (if multiple, returns the first) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 514cd60..079d8e9 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -90,20 +90,22 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) sDB = { last_status_update = 0, control_state = false, - degraded = false, + no_reactor = false, + formed = false, rps_tripped = false, rps_trip_cause = "ok", ---@type rps_trip_cause ---@class rps_status rps_status = { dmg_crit = false, - ex_hcool = false, - ex_waste = false, high_temp = false, - no_fuel = false, no_cool = false, + ex_waste = false, + ex_hcool = false, + no_fuel = false, fault = false, timeout = false, - manual = false + manual = false, + sys_fail = false }, ---@class mek_status mek_status = { @@ -159,14 +161,15 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) ---@param rps_status table local function _copy_rps_status(rps_status) self.sDB.rps_status.dmg_crit = rps_status[1] - self.sDB.rps_status.ex_hcool = rps_status[2] - self.sDB.rps_status.ex_waste = rps_status[3] - self.sDB.rps_status.high_temp = rps_status[4] - self.sDB.rps_status.no_fuel = rps_status[5] - self.sDB.rps_status.no_cool = rps_status[6] + self.sDB.rps_status.high_temp = rps_status[2] + self.sDB.rps_status.no_cool = rps_status[3] + self.sDB.rps_status.ex_waste = rps_status[4] + self.sDB.rps_status.ex_hcool = rps_status[5] + self.sDB.rps_status.no_fuel = rps_status[6] self.sDB.rps_status.fault = rps_status[7] self.sDB.rps_status.timeout = rps_status[8] self.sDB.rps_status.manual = rps_status[9] + self.sDB.rps_status.sys_fail = rps_status[10] end -- copy in the reactor status @@ -205,20 +208,19 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) -- copy in the reactor structure ---@param mek_data table local function _copy_struct(mek_data) - self.sDB.mek_struct.formed = mek_data[1] - self.sDB.mek_struct.length = mek_data[2] - self.sDB.mek_struct.width = mek_data[3] - self.sDB.mek_struct.height = mek_data[4] - self.sDB.mek_struct.min_pos = mek_data[5] - self.sDB.mek_struct.max_pos = mek_data[6] - self.sDB.mek_struct.heat_cap = mek_data[7] - self.sDB.mek_struct.fuel_asm = mek_data[8] - self.sDB.mek_struct.fuel_sa = mek_data[9] - self.sDB.mek_struct.fuel_cap = mek_data[10] - self.sDB.mek_struct.waste_cap = mek_data[11] - self.sDB.mek_struct.ccool_cap = mek_data[12] - self.sDB.mek_struct.hcool_cap = mek_data[13] - self.sDB.mek_struct.max_burn = mek_data[14] + self.sDB.mek_struct.length = mek_data[1] + self.sDB.mek_struct.width = mek_data[2] + self.sDB.mek_struct.height = mek_data[3] + self.sDB.mek_struct.min_pos = mek_data[4] + self.sDB.mek_struct.max_pos = mek_data[5] + self.sDB.mek_struct.heat_cap = mek_data[6] + self.sDB.mek_struct.fuel_asm = mek_data[7] + self.sDB.mek_struct.fuel_sa = mek_data[8] + self.sDB.mek_struct.fuel_cap = mek_data[9] + self.sDB.mek_struct.waste_cap = mek_data[10] + self.sDB.mek_struct.ccool_cap = mek_data[11] + self.sDB.mek_struct.hcool_cap = mek_data[12] + self.sDB.mek_struct.max_burn = mek_data[13] end -- mark this PLC session as closed, stop watchdog @@ -298,18 +300,22 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) self.sDB.last_status_update = pkt.data[1] self.sDB.control_state = pkt.data[2] self.sDB.rps_tripped = pkt.data[3] - self.sDB.degraded = pkt.data[4] - self.sDB.mek_status.heating_rate = pkt.data[5] + self.sDB.no_reactor = pkt.data[4] + self.sDB.formed = pkt.data[5] - -- attempt to read mek_data table - if pkt.data[6] ~= nil then - local status = pcall(_copy_status, pkt.data[6]) - if status then - -- copied in status data OK - self.received_status_cache = true - else - -- error copying status data - log.error(log_header .. "failed to parse status packet data") + if not self.sDB.no_reactor and self.sDB.formed then + self.sDB.mek_status.heating_rate = pkt.data[6] + + -- attempt to read mek_data table + if pkt.data[7] ~= nil then + local status = pcall(_copy_status, pkt.data[7]) + if status then + -- copied in status data OK + self.received_status_cache = true + else + -- error copying status data + log.error(log_header .. "failed to parse status packet data") + end end end else @@ -379,7 +385,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) }) elseif pkt.type == RPLC_TYPES.RPS_STATUS then -- RPS status packet received, copy data - if pkt.length == 9 then + if pkt.length == 10 then local status = pcall(_copy_rps_status, pkt.data) if status then -- copied in RPS status data OK @@ -392,7 +398,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) end elseif pkt.type == RPLC_TYPES.RPS_ALARM then -- RPS alarm - if pkt.length == 10 then + if pkt.length == 11 then self.sDB.rps_tripped = true self.sDB.rps_trip_cause = pkt.data[1] local status = pcall(_copy_rps_status, { table.unpack(pkt.data, 2, pkt.length) }) @@ -490,7 +496,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) self.sDB.control_state, self.sDB.rps_tripped, self.sDB.rps_trip_cause, - self.sDB.degraded + self.sDB.no_reactor, + self.sDB.formed } end @@ -609,21 +616,41 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) local rtimes = self.retry_times - -- struct request retry + if (not self.sDB.no_reactor) and self.sDB.formed then + -- struct request retry - if not self.received_struct then - if rtimes.struct_req - util.time() <= 0 then - _send(RPLC_TYPES.MEK_STRUCT, {}) - rtimes.struct_req = util.time() + RETRY_PERIOD + if not self.received_struct then + if rtimes.struct_req - util.time() <= 0 then + _send(RPLC_TYPES.MEK_STRUCT, {}) + rtimes.struct_req = util.time() + RETRY_PERIOD + end end - end - -- status cache request retry + -- status cache request retry - if not self.received_status_cache then - if rtimes.status_req - util.time() <= 0 then - _send(RPLC_TYPES.MEK_STATUS, {}) - rtimes.status_req = util.time() + RETRY_PERIOD + if not self.received_status_cache then + if rtimes.status_req - util.time() <= 0 then + _send(RPLC_TYPES.MEK_STATUS, {}) + rtimes.status_req = util.time() + RETRY_PERIOD + end + end + + -- enable request retry + + if not self.acks.enable then + if rtimes.enable_req - util.time() <= 0 then + _send(RPLC_TYPES.RPS_ENABLE, {}) + rtimes.enable_req = util.time() + RETRY_PERIOD + end + end + + -- burn rate request retry + + if not self.acks.burn_rate then + if rtimes.burn_rate_req - util.time() <= 0 then + _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + rtimes.burn_rate_req = util.time() + RETRY_PERIOD + end end end @@ -636,24 +663,6 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) end end - -- enable request retry - - if not self.acks.enable then - if rtimes.enable_req - util.time() <= 0 then - _send(RPLC_TYPES.RPS_ENABLE, {}) - rtimes.enable_req = util.time() + RETRY_PERIOD - end - end - - -- burn rate request retry - - if not self.acks.burn_rate then - if rtimes.burn_rate_req - util.time() <= 0 then - _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) - rtimes.burn_rate_req = util.time() + RETRY_PERIOD - end - end - -- RPS reset request retry if not self.acks.rps_reset then diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 1e3148b..adf6437 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.6.5" +local SUPERVISOR_VERSION = "beta-v0.6.6" local print = util.print local println = util.println From 57bac57e3f4ff72bfa06d9c31735c1c3c725fb26 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 25 Oct 2022 13:30:41 -0400 Subject: [PATCH 421/587] adjusted TCD unserviced call delay --- coordinator/startup.lua | 2 +- scada-common/tcallbackdsp.lua | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index e8717c4..83a445d 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.5.10" +local COORDINATOR_VERSION = "alpha-v0.5.11" local print = util.print local println = util.println diff --git a/scada-common/tcallbackdsp.lua b/scada-common/tcallbackdsp.lua index 339d1b5..98d9b6f 100644 --- a/scada-common/tcallbackdsp.lua +++ b/scada-common/tcallbackdsp.lua @@ -9,7 +9,10 @@ local tcallbackdsp = {} local registry = {} -local UNSERVICED_CALL_DELAY = util.TICK_TIME_S +---@todo possibly move this to a config file? +-- maximum 5 ticks late (0.25 seconds)
+-- heavily modded servers and large multiplayer servers tend to significantly slow tick times, so nominal 0.05s ticks are unlikely +local UNSERVICED_CALL_DELAY = util.TICK_TIME_S * 5 -- request a function to be called after the specified time ---@param time number seconds From 004c960e4d1b04870671ac6c1822d298d9eadbd9 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 25 Oct 2022 23:40:36 -0400 Subject: [PATCH 422/587] #106 fixes to reactor isFormed support --- reactor-plc/plc.lua | 65 +++++++++++++++++++++++------------------ reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 48 +++++++++++++++++------------- scada-common/ppm.lua | 52 ++++++++++++++++----------------- scada-common/types.lua | 3 +- 5 files changed, 94 insertions(+), 76 deletions(-) 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 From d87dfb9ebdae6dc76c56b58c8fefd04e0ac9f251 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 2 Nov 2022 12:02:52 -0400 Subject: [PATCH 423/587] #112 fixed bug with flasher --- coordinator/startup.lua | 2 +- graphics/flasher.lua | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 83a445d..5416262 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.5.11" +local COORDINATOR_VERSION = "alpha-v0.5.12" local print = util.print local println = util.println diff --git a/graphics/flasher.lua b/graphics/flasher.lua index 5fa856a..12c6670 100644 --- a/graphics/flasher.lua +++ b/graphics/flasher.lua @@ -2,7 +2,7 @@ -- Indicator Light Flasher -- -local tcd = require("scada-common.tcallbackdsp") +local tcd = require("scada-common.tcallbackdsp") local flasher = {} @@ -26,14 +26,14 @@ local callback_counter = 0 -- this assumes it is called every 250ms, it does no checking of time on its own local function callback_250ms() if active then - for _, f in pairs(registry[PERIOD.BLINK_250_MS]) do f() end + for _, f in ipairs(registry[PERIOD.BLINK_250_MS]) do f() end if callback_counter % 2 == 0 then - for _, f in pairs(registry[PERIOD.BLINK_500_MS]) do f() end + for _, f in ipairs(registry[PERIOD.BLINK_500_MS]) do f() end end if callback_counter % 4 == 0 then - for _, f in pairs(registry[PERIOD.BLINK_1000_MS]) do f() end + for _, f in ipairs(registry[PERIOD.BLINK_1000_MS]) do f() end end callback_counter = callback_counter + 1 @@ -70,10 +70,10 @@ end ---@param f function function callback registered function flasher.stop(f) for i = 1, #registry do - for j = 1, #registry[i] do - if registry[i][j] == f then - registry[i][j] = nil - break + for key, val in ipairs(registry[i]) do + if val == f then + table.remove(registry[i], key) + return end end end From 54264f51498c2eeda04a1e0877c6c5a7438ece6c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 2 Nov 2022 13:45:52 -0400 Subject: [PATCH 424/587] #111 support unformed reactors --- coordinator/iocontrol.lua | 3 ++- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 4 +++- supervisor/session/plc.lua | 6 +++--- supervisor/startup.lua | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 0a42038..26ef62b 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -112,7 +112,8 @@ function iocontrol.record_builds(builds) unit.reactor_ps.publish(key, val) end - if unit.reactor_data.mek_struct.length ~= 0 and unit.reactor_data.mek_struct.width ~= 0 then + if (type(unit.reactor_data.mek_struct.length) == "number") and (unit.reactor_data.mek_struct.length ~= 0) and + (type(unit.reactor_data.mek_struct.width) == "number") and (unit.reactor_data.mek_struct.width ~= 0) then unit.reactor_ps.publish("size", { unit.reactor_data.mek_struct.length, unit.reactor_data.mek_struct.width }) end end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 5416262..b4f3db4 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.5.12" +local COORDINATOR_VERSION = "alpha-v0.6.0" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index df79a00..182b773 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -141,6 +141,7 @@ local function init(parent, id) local rps_noc = IndicatorLight{parent=annunciator,label="No Coolant",colors=cpair(colors.yellow,colors.gray)} local rps_flt = IndicatorLight{parent=annunciator,label="PPM Fault",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} local rps_tmo = IndicatorLight{parent=annunciator,label="Timeout",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} + local rps_sfl = IndicatorLight{parent=annunciator,label="System Failure",colors=cpair(colors.orange,colors.gray),flash=true,period=period.BLINK_500_MS} r_ps.subscribe("rps_tripped", rps_trp.update) r_ps.subscribe("dmg_crit", rps_dmg.update) @@ -151,6 +152,7 @@ local function init(parent, id) r_ps.subscribe("no_cool", rps_noc.update) r_ps.subscribe("fault", rps_flt.update) r_ps.subscribe("timeout", rps_tmo.update) + r_ps.subscribe("sys_fail", rps_sfl.update) annunciator.line_break() @@ -171,7 +173,7 @@ local function init(parent, id) -- machine-specific indicators if unit.num_boilers > 0 then - TextBox{parent=main,x=32,y=34,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=main,x=32,y=35,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local b1_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} b_ps[1].subscribe("HeatingRateLow", b1_hr.update) end diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 079d8e9..6b066ef 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -304,7 +304,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) self.sDB.formed = pkt.data[5] if not self.sDB.no_reactor and self.sDB.formed then - self.sDB.mek_status.heating_rate = pkt.data[6] + self.sDB.mek_status.heating_rate = pkt.data[6] or 0.0 -- attempt to read mek_data table if pkt.data[7] ~= nil then @@ -471,7 +471,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) if self.received_struct then return self.sDB.mek_struct else - return nil + return {} end end @@ -480,7 +480,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) if self.received_status_cache then return self.sDB.mek_status else - return nil + return {} end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index adf6437..2b3412d 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.6.6" +local SUPERVISOR_VERSION = "beta-v0.6.7" local print = util.print local println = util.println From c620310e51472d0dfe801badd69c3b1b1c6d0dbc Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 2 Nov 2022 14:47:18 -0400 Subject: [PATCH 425/587] #113 power formatting on turbine energy in main overview --- coordinator/startup.lua | 2 +- coordinator/ui/components/turbine.lua | 3 +- graphics/element.lua | 1 + graphics/elements/indicators/data.lua | 25 +------- graphics/elements/indicators/power.lua | 74 ++++++++++++++++++++++ scada-common/util.lua | 88 ++++++++++++++++++++++++-- 6 files changed, 160 insertions(+), 33 deletions(-) create mode 100644 graphics/elements/indicators/power.lua diff --git a/coordinator/startup.lua b/coordinator/startup.lua index b4f3db4..1ba171b 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.0" +local COORDINATOR_VERSION = "alpha-v0.6.1" local print = util.print local println = util.println diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua index 119caf2..f4cbcf1 100644 --- a/coordinator/ui/components/turbine.lua +++ b/coordinator/ui/components/turbine.lua @@ -4,6 +4,7 @@ local util = require("scada-common.util") local style = require("coordinator.ui.style") local DataIndicator = require("graphics.elements.indicators.data") +local PowerIndicator = require("graphics.elements.indicators.power") local StateIndicator = require("graphics.elements.indicators.state") local Rectangle = require("graphics.elements.rectangle") local VerticalBar = require("graphics.elements.indicators.vbar") @@ -23,7 +24,7 @@ local function new_view(root, x, y, ps) local lu_col = cpair(colors.gray, colors.gray) local status = StateIndicator{parent=turbine,x=8,y=1,states=style.turbine.states,value=1,min_width=10} - local prod_rate = DataIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",unit="FE",format="%10.2f",value=0,width=16,fg_bg=text_fg_bg} + local prod_rate = PowerIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",format="%10.2f",value=0,width=16,fg_bg=text_fg_bg} local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=0,commas=true,width=16,fg_bg=text_fg_bg} ps.subscribe("computed_status", status.update) diff --git a/graphics/element.lua b/graphics/element.lua index 70b784c..178f2d7 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -31,6 +31,7 @@ local element = {} ---|hbar_args ---|icon_indicator_args ---|indicator_light_args +---|power_indicator_args ---|state_indicator_args ---|tristate_indicator_light_args ---|vbar_args diff --git a/graphics/elements/indicators/data.lua b/graphics/elements/indicators/data.lua index c566791..5b5a35b 100644 --- a/graphics/elements/indicators/data.lua +++ b/graphics/elements/indicators/data.lua @@ -4,29 +4,6 @@ local util = require("scada-common.util") local element = require("graphics.element") --- format a number string with commas as the thousands separator --- --- subtracts from spaces at the start if present for each comma used ----@param num string number string ----@return string -local function comma_format(num) - local formatted = num - local commas = 0 - local i = 1 - - while i > 0 do - formatted, i = formatted:gsub("^(%s-%d+)(%d%d%d)", '%1,%2') - if i > 0 then commas = commas + 1 end - end - - local _, num_spaces = formatted:gsub(" %s-", "") - local remove = math.min(num_spaces, commas) - - formatted = string.sub(formatted, remove + 1) - - return formatted -end - ---@class data_indicator_args ---@field label string indicator label ---@field unit? string indicator unit @@ -78,7 +55,7 @@ local function data(args) e.window.setCursorPos(data_start, 1) e.window.setTextColor(e.fg_bg.fgd) if args.commas then - e.window.write(comma_format(data_str)) + e.window.write(util.comma_format(data_str)) else e.window.write(data_str) end diff --git a/graphics/elements/indicators/power.lua b/graphics/elements/indicators/power.lua new file mode 100644 index 0000000..70d6479 --- /dev/null +++ b/graphics/elements/indicators/power.lua @@ -0,0 +1,74 @@ +-- Power Indicator Graphics Element + +local util = require("scada-common.util") + +local element = require("graphics.element") + +---@class power_indicator_args +---@field label string indicator label +---@field format string power format override (lua string format) +---@field lu_colors? cpair label foreground color (a), unit foreground color (b) +---@field value any default value +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field width integer length +---@field fg_bg? cpair foreground/background colors + +-- new power indicator +---@param args power_indicator_args +---@return graphics_element element, element_id id +local function power(args) + assert(args.value ~= nil, "graphics.elements.indicators.power: value is a required field") + assert(util.is_int(args.width), "graphics.elements.indicators.power: width is a required field") + + -- single line + args.height = 1 + + -- create new graphics element base object + local e = element.new(args) + + -- label color + if args.lu_colors ~= nil then + e.window.setTextColor(args.lu_colors.color_a) + end + + -- write label + e.window.setCursorPos(1, 1) + e.window.write(args.label) + + local data_start = string.len(args.label) + 2 + + -- on state change + ---@param value any new value + function e.on_update(value) + e.value = value + + local data_str, unit = util.power_format(value, false, args.format) + + -- write data + e.window.setCursorPos(data_start, 1) + e.window.setTextColor(e.fg_bg.fgd) + e.window.write(util.comma_format(data_str)) + + -- write unit + if args.lu_colors ~= nil then + e.window.setTextColor(args.lu_colors.color_b) + end + -- add space so we don't end up with FEE (after having kFE for example) + if unit == "FE" then unit = "FE " end + e.window.write(" " .. unit) + end + + -- set the value + ---@param val any new value + function e.set_value(val) e.on_update(val) end + + -- initial value draw + e.on_update(args.value) + + return e.get() +end + +return power diff --git a/scada-common/util.lua b/scada-common/util.lua index 440b9e0..1153c6b 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -11,6 +11,7 @@ util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 -- OPERATORS -- +--#region -- trinary operator ---@param cond boolean condition @@ -21,7 +22,10 @@ function util.trinary(cond, a, b) if cond then return a else return b end end +--#endregion + -- PRINT -- +--#region -- print ---@param message any @@ -47,7 +51,10 @@ function util.println_ts(message) print(os.date("[%H:%M:%S] ") .. tostring(message)) end +--#endregion + -- STRING TOOLS -- +--#region -- get a value as a string ---@param val any @@ -160,7 +167,33 @@ function util.sprintf(format, ...) return string.format(format, table.unpack(arg)) end +-- format a number string with commas as the thousands separator +-- +-- subtracts from spaces at the start if present for each comma used +---@param num string number string +---@return string +function util.comma_format(num) + local formatted = num + local commas = 0 + local i = 1 + + while i > 0 do + formatted, i = formatted:gsub("^(%s-%d+)(%d%d%d)", '%1,%2') + if i > 0 then commas = commas + 1 end + end + + local _, num_spaces = formatted:gsub(" %s-", "") + local remove = math.min(num_spaces, commas) + + formatted = string.sub(formatted, remove + 1) + + return formatted +end + +--#endregion + -- MATH -- +--#region -- is a value an integer ---@param x any value @@ -197,7 +230,10 @@ function util.time() return util.time_ms() end +--#endregion + -- OS -- +--#region -- OS pull event raw wrapper with types ---@param target_event? string event to wait for @@ -234,7 +270,10 @@ function util.cancel_timer(timer) os.cancelTimer(timer) end +--#endregion + -- PARALLELIZATION -- +--#region -- protected sleep call so we still are in charge of catching termination ---@param t integer seconds @@ -265,7 +304,10 @@ function util.adaptive_delay(target_timing, last_update) return util.time() end +--#endregion + -- TABLE UTILITIES -- +--#region -- delete elements from a table if the passed function returns false when passed a table element -- @@ -303,7 +345,10 @@ function util.table_contains(t, element) return false end +--#endregion + -- MEKANISM POWER -- +--#region -- convert Joules to FE ---@param J number Joules @@ -322,21 +367,46 @@ local function TFE(fe) return fe / 1000000000000.0 end -- format a power value into XXX.XX UNIT format (FE, kFE, MFE, GFE, TFE) ---@param fe number forge energy value ----@return string str formatted string -function util.power_format(fe) +---@param combine_label boolean if a label should be included in the string itself +---@param format string format override +---@return string str, string? unit +function util.power_format(fe, combine_label, format) + local unit + local value + + if type(format) ~= "string" then + format = "%.2f" + end + if fe < 1000 then - return string.format("%.2f FE", fe) + unit = "FE" + value = fe elseif fe < 1000000 then - return string.format("%.2f kFE", kFE(fe)) + unit = "kFE" + value = kFE(fe) elseif fe < 1000000000 then - return string.format("%.2f MFE", MFE(fe)) + unit = "MFE" + value = MFE(fe) elseif fe < 1000000000000 then - return string.format("%.2f GFE", GFE(fe)) + unit = "GFE" + value = GFE(fe) else - return string.format("%.2f TFE", TFE(fe)) + unit = "TFE" + value = TFE(fe) + end + + if combine_label then + return util.sprintf(util.c(format, " %s"), value, unit) + else + return util.sprintf(format, value), unit end end +--#endregion + +-- UTILITY CLASSES -- +--#region + -- WATCHDOG -- -- ComputerCraft OS Timer based Watchdog @@ -403,6 +473,8 @@ function util.new_clock(period) return public end +-- FIELD VALIDATOR -- + -- create a new type validator -- -- can execute sequential checks and check valid() to see if it is still valid @@ -433,4 +505,6 @@ function util.new_validator() return public end +--#endregion + return util From 18289208733c6d32fb995f37d72b85c0ac93a250 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 2 Nov 2022 17:00:33 -0400 Subject: [PATCH 426/587] #110, #114 no longer use mekanism energy helper functions as those are event consuming --- coordinator/startup.lua | 5 +---- scada-common/tcallbackdsp.lua | 32 -------------------------------- scada-common/util.lua | 4 ++-- 3 files changed, 3 insertions(+), 38 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 1ba171b..0d2a81d 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.1" +local COORDINATOR_VERSION = "alpha-v0.6.2" local print = util.print local println = util.println @@ -298,9 +298,6 @@ while ui_ok do renderer.handle_touch(core.events.touch(param1, param2, param3)) end - -- call unserviced TCD callbacks - tcallbackdsp.call_unserviced() - -- check for termination request if event == "terminate" or ppm.should_terminate() then println_ts("terminate requested, closing connections...") diff --git a/scada-common/tcallbackdsp.lua b/scada-common/tcallbackdsp.lua index 98d9b6f..d4926c0 100644 --- a/scada-common/tcallbackdsp.lua +++ b/scada-common/tcallbackdsp.lua @@ -9,11 +9,6 @@ local tcallbackdsp = {} local registry = {} ----@todo possibly move this to a config file? --- maximum 5 ticks late (0.25 seconds)
--- heavily modded servers and large multiplayer servers tend to significantly slow tick times, so nominal 0.05s ticks are unlikely -local UNSERVICED_CALL_DELAY = util.TICK_TIME_S * 5 - -- request a function to be called after the specified time ---@param time number seconds ---@param f function callback function @@ -65,33 +60,6 @@ function tcallbackdsp.handle(event) end end --- execute any callbacks that are overdo their time and have not been serviced --- --- this can be called periodically to prevent loss of any callbacks do to timer events that are lost (see github issue #110) -function tcallbackdsp.call_unserviced() - local found_unserviced = true - - while found_unserviced do - found_unserviced = false - - -- go through registry, restart if unserviced entries were found due to mutating registry table - for timer, entry in pairs(registry) do - found_unserviced = util.time_s() > (entry.expiry + UNSERVICED_CALL_DELAY) - if found_unserviced then - local overtime = util.time_s() - entry.expiry - local callback = entry.callback - - log.warning(util.c("TCD: executing unserviced callback ", entry.callback, " (", overtime, "s late) [timer: ", timer, "]")) - - -- clear first so that dispatch_unique call from inside callback won't see it as a conflict - registry[timer] = nil - callback() - break - end - end - end -end - -- identify any overdo callbacks -- -- prints to log debug output diff --git a/scada-common/util.lua b/scada-common/util.lua index 1153c6b..867a77e 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -353,12 +353,12 @@ end -- convert Joules to FE ---@param J number Joules ---@return number FE Forge Energy -function util.joules_to_fe(J) return mekanismEnergyHelper.joulesToFE(J) end +function util.joules_to_fe(J) return (J * 0.4) end -- convert FE to Joules ---@param FE number Forge Energy ---@return number J Joules -function util.fe_to_joules(FE) return mekanismEnergyHelper.feToJoules(FE) end +function util.fe_to_joules(FE) return (FE * 2.5) end local function kFE(fe) return fe / 1000.0 end local function MFE(fe) return fe / 1000000.0 end From aaab34f1a8d311ae0d14f5e3cd1058aa4bc1a21a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 5 Nov 2022 12:44:40 -0400 Subject: [PATCH 427/587] #115, #116 multiple bugfixes with reactor PLC code --- reactor-plc/plc.lua | 13 ++++++++----- reactor-plc/startup.lua | 4 +++- reactor-plc/threads.lua | 39 +++++++++++++++++++++++---------------- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 6845b37..578d0d8 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -82,13 +82,16 @@ function plc.rps_init(reactor, is_formed) -- check if the reactor is formed local function _is_formed() - local is_formed = self.reactor.isFormed() - if is_formed == ppm.ACCESS_FAULT then + local formed = self.reactor.isFormed() + if formed == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() - elseif not self.state[state_keys.sys_fail] then - self.formed = is_formed - self.state[state_keys.sys_fail] = not is_formed + else + self.formed = formed + + if not self.state[state_keys.sys_fail] then + self.state[state_keys.sys_fail] = not formed + end end end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index d26291c..02786f0 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.1" +local R_PLC_VERSION = "beta-v0.9.2" local print = util.print local println = util.println @@ -134,6 +134,8 @@ if __shared_memory.networked and smem_dev.modem == nil then end -- PLC init +--- +--- 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) diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 1369a51..0a96655 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -80,7 +80,7 @@ function threads.thread__main(smem, init) end -- are we now formed after waiting to be formed? - if not plc_state.reactor_formed and rps.is_formed() then + if (not plc_state.reactor_formed) and rps.is_formed() then -- push a connect event and unmount it from the PPM local iface = ppm.get_iface(plc_dev.reactor) if iface then @@ -89,21 +89,19 @@ function threads.thread__main(smem, init) local type, device = ppm.mount(iface) - if type ~= "fissionReactorLogicAdapter" and device ~= nil then + if type == "fissionReactorLogicAdapter" and device ~= nil then -- reconnect reactor plc_dev.reactor = device - plc_state.reactor_formed = device.isFormed() - if plc_state.reactor_formed then - println_ts("reactor reconnected as formed.") - log.info("reactor reconnected as formed") + -- we need to assume formed here as we cannot check in this main loop + -- RPS will identify if it isn't and this will get set false later + plc_state.reactor_formed = true - -- 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 + println_ts("reactor reconnected.") + log.info("reactor reconnected") + + -- SCRAM newly connected reactor + smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM) -- determine if we are still in a degraded state if not networked or not plc_state.no_modem then @@ -114,6 +112,10 @@ function threads.thread__main(smem, init) if networked then plc_comms.reconnect_reactor(plc_dev.reactor) end + + -- reset RPS for newly connected reactor + -- without this, is_formed will be out of date and cause it to think its no longer formed again + rps.reset() else -- fully lost the reactor now :( println_ts("reactor lost (failed reconnect)!") @@ -183,7 +185,10 @@ function threads.thread__main(smem, init) log.info("reactor reconnected") plc_state.no_reactor = false - plc_state.reactor_formed = device.isFormed() + + -- we need to assume formed here as we cannot check in this main loop + -- RPS will identify if it isn't and this will get set false later + plc_state.reactor_formed = true -- determine if we are still in a degraded state if (not networked or not plc_state.no_modem) and plc_state.reactor_formed then @@ -191,14 +196,16 @@ function threads.thread__main(smem, init) end if plc_state.init_ok then - if plc_state.reactor_formed then - smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM) - end + smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM) rps.reconnect_reactor(plc_dev.reactor) if networked then plc_comms.reconnect_reactor(plc_dev.reactor) end + + -- reset RPS for newly connected reactor + -- without this, is_formed will be out of date and cause it to think its no longer formed again + rps.reset() end elseif networked and type == "modem" then if device.isWireless() then From 806b217d58da577fcc75159bfb6c19bc04e73378 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 6 Nov 2022 18:41:52 -0500 Subject: [PATCH 428/587] #100 interactive reactor controls (start, scram, reset) --- coordinator/coordinator.lua | 24 +++- coordinator/iocontrol.lua | 12 ++ coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 30 +++-- graphics/element.lua | 11 ++ graphics/elements/controls/hazard_button.lua | 121 +++++++++++++++++-- scada-common/tcallbackdsp.lua | 14 ++- supervisor/session/coordinator.lua | 7 ++ supervisor/session/plc.lua | 14 --- supervisor/session/svqtypes.lua | 5 + supervisor/session/svsessions.lua | 8 +- supervisor/startup.lua | 2 +- 12 files changed, 211 insertions(+), 39 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 13ec9a4..05b8443 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -431,15 +431,27 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa -- unit command acknowledgement if packet.length == 3 then local cmd = packet.data[1] - local unit = packet.data[2] + local unit_id = packet.data[2] local ack = packet.data[3] - if cmd == CRDN_COMMANDS.SCRAM then - elseif cmd == CRDN_COMMANDS.START then - elseif cmd == CRDN_COMMANDS.RESET_RPS then - elseif cmd == CRDN_COMMANDS.SET_BURN then - elseif cmd == CRDN_COMMANDS.SET_WASTE then + local unit = iocontrol.get_db().units[unit_id] ---@type ioctl_entry + + if unit ~= nil then + if cmd == CRDN_COMMANDS.SCRAM then + unit.scram_ack(ack) + elseif cmd == CRDN_COMMANDS.START then + unit.start_ack(ack) + elseif cmd == CRDN_COMMANDS.RESET_RPS then + unit.reset_rps_ack(ack) + elseif cmd == CRDN_COMMANDS.SET_BURN then + unit.set_burn_ack(ack) + elseif cmd == CRDN_COMMANDS.SET_WASTE then + unit.set_waste_ack(ack) + else + log.debug(util.c("received command ack with unknown command ", cmd)) + end else + log.debug(util.c("received command ack with unknown unit ", unit_id)) end else log.debug("unit command ack packet length mismatch") diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 26ef62b..0d953ae 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -38,6 +38,13 @@ function iocontrol.init(conf, comms) scram = function () end, reset_rps = function () end, set_burn = function (rate) end, + set_waste = function (mode) end, + + start_ack = function (success) end, + scram_ack = function (success) end, + reset_rps_ack = function (success) end, + set_burn_ack = function (success) end, + set_waste_ack = function (success) end, reactor_ps = psil.create(), reactor_data = {}, ---@type reactor_db @@ -71,6 +78,11 @@ function iocontrol.init(conf, comms) log.debug(util.c("UNIT[", i, "]: SET_BURN = ", rate)) end + function entry.set_waste(mode) + comms.send_command(CRDN_COMMANDS.SET_WASTE, i, mode) + log.debug(util.c("UNIT[", i, "]: SET_WASTE = ", mode)) + end + -- create boiler tables for _ = 1, conf.defs[(i * 2) - 1] do local data = {} ---@type boilerv_session_db diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 0d2a81d..95616d0 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.2" +local COORDINATOR_VERSION = "alpha-v0.6.3" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 182b773..04fa8ee 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -239,15 +239,32 @@ local function init(parent, id) ---@todo radiation monitor IndicatorLight{parent=annunciator,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)} IndicatorLight{parent=annunciator,label="Radiation Alarm",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - DataIndicator{parent=main,x=34,y=51,label="",format="%10.1f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=18,fg_bg=stat_fg_bg} + DataIndicator{parent=main,x=22,y=22,label="",format="%3.2f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=11,fg_bg=stat_fg_bg} -- reactor controls -- - HazardButton{parent=main,x=2,y=44,text="START",accent=colors.lightBlue,callback=unit.start,fg_bg=scram_fg_bg} - HazardButton{parent=main,x=12,y=44,text="SCRAM",accent=colors.yellow,callback=unit.scram,fg_bg=scram_fg_bg} - HazardButton{parent=main,x=22,y=44,text="RESET",accent=colors.red,callback=unit.reset_rps,fg_bg=scram_fg_bg} + local dis_colors = cpair(colors.white, colors.lightGray) - local burn_control = Div{parent=main,x=12,y=40,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} + local start = HazardButton{parent=main,x=2,y=26,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=scram_fg_bg} + local scram = HazardButton{parent=main,x=12,y=26,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=scram_fg_bg} + local reset = HazardButton{parent=main,x=22,y=26,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,fg_bg=scram_fg_bg} + + unit.start_ack = start.on_response + unit.scram_ack = scram.on_response + unit.reset_rps_ack = reset.on_response + + local function start_button_en_check() + if (unit.reactor_data ~= nil) and (unit.reactor_data.mek_status ~= nil) then + local can_start = (not unit.reactor_data.mek_status.status) and (not unit.reactor_data.rps_tripped) + if can_start then start.enable() else start.disable() end + end + end + + r_ps.subscribe("status", start_button_en_check) + r_ps.subscribe("rps_tripped", start_button_en_check) + r_ps.subscribe("rps_tripped", function (active) if active then reset.enable() else reset.disable() end end) + + local burn_control = Div{parent=main,x=2,y=22,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} TextBox{parent=burn_control,x=9,y=2,text="mB/t"} @@ -280,10 +297,9 @@ local function init(parent, id) } ---@todo waste selection - local waste_sel_f = function (s) print("waste: " .. s) end local waste_sel = Div{parent=main,x=2,y=48,width=29,height=2,fg_bg=cpair(colors.black, colors.white)} - MultiButton{parent=waste_sel,x=1,y=1,options=opts,callback=waste_sel_f,min_width=6,fg_bg=cpair(colors.black, colors.white)} + MultiButton{parent=waste_sel,x=1,y=1,options=opts,callback=unit.set_waste,min_width=6,fg_bg=cpair(colors.black, colors.white)} TextBox{parent=waste_sel,text="Waste Processing",alignment=TEXT_ALIGN.CENTER,x=1,y=1,height=1} ---@fixme test code diff --git a/graphics/element.lua b/graphics/element.lua index 178f2d7..33decff 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -163,6 +163,11 @@ function element.new(args) function protected.on_update(...) end + -- callback on control press responses + ---@param result any + function protected.response_callback(result) + end + -- get value function protected.get_value() return protected.value @@ -354,6 +359,12 @@ function element.new(args) protected.on_update(...) end + -- on a control request response + ---@param result any + function public.on_response(result) + protected.response_callback(result) + end + -- VISIBILITY -- -- show the element diff --git a/graphics/elements/controls/hazard_button.lua b/graphics/elements/controls/hazard_button.lua index 5fca7fe..7369866 100644 --- a/graphics/elements/controls/hazard_button.lua +++ b/graphics/elements/controls/hazard_button.lua @@ -8,6 +8,7 @@ local element = require("graphics.element") ---@class hazard_button_args ---@field text string text to show on button ---@field accent color accent color for hazard border +---@field dis_colors? cpair text color and border color when disabled ---@field callback function function to call on touch ---@field parent graphics_element ---@field id? string element id @@ -62,6 +63,82 @@ local function hazard_button(args) e.window.write("\x99\x98\x98\x98\x98\x98\x98\x98\x99") end + -- on request timeout: recursively calls itself to double flash button text + ---@param n integer call count + local function on_timeout(n) + -- start at 0 + if n == nil then n = 0 end + + if n == 0 then + -- go back off + e.window.setTextColor(args.fg_bg.fgd) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + end + + if n >= 4 then + -- done + elseif n % 2 == 0 then + -- toggle text color on after 0.25 seconds + tcd.dispatch(0.25, function () + e.window.setTextColor(args.accent) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + on_timeout(n + 1) + on_timeout(n + 1) + end) + elseif n % 1 then + -- toggle text color off after 0.25 seconds + tcd.dispatch(0.25, function () + e.window.setTextColor(args.fg_bg.fgd) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + on_timeout(n + 1) + end) + end + end + + -- blink routine for success indication + local function on_success() + e.window.setTextColor(args.fg_bg.fgd) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + end + + -- blink routine for failure indication + ---@param n integer call count + local function on_failure(n) + -- start at 0 + if n == nil then n = 0 end + + if n == 0 then + -- go back off + e.window.setTextColor(args.fg_bg.fgd) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + end + + if n >= 2 then + -- done + elseif n % 2 == 0 then + -- toggle text color on after 0.5 seconds + tcd.dispatch(0.5, function () + e.window.setTextColor(args.accent) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + on_failure(n + 1) + end) + elseif n % 1 then + -- toggle text color off after 0.25 seconds + tcd.dispatch(0.25, function () + e.window.setTextColor(args.fg_bg.fgd) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + on_failure(n + 1) + end) + end + end + -- handle touch ---@param event monitor_touch monitor touch event ---@diagnostic disable-next-line: unused-local @@ -75,12 +152,25 @@ local function hazard_button(args) e.window.setCursorPos(3, 2) e.window.write(args.text) - -- restore text color after 1 second - tcd.dispatch(1, function () - e.window.setTextColor(args.fg_bg.fgd) - e.window.setCursorPos(3, 2) - e.window.write(args.text) - end) + -- abort any other callbacks + tcd.abort(on_timeout) + tcd.abort(on_success) + tcd.abort(on_failure) + + -- 1.5 second timeout + tcd.dispatch(1.5, on_timeout) + end + end + + -- callback on request response + ---@param result boolean true for success, false for failure + function e.response_callback(result) + tcd.abort(on_timeout) + + if result then + on_success() + else + on_failure(0) end end @@ -90,8 +180,25 @@ local function hazard_button(args) if val then e.handle_touch(core.events.touch("", 1, 1)) end end + -- show the button as disabled + function e.disable() + if args.dis_colors then + draw_border(args.dis_colors.color_a) + e.window.setTextColor(args.dis_colors.color_b) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + end + end + + -- show the button as enabled + function e.enable() + draw_border(args.accent) + e.window.setTextColor(args.fg_bg.fgd) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + end + -- initial draw of border - ---@todo disabling will change border draw_border(args.accent) return e.get() diff --git a/scada-common/tcallbackdsp.lua b/scada-common/tcallbackdsp.lua index d4926c0..52f55da 100644 --- a/scada-common/tcallbackdsp.lua +++ b/scada-common/tcallbackdsp.lua @@ -27,7 +27,7 @@ end ---@param time number seconds ---@param f function callback function function tcallbackdsp.dispatch_unique(time, f) - -- ignore if already registered + -- cancel if already registered for timer, entry in pairs(registry) do if entry.callback == f then -- found an instance of this function reference, abort it @@ -49,6 +49,18 @@ function tcallbackdsp.dispatch_unique(time, f) -- log.debug(util.c("TCD: queued callback for ", f, " [timer: ", timer, "]")) end +-- abort a requested callback +---@param f function callback function +function tcallbackdsp.abort(f) + for timer, entry in pairs(registry) do + if entry.callback == f then + -- cancel event and remove from registry (even if it fires it won't call) + util.cancel_timer(timer) + registry[timer] = nil + end + end +end + -- lookup a timer event and execute the callback if found ---@param event integer timer event timer ID function tcallbackdsp.handle(event) diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 3db1da7..53ba62c 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -29,6 +29,7 @@ local CRD_S_CMDS = { } local CRD_S_DATA = { + CMD_ACK = 1 } coordinator.CRD_S_CMDS = CRD_S_CMDS @@ -271,6 +272,12 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) end elseif message.qtype == mqueue.TYPE.DATA then -- instruction with body + local cmd = message.message ---@type queue_data + + if cmd.key == CRD_S_DATA.CMD_ACK then + local ack = cmd.val ---@type coord_ack + _send(SCADA_CRDN_TYPES.COMMAND_UNIT, { ack.cmd, ack.unit, ack.ack }) + end end end diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 6b066ef..4e72a68 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -74,14 +74,12 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) struct_req = (util.time() + 500), status_req = (util.time() + 500), scram_req = 0, - enable_req = 0, burn_rate_req = 0, rps_reset_req = 0 }, -- command acknowledgements acks = { scram = true, - enable = true, burn_rate = true, rps_reset = true }, @@ -355,7 +353,6 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) -- enable acknowledgement local ack = _get_ack(pkt) if ack then - self.acks.enable = true self.sDB.control_state = true elseif ack == false then log.debug(log_header .. "enable failed!") @@ -537,8 +534,6 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) local cmd = message.message if cmd == PLC_S_CMDS.ENABLE then -- enable reactor - self.acks.enable = false - self.retry_times.enable_req = util.time() + INITIAL_WAIT _send(RPLC_TYPES.RPS_ENABLE, {}) elseif cmd == PLC_S_CMDS.SCRAM then -- SCRAM reactor @@ -635,15 +630,6 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) end end - -- enable request retry - - if not self.acks.enable then - if rtimes.enable_req - util.time() <= 0 then - _send(RPLC_TYPES.RPS_ENABLE, {}) - rtimes.enable_req = util.time() + RETRY_PERIOD - end - end - -- burn rate request retry if not self.acks.burn_rate then diff --git a/supervisor/session/svqtypes.lua b/supervisor/session/svqtypes.lua index 19f9d54..1b8b131 100644 --- a/supervisor/session/svqtypes.lua +++ b/supervisor/session/svqtypes.lua @@ -14,6 +14,11 @@ local SV_Q_DATA = { CRDN_ACK = 7 } +---@class coord_ack +---@field unit integer +---@field cmd integer +---@field ack boolean + svqtypes.SV_Q_CMDS = SV_Q_CMDS svqtypes.SV_Q_DATA = SV_Q_DATA diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index de54493..29a3e07 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -17,6 +17,7 @@ local SV_Q_DATA = svqtypes.SV_Q_DATA local PLC_S_CMDS = plc.PLC_S_CMDS local PLC_S_DATA = plc.PLC_S_DATA local CRD_S_CMDS = coordinator.CRD_S_CMDS +local CRD_S_DATA = coordinator.CRD_S_DATA local svsessions = {} @@ -70,7 +71,6 @@ local function _sv_handle_outq(session) if cmd.key < SV_Q_DATA.__END_PLC_CMDS__ then -- PLC commands from coordinator - local crdn_sid = session.instance.get_id() local plc_s = svsessions.get_reactor_session(cmd.val[1]) if plc_s ~= nil then @@ -90,7 +90,11 @@ local function _sv_handle_outq(session) end else if cmd.key == SV_Q_DATA.CRDN_ACK then - ---@todo ack to be sent to coordinator + -- ack to be sent to coordinator + local crd_s = svsessions.get_coord_session() + if crd_s ~= nil then + crd_s.in_queue.push_data(CRD_S_DATA.CMD_ACK, cmd.val) + end end end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 2b3412d..afe2745 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.6.7" +local SUPERVISOR_VERSION = "beta-v0.6.8" local print = util.print local println = util.println From bc63a06b095acb72900fb7a14fbe7c010bff3c43 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 10 Nov 2022 12:00:23 -0500 Subject: [PATCH 429/587] someone had PFE in an induction matrix so now i've gotta support some bigger numbers in the power format --- coordinator/startup.lua | 2 +- scada-common/util.lua | 28 ++++++++++++++++++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 95616d0..6740bdd 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.3" +local COORDINATOR_VERSION = "alpha-v0.6.4" local print = util.print local println = util.println diff --git a/scada-common/util.lua b/scada-common/util.lua index 867a77e..e02d236 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -364,11 +364,14 @@ local function kFE(fe) return fe / 1000.0 end local function MFE(fe) return fe / 1000000.0 end local function GFE(fe) return fe / 1000000000.0 end local function TFE(fe) return fe / 1000000000000.0 end +local function PFE(fe) return fe / 1000000000000000.0 end +local function EFE(fe) return fe / 1000000000000000000.0 end -- if you accomplish this please touch grass +local function ZFE(fe) return fe / 1000000000000000000000.0 end -- please stop --- format a power value into XXX.XX UNIT format (FE, kFE, MFE, GFE, TFE) +-- format a power value into XXX.XX UNIT format (FE, kFE, MFE, GFE, TFE, PFE, EFE, ZFE) ---@param fe number forge energy value ----@param combine_label boolean if a label should be included in the string itself ----@param format string format override +---@param combine_label? boolean if a label should be included in the string itself +---@param format? string format override ---@return string str, string? unit function util.power_format(fe, combine_label, format) local unit @@ -378,21 +381,30 @@ function util.power_format(fe, combine_label, format) format = "%.2f" end - if fe < 1000 then + if fe < 1000.0 then unit = "FE" value = fe - elseif fe < 1000000 then + elseif fe < 1000000.0 then unit = "kFE" value = kFE(fe) - elseif fe < 1000000000 then + elseif fe < 1000000000.0 then unit = "MFE" value = MFE(fe) - elseif fe < 1000000000000 then + elseif fe < 1000000000000.0 then unit = "GFE" value = GFE(fe) - else + elseif fe < 1000000000000000.0 then unit = "TFE" value = TFE(fe) + elseif fe < 1000000000000000000.0 then + unit = "PFE" + value = PFE(fe) + elseif fe < 1000000000000000000000.0 then + unit = "EFE" + value = EFE(fe) + else + unit = "ZFE" + value = ZFE(fe) end if combine_label then From 83cf645da47222fa435f44b09391110d1f53fc7e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 11 Nov 2022 14:59:53 -0500 Subject: [PATCH 430/587] #107, #121 RTU build changes, formed handling --- coordinator/iocontrol.lua | 62 +++++++++++------- coordinator/startup.lua | 2 +- coordinator/ui/components/boiler.lua | 2 +- coordinator/ui/components/turbine.lua | 2 +- coordinator/ui/style.lua | 16 +++++ rtu/rtu.lua | 8 +++ rtu/startup.lua | 39 ++++++++++-- rtu/threads.lua | 83 ++++++++++++++++++++++++- scada-common/comms.lua | 8 ++- supervisor/session/rtu.lua | 10 +++ supervisor/session/rtu/boilerv.lua | 9 +++ supervisor/session/rtu/envd.lua | 5 ++ supervisor/session/rtu/imatrix.lua | 9 +++ supervisor/session/rtu/redstone.lua | 5 ++ supervisor/session/rtu/sna.lua | 8 +++ supervisor/session/rtu/sps.lua | 9 +++ supervisor/session/rtu/turbinev.lua | 9 +++ supervisor/session/rtu/unit_session.lua | 15 +++++ supervisor/session/unit.lua | 18 ++++-- supervisor/startup.lua | 2 +- 20 files changed, 282 insertions(+), 39 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 0d953ae..4258bc1 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -133,12 +133,10 @@ function iocontrol.record_builds(builds) -- boiler builds if type(build.boilers) == "table" then for id, boiler in pairs(build.boilers) do - unit.boiler_data_tbl[id] = { - formed = boiler[2], ---@type boolean|nil - build = boiler[1] ---@type table - } + unit.boiler_data_tbl[id].formed = boiler[1] ---@type boolean + unit.boiler_data_tbl[id].build = boiler[2] ---@type table - unit.boiler_ps_tbl[id].publish("formed", boiler[2]) + unit.boiler_ps_tbl[id].publish("formed", boiler[1]) for key, val in pairs(unit.boiler_data_tbl[id].build) do unit.boiler_ps_tbl[id].publish(key, val) @@ -149,12 +147,10 @@ function iocontrol.record_builds(builds) -- turbine builds if type(build.turbines) == "table" then for id, turbine in pairs(build.turbines) do - unit.turbine_data_tbl[id] = { - formed = turbine[2], ---@type boolean|nil - build = turbine[1] ---@type table - } + unit.turbine_data_tbl[id].formed = turbine[1] ---@type boolean + unit.turbine_data_tbl[id].build = turbine[2] ---@type table - unit.turbine_ps_tbl[id].publish("formed", turbine[2]) + unit.turbine_ps_tbl[id].publish("formed", turbine[1]) for key, val in pairs(unit.turbine_data_tbl[id].build) do unit.turbine_ps_tbl[id].publish(key, val) @@ -290,15 +286,26 @@ function iocontrol.update_statuses(statuses) end for id, boiler in pairs(rtu_statuses.boilers) do - unit.boiler_data_tbl[id].state = boiler[1] ---@type table - unit.boiler_data_tbl[id].tanks = boiler[2] ---@type table + local rtu_faulted = boiler[1] ---@type boolean + unit.boiler_data_tbl[id].formed = boiler[2] ---@type boolean + unit.boiler_data_tbl[id].state = boiler[3] ---@type table + unit.boiler_data_tbl[id].tanks = boiler[4] ---@type table local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db - if data.state.boil_rate > 0 then - unit.boiler_ps_tbl[id].publish("computed_status", 3) -- active + unit.boiler_ps_tbl[id].publish("formed", data.formed) + unit.boiler_ps_tbl[id].publish("faulted", rtu_faulted) + + if data.formed then + if rtu_faulted then + unit.boiler_ps_tbl[id].publish("computed_status", 4) -- faulted + elseif data.state.boil_rate > 0 then + unit.boiler_ps_tbl[id].publish("computed_status", 3) -- active + else + unit.boiler_ps_tbl[id].publish("computed_status", 2) -- idle + end else - unit.boiler_ps_tbl[id].publish("computed_status", 2) -- idle + unit.boiler_ps_tbl[id].publish("computed_status", 5) -- not formed end for key, val in pairs(unit.boiler_data_tbl[id].state) do @@ -322,17 +329,28 @@ function iocontrol.update_statuses(statuses) end for id, turbine in pairs(rtu_statuses.turbines) do - unit.turbine_data_tbl[id].state = turbine[1] ---@type table - unit.turbine_data_tbl[id].tanks = turbine[2] ---@type table + local rtu_faulted = turbine[1] ---@type boolean + unit.turbine_data_tbl[id].formed = turbine[2] ---@type boolean + unit.turbine_data_tbl[id].state = turbine[3] ---@type table + unit.turbine_data_tbl[id].tanks = turbine[4] ---@type table local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db - if data.tanks.steam_fill >= 0.99 then - unit.turbine_ps_tbl[id].publish("computed_status", 4) -- trip - elseif data.state.flow_rate < 100 then - unit.turbine_ps_tbl[id].publish("computed_status", 2) -- idle + unit.turbine_ps_tbl[id].publish("formed", data.formed) + unit.turbine_ps_tbl[id].publish("faulted", rtu_faulted) + + if data.formed then + if data.tanks.energy_fill >= 0.99 then + unit.turbine_ps_tbl[id].publish("computed_status", 4) -- trip + elseif rtu_faulted then + unit.turbine_ps_tbl[id].publish("computed_status", 5) -- faulted + elseif data.state.flow_rate < 100 then + unit.turbine_ps_tbl[id].publish("computed_status", 2) -- idle + else + unit.turbine_ps_tbl[id].publish("computed_status", 3) -- active + end else - unit.turbine_ps_tbl[id].publish("computed_status", 3) -- active + unit.turbine_ps_tbl[id].publish("computed_status", 6) -- not formed end for key, val in pairs(unit.turbine_data_tbl[id].state) do diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 6740bdd..1c6b90b 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.4" +local COORDINATOR_VERSION = "alpha-v0.6.5" local print = util.print local println = util.println diff --git a/coordinator/ui/components/boiler.lua b/coordinator/ui/components/boiler.lua index 08cab0a..c7a1b97 100644 --- a/coordinator/ui/components/boiler.lua +++ b/coordinator/ui/components/boiler.lua @@ -22,7 +22,7 @@ local function new_view(root, x, y, ps) local text_fg_bg = cpair(colors.black, colors.lightGray) local lu_col = cpair(colors.gray, colors.gray) - local status = StateIndicator{parent=boiler,x=10,y=1,states=style.boiler.states,value=1,min_width=10} + local status = StateIndicator{parent=boiler,x=9,y=1,states=style.boiler.states,value=1,min_width=12} local temp = DataIndicator{parent=boiler,x=5,y=3,lu_colors=lu_col,label="Temp:",unit="K",format="%10.2f",value=0,width=22,fg_bg=text_fg_bg} local boil_r = DataIndicator{parent=boiler,x=5,y=4,lu_colors=lu_col,label="Boil:",unit="mB/t",format="%10.0f",value=0,commas=true,width=22,fg_bg=text_fg_bg} diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua index f4cbcf1..4070eb0 100644 --- a/coordinator/ui/components/turbine.lua +++ b/coordinator/ui/components/turbine.lua @@ -23,7 +23,7 @@ local function new_view(root, x, y, ps) local text_fg_bg = cpair(colors.black, colors.lightGray) local lu_col = cpair(colors.gray, colors.gray) - local status = StateIndicator{parent=turbine,x=8,y=1,states=style.turbine.states,value=1,min_width=10} + local status = StateIndicator{parent=turbine,x=7,y=1,states=style.turbine.states,value=1,min_width=12} local prod_rate = PowerIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",format="%10.2f",value=0,width=16,fg_bg=text_fg_bg} local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=0,commas=true,width=16,fg_bg=text_fg_bg} diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index 395e6f2..ae83155 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -76,6 +76,14 @@ style.boiler = { { color = cpair(colors.black, colors.green), text = "ACTIVE" + }, + { + color = cpair(colors.black, colors.orange), + text = "RTU FAULT" + }, + { + color = cpair(colors.black, colors.orange), + text = "NOT FORMED" } } } @@ -98,6 +106,14 @@ style.turbine = { { color = cpair(colors.black, colors.red), text = "TRIP" + }, + { + color = cpair(colors.black, colors.orange), + text = "RTU FAULT" + }, + { + color = cpair(colors.black, colors.orange), + text = "NOT FORMED" } } } diff --git a/rtu/rtu.lua b/rtu/rtu.lua index c427f54..229d58b 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -271,6 +271,12 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog) _send(SCADA_MGMT_TYPES.RTU_ADVERT, advertisement) end + -- notify that a peripheral was remounted + ---@param unit_index integer RTU unit ID + function public.send_remounted(unit_index) + _send(SCADA_MGMT_TYPES.RTU_DEV_REMOUNT, { unit_index }) + end + -- parse a MODBUS/SCADA packet ---@param side string ---@param sender integer @@ -400,6 +406,8 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog) -- acknowledgement rtu_state.linked = true self.r_seq_num = nil + println_ts("supervisor connection established") + log.info("supervisor connection established") elseif packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then -- request for capabilities again public.send_advertisement(units) diff --git a/rtu/startup.lua b/rtu/startup.lua index 7e6978d..6ce9ffe 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -24,7 +24,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.8.2" +local RTU_VERSION = "beta-v0.9.0" local rtu_t = types.rtu_t @@ -116,8 +116,8 @@ local function configure() -- redstone interfaces for entry_idx = 1, #rtu_redstone do local rs_rtu = redstone_rtu.new() - local io_table = rtu_redstone[entry_idx].io - local io_reactor = rtu_redstone[entry_idx].for_reactor + local io_table = rtu_redstone[entry_idx].io ---@type table + local io_reactor = rtu_redstone[entry_idx].for_reactor ---@type integer -- CHECK: reactor ID must be >= to 1 if (not util.is_int(io_reactor)) or (io_reactor <= 0) then @@ -218,6 +218,7 @@ local function configure() 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 @@ -265,26 +266,55 @@ local function configure() local type = ppm.get_type(name) local rtu_iface = nil ---@type rtu_device local rtu_type = "" + local formed = nil ---@type boolean|nil 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 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.sps + rtu_type = rtu_t.sna rtu_iface = sna_rtu.new(device) elseif type == "environmentDetector" then -- advanced peripherals environment detector @@ -305,6 +335,7 @@ local function configure() 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 diff --git a/rtu/threads.lua b/rtu/threads.lua index 0cfcd7a..e3d4cd7 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -5,7 +5,10 @@ local types = require("scada-common.types") local util = require("scada-common.util") local boilerv_rtu = require("rtu.dev.boilerv_rtu") +local envd_rtu = require("rtu.dev.envd_rtu") local imatrix_rtu = require("rtu.dev.imatrix_rtu") +local sna_rtu = require("rtu.dev.sna_rtu") +local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") local modbus = require("rtu.modbus") @@ -117,20 +120,35 @@ function threads.thread__main(smem) local unit = units[i] ---@type rtu_unit_registry_entry -- find disconnected device to reconnect + -- note: cannot check isFormed as that would yield this coroutine and consume events if unit.name == param1 then -- found, re-link unit.device = device if unit.type == rtu_t.boiler_valve then unit.rtu = boilerv_rtu.new(device) + unit.formed = true elseif unit.type == rtu_t.turbine_valve then unit.rtu = turbinev_rtu.new(device) + unit.formed = true elseif unit.type == rtu_t.induction_matrix then unit.rtu = imatrix_rtu.new(device) + unit.formed = true + elseif unit.type == rtu_t.sps then + unit.rtu = sps_rtu.new(device) + unit.formed = true + elseif unit.type == rtu_t.sna then + unit.rtu = sna_rtu.new(device) + elseif unit.type == rtu_t.env_detector then + unit.rtu = envd_rtu.new(device) + else + log.error(util.c("unreachable case occured trying to identify reconnected RTU unit type (", unit.name, ")"), true) end unit.modbus_io = modbus.new(unit.rtu, true) + rtu_comms.send_remounted(unit.index) + println_ts("reconnected the " .. unit.type .. " on interface " .. unit.name) end end @@ -256,6 +274,12 @@ function threads.thread__unit_comms(smem, unit) local last_update = util.time() + local check_formed = type(unit.formed) == "boolean" + local last_f_check = 0 + + local detail_name = util.c(unit.type, " (", unit.name, ") [", unit.index, "] for reactor ", unit.reactor) + local short_name = util.c(unit.type, " (", unit.name, ")") + if packet_queue == nil then log.error("rtu unit thread created without a message queue, exiting...", true) return @@ -283,9 +307,64 @@ function threads.thread__unit_comms(smem, unit) util.nop() end + + -- check if multiblocks is still formed + if check_formed and (util.time() - last_f_check > 1000) then + if (not unit.formed) and unit.device.isFormed() then + -- newly re-formed + local iface = ppm.get_iface(unit.device) + if iface then + log.info(util.c("unmounting and remounting reformed RTU unit ", detail_name)) + + ppm.unmount(unit.device) + + local type, device = ppm.mount(iface) + + if device ~= nil then + if type == "boilerValve" and unit.type == rtu_t.boiler_valve then + -- boiler multiblock + unit.device = device + unit.rtu = boilerv_rtu.new(device) + unit.formed = device.isFormed() + unit.modbus_io = modbus.new(unit.rtu, true) + elseif type == "turbineValve" and unit.type == rtu_t.turbine_valve then + -- turbine multiblock + unit.device = device + unit.rtu = turbinev_rtu.new(device) + unit.formed = device.isFormed() + unit.modbus_io = modbus.new(unit.rtu, true) + elseif type == "inductionPort" and unit.type == rtu_t.induction_matrix then + -- induction matrix multiblock + unit.device = device + unit.rtu = imatrix_rtu.new(device) + unit.formed = device.isFormed() + unit.modbus_io = modbus.new(unit.rtu, true) + elseif type == "spsPort" and unit.type == rtu_t.sps then + -- SPS multiblock + unit.device = device + unit.rtu = sps_rtu.new(device) + unit.formed = device.isFormed() + unit.modbus_io = modbus.new(unit.rtu, true) + else + log.error("illegal remount of non-multiblock RTU attempted for " .. short_name, true) + end + + rtu_comms.send_remounted(unit.index) + else + -- fully lost the peripheral now :( + log.error(util.c(unit.name, " lost (failed reconnect)")) + end + + log.info("reconnected the " .. unit.type .. " on interface " .. unit.name) + else + log.error("failed to get interface of previously connected RTU unit " .. detail_name, true) + end + end + end + -- check for termination request if rtu_state.shutdown then - log.info("rtu unit thread exiting -> " .. unit.type .. "(" .. unit.name .. ")") + log.info("rtu unit thread exiting -> " .. short_name) break end @@ -305,7 +384,7 @@ function threads.thread__unit_comms(smem, unit) end if not rtu_state.shutdown then - log.info(util.c("rtu unit thread ", unit.type, "(", unit.name, ") restarting in 5 seconds...")) + log.info(util.c("rtu unit thread ", unit.type, "(", unit.name, " restarting in 5 seconds...")) util.psleep(5) end end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 352e77f..e4df65c 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -44,8 +44,9 @@ local RPLC_LINKING = { local SCADA_MGMT_TYPES = { KEEP_ALIVE = 0, -- keep alive packet w/ RTT CLOSE = 1, -- close a connection - RTU_ADVERT = 2, -- RTU capability advertisement - REMOTE_LINKED = 3 -- remote device linked + REMOTE_LINKED = 2, -- remote device linked + RTU_ADVERT = 3, -- RTU capability advertisement + RTU_DEV_REMOUNT = 4 -- RTU multiblock possbily changed (formed, unformed) due to PPM remount } ---@alias SCADA_CRDN_TYPES integer @@ -383,7 +384,8 @@ function comms.mgmt_packet() return self.type == SCADA_MGMT_TYPES.KEEP_ALIVE or self.type == SCADA_MGMT_TYPES.CLOSE or self.type == SCADA_MGMT_TYPES.REMOTE_LINKED or - self.type == SCADA_MGMT_TYPES.RTU_ADVERT + self.type == SCADA_MGMT_TYPES.RTU_ADVERT or + self.type == SCADA_MGMT_TYPES.RTU_DEV_REMOUNT end -- make a SCADA management packet diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 1469863..ec48bc0 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -288,6 +288,16 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) -- handle advertisement; this will re-create all unit sub-sessions self.advert = pkt.data _handle_advertisement() + elseif pkt.type == SCADA_MGMT_TYPES.RTU_DEV_REMOUNT then + if pkt.length == 1 then + local unit_id = pkt[1] + if self.units[unit_id] ~= nil then + local unit = self.units[unit_id] ---@type unit_session + unit.invalidate_cache() + end + else + log.debug(log_header .. "SCADA RTU device re-mount packet length mismatch") + end else log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) end diff --git a/supervisor/session/rtu/boilerv.lua b/supervisor/session/rtu/boilerv.lua index bd9aaad..8b5b02e 100644 --- a/supervisor/session/rtu/boilerv.lua +++ b/supervisor/session/rtu/boilerv.lua @@ -159,6 +159,8 @@ function boilerv.new(session_id, unit_id, advert, out_queue) self.db.build.max_boil_rate = m_pkt.data[12] self.db.build.env_loss = m_pkt.data[13] self.has_build = true + + out_queue.push_command(unit_session.RTU_US_CMDS.BUILD_CHANGED) else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end @@ -227,6 +229,13 @@ function boilerv.new(session_id, unit_id, advert, out_queue) self.session.post_update() end + -- invalidate build cache + function public.invalidate_cache() + self.periodics.next_formed_req = 0 + self.periodics.next_build_req = 0 + self.has_build = false + end + -- get the unit session database function public.get_db() return self.db end diff --git a/supervisor/session/rtu/envd.lua b/supervisor/session/rtu/envd.lua index 3050835..a5546e4 100644 --- a/supervisor/session/rtu/envd.lua +++ b/supervisor/session/rtu/envd.lua @@ -94,6 +94,11 @@ function envd.new(session_id, unit_id, advert, out_queue) self.session.post_update() end + -- invalidate build cache + function public.invalidate_cache() + -- no build cache for this device + end + -- get the unit session database function public.get_db() return self.db end diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua index fc64b6d..3ebc7ab 100644 --- a/supervisor/session/rtu/imatrix.lua +++ b/supervisor/session/rtu/imatrix.lua @@ -142,6 +142,8 @@ function imatrix.new(session_id, unit_id, advert, out_queue) self.db.build.cells = m_pkt.data[8] self.db.build.providers = m_pkt.data[9] self.has_build = true + + out_queue.push_command(unit_session.RTU_US_CMDS.BUILD_CHANGED) else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end @@ -201,6 +203,13 @@ function imatrix.new(session_id, unit_id, advert, out_queue) self.session.post_update() end + -- invalidate build cache + function public.invalidate_cache() + self.periodics.next_formed_req = 0 + self.periodics.next_build_req = 0 + self.has_build = false + end + -- get the unit session database function public.get_db() return self.db end diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index 7ebd28b..35764ac 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -254,6 +254,11 @@ function redstone.new(session_id, unit_id, advert, out_queue) self.session.post_update() end + -- invalidate build cache + function public.invalidate_cache() + -- no build cache for this device + end + -- get the unit session database function public.get_db() return self.db end diff --git a/supervisor/session/rtu/sna.lua b/supervisor/session/rtu/sna.lua index 68f38ae..297708a 100644 --- a/supervisor/session/rtu/sna.lua +++ b/supervisor/session/rtu/sna.lua @@ -112,6 +112,8 @@ function sna.new(session_id, unit_id, advert, out_queue) self.db.build.input_cap = m_pkt.data[1] self.db.build.output_cap = m_pkt.data[2] self.has_build = true + + out_queue.push_command(unit_session.RTU_US_CMDS.BUILD_CHANGED) else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end @@ -167,6 +169,12 @@ function sna.new(session_id, unit_id, advert, out_queue) self.session.post_update() end + -- invalidate build cache + function public.invalidate_cache() + self.periodics.next_build_req = 0 + self.has_build = false + end + -- get the unit session database function public.get_db() return self.db end diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua index badad45..8b42bd1 100644 --- a/supervisor/session/rtu/sps.lua +++ b/supervisor/session/rtu/sps.lua @@ -147,6 +147,8 @@ function sps.new(session_id, unit_id, advert, out_queue) self.db.build.output_cap = m_pkt.data[8] self.db.build.max_energy = m_pkt.data[9] self.has_build = true + + out_queue.push_command(unit_session.RTU_US_CMDS.BUILD_CHANGED) else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end @@ -211,6 +213,13 @@ function sps.new(session_id, unit_id, advert, out_queue) self.session.post_update() end + -- invalidate build cache + function public.invalidate_cache() + self.periodics.next_formed_req = 0 + self.periodics.next_build_req = 0 + self.has_build = false + end + -- get the unit session database function public.get_db() return self.db end diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua index d30733c..2927fd2 100644 --- a/supervisor/session/rtu/turbinev.lua +++ b/supervisor/session/rtu/turbinev.lua @@ -198,6 +198,8 @@ function turbinev.new(session_id, unit_id, advert, out_queue) self.db.build.max_production = m_pkt.data[14] self.db.build.max_water_output = m_pkt.data[15] self.has_build = true + + out_queue.push_command(unit_session.RTU_US_CMDS.BUILD_CHANGED) else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end @@ -301,6 +303,13 @@ function turbinev.new(session_id, unit_id, advert, out_queue) self.session.post_update() end + -- invalidate build cache + function public.invalidate_cache() + self.periodics.next_formed_req = 0 + self.periodics.next_build_req = 0 + self.has_build = false + end + -- get the unit session database function public.get_db() return self.db end diff --git a/supervisor/session/rtu/unit_session.lua b/supervisor/session/rtu/unit_session.lua index 3a70f26..245461e 100644 --- a/supervisor/session/rtu/unit_session.lua +++ b/supervisor/session/rtu/unit_session.lua @@ -11,6 +11,16 @@ local PROTOCOLS = comms.PROTOCOLS local MODBUS_FCODE = types.MODBUS_FCODE local MODBUS_EXCODE = types.MODBUS_EXCODE +local RTU_US_CMDS = { + BUILD_CHANGED = 1 +} + +local RTU_US_DATA = { +} + +unit_session.RTU_US_CMDS = RTU_US_CMDS +unit_session.RTU_US_DATA = RTU_US_DATA + -- create a new unit session runner ---@param unit_id integer MODBUS unit ID ---@param advert rtu_advertisement RTU advertisement for this unit @@ -152,6 +162,11 @@ function unit_session.new(unit_id, advert, out_queue, log_tag, txn_tags) log.debug("template unit_session.update() called", true) end + -- invalidate build cache + function public.invalidate_cache() + log.debug("template unit_session.invalidate_cache() called", true) + end + -- get the unit session database function public.get_db() return {} end diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index b8dcc03..2ad30c4 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -429,13 +429,13 @@ function unit.new(for_reactor, num_boilers, num_turbines) build.boilers = {} for i = 1, #self.boilers do local boiler = self.boilers[i] ---@type unit_session - build.boilers[boiler.get_device_idx()] = { boiler.get_db().build, boiler.get_db().formed } + build.boilers[boiler.get_device_idx()] = { boiler.get_db().formed, boiler.get_db().build } end build.turbines = {} for i = 1, #self.turbines do local turbine = self.turbines[i] ---@type unit_session - build.turbines[turbine.get_device_idx()] = { turbine.get_db().build, turbine.get_db().formed } + build.turbines[turbine.get_device_idx()] = { turbine.get_db().formed, turbine.get_db().build } end return build @@ -461,14 +461,24 @@ function unit.new(for_reactor, num_boilers, num_turbines) status.boilers = {} for i = 1, #self.boilers do local boiler = self.boilers[i] ---@type unit_session - status.boilers[boiler.get_device_idx()] = { boiler.get_db().state, boiler.get_db().tanks } + status.boilers[boiler.get_device_idx()] = { + boiler.is_faulted(), + boiler.get_db().formed, + boiler.get_db().state, + boiler.get_db().tanks + } end -- status of turbines (including tanks) status.turbines = {} for i = 1, #self.turbines do local turbine = self.turbines[i] ---@type unit_session - status.turbines[turbine.get_device_idx()] = { turbine.get_db().state, turbine.get_db().tanks } + status.turbines[turbine.get_device_idx()] = { + turbine.is_faulted(), + turbine.get_db().formed, + turbine.get_db().state, + turbine.get_db().tanks + } end ---@todo other RTU statuses diff --git a/supervisor/startup.lua b/supervisor/startup.lua index afe2745..2459673 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.6.8" +local SUPERVISOR_VERSION = "beta-v0.7.0" local print = util.print local println = util.println From c221ffa1293f10e69606497632d21fc0997eac16 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 11 Nov 2022 15:45:46 -0500 Subject: [PATCH 431/587] #81 handle force disabled --- coordinator/iocontrol.lua | 2 ++ coordinator/startup.lua | 2 +- coordinator/ui/components/reactor.lua | 2 +- coordinator/ui/style.lua | 4 +++ reactor-plc/plc.lua | 42 +++++++++++++++++++++++++-- reactor-plc/startup.lua | 2 +- scada-common/types.lua | 7 ++++- supervisor/session/plc.lua | 11 ++++--- supervisor/startup.lua | 2 +- 9 files changed, 62 insertions(+), 12 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 4258bc1..8f461cc 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -207,6 +207,8 @@ function iocontrol.update_statuses(statuses) unit.reactor_ps.publish("computed_status", 5) -- faulted elseif not unit.reactor_data.formed then unit.reactor_ps.publish("computed_status", 6) -- multiblock not formed + elseif unit.reactor_data.rps_status.force_dis then + unit.reactor_ps.publish("computed_status", 7) -- reactor force disabled elseif unit.reactor_data.rps_tripped and unit.reactor_data.rps_trip_cause ~= "manual" then unit.reactor_ps.publish("computed_status", 4) -- SCRAM else diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 1c6b90b..86d49be 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.5" +local COORDINATOR_VERSION = "alpha-v0.6.6" local print = util.print local println = util.println diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua index e04a34e..8efe9ce 100644 --- a/coordinator/ui/components/reactor.lua +++ b/coordinator/ui/components/reactor.lua @@ -27,7 +27,7 @@ local function new_view(root, x, y, data, ps) local text_fg_bg = cpair(colors.black, colors.lightGray) local lu_col = cpair(colors.gray, colors.gray) - local status = StateIndicator{parent=reactor,x=8,y=1,states=style.reactor.states,value=1,min_width=14} + local status = StateIndicator{parent=reactor,x=6,y=1,states=style.reactor.states,value=1,min_width=16} local core_temp = DataIndicator{parent=reactor,x=2,y=3,lu_colors=lu_col,label="Core Temp:",unit="K",format="%10.2f",value=0,width=26,fg_bg=text_fg_bg} local burn_r = DataIndicator{parent=reactor,x=2,y=4,lu_colors=lu_col,label="Burn Rate:",unit="mB/t",format="%10.1f",value=0,width=26,fg_bg=text_fg_bg} local heating_r = DataIndicator{parent=reactor,x=2,y=5,lu_colors=lu_col,label="Heating:",unit="mB/t",format="%12.0f",value=0,commas=true,width=26,fg_bg=text_fg_bg} diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index ae83155..67453c7 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -58,6 +58,10 @@ style.reactor = { { color = cpair(colors.black, colors.orange), text = "NOT FORMED" + }, + { + color = cpair(colors.black, colors.red), + text = "FORCE DISABLED" } } } diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 578d0d8..cdc131e 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -51,14 +51,17 @@ function plc.rps_init(reactor, is_formed) fault = 7, timeout = 8, manual = 9, - sys_fail = 10 + automatic = 10, + sys_fail = 11, + force_disabled = 12 } local self = { reactor = reactor, - state = { false, false, false, false, false, false, false, false, false, false }, + state = { false, false, false, false, false, false, false, false, false, false, false, false }, reactor_enabled = false, formed = is_formed, + force_disabled = false, tripped = false, trip_cause = "" ---@type rps_trip_cause } @@ -95,6 +98,21 @@ function plc.rps_init(reactor, is_formed) end end + -- check if the reactor is force disabled + local function _is_force_disabled() + local disabled = self.reactor.isForceDisabled() + if disabled == ppm.ACCESS_FAULT then + -- lost the peripheral or terminated, handled later + _set_fault() + else + self.force_disabled = disabled + + if not self.state[state_keys.force_disabled] then + self.state[state_keys.force_disabled] = disabled + end + end + end + -- check for critical damage local function _damage_critical() local damage_percent = self.reactor.getDamagePercent() @@ -185,6 +203,11 @@ function plc.rps_init(reactor, is_formed) self.state[state_keys.manual] = true end + -- automatic SCRAM commanded by supervisor/coordinator + function public.trip_auto() + self.state[state_keys.automatic] = true + end + -- trip for unformed reactor function public.trip_sys_fail() self.state[state_keys.fault] = true @@ -237,6 +260,7 @@ function plc.rps_init(reactor, is_formed) -- update state parallel.waitForAll( _is_formed, + _is_force_disabled, _damage_critical, _high_temp, _no_coolant, @@ -255,6 +279,9 @@ function plc.rps_init(reactor, is_formed) elseif self.state[state_keys.sys_fail] then log.warning("RPS: system failure, reactor not formed") status = rps_status_t.sys_fail + elseif self.state[state_keys.force_disabled] then + log.warning("RPS: reactor was force disabled") + status = rps_status_t.force_disabled elseif self.state[state_keys.dmg_crit] then log.warning("RPS: damage critical") status = rps_status_t.dmg_crit @@ -282,6 +309,9 @@ function plc.rps_init(reactor, is_formed) elseif self.state[state_keys.manual] then log.warning("RPS: manual SCRAM requested") status = rps_status_t.manual + elseif self.state[state_keys.automatic] then + log.warning("RPS: automatic SCRAM requested") + status = rps_status_t.automatic else self.tripped = false end @@ -292,8 +322,13 @@ function plc.rps_init(reactor, is_formed) self.tripped = true self.trip_cause = status + -- in the case that the reactor is detected to be active, it will be scrammed shortly after this in the main RPS loop if we don't here if self.formed then - public.scram() + if not self.force_disabled then + public.scram() + else + log.warning("RPS: skipping SCRAM due to reactor being force disabled") + end else log.warning("RPS: skipping SCRAM due to not being formed") end @@ -306,6 +341,7 @@ function plc.rps_init(reactor, is_formed) function public.is_tripped() return self.tripped end function public.is_active() return self.reactor_enabled end function public.is_formed() return self.formed end + function public.is_force_disabled() return self.force_disabled end -- reset the RPS ---@param quiet? boolean true to suppress the info log message diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 02786f0..2d56bdd 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.2" +local R_PLC_VERSION = "beta-v0.9.3" local print = util.print local println = util.println diff --git a/scada-common/types.lua b/scada-common/types.lua index de49cf0..b86aca7 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -82,6 +82,9 @@ types.TRI_FAIL = { ---| "fault" ---| "timeout" ---| "manual" +---| "automatic" +---| "sys_fail" +---| "force_disabled" ---@alias rtu_t string types.rtu_t = { @@ -106,7 +109,9 @@ types.rps_status_t = { fault = "fault", timeout = "timeout", manual = "manual", - sys_fail = "sys_fail" + automatic = "automatic", + sys_fail = "sys_fail", + force_disabled = "force_disabled" } -- turbine steam dumping modes diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 4e72a68..6ee2115 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -103,7 +103,9 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) fault = false, timeout = false, manual = false, - sys_fail = false + automatic = false, + sys_fail = false, + force_dis = false }, ---@class mek_status mek_status = { @@ -134,7 +136,6 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) }, ---@class mek_struct mek_struct = { - formed = false, length = 0, width = 0, height = 0, @@ -167,7 +168,9 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) self.sDB.rps_status.fault = rps_status[7] self.sDB.rps_status.timeout = rps_status[8] self.sDB.rps_status.manual = rps_status[9] - self.sDB.rps_status.sys_fail = rps_status[10] + self.sDB.rps_status.automatic = rps_status[10] + self.sDB.rps_status.sys_fail = rps_status[11] + self.sDB.rps_status.force_dis = rps_status[12] end -- copy in the reactor status @@ -382,7 +385,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) }) elseif pkt.type == RPLC_TYPES.RPS_STATUS then -- RPS status packet received, copy data - if pkt.length == 10 then + if pkt.length == 12 then local status = pcall(_copy_rps_status, pkt.data) if status then -- copied in RPS status data OK diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 2459673..b84fecf 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.7.0" +local SUPERVISOR_VERSION = "beta-v0.7.1" local print = util.print local println = util.println From af57c3b1fc322b11fa8af69a6202b3a9eb8ee538 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 11 Nov 2022 16:15:44 -0500 Subject: [PATCH 432/587] automatic reactor scram functionality for future use --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 2 ++ reactor-plc/plc.lua | 7 ++++- reactor-plc/startup.lua | 2 +- scada-common/comms.lua | 11 +++++--- supervisor/session/plc.lua | 33 +++++++++++++++++++---- supervisor/session/unit.lua | 2 ++ supervisor/startup.lua | 2 +- 8 files changed, 48 insertions(+), 13 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 86d49be..847ee2d 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.6" +local COORDINATOR_VERSION = "alpha-v0.6.7" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 04fa8ee..b484578 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -111,6 +111,7 @@ local function init(parent, id) -- annunciator fields local r_scram = IndicatorLight{parent=annunciator,label="Reactor SCRAM",colors=cpair(colors.red,colors.gray)} local r_mscrm = IndicatorLight{parent=annunciator,label="Manual Reactor SCRAM",colors=cpair(colors.red,colors.gray)} + local r_ascrm = IndicatorLight{parent=annunciator,label="Auto Reactor SCRAM",colors=cpair(colors.red,colors.gray)} local r_rtrip = IndicatorLight{parent=annunciator,label="RCP Trip",colors=cpair(colors.red,colors.gray)} local r_cflow = IndicatorLight{parent=annunciator,label="RCS Flow Low",colors=cpair(colors.yellow,colors.gray)} local r_temp = IndicatorLight{parent=annunciator,label="Reactor Temp. High",colors=cpair(colors.red,colors.gray)} @@ -121,6 +122,7 @@ local function init(parent, id) r_ps.subscribe("ReactorSCRAM", r_scram.update) r_ps.subscribe("ManualReactorSCRAM", r_mscrm.update) + r_ps.subscribe("AutoReactorSCRAM", r_ascrm.update) r_ps.subscribe("RCPTrip", r_rtrip.update) r_ps.subscribe("RCSFlowLow", r_cflow.update) r_ps.subscribe("ReactorTempHigh", r_temp.update) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index cdc131e..c59de20 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -789,10 +789,15 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co self.scrammed = false _send_ack(packet.type, rps.activate()) elseif packet.type == RPLC_TYPES.RPS_SCRAM then - -- disable the reactor + -- disable the reactor per manual request self.scrammed = true rps.trip_manual() _send_ack(packet.type, true) + elseif packet.type == RPLC_TYPES.RPS_ASCRAM then + -- disable the reactor per automatic request + self.scrammed = true + rps.trip_auto() + _send_ack(packet.type, true) elseif packet.type == RPLC_TYPES.RPS_RESET then -- reset the RPS status rps.reset() diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 2d56bdd..6af916a 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.3" +local R_PLC_VERSION = "beta-v0.9.4" local print = util.print local println = util.println diff --git a/scada-common/comms.lua b/scada-common/comms.lua index e4df65c..696547e 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -9,6 +9,7 @@ local types = require("scada-common.types") local comms = {} local rtu_t = types.rtu_t + local insert = table.insert ---@alias PROTOCOLS integer @@ -27,10 +28,11 @@ local RPLC_TYPES = { MEK_STRUCT = 2, -- mekanism build structure MEK_BURN_RATE = 3, -- set burn rate RPS_ENABLE = 4, -- enable reactor - RPS_SCRAM = 5, -- SCRAM reactor - RPS_STATUS = 6, -- RPS status - RPS_ALARM = 7, -- RPS alarm broadcast - RPS_RESET = 8 -- clear RPS trip (if in bad state, will trip immediately) + RPS_SCRAM = 5, -- SCRAM reactor (manual request) + RPS_ASCRAM = 6, -- SCRAM reactor (automatic request) + RPS_STATUS = 7, -- RPS status + RPS_ALARM = 8, -- RPS alarm broadcast + RPS_RESET = 9 -- clear RPS trip (if in bad state, will trip immediately) } ---@alias RPLC_LINKING integer @@ -290,6 +292,7 @@ function comms.rplc_packet() self.type == RPLC_TYPES.MEK_BURN_RATE or self.type == RPLC_TYPES.RPS_ENABLE or self.type == RPLC_TYPES.RPS_SCRAM or + self.type == RPLC_TYPES.RPS_ASCRAM or self.type == RPLC_TYPES.RPS_ALARM or self.type == RPLC_TYPES.RPS_STATUS or self.type == RPLC_TYPES.RPS_RESET diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 6ee2115..b1e62a8 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -24,8 +24,9 @@ local RETRY_PERIOD = 1000 local PLC_S_CMDS = { SCRAM = 1, - ENABLE = 2, - RPS_RESET = 3 + ASCRAM = 2, + ENABLE = 3, + RPS_RESET = 4 } local PLC_S_DATA = { @@ -56,6 +57,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) commanded_state = false, commanded_burn_rate = 0.0, ramping_rate = false, + auto_scram = false, -- connection properties seq_num = 0, r_seq_num = nil, @@ -368,13 +370,13 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) ack = ack }) elseif pkt.type == RPLC_TYPES.RPS_SCRAM then - -- SCRAM acknowledgement + -- manual SCRAM acknowledgement local ack = _get_ack(pkt) if ack then self.acks.scram = true self.sDB.control_state = false elseif ack == false then - log.debug(log_header .. "SCRAM failed!") + log.debug(log_header .. "manual SCRAM failed!") end -- send acknowledgement to coordinator @@ -383,6 +385,15 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) cmd = CRDN_COMMANDS.SCRAM, ack = ack }) + elseif pkt.type == RPLC_TYPES.RPS_ASCRAM then + -- automatic SCRAM acknowledgement + local ack = _get_ack(pkt) + if ack then + self.acks.scram = true + self.sDB.control_state = false + elseif ack == false then + log.debug(log_header .. " automatic SCRAM failed!") + end elseif pkt.type == RPLC_TYPES.RPS_STATUS then -- RPS status packet received, copy data if pkt.length == 12 then @@ -540,9 +551,16 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) _send(RPLC_TYPES.RPS_ENABLE, {}) elseif cmd == PLC_S_CMDS.SCRAM then -- SCRAM reactor + self.auto_scram = false self.acks.scram = false self.retry_times.scram_req = util.time() + INITIAL_WAIT _send(RPLC_TYPES.RPS_SCRAM, {}) + elseif cmd == PLC_S_CMDS.ASCRAM then + -- SCRAM reactor + self.auto_scram = true + self.acks.scram = false + self.retry_times.scram_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.RPS_ASCRAM, {}) elseif cmd == PLC_S_CMDS.RPS_RESET then -- reset RPS self.acks.rps_reset = false @@ -647,7 +665,12 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) if not self.acks.scram then if rtimes.scram_req - util.time() <= 0 then - _send(RPLC_TYPES.RPS_SCRAM, {}) + if self.auto_scram then + _send(RPLC_TYPES.RPS_ASCRAM, {}) + else + _send(RPLC_TYPES.RPS_SCRAM, {}) + end + rtimes.scram_req = util.time() + RETRY_PERIOD end end diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 2ad30c4..467fb60 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -44,6 +44,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) PLCHeartbeat = false, -- alternate true/false to blink, each time there is a keep_alive ReactorSCRAM = false, ManualReactorSCRAM = false, + AutoReactorSCRAM = false, RCPTrip = false, RCSFlowLow = false, ReactorTempHigh = false, @@ -186,6 +187,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- update other annunciator fields self.db.annunciator.ReactorSCRAM = plc_db.rps_tripped self.db.annunciator.ManualReactorSCRAM = plc_db.rps_trip_cause == types.rps_status_t.manual + self.db.annunciator.AutoReactorSCRAM = plc_db.rps_trip_cause == types.rps_status_t.automatic self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.no_cool) self.db.annunciator.RCSFlowLow = plc_db.mek_status.ccool_fill < 0.75 or plc_db.mek_status.hcool_fill > 0.25 self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > 1000 diff --git a/supervisor/startup.lua b/supervisor/startup.lua index b84fecf..70d723a 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.7.1" +local SUPERVISOR_VERSION = "beta-v0.7.2" local print = util.print local println = util.println From ffeff86507b33617b64cdc7990cce05793ab0c06 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 11 Nov 2022 16:32:14 -0500 Subject: [PATCH 433/587] adjusted containment integrity to just be damage percent, moved up radiation indicator --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 20 ++++++++++++-------- graphics/elements/indicators/data.lua | 3 ++- graphics/elements/indicators/power.lua | 1 + 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 847ee2d..84d1f4b 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.7" +local COORDINATOR_VERSION = "alpha-v0.6.8" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index b484578..9f91932 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -55,28 +55,33 @@ local function init(parent, id) local stat_fg_bg = cpair(colors.black,colors.white) TextBox{parent=main,x=21,y=3,text="Core Temp",height=1,fg_bg=style.label} - local core_temp = DataIndicator{parent=main,x=21,label="",format="%9.2f",value=0,unit="K",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + local core_temp = DataIndicator{parent=main,x=21,label="",format="%10.2f",value=0,unit="K",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} r_ps.subscribe("temp", core_temp.update) main.line_break() TextBox{parent=main,x=21,text="Burn Rate",height=1,width=12,fg_bg=style.label} - local act_burn_r = DataIndicator{parent=main,x=21,label="",format="%6.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + local act_burn_r = DataIndicator{parent=main,x=21,label="",format="%7.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} r_ps.subscribe("act_burn_rate", act_burn_r.update) main.line_break() TextBox{parent=main,x=21,text="Commanded Burn Rate",height=2,width=12,fg_bg=style.label} - local burn_r = DataIndicator{parent=main,x=21,label="",format="%6.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + local burn_r = DataIndicator{parent=main,x=21,label="",format="%7.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} r_ps.subscribe("burn_rate", burn_r.update) main.line_break() TextBox{parent=main,x=21,text="Heating Rate",height=1,width=12,fg_bg=style.label} - local heating_r = DataIndicator{parent=main,x=21,label="",format="%11.0f",value=0,unit="",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + local heating_r = DataIndicator{parent=main,x=21,label="",format="%12.0f",value=0,unit="",commas=true,lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} r_ps.subscribe("heating_rate", heating_r.update) main.line_break() - TextBox{parent=main,x=21,text="Containment Integrity",height=2,width=12,fg_bg=style.label} - local integ = DataIndicator{parent=main,x=21,label="",format="%9.0f",value=100,unit="%",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} - r_ps.subscribe("damage", function (x) integ.update(100.0 - x) end) + TextBox{parent=main,x=21,text="Damage",height=1,width=12,fg_bg=style.label} + local damage_p = DataIndicator{parent=main,x=21,label="",format="%10.0f",value=100,unit="%",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + r_ps.subscribe("damage", damage_p.update) + main.line_break() + + ---@todo radiation monitor + TextBox{parent=main,x=21,text="Radiation",height=1,width=12,fg_bg=style.label} + DataIndicator{parent=main,x=21,label="",format="%6.2f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} main.line_break() -- TextBox{parent=main,text="FL",x=21,y=19,height=1,width=2,fg_bg=style.label} @@ -241,7 +246,6 @@ local function init(parent, id) ---@todo radiation monitor IndicatorLight{parent=annunciator,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)} IndicatorLight{parent=annunciator,label="Radiation Alarm",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - DataIndicator{parent=main,x=22,y=22,label="",format="%3.2f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=11,fg_bg=stat_fg_bg} -- reactor controls -- diff --git a/graphics/elements/indicators/data.lua b/graphics/elements/indicators/data.lua index 5b5a35b..5ebc9cb 100644 --- a/graphics/elements/indicators/data.lua +++ b/graphics/elements/indicators/data.lua @@ -8,7 +8,7 @@ local element = require("graphics.element") ---@field label string indicator label ---@field unit? string indicator unit ---@field format string data format (lua string format) ----@field commas boolean whether to use commas if a number is given (default to false) +---@field commas? boolean whether to use commas if a number is given (default to false) ---@field lu_colors? cpair label foreground color (a), unit foreground color (b) ---@field value any default value ---@field parent graphics_element @@ -43,6 +43,7 @@ local function data(args) e.window.write(args.label) local data_start = string.len(args.label) + 2 + if string.len(args.label) == 0 then data_start = 1 end -- on state change ---@param value any new value diff --git a/graphics/elements/indicators/power.lua b/graphics/elements/indicators/power.lua index 70d6479..6c7282b 100644 --- a/graphics/elements/indicators/power.lua +++ b/graphics/elements/indicators/power.lua @@ -39,6 +39,7 @@ local function power(args) e.window.write(args.label) local data_start = string.len(args.label) + 2 + if string.len(args.label) == 0 then data_start = 1 end -- on state change ---@param value any new value From 8b65bf4852bf702119bfed573a4fc7e77d73cc06 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 11 Nov 2022 16:46:38 -0500 Subject: [PATCH 434/587] fixed rps alarm packet length check --- supervisor/session/plc.lua | 2 +- supervisor/startup.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index b1e62a8..473e5b1 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -409,7 +409,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) end elseif pkt.type == RPLC_TYPES.RPS_ALARM then -- RPS alarm - if pkt.length == 11 then + if pkt.length == 13 then self.sDB.rps_tripped = true self.sDB.rps_trip_cause = pkt.data[1] local status = pcall(_copy_rps_status, { table.unpack(pkt.data, 2, pkt.length) }) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 70d723a..0f1a4be 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.7.2" +local SUPERVISOR_VERSION = "beta-v0.7.3" local print = util.print local println = util.println From 8e28dbf2a615288ba440cd6f739c42273433e0b6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 11 Nov 2022 16:59:28 -0500 Subject: [PATCH 435/587] #120 fixed steam dump indicator, fixed index tags --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 84d1f4b..3792eb7 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.8" +local COORDINATOR_VERSION = "alpha-v0.6.9" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 9f91932..5568611 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -180,7 +180,7 @@ local function init(parent, id) -- machine-specific indicators if unit.num_boilers > 0 then - TextBox{parent=main,x=32,y=35,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=main,x=32,y=36,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local b1_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} b_ps[1].subscribe("HeatingRateLow", b1_hr.update) end @@ -197,7 +197,7 @@ local function init(parent, id) TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local t1_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} - t_ps[1].subscribe("SteamDumpOpen", t1_sdo.update) + t_ps[1].subscribe("SteamDumpOpen", function (val) t1_sdo.update(val + 1) end) TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local t1_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} @@ -213,7 +213,7 @@ local function init(parent, id) if unit.num_turbines > 1 then TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local t2_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} - t_ps[2].subscribe("SteamDumpOpen", t2_sdo.update) + t_ps[2].subscribe("SteamDumpOpen", function (val) t2_sdo.update(val + 1) end) TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local t2_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} @@ -230,7 +230,7 @@ local function init(parent, id) if unit.num_turbines > 2 then TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local t3_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} - t_ps[3].subscribe("SteamDumpOpen", t3_sdo.update) + t_ps[3].subscribe("SteamDumpOpen", function (val) t3_sdo.update(val + 1) end) TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} local t3_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} From f940c136bf9a0f9e4e0cc178a08c2fdf538dbb82 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 11 Nov 2022 23:49:45 -0500 Subject: [PATCH 436/587] fixes to rtu modbus --- rtu/modbus.lua | 32 ++++++++++++++++---------------- rtu/startup.lua | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/rtu/modbus.lua b/rtu/modbus.lua index 802c2dc..fea8e45 100644 --- a/rtu/modbus.lua +++ b/rtu/modbus.lua @@ -56,11 +56,11 @@ function modbus.new(rtu_dev, use_parallel_read) -- run parallel tasks if configured if self.use_parallel then parallel.waitForAll(table.unpack(tasks)) + end - if access_fault then - return_ok = false - readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL - end + if access_fault or #readings ~= count then + return_ok = false + readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL end else readings = MODBUS_EXCODE.ILLEGAL_DATA_ADDR @@ -105,11 +105,11 @@ function modbus.new(rtu_dev, use_parallel_read) -- run parallel tasks if configured if self.use_parallel then parallel.waitForAll(table.unpack(tasks)) + end - if access_fault then - return_ok = false - readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL - end + if access_fault or #readings ~= count then + return_ok = false + readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL end else readings = MODBUS_EXCODE.ILLEGAL_DATA_ADDR @@ -154,11 +154,11 @@ function modbus.new(rtu_dev, use_parallel_read) -- run parallel tasks if configured if self.use_parallel then parallel.waitForAll(table.unpack(tasks)) + end - if access_fault then - return_ok = false - readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL - end + if access_fault or #readings ~= count then + return_ok = false + readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL end else readings = MODBUS_EXCODE.ILLEGAL_DATA_ADDR @@ -203,11 +203,11 @@ function modbus.new(rtu_dev, use_parallel_read) -- run parallel tasks if configured if self.use_parallel then parallel.waitForAll(table.unpack(tasks)) + end - if access_fault then - return_ok = false - readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL - end + if access_fault or #readings ~= count then + return_ok = false + readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL end else readings = MODBUS_EXCODE.ILLEGAL_DATA_ADDR diff --git a/rtu/startup.lua b/rtu/startup.lua index 6ce9ffe..12a166d 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -24,7 +24,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.9.0" +local RTU_VERSION = "beta-v0.9.1" local rtu_t = types.rtu_t From 1a01bec7e41daea02b5a248754fc0a55b9f637ee Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 12 Nov 2022 01:35:31 -0500 Subject: [PATCH 437/587] #123 RTU startup without devices, fixed repeat RTU advert handling, added PPM virtual devices, fixed log out of space detection, updated RTU type conversion functions in comms --- coordinator/startup.lua | 2 +- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 172 ++++++++++++------------ rtu/threads.lua | 44 +++++- scada-common/comms.lua | 12 ++ scada-common/log.lua | 11 +- scada-common/ppm.lua | 26 +++- supervisor/session/rtu.lua | 16 ++- supervisor/session/rtu/boilerv.lua | 2 +- supervisor/session/rtu/envd.lua | 2 +- supervisor/session/rtu/imatrix.lua | 2 +- supervisor/session/rtu/redstone.lua | 2 +- supervisor/session/rtu/sna.lua | 2 +- supervisor/session/rtu/sps.lua | 2 +- supervisor/session/rtu/turbinev.lua | 2 +- supervisor/session/rtu/unit_session.lua | 5 +- supervisor/session/unit.lua | 8 ++ supervisor/startup.lua | 2 +- 18 files changed, 205 insertions(+), 109 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 3792eb7..fa3576f 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.9" +local COORDINATOR_VERSION = "alpha-v0.6.10" local print = util.print local println = util.println diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 6af916a..785d668 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.4" +local R_PLC_VERSION = "beta-v0.9.5" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index 12a166d..e50c93f 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -24,7 +24,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.9.1" +local RTU_VERSION = "beta-v0.9.2" local rtu_t = types.rtu_t @@ -257,98 +257,104 @@ local function configure() 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") + local message = util.c("configure> '", name, "' not found, using placeholder") println(message) - log.fatal(message) - return false + log.warning(message) + + -- mount a virtual (placeholder) device + type, device = ppm.mount_virtual() else - local type = ppm.get_type(name) - local rtu_iface = nil ---@type rtu_device - local rtu_type = "" - local formed = nil ---@type boolean|nil + 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 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 - 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) - else - local message = util.c("configure> device '", name, "' is not a known type (", type, ")") - println_ts(message) - log.fatal(message) + 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 + elseif type == "turbineValve" then + -- turbine multiblock + rtu_type = rtu_t.turbine_valve + rtu_iface = turbinev_rtu.new(device) + formed = device.isFormed() - if rtu_iface ~= nil then - ---@class rtu_unit_registry_entry - local rtu_unit = { - name = name, - type = rtu_type, - 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)) + 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 + + ---@class rtu_unit_registry_entry + local rtu_unit = { + name = name, + type = rtu_type, + 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 -- we made it through all that trusting-user-to-write-a-config-file chaos diff --git a/rtu/threads.lua b/rtu/threads.lua index e3d4cd7..fd08759 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -122,9 +122,37 @@ function threads.thread__main(smem) -- find disconnected device to reconnect -- note: cannot check isFormed as that would yield this coroutine and consume events if unit.name == param1 then + local resend_advert = false + -- found, re-link unit.device = device + if unit.type == "virtual" then + resend_advert = true + if type == "boilerValve" then + -- boiler multiblock + unit.type = rtu_t.boiler_valve + elseif type == "turbineValve" then + -- turbine multiblock + unit.type = rtu_t.turbine_valve + elseif type == "inductionPort" then + -- induction matrix multiblock + unit.type = rtu_t.induction_matrix + elseif type == "spsPort" then + -- SPS multiblock + unit.type = rtu_t.sps + elseif type == "solarNeutronActivator" then + -- SNA + unit.type = rtu_t.sna + elseif type == "environmentDetector" then + -- advanced peripherals environment detector + unit.type = rtu_t.env_detector + else + resend_advert = false + log.error(util.c("virtual device '", unit.name, "' cannot init to an unknown type (", type, ")")) + end + end + if unit.type == rtu_t.boiler_valve then unit.rtu = boilerv_rtu.new(device) unit.formed = true @@ -142,14 +170,19 @@ function threads.thread__main(smem) elseif unit.type == rtu_t.env_detector then unit.rtu = envd_rtu.new(device) else - log.error(util.c("unreachable case occured trying to identify reconnected RTU unit type (", unit.name, ")"), true) + log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true) end unit.modbus_io = modbus.new(unit.rtu, true) - rtu_comms.send_remounted(unit.index) - println_ts("reconnected the " .. unit.type .. " on interface " .. unit.name) + log.info("reconnected the " .. unit.type .. " on interface " .. unit.name) + + if resend_advert then + rtu_comms.send_advertisement(units) + else + rtu_comms.send_remounted(unit.index) + end end end end @@ -274,7 +307,6 @@ function threads.thread__unit_comms(smem, unit) local last_update = util.time() - local check_formed = type(unit.formed) == "boolean" local last_f_check = 0 local detail_name = util.c(unit.type, " (", unit.name, ") [", unit.index, "] for reactor ", unit.reactor) @@ -308,8 +340,8 @@ function threads.thread__unit_comms(smem, unit) end - -- check if multiblocks is still formed - if check_formed and (util.time() - last_f_check > 1000) then + -- check if multiblock is still formed if this is a multiblock + if (type(unit.formed) == "boolean") and (util.time() - last_f_check > 1000) then if (not unit.formed) and unit.device.isFormed() then -- newly re-formed local iface = ppm.get_iface(unit.device) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 696547e..16092ee 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -642,6 +642,12 @@ function comms.rtu_t_to_unit_type(type) return RTU_UNIT_TYPES.TURBINE_VALVE elseif type == rtu_t.induction_matrix then return RTU_UNIT_TYPES.IMATRIX + elseif type == rtu_t.sps then + return RTU_UNIT_TYPES.SPS + elseif type == rtu_t.sna then + return RTU_UNIT_TYPES.SNA + elseif type == rtu_t.env_detector then + return RTU_UNIT_TYPES.ENV_DETECTOR end return nil @@ -659,6 +665,12 @@ function comms.advert_type_to_rtu_t(utype) return rtu_t.turbine_valve elseif utype == RTU_UNIT_TYPES.IMATRIX then return rtu_t.induction_matrix + elseif utype == RTU_UNIT_TYPES.SPS then + return rtu_t.sps + elseif utype == RTU_UNIT_TYPES.SNA then + return rtu_t.sna + elseif utype == RTU_UNIT_TYPES.ENV_DETECTOR then + return rtu_t.env_detector end return nil diff --git a/scada-common/log.lua b/scada-common/log.lua index dadba40..1a44356 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -58,6 +58,7 @@ end -- private log write function ---@param msg string local function _log(msg) + local out_of_space = false local time_stamp = os.date("[%c] ") local stamped = time_stamp .. util.strval(msg) @@ -69,15 +70,17 @@ local function _log(msg) -- if we don't have space, we need to create a new log file - if not status then - if result == "Out of space" then + if (not status) and (result ~= nil) then + out_of_space = string.find(result, "Out of space") ~= nil + + if out_of_space then -- will delete log file - elseif result ~= nil then + else util.println("unknown error writing to logfile: " .. result) end end - if (result == "Out of space") or (free_space(_log_sys.path) < 100) then + if out_of_space or (free_space(_log_sys.path) < 100) then -- delete the old log file and open a new one _log_sys.file.close() fs.delete(_log_sys.path) diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index d7753cd..f69de1e 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -12,8 +12,11 @@ local ACCESS_FAULT = nil ---@type nil local UNDEFINED_FIELD = "undefined field" +local VIRTUAL_DEVICE_TYPE = "ppm_vdev" + ppm.ACCESS_FAULT = ACCESS_FAULT ppm.UNDEFINED_FIELD = UNDEFINED_FIELD +ppm.VIRTUAL_DEVICE_TYPE = VIRTUAL_DEVICE_TYPE ---------------------------- -- PRIVATE DATA/FUNCTIONS -- @@ -23,6 +26,7 @@ local REPORT_FREQUENCY = 20 -- log every 20 faults per function local _ppm_sys = { mounts = {}, + next_vid = 0, auto_cf = false, faulted = false, last_fault = "", @@ -42,10 +46,15 @@ local function peri_init(iface) last_fault = "", fault_counts = {}, auto_cf = true, - type = peripheral.getType(iface), - device = peripheral.wrap(iface) + type = VIRTUAL_DEVICE_TYPE, + device = {} } + if iface ~= "__virtual__" then + self.type = peripheral.getType(iface) + self.device = peripheral.wrap(iface) + end + -- initialization process (re-map) for key, func in pairs(self.device) do @@ -245,6 +254,19 @@ function ppm.mount(iface) return pm_type, pm_dev end +-- mount a virtual, placeholder device (specifically designed for RTU startup with missing devices) +---@return string type, table device +function ppm.mount_virtual() + local iface = "ppm_vdev_" .. _ppm_sys.next_vid + + _ppm_sys.mounts[iface] = peri_init("__virtual__") + _ppm_sys.next_vid = _ppm_sys.next_vid + 1 + + log.info(util.c("PPM: mount_virtual() -> allocated new virtual device ", iface)) + + return _ppm_sys.mounts[iface].type, _ppm_sys.mounts[iface].dev +end + -- manually unmount a peripheral from the PPM ---@param device table device table function ppm.unmount(device) diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index ec48bc0..b520ddc 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -89,8 +89,12 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) -- parse the recorded advertisement and create unit sub-sessions local function _handle_advertisement() - self.units = {} - self.rs_io_q = {} + _reset_config() + + for i = 1, #self.f_units do + local unit = self.f_units[i] ---@type reactor_unit + unit.purge_rtu_devices(self.id) + end for i = 1, #self.advert do local unit = nil ---@type unit_session|nil @@ -130,6 +134,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) if u_type == false then -- validation fail + log.debug(log_header .. "advertisement unit validation failure") else local target_unit = self.f_units[unit_advert.reactor] ---@type reactor_unit @@ -285,8 +290,13 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) _close() elseif pkt.type == SCADA_MGMT_TYPES.RTU_ADVERT then -- RTU unit advertisement - -- handle advertisement; this will re-create all unit sub-sessions + log.debug(log_header .. "received updated advertisement") + + -- copy advertisement and remove version tag self.advert = pkt.data + table.remove(self.advert, 1) + + -- handle advertisement; this will re-create all unit sub-sessions _handle_advertisement() elseif pkt.type == SCADA_MGMT_TYPES.RTU_DEV_REMOUNT then if pkt.length == 1 then diff --git a/supervisor/session/rtu/boilerv.lua b/supervisor/session/rtu/boilerv.lua index 8b5b02e..c0d9eb3 100644 --- a/supervisor/session/rtu/boilerv.lua +++ b/supervisor/session/rtu/boilerv.lua @@ -46,7 +46,7 @@ function boilerv.new(session_id, unit_id, advert, out_queue) local log_tag = "session.rtu(" .. session_id .. ").boilerv(" .. advert.index .. "): " local self = { - session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), + session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), has_build = false, periodics = { next_formed_req = 0, diff --git a/supervisor/session/rtu/envd.lua b/supervisor/session/rtu/envd.lua index a5546e4..4148b7d 100644 --- a/supervisor/session/rtu/envd.lua +++ b/supervisor/session/rtu/envd.lua @@ -37,7 +37,7 @@ function envd.new(session_id, unit_id, advert, out_queue) local log_tag = "session.rtu(" .. session_id .. ").envd(" .. advert.index .. "): " local self = { - session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), + session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), periodics = { next_rad_req = 0 }, diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua index 3ebc7ab..ab5704c 100644 --- a/supervisor/session/rtu/imatrix.lua +++ b/supervisor/session/rtu/imatrix.lua @@ -46,7 +46,7 @@ function imatrix.new(session_id, unit_id, advert, out_queue) local log_tag = "session.rtu(" .. session_id .. ").imatrix(" .. advert.index .. "): " local self = { - session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), + session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), has_build = false, periodics = { next_formed_req = 0, diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index 35764ac..90e4b58 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -61,7 +61,7 @@ function redstone.new(session_id, unit_id, advert, out_queue) local log_tag = "session.rtu(" .. session_id .. ").redstone(" .. unit_id .. "): " local self = { - session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), + session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), in_q = mqueue.new(), has_di = false, has_ai = false, diff --git a/supervisor/session/rtu/sna.lua b/supervisor/session/rtu/sna.lua index 297708a..40014d3 100644 --- a/supervisor/session/rtu/sna.lua +++ b/supervisor/session/rtu/sna.lua @@ -43,7 +43,7 @@ function sna.new(session_id, unit_id, advert, out_queue) local log_tag = "session.rtu(" .. session_id .. ").sna(" .. advert.index .. "): " local self = { - session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), + session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), has_build = false, periodics = { next_build_req = 0, diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua index 8b42bd1..f389049 100644 --- a/supervisor/session/rtu/sps.lua +++ b/supervisor/session/rtu/sps.lua @@ -46,7 +46,7 @@ function sps.new(session_id, unit_id, advert, out_queue) local log_tag = "session.rtu(" .. session_id .. ").sps(" .. advert.index .. "): " local self = { - session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), + session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), has_build = false, periodics = { next_formed_req = 0, diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua index 2927fd2..ad3fea7 100644 --- a/supervisor/session/rtu/turbinev.lua +++ b/supervisor/session/rtu/turbinev.lua @@ -66,7 +66,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue) local log_tag = "session.rtu(" .. session_id .. ").turbinev(" .. advert.index .. "): " local self = { - session = unit_session.new(unit_id, advert, out_queue, log_tag, TXN_TAGS), + session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), in_q = mqueue.new(), has_build = false, periodics = { diff --git a/supervisor/session/rtu/unit_session.lua b/supervisor/session/rtu/unit_session.lua index 245461e..a3f27f2 100644 --- a/supervisor/session/rtu/unit_session.lua +++ b/supervisor/session/rtu/unit_session.lua @@ -22,12 +22,13 @@ unit_session.RTU_US_CMDS = RTU_US_CMDS unit_session.RTU_US_DATA = RTU_US_DATA -- create a new unit session runner +---@param session_id integer RTU session ID ---@param unit_id integer MODBUS unit ID ---@param advert rtu_advertisement RTU advertisement for this unit ---@param out_queue mqueue send queue ---@param log_tag string logging tag ---@param txn_tags table transaction log tags -function unit_session.new(unit_id, advert, out_queue, log_tag, txn_tags) +function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_tags) local self = { log_tag = log_tag, txn_tags = txn_tags, @@ -132,6 +133,8 @@ function unit_session.new(unit_id, advert, out_queue, log_tag, txn_tags) -- PUBLIC FUNCTIONS -- + -- get the unit ID + function public.get_session_id() return session_id end -- get the unit ID function public.get_unit_id() return self.unit_id end -- get the device index diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 467fb60..86df0dd 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -401,6 +401,14 @@ function unit.new(for_reactor, num_boilers, num_turbines) table.insert(self.redstone[field], accessor) end + -- purge devices associated with the given RTU session ID + ---@param session integer RTU session ID + function public.purge_rtu_devices(session) + util.filter_table(self.turbines, function (s) return s.get_session_id() ~= session end) + util.filter_table(self.boilers, function (s) return s.get_session_id() ~= session end) + util.filter_table(self.redstone, function (s) return s.get_session_id() ~= session end) + end + -- UPDATE SESSION -- -- update (iterate) this unit diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 0f1a4be..6a3abea 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.7.3" +local SUPERVISOR_VERSION = "beta-v0.7.4" local print = util.print local println = util.println From e679b5a25aafa4676af4801c1ce323caa24edd81 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 13 Nov 2022 14:13:30 -0500 Subject: [PATCH 438/587] #122 versioned comms protocol with unified establish protocol --- coordinator/coordinator.lua | 188 +++++++++++++++------------ coordinator/startup.lua | 2 +- reactor-plc/plc.lua | 146 ++++++++++----------- reactor-plc/startup.lua | 2 +- rtu/rtu.lua | 206 +++++++++++++++++------------ rtu/startup.lua | 2 +- rtu/threads.lua | 10 +- scada-common/comms.lua | 76 ++++++----- supervisor/session/svsessions.lua | 6 +- supervisor/startup.lua | 2 +- supervisor/supervisor.lua | 207 ++++++++++++++++-------------- 11 files changed, 463 insertions(+), 384 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 05b8443..2a211cb 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -16,6 +16,8 @@ local print_ts = util.print_ts local println_ts = util.println_ts local PROTOCOLS = comms.PROTOCOLS +local DEVICE_TYPES = comms.DEVICE_TYPES +local ESTABLISH_ACK = comms.ESTABLISH_ACK local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES local CRDN_COMMANDS = comms.CRDN_COMMANDS @@ -236,7 +238,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa -- attempt connection establishment local function _send_establish() - _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.ESTABLISH, { version }) + _send_sv(PROTOCOLS.SCADA_MGMT, SCADA_MGMT_TYPES.ESTABLISH, { comms.version, version, DEVICE_TYPES.CRDN }) end -- keep alive ack @@ -288,7 +290,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa elseif event == "modem_message" then -- handle message local packet = public.parse_packet(p1, p2, p3, p4, p5) - if packet ~= nil and packet.type == SCADA_CRDN_TYPES.ESTABLISH then + if packet ~= nil and packet.type == SCADA_MGMT_TYPES.ESTABLISH then public.handle_packet(packet) end elseif event == "terminate" then @@ -385,106 +387,124 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa -- handle packet if protocol == PROTOCOLS.SCADA_CRDN then - if packet.type == SCADA_CRDN_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 io controller - iocontrol.init(conf, public) - - self.sv_linked = true + if self.sv_linked then + if packet.type == SCADA_CRDN_TYPES.STRUCT_BUILDS then + -- record builds + if iocontrol.record_builds(packet.data) then + -- acknowledge receipt of builds + _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.STRUCT_BUILDS, {}) else - log.debug("supervisor conn establish packet length mismatch") + log.error("received invalid SCADA_CRDN build packet") end - elseif packet.length == 1 and packet.data[1] == false then - log.debug("supervisor connection denied") - else - log.debug("supervisor conn establish packet length mismatch") - end - elseif packet.type == SCADA_CRDN_TYPES.STRUCT_BUILDS then - -- record builds - if iocontrol.record_builds(packet.data) then - -- acknowledge receipt of builds - _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.STRUCT_BUILDS, {}) - else - log.error("received invalid build packet") - end - elseif packet.type == SCADA_CRDN_TYPES.UNIT_STATUSES then - -- update statuses - if not iocontrol.update_statuses(packet.data) then - log.error("received invalid unit statuses packet") - end - elseif packet.type == SCADA_CRDN_TYPES.COMMAND_UNIT then - -- unit command acknowledgement - if packet.length == 3 then - local cmd = packet.data[1] - local unit_id = packet.data[2] - local ack = packet.data[3] + elseif packet.type == SCADA_CRDN_TYPES.UNIT_STATUSES then + -- update statuses + if not iocontrol.update_statuses(packet.data) then + log.error("received invalid SCADA_CRDN unit statuses packet") + end + elseif packet.type == SCADA_CRDN_TYPES.COMMAND_UNIT then + -- unit command acknowledgement + if packet.length == 3 then + local cmd = packet.data[1] + local unit_id = packet.data[2] + local ack = packet.data[3] - local unit = iocontrol.get_db().units[unit_id] ---@type ioctl_entry + local unit = iocontrol.get_db().units[unit_id] ---@type ioctl_entry - if unit ~= nil then - if cmd == CRDN_COMMANDS.SCRAM then - unit.scram_ack(ack) - elseif cmd == CRDN_COMMANDS.START then - unit.start_ack(ack) - elseif cmd == CRDN_COMMANDS.RESET_RPS then - unit.reset_rps_ack(ack) - elseif cmd == CRDN_COMMANDS.SET_BURN then - unit.set_burn_ack(ack) - elseif cmd == CRDN_COMMANDS.SET_WASTE then - unit.set_waste_ack(ack) + if unit ~= nil then + if cmd == CRDN_COMMANDS.SCRAM then + unit.scram_ack(ack) + elseif cmd == CRDN_COMMANDS.START then + unit.start_ack(ack) + elseif cmd == CRDN_COMMANDS.RESET_RPS then + unit.reset_rps_ack(ack) + elseif cmd == CRDN_COMMANDS.SET_BURN then + unit.set_burn_ack(ack) + elseif cmd == CRDN_COMMANDS.SET_WASTE then + unit.set_waste_ack(ack) + else + log.debug(util.c("received command ack with unknown command ", cmd)) + end else - log.debug(util.c("received command ack with unknown command ", cmd)) + log.debug(util.c("received command ack with unknown unit ", unit_id)) end else - log.debug(util.c("received command ack with unknown unit ", unit_id)) + log.debug("SCADA_CRDN unit command ack packet length mismatch") end + elseif packet.type == SCADA_CRDN_TYPES.ALARM then + ---@todo alarm/architecture handling else - log.debug("unit command ack packet length mismatch") + log.warning("received unknown SCADA_CRDN packet type " .. packet.type) end - elseif packet.type == SCADA_CRDN_TYPES.ALARM then else - log.warning("received unknown SCADA_CRDN packet type " .. packet.type) + log.debug("discarding SCADA_CRDN packet before linked") end elseif protocol == PROTOCOLS.SCADA_MGMT then - if packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then - -- keep alive request received, echo back - if packet.length == 1 then - local timestamp = packet.data[1] - local trip_time = util.time() - timestamp + if packet.type == SCADA_MGMT_TYPES.ESTABLISH then + -- connection with supervisor established + if packet.length == 2 then + local est_ack = packet.data[1] + local config = packet.data[2] - if trip_time > 500 then - log.warning("coord KEEP_ALIVE trip time > 500ms (" .. trip_time .. "ms)") + if est_ack == ESTABLISH_ACK.ALLOW then + if type(config) == "table" and #config > 1 then + -- get configuration + + ---@class facility_conf + local conf = { + num_units = config[1], + defs = {} -- boilers and turbines + } + + if (#config - 1) == (conf.num_units * 2) then + -- record sequence of pairs of [#boilers, #turbines] per unit + for i = 2, #config do + table.insert(conf.defs, config[i]) + end + + -- init io controller + iocontrol.init(conf, public) + + self.sv_linked = true + else + log.error("invalid supervisor configuration definitions received, establish failed") + end + else + log.error("invalid supervisor configuration table received, establish failed") + end + else + log.debug("supervisor connection denied") end - - -- log.debug("coord RTT = " .. trip_time .. "ms") - - _send_keep_alive_ack(timestamp) else - log.debug("SCADA keep alive packet length mismatch") + log.debug("SCADA_MGMT establish packet length mismatch") + end + elseif self.sv_linked then + if packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + -- keep alive request received, echo back + if packet.length == 1 then + local timestamp = packet.data[1] + local trip_time = util.time() - timestamp + + if trip_time > 500 then + log.warning("coord KEEP_ALIVE trip time > 500ms (" .. trip_time .. "ms)") + end + + -- log.debug("coord RTT = " .. trip_time .. "ms") + + _send_keep_alive_ack(timestamp) + else + log.debug("SCADA keep alive packet length mismatch") + end + elseif packet.type == SCADA_MGMT_TYPES.CLOSE then + -- handle session close + sv_watchdog.cancel() + self.sv_linked = false + println_ts("server connection closed by remote host") + log.warning("server connection closed by remote host") + else + log.warning("received unknown SCADA_MGMT packet type " .. packet.type) end - elseif packet.type == SCADA_MGMT_TYPES.CLOSE then - -- handle session close - sv_watchdog.cancel() - self.sv_linked = false - println_ts("server connection closed by remote host") - log.warning("server connection closed by remote host") else - log.warning("received unknown SCADA_MGMT packet type " .. packet.type) + log.debug("discarding non-link SCADA_MGMT packet before linked") end else log.debug("illegal packet type " .. protocol .. " on supervisor listening channel", true) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index fa3576f..e7028e7 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.10" +local COORDINATOR_VERSION = "alpha-v0.6.11" local print = util.print local println = util.println diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index c59de20..70afdfd 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -9,8 +9,9 @@ local plc = {} local rps_status_t = types.rps_status_t local PROTOCOLS = comms.PROTOCOLS +local DEVICE_TYPES = comms.DEVICE_TYPES +local ESTABLISH_ACK = comms.ESTABLISH_ACK local RPLC_TYPES = comms.RPLC_TYPES -local RPLC_LINKING = comms.RPLC_LINKING local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local print = util.print @@ -600,7 +601,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co -- attempt to establish link with supervisor function public.send_link_req() - _send(RPLC_TYPES.LINK_REQ, { id, version }) + _send_mgmt(SCADA_MGMT_TYPES.ESTABLISH, { comms.version, version, DEVICE_TYPES.PLC, id }) end -- send live status information @@ -716,34 +717,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co -- handle packet if protocol == PROTOCOLS.RPLC then if self.linked then - if packet.type == RPLC_TYPES.LINK_REQ then - -- link request confirmation - if packet.length == 1 then - log.debug("received unsolicited link request response") - - local link_ack = packet.data[1] - - if link_ack == RPLC_LINKING.ALLOW then - self.status_cache = nil - _send_struct() - public.send_status(plc_state.no_reactor, plc_state.reactor_formed) - log.debug("re-sent initial status data") - elseif link_ack == RPLC_LINKING.DENY then - println_ts("received unsolicited link denial, unlinking") - log.debug("unsolicited RPLC link request denied") - elseif link_ack == RPLC_LINKING.COLLISION then - println_ts("received unsolicited link collision, unlinking") - log.warning("unsolicited RPLC link request collision") - else - println_ts("invalid unsolicited link response") - log.error("unsolicited unknown RPLC link request response") - end - - self.linked = link_ack == RPLC_LINKING.ALLOW - else - log.debug("RPLC link req packet length mismatch") - end - elseif packet.type == RPLC_TYPES.STATUS then + if packet.type == RPLC_TYPES.STATUS then -- request of full status, clear cache first self.status_cache = nil public.send_status(plc_state.no_reactor, plc_state.reactor_formed) @@ -805,69 +779,97 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co else log.warning("received unknown RPLC packet type " .. packet.type) end - elseif packet.type == RPLC_TYPES.LINK_REQ then + else + log.debug("discarding RPLC packet before linked") + end + elseif protocol == PROTOCOLS.SCADA_MGMT then + if self.linked then + if packet.type == SCADA_MGMT_TYPES.ESTABLISH then + -- link request confirmation + if packet.length == 1 then + log.debug("received unsolicited establish response") + + local est_ack = packet.data[1] + + if est_ack == ESTABLISH_ACK.ALLOW then + self.status_cache = nil + _send_struct() + public.send_status(plc_state.no_reactor, plc_state.reactor_formed) + log.debug("re-sent initial status data") + elseif est_ack == ESTABLISH_ACK.DENY then + println_ts("received unsolicited link denial, unlinking") + log.info("unsolicited establish request denied") + elseif est_ack == ESTABLISH_ACK.COLLISION then + println_ts("received unsolicited link collision, unlinking") + log.warning("unsolicited establish request collision") + else + println_ts("invalid unsolicited link response") + log.error("unsolicited unknown establish request response") + end + + self.linked = est_ack == ESTABLISH_ACK.ALLOW + else + log.debug("SCADA_MGMT establish packet length mismatch") + end + elseif packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + -- keep alive request received, echo back + if packet.length == 1 and type(packet.data[1]) == "number" then + local timestamp = packet.data[1] + local trip_time = util.time() - timestamp + + if trip_time > 500 then + log.warning("PLC KEEP_ALIVE trip time > 500ms (" .. trip_time .. "ms)") + end + + -- log.debug("RPLC RTT = " .. trip_time .. "ms") + + _send_keep_alive_ack(timestamp) + else + log.debug("SCADA_MGMT keep alive packet length/type mismatch") + end + elseif packet.type == SCADA_MGMT_TYPES.CLOSE then + -- handle session close + conn_watchdog.cancel() + public.unlink() + println_ts("server connection closed by remote host") + log.warning("server connection closed by remote host") + else + log.warning("received unsupported SCADA_MGMT packet type " .. packet.type) + end + elseif packet.type == SCADA_MGMT_TYPES.ESTABLISH then -- link request confirmation if packet.length == 1 then - local link_ack = packet.data[1] + local est_ack = packet.data[1] - if link_ack == RPLC_LINKING.ALLOW then + if est_ack == ESTABLISH_ACK.ALLOW then println_ts("linked!") - log.debug("RPLC link request approved") + log.debug("supervisor establish request approved") -- reset remote sequence number and cache self.r_seq_num = nil self.status_cache = nil - if plc_state.reactor_formed then - _send_struct() - end - + if plc_state.reactor_formed then _send_struct() end public.send_status(plc_state.no_reactor, plc_state.reactor_formed) log.debug("sent initial status data") - elseif link_ack == RPLC_LINKING.DENY then + elseif est_ack == ESTABLISH_ACK.DENY then println_ts("link request denied, retrying...") - log.debug("RPLC link request denied") - elseif link_ack == RPLC_LINKING.COLLISION then + log.debug("establish request denied") + elseif est_ack == ESTABLISH_ACK.COLLISION then println_ts("reactor PLC ID collision (check config), retrying...") - log.warning("RPLC link request collision") + log.warning("establish request collision") else println_ts("invalid link response, bad channel? retrying...") - log.error("unknown RPLC link request response") + log.error("unknown establish request response") end - self.linked = link_ack == RPLC_LINKING.ALLOW + self.linked = est_ack == ESTABLISH_ACK.ALLOW else - log.debug("RPLC link req packet length mismatch") + log.debug("SCADA_MGMT establish packet length mismatch") end else - log.debug("discarding non-link packet before linked") - end - elseif protocol == PROTOCOLS.SCADA_MGMT then - if packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then - -- keep alive request received, echo back - if packet.length == 1 then - local timestamp = packet.data[1] - local trip_time = util.time() - timestamp - - if trip_time > 500 then - log.warning("PLC KEEP_ALIVE trip time > 500ms (" .. trip_time .. "ms)") - end - - -- log.debug("RPLC RTT = " .. trip_time .. "ms") - - _send_keep_alive_ack(timestamp) - else - log.debug("SCADA keep alive packet length mismatch") - end - elseif packet.type == SCADA_MGMT_TYPES.CLOSE then - -- handle session close - conn_watchdog.cancel() - public.unlink() - println_ts("server connection closed by remote host") - log.warning("server connection closed by remote host") - else - log.warning("received unknown SCADA_MGMT packet type " .. packet.type) + log.debug("discarding non-link SCADA_MGMT packet before linked") end else -- should be unreachable assuming packet is from parse_packet() diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 785d668..84a653d 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.5" +local R_PLC_VERSION = "beta-v0.9.6" local print = util.print local println = util.println diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 229d58b..88516d1 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -8,6 +8,8 @@ local modbus = require("rtu.modbus") local rtu = {} local PROTOCOLS = comms.PROTOCOLS +local DEVICE_TYPES = comms.DEVICE_TYPES +local ESTABLISH_ACK = comms.ESTABLISH_ACK local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES @@ -210,6 +212,34 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog) _send(SCADA_MGMT_TYPES.KEEP_ALIVE, { srv_time, util.time() }) end + -- generate device advertisement table + ---@param units table + ---@return table advertisement + local function _generate_advertisement(units) + local advertisement = {} + + for i = 1, #units do + local unit = units[i] --@type rtu_unit_registry_entry + local type = comms.rtu_t_to_unit_type(unit.type) + + if type ~= nil then + local advert = { + type, + unit.index, + unit.reactor + } + + if type == RTU_UNIT_TYPES.REDSTONE then + insert(advert, unit.device) + end + + insert(advertisement, advert) + end + end + + return advertisement + end + -- PUBLIC FUNCTIONS -- -- send a MODBUS TCP packet @@ -244,31 +274,16 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog) _send(SCADA_MGMT_TYPES.CLOSE, {}) end + -- send establish request (includes advertisement) + ---@param units table + function public.send_establish(units) + _send(SCADA_MGMT_TYPES.ESTABLISH, { comms.version, self.version, DEVICE_TYPES.RTU, _generate_advertisement(units) }) + end + -- send capability advertisement ---@param units table function public.send_advertisement(units) - local advertisement = { self.version } - - for i = 1, #units do - local unit = units[i] --@type rtu_unit_registry_entry - local type = comms.rtu_t_to_unit_type(unit.type) - - if type ~= nil then - local advert = { - type, - unit.index, - unit.reactor - } - - if type == RTU_UNIT_TYPES.REDSTONE then - insert(advert, unit.device) - end - - insert(advertisement, advert) - end - end - - _send(SCADA_MGMT_TYPES.RTU_ADVERT, advertisement) + _send(SCADA_MGMT_TYPES.RTU_ADVERT, _generate_advertisement(units)) end -- notify that a peripheral was remounted @@ -334,86 +349,107 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog) local protocol = packet.scada_frame.protocol() if protocol == PROTOCOLS.MODBUS_TCP then - local return_code = false + if rtu_state.linked then + local return_code = false ---@diagnostic disable-next-line: param-type-mismatch - local reply = modbus.reply__neg_ack(packet) + local reply = modbus.reply__neg_ack(packet) - -- handle MODBUS instruction - if packet.unit_id <= #units then - local unit = units[packet.unit_id] ---@type rtu_unit_registry_entry - local unit_dbg_tag = " (unit " .. packet.unit_id .. ")" + -- handle MODBUS instruction + if packet.unit_id <= #units then + local unit = units[packet.unit_id] ---@type rtu_unit_registry_entry + local unit_dbg_tag = " (unit " .. packet.unit_id .. ")" - if unit.name == "redstone_io" then - -- immediately execute redstone RTU requests + if unit.name == "redstone_io" then + -- immediately execute redstone RTU requests ---@diagnostic disable-next-line: param-type-mismatch - return_code, reply = unit.modbus_io.handle_packet(packet) - if not return_code then - log.warning("requested MODBUS operation failed" .. unit_dbg_tag) + return_code, reply = unit.modbus_io.handle_packet(packet) + if not return_code then + log.warning("requested MODBUS operation failed" .. unit_dbg_tag) + end + else + -- check validity then pass off to unit comms thread +---@diagnostic disable-next-line: param-type-mismatch + return_code, reply = unit.modbus_io.check_request(packet) + if return_code then + -- check if there are more than 3 active transactions + -- still queue the packet, but this may indicate a problem + if unit.pkt_queue.length() > 3 then +---@diagnostic disable-next-line: param-type-mismatch + reply = modbus.reply__srv_device_busy(packet) + log.debug("queueing new request with " .. unit.pkt_queue.length() .. + " transactions already in the queue" .. unit_dbg_tag) + end + + -- always queue the command even if busy + unit.pkt_queue.push_packet(packet) + else + log.warning("cannot perform requested MODBUS operation" .. unit_dbg_tag) + end end else - -- check validity then pass off to unit comms thread + -- unit ID out of range? ---@diagnostic disable-next-line: param-type-mismatch - return_code, reply = unit.modbus_io.check_request(packet) - if return_code then - -- check if there are more than 3 active transactions - -- still queue the packet, but this may indicate a problem - if unit.pkt_queue.length() > 3 then ----@diagnostic disable-next-line: param-type-mismatch - reply = modbus.reply__srv_device_busy(packet) - log.debug("queueing new request with " .. unit.pkt_queue.length() .. - " transactions already in the queue" .. unit_dbg_tag) - end - - -- always queue the command even if busy - unit.pkt_queue.push_packet(packet) - else - log.warning("cannot perform requested MODBUS operation" .. unit_dbg_tag) - end + reply = modbus.reply__gw_unavailable(packet) + log.error("received MODBUS packet for non-existent unit") end - else - -- unit ID out of range? ----@diagnostic disable-next-line: param-type-mismatch - reply = modbus.reply__gw_unavailable(packet) - log.error("received MODBUS packet for non-existent unit") - end - public.send_modbus(reply) + public.send_modbus(reply) + else + log.debug("discarding MODBUS packet before linked") + end elseif protocol == PROTOCOLS.SCADA_MGMT then -- SCADA management packet - if packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then - -- keep alive request received, echo back + if packet.type == SCADA_MGMT_TYPES.ESTABLISH then if packet.length == 1 then - local timestamp = packet.data[1] - local trip_time = util.time() - timestamp + local est_ack = packet.data[1] - if trip_time > 500 then - log.warning("RTU KEEP_ALIVE trip time > 500ms (" .. trip_time .. "ms)") + if est_ack == ESTABLISH_ACK.ALLOW then + -- establish allowed + rtu_state.linked = true + self.r_seq_num = nil + println_ts("supervisor connection established") + log.info("supervisor connection established") + else + -- establish denied + public.unlink(rtu_state) + println_ts("supervisor connection") + log.warning("supervisor connection denied by remote host") end - - -- log.debug("RTU RTT = " .. trip_time .. "ms") - - _send_keep_alive_ack(timestamp) else - log.debug("SCADA keep alive packet length mismatch") + log.debug("SCADA_MGMT establish packet length mismatch") + end + elseif rtu_state.linked then + if packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + -- keep alive request received, echo back + if packet.length == 1 and type(packet.data[1]) == "number" then + local timestamp = packet.data[1] + local trip_time = util.time() - timestamp + + if trip_time > 500 then + log.warning("RTU KEEP_ALIVE trip time > 500ms (" .. trip_time .. "ms)") + end + + -- log.debug("RTU RTT = " .. trip_time .. "ms") + + _send_keep_alive_ack(timestamp) + else + log.debug("SCADA_MGMT keep alive packet length/type mismatch") + end + elseif packet.type == SCADA_MGMT_TYPES.CLOSE then + -- close connection + self.conn_watchdog.cancel() + public.unlink(rtu_state) + println_ts("server connection closed by remote host") + log.warning("server connection closed by remote host") + elseif packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then + -- request for capabilities again + public.send_advertisement(units) + else + -- not supported + log.warning("received unsupported SCADA_MGMT message type " .. packet.type) end - elseif packet.type == SCADA_MGMT_TYPES.CLOSE then - -- close connection - self.conn_watchdog.cancel() - public.unlink(rtu_state) - println_ts("server connection closed by remote host") - log.warning("server connection closed by remote host") - elseif packet.type == SCADA_MGMT_TYPES.REMOTE_LINKED then - -- acknowledgement - rtu_state.linked = true - self.r_seq_num = nil - println_ts("supervisor connection established") - log.info("supervisor connection established") - elseif packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then - -- request for capabilities again - public.send_advertisement(units) else - -- not supported - log.warning("RTU got unexpected SCADA message type " .. packet.type) + log.debug("discarding non-link SCADA_MGMT packet before linked") end else -- should be unreachable assuming packet is from parse_packet() diff --git a/rtu/startup.lua b/rtu/startup.lua index e50c93f..03dfbaa 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -24,7 +24,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.9.2" +local RTU_VERSION = "beta-v0.9.3" local rtu_t = types.rtu_t diff --git a/rtu/threads.lua b/rtu/threads.lua index fd08759..3d5b690 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -11,7 +11,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local modbus = require("rtu.modbus") +local modbus = require("rtu.modbus") local threads = {} @@ -58,10 +58,10 @@ function threads.thread__main(smem) -- start next clock timer loop_clock.start() - -- period tick, if we are not linked send advertisement + -- period tick, if we are not linked send establish request if not rtu_state.linked then -- advertise units - rtu_comms.send_advertisement(units) + rtu_comms.send_establish(units) end elseif event == "modem_message" then -- got a packet @@ -93,7 +93,9 @@ function threads.thread__main(smem) -- we are going to let the PPM prevent crashes -- return fault flags/codes to MODBUS queries local unit = units[i] - println_ts("lost the " .. unit.type .. " on interface " .. unit.name) + println_ts(util.c("lost the ", unit.type, " on interface ", unit.name)) + log.warning(util.c("lost the ", unit.type, " unit peripheral on interface ", unit.name)) + break end end end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 16092ee..adf58c4 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -12,6 +12,8 @@ local rtu_t = types.rtu_t local insert = table.insert +comms.version = "1.0.0" + ---@alias PROTOCOLS integer local PROTOCOLS = { MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol @@ -21,43 +23,49 @@ local PROTOCOLS = { COORD_API = 4 -- data/control packets for pocket computers to/from coordinators } ----@alias RPLC_TYPES integer -local RPLC_TYPES = { - LINK_REQ = 0, -- linking requests - STATUS = 1, -- reactor/system status - MEK_STRUCT = 2, -- mekanism build structure - MEK_BURN_RATE = 3, -- set burn rate - RPS_ENABLE = 4, -- enable reactor - RPS_SCRAM = 5, -- SCRAM reactor (manual request) - RPS_ASCRAM = 6, -- SCRAM reactor (automatic request) - RPS_STATUS = 7, -- RPS status - RPS_ALARM = 8, -- RPS alarm broadcast - RPS_RESET = 9 -- clear RPS trip (if in bad state, will trip immediately) +---@alias DEVICE_TYPES integer +local DEVICE_TYPES = { + PLC = 0, -- PLC device type for establish + RTU = 1, -- RTU device type for establish + SV = 2, -- supervisor device type for establish + CRDN = 3 -- coordinator device type for establish } ----@alias RPLC_LINKING integer -local RPLC_LINKING = { +---@alias RPLC_TYPES integer +local RPLC_TYPES = { + STATUS = 0, -- reactor/system status + MEK_STRUCT = 1, -- mekanism build structure + MEK_BURN_RATE = 2, -- set burn rate + RPS_ENABLE = 3, -- enable reactor + RPS_SCRAM = 4, -- SCRAM reactor (manual request) + RPS_ASCRAM = 5, -- SCRAM reactor (automatic request) + RPS_STATUS = 6, -- RPS status + RPS_ALARM = 7, -- RPS alarm broadcast + RPS_RESET = 8 -- clear RPS trip (if in bad state, will trip immediately) +} + +---@alias SCADA_MGMT_TYPES integer +local SCADA_MGMT_TYPES = { + ESTABLISH = 0, -- establish new connection + KEEP_ALIVE = 1, -- keep alive packet w/ RTT + CLOSE = 2, -- close a connection + RTU_ADVERT = 3, -- RTU capability advertisement + RTU_DEV_REMOUNT = 4 -- RTU multiblock possbily changed (formed, unformed) due to PPM remount +} + +---@alias ESTABLISH_ACK integer +local ESTABLISH_ACK = { ALLOW = 0, -- link approved DENY = 1, -- link denied COLLISION = 2 -- link denied due to existing active link } ----@alias SCADA_MGMT_TYPES integer -local SCADA_MGMT_TYPES = { - KEEP_ALIVE = 0, -- keep alive packet w/ RTT - CLOSE = 1, -- close a connection - REMOTE_LINKED = 2, -- remote device linked - RTU_ADVERT = 3, -- RTU capability advertisement - RTU_DEV_REMOUNT = 4 -- RTU multiblock possbily changed (formed, unformed) due to PPM remount -} - ---@alias SCADA_CRDN_TYPES integer local SCADA_CRDN_TYPES = { - ESTABLISH = 0, -- initial greeting - STRUCT_BUILDS = 1, -- mekanism structure builds - UNIT_STATUSES = 2, -- state of reactor units - COMMAND_UNIT = 3, -- command a reactor unit - ALARM = 4 -- alarm signaling + STRUCT_BUILDS = 0, -- mekanism structure builds + UNIT_STATUSES = 1, -- state of reactor units + COMMAND_UNIT = 2, -- command a reactor unit + ALARM = 3 -- alarm signaling } ---@alias CRDN_COMMANDS integer @@ -86,8 +94,9 @@ local RTU_UNIT_TYPES = { } comms.PROTOCOLS = PROTOCOLS +comms.DEVICE_TYPES = DEVICE_TYPES comms.RPLC_TYPES = RPLC_TYPES -comms.RPLC_LINKING = RPLC_LINKING +comms.ESTABLISH_ACK = ESTABLISH_ACK comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES comms.SCADA_CRDN_TYPES = SCADA_CRDN_TYPES comms.CRDN_COMMANDS = CRDN_COMMANDS @@ -286,8 +295,7 @@ function comms.rplc_packet() -- check that type is known local function _rplc_type_valid() - return self.type == RPLC_TYPES.LINK_REQ or - self.type == RPLC_TYPES.STATUS or + return self.type == RPLC_TYPES.STATUS or self.type == RPLC_TYPES.MEK_STRUCT or self.type == RPLC_TYPES.MEK_BURN_RATE or self.type == RPLC_TYPES.RPS_ENABLE or @@ -384,7 +392,8 @@ function comms.mgmt_packet() -- check that type is known local function _scada_type_valid() - return self.type == SCADA_MGMT_TYPES.KEEP_ALIVE or + return self.type == SCADA_MGMT_TYPES.ESTABLISH or + self.type == SCADA_MGMT_TYPES.KEEP_ALIVE or self.type == SCADA_MGMT_TYPES.CLOSE or self.type == SCADA_MGMT_TYPES.REMOTE_LINKED or self.type == SCADA_MGMT_TYPES.RTU_ADVERT or @@ -472,8 +481,7 @@ function comms.crdn_packet() -- check that type is known local function _crdn_type_valid() - return self.type == SCADA_CRDN_TYPES.ESTABLISH or - self.type == SCADA_CRDN_TYPES.STRUCT_BUILDS or + return self.type == SCADA_CRDN_TYPES.STRUCT_BUILDS or self.type == SCADA_CRDN_TYPES.UNIT_STATUSES or self.type == SCADA_CRDN_TYPES.COMMAND_UNIT or self.type == SCADA_CRDN_TYPES.ALARM diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 29a3e07..c4a6c8f 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -317,11 +317,9 @@ end ---@param local_port integer ---@param remote_port integer ---@param advertisement table +---@param version string ---@return integer session_id -function svsessions.establish_rtu_session(local_port, remote_port, advertisement) - -- pull version from advertisement - local version = table.remove(advertisement, 1) - +function svsessions.establish_rtu_session(local_port, remote_port, advertisement, version) ---@class rtu_session_struct local rtu_s = { s_type = "rtu", diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 6a3abea..f213501 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.7.4" +local SUPERVISOR_VERSION = "beta-v0.7.5" local print = util.print local println = util.println diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index ec85623..b86e0e6 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -7,8 +7,9 @@ local svsessions = require("supervisor.session.svsessions") local supervisor = {} local PROTOCOLS = comms.PROTOCOLS +local DEVICE_TYPES = comms.DEVICE_TYPES +local ESTABLISH_ACK = comms.ESTABLISH_ACK local RPLC_TYPES = comms.RPLC_TYPES -local RPLC_LINKING = comms.RPLC_LINKING local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES @@ -51,27 +52,14 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen -- link modem to svsessions svsessions.init(self.modem, num_reactors, cooling_conf) - -- send PLC link request response + -- send an establish request response to a PLC/RTU ---@param dest integer ---@param msg table - local function _send_plc_linking(seq_id, dest, msg) - local s_pkt = comms.scada_packet() - local r_pkt = comms.rplc_packet() - - r_pkt.make(0, RPLC_TYPES.LINK_REQ, msg) - s_pkt.make(seq_id, PROTOCOLS.RPLC, r_pkt.raw_sendable()) - - self.modem.transmit(dest, self.dev_listen, s_pkt.raw_sendable()) - end - - -- send RTU advertisement response - ---@param seq_id integer - ---@param dest integer - local function _send_remote_linked(seq_id, dest) + local function _send_dev_establish(seq_id, dest, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() - m_pkt.make(SCADA_MGMT_TYPES.REMOTE_LINKED, {}) + m_pkt.make(SCADA_MGMT_TYPES.ESTABLISH, msg) s_pkt.make(seq_id, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) self.modem.transmit(dest, self.dev_listen, s_pkt.raw_sendable()) @@ -80,23 +68,13 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen -- send coordinator connection establish response ---@param seq_id integer ---@param dest integer - ---@param allow boolean - local function _send_crdn_establish(seq_id, dest, allow) + ---@param msg table + local function _send_crdn_establish(seq_id, dest, msg) local s_pkt = comms.scada_packet() - local c_pkt = comms.crdn_packet() + local c_pkt = comms.mgmt_packet() - local config = { false } - - if allow then - config = { self.num_reactors } - for i = 1, #cooling_conf do - table.insert(config, cooling_conf[i].BOILERS) - table.insert(config, cooling_conf[i].TURBINES) - end - end - - c_pkt.make(SCADA_CRDN_TYPES.ESTABLISH, config) - s_pkt.make(seq_id, PROTOCOLS.SCADA_CRDN, c_pkt.raw_sendable()) + c_pkt.make(SCADA_MGMT_TYPES.ESTABLISH, msg) + s_pkt.make(seq_id, PROTOCOLS.SCADA_MGMT, c_pkt.raw_sendable()) self.modem.transmit(dest, self.coord_listen, s_pkt.raw_sendable()) end @@ -187,46 +165,12 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen -- reactor PLC packet if session ~= nil then - if packet.type == RPLC_TYPES.LINK_REQ then - -- new device on this port? that's a collision - log.debug("PLC_LNK: request from existing connection received on " .. r_port .. ", responding with collision") - _send_plc_linking(packet.scada_frame.seq_num() + 1, r_port, { RPLC_LINKING.COLLISION }) - else - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - end + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) else - local next_seq_id = packet.scada_frame.seq_num() + 1 - - -- unknown session, is this a linking request? - if packet.type == RPLC_TYPES.LINK_REQ then - if packet.length == 2 then - -- this is a linking request - if type(packet.data[1]) == "number" and type(packet.data[2]) == "string" then - local plc_id = svsessions.establish_plc_session(l_port, r_port, packet.data[1], packet.data[2]) - if plc_id == false then - -- reactor already has a PLC assigned - log.debug(util.c("PLC_LNK: assignment collision with reactor ", packet.data[1])) - _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.COLLISION }) - else - -- got an ID; assigned to a reactor successfully - println(util.c("connected to reactor ", packet.data[1], " PLC (", packet.data[2], ") [:", r_port, "]")) - log.debug("PLC_LNK: allowed for device at " .. r_port) - _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.ALLOW }) - end - else - log.debug("PLC_LNK: bad parameter types") - _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.DENY }) - end - else - log.debug("PLC_LNK: new linking packet length mismatch") - _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.DENY }) - end - else - -- force a re-link - log.debug("PLC_LNK: no session but not a link, force relink") - _send_plc_linking(next_seq_id, r_port, { RPLC_LINKING.DENY }) - end + -- unknown session, force a re-link + log.debug("PLC_EST: no session but not an establish, force relink") + _send_dev_establish((packet.scada_frame.seq_num() + 1), r_port, { ESTABLISH_ACK.DENY }) end elseif protocol == PROTOCOLS.SCADA_MGMT then -- look for an associated session @@ -236,20 +180,63 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen if session ~= nil then -- pass the packet onto the session handler session.in_queue.push_packet(packet) - elseif packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then - if packet.length >= 1 then - -- this is an RTU advertisement for a new session - local rtu_version = packet.data[1] + elseif packet.type == SCADA_MGMT_TYPES.ESTABLISH then + -- establish a new session + local next_seq_id = packet.scada_frame.seq_num() + 1 - -- note: this function mutates packet.data - svsessions.establish_rtu_session(l_port, r_port, packet.data) + -- validate packet and continue + if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then + local comms_v = packet.data[1] + local firmware_v = packet.data[2] + local dev_type = packet.data[3] - println(util.c("connected to RTU (", rtu_version, ") [:", r_port, "]")) - log.debug("RTU_ADVERT: linked " .. r_port) + if comms_v ~= comms.version then + log.debug(util.c("dropping establish packet with incorrect comms version v", comms_v, + " (expected v", comms.version, ")")) + _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) + return + end - _send_remote_linked(packet.scada_frame.seq_num() + 1, r_port) + if dev_type == DEVICE_TYPES.PLC then + -- PLC linking request + if packet.length == 4 and type(packet.data[4]) == "number" then + local reactor_id = packet.data[4] + local plc_id = svsessions.establish_plc_session(l_port, r_port, reactor_id, firmware_v) + + if plc_id == false then + -- reactor already has a PLC assigned + log.debug(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) + _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.COLLISION }) + else + -- got an ID; assigned to a reactor successfully + println(util.c("reactor ", reactor_id, " PLC (", firmware_v, ") [:", r_port, "] \xbb connected")) + log.debug(util.c("PLC_ESTABLISH: link accepted for PLC (", firmware_v, ") [:", r_port, "] connected with session ID ", plc_id)) + _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW }) + end + else + log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type") + _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) + end + elseif dev_type == DEVICE_TYPES.RTU then + if packet.length == 4 then + -- this is an RTU advertisement for a new session + local rtu_advert = packet.data[4] + local s_id = svsessions.establish_rtu_session(l_port, r_port, rtu_advert, firmware_v) + + println(util.c("RTU (", firmware_v, ") [:", r_port, "] \xbb connected")) + log.debug(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [:", r_port, "] connected with session ID ", s_id)) + _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW }) + else + log.debug("RTU_ESTABLISH: packet length mismatch") + _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) + end + else + log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC/RTU listening channel")) + _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) + end else - log.debug("RTU_ADVERT: advertisement packet empty") + log.debug("invalid establish packet (on PLC/RTU listening channel)") + _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) end else -- any other packet should be session related, discard it @@ -268,6 +255,48 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen if session ~= nil then -- pass the packet onto the session handler session.in_queue.push_packet(packet) + elseif packet.type == SCADA_MGMT_TYPES.ESTABLISH then + -- establish a new session + local next_seq_id = packet.scada_frame.seq_num() + 1 + + -- validate packet and continue + if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then + local comms_v = packet.data[1] + local firmware_v = packet.data[2] + local dev_type = packet.data[3] + + if comms_v ~= comms.version then + log.debug(util.c("dropping establish packet with incorrect comms version v", comms_v, + " (expected v", comms.version, ")")) + _send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) + return + elseif dev_type ~= DEVICE_TYPES.CRDN then + log.debug(util.c("illegal establish packet for device ", dev_type, " on CRDN listening channel")) + _send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) + return + end + + -- this is an attempt to establish a new session + local s_id = svsessions.establish_coord_session(l_port, r_port, firmware_v) + + if s_id ~= false then + local config = { self.num_reactors } + for i = 1, #cooling_conf do + table.insert(config, cooling_conf[i].BOILERS) + table.insert(config, cooling_conf[i].TURBINES) + end + + println(util.c("coordinator (",firmware_v, ") [:", r_port, "] \xbb connected")) + log.debug(util.c("CRDN_ESTABLISH: coordinator (",firmware_v, ") [:", r_port, "] connected with session ID ", s_id)) + _send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW, config }) + else + log.debug("CRDN_ESTABLISH: denied new coordinator due to already being connected to another coordinator") + _send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.COLLISION }) + end + else + log.debug("CRDN_ESTABLISH: establish packet length mismatch") + _send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) + end else -- any other packet should be session related, discard it log.debug(r_port .. "->" .. l_port .. ": discarding SCADA_MGMT packet without a known session") @@ -277,22 +306,6 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen if session ~= nil then -- pass the packet onto the session handler session.in_queue.push_packet(packet) - elseif packet.type == SCADA_CRDN_TYPES.ESTABLISH then - if packet.length == 1 then - -- this is an attempt to establish a new session - local s_id = svsessions.establish_coord_session(l_port, r_port, packet.data[1]) - - if s_id ~= false then - println(util.c("connected to coordinator (", packet.data[1], ") [:", r_port, "]")) - log.debug("CRDN_ESTABLISH: connected to " .. r_port) - else - log.debug("CRDN_ESTABLISH: denied new coordinator due to already being connected to another coordinator") - end - - _send_crdn_establish(packet.scada_frame.seq_num() + 1, r_port, (s_id ~= false)) - else - log.debug("CRDN_ESTABLISH: establish packet length mismatch") - end else -- any other packet should be session related, discard it log.debug(r_port .. "->" .. l_port .. ": discarding SCADA_CRDN packet without a known session") From 9761228b8eb87848f848387b5c553299f3f4f95a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 13 Nov 2022 15:56:27 -0500 Subject: [PATCH 439/587] #124 debug stack trace on error --- .vscode/settings.json | 3 +- coordinator/startup.lua | 447 +++++++++++++++--------------- reactor-plc/startup.lua | 277 ++++++++++--------- rtu/startup.lua | 591 ++++++++++++++++++++-------------------- scada-common/crash.lua | 46 ++++ supervisor/startup.lua | 177 ++++++------ 6 files changed, 816 insertions(+), 725 deletions(-) create mode 100644 scada-common/crash.lua diff --git a/.vscode/settings.json b/.vscode/settings.json index 46b3a86..0cc34d6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,6 +13,7 @@ "window", "read", "periphemu", - "mekanismEnergyHelper" + "mekanismEnergyHelper", + "_HOST" ] } diff --git a/coordinator/startup.lua b/coordinator/startup.lua index e7028e7..1289dd8 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -4,6 +4,7 @@ require("/initenv").init_env() +local crash = require("scada-common.crash") local log = require("scada-common.log") local ppm = require("scada-common.ppm") local tcallbackdsp = require("scada-common.tcallbackdsp") @@ -16,7 +17,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.11" +local COORDINATOR_VERSION = "alpha-v0.6.12" local print = util.print local println = util.println @@ -57,262 +58,272 @@ log.info("BOOTING coordinator.startup " .. COORDINATOR_VERSION) log.info("========================================") println(">> SCADA 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) +crash.set_env("coordinator", COORDINATOR_VERSION) ---------------------------------------- --- setup communications +-- main application ---------------------------------------- --- get the communications modem -local modem = ppm.get_wireless_modem() -if modem == nil then - 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 +local function main() + ---------------------------------------- + -- system startup + ---------------------------------------- --- create connection watchdog -local conn_watchdog = util.new_watchdog(5) -conn_watchdog.cancel() -log.debug("boot> conn watchdog created") + -- mount connected devices + ppm.mount_all() --- 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) -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 + -- 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 - return true -end + log.info("monitors ready, dmesg output incoming...") -if not init_connect_sv() then - println("boot> failed to connect to supervisor") - log_sys("system shutdown") - return -else - log_sys("supervisor connected, proceeding to UI start") -end + -- init renderer + renderer.set_displays(monitors) + renderer.reset(config.RECOLOR) + renderer.init_dmesg() ----------------------------------------- --- start the UI ----------------------------------------- + log_graphics("displays connected and reset") + log_sys("system start on " .. os.date("%c")) + log_boot("starting " .. COORDINATOR_VERSION) --- start up the UI ----@return boolean ui_ok started ok -local function init_start_ui() - log_graphics("starting UI...") + ---------------------------------------- + -- setup communications + ---------------------------------------- - local draw_start = util.time_ms() - - local ui_ok, message = pcall(renderer.start_ui) - if not ui_ok then - renderer.close_ui() - log_graphics(util.c("UI crashed: ", message)) - println_ts("UI crashed") - log.fatal(util.c("ui crashed with error ", message)) + -- get the communications modem + local modem = ppm.get_wireless_modem() + if modem == nil then + log_comms("wireless modem not found") + println("boot> wireless modem not found") + log.fatal("no wireless modem on startup") + return else - log_graphics("first UI draw took " .. (util.time_ms() - draw_start) .. "ms") - - -- start clock - loop_clock.start() + log_comms("wireless modem connected") end - return ui_ok -end + -- create connection watchdog + 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") ----------------------------------------- --- main event loop ----------------------------------------- + -- base loop clock (2Hz, 10 ticks) + 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 - -- start connection watchdog - conn_watchdog.feed() - log.debug("boot> conn watchdog started") + -- 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) - log_sys("system started successfully") -end + -- 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 --- main event loop -while ui_ok do - local event, param1, param2, param3, param4, param5 = util.pull_event() + return true + end - -- handle event - if event == "peripheral_detach" then - local type, device = ppm.handle_unmount(param1) + if not init_connect_sv() then + println("boot> failed to connect to supervisor") + 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 - -- 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!") + ---------------------------------------- + -- start the UI + ---------------------------------------- - -- close out UI - renderer.close_ui() + -- start up the UI + ---@return boolean ui_ok started ok + local function init_start_ui() + log_graphics("starting 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") + local draw_start = util.time_ms() + + local ui_ok, message = pcall(renderer.start_ui) + if not ui_ok then + renderer.close_ui() + log_graphics(util.c("UI crashed: ", message)) + println_ts("UI crashed") + log.fatal(util.c("ui crashed with error ", message)) + else + log_graphics("first UI draw took " .. (util.time_ms() - draw_start) .. "ms") + + -- start clock + 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 - elseif event == "peripheral" then - local type, device = ppm.mount(param1) + 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 - no_modem = false - modem = device - coord_comms.reconnect_modem(modem) + if type ~= nil and device ~= nil then + if type == "modem" then + if device.isWireless() then + -- reconnected modem + no_modem = false + modem = device + coord_comms.reconnect_modem(modem) - log_sys("comms modem reconnected") - println_ts("wireless modem reconnected.") + log_sys("comms 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 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 + else + -- a non-clock/main watchdog timer event + + --check API watchdogs + --apisessions.check_all_watchdogs(param1) + + -- notify timer callback dispatcher + tcallbackdsp.handle(param1) 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 - 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 + -- check for termination request + if event == "terminate" or ppm.should_terminate() then + println_ts("terminate requested, closing connections...") + log_comms("terminate requested, closing supervisor connection...") 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 - else - -- a non-clock/main watchdog timer event - - --check API watchdogs - --apisessions.check_all_watchdogs(param1) - - -- notify timer callback dispatcher - tcallbackdsp.handle(param1) + log_comms("supervisor connection closed") + log_comms("closing api sessions...") + apisessions.close_all() + log_comms("api sessions closed") + break 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 - -- check for termination request - if event == "terminate" or ppm.should_terminate() then - println_ts("terminate requested, closing connections...") - log_comms("terminate requested, closing supervisor connection...") - coord_comms.close() - log_comms("supervisor connection closed") - log_comms("closing api sessions...") - apisessions.close_all() - log_comms("api sessions closed") - break - end + renderer.close_ui() + log_sys("system shutdown") + + println_ts("exited") + log.info("exited") end -renderer.close_ui() -log_sys("system shutdown") - -println_ts("exited") -log.info("exited") +if not xpcall(main, crash.handler) then crash.exit() end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 84a653d..3f6c0d5 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -4,6 +4,7 @@ require("/initenv").init_env() +local crash = require("scada-common.crash") local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") local ppm = require("scada-common.ppm") @@ -13,7 +14,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.6" +local R_PLC_VERSION = "beta-v0.9.7" local print = util.print local println = util.println @@ -45,166 +46,176 @@ log.info("BOOTING reactor-plc.startup " .. R_PLC_VERSION) log.info("========================================") println(">> Reactor PLC " .. R_PLC_VERSION .. " <<") +crash.set_env("plc", R_PLC_VERSION) + ---------------------------------------- --- startup +-- main application ---------------------------------------- --- mount connected devices -ppm.mount_all() +local function main() + ---------------------------------------- + -- startup + ---------------------------------------- --- shared memory across threads ----@class plc_shared_memory -local __shared_memory = { - -- networked setting - networked = config.NETWORKED, ---@type boolean + -- mount connected devices + ppm.mount_all() - -- PLC system state flags - ---@class plc_state - plc_state = { - init_ok = true, - shutdown = false, - degraded = false, - reactor_formed = true, - no_reactor = false, - no_modem = false - }, + -- shared memory across threads + ---@class plc_shared_memory + local __shared_memory = { + -- networked setting + networked = config.NETWORKED, ---@type boolean - -- control setpoints - ---@class setpoints - setpoints = { - burn_rate_en = false, - burn_rate = 0.0 - }, + -- PLC system state flags + ---@class plc_state + plc_state = { + init_ok = true, + shutdown = false, + degraded = false, + reactor_formed = true, + no_reactor = false, + no_modem = false + }, - -- core PLC devices - plc_dev = { - reactor = ppm.get_fission_reactor(), - modem = ppm.get_wireless_modem() - }, + -- control setpoints + ---@class setpoints + setpoints = { + burn_rate_en = false, + burn_rate = 0.0 + }, - -- system objects - plc_sys = { - rps = nil, ---@type rps - plc_comms = nil, ---@type plc_comms - conn_watchdog = nil ---@type watchdog - }, + -- core PLC devices + plc_dev = { + reactor = ppm.get_fission_reactor(), + modem = ppm.get_wireless_modem() + }, - -- message queues - q = { - mq_rps = mqueue.new(), - mq_comms_tx = mqueue.new(), - mq_comms_rx = mqueue.new() + -- system objects + plc_sys = { + rps = nil, ---@type rps + plc_comms = nil, ---@type plc_comms + 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_sys = __shared_memory.plc_sys + local smem_dev = __shared_memory.plc_dev + 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 -if smem_dev.reactor == nil then - println("boot> fission reactor not found"); - log.warning("no reactor on startup") + -- we need a reactor, can at least do some things even if it isn't formed though + if smem_dev.reactor == nil then + println("boot> fission reactor not found"); + log.warning("no reactor on startup") - plc_state.init_ok = false - plc_state.degraded = true - plc_state.no_reactor = true -elseif not smem_dev.reactor.isFormed() then - println("boot> fission reactor not formed"); - log.warning("reactor logic adapter present, but reactor is not formed") + plc_state.init_ok = false + plc_state.degraded = true + plc_state.no_reactor = true + elseif not smem_dev.reactor.isFormed() then + println("boot> fission reactor not formed"); + log.warning("reactor logic adapter present, but reactor is not formed") - plc_state.degraded = true - 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() + plc_state.degraded = true + plc_state.reactor_formed = false end - plc_state.init_ok = false - plc_state.degraded = true - plc_state.no_modem = true -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") --- PLC init ---- ---- 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 + -- 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 - -- init reactor protection system - smem_sys.rps = plc.rps_init(smem_dev.reactor, plc_state.reactor_formed) - log.debug("init> rps init") + plc_state.init_ok = false + plc_state.degraded = true + plc_state.no_modem = true + end - if __shared_memory.networked then - -- comms watchdog, 3 second timeout - smem_sys.conn_watchdog = util.new_watchdog(3) - log.debug("init> conn watchdog started") + -- PLC init + --- + --- 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() + end - -- 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") + -- init reactor protection system + smem_sys.rps = plc.rps_init(smem_dev.reactor, plc_state.reactor_formed) + log.debug("init> rps 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 - println("boot> starting in offline mode") - log.debug("init> running without networking") + println("boot> system in degraded state, awaiting devices...") + log.warning("init> booted in a degraded state, awaiting peripheral connections...") end + end - util.push_event("clock_start") + ---------------------------------------- + -- start system + ---------------------------------------- - println("boot> completed") - log.debug("init> boot completed") + -- 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 - println("boot> system in degraded state, awaiting devices...") - log.warning("init> booted in a degraded state, awaiting peripheral connections...") + -- run threads, excluding comms + parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec) end + + println_ts("exited") + log.info("exited") 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") +if not xpcall(main, crash.handler) then crash.exit() end diff --git a/rtu/startup.lua b/rtu/startup.lua index 03dfbaa..8cce249 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -4,6 +4,7 @@ require("/initenv").init_env() +local crash = require("scada-common.crash") local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") 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 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 @@ -58,341 +59,351 @@ log.info("BOOTING rtu.startup " .. RTU_VERSION) log.info("========================================") println(">> RTU GATEWAY " .. RTU_VERSION .. " <<") +crash.set_env("rtu", RTU_VERSION) + ---------------------------------------- --- startup +-- main application ---------------------------------------- --- mount connected devices -ppm.mount_all() +local function main() + ---------------------------------------- + -- startup + ---------------------------------------- ----@class rtu_shared_memory -local __shared_memory = { - -- RTU system state flags - ---@class rtu_state - rtu_state = { - linked = false, - shutdown = false - }, + -- mount connected devices + ppm.mount_all() - -- core RTU devices - rtu_dev = { - modem = ppm.get_wireless_modem() - }, + ---@class rtu_shared_memory + local __shared_memory = { + -- RTU system state flags + ---@class rtu_state + rtu_state = { + linked = false, + shutdown = false + }, - -- system objects - rtu_sys = { - rtu_comms = nil, ---@type rtu_comms - conn_watchdog = nil, ---@type watchdog - units = {} ---@type table - }, + -- core RTU devices + rtu_dev = { + modem = ppm.get_wireless_modem() + }, - -- message queues - q = { - mq_comms = mqueue.new() + -- system objects + rtu_sys = { + 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_sys = __shared_memory.rtu_sys + local smem_dev = __shared_memory.rtu_dev + local smem_sys = __shared_memory.rtu_sys --- get modem -if smem_dev.modem == nil then - println("boot> wireless modem not found") - log.fatal("no wireless modem on startup") - return -end + -- get modem + if smem_dev.modem == nil then + println("boot> wireless modem not found") + log.fatal("no wireless modem on startup") + return + 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_devices = config.RTU_DEVICES + local rtu_redstone = config.RTU_REDSTONE + local rtu_devices = config.RTU_DEVICES --- configure RTU gateway based on config file definitions -local function configure() - -- redstone interfaces - for entry_idx = 1, #rtu_redstone do - local rs_rtu = redstone_rtu.new() - local io_table = rtu_redstone[entry_idx].io ---@type table - local io_reactor = rtu_redstone[entry_idx].for_reactor ---@type integer + -- configure RTU gateway based on config file definitions + local function configure() + -- redstone interfaces + for entry_idx = 1, #rtu_redstone do + local rs_rtu = redstone_rtu.new() + local io_table = rtu_redstone[entry_idx].io ---@type table + local io_reactor = rtu_redstone[entry_idx].for_reactor ---@type integer - -- CHECK: reactor ID must be >= to 1 - 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")) - return false - end + -- CHECK: reactor ID must be >= to 1 + 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")) + return false + end - -- CHECK: io table exists - if type(io_table) ~= "table" then - println(util.c("configure> redstone entry #", entry_idx, " no IO table found")) - return false - end + -- CHECK: io table exists + if type(io_table) ~= "table" then + println(util.c("configure> redstone entry #", entry_idx, " no IO table found")) + return false + 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 - for i = 1, #units do - local unit = units[i] ---@type rtu_unit_registry_entry - if unit.reactor == io_reactor and unit.type == rtu_t.redstone then - -- duplicate entry - local message = util.c("configure> skipping definition block #", entry_idx, " for reactor ", io_reactor, - " with already defined redstone I/O") - println(message) - log.warning(message) - continue = false - break + -- check for duplicate entries + for i = 1, #units do + local unit = units[i] ---@type rtu_unit_registry_entry + if unit.reactor == io_reactor and unit.type == rtu_t.redstone then + -- duplicate entry + local message = util.c("configure> skipping definition block #", entry_idx, " for reactor ", io_reactor, + " with already defined redstone I/O") + println(message) + log.warning(message) + continue = false + 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 - -- not a duplicate - if continue then - for i = 1, #io_table do - local valid = false - local conf = io_table[i] + -- mounted peripherals + for i = 1, #rtu_devices do + local name = rtu_devices[i].name + local index = rtu_devices[i].index + local for_reactor = rtu_devices[i].for_reactor - -- 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 + -- CHECK: name is a string + if type(name) ~= "string" then + println(util.c("configure> device entry #", i, ": device ", name, " isn't a string")) + return false + 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) + -- CHECK: index is an integer >= 1 + if (not util.is_int(index)) or (index <= 0) then + println(util.c("configure> device entry #", i, ": index ", index, " isn't an integer >= 1")) + return false + 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 - 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 + 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 ---@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 + local rtu_unit = { + name = name, + type = rtu_type, + 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 } - 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 + + -- we made it through all that trusting-user-to-write-a-config-file chaos + return true end - -- mounted peripherals - for i = 1, #rtu_devices do - local name = rtu_devices[i].name - local index = rtu_devices[i].index - local for_reactor = rtu_devices[i].for_reactor + ---------------------------------------- + -- start system + ---------------------------------------- - -- CHECK: name is a string - if type(name) ~= "string" then - println(util.c("configure> device entry #", i, ": device ", name, " isn't a string")) - return false - end + log.debug("boot> running configure()") - -- CHECK: index is an integer >= 1 - if (not util.is_int(index)) or (index <= 0) then - println(util.c("configure> device entry #", i, ": index ", index, " isn't an integer >= 1")) - return false - end + if configure() then + -- start connection watchdog + smem_sys.conn_watchdog = util.new_watchdog(5) + log.debug("boot> conn watchdog started") - -- 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 + -- 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") - 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 - 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 + -- 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 - 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 - ---@class rtu_unit_registry_entry - local rtu_unit = { - name = name, - type = rtu_type, - 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)) + -- run threads + parallel.waitForAll(table.unpack(_threads)) + else + println("configuration failed, exiting...") end - -- we made it through all that trusting-user-to-write-a-config-file chaos - return true + println_ts("exited") + log.info("exited") 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") +if not xpcall(main, crash.handler) then crash.exit() end diff --git a/scada-common/crash.lua b/scada-common/crash.lua new file mode 100644 index 0000000..66bfda1 --- /dev/null +++ b/scada-common/crash.lua @@ -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 diff --git a/supervisor/startup.lua b/supervisor/startup.lua index f213501..8f1238a 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -4,6 +4,7 @@ require("/initenv").init_env() +local crash = require("scada-common.crash") local log = require("scada-common.log") local ppm = require("scada-common.ppm") local util = require("scada-common.util") @@ -13,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.7.5" +local SUPERVISOR_VERSION = "beta-v0.7.6" local print = util.print local println = util.println @@ -57,95 +58,105 @@ log.info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION) log.info("========================================") println(">> SCADA Supervisor " .. SUPERVISOR_VERSION .. " <<") +crash.set_env("supervisor", SUPERVISOR_VERSION) + ---------------------------------------- --- startup +-- main application ---------------------------------------- --- mount connected devices -ppm.mount_all() +local function main() + ---------------------------------------- + -- startup + ---------------------------------------- -local modem = ppm.get_wireless_modem() -if modem == nil then - println("boot> wireless modem not found") - log.fatal("no wireless modem on startup") - return -end + -- mount connected devices + ppm.mount_all() --- start comms, open all channels -local superv_comms = supervisor.comms(SUPERVISOR_VERSION, config.NUM_REACTORS, config.REACTOR_COOLING, modem, - config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN) - --- 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() - --- 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) + local modem = ppm.get_wireless_modem() + if modem == nil then + println("boot> wireless modem not found") + log.fatal("no wireless modem on startup") + return 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 + -- start comms, open all channels + local superv_comms = supervisor.comms(SUPERVISOR_VERSION, config.NUM_REACTORS, config.REACTOR_COOLING, modem, + config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN) + + -- 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() + + -- 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 + + println_ts("exited") + log.info("exited") end -println_ts("exited") -log.info("exited") +if not xpcall(main, crash.handler) then crash.exit() end From 7c39e8c72bd9d6af5f906375acb7b9f374b964c3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 14 Nov 2022 21:43:02 -0500 Subject: [PATCH 440/587] #126 fixed RTU builds not being sent to coordinator at the correct times --- supervisor/session/rtu.lua | 19 +++++++++++++------ supervisor/startup.lua | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index b520ddc..997e198 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -7,6 +7,7 @@ local util = require("scada-common.util") local svqtypes = require("supervisor.session.svqtypes") -- supervisor rtu sessions (svrs) +local unit_session = require("supervisor.session.rtu.unit_session") local svrs_boilerv = require("supervisor.session.rtu.boilerv") local svrs_envd = require("supervisor.session.rtu.envd") local svrs_imatrix = require("supervisor.session.rtu.imatrix") @@ -202,9 +203,6 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) break end end - - -- report build changed - self.out_q.push_command(svqtypes.SV_Q_CMDS.BUILD_CHANGED) end -- mark this RTU session as closed, stop watchdog @@ -419,9 +417,9 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) self.periodics.last_update = util.time() - ---------------------------------------------- - -- pass MODBUS packets on to main out queue -- - ---------------------------------------------- + -------------------------------------------- + -- process RTU session handler out queues -- + -------------------------------------------- for _ = 1, self.modbus_q.length() do -- get the next message @@ -429,7 +427,16 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) if msg ~= nil then if msg.qtype == mqueue.TYPE.PACKET then + -- handle a packet _send_modbus(msg.message) + elseif msg.qtype == mqueue.TYPE.COMMAND then + -- handle instruction + local cmd = msg.message + if cmd == unit_session.RTU_US_CMDS.BUILD_CHANGED then + self.out_q.push_command(svqtypes.SV_Q_CMDS.BUILD_CHANGED) + end + elseif msg.qtype == mqueue.TYPE.DATA then + -- instruction with body end end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 8f1238a..99decc7 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.7.6" +local SUPERVISOR_VERSION = "beta-v0.7.7" local print = util.print local println = util.println From 6fcd18e17a7f3be8148d1d975017ac6937b671f4 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 14 Nov 2022 21:50:32 -0500 Subject: [PATCH 441/587] #125 moved environmental loss on boilers from build to state category --- rtu/dev/boilerv_rtu.lua | 2 +- rtu/startup.lua | 2 +- supervisor/session/rtu/boilerv.lua | 18 +++++++++--------- supervisor/startup.lua | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/rtu/dev/boilerv_rtu.lua b/rtu/dev/boilerv_rtu.lua index 782332f..ed7cdb5 100644 --- a/rtu/dev/boilerv_rtu.lua +++ b/rtu/dev/boilerv_rtu.lua @@ -28,10 +28,10 @@ function boilerv_rtu.new(boiler) unit.connect_input_reg(boiler.getCooledCoolantCapacity) unit.connect_input_reg(boiler.getSuperheaters) unit.connect_input_reg(boiler.getMaxBoilRate) - unit.connect_input_reg(boiler.getEnvironmentalLoss) -- current state unit.connect_input_reg(boiler.getTemperature) unit.connect_input_reg(boiler.getBoilRate) + unit.connect_input_reg(boiler.getEnvironmentalLoss) -- tanks unit.connect_input_reg(boiler.getSteam) unit.connect_input_reg(boiler.getSteamNeeded) diff --git a/rtu/startup.lua b/rtu/startup.lua index 8cce249..67a9867 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.9.4" +local RTU_VERSION = "beta-v0.9.5" local rtu_t = types.rtu_t diff --git a/supervisor/session/rtu/boilerv.lua b/supervisor/session/rtu/boilerv.lua index c0d9eb3..d35517d 100644 --- a/supervisor/session/rtu/boilerv.lua +++ b/supervisor/session/rtu/boilerv.lua @@ -71,12 +71,12 @@ function boilerv.new(session_id, unit_id, advert, out_queue) ccoolant_cap = 0, superheaters = 0, max_boil_rate = 0.0, - env_loss = 0.0 }, state = { last_update = 0, temperature = 0.0, - boil_rate = 0.0 + boil_rate = 0.0, + env_loss = 0.0 }, tanks = { last_update = 0, @@ -108,14 +108,14 @@ function boilerv.new(session_id, unit_id, advert, out_queue) -- query the build of the device local function _request_build() - -- read input registers 1 through 13 (start = 1, count = 13) - self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 13 }) + -- read input registers 1 through 12 (start = 1, count = 12) + self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 12 }) end -- query the state of the device local function _request_state() - -- read input registers 14 through 15 (start = 14, count = 2) - self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 14, 2 }) + -- read input registers 13 through 15 (start = 13, count = 3) + self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 13, 3 }) end -- query the tanks of the device @@ -143,7 +143,7 @@ function boilerv.new(session_id, unit_id, advert, out_queue) elseif txn_type == TXN_TYPES.BUILD then -- build response -- load in data if correct length - if m_pkt.length == 13 then + if m_pkt.length == 12 then self.db.build.last_update = util.time_ms() self.db.build.length = m_pkt.data[1] self.db.build.width = m_pkt.data[2] @@ -157,7 +157,6 @@ function boilerv.new(session_id, unit_id, advert, out_queue) self.db.build.ccoolant_cap = m_pkt.data[10] self.db.build.superheaters = m_pkt.data[11] self.db.build.max_boil_rate = m_pkt.data[12] - self.db.build.env_loss = m_pkt.data[13] self.has_build = true out_queue.push_command(unit_session.RTU_US_CMDS.BUILD_CHANGED) @@ -167,10 +166,11 @@ function boilerv.new(session_id, unit_id, advert, out_queue) elseif txn_type == TXN_TYPES.STATE then -- state response -- load in data if correct length - if m_pkt.length == 2 then + if m_pkt.length == 3 then self.db.state.last_update = util.time_ms() self.db.state.temperature = m_pkt.data[1] self.db.state.boil_rate = m_pkt.data[2] + self.db.state.env_loss = m_pkt.data[3] else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 99decc7..3886aab 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.7.7" +local SUPERVISOR_VERSION = "beta-v0.7.8" local print = util.print local println = util.println From c93a386e74d0471f4b0273417e047a3dc70f811c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 17 Nov 2022 11:20:53 -0500 Subject: [PATCH 442/587] #127 adjusted annunciator rate/feed checks --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 32 ++++------- supervisor/session/unit.lua | 65 ++++++++++++++++------- supervisor/startup.lua | 2 +- 4 files changed, 59 insertions(+), 42 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 1289dd8..36df275 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -17,7 +17,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.12" +local COORDINATOR_VERSION = "alpha-v0.6.13" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 5568611..34850ef 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -84,16 +84,6 @@ local function init(parent, id) DataIndicator{parent=main,x=21,label="",format="%6.2f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} main.line_break() - -- TextBox{parent=main,text="FL",x=21,y=19,height=1,width=2,fg_bg=style.label} - -- TextBox{parent=main,text="WS",x=24,y=19,height=1,width=2,fg_bg=style.label} - -- TextBox{parent=main,text="CL",x=28,y=19,height=1,width=2,fg_bg=style.label} - -- TextBox{parent=main,text="HC",x=31,y=19,height=1,width=2,fg_bg=style.label} - - -- local fuel = VerticalBar{parent=main,x=21,y=12,fg_bg=cpair(colors.black,colors.gray),height=6,width=2} - -- local waste = VerticalBar{parent=main,x=24,y=12,fg_bg=cpair(colors.brown,colors.gray),height=6,width=2} - -- local ccool = VerticalBar{parent=main,x=28,y=12,fg_bg=cpair(colors.lightBlue,colors.gray),height=6,width=2} - -- local hcool = VerticalBar{parent=main,x=31,y=12,fg_bg=cpair(colors.orange,colors.gray),height=6,width=2} - -- annunciator -- local annunciator = Div{parent=main,x=34,y=3} @@ -164,14 +154,14 @@ local function init(parent, id) annunciator.line_break() -- cooling - local c_brm = IndicatorLight{parent=annunciator,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)} local c_cfm = IndicatorLight{parent=annunciator,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_brm = IndicatorLight{parent=annunciator,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)} local c_sfm = IndicatorLight{parent=annunciator,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} local c_mwrf = IndicatorLight{parent=annunciator,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} local c_tbnt = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - r_ps.subscribe("BoilRateMismatch", c_brm.update) r_ps.subscribe("CoolantFeedMismatch", c_cfm.update) + r_ps.subscribe("BoilRateMismatch", c_brm.update) r_ps.subscribe("SteamFeedMismatch", c_sfm.update) r_ps.subscribe("MaxWaterReturnFeed", c_mwrf.update) r_ps.subscribe("TurbineTrip", c_tbnt.update) @@ -249,6 +239,15 @@ local function init(parent, id) -- reactor controls -- + local burn_control = Div{parent=main,x=2,y=22,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} + local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} + TextBox{parent=burn_control,x=9,y=2,text="mB/t"} + + local set_burn = function () unit.set_burn(burn_rate.get_value()) end + PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} + + r_ps.subscribe("burn_rate", function (v) burn_rate.set_value(v) end) + local dis_colors = cpair(colors.white, colors.lightGray) local start = HazardButton{parent=main,x=2,y=26,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=scram_fg_bg} @@ -270,15 +269,6 @@ local function init(parent, id) r_ps.subscribe("rps_tripped", start_button_en_check) r_ps.subscribe("rps_tripped", function (active) if active then reset.enable() else reset.disable() end end) - local burn_control = Div{parent=main,x=2,y=22,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} - local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} - TextBox{parent=burn_control,x=9,y=2,text="mB/t"} - - local set_burn = function () unit.set_burn(burn_rate.get_value()) end - PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} - - r_ps.subscribe("burn_rate", function (v) burn_rate.set_value(v) end) - local opts = { { text = "Auto", diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 86df0dd..b8e9463 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -95,7 +95,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) if self.deltas[key] then local data = self.deltas[key] - if time ~= data.last_t then + if time > data.last_t then data.dt = (value - data.last_v) / (time - data.last_t) data.last_v = value @@ -168,6 +168,9 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- update deltas _dt__compute_all() + -- variables for boiler, or reactor if no boilers used + local total_boil_rate = 0.0 + ------------- -- REACTOR -- ------------- @@ -192,10 +195,15 @@ function unit.new(for_reactor, num_boilers, num_turbines) self.db.annunciator.RCSFlowLow = plc_db.mek_status.ccool_fill < 0.75 or plc_db.mek_status.hcool_fill > 0.25 self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > 1000 self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > 100 - self.db.annunciator.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < 0.0 or plc_db.mek_status.fuel_fill <= 0.01 - self.db.annunciator.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 0.0 or plc_db.mek_status.waste_fill >= 0.85 + self.db.annunciator.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < -1.0 or plc_db.mek_status.fuel_fill <= 0.01 + self.db.annunciator.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 1.0 or plc_db.mek_status.waste_fill >= 0.85 ---@todo this is dependent on setup, i.e. how much coolant is buffered and the turbine setup self.db.annunciator.HighStartupRate = not plc_db.mek_status.status and plc_db.mek_status.burn_rate > 40 + + -- if no boilers, use reactor heating rate to check for boil rate mismatch + if self.counts.boilers == 0 then + total_boil_rate = plc_db.mek_status.heating_rate + end end ------------- @@ -206,10 +214,10 @@ function unit.new(for_reactor, num_boilers, num_turbines) for i = 1, self.counts.boilers do self.db.annunciator.BoilerOnline[i] = false end -- aggregated statistics - local total_boil_rate = 0.0 local boiler_steam_dt_sum = 0.0 local boiler_water_dt_sum = 0.0 + if self.counts.boilers > 0 then -- go through boilers for stats and online for i = 1, #self.boilers do local session = self.boilers[i] ---@type unit_session @@ -223,7 +231,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) end -- check heating rate low - if self.plc_s ~= nil then + if self.plc_s ~= nil and #self.boilers > 0 then local r_db = self.plc_i.get_db() -- check for inactive boilers while reactor is active @@ -238,25 +246,41 @@ function unit.new(for_reactor, num_boilers, num_turbines) self.db.annunciator.HeatingRateLow[idx] = false end end - - -- check for rate mismatch - local expected_boil_rate = r_db.mek_status.heating_rate / 10.0 - self.db.annunciator.BoilRateMismatch = math.abs(expected_boil_rate - total_boil_rate) > 25.0 + end + else + boiler_steam_dt_sum = _get_dt(DT_KEYS.ReactorHCool) + boiler_water_dt_sum = _get_dt(DT_KEYS.ReactorCCool) end - -- check coolant feed mismatch - local cfmismatch = false - for i = 1, #self.boilers do - local boiler = self.boilers[i] ---@type unit_session - local idx = boiler.get_device_idx() - local db = boiler.get_db() ---@type boilerv_session_db + --------------------------- + -- COOLANT FEED MISMATCH -- + --------------------------- - local gaining_hc = _get_dt(DT_KEYS.BoilerHCool .. idx) > 0 or db.tanks.hcool_fill == 1 + -- check coolant feed mismatch if using boilers, otherwise calculate with reactor + local cfmismatch = false + + if self.counts.boilers > 0 then + for i = 1, #self.boilers do + local boiler = self.boilers[i] ---@type unit_session + local idx = boiler.get_device_idx() + local db = boiler.get_db() ---@type boilerv_session_db + + local gaining_hc = _get_dt(DT_KEYS.BoilerHCool .. idx) > 10.0 or db.tanks.hcool_fill == 1 -- gaining heated coolant cfmismatch = cfmismatch or gaining_hc -- losing cooled coolant - cfmismatch = cfmismatch or _get_dt(DT_KEYS.BoilerCCool .. idx) < 0 or (gaining_hc and db.tanks.ccool_fill == 0) + cfmismatch = cfmismatch or _get_dt(DT_KEYS.BoilerCCool .. idx) < -10.0 or (gaining_hc and db.tanks.ccool_fill == 0) + end + elseif self.plc_s ~= nil then + local r_db = self.plc_i.get_db() + + local gaining_hc = _get_dt(DT_KEYS.ReactorHCool) > 10.0 or r_db.mek_status.hcool_fill == 1 + + -- gaining heated coolant (steam) + cfmismatch = cfmismatch or gaining_hc + -- losing cooled coolant (water) + cfmismatch = cfmismatch or _get_dt(DT_KEYS.ReactorCCool) < -10.0 or (gaining_hc and r_db.mek_status.ccool_fill == 0) end self.db.annunciator.CoolantFeedMismatch = cfmismatch @@ -285,9 +309,12 @@ function unit.new(for_reactor, num_boilers, num_turbines) self.db.annunciator.TurbineOnline[session.get_device_idx()] = true end + -- check for boil rate mismatch (either between reactor and turbine or boiler and turbine) + self.db.annunciator.BoilRateMismatch = math.abs(total_boil_rate - total_input_rate) > 4 + -- check for steam feed mismatch and max return rate local sfmismatch = math.abs(total_flow_rate - total_input_rate) > 10 - sfmismatch = sfmismatch or boiler_steam_dt_sum > 0 or boiler_water_dt_sum < 0 + sfmismatch = sfmismatch or boiler_steam_dt_sum > 2.0 or boiler_water_dt_sum < -2.0 self.db.annunciator.SteamFeedMismatch = sfmismatch self.db.annunciator.MaxWaterReturnFeed = max_water_return_rate == total_flow_rate and total_flow_rate ~= 0 @@ -312,7 +339,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) local db = turbine.get_db() ---@type turbinev_session_db local idx = turbine.get_device_idx() - self.db.annunciator.TurbineOverSpeed[idx] = (db.state.flow_rate == db.build.max_flow_rate) and (_get_dt(DT_KEYS.TurbineSteam .. idx) > 0) + self.db.annunciator.TurbineOverSpeed[idx] = (db.state.flow_rate == db.build.max_flow_rate) and (_get_dt(DT_KEYS.TurbineSteam .. idx) > 0.0) end --[[ diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 3886aab..95a050f 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.7.8" +local SUPERVISOR_VERSION = "beta-v0.7.9" local print = util.print local println = util.println From 9c32074b56c53dfc8e2b8855e2357fb8e6bc323c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 17 Nov 2022 11:58:14 -0500 Subject: [PATCH 443/587] #128 limit max burn rate control to actual max burn rate --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 1 + .../elements/controls/spinbox_numeric.lua | 59 ++++++++++++++----- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 36df275..9564160 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -17,7 +17,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.13" +local COORDINATOR_VERSION = "alpha-v0.6.14" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 34850ef..26b2074 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -247,6 +247,7 @@ local function init(parent, id) PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} r_ps.subscribe("burn_rate", function (v) burn_rate.set_value(v) end) + r_ps.subscribe("max_burn", function (v) burn_rate.set_max(v) end) local dis_colors = cpair(colors.white, colors.lightGray) diff --git a/graphics/elements/controls/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua index 23065e2..10f74bd 100644 --- a/graphics/elements/controls/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -6,6 +6,8 @@ local util = require("scada-common.util") ---@class spinbox_args ---@field default? number default value, defaults to 0.0 +---@field min? number default 0, currently must be 0 or greater +---@field max? number default max number that can be displayed with the digits configuration ---@field whole_num_precision integer number of whole number digits ---@field fractional_precision integer number of fractional digits ---@field arrow_fg_bg cpair arrow foreground/background colors @@ -61,6 +63,7 @@ local function spinbox(args) local function set_digits() local initial_str = util.sprintf(fmt_init, e.value) + digits = {} ---@diagnostic disable-next-line: discard-returns initial_str:gsub("%d", function (char) table.insert(digits, char) end) end @@ -85,13 +88,23 @@ local function spinbox(args) if e.value < 0 then for i = 1, #digits do digits[i] = 0 end e.value = 0 - -- max printable - elseif string.len(util.sprintf(fmt, e.value)) > args.width then - -- max out - for i = 1, #digits do digits[i] = 9 end - - -- re-update value - update_value() + -- min configured + elseif (type(args.min) == "number") and (e.value < args.min) then + -- cap at min + e.value = args.min + set_digits() + else + -- max printable + if string.len(util.sprintf(fmt, e.value)) > args.width then + -- max out to all 9s + for i = 1, #digits do digits[i] = 9 end + update_value() + -- max configured + elseif (type(args.max) == "number") and (e.value > args.max) then + -- cap at max + e.value = args.max + set_digits() + end end -- draw @@ -110,16 +123,18 @@ local function spinbox(args) -- only handle if on an increment or decrement arrow if e.enabled and event.x ~= dec_point_x then local idx = util.trinary(event.x > dec_point_x, event.x - 1, event.x) - if event.y == 1 then - -- increment - digits[idx] = digits[idx] + 1 - elseif event.y == 3 then - -- decrement - digits[idx] = digits[idx] - 1 - end + if digits[idx] ~= nil then + if event.y == 1 then + -- increment + digits[idx] = digits[idx] + 1 + elseif event.y == 3 then + -- decrement + digits[idx] = digits[idx] - 1 + end - update_value() - show_num() + update_value() + show_num() + end end end @@ -132,6 +147,18 @@ local function spinbox(args) show_num() end + -- set minimum input value + ---@param min integer minimum allowed value + function e.set_min(min) + if min >= 0 then args.min = min end + end + + -- set maximum input value + ---@param max integer maximum allowed value + function e.set_max(max) + args.max = max + end + return e.get() end From 29793ba7c4151bd249893eabaa3c10fc39298a12 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 17 Nov 2022 12:00:00 -0500 Subject: [PATCH 444/587] #128 element changes and show number after setting min/max for spinbox --- coordinator/startup.lua | 2 +- graphics/element.lua | 22 +++++++++++++++++++ .../elements/controls/spinbox_numeric.lua | 6 ++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 9564160..bdb0a17 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -17,7 +17,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.14" +local COORDINATOR_VERSION = "alpha-v0.6.15" local print = util.print local println = util.println diff --git a/graphics/element.lua b/graphics/element.lua index 33decff..d987a7a 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -178,6 +178,16 @@ function element.new(args) function protected.set_value(value) end + -- set minimum input value + ---@param min integer minimum allowed value + function protected.set_min(min) + end + + -- set maximum input value + ---@param max integer maximum allowed value + function protected.set_max(max) + end + -- enable the control function protected.enable() end @@ -316,6 +326,18 @@ function element.new(args) protected.set_value(value) end + -- set minimum input value + ---@param min integer minimum allowed value + function public.set_min(min) + protected.set_min(min) + end + + -- set maximum input value + ---@param max integer maximum allowed value + function public.set_max(max) + protected.set_max(max) + end + -- enable the element function public.enable() protected.enabled = true diff --git a/graphics/elements/controls/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua index 10f74bd..3bce169 100644 --- a/graphics/elements/controls/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -150,13 +150,17 @@ local function spinbox(args) -- set minimum input value ---@param min integer minimum allowed value function e.set_min(min) - if min >= 0 then args.min = min end + if min >= 0 then + args.min = min + show_num() + end end -- set maximum input value ---@param max integer maximum allowed value function e.set_max(max) args.max = max + show_num() end return e.get() From 657cd15c5930ffa2350efce9af36aa29361ae985 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 17 Nov 2022 12:04:30 -0500 Subject: [PATCH 445/587] #127 uncommitted changes for annunciator changes --- supervisor/session/unit.lua | 60 ++++++++++++++++++------------------- supervisor/startup.lua | 2 +- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index b8e9463..2b80cb1 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -218,35 +218,35 @@ function unit.new(for_reactor, num_boilers, num_turbines) local boiler_water_dt_sum = 0.0 if self.counts.boilers > 0 then - -- go through boilers for stats and online - for i = 1, #self.boilers do - local session = self.boilers[i] ---@type unit_session - local boiler = session.get_db() ---@type boilerv_session_db - - total_boil_rate = total_boil_rate + boiler.state.boil_rate - boiler_steam_dt_sum = _get_dt(DT_KEYS.BoilerSteam .. self.boilers[i].get_device_idx()) - boiler_water_dt_sum = _get_dt(DT_KEYS.BoilerWater .. self.boilers[i].get_device_idx()) - - self.db.annunciator.BoilerOnline[session.get_device_idx()] = true - end - - -- check heating rate low - if self.plc_s ~= nil and #self.boilers > 0 then - local r_db = self.plc_i.get_db() - - -- check for inactive boilers while reactor is active + -- go through boilers for stats and online for i = 1, #self.boilers do - local boiler = self.boilers[i] ---@type unit_session - local idx = boiler.get_device_idx() - local db = boiler.get_db() ---@type boilerv_session_db + local session = self.boilers[i] ---@type unit_session + local boiler = session.get_db() ---@type boilerv_session_db - if r_db.mek_status.status then - self.db.annunciator.HeatingRateLow[idx] = db.state.boil_rate == 0 - else - self.db.annunciator.HeatingRateLow[idx] = false + total_boil_rate = total_boil_rate + boiler.state.boil_rate + boiler_steam_dt_sum = _get_dt(DT_KEYS.BoilerSteam .. self.boilers[i].get_device_idx()) + boiler_water_dt_sum = _get_dt(DT_KEYS.BoilerWater .. self.boilers[i].get_device_idx()) + + self.db.annunciator.BoilerOnline[session.get_device_idx()] = true + end + + -- check heating rate low + if self.plc_s ~= nil and #self.boilers > 0 then + local r_db = self.plc_i.get_db() + + -- check for inactive boilers while reactor is active + for i = 1, #self.boilers do + local boiler = self.boilers[i] ---@type unit_session + local idx = boiler.get_device_idx() + local db = boiler.get_db() ---@type boilerv_session_db + + if r_db.mek_status.status then + self.db.annunciator.HeatingRateLow[idx] = db.state.boil_rate == 0 + else + self.db.annunciator.HeatingRateLow[idx] = false + end end end - end else boiler_steam_dt_sum = _get_dt(DT_KEYS.ReactorHCool) boiler_water_dt_sum = _get_dt(DT_KEYS.ReactorCCool) @@ -260,16 +260,16 @@ function unit.new(for_reactor, num_boilers, num_turbines) local cfmismatch = false if self.counts.boilers > 0 then - for i = 1, #self.boilers do + for i = 1, #self.boilers do local boiler = self.boilers[i] ---@type unit_session - local idx = boiler.get_device_idx() + local idx = boiler.get_device_idx() local db = boiler.get_db() ---@type boilerv_session_db local gaining_hc = _get_dt(DT_KEYS.BoilerHCool .. idx) > 10.0 or db.tanks.hcool_fill == 1 - -- gaining heated coolant - cfmismatch = cfmismatch or gaining_hc - -- losing cooled coolant + -- gaining heated coolant + cfmismatch = cfmismatch or gaining_hc + -- losing cooled coolant cfmismatch = cfmismatch or _get_dt(DT_KEYS.BoilerCCool .. idx) < -10.0 or (gaining_hc and db.tanks.ccool_fill == 0) end elseif self.plc_s ~= nil then diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 95a050f..6a5db92 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.7.9" +local SUPERVISOR_VERSION = "beta-v0.7.10" local print = util.print local println = util.println From 3685e25713ff9ab59ed01246962200d05a5708af Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 21 Nov 2022 21:32:45 -0500 Subject: [PATCH 446/587] likely finalized color palette, removed color map from unit displays --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 4 ---- coordinator/ui/style.lua | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index bdb0a17..4becc59 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -17,7 +17,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.15" +local COORDINATOR_VERSION = "alpha-v0.6.16" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 26b2074..c036827 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -299,10 +299,6 @@ local function init(parent, id) MultiButton{parent=waste_sel,x=1,y=1,options=opts,callback=unit.set_waste,min_width=6,fg_bg=cpair(colors.black, colors.white)} TextBox{parent=waste_sel,text="Waste Processing",alignment=TEXT_ALIGN.CENTER,x=1,y=1,height=1} - ---@fixme test code - main.line_break() - ColorMap{parent=main,x=2,y=51} - return main end diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index 67453c7..509e87a 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -15,7 +15,7 @@ style.colors = { { c = colors.red, hex = 0xdf4949 }, { c = colors.orange, hex = 0xffb659 }, { c = colors.yellow, hex = 0xfffc79 }, - { c = colors.lime, hex = 0x64dd20 }, + { c = colors.lime, hex = 0x80ff80 }, { c = colors.green, hex = 0x4aee8a }, { c = colors.cyan, hex = 0x34bac8 }, { c = colors.lightBlue, hex = 0x6cc0f2 }, From 5628df56a2d98854b351e837dfb6eec2413d621b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 24 Nov 2022 14:20:11 -0500 Subject: [PATCH 447/587] removed hardcoded push button padding --- graphics/elements/controls/push_button.lua | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/graphics/elements/controls/push_button.lua b/graphics/elements/controls/push_button.lua index d4fbcaa..397a367 100644 --- a/graphics/elements/controls/push_button.lua +++ b/graphics/elements/controls/push_button.lua @@ -24,13 +24,12 @@ local function push_button(args) assert(type(args.text) == "string", "graphics.elements.controls.push_button: text is a required field") assert(type(args.callback) == "function", "graphics.elements.controls.push_button: callback is a required field") - -- single line - args.height = 1 - - args.min_width = args.min_width or 0 - local text_width = string.len(args.text) - args.width = math.max(text_width + 2, args.min_width) + + -- single line height, calculate width + args.height = 1 + args.min_width = args.min_width or 0 + args.width = math.max(text_width, args.min_width) -- create new graphics element base object local e = element.new(args) From f68c38ccee33b74cb5f78c4649378ef0d188ea73 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 24 Nov 2022 22:49:35 -0500 Subject: [PATCH 448/587] cleanup of requires --- coordinator/renderer.lua | 2 -- coordinator/startup.lua | 2 +- graphics/elements/controls/multi_button.lua | 4 ++-- graphics/elements/controls/spinbox_numeric.lua | 4 ++-- graphics/elements/indicators/light.lua | 3 ++- graphics/flasher.lua | 2 +- 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index fe81f5e..e66fb99 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -1,8 +1,6 @@ local log = require("scada-common.log") local flasher = require("graphics.flasher") -local iocontrol = require("coordinator.iocontrol") - local style = require("coordinator.ui.style") local main_view = require("coordinator.ui.layout.main_view") diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 4becc59..6cc3481 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -17,7 +17,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.16" +local COORDINATOR_VERSION = "alpha-v0.6.17" local print = util.print local println = util.println diff --git a/graphics/elements/controls/multi_button.lua b/graphics/elements/controls/multi_button.lua index 1dd39b4..4948b33 100644 --- a/graphics/elements/controls/multi_button.lua +++ b/graphics/elements/controls/multi_button.lua @@ -1,9 +1,9 @@ -- Button Graphics Element -local element = require("graphics.element") - local util = require("scada-common.util") +local element = require("graphics.element") + ---@class button_option ---@field text string ---@field fg_bg cpair diff --git a/graphics/elements/controls/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua index 3bce169..bfb1552 100644 --- a/graphics/elements/controls/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -1,9 +1,9 @@ -- Spinbox Numeric Graphics Element -local element = require("graphics.element") - local util = require("scada-common.util") +local element = require("graphics.element") + ---@class spinbox_args ---@field default? number default value, defaults to 0.0 ---@field min? number default 0, currently must be 0 or greater diff --git a/graphics/elements/indicators/light.lua b/graphics/elements/indicators/light.lua index 0c4c340..3695553 100644 --- a/graphics/elements/indicators/light.lua +++ b/graphics/elements/indicators/light.lua @@ -1,8 +1,9 @@ -- Indicator Light Graphics Element +local util = require("scada-common.util") + local element = require("graphics.element") local flasher = require("graphics.flasher") -local util = require("scada-common.util") ---@class indicator_light_args ---@field label string indicator label diff --git a/graphics/flasher.lua b/graphics/flasher.lua index 12c6670..b5eed69 100644 --- a/graphics/flasher.lua +++ b/graphics/flasher.lua @@ -2,7 +2,7 @@ -- Indicator Light Flasher -- -local tcd = require("scada-common.tcallbackdsp") +local tcd = require("scada-common.tcallbackdsp") local flasher = {} From d4ae18eee7bb2dce8b807f029ce620ec74bd0c72 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 26 Nov 2022 16:18:31 -0500 Subject: [PATCH 449/587] #10 #133 alarm system logic and display, change to comms to support alarm actions, get_x get_y to graphics elements, bugfixes to coord establish and rtu establish, flashing trilight and alarm light indicators --- coordinator/coordinator.lua | 6 + coordinator/iocontrol.lua | 83 +++++- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 234 +++++++++++++---- graphics/element.lua | 13 + graphics/elements/indicators/alight.lua | 113 ++++++++ graphics/elements/indicators/trilight.lua | 51 +++- reactor-plc/startup.lua | 2 +- rtu/rtu.lua | 2 +- rtu/startup.lua | 2 +- scada-common/alarm.lua | 73 ------ scada-common/comms.lua | 7 +- scada-common/types.lua | 70 +++++ supervisor/session/coordinator.lua | 19 +- supervisor/session/unit.lua | 305 +++++++++++++++++++++- supervisor/startup.lua | 2 +- 16 files changed, 824 insertions(+), 160 deletions(-) create mode 100644 graphics/elements/indicators/alight.lua delete mode 100644 scada-common/alarm.lua diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 2a211cb..5662cd6 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -421,6 +421,8 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa unit.set_burn_ack(ack) elseif cmd == CRDN_COMMANDS.SET_WASTE then unit.set_waste_ack(ack) + elseif cmd == CRDN_COMMANDS.ACK_ALL_ALARMS then + unit.ack_alarms_ack(ack) else log.debug(util.c("received command ack with unknown command ", cmd)) end @@ -474,6 +476,10 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa else log.debug("supervisor connection denied") end + elseif packet.length == 1 and packet.data[1] == ESTABLISH_ACK.DENY then + log.debug("supervisor connection denied") + elseif packet.length == 1 and packet.data[1] == ESTABLISH_ACK.COLLISION then + log.debug("supervisor connection denied due to collision") else log.debug("SCADA_MGMT establish packet length mismatch") end diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 8f461cc..67727cb 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -1,6 +1,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local psil = require("scada-common.psil") +local types = require("scada-common.types") local util = require("scada-common.util") local CRDN_COMMANDS = comms.CRDN_COMMANDS @@ -22,9 +23,19 @@ function iocontrol.init(conf, comms) io.units = {} for i = 1, conf.num_units do + local function ack(alarm) + comms.send_command(CRDN_COMMANDS.ACK_ALARM, i, alarm) + log.debug(util.c("UNIT[", i, "]: ACK ALARM ", alarm)) + end + + local function reset(alarm) + comms.send_command(CRDN_COMMANDS.RESET_ALARM, i, alarm) + log.debug(util.c("UNIT[", i, "]: RESET ALARM ", alarm)) + end + ---@class ioctl_entry local entry = { - unit_id = i, ---@type integer + unit_id = i, ---@type integer initialized = false, num_boilers = 0, @@ -37,17 +48,36 @@ function iocontrol.init(conf, comms) start = function () end, scram = function () end, reset_rps = function () end, - set_burn = function (rate) end, - set_waste = function (mode) end, + ack_alarms = function () end, + set_burn = function (rate) end, ---@param rate number + set_waste = function (mode) end, ---@param mode integer - start_ack = function (success) end, - scram_ack = function (success) end, - reset_rps_ack = function (success) end, - set_burn_ack = function (success) end, - set_waste_ack = function (success) end, + start_ack = function (success) end, ---@param success boolean + scram_ack = function (success) end, ---@param success boolean + reset_rps_ack = function (success) end, ---@param success boolean + ack_alarms_ack = function (success) end,---@param success boolean + set_burn_ack = function (success) end, ---@param success boolean + set_waste_ack = function (success) end, ---@param success boolean + + alarm_callbacks = { + c_breach = { ack = function () ack(1) end, reset = function () reset(1) end }, + radiation = { ack = function () ack(2) end, reset = function () reset(2) end }, + dmg_crit = { ack = function () ack(3) end, reset = function () reset(3) end }, + r_lost = { ack = function () ack(4) end, reset = function () reset(4) end }, + damage = { ack = function () ack(5) end, reset = function () reset(5) end }, + over_temp = { ack = function () ack(6) end, reset = function () reset(6) end }, + high_temp = { ack = function () ack(7) end, reset = function () reset(7) end }, + waste_leak = { ack = function () ack(8) end, reset = function () reset(8) end }, + waste_high = { ack = function () ack(9) end, reset = function () reset(9) end }, + rps_trans = { ack = function () ack(10) end, reset = function () reset(10) end }, + rcs_trans = { ack = function () ack(11) end, reset = function () reset(11) end }, + t_trip = { ack = function () ack(12) end, reset = function () reset(12) end } + }, + + alarms = {}, ---@type alarms reactor_ps = psil.create(), - reactor_data = {}, ---@type reactor_db + reactor_data = {}, ---@type reactor_db boiler_ps_tbl = {}, boiler_data_tbl = {}, @@ -73,6 +103,11 @@ function iocontrol.init(conf, comms) log.debug(util.c("UNIT[", i, "]: RESET_RPS")) end + function entry.ack_alarms() + comms.send_command(CRDN_COMMANDS.ACK_ALL_ALARMS, i) + log.debug(util.c("UNIT[", i, "]: ACK_ALL_ALARMS")) + end + function entry.set_burn(rate) comms.send_command(CRDN_COMMANDS.SET_BURN, i, rate) log.debug(util.c("UNIT[", i, "]: SET_BURN = ", rate)) @@ -167,7 +202,10 @@ end ---@param statuses table ---@return boolean valid function iocontrol.update_statuses(statuses) - if #statuses ~= #io.units then + if type(statuses) ~= "table" then + log.error("unit statuses not a table") + return false + elseif #statuses ~= #io.units then log.error("number of provided unit statuses does not match expected number of units") return false else @@ -175,6 +213,11 @@ function iocontrol.update_statuses(statuses) local unit = io.units[i] ---@type ioctl_entry local status = statuses[i] + if type(status) ~= "table" or #status ~= 4 then + log.error("invalid status entry in unit statuses (not a table or invalid length)") + return false + end + -- reactor PLC status local reactor_status = status[1] @@ -253,7 +296,7 @@ function iocontrol.update_statuses(statuses) end unit.reactor_ps.publish("TurbineTrip", any) - elseif key == "BoilerOnline" or key == "HeatingRateLow" then + elseif key == "BoilerOnline" or key == "HeatingRateLow" or key == "WaterLevelLow" then -- split up array for all boilers for id = 1, #val do unit.boiler_ps_tbl[id].publish(key, val[id]) @@ -272,9 +315,25 @@ function iocontrol.update_statuses(statuses) end end + -- alarms + + local alarm_states = status[3] + + for id = 1, #alarm_states do + local state = alarm_states[id] + + if state == types.ALARM_STATE.TRIPPED or state == types.ALARM_STATE.ACKED then + unit.reactor_ps.publish("ALM" .. id, 2) + elseif state == types.ALARM_STATE.RING_BACK then + unit.reactor_ps.publish("ALM" .. id, 3) + else + unit.reactor_ps.publish("ALM" .. id, 1) + end + end + -- RTU statuses - local rtu_statuses = status[3] + local rtu_statuses = status[4] if type(rtu_statuses) == "table" then if type(rtu_statuses.boilers) == "table" then diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 6cc3481..b82fa4f 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -17,7 +17,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.17" +local COORDINATOR_VERSION = "alpha-v0.7.0" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index c036827..d72b9fc 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -2,8 +2,6 @@ -- Reactor Unit SCADA Coordinator GUI -- -local tcallbackdsp = require("scada-common.tcallbackdsp") - local iocontrol = require("coordinator.iocontrol") local style = require("coordinator.ui.style") @@ -12,8 +10,8 @@ local core = require("graphics.core") local Div = require("graphics.elements.div") local TextBox = require("graphics.elements.textbox") -local ColorMap = require("graphics.elements.colormap") +local AlarmLight = require("graphics.elements.indicators.alight") local CoreMap = require("graphics.elements.indicators.coremap") local DataIndicator = require("graphics.elements.indicators.data") local IndicatorLight = require("graphics.elements.indicators.light") @@ -30,6 +28,29 @@ local cpair = core.graphics.cpair local period = core.flasher.PERIOD +local waste_opts = { + { + text = "Auto", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.white, colors.gray) + }, + { + text = "Pu", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.black, colors.green) + }, + { + text = "Po", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.black, colors.cyan) + }, + { + text = "AM", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.black, colors.purple) + } +} + -- create a unit view ---@param parent graphics_element parent ---@param id integer @@ -43,10 +64,12 @@ local function init(parent, id) TextBox{parent=main,text="Reactor Unit #" .. id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} - local scram_fg_bg = cpair(colors.white, colors.gray) - local lu_cpair = cpair(colors.gray, colors.gray) + local hzd_fg_bg = cpair(colors.white, colors.gray) + local lu_cpair = cpair(colors.gray, colors.gray) + ----------------------------- -- main stats and core map -- + ----------------------------- local core_map = CoreMap{parent=main,x=2,y=3,reactor_l=18,reactor_w=18} r_ps.subscribe("temp", core_map.update) @@ -84,18 +107,20 @@ local function init(parent, id) DataIndicator{parent=main,x=21,label="",format="%6.2f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} main.line_break() + ----------------- -- annunciator -- + ----------------- - local annunciator = Div{parent=main,x=34,y=3} + -- annunciator colors (generally) per IAEA-TECDOC-812 recommendations - -- annunciator colors per IAEA-TECDOC-812 recommendations + local annunciator = Div{parent=main,x=35,y=3} -- connectivity/basic state local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(colors.green,colors.red)} local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=cpair(colors.white,colors.gray)} local r_active = IndicatorLight{parent=annunciator,label="Active",colors=cpair(colors.green,colors.gray)} ---@todo auto control as info sent here - local r_auto = IndicatorLight{parent=annunciator,label="Auto Control",colors=cpair(colors.blue,colors.gray)} + local r_auto = IndicatorLight{parent=annunciator,label="Auto. Control",colors=cpair(colors.blue,colors.gray)} r_ps.subscribe("PLCOnline", plc_online.update) r_ps.subscribe("PLCHeartbeat", plc_hbeat.update) @@ -103,23 +128,25 @@ local function init(parent, id) annunciator.line_break() - -- annunciator fields + -- non-RPS reactor annunciator panel local r_scram = IndicatorLight{parent=annunciator,label="Reactor SCRAM",colors=cpair(colors.red,colors.gray)} local r_mscrm = IndicatorLight{parent=annunciator,label="Manual Reactor SCRAM",colors=cpair(colors.red,colors.gray)} local r_ascrm = IndicatorLight{parent=annunciator,label="Auto Reactor SCRAM",colors=cpair(colors.red,colors.gray)} local r_rtrip = IndicatorLight{parent=annunciator,label="RCP Trip",colors=cpair(colors.red,colors.gray)} local r_cflow = IndicatorLight{parent=annunciator,label="RCS Flow Low",colors=cpair(colors.yellow,colors.gray)} + local r_clow = IndicatorLight{parent=annunciator,label="Coolant Level Low",colors=cpair(colors.yellow,colors.gray)} local r_temp = IndicatorLight{parent=annunciator,label="Reactor Temp. High",colors=cpair(colors.red,colors.gray)} local r_rhdt = IndicatorLight{parent=annunciator,label="Reactor High Delta T",colors=cpair(colors.yellow,colors.gray)} local r_firl = IndicatorLight{parent=annunciator,label="Fuel Input Rate Low",colors=cpair(colors.yellow,colors.gray)} local r_wloc = IndicatorLight{parent=annunciator,label="Waste Line Occlusion",colors=cpair(colors.yellow,colors.gray)} - local r_hsrt = IndicatorLight{parent=annunciator,label="High Startup Rate",colors=cpair(colors.yellow,colors.gray)} + local r_hsrt = IndicatorLight{parent=annunciator,label="Startup Rate High",colors=cpair(colors.yellow,colors.gray)} r_ps.subscribe("ReactorSCRAM", r_scram.update) r_ps.subscribe("ManualReactorSCRAM", r_mscrm.update) r_ps.subscribe("AutoReactorSCRAM", r_ascrm.update) r_ps.subscribe("RCPTrip", r_rtrip.update) r_ps.subscribe("RCSFlowLow", r_cflow.update) + r_ps.subscribe("CoolantLevelLow", r_clow.update) r_ps.subscribe("ReactorTempHigh", r_temp.update) r_ps.subscribe("ReactorHighDeltaT", r_rhdt.update) r_ps.subscribe("FuelInputRateLow", r_firl.update) @@ -128,14 +155,24 @@ local function init(parent, id) annunciator.line_break() - -- RPS + -- RPS annunciator panel + TextBox{parent=main,x=34,y=20,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} + TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} + TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} + TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} + TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} + TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} + TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} + TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} + TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} + TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} local rps_trp = IndicatorLight{parent=annunciator,label="RPS Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} local rps_dmg = IndicatorLight{parent=annunciator,label="Damage Critical",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} local rps_exh = IndicatorLight{parent=annunciator,label="Excess Heated Coolant",colors=cpair(colors.yellow,colors.gray)} local rps_exw = IndicatorLight{parent=annunciator,label="Excess Waste",colors=cpair(colors.yellow,colors.gray)} - local rps_tmp = IndicatorLight{parent=annunciator,label="High Core Temp",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local rps_tmp = IndicatorLight{parent=annunciator,label="Core Temp. High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} local rps_nof = IndicatorLight{parent=annunciator,label="No Fuel",colors=cpair(colors.yellow,colors.gray)} - local rps_noc = IndicatorLight{parent=annunciator,label="No Coolant",colors=cpair(colors.yellow,colors.gray)} + local rps_noc = IndicatorLight{parent=annunciator,label="Coolant Level Low Low",colors=cpair(colors.yellow,colors.gray)} local rps_flt = IndicatorLight{parent=annunciator,label="PPM Fault",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} local rps_tmo = IndicatorLight{parent=annunciator,label="Timeout",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} local rps_sfl = IndicatorLight{parent=annunciator,label="System Failure",colors=cpair(colors.orange,colors.gray),flash=true,period=period.BLINK_500_MS} @@ -153,7 +190,12 @@ local function init(parent, id) annunciator.line_break() - -- cooling + -- cooling annunciator panel + TextBox{parent=main,x=34,y=31,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)} + TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)} + TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)} + TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)} + TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.cyan)} local c_cfm = IndicatorLight{parent=annunciator,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} local c_brm = IndicatorLight{parent=annunciator,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)} local c_sfm = IndicatorLight{parent=annunciator,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} @@ -168,16 +210,31 @@ local function init(parent, id) annunciator.line_break() - -- machine-specific indicators + -- boiler annunciator panel(s) + + local tag_y = 1 + if unit.num_boilers > 0 then - TextBox{parent=main,x=32,y=36,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + tag_y = TextBox{parent=main,x=32,y=37,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y() + local b1_wll = IndicatorLight{parent=annunciator,label="Water Level Low",colors=cpair(colors.red,colors.gray)} + b_ps[1].subscribe("WasterLevelLow", b1_wll.update) + TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)} + + tag_y = TextBox{parent=main,x=32,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y() local b1_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} b_ps[1].subscribe("HeatingRateLow", b1_hr.update) + TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)} end if unit.num_boilers > 1 then - TextBox{parent=main,x=32,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + tag_y = TextBox{parent=main,x=32,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y() + local b2_wll = IndicatorLight{parent=annunciator,label="Water Level Low",colors=cpair(colors.red,colors.gray)} + b_ps[2].subscribe("WasterLevelLow", b2_wll.update) + TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)} + + tag_y = TextBox{parent=main,x=32,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y() local b2_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} b_ps[2].subscribe("HeatingRateLow", b2_hr.update) + TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)} end if unit.num_boilers > 0 then @@ -185,7 +242,14 @@ local function init(parent, id) annunciator.line_break() end - TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + -- turbine annunciator panels + + if unit.num_boilers == 0 then + TextBox{parent=main,x=32,y=37,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + else + TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + end + local t1_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} t_ps[1].subscribe("SteamDumpOpen", function (val) t1_sdo.update(val + 1) end) @@ -193,12 +257,10 @@ local function init(parent, id) local t1_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} t_ps[1].subscribe("TurbineOverSpeed", t1_tos.update) - TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + tag_y = TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y() local t1_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} t_ps[1].subscribe("TurbineTrip", t1_trp.update) - - main.line_break() - annunciator.line_break() + TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.cyan)} if unit.num_turbines > 1 then TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} @@ -209,12 +271,10 @@ local function init(parent, id) local t2_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} t_ps[2].subscribe("TurbineOverSpeed", t2_tos.update) - TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + tag_y = TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y() local t2_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} t_ps[2].subscribe("TurbineTrip", t2_trp.update) - - main.line_break() - annunciator.line_break() + TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.cyan)} end if unit.num_turbines > 2 then @@ -226,18 +286,20 @@ local function init(parent, id) local t3_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} t_ps[3].subscribe("TurbineOverSpeed", t3_tos.update) - TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + tag_y = TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y() local t3_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} t_ps[3].subscribe("TurbineTrip", t3_trp.update) - - annunciator.line_break() + TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.cyan)} end + annunciator.line_break() + ---@todo radiation monitor IndicatorLight{parent=annunciator,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)} - IndicatorLight{parent=annunciator,label="Radiation Alarm",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + ---------------------- -- reactor controls -- + ---------------------- local burn_control = Div{parent=main,x=2,y=22,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} @@ -251,13 +313,15 @@ local function init(parent, id) local dis_colors = cpair(colors.white, colors.lightGray) - local start = HazardButton{parent=main,x=2,y=26,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=scram_fg_bg} - local scram = HazardButton{parent=main,x=12,y=26,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=scram_fg_bg} - local reset = HazardButton{parent=main,x=22,y=26,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,fg_bg=scram_fg_bg} + local start = HazardButton{parent=main,x=22,y=22,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=hzd_fg_bg} + local ack_a = HazardButton{parent=main,x=12,y=26,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=unit.ack_alarms,fg_bg=hzd_fg_bg} + local scram = HazardButton{parent=main,x=2,y=26,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=hzd_fg_bg} + local reset = HazardButton{parent=main,x=22,y=26,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,fg_bg=hzd_fg_bg} unit.start_ack = start.on_response unit.scram_ack = scram.on_response unit.reset_rps_ack = reset.on_response + unit.ack_alarms_ack = ack_a.on_response local function start_button_en_check() if (unit.reactor_data ~= nil) and (unit.reactor_data.mek_status ~= nil) then @@ -270,35 +334,89 @@ local function init(parent, id) r_ps.subscribe("rps_tripped", start_button_en_check) r_ps.subscribe("rps_tripped", function (active) if active then reset.enable() else reset.disable() end end) - local opts = { - { - text = "Auto", - fg_bg = cpair(colors.black, colors.lightGray), - active_fg_bg = cpair(colors.white, colors.gray) - }, - { - text = "Pu", - fg_bg = cpair(colors.black, colors.lightGray), - active_fg_bg = cpair(colors.black, colors.lime) - }, - { - text = "Po", - fg_bg = cpair(colors.black, colors.lightGray), - active_fg_bg = cpair(colors.black, colors.cyan) - }, - { - text = "AM", - fg_bg = cpair(colors.black, colors.lightGray), - active_fg_bg = cpair(colors.black, colors.purple) - } - } + TextBox{parent=main,x=2,y=30,text="Idle",width=29,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} - ---@todo waste selection - local waste_sel = Div{parent=main,x=2,y=48,width=29,height=2,fg_bg=cpair(colors.black, colors.white)} + local waste_sel = Div{parent=main,x=2,y=50,width=29,height=2,fg_bg=cpair(colors.black, colors.white)} - MultiButton{parent=waste_sel,x=1,y=1,options=opts,callback=unit.set_waste,min_width=6,fg_bg=cpair(colors.black, colors.white)} + MultiButton{parent=waste_sel,x=1,y=1,options=waste_opts,callback=unit.set_waste,min_width=6,fg_bg=cpair(colors.black, colors.white)} TextBox{parent=waste_sel,text="Waste Processing",alignment=TEXT_ALIGN.CENTER,x=1,y=1,height=1} + ---------------------- + -- alarm management -- + ---------------------- + + local alarm_panel = Div{parent=main,x=2,y=32,width=29,height=16,fg_bg=cpair(colors.black,colors.white)} + + local a_brc = AlarmLight{parent=alarm_panel,x=6,y=2,label="Containment Breach",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} + local a_rad = AlarmLight{parent=alarm_panel,x=6,label="Containment Radiation",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} + local a_dmg = AlarmLight{parent=alarm_panel,x=6,label="Critical Damage",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} + alarm_panel.line_break() + local a_rcl = AlarmLight{parent=alarm_panel,x=6,label="Reactor Lost",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} + local a_rcd = AlarmLight{parent=alarm_panel,x=6,label="Reactor Damage",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} + local a_rot = AlarmLight{parent=alarm_panel,x=6,label="Reactor Over Temp",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} + local a_rht = AlarmLight{parent=alarm_panel,x=6,label="Reactor High Temp",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS} + local a_rwl = AlarmLight{parent=alarm_panel,x=6,label="Reactor Waste Leak",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} + local a_rwh = AlarmLight{parent=alarm_panel,x=6,label="Reactor Waste High",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS} + alarm_panel.line_break() + local a_rps = AlarmLight{parent=alarm_panel,x=6,label="RPS Transient",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS} + local a_clt = AlarmLight{parent=alarm_panel,x=6,label="RCS Transient",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS} + local a_tbt = AlarmLight{parent=alarm_panel,x=6,label="Turbine Trip",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} + + r_ps.subscribe("ALM1", a_brc.update) + r_ps.subscribe("ALM2", a_rad.update) + r_ps.subscribe("ALM4", a_dmg.update) + + r_ps.subscribe("ALM3", a_rcl.update) + r_ps.subscribe("ALM5", a_rcd.update) + r_ps.subscribe("ALM6", a_rot.update) + r_ps.subscribe("ALM7", a_rht.update) + r_ps.subscribe("ALM8", a_rwl.update) + r_ps.subscribe("ALM9", a_rwh.update) + + r_ps.subscribe("ALM10", a_rps.update) + r_ps.subscribe("ALM11", a_clt.update) + r_ps.subscribe("ALM12", a_tbt.update) + + -- ack's and resets + + local c = unit.alarm_callbacks + local ack_fg_bg = cpair(colors.black, colors.orange) + local rst_fg_bg = cpair(colors.black, colors.lime) + local active_fg_bg = cpair(colors.white, colors.gray) + + PushButton{parent=alarm_panel,x=2,y=2,text="\x13",min_width=1,callback=c.c_breach.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=2,text="R",min_width=1,callback=c.c_breach.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=3,text="\x13",min_width=1,callback=c.radiation.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=3,text="R",min_width=1,callback=c.radiation.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=4,text="\x13",min_width=1,callback=c.dmg_crit.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=4,text="R",min_width=1,callback=c.dmg_crit.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + + PushButton{parent=alarm_panel,x=2,y=6,text="\x13",min_width=1,callback=c.r_lost.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=6,text="R",min_width=1,callback=c.r_lost.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=7,text="\x13",min_width=1,callback=c.damage.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=7,text="R",min_width=1,callback=c.damage.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=8,text="\x13",min_width=1,callback=c.over_temp.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=8,text="R",min_width=1,callback=c.over_temp.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=9,text="\x13",min_width=1,callback=c.high_temp.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=9,text="R",min_width=1,callback=c.high_temp.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=10,text="\x13",min_width=1,callback=c.waste_leak.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=10,text="R",min_width=1,callback=c.waste_leak.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=11,text="\x13",min_width=1,callback=c.waste_high.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=11,text="R",min_width=1,callback=c.waste_high.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + + PushButton{parent=alarm_panel,x=2,y=13,text="\x13",min_width=1,callback=c.rps_trans.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=13,text="R",min_width=1,callback=c.rps_trans.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=14,text="\x13",min_width=1,callback=c.rcs_trans.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=14,text="R",min_width=1,callback=c.rcs_trans.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=15,text="\x13",min_width=1,callback=c.t_trip.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=15,text="R",min_width=1,callback=c.t_trip.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + + -- color tags + + TextBox{parent=alarm_panel,x=5,y=13,text="\x95",width=1,height=1,fg_bg=cpair(colors.white, colors.brown)} + TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(colors.white, colors.blue)} + TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(colors.white, colors.cyan)} + return main end diff --git a/graphics/element.lua b/graphics/element.lua index d987a7a..7bf8b71 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -26,6 +26,7 @@ local element = {} ---|push_button_args ---|spinbox_args ---|switch_button_args +---|alarm_indicator_light ---|core_map_args ---|data_indicator_args ---|hbar_args @@ -302,6 +303,18 @@ function element.new(args) ---@return cpair fg_bg function public.get_fg_bg() return protected.fg_bg end + -- get element x + ---@return integer x + function public.get_x() + return protected.frame.x + end + + -- get element y + ---@return integer y + function public.get_y() + return protected.frame.y + end + -- get element width ---@return integer width function public.width() diff --git a/graphics/elements/indicators/alight.lua b/graphics/elements/indicators/alight.lua new file mode 100644 index 0000000..eea103a --- /dev/null +++ b/graphics/elements/indicators/alight.lua @@ -0,0 +1,113 @@ +-- Tri-State Alarm Indicator Light Graphics Element + +local util = require("scada-common.util") + +local element = require("graphics.element") +local flasher = require("graphics.flasher") + +---@class alarm_indicator_light +---@field label string indicator label +---@field c1 color color for off state +---@field c2 color color for alarm state +---@field c3 color color for ring-back state +---@field min_label_width? integer label length if omitted +---@field flash? boolean whether to flash on alarm state rather than stay on +---@field period? PERIOD flash period +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field fg_bg? cpair foreground/background colors + +-- new alarm indicator light +---@param args alarm_indicator_light +---@return graphics_element element, element_id id +local function alarm_indicator_light(args) + assert(type(args.label) == "string", "graphics.elements.indicators.alight: label is a required field") + assert(type(args.c1) == "number", "graphics.elements.indicators.alight: c1 is a required field") + assert(type(args.c2) == "number", "graphics.elements.indicators.alight: c2 is a required field") + assert(type(args.c3) == "number", "graphics.elements.indicators.alight: c3 is a required field") + + if args.flash then + assert(util.is_int(args.period), "graphics.elements.indicators.alight: period is a required field if flash is enabled") + end + + -- single line + args.height = 1 + + -- determine width + args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2 + + -- flasher state + local flash_on = true + + -- blit translations + local c1 = colors.toBlit(args.c1) + local c2 = colors.toBlit(args.c2) + local c3 = colors.toBlit(args.c3) + + -- create new graphics element base object + local e = element.new(args) + + -- called by flasher when enabled + local function flash_callback() + e.window.setCursorPos(1, 1) + + if flash_on then + if e.value == 2 then + e.window.blit(" \x95", "0" .. c2, c2 .. e.fg_bg.blit_bkg) + end + else + if e.value == 3 then + e.window.blit(" \x95", "0" .. c3, c3 .. e.fg_bg.blit_bkg) + else + e.window.blit(" \x95", "0" .. c1, c1 .. e.fg_bg.blit_bkg) + end + end + + flash_on = not flash_on + end + + -- on state change + ---@param new_state integer indicator state + function e.on_update(new_state) + local was_off = e.value ~= 2 + + e.value = new_state + e.window.setCursorPos(1, 1) + + if args.flash then + if was_off and (new_state == 2) then + flash_on = true + flasher.start(flash_callback, args.period) + elseif new_state ~= 2 then + flash_on = false + flasher.stop(flash_callback) + + if new_state == 3 then + e.window.blit(" \x95", "0" .. c3, c3 .. e.fg_bg.blit_bkg) + else + e.window.blit(" \x95", "0" .. c1, c1 .. e.fg_bg.blit_bkg) + end + end + elseif new_state == 2 then + e.window.blit(" \x95", "0" .. c2, c2 .. e.fg_bg.blit_bkg) + elseif new_state == 3 then + e.window.blit(" \x95", "0" .. c3, c3 .. e.fg_bg.blit_bkg) + else + e.window.blit(" \x95", "0" .. c1, c1 .. e.fg_bg.blit_bkg) + end + end + + -- set indicator state + ---@param val integer indicator state + function e.set_value(val) e.on_update(val) end + + -- write label and initial indicator light + e.on_update(1) + e.window.write(args.label) + + return e.get() +end + +return alarm_indicator_light diff --git a/graphics/elements/indicators/trilight.lua b/graphics/elements/indicators/trilight.lua index 83aef37..2c61fb7 100644 --- a/graphics/elements/indicators/trilight.lua +++ b/graphics/elements/indicators/trilight.lua @@ -1,6 +1,9 @@ -- Tri-State Indicator Light Graphics Element +local util = require("scada-common.util") + local element = require("graphics.element") +local flasher = require("graphics.flasher") ---@class tristate_indicator_light_args ---@field label string indicator label @@ -8,13 +11,15 @@ local element = require("graphics.element") ---@field c2 color color for state 2 ---@field c3 color color for state 3 ---@field min_label_width? integer label length if omitted +---@field flash? boolean whether to flash on state 2 or 3 rather than stay on +---@field period? PERIOD flash period ---@field parent graphics_element ---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer 1 if omitted ---@field fg_bg? cpair foreground/background colors --- new indicator light +-- new tri-state indicator light ---@param args tristate_indicator_light_args ---@return graphics_element element, element_id id local function tristate_indicator_light(args) @@ -23,12 +28,19 @@ local function tristate_indicator_light(args) assert(type(args.c2) == "number", "graphics.elements.indicators.trilight: c2 is a required field") assert(type(args.c3) == "number", "graphics.elements.indicators.trilight: c3 is a required field") + if args.flash then + assert(util.is_int(args.period), "graphics.elements.indicators.trilight: period is a required field if flash is enabled") + end + -- single line args.height = 1 -- determine width args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2 + -- flasher state + local flash_on = true + -- blit translations local c1 = colors.toBlit(args.c1) local c2 = colors.toBlit(args.c2) @@ -37,12 +49,45 @@ local function tristate_indicator_light(args) -- create new graphics element base object local e = element.new(args) + -- init value for initial check in on_update + e.value = 1 + + -- called by flasher when enabled + local function flash_callback() + e.window.setCursorPos(1, 1) + + if flash_on then + if e.value == 2 then + e.window.blit(" \x95", "0" .. c2, c2 .. e.fg_bg.blit_bkg) + elseif e.value == 3 then + e.window.blit(" \x95", "0" .. c3, c3 .. e.fg_bg.blit_bkg) + end + else + e.window.blit(" \x95", "0" .. c1, c1 .. e.fg_bg.blit_bkg) + end + + flash_on = not flash_on + end + -- on state change ---@param new_state integer indicator state function e.on_update(new_state) + local was_off = e.value <= 1 + e.value = new_state e.window.setCursorPos(1, 1) - if new_state == 2 then + + if args.flash then + if was_off and (new_state > 1) then + flash_on = true + flasher.start(flash_callback, args.period) + elseif new_state <= 1 then + flash_on = false + flasher.stop(flash_callback) + + e.window.blit(" \x95", "0" .. c1, c1 .. e.fg_bg.blit_bkg) + end + elseif new_state == 2 then e.window.blit(" \x95", "0" .. c2, c2 .. e.fg_bg.blit_bkg) elseif new_state == 3 then e.window.blit(" \x95", "0" .. c3, c3 .. e.fg_bg.blit_bkg) @@ -56,7 +101,7 @@ local function tristate_indicator_light(args) function e.set_value(val) e.on_update(val) end -- write label and initial indicator light - e.on_update(0) + e.on_update(1) e.window.write(args.label) return e.get() diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 3f6c0d5..752f795 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.7" +local R_PLC_VERSION = "beta-v0.9.8" local print = util.print local println = util.println diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 88516d1..2831cf4 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -412,7 +412,7 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog) else -- establish denied public.unlink(rtu_state) - println_ts("supervisor connection") + println_ts("supervisor connection denied") log.warning("supervisor connection denied by remote host") end else diff --git a/rtu/startup.lua b/rtu/startup.lua index 67a9867..2e2272d 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.9.5" +local RTU_VERSION = "beta-v0.9.6" local rtu_t = types.rtu_t diff --git a/scada-common/alarm.lua b/scada-common/alarm.lua deleted file mode 100644 index 0a3038c..0000000 --- a/scada-common/alarm.lua +++ /dev/null @@ -1,73 +0,0 @@ -local util = require("scada-common.util") - ----@class alarm -local alarm = {} - ----@alias SEVERITY integer -SEVERITY = { - INFO = 0, -- basic info message - WARNING = 1, -- warning about some abnormal state - ALERT = 2, -- important device state changes - FACILITY = 3, -- facility-wide alert - SAFETY = 4, -- safety alerts - EMERGENCY = 5 -- critical safety alarm -} - -alarm.SEVERITY = SEVERITY - --- severity integer to string ----@param severity SEVERITY -function alarm.severity_to_string(severity) - if severity == SEVERITY.INFO then - return "INFO" - elseif severity == SEVERITY.WARNING then - return "WARNING" - elseif severity == SEVERITY.ALERT then - return "ALERT" - elseif severity == SEVERITY.FACILITY then - return "FACILITY" - elseif severity == SEVERITY.SAFETY then - return "SAFETY" - elseif severity == SEVERITY.EMERGENCY then - return "EMERGENCY" - else - return "UNKNOWN" - end -end - --- create a new scada alarm entry ----@param severity SEVERITY ----@param device string ----@param message string -function alarm.scada_alarm(severity, device, message) - local self = { - time = util.time(), - ts_string = os.date("[%H:%M:%S]"), - severity = severity, - device = device, - message = message - } - - ---@class scada_alarm - local public = {} - - -- format the alarm as a string - ---@return string message - function public.format() - return self.ts_string .. " [" .. alarm.severity_to_string(self.severity) .. "] (" .. self.device .. ") >> " .. self.message - end - - -- get alarm properties - function public.properties() - return { - time = self.time, - severity = self.severity, - device = self.device, - message = self.message - } - end - - return public -end - -return alarm diff --git a/scada-common/comms.lua b/scada-common/comms.lua index adf58c4..f25df22 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -12,7 +12,7 @@ local rtu_t = types.rtu_t local insert = table.insert -comms.version = "1.0.0" +comms.version = "1.0.1" ---@alias PROTOCOLS integer local PROTOCOLS = { @@ -74,7 +74,10 @@ local CRDN_COMMANDS = { START = 1, -- start the reactor RESET_RPS = 2, -- reset the RPS SET_BURN = 3, -- set the burn rate - SET_WASTE = 4 -- set the waste processing mode + SET_WASTE = 4, -- set the waste processing mode + ACK_ALL_ALARMS = 5, -- ack all active alarms + ACK_ALARM = 6, -- ack a particular alarm + RESET_ALARM = 7 -- reset a particular alarm } ---@alias CAPI_TYPES integer diff --git a/scada-common/types.lua b/scada-common/types.lua index b86aca7..eaeced6 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -35,6 +35,76 @@ types.TRI_FAIL = { FULL = 2 } +---@alias ALARM integer +types.ALARM = { + ContainmentBreach = 1, + ContainmentRadiation = 2, + ReactorLost = 3, + CriticalDamage = 4, + ReactorDamage = 5, + ReactorOverTemp = 6, + ReactorHighTemp = 7, + ReactorWasteLeak = 8, + ReactorHighWaste = 9, + RPSTransient = 10, + RCSTransient = 11, + TurbineTrip = 12 +} + +types.alarm_string = { + "ContainmentBreach", + "ContainmentRadiation", + "ReactorLost", + "CriticalDamage", + "ReactorDamage", + "ReactorOverTemp", + "ReactorHighTemp", + "ReactorWasteLeak", + "ReactorHighWaste", + "RPSTransient", + "RCSTransient", + "TurbineTrip" +} + +---@alias ALARM_PRIORITY integer +types.ALARM_PRIORITY = { + CRITICAL = 0, + EMERGENCY = 1, + URGENT = 2, + TIMELY = 3 +} + +types.alarm_prio_string = { + "CRITICAL", + "EMERGENCY", + "URGENT", + "TIMELY" +} + +-- map alarms to alarm priority +types.ALARM_PRIO_MAP = { + types.ALARM_PRIORITY.CRITICAL, + types.ALARM_PRIORITY.CRITICAL, + types.ALARM_PRIORITY.URGENT, + types.ALARM_PRIORITY.CRITICAL, + types.ALARM_PRIORITY.EMERGENCY, + types.ALARM_PRIORITY.URGENT, + types.ALARM_PRIORITY.TIMELY, + types.ALARM_PRIORITY.EMERGENCY, + types.ALARM_PRIORITY.TIMELY, + types.ALARM_PRIORITY.URGENT, + types.ALARM_PRIORITY.TIMELY, + types.ALARM_PRIORITY.URGENT +} + +---@alias ALARM_STATE integer +types.ALARM_STATE = { + INACTIVE = 0, + TRIPPED = 1, + ACKED = 2, + RING_BACK = 3 +} + -- STRING TYPES -- ---@alias os_event diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 53ba62c..675c481 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -129,7 +129,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) for i = 1, #self.units do local unit = self.units[i] ---@type reactor_unit - status[unit.get_id()] = { unit.get_reactor_status(), unit.get_annunciator(), unit.get_rtu_statuses() } + status[unit.get_id()] = { unit.get_reactor_status(), unit.get_annunciator(), unit.get_alarms(), unit.get_rtu_statuses() } end _send(SCADA_CRDN_TYPES.UNIT_STATUSES, status) @@ -191,6 +191,8 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) -- continue if valid unit id if util.is_int(uid) and uid > 0 and uid <= #self.units then + local unit = self.units[uid] ---@type reactor_unit + if cmd == CRDN_COMMANDS.START then self.out_q.push_data(SV_Q_DATA.START, data) elseif cmd == CRDN_COMMANDS.SCRAM then @@ -209,6 +211,21 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) else log.debug(log_header .. "CRDN command unit set waste missing option") end + elseif cmd == CRDN_COMMANDS.ACK_ALL_ALARMS then + unit.ack_all() + _send(SCADA_CRDN_TYPES.COMMAND_UNIT, { cmd, uid, true }) + elseif cmd == CRDN_COMMANDS.ACK_ALARM then + if pkt.length == 3 then + unit.ack_alarm(pkt.data[3]) + else + log.debug(log_header .. "CRDN command unit ack alarm missing id") + end + elseif cmd == CRDN_COMMANDS.RESET_ALARM then + if pkt.length == 3 then + unit.reset_alarm(pkt.data[3]) + else + log.debug(log_header .. "CRDN command unit reset alarm missing id") + end else log.debug(log_header .. "CRDN command unknown") end diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 2b80cb1..da74b30 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -4,9 +4,15 @@ local log = require("scada-common.log") local unit = {} +local ALARM = types.ALARM +local PRIO = types.ALARM_PRIORITY +local ALARM_STATE = types.ALARM_STATE + local TRI_FAIL = types.TRI_FAIL local DUMPING_MODE = types.DUMPING_MODE +local FLOW_STABILITY_DELAY_MS = 15000 + local DT_KEYS = { ReactorTemp = "RTP", ReactorFuel = "RFL", @@ -21,6 +27,32 @@ local DT_KEYS = { TurbinePower = "TPR" } +---@alias ALARM_INT_STATE integer +local AISTATE = { + INACTIVE = 0, + TRIPPING = 1, + TRIPPED = 2, + ACKED = 3, + RING_BACK = 4, + RING_BACK_TRIPPING = 5 +} + +local aistate_string = { + "INACTIVE", + "TRIPPING", + "TRIPPED", + "ACKED", + "RING_BACK", + "RING_BACK_TRIPPING" +} + +---@class alarm_def +---@field state ALARM_INT_STATE internal alarm state +---@field trip_time integer time (ms) when first tripped +---@field hold_time integer time (s) to hold before tripping +---@field id ALARM alarm ID +---@field tier integer alarm urgency tier (0 = highest) + -- create a new reactor unit ---@param for_reactor integer reactor unit number ---@param num_boilers integer number of boilers expected @@ -30,12 +62,49 @@ function unit.new(for_reactor, num_boilers, num_turbines) r_id = for_reactor, plc_s = nil, ---@class plc_session_struct plc_i = nil, ---@class plc_session - counts = { boilers = num_boilers, turbines = num_turbines }, turbines = {}, boilers = {}, redstone = {}, deltas = {}, last_heartbeat = 0, + -- logic for alarms + had_reactor = false, + start_time = 0, + plc_cache = { + ok = false, + rps_trip = false, + rps_status = {}, ---@type rps_status + damage = 0, + temp = 0, + waste = 0 + }, + ---@class alarm_monitors + alarms = { + -- reactor lost under the condition of meltdown imminent + ContainmentBreach = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ContainmentBreach, tier = PRIO.CRITICAL }, + -- radiation monitor alarm for this unit + ContainmentRadiation = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ContainmentRadiation, tier = PRIO.CRITICAL }, + -- reactor offline after being online + ReactorLost = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorLost, tier = PRIO.URGENT }, + -- damage >100% + CriticalDamage = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.CriticalDamage, tier = PRIO.CRITICAL }, + -- reactor damage increasing + ReactorDamage = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorDamage, tier = PRIO.EMERGENCY }, + -- reactor >1200K + ReactorOverTemp = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorOverTemp, tier = PRIO.URGENT }, + -- reactor >1100K + ReactorHighTemp = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 2, id = ALARM.ReactorHighTemp, tier = PRIO.TIMELY }, + -- waste = 100% + ReactorWasteLeak = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorWasteLeak, tier = PRIO.EMERGENCY }, + -- waste >85% + ReactorHighWaste = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 2, id = ALARM.ReactorHighWaste, tier = PRIO.TIMELY }, + -- RPS trip occured + RPSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.RPSTransient, tier = PRIO.URGENT }, + -- BoilRateMismatch, CoolantFeedMismatch, SteamFeedMismatch, MaxWaterReturnFeed + RCSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 5, id = ALARM.RCSTransient, tier = PRIO.TIMELY }, + -- "It's just a routine turbin' trip!" -Bill Gibson + TurbineTrip = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.TurbineTrip, tier = PRIO.URGENT } + }, db = { ---@class annunciator annunciator = { @@ -47,6 +116,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) AutoReactorSCRAM = false, RCPTrip = false, RCSFlowLow = false, + CoolantLevelLow = false, ReactorTempHigh = false, ReactorHighDeltaT = false, FuelInputRateLow = false, @@ -55,6 +125,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- boiler BoilerOnline = {}, HeatingRateLow = {}, + WaterLevelLow = {}, BoilRateMismatch = false, CoolantFeedMismatch = false, -- turbine @@ -64,6 +135,21 @@ function unit.new(for_reactor, num_boilers, num_turbines) SteamDumpOpen = {}, TurbineOverSpeed = {}, TurbineTrip = {} + }, + ---@class alarms + alarm_states = { + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE } } } @@ -125,6 +211,92 @@ function unit.new(for_reactor, num_boilers, num_turbines) end end + -- update an alarm state given conditions + ---@param tripped boolean if the alarm condition is still active + ---@param alarm alarm_def alarm table + local function _update_alarm_state(tripped, alarm) + local int_state = alarm.state + local ext_state = self.db.alarm_states[alarm.id] + + -- alarm inactive + if int_state == AISTATE.INACTIVE then + if tripped then + alarm.trip_time = util.time_ms() + if alarm.hold_time > 0 then + alarm.state = AISTATE.TRIPPING + self.db.alarm_states[alarm.id] = ALARM_STATE.INACTIVE + else + alarm.state = AISTATE.TRIPPED + self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED + log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): TRIPPED [PRIORITY ", + types.alarm_prio_string[alarm.tier + 1],"]")) + end + else + alarm.trip_time = util.time_ms() + self.db.alarm_states[alarm.id] = ALARM_STATE.INACTIVE + end + -- alarm condition met, but not yet for required hold time + elseif (int_state == AISTATE.TRIPPING) or (int_state == AISTATE.RING_BACK_TRIPPING) then + if tripped then + local elapsed = util.time_ms() - alarm.trip_time + if elapsed > (alarm.hold_time * 1000) then + alarm.state = AISTATE.TRIPPED + self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED + log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): TRIPPED [PRIORITY ", + types.alarm_prio_string[alarm.tier + 1],"]")) + end + elseif int_state == AISTATE.RING_BACK_TRIPPING then + alarm.trip_time = 0 + alarm.state = AISTATE.RING_BACK + self.db.alarm_states[alarm.id] = ALARM_STATE.RING_BACK + else + alarm.trip_time = 0 + alarm.state = AISTATE.INACTIVE + self.db.alarm_states[alarm.id] = ALARM_STATE.INACTIVE + end + -- alarm tripped and alarming + elseif int_state == AISTATE.TRIPPED then + if tripped then + if ext_state == ALARM_STATE.ACKED then + -- was acked by coordinator + alarm.state = AISTATE.ACKED + end + else + alarm.state = AISTATE.RING_BACK + self.db.alarm_states[alarm.id] = ALARM_STATE.RING_BACK + end + -- alarm acknowledged but still tripped + elseif int_state == AISTATE.ACKED then + if not tripped then + alarm.state = AISTATE.RING_BACK + self.db.alarm_states[alarm.id] = ALARM_STATE.RING_BACK + end + -- alarm no longer tripped, operator must reset to clear + elseif int_state == AISTATE.RING_BACK then + if tripped then + alarm.trip_time = util.time_ms() + if alarm.hold_time > 0 then + alarm.state = AISTATE.RING_BACK_TRIPPING + else + alarm.state = AISTATE.TRIPPED + self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED + end + elseif ext_state == ALARM_STATE.INACTIVE then + -- was reset by coordinator + alarm.state = AISTATE.INACTIVE + alarm.trip_time = 0 + end + else + log.error(util.c("invalid alarm state for unit ", self.r_id, " alarm ", alarm.id), true) + end + + -- check for state change + if alarm.state ~= int_state then + local change_str = util.c(aistate_string[int_state + 1], " -> ", aistate_string[alarm.state + 1]) + log.debug(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): ", change_str)) + end + end + -- update all delta computations local function _dt__compute_all() if self.plc_s ~= nil then @@ -181,6 +353,21 @@ function unit.new(for_reactor, num_boilers, num_turbines) if self.plc_s ~= nil then local plc_db = self.plc_i.get_db() + -- record reactor start time (some alarms are delayed during reactor heatup) + if self.start_time == 0 and plc_db.mek_status.status then + self.start_time = util.time_ms() + elseif not plc_db.mek_status.status then + self.start_time = 0 + end + + -- record reactor stats + self.plc_cache.ok = not (plc_db.rps_status.fault or plc_db.rps_status.sys_fail or plc_db.rps_status.force_dis) + self.plc_cache.rps_trip = plc_db.rps_tripped + self.plc_cache.rps_status = plc_db.rps_status + self.plc_cache.damage = plc_db.mek_status.damage + self.plc_cache.temp = plc_db.mek_status.temp + self.plc_cache.waste = plc_db.mek_status.waste_fill + -- heartbeat blink about every second if self.last_heartbeat + 1000 < plc_db.last_status_update then self.db.annunciator.PLCHeartbeat = not self.db.annunciator.PLCHeartbeat @@ -201,9 +388,11 @@ function unit.new(for_reactor, num_boilers, num_turbines) self.db.annunciator.HighStartupRate = not plc_db.mek_status.status and plc_db.mek_status.burn_rate > 40 -- if no boilers, use reactor heating rate to check for boil rate mismatch - if self.counts.boilers == 0 then + if num_boilers == 0 then total_boil_rate = plc_db.mek_status.heating_rate end + else + self.plc_cache.ok = false end ------------- @@ -211,13 +400,13 @@ function unit.new(for_reactor, num_boilers, num_turbines) ------------- -- clear boiler online flags - for i = 1, self.counts.boilers do self.db.annunciator.BoilerOnline[i] = false end + for i = 1, num_boilers do self.db.annunciator.BoilerOnline[i] = false end -- aggregated statistics local boiler_steam_dt_sum = 0.0 local boiler_water_dt_sum = 0.0 - if self.counts.boilers > 0 then + if num_boilers > 0 then -- go through boilers for stats and online for i = 1, #self.boilers do local session = self.boilers[i] ---@type unit_session @@ -259,7 +448,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- check coolant feed mismatch if using boilers, otherwise calculate with reactor local cfmismatch = false - if self.counts.boilers > 0 then + if num_boilers > 0 then for i = 1, #self.boilers do local boiler = self.boilers[i] ---@type unit_session local idx = boiler.get_device_idx() @@ -290,7 +479,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -------------- -- clear turbine online flags - for i = 1, self.counts.turbines do self.db.annunciator.TurbineOnline[i] = false end + for i = 1, num_turbines do self.db.annunciator.TurbineOnline[i] = false end -- aggregated statistics local total_flow_rate = 0 @@ -359,6 +548,75 @@ function unit.new(for_reactor, num_boilers, num_turbines) end end + -- evaluate alarm conditions + local function _update_alarms() + local annunc = self.db.annunciator + local plc_cache = self.plc_cache + + -- Containment Breach + -- lost plc with critical damage (rip plc, you will be missed) + _update_alarm_state((not plc_cache.ok) and (plc_cache.damage > 99), self.alarms.ContainmentBreach) + + -- Containment Radiation + ---@todo containment radiation alarm + _update_alarm_state(false, self.alarms.ContainmentRadiation) + + -- Reactor Lost + _update_alarm_state(self.had_reactor and self.plc_s == nil, self.alarms.ReactorLost) + + -- Critical Damage + _update_alarm_state(plc_cache.damage >= 100, self.alarms.CriticalDamage) + + -- Reactor Damage + _update_alarm_state(plc_cache.damage > 0, self.alarms.ReactorDamage) + + -- Over-Temperature + _update_alarm_state(plc_cache.temp >= 1200, self.alarms.ReactorOverTemp) + + -- High Temperature + _update_alarm_state(plc_cache.temp > 1150, self.alarms.ReactorHighTemp) + + -- Waste Leak + _update_alarm_state(plc_cache.waste >= 0.99, self.alarms.ReactorWasteLeak) + + -- High Waste + _update_alarm_state(plc_cache.waste > 0.50, self.alarms.ReactorHighWaste) + + -- RPS Transient (excludes timeouts and manual trips) + local rps_alarm = false + if plc_cache.rps_status.manual ~= nil then + if plc_cache.rps_trip then + for key, val in pairs(plc_cache.rps_status) do + if key ~= "manual" and key ~= "timeout" then + rps_alarm = rps_alarm or val + end + end + end + end + + _update_alarm_state(rps_alarm, self.alarms.RPSTransient) + + -- RCS Transient + local any_low = false + local any_over = false + for i = 1, #annunc.WaterLevelLow do any_low = any_low or annunc.WaterLevelLow[i] end + for i = 1, #annunc.TurbineOverSpeed do any_over = any_over or annunc.TurbineOverSpeed[i] end + + local rcs_trans = any_low or any_over or annunc.RCPTrip or annunc.RCSFlowLow or annunc.MaxWaterReturnFeed + + -- flow is ramping up right after reactor start, annunciator indicators for these states may not indicate a real issue + if util.time_ms() - self.start_time > FLOW_STABILITY_DELAY_MS then + rcs_trans = rcs_trans or annunc.BoilRateMismatch or annunc.CoolantFeedMismatch or annunc.SteamFeedMismatch + end + + _update_alarm_state(rcs_trans, self.alarms.RCSTransient) + + -- Turbine Trip + local any_trip = false + for i = 1, #annunc.TurbineTrip do any_trip = any_trip or annunc.TurbineTrip[i] end + _update_alarm_state(any_trip, self.alarms.TurbineTrip) + end + -- unlink disconnected units ---@param sessions table local function _unlink_disconnected_units(sessions) @@ -372,6 +630,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- link the PLC ---@param plc_session plc_session_struct function public.link_plc_session(plc_session) + self.had_reactor = true self.plc_s = plc_session self.plc_i = plc_session.instance @@ -443,6 +702,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- unlink PLC if session was closed if self.plc_s ~= nil and not self.plc_s.open then self.plc_s = nil + self.plc_i = nil end -- unlink RTU unit sessions if they are closed @@ -451,6 +711,36 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- update annunciator logic _update_annunciator() + + -- update alarm status + _update_alarms() + end + + -- ACK/RESET ALARMS -- + + -- acknowledge all alarms (if possible) + function public.ack_all() + for i = 1, #self.db.alarm_states do + if self.db.alarm_states[i] == ALARM_STATE.TRIPPED then + self.db.alarm_states[i] = ALARM_STATE.ACKED + end + end + end + + -- acknowledge an alarm (if possible) + ---@param id ALARM alarm ID + function public.ack_alarm(id) + if (type(id) == "number") and (self.db.alarm_states[id] == ALARM_STATE.TRIPPED) then + self.db.alarm_states[id] = ALARM_STATE.ACKED + end + end + + -- reset an alarm (if possible) + ---@param id ALARM alarm ID + function public.reset_alarm(id) + if (type(id) == "number") and (self.db.alarm_states[id] == ALARM_STATE.RING_BACK) then + self.db.alarm_states[id] = ALARM_STATE.INACTIVE + end end -- READ STATES/PROPERTIES -- @@ -526,6 +816,9 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- get the annunciator status function public.get_annunciator() return self.db.annunciator end + -- get the alarm states + function public.get_alarms() return self.db.alarm_states end + -- get the reactor ID function public.get_id() return self.r_id end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 6a5db92..5fa14de 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.7.10" +local SUPERVISOR_VERSION = "beta-v0.8.0" local print = util.print local println = util.println From afb3b0957e0d32bc1c276188b30ecb7f97f710b5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 27 Nov 2022 22:44:47 -0500 Subject: [PATCH 450/587] bugfix for RTU re-formed detection --- rtu/startup.lua | 2 +- rtu/threads.lua | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/rtu/startup.lua b/rtu/startup.lua index 2e2272d..76944c6 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.9.6" +local RTU_VERSION = "beta-v0.9.7" local rtu_t = types.rtu_t diff --git a/rtu/threads.lua b/rtu/threads.lua index 3d5b690..8027f34 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -341,10 +341,11 @@ function threads.thread__unit_comms(smem, unit) util.nop() end - -- check if multiblock is still formed if this is a multiblock if (type(unit.formed) == "boolean") and (util.time() - last_f_check > 1000) then - if (not unit.formed) and unit.device.isFormed() then + local is_formed = unit.device.isFormed() + + if (not unit.formed) and is_formed then -- newly re-formed local iface = ppm.get_iface(unit.device) if iface then @@ -394,6 +395,8 @@ function threads.thread__unit_comms(smem, unit) log.error("failed to get interface of previously connected RTU unit " .. detail_name, true) end end + + unit.formed = is_formed end -- check for termination request From 9c27ac7ae6946ece44a7ccbe2d7d5a407fd7769b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 27 Nov 2022 22:53:44 -0500 Subject: [PATCH 451/587] bugfix with reset/ack button mappings on coordinator GUI --- coordinator/iocontrol.lua | 4 ++-- coordinator/startup.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 67727cb..2926289 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -62,8 +62,8 @@ function iocontrol.init(conf, comms) alarm_callbacks = { c_breach = { ack = function () ack(1) end, reset = function () reset(1) end }, radiation = { ack = function () ack(2) end, reset = function () reset(2) end }, - dmg_crit = { ack = function () ack(3) end, reset = function () reset(3) end }, - r_lost = { ack = function () ack(4) end, reset = function () reset(4) end }, + r_lost = { ack = function () ack(3) end, reset = function () reset(3) end }, + dmg_crit = { ack = function () ack(4) end, reset = function () reset(4) end }, damage = { ack = function () ack(5) end, reset = function () reset(5) end }, over_temp = { ack = function () ack(6) end, reset = function () reset(6) end }, high_temp = { ack = function () ack(7) end, reset = function () reset(7) end }, diff --git a/coordinator/startup.lua b/coordinator/startup.lua index b82fa4f..225c681 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -17,7 +17,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.7.0" +local COORDINATOR_VERSION = "alpha-v0.7.1" local print = util.print local println = util.println From e1d7c7b1c0fd6283c46a5d446dea172417557ad7 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 30 Nov 2022 23:31:14 -0500 Subject: [PATCH 452/587] #134 #104 redstone RTU integration with supervisor unit, waste routing implemented, changed how redstone I/O works (again, should be good now), modbus fixes --- rtu/config.lua | 6 +- rtu/dev/redstone_rtu.lua | 26 +- rtu/modbus.lua | 7 +- rtu/startup.lua | 24 +- scada-common/rsio.lua | 160 +++++++----- scada-common/types.lua | 8 + supervisor/session/coordinator.lua | 2 +- supervisor/session/rtu.lua | 69 +---- supervisor/session/rtu/qtypes.lua | 16 ++ supervisor/session/rtu/redstone.lua | 330 ++++++++++++++++-------- supervisor/session/rtu/turbinev.lua | 33 ++- supervisor/session/rtu/unit_session.lua | 21 +- supervisor/session/svqtypes.lua | 5 +- supervisor/session/svsessions.lua | 2 - supervisor/session/unit.lua | 108 ++++++-- supervisor/startup.lua | 2 +- test/rstest.lua | 54 ++-- 17 files changed, 527 insertions(+), 346 deletions(-) create mode 100644 supervisor/session/rtu/qtypes.lua diff --git a/rtu/config.lua b/rtu/config.lua index 55968e1..7dc1baa 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -31,17 +31,17 @@ config.RTU_REDSTONE = { -- for_reactor = 1, -- io = { -- { - -- channel = rsio.IO.WASTE_PO, + -- port = rsio.IO.WASTE_PO, -- side = "top", -- bundled_color = colors.blue -- }, -- { - -- channel = rsio.IO.WASTE_PU, + -- port = rsio.IO.WASTE_PU, -- side = "top", -- bundled_color = colors.cyan -- }, -- { - -- channel = rsio.IO.WASTE_AM, + -- port = rsio.IO.WASTE_AM, -- side = "top", -- bundled_color = colors.purple -- } diff --git a/rtu/dev/redstone_rtu.lua b/rtu/dev/redstone_rtu.lua index f461fdf..13ca83b 100644 --- a/rtu/dev/redstone_rtu.lua +++ b/rtu/dev/redstone_rtu.lua @@ -4,9 +4,10 @@ local rsio = require("scada-common.rsio") local redstone_rtu = {} +local IO_LVL = rsio.IO_LVL + local digital_read = rsio.digital_read local digital_write = rsio.digital_write -local digital_is_active = rsio.digital_is_active -- create new redstone device function redstone_rtu.new() @@ -47,10 +48,9 @@ function redstone_rtu.new() end -- link digital output - ---@param channel RS_IO ---@param side string ---@param color integer - function public.link_do(channel, side, color) + function public.link_do(side, color) local f_read = nil local f_write = nil @@ -60,15 +60,17 @@ function redstone_rtu.new() end f_write = function (level) - local output = rs.getBundledOutput(side) + if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then + local output = rs.getBundledOutput(side) - if digital_write(channel, level) then - output = colors.combine(output, color) - else - output = colors.subtract(output, color) + if digital_write(level) then + output = colors.combine(output, color) + else + output = colors.subtract(output, color) + end + + rs.setBundledOutput(side, output) end - - rs.setBundledOutput(side, output) end else f_read = function () @@ -76,7 +78,9 @@ function redstone_rtu.new() end f_write = function (level) - rs.setOutput(side, digital_is_active(channel, level)) + if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then + rs.setOutput(side, digital_write(level)) + end end end diff --git a/rtu/modbus.lua b/rtu/modbus.lua index fea8e45..5411f37 100644 --- a/rtu/modbus.lua +++ b/rtu/modbus.lua @@ -366,7 +366,7 @@ function modbus.new(rtu_dev, use_parallel_read) local return_code = true local response = nil - if packet.length == 2 then + if packet.length >= 2 then -- handle by function code if packet.func_code == MODBUS_FCODE.READ_COILS then return_code, response = _1_read_coils(packet.data[1], packet.data[2]) @@ -381,9 +381,9 @@ function modbus.new(rtu_dev, use_parallel_read) elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_HOLD_REG then return_code, response = _6_write_single_holding_register(packet.data[1], packet.data[2]) elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_COILS then - return_code, response = _15_write_multiple_coils(packet.data[1], packet.data[2]) + return_code, response = _15_write_multiple_coils(packet.data[1], { table.unpack(packet.data, 2, packet.length) }) elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_HOLD_REGS then - return_code, response = _16_write_multiple_holding_registers(packet.data[1], packet.data[2]) + return_code, response = _16_write_multiple_holding_registers(packet.data[1], { table.unpack(packet.data, 2, packet.length) }) else -- unknown function return_code = false @@ -392,6 +392,7 @@ function modbus.new(rtu_dev, use_parallel_read) else -- invalid length return_code = false + response = MODBUS_EXCODE.NEG_ACKNOWLEDGE end -- default is to echo back diff --git a/rtu/startup.lua b/rtu/startup.lua index 76944c6..2306bcd 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.9.7" +local RTU_VERSION = "beta-v0.9.8" local rtu_t = types.rtu_t @@ -166,7 +166,7 @@ local function main() local conf = io_table[i] -- verify configuration - if rsio.is_valid_channel(conf.channel) and rsio.is_valid_side(conf.side) then + if rsio.is_valid_port(conf.port) and rsio.is_valid_side(conf.side) then if conf.bundled_color then valid = rsio.is_color(conf.bundled_color) else @@ -182,22 +182,22 @@ local function main() return false else -- link redstone in RTU - local mode = rsio.get_io_mode(conf.channel) + local mode = rsio.get_io_mode(conf.port) 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) + if util.table_contains(capabilities, conf.port) then + local message = util.c("configure> skipping duplicate input for port ", rsio.to_string(conf.port), " 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) + rs_rtu.link_do(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) + if util.table_contains(capabilities, conf.port) then + local message = util.c("configure> skipping duplicate input for port ", rsio.to_string(conf.port), " on side ", conf.side) println(message) log.warning(message) else @@ -206,15 +206,15 @@ local function main() elseif mode == rsio.IO_MODE.ANALOG_OUT then rs_rtu.link_ao(conf.side) else - -- should be unreachable code, we already validated channels + -- should be unreachable code, we already validated ports 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) + table.insert(capabilities, conf.port) - log.debug(util.c("configure> linked redstone ", #capabilities, ": ", rsio.to_string(conf.channel), + log.debug(util.c("configure> linked redstone ", #capabilities, ": ", rsio.to_string(conf.port), " (", conf.side, ") for reactor ", io_reactor)) end end @@ -225,7 +225,7 @@ local function main() type = rtu_t.redstone, index = entry_idx, reactor = io_reactor, - device = capabilities, -- use device field for redstone channels + device = capabilities, -- use device field for redstone ports formed = nil, ---@type boolean|nil rtu = rs_rtu, ---@type rtu_device|rtu_rs_device modbus_io = modbus.new(rs_rtu, false), diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 8598e85..15bb258 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -4,6 +4,7 @@ local util = require("scada-common.util") +---@class rsio local rsio = {} ---------------------- @@ -12,9 +13,10 @@ local rsio = {} ---@alias IO_LVL integer local IO_LVL = { + DISCONNECT = -1, -- use for RTU session to indicate this RTU is not connected to this port LOW = 0, HIGH = 1, - DISCONNECT = -1 -- use for RTU session to indicate this RTU is not connected to this channel + FLOATING = 2 -- use for RTU session to indicate this RTU is connected but not yet read } ---@alias IO_DIR integer @@ -31,8 +33,8 @@ local IO_MODE = { ANALOG_OUT = 3 } ----@alias RS_IO integer -local RS_IO = { +---@alias IO_PORT integer +local IO_PORT = { -- digital inputs -- -- facility @@ -48,45 +50,47 @@ local RS_IO = { F_ALARM = 4, -- active high, facility safety alarm -- waste - WASTE_PO = 5, -- active low, polonium routing - WASTE_PU = 6, -- active low, plutonium routing - WASTE_AM = 7, -- active low, antimatter routing + WASTE_PU = 5, -- active low, waste -> plutonium -> pellets route + WASTE_PO = 6, -- active low, waste -> polonium route + WASTE_POPL = 7, -- active low, polonium -> pellets route + WASTE_AM = 8, -- active low, polonium -> anti-matter route -- reactor - R_ALARM = 8, -- active high, reactor safety alarm - R_SCRAMMED = 9, -- active high, if the reactor is scrammed - R_AUTO_SCRAM = 10, -- active high, if the reactor was automatically scrammed - R_ACTIVE = 11, -- active high, if the reactor is active - R_AUTO_CTRL = 12, -- active high, if the reactor burn rate is automatic - R_DMG_CRIT = 13, -- active high, if the reactor damage is critical - R_HIGH_TEMP = 14, -- active high, if the reactor is at a high temperature - R_NO_COOLANT = 15, -- active high, if the reactor has no coolant - R_EXCESS_HC = 16, -- active high, if the reactor has excess heated coolant - R_EXCESS_WS = 17, -- active high, if the reactor has excess waste - R_INSUFF_FUEL = 18, -- active high, if the reactor has insufficent fuel - R_PLC_FAULT = 19, -- active high, if the reactor PLC reports a device access fault - R_PLC_TIMEOUT = 20 -- active high, if the reactor PLC has not been heard from + R_ALARM = 9, -- active high, reactor safety alarm + R_SCRAMMED = 10, -- active high, if the reactor is scrammed + R_AUTO_SCRAM = 11, -- active high, if the reactor was automatically scrammed + R_ACTIVE = 12, -- active high, if the reactor is active + R_AUTO_CTRL = 13, -- active high, if the reactor burn rate is automatic + R_DMG_CRIT = 14, -- active high, if the reactor damage is critical + R_HIGH_TEMP = 15, -- active high, if the reactor is at a high temperature + R_NO_COOLANT = 16, -- active high, if the reactor has no coolant + R_EXCESS_HC = 17, -- active high, if the reactor has excess heated coolant + R_EXCESS_WS = 18, -- active high, if the reactor has excess waste + R_INSUFF_FUEL = 19, -- active high, if the reactor has insufficent fuel + R_PLC_FAULT = 20, -- active high, if the reactor PLC reports a device access fault + R_PLC_TIMEOUT = 21 -- active high, if the reactor PLC has not been heard from } rsio.IO_LVL = IO_LVL rsio.IO_DIR = IO_DIR rsio.IO_MODE = IO_MODE -rsio.IO = RS_IO +rsio.IO = IO_PORT ----------------------- -- UTILITY FUNCTIONS -- ----------------------- --- channel to string ----@param channel RS_IO -function rsio.to_string(channel) +-- port to string +---@param port IO_PORT +function rsio.to_string(port) local names = { "F_SCRAM", "R_SCRAM", "R_ENABLE", "F_ALARM", - "WASTE_PO", "WASTE_PU", + "WASTE_PO", + "WASTE_POPL", "WASTE_AM", "R_ALARM", "R_SCRAMMED", @@ -103,8 +107,8 @@ function rsio.to_string(channel) "R_PLC_TIMEOUT" } - if util.is_int(channel) and channel > 0 and channel <= #names then - return names[channel] + if util.is_int(port) and port > 0 and port <= #names then + return names[port] else return "" end @@ -112,64 +116,69 @@ end local _B_AND = bit.band -local function _ACTIVE_HIGH(level) return level == IO_LVL.HIGH end -local function _ACTIVE_LOW(level) return level == IO_LVL.LOW end +local function _I_ACTIVE_HIGH(level) return level == IO_LVL.HIGH end +local function _I_ACTIVE_LOW(level) return level == IO_LVL.LOW end +local function _O_ACTIVE_HIGH(active) if active then return IO_LVL.HIGH else return IO_LVL.LOW end end +local function _O_ACTIVE_LOW(active) if active then return IO_LVL.LOW else return IO_LVL.HIGH end end -- I/O mappings to I/O function and I/O mode local RS_DIO_MAP = { -- F_SCRAM - { _f = _ACTIVE_LOW, mode = IO_DIR.IN }, + { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN }, -- R_SCRAM - { _f = _ACTIVE_LOW, mode = IO_DIR.IN }, + { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN }, -- R_ENABLE - { _f = _ACTIVE_HIGH, mode = IO_DIR.IN }, + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN }, -- F_ALARM - { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- WASTE_PO - { _f = _ACTIVE_LOW, mode = IO_DIR.OUT }, + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- WASTE_PU - { _f = _ACTIVE_LOW, mode = IO_DIR.OUT }, + { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, + -- WASTE_PO + { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, + -- WASTE_POPL + { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, -- WASTE_AM - { _f = _ACTIVE_LOW, mode = IO_DIR.OUT }, + { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, -- R_ALARM - { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_SCRAMMED - { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_AUTO_SCRAM - { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_ACTIVE - { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_AUTO_CTRL - { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_DMG_CRIT - { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_HIGH_TEMP - { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_NO_COOLANT - { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_EXCESS_HC - { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_EXCESS_WS - { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_INSUFF_FUEL - { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_PLC_FAULT - { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT }, + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_PLC_TIMEOUT - { _f = _ACTIVE_HIGH, mode = IO_DIR.OUT } + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT } } --- get the mode of a channel ----@param channel RS_IO +-- get the mode of a port +---@param port IO_PORT ---@return IO_MODE -function rsio.get_io_mode(channel) +function rsio.get_io_mode(port) local modes = { IO_MODE.DIGITAL_IN, -- F_SCRAM IO_MODE.DIGITAL_IN, -- R_SCRAM IO_MODE.DIGITAL_IN, -- R_ENABLE IO_MODE.DIGITAL_OUT, -- F_ALARM - IO_MODE.DIGITAL_OUT, -- WASTE_PO IO_MODE.DIGITAL_OUT, -- WASTE_PU + IO_MODE.DIGITAL_OUT, -- WASTE_PO + IO_MODE.DIGITAL_OUT, -- WASTE_POPL IO_MODE.DIGITAL_OUT, -- WASTE_AM IO_MODE.DIGITAL_OUT, -- R_ALARM IO_MODE.DIGITAL_OUT, -- R_SCRAMMED @@ -186,8 +195,8 @@ function rsio.get_io_mode(channel) IO_MODE.DIGITAL_OUT -- R_PLC_TIMEOUT } - if util.is_int(channel) and channel > 0 and channel <= #modes then - return modes[channel] + if util.is_int(port) and port > 0 and port <= #modes then + return modes[port] else return IO_MODE.ANALOG_IN end @@ -199,11 +208,11 @@ end local RS_SIDES = rs.getSides() --- check if a channel is valid ----@param channel RS_IO +-- check if a port is valid +---@param port IO_PORT ---@return boolean valid -function rsio.is_valid_channel(channel) - return util.is_int(channel) and (channel > 0) and (channel <= RS_IO.R_PLC_TIMEOUT) +function rsio.is_valid_port(port) + return util.is_int(port) and (port > 0) and (port <= IO_PORT.R_PLC_TIMEOUT) end -- check if a side is valid @@ -229,7 +238,7 @@ end -- DIGITAL I/O -- ----------------- --- get digital IO level reading +-- get digital I/O level reading from a redstone boolean input value ---@param rs_value boolean ---@return IO_LVL function rsio.digital_read(rs_value) @@ -240,27 +249,36 @@ function rsio.digital_read(rs_value) end end --- returns the level corresponding to active ----@param channel RS_IO +-- get redstone boolean output value corresponding to a digital I/O level ---@param level IO_LVL ---@return boolean -function rsio.digital_write(channel, level) - if (not util.is_int(channel)) or (channel < RS_IO.F_ALARM) or (channel > RS_IO.R_PLC_TIMEOUT) then +function rsio.digital_write(level) + return level == IO_LVL.HIGH +end + +-- returns the level corresponding to active +---@param port IO_PORT +---@param active boolean +---@return IO_LVL|false +function rsio.digital_write_active(port, active) + if (not util.is_int(port)) or (port < IO_PORT.F_ALARM) or (port > IO_PORT.R_PLC_TIMEOUT) then return false else - return RS_DIO_MAP[channel]._f(level) + return RS_DIO_MAP[port]._out(active) end end -- returns true if the level corresponds to active ----@param channel RS_IO +---@param port IO_PORT ---@param level IO_LVL ----@return boolean -function rsio.digital_is_active(channel, level) - if (not util.is_int(channel)) or (channel > RS_IO.R_ENABLE) then - return false +---@return boolean|nil +function rsio.digital_is_active(port, level) + if (not util.is_int(port)) or (port > IO_PORT.R_ENABLE) then + return nil + elseif level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then + return nil else - return RS_DIO_MAP[channel]._f(level) + return RS_DIO_MAP[port]._in(level) end end diff --git a/scada-common/types.lua b/scada-common/types.lua index eaeced6..2ef80b3 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -35,6 +35,14 @@ types.TRI_FAIL = { FULL = 2 } +---@alias WASTE_MODE integer +types.WASTE_MODE = { + AUTO = 1, + PLUTONIUM = 2, + POLONIUM = 3, + ANTI_MATTER = 4 +} + ---@alias ALARM integer types.ALARM = { ContainmentBreach = 1, diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 675c481..4dac34a 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -207,7 +207,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) end elseif cmd == CRDN_COMMANDS.SET_WASTE then if pkt.length == 3 then - self.out_q.push_data(SV_Q_DATA.SET_WASTE, data) + unit.set_waste(pkt.data[3]) else log.debug(log_header .. "CRDN command unit set waste missing option") end diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 997e198..922112f 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -27,26 +27,10 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts -local RTU_S_CMDS = { -} - -local RTU_S_DATA = { - RS_COMMAND = 1, - UNIT_COMMAND = 2 -} - -rtu.RTU_S_CMDS = RTU_S_CMDS -rtu.RTU_S_DATA = RTU_S_DATA - local PERIODICS = { KEEP_ALIVE = 2000 } ----@class rs_session_command ----@field reactor integer ----@field channel RS_IO ----@field value integer|boolean - -- create a new RTU session ---@param id integer ---@param in_queue mqueue @@ -74,8 +58,6 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) last_update = 0, keep_alive = 0 }, - rs_io_q = {}, - turbine_cmd_q = {}, units = {} } @@ -84,8 +66,6 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) local function _reset_config() self.units = {} - self.rs_io_q = {} - self.turbine_cmd_q = {} end -- parse the recorded advertisement and create unit sub-sessions @@ -141,14 +121,15 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) if u_type == RTU_UNIT_TYPES.REDSTONE then -- redstone - unit, rs_in_q = svrs_redstone.new(self.id, i, unit_advert, self.modbus_q) + unit = svrs_redstone.new(self.id, i, unit_advert, self.modbus_q) + if type(unit) ~= "nil" then target_unit.add_redstone(unit) end elseif u_type == RTU_UNIT_TYPES.BOILER_VALVE then -- boiler (Mekanism 10.1+) unit = svrs_boilerv.new(self.id, i, unit_advert, self.modbus_q) if type(unit) ~= "nil" then target_unit.add_boiler(unit) end elseif u_type == RTU_UNIT_TYPES.TURBINE_VALVE then -- turbine (Mekanism 10.1+) - unit, tbv_in_q = svrs_turbinev.new(self.id, i, unit_advert, self.modbus_q) + unit = svrs_turbinev.new(self.id, i, unit_advert, self.modbus_q) if type(unit) ~= "nil" then target_unit.add_turbine(unit) end elseif u_type == RTU_UNIT_TYPES.IMATRIX then -- induction matrix @@ -169,31 +150,6 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) if unit ~= nil then table.insert(self.units, unit) - - if u_type == RTU_UNIT_TYPES.REDSTONE then - if self.rs_io_q[unit_advert.reactor] == nil then - self.rs_io_q[unit_advert.reactor] = rs_in_q - else - _reset_config() - log.error(log_header .. util.c("bad advertisement: duplicate redstone RTU for reactor " .. unit_advert.reactor)) - break - end - elseif u_type == RTU_UNIT_TYPES.TURBINE_VALVE then - if self.turbine_cmd_q[unit_advert.reactor] == nil then - self.turbine_cmd_q[unit_advert.reactor] = {} - end - - local queues = self.turbine_cmd_q[unit_advert.reactor] - - if queues[unit_advert.index] == nil then - queues[unit_advert.index] = tbv_in_q - else - _reset_config() - log.error(log_header .. util.c("bad advertisement: duplicate turbine RTU (same index of ", - unit_advert.index, ") for reactor ", unit_advert.reactor)) - break - end - end else _reset_config() if type(u_type) == "number" then @@ -353,25 +309,6 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) -- handle instruction elseif msg.qtype == mqueue.TYPE.DATA then -- instruction with body - local cmd = msg.message ---@type queue_data - if cmd.key == RTU_S_DATA.RS_COMMAND then - local rs_cmd = cmd.val ---@type rs_session_command - - if rsio.is_valid_channel(rs_cmd.channel) then - cmd.key = svrs_redstone.RS_RTU_S_DATA.RS_COMMAND - if rs_cmd.reactor == nil then - -- for all reactors (facility) - for i = 1, #self.rs_io_q do - local q = self.rs_io.q[i] ---@type mqueue - q.push_data(msg) - end - elseif self.rs_io_q[rs_cmd.reactor] ~= nil then - -- for just one reactor - local q = self.rs_io.q[rs_cmd.reactor] ---@type mqueue - q.push_data(msg) - end - end - end end end diff --git a/supervisor/session/rtu/qtypes.lua b/supervisor/session/rtu/qtypes.lua new file mode 100644 index 0000000..92a927f --- /dev/null +++ b/supervisor/session/rtu/qtypes.lua @@ -0,0 +1,16 @@ +---@class rtu_unit_qtypes +local qtypes = {} + +local TBV_RTU_S_CMDS = { + INC_DUMP_MODE = 1, + DEC_DUMP_MODE = 2 +} + +local TBV_RTU_S_DATA = { + SET_DUMP_MODE = 1 +} + +qtypes.TBV_RTU_S_CMDS = TBV_RTU_S_CMDS +qtypes.TBV_RTU_S_DATA = TBV_RTU_S_DATA + +return qtypes diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index 90e4b58..a286f9c 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -12,39 +12,40 @@ local redstone = {} local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES local MODBUS_FCODE = types.MODBUS_FCODE -local RS_IO = rsio.IO +local IO_PORT = rsio.IO local IO_LVL = rsio.IO_LVL local IO_DIR = rsio.IO_DIR local IO_MODE = rsio.IO_MODE -local RS_RTU_S_CMDS = { -} - -local RS_RTU_S_DATA = { - RS_COMMAND = 1 -} - -redstone.RS_RTU_S_CMDS = RS_RTU_S_CMDS -redstone.RS_RTU_S_DATA = RS_RTU_S_DATA +local TXN_READY = -1 local TXN_TYPES = { DI_READ = 1, COIL_WRITE = 2, - INPUT_REG_READ = 3, - HOLD_REG_WRITE = 4 + COIL_READ = 3, + INPUT_REG_READ = 4, + HOLD_REG_WRITE = 5, + HOLD_REG_READ = 6 } local TXN_TAGS = { "redstone.di_read", "redstone.coil_write", - "redstone.input_reg_write", - "redstone.hold_reg_write" + "redstone.coil_read", + "redstone.input_reg_read", + "redstone.hold_reg_write", + "redstone.hold_reg_read" } local PERIODICS = { - INPUT_READ = 200 + INPUT_READ = 200, + OUTPUT_SYNC = 200 } +---@class phy_entry +---@field phy IO_LVL +---@field req IO_LVL + -- create a new redstone rtu session runner ---@param session_id integer ---@param unit_id integer @@ -62,57 +63,127 @@ function redstone.new(session_id, unit_id, advert, out_queue) local self = { session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), - in_q = mqueue.new(), has_di = false, + has_do = false, has_ai = false, + has_ao = false, periodics = { - next_di_req = 0, - next_ir_req = 0 + next_di_req = 0, + next_cl_sync = 0, + next_ir_req = 0, + next_hr_sync = 0 }, + ---@class rs_io_list io_list = { - digital_in = {}, -- discrete inputs - digital_out = {}, -- coils - analog_in = {}, -- input registers - analog_out = {} -- holding registers + digital_in = {}, -- discrete inputs + digital_out = {}, -- coils + analog_in = {}, -- input registers + analog_out = {} -- holding registers }, - db = {} + phy_trans = { coils = -1, hold_regs = -1 }, + -- last set/read ports (reflecting the current state of the RTU) + ---@class rs_io_states + phy_io = { + digital_in = {}, -- discrete inputs + digital_out = {}, -- coils + analog_in = {}, -- input registers + analog_out = {} -- holding registers + }, + ---@class redstone_session_db + db = { + -- read/write functions for connected I/O + io = {} + } } local public = self.session.get() -- INITIALIZE -- - -- create all channels as disconnected - for _ = 1, #RS_IO do + -- create all ports as disconnected + for _ = 1, #IO_PORT do table.insert(self.db, IO_LVL.DISCONNECT) end -- setup I/O for i = 1, #advert.rsio do - local channel = advert.rsio[i] + local port = advert.rsio[i] - if rsio.is_valid_channel(channel) then - local mode = rsio.get_io_mode(channel) + if rsio.is_valid_port(port) then + local mode = rsio.get_io_mode(port) if mode == IO_MODE.DIGITAL_IN then self.has_di = true - table.insert(self.io_list.digital_in, channel) + table.insert(self.io_list.digital_in, port) + + self.phy_io.digital_in[port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING } + + ---@class rs_db_dig_io + local io_f = { + read = function () return rsio.digital_is_active(port, self.phy_io.digital_in[port].phy) end, + ---@param active boolean + write = function (active) end + } + + self.db.io[port] = io_f elseif mode == IO_MODE.DIGITAL_OUT then - table.insert(self.io_list.digital_out, channel) + self.has_do = true + table.insert(self.io_list.digital_out, port) + + self.phy_io.digital_out[port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING } + + ---@class rs_db_dig_io + local io_f = { + read = function () return rsio.digital_is_active(port, self.phy_io.digital_out[port].phy) end, + ---@param active boolean + write = function (active) + local level = rsio.digital_write_active(port, active) + if level ~= nil then self.phy_io.digital_out[port].req = level end + end + } + + self.db.io[port] = io_f elseif mode == IO_MODE.ANALOG_IN then self.has_ai = true - table.insert(self.io_list.analog_in, channel) + table.insert(self.io_list.analog_in, port) + + self.phy_io.analog_in[port] = { phy = 0, req = 0 } + + ---@class rs_db_ana_io + local io_f = { + ---@return integer + read = function () return self.phy_io.analog_in[port].phy end, + ---@param value integer + write = function (value) end + } + + self.db.io[port] = io_f elseif mode == IO_MODE.ANALOG_OUT then - table.insert(self.io_list.analog_out, channel) + self.has_ao = true + table.insert(self.io_list.analog_out, port) + + self.phy_io.analog_out[port] = { phy = 0, req = 0 } + + ---@class rs_db_ana_io + local io_f = { + ---@return integer + read = function () return self.phy_io.analog_out[port].phy end, + ---@param value integer + write = function (value) + if value >= 0 and value <= 15 then + self.phy_io.analog_out[port].req = value + end + end + } + + self.db.io[port] = io_f else - -- should be unreachable code, we already validated channels - log.error(util.c(log_tag, "failed to identify advertisement channel IO mode (", channel, ")"), true) + -- should be unreachable code, we already validated ports + log.error(util.c(log_tag, "failed to identify advertisement port IO mode (", port, ")"), true) return nil end - - self.db[channel] = IO_LVL.LOW else - log.error(util.c(log_tag, "invalid advertisement channel (", channel, ")"), true) + log.error(util.c(log_tag, "invalid advertisement port (", port, ")"), true) return nil end end @@ -129,14 +200,40 @@ function redstone.new(session_id, unit_id, advert, out_queue) self.session.send_request(TXN_TYPES.INPUT_REG_READ, MODBUS_FCODE.READ_INPUT_REGS, { 1, #self.io_list.analog_in }) end - -- write coil output - local function _write_coil(coil, value) - self.session.send_request(TXN_TYPES.COIL_WRITE, MODBUS_FCODE.WRITE_MUL_COILS, { coil, value }) + -- write all coil outputs + local function _write_coils() + local params = { 1 } + + local outputs = self.phy_io.digital_out + for i = 1, #self.io_list.digital_out do + local port = self.io_list.digital_out[i] + table.insert(params, outputs[port].req) + end + + self.phy_trans.coils = self.session.send_request(TXN_TYPES.COIL_WRITE, MODBUS_FCODE.WRITE_MUL_COILS, params) end - -- write holding register output - local function _write_holding_register(reg, value) - self.session.send_request(TXN_TYPES.HOLD_REG_WRITE, MODBUS_FCODE.WRITE_MUL_HOLD_REGS, { reg, value }) + -- read all coil outputs + local function _read_coils() + self.session.send_request(TXN_TYPES.COIL_READ, MODBUS_FCODE.READ_COILS, { 1, #self.io_list.digital_out }) + end + + -- write all holding register outputs + local function _write_holding_registers() + local params = { 1 } + + local outputs = self.phy_io.analog_out + for i = 1, #self.io_list.analog_out do + local port = self.io_list.analog_out[i] + table.insert(params, outputs[port].req) + end + + self.phy_trans.hold_regs = self.session.send_request(TXN_TYPES.HOLD_REG_WRITE, MODBUS_FCODE.WRITE_MUL_HOLD_REGS, params) + end + + -- read all holding register outputs + local function _read_holding_registers() + self.session.send_request(TXN_TYPES.HOLD_REG_READ, MODBUS_FCODE.READ_MUL_HOLD_REGS, { 1, #self.io_list.analog_out }) end -- PUBLIC FUNCTIONS -- @@ -146,14 +243,23 @@ function redstone.new(session_id, unit_id, advert, out_queue) function public.handle_packet(m_pkt) local txn_type = self.session.try_resolve(m_pkt) if txn_type == false then - -- nothing to do + -- check if this is a failed write request + -- redstone operations are always immediately executed, so this would not be from an ACK or BUSY + if m_pkt.txn_id == self.phy_trans.coils then + self.phy_trans.coils = TXN_READY + log.debug(log_tag .. "failed to write coils, retrying soon") + elseif m_pkt.txn_id == self.phy_trans.hold_regs then + self.phy_trans.hold_regs = TXN_READY + log.debug(log_tag .. "failed to write holding registers, retrying soon") + end elseif txn_type == TXN_TYPES.DI_READ then -- discrete input read response if m_pkt.length == #self.io_list.digital_in then for i = 1, m_pkt.length do - local channel = self.io_list.digital_in[i] + local port = self.io_list.digital_in[i] local value = m_pkt.data[i] - self.db[channel] = value + + self.phy_io.digital_in[port].phy = value end else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") @@ -162,15 +268,55 @@ function redstone.new(session_id, unit_id, advert, out_queue) -- input register read response if m_pkt.length == #self.io_list.analog_in then for i = 1, m_pkt.length do - local channel = self.io_list.analog_in[i] + local port = self.io_list.analog_in[i] local value = m_pkt.data[i] - self.db[channel] = value + + self.phy_io.analog_in[port].phy = value end else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end - elseif txn_type == TXN_TYPES.COIL_WRITE or txn_type == TXN_TYPES.HOLD_REG_WRITE then - -- successful acknowledgement + elseif txn_type == TXN_TYPES.COIL_WRITE then + -- successful acknowledgement, read back + _read_coils() + elseif txn_type == TXN_TYPES.COIL_READ then + -- update phy I/O table + -- if there are multiple outputs for the same port, they will overwrite eachother (but *should* be identical) + -- given these are redstone outputs, if one worked they all should have, so no additional verification will be done + if m_pkt.length == #self.io_list.digital_out then + for i = 1, m_pkt.length do + local port = self.io_list.digital_out[i] + local value = m_pkt.data[i] + + self.phy_io.digital_out[port].phy = value + if self.phy_io.digital_out[port].req == IO_LVL.FLOATING then + self.phy_io.digital_out[port].req = value + end + end + + self.phy_trans.coils = TXN_READY + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + elseif txn_type == TXN_TYPES.HOLD_REG_WRITE then + -- successful acknowledgement, read back + _read_holding_registers() + elseif txn_type == TXN_TYPES.HOLD_REG_READ then + -- update phy I/O table + -- if there are multiple outputs for the same port, they will overwrite eachother (but *should* be identical) + -- given these are redstone outputs, if one worked they all should have, so no additional verification will be done + if m_pkt.length == #self.io_list.analog_out then + for i = 1, m_pkt.length do + local port = self.io_list.analog_out[i] + local value = m_pkt.data[i] + + self.phy_io.analog_out[port].phy = value + end + else + log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") + end + + self.phy_trans.hold_regs = TXN_READY elseif txn_type == nil then log.error(log_tag .. "unknown transaction reply") else @@ -181,60 +327,6 @@ function redstone.new(session_id, unit_id, advert, out_queue) -- update this runner ---@param time_now integer milliseconds function public.update(time_now) - -- check command queue - while self.in_q.ready() do - -- get a new message to process - local msg = self.in_q.pop() - - if msg ~= nil then - if msg.qtype == mqueue.TYPE.DATA then - -- instruction with body - local cmd = msg.message ---@type queue_data - if cmd.key == RS_RTU_S_DATA.RS_COMMAND then - local rs_cmd = cmd.val ---@type rs_session_command - - if self.db[rs_cmd.channel] ~= IO_LVL.DISCONNECT then - -- we have this as a connected channel - local mode = rsio.get_io_mode(rs_cmd.channel) - if mode == IO_MODE.DIGITAL_OUT then - -- record the value for retries - self.db[rs_cmd.channel] = rs_cmd.value - - -- find the coil address then write to it - for i = 0, #self.digital_out do - if self.digital_out[i] == rs_cmd.channel then - _write_coil(i, rs_cmd.value) - break - end - end - elseif mode == IO_MODE.ANALOG_OUT then - -- record the value for retries - self.db[rs_cmd.channel] = rs_cmd.value - - -- find the holding register address then write to it - for i = 0, #self.analog_out do - if self.analog_out[i] == rs_cmd.channel then - _write_holding_register(i, rs_cmd.value) - break - end - end - else - log.debug(util.c(log_tag, "attemted write to non D/O or A/O mode ", mode)) - end - end - end - end - end - - -- max 100ms spent processing queue - if util.time() - time_now > 100 then - log.warning(log_tag .. "exceeded 100ms queue process limit") - break - end - end - - time_now = util.time() - -- poll digital inputs if self.has_di then if self.periodics.next_di_req <= time_now then @@ -243,6 +335,20 @@ function redstone.new(session_id, unit_id, advert, out_queue) end end + -- sync digital outputs + if self.has_do then + if (self.periodics.next_cl_sync <= time_now) and (self.phy_trans.coils == TXN_READY) then + for _, entry in pairs(self.phy_io.digital_out) do + if entry.phy ~= entry.req then + _write_coils() + break + end + end + + self.periodics.next_cl_sync = time_now + PERIODICS.OUTPUT_SYNC + end + end + -- poll analog inputs if self.has_ai then if self.periodics.next_ir_req <= time_now then @@ -251,6 +357,20 @@ function redstone.new(session_id, unit_id, advert, out_queue) end end + -- sync analog outputs + if self.has_ao then + if (self.periodics.next_hr_sync <= time_now) and (self.phy_trans.hold_regs == TXN_READY) then + for _, entry in pairs(self.phy_io.analog_out) do + if entry.phy ~= entry.req then + _write_holding_registers() + break + end + end + + self.periodics.next_hr_sync = time_now + PERIODICS.OUTPUT_SYNC + end + end + self.session.post_update() end @@ -262,7 +382,7 @@ function redstone.new(session_id, unit_id, advert, out_queue) -- get the unit session database function public.get_db() return self.db end - return public, self.in_q + return public end return redstone diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua index ad3fea7..d1bba6b 100644 --- a/supervisor/session/rtu/turbinev.lua +++ b/supervisor/session/rtu/turbinev.lua @@ -4,6 +4,7 @@ local mqueue = require("scada-common.mqueue") local types = require("scada-common.types") local util = require("scada-common.util") +local qtypes = require("supervisor.session.rtu.qtypes") local unit_session = require("supervisor.session.rtu.unit_session") local turbinev = {} @@ -12,17 +13,8 @@ local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES local DUMPING_MODE = types.DUMPING_MODE local MODBUS_FCODE = types.MODBUS_FCODE -local TBV_RTU_S_CMDS = { - INC_DUMP_MODE = 1, - DEC_DUMP_MODE = 2 -} - -local TBV_RTU_S_DATA = { - SET_DUMP_MODE = 1 -} - -turbinev.RS_RTU_S_CMDS = TBV_RTU_S_CMDS -turbinev.RS_RTU_S_DATA = TBV_RTU_S_DATA +local TBV_RTU_S_CMDS = qtypes.TBV_RTU_S_CMDS +local TBV_RTU_S_DATA = qtypes.TBV_RTU_S_DATA local TXN_TYPES = { FORMED = 1, @@ -67,7 +59,6 @@ function turbinev.new(session_id, unit_id, advert, out_queue) local self = { session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), - in_q = mqueue.new(), has_build = false, periodics = { next_formed_req = 0, @@ -240,9 +231,9 @@ function turbinev.new(session_id, unit_id, advert, out_queue) ---@param time_now integer milliseconds function public.update(time_now) -- check command queue - while self.in_q.ready() do + while self.session.in_q.ready() do -- get a new message to process - local msg = self.in_q.pop() + local msg = self.session.in_q.pop() if msg ~= nil then if msg.qtype == mqueue.TYPE.COMMAND then @@ -254,15 +245,21 @@ function turbinev.new(session_id, unit_id, advert, out_queue) elseif cmd == TBV_RTU_S_CMDS.DEC_DUMP_MODE then _dec_dump_mode() else - log.debug(util.c(log_tag, "unrecognized in_q command ", cmd)) + log.debug(util.c(log_tag, "unrecognized in-queue command ", cmd)) end elseif msg.qtype == mqueue.TYPE.DATA then -- instruction with body local cmd = msg.message ---@type queue_data if cmd.key == TBV_RTU_S_DATA.SET_DUMP_MODE then - _set_dump_mode(cmd.val) + if cmd.val == types.DUMPING_MODE.IDLE or + cmd.val == types.DUMPING_MODE.DUMPING_EXCESS or + cmd.val == types.DUMPING_MODE.DUMPING then + _set_dump_mode(cmd.val) + else + log.debug(util.c(log_tag, "unrecognized dumping mode \"", cmd.val, "\"")) + end else - log.debug(util.c(log_tag, "unrecognized in_q data ", cmd.key)) + log.debug(util.c(log_tag, "unrecognized in-queue data ", cmd.key)) end end end @@ -313,7 +310,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue) -- get the unit session database function public.get_db() return self.db end - return public, self.in_q + return public end return turbinev diff --git a/supervisor/session/rtu/unit_session.lua b/supervisor/session/rtu/unit_session.lua index a3f27f2..f6e4297 100644 --- a/supervisor/session/rtu/unit_session.lua +++ b/supervisor/session/rtu/unit_session.lua @@ -1,5 +1,6 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") local types = require("scada-common.types") local util = require("scada-common.util") @@ -42,7 +43,9 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t } ---@class _unit_session - local protected = {} + local protected = { + in_q = mqueue.new() + } ---@class unit_session local public = {} @@ -53,6 +56,7 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t ---@param txn_type integer transaction type ---@param f_code MODBUS_FCODE function code ---@param register_param table register range or register and values + ---@return integer txn_id transaction ID of this transaction function protected.send_request(txn_type, f_code, register_param) local m_pkt = comms.modbus_packet() local txn_id = self.transaction_controller.create(txn_type) @@ -60,11 +64,13 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t m_pkt.make(txn_id, self.unit_id, f_code, register_param) self.out_q.push_packet(m_pkt) + + return txn_id end -- try to resolve a MODBUS transaction ---@param m_pkt modbus_frame MODBUS packet - ---@return integer|false txn_type transaction type or false on error/busy + ---@return integer|false txn_type, integer txn_id transaction type or false on error/busy, transaction ID function protected.try_resolve(m_pkt) if m_pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then if m_pkt.unit_id == self.unit_id then @@ -110,7 +116,7 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t self.device_fail = false -- no error, return the transaction type - return txn_type + return txn_type, m_pkt.txn_id end else log.error(log_tag .. "wrong unit ID: " .. m_pkt.unit_id, true) @@ -120,7 +126,7 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t end -- error or transaction in progress, return false - return false + return false, m_pkt.txn_id end -- post update tasks @@ -141,6 +147,8 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t function public.get_device_idx() return self.device_index end -- get the reactor ID function public.get_reactor() return self.reactor end + -- get the command queue + function public.get_cmd_queue() return protected.in_q end -- close this unit function public.close() self.connected = false end @@ -171,7 +179,10 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t end -- get the unit session database - function public.get_db() return {} end + function public.get_db() + log.debug("template unit_session.get_db() called", true) + return {} + end return protected end diff --git a/supervisor/session/svqtypes.lua b/supervisor/session/svqtypes.lua index 1b8b131..09ef4f1 100644 --- a/supervisor/session/svqtypes.lua +++ b/supervisor/session/svqtypes.lua @@ -9,9 +9,8 @@ local SV_Q_DATA = { SCRAM = 2, RESET_RPS = 3, SET_BURN = 4, - SET_WASTE = 5, - __END_PLC_CMDS__ = 6, - CRDN_ACK = 7 + __END_PLC_CMDS__ = 5, + CRDN_ACK = 6 } ---@class coord_ack diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index c4a6c8f..80ea0d6 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -82,8 +82,6 @@ local function _sv_handle_outq(session) plc_s.in_queue.push_command(PLC_S_CMDS.RPS_RESET) elseif cmd.key == SV_Q_DATA.SET_BURN and type(cmd.val) == "table" and #cmd.val == 2 then plc_s.in_queue.push_data(PLC_S_DATA.BURN_RATE, cmd.val[2]) - elseif cmd.key == SV_Q_DATA.SET_WASTE and type(cmd.val) == "table" and #cmd.val == 2 then - ---@todo set waste else log.debug(util.c("unknown PLC SV queue command ", cmd.key)) end diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index da74b30..fa85adc 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -1,9 +1,15 @@ -local types = require("scada-common.types") -local util = require("scada-common.util") -local log = require("scada-common.log") +local log = require("scada-common.log") +local rsio = require("scada-common.rsio") +local types = require("scada-common.types") +local util = require("scada-common.util") +local qtypes = require("supervisor.session.rtu.qtypes") + +---@class reactor_control_unit local unit = {} +local WASTE_MODE = types.WASTE_MODE + local ALARM = types.ALARM local PRIO = types.ALARM_PRIORITY local ALARM_STATE = types.ALARM_STATE @@ -11,6 +17,8 @@ local ALARM_STATE = types.ALARM_STATE local TRI_FAIL = types.TRI_FAIL local DUMPING_MODE = types.DUMPING_MODE +local IO = rsio.IO + local FLOW_STABILITY_DELAY_MS = 15000 local DT_KEYS = { @@ -105,6 +113,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- "It's just a routine turbin' trip!" -Bill Gibson TurbineTrip = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.TurbineTrip, tier = PRIO.URGENT } }, + ---@class unit_db db = { ---@class annunciator annunciator = { @@ -173,6 +182,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- PRIVATE FUNCTIONS -- + --#region time derivative utility functions + -- compute a change with respect to time of the given value ---@param key string value key ---@param value number value @@ -204,13 +215,44 @@ function unit.new(for_reactor, num_boilers, num_turbines) ---@param key string value key ---@return number local function _get_dt(key) - if self.deltas[key] then - return self.deltas[key].dt - else - return 0.0 + if self.deltas[key] then return self.deltas[key].dt else return 0.0 end + end + + --#endregion + + --#region redstone I/O + + -- write to a redstone port + local function __rs_w(port, value) + for i = 1, #self.redstone do + local db = self.redstone[i].get_db() ---@type redstone_session_db + local io = db.io[port] ---@type rs_db_dig_io|nil + if io ~= nil then io.write(value) end end end + -- read a redstone port
+ -- this will read from the first one encountered if there are multiple, because there should not be multiple + ---@param port IO_PORT + ---@return boolean|nil + local function __rs_r(port) + for i = 1, #self.redstone do + local db = self.redstone[i].get_db() ---@type redstone_session_db + local io = db.io[port] ---@type rs_db_dig_io|nil + if io ~= nil then return io.read() end + end + end + + -- waste valves + local waste_pu = { open = function () __rs_w(IO.WASTE_PU, true) end, close = function () __rs_w(IO.WASTE_PU, false) end } + local waste_sna = { open = function () __rs_w(IO.WASTE_PO, true) end, close = function () __rs_w(IO.WASTE_PO, false) end } + local waste_po = { open = function () __rs_w(IO.WASTE_POPL, true) end, close = function () __rs_w(IO.WASTE_POPL, false) end } + local waste_sps = { open = function () __rs_w(IO.WASTE_AM, true) end, close = function () __rs_w(IO.WASTE_AM, false) end } + + --#endregion + + --#region task helpers + -- update an alarm state given conditions ---@param tripped boolean if the alarm condition is still active ---@param alarm alarm_def alarm table @@ -335,6 +377,10 @@ function unit.new(for_reactor, num_boilers, num_turbines) end end + --#endregion + + --#region alarms and annunciator + -- update the annunciator local function _update_annunciator() -- update deltas @@ -617,6 +663,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) _update_alarm_state(any_trip, self.alarms.TurbineTrip) end + --#endregion + -- unlink disconnected units ---@param sessions table local function _unlink_disconnected_units(sessions) @@ -642,6 +690,13 @@ function unit.new(for_reactor, num_boilers, num_turbines) _reset_dt(DT_KEYS.ReactorHCool) end + -- link a redstone RTU session + ---@param rs_unit unit_session + function public.add_redstone(rs_unit) + -- insert into list + table.insert(self.redstone, rs_unit) + end + -- link a turbine RTU session ---@param turbine unit_session function public.add_turbine(turbine) @@ -676,17 +731,6 @@ function unit.new(for_reactor, num_boilers, num_turbines) end end - -- link a redstone RTU capability - function public.add_redstone(field, accessor) - -- ensure field exists - if self.redstone[field] == nil then - self.redstone[field] = {} - end - - -- insert into list - table.insert(self.redstone[field], accessor) - end - -- purge devices associated with the given RTU session ID ---@param session integer RTU session ID function public.purge_rtu_devices(session) @@ -716,7 +760,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) _update_alarms() end - -- ACK/RESET ALARMS -- + -- OPERATIONS -- -- acknowledge all alarms (if possible) function public.ack_all() @@ -743,6 +787,32 @@ function unit.new(for_reactor, num_boilers, num_turbines) end end + -- route reactor waste + ---@param mode WASTE_MODE waste handling mode + function public.set_waste(mode) + if mode == WASTE_MODE.AUTO then + ---@todo automatic waste routing + elseif mode == WASTE_MODE.PLUTONIUM then + -- route through plutonium generation + waste_pu.open() + waste_sna.close() + waste_po.close() + waste_sps.close() + elseif mode == WASTE_MODE.POLONIUM then + -- route through polonium generation into pellets + waste_pu.close() + waste_sna.open() + waste_po.open() + waste_sps.close() + elseif mode == WASTE_MODE.ANTI_MATTER then + -- route through polonium generation into SPS + waste_pu.close() + waste_sna.open() + waste_po.close() + waste_sps.open() + end + end + -- READ STATES/PROPERTIES -- -- get build properties of all machines diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 5fa14de..3d27727 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.8.0" +local SUPERVISOR_VERSION = "beta-v0.8.1" local print = util.print local println = util.println diff --git a/test/rstest.lua b/test/rstest.lua index 1ed6827..d195fc6 100644 --- a/test/rstest.lua +++ b/test/rstest.lua @@ -16,9 +16,9 @@ local IO_MODE = rsio.IO_MODE println("starting RSIO tester") println("") -println(">>> checking valid channels:") +println(">>> checking valid ports:") --- channel function tests +-- port function tests local cid = 0 local max_value = 1 for key, value in pairs(IO) do @@ -38,18 +38,18 @@ for key, value in pairs(IO) do elseif io_mode == IO_MODE.ANALOG_OUT then mode = " (ANALOG_OUT)" else - error("unknown mode for channel " .. key) + error("unknown mode for port " .. key) end assert(key == c_name, c_name .. " != " .. key .. ": " .. value .. mode) println(c_name .. ": " .. value .. mode) end -assert(max_value == cid, "RS_IO last IDx out-of-sync with count: " .. max_value .. " (count " .. cid .. ")") +assert(max_value == cid, "IO_PORT last IDx out-of-sync with count: " .. max_value .. " (count " .. cid .. ")") testutils.pause() -println(">>> checking invalid channels:") +println(">>> checking invalid ports:") testutils.test_func("rsio.to_string", rsio.to_string, { -1, 100, false }, "") testutils.test_func_nil("rsio.to_string", rsio.to_string, "") @@ -61,8 +61,8 @@ testutils.pause() println(">>> checking validity checks:") local ivc_t_list = { 0, -1, 100 } -testutils.test_func("rsio.is_valid_channel", rsio.is_valid_channel, ivc_t_list, false) -testutils.test_func_nil("rsio.is_valid_channel", rsio.is_valid_channel, false) +testutils.test_func("rsio.is_valid_port", rsio.is_valid_port, ivc_t_list, false) +testutils.test_func_nil("rsio.is_valid_port", rsio.is_valid_port, false) local ivs_t_list = rs.getSides() testutils.test_func("rsio.is_valid_side", rsio.is_valid_side, ivs_t_list, true) @@ -76,7 +76,7 @@ testutils.test_func_nil("rsio.is_color", rsio.is_color, false) testutils.pause() -println(">>> checking channel-independent I/O wrappers:") +println(">>> checking port-independent I/O wrappers:") testutils.test_func("rsio.digital_read", rsio.digital_read, { true, false }, { IO_LVL.HIGH, IO_LVL.LOW }) @@ -97,11 +97,11 @@ println("PASS") testutils.pause() -println(">>> checking channel I/O:") +println(">>> checking port I/O:") print("rsio.digital_is_active(...): ") --- check input channels +-- check input ports assert(rsio.digital_is_active(IO.F_SCRAM, IO_LVL.LOW) == true, "IO_F_SCRAM_HIGH") assert(rsio.digital_is_active(IO.F_SCRAM, IO_LVL.HIGH) == false, "IO_F_SCRAM_LOW") assert(rsio.digital_is_active(IO.R_SCRAM, IO_LVL.LOW) == true, "IO_R_SCRAM_HIGH") @@ -115,30 +115,32 @@ assert(rsio.digital_is_active(IO.F_ALARM, IO_LVL.HIGH) == false, "IO_OUT_READ_HI println("PASS") --- check output channels +-- check output ports print("rsio.digital_write(...): ") --- check output channels -assert(rsio.digital_write(IO.F_ALARM, IO_LVL.LOW) == false, "IO_F_ALARM_FALSE") -assert(rsio.digital_write(IO.F_ALARM, IO_LVL.HIGH) == true, "IO_F_ALARM_TRUE") -assert(rsio.digital_write(IO.WASTE_PO, IO_LVL.HIGH) == false, "IO_WASTE_PO_FALSE") -assert(rsio.digital_write(IO.WASTE_PO, IO_LVL.LOW) == true, "IO_WASTE_PO_TRUE") -assert(rsio.digital_write(IO.WASTE_PU, IO_LVL.HIGH) == false, "IO_WASTE_PU_FALSE") -assert(rsio.digital_write(IO.WASTE_PU, IO_LVL.LOW) == true, "IO_WASTE_PU_TRUE") -assert(rsio.digital_write(IO.WASTE_AM, IO_LVL.HIGH) == false, "IO_WASTE_AM_FALSE") -assert(rsio.digital_write(IO.WASTE_AM, IO_LVL.LOW) == true, "IO_WASTE_AM_TRUE") +-- check output ports +assert(rsio.digital_write_active(IO.F_ALARM, true) == IO_LVL.LOW, "IO_F_ALARM_LOW") +assert(rsio.digital_write_active(IO.F_ALARM, true) == IO_LVL.HIGH, "IO_F_ALARM_HIGH") +assert(rsio.digital_write_active(IO.WASTE_PU, true) == IO_LVL.HIGH, "IO_WASTE_PU_HIGH") +assert(rsio.digital_write_active(IO.WASTE_PU, true) == IO_LVL.LOW, "IO_WASTE_PU_LOW") +assert(rsio.digital_write_active(IO.WASTE_PO, true) == IO_LVL.HIGH, "IO_WASTE_PO_HIGH") +assert(rsio.digital_write_active(IO.WASTE_PO, true) == IO_LVL.LOW, "IO_WASTE_PO_LOW") +assert(rsio.digital_write_active(IO.WASTE_POPL, true) == IO_LVL.HIGH, "IO_WASTE_POPL_HIGH") +assert(rsio.digital_write_active(IO.WASTE_POPL, true) == IO_LVL.LOW, "IO_WASTE_POPL_LOW") +assert(rsio.digital_write_active(IO.WASTE_AM, true) == IO_LVL.HIGH, "IO_WASTE_AM_HIGH") +assert(rsio.digital_write_active(IO.WASTE_AM, true) == IO_LVL.LOW, "IO_WASTE_AM_LOW") --- check all reactor output channels (all are active high) +-- check all reactor output ports (all are active high) for i = IO.R_ALARM, (IO.R_PLC_TIMEOUT - IO.R_ALARM + 1) do - assert(rsio.to_string(i) ~= "", "REACTOR_IO_BAD_CHANNEL") - assert(rsio.digital_write(i, IO_LVL.LOW) == false, "IO_" .. rsio.to_string(i) .. "_FALSE") - assert(rsio.digital_write(i, IO_LVL.HIGH) == true, "IO_" .. rsio.to_string(i) .. "_TRUE") + assert(rsio.to_string(i) ~= "", "REACTOR_IO_BAD_PORT") + assert(rsio.digital_write_active(i, false) == IO_LVL.LOW, "IO_" .. rsio.to_string(i) .. "_LOW") + assert(rsio.digital_write_active(i, true) == IO_LVL.HIGH, "IO_" .. rsio.to_string(i) .. "_HIGH") end -- non-outputs should always return false -assert(rsio.digital_write(IO.F_SCRAM, IO_LVL.LOW) == false, "IO_IN_WRITE_LOW") -assert(rsio.digital_write(IO.F_SCRAM, IO_LVL.LOW) == false, "IO_IN_WRITE_HIGH") +assert(rsio.digital_write_active(IO.F_SCRAM, false) == IO_LVL.LOW, "IO_IN_WRITE_FALSE") +assert(rsio.digital_write_active(IO.F_SCRAM, true) == IO_LVL.LOW, "IO_IN_WRITE_TRUE") println("PASS") From 518ee8272ab4376e8d4ad752768ade64100f6369 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 30 Nov 2022 23:32:29 -0500 Subject: [PATCH 453/587] updated modbustest --- test/modbustest.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/modbustest.lua b/test/modbustest.lua index 1d0b9ea..40d2b2f 100644 --- a/test/modbustest.lua +++ b/test/modbustest.lua @@ -31,8 +31,8 @@ assert(di == 0 and c == 0 and ir == 0 and hr == 0, "IOCOUNT_0") rs_rtu.link_di("back", colors.black) rs_rtu.link_di("back", colors.blue) -rs_rtu.link_do(rsio.IO.F_ALARM, "back", colors.red) -rs_rtu.link_do(rsio.IO.WASTE_AM, "back", colors.purple) +rs_rtu.link_do("back", colors.red) +rs_rtu.link_do("back", colors.purple) rs_rtu.link_ai("right") rs_rtu.link_ao("left") From 4030fdc5c9b2f686e0f10065a7b12e8ab440368f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 4 Dec 2022 13:59:10 -0500 Subject: [PATCH 454/587] #77 alarm sounder --- coordinator/iocontrol.lua | 69 ++- coordinator/sounder.lua | 453 +++++++++++++++++++ coordinator/startup.lua | 25 +- coordinator/ui/components/unit_detail.lua | 6 +- coordinator/ui/layout/main_view.lua | 36 ++ graphics/elements/controls/switch_button.lua | 5 +- scada-common/types.lua | 2 +- supervisor/session/coordinator.lua | 2 +- supervisor/startup.lua | 2 +- 9 files changed, 575 insertions(+), 25 deletions(-) create mode 100644 coordinator/sounder.lua diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 2926289..6aa2caa 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -1,11 +1,15 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local psil = require("scada-common.psil") -local types = require("scada-common.types") -local util = require("scada-common.util") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local psil = require("scada-common.psil") +local types = require("scada-common.types") +local util = require("scada-common.util") + +local sounder = require("coordinator.sounder") local CRDN_COMMANDS = comms.CRDN_COMMANDS +local ALARM_STATE = types.ALARM_STATE + local iocontrol = {} ---@class ioctl @@ -74,7 +78,21 @@ function iocontrol.init(conf, comms) t_trip = { ack = function () ack(12) end, reset = function () reset(12) end } }, - alarms = {}, ---@type alarms + ---@type alarms + alarms = { + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE, + ALARM_STATE.INACTIVE + }, reactor_ps = psil.create(), reactor_data = {}, ---@type reactor_db @@ -203,10 +221,10 @@ end ---@return boolean valid function iocontrol.update_statuses(statuses) if type(statuses) ~= "table" then - log.error("unit statuses not a table") + log.debug("unit statuses not a table") return false elseif #statuses ~= #io.units then - log.error("number of provided unit statuses does not match expected number of units") + log.debug("number of provided unit statuses does not match expected number of units") return false else for i = 1, #statuses do @@ -214,7 +232,7 @@ function iocontrol.update_statuses(statuses) local status = statuses[i] if type(status) ~= "table" or #status ~= 4 then - log.error("invalid status entry in unit statuses (not a table or invalid length)") + log.debug("invalid status entry in unit statuses (not a table or invalid length)") return false end @@ -319,16 +337,23 @@ function iocontrol.update_statuses(statuses) local alarm_states = status[3] - for id = 1, #alarm_states do - local state = alarm_states[id] + if type(alarm_states) == "table" then + for id = 1, #alarm_states do + local state = alarm_states[id] - if state == types.ALARM_STATE.TRIPPED or state == types.ALARM_STATE.ACKED then - unit.reactor_ps.publish("ALM" .. id, 2) - elseif state == types.ALARM_STATE.RING_BACK then - unit.reactor_ps.publish("ALM" .. id, 3) - else - unit.reactor_ps.publish("ALM" .. id, 1) + unit.alarms[id] = state + + if state == types.ALARM_STATE.TRIPPED or state == types.ALARM_STATE.ACKED then + unit.reactor_ps.publish("ALM" .. id, 2) + elseif state == types.ALARM_STATE.RING_BACK then + unit.reactor_ps.publish("ALM" .. id, 3) + else + unit.reactor_ps.publish("ALM" .. id, 1) + end end + else + log.debug("alarm states not a table") + return false end -- RTU statuses @@ -377,6 +402,8 @@ function iocontrol.update_statuses(statuses) unit.boiler_ps_tbl[id].publish(key, val) end end + else + log.debug("boiler list not a table") end if type(rtu_statuses.turbines) == "table" then @@ -422,9 +449,17 @@ function iocontrol.update_statuses(statuses) unit.turbine_ps_tbl[id].publish(key, val) end end + else + log.debug("turbine list not a table") + return false end + else + log.debug("rtu list not a table") end end + + -- update alarm sounder + sounder.eval(io.units) end return true diff --git a/coordinator/sounder.lua b/coordinator/sounder.lua new file mode 100644 index 0000000..7e7b779 --- /dev/null +++ b/coordinator/sounder.lua @@ -0,0 +1,453 @@ +-- +-- Alarm Sounder +-- + +local types = require("scada-common.types") +local util = require("scada-common.util") +local log = require("scada-common.log") + +local ALARM = types.ALARM +local ALARM_STATE = types.ALARM_STATE + +---@class sounder +local sounder = {} + +local _2_PI = 2 * math.pi -- 2 whole pies, hope you're hungry +local _DRATE = 48000 -- 48kHz audio +local _MAX_VAL = 127/2 -- max signed integer in this 8-bit audio +local _MAX_SAMPLES = 0x20000 -- 128 * 1024 samples +local _05s_SAMPLES = 24000 -- half a second worth of samples + +local test_alarms = { false, false, false, false, false, false, false, false, false, false, false, false } + +local alarm_ctl = { + speaker = nil, + volume = 0.5, + playing = false, + num_active = 0, + next_block = 1, + quad_buffer = { {}, {}, {}, {} } -- split audio up into 0.5s samples so specific components can be ended quicker +} + +-- sounds modeled after https://www.e2s.com/references-and-guidelines/listen-and-download-alarm-tones + +local T_340Hz_Int_2Hz = 1 +local T_544Hz_440Hz_Alt = 2 +local T_660Hz_Int_125ms = 3 +local T_745Hz_Int_1Hz = 4 +local T_800Hz_Int = 5 +local T_800Hz_1000Hz_Alt = 6 +local T_1000Hz_Int = 7 +local T_1800Hz_Int_4Hz = 8 + +local TONES = { + { active = false, component = { {}, {}, {}, {} } }, -- 340Hz @ 2Hz Intermittent + { active = false, component = { {}, {}, {}, {} } }, -- 544Hz 100mS / 440Hz 400mS Alternating + { active = false, component = { {}, {}, {}, {} } }, -- 660Hz @ 125ms On 125ms Off + { active = false, component = { {}, {}, {}, {} } }, -- 745Hz @ 1Hz Intermittent + { active = false, component = { {}, {}, {}, {} } }, -- 800Hz @ 0.25s On 1.75s Off + { active = false, component = { {}, {}, {}, {} } }, -- 800/1000Hz @ 0.25s Alternating + { active = false, component = { {}, {}, {}, {} } }, -- 1KHz 1s on, 1s off Intermittent + { active = false, component = { {}, {}, {}, {} } } -- 1.8KHz @ 4Hz Intermittent +} + +-- calculate how many samples are in the given number of milliseconds +---@param ms integer milliseconds +---@return integer samples +local function ms_to_samples(ms) return math.floor(ms * 48) end + +--#region Tone Generation (the Maths) + +-- 340Hz @ 2Hz Intermittent +local function gen_tone_1() + local t, dt = 0, _2_PI * 340 / _DRATE + + for i = 1, _05s_SAMPLES do + local val = math.floor(math.sin(t) * _MAX_VAL) + TONES[1].component[1][i] = val + TONES[1].component[3][i] = val + TONES[1].component[2][i] = 0 + TONES[1].component[4][i] = 0 + t = (t + dt) % _2_PI + end +end + +-- 544Hz 100mS / 440Hz 400mS Alternating +local function gen_tone_2() + local t1, dt1 = 0, _2_PI * 544 / _DRATE + local t2, dt2 = 0, _2_PI * 440 / _DRATE + local alternate_at = ms_to_samples(100) + + for i = 1, _05s_SAMPLES do + local value + + if i <= alternate_at then + value = math.floor(math.sin(t1) * _MAX_VAL) + t1 = (t1 + dt1) % _2_PI + else + value = math.floor(math.sin(t2) * _MAX_VAL) + t2 = (t2 + dt2) % _2_PI + end + + TONES[2].component[1][i] = value + TONES[2].component[2][i] = value + TONES[2].component[3][i] = value + TONES[2].component[4][i] = value + end +end + +-- 660Hz @ 125ms On 125ms Off +local function gen_tone_3() + local elapsed_samples = 0 + local alternate_after = ms_to_samples(125) + local alternate_at = alternate_after + local mode = true + + local t, dt = 0, _2_PI * 660 / _DRATE + + for set = 1, 4 do + for i = 1, _05s_SAMPLES do + if mode then + local val = math.floor(math.sin(t) * _MAX_VAL) + TONES[3].component[set][i] = val + t = (t + dt) % _2_PI + else + t = 0 + TONES[3].component[set][i] = 0 + end + + if elapsed_samples == alternate_at then + mode = not mode + alternate_at = elapsed_samples + alternate_after + end + + elapsed_samples = elapsed_samples + 1 + end + end +end + +-- 745Hz @ 1Hz Intermittent +local function gen_tone_4() + local t, dt = 0, _2_PI * 745 / _DRATE + + for i = 1, _05s_SAMPLES do + local val = math.floor(math.sin(t) * _MAX_VAL) + TONES[4].component[1][i] = val + TONES[4].component[3][i] = val + TONES[4].component[2][i] = 0 + TONES[4].component[4][i] = 0 + t = (t + dt) % _2_PI + end +end + +-- 800Hz @ 0.25s On 1.75s Off +local function gen_tone_5() + local t, dt = 0, _2_PI * 800 / _DRATE + local stop_at = ms_to_samples(250) + + for i = 1, _05s_SAMPLES do + local val = math.floor(math.sin(t) * _MAX_VAL) + + if i > stop_at then + TONES[5].component[1][i] = val + else + TONES[5].component[1][i] = 0 + end + + TONES[5].component[2][i] = 0 + TONES[5].component[3][i] = 0 + TONES[5].component[4][i] = 0 + + t = (t + dt) % _2_PI + end +end + +-- 1000/800Hz @ 0.25s Alternating +local function gen_tone_6() + local t1, dt1 = 0, _2_PI * 1000 / _DRATE + local t2, dt2 = 0, _2_PI * 800 / _DRATE + + local alternate_at = ms_to_samples(250) + + for i = 1, _05s_SAMPLES do + local val + if i <= alternate_at then + val = math.floor(math.sin(t1) * _MAX_VAL) + t1 = (t1 + dt1) % _2_PI + else + val = math.floor(math.sin(t2) * _MAX_VAL) + t2 = (t2 + dt2) % _2_PI + end + + TONES[6].component[1][i] = val + TONES[6].component[2][i] = val + TONES[6].component[3][i] = val + TONES[6].component[4][i] = val + end +end + +-- 1KHz 1s on, 1s off Intermittent +local function gen_tone_7() + local t, dt = 0, _2_PI * 1000 / _DRATE + + for i = 1, _05s_SAMPLES do + local val = math.floor(math.sin(t) * _MAX_VAL) + TONES[7].component[1][i] = val + TONES[7].component[2][i] = val + TONES[7].component[3][i] = 0 + TONES[7].component[4][i] = 0 + t = (t + dt) % _2_PI + end +end + +-- 1800Hz @ 4Hz Intermittent +local function gen_tone_8() + local t, dt = 0, _2_PI * 1800 / _DRATE + + local off_at = ms_to_samples(250) + + for i = 1, _05s_SAMPLES do + local val = 0 + + if i <= off_at then + val = math.floor(math.sin(t) * _MAX_VAL) + t = (t + dt) % _2_PI + end + + TONES[8].component[1][i] = val + TONES[8].component[2][i] = val + TONES[8].component[3][i] = val + TONES[8].component[4][i] = val + end +end + +--#endregion + +-- hard audio limiter +---@param output number output level +---@return number limited -128.0 to 127.0 +local function limit(output) + return math.max(-128, math.min(127, output)) +end + +-- zero the alarm audio buffer +local function zero() + for i = 1, 4 do + for s = 1, _05s_SAMPLES do alarm_ctl.quad_buffer[i][s] = 0 end + end +end + +-- add an alarm to the output buffer +---@param alarm_idx integer tone ID +local function add(alarm_idx) + alarm_ctl.num_active = alarm_ctl.num_active + 1 + TONES[alarm_idx].active = true + + for i = 1, 4 do + for s = 1, _05s_SAMPLES do + alarm_ctl.quad_buffer[i][s] = limit(alarm_ctl.quad_buffer[i][s] + TONES[alarm_idx].component[i][s]) + end + end +end + +-- start audio or continue audio on buffer empty +---@return boolean success successfully added buffer to audio output +local function play() + if not alarm_ctl.playing then + alarm_ctl.playing = true + alarm_ctl.next_block = 1 + + return sounder.continue() + else + return true + end +end + +-- initialize the annunciator alarm system +---@param speaker table speaker peripheral +function sounder.init(speaker) + alarm_ctl.speaker = speaker + alarm_ctl.speaker.stop() + + alarm_ctl.playing = false + alarm_ctl.num_active = 0 + alarm_ctl.next_block = 1 + + zero() + + -- generate tones + gen_tone_1() + gen_tone_2() + gen_tone_3() + gen_tone_4() + gen_tone_5() + gen_tone_6() + gen_tone_7() + gen_tone_8() +end + +-- check alarm state to enable/disable alarms +---@param units table|nil unit list or nil to use test mode +function sounder.eval(units) + local changed = false + local any_active = false + local new_states = { false, false, false, false, false, false, false, false } + local alarms = { false, false, false, false, false, false, false, false, false, false, false, false } + + if units ~= nil then + -- check all alarms for all units + for i = 1, #units do + local unit = units[i] ---@type ioctl_entry + for id = 1, #unit.alarms do + alarms[id] = alarms[id] or (unit.alarms[id] == ALARM_STATE.TRIPPED) + end + end + else + alarms = test_alarms + end + + -- containment breach is worst case CRITICAL alarm, this takes priority + if alarms[ALARM.ContainmentBreach] then + new_states[T_1800Hz_Int_4Hz] = true + else + -- critical damage is highest priority CRITICAL level alarm + if alarms[ALARM.CriticalDamage] then + new_states[T_660Hz_Int_125ms] = true + else + -- EMERGENCY level alarms + if alarms[ALARM.ReactorDamage] or alarms[ALARM.ReactorOverTemp] or alarms[ALARM.ReactorWasteLeak] then + new_states[T_544Hz_440Hz_Alt] = true + -- URGENT level turbine trip + elseif alarms[ALARM.TurbineTrip] then + new_states[T_745Hz_Int_1Hz] = true + -- URGENT level reactor lost + elseif alarms[ALARM.ReactorLost] then + new_states[T_340Hz_Int_2Hz] = true + -- TIMELY level alarms + elseif alarms[ALARM.ReactorHighTemp] or alarms[ALARM.ReactorHighWaste] or alarms[ALARM.RCSTransient] then + new_states[T_800Hz_Int] = true + end + end + + -- check RPS transient URGENT level alarm + if alarms[ALARM.RPSTransient] then + new_states[T_1000Hz_Int] = true + -- disable really painful audio combination + new_states[T_340Hz_Int_2Hz] = false + end + end + + -- radiation is a big concern, always play this CRITICAL level alarm if active + if alarms[ALARM.ContainmentRadiation] then + new_states[T_800Hz_1000Hz_Alt] = true + -- we are going to disable the RPS trip alarm audio due to conflict, and if it was enabled + -- then we can re-enable the reactor lost alarm audio since it doesn't painfully combine with this one + if new_states[T_1000Hz_Int] and alarms[ALARM.ReactorLost] then new_states[T_340Hz_Int_2Hz] = true end + -- it sounds *really* bad if this is in conjunction with these other tones, so disable them + new_states[T_745Hz_Int_1Hz] = false + new_states[T_800Hz_Int] = false + new_states[T_1000Hz_Int] = false + end + + -- check if any changed, check if any active, update active flags + for id = 1, #TONES do + if new_states[id] ~= TONES[id].active then + TONES[id].active = new_states[id] + changed = true + end + + if TONES[id].active then any_active = true end + end + + -- zero and re-add tones if changed + if changed then + zero() + + for id = 1, #TONES do + if TONES[id].active then add(id) end + end + end + + if any_active then play() else sounder.stop() end +end + +-- stop all audio and clear output buffer +function sounder.stop() + alarm_ctl.playing = false + alarm_ctl.speaker.stop() + alarm_ctl.next_block = 1 + alarm_ctl.num_active = 0 + for id = 1, #TONES do TONES[id].active = false end + zero() +end + +-- continue audio on buffer empty +---@return boolean success successfully added buffer to audio output +function sounder.continue() + if alarm_ctl.playing then + if alarm_ctl.speaker ~= nil and #alarm_ctl.quad_buffer[alarm_ctl.next_block] > 0 then + local success = alarm_ctl.speaker.playAudio(alarm_ctl.quad_buffer[alarm_ctl.next_block], alarm_ctl.volume) + + alarm_ctl.next_block = alarm_ctl.next_block + 1 + if alarm_ctl.next_block > 4 then alarm_ctl.next_block = 1 end + + if not success then + log.debug("SOUNDER: error playing audio") + end + + return success + else + return false + end + else + return false + end +end + +--#region Test Functions + +function sounder.test_1() add(1) play() end -- play tone T_340Hz_Int_2Hz +function sounder.test_2() add(2) play() end -- play tone T_544Hz_440Hz_Alt +function sounder.test_3() add(3) play() end -- play tone T_660Hz_Int_125ms +function sounder.test_4() add(4) play() end -- play tone T_745Hz_Int_1Hz +function sounder.test_5() add(5) play() end -- play tone T_800Hz_Int +function sounder.test_6() add(6) play() end -- play tone T_800Hz_1000Hz_Alt +function sounder.test_7() add(7) play() end -- play tone T_1000Hz_Int +function sounder.test_8() add(8) play() end -- play tone T_1800Hz_Int_4Hz + +function sounder.test_breach(active) test_alarms[ALARM.ContainmentBreach] = active end ---@param active boolean +function sounder.test_rad(active) test_alarms[ALARM.ContainmentRadiation] = active end ---@param active boolean +function sounder.test_lost(active) test_alarms[ALARM.ReactorLost] = active end ---@param active boolean +function sounder.test_crit(active) test_alarms[ALARM.CriticalDamage] = active end ---@param active boolean +function sounder.test_dmg(active) test_alarms[ALARM.ReactorDamage] = active end ---@param active boolean +function sounder.test_overtemp(active) test_alarms[ALARM.ReactorOverTemp] = active end ---@param active boolean +function sounder.test_hightemp(active) test_alarms[ALARM.ReactorHighTemp] = active end ---@param active boolean +function sounder.test_wasteleak(active) test_alarms[ALARM.ReactorWasteLeak] = active end ---@param active boolean +function sounder.test_highwaste(active) test_alarms[ALARM.ReactorHighWaste] = active end ---@param active boolean +function sounder.test_rps(active) test_alarms[ALARM.RPSTransient] = active end ---@param active boolean +function sounder.test_rcs(active) test_alarms[ALARM.RCSTransient] = active end ---@param active boolean +function sounder.test_turbinet(active) test_alarms[ALARM.TurbineTrip] = active end ---@param active boolean + +-- power rescaling limiter test +function sounder.test_power_scale() + local start = util.time_ms() + + zero() + + for id = 1, #TONES do + if TONES[id].active then + for i = 1, 4 do + for s = 1, _05s_SAMPLES do + alarm_ctl.quad_buffer[i][s] = limit(alarm_ctl.quad_buffer[i][s] + + (TONES[id].component[i][s] / math.sqrt(alarm_ctl.num_active))) + end + end + end + end + + log.debug("power rescale test took " .. (util.time_ms() - start) .. "ms") +end + +--#endregion + +return sounder diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 225c681..1be9aa6 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,8 +16,9 @@ local apisessions = require("coordinator.apisessions") local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") +local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "alpha-v0.7.1" +local COORDINATOR_VERSION = "beta-v0.7.2" local print = util.print local println = util.println @@ -91,6 +92,24 @@ local function main() log_sys("system start on " .. os.date("%c")) log_boot("starting " .. COORDINATOR_VERSION) + ---------------------------------------- + -- setup alarm sounder subsystem + ---------------------------------------- + + local speaker = ppm.get_device("speaker") + if speaker == nil then + log_boot("annunciator alarm speaker not found") + println("boot> speaker not found") + log.fatal("no annunciator alarm speaker found") + return + else + local sounder_start = util.time_ms() + log_boot("annunciator alarm speaker connected") + sounder.init(speaker) + log_boot("tone generation took " .. (util.time_ms() - sounder_start) .. "ms") + log_sys("annunciator alarm configured") + end + ---------------------------------------- -- setup communications ---------------------------------------- @@ -304,6 +323,9 @@ local function main() elseif event == "monitor_touch" then -- handle a monitor touch event renderer.handle_touch(core.events.touch(param1, param2, param3)) + elseif event == "speaker_audio_empty" then + -- handle speaker buffer emptied + sounder.continue() end -- check for termination request @@ -320,6 +342,7 @@ local function main() end renderer.close_ui() + sounder.stop() log_sys("system shutdown") println_ts("exited") diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index d72b9fc..c2ebf16 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -250,7 +250,7 @@ local function init(parent, id) TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} end - local t1_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} + local t1_sdo = TriIndicatorLight{parent=annunciator,label="Steam Relief Valve",c1=colors.gray,c2=colors.yellow,c3=colors.red} t_ps[1].subscribe("SteamDumpOpen", function (val) t1_sdo.update(val + 1) end) TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} @@ -264,7 +264,7 @@ local function init(parent, id) if unit.num_turbines > 1 then TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t2_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} + local t2_sdo = TriIndicatorLight{parent=annunciator,label="Steam Relief Valve",c1=colors.gray,c2=colors.yellow,c3=colors.red} t_ps[2].subscribe("SteamDumpOpen", function (val) t2_sdo.update(val + 1) end) TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} @@ -279,7 +279,7 @@ local function init(parent, id) if unit.num_turbines > 2 then TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t3_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} + local t3_sdo = TriIndicatorLight{parent=annunciator,label="Steam Relief Valve",c1=colors.gray,c2=colors.yellow,c3=colors.red} t_ps[3].subscribe("SteamDumpOpen", function (val) t3_sdo.update(val + 1) end) TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index b9145a3..0a70971 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -3,6 +3,7 @@ -- local iocontrol = require("coordinator.iocontrol") +local sounder = require("coordinator.sounder") local style = require("coordinator.ui.style") @@ -10,11 +11,17 @@ local unit_overview = require("coordinator.ui.components.unit_overview") local core = require("graphics.core") +local ColorMap = require("graphics.elements.colormap") local DisplayBox = require("graphics.elements.displaybox") local TextBox = require("graphics.elements.textbox") +local PushButton = require("graphics.elements.controls.push_button") +local SwitchButton = require("graphics.elements.controls.switch_button") + local TEXT_ALIGN = core.graphics.TEXT_ALIGN +local cpair = core.graphics.cpair + -- create new main view ---@param monitor table main viewscreen local function init(monitor) @@ -41,6 +48,35 @@ local function init(monitor) -- command & control + -- testing + ---@fixme remove test code + + ColorMap{parent=main,x=2,y=(main.height()-1)} + + PushButton{parent=main,x=2,y=(main.height()-20),text="TEST 1",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_1} + PushButton{parent=main,x=2,text="TEST 2",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_2} + PushButton{parent=main,x=2,text="TEST 3",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_3} + PushButton{parent=main,x=2,text="TEST 4",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_4} + PushButton{parent=main,x=2,text="TEST 5",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_5} + PushButton{parent=main,x=2,text="TEST 6",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_6} + PushButton{parent=main,x=2,text="TEST 7",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_7} + PushButton{parent=main,x=2,text="TEST 8",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_8} + PushButton{parent=main,x=2,text="STOP",min_width=8,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.stop} + PushButton{parent=main,x=2,text="PSCALE",min_width=8,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_power_scale} + + SwitchButton{parent=main,x=12,y=(main.height()-20),text="CONTAINMENT BREACH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_breach} + SwitchButton{parent=main,x=12,text="CONTAINMENT RADIATION",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rad} + SwitchButton{parent=main,x=12,text="REACTOR LOST",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_lost} + SwitchButton{parent=main,x=12,text="CRITICAL DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_crit} + SwitchButton{parent=main,x=12,text="REACTOR DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_dmg} + SwitchButton{parent=main,x=12,text="REACTOR OVER TEMP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_overtemp} + SwitchButton{parent=main,x=12,text="REACTOR HIGH TEMP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_hightemp} + SwitchButton{parent=main,x=12,text="REACTOR WASTE LEAK",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_wasteleak} + SwitchButton{parent=main,x=12,text="REACTOR WASTE HIGH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_highwaste} + SwitchButton{parent=main,x=12,text="RPS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rps} + SwitchButton{parent=main,x=12,text="RCS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rcs} + SwitchButton{parent=main,x=12,text="TURBINE TRIP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_turbinet} + return main end diff --git a/graphics/elements/controls/switch_button.lua b/graphics/elements/controls/switch_button.lua index cb003cb..46364b5 100644 --- a/graphics/elements/controls/switch_button.lua +++ b/graphics/elements/controls/switch_button.lua @@ -36,7 +36,7 @@ local function switch_button(args) -- button state (convert nil to false if missing) e.value = args.default or false - local h_pad = math.floor((e.frame.w - text_width) / 2) + local h_pad = math.floor((e.frame.w - text_width) / 2) + 1 local v_pad = math.floor(e.frame.h / 2) + 1 -- show the button state @@ -51,6 +51,9 @@ local function switch_button(args) e.window.setBackgroundColor(e.fg_bg.bkg) end + -- clear to redraw background + e.window.clear() + -- write the button text e.window.setCursorPos(h_pad, v_pad) e.window.write(args.text) diff --git a/scada-common/types.lua b/scada-common/types.lua index 2ef80b3..abc7561 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -96,7 +96,7 @@ types.ALARM_PRIO_MAP = { types.ALARM_PRIORITY.URGENT, types.ALARM_PRIORITY.CRITICAL, types.ALARM_PRIORITY.EMERGENCY, - types.ALARM_PRIORITY.URGENT, + types.ALARM_PRIORITY.EMERGENCY, types.ALARM_PRIORITY.TIMELY, types.ALARM_PRIORITY.EMERGENCY, types.ALARM_PRIORITY.TIMELY, diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 4dac34a..1aaf384 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -57,7 +57,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) seq_num = 0, r_seq_num = nil, connected = true, - conn_watchdog = util.new_watchdog(3), + conn_watchdog = util.new_watchdog(5), last_rtt = 0, -- periodic messages periodics = { diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 3d27727..6257679 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.8.1" +local SUPERVISOR_VERSION = "beta-v0.8.2" local print = util.print local println = util.println From 947570093023daf0f7cba98f772ebc98ec2e1576 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 4 Dec 2022 14:29:39 -0500 Subject: [PATCH 455/587] added sounder volume to config --- coordinator/config.lua | 3 +++ coordinator/sounder.lua | 4 +++- coordinator/startup.lua | 5 +++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/coordinator/config.lua b/coordinator/config.lua index 48088b5..ccd8b04 100644 --- a/coordinator/config.lua +++ b/coordinator/config.lua @@ -10,6 +10,9 @@ config.SCADA_API_LISTEN = 16200 config.NUM_UNITS = 4 -- graphics color config.RECOLOR = true +-- alarm sounder volume (0.0 to 3.0, 1.0 being standard max volume, this is the option given to to speaker.play()) +-- note: alarm sine waves are at half saturation, so that multiple will be required to reach full scale +config.SOUNDER_VOLUME = 1.0 -- log path config.LOG_PATH = "/log.txt" -- log mode diff --git a/coordinator/sounder.lua b/coordinator/sounder.lua index 7e7b779..92a69ee 100644 --- a/coordinator/sounder.lua +++ b/coordinator/sounder.lua @@ -265,10 +265,12 @@ end -- initialize the annunciator alarm system ---@param speaker table speaker peripheral -function sounder.init(speaker) +---@param volume number speaker volume +function sounder.init(speaker, volume) alarm_ctl.speaker = speaker alarm_ctl.speaker.stop() + alarm_ctl.volume = volume alarm_ctl.playing = false alarm_ctl.num_active = 0 alarm_ctl.next_block = 1 diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 1be9aa6..9ede0fc 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -18,7 +18,7 @@ local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.7.2" +local COORDINATOR_VERSION = "beta-v0.7.3" local print = util.print local println = util.println @@ -42,6 +42,7 @@ cfv.assert_port(config.SCADA_SV_LISTEN) cfv.assert_port(config.SCADA_API_LISTEN) cfv.assert_type_int(config.NUM_UNITS) cfv.assert_type_bool(config.RECOLOR) +cfv.assert_type_num(config.SOUNDER_VOLUME) cfv.assert_type_str(config.LOG_PATH) cfv.assert_type_int(config.LOG_MODE) cfv.assert_type_bool(config.SECURE) @@ -105,7 +106,7 @@ local function main() else local sounder_start = util.time_ms() log_boot("annunciator alarm speaker connected") - sounder.init(speaker) + sounder.init(speaker, config.SOUNDER_VOLUME) log_boot("tone generation took " .. (util.time_ms() - sounder_start) .. "ms") log_sys("annunciator alarm configured") end From 5224dcbd25583360b5480bf03b77b8af6e423c57 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 4 Dec 2022 14:36:29 -0500 Subject: [PATCH 456/587] reconnect alarm sounder speaker on peripheral reconnect --- coordinator/sounder.lua | 7 +++++++ coordinator/startup.lua | 10 +++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/coordinator/sounder.lua b/coordinator/sounder.lua index 92a69ee..594d6c3 100644 --- a/coordinator/sounder.lua +++ b/coordinator/sounder.lua @@ -288,6 +288,13 @@ function sounder.init(speaker, volume) gen_tone_8() end +-- reconnect the speaker peripheral +---@param speaker table speaker peripheral +function sounder.reconnect(speaker) + alarm_ctl.speaker = speaker + alarm_ctl.playing = false +end + -- check alarm state to enable/disable alarms ---@param units table|nil unit list or nil to use test mode function sounder.eval(units) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 9ede0fc..14cf63d 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -18,7 +18,7 @@ local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.7.3" +local COORDINATOR_VERSION = "beta-v0.7.4" local print = util.print local println = util.println @@ -238,11 +238,15 @@ local function main() elseif type == "monitor" then if renderer.is_monitor_used(device) then -- "halt and catch fire" style handling + println_ts("lost a configured monitor, system will now exit") log_sys("lost a configured monitor, system will now exit") break else log_sys("lost unused monitor, ignoring") end + elseif type == "speaker" then + println_ts("lost alarm sounder speaker") + log_sys("lost alarm sounder speaker") end end elseif event == "peripheral" then @@ -267,6 +271,10 @@ local function main() end elseif type == "monitor" then -- not supported, system will exit on loss of in-use monitors + elseif type == "speaker" then + println_ts("alarm sounder speaker reconnected") + log_sys("alarm sounder speaker reconnected") + sounder.reconnect(device) end end elseif event == "timer" then From 6bdde022685c96a680e96a5c97d059b6975a6513 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 5 Dec 2022 16:17:09 -0500 Subject: [PATCH 457/587] #131 start of unit status text, added updating coordinator waste processing option on reconnect --- coordinator/iocontrol.lua | 178 ++++++++++--------- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 40 +++-- coordinator/ui/layout/main_view.lua | 27 ++- coordinator/ui/layout/unit_view.lua | 10 +- graphics/elements/controls/hazard_button.lua | 2 +- graphics/elements/controls/multi_button.lua | 1 - graphics/elements/controls/push_button.lua | 2 +- graphics/elements/controls/switch_button.lua | 3 - supervisor/session/coordinator.lua | 8 +- supervisor/session/unit.lua | 92 ++++++++-- supervisor/startup.lua | 2 +- 12 files changed, 233 insertions(+), 134 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 6aa2caa..76e10cc 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -39,7 +39,7 @@ function iocontrol.init(conf, comms) ---@class ioctl_entry local entry = { - unit_id = i, ---@type integer + unit_id = i, ---@type integer initialized = false, num_boilers = 0, @@ -53,15 +53,15 @@ function iocontrol.init(conf, comms) scram = function () end, reset_rps = function () end, ack_alarms = function () end, - set_burn = function (rate) end, ---@param rate number - set_waste = function (mode) end, ---@param mode integer + set_burn = function (rate) end, ---@param rate number + set_waste = function (mode) end, ---@param mode integer - start_ack = function (success) end, ---@param success boolean - scram_ack = function (success) end, ---@param success boolean - reset_rps_ack = function (success) end, ---@param success boolean - ack_alarms_ack = function (success) end,---@param success boolean - set_burn_ack = function (success) end, ---@param success boolean - set_waste_ack = function (success) end, ---@param success boolean + start_ack = function (success) end, ---@param success boolean + scram_ack = function (success) end, ---@param success boolean + reset_rps_ack = function (success) end, ---@param success boolean + ack_alarms_ack = function (success) end, ---@param success boolean + set_burn_ack = function (success) end, ---@param success boolean + set_waste_ack = function (success) end, ---@param success boolean alarm_callbacks = { c_breach = { ack = function () ack(1) end, reset = function () reset(1) end }, @@ -95,7 +95,7 @@ function iocontrol.init(conf, comms) }, reactor_ps = psil.create(), - reactor_data = {}, ---@type reactor_db + reactor_data = {}, ---@type reactor_db boiler_ps_tbl = {}, boiler_data_tbl = {}, @@ -221,18 +221,19 @@ end ---@return boolean valid function iocontrol.update_statuses(statuses) if type(statuses) ~= "table" then - log.debug("unit statuses not a table") + log.debug("iocontrol.update_statuses: unit statuses not a table") return false elseif #statuses ~= #io.units then - log.debug("number of provided unit statuses does not match expected number of units") + log.debug("iocontrol.update_statuses: number of provided unit statuses does not match expected number of units") return false else for i = 1, #statuses do + local log_header = util.c("iocontrol.update_statuses[unit ", i, "]: ") local unit = io.units[i] ---@type ioctl_entry local status = statuses[i] - if type(status) ~= "table" or #status ~= 4 then - log.debug("invalid status entry in unit statuses (not a table or invalid length)") + if type(status) ~= "table" or #status ~= 5 then + log.debug(log_header .. "invalid status entry in unit statuses (not a table or invalid length)") return false end @@ -255,7 +256,7 @@ function iocontrol.update_statuses(statuses) unit.reactor_data.no_reactor = gen_status[5] unit.reactor_data.formed = gen_status[6] else - log.debug("reactor general status length mismatch") + log.debug(log_header .. "reactor general status length mismatch") end unit.reactor_data.rps_status = rps_status ---@type rps_status @@ -295,75 +296,16 @@ function iocontrol.update_statuses(statuses) end end else - log.debug("reactor status length mismatch") - end - - -- annunciator - - local annunciator = status[2] ---@type annunciator - - for key, val in pairs(annunciator) do - if key == "TurbineTrip" then - -- split up turbine trip table for all turbines and a general OR combination - local trips = val - local any = false - - for id = 1, #trips do - any = any or trips[id] - unit.turbine_ps_tbl[id].publish(key, trips[id]) - end - - unit.reactor_ps.publish("TurbineTrip", any) - elseif key == "BoilerOnline" or key == "HeatingRateLow" or key == "WaterLevelLow" then - -- split up array for all boilers - for id = 1, #val do - unit.boiler_ps_tbl[id].publish(key, val[id]) - end - elseif key == "TurbineOnline" or key == "SteamDumpOpen" or key == "TurbineOverSpeed" then - -- split up array for all turbines - for id = 1, #val do - unit.turbine_ps_tbl[id].publish(key, val[id]) - end - elseif type(val) == "table" then - -- we missed one of the tables? - log.error("unrecognized table found in annunciator list, this is a bug", true) - else - -- non-table fields - unit.reactor_ps.publish(key, val) - end - end - - -- alarms - - local alarm_states = status[3] - - if type(alarm_states) == "table" then - for id = 1, #alarm_states do - local state = alarm_states[id] - - unit.alarms[id] = state - - if state == types.ALARM_STATE.TRIPPED or state == types.ALARM_STATE.ACKED then - unit.reactor_ps.publish("ALM" .. id, 2) - elseif state == types.ALARM_STATE.RING_BACK then - unit.reactor_ps.publish("ALM" .. id, 3) - else - unit.reactor_ps.publish("ALM" .. id, 1) - end - end - else - log.debug("alarm states not a table") - return false + log.debug(log_header .. "reactor status length mismatch") end -- RTU statuses - local rtu_statuses = status[4] + local rtu_statuses = status[2] if type(rtu_statuses) == "table" then + -- boiler statuses if type(rtu_statuses.boilers) == "table" then - -- boiler statuses - for id = 1, #unit.boiler_data_tbl do if rtu_statuses.boilers[i] == nil then -- disconnected @@ -403,12 +345,11 @@ function iocontrol.update_statuses(statuses) end end else - log.debug("boiler list not a table") + log.debug(log_header .. "boiler list not a table") end + -- turbine statuses if type(rtu_statuses.turbines) == "table" then - -- turbine statuses - for id = 1, #unit.turbine_ps_tbl do if rtu_statuses.turbines[i] == nil then -- disconnected @@ -450,11 +391,84 @@ function iocontrol.update_statuses(statuses) end end else - log.debug("turbine list not a table") + log.debug(log_header .. "turbine list not a table") return false end else - log.debug("rtu list not a table") + log.debug(log_header .. "rtu list not a table") + end + + -- annunciator + + local annunciator = status[3] ---@type annunciator + + for key, val in pairs(annunciator) do + if key == "TurbineTrip" then + -- split up turbine trip table for all turbines and a general OR combination + local trips = val + local any = false + + for id = 1, #trips do + any = any or trips[id] + unit.turbine_ps_tbl[id].publish(key, trips[id]) + end + + unit.reactor_ps.publish("TurbineTrip", any) + elseif key == "BoilerOnline" or key == "HeatingRateLow" or key == "WaterLevelLow" then + -- split up array for all boilers + for id = 1, #val do + unit.boiler_ps_tbl[id].publish(key, val[id]) + end + elseif key == "TurbineOnline" or key == "SteamDumpOpen" or key == "TurbineOverSpeed" then + -- split up array for all turbines + for id = 1, #val do + unit.turbine_ps_tbl[id].publish(key, val[id]) + end + elseif type(val) == "table" then + -- we missed one of the tables? + log.error(log_header .. "unrecognized table found in annunciator list, this is a bug", true) + else + -- non-table fields + unit.reactor_ps.publish(key, val) + end + end + + -- alarms + + local alarm_states = status[4] + + if type(alarm_states) == "table" then + for id = 1, #alarm_states do + local state = alarm_states[id] + + unit.alarms[id] = state + + if state == types.ALARM_STATE.TRIPPED or state == types.ALARM_STATE.ACKED then + unit.reactor_ps.publish("Alarm_" .. id, 2) + elseif state == types.ALARM_STATE.RING_BACK then + unit.reactor_ps.publish("Alarm_" .. id, 3) + else + unit.reactor_ps.publish("Alarm_" .. id, 1) + end + end + else + log.debug(log_header .. "alarm states not a table") + end + + -- unit state fields + + local unit_state = status[5] + + if type(unit_state) == "table" then + if #unit_state == 3 then + unit.reactor_ps.publish("U_StatusLine1", unit_state[1]) + unit.reactor_ps.publish("U_StatusLine2", unit_state[2]) + unit.reactor_ps.publish("U_WasteMode", unit_state[3]) + else + log.debug(log_header .. "unit state length mismatch") + end + else + log.debug(log_header .. "unit state not a table") end end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 14cf63d..db8c426 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -18,7 +18,7 @@ local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.7.4" +local COORDINATOR_VERSION = "beta-v0.7.5" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index c2ebf16..39bfeb4 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -308,8 +308,8 @@ local function init(parent, id) local set_burn = function () unit.set_burn(burn_rate.get_value()) end PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} - r_ps.subscribe("burn_rate", function (v) burn_rate.set_value(v) end) - r_ps.subscribe("max_burn", function (v) burn_rate.set_max(v) end) + r_ps.subscribe("burn_rate", burn_rate.set_value) + r_ps.subscribe("max_burn", burn_rate.set_max) local dis_colors = cpair(colors.white, colors.lightGray) @@ -334,18 +334,24 @@ local function init(parent, id) r_ps.subscribe("rps_tripped", start_button_en_check) r_ps.subscribe("rps_tripped", function (active) if active then reset.enable() else reset.disable() end end) - TextBox{parent=main,x=2,y=30,text="Idle",width=29,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} + local stat_line_1 = DataIndicator{parent=main,x=2,y=30,label="",format="%s",value="",width=29,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} + local stat_line_2 = DataIndicator{parent=main,x=2,y=31,label="",format="%s",value="",width=29,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} + + r_ps.subscribe("U_StatusLine1", stat_line_1.update) + r_ps.subscribe("U_StatusLine2", stat_line_2.update) local waste_sel = Div{parent=main,x=2,y=50,width=29,height=2,fg_bg=cpair(colors.black, colors.white)} - MultiButton{parent=waste_sel,x=1,y=1,options=waste_opts,callback=unit.set_waste,min_width=6,fg_bg=cpair(colors.black, colors.white)} + local waste_mode = MultiButton{parent=waste_sel,x=1,y=1,options=waste_opts,callback=unit.set_waste,min_width=6,fg_bg=cpair(colors.black, colors.white)} TextBox{parent=waste_sel,text="Waste Processing",alignment=TEXT_ALIGN.CENTER,x=1,y=1,height=1} + r_ps.subscribe("U_WasteMode", waste_mode.set_value) + ---------------------- -- alarm management -- ---------------------- - local alarm_panel = Div{parent=main,x=2,y=32,width=29,height=16,fg_bg=cpair(colors.black,colors.white)} + local alarm_panel = Div{parent=main,x=2,y=33,width=29,height=16,fg_bg=cpair(colors.black,colors.white)} local a_brc = AlarmLight{parent=alarm_panel,x=6,y=2,label="Containment Breach",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} local a_rad = AlarmLight{parent=alarm_panel,x=6,label="Containment Radiation",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} @@ -362,20 +368,20 @@ local function init(parent, id) local a_clt = AlarmLight{parent=alarm_panel,x=6,label="RCS Transient",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS} local a_tbt = AlarmLight{parent=alarm_panel,x=6,label="Turbine Trip",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} - r_ps.subscribe("ALM1", a_brc.update) - r_ps.subscribe("ALM2", a_rad.update) - r_ps.subscribe("ALM4", a_dmg.update) + r_ps.subscribe("Alarm_1", a_brc.update) + r_ps.subscribe("Alarm_2", a_rad.update) + r_ps.subscribe("Alarm_4", a_dmg.update) - r_ps.subscribe("ALM3", a_rcl.update) - r_ps.subscribe("ALM5", a_rcd.update) - r_ps.subscribe("ALM6", a_rot.update) - r_ps.subscribe("ALM7", a_rht.update) - r_ps.subscribe("ALM8", a_rwl.update) - r_ps.subscribe("ALM9", a_rwh.update) + r_ps.subscribe("Alarm_3", a_rcl.update) + r_ps.subscribe("Alarm_5", a_rcd.update) + r_ps.subscribe("Alarm_6", a_rot.update) + r_ps.subscribe("Alarm_7", a_rht.update) + r_ps.subscribe("Alarm_8", a_rwl.update) + r_ps.subscribe("Alarm_9", a_rwh.update) - r_ps.subscribe("ALM10", a_rps.update) - r_ps.subscribe("ALM11", a_clt.update) - r_ps.subscribe("ALM12", a_tbt.update) + r_ps.subscribe("Alarm_10", a_rps.update) + r_ps.subscribe("Alarm_11", a_clt.update) + r_ps.subscribe("Alarm_12", a_tbt.update) -- ack's and resets diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index 0a70971..b5fd744 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -4,6 +4,7 @@ local iocontrol = require("coordinator.iocontrol") local sounder = require("coordinator.sounder") +local util = require("scada-common.util") local style = require("coordinator.ui.style") @@ -28,32 +29,46 @@ local function init(monitor) local main = DisplayBox{window=monitor,fg_bg=style.root} -- window header message - TextBox{parent=main,text="Nuclear Generation Facility SCADA Coordinator",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + local header = TextBox{parent=main,text="Nuclear Generation Facility SCADA Coordinator",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} local db = iocontrol.get_db() local uo_1, uo_2, uo_3, uo_4 ---@type graphics_element + local cnc_y_start = 3 + -- unit overviews - if db.facility.num_units >= 1 then uo_1 = unit_overview(main, 2, 3, db.units[1]) end - if db.facility.num_units >= 2 then uo_2 = unit_overview(main, 84, 3, db.units[2]) end + if db.facility.num_units >= 1 then + uo_1 = unit_overview(main, 2, 3, db.units[1]) + cnc_y_start = cnc_y_start + uo_1.height() + 1 + end + + if db.facility.num_units >= 2 then + uo_2 = unit_overview(main, 84, 3, db.units[2]) + end if db.facility.num_units >= 3 then -- base offset 3, spacing 1, max height of units 1 and 2 local row_2_offset = 3 + 1 + math.max(uo_1.height(), uo_2.height()) uo_3 = unit_overview(main, 2, row_2_offset, db.units[3]) - if db.facility.num_units == 4 then uo_4 = unit_overview(main, 84, row_2_offset, db.units[4]) end + cnc_y_start = cnc_y_start + uo_3.height() + 1 + + if db.facility.num_units == 4 then + uo_4 = unit_overview(main, 84, row_2_offset, db.units[4]) + end end -- command & control + TextBox{parent=main,y=cnc_y_start,text=util.strrep("\x8c", header.width()),alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + -- testing ---@fixme remove test code ColorMap{parent=main,x=2,y=(main.height()-1)} - PushButton{parent=main,x=2,y=(main.height()-20),text="TEST 1",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_1} + PushButton{parent=main,x=2,y=(cnc_y_start+2),text="TEST 1",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_1} PushButton{parent=main,x=2,text="TEST 2",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_2} PushButton{parent=main,x=2,text="TEST 3",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_3} PushButton{parent=main,x=2,text="TEST 4",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_4} @@ -64,7 +79,7 @@ local function init(monitor) PushButton{parent=main,x=2,text="STOP",min_width=8,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.stop} PushButton{parent=main,x=2,text="PSCALE",min_width=8,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_power_scale} - SwitchButton{parent=main,x=12,y=(main.height()-20),text="CONTAINMENT BREACH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_breach} + SwitchButton{parent=main,x=12,y=(cnc_y_start+2),text="CONTAINMENT BREACH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_breach} SwitchButton{parent=main,x=12,text="CONTAINMENT RADIATION",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rad} SwitchButton{parent=main,x=12,text="REACTOR LOST",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_lost} SwitchButton{parent=main,x=12,text="CRITICAL DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_crit} diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index bdf0537..1c5fddf 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -2,15 +2,11 @@ -- Reactor Unit SCADA Coordinator GUI -- -local tcallbackdsp = require("scada-common.tcallbackdsp") +local style = require("coordinator.ui.style") -local iocontrol = require("coordinator.iocontrol") +local unit_detail = require("coordinator.ui.components.unit_detail") -local style = require("coordinator.ui.style") - -local unit_detail = require("coordinator.ui.components.unit_detail") - -local DisplayBox = require("graphics.elements.displaybox") +local DisplayBox = require("graphics.elements.displaybox") -- create a unit view ---@param monitor table diff --git a/graphics/elements/controls/hazard_button.lua b/graphics/elements/controls/hazard_button.lua index 7369866..51edfb8 100644 --- a/graphics/elements/controls/hazard_button.lua +++ b/graphics/elements/controls/hazard_button.lua @@ -174,7 +174,7 @@ local function hazard_button(args) end end - -- set the value + -- set the value (true simulates pressing the button) ---@param val boolean new value function e.set_value(val) if val then e.handle_touch(core.events.touch("", 1, 1)) end diff --git a/graphics/elements/controls/multi_button.lua b/graphics/elements/controls/multi_button.lua index 4948b33..24614c8 100644 --- a/graphics/elements/controls/multi_button.lua +++ b/graphics/elements/controls/multi_button.lua @@ -109,7 +109,6 @@ local function multi_button(args) function e.set_value(val) e.value = val draw() - args.callback(e.value) end -- initial draw diff --git a/graphics/elements/controls/push_button.lua b/graphics/elements/controls/push_button.lua index 397a367..018b0e9 100644 --- a/graphics/elements/controls/push_button.lua +++ b/graphics/elements/controls/push_button.lua @@ -72,7 +72,7 @@ local function push_button(args) end end - -- set the value + -- set the value (true simulates pressing the button) ---@param val boolean new value function e.set_value(val) if val then e.handle_touch(core.events.touch("", 1, 1)) end diff --git a/graphics/elements/controls/switch_button.lua b/graphics/elements/controls/switch_button.lua index 46364b5..bf138f2 100644 --- a/graphics/elements/controls/switch_button.lua +++ b/graphics/elements/controls/switch_button.lua @@ -82,9 +82,6 @@ local function switch_button(args) -- set state e.value = val draw_state() - - -- call the touch callback with state - args.callback(e.value) end return e.get() diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 1aaf384..9a5f10f 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -129,7 +129,13 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) for i = 1, #self.units do local unit = self.units[i] ---@type reactor_unit - status[unit.get_id()] = { unit.get_reactor_status(), unit.get_annunciator(), unit.get_alarms(), unit.get_rtu_statuses() } + status[unit.get_id()] = { + unit.get_reactor_status(), + unit.get_rtu_statuses(), + unit.get_annunciator(), + unit.get_alarms(), + unit.get_state() + } end _send(SCADA_CRDN_TYPES.UNIT_STATUSES, status) diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index fa85adc..4cf3ac1 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -54,6 +54,13 @@ local aistate_string = { "RING_BACK_TRIPPING" } +-- check if an alarm is active (tripped or ack'd) +---@param alarm table alarm entry +---@return boolean active +local function is_active(alarm) + return alarm.state == AISTATE.TRIPPED or alarm.state == AISTATE.ACKED +end + ---@class alarm_def ---@field state ALARM_INT_STATE internal alarm state ---@field trip_time integer time (ms) when first tripped @@ -73,11 +80,16 @@ function unit.new(for_reactor, num_boilers, num_turbines) turbines = {}, boilers = {}, redstone = {}, + -- state tracking deltas = {}, last_heartbeat = 0, + damage_initial = 0, + damage_start = 0, + waste_mode = WASTE_MODE.AUTO, + status_text = { "Unknown", "Awaiting Connection..." }, -- logic for alarms had_reactor = false, - start_time = 0, + start_ms = 0, plc_cache = { ok = false, rps_trip = false, @@ -110,7 +122,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) RPSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.RPSTransient, tier = PRIO.URGENT }, -- BoilRateMismatch, CoolantFeedMismatch, SteamFeedMismatch, MaxWaterReturnFeed RCSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 5, id = ALARM.RCSTransient, tier = PRIO.TIMELY }, - -- "It's just a routine turbin' trip!" -Bill Gibson + -- "It's just a routine turbin' trip!" -Bill Gibson, "The China Syndrome" TurbineTrip = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.TurbineTrip, tier = PRIO.URGENT } }, ---@class unit_db @@ -244,10 +256,10 @@ function unit.new(for_reactor, num_boilers, num_turbines) end -- waste valves - local waste_pu = { open = function () __rs_w(IO.WASTE_PU, true) end, close = function () __rs_w(IO.WASTE_PU, false) end } - local waste_sna = { open = function () __rs_w(IO.WASTE_PO, true) end, close = function () __rs_w(IO.WASTE_PO, false) end } + local waste_pu = { open = function () __rs_w(IO.WASTE_PU, true) end, close = function () __rs_w(IO.WASTE_PU, false) end } + local waste_sna = { open = function () __rs_w(IO.WASTE_PO, true) end, close = function () __rs_w(IO.WASTE_PO, false) end } local waste_po = { open = function () __rs_w(IO.WASTE_POPL, true) end, close = function () __rs_w(IO.WASTE_POPL, false) end } - local waste_sps = { open = function () __rs_w(IO.WASTE_AM, true) end, close = function () __rs_w(IO.WASTE_AM, false) end } + local waste_sps = { open = function () __rs_w(IO.WASTE_AM, true) end, close = function () __rs_w(IO.WASTE_AM, false) end } --#endregion @@ -396,14 +408,14 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- check PLC status self.db.annunciator.PLCOnline = (self.plc_s ~= nil) and (self.plc_s.open) - if self.plc_s ~= nil then + if self.plc_i ~= nil then local plc_db = self.plc_i.get_db() -- record reactor start time (some alarms are delayed during reactor heatup) - if self.start_time == 0 and plc_db.mek_status.status then - self.start_time = util.time_ms() + if self.start_ms == 0 and plc_db.mek_status.status then + self.start_ms = util.time_ms() elseif not plc_db.mek_status.status then - self.start_time = 0 + self.start_ms = 0 end -- record reactor stats @@ -414,6 +426,15 @@ function unit.new(for_reactor, num_boilers, num_turbines) self.plc_cache.temp = plc_db.mek_status.temp self.plc_cache.waste = plc_db.mek_status.waste_fill + -- track damage + if (self.damage_initial == 0) and (plc_db.mek_status.damage > 0) then + self.damage_start = util.time_s() + self.damage_initial = plc_db.mek_status.damage + else + self.damage_initial = 0 + self.damage_start = 0 + end + -- heartbeat blink about every second if self.last_heartbeat + 1000 < plc_db.last_status_update then self.db.annunciator.PLCHeartbeat = not self.db.annunciator.PLCHeartbeat @@ -633,9 +654,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) if plc_cache.rps_status.manual ~= nil then if plc_cache.rps_trip then for key, val in pairs(plc_cache.rps_status) do - if key ~= "manual" and key ~= "timeout" then - rps_alarm = rps_alarm or val - end + if key ~= "manual" and key ~= "timeout" then rps_alarm = rps_alarm or val end end end end @@ -651,7 +670,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) local rcs_trans = any_low or any_over or annunc.RCPTrip or annunc.RCSFlowLow or annunc.MaxWaterReturnFeed -- flow is ramping up right after reactor start, annunciator indicators for these states may not indicate a real issue - if util.time_ms() - self.start_time > FLOW_STABILITY_DELAY_MS then + if util.time_ms() - self.start_ms > FLOW_STABILITY_DELAY_MS then rcs_trans = rcs_trans or annunc.BoilRateMismatch or annunc.CoolantFeedMismatch or annunc.SteamFeedMismatch end @@ -758,6 +777,42 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- update alarm status _update_alarms() + + -- update status text (what the reactor doin?) + if is_active(self.alarms.ContainmentBreach) then + -- boom? or was boom disabled + if self.plc_i ~= nil and self.plc_i.get_rps().force_dis then + self.status_text = { "REACTOR FORCE DISABLED", "meltdown would have occured" } + else + self.status_text = { "CORE MELTDOWN", "reactor destroyed" } + end + elseif is_active(self.alarms.CriticalDamage) then + -- so much for it being a "routine turbin' trip"... + self.status_text = { "MELTDOWN IMMINENT", "evacuate facility immediately" } + elseif is_active(self.alarms.ReactorDamage) then + -- attempt to determine when a chance of a meltdown will occur + self.status_text[1] = "Containment Taking Damage" + if self.plc_cache.damage >= 100 then + self.status_text[2] = "damage critical" + elseif (self.plc_cache.damage - self.damage_initial) > 0 then + local rate = (self.plc_cache.damage - self.damage_initial) / (util.time_s() - self.damage_start) + local remaining_s = (100 - self.plc_cache.damage) * rate + + self.status_text[2] = util.c("damage critical in ", remaining_s, "s") + else + self.status_text[2] = "estimating time to critical..." + end + -- connection dependent states + elseif self.plc_i ~= nil then + local plc_db = self.plc_i.get_db() + if plc_db.mek_status.status then + self.status_text = { "Active", "reactor nominal" } + else + self.status_text = { "Idle", "" } + end + else + self.status_text = { "Reactor Off-line", "awaiting connection..." } + end end -- OPERATIONS -- @@ -792,24 +847,30 @@ function unit.new(for_reactor, num_boilers, num_turbines) function public.set_waste(mode) if mode == WASTE_MODE.AUTO then ---@todo automatic waste routing + self.waste_mode = mode elseif mode == WASTE_MODE.PLUTONIUM then -- route through plutonium generation + self.waste_mode = mode waste_pu.open() waste_sna.close() waste_po.close() waste_sps.close() elseif mode == WASTE_MODE.POLONIUM then -- route through polonium generation into pellets + self.waste_mode = mode waste_pu.close() waste_sna.open() waste_po.open() waste_sps.close() elseif mode == WASTE_MODE.ANTI_MATTER then -- route through polonium generation into SPS + self.waste_mode = mode waste_pu.close() waste_sna.open() waste_po.close() waste_sps.open() + else + log.debug(util.c("invalid waste mode setting ", mode)) end end @@ -889,6 +950,11 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- get the alarm states function public.get_alarms() return self.db.alarm_states end + -- get unit state (currently only waste mode) + function public.get_state() + return { self.status_text[1], self.status_text[2], self.waste_mode } + end + -- get the reactor ID function public.get_id() return self.r_id end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 6257679..e3f0415 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.8.2" +local SUPERVISOR_VERSION = "beta-v0.8.3" local print = util.print local println = util.println From c23ddaf5eacf571748606f7639e4781681c19776 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 6 Dec 2022 11:40:13 -0500 Subject: [PATCH 458/587] #135 added clock and supervisor trip time to coordinator main view --- coordinator/config.lua | 8 +++++++ coordinator/coordinator.lua | 16 ++++++++------ coordinator/iocontrol.lua | 1 + coordinator/startup.lua | 9 +++++++- coordinator/ui/layout/main_view.lua | 31 +++++++++++++++++---------- graphics/elements/indicators/data.lua | 15 ++++++++++--- 6 files changed, 58 insertions(+), 22 deletions(-) diff --git a/coordinator/config.lua b/coordinator/config.lua index ccd8b04..983c7d9 100644 --- a/coordinator/config.lua +++ b/coordinator/config.lua @@ -6,19 +6,27 @@ 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, used only to require that number of unit monitors config.NUM_UNITS = 4 + -- graphics color config.RECOLOR = true + -- alarm sounder volume (0.0 to 3.0, 1.0 being standard max volume, this is the option given to to speaker.play()) -- note: alarm sine waves are at half saturation, so that multiple will be required to reach full scale config.SOUNDER_VOLUME = 1.0 + +-- true for 24 hour time on main view screen +config.TIME_24_HOUR = true + -- log path config.LOG_PATH = "/log.txt" -- log mode -- 0 = APPEND (adds to existing file on start) -- 1 = NEW (replaces existing file on start) config.LOG_MODE = 0 + -- crypto config config.SECURE = true -- must be common between all devices diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 5662cd6..9513b55 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -1,14 +1,12 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local ppm = require("scada-common.ppm") -local util = require("scada-common.util") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") local apisessions = require("coordinator.apisessions") local iocontrol = require("coordinator.iocontrol") -local dialog = require("coordinator.ui.dialog") - -local coordinator = {} +local dialog = require("coordinator.ui.dialog") local print = util.print local println = util.println @@ -22,6 +20,8 @@ local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES local CRDN_COMMANDS = comms.CRDN_COMMANDS +local coordinator = {} + -- request the user to select a monitor ---@param names table available monitors ---@return boolean|string|nil @@ -496,6 +496,8 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa -- log.debug("coord RTT = " .. trip_time .. "ms") + iocontrol.get_db().facility.ps.publish("sv_ping", trip_time) + _send_keep_alive_ack(timestamp) else log.debug("SCADA keep alive packet length mismatch") diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 76e10cc..08adbc2 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -18,6 +18,7 @@ local io = {} -- initialize the coordinator IO controller ---@param conf facility_conf configuration ---@param comms coord_comms comms reference +---@diagnostic disable-next-line: redefined-local function iocontrol.init(conf, comms) io.facility = { scram = false, diff --git a/coordinator/startup.lua b/coordinator/startup.lua index db8c426..efc900e 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -15,10 +15,11 @@ local core = require("graphics.core") local apisessions = require("coordinator.apisessions") local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") +local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.7.5" +local COORDINATOR_VERSION = "beta-v0.7.6" local print = util.print local println = util.println @@ -43,6 +44,7 @@ cfv.assert_port(config.SCADA_API_LISTEN) cfv.assert_type_int(config.NUM_UNITS) cfv.assert_type_bool(config.RECOLOR) cfv.assert_type_num(config.SOUNDER_VOLUME) +cfv.assert_type_bool(config.TIME_24_HOUR) cfv.assert_type_str(config.LOG_PATH) cfv.assert_type_int(config.LOG_MODE) cfv.assert_type_bool(config.SECURE) @@ -199,6 +201,8 @@ local function main() -- main event loop ---------------------------------------- + local date_format = util.trinary(config.TIME_24_HOUR, "%X \x04 %A, %B %d %Y", "%r \x04 %A, %B %d %Y") + local no_modem = false if ui_ok then @@ -284,6 +288,9 @@ local function main() -- free any closed sessions --apisessions.free_all_closed() + -- update date and time string for main display + iocontrol.get_db().facility.ps.publish("date_time", os.date(date_format)) + loop_clock.start() elseif conn_watchdog.is_timer(param1) then -- supervisor watchdog timeout diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index b5fd744..16c114d 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -19,6 +19,8 @@ local TextBox = require("graphics.elements.textbox") local PushButton = require("graphics.elements.controls.push_button") local SwitchButton = require("graphics.elements.controls.switch_button") +local DataIndicator = require("graphics.elements.indicators.data") + local TEXT_ALIGN = core.graphics.TEXT_ALIGN local cpair = core.graphics.cpair @@ -26,42 +28,49 @@ local cpair = core.graphics.cpair -- create new main view ---@param monitor table main viewscreen local function init(monitor) + local facility = iocontrol.get_db().facility + local units = iocontrol.get_db().units + local main = DisplayBox{window=monitor,fg_bg=style.root} -- window header message - local header = TextBox{parent=main,text="Nuclear Generation Facility SCADA Coordinator",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + local header = TextBox{parent=main,y=1,text="Nuclear Generation Facility SCADA Coordinator",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + local ping = DataIndicator{parent=main,x=1,y=1,label="SVTT",format="%d",value=0,unit="ms",lu_colors=cpair(colors.lightGray, colors.white),width=12,fg_bg=style.header} + -- max length example: "01:23:45 AM - Wednesday, September 28 2022" + local datetime = TextBox{parent=main,x=(header.width()-42),y=1,text="",alignment=TEXT_ALIGN.RIGHT,width=42,height=1,fg_bg=style.header} - local db = iocontrol.get_db() + facility.ps.subscribe("sv_ping", ping.update) + facility.ps.subscribe("date_time", datetime.set_value) local uo_1, uo_2, uo_3, uo_4 ---@type graphics_element local cnc_y_start = 3 -- unit overviews - if db.facility.num_units >= 1 then - uo_1 = unit_overview(main, 2, 3, db.units[1]) + if facility.num_units >= 1 then + uo_1 = unit_overview(main, 2, 3, units[1]) cnc_y_start = cnc_y_start + uo_1.height() + 1 end - if db.facility.num_units >= 2 then - uo_2 = unit_overview(main, 84, 3, db.units[2]) + if facility.num_units >= 2 then + uo_2 = unit_overview(main, 84, 3, units[2]) end - if db.facility.num_units >= 3 then + if facility.num_units >= 3 then -- base offset 3, spacing 1, max height of units 1 and 2 local row_2_offset = 3 + 1 + math.max(uo_1.height(), uo_2.height()) - uo_3 = unit_overview(main, 2, row_2_offset, db.units[3]) + uo_3 = unit_overview(main, 2, row_2_offset, units[3]) cnc_y_start = cnc_y_start + uo_3.height() + 1 - if db.facility.num_units == 4 then - uo_4 = unit_overview(main, 84, row_2_offset, db.units[4]) + if facility.num_units == 4 then + uo_4 = unit_overview(main, 84, row_2_offset, units[4]) end end -- command & control - TextBox{parent=main,y=cnc_y_start,text=util.strrep("\x8c", header.width()),alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + TextBox{parent=main,y=cnc_y_start,text=util.strrep("\x8c", header.width()),alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=cpair(colors.lightGray,colors.gray)} -- testing ---@fixme remove test code diff --git a/graphics/elements/indicators/data.lua b/graphics/elements/indicators/data.lua index 5ebc9cb..d19fab0 100644 --- a/graphics/elements/indicators/data.lua +++ b/graphics/elements/indicators/data.lua @@ -42,17 +42,26 @@ local function data(args) e.window.setCursorPos(1, 1) e.window.write(args.label) - local data_start = string.len(args.label) + 2 - if string.len(args.label) == 0 then data_start = 1 end + local label_len = string.len(args.label) + local data_start = 1 + local clear_width = args.width + + if label_len > 0 then + data_start = data_start + (label_len + 1) + clear_width = args.width - (label_len + 1) + end -- on state change ---@param value any new value function e.on_update(value) e.value = value - local data_str = util.sprintf(args.format, value) + -- clear old data and label + e.window.setCursorPos(data_start, 1) + e.window.write(util.spaces(clear_width)) -- write data + local data_str = util.sprintf(args.format, value) e.window.setCursorPos(data_start, 1) e.window.setTextColor(e.fg_bg.fgd) if args.commas then From 52603e357937e5937bef60f7068ad72ce5a418ba Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 6 Dec 2022 23:39:35 -0500 Subject: [PATCH 459/587] #131 first pass of unit status text --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 22 ++--- scada-common/types.lua | 2 +- supervisor/session/unit.lua | 114 +++++++++++++++++++--- supervisor/startup.lua | 2 +- 5 files changed, 115 insertions(+), 27 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index efc900e..7b63302 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.7.6" +local COORDINATOR_VERSION = "beta-v0.7.7" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 39bfeb4..d92f5e7 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -107,6 +107,12 @@ local function init(parent, id) DataIndicator{parent=main,x=21,label="",format="%6.2f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} main.line_break() + local stat_line_1 = TextBox{parent=main,x=2,text="UNKNOWN",width=31,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.black, colors.white)} + local stat_line_2 = TextBox{parent=main,x=2,text="awaiting data",width=31,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} + + r_ps.subscribe("U_StatusLine1", stat_line_1.set_value) + r_ps.subscribe("U_StatusLine2", stat_line_2.set_value) + ----------------- -- annunciator -- ----------------- @@ -301,7 +307,7 @@ local function init(parent, id) -- reactor controls -- ---------------------- - local burn_control = Div{parent=main,x=2,y=22,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} + local burn_control = Div{parent=main,x=2,y=25,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} TextBox{parent=burn_control,x=9,y=2,text="mB/t"} @@ -313,10 +319,10 @@ local function init(parent, id) local dis_colors = cpair(colors.white, colors.lightGray) - local start = HazardButton{parent=main,x=22,y=22,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=hzd_fg_bg} - local ack_a = HazardButton{parent=main,x=12,y=26,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=unit.ack_alarms,fg_bg=hzd_fg_bg} - local scram = HazardButton{parent=main,x=2,y=26,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=hzd_fg_bg} - local reset = HazardButton{parent=main,x=22,y=26,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,fg_bg=hzd_fg_bg} + local start = HazardButton{parent=main,x=22,y=25,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=hzd_fg_bg} + local ack_a = HazardButton{parent=main,x=12,y=29,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=unit.ack_alarms,fg_bg=hzd_fg_bg} + local scram = HazardButton{parent=main,x=2,y=29,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=hzd_fg_bg} + local reset = HazardButton{parent=main,x=22,y=29,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,fg_bg=hzd_fg_bg} unit.start_ack = start.on_response unit.scram_ack = scram.on_response @@ -334,12 +340,6 @@ local function init(parent, id) r_ps.subscribe("rps_tripped", start_button_en_check) r_ps.subscribe("rps_tripped", function (active) if active then reset.enable() else reset.disable() end end) - local stat_line_1 = DataIndicator{parent=main,x=2,y=30,label="",format="%s",value="",width=29,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} - local stat_line_2 = DataIndicator{parent=main,x=2,y=31,label="",format="%s",value="",width=29,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} - - r_ps.subscribe("U_StatusLine1", stat_line_1.update) - r_ps.subscribe("U_StatusLine2", stat_line_2.update) - local waste_sel = Div{parent=main,x=2,y=50,width=29,height=2,fg_bg=cpair(colors.black, colors.white)} local waste_mode = MultiButton{parent=waste_sel,x=1,y=1,options=waste_opts,callback=unit.set_waste,min_width=6,fg_bg=cpair(colors.black, colors.white)} diff --git a/scada-common/types.lua b/scada-common/types.lua index abc7561..cfdfcad 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -175,7 +175,7 @@ types.rtu_t = { env_detector = "environment_detector" } ----@alias rps_status_t string +---@alias rps_status_t rps_trip_cause types.rps_status_t = { ok = "ok", dmg_crit = "dmg_crit", diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 4cf3ac1..b0d0fea 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -85,12 +85,15 @@ function unit.new(for_reactor, num_boilers, num_turbines) last_heartbeat = 0, damage_initial = 0, damage_start = 0, + damage_last = 0, + damage_est_last = 0, waste_mode = WASTE_MODE.AUTO, status_text = { "Unknown", "Awaiting Connection..." }, -- logic for alarms had_reactor = false, start_ms = 0, plc_cache = { + active = false, ok = false, rps_trip = false, rps_status = {}, ---@type rps_status @@ -419,6 +422,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) end -- record reactor stats + self.plc_cache.active = plc_db.mek_status.status self.plc_cache.ok = not (plc_db.rps_status.fault or plc_db.rps_status.sys_fail or plc_db.rps_status.force_dis) self.plc_cache.rps_trip = plc_db.rps_tripped self.plc_cache.rps_status = plc_db.rps_status @@ -427,12 +431,16 @@ function unit.new(for_reactor, num_boilers, num_turbines) self.plc_cache.waste = plc_db.mek_status.waste_fill -- track damage - if (self.damage_initial == 0) and (plc_db.mek_status.damage > 0) then - self.damage_start = util.time_s() - self.damage_initial = plc_db.mek_status.damage + if plc_db.mek_status.damage > 0 then + if self.damage_start == 0 then + self.damage_start = util.time_s() + self.damage_initial = plc_db.mek_status.damage + end else - self.damage_initial = 0 self.damage_start = 0 + self.damage_initial = 0 + self.damage_last = 0 + self.damage_est_last = 0 end -- heartbeat blink about every second @@ -662,15 +670,17 @@ function unit.new(for_reactor, num_boilers, num_turbines) _update_alarm_state(rps_alarm, self.alarms.RPSTransient) -- RCS Transient - local any_low = false + local any_low = annunc.CoolantLevelLow local any_over = false for i = 1, #annunc.WaterLevelLow do any_low = any_low or annunc.WaterLevelLow[i] end for i = 1, #annunc.TurbineOverSpeed do any_over = any_over or annunc.TurbineOverSpeed[i] end local rcs_trans = any_low or any_over or annunc.RCPTrip or annunc.RCSFlowLow or annunc.MaxWaterReturnFeed - -- flow is ramping up right after reactor start, annunciator indicators for these states may not indicate a real issue - if util.time_ms() - self.start_ms > FLOW_STABILITY_DELAY_MS then + -- annunciator indicators for these states may not indicate a real issue when: + -- > flow is ramping up right after reactor start + -- > flow is ramping down after reactor shutdown + if (util.time_ms() - self.start_ms > FLOW_STABILITY_DELAY_MS) and plc_cache.active then rcs_trans = rcs_trans or annunc.BoilRateMismatch or annunc.CoolantFeedMismatch or annunc.SteamFeedMismatch end @@ -791,24 +801,102 @@ function unit.new(for_reactor, num_boilers, num_turbines) self.status_text = { "MELTDOWN IMMINENT", "evacuate facility immediately" } elseif is_active(self.alarms.ReactorDamage) then -- attempt to determine when a chance of a meltdown will occur - self.status_text[1] = "Containment Taking Damage" + self.status_text[1] = "CONTAINMENT TAKING DAMAGE" if self.plc_cache.damage >= 100 then self.status_text[2] = "damage critical" elseif (self.plc_cache.damage - self.damage_initial) > 0 then - local rate = (self.plc_cache.damage - self.damage_initial) / (util.time_s() - self.damage_start) - local remaining_s = (100 - self.plc_cache.damage) * rate + if self.plc_cache.damage > self.damage_last then + self.damage_last = self.plc_cache.damage + local rate = (self.plc_cache.damage - self.damage_initial) / (util.time_s() - self.damage_start) + self.damage_est_last = (100 - self.plc_cache.damage) / rate + end - self.status_text[2] = util.c("damage critical in ", remaining_s, "s") + self.status_text[2] = util.c("damage critical in ", util.sprintf("%.1f", self.damage_est_last), "s") else self.status_text[2] = "estimating time to critical..." end + elseif is_active(self.alarms.ContainmentRadiation) then + self.status_text = { "RADIATION DETECTED", "radiation levels above normal" } + -- elseif is_active(self.alarms.RPSTransient) then + -- RPS status handled when checking reactor status + elseif is_active(self.alarms.RCSTransient) then + self.status_text = { "RCS TRANSIENT", "check coolant system" } + elseif is_active(self.alarms.ReactorOverTemp) then + self.status_text = { "CORE OVER TEMP", "reactor core temperature >=1200K" } + elseif is_active(self.alarms.ReactorWasteLeak) then + self.status_text = { "WASTE LEAK", "radioactive waste leak detected" } + elseif is_active(self.alarms.ReactorHighTemp) then + self.status_text = { "CORE TEMP HIGH", "reactor core temperature >1150K" } + elseif is_active(self.alarms.ReactorHighWaste) then + self.status_text = { "WASTE LEVEL HIGH", "waste accumulating in reactor" } + elseif is_active(self.alarms.TurbineTrip) then + self.status_text = { "TURBINE TRIP", "turbine stall occured" } -- connection dependent states elseif self.plc_i ~= nil then local plc_db = self.plc_i.get_db() if plc_db.mek_status.status then - self.status_text = { "Active", "reactor nominal" } + self.status_text[1] = "ACTIVE" + + if self.db.annunciator.ReactorHighDeltaT then + self.status_text[2] = "core temperature rising" + elseif self.db.annunciator.ReactorTempHigh then + self.status_text[2] = "core temp high, system nominal" + elseif self.db.annunciator.FuelInputRateLow then + self.status_text[2] = "insufficient fuel input rate" + elseif self.db.annunciator.WasteLineOcclusion then + self.status_text[2] = "insufficient waste output rate" + elseif (util.time_ms() - self.start_ms) <= FLOW_STABILITY_DELAY_MS then + if num_turbines > 1 then + self.status_text[2] = "turbines spinning up" + else + self.status_text[2] = "turbine spinning up" + end + else + self.status_text[2] = "system nominal" + end + elseif plc_db.rps_tripped then + local cause = "unknown" + + if plc_db.rps_trip_cause == "ok" then + -- hmm... + elseif plc_db.rps_trip_cause == "dmg_crit" then + cause = "core damage critical" + elseif plc_db.rps_trip_cause == "high_temp" then + cause = "core temperature high" + elseif plc_db.rps_trip_cause == "no_coolant" then + cause = "insufficient coolant" + elseif plc_db.rps_trip_cause == "full_waste" then + cause = "excess waste" + elseif plc_db.rps_trip_cause == "heated_coolant_backup" then + cause = "excess heated coolant" + elseif plc_db.rps_trip_cause == "no_fuel" then + cause = "insufficient fuel" + elseif plc_db.rps_trip_cause == "fault" then + cause = "hardware fault" + elseif plc_db.rps_trip_cause == "timeout" then + cause = "connection timed out" + elseif plc_db.rps_trip_cause == "manual" then + cause = "manual operator SCRAM" + elseif plc_db.rps_trip_cause == "automatic" then + cause = "automated system SCRAM" + elseif plc_db.rps_trip_cause == "sys_fail" then + cause = "PLC system failure" + elseif plc_db.rps_trip_cause == "force_disabled" then + cause = "reactor force disabled" + end + + self.status_text = { "RPS SCRAM", cause } else - self.status_text = { "Idle", "" } + self.status_text[1] = "IDLE" + + local temp = plc_db.mek_status.temp + if temp < 350 then + self.status_text[2] = "core cold" + elseif temp < 600 then + self.status_text[2] = "core warm" + else + self.status_text[2] = "core hot" + end end else self.status_text = { "Reactor Off-line", "awaiting connection..." } diff --git a/supervisor/startup.lua b/supervisor/startup.lua index e3f0415..f54330a 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.8.3" +local SUPERVISOR_VERSION = "beta-v0.8.4" local print = util.print local println = util.println From 2a99d1d385735370e2a844e28ac0a44447597bbf Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 7 Dec 2022 12:59:21 -0500 Subject: [PATCH 460/587] #136 send rps trip cause with status, moved rps is_tripped to rps status from main status, increased plc status send rate to 2 Hz --- reactor-plc/plc.lua | 7 ++++-- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 8 +++---- supervisor/session/plc.lua | 45 +++++++++++++++++++------------------- supervisor/startup.lua | 2 +- 5 files changed, 33 insertions(+), 31 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 70afdfd..afcd255 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -315,6 +315,7 @@ function plc.rps_init(reactor, is_formed) status = rps_status_t.automatic else self.tripped = false + self.trip_cause = rps_status_t.ok end -- if a new trip occured... @@ -339,7 +340,10 @@ function plc.rps_init(reactor, is_formed) end function public.status() return self.state end + function public.is_tripped() return self.tripped end + function public.get_trip_cause() return self.trip_cause end + function public.is_active() return self.reactor_enabled end function public.is_formed() return self.formed end function public.is_force_disabled() return self.force_disabled end @@ -623,7 +627,6 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co local sys_status = { util.time(), -- timestamp (not self.scrammed), -- requested control state - rps.is_tripped(), -- rps_tripped no_reactor, -- no reactor peripheral connected formed, -- reactor formed heating_rate, -- heating rate @@ -641,7 +644,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co -- send reactor protection system status function public.send_rps_status() if self.linked then - _send(RPLC_TYPES.RPS_STATUS, rps.status()) + _send(RPLC_TYPES.RPS_STATUS, { rps.is_tripped(), rps.get_trip_cause(), table.unpack(rps.status()) }) end end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 752f795..583fd39 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.8" +local R_PLC_VERSION = "beta-v0.9.9" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 0a96655..b1f35d0 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -10,7 +10,7 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts -local MAIN_CLOCK = 1 -- (1Hz, 20 ticks) +local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks) local RPS_SLEEP = 250 -- (250ms, 5 ticks) local COMMS_SLEEP = 150 -- (150ms, 3 ticks) local SP_CTRL_SLEEP = 250 -- (250ms, 5 ticks) @@ -37,9 +37,9 @@ function threads.thread__main(smem, init) function public.exec() log.debug("main thread init, clock inactive") - -- send status updates at 1Hz (every 20 server ticks) (every loop tick) - -- send link requests at 0.5Hz (every 40 server ticks) (every 4 loop ticks) - local LINK_TICKS = 4 + -- send status updates at 2Hz (every 10 server ticks) (every loop tick) + -- send link requests at 0.5Hz (every 40 server ticks) (every 8 loop ticks) + local LINK_TICKS = 8 local ticks_to_update = 0 local loop_clock = util.new_clock(MAIN_CLOCK) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 473e5b1..ec821ff 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -161,18 +161,20 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) -- copy in the RPS status ---@param rps_status table local function _copy_rps_status(rps_status) - self.sDB.rps_status.dmg_crit = rps_status[1] - self.sDB.rps_status.high_temp = rps_status[2] - self.sDB.rps_status.no_cool = rps_status[3] - self.sDB.rps_status.ex_waste = rps_status[4] - self.sDB.rps_status.ex_hcool = rps_status[5] - self.sDB.rps_status.no_fuel = rps_status[6] - self.sDB.rps_status.fault = rps_status[7] - self.sDB.rps_status.timeout = rps_status[8] - self.sDB.rps_status.manual = rps_status[9] - self.sDB.rps_status.automatic = rps_status[10] - self.sDB.rps_status.sys_fail = rps_status[11] - self.sDB.rps_status.force_dis = rps_status[12] + self.sDB.rps_tripped = rps_status[1] + self.sDB.rps_trip_cause = rps_status[2] + self.sDB.rps_status.dmg_crit = rps_status[3] + self.sDB.rps_status.high_temp = rps_status[4] + self.sDB.rps_status.no_cool = rps_status[5] + self.sDB.rps_status.ex_waste = rps_status[6] + self.sDB.rps_status.ex_hcool = rps_status[7] + self.sDB.rps_status.no_fuel = rps_status[8] + self.sDB.rps_status.fault = rps_status[9] + self.sDB.rps_status.timeout = rps_status[10] + self.sDB.rps_status.manual = rps_status[11] + self.sDB.rps_status.automatic = rps_status[12] + self.sDB.rps_status.sys_fail = rps_status[13] + self.sDB.rps_status.force_dis = rps_status[14] end -- copy in the reactor status @@ -299,19 +301,18 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) -- handle packet by type if pkt.type == RPLC_TYPES.STATUS then -- status packet received, update data - if pkt.length >= 5 then + if pkt.length >= 4 then self.sDB.last_status_update = pkt.data[1] self.sDB.control_state = pkt.data[2] - self.sDB.rps_tripped = pkt.data[3] - self.sDB.no_reactor = pkt.data[4] - self.sDB.formed = pkt.data[5] + self.sDB.no_reactor = pkt.data[3] + self.sDB.formed = pkt.data[4] if not self.sDB.no_reactor and self.sDB.formed then - self.sDB.mek_status.heating_rate = pkt.data[6] or 0.0 + self.sDB.mek_status.heating_rate = pkt.data[5] or 0.0 -- attempt to read mek_data table - if pkt.data[7] ~= nil then - local status = pcall(_copy_status, pkt.data[7]) + if pkt.data[6] ~= nil then + local status = pcall(_copy_status, pkt.data[6]) if status then -- copied in status data OK self.received_status_cache = true @@ -396,7 +397,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) end elseif pkt.type == RPLC_TYPES.RPS_STATUS then -- RPS status packet received, copy data - if pkt.length == 12 then + if pkt.length == 14 then local status = pcall(_copy_rps_status, pkt.data) if status then -- copied in RPS status data OK @@ -410,9 +411,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) elseif pkt.type == RPLC_TYPES.RPS_ALARM then -- RPS alarm if pkt.length == 13 then - self.sDB.rps_tripped = true - self.sDB.rps_trip_cause = pkt.data[1] - local status = pcall(_copy_rps_status, { table.unpack(pkt.data, 2, pkt.length) }) + local status = pcall(_copy_rps_status, { true, table.unpack(pkt.data) }) if status then -- copied in RPS status data OK else diff --git a/supervisor/startup.lua b/supervisor/startup.lua index f54330a..96ee10e 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.8.4" +local SUPERVISOR_VERSION = "beta-v0.8.5" local print = util.print local println = util.println From 41913441d583975c2e953effa9cd60ae7e39a1b9 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 7 Dec 2022 23:17:11 -0500 Subject: [PATCH 461/587] RTU support for non reactor specific devices --- rtu/config.lua | 2 +- rtu/startup.lua | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/rtu/config.lua b/rtu/config.lua index 7dc1baa..80f9e9e 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -12,7 +12,7 @@ config.LOG_PATH = "/log.txt" -- 0 = APPEND (adds to existing file on start) -- 1 = NEW (replaces existing file on start) config.LOG_MODE = 0 --- RTU peripheral devices (named: side/network device name) +-- RTU peripheral devices (name: side/network device name) config.RTU_DEVICES = { { name = "boilerValve_0", diff --git a/rtu/startup.lua b/rtu/startup.lua index 2306bcd..d87b0ff 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.9.8" +local RTU_VERSION = "beta-v0.9.9" local rtu_t = types.rtu_t @@ -257,9 +257,9 @@ local function main() return false 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")) + -- CHECK: reactor is an integer >= 0 + 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 >= 0")) return false end @@ -362,7 +362,12 @@ local function main() table.insert(units, rtu_unit) - log.debug(util.c("configure> initialized RTU unit #", #units, ": ", name, " (", rtu_type, ") [", index, "] for reactor ", for_reactor)) + local for_message = "facility" + if for_reactor > 0 then + for_message = util.c("reactor ", for_reactor) + end + + log.debug(util.c("configure> initialized RTU unit #", #units, ": ", name, " (", rtu_type, ") [", index, "] for ", for_message)) end -- we made it through all that trusting-user-to-write-a-config-file chaos From 03f0216d515eee644bff1ac8b68325d26e373f10 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 10 Dec 2022 13:58:17 -0500 Subject: [PATCH 462/587] #130 facility data object, some code cleanup, comms protocol changed from 1.0.1 to 1.1.0 --- coordinator/coordinator.lua | 51 +++-- coordinator/iocontrol.lua | 344 ++++++++++++++++++++--------- coordinator/startup.lua | 8 +- coordinator/ui/style.lua | 68 ++++-- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- scada-common/comms.lua | 28 ++- supervisor/session/coordinator.lua | 111 ++++++---- supervisor/session/facility.lua | 100 +++++++++ supervisor/session/plc.lua | 10 +- supervisor/session/rsctl.lua | 39 ++++ supervisor/session/rtu.lua | 89 ++++---- supervisor/session/svsessions.lua | 21 +- supervisor/session/unit.lua | 43 ++-- supervisor/startup.lua | 2 +- 15 files changed, 635 insertions(+), 283 deletions(-) create mode 100644 supervisor/session/facility.lua create mode 100644 supervisor/session/rsctl.lua diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 9513b55..a6597c4 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -18,7 +18,7 @@ local DEVICE_TYPES = comms.DEVICE_TYPES local ESTABLISH_ACK = comms.ESTABLISH_ACK local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES -local CRDN_COMMANDS = comms.CRDN_COMMANDS +local UNIT_COMMANDS = comms.UNIT_COMMANDS local coordinator = {} @@ -309,11 +309,11 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa end -- send a unit command - ---@param cmd CRDN_COMMANDS command + ---@param cmd UNIT_COMMANDS command ---@param unit integer unit ID ---@param option any? optional options (like burn rate) function public.send_command(cmd, unit, option) - _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.COMMAND_UNIT, { cmd, unit, option }) + _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.UNIT_CMD, { cmd, unit, option }) end -- parse a packet @@ -388,20 +388,35 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa -- handle packet if protocol == PROTOCOLS.SCADA_CRDN then if self.sv_linked then - if packet.type == SCADA_CRDN_TYPES.STRUCT_BUILDS then - -- record builds - if iocontrol.record_builds(packet.data) then + if packet.type == SCADA_CRDN_TYPES.FAC_BUILDS then + -- record facility builds + if iocontrol.record_facility_builds(packet.data) then -- acknowledge receipt of builds - _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.STRUCT_BUILDS, {}) + _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.FAC_BUILDS, {}) else - log.error("received invalid SCADA_CRDN build packet") + log.error("received invalid FAC_BUILDS packet") + end + elseif packet.type == SCADA_CRDN_TYPES.FAC_STATUS then + -- update facility status + if not iocontrol.update_facility_status(packet.data) then + log.error("received invalid FAC_STATUS packet") + end + elseif packet.type == SCADA_CRDN_TYPES.FAC_CMD then + -- facility command acknowledgement + elseif packet.type == SCADA_CRDN_TYPES.UNIT_BUILDS then + -- record builds + if iocontrol.record_unit_builds(packet.data) then + -- acknowledge receipt of builds + _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.UNIT_BUILDS, {}) + else + log.error("received invalid UNIT_BUILDS packet") end elseif packet.type == SCADA_CRDN_TYPES.UNIT_STATUSES then -- update statuses - if not iocontrol.update_statuses(packet.data) then - log.error("received invalid SCADA_CRDN unit statuses packet") + if not iocontrol.update_unit_statuses(packet.data) then + log.error("received invalid UNIT_STATUSES packet") end - elseif packet.type == SCADA_CRDN_TYPES.COMMAND_UNIT then + elseif packet.type == SCADA_CRDN_TYPES.UNIT_CMD then -- unit command acknowledgement if packet.length == 3 then local cmd = packet.data[1] @@ -411,17 +426,17 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa local unit = iocontrol.get_db().units[unit_id] ---@type ioctl_entry if unit ~= nil then - if cmd == CRDN_COMMANDS.SCRAM then + if cmd == UNIT_COMMANDS.SCRAM then unit.scram_ack(ack) - elseif cmd == CRDN_COMMANDS.START then + elseif cmd == UNIT_COMMANDS.START then unit.start_ack(ack) - elseif cmd == CRDN_COMMANDS.RESET_RPS then + elseif cmd == UNIT_COMMANDS.RESET_RPS then unit.reset_rps_ack(ack) - elseif cmd == CRDN_COMMANDS.SET_BURN then + elseif cmd == UNIT_COMMANDS.SET_BURN then unit.set_burn_ack(ack) - elseif cmd == CRDN_COMMANDS.SET_WASTE then + elseif cmd == UNIT_COMMANDS.SET_WASTE then unit.set_waste_ack(ack) - elseif cmd == CRDN_COMMANDS.ACK_ALL_ALARMS then + elseif cmd == UNIT_COMMANDS.ACK_ALL_ALARMS then unit.ack_alarms_ack(ack) else log.debug(util.c("received command ack with unknown command ", cmd)) @@ -432,8 +447,6 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa else log.debug("SCADA_CRDN unit command ack packet length mismatch") end - elseif packet.type == SCADA_CRDN_TYPES.ALARM then - ---@todo alarm/architecture handling else log.warning("received unknown SCADA_CRDN packet type " .. packet.type) end diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 08adbc2..775cfb9 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -6,7 +6,7 @@ local util = require("scada-common.util") local sounder = require("coordinator.sounder") -local CRDN_COMMANDS = comms.CRDN_COMMANDS +local UNIT_COMMANDS = comms.UNIT_COMMANDS local ALARM_STATE = types.ALARM_STATE @@ -22,19 +22,29 @@ local io = {} function iocontrol.init(conf, comms) io.facility = { scram = false, - num_units = conf.num_units, - ps = psil.create() + num_units = conf.num_units, ---@type integer + ps = psil.create(), + + induction_ps_tbl = {}, + induction_data_tbl = {} } + -- create induction tables (max 1 per unit, preferably 1 total) + for _ = 1, conf.num_units do + local data = {} ---@type imatrix_session_db + table.insert(io.facility.induction_ps_tbl, psil.create()) + table.insert(io.facility.induction_data_tbl, data) + end + io.units = {} for i = 1, conf.num_units do local function ack(alarm) - comms.send_command(CRDN_COMMANDS.ACK_ALARM, i, alarm) + comms.send_command(UNIT_COMMANDS.ACK_ALARM, i, alarm) log.debug(util.c("UNIT[", i, "]: ACK ALARM ", alarm)) end local function reset(alarm) - comms.send_command(CRDN_COMMANDS.RESET_ALARM, i, alarm) + comms.send_command(UNIT_COMMANDS.RESET_ALARM, i, alarm) log.debug(util.c("UNIT[", i, "]: RESET ALARM ", alarm)) end @@ -107,33 +117,33 @@ function iocontrol.init(conf, comms) function entry.start() entry.control_state = true - comms.send_command(CRDN_COMMANDS.START, i) + comms.send_command(UNIT_COMMANDS.START, i) log.debug(util.c("UNIT[", i, "]: START")) end function entry.scram() entry.control_state = false - comms.send_command(CRDN_COMMANDS.SCRAM, i) + comms.send_command(UNIT_COMMANDS.SCRAM, i) log.debug(util.c("UNIT[", i, "]: SCRAM")) end function entry.reset_rps() - comms.send_command(CRDN_COMMANDS.RESET_RPS, i) + comms.send_command(UNIT_COMMANDS.RESET_RPS, i) log.debug(util.c("UNIT[", i, "]: RESET_RPS")) end function entry.ack_alarms() - comms.send_command(CRDN_COMMANDS.ACK_ALL_ALARMS, i) + comms.send_command(UNIT_COMMANDS.ACK_ALL_ALARMS, i) log.debug(util.c("UNIT[", i, "]: ACK_ALL_ALARMS")) end function entry.set_burn(rate) - comms.send_command(CRDN_COMMANDS.SET_BURN, i, rate) + comms.send_command(UNIT_COMMANDS.SET_BURN, i, rate) log.debug(util.c("UNIT[", i, "]: SET_BURN = ", rate)) end function entry.set_waste(mode) - comms.send_command(CRDN_COMMANDS.SET_WASTE, i, mode) + comms.send_command(UNIT_COMMANDS.SET_WASTE, i, mode) log.debug(util.c("UNIT[", i, "]: SET_WASTE = ", mode)) end @@ -158,58 +168,173 @@ function iocontrol.init(conf, comms) end end --- populate structure builds +-- populate facility structure builds +---@param build table +---@return boolean valid +function iocontrol.record_facility_builds(build) + if type(build) == "table" then + local fac = io.facility + + -- induction matricies + if type(build.induction) == "table" then + for id, matrix in pairs(build.induction) do + if type(fac.induction_data_tbl[id]) == "table" then + fac.induction_data_tbl[id].formed = matrix[1] ---@type boolean + fac.induction_data_tbl[id].build = matrix[2] ---@type table + + fac.induction_ps_tbl[id].publish("formed", matrix[1]) + + for key, val in pairs(fac.induction_data_tbl[id].build) do + fac.induction_ps_tbl[id].publish(key, val) + end + else + log.debug(util.c("iocontrol.record_facility_builds: invalid induction matrix id ", id)) + end + end + end + else + log.error("facility builds not a table") + return false + end + + return true +end + +-- populate unit structure builds ---@param builds table ---@return boolean valid -function iocontrol.record_builds(builds) - if #builds ~= #io.units then - log.error("number of provided unit builds does not match expected number of units") +function iocontrol.record_unit_builds(builds) + -- note: if not all units and RTUs are connected, some will be nil + for id, build in pairs(builds) do + local unit = io.units[id] ---@type ioctl_entry + + if type(build) ~= "table" then + log.error(util.c("corrupted unit builds provided, unit ", id, " not a table")) + return false + elseif type(unit) ~= "table" then + log.error(util.c("corrupted unit builds provided, invalid unit ", id)) + return false + end + + local log_header = util.c("iocontrol.record_unit_builds[unit ", id, "]: ") + + -- reactor build + if type(build.reactor) == "table" then + unit.reactor_data.mek_struct = build.reactor + for key, val in pairs(unit.reactor_data.mek_struct) do + unit.reactor_ps.publish(key, val) + end + + if (type(unit.reactor_data.mek_struct.length) == "number") and (unit.reactor_data.mek_struct.length ~= 0) and + (type(unit.reactor_data.mek_struct.width) == "number") and (unit.reactor_data.mek_struct.width ~= 0) then + unit.reactor_ps.publish("size", { unit.reactor_data.mek_struct.length, unit.reactor_data.mek_struct.width }) + end + end + + -- boiler builds + if type(build.boilers) == "table" then + for b_id, boiler in pairs(build.boilers) do + if type(unit.boiler_data_tbl[b_id]) == "table" then + unit.boiler_data_tbl[b_id].formed = boiler[1] ---@type boolean + unit.boiler_data_tbl[b_id].build = boiler[2] ---@type table + + unit.boiler_ps_tbl[b_id].publish("formed", boiler[1]) + + for key, val in pairs(unit.boiler_data_tbl[b_id].build) do + unit.boiler_ps_tbl[b_id].publish(key, val) + end + else + log.debug(util.c(log_header, "invalid boiler id ", b_id)) + end + end + end + + -- turbine builds + if type(build.turbines) == "table" then + for t_id, turbine in pairs(build.turbines) do + if type(unit.turbine_data_tbl[t_id]) == "table" then + unit.turbine_data_tbl[t_id].formed = turbine[1] ---@type boolean + unit.turbine_data_tbl[t_id].build = turbine[2] ---@type table + + unit.turbine_ps_tbl[t_id].publish("formed", turbine[1]) + + for key, val in pairs(unit.turbine_data_tbl[t_id].build) do + unit.turbine_ps_tbl[t_id].publish(key, val) + end + else + log.debug(util.c(log_header, "invalid turbine id ", t_id)) + end + end + end + end + + return true +end + +-- update facility status +---@param status table +---@return boolean valid +function iocontrol.update_facility_status(status) + local log_header = util.c("iocontrol.update_facility_status: ") + if type(status) ~= "table" then + log.debug(log_header .. "status not a table") return false else - -- note: if not all units and RTUs are connected, some will be nil - for i = 1, #builds do - local unit = io.units[i] ---@type ioctl_entry - local build = builds[i] + local fac = io.facility - -- reactor build - if type(build.reactor) == "table" then - unit.reactor_data.mek_struct = build.reactor - for key, val in pairs(unit.reactor_data.mek_struct) do - unit.reactor_ps.publish(key, val) - end + -- RTU statuses - if (type(unit.reactor_data.mek_struct.length) == "number") and (unit.reactor_data.mek_struct.length ~= 0) and - (type(unit.reactor_data.mek_struct.width) == "number") and (unit.reactor_data.mek_struct.width ~= 0) then - unit.reactor_ps.publish("size", { unit.reactor_data.mek_struct.length, unit.reactor_data.mek_struct.width }) - end - end + local rtu_statuses = status[1] - -- boiler builds - if type(build.boilers) == "table" then - for id, boiler in pairs(build.boilers) do - unit.boiler_data_tbl[id].formed = boiler[1] ---@type boolean - unit.boiler_data_tbl[id].build = boiler[2] ---@type table - - unit.boiler_ps_tbl[id].publish("formed", boiler[1]) - - for key, val in pairs(unit.boiler_data_tbl[id].build) do - unit.boiler_ps_tbl[id].publish(key, val) + if type(rtu_statuses) == "table" then + -- induction matricies statuses + if type(rtu_statuses.induction) == "table" then + for id = 1, #fac.induction_ps_tbl do + if rtu_statuses.induction[id] == nil then + -- disconnected + fac.induction_ps_tbl[id].publish("computed_status", 1) end end - end - -- turbine builds - if type(build.turbines) == "table" then - for id, turbine in pairs(build.turbines) do - unit.turbine_data_tbl[id].formed = turbine[1] ---@type boolean - unit.turbine_data_tbl[id].build = turbine[2] ---@type table + for id, matrix in pairs(rtu_statuses.induction) do + if type(fac.induction_data_tbl[id]) == "table" then + local rtu_faulted = matrix[1] ---@type boolean + fac.induction_data_tbl[id].formed = matrix[2] ---@type boolean + fac.induction_data_tbl[id].state = matrix[3] ---@type table + fac.induction_data_tbl[id].tanks = matrix[4] ---@type table - unit.turbine_ps_tbl[id].publish("formed", turbine[1]) + local data = fac.induction_data_tbl[id] ---@type imatrix_session_db - for key, val in pairs(unit.turbine_data_tbl[id].build) do - unit.turbine_ps_tbl[id].publish(key, val) + fac.induction_ps_tbl[id].publish("formed", data.formed) + fac.induction_ps_tbl[id].publish("faulted", rtu_faulted) + + if data.formed then + if rtu_faulted then + fac.induction_ps_tbl[id].publish("computed_status", 3) -- faulted + elseif data.tanks.energy_fill >= 0.99 then + fac.induction_ps_tbl[id].publish("computed_status", 6) -- full + elseif data.tanks.energy_fill <= 0.01 then + fac.induction_ps_tbl[id].publish("computed_status", 5) -- empty + else + fac.induction_ps_tbl[id].publish("computed_status", 4) -- on-line + end + else + fac.induction_ps_tbl[id].publish("computed_status", 2) -- not formed + end + + for key, val in pairs(fac.induction_data_tbl[id].state) do + fac.induction_ps_tbl[id].publish(key, val) + end + + for key, val in pairs(fac.induction_data_tbl[id].tanks) do + fac.induction_ps_tbl[id].publish(key, val) + end + else + log.debug(util.c(log_header, "invalid induction matrix id ", id)) end end + else + log.debug(log_header .. "induction matrix list not a table") end end end @@ -220,16 +345,17 @@ end -- update unit statuses ---@param statuses table ---@return boolean valid -function iocontrol.update_statuses(statuses) +function iocontrol.update_unit_statuses(statuses) if type(statuses) ~= "table" then - log.debug("iocontrol.update_statuses: unit statuses not a table") + log.debug("iocontrol.update_unit_statuses: unit statuses not a table") return false elseif #statuses ~= #io.units then - log.debug("iocontrol.update_statuses: number of provided unit statuses does not match expected number of units") + log.debug("iocontrol.update_unit_statuses: number of provided unit statuses does not match expected number of units") return false else + -- get all unit statuses for i = 1, #statuses do - local log_header = util.c("iocontrol.update_statuses[unit ", i, "]: ") + local log_header = util.c("iocontrol.update_unit_statuses[unit ", i, "]: ") local unit = io.units[i] ---@type ioctl_entry local status = statuses[i] @@ -264,18 +390,18 @@ function iocontrol.update_statuses(statuses) unit.reactor_data.mek_status = mek_status ---@type mek_status if unit.reactor_data.mek_status.status then - unit.reactor_ps.publish("computed_status", 3) -- running + unit.reactor_ps.publish("computed_status", 5) -- running else if unit.reactor_data.no_reactor then - unit.reactor_ps.publish("computed_status", 5) -- faulted + unit.reactor_ps.publish("computed_status", 3) -- faulted elseif not unit.reactor_data.formed then - unit.reactor_ps.publish("computed_status", 6) -- multiblock not formed + unit.reactor_ps.publish("computed_status", 2) -- multiblock not formed elseif unit.reactor_data.rps_status.force_dis then unit.reactor_ps.publish("computed_status", 7) -- reactor force disabled elseif unit.reactor_data.rps_tripped and unit.reactor_data.rps_trip_cause ~= "manual" then - unit.reactor_ps.publish("computed_status", 4) -- SCRAM + unit.reactor_ps.publish("computed_status", 6) -- SCRAM else - unit.reactor_ps.publish("computed_status", 2) -- disabled + unit.reactor_ps.publish("computed_status", 4) -- disabled end end @@ -307,7 +433,7 @@ function iocontrol.update_statuses(statuses) if type(rtu_statuses) == "table" then -- boiler statuses if type(rtu_statuses.boilers) == "table" then - for id = 1, #unit.boiler_data_tbl do + for id = 1, #unit.boiler_ps_tbl do if rtu_statuses.boilers[i] == nil then -- disconnected unit.boiler_ps_tbl[id].publish("computed_status", 1) @@ -315,34 +441,38 @@ function iocontrol.update_statuses(statuses) end for id, boiler in pairs(rtu_statuses.boilers) do - local rtu_faulted = boiler[1] ---@type boolean - unit.boiler_data_tbl[id].formed = boiler[2] ---@type boolean - unit.boiler_data_tbl[id].state = boiler[3] ---@type table - unit.boiler_data_tbl[id].tanks = boiler[4] ---@type table + if type(unit.boiler_data_tbl[id]) == "table" then + local rtu_faulted = boiler[1] ---@type boolean + unit.boiler_data_tbl[id].formed = boiler[2] ---@type boolean + unit.boiler_data_tbl[id].state = boiler[3] ---@type table + unit.boiler_data_tbl[id].tanks = boiler[4] ---@type table - local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db + local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db - unit.boiler_ps_tbl[id].publish("formed", data.formed) - unit.boiler_ps_tbl[id].publish("faulted", rtu_faulted) + unit.boiler_ps_tbl[id].publish("formed", data.formed) + unit.boiler_ps_tbl[id].publish("faulted", rtu_faulted) - if data.formed then - if rtu_faulted then - unit.boiler_ps_tbl[id].publish("computed_status", 4) -- faulted - elseif data.state.boil_rate > 0 then - unit.boiler_ps_tbl[id].publish("computed_status", 3) -- active + if data.formed then + if rtu_faulted then + unit.boiler_ps_tbl[id].publish("computed_status", 3) -- faulted + elseif data.state.boil_rate > 0 then + unit.boiler_ps_tbl[id].publish("computed_status", 5) -- active + else + unit.boiler_ps_tbl[id].publish("computed_status", 4) -- idle + end else - unit.boiler_ps_tbl[id].publish("computed_status", 2) -- idle + unit.boiler_ps_tbl[id].publish("computed_status", 2) -- not formed + end + + for key, val in pairs(unit.boiler_data_tbl[id].state) do + unit.boiler_ps_tbl[id].publish(key, val) + end + + for key, val in pairs(unit.boiler_data_tbl[id].tanks) do + unit.boiler_ps_tbl[id].publish(key, val) end else - unit.boiler_ps_tbl[id].publish("computed_status", 5) -- not formed - end - - for key, val in pairs(unit.boiler_data_tbl[id].state) do - unit.boiler_ps_tbl[id].publish(key, val) - end - - for key, val in pairs(unit.boiler_data_tbl[id].tanks) do - unit.boiler_ps_tbl[id].publish(key, val) + log.debug(util.c(log_header, "invalid boiler id ", id)) end end else @@ -359,36 +489,40 @@ function iocontrol.update_statuses(statuses) end for id, turbine in pairs(rtu_statuses.turbines) do - local rtu_faulted = turbine[1] ---@type boolean - unit.turbine_data_tbl[id].formed = turbine[2] ---@type boolean - unit.turbine_data_tbl[id].state = turbine[3] ---@type table - unit.turbine_data_tbl[id].tanks = turbine[4] ---@type table + if type(unit.turbine_data_tbl[id]) == "table" then + local rtu_faulted = turbine[1] ---@type boolean + unit.turbine_data_tbl[id].formed = turbine[2] ---@type boolean + unit.turbine_data_tbl[id].state = turbine[3] ---@type table + unit.turbine_data_tbl[id].tanks = turbine[4] ---@type table - local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db + local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db - unit.turbine_ps_tbl[id].publish("formed", data.formed) - unit.turbine_ps_tbl[id].publish("faulted", rtu_faulted) + unit.turbine_ps_tbl[id].publish("formed", data.formed) + unit.turbine_ps_tbl[id].publish("faulted", rtu_faulted) - if data.formed then - if data.tanks.energy_fill >= 0.99 then - unit.turbine_ps_tbl[id].publish("computed_status", 4) -- trip - elseif rtu_faulted then - unit.turbine_ps_tbl[id].publish("computed_status", 5) -- faulted - elseif data.state.flow_rate < 100 then - unit.turbine_ps_tbl[id].publish("computed_status", 2) -- idle + if data.formed then + if data.tanks.energy_fill >= 0.99 then + unit.turbine_ps_tbl[id].publish("computed_status", 6) -- trip + elseif rtu_faulted then + unit.turbine_ps_tbl[id].publish("computed_status", 3) -- faulted + elseif data.state.flow_rate < 100 then + unit.turbine_ps_tbl[id].publish("computed_status", 4) -- idle + else + unit.turbine_ps_tbl[id].publish("computed_status", 5) -- active + end else - unit.turbine_ps_tbl[id].publish("computed_status", 3) -- active + unit.turbine_ps_tbl[id].publish("computed_status", 2) -- not formed + end + + for key, val in pairs(unit.turbine_data_tbl[id].state) do + unit.turbine_ps_tbl[id].publish(key, val) + end + + for key, val in pairs(unit.turbine_data_tbl[id].tanks) do + unit.turbine_ps_tbl[id].publish(key, val) end else - unit.turbine_ps_tbl[id].publish("computed_status", 6) -- not formed - end - - for key, val in pairs(unit.turbine_data_tbl[id].state) do - unit.turbine_ps_tbl[id].publish(key, val) - end - - for key, val in pairs(unit.turbine_data_tbl[id].tanks) do - unit.turbine_ps_tbl[id].publish(key, val) + log.debug(util.c(log_header, "invalid turbine id ", id)) end end else diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 7b63302..a95b861 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.7.7" +local COORDINATOR_VERSION = "beta-v0.8.0" local print = util.print local println = util.println @@ -365,4 +365,8 @@ local function main() log.info("exited") end -if not xpcall(main, crash.handler) then crash.exit() end +if not xpcall(main, crash.handler) then + pcall(renderer.close_ui) + pcall(sounder.stop) + crash.exit() +end diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index 509e87a..6fb8f88 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -39,6 +39,14 @@ style.reactor = { color = cpair(colors.black, colors.yellow), text = "PLC OFF-LINE" }, + { + color = cpair(colors.black, colors.orange), + text = "NOT FORMED" + }, + { + color = cpair(colors.black, colors.orange), + text = "PLC FAULT" + }, { color = cpair(colors.white, colors.gray), text = "DISABLED" @@ -51,14 +59,6 @@ style.reactor = { color = cpair(colors.black, colors.red), text = "SCRAMMED" }, - { - color = cpair(colors.black, colors.orange), - text = "PLC FAULT" - }, - { - color = cpair(colors.black, colors.orange), - text = "NOT FORMED" - }, { color = cpair(colors.black, colors.red), text = "FORCE DISABLED" @@ -73,14 +73,6 @@ style.boiler = { color = cpair(colors.black, colors.yellow), text = "OFF-LINE" }, - { - color = cpair(colors.white, colors.gray), - text = "IDLE" - }, - { - color = cpair(colors.black, colors.green), - text = "ACTIVE" - }, { color = cpair(colors.black, colors.orange), text = "RTU FAULT" @@ -88,6 +80,14 @@ style.boiler = { { color = cpair(colors.black, colors.orange), text = "NOT FORMED" + }, + { + color = cpair(colors.white, colors.gray), + text = "IDLE" + }, + { + color = cpair(colors.black, colors.green), + text = "ACTIVE" } } } @@ -99,6 +99,14 @@ style.turbine = { color = cpair(colors.black, colors.yellow), text = "OFF-LINE" }, + { + color = cpair(colors.black, colors.orange), + text = "NOT FORMED" + }, + { + color = cpair(colors.black, colors.orange), + text = "RTU FAULT" + }, { color = cpair(colors.white, colors.gray), text = "IDLE" @@ -110,15 +118,37 @@ style.turbine = { { color = cpair(colors.black, colors.red), text = "TRIP" + } + } +} + +style.imatrix = { + -- induction matrix states + states = { + { + color = cpair(colors.black, colors.yellow), + text = "OFF-LINE" + }, + { + color = cpair(colors.black, colors.orange), + text = "NOT FORMED" }, { color = cpair(colors.black, colors.orange), text = "RTU FAULT" }, { - color = cpair(colors.black, colors.orange), - text = "NOT FORMED" - } + color = cpair(colors.white, colors.green), + text = "ONLINE" + }, + { + color = cpair(colors.black, colors.yellow), + text = "LOW CHARGE" + }, + { + color = cpair(colors.black, colors.red), + text = "HIGH CHARGE" + }, } } diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 583fd39..5e0dca5 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.9" +local R_PLC_VERSION = "beta-v0.9.10" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index d87b0ff..ec5fc0a 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.9.9" +local RTU_VERSION = "beta-v0.9.10" local rtu_t = types.rtu_t diff --git a/scada-common/comms.lua b/scada-common/comms.lua index f25df22..025a5c9 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -12,7 +12,7 @@ local rtu_t = types.rtu_t local insert = table.insert -comms.version = "1.0.1" +comms.version = "1.1.0" ---@alias PROTOCOLS integer local PROTOCOLS = { @@ -38,7 +38,7 @@ local RPLC_TYPES = { MEK_BURN_RATE = 2, -- set burn rate RPS_ENABLE = 3, -- enable reactor RPS_SCRAM = 4, -- SCRAM reactor (manual request) - RPS_ASCRAM = 5, -- SCRAM reactor (automatic request) + RPS_ASCRAM = 5, -- SCRAM reactor (automatic request) RPS_STATUS = 6, -- RPS status RPS_ALARM = 7, -- RPS alarm broadcast RPS_RESET = 8 -- clear RPS trip (if in bad state, will trip immediately) @@ -62,14 +62,16 @@ local ESTABLISH_ACK = { ---@alias SCADA_CRDN_TYPES integer local SCADA_CRDN_TYPES = { - STRUCT_BUILDS = 0, -- mekanism structure builds - UNIT_STATUSES = 1, -- state of reactor units - COMMAND_UNIT = 2, -- command a reactor unit - ALARM = 3 -- alarm signaling + FAC_BUILDS = 0, -- facility RTU builds + FAC_STATUS = 1, -- state of facility and facility devices + FAC_CMD = 2, -- faility command + UNIT_BUILDS = 3, -- build of each reactor unit (reactor + RTUs) + UNIT_STATUSES = 4, -- state of each of the reactor units + UNIT_CMD = 5 -- command a reactor unit } ----@alias CRDN_COMMANDS integer -local CRDN_COMMANDS = { +---@alias UNIT_COMMANDS integer +local UNIT_COMMANDS = { SCRAM = 0, -- SCRAM the reactor START = 1, -- start the reactor RESET_RPS = 2, -- reset the RPS @@ -102,7 +104,7 @@ comms.RPLC_TYPES = RPLC_TYPES comms.ESTABLISH_ACK = ESTABLISH_ACK comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES comms.SCADA_CRDN_TYPES = SCADA_CRDN_TYPES -comms.CRDN_COMMANDS = CRDN_COMMANDS +comms.UNIT_COMMANDS = UNIT_COMMANDS comms.CAPI_TYPES = CAPI_TYPES comms.RTU_UNIT_TYPES = RTU_UNIT_TYPES @@ -484,10 +486,12 @@ function comms.crdn_packet() -- check that type is known local function _crdn_type_valid() - return self.type == SCADA_CRDN_TYPES.STRUCT_BUILDS or + return self.type == SCADA_CRDN_TYPES.FAC_BUILDS or + self.type == SCADA_CRDN_TYPES.FAC_STATUS or + self.type == SCADA_CRDN_TYPES.FAC_CMD or + self.type == SCADA_CRDN_TYPES.UNIT_BUILDS or self.type == SCADA_CRDN_TYPES.UNIT_STATUSES or - self.type == SCADA_CRDN_TYPES.COMMAND_UNIT or - self.type == SCADA_CRDN_TYPES.ALARM + self.type == SCADA_CRDN_TYPES.UNIT_STATUSES end -- make a coordinator packet diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 9a5f10f..2ce66df 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -10,7 +10,7 @@ local coordinator = {} local PROTOCOLS = comms.PROTOCOLS local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES -local CRDN_COMMANDS = comms.CRDN_COMMANDS +local UNIT_COMMANDS = comms.UNIT_COMMANDS local SV_Q_CMDS = svqtypes.SV_Q_CMDS local SV_Q_DATA = svqtypes.SV_Q_DATA @@ -44,15 +44,14 @@ local PERIODICS = { ---@param id integer ---@param in_queue mqueue ---@param out_queue mqueue ----@param facility_units table -function coordinator.new_session(id, in_queue, out_queue, facility_units) +---@param facility facility +---@param units table +function coordinator.new_session(id, in_queue, out_queue, facility, units) local log_header = "crdn_session(" .. id .. "): " local self = { - id = id, in_q = in_queue, out_q = out_queue, - units = facility_units, -- connection properties seq_num = 0, r_seq_num = nil, @@ -67,11 +66,13 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) }, -- when to next retry one of these messages retry_times = { - builds_packet = 0 + f_builds_packet = 0, + u_builds_packet = 0 }, -- message acknowledgements acks = { - builds = false + fac_builds = false, + unit_builds = false } } @@ -109,26 +110,41 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) self.seq_num = self.seq_num + 1 end + -- send facility builds + local function _send_fac_builds() + self.acks.fac_builds = false + _send(SCADA_CRDN_TYPES.FAC_BUILDS, facility.get_build()) + end + -- send unit builds - local function _send_builds() - self.acks.builds = false + local function _send_unit_builds() + self.acks.unit_builds = false local builds = {} - for i = 1, #self.units do - local unit = self.units[i] ---@type reactor_unit + for i = 1, #units do + local unit = units[i] ---@type reactor_unit builds[unit.get_id()] = unit.get_build() end - _send(SCADA_CRDN_TYPES.STRUCT_BUILDS, builds) + _send(SCADA_CRDN_TYPES.UNIT_BUILDS, builds) + end + + -- send facility status + local function _send_fac_status() + local status = { + facility.get_rtu_statuses() + } + + _send(SCADA_CRDN_TYPES.FAC_STATUS, status) end -- send unit statuses - local function _send_status() + local function _send_unit_statuses() local status = {} - for i = 1, #self.units do - local unit = self.units[i] ---@type reactor_unit + for i = 1, #units do + local unit = units[i] ---@type reactor_unit status[unit.get_id()] = { unit.get_reactor_status(), unit.get_rtu_statuses(), @@ -183,10 +199,13 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) end elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_CRDN then - if pkt.type == SCADA_CRDN_TYPES.STRUCT_BUILDS then + if pkt.type == SCADA_CRDN_TYPES.FAC_BUILDS then -- acknowledgement to coordinator receiving builds - self.acks.builds = true - elseif pkt.type == SCADA_CRDN_TYPES.COMMAND_UNIT then + self.acks.fac_builds = true + elseif pkt.type == SCADA_CRDN_TYPES.UNIT_BUILDS then + -- acknowledgement to coordinator receiving builds + self.acks.unit_builds = true + elseif pkt.type == SCADA_CRDN_TYPES.UNIT_CMD then if pkt.length >= 2 then -- get command and unit id local cmd = pkt.data[1] @@ -196,37 +215,37 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) local data = { uid, pkt.data[3] } -- continue if valid unit id - if util.is_int(uid) and uid > 0 and uid <= #self.units then - local unit = self.units[uid] ---@type reactor_unit + if util.is_int(uid) and uid > 0 and uid <= #units then + local unit = units[uid] ---@type reactor_unit - if cmd == CRDN_COMMANDS.START then + if cmd == UNIT_COMMANDS.START then self.out_q.push_data(SV_Q_DATA.START, data) - elseif cmd == CRDN_COMMANDS.SCRAM then + elseif cmd == UNIT_COMMANDS.SCRAM then self.out_q.push_data(SV_Q_DATA.SCRAM, data) - elseif cmd == CRDN_COMMANDS.RESET_RPS then + elseif cmd == UNIT_COMMANDS.RESET_RPS then self.out_q.push_data(SV_Q_DATA.RESET_RPS, data) - elseif cmd == CRDN_COMMANDS.SET_BURN then + elseif cmd == UNIT_COMMANDS.SET_BURN then if pkt.length == 3 then self.out_q.push_data(SV_Q_DATA.SET_BURN, data) else log.debug(log_header .. "CRDN command unit burn rate missing option") end - elseif cmd == CRDN_COMMANDS.SET_WASTE then + elseif cmd == UNIT_COMMANDS.SET_WASTE then if pkt.length == 3 then unit.set_waste(pkt.data[3]) else log.debug(log_header .. "CRDN command unit set waste missing option") end - elseif cmd == CRDN_COMMANDS.ACK_ALL_ALARMS then + elseif cmd == UNIT_COMMANDS.ACK_ALL_ALARMS then unit.ack_all() - _send(SCADA_CRDN_TYPES.COMMAND_UNIT, { cmd, uid, true }) - elseif cmd == CRDN_COMMANDS.ACK_ALARM then + _send(SCADA_CRDN_TYPES.UNIT_CMD, { cmd, uid, true }) + elseif cmd == UNIT_COMMANDS.ACK_ALARM then if pkt.length == 3 then unit.ack_alarm(pkt.data[3]) else log.debug(log_header .. "CRDN command unit ack alarm missing id") end - elseif cmd == CRDN_COMMANDS.RESET_ALARM then + elseif cmd == UNIT_COMMANDS.RESET_ALARM then if pkt.length == 3 then unit.reset_alarm(pkt.data[3]) else @@ -251,7 +270,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) local public = {} -- get the session ID - function public.get_id() return self.id end + function public.get_id() return id end -- check if a timer matches this session's watchdog function public.check_wd(timer) @@ -262,7 +281,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) function public.close() _close() _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) - println("connection to coordinator " .. self.id .. " closed by server") + println("connection to coordinator " .. id .. " closed by server") log.info(log_header .. "session closed by server") end @@ -289,9 +308,9 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) local cmd = message.message if cmd == CRD_S_CMDS.RESEND_BUILDS then -- re-send builds - self.acks.builds = false self.retry_times.builds_packet = util.time() + RETRY_PERIOD - _send_builds() + _send_fac_builds() + _send_unit_builds() end elseif message.qtype == mqueue.TYPE.DATA then -- instruction with body @@ -299,7 +318,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) if cmd.key == CRD_S_DATA.CMD_ACK then local ack = cmd.val ---@type coord_ack - _send(SCADA_CRDN_TYPES.COMMAND_UNIT, { ack.cmd, ack.unit, ack.ack }) + _send(SCADA_CRDN_TYPES.UNIT_CMD, { ack.cmd, ack.unit, ack.ack }) end end end @@ -313,7 +332,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) -- exit if connection was closed if not self.connected then - println("connection to coordinator " .. self.id .. " closed by remote host") + println("connection to coordinator " .. id .. " closed by remote host") log.info(log_header .. "session closed by remote host") return self.connected end @@ -334,11 +353,12 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) periodics.keep_alive = 0 end - -- unit statuses to coordinator + -- statuses to coordinator periodics.status_packet = periodics.status_packet + elapsed if periodics.status_packet >= PERIODICS.STATUS then - _send_status() + _send_fac_status() + _send_unit_statuses() periodics.status_packet = 0 end @@ -350,12 +370,19 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) local rtimes = self.retry_times - -- builds packet retry + -- builds packet retries - if not self.acks.builds then - if rtimes.builds_packet - util.time() <= 0 then - _send_builds() - rtimes.builds_packet = util.time() + RETRY_PERIOD + if not self.acks.fac_builds then + if rtimes.f_builds_packet - util.time() <= 0 then + _send_fac_builds() + rtimes.f_builds_packet = util.time() + RETRY_PERIOD + end + end + + if not self.acks.unit_builds then + if rtimes.u_builds_packet - util.time() <= 0 then + _send_unit_builds() + rtimes.u_builds_packet = util.time() + RETRY_PERIOD end end end diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua new file mode 100644 index 0000000..454606c --- /dev/null +++ b/supervisor/session/facility.lua @@ -0,0 +1,100 @@ +local log = require("scada-common.log") +local rsio = require("scada-common.rsio") +local util = require("scada-common.util") + +local rsctl = require("supervisor.session.rsctl") + +---@class facility_management +local facility = {} + +-- create a new facility management object +function facility.new() + local self = { + induction = {}, + redstone = {} + } + + -- init redstone RTU I/O controller + local rs_rtu_io_ctl = rsctl.new(self.redstone) + + -- unlink disconnected units + ---@param sessions table + local function _unlink_disconnected_units(sessions) + util.filter_table(sessions, function (u) return u.is_connected() end) + end + + -- PUBLIC FUNCTIONS -- + + ---@class facility + local public = {} + + -- ADD/LINK DEVICES -- + + -- link a redstone RTU session + ---@param rs_unit unit_session + function public.add_redstone(rs_unit) + table.insert(self.redstone, rs_unit) + end + + -- link an imatrix RTU session + ---@param imatrix unit_session + function public.add_imatrix(imatrix) + table.insert(self.induction, imatrix) + end + + -- purge devices associated with the given RTU session ID + ---@param session integer RTU session ID + function public.purge_rtu_devices(session) + util.filter_table(self.redstone, function (s) return s.get_session_id() ~= session end) + util.filter_table(self.induction, function (s) return s.get_session_id() ~= session end) + end + + -- UPDATE -- + + -- update (iterate) the facility management + function public.update() + -- unlink RTU unit sessions if they are closed + _unlink_disconnected_units(self.induction) + _unlink_disconnected_units(self.redstone) + end + + -- READ STATES/PROPERTIES -- + + -- get build properties of all machines + function public.get_build() + local build = {} + + build.induction = {} + for i = 1, #self.induction do + local matrix = self.induction[i] ---@type unit_session + build.induction[matrix.get_device_idx()] = { matrix.get_db().formed, matrix.get_db().build } + end + + return build + end + + -- get RTU statuses + function public.get_rtu_statuses() + local status = {} + + -- status of induction matricies (including tanks) + status.induction = {} + for i = 1, #self.induction do + local matrix = self.induction[i] ---@type unit_session + status.induction[matrix.get_device_idx()] = { + matrix.is_faulted(), + matrix.get_db().formed, + matrix.get_db().state, + matrix.get_db().tanks + } + end + + ---@todo other RTU statuses + + return status + end + + return public +end + +return facility diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index ec821ff..316a584 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -11,7 +11,7 @@ local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES -local CRDN_COMMANDS = comms.CRDN_COMMANDS +local UNIT_COMMANDS = comms.UNIT_COMMANDS local print = util.print local println = util.println @@ -352,7 +352,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) -- send acknowledgement to coordinator self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { unit = self.for_reactor, - cmd = CRDN_COMMANDS.SET_BURN, + cmd = UNIT_COMMANDS.SET_BURN, ack = ack }) elseif pkt.type == RPLC_TYPES.RPS_ENABLE then @@ -367,7 +367,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) -- send acknowledgement to coordinator self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { unit = self.for_reactor, - cmd = CRDN_COMMANDS.START, + cmd = UNIT_COMMANDS.START, ack = ack }) elseif pkt.type == RPLC_TYPES.RPS_SCRAM then @@ -383,7 +383,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) -- send acknowledgement to coordinator self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { unit = self.for_reactor, - cmd = CRDN_COMMANDS.SCRAM, + cmd = UNIT_COMMANDS.SCRAM, ack = ack }) elseif pkt.type == RPLC_TYPES.RPS_ASCRAM then @@ -435,7 +435,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) -- send acknowledgement to coordinator self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { unit = self.for_reactor, - cmd = CRDN_COMMANDS.RESET_RPS, + cmd = UNIT_COMMANDS.RESET_RPS, ack = ack }) else diff --git a/supervisor/session/rsctl.lua b/supervisor/session/rsctl.lua new file mode 100644 index 0000000..1544cc3 --- /dev/null +++ b/supervisor/session/rsctl.lua @@ -0,0 +1,39 @@ +-- +-- Redstone RTU Session I/O Controller +-- + +local rsctl = {} + +-- create a new redstone RTU I/O controller +---@param redstone_rtus table redstone RTU sessions +function rsctl.new(redstone_rtus) + ---@class rs_controller + local public = {} + + -- write to a digital redstone port (applies to all RTUs) + ---@param port IO_PORT + ---@param value boolean + function public.digital_write(port, value) + for i = 1, #redstone_rtus do + local db = redstone_rtus[i].get_db() ---@type redstone_session_db + local io = db.io[port] ---@type rs_db_dig_io|nil + if io ~= nil then io.write(value) end + end + end + + -- read a digital redstone port
+ -- this will read from the first one encountered if there are multiple, because there should not be multiple + ---@param port IO_PORT + ---@return boolean|nil + function public.digital_read(port) + for i = 1, #redstone_rtus do + local db = redstone_rtus[i].get_db() ---@type redstone_session_db + local io = db.io[port] ---@type rs_db_dig_io|nil + if io ~= nil then return io.read() end + end + end + + return public +end + +return rsctl diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 922112f..0d5bc1d 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -36,16 +36,15 @@ local PERIODICS = { ---@param in_queue mqueue ---@param out_queue mqueue ---@param advertisement table +---@param facility facility ---@param facility_units table -function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) +function rtu.new_session(id, in_queue, out_queue, advertisement, facility, facility_units) local log_header = "rtu_session(" .. id .. "): " local self = { - id = id, in_q = in_queue, out_q = out_queue, modbus_q = mqueue.new(), - f_units = facility_units, advert = advertisement, -- connection properties seq_num = 0, @@ -72,9 +71,10 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) local function _handle_advertisement() _reset_config() - for i = 1, #self.f_units do - local unit = self.f_units[i] ---@type reactor_unit - unit.purge_rtu_devices(self.id) + for i = 1, #facility_units do + local unit = facility_units[i] ---@type reactor_unit + unit.purge_rtu_devices(id) + facility.purge_rtu_devices(id) end for i = 1, #self.advert do @@ -104,47 +104,61 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) if advert_validator.valid() then advert_validator.assert_min(unit_advert.index, 1) - advert_validator.assert_min(unit_advert.reactor, 1) - advert_validator.assert_max(unit_advert.reactor, #self.f_units) + advert_validator.assert_min(unit_advert.reactor, 0) + advert_validator.assert_max(unit_advert.reactor, #facility_units) if not advert_validator.valid() then u_type = false end else u_type = false end + local type_string = util.strval(u_type) + if type(u_type) == "number" then type_string = util.strval(comms.advert_type_to_rtu_t(u_type)) end + -- create unit by type if u_type == false then -- validation fail log.debug(log_header .. "advertisement unit validation failure") else - local target_unit = self.f_units[unit_advert.reactor] ---@type reactor_unit + if unit_advert.reactor > 0 then + local target_unit = facility_units[unit_advert.reactor] ---@type reactor_unit - if u_type == RTU_UNIT_TYPES.REDSTONE then - -- redstone - unit = svrs_redstone.new(self.id, i, unit_advert, self.modbus_q) - if type(unit) ~= "nil" then target_unit.add_redstone(unit) end - elseif u_type == RTU_UNIT_TYPES.BOILER_VALVE then - -- boiler (Mekanism 10.1+) - unit = svrs_boilerv.new(self.id, i, unit_advert, self.modbus_q) - if type(unit) ~= "nil" then target_unit.add_boiler(unit) end - elseif u_type == RTU_UNIT_TYPES.TURBINE_VALVE then - -- turbine (Mekanism 10.1+) - unit = svrs_turbinev.new(self.id, i, unit_advert, self.modbus_q) - if type(unit) ~= "nil" then target_unit.add_turbine(unit) end - elseif u_type == RTU_UNIT_TYPES.IMATRIX then - -- induction matrix - unit = svrs_imatrix.new(self.id, i, unit_advert, self.modbus_q) - elseif u_type == RTU_UNIT_TYPES.SPS then - -- super-critical phase shifter - unit = svrs_sps.new(self.id, i, unit_advert, self.modbus_q) - elseif u_type == RTU_UNIT_TYPES.SNA then - -- solar neutron activator - unit = svrs_sna.new(self.id, i, unit_advert, self.modbus_q) - elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then - -- environment detector - unit = svrs_envd.new(self.id, i, unit_advert, self.modbus_q) + if u_type == RTU_UNIT_TYPES.REDSTONE then + -- redstone + unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q) + if type(unit) ~= "nil" then target_unit.add_redstone(unit) end + elseif u_type == RTU_UNIT_TYPES.BOILER_VALVE then + -- boiler (Mekanism 10.1+) + unit = svrs_boilerv.new(id, i, unit_advert, self.modbus_q) + if type(unit) ~= "nil" then target_unit.add_boiler(unit) end + elseif u_type == RTU_UNIT_TYPES.TURBINE_VALVE then + -- turbine (Mekanism 10.1+) + unit = svrs_turbinev.new(id, i, unit_advert, self.modbus_q) + if type(unit) ~= "nil" then target_unit.add_turbine(unit) end + else + log.error(util.c(log_header, "bad advertisement: encountered unsupported reactor-specific RTU type ", type_string)) + end else - log.error(log_header .. "bad advertisement: encountered unsupported RTU type") + if u_type == RTU_UNIT_TYPES.REDSTONE then + -- redstone + unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q) + if type(unit) ~= "nil" then facility.add_redstone(unit) end + elseif u_type == RTU_UNIT_TYPES.IMATRIX then + -- induction matrix + unit = svrs_imatrix.new(id, i, unit_advert, self.modbus_q) + if type(unit) ~= "nil" then facility.add_imatrix(unit) end + elseif u_type == RTU_UNIT_TYPES.SPS then + -- super-critical phase shifter + unit = svrs_sps.new(id, i, unit_advert, self.modbus_q) + elseif u_type == RTU_UNIT_TYPES.SNA then + -- solar neutron activator + unit = svrs_sna.new(id, i, unit_advert, self.modbus_q) + elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then + -- environment detector + unit = svrs_envd.new(id, i, unit_advert, self.modbus_q) + else + log.error(util.c(log_header, "bad advertisement: encountered unsupported reactor-independent RTU type ", type_string)) + end end end @@ -152,10 +166,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) table.insert(self.units, unit) else _reset_config() - if type(u_type) == "number" then - local type_string = util.strval(comms.advert_type_to_rtu_t(u_type)) - log.error(log_header .. "bad advertisement: error occured while creating a unit (type is " .. type_string .. ")") - end + log.error(util.c(log_header, "bad advertisement: error occured while creating a unit (type is ", type_string, ")")) break end end @@ -271,7 +282,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) -- PUBLIC FUNCTIONS -- -- get the session ID - function public.get_id() return self.id end + function public.get_id() return id end -- check if a timer matches this session's watchdog ---@param timer number diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 80ea0d6..91b4873 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -2,6 +2,7 @@ local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") local util = require("scada-common.util") +local facility = require("supervisor.session.facility") local svqtypes = require("supervisor.session.svqtypes") local unit = require("supervisor.session.unit") @@ -32,7 +33,8 @@ svsessions.SESSION_TYPE = SESSION_TYPE local self = { modem = nil, num_reactors = 0, - facility_units = {}, + facility = facility.new(), + units = {}, rtu_sessions = {}, plc_sessions = {}, coord_sessions = {}, @@ -197,10 +199,10 @@ end function svsessions.init(modem, num_reactors, cooling_conf) self.modem = modem self.num_reactors = num_reactors - self.facility_units = {} + self.units = {} for i = 1, self.num_reactors do - table.insert(self.facility_units, unit.new(i, cooling_conf[i].BOILERS, cooling_conf[i].TURBINES)) + table.insert(self.units, unit.new(i, cooling_conf[i].BOILERS, cooling_conf[i].TURBINES)) end end @@ -297,7 +299,7 @@ function svsessions.establish_plc_session(local_port, remote_port, for_reactor, plc_s.instance = plc.new_session(self.next_plc_id, for_reactor, plc_s.in_queue, plc_s.out_queue) table.insert(self.plc_sessions, plc_s) - self.facility_units[for_reactor].link_plc_session(plc_s) + self.units[for_reactor].link_plc_session(plc_s) log.debug("established new PLC session to " .. remote_port .. " with ID " .. self.next_plc_id) @@ -330,7 +332,7 @@ function svsessions.establish_rtu_session(local_port, remote_port, advertisement instance = nil ---@type rtu_session } - rtu_s.instance = rtu.new_session(self.next_rtu_id, rtu_s.in_queue, rtu_s.out_queue, advertisement, self.facility_units) + rtu_s.instance = rtu.new_session(self.next_rtu_id, rtu_s.in_queue, rtu_s.out_queue, advertisement, self.facility, self.units) table.insert(self.rtu_sessions, rtu_s) log.debug("established new RTU session to " .. remote_port .. " with ID " .. self.next_rtu_id) @@ -360,7 +362,7 @@ function svsessions.establish_coord_session(local_port, remote_port, version) instance = nil ---@type coord_session } - coord_s.instance = coordinator.new_session(self.next_coord_id, coord_s.in_queue, coord_s.out_queue, self.facility_units) + coord_s.instance = coordinator.new_session(self.next_coord_id, coord_s.in_queue, coord_s.out_queue, self.facility, self.units) table.insert(self.coord_sessions, coord_s) log.debug("established new coordinator session to " .. remote_port .. " with ID " .. self.next_coord_id) @@ -399,9 +401,12 @@ function svsessions.iterate_all() -- iterate coordinator sessions _iterate(self.coord_sessions) + -- iterate facility + self.facility.update() + -- iterate units - for i = 1, #self.facility_units do - local u = self.facility_units[i] ---@type reactor_unit + for i = 1, #self.units do + local u = self.units[i] ---@type reactor_unit u.update() end end diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index b0d0fea..3c378e6 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -1,9 +1,9 @@ -local log = require("scada-common.log") -local rsio = require("scada-common.rsio") -local types = require("scada-common.types") -local util = require("scada-common.util") +local log = require("scada-common.log") +local rsio = require("scada-common.rsio") +local types = require("scada-common.types") +local util = require("scada-common.util") -local qtypes = require("supervisor.session.rtu.qtypes") +local rsctl = require("supervisor.session.rsctl") ---@class reactor_control_unit local unit = {} @@ -178,6 +178,9 @@ function unit.new(for_reactor, num_boilers, num_turbines) } } + -- init redstone RTU I/O controller + local rs_rtu_io_ctl = rsctl.new(self.redstone) + -- init boiler table fields for _ = 1, num_boilers do table.insert(self.db.annunciator.BoilerOnline, false) @@ -192,9 +195,6 @@ function unit.new(for_reactor, num_boilers, num_turbines) table.insert(self.db.annunciator.TurbineTrip, false) end - ---@class reactor_unit - local public = {} - -- PRIVATE FUNCTIONS -- --#region time derivative utility functions @@ -237,26 +237,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) --#region redstone I/O - -- write to a redstone port - local function __rs_w(port, value) - for i = 1, #self.redstone do - local db = self.redstone[i].get_db() ---@type redstone_session_db - local io = db.io[port] ---@type rs_db_dig_io|nil - if io ~= nil then io.write(value) end - end - end - - -- read a redstone port
- -- this will read from the first one encountered if there are multiple, because there should not be multiple - ---@param port IO_PORT - ---@return boolean|nil - local function __rs_r(port) - for i = 1, #self.redstone do - local db = self.redstone[i].get_db() ---@type redstone_session_db - local io = db.io[port] ---@type rs_db_dig_io|nil - if io ~= nil then return io.read() end - end - end + local __rs_w = rs_rtu_io_ctl.digital_write + local __rs_r = rs_rtu_io_ctl.digital_read -- waste valves local waste_pu = { open = function () __rs_w(IO.WASTE_PU, true) end, close = function () __rs_w(IO.WASTE_PU, false) end } @@ -702,6 +684,9 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- PUBLIC FUNCTIONS -- + ---@class reactor_unit + local public = {} + -- ADD/LINK DEVICES -- -- link the PLC @@ -722,7 +707,6 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- link a redstone RTU session ---@param rs_unit unit_session function public.add_redstone(rs_unit) - -- insert into list table.insert(self.redstone, rs_unit) end @@ -781,6 +765,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- unlink RTU unit sessions if they are closed _unlink_disconnected_units(self.boilers) _unlink_disconnected_units(self.turbines) + _unlink_disconnected_units(self.redstone) -- update annunciator logic _update_annunciator() diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 96ee10e..4467a3b 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.8.5" +local SUPERVISOR_VERSION = "beta-v0.9.0" local print = util.print local println = util.println From 6517f78c1c741f19988b90ea153032afe8adfedd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 10 Dec 2022 15:44:11 -0500 Subject: [PATCH 463/587] #129 induction matrix view --- coordinator/coordinator.lua | 14 +++-- coordinator/startup.lua | 2 +- coordinator/ui/components/boiler.lua | 5 +- coordinator/ui/components/imatrix.lua | 86 ++++++++++++++++++++++++++ coordinator/ui/components/reactor.lua | 7 ++- coordinator/ui/components/turbine.lua | 3 +- coordinator/ui/layout/main_view.lua | 9 ++- coordinator/ui/style.lua | 2 +- graphics/elements/indicators/power.lua | 13 +++- scada-common/comms.lua | 2 +- supervisor/session/coordinator.lua | 2 +- supervisor/startup.lua | 2 +- 12 files changed, 127 insertions(+), 20 deletions(-) create mode 100644 coordinator/ui/components/imatrix.lua diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index a6597c4..25ffa02 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -389,12 +389,16 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa if protocol == PROTOCOLS.SCADA_CRDN then if self.sv_linked then if packet.type == SCADA_CRDN_TYPES.FAC_BUILDS then - -- record facility builds - if iocontrol.record_facility_builds(packet.data) then - -- acknowledge receipt of builds - _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.FAC_BUILDS, {}) + if packet.length == 1 then + -- record facility builds + if iocontrol.record_facility_builds(packet.data[1]) then + -- acknowledge receipt of builds + _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.FAC_BUILDS, {}) + else + log.error("received invalid FAC_BUILDS packet") + end else - log.error("received invalid FAC_BUILDS packet") + log.debug("FAC_BUILDS packet length mismatch") end elseif packet.type == SCADA_CRDN_TYPES.FAC_STATUS then -- update facility status diff --git a/coordinator/startup.lua b/coordinator/startup.lua index a95b861..5416a73 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.0" +local COORDINATOR_VERSION = "beta-v0.8.1" local print = util.print local println = util.println diff --git a/coordinator/ui/components/boiler.lua b/coordinator/ui/components/boiler.lua index c7a1b97..ee463a9 100644 --- a/coordinator/ui/components/boiler.lua +++ b/coordinator/ui/components/boiler.lua @@ -2,10 +2,11 @@ local core = require("graphics.core") local style = require("coordinator.ui.style") -local DataIndicator = require("graphics.elements.indicators.data") -local StateIndicator = require("graphics.elements.indicators.state") local Rectangle = require("graphics.elements.rectangle") local TextBox = require("graphics.elements.textbox") + +local DataIndicator = require("graphics.elements.indicators.data") +local StateIndicator = require("graphics.elements.indicators.state") local VerticalBar = require("graphics.elements.indicators.vbar") local cpair = core.graphics.cpair diff --git a/coordinator/ui/components/imatrix.lua b/coordinator/ui/components/imatrix.lua new file mode 100644 index 0000000..abd81b0 --- /dev/null +++ b/coordinator/ui/components/imatrix.lua @@ -0,0 +1,86 @@ +local core = require("graphics.core") +local util = require("scada-common.util") + +local style = require("coordinator.ui.style") + +local Div = require("graphics.elements.div") +local Rectangle = require("graphics.elements.rectangle") +local TextBox = require("graphics.elements.textbox") + +local DataIndicator = require("graphics.elements.indicators.data") +local PowerIndicator = require("graphics.elements.indicators.power") +local StateIndicator = require("graphics.elements.indicators.state") +local VerticalBar = require("graphics.elements.indicators.vbar") + +local cpair = core.graphics.cpair +local border = core.graphics.border + +local TEXT_ALIGN = core.graphics.TEXT_ALIGN + +-- new induction matrix view +---@param root graphics_element parent +---@param x integer top left x +---@param y integer top left y +---@param data imatrix_session_db matrix data +---@param ps psil ps interface +local function new_view(root, x, y, data, ps, id) + local title = "INDUCTION MATRIX" + if type(id) == "number" then title = title .. id end + + local matrix = Div{parent=root,fg_bg=style.root,width=33,height=21,x=x,y=y} + + TextBox{parent=matrix,text=" ",width=33,height=1,x=1,y=1,fg_bg=cpair(colors.lightGray,colors.gray)} + TextBox{parent=matrix,text=title,alignment=TEXT_ALIGN.CENTER,width=33,height=1,x=1,y=2,fg_bg=cpair(colors.lightGray,colors.gray)} + + local rect = Rectangle{parent=matrix,border=border(1, colors.gray, true),width=33,height=18,x=1,y=3} + + local text_fg_bg = cpair(colors.black, colors.lightGray) + local label_fg_bg = cpair(colors.gray, colors.lightGray) + local lu_col = cpair(colors.gray, colors.gray) + + local status = StateIndicator{parent=rect,x=11,y=1,states=style.imatrix.states,value=1,min_width=12} + local energy = PowerIndicator{parent=rect,x=7,y=3,lu_colors=lu_col,label="Energy: ",format="%8.2f",value=0,width=26,fg_bg=text_fg_bg} + local capacity = PowerIndicator{parent=rect,x=7,y=4,lu_colors=lu_col,label="Capacity:",format="%8.2f",value=0,width=26,fg_bg=text_fg_bg} + local input = PowerIndicator{parent=rect,x=7,y=5,lu_colors=lu_col,label="Input: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg_bg} + local output = PowerIndicator{parent=rect,x=7,y=6,lu_colors=lu_col,label="Output: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg_bg} + + ps.subscribe("computed_status", status.update) + ps.subscribe("energy", function (val) energy.update(util.joules_to_fe(val)) end) + ps.subscribe("max_energy", function (val) capacity.update(util.joules_to_fe(val)) end) + ps.subscribe("last_input", function (val) input.update(util.joules_to_fe(val)) end) + ps.subscribe("last_output", function (val) output.update(util.joules_to_fe(val)) end) + + local fill = DataIndicator{parent=rect,x=11,y=8,lu_colors=lu_col,label="Fill:",unit="%",format="%8.2f",value=0,width=18,fg_bg=text_fg_bg} + + local cells = DataIndicator{parent=rect,x=11,y=10,lu_colors=lu_col,label="Cells: ",format="%7d",value=0,width=18,fg_bg=text_fg_bg} + local providers = DataIndicator{parent=rect,x=11,y=11,lu_colors=lu_col,label="Providers:",format="%7d",value=0,width=18,fg_bg=text_fg_bg} + + TextBox{parent=rect,text="Transfer Capacity",x=11,y=13,height=1,width=17,fg_bg=label_fg_bg} + local trans_cap = PowerIndicator{parent=rect,x=19,y=14,lu_colors=lu_col,label="",format="%5.2f",rate=true,value=0,width=12,fg_bg=text_fg_bg} + + ps.subscribe("cells", cells.update) + ps.subscribe("providers", providers.update) + ps.subscribe("energy_fill", function (val) fill.update(val * 100) end) + ps.subscribe("transfer_cap", function (val) trans_cap.update(util.joules_to_fe(val)) end) + + local charge = VerticalBar{parent=rect,x=2,y=2,fg_bg=cpair(colors.green,colors.gray),height=13,width=4} + local in_cap = VerticalBar{parent=rect,x=7,y=8,fg_bg=cpair(colors.red,colors.gray),height=7,width=1} + local out_cap = VerticalBar{parent=rect,x=9,y=8,fg_bg=cpair(colors.blue,colors.gray),height=7,width=1} + + TextBox{parent=rect,text="FILL",x=2,y=16,height=1,width=4,fg_bg=text_fg_bg} + TextBox{parent=rect,text="I/O",x=7,y=16,height=1,width=3,fg_bg=text_fg_bg} + + local function calc_saturation(val) + if (type(data.build) == "table") and (type(data.build.transfer_cap) == "number") and (data.build.transfer_cap > 0) then + return val / data.build.transfer_cap + else + return 0 + end + end + + ps.subscribe("energy_fill", charge.update) + ps.subscribe("last_input", function (val) in_cap.update(calc_saturation(val)) end) + ps.subscribe("last_output", function (val) out_cap.update(calc_saturation(val)) end) +end + +return new_view diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua index 8efe9ce..1c8978b 100644 --- a/coordinator/ui/components/reactor.lua +++ b/coordinator/ui/components/reactor.lua @@ -4,12 +4,13 @@ local core = require("graphics.core") local style = require("coordinator.ui.style") -local HorizontalBar = require("graphics.elements.indicators.hbar") -local DataIndicator = require("graphics.elements.indicators.data") -local StateIndicator = require("graphics.elements.indicators.state") local Rectangle = require("graphics.elements.rectangle") local TextBox = require("graphics.elements.textbox") +local DataIndicator = require("graphics.elements.indicators.data") +local HorizontalBar = require("graphics.elements.indicators.hbar") +local StateIndicator = require("graphics.elements.indicators.state") + local TEXT_ALIGN = core.graphics.TEXT_ALIGN local cpair = core.graphics.cpair diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua index 4070eb0..5577bf3 100644 --- a/coordinator/ui/components/turbine.lua +++ b/coordinator/ui/components/turbine.lua @@ -3,10 +3,11 @@ local util = require("scada-common.util") local style = require("coordinator.ui.style") +local Rectangle = require("graphics.elements.rectangle") + local DataIndicator = require("graphics.elements.indicators.data") local PowerIndicator = require("graphics.elements.indicators.power") local StateIndicator = require("graphics.elements.indicators.state") -local Rectangle = require("graphics.elements.rectangle") local VerticalBar = require("graphics.elements.indicators.vbar") local cpair = core.graphics.cpair diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index 16c114d..7c0f05c 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -9,6 +9,7 @@ local util = require("scada-common.util") local style = require("coordinator.ui.style") local unit_overview = require("coordinator.ui.components.unit_overview") +local imatrix = require("coordinator.ui.components.imatrix") local core = require("graphics.core") @@ -72,12 +73,14 @@ local function init(monitor) TextBox{parent=main,y=cnc_y_start,text=util.strrep("\x8c", header.width()),alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=cpair(colors.lightGray,colors.gray)} + cnc_y_start = cnc_y_start + 2 + -- testing ---@fixme remove test code ColorMap{parent=main,x=2,y=(main.height()-1)} - PushButton{parent=main,x=2,y=(cnc_y_start+2),text="TEST 1",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_1} + PushButton{parent=main,x=2,y=cnc_y_start,text="TEST 1",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_1} PushButton{parent=main,x=2,text="TEST 2",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_2} PushButton{parent=main,x=2,text="TEST 3",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_3} PushButton{parent=main,x=2,text="TEST 4",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_4} @@ -88,7 +91,7 @@ local function init(monitor) PushButton{parent=main,x=2,text="STOP",min_width=8,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.stop} PushButton{parent=main,x=2,text="PSCALE",min_width=8,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_power_scale} - SwitchButton{parent=main,x=12,y=(cnc_y_start+2),text="CONTAINMENT BREACH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_breach} + SwitchButton{parent=main,x=12,y=cnc_y_start,text="CONTAINMENT BREACH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_breach} SwitchButton{parent=main,x=12,text="CONTAINMENT RADIATION",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rad} SwitchButton{parent=main,x=12,text="REACTOR LOST",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_lost} SwitchButton{parent=main,x=12,text="CRITICAL DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_crit} @@ -101,6 +104,8 @@ local function init(monitor) SwitchButton{parent=main,x=12,text="RCS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rcs} SwitchButton{parent=main,x=12,text="TURBINE TRIP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_turbinet} + local imatrix_1 = imatrix(main, 131, cnc_y_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1]) + return main end diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index 6fb8f88..24ade2e 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -138,7 +138,7 @@ style.imatrix = { text = "RTU FAULT" }, { - color = cpair(colors.white, colors.green), + color = cpair(colors.black, colors.green), text = "ONLINE" }, { diff --git a/graphics/elements/indicators/power.lua b/graphics/elements/indicators/power.lua index 6c7282b..e76f690 100644 --- a/graphics/elements/indicators/power.lua +++ b/graphics/elements/indicators/power.lua @@ -7,6 +7,7 @@ local element = require("graphics.element") ---@class power_indicator_args ---@field label string indicator label ---@field format string power format override (lua string format) +---@field rate boolean? whether to append /t to the end (power per tick) ---@field lu_colors? cpair label foreground color (a), unit foreground color (b) ---@field value any default value ---@field parent graphics_element @@ -57,8 +58,16 @@ local function power(args) if args.lu_colors ~= nil then e.window.setTextColor(args.lu_colors.color_b) end - -- add space so we don't end up with FEE (after having kFE for example) - if unit == "FE" then unit = "FE " end + + -- append per tick if rate is set + -- add space to FE so we don't end up with FEE (after having kFE for example) + if args.rate == true then + unit = unit .. "/t" + if unit == "FE/t" then unit = "FE/t " end + else + if unit == "FE" then unit = "FE " end + end + e.window.write(" " .. unit) end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 025a5c9..bb876dd 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -491,7 +491,7 @@ function comms.crdn_packet() self.type == SCADA_CRDN_TYPES.FAC_CMD or self.type == SCADA_CRDN_TYPES.UNIT_BUILDS or self.type == SCADA_CRDN_TYPES.UNIT_STATUSES or - self.type == SCADA_CRDN_TYPES.UNIT_STATUSES + self.type == SCADA_CRDN_TYPES.UNIT_CMD end -- make a coordinator packet diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 2ce66df..1a5e93f 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -113,7 +113,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility, units) -- send facility builds local function _send_fac_builds() self.acks.fac_builds = false - _send(SCADA_CRDN_TYPES.FAC_BUILDS, facility.get_build()) + _send(SCADA_CRDN_TYPES.FAC_BUILDS, { facility.get_build() }) end -- send unit builds diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 4467a3b..79fa007 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.9.0" +local SUPERVISOR_VERSION = "beta-v0.9.1" local print = util.print local println = util.println From a633f5b4c3ee444b430523cec2e00eb7282d7e81 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 10 Dec 2022 23:56:07 -0500 Subject: [PATCH 464/587] #132 expanded unit displays to use 4x4 monitors --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 242 ++++++++++---------- graphics/elements/controls/multi_button.lua | 6 +- graphics/elements/rectangle.lua | 69 ++++-- 4 files changed, 183 insertions(+), 136 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 5416a73..7417bd6 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.1" +local COORDINATOR_VERSION = "beta-v0.8.2" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index d92f5e7..7a95447 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -9,6 +9,7 @@ local style = require("coordinator.ui.style") local core = require("graphics.core") local Div = require("graphics.elements.div") +local Rectangle = require("graphics.elements.rectangle") local TextBox = require("graphics.elements.textbox") local AlarmLight = require("graphics.elements.indicators.alight") @@ -16,6 +17,7 @@ local CoreMap = require("graphics.elements.indicators.coremap") local DataIndicator = require("graphics.elements.indicators.data") local IndicatorLight = require("graphics.elements.indicators.light") local TriIndicatorLight = require("graphics.elements.indicators.trilight") +local VerticalBar = require("graphics.elements.indicators.vbar") local HazardButton = require("graphics.elements.controls.hazard_button") local MultiButton = require("graphics.elements.controls.multi_button") @@ -25,6 +27,7 @@ local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") local TEXT_ALIGN = core.graphics.TEXT_ALIGN local cpair = core.graphics.cpair +local border = core.graphics.border local period = core.flasher.PERIOD @@ -77,38 +80,52 @@ local function init(parent, id) local stat_fg_bg = cpair(colors.black,colors.white) - TextBox{parent=main,x=21,y=3,text="Core Temp",height=1,fg_bg=style.label} - local core_temp = DataIndicator{parent=main,x=21,label="",format="%10.2f",value=0,unit="K",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} - r_ps.subscribe("temp", core_temp.update) - main.line_break() - - TextBox{parent=main,x=21,text="Burn Rate",height=1,width=12,fg_bg=style.label} - local act_burn_r = DataIndicator{parent=main,x=21,label="",format="%7.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} - r_ps.subscribe("act_burn_rate", act_burn_r.update) - main.line_break() - - TextBox{parent=main,x=21,text="Commanded Burn Rate",height=2,width=12,fg_bg=style.label} - local burn_r = DataIndicator{parent=main,x=21,label="",format="%7.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} - r_ps.subscribe("burn_rate", burn_r.update) - main.line_break() - - TextBox{parent=main,x=21,text="Heating Rate",height=1,width=12,fg_bg=style.label} - local heating_r = DataIndicator{parent=main,x=21,label="",format="%12.0f",value=0,unit="",commas=true,lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + TextBox{parent=main,x=2,y=22,text="Heating Rate",height=1,width=12,fg_bg=style.label} + local heating_r = DataIndicator{parent=main,x=2,label="",format="%14.0f",value=0,unit="mB/t",commas=true,lu_colors=lu_cpair,width=19,fg_bg=stat_fg_bg} r_ps.subscribe("heating_rate", heating_r.update) - main.line_break() - TextBox{parent=main,x=21,text="Damage",height=1,width=12,fg_bg=style.label} - local damage_p = DataIndicator{parent=main,x=21,label="",format="%10.0f",value=100,unit="%",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} + TextBox{parent=main,x=2,y=25,text="Commanded Burn Rate",height=1,width=19,fg_bg=style.label} + local burn_r = DataIndicator{parent=main,x=2,label="",format="%14.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=19,fg_bg=stat_fg_bg} + r_ps.subscribe("burn_rate", burn_r.update) + + TextBox{parent=main,text="F",x=23,y=22,width=1,height=1,fg_bg=style.label} + TextBox{parent=main,text="C",x=25,y=22,width=1,height=1,fg_bg=style.label} + TextBox{parent=main,text="H",x=27,y=22,width=1,height=1,fg_bg=style.label} + TextBox{parent=main,text="W",x=29,y=22,width=1,height=1,fg_bg=style.label} + + local fuel = VerticalBar{parent=main,x=23,y=23,fg_bg=cpair(colors.black,colors.gray),height=4,width=1} + local ccool = VerticalBar{parent=main,x=25,y=23,fg_bg=cpair(colors.lightBlue,colors.gray),height=4,width=1} + local hcool = VerticalBar{parent=main,x=27,y=23,fg_bg=cpair(colors.orange,colors.gray),height=4,width=1} + local waste = VerticalBar{parent=main,x=29,y=23,fg_bg=cpair(colors.brown,colors.gray),height=4,width=1} + + r_ps.subscribe("fuel_fill", fuel.update) + r_ps.subscribe("ccool_fill", ccool.update) + r_ps.subscribe("hcool_fill", hcool.update) + r_ps.subscribe("waste_fill", waste.update) + + TextBox{parent=main,x=32,y=22,text="Core Temp",height=1,width=9,fg_bg=style.label} + local core_temp = DataIndicator{parent=main,x=32,label="",format="%11.2f",value=0,unit="K",lu_colors=lu_cpair,width=13,fg_bg=stat_fg_bg} + r_ps.subscribe("temp", core_temp.update) + + TextBox{parent=main,x=32,y=25,text="Burn Rate",height=1,width=9,fg_bg=style.label} + local act_burn_r = DataIndicator{parent=main,x=32,label="",format="%8.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=13,fg_bg=stat_fg_bg} + r_ps.subscribe("act_burn_rate", act_burn_r.update) + + TextBox{parent=main,x=32,y=28,text="Damage",height=1,width=6,fg_bg=style.label} + local damage_p = DataIndicator{parent=main,x=32,label="",format="%11.0f",value=0,unit="%",lu_colors=lu_cpair,width=13,fg_bg=stat_fg_bg} r_ps.subscribe("damage", damage_p.update) - main.line_break() ---@todo radiation monitor - TextBox{parent=main,x=21,text="Radiation",height=1,width=12,fg_bg=style.label} - DataIndicator{parent=main,x=21,label="",format="%6.2f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg} - main.line_break() + TextBox{parent=main,x=32,y=31,text="Radiation",height=1,width=21,fg_bg=style.label} + DataIndicator{parent=main,x=32,label="",format="%7.2f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=13,fg_bg=stat_fg_bg} - local stat_line_1 = TextBox{parent=main,x=2,text="UNKNOWN",width=31,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.black, colors.white)} - local stat_line_2 = TextBox{parent=main,x=2,text="awaiting data",width=31,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} + ------------------- + -- system status -- + ------------------- + + local u_stat = Rectangle{parent=main,border=border(1, colors.gray, true),thin=true,width=33,height=4,x=46,y=3,fg_bg=cpair(colors.black,colors.white)} + local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=33,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.black, colors.white)} + local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data",width=33,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} r_ps.subscribe("U_StatusLine1", stat_line_1.set_value) r_ps.subscribe("U_StatusLine2", stat_line_2.set_value) @@ -119,7 +136,7 @@ local function init(parent, id) -- annunciator colors (generally) per IAEA-TECDOC-812 recommendations - local annunciator = Div{parent=main,x=35,y=3} + local annunciator = Div{parent=main,width=23,height=18,x=22,y=3} -- connectivity/basic state local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(colors.green,colors.red)} @@ -128,6 +145,11 @@ local function init(parent, id) ---@todo auto control as info sent here local r_auto = IndicatorLight{parent=annunciator,label="Auto. Control",colors=cpair(colors.blue,colors.gray)} + annunciator.line_break() + + ---@todo radiation monitor + local rad_mon = IndicatorLight{parent=annunciator,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)} + r_ps.subscribe("PLCOnline", plc_online.update) r_ps.subscribe("PLCHeartbeat", plc_hbeat.update) r_ps.subscribe("status", r_active.update) @@ -159,29 +181,22 @@ local function init(parent, id) r_ps.subscribe("WasteLineOcclusion", r_wloc.update) r_ps.subscribe("HighStartupRate", r_hsrt.update) - annunciator.line_break() - -- RPS annunciator panel - TextBox{parent=main,x=34,y=20,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} - TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} - TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} - TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} - TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} - TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} - TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} - TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} - TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} - TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)} - local rps_trp = IndicatorLight{parent=annunciator,label="RPS Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - local rps_dmg = IndicatorLight{parent=annunciator,label="Damage Critical",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - local rps_exh = IndicatorLight{parent=annunciator,label="Excess Heated Coolant",colors=cpair(colors.yellow,colors.gray)} - local rps_exw = IndicatorLight{parent=annunciator,label="Excess Waste",colors=cpair(colors.yellow,colors.gray)} - local rps_tmp = IndicatorLight{parent=annunciator,label="Core Temp. High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - local rps_nof = IndicatorLight{parent=annunciator,label="No Fuel",colors=cpair(colors.yellow,colors.gray)} - local rps_noc = IndicatorLight{parent=annunciator,label="Coolant Level Low Low",colors=cpair(colors.yellow,colors.gray)} - local rps_flt = IndicatorLight{parent=annunciator,label="PPM Fault",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} - local rps_tmo = IndicatorLight{parent=annunciator,label="Timeout",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} - local rps_sfl = IndicatorLight{parent=annunciator,label="System Failure",colors=cpair(colors.orange,colors.gray),flash=true,period=period.BLINK_500_MS} + + TextBox{parent=main,text="REACTOR PROTECTION SYSTEM",fg_bg=cpair(colors.black,colors.cyan),alignment=TEXT_ALIGN.CENTER,width=33,height=1,x=46,y=8} + local rps = Rectangle{parent=main,border=border(1, colors.cyan, true),thin=true,width=33,height=12,x=46,y=9} + local rps_annunc = Div{parent=rps,width=31,height=10,x=2,y=1} + + local rps_trp = IndicatorLight{parent=rps_annunc,label="RPS Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local rps_dmg = IndicatorLight{parent=rps_annunc,label="Damage Critical",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local rps_exh = IndicatorLight{parent=rps_annunc,label="Excess Heated Coolant",colors=cpair(colors.yellow,colors.gray)} + local rps_exw = IndicatorLight{parent=rps_annunc,label="Excess Waste",colors=cpair(colors.yellow,colors.gray)} + local rps_tmp = IndicatorLight{parent=rps_annunc,label="Core Temp. High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local rps_nof = IndicatorLight{parent=rps_annunc,label="No Fuel",colors=cpair(colors.yellow,colors.gray)} + local rps_noc = IndicatorLight{parent=rps_annunc,label="Coolant Level Low Low",colors=cpair(colors.yellow,colors.gray)} + local rps_flt = IndicatorLight{parent=rps_annunc,label="PPM Fault",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} + local rps_tmo = IndicatorLight{parent=rps_annunc,label="Timeout",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} + local rps_sfl = IndicatorLight{parent=rps_annunc,label="System Failure",colors=cpair(colors.orange,colors.gray),flash=true,period=period.BLINK_500_MS} r_ps.subscribe("rps_tripped", rps_trp.update) r_ps.subscribe("dmg_crit", rps_dmg.update) @@ -194,19 +209,18 @@ local function init(parent, id) r_ps.subscribe("timeout", rps_tmo.update) r_ps.subscribe("sys_fail", rps_sfl.update) - annunciator.line_break() - -- cooling annunciator panel - TextBox{parent=main,x=34,y=31,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)} - TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)} - TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)} - TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)} - TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.cyan)} - local c_cfm = IndicatorLight{parent=annunciator,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} - local c_brm = IndicatorLight{parent=annunciator,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)} - local c_sfm = IndicatorLight{parent=annunciator,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} - local c_mwrf = IndicatorLight{parent=annunciator,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} - local c_tbnt = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + + TextBox{parent=main,text="REACTOR COOLANT SYSTEM",fg_bg=cpair(colors.black,colors.blue),alignment=TEXT_ALIGN.CENTER,width=33,height=1,x=46,y=22} + local rcs = Rectangle{parent=main,border=border(1, colors.blue, true),thin=true,width=33,height=24,x=46,y=23} + local rcs_annunc = Div{parent=rcs,width=27,height=22,x=2,y=1} + local rcs_tags = Div{parent=rcs,width=2,height=22,x=29,y=1} + + local c_cfm = IndicatorLight{parent=rcs_annunc,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_brm = IndicatorLight{parent=rcs_annunc,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_sfm = IndicatorLight{parent=rcs_annunc,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_mwrf = IndicatorLight{parent=rcs_annunc,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} + local c_tbnt = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} r_ps.subscribe("CoolantFeedMismatch", c_cfm.update) r_ps.subscribe("BoilRateMismatch", c_brm.update) @@ -214,100 +228,89 @@ local function init(parent, id) r_ps.subscribe("MaxWaterReturnFeed", c_mwrf.update) r_ps.subscribe("TurbineTrip", c_tbnt.update) - annunciator.line_break() + rcs_annunc.line_break() -- boiler annunciator panel(s) - local tag_y = 1 - if unit.num_boilers > 0 then - tag_y = TextBox{parent=main,x=32,y=37,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y() - local b1_wll = IndicatorLight{parent=annunciator,label="Water Level Low",colors=cpair(colors.red,colors.gray)} + TextBox{parent=rcs_tags,x=1,y=7,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local b1_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=cpair(colors.red,colors.gray)} b_ps[1].subscribe("WasterLevelLow", b1_wll.update) - TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)} - tag_y = TextBox{parent=main,x=32,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y() - local b1_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} + TextBox{parent=rcs_tags,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local b1_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} b_ps[1].subscribe("HeatingRateLow", b1_hr.update) - TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)} end if unit.num_boilers > 1 then - tag_y = TextBox{parent=main,x=32,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y() - local b2_wll = IndicatorLight{parent=annunciator,label="Water Level Low",colors=cpair(colors.red,colors.gray)} + TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local b2_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=cpair(colors.red,colors.gray)} b_ps[2].subscribe("WasterLevelLow", b2_wll.update) - TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)} - tag_y = TextBox{parent=main,x=32,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y() - local b2_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} + TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local b2_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} b_ps[2].subscribe("HeatingRateLow", b2_hr.update) - TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)} - end - - if unit.num_boilers > 0 then - main.line_break() - annunciator.line_break() end -- turbine annunciator panels if unit.num_boilers == 0 then - TextBox{parent=main,x=32,y=37,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=rcs_tags,y=7,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} else - TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + rcs_tags.line_break() + rcs_annunc.line_break() + TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} end - local t1_sdo = TriIndicatorLight{parent=annunciator,label="Steam Relief Valve",c1=colors.gray,c2=colors.yellow,c3=colors.red} + local t1_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} t_ps[1].subscribe("SteamDumpOpen", function (val) t1_sdo.update(val + 1) end) - TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t1_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} + TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t1_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} t_ps[1].subscribe("TurbineOverSpeed", t1_tos.update) - tag_y = TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y() - local t1_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t1_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} t_ps[1].subscribe("TurbineTrip", t1_trp.update) - TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.cyan)} if unit.num_turbines > 1 then - TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t2_sdo = TriIndicatorLight{parent=annunciator,label="Steam Relief Valve",c1=colors.gray,c2=colors.yellow,c3=colors.red} + rcs_tags.line_break() + rcs_annunc.line_break() + + TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t2_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} t_ps[2].subscribe("SteamDumpOpen", function (val) t2_sdo.update(val + 1) end) - TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t2_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} + TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t2_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} t_ps[2].subscribe("TurbineOverSpeed", t2_tos.update) - tag_y = TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y() - local t2_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t2_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} t_ps[2].subscribe("TurbineTrip", t2_trp.update) - TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.cyan)} end if unit.num_turbines > 2 then - TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t3_sdo = TriIndicatorLight{parent=annunciator,label="Steam Relief Valve",c1=colors.gray,c2=colors.yellow,c3=colors.red} + rcs_tags.line_break() + rcs_annunc.line_break() + + TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t3_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} t_ps[3].subscribe("SteamDumpOpen", function (val) t3_sdo.update(val + 1) end) - TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} - local t3_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} + TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t3_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} t_ps[3].subscribe("TurbineOverSpeed", t3_tos.update) - tag_y = TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y() - local t3_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + local t3_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} t_ps[3].subscribe("TurbineTrip", t3_trp.update) - TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.cyan)} end - annunciator.line_break() - - ---@todo radiation monitor - IndicatorLight{parent=annunciator,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)} - ---------------------- -- reactor controls -- ---------------------- - local burn_control = Div{parent=main,x=2,y=25,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} + local burn_control = Div{parent=main,x=2,y=28,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} TextBox{parent=burn_control,x=9,y=2,text="mB/t"} @@ -319,10 +322,10 @@ local function init(parent, id) local dis_colors = cpair(colors.white, colors.lightGray) - local start = HazardButton{parent=main,x=22,y=25,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=hzd_fg_bg} - local ack_a = HazardButton{parent=main,x=12,y=29,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=unit.ack_alarms,fg_bg=hzd_fg_bg} - local scram = HazardButton{parent=main,x=2,y=29,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=hzd_fg_bg} - local reset = HazardButton{parent=main,x=22,y=29,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,fg_bg=hzd_fg_bg} + local start = HazardButton{parent=main,x=22,y=28,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=hzd_fg_bg} + local ack_a = HazardButton{parent=main,x=12,y=32,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=unit.ack_alarms,fg_bg=hzd_fg_bg} + local scram = HazardButton{parent=main,x=2,y=32,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=hzd_fg_bg} + local reset = HazardButton{parent=main,x=22,y=32,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,fg_bg=hzd_fg_bg} unit.start_ack = start.on_response unit.scram_ack = scram.on_response @@ -340,10 +343,11 @@ local function init(parent, id) r_ps.subscribe("rps_tripped", start_button_en_check) r_ps.subscribe("rps_tripped", function (active) if active then reset.enable() else reset.disable() end end) - local waste_sel = Div{parent=main,x=2,y=50,width=29,height=2,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=main,text="WASTE PROCESSING",fg_bg=cpair(colors.black,colors.brown),alignment=TEXT_ALIGN.CENTER,width=33,height=1,x=46,y=48} + local waste_proc = Rectangle{parent=main,border=border(1, colors.brown, true),thin=true,width=33,height=3,x=46,y=49} + local waste_div = Div{parent=waste_proc,x=2,y=1,width=31,height=1} - local waste_mode = MultiButton{parent=waste_sel,x=1,y=1,options=waste_opts,callback=unit.set_waste,min_width=6,fg_bg=cpair(colors.black, colors.white)} - TextBox{parent=waste_sel,text="Waste Processing",alignment=TEXT_ALIGN.CENTER,x=1,y=1,height=1} + local waste_mode = MultiButton{parent=waste_div,x=1,y=1,options=waste_opts,callback=unit.set_waste,min_width=6} r_ps.subscribe("U_WasteMode", waste_mode.set_value) @@ -351,7 +355,7 @@ local function init(parent, id) -- alarm management -- ---------------------- - local alarm_panel = Div{parent=main,x=2,y=33,width=29,height=16,fg_bg=cpair(colors.black,colors.white)} + local alarm_panel = Div{parent=main,x=2,y=36,width=29,height=16,fg_bg=cpair(colors.black,colors.white)} local a_brc = AlarmLight{parent=alarm_panel,x=6,y=2,label="Containment Breach",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} local a_rad = AlarmLight{parent=alarm_panel,x=6,label="Containment Radiation",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} @@ -419,9 +423,9 @@ local function init(parent, id) -- color tags - TextBox{parent=alarm_panel,x=5,y=13,text="\x95",width=1,height=1,fg_bg=cpair(colors.white, colors.brown)} + TextBox{parent=alarm_panel,x=5,y=13,text="\x95",width=1,height=1,fg_bg=cpair(colors.white, colors.cyan)} + TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(colors.white, colors.blue)} TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(colors.white, colors.blue)} - TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(colors.white, colors.cyan)} return main end diff --git a/graphics/elements/controls/multi_button.lua b/graphics/elements/controls/multi_button.lua index 24614c8..c92e9f4 100644 --- a/graphics/elements/controls/multi_button.lua +++ b/graphics/elements/controls/multi_button.lua @@ -32,7 +32,7 @@ local function multi_button(args) assert(type(args.callback) == "function", "graphics.elements.controls.multi_button: callback is a required field") -- single line - args.height = 3 + args.height = 1 -- determine widths local max_width = 1 @@ -71,7 +71,7 @@ local function multi_button(args) for i = 1, #args.options do local opt = args.options[i] ---@type button_option - e.window.setCursorPos(opt._start_x, 2) + e.window.setCursorPos(opt._start_x, 1) if e.value == i then -- show as pressed @@ -91,7 +91,7 @@ local function multi_button(args) ---@param event monitor_touch monitor touch event function e.handle_touch(event) -- determine what was pressed - if e.enabled and event.y == 2 then + if e.enabled and event.y == 1 then for i = 1, #args.options do local opt = args.options[i] ---@type button_option diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua index b7ece93..09c20da 100644 --- a/graphics/elements/rectangle.lua +++ b/graphics/elements/rectangle.lua @@ -6,6 +6,7 @@ local element = require("graphics.element") ---@class rectangle_args ---@field border? graphics_border +---@field thin? boolean true to use extra thin even borders ---@field parent graphics_element ---@field id? string element id ---@field x? integer 1 if omitted @@ -19,6 +20,14 @@ local element = require("graphics.element") ---@param args rectangle_args ---@return graphics_element element, element_id id local function rectangle(args) + assert(args.border ~= nil or args.thin ~= true, "graphics.elements.rectangle: thin requires border to be provided") + + -- if thin, then width will always need to be 1 + if args.thin == true then + args.border.width = 1 + args.border.even = true + end + -- offset children if args.border ~= nil then args.offset_x = args.border.width @@ -52,22 +61,42 @@ local function rectangle(args) -- form the basic line strings and top/bottom blit strings local spaces = util.spaces(e.frame.w) local blit_fg = util.strrep(e.fg_bg.blit_fgd, e.frame.w) + local blit_fg_sides = blit_fg local blit_bg_sides = "" local blit_bg_top_bot = util.strrep(border_blit, e.frame.w) -- partial bars local p_a = util.spaces(border_width) .. util.strrep("\x8f", inner_width) .. util.spaces(border_width) local p_b = util.spaces(border_width) .. util.strrep("\x83", inner_width) .. util.spaces(border_width) + local p_s = spaces + + if args.thin == true then + p_a = "\x97" .. util.strrep("\x83", inner_width) .. "\x94" + p_b = "\x8a" .. util.strrep("\x8f", inner_width) .. "\x85" + p_s = "\x95" .. util.spaces(inner_width) .. "\x95" + end + local p_inv_fg = util.strrep(border_blit, border_width) .. util.strrep(e.fg_bg.blit_bkg, inner_width) .. util.strrep(border_blit, border_width) local p_inv_bg = util.strrep(e.fg_bg.blit_bkg, border_width) .. util.strrep(border_blit, inner_width) .. util.strrep(e.fg_bg.blit_bkg, border_width) + if args.thin == true then + p_inv_fg = e.fg_bg.blit_bkg .. util.strrep(e.fg_bg.blit_bkg, inner_width) .. util.strrep(border_blit, border_width) + p_inv_bg = border_blit .. util.strrep(border_blit, inner_width) .. util.strrep(e.fg_bg.blit_bkg, border_width) + + blit_fg_sides = border_blit .. util.strrep(e.fg_bg.blit_bkg, inner_width) .. e.fg_bg.blit_bkg + end + -- form the body blit strings (sides are border, inside is normal) for x = 1, e.frame.w do -- edges get border color, center gets normal if x <= border_width or x > (e.frame.w - border_width) then - blit_bg_sides = blit_bg_sides .. border_blit + if args.thin and x == 1 then + blit_bg_sides = blit_bg_sides .. e.fg_bg.blit_bkg + else + blit_bg_sides = blit_bg_sides .. border_blit + end else blit_bg_sides = blit_bg_sides .. e.fg_bg.blit_bkg end @@ -76,36 +105,50 @@ local function rectangle(args) -- draw rectangle with borders for y = 1, e.frame.h do e.window.setCursorPos(1, y) + -- top border if y <= border_height then -- partial pixel fill if args.border.even and y == border_height then - if width_x2 % 3 == 1 then - e.window.blit(p_b, p_inv_bg, p_inv_fg) - elseif width_x2 % 3 == 2 then + if args.thin == true then e.window.blit(p_a, p_inv_bg, p_inv_fg) else - -- skip line - e.window.blit(spaces, blit_fg, blit_bg_sides) + if width_x2 % 3 == 1 then + e.window.blit(p_b, p_inv_bg, p_inv_fg) + elseif width_x2 % 3 == 2 then + e.window.blit(p_a, p_inv_bg, p_inv_fg) + else + -- skip line + e.window.blit(spaces, blit_fg, blit_bg_sides) + end end else e.window.blit(spaces, blit_fg, blit_bg_top_bot) end + -- bottom border elseif y > (e.frame.h - border_width) then -- partial pixel fill if args.border.even and y == ((e.frame.h - border_width) + 1) then - if width_x2 % 3 == 1 then - e.window.blit(p_a, p_inv_fg, blit_bg_top_bot) - elseif width_x2 % 3 == 2 then - e.window.blit(p_b, p_inv_fg, blit_bg_top_bot) + if args.thin == true then + e.window.blit(p_b, util.strrep(e.fg_bg.blit_bkg, e.frame.w), blit_bg_top_bot) else - -- skip line - e.window.blit(spaces, blit_fg, blit_bg_sides) + if width_x2 % 3 == 1 then + e.window.blit(p_a, p_inv_fg, blit_bg_top_bot) + elseif width_x2 % 3 == 2 or (args.thin == true) then + e.window.blit(p_b, p_inv_fg, blit_bg_top_bot) + else + -- skip line + e.window.blit(spaces, blit_fg, blit_bg_sides) + end end else e.window.blit(spaces, blit_fg, blit_bg_top_bot) end else - e.window.blit(spaces, blit_fg, blit_bg_sides) + if (args.thin == true) then + e.window.blit(p_s, blit_fg_sides, blit_bg_sides) + else + e.window.blit(p_s, blit_fg, blit_bg_sides) + end end end end From a591cab338d9a36d9ca23322aacc41f6f301dd60 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 11 Dec 2022 10:51:45 -0500 Subject: [PATCH 465/587] color reactor coolant bars based on coolant type --- coordinator/startup.lua | 2 +- coordinator/ui/components/reactor.lua | 25 ++++++++++++++++------- coordinator/ui/components/unit_detail.lua | 20 ++++++++++++++++-- graphics/element.lua | 6 ++++++ graphics/elements/indicators/hbar.lua | 4 +++- graphics/elements/indicators/vbar.lua | 4 +++- 6 files changed, 49 insertions(+), 12 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 7417bd6..6cfaa0e 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.2" +local COORDINATOR_VERSION = "beta-v0.8.3" local print = util.print local println = util.println diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua index 1c8978b..d81d662 100644 --- a/coordinator/ui/components/reactor.lua +++ b/coordinator/ui/components/reactor.lua @@ -45,16 +45,27 @@ local function new_view(root, x, y, data, ps) TextBox{parent=reactor_fills,text="HCOOL",x=2,y=4,height=1,fg_bg=text_fg_bg} TextBox{parent=reactor_fills,text="WASTE",x=2,y=5,height=1,fg_bg=text_fg_bg} - -- local ccool_color = util.trinary(data.mek_status.ccool_type == "sodium", cpair(colors.lightBlue,colors.gray), cpair(colors.blue,colors.gray)) - -- local hcool_color = util.trinary(data.mek_status.hcool_type == "superheated_sodium", cpair(colors.orange,colors.gray), cpair(colors.white,colors.gray)) - local ccool_color = util.trinary(true, cpair(colors.lightBlue,colors.gray), cpair(colors.blue,colors.gray)) - local hcool_color = util.trinary(true, cpair(colors.orange,colors.gray), cpair(colors.white,colors.gray)) - local fuel = HorizontalBar{parent=reactor_fills,x=8,y=1,show_percent=true,bar_fg_bg=cpair(colors.black,colors.gray),height=1,width=14} - local ccool = HorizontalBar{parent=reactor_fills,x=8,y=2,show_percent=true,bar_fg_bg=ccool_color,height=1,width=14} - local hcool = HorizontalBar{parent=reactor_fills,x=8,y=4,show_percent=true,bar_fg_bg=hcool_color,height=1,width=14} + local ccool = HorizontalBar{parent=reactor_fills,x=8,y=2,show_percent=true,bar_fg_bg=cpair(colors.blue,colors.gray),height=1,width=14} + local hcool = HorizontalBar{parent=reactor_fills,x=8,y=4,show_percent=true,bar_fg_bg=cpair(colors.white,colors.gray),height=1,width=14} local waste = HorizontalBar{parent=reactor_fills,x=8,y=5,show_percent=true,bar_fg_bg=cpair(colors.brown,colors.gray),height=1,width=14} + ps.subscribe("ccool_type", function (type) + if type == "mekanism:sodium" then + ccool.recolor(cpair(colors.lightBlue, colors.gray)) + else + ccool.recolor(cpair(colors.blue, colors.gray)) + end + end) + + ps.subscribe("hcool_type", function (type) + if type == "mekanism:superheated_sodium" then + hcool.recolor(cpair(colors.orange, colors.gray)) + else + hcool.recolor(cpair(colors.white, colors.gray)) + end + end) + ps.subscribe("fuel_fill", fuel.update) ps.subscribe("ccool_fill", ccool.update) ps.subscribe("hcool_fill", hcool.update) diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 7a95447..9b3d170 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -94,8 +94,8 @@ local function init(parent, id) TextBox{parent=main,text="W",x=29,y=22,width=1,height=1,fg_bg=style.label} local fuel = VerticalBar{parent=main,x=23,y=23,fg_bg=cpair(colors.black,colors.gray),height=4,width=1} - local ccool = VerticalBar{parent=main,x=25,y=23,fg_bg=cpair(colors.lightBlue,colors.gray),height=4,width=1} - local hcool = VerticalBar{parent=main,x=27,y=23,fg_bg=cpair(colors.orange,colors.gray),height=4,width=1} + local ccool = VerticalBar{parent=main,x=25,y=23,fg_bg=cpair(colors.blue,colors.gray),height=4,width=1} + local hcool = VerticalBar{parent=main,x=27,y=23,fg_bg=cpair(colors.white,colors.gray),height=4,width=1} local waste = VerticalBar{parent=main,x=29,y=23,fg_bg=cpair(colors.brown,colors.gray),height=4,width=1} r_ps.subscribe("fuel_fill", fuel.update) @@ -103,6 +103,22 @@ local function init(parent, id) r_ps.subscribe("hcool_fill", hcool.update) r_ps.subscribe("waste_fill", waste.update) + r_ps.subscribe("ccool_type", function (type) + if type == "mekanism:sodium" then + ccool.recolor(cpair(colors.lightBlue, colors.gray)) + else + ccool.recolor(cpair(colors.blue, colors.gray)) + end + end) + + r_ps.subscribe("hcool_type", function (type) + if type == "mekanism:superheated_sodium" then + hcool.recolor(cpair(colors.orange, colors.gray)) + else + hcool.recolor(cpair(colors.white, colors.gray)) + end + end) + TextBox{parent=main,x=32,y=22,text="Core Temp",height=1,width=9,fg_bg=style.label} local core_temp = DataIndicator{parent=main,x=32,label="",format="%11.2f",value=0,unit="K",lu_colors=lu_cpair,width=13,fg_bg=stat_fg_bg} r_ps.subscribe("temp", core_temp.update) diff --git a/graphics/element.lua b/graphics/element.lua index 7bf8b71..32a67a7 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -363,6 +363,12 @@ function element.new(args) protected.disable() end + -- custom recolor command, varies by element if implemented + ---@vararg cpair|color color(s) + function public.recolor(...) + protected.recolor(...) + end + -- resize attributes of the element value if supported ---@vararg number dimensions (element specific) function public.resize(...) diff --git a/graphics/elements/indicators/hbar.lua b/graphics/elements/indicators/hbar.lua index 092d88e..a05cdb6 100644 --- a/graphics/elements/indicators/hbar.lua +++ b/graphics/elements/indicators/hbar.lua @@ -106,7 +106,9 @@ local function hbar(args) -- re-draw last_num_bars = 0 - e.on_update(e.value) + if type(e.value) == "number" then + e.on_update(e.value) + end end -- set the percentage value diff --git a/graphics/elements/indicators/vbar.lua b/graphics/elements/indicators/vbar.lua index f56c60c..be7d9e4 100644 --- a/graphics/elements/indicators/vbar.lua +++ b/graphics/elements/indicators/vbar.lua @@ -89,7 +89,9 @@ local function vbar(args) -- re-draw last_num_bars = 0 - e.on_update(e.value) + if type(e.value) == "number" then + e.on_update(e.value) + end end -- set the percentage value From 93a0dedcb1f99c91066a03732e638f6ac6251102 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 13 Dec 2022 15:18:29 -0500 Subject: [PATCH 466/587] #24 GUI for unit displays to set unit group --- coordinator/startup.lua | 3 +- coordinator/ui/components/unit_detail.lua | 174 ++++++++++++-------- graphics/element.lua | 1 + graphics/elements/controls/multi_button.lua | 11 +- graphics/elements/controls/radio_button.lua | 108 ++++++++++++ 5 files changed, 222 insertions(+), 75 deletions(-) create mode 100644 graphics/elements/controls/radio_button.lua diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 6cfaa0e..e39d535 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.3" +local COORDINATOR_VERSION = "beta-v0.8.4" local print = util.print local println = util.println @@ -297,7 +297,6 @@ local function main() local msg = "supervisor server timeout" log_comms(msg) println_ts(msg) - log.warning(msg) -- close connection and UI coord_comms.close() diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 9b3d170..119071e 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -22,6 +22,7 @@ local VerticalBar = require("graphics.elements.indicators.vbar") local HazardButton = require("graphics.elements.controls.hazard_button") local MultiButton = require("graphics.elements.controls.multi_button") local PushButton = require("graphics.elements.controls.push_button") +local RadioButton = require("graphics.elements.controls.radio_button") local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") local TEXT_ALIGN = core.graphics.TEXT_ALIGN @@ -67,6 +68,7 @@ local function init(parent, id) TextBox{parent=main,text="Reactor Unit #" .. id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + local bw_fg_bg = cpair(colors.black, colors.white) local hzd_fg_bg = cpair(colors.white, colors.gray) local lu_cpair = cpair(colors.gray, colors.gray) @@ -78,25 +80,25 @@ local function init(parent, id) r_ps.subscribe("temp", core_map.update) r_ps.subscribe("size", function (s) core_map.resize(s[1], s[2]) end) - local stat_fg_bg = cpair(colors.black,colors.white) - - TextBox{parent=main,x=2,y=22,text="Heating Rate",height=1,width=12,fg_bg=style.label} - local heating_r = DataIndicator{parent=main,x=2,label="",format="%14.0f",value=0,unit="mB/t",commas=true,lu_colors=lu_cpair,width=19,fg_bg=stat_fg_bg} + TextBox{parent=main,x=12,y=22,text="Heating Rate",height=1,width=12,fg_bg=style.label} + local heating_r = DataIndicator{parent=main,x=12,label="",format="%14.0f",value=0,unit="mB/t",commas=true,lu_colors=lu_cpair,width=19,fg_bg=bw_fg_bg} r_ps.subscribe("heating_rate", heating_r.update) - TextBox{parent=main,x=2,y=25,text="Commanded Burn Rate",height=1,width=19,fg_bg=style.label} - local burn_r = DataIndicator{parent=main,x=2,label="",format="%14.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=19,fg_bg=stat_fg_bg} + TextBox{parent=main,x=12,y=25,text="Commanded Burn Rate",height=1,width=19,fg_bg=style.label} + local burn_r = DataIndicator{parent=main,x=12,label="",format="%14.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=19,fg_bg=bw_fg_bg} r_ps.subscribe("burn_rate", burn_r.update) - TextBox{parent=main,text="F",x=23,y=22,width=1,height=1,fg_bg=style.label} - TextBox{parent=main,text="C",x=25,y=22,width=1,height=1,fg_bg=style.label} - TextBox{parent=main,text="H",x=27,y=22,width=1,height=1,fg_bg=style.label} - TextBox{parent=main,text="W",x=29,y=22,width=1,height=1,fg_bg=style.label} + TextBox{parent=main,text="F",x=2,y=22,width=1,height=1,fg_bg=style.label} + TextBox{parent=main,text="C",x=4,y=22,width=1,height=1,fg_bg=style.label} + TextBox{parent=main,text="\x1a",x=6,y=24,width=1,height=1,fg_bg=style.label} + TextBox{parent=main,text="\x1a",x=6,y=25,width=1,height=1,fg_bg=style.label} + TextBox{parent=main,text="H",x=8,y=22,width=1,height=1,fg_bg=style.label} + TextBox{parent=main,text="W",x=10,y=22,width=1,height=1,fg_bg=style.label} - local fuel = VerticalBar{parent=main,x=23,y=23,fg_bg=cpair(colors.black,colors.gray),height=4,width=1} - local ccool = VerticalBar{parent=main,x=25,y=23,fg_bg=cpair(colors.blue,colors.gray),height=4,width=1} - local hcool = VerticalBar{parent=main,x=27,y=23,fg_bg=cpair(colors.white,colors.gray),height=4,width=1} - local waste = VerticalBar{parent=main,x=29,y=23,fg_bg=cpair(colors.brown,colors.gray),height=4,width=1} + local fuel = VerticalBar{parent=main,x=2,y=23,fg_bg=cpair(colors.black,colors.gray),height=4,width=1} + local ccool = VerticalBar{parent=main,x=4,y=23,fg_bg=cpair(colors.blue,colors.gray),height=4,width=1} + local hcool = VerticalBar{parent=main,x=8,y=23,fg_bg=cpair(colors.white,colors.gray),height=4,width=1} + local waste = VerticalBar{parent=main,x=10,y=23,fg_bg=cpair(colors.brown,colors.gray),height=4,width=1} r_ps.subscribe("fuel_fill", fuel.update) r_ps.subscribe("ccool_fill", ccool.update) @@ -120,27 +122,27 @@ local function init(parent, id) end) TextBox{parent=main,x=32,y=22,text="Core Temp",height=1,width=9,fg_bg=style.label} - local core_temp = DataIndicator{parent=main,x=32,label="",format="%11.2f",value=0,unit="K",lu_colors=lu_cpair,width=13,fg_bg=stat_fg_bg} + local core_temp = DataIndicator{parent=main,x=32,label="",format="%11.2f",value=0,unit="K",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} r_ps.subscribe("temp", core_temp.update) TextBox{parent=main,x=32,y=25,text="Burn Rate",height=1,width=9,fg_bg=style.label} - local act_burn_r = DataIndicator{parent=main,x=32,label="",format="%8.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=13,fg_bg=stat_fg_bg} + local act_burn_r = DataIndicator{parent=main,x=32,label="",format="%8.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} r_ps.subscribe("act_burn_rate", act_burn_r.update) TextBox{parent=main,x=32,y=28,text="Damage",height=1,width=6,fg_bg=style.label} - local damage_p = DataIndicator{parent=main,x=32,label="",format="%11.0f",value=0,unit="%",lu_colors=lu_cpair,width=13,fg_bg=stat_fg_bg} + local damage_p = DataIndicator{parent=main,x=32,label="",format="%11.0f",value=0,unit="%",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} r_ps.subscribe("damage", damage_p.update) ---@todo radiation monitor TextBox{parent=main,x=32,y=31,text="Radiation",height=1,width=21,fg_bg=style.label} - DataIndicator{parent=main,x=32,label="",format="%7.2f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=13,fg_bg=stat_fg_bg} + DataIndicator{parent=main,x=32,label="",format="%7.2f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} ------------------- -- system status -- ------------------- - local u_stat = Rectangle{parent=main,border=border(1, colors.gray, true),thin=true,width=33,height=4,x=46,y=3,fg_bg=cpair(colors.black,colors.white)} - local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=33,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.black, colors.white)} + local u_stat = Rectangle{parent=main,border=border(1,colors.gray,true),thin=true,width=33,height=4,x=46,y=3,fg_bg=bw_fg_bg} + local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=33,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=bw_fg_bg} local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data",width=33,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} r_ps.subscribe("U_StatusLine1", stat_line_1.set_value) @@ -159,7 +161,7 @@ local function init(parent, id) local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=cpair(colors.white,colors.gray)} local r_active = IndicatorLight{parent=annunciator,label="Active",colors=cpair(colors.green,colors.gray)} ---@todo auto control as info sent here - local r_auto = IndicatorLight{parent=annunciator,label="Auto. Control",colors=cpair(colors.blue,colors.gray)} + local r_auto = IndicatorLight{parent=annunciator,label="Automatic Control",colors=cpair(colors.blue,colors.gray)} annunciator.line_break() @@ -200,14 +202,14 @@ local function init(parent, id) -- RPS annunciator panel TextBox{parent=main,text="REACTOR PROTECTION SYSTEM",fg_bg=cpair(colors.black,colors.cyan),alignment=TEXT_ALIGN.CENTER,width=33,height=1,x=46,y=8} - local rps = Rectangle{parent=main,border=border(1, colors.cyan, true),thin=true,width=33,height=12,x=46,y=9} + local rps = Rectangle{parent=main,border=border(1,colors.cyan,true),thin=true,width=33,height=12,x=46,y=9} local rps_annunc = Div{parent=rps,width=31,height=10,x=2,y=1} local rps_trp = IndicatorLight{parent=rps_annunc,label="RPS Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} local rps_dmg = IndicatorLight{parent=rps_annunc,label="Damage Critical",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} local rps_exh = IndicatorLight{parent=rps_annunc,label="Excess Heated Coolant",colors=cpair(colors.yellow,colors.gray)} local rps_exw = IndicatorLight{parent=rps_annunc,label="Excess Waste",colors=cpair(colors.yellow,colors.gray)} - local rps_tmp = IndicatorLight{parent=rps_annunc,label="Core Temp. High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local rps_tmp = IndicatorLight{parent=rps_annunc,label="Core Temperature High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} local rps_nof = IndicatorLight{parent=rps_annunc,label="No Fuel",colors=cpair(colors.yellow,colors.gray)} local rps_noc = IndicatorLight{parent=rps_annunc,label="Coolant Level Low Low",colors=cpair(colors.yellow,colors.gray)} local rps_flt = IndicatorLight{parent=rps_annunc,label="PPM Fault",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} @@ -228,7 +230,7 @@ local function init(parent, id) -- cooling annunciator panel TextBox{parent=main,text="REACTOR COOLANT SYSTEM",fg_bg=cpair(colors.black,colors.blue),alignment=TEXT_ALIGN.CENTER,width=33,height=1,x=46,y=22} - local rcs = Rectangle{parent=main,border=border(1, colors.blue, true),thin=true,width=33,height=24,x=46,y=23} + local rcs = Rectangle{parent=main,border=border(1,colors.blue,true),thin=true,width=33,height=24,x=46,y=23} local rcs_annunc = Div{parent=rcs,width=27,height=22,x=2,y=1} local rcs_tags = Div{parent=rcs,width=2,height=22,x=29,y=1} @@ -249,20 +251,20 @@ local function init(parent, id) -- boiler annunciator panel(s) if unit.num_boilers > 0 then - TextBox{parent=rcs_tags,x=1,y=7,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=rcs_tags,x=1,y=7,text="B1",width=2,height=1,fg_bg=bw_fg_bg} local b1_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=cpair(colors.red,colors.gray)} b_ps[1].subscribe("WasterLevelLow", b1_wll.update) - TextBox{parent=rcs_tags,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=rcs_tags,text="B1",width=2,height=1,fg_bg=bw_fg_bg} local b1_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} b_ps[1].subscribe("HeatingRateLow", b1_hr.update) end if unit.num_boilers > 1 then - TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=bw_fg_bg} local b2_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=cpair(colors.red,colors.gray)} b_ps[2].subscribe("WasterLevelLow", b2_wll.update) - TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=bw_fg_bg} local b2_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} b_ps[2].subscribe("HeatingRateLow", b2_hr.update) end @@ -270,21 +272,21 @@ local function init(parent, id) -- turbine annunciator panels if unit.num_boilers == 0 then - TextBox{parent=rcs_tags,y=7,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=rcs_tags,y=7,text="T1",width=2,height=1,fg_bg=bw_fg_bg} else rcs_tags.line_break() rcs_annunc.line_break() - TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg} end local t1_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} t_ps[1].subscribe("SteamDumpOpen", function (val) t1_sdo.update(val + 1) end) - TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg} local t1_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} t_ps[1].subscribe("TurbineOverSpeed", t1_tos.update) - TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg} local t1_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} t_ps[1].subscribe("TurbineTrip", t1_trp.update) @@ -292,15 +294,15 @@ local function init(parent, id) rcs_tags.line_break() rcs_annunc.line_break() - TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg} local t2_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} t_ps[2].subscribe("SteamDumpOpen", function (val) t2_sdo.update(val + 1) end) - TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg} local t2_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} t_ps[2].subscribe("TurbineOverSpeed", t2_tos.update) - TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg} local t2_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} t_ps[2].subscribe("TurbineTrip", t2_trp.update) end @@ -309,15 +311,15 @@ local function init(parent, id) rcs_tags.line_break() rcs_annunc.line_break() - TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg} local t3_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} t_ps[3].subscribe("SteamDumpOpen", function (val) t3_sdo.update(val + 1) end) - TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg} local t3_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} t_ps[3].subscribe("TurbineOverSpeed", t3_tos.update) - TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)} + TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg} local t3_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} t_ps[3].subscribe("TurbineTrip", t3_trp.update) end @@ -326,8 +328,8 @@ local function init(parent, id) -- reactor controls -- ---------------------- - local burn_control = Div{parent=main,x=2,y=28,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} - local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} + local burn_control = Div{parent=main,x=12,y=28,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} + local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg} TextBox{parent=burn_control,x=9,y=2,text="mB/t"} local set_burn = function () unit.set_burn(burn_rate.get_value()) end @@ -338,7 +340,7 @@ local function init(parent, id) local dis_colors = cpair(colors.white, colors.lightGray) - local start = HazardButton{parent=main,x=22,y=28,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=hzd_fg_bg} + local start = HazardButton{parent=main,x=2,y=28,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=hzd_fg_bg} local ack_a = HazardButton{parent=main,x=12,y=32,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=unit.ack_alarms,fg_bg=hzd_fg_bg} local scram = HazardButton{parent=main,x=2,y=32,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=hzd_fg_bg} local reset = HazardButton{parent=main,x=22,y=32,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,fg_bg=hzd_fg_bg} @@ -360,7 +362,7 @@ local function init(parent, id) r_ps.subscribe("rps_tripped", function (active) if active then reset.enable() else reset.disable() end end) TextBox{parent=main,text="WASTE PROCESSING",fg_bg=cpair(colors.black,colors.brown),alignment=TEXT_ALIGN.CENTER,width=33,height=1,x=46,y=48} - local waste_proc = Rectangle{parent=main,border=border(1, colors.brown, true),thin=true,width=33,height=3,x=46,y=49} + local waste_proc = Rectangle{parent=main,border=border(1,colors.brown,true),thin=true,width=33,height=3,x=46,y=49} local waste_div = Div{parent=waste_proc,x=2,y=1,width=31,height=1} local waste_mode = MultiButton{parent=waste_div,x=1,y=1,options=waste_opts,callback=unit.set_waste,min_width=6} @@ -371,7 +373,7 @@ local function init(parent, id) -- alarm management -- ---------------------- - local alarm_panel = Div{parent=main,x=2,y=36,width=29,height=16,fg_bg=cpair(colors.black,colors.white)} + local alarm_panel = Div{parent=main,x=2,y=36,width=29,height=16,fg_bg=bw_fg_bg} local a_brc = AlarmLight{parent=alarm_panel,x=6,y=2,label="Containment Breach",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} local a_rad = AlarmLight{parent=alarm_panel,x=6,label="Containment Radiation",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} @@ -410,38 +412,70 @@ local function init(parent, id) local rst_fg_bg = cpair(colors.black, colors.lime) local active_fg_bg = cpair(colors.white, colors.gray) - PushButton{parent=alarm_panel,x=2,y=2,text="\x13",min_width=1,callback=c.c_breach.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=4,y=2,text="R",min_width=1,callback=c.c_breach.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=2,y=3,text="\x13",min_width=1,callback=c.radiation.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=4,y=3,text="R",min_width=1,callback=c.radiation.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=2,y=4,text="\x13",min_width=1,callback=c.dmg_crit.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=4,y=4,text="R",min_width=1,callback=c.dmg_crit.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=2,text="\x13",callback=c.c_breach.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=2,text="R",callback=c.c_breach.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=3,text="\x13",callback=c.radiation.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=3,text="R",callback=c.radiation.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=4,text="\x13",callback=c.dmg_crit.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=4,text="R",callback=c.dmg_crit.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=2,y=6,text="\x13",min_width=1,callback=c.r_lost.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=4,y=6,text="R",min_width=1,callback=c.r_lost.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=2,y=7,text="\x13",min_width=1,callback=c.damage.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=4,y=7,text="R",min_width=1,callback=c.damage.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=2,y=8,text="\x13",min_width=1,callback=c.over_temp.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=4,y=8,text="R",min_width=1,callback=c.over_temp.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=2,y=9,text="\x13",min_width=1,callback=c.high_temp.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=4,y=9,text="R",min_width=1,callback=c.high_temp.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=2,y=10,text="\x13",min_width=1,callback=c.waste_leak.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=4,y=10,text="R",min_width=1,callback=c.waste_leak.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=2,y=11,text="\x13",min_width=1,callback=c.waste_high.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=4,y=11,text="R",min_width=1,callback=c.waste_high.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=6,text="\x13",callback=c.r_lost.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=6,text="R",callback=c.r_lost.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=7,text="\x13",callback=c.damage.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=7,text="R",callback=c.damage.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=8,text="\x13",callback=c.over_temp.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=8,text="R",callback=c.over_temp.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=9,text="\x13",callback=c.high_temp.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=9,text="R",callback=c.high_temp.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=10,text="\x13",callback=c.waste_leak.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=10,text="R",callback=c.waste_leak.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=11,text="\x13",callback=c.waste_high.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=11,text="R",callback=c.waste_high.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=2,y=13,text="\x13",min_width=1,callback=c.rps_trans.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=4,y=13,text="R",min_width=1,callback=c.rps_trans.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=2,y=14,text="\x13",min_width=1,callback=c.rcs_trans.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=4,y=14,text="R",min_width=1,callback=c.rcs_trans.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=2,y=15,text="\x13",min_width=1,callback=c.t_trip.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} - PushButton{parent=alarm_panel,x=4,y=15,text="R",min_width=1,callback=c.t_trip.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=13,text="\x13",callback=c.rps_trans.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=13,text="R",callback=c.rps_trans.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=14,text="\x13",callback=c.rcs_trans.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=14,text="R",callback=c.rcs_trans.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=2,y=15,text="\x13",callback=c.t_trip.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg} + PushButton{parent=alarm_panel,x=4,y=15,text="R",callback=c.t_trip.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg} -- color tags - TextBox{parent=alarm_panel,x=5,y=13,text="\x95",width=1,height=1,fg_bg=cpair(colors.white, colors.cyan)} - TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(colors.white, colors.blue)} - TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(colors.white, colors.blue)} + TextBox{parent=alarm_panel,x=5,y=13,text="\x95",width=1,height=1,fg_bg=cpair(colors.white,colors.cyan)} + TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(colors.white,colors.blue)} + TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(colors.white,colors.blue)} + + -------------------------------- + -- automatic control settings -- + -------------------------------- + + TextBox{parent=main,text="AUTO CTRL",fg_bg=cpair(colors.black,colors.purple),alignment=TEXT_ALIGN.CENTER,width=13,height=1,x=32,y=36} + local auto_ctl = Rectangle{parent=main,border=border(1,colors.purple,true),thin=true,width=13,height=15,x=32,y=37} + local auto_div = Div{parent=auto_ctl,width=13,height=15,x=2,y=1} + + local ctl_opts = { "Manual", "Group A", "Group B", "Group C", "Group D" } + + local a_prm, a_stb + local test = function (val) + a_prm.update(val ~= 1) + a_stb.update(val ~= 1) + end + + RadioButton{parent=auto_div,options=ctl_opts,callback=test,radio_colors=cpair(colors.blue,colors.white),radio_bg=colors.gray} + + auto_div.line_break() + + PushButton{parent=auto_div,text="SET",x=3,min_width=5,fg_bg=cpair(colors.black,colors.white),active_fg_bg=cpair(colors.white,colors.gray),callback=function()end} + + auto_div.line_break() + + TextBox{parent=auto_div,text="CRD Group",height=1,width=9,fg_bg=style.label} + local auto_grp = TextBox{parent=auto_div,text="Manual",height=1,width=9,fg_bg=bw_fg_bg} + + auto_div.line_break() + + a_prm = IndicatorLight{parent=auto_div,label="Primary",colors=cpair(colors.green,colors.gray)} + a_stb = IndicatorLight{parent=auto_div,label="Standby",colors=cpair(colors.white,colors.gray)} return main end diff --git a/graphics/element.lua b/graphics/element.lua index 32a67a7..fdd0bfe 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -24,6 +24,7 @@ local element = {} ---|hazard_button_args ---|multi_button_args ---|push_button_args +---|radio_button_args ---|spinbox_args ---|switch_button_args ---|alarm_indicator_light diff --git a/graphics/elements/controls/multi_button.lua b/graphics/elements/controls/multi_button.lua index c92e9f4..2cf583a 100644 --- a/graphics/elements/controls/multi_button.lua +++ b/graphics/elements/controls/multi_button.lua @@ -1,4 +1,4 @@ --- Button Graphics Element +-- Multi Button Graphics Element local util = require("scada-common.util") @@ -15,7 +15,7 @@ local element = require("graphics.element") ---@class multi_button_args ---@field options table button options ---@field callback function function to call on touch ----@field default? boolean default state, defaults to options[1] +---@field default? integer default state, defaults to options[1] ---@field min_width? integer text length + 2 if omitted ---@field parent graphics_element ---@field id? string element id @@ -29,7 +29,12 @@ local element = require("graphics.element") ---@return graphics_element element, element_id id local function multi_button(args) assert(type(args.options) == "table", "graphics.elements.controls.multi_button: options is a required field") + assert(#args.options > 0, "graphics.elements.controls.multi_button: at least one option is required") assert(type(args.callback) == "function", "graphics.elements.controls.multi_button: callback is a required field") + assert(type(args.default) == "nil" or (type(args.default) == "number" and args.default > 0), + "graphics.elements.controls.multi_button: default must be nil or a number > 0") + assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), + "graphics.elements.controls.multi_button: min_width must be nil or a number > 0") -- single line args.height = 1 @@ -43,7 +48,7 @@ local function multi_button(args) end end - local button_width = math.max(max_width, args.min_width or 1) + local button_width = math.max(max_width, args.min_width or 0) args.width = (button_width * #args.options) + #args.options + 1 diff --git a/graphics/elements/controls/radio_button.lua b/graphics/elements/controls/radio_button.lua new file mode 100644 index 0000000..025cad1 --- /dev/null +++ b/graphics/elements/controls/radio_button.lua @@ -0,0 +1,108 @@ +-- Radio Button Graphics Element + +local element = require("graphics.element") + +---@class radio_button_args +---@field options table button options +---@field callback function function to call on touch +---@field radio_colors cpair colors for radio button center dot when active (a) or inactive (b) +---@field radio_bg color background color of radio button +---@field default? integer default state, defaults to options[1] +---@field min_width? integer text length + 2 if omitted +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field fg_bg? cpair foreground/background colors + +-- new radio button list (latch selection, exclusively one button at a time) +---@param args radio_button_args +---@return graphics_element element, element_id id +local function radio_button(args) + assert(type(args.options) == "table", "graphics.elements.controls.radio_button: options is a required field") + assert(#args.options > 0, "graphics.elements.controls.radio_button: at least one option is required") + assert(type(args.callback) == "function", "graphics.elements.controls.radio_button: callback is a required field") + assert(type(args.default) == "nil" or (type(args.default) == "number" and args.default > 0), + "graphics.elements.controls.radio_button: default must be nil or a number > 0") + assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), + "graphics.elements.controls.radio_button: min_width must be nil or a number > 0") + + -- one line per option + args.height = #args.options + + -- determine widths + local max_width = 1 + for i = 1, #args.options do + local opt = args.options[i] ---@type string + if string.len(opt) > max_width then + max_width = string.len(opt) + end + end + + local button_text_width = math.max(max_width, args.min_width or 0) + + args.width = button_text_width + 2 + + -- create new graphics element base object + local e = element.new(args) + + -- button state (convert nil to 1 if missing) + e.value = args.default or 1 + + -- show the button state + local function draw() + for i = 1, #args.options do + local opt = args.options[i] ---@type string + + e.window.setCursorPos(1, i) + + if e.value == i then + -- show as selected + e.window.setTextColor(args.radio_colors.color_a) + e.window.setBackgroundColor(args.radio_bg) + else + -- show as unselected + e.window.setTextColor(args.radio_colors.color_b) + e.window.setBackgroundColor(args.radio_bg) + end + + e.window.write("\x88") + + e.window.setTextColor(args.radio_bg) + e.window.setBackgroundColor(e.fg_bg.bkg) + e.window.write("\x95") + + -- write button text + e.window.setTextColor(e.fg_bg.fgd) + e.window.setBackgroundColor(e.fg_bg.bkg) + e.window.write(opt) + end + end + + -- handle touch + ---@param event monitor_touch monitor touch event + function e.handle_touch(event) + -- determine what was pressed + if e.enabled then + if args.options[event.y] ~= nil then + e.value = event.y + draw() + args.callback(e.value) + end + end + end + + -- set the value + ---@param val integer new value + function e.set_value(val) + e.value = val + draw() + end + + -- initial draw + draw() + + return e.get() +end + +return radio_button From ca2983506e7e86f62670e0a87fb63d2fbd021c32 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 18 Dec 2022 13:56:04 -0500 Subject: [PATCH 467/587] #24 coordinator/supervisor setting process groups and unit burn rate limits --- coordinator/coordinator.lua | 5 + coordinator/iocontrol.lua | 136 ++++++++------------ coordinator/process.lua | 117 +++++++++++++++++ coordinator/startup.lua | 2 +- coordinator/ui/components/unit_overview.lua | 10 +- scada-common/comms.lua | 6 +- supervisor/session/coordinator.lua | 34 +++-- supervisor/session/facility.lua | 100 +++++++++++++- supervisor/session/rtu.lua | 12 +- supervisor/session/svsessions.lua | 22 +--- supervisor/session/unit.lua | 27 +++- supervisor/startup.lua | 2 +- 12 files changed, 349 insertions(+), 124 deletions(-) create mode 100644 coordinator/process.lua diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 25ffa02..13430e0 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -2,6 +2,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local ppm = require("scada-common.ppm") local util = require("scada-common.util") +local process = require("coordinator.process") local apisessions = require("coordinator.apisessions") local iocontrol = require("coordinator.iocontrol") @@ -442,6 +443,10 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa unit.set_waste_ack(ack) elseif cmd == UNIT_COMMANDS.ACK_ALL_ALARMS then unit.ack_alarms_ack(ack) + elseif cmd == UNIT_COMMANDS.SET_GROUP then + process.sv_assign(unit_id, ack) + elseif cmd == UNIT_COMMANDS.SET_LIMIT then + process.sv_limit(unit_id, ack) else log.debug(util.c("received command ack with unknown command ", cmd)) end diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 775cfb9..c66d8d5 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -4,6 +4,7 @@ local psil = require("scada-common.psil") local types = require("scada-common.types") local util = require("scada-common.util") +local process = require("coordinator.process") local sounder = require("coordinator.sounder") local UNIT_COMMANDS = comms.UNIT_COMMANDS @@ -20,13 +21,21 @@ local io = {} ---@param comms coord_comms comms reference ---@diagnostic disable-next-line: redefined-local function iocontrol.init(conf, comms) + -- pass IO control here since it can't be require'd due to a require loop + process.init(iocontrol, comms) + io.facility = { + auto_active = false, scram = false, + num_units = conf.num_units, ---@type integer ps = psil.create(), induction_ps_tbl = {}, - induction_data_tbl = {} + induction_data_tbl = {}, + + env_d_ps = psil.create(), + env_d_data = {} } -- create induction tables (max 1 per unit, preferably 1 total) @@ -38,20 +47,12 @@ function iocontrol.init(conf, comms) io.units = {} for i = 1, conf.num_units do - local function ack(alarm) - comms.send_command(UNIT_COMMANDS.ACK_ALARM, i, alarm) - log.debug(util.c("UNIT[", i, "]: ACK ALARM ", alarm)) - end - - local function reset(alarm) - comms.send_command(UNIT_COMMANDS.RESET_ALARM, i, alarm) - log.debug(util.c("UNIT[", i, "]: RESET ALARM ", alarm)) - end + local function ack(alarm) process.ack_alarm(i, alarm) end + local function reset(alarm) process.reset_alarm(i, alarm) end ---@class ioctl_entry local entry = { - unit_id = i, ---@type integer - initialized = false, + unit_id = i, ---@type integer num_boilers = 0, num_turbines = 0, @@ -60,53 +61,58 @@ function iocontrol.init(conf, comms) burn_rate_cmd = 0.0, waste_control = 0, - start = function () end, - scram = function () end, - reset_rps = function () end, - ack_alarms = function () end, - set_burn = function (rate) end, ---@param rate number - set_waste = function (mode) end, ---@param mode integer + a_group = 0, -- auto control group + a_limit = 0.0, -- auto control limit - start_ack = function (success) end, ---@param success boolean - scram_ack = function (success) end, ---@param success boolean - reset_rps_ack = function (success) end, ---@param success boolean - ack_alarms_ack = function (success) end, ---@param success boolean - set_burn_ack = function (success) end, ---@param success boolean - set_waste_ack = function (success) end, ---@param success boolean + start = function () process.start(i) end, + scram = function () process.scram(i) end, + reset_rps = function () process.reset_rps(i) end, + ack_alarms = function () process.ack_all_alarms(i) end, + set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate + set_waste = function (mode) process.set_waste(i, mode) end, ---@param mode integer waste processing mode + + set_group = function (g) process.set_group(i, g) end, ---@param g integer|0 group ID or 0 + + start_ack = function (success) end, ---@param success boolean + scram_ack = function (success) end, ---@param success boolean + reset_rps_ack = function (success) end, ---@param success boolean + ack_alarms_ack = function (success) end, ---@param success boolean + set_burn_ack = function (success) end, ---@param success boolean + set_waste_ack = function (success) end, ---@param success boolean alarm_callbacks = { - c_breach = { ack = function () ack(1) end, reset = function () reset(1) end }, - radiation = { ack = function () ack(2) end, reset = function () reset(2) end }, - r_lost = { ack = function () ack(3) end, reset = function () reset(3) end }, - dmg_crit = { ack = function () ack(4) end, reset = function () reset(4) end }, - damage = { ack = function () ack(5) end, reset = function () reset(5) end }, - over_temp = { ack = function () ack(6) end, reset = function () reset(6) end }, - high_temp = { ack = function () ack(7) end, reset = function () reset(7) end }, - waste_leak = { ack = function () ack(8) end, reset = function () reset(8) end }, - waste_high = { ack = function () ack(9) end, reset = function () reset(9) end }, - rps_trans = { ack = function () ack(10) end, reset = function () reset(10) end }, - rcs_trans = { ack = function () ack(11) end, reset = function () reset(11) end }, - t_trip = { ack = function () ack(12) end, reset = function () reset(12) end } + c_breach = { ack = function () ack(1) end, reset = function () reset(1) end }, + radiation = { ack = function () ack(2) end, reset = function () reset(2) end }, + r_lost = { ack = function () ack(3) end, reset = function () reset(3) end }, + dmg_crit = { ack = function () ack(4) end, reset = function () reset(4) end }, + damage = { ack = function () ack(5) end, reset = function () reset(5) end }, + over_temp = { ack = function () ack(6) end, reset = function () reset(6) end }, + high_temp = { ack = function () ack(7) end, reset = function () reset(7) end }, + waste_leak = { ack = function () ack(8) end, reset = function () reset(8) end }, + waste_high = { ack = function () ack(9) end, reset = function () reset(9) end }, + rps_trans = { ack = function () ack(10) end, reset = function () reset(10) end }, + rcs_trans = { ack = function () ack(11) end, reset = function () reset(11) end }, + t_trip = { ack = function () ack(12) end, reset = function () reset(12) end } }, ---@type alarms alarms = { - ALARM_STATE.INACTIVE, - ALARM_STATE.INACTIVE, - ALARM_STATE.INACTIVE, - ALARM_STATE.INACTIVE, - ALARM_STATE.INACTIVE, - ALARM_STATE.INACTIVE, - ALARM_STATE.INACTIVE, - ALARM_STATE.INACTIVE, - ALARM_STATE.INACTIVE, - ALARM_STATE.INACTIVE, - ALARM_STATE.INACTIVE, - ALARM_STATE.INACTIVE + ALARM_STATE.INACTIVE, -- containment breach + ALARM_STATE.INACTIVE, -- containment radiation + ALARM_STATE.INACTIVE, -- reactor lost + ALARM_STATE.INACTIVE, -- damage critical + ALARM_STATE.INACTIVE, -- reactor taking damage + ALARM_STATE.INACTIVE, -- reactor over temperature + ALARM_STATE.INACTIVE, -- reactor high temperature + ALARM_STATE.INACTIVE, -- waste leak + ALARM_STATE.INACTIVE, -- waste level high + ALARM_STATE.INACTIVE, -- RPS transient + ALARM_STATE.INACTIVE, -- RCS transient + ALARM_STATE.INACTIVE -- turbine trip }, reactor_ps = psil.create(), - reactor_data = {}, ---@type reactor_db + reactor_data = {}, ---@type reactor_db boiler_ps_tbl = {}, boiler_data_tbl = {}, @@ -115,38 +121,6 @@ function iocontrol.init(conf, comms) turbine_data_tbl = {} } - function entry.start() - entry.control_state = true - comms.send_command(UNIT_COMMANDS.START, i) - log.debug(util.c("UNIT[", i, "]: START")) - end - - function entry.scram() - entry.control_state = false - comms.send_command(UNIT_COMMANDS.SCRAM, i) - log.debug(util.c("UNIT[", i, "]: SCRAM")) - end - - function entry.reset_rps() - comms.send_command(UNIT_COMMANDS.RESET_RPS, i) - log.debug(util.c("UNIT[", i, "]: RESET_RPS")) - end - - function entry.ack_alarms() - comms.send_command(UNIT_COMMANDS.ACK_ALL_ALARMS, i) - log.debug(util.c("UNIT[", i, "]: ACK_ALL_ALARMS")) - end - - function entry.set_burn(rate) - comms.send_command(UNIT_COMMANDS.SET_BURN, i, rate) - log.debug(util.c("UNIT[", i, "]: SET_BURN = ", rate)) - end - - function entry.set_waste(mode) - comms.send_command(UNIT_COMMANDS.SET_WASTE, i, mode) - log.debug(util.c("UNIT[", i, "]: SET_WASTE = ", mode)) - end - -- create boiler tables for _ = 1, conf.defs[(i * 2) - 1] do local data = {} ---@type boilerv_session_db diff --git a/coordinator/process.lua b/coordinator/process.lua new file mode 100644 index 0000000..b594306 --- /dev/null +++ b/coordinator/process.lua @@ -0,0 +1,117 @@ + +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local util = require("scada-common.util") + +local UNIT_COMMANDS = comms.UNIT_COMMANDS + +---@class process_controller +local process = {} + +local self = { + io = nil, ---@type ioctl + comms = nil ---@type coord_comms +} + +-------------------------- +-- UNIT COMMAND CONTROL -- +-------------------------- + +-- initialize the process controller +---@param iocontrol ioctl +---@diagnostic disable-next-line: redefined-local +function process.init(iocontrol, comms) + self.io = iocontrol + self.comms = comms +end + +-- start reactor +---@param id integer unit ID +function process.start(id) + self.io.units[id].control_state = true + self.comms.send_command(UNIT_COMMANDS.START, id) + log.debug(util.c("UNIT[", id, "]: START")) +end + +-- SCRAM reactor +---@param id integer unit ID +function process.scram(id) + self.io.units[id].control_state = false + self.comms.send_command(UNIT_COMMANDS.SCRAM, id) + log.debug(util.c("UNIT[", id, "]: SCRAM")) +end + +-- reset reactor protection system +---@param id integer unit ID +function process.reset_rps(id) + self.comms.send_command(UNIT_COMMANDS.RESET_RPS, id) + log.debug(util.c("UNIT[", id, "]: RESET RPS")) +end + +-- set burn rate or burn limit if part of a group +---@param id integer unit ID +---@param rate number burn rate +function process.set_rate(id, rate) + if self.io.units[id].group == 0 then + self.comms.send_command(UNIT_COMMANDS.SET_BURN, id, rate) + log.debug(util.c("UNIT[", id, "]: SET BURN = ", rate)) + else + self.comms.send_command(UNIT_COMMANDS.SET_LIMIT, id, rate) + log.debug(util.c("UNIT[", id, "]: SET LIMIT = ", rate)) + end +end + +-- set waste mode +---@param id integer unit ID +---@param mode integer waste mode +function process.set_waste(id, mode) + self.comms.send_command(UNIT_COMMANDS.SET_WASTE, id, mode) + log.debug(util.c("UNIT[", id, "]: SET WASTE = ", mode)) +end + +-- acknowledge all alarms +---@param id integer unit ID +function process.ack_all_alarms(id) + self.comms.send_command(UNIT_COMMANDS.ACK_ALL_ALARMS, id) + log.debug(util.c("UNIT[", id, "]: ACK ALL ALARMS")) +end + +-- acknowledge an alarm +---@param id integer unit ID +---@param alarm integer alarm ID +function process.ack_alarm(id, alarm) + self.comms.send_command(UNIT_COMMANDS.ACK_ALARM, id, alarm) + log.debug(util.c("UNIT[", id, "]: ACK ALARM ", alarm)) +end + +-- reset an alarm +---@param id integer unit ID +---@param alarm integer alarm ID +function process.reset_alarm(id, alarm) + self.comms.send_command(UNIT_COMMANDS.RESET_ALARM, id, alarm) + log.debug(util.c("UNIT[", id, "]: RESET ALARM ", alarm)) +end + +-- assign a unit to a group +---@param unit_id integer unit ID +---@param group_id integer|0 group ID or 0 for independent +function process.set_group(unit_id, group_id) + self.comms.send_command(UNIT_COMMANDS.SET_GROUP, unit_id, group_id) + log.debug(util.c("UNIT[", unit_id, "]: SET GROUP ", group_id)) +end + +-------------------------- +-- SUPERVISOR RESPONSES -- +-------------------------- + +-- acknowledgement from the supervisor to assign a unit to a group +function process.sv_assign(unit_id, group_id) + self.io.units[unit_id].group = group_id +end + +-- acknowledgement from the supervisor to assign a unit a burn rate limit +function process.sv_limit(unit_id, limit) + self.io.units[unit_id].limit = limit +end + +return process diff --git a/coordinator/startup.lua b/coordinator/startup.lua index e39d535..5faab51 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.4" +local COORDINATOR_VERSION = "beta-v0.8.5" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 7e24396..71a2d87 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -2,17 +2,17 @@ -- Basic Unit Overview -- -local core = require("graphics.core") +local core = require("graphics.core") -local style = require("coordinator.ui.style") +local style = require("coordinator.ui.style") local reactor_view = require("coordinator.ui.components.reactor") local boiler_view = require("coordinator.ui.components.boiler") local turbine_view = require("coordinator.ui.components.turbine") -local Div = require("graphics.elements.div") -local PipeNetwork = require("graphics.elements.pipenet") -local TextBox = require("graphics.elements.textbox") +local Div = require("graphics.elements.div") +local PipeNetwork = require("graphics.elements.pipenet") +local TextBox = require("graphics.elements.textbox") local TEXT_ALIGN = core.graphics.TEXT_ALIGN diff --git a/scada-common/comms.lua b/scada-common/comms.lua index bb876dd..2abc533 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -12,7 +12,7 @@ local rtu_t = types.rtu_t local insert = table.insert -comms.version = "1.1.0" +comms.version = "1.1.1" ---@alias PROTOCOLS integer local PROTOCOLS = { @@ -79,7 +79,9 @@ local UNIT_COMMANDS = { SET_WASTE = 4, -- set the waste processing mode ACK_ALL_ALARMS = 5, -- ack all active alarms ACK_ALARM = 6, -- ack a particular alarm - RESET_ALARM = 7 -- reset a particular alarm + RESET_ALARM = 7, -- reset a particular alarm + SET_GROUP = 8, -- assign this unit to a group + SET_LIMIT = 9 -- set this unit maximum auto burn rate } ---@alias CAPI_TYPES integer diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 1a5e93f..9a19ce2 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -45,13 +45,13 @@ local PERIODICS = { ---@param in_queue mqueue ---@param out_queue mqueue ---@param facility facility ----@param units table -function coordinator.new_session(id, in_queue, out_queue, facility, units) +function coordinator.new_session(id, in_queue, out_queue, facility) local log_header = "crdn_session(" .. id .. "): " local self = { in_q = in_queue, out_q = out_queue, + units = facility.get_units(), -- connection properties seq_num = 0, r_seq_num = nil, @@ -122,8 +122,8 @@ function coordinator.new_session(id, in_queue, out_queue, facility, units) local builds = {} - for i = 1, #units do - local unit = units[i] ---@type reactor_unit + for i = 1, #self.units do + local unit = self.units[i] ---@type reactor_unit builds[unit.get_id()] = unit.get_build() end @@ -143,8 +143,8 @@ function coordinator.new_session(id, in_queue, out_queue, facility, units) local function _send_unit_statuses() local status = {} - for i = 1, #units do - local unit = units[i] ---@type reactor_unit + for i = 1, #self.units do + local unit = self.units[i] ---@type reactor_unit status[unit.get_id()] = { unit.get_reactor_status(), unit.get_rtu_statuses(), @@ -215,8 +215,8 @@ function coordinator.new_session(id, in_queue, out_queue, facility, units) local data = { uid, pkt.data[3] } -- continue if valid unit id - if util.is_int(uid) and uid > 0 and uid <= #units then - local unit = units[uid] ---@type reactor_unit + if util.is_int(uid) and uid > 0 and uid <= #self.units then + local unit = self.units[uid] ---@type reactor_unit if cmd == UNIT_COMMANDS.START then self.out_q.push_data(SV_Q_DATA.START, data) @@ -243,13 +243,27 @@ function coordinator.new_session(id, in_queue, out_queue, facility, units) if pkt.length == 3 then unit.ack_alarm(pkt.data[3]) else - log.debug(log_header .. "CRDN command unit ack alarm missing id") + log.debug(log_header .. "CRDN command unit ack alarm missing alarm id") end elseif cmd == UNIT_COMMANDS.RESET_ALARM then if pkt.length == 3 then unit.reset_alarm(pkt.data[3]) else - log.debug(log_header .. "CRDN command unit reset alarm missing id") + log.debug(log_header .. "CRDN command unit reset alarm missing alarm id") + end + elseif cmd == UNIT_COMMANDS.SET_GROUP then + if pkt.length == 3 then + unit.set_group(pkt.data[3]) + _send(SCADA_CRDN_TYPES.UNIT_CMD, { cmd, uid, pkt.data[3] }) + else + log.debug(log_header .. "CRDN command unit set group missing group id") + end + elseif cmd == UNIT_COMMANDS.SET_LIMIT then + if pkt.length == 3 then + unit.set_burn_limit(pkt.data[3]) + _send(SCADA_CRDN_TYPES.UNIT_CMD, { cmd, uid, pkt.data[3] }) + else + log.debug(log_header .. "CRDN command unit set limit missing group id") end else log.debug(log_header .. "CRDN command unknown") diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index 454606c..acb101b 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -3,17 +3,102 @@ local rsio = require("scada-common.rsio") local util = require("scada-common.util") local rsctl = require("supervisor.session.rsctl") +local unit = require("supervisor.session.unit") + +local HEATING_WATER = 20000 +local HEATING_SODIUM = 200000 + +-- 7.14 kJ per blade for 1 mB of fissile fuel +local POWER_PER_BLADE = util.joules_to_fe(7140) + +local function m_avg(length, default) + local data = {} + local index = 1 + local last_t = 0 ---@type number|nil + + ---@class moving_average + local public = {} + + -- reset all to a given value + ---@param x number value + function public.reset(x) + data = {} + for _ = 1, length do table.insert(data, x) end + end + + -- record a new value + ---@param x number new value + ---@param t number? optional last update time to prevent duplicated entries + function public.record(x, t) + if type(t) == "number" and last_t == t then + return + end + + data[index] = x + last_t = t + + index = index + 1 + if index > length then index = 1 end + end + + -- compute the moving average + ---@return number average + function public.compute() + local sum = 0 + for i = 1, length do sum = sum + data[i] end + return sum + end + + public.reset(default) + + return public +end + +---@alias PROCESS integer +local PROCESS = { + INACTIVE = 1, + SIMPLE = 2, + CHARGE = 3, + GEN_RATE = 4, + BURN_RATE = 5 +} ---@class facility_management local facility = {} +facility.PROCESS_MODES = PROCESS + -- create a new facility management object -function facility.new() +---@param num_reactors integer number of reactor units +---@param cooling_conf table cooling configurations of reactor units +function facility.new(num_reactors, cooling_conf) local self = { + -- components + units = {}, induction = {}, - redstone = {} + redstone = {}, + -- process control + mode = PROCESS.INACTIVE, + charge_target = 0, -- FE + charge_rate = 0, -- FE/t + charge_limit = 0.99, -- percentage + burn_rate_set = 0, + unit_limits = {}, + -- statistics + im_stat_init = false, + avg_charge = m_avg(10, 0.0), + avg_inflow = m_avg(10, 0.0), + avg_outflow = m_avg(10, 0.0) } + -- create units + for i = 1, num_reactors do + table.insert(self.units, unit.new(i, cooling_conf[i].BOILERS, cooling_conf[i].TURBINES)) + + local u_lim = { burn_rate = -1.0, temp = 1100 } ---@class unit_limit + table.insert(self.unit_limits, u_lim) + end + -- init redstone RTU I/O controller local rs_rtu_io_ctl = rsctl.new(self.redstone) @@ -58,6 +143,13 @@ function facility.new() _unlink_disconnected_units(self.redstone) end + function public.update_units() + for i = 1, #self.units do + local u = self.units[i] ---@type reactor_unit + u.update() + end + end + -- READ STATES/PROPERTIES -- -- get build properties of all machines @@ -94,6 +186,10 @@ function facility.new() return status end + function public.get_units() + return self.units + end + return public end diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 0d5bc1d..b5a72b2 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -37,8 +37,7 @@ local PERIODICS = { ---@param out_queue mqueue ---@param advertisement table ---@param facility facility ----@param facility_units table -function rtu.new_session(id, in_queue, out_queue, advertisement, facility, facility_units) +function rtu.new_session(id, in_queue, out_queue, advertisement, facility) local log_header = "rtu_session(" .. id .. "): " local self = { @@ -46,6 +45,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility, facil out_q = out_queue, modbus_q = mqueue.new(), advert = advertisement, + fac_units = facility.get_units(), -- connection properties seq_num = 0, r_seq_num = nil, @@ -71,8 +71,8 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility, facil local function _handle_advertisement() _reset_config() - for i = 1, #facility_units do - local unit = facility_units[i] ---@type reactor_unit + for i = 1, #self.fac_units do + local unit = self.fac_units[i] ---@type reactor_unit unit.purge_rtu_devices(id) facility.purge_rtu_devices(id) end @@ -105,7 +105,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility, facil if advert_validator.valid() then advert_validator.assert_min(unit_advert.index, 1) advert_validator.assert_min(unit_advert.reactor, 0) - advert_validator.assert_max(unit_advert.reactor, #facility_units) + advert_validator.assert_max(unit_advert.reactor, #self.fac_units) if not advert_validator.valid() then u_type = false end else u_type = false @@ -121,7 +121,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility, facil log.debug(log_header .. "advertisement unit validation failure") else if unit_advert.reactor > 0 then - local target_unit = facility_units[unit_advert.reactor] ---@type reactor_unit + local target_unit = self.fac_units[unit_advert.reactor] ---@type reactor_unit if u_type == RTU_UNIT_TYPES.REDSTONE then -- redstone diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 91b4873..10b5b96 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -4,7 +4,6 @@ local util = require("scada-common.util") local facility = require("supervisor.session.facility") local svqtypes = require("supervisor.session.svqtypes") -local unit = require("supervisor.session.unit") local coordinator = require("supervisor.session.coordinator") local plc = require("supervisor.session.plc") @@ -33,8 +32,7 @@ svsessions.SESSION_TYPE = SESSION_TYPE local self = { modem = nil, num_reactors = 0, - facility = facility.new(), - units = {}, + facility = nil, ---@type facility rtu_sessions = {}, plc_sessions = {}, coord_sessions = {}, @@ -199,11 +197,7 @@ end function svsessions.init(modem, num_reactors, cooling_conf) self.modem = modem self.num_reactors = num_reactors - self.units = {} - - for i = 1, self.num_reactors do - table.insert(self.units, unit.new(i, cooling_conf[i].BOILERS, cooling_conf[i].TURBINES)) - end + self.facility = facility.new(num_reactors, cooling_conf) end -- re-link the modem @@ -299,7 +293,8 @@ function svsessions.establish_plc_session(local_port, remote_port, for_reactor, plc_s.instance = plc.new_session(self.next_plc_id, for_reactor, plc_s.in_queue, plc_s.out_queue) table.insert(self.plc_sessions, plc_s) - self.units[for_reactor].link_plc_session(plc_s) + local units = self.facility.get_units() + units[for_reactor].link_plc_session(plc_s) log.debug("established new PLC session to " .. remote_port .. " with ID " .. self.next_plc_id) @@ -332,7 +327,7 @@ function svsessions.establish_rtu_session(local_port, remote_port, advertisement instance = nil ---@type rtu_session } - rtu_s.instance = rtu.new_session(self.next_rtu_id, rtu_s.in_queue, rtu_s.out_queue, advertisement, self.facility, self.units) + rtu_s.instance = rtu.new_session(self.next_rtu_id, rtu_s.in_queue, rtu_s.out_queue, advertisement, self.facility) table.insert(self.rtu_sessions, rtu_s) log.debug("established new RTU session to " .. remote_port .. " with ID " .. self.next_rtu_id) @@ -362,7 +357,7 @@ function svsessions.establish_coord_session(local_port, remote_port, version) instance = nil ---@type coord_session } - coord_s.instance = coordinator.new_session(self.next_coord_id, coord_s.in_queue, coord_s.out_queue, self.facility, self.units) + coord_s.instance = coordinator.new_session(self.next_coord_id, coord_s.in_queue, coord_s.out_queue, self.facility) table.insert(self.coord_sessions, coord_s) log.debug("established new coordinator session to " .. remote_port .. " with ID " .. self.next_coord_id) @@ -405,10 +400,7 @@ function svsessions.iterate_all() self.facility.update() -- iterate units - for i = 1, #self.units do - local u = self.units[i] ---@type reactor_unit - u.update() - end + self.facility.update_units() end -- delete all closed sessions diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 3c378e6..c61a661 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -88,7 +88,10 @@ function unit.new(for_reactor, num_boilers, num_turbines) damage_last = 0, damage_est_last = 0, waste_mode = WASTE_MODE.AUTO, - status_text = { "Unknown", "Awaiting Connection..." }, + status_text = { "UNKNOWN", "awaiting connection..." }, + -- auto control + group = 0, + limit = 0.0, -- logic for alarms had_reactor = false, start_ms = 0, @@ -947,6 +950,28 @@ function unit.new(for_reactor, num_boilers, num_turbines) end end + -- set the automatic control group of this unit + ---@param group integer group ID or 0 for independent + function public.set_group(group) + if group >= 0 and group <= 4 then + self.group = group + end + end + + -- set the automatic control max burn rate for this unit + ---@param limit number burn rate limit for auto control + function public.set_burn_limit(limit) + if limit >= 0 then + self.limit = limit + + if self.plc_i ~= nil then + if limit > self.plc_i.get_struct().max_burn then + self.limit = self.plc_i.get_struct().max_burn + end + end + end + end + -- READ STATES/PROPERTIES -- -- get build properties of all machines diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 79fa007..2c340dc 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.9.1" +local SUPERVISOR_VERSION = "beta-v0.9.2" local print = util.print local println = util.println From 6fe257d1d75ff763b22e50fca27a712ec506bc69 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 18 Dec 2022 14:11:25 -0500 Subject: [PATCH 468/587] #138 fixed bug with dmesg output resetting to default if log file is recycled --- coordinator/startup.lua | 2 +- scada-common/log.lua | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 5faab51..c5072d1 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.5" +local COORDINATOR_VERSION = "beta-v0.8.6" local print = util.print local println = util.println diff --git a/scada-common/log.lua b/scada-common/log.lua index 1a44356..2aeb714 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -81,10 +81,12 @@ local function _log(msg) end if out_of_space or (free_space(_log_sys.path) < 100) then - -- delete the old log file and open a new one + -- delete the old log file before opening a new one _log_sys.file.close() fs.delete(_log_sys.path) - log.init(_log_sys.path, _log_sys.mode) + + -- re-init logger and pass dmesg_out so that it doesn't change + log.init(_log_sys.path, _log_sys.mode, _log_sys.dmesg_out) -- leave a message _log_sys.file.writeLine(time_stamp .. "recycled log file") From 41838ee3403cdad4b93680df4b1e1ba529a31b70 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 3 Jan 2023 16:50:31 -0500 Subject: [PATCH 469/587] #102 #20 #19 #21 work in progress on auto control, added control loop, started auto scram checks, implemented limiting and balancing, re-organized for priority groups --- scada-common/util.lua | 46 +++ supervisor/session/coordinator.lua | 2 +- supervisor/session/facility.lua | 369 ++++++++++++++--- supervisor/session/plc.lua | 51 ++- supervisor/session/unit.lua | 639 ++++------------------------- supervisor/session/unitlogic.lua | 547 ++++++++++++++++++++++++ supervisor/startup.lua | 2 +- 7 files changed, 1040 insertions(+), 616 deletions(-) create mode 100644 supervisor/session/unitlogic.lua diff --git a/scada-common/util.lua b/scada-common/util.lua index e02d236..979172e 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -208,6 +208,52 @@ function util.round(x) return math.floor(x + 0.5) end +-- get a new moving average object +---@param length integer history length +---@param default number value to fill history with for first call to compute() +function util.mov_avg(length, default) + local data = {} + local index = 1 + local last_t = 0 ---@type number|nil + + ---@class moving_average + local public = {} + + -- reset all to a given value + ---@param x number value + function public.reset(x) + data = {} + for _ = 1, length do table.insert(data, x) end + end + + -- record a new value + ---@param x number new value + ---@param t number? optional last update time to prevent duplicated entries + function public.record(x, t) + if type(t) == "number" and last_t == t then + return + end + + data[index] = x + last_t = t + + index = index + 1 + if index > length then index = 1 end + end + + -- compute the moving average + ---@return number average + function public.compute() + local sum = 0 + for i = 1, length do sum = sum + data[i] end + return sum + end + + public.reset(default) + + return public +end + -- TIME -- -- current time diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 9a19ce2..2e5fdb0 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -253,7 +253,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility) end elseif cmd == UNIT_COMMANDS.SET_GROUP then if pkt.length == 3 then - unit.set_group(pkt.data[3]) + facility.set_group(unit.get_id(), pkt.data[3]) _send(SCADA_CRDN_TYPES.UNIT_CMD, { cmd, uid, pkt.data[3] }) else log.debug(log_header .. "CRDN command unit set group missing group id") diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index acb101b..d7ceadc 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -8,51 +8,12 @@ local unit = require("supervisor.session.unit") local HEATING_WATER = 20000 local HEATING_SODIUM = 200000 --- 7.14 kJ per blade for 1 mB of fissile fuel +-- 7.14 kJ per blade for 1 mB of fissile fuel
+-- 2856 FE per blade per 1 mB, 285.6 FE per blade per 0.1 mB (minimum) local POWER_PER_BLADE = util.joules_to_fe(7140) -local function m_avg(length, default) - local data = {} - local index = 1 - local last_t = 0 ---@type number|nil - - ---@class moving_average - local public = {} - - -- reset all to a given value - ---@param x number value - function public.reset(x) - data = {} - for _ = 1, length do table.insert(data, x) end - end - - -- record a new value - ---@param x number new value - ---@param t number? optional last update time to prevent duplicated entries - function public.record(x, t) - if type(t) == "number" and last_t == t then - return - end - - data[index] = x - last_t = t - - index = index + 1 - if index > length then index = 1 end - end - - -- compute the moving average - ---@return number average - function public.compute() - local sum = 0 - for i = 1, length do sum = sum + data[i] end - return sum - end - - public.reset(default) - - return public -end +local MAX_CHARGE = 0.99 +local RE_ENABLE_CHARGE = 0.95 ---@alias PROCESS integer local PROCESS = { @@ -63,6 +24,20 @@ local PROCESS = { BURN_RATE = 5 } +local AUTO_SCRAM = { + NONE = 0, + MATRIX_DC = 1, + MATRIX_FILL = 2 +} + +local charge_Kp = 1.0 +local charge_Ki = 0.0 +local charge_Kd = 0.0 + +local rate_Kp = 1.0 +local rate_Ki = 0.00001 +local rate_Kd = 0.0 + ---@class facility_management local facility = {} @@ -73,30 +48,37 @@ facility.PROCESS_MODES = PROCESS ---@param cooling_conf table cooling configurations of reactor units function facility.new(num_reactors, cooling_conf) local self = { - -- components units = {}, induction = {}, redstone = {}, -- process control mode = PROCESS.INACTIVE, - charge_target = 0, -- FE - charge_rate = 0, -- FE/t - charge_limit = 0.99, -- percentage - burn_rate_set = 0, - unit_limits = {}, + last_mode = PROCESS.INACTIVE, + burn_target = 0.0, -- burn rate target for aggregate burn mode + charge_target = 0, -- FE charge target + charge_rate = 0, -- FE/t charge rate target + group_map = { 0, 0, 0, 0 }, -- units -> group IDs + prio_defs = { {}, {}, {}, {} }, -- priority definitions (each level is a table of units) + ascram = false, + ascram_reason = AUTO_SCRAM.NONE, + -- closed loop control + charge_conversion = 1.0, + time_start = 0.0, + initial_ramp = true, + waiting_on_ramp = false, + accumulator = 0.0, + last_error = 0.0, + last_time = 0.0, -- statistics im_stat_init = false, - avg_charge = m_avg(10, 0.0), - avg_inflow = m_avg(10, 0.0), - avg_outflow = m_avg(10, 0.0) + avg_charge = util.mov_avg(10, 0.0), + avg_inflow = util.mov_avg(10, 0.0), + avg_outflow = util.mov_avg(10, 0.0) } -- create units for i = 1, num_reactors do table.insert(self.units, unit.new(i, cooling_conf[i].BOILERS, cooling_conf[i].TURBINES)) - - local u_lim = { burn_rate = -1.0, temp = 1100 } ---@class unit_limit - table.insert(self.unit_limits, u_lim) end -- init redstone RTU I/O controller @@ -108,6 +90,60 @@ function facility.new(num_reactors, cooling_conf) util.filter_table(sessions, function (u) return u.is_connected() end) end + -- check if all auto-controlled units completed ramping + local function _all_units_ramped() + local all_ramped = true + + for i = 1, #self.prio_defs do + local units = self.prio_defs[i] + for u = 1, #units do + all_ramped = all_ramped and units[u].a_ramp_complete() + end + end + + return all_ramped + end + + -- split a burn rate among the reactors + ---@param burn_rate number burn rate assignment + ---@param ramp boolean true to ramp, false to set right away + local function _allocate_burn_rate(burn_rate, ramp) + local unallocated = math.floor(burn_rate * 10) + + -- go through alll priority groups + for i = 1, #self.prio_defs and (unallocated > 0) do + local units = self.prio_defs[i] + local split = math.floor(unallocated / #units) + + local splits = {} + for u = 1, #units do splits[u] = split end + splits[#units] = splits[#units] + (unallocated % #units) + + -- go through all reactor units in this group + for u = 1, #units do + local ctl = units[u].get_control_inf() ---@type unit_control + local last = ctl.br10 + + if splits[u] <= ctl.lim_br10 then + ctl.br10 = splits[u] + else + ctl.br10 = ctl.lim_br10 + + if u < #units then + local remaining = #units - u + split = math.floor(unallocated / remaining) + for x = (u + 1), #units do splits[x] = split end + splits[#units] = splits[#units] + (unallocated % remaining) + end + end + + unallocated = unallocated - ctl.br10 + + if last ~= ctl.br10 then units[u].a_commit_br10(ramp) end + end + end + end + -- PUBLIC FUNCTIONS -- ---@class facility @@ -141,8 +177,209 @@ function facility.new(num_reactors, cooling_conf) -- unlink RTU unit sessions if they are closed _unlink_disconnected_units(self.induction) _unlink_disconnected_units(self.redstone) + + -- calculate moving averages for induction matrix + if self.induction[1] ~= nil then + local matrix = self.induction[1] ---@type unit_session + local db = matrix.get_db() ---@type imatrix_session_db + + if (db.state.last_update > 0) and (db.tanks.last_update > 0) then + if self.im_stat_init then + self.avg_charge.record(db.tanks.energy, db.tanks.last_update) + self.avg_inflow.record(db.state.last_input, db.state.last_update) + self.avg_outflow.record(db.state.last_output, db.state.last_update) + else + self.im_stat_init = true + self.avg_charge.reset(db.tanks.energy) + self.avg_inflow.reset(db.state.last_input) + self.avg_outflow.reset(db.state.last_output) + end + end + else + self.im_stat_init = false + end + + ------------------------- + -- Run Process Control -- + ------------------------- + + local avg_charge = self.avg_charge.compute() + local avg_inflow = self.avg_inflow.compute() + + local now = util.time_s() + + local state_changed = self.mode ~= self.last_mode + + -- once auto control is started, sort the priority sublists by limits + if state_changed then + if self.last_mode == PROCESS.INACTIVE then + local blade_count = 0 + for i = 1, #self.prio_defs do + table.sort(self.prio_defs[i], + ---@param a reactor_unit + ---@param b reactor_unit + function (a, b) return a.get_control_inf().lim_br10 < b.get_control_inf().lim_br10 end + ) + + for _, u in pairs(self.prio_defs[i]) do + blade_count = blade_count + u.get_db().blade_count + u.a_engage() + end + end + + self.charge_conversion = blade_count * POWER_PER_BLADE + elseif self.mode == PROCESS.INACTIVE then + for i = 1, #self.prio_defs do + for _, u in pairs(self.prio_defs[i]) do + u.a_disengage() + end + end + end + + self.initial_ramp = true + self.waiting_on_ramp = false + else + self.initial_ramp = false + end + + if self.mode == PROCESS.SIMPLE then + -- run units at their last configured set point + if state_changed then + self.time_start = now + end + elseif self.mode == PROCESS.CHARGE then + -- target a level of charge + local error = (self.charge_target - avg_charge) / self.charge_conversion + + if state_changed then + -- nothing special to do + elseif self.waiting_on_ramp and _all_units_ramped() then + self.waiting_on_ramp = false + + self.time_start = now + self.accumulator = 0 + end + + if not self.waiting_on_ramp then + self.accumulator = self.accumulator + (avg_charge / self.charge_conversion) + + local runtime = now - self.time_start + local integral = self.accumulator / runtime + local derivative = (error - self.last_error) / (now - self.last_time) + + local P = (charge_Kp * error) + local I = (charge_Ki * integral) + local D = (charge_Kd * derivative) + + local setpoint = P + I + D + local sp_r = util.round(setpoint * 10.0) / 10.0 + + log.debug(util.sprintf("PROC_CHRG[%f] { CHRG[%f] ERR[%f] INT[%f] => SP[%f] SP_R[%f] <= P[%f] I[%f] D[%d] }", + runtime, avg_charge, error, integral, setpoint, sp_r, P, I, D)) + + _allocate_burn_rate(sp_r, self.initial_ramp) + + if self.initial_ramp then + self.waiting_on_ramp = true + end + end + elseif self.mode == PROCESS.GEN_RATE then + -- target a rate of generation + local error = (self.charge_rate - avg_inflow) / self.charge_conversion + local setpoint = 0.0 + + if state_changed then + -- estimate an initial setpoint + setpoint = error / self.charge_conversion + + local sp_r = util.round(setpoint * 10.0) / 10.0 + + _allocate_burn_rate(sp_r, true) + elseif self.waiting_on_ramp and _all_units_ramped() then + self.waiting_on_ramp = false + + self.time_start = now + self.accumulator = 0 + end + + if not self.waiting_on_ramp then + self.accumulator = self.accumulator + (avg_inflow / self.charge_conversion) + + local runtime = util.time_s() - self.time_start + local integral = self.accumulator / runtime + local derivative = (error - self.last_error) / (now - self.last_time) + + local P = (rate_Kp * error) + local I = (rate_Ki * integral) + local D = (rate_Kd * derivative) + + setpoint = P + I + D + + local sp_r = util.round(setpoint * 10.0) / 10.0 + + log.debug(util.sprintf("PROC_RATE[%f] { RATE[%f] ERR[%f] INT[%f] => SP[%f] SP_R[%f] <= P[%f] I[%f] D[%f] }", + runtime, avg_inflow, error, integral, setpoint, sp_r, P, I, D)) + + _allocate_burn_rate(sp_r, false) + end + elseif self.mode == PROCESS.BURN_RATE then + -- a total aggregate burn rate + if state_changed then + -- nothing special to do + elseif self.waiting_on_ramp and _all_units_ramped() then + self.waiting_on_ramp = false + self.time_start = now + end + + if not self.waiting_on_ramp then + _allocate_burn_rate(self.burn_target, self.initial_ramp) + end + end + + ------------------------------ + -- Evaluate Automatic SCRAM -- + ------------------------------ + + if self.mode ~= PROCESS.INACTIVE then + local scram = false + + if self.induction[1] ~= nil then + local matrix = self.induction[1] ---@type unit_session + local db = matrix.get_db() ---@type imatrix_session_db + + if self.ascram_reason == AUTO_SCRAM.MATRIX_DC then + self.ascram_reason = AUTO_SCRAM.NONE + end + + if (db.tanks.energy_fill > MAX_CHARGE) or + (self.ascram_reason == AUTO_SCRAM.MATRIX_FILL and db.tanks.energy_fill > RE_ENABLE_CHARGE) then + scram = true + + if self.ascram_reason == AUTO_SCRAM.NONE then + self.ascram_reason = AUTO_SCRAM.MATRIX_FILL + end + end + else + scram = true + if self.ascram_reason == AUTO_SCRAM.NONE then + self.ascram_reason = AUTO_SCRAM.MATRIX_DC + end + end + + -- SCRAM all units + if not self.ascram and scram then + for i = 1, #self.prio_defs do + for _, u in pairs(self.prio_defs[i]) do + u.a_scram() + end + end + + self.ascram = true + end + end end + -- call the update function of all units in the facility function public.update_units() for i = 1, #self.units do local u = self.units[i] ---@type reactor_unit @@ -150,6 +387,28 @@ function facility.new(num_reactors, cooling_conf) end end + -- SETTINGS -- + + -- set the automatic control group of a unit + ---@param unit_id integer unit ID + ---@param group integer group ID or 0 for independent + function public.set_group(unit_id, group) + if group >= 0 and group <= 4 and self.mode == PROCESS.INACTIVE then + -- remove from old group if previously assigned + local old_group = self.group_map[unit_id] + if old_group ~= 0 then + util.filter_table(self.prio_defs[old_group], function (u) return u.get_id() ~= unit_id end) + end + + self.group_map[unit] = group + + -- add to group if not independent + if group > 0 then + table.insert(self.prio_defs[group], self.units[unit_id]) + end + end + end + -- READ STATES/PROPERTIES -- -- get build properties of all machines diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 316a584..9027e5f 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -31,7 +31,8 @@ local PLC_S_CMDS = { local PLC_S_DATA = { BURN_RATE = 1, - RAMP_BURN_RATE = 2 + RAMP_BURN_RATE = 2, + AUTO_BURN_RATE = 3 } plc.PLC_S_CMDS = PLC_S_CMDS @@ -58,6 +59,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) commanded_burn_rate = 0.0, ramping_rate = false, auto_scram = false, + auto_lock = false, -- connection properties seq_num = 0, r_seq_num = nil, @@ -511,6 +513,20 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) } end + -- lock out some manual operator actions during automatic control + ---@param engage boolean true to engage the lockout + function public.auto_lock(engage) + self.auto_lock = engage + end + + -- set the burn rate on behalf of automatic control + ---@param rate number burn rate + ---@param ramp boolean true to ramp, false to not + function public.auto_set_burn(rate, ramp) + self.ramping_rate = ramp + self.in_q.push_data(PLC_S_DATA.AUTO_BURN_RATE, rate) + end + -- check if a timer matches this session's watchdog function public.check_wd(timer) return self.plc_conn_watchdog.is_timer(timer) and self.connected @@ -547,7 +563,9 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) local cmd = message.message if cmd == PLC_S_CMDS.ENABLE then -- enable reactor - _send(RPLC_TYPES.RPS_ENABLE, {}) + if not self.auto_lock then + _send(RPLC_TYPES.RPS_ENABLE, {}) + end elseif cmd == PLC_S_CMDS.SCRAM then -- SCRAM reactor self.auto_scram = false @@ -571,20 +589,33 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) local cmd = message.message ---@type queue_data if cmd.key == PLC_S_DATA.BURN_RATE then -- update burn rate - cmd.val = math.floor(cmd.val * 10) / 10 -- round to 10ths place - if cmd.val > 0 and cmd.val <= self.sDB.mek_struct.max_burn then - self.commanded_burn_rate = cmd.val - self.ramping_rate = false - self.acks.burn_rate = false - self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + if not self.auto_lock then + cmd.val = math.floor(cmd.val * 10) / 10 -- round to 10ths place + if cmd.val > 0 and cmd.val <= self.sDB.mek_struct.max_burn then + self.commanded_burn_rate = cmd.val + self.ramping_rate = false + self.acks.burn_rate = false + self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + end end elseif cmd.key == PLC_S_DATA.RAMP_BURN_RATE then -- ramp to burn rate + if not self.auto_lock then + cmd.val = math.floor(cmd.val * 10) / 10 -- round to 10ths place + if cmd.val > 0 and cmd.val <= self.sDB.mek_struct.max_burn then + self.commanded_burn_rate = cmd.val + self.ramping_rate = true + self.acks.burn_rate = false + self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + end + end + elseif cmd.key == PLC_S_DATA.AUTO_BURN_RATE then + -- set automatic burn rate cmd.val = math.floor(cmd.val * 10) / 10 -- round to 10ths place if cmd.val > 0 and cmd.val <= self.sDB.mek_struct.max_burn then self.commanded_burn_rate = cmd.val - self.ramping_rate = true self.acks.burn_rate = false self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index c61a661..076036e 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -3,6 +3,8 @@ local rsio = require("scada-common.rsio") local types = require("scada-common.types") local util = require("scada-common.util") +local logic = require("supervisor.session.unitlogic") +local plc = require("supervisor.session.plc") local rsctl = require("supervisor.session.rsctl") ---@class reactor_control_unit @@ -17,6 +19,8 @@ local ALARM_STATE = types.ALARM_STATE local TRI_FAIL = types.TRI_FAIL local DUMPING_MODE = types.DUMPING_MODE +local PLC_S_CMDS = plc.PLC_S_CMDS + local IO = rsio.IO local FLOW_STABILITY_DELAY_MS = 15000 @@ -45,22 +49,6 @@ local AISTATE = { RING_BACK_TRIPPING = 5 } -local aistate_string = { - "INACTIVE", - "TRIPPING", - "TRIPPED", - "ACKED", - "RING_BACK", - "RING_BACK_TRIPPING" -} - --- check if an alarm is active (tripped or ack'd) ----@param alarm table alarm entry ----@return boolean active -local function is_active(alarm) - return alarm.state == AISTATE.TRIPPED or alarm.state == AISTATE.ACKED -end - ---@class alarm_def ---@field state ALARM_INT_STATE internal alarm state ---@field trip_time integer time (ms) when first tripped @@ -73,13 +61,20 @@ end ---@param num_boilers integer number of boilers expected ---@param num_turbines integer number of turbines expected function unit.new(for_reactor, num_boilers, num_turbines) + ---@class _unit_self local self = { r_id = for_reactor, plc_s = nil, ---@class plc_session_struct plc_i = nil, ---@class plc_session + num_boilers = num_boilers, + num_turbines = num_turbines, + types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE }, + defs = { FLOW_STABILITY_DELAY_MS = FLOW_STABILITY_DELAY_MS }, turbines = {}, boilers = {}, redstone = {}, + -- auto control + ramp_target_br10 = 0, -- state tracking deltas = {}, last_heartbeat = 0, @@ -89,9 +84,6 @@ function unit.new(for_reactor, num_boilers, num_turbines) damage_est_last = 0, waste_mode = WASTE_MODE.AUTO, status_text = { "UNKNOWN", "awaiting connection..." }, - -- auto control - group = 0, - limit = 0.0, -- logic for alarms had_reactor = false, start_ms = 0, @@ -138,6 +130,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- reactor PLCOnline = false, PLCHeartbeat = false, -- alternate true/false to blink, each time there is a keep_alive + AutoControl = false, ReactorSCRAM = false, ManualReactorSCRAM = false, AutoReactorSCRAM = false, @@ -177,6 +170,13 @@ function unit.new(for_reactor, num_boilers, num_turbines) ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE + }, + -- fields for facility control + ---@class unit_control + control = { + blade_count = 0, + br10 = 0, + lim_br10 = 0 } } } @@ -232,116 +232,13 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- get the delta t of a value ---@param key string value key ---@return number - local function _get_dt(key) + function self._get_dt(key) if self.deltas[key] then return self.deltas[key].dt else return 0.0 end end - --#endregion - - --#region redstone I/O - - local __rs_w = rs_rtu_io_ctl.digital_write - local __rs_r = rs_rtu_io_ctl.digital_read - - -- waste valves - local waste_pu = { open = function () __rs_w(IO.WASTE_PU, true) end, close = function () __rs_w(IO.WASTE_PU, false) end } - local waste_sna = { open = function () __rs_w(IO.WASTE_PO, true) end, close = function () __rs_w(IO.WASTE_PO, false) end } - local waste_po = { open = function () __rs_w(IO.WASTE_POPL, true) end, close = function () __rs_w(IO.WASTE_POPL, false) end } - local waste_sps = { open = function () __rs_w(IO.WASTE_AM, true) end, close = function () __rs_w(IO.WASTE_AM, false) end } - - --#endregion - - --#region task helpers - - -- update an alarm state given conditions - ---@param tripped boolean if the alarm condition is still active - ---@param alarm alarm_def alarm table - local function _update_alarm_state(tripped, alarm) - local int_state = alarm.state - local ext_state = self.db.alarm_states[alarm.id] - - -- alarm inactive - if int_state == AISTATE.INACTIVE then - if tripped then - alarm.trip_time = util.time_ms() - if alarm.hold_time > 0 then - alarm.state = AISTATE.TRIPPING - self.db.alarm_states[alarm.id] = ALARM_STATE.INACTIVE - else - alarm.state = AISTATE.TRIPPED - self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED - log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): TRIPPED [PRIORITY ", - types.alarm_prio_string[alarm.tier + 1],"]")) - end - else - alarm.trip_time = util.time_ms() - self.db.alarm_states[alarm.id] = ALARM_STATE.INACTIVE - end - -- alarm condition met, but not yet for required hold time - elseif (int_state == AISTATE.TRIPPING) or (int_state == AISTATE.RING_BACK_TRIPPING) then - if tripped then - local elapsed = util.time_ms() - alarm.trip_time - if elapsed > (alarm.hold_time * 1000) then - alarm.state = AISTATE.TRIPPED - self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED - log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): TRIPPED [PRIORITY ", - types.alarm_prio_string[alarm.tier + 1],"]")) - end - elseif int_state == AISTATE.RING_BACK_TRIPPING then - alarm.trip_time = 0 - alarm.state = AISTATE.RING_BACK - self.db.alarm_states[alarm.id] = ALARM_STATE.RING_BACK - else - alarm.trip_time = 0 - alarm.state = AISTATE.INACTIVE - self.db.alarm_states[alarm.id] = ALARM_STATE.INACTIVE - end - -- alarm tripped and alarming - elseif int_state == AISTATE.TRIPPED then - if tripped then - if ext_state == ALARM_STATE.ACKED then - -- was acked by coordinator - alarm.state = AISTATE.ACKED - end - else - alarm.state = AISTATE.RING_BACK - self.db.alarm_states[alarm.id] = ALARM_STATE.RING_BACK - end - -- alarm acknowledged but still tripped - elseif int_state == AISTATE.ACKED then - if not tripped then - alarm.state = AISTATE.RING_BACK - self.db.alarm_states[alarm.id] = ALARM_STATE.RING_BACK - end - -- alarm no longer tripped, operator must reset to clear - elseif int_state == AISTATE.RING_BACK then - if tripped then - alarm.trip_time = util.time_ms() - if alarm.hold_time > 0 then - alarm.state = AISTATE.RING_BACK_TRIPPING - else - alarm.state = AISTATE.TRIPPED - self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED - end - elseif ext_state == ALARM_STATE.INACTIVE then - -- was reset by coordinator - alarm.state = AISTATE.INACTIVE - alarm.trip_time = 0 - end - else - log.error(util.c("invalid alarm state for unit ", self.r_id, " alarm ", alarm.id), true) - end - - -- check for state change - if alarm.state ~= int_state then - local change_str = util.c(aistate_string[int_state + 1], " -> ", aistate_string[alarm.state + 1]) - log.debug(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): ", change_str)) - end - end - -- update all delta computations local function _dt__compute_all() - if self.plc_s ~= nil then + if self.plc_i ~= nil then local plc_db = self.plc_i.get_db() local last_update_s = plc_db.last_status_update / 1000.0 @@ -379,303 +276,16 @@ function unit.new(for_reactor, num_boilers, num_turbines) --#endregion - --#region alarms and annunciator + --#region redstone I/O - -- update the annunciator - local function _update_annunciator() - -- update deltas - _dt__compute_all() + local __rs_w = rs_rtu_io_ctl.digital_write + local __rs_r = rs_rtu_io_ctl.digital_read - -- variables for boiler, or reactor if no boilers used - local total_boil_rate = 0.0 - - ------------- - -- REACTOR -- - ------------- - - -- check PLC status - self.db.annunciator.PLCOnline = (self.plc_s ~= nil) and (self.plc_s.open) - - if self.plc_i ~= nil then - local plc_db = self.plc_i.get_db() - - -- record reactor start time (some alarms are delayed during reactor heatup) - if self.start_ms == 0 and plc_db.mek_status.status then - self.start_ms = util.time_ms() - elseif not plc_db.mek_status.status then - self.start_ms = 0 - end - - -- record reactor stats - self.plc_cache.active = plc_db.mek_status.status - self.plc_cache.ok = not (plc_db.rps_status.fault or plc_db.rps_status.sys_fail or plc_db.rps_status.force_dis) - self.plc_cache.rps_trip = plc_db.rps_tripped - self.plc_cache.rps_status = plc_db.rps_status - self.plc_cache.damage = plc_db.mek_status.damage - self.plc_cache.temp = plc_db.mek_status.temp - self.plc_cache.waste = plc_db.mek_status.waste_fill - - -- track damage - if plc_db.mek_status.damage > 0 then - if self.damage_start == 0 then - self.damage_start = util.time_s() - self.damage_initial = plc_db.mek_status.damage - end - else - self.damage_start = 0 - self.damage_initial = 0 - self.damage_last = 0 - self.damage_est_last = 0 - end - - -- heartbeat blink about every second - if self.last_heartbeat + 1000 < plc_db.last_status_update then - self.db.annunciator.PLCHeartbeat = not self.db.annunciator.PLCHeartbeat - self.last_heartbeat = plc_db.last_status_update - end - - -- update other annunciator fields - self.db.annunciator.ReactorSCRAM = plc_db.rps_tripped - self.db.annunciator.ManualReactorSCRAM = plc_db.rps_trip_cause == types.rps_status_t.manual - self.db.annunciator.AutoReactorSCRAM = plc_db.rps_trip_cause == types.rps_status_t.automatic - self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.no_cool) - self.db.annunciator.RCSFlowLow = plc_db.mek_status.ccool_fill < 0.75 or plc_db.mek_status.hcool_fill > 0.25 - self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > 1000 - self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > 100 - self.db.annunciator.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < -1.0 or plc_db.mek_status.fuel_fill <= 0.01 - self.db.annunciator.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 1.0 or plc_db.mek_status.waste_fill >= 0.85 - ---@todo this is dependent on setup, i.e. how much coolant is buffered and the turbine setup - self.db.annunciator.HighStartupRate = not plc_db.mek_status.status and plc_db.mek_status.burn_rate > 40 - - -- if no boilers, use reactor heating rate to check for boil rate mismatch - if num_boilers == 0 then - total_boil_rate = plc_db.mek_status.heating_rate - end - else - self.plc_cache.ok = false - end - - ------------- - -- BOILERS -- - ------------- - - -- clear boiler online flags - for i = 1, num_boilers do self.db.annunciator.BoilerOnline[i] = false end - - -- aggregated statistics - local boiler_steam_dt_sum = 0.0 - local boiler_water_dt_sum = 0.0 - - if num_boilers > 0 then - -- go through boilers for stats and online - for i = 1, #self.boilers do - local session = self.boilers[i] ---@type unit_session - local boiler = session.get_db() ---@type boilerv_session_db - - total_boil_rate = total_boil_rate + boiler.state.boil_rate - boiler_steam_dt_sum = _get_dt(DT_KEYS.BoilerSteam .. self.boilers[i].get_device_idx()) - boiler_water_dt_sum = _get_dt(DT_KEYS.BoilerWater .. self.boilers[i].get_device_idx()) - - self.db.annunciator.BoilerOnline[session.get_device_idx()] = true - end - - -- check heating rate low - if self.plc_s ~= nil and #self.boilers > 0 then - local r_db = self.plc_i.get_db() - - -- check for inactive boilers while reactor is active - for i = 1, #self.boilers do - local boiler = self.boilers[i] ---@type unit_session - local idx = boiler.get_device_idx() - local db = boiler.get_db() ---@type boilerv_session_db - - if r_db.mek_status.status then - self.db.annunciator.HeatingRateLow[idx] = db.state.boil_rate == 0 - else - self.db.annunciator.HeatingRateLow[idx] = false - end - end - end - else - boiler_steam_dt_sum = _get_dt(DT_KEYS.ReactorHCool) - boiler_water_dt_sum = _get_dt(DT_KEYS.ReactorCCool) - end - - --------------------------- - -- COOLANT FEED MISMATCH -- - --------------------------- - - -- check coolant feed mismatch if using boilers, otherwise calculate with reactor - local cfmismatch = false - - if num_boilers > 0 then - for i = 1, #self.boilers do - local boiler = self.boilers[i] ---@type unit_session - local idx = boiler.get_device_idx() - local db = boiler.get_db() ---@type boilerv_session_db - - local gaining_hc = _get_dt(DT_KEYS.BoilerHCool .. idx) > 10.0 or db.tanks.hcool_fill == 1 - - -- gaining heated coolant - cfmismatch = cfmismatch or gaining_hc - -- losing cooled coolant - cfmismatch = cfmismatch or _get_dt(DT_KEYS.BoilerCCool .. idx) < -10.0 or (gaining_hc and db.tanks.ccool_fill == 0) - end - elseif self.plc_s ~= nil then - local r_db = self.plc_i.get_db() - - local gaining_hc = _get_dt(DT_KEYS.ReactorHCool) > 10.0 or r_db.mek_status.hcool_fill == 1 - - -- gaining heated coolant (steam) - cfmismatch = cfmismatch or gaining_hc - -- losing cooled coolant (water) - cfmismatch = cfmismatch or _get_dt(DT_KEYS.ReactorCCool) < -10.0 or (gaining_hc and r_db.mek_status.ccool_fill == 0) - end - - self.db.annunciator.CoolantFeedMismatch = cfmismatch - - -------------- - -- TURBINES -- - -------------- - - -- clear turbine online flags - for i = 1, num_turbines do self.db.annunciator.TurbineOnline[i] = false end - - -- aggregated statistics - local total_flow_rate = 0 - local total_input_rate = 0 - local max_water_return_rate = 0 - - -- go through turbines for stats and online - for i = 1, #self.turbines do - local session = self.turbines[i] ---@type unit_session - local turbine = session.get_db() ---@type turbinev_session_db - - total_flow_rate = total_flow_rate + turbine.state.flow_rate - total_input_rate = total_input_rate + turbine.state.steam_input_rate - max_water_return_rate = max_water_return_rate + turbine.build.max_water_output - - self.db.annunciator.TurbineOnline[session.get_device_idx()] = true - end - - -- check for boil rate mismatch (either between reactor and turbine or boiler and turbine) - self.db.annunciator.BoilRateMismatch = math.abs(total_boil_rate - total_input_rate) > 4 - - -- check for steam feed mismatch and max return rate - local sfmismatch = math.abs(total_flow_rate - total_input_rate) > 10 - sfmismatch = sfmismatch or boiler_steam_dt_sum > 2.0 or boiler_water_dt_sum < -2.0 - self.db.annunciator.SteamFeedMismatch = sfmismatch - self.db.annunciator.MaxWaterReturnFeed = max_water_return_rate == total_flow_rate and total_flow_rate ~= 0 - - -- check if steam dumps are open - for i = 1, #self.turbines do - local turbine = self.turbines[i] ---@type unit_session - local db = turbine.get_db() ---@type turbinev_session_db - local idx = turbine.get_device_idx() - - if db.state.dumping_mode == DUMPING_MODE.IDLE then - self.db.annunciator.SteamDumpOpen[idx] = TRI_FAIL.OK - elseif db.state.dumping_mode == DUMPING_MODE.DUMPING_EXCESS then - self.db.annunciator.SteamDumpOpen[idx] = TRI_FAIL.PARTIAL - else - self.db.annunciator.SteamDumpOpen[idx] = TRI_FAIL.FULL - end - end - - -- check if turbines are at max speed but not keeping up - for i = 1, #self.turbines do - local turbine = self.turbines[i] ---@type unit_session - local db = turbine.get_db() ---@type turbinev_session_db - local idx = turbine.get_device_idx() - - self.db.annunciator.TurbineOverSpeed[idx] = (db.state.flow_rate == db.build.max_flow_rate) and (_get_dt(DT_KEYS.TurbineSteam .. idx) > 0.0) - end - - --[[ - Turbine Trip - a turbine trip is when the turbine stops, which means we are no longer receiving water and lose the ability to cool. - this can be identified by these conditions: - - the current flow rate is 0 mB/t and it should not be - - can initially catch this by detecting a 0 flow rate with a non-zero input rate, but eventually the steam will fill up - - can later identified by presence of steam in tank with a 0 flow rate - ]]-- - for i = 1, #self.turbines do - local turbine = self.turbines[i] ---@type unit_session - local db = turbine.get_db() ---@type turbinev_session_db - - local has_steam = db.state.steam_input_rate > 0 or db.tanks.steam_fill > 0.01 - self.db.annunciator.TurbineTrip[turbine.get_device_idx()] = has_steam and db.state.flow_rate == 0 - end - end - - -- evaluate alarm conditions - local function _update_alarms() - local annunc = self.db.annunciator - local plc_cache = self.plc_cache - - -- Containment Breach - -- lost plc with critical damage (rip plc, you will be missed) - _update_alarm_state((not plc_cache.ok) and (plc_cache.damage > 99), self.alarms.ContainmentBreach) - - -- Containment Radiation - ---@todo containment radiation alarm - _update_alarm_state(false, self.alarms.ContainmentRadiation) - - -- Reactor Lost - _update_alarm_state(self.had_reactor and self.plc_s == nil, self.alarms.ReactorLost) - - -- Critical Damage - _update_alarm_state(plc_cache.damage >= 100, self.alarms.CriticalDamage) - - -- Reactor Damage - _update_alarm_state(plc_cache.damage > 0, self.alarms.ReactorDamage) - - -- Over-Temperature - _update_alarm_state(plc_cache.temp >= 1200, self.alarms.ReactorOverTemp) - - -- High Temperature - _update_alarm_state(plc_cache.temp > 1150, self.alarms.ReactorHighTemp) - - -- Waste Leak - _update_alarm_state(plc_cache.waste >= 0.99, self.alarms.ReactorWasteLeak) - - -- High Waste - _update_alarm_state(plc_cache.waste > 0.50, self.alarms.ReactorHighWaste) - - -- RPS Transient (excludes timeouts and manual trips) - local rps_alarm = false - if plc_cache.rps_status.manual ~= nil then - if plc_cache.rps_trip then - for key, val in pairs(plc_cache.rps_status) do - if key ~= "manual" and key ~= "timeout" then rps_alarm = rps_alarm or val end - end - end - end - - _update_alarm_state(rps_alarm, self.alarms.RPSTransient) - - -- RCS Transient - local any_low = annunc.CoolantLevelLow - local any_over = false - for i = 1, #annunc.WaterLevelLow do any_low = any_low or annunc.WaterLevelLow[i] end - for i = 1, #annunc.TurbineOverSpeed do any_over = any_over or annunc.TurbineOverSpeed[i] end - - local rcs_trans = any_low or any_over or annunc.RCPTrip or annunc.RCSFlowLow or annunc.MaxWaterReturnFeed - - -- annunciator indicators for these states may not indicate a real issue when: - -- > flow is ramping up right after reactor start - -- > flow is ramping down after reactor shutdown - if (util.time_ms() - self.start_ms > FLOW_STABILITY_DELAY_MS) and plc_cache.active then - rcs_trans = rcs_trans or annunc.BoilRateMismatch or annunc.CoolantFeedMismatch or annunc.SteamFeedMismatch - end - - _update_alarm_state(rcs_trans, self.alarms.RCSTransient) - - -- Turbine Trip - local any_trip = false - for i = 1, #annunc.TurbineTrip do any_trip = any_trip or annunc.TurbineTrip[i] end - _update_alarm_state(any_trip, self.alarms.TurbineTrip) - end + -- waste valves + local waste_pu = { open = function () __rs_w(IO.WASTE_PU, true) end, close = function () __rs_w(IO.WASTE_PU, false) end } + local waste_sna = { open = function () __rs_w(IO.WASTE_PO, true) end, close = function () __rs_w(IO.WASTE_PO, false) end } + local waste_po = { open = function () __rs_w(IO.WASTE_POPL, true) end, close = function () __rs_w(IO.WASTE_POPL, false) end } + local waste_sps = { open = function () __rs_w(IO.WASTE_AM, true) end, close = function () __rs_w(IO.WASTE_AM, false) end } --#endregion @@ -755,6 +365,51 @@ function unit.new(for_reactor, num_boilers, num_turbines) util.filter_table(self.redstone, function (s) return s.get_session_id() ~= session end) end + -- AUTO CONTROL -- + + -- engage automatic control + function public.a_engage() + self.db.annunciator.AutoControl = true + if self.plc_i ~= nil then + self.plc_i.auto_lock(true) + end + end + + -- disengage automatic control + function public.a_disengage() + self.db.annunciator.AutoControl = false + if self.plc_i ~= nil then + self.plc_i.auto_lock(false) + end + end + + -- set the automatic burn rate based on the last set br10 + ---@param ramp boolean true to ramp to rate, false to set right away + function public.a_commit_br10(ramp) + if self.db.annunciator.AutoControl then + if self.plc_i ~= nil then + self.plc_i.auto_set_burn(self.db.control.br10 / 10, ramp) + + if ramp then self.ramp_target_br10 = self.db.control.br10 / 10 end + end + end + end + + -- check if ramping is complete (burn rate is same as target) + ---@return boolean complete + function public.a_ramp_complete() + if self.plc_i ~= nil then + return (math.floor(self.plc_i.get_db().mek_status.burn_rate * 10) == self.ramp_target_br10) or (self.ramp_target_br10 == 0) + else return false end + end + + -- perform an automatic SCRAM + function public.a_scram() + if self.plc_s ~= nil then + self.plc_s.in_queue.push_command(PLC_S_CMDS.ASCRAM) + end + end + -- UPDATE SESSION -- -- update (iterate) this unit @@ -763,6 +418,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) if self.plc_s ~= nil and not self.plc_s.open then self.plc_s = nil self.plc_i = nil + self.db.control.lim_br10 = 0 end -- unlink RTU unit sessions if they are closed @@ -770,125 +426,17 @@ function unit.new(for_reactor, num_boilers, num_turbines) _unlink_disconnected_units(self.turbines) _unlink_disconnected_units(self.redstone) + -- update deltas + _dt__compute_all() + -- update annunciator logic - _update_annunciator() + logic.update_annunciator(self) -- update alarm status - _update_alarms() + logic.update_alarms(self) - -- update status text (what the reactor doin?) - if is_active(self.alarms.ContainmentBreach) then - -- boom? or was boom disabled - if self.plc_i ~= nil and self.plc_i.get_rps().force_dis then - self.status_text = { "REACTOR FORCE DISABLED", "meltdown would have occured" } - else - self.status_text = { "CORE MELTDOWN", "reactor destroyed" } - end - elseif is_active(self.alarms.CriticalDamage) then - -- so much for it being a "routine turbin' trip"... - self.status_text = { "MELTDOWN IMMINENT", "evacuate facility immediately" } - elseif is_active(self.alarms.ReactorDamage) then - -- attempt to determine when a chance of a meltdown will occur - self.status_text[1] = "CONTAINMENT TAKING DAMAGE" - if self.plc_cache.damage >= 100 then - self.status_text[2] = "damage critical" - elseif (self.plc_cache.damage - self.damage_initial) > 0 then - if self.plc_cache.damage > self.damage_last then - self.damage_last = self.plc_cache.damage - local rate = (self.plc_cache.damage - self.damage_initial) / (util.time_s() - self.damage_start) - self.damage_est_last = (100 - self.plc_cache.damage) / rate - end - - self.status_text[2] = util.c("damage critical in ", util.sprintf("%.1f", self.damage_est_last), "s") - else - self.status_text[2] = "estimating time to critical..." - end - elseif is_active(self.alarms.ContainmentRadiation) then - self.status_text = { "RADIATION DETECTED", "radiation levels above normal" } - -- elseif is_active(self.alarms.RPSTransient) then - -- RPS status handled when checking reactor status - elseif is_active(self.alarms.RCSTransient) then - self.status_text = { "RCS TRANSIENT", "check coolant system" } - elseif is_active(self.alarms.ReactorOverTemp) then - self.status_text = { "CORE OVER TEMP", "reactor core temperature >=1200K" } - elseif is_active(self.alarms.ReactorWasteLeak) then - self.status_text = { "WASTE LEAK", "radioactive waste leak detected" } - elseif is_active(self.alarms.ReactorHighTemp) then - self.status_text = { "CORE TEMP HIGH", "reactor core temperature >1150K" } - elseif is_active(self.alarms.ReactorHighWaste) then - self.status_text = { "WASTE LEVEL HIGH", "waste accumulating in reactor" } - elseif is_active(self.alarms.TurbineTrip) then - self.status_text = { "TURBINE TRIP", "turbine stall occured" } - -- connection dependent states - elseif self.plc_i ~= nil then - local plc_db = self.plc_i.get_db() - if plc_db.mek_status.status then - self.status_text[1] = "ACTIVE" - - if self.db.annunciator.ReactorHighDeltaT then - self.status_text[2] = "core temperature rising" - elseif self.db.annunciator.ReactorTempHigh then - self.status_text[2] = "core temp high, system nominal" - elseif self.db.annunciator.FuelInputRateLow then - self.status_text[2] = "insufficient fuel input rate" - elseif self.db.annunciator.WasteLineOcclusion then - self.status_text[2] = "insufficient waste output rate" - elseif (util.time_ms() - self.start_ms) <= FLOW_STABILITY_DELAY_MS then - if num_turbines > 1 then - self.status_text[2] = "turbines spinning up" - else - self.status_text[2] = "turbine spinning up" - end - else - self.status_text[2] = "system nominal" - end - elseif plc_db.rps_tripped then - local cause = "unknown" - - if plc_db.rps_trip_cause == "ok" then - -- hmm... - elseif plc_db.rps_trip_cause == "dmg_crit" then - cause = "core damage critical" - elseif plc_db.rps_trip_cause == "high_temp" then - cause = "core temperature high" - elseif plc_db.rps_trip_cause == "no_coolant" then - cause = "insufficient coolant" - elseif plc_db.rps_trip_cause == "full_waste" then - cause = "excess waste" - elseif plc_db.rps_trip_cause == "heated_coolant_backup" then - cause = "excess heated coolant" - elseif plc_db.rps_trip_cause == "no_fuel" then - cause = "insufficient fuel" - elseif plc_db.rps_trip_cause == "fault" then - cause = "hardware fault" - elseif plc_db.rps_trip_cause == "timeout" then - cause = "connection timed out" - elseif plc_db.rps_trip_cause == "manual" then - cause = "manual operator SCRAM" - elseif plc_db.rps_trip_cause == "automatic" then - cause = "automated system SCRAM" - elseif plc_db.rps_trip_cause == "sys_fail" then - cause = "PLC system failure" - elseif plc_db.rps_trip_cause == "force_disabled" then - cause = "reactor force disabled" - end - - self.status_text = { "RPS SCRAM", cause } - else - self.status_text[1] = "IDLE" - - local temp = plc_db.mek_status.temp - if temp < 350 then - self.status_text[2] = "core cold" - elseif temp < 600 then - self.status_text[2] = "core warm" - else - self.status_text[2] = "core hot" - end - end - else - self.status_text = { "Reactor Off-line", "awaiting connection..." } - end + -- update status text + logic.update_status_text(self) end -- OPERATIONS -- @@ -950,23 +498,15 @@ function unit.new(for_reactor, num_boilers, num_turbines) end end - -- set the automatic control group of this unit - ---@param group integer group ID or 0 for independent - function public.set_group(group) - if group >= 0 and group <= 4 then - self.group = group - end - end - -- set the automatic control max burn rate for this unit ---@param limit number burn rate limit for auto control function public.set_burn_limit(limit) if limit >= 0 then - self.limit = limit + self.db.control.lim_br10 = math.floor(limit * 10) if self.plc_i ~= nil then if limit > self.plc_i.get_struct().max_burn then - self.limit = self.plc_i.get_struct().max_burn + self.db.control.lim_br10 = math.floor(self.plc_i.get_struct().max_burn * 10) end end end @@ -978,7 +518,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) function public.get_build() local build = {} - if self.plc_s ~= nil then + if self.plc_i ~= nil then build.reactor = self.plc_i.get_struct() end @@ -1000,10 +540,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- get reactor status function public.get_reactor_status() local status = {} - - if self.plc_s ~= nil then - local reactor = self.plc_i - status = { reactor.get_status(), reactor.get_rps(), reactor.get_general_status() } + if self.plc_i ~= nil then + status = { self.plc_i.get_status(), self.plc_i.get_rps(), self.plc_i.get_general_status() } end return status @@ -1048,6 +586,9 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- get the alarm states function public.get_alarms() return self.db.alarm_states end + -- get information required for automatic reactor control + function public.get_control_inf() return self.db.control end + -- get unit state (currently only waste mode) function public.get_state() return { self.status_text[1], self.status_text[2], self.waste_mode } diff --git a/supervisor/session/unitlogic.lua b/supervisor/session/unitlogic.lua new file mode 100644 index 0000000..86617ac --- /dev/null +++ b/supervisor/session/unitlogic.lua @@ -0,0 +1,547 @@ +local log = require("scada-common.log") +local types = require("scada-common.types") +local util = require("scada-common.util") + +local ALARM_STATE = types.ALARM_STATE + +local TRI_FAIL = types.TRI_FAIL +local DUMPING_MODE = types.DUMPING_MODE + +local aistate_string = { + "INACTIVE", + "TRIPPING", + "TRIPPED", + "ACKED", + "RING_BACK", + "RING_BACK_TRIPPING" +} + +---@class unit_logic_extension +local logic = {} + +-- update the annunciator +---@param self _unit_self +function logic.update_annunciator(self) + local DT_KEYS = self.types.DT_KEYS + local _get_dt = self._get_dt + + local num_boilers = self.num_boilers + local num_turbines = self.num_turbines + + -- variables for boiler, or reactor if no boilers used + local total_boil_rate = 0.0 + + ------------- + -- REACTOR -- + ------------- + + -- check PLC status + self.db.annunciator.PLCOnline = self.plc_i ~= nil + + if self.db.annunciator.PLCOnline then + local plc_db = self.plc_i.get_db() + + -- update auto control limit + if self.db.control.limit == 0.0 or self.db.control.limit > plc_db.mek_struct.max_burn then + self.db.control.limit = plc_db.mek_struct.max_burn + end + + -- record reactor start time (some alarms are delayed during reactor heatup) + if self.start_ms == 0 and plc_db.mek_status.status then + self.start_ms = util.time_ms() + elseif not plc_db.mek_status.status then + self.start_ms = 0 + end + + -- record reactor stats + self.plc_cache.active = plc_db.mek_status.status + self.plc_cache.ok = not (plc_db.rps_status.fault or plc_db.rps_status.sys_fail or plc_db.rps_status.force_dis) + self.plc_cache.rps_trip = plc_db.rps_tripped + self.plc_cache.rps_status = plc_db.rps_status + self.plc_cache.damage = plc_db.mek_status.damage + self.plc_cache.temp = plc_db.mek_status.temp + self.plc_cache.waste = plc_db.mek_status.waste_fill + + -- track damage + if plc_db.mek_status.damage > 0 then + if self.damage_start == 0 then + self.damage_start = util.time_s() + self.damage_initial = plc_db.mek_status.damage + end + else + self.damage_start = 0 + self.damage_initial = 0 + self.damage_last = 0 + self.damage_est_last = 0 + end + + -- heartbeat blink about every second + if self.last_heartbeat + 1000 < plc_db.last_status_update then + self.db.annunciator.PLCHeartbeat = not self.db.annunciator.PLCHeartbeat + self.last_heartbeat = plc_db.last_status_update + end + + -- update other annunciator fields + self.db.annunciator.ReactorSCRAM = plc_db.rps_tripped + self.db.annunciator.ManualReactorSCRAM = plc_db.rps_trip_cause == types.rps_status_t.manual + self.db.annunciator.AutoReactorSCRAM = plc_db.rps_trip_cause == types.rps_status_t.automatic + self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.no_cool) + self.db.annunciator.RCSFlowLow = plc_db.mek_status.ccool_fill < 0.75 or plc_db.mek_status.hcool_fill > 0.25 + self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > 1000 + self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > 100 + self.db.annunciator.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < -1.0 or plc_db.mek_status.fuel_fill <= 0.01 + self.db.annunciator.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 1.0 or plc_db.mek_status.waste_fill >= 0.85 + ---@todo this is dependent on setup, i.e. how much coolant is buffered and the turbine setup + self.db.annunciator.HighStartupRate = not plc_db.mek_status.status and plc_db.mek_status.burn_rate > 40 + + -- if no boilers, use reactor heating rate to check for boil rate mismatch + if num_boilers == 0 then + total_boil_rate = plc_db.mek_status.heating_rate + end + else + self.plc_cache.ok = false + end + + ------------- + -- BOILERS -- + ------------- + + -- clear boiler online flags + for i = 1, num_boilers do self.db.annunciator.BoilerOnline[i] = false end + + -- aggregated statistics + local boiler_steam_dt_sum = 0.0 + local boiler_water_dt_sum = 0.0 + + if num_boilers > 0 then + -- go through boilers for stats and online + for i = 1, #self.boilers do + local session = self.boilers[i] ---@type unit_session + local boiler = session.get_db() ---@type boilerv_session_db + + total_boil_rate = total_boil_rate + boiler.state.boil_rate + boiler_steam_dt_sum = _get_dt(DT_KEYS.BoilerSteam .. self.boilers[i].get_device_idx()) + boiler_water_dt_sum = _get_dt(DT_KEYS.BoilerWater .. self.boilers[i].get_device_idx()) + + self.db.annunciator.BoilerOnline[session.get_device_idx()] = true + end + + -- check heating rate low + if self.plc_i ~= nil and #self.boilers > 0 then + local r_db = self.plc_i.get_db() + + -- check for inactive boilers while reactor is active + for i = 1, #self.boilers do + local boiler = self.boilers[i] ---@type unit_session + local idx = boiler.get_device_idx() + local db = boiler.get_db() ---@type boilerv_session_db + + if r_db.mek_status.status then + self.db.annunciator.HeatingRateLow[idx] = db.state.boil_rate == 0 + else + self.db.annunciator.HeatingRateLow[idx] = false + end + end + end + else + boiler_steam_dt_sum = _get_dt(DT_KEYS.ReactorHCool) + boiler_water_dt_sum = _get_dt(DT_KEYS.ReactorCCool) + end + + --------------------------- + -- COOLANT FEED MISMATCH -- + --------------------------- + + -- check coolant feed mismatch if using boilers, otherwise calculate with reactor + local cfmismatch = false + + if num_boilers > 0 then + for i = 1, #self.boilers do + local boiler = self.boilers[i] ---@type unit_session + local idx = boiler.get_device_idx() + local db = boiler.get_db() ---@type boilerv_session_db + + local gaining_hc = _get_dt(DT_KEYS.BoilerHCool .. idx) > 10.0 or db.tanks.hcool_fill == 1 + + -- gaining heated coolant + cfmismatch = cfmismatch or gaining_hc + -- losing cooled coolant + cfmismatch = cfmismatch or _get_dt(DT_KEYS.BoilerCCool .. idx) < -10.0 or (gaining_hc and db.tanks.ccool_fill == 0) + end + elseif self.plc_i ~= nil then + local r_db = self.plc_i.get_db() + + local gaining_hc = _get_dt(DT_KEYS.ReactorHCool) > 10.0 or r_db.mek_status.hcool_fill == 1 + + -- gaining heated coolant (steam) + cfmismatch = cfmismatch or gaining_hc + -- losing cooled coolant (water) + cfmismatch = cfmismatch or _get_dt(DT_KEYS.ReactorCCool) < -10.0 or (gaining_hc and r_db.mek_status.ccool_fill == 0) + end + + self.db.annunciator.CoolantFeedMismatch = cfmismatch + + -------------- + -- TURBINES -- + -------------- + + -- clear turbine online flags + for i = 1, num_turbines do self.db.annunciator.TurbineOnline[i] = false end + + -- aggregated statistics + local total_flow_rate = 0 + local total_input_rate = 0 + local max_water_return_rate = 0 + + -- recompute blade count on the chance that it may have changed + self.db.blade_count = 0 + + -- go through turbines for stats and online + for i = 1, #self.turbines do + local session = self.turbines[i] ---@type unit_session + local turbine = session.get_db() ---@type turbinev_session_db + + total_flow_rate = total_flow_rate + turbine.state.flow_rate + total_input_rate = total_input_rate + turbine.state.steam_input_rate + max_water_return_rate = max_water_return_rate + turbine.build.max_water_output + self.db.blade_count = self.db.blade_count + turbine.build.blades + + self.db.annunciator.TurbineOnline[session.get_device_idx()] = true + end + + -- check for boil rate mismatch (either between reactor and turbine or boiler and turbine) + self.db.annunciator.BoilRateMismatch = math.abs(total_boil_rate - total_input_rate) > 4 + + -- check for steam feed mismatch and max return rate + local sfmismatch = math.abs(total_flow_rate - total_input_rate) > 10 + sfmismatch = sfmismatch or boiler_steam_dt_sum > 2.0 or boiler_water_dt_sum < -2.0 + self.db.annunciator.SteamFeedMismatch = sfmismatch + self.db.annunciator.MaxWaterReturnFeed = max_water_return_rate == total_flow_rate and total_flow_rate ~= 0 + + -- check if steam dumps are open + for i = 1, #self.turbines do + local turbine = self.turbines[i] ---@type unit_session + local db = turbine.get_db() ---@type turbinev_session_db + local idx = turbine.get_device_idx() + + if db.state.dumping_mode == DUMPING_MODE.IDLE then + self.db.annunciator.SteamDumpOpen[idx] = TRI_FAIL.OK + elseif db.state.dumping_mode == DUMPING_MODE.DUMPING_EXCESS then + self.db.annunciator.SteamDumpOpen[idx] = TRI_FAIL.PARTIAL + else + self.db.annunciator.SteamDumpOpen[idx] = TRI_FAIL.FULL + end + end + + -- check if turbines are at max speed but not keeping up + for i = 1, #self.turbines do + local turbine = self.turbines[i] ---@type unit_session + local db = turbine.get_db() ---@type turbinev_session_db + local idx = turbine.get_device_idx() + + self.db.annunciator.TurbineOverSpeed[idx] = (db.state.flow_rate == db.build.max_flow_rate) and (_get_dt(DT_KEYS.TurbineSteam .. idx) > 0.0) + end + + --[[ + Turbine Trip + a turbine trip is when the turbine stops, which means we are no longer receiving water and lose the ability to cool. + this can be identified by these conditions: + - the current flow rate is 0 mB/t and it should not be + - can initially catch this by detecting a 0 flow rate with a non-zero input rate, but eventually the steam will fill up + - can later identified by presence of steam in tank with a 0 flow rate + ]]-- + for i = 1, #self.turbines do + local turbine = self.turbines[i] ---@type unit_session + local db = turbine.get_db() ---@type turbinev_session_db + + local has_steam = db.state.steam_input_rate > 0 or db.tanks.steam_fill > 0.01 + self.db.annunciator.TurbineTrip[turbine.get_device_idx()] = has_steam and db.state.flow_rate == 0 + end +end + +-- update an alarm state given conditions +---@param self _unit_self unit instance +---@param tripped boolean if the alarm condition is still active +---@param alarm alarm_def alarm table +local function _update_alarm_state(self, tripped, alarm) + local AISTATE = self.types.AISTATE + local int_state = alarm.state + local ext_state = self.db.alarm_states[alarm.id] + + -- alarm inactive + if int_state == AISTATE.INACTIVE then + if tripped then + alarm.trip_time = util.time_ms() + if alarm.hold_time > 0 then + alarm.state = AISTATE.TRIPPING + self.db.alarm_states[alarm.id] = ALARM_STATE.INACTIVE + else + alarm.state = AISTATE.TRIPPED + self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED + log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): TRIPPED [PRIORITY ", + types.alarm_prio_string[alarm.tier + 1],"]")) + end + else + alarm.trip_time = util.time_ms() + self.db.alarm_states[alarm.id] = ALARM_STATE.INACTIVE + end + -- alarm condition met, but not yet for required hold time + elseif (int_state == AISTATE.TRIPPING) or (int_state == AISTATE.RING_BACK_TRIPPING) then + if tripped then + local elapsed = util.time_ms() - alarm.trip_time + if elapsed > (alarm.hold_time * 1000) then + alarm.state = AISTATE.TRIPPED + self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED + log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): TRIPPED [PRIORITY ", + types.alarm_prio_string[alarm.tier + 1],"]")) + end + elseif int_state == AISTATE.RING_BACK_TRIPPING then + alarm.trip_time = 0 + alarm.state = AISTATE.RING_BACK + self.db.alarm_states[alarm.id] = ALARM_STATE.RING_BACK + else + alarm.trip_time = 0 + alarm.state = AISTATE.INACTIVE + self.db.alarm_states[alarm.id] = ALARM_STATE.INACTIVE + end + -- alarm tripped and alarming + elseif int_state == AISTATE.TRIPPED then + if tripped then + if ext_state == ALARM_STATE.ACKED then + -- was acked by coordinator + alarm.state = AISTATE.ACKED + end + else + alarm.state = AISTATE.RING_BACK + self.db.alarm_states[alarm.id] = ALARM_STATE.RING_BACK + end + -- alarm acknowledged but still tripped + elseif int_state == AISTATE.ACKED then + if not tripped then + alarm.state = AISTATE.RING_BACK + self.db.alarm_states[alarm.id] = ALARM_STATE.RING_BACK + end + -- alarm no longer tripped, operator must reset to clear + elseif int_state == AISTATE.RING_BACK then + if tripped then + alarm.trip_time = util.time_ms() + if alarm.hold_time > 0 then + alarm.state = AISTATE.RING_BACK_TRIPPING + else + alarm.state = AISTATE.TRIPPED + self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED + end + elseif ext_state == ALARM_STATE.INACTIVE then + -- was reset by coordinator + alarm.state = AISTATE.INACTIVE + alarm.trip_time = 0 + end + else + log.error(util.c("invalid alarm state for unit ", self.r_id, " alarm ", alarm.id), true) + end + + -- check for state change + if alarm.state ~= int_state then + local change_str = util.c(aistate_string[int_state + 1], " -> ", aistate_string[alarm.state + 1]) + log.debug(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): ", change_str)) + end +end + +-- evaluate alarm conditions +---@param self _unit_self unit instance +function logic.update_alarms(self) + local annunc = self.db.annunciator + local plc_cache = self.plc_cache + + -- Containment Breach + -- lost plc with critical damage (rip plc, you will be missed) + _update_alarm_state(self, (not plc_cache.ok) and (plc_cache.damage > 99), self.alarms.ContainmentBreach) + + -- Containment Radiation + ---@todo containment radiation alarm + _update_alarm_state(self, false, self.alarms.ContainmentRadiation) + + -- Reactor Lost + _update_alarm_state(self, self.had_reactor and self.plc_i == nil, self.alarms.ReactorLost) + + -- Critical Damage + _update_alarm_state(self, plc_cache.damage >= 100, self.alarms.CriticalDamage) + + -- Reactor Damage + _update_alarm_state(self, plc_cache.damage > 0, self.alarms.ReactorDamage) + + -- Over-Temperature + _update_alarm_state(self, plc_cache.temp >= 1200, self.alarms.ReactorOverTemp) + + -- High Temperature + _update_alarm_state(self, plc_cache.temp > 1150, self.alarms.ReactorHighTemp) + + -- Waste Leak + _update_alarm_state(self, plc_cache.waste >= 0.99, self.alarms.ReactorWasteLeak) + + -- High Waste + _update_alarm_state(self, plc_cache.waste > 0.50, self.alarms.ReactorHighWaste) + + -- RPS Transient (excludes timeouts and manual trips) + local rps_alarm = false + if plc_cache.rps_status.manual ~= nil then + if plc_cache.rps_trip then + for key, val in pairs(plc_cache.rps_status) do + if key ~= "manual" and key ~= "timeout" then rps_alarm = rps_alarm or val end + end + end + end + + _update_alarm_state(self, rps_alarm, self.alarms.RPSTransient) + + -- RCS Transient + local any_low = annunc.CoolantLevelLow + local any_over = false + for i = 1, #annunc.WaterLevelLow do any_low = any_low or annunc.WaterLevelLow[i] end + for i = 1, #annunc.TurbineOverSpeed do any_over = any_over or annunc.TurbineOverSpeed[i] end + + local rcs_trans = any_low or any_over or annunc.RCPTrip or annunc.RCSFlowLow or annunc.MaxWaterReturnFeed + + -- annunciator indicators for these states may not indicate a real issue when: + -- > flow is ramping up right after reactor start + -- > flow is ramping down after reactor shutdown + if (util.time_ms() - self.start_ms > self.defs.FLOW_STABILITY_DELAY_MS) and plc_cache.active then + rcs_trans = rcs_trans or annunc.BoilRateMismatch or annunc.CoolantFeedMismatch or annunc.SteamFeedMismatch + end + + _update_alarm_state(self, rcs_trans, self.alarms.RCSTransient) + + -- Turbine Trip + local any_trip = false + for i = 1, #annunc.TurbineTrip do any_trip = any_trip or annunc.TurbineTrip[i] end + _update_alarm_state(self, any_trip, self.alarms.TurbineTrip) +end + +-- update the two unit status text messages +---@param self _unit_self unit instance +function logic.update_status_text(self) + local AISTATE = self.types.AISTATE + + -- check if an alarm is active (tripped or ack'd) + ---@param alarm table alarm entry + ---@return boolean active + local function is_active(alarm) + return alarm.state == AISTATE.TRIPPED or alarm.state == AISTATE.ACKED + end + + -- update status text (what the reactor doin?) + if is_active(self.alarms.ContainmentBreach) then + -- boom? or was boom disabled + if self.plc_i ~= nil and self.plc_i.get_rps().force_dis then + self.status_text = { "REACTOR FORCE DISABLED", "meltdown would have occured" } + else + self.status_text = { "CORE MELTDOWN", "reactor destroyed" } + end + elseif is_active(self.alarms.CriticalDamage) then + -- so much for it being a "routine turbin' trip"... + self.status_text = { "MELTDOWN IMMINENT", "evacuate facility immediately" } + elseif is_active(self.alarms.ReactorDamage) then + -- attempt to determine when a chance of a meltdown will occur + self.status_text[1] = "CONTAINMENT TAKING DAMAGE" + if self.plc_cache.damage >= 100 then + self.status_text[2] = "damage critical" + elseif (self.plc_cache.damage - self.damage_initial) > 0 then + if self.plc_cache.damage > self.damage_last then + self.damage_last = self.plc_cache.damage + local rate = (self.plc_cache.damage - self.damage_initial) / (util.time_s() - self.damage_start) + self.damage_est_last = (100 - self.plc_cache.damage) / rate + end + + self.status_text[2] = util.c("damage critical in ", util.sprintf("%.1f", self.damage_est_last), "s") + else + self.status_text[2] = "estimating time to critical..." + end + elseif is_active(self.alarms.ContainmentRadiation) then + self.status_text = { "RADIATION DETECTED", "radiation levels above normal" } + -- elseif is_active(self.alarms.RPSTransient) then + -- RPS status handled when checking reactor status + elseif is_active(self.alarms.RCSTransient) then + self.status_text = { "RCS TRANSIENT", "check coolant system" } + elseif is_active(self.alarms.ReactorOverTemp) then + self.status_text = { "CORE OVER TEMP", "reactor core temperature >=1200K" } + elseif is_active(self.alarms.ReactorWasteLeak) then + self.status_text = { "WASTE LEAK", "radioactive waste leak detected" } + elseif is_active(self.alarms.ReactorHighTemp) then + self.status_text = { "CORE TEMP HIGH", "reactor core temperature >1150K" } + elseif is_active(self.alarms.ReactorHighWaste) then + self.status_text = { "WASTE LEVEL HIGH", "waste accumulating in reactor" } + elseif is_active(self.alarms.TurbineTrip) then + self.status_text = { "TURBINE TRIP", "turbine stall occured" } + -- connection dependent states + elseif self.plc_i ~= nil then + local plc_db = self.plc_i.get_db() + if plc_db.mek_status.status then + self.status_text[1] = "ACTIVE" + + if self.db.annunciator.ReactorHighDeltaT then + self.status_text[2] = "core temperature rising" + elseif self.db.annunciator.ReactorTempHigh then + self.status_text[2] = "core temp high, system nominal" + elseif self.db.annunciator.FuelInputRateLow then + self.status_text[2] = "insufficient fuel input rate" + elseif self.db.annunciator.WasteLineOcclusion then + self.status_text[2] = "insufficient waste output rate" + elseif (util.time_ms() - self.start_ms) <= self.defs.FLOW_STABILITY_DELAY_MS then + if self.num_turbines > 1 then + self.status_text[2] = "turbines spinning up" + else + self.status_text[2] = "turbine spinning up" + end + else + self.status_text[2] = "system nominal" + end + elseif plc_db.rps_tripped then + local cause = "unknown" + + if plc_db.rps_trip_cause == "ok" then + -- hmm... + elseif plc_db.rps_trip_cause == "dmg_crit" then + cause = "core damage critical" + elseif plc_db.rps_trip_cause == "high_temp" then + cause = "core temperature high" + elseif plc_db.rps_trip_cause == "no_coolant" then + cause = "insufficient coolant" + elseif plc_db.rps_trip_cause == "full_waste" then + cause = "excess waste" + elseif plc_db.rps_trip_cause == "heated_coolant_backup" then + cause = "excess heated coolant" + elseif plc_db.rps_trip_cause == "no_fuel" then + cause = "insufficient fuel" + elseif plc_db.rps_trip_cause == "fault" then + cause = "hardware fault" + elseif plc_db.rps_trip_cause == "timeout" then + cause = "connection timed out" + elseif plc_db.rps_trip_cause == "manual" then + cause = "manual operator SCRAM" + elseif plc_db.rps_trip_cause == "automatic" then + cause = "automated system SCRAM" + elseif plc_db.rps_trip_cause == "sys_fail" then + cause = "PLC system failure" + elseif plc_db.rps_trip_cause == "force_disabled" then + cause = "reactor force disabled" + end + + self.status_text = { "RPS SCRAM", cause } + else + self.status_text[1] = "IDLE" + + local temp = plc_db.mek_status.temp + if temp < 350 then + self.status_text[2] = "core cold" + elseif temp < 600 then + self.status_text[2] = "core warm" + else + self.status_text[2] = "core hot" + end + end + else + self.status_text = { "Reactor Off-line", "awaiting connection..." } + end +end + +return logic diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 2c340dc..e408ffb 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.9.2" +local SUPERVISOR_VERSION = "beta-v0.9.3" local print = util.print local println = util.println From a1c1125d5409f6cfaca06f65676020ef14ff0757 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 3 Jan 2023 17:03:20 -0500 Subject: [PATCH 470/587] fixed bug with automatic limit update --- supervisor/session/unitlogic.lua | 4 ++-- supervisor/startup.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/supervisor/session/unitlogic.lua b/supervisor/session/unitlogic.lua index 86617ac..0531038 100644 --- a/supervisor/session/unitlogic.lua +++ b/supervisor/session/unitlogic.lua @@ -42,8 +42,8 @@ function logic.update_annunciator(self) local plc_db = self.plc_i.get_db() -- update auto control limit - if self.db.control.limit == 0.0 or self.db.control.limit > plc_db.mek_struct.max_burn then - self.db.control.limit = plc_db.mek_struct.max_burn + if (self.db.control.lim_br10 == 0) or ((self.db.control.lim_br10 / 10) > plc_db.mek_struct.max_burn) then + self.db.control.lim_br10 = math.floor(plc_db.mek_struct.max_burn * 10) end -- record reactor start time (some alarms are delayed during reactor heatup) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index e408ffb..a07c1e3 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.9.3" +local SUPERVISOR_VERSION = "beta-v0.9.4" local print = util.print local println = util.println From b7d4bc3a5b6ecee450aa7a24fe7df80f2b4c7c5c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 13 Jan 2023 14:03:47 -0500 Subject: [PATCH 471/587] #142 fixed bug with setting burn rates --- coordinator/iocontrol.lua | 5 +++-- coordinator/process.lua | 19 +++++++++++-------- coordinator/startup.lua | 2 +- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index c66d8d5..31fb9aa 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -22,7 +22,7 @@ local io = {} ---@diagnostic disable-next-line: redefined-local function iocontrol.init(conf, comms) -- pass IO control here since it can't be require'd due to a require loop - process.init(iocontrol, comms) + process.init(io, comms) io.facility = { auto_active = false, @@ -71,7 +71,8 @@ function iocontrol.init(conf, comms) set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate set_waste = function (mode) process.set_waste(i, mode) end, ---@param mode integer waste processing mode - set_group = function (g) process.set_group(i, g) end, ---@param g integer|0 group ID or 0 + set_group = function (grp) process.set_group(i, grp) end, ---@param grp integer|0 group ID or 0 + set_limit = function (lim) process.set_limit(i, lim) end, ---@param lim number burn rate limit start_ack = function (success) end, ---@param success boolean scram_ack = function (success) end, ---@param success boolean diff --git a/coordinator/process.lua b/coordinator/process.lua index b594306..d404e06 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -48,17 +48,12 @@ function process.reset_rps(id) log.debug(util.c("UNIT[", id, "]: RESET RPS")) end --- set burn rate or burn limit if part of a group +-- set burn rate ---@param id integer unit ID ---@param rate number burn rate function process.set_rate(id, rate) - if self.io.units[id].group == 0 then - self.comms.send_command(UNIT_COMMANDS.SET_BURN, id, rate) - log.debug(util.c("UNIT[", id, "]: SET BURN = ", rate)) - else - self.comms.send_command(UNIT_COMMANDS.SET_LIMIT, id, rate) - log.debug(util.c("UNIT[", id, "]: SET LIMIT = ", rate)) - end + self.comms.send_command(UNIT_COMMANDS.SET_BURN, id, rate) + log.debug(util.c("UNIT[", id, "]: SET BURN = ", rate)) end -- set waste mode @@ -100,6 +95,14 @@ function process.set_group(unit_id, group_id) log.debug(util.c("UNIT[", unit_id, "]: SET GROUP ", group_id)) end +-- set the burn rate limit +---@param id integer unit ID +---@param limit number burn rate limit +function process.set_limit(id, limit) + self.comms.send_command(UNIT_COMMANDS.SET_LIMIT, id, limit) + log.debug(util.c("UNIT[", id, "]: SET LIMIT = ", limit)) +end + -------------------------- -- SUPERVISOR RESPONSES -- -------------------------- diff --git a/coordinator/startup.lua b/coordinator/startup.lua index c5072d1..dcba605 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.6" +local COORDINATOR_VERSION = "beta-v0.8.7" local print = util.print local println = util.println From 4145949ba7b6f0db33f788147d69554252db2b83 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 15 Jan 2023 13:11:46 -0500 Subject: [PATCH 472/587] #141 setting unit limits with coordinator --- coordinator/iocontrol.lua | 27 +++++++- coordinator/startup.lua | 2 +- coordinator/ui/components/processctl.lua | 65 +++++++++++++++++++ coordinator/ui/components/unit_detail.lua | 22 +++---- coordinator/ui/layout/main_view.lua | 55 +++++++++------- .../elements/controls/spinbox_numeric.lua | 24 ++++--- supervisor/session/coordinator.lua | 8 ++- supervisor/session/unit.lua | 8 ++- supervisor/startup.lua | 2 +- 9 files changed, 156 insertions(+), 57 deletions(-) create mode 100644 coordinator/ui/components/processctl.lua diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 31fb9aa..f37f9d2 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -62,7 +62,6 @@ function iocontrol.init(conf, comms) waste_control = 0, a_group = 0, -- auto control group - a_limit = 0.0, -- auto control limit start = function () process.start(i) end, scram = function () process.scram(i) end, @@ -334,7 +333,7 @@ function iocontrol.update_unit_statuses(statuses) local unit = io.units[i] ---@type ioctl_entry local status = statuses[i] - if type(status) ~= "table" or #status ~= 5 then + if type(status) ~= "table" or #status ~= 6 then log.debug(log_header .. "invalid status entry in unit statuses (not a table or invalid length)") return false end @@ -343,6 +342,11 @@ function iocontrol.update_unit_statuses(statuses) local reactor_status = status[1] + if type(reactor_status) ~= "table" then + reactor_status = {} + log.debug(log_header .. "reactor status not a table") + end + if #reactor_status == 0 then unit.reactor_ps.publish("computed_status", 1) -- disconnected elseif #reactor_status == 3 then @@ -512,6 +516,11 @@ function iocontrol.update_unit_statuses(statuses) local annunciator = status[3] ---@type annunciator + if type(annunciator) ~= "table" then + annunciator = {} + log.debug(log_header .. "annunciator state not a table") + end + for key, val in pairs(annunciator) do if key == "TurbineTrip" then -- split up turbine trip table for all turbines and a general OR combination @@ -580,6 +589,20 @@ function iocontrol.update_unit_statuses(statuses) else log.debug(log_header .. "unit state not a table") end + + -- auto control state fields + + local auto_ctl_state = status[6] + + if type(auto_ctl_state) == "table" then + if #auto_ctl_state == 1 then + unit.reactor_ps.publish("burn_limit", auto_ctl_state[1]) + else + log.debug(log_header .. "auto control state length mismatch") + end + else + log.debug(log_header .. "auto control state not a table") + end end -- update alarm sounder diff --git a/coordinator/startup.lua b/coordinator/startup.lua index dcba605..1f1a829 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.7" +local COORDINATOR_VERSION = "beta-v0.8.8" local print = util.print local println = util.println diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua new file mode 100644 index 0000000..d8dece4 --- /dev/null +++ b/coordinator/ui/components/processctl.lua @@ -0,0 +1,65 @@ +local util = require("scada-common.util") + +local iocontrol = require("coordinator.iocontrol") + +local style = require("coordinator.ui.style") + +local core = require("graphics.core") + +local Div = require("graphics.elements.div") +local Rectangle = require("graphics.elements.rectangle") +local TextBox = require("graphics.elements.textbox") + +local AlarmLight = require("graphics.elements.indicators.alight") +local CoreMap = require("graphics.elements.indicators.coremap") +local DataIndicator = require("graphics.elements.indicators.data") +local IndicatorLight = require("graphics.elements.indicators.light") +local TriIndicatorLight = require("graphics.elements.indicators.trilight") +local VerticalBar = require("graphics.elements.indicators.vbar") + +local HazardButton = require("graphics.elements.controls.hazard_button") +local MultiButton = require("graphics.elements.controls.multi_button") +local PushButton = require("graphics.elements.controls.push_button") +local RadioButton = require("graphics.elements.controls.radio_button") +local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") + +local TEXT_ALIGN = core.graphics.TEXT_ALIGN + +local cpair = core.graphics.cpair +local border = core.graphics.border + +-- new process control view +---@param root graphics_element parent +---@param x integer top left x +---@param y integer top left y +local function new_view(root, x, y) + local facility = iocontrol.get_db().facility + local units = iocontrol.get_db().units + + local bw_fg_bg = cpair(colors.black, colors.white) + + local proc = Div{parent=root,width=60,height=24,x=x,y=y} + + local limits = Div{parent=proc,width=40,height=24,x=30,y=1} + + for i = 1, facility.num_units do + local unit = units[i] ---@type ioctl_entry + + local _y = ((i - 1) * 4) + 1 + + TextBox{parent=limits,x=1,y=_y+1,text="Unit "..i} + + local lim_ctl = Div{parent=limits,x=8,y=_y,width=20,height=3,fg_bg=cpair(colors.gray,colors.white)} + local burn_rate = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,max=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg} + + unit.reactor_ps.subscribe("max_burn", burn_rate.set_max) + unit.reactor_ps.subscribe("burn_limit", burn_rate.set_value) + + TextBox{parent=lim_ctl,x=9,y=2,text="mB/t"} + + local set_burn = function () unit.set_limit(burn_rate.get_value()) end + PushButton{parent=lim_ctl,x=14,y=2,text="SAVE",min_width=6,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} + end +end + +return new_view diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 119071e..a35bf9a 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -451,31 +451,25 @@ local function init(parent, id) TextBox{parent=main,text="AUTO CTRL",fg_bg=cpair(colors.black,colors.purple),alignment=TEXT_ALIGN.CENTER,width=13,height=1,x=32,y=36} local auto_ctl = Rectangle{parent=main,border=border(1,colors.purple,true),thin=true,width=13,height=15,x=32,y=37} - local auto_div = Div{parent=auto_ctl,width=13,height=15,x=2,y=1} + local auto_div = Div{parent=auto_ctl,width=13,height=15,x=1,y=1} - local ctl_opts = { "Manual", "Group A", "Group B", "Group C", "Group D" } + local ctl_opts = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } - local a_prm, a_stb - local test = function (val) - a_prm.update(val ~= 1) - a_stb.update(val ~= 1) - end - - RadioButton{parent=auto_div,options=ctl_opts,callback=test,radio_colors=cpair(colors.blue,colors.white),radio_bg=colors.gray} + RadioButton{parent=auto_div,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.blue,colors.white),radio_bg=colors.gray} auto_div.line_break() - PushButton{parent=auto_div,text="SET",x=3,min_width=5,fg_bg=cpair(colors.black,colors.white),active_fg_bg=cpair(colors.white,colors.gray),callback=function()end} + PushButton{parent=auto_div,text="SET",x=4,min_width=5,fg_bg=cpair(colors.black,colors.white),active_fg_bg=cpair(colors.white,colors.gray),callback=function()end} auto_div.line_break() - TextBox{parent=auto_div,text="CRD Group",height=1,width=9,fg_bg=style.label} - local auto_grp = TextBox{parent=auto_div,text="Manual",height=1,width=9,fg_bg=bw_fg_bg} + TextBox{parent=auto_div,text="Prio. Group",height=1,width=11,fg_bg=style.label} + local auto_grp = TextBox{parent=auto_div,text="Manual",height=1,width=11,fg_bg=bw_fg_bg} auto_div.line_break() - a_prm = IndicatorLight{parent=auto_div,label="Primary",colors=cpair(colors.green,colors.gray)} - a_stb = IndicatorLight{parent=auto_div,label="Standby",colors=cpair(colors.white,colors.gray)} + local a_prm = IndicatorLight{parent=auto_div,label="Active",x=2,colors=cpair(colors.green,colors.gray)} + local a_stb = IndicatorLight{parent=auto_div,label="Standby",x=2,colors=cpair(colors.white,colors.gray)} return main end diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index 7c0f05c..8609f73 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -2,19 +2,22 @@ -- Main SCADA Coordinator GUI -- +local util = require("scada-common.util") + local iocontrol = require("coordinator.iocontrol") local sounder = require("coordinator.sounder") -local util = require("scada-common.util") local style = require("coordinator.ui.style") +local imatrix = require("coordinator.ui.components.imatrix") +local process_ctl = require("coordinator.ui.components.processctl") local unit_overview = require("coordinator.ui.components.unit_overview") -local imatrix = require("coordinator.ui.components.imatrix") local core = require("graphics.core") local ColorMap = require("graphics.elements.colormap") local DisplayBox = require("graphics.elements.displaybox") +local Div = require("graphics.elements.div") local TextBox = require("graphics.elements.textbox") local PushButton = require("graphics.elements.controls.push_button") @@ -75,34 +78,38 @@ local function init(monitor) cnc_y_start = cnc_y_start + 2 + local process = process_ctl(main, 2, cnc_y_start) + -- testing ---@fixme remove test code ColorMap{parent=main,x=2,y=(main.height()-1)} - PushButton{parent=main,x=2,y=cnc_y_start,text="TEST 1",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_1} - PushButton{parent=main,x=2,text="TEST 2",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_2} - PushButton{parent=main,x=2,text="TEST 3",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_3} - PushButton{parent=main,x=2,text="TEST 4",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_4} - PushButton{parent=main,x=2,text="TEST 5",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_5} - PushButton{parent=main,x=2,text="TEST 6",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_6} - PushButton{parent=main,x=2,text="TEST 7",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_7} - PushButton{parent=main,x=2,text="TEST 8",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_8} - PushButton{parent=main,x=2,text="STOP",min_width=8,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.stop} - PushButton{parent=main,x=2,text="PSCALE",min_width=8,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_power_scale} + local audio = Div{parent=main,width=34,height=15,x=95,y=cnc_y_start} - SwitchButton{parent=main,x=12,y=cnc_y_start,text="CONTAINMENT BREACH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_breach} - SwitchButton{parent=main,x=12,text="CONTAINMENT RADIATION",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rad} - SwitchButton{parent=main,x=12,text="REACTOR LOST",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_lost} - SwitchButton{parent=main,x=12,text="CRITICAL DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_crit} - SwitchButton{parent=main,x=12,text="REACTOR DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_dmg} - SwitchButton{parent=main,x=12,text="REACTOR OVER TEMP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_overtemp} - SwitchButton{parent=main,x=12,text="REACTOR HIGH TEMP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_hightemp} - SwitchButton{parent=main,x=12,text="REACTOR WASTE LEAK",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_wasteleak} - SwitchButton{parent=main,x=12,text="REACTOR WASTE HIGH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_highwaste} - SwitchButton{parent=main,x=12,text="RPS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rps} - SwitchButton{parent=main,x=12,text="RCS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rcs} - SwitchButton{parent=main,x=12,text="TURBINE TRIP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_turbinet} + PushButton{parent=audio,x=1,y=1,text="TEST 1",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_1} + PushButton{parent=audio,x=1,text="TEST 2",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_2} + PushButton{parent=audio,x=1,text="TEST 3",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_3} + PushButton{parent=audio,x=1,text="TEST 4",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_4} + PushButton{parent=audio,x=1,text="TEST 5",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_5} + PushButton{parent=audio,x=1,text="TEST 6",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_6} + PushButton{parent=audio,x=1,text="TEST 7",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_7} + PushButton{parent=audio,x=1,text="TEST 8",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_8} + PushButton{parent=audio,x=1,text="STOP",min_width=8,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.stop} + PushButton{parent=audio,x=1,text="PSCALE",min_width=8,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_power_scale} + + SwitchButton{parent=audio,x=11,y=1,text="CONTAINMENT BREACH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_breach} + SwitchButton{parent=audio,x=11,text="CONTAINMENT RADIATION",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rad} + SwitchButton{parent=audio,x=11,text="REACTOR LOST",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_lost} + SwitchButton{parent=audio,x=11,text="CRITICAL DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_crit} + SwitchButton{parent=audio,x=11,text="REACTOR DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_dmg} + SwitchButton{parent=audio,x=11,text="REACTOR OVER TEMP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_overtemp} + SwitchButton{parent=audio,x=11,text="REACTOR HIGH TEMP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_hightemp} + SwitchButton{parent=audio,x=11,text="REACTOR WASTE LEAK",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_wasteleak} + SwitchButton{parent=audio,x=11,text="REACTOR WASTE HIGH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_highwaste} + SwitchButton{parent=audio,x=11,text="RPS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rps} + SwitchButton{parent=audio,x=11,text="RCS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rcs} + SwitchButton{parent=audio,x=11,text="TURBINE TRIP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_turbinet} local imatrix_1 = imatrix(main, 131, cnc_y_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1]) diff --git a/graphics/elements/controls/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua index bfb1552..f76ad21 100644 --- a/graphics/elements/controls/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -84,26 +84,22 @@ local function spinbox(args) -- print out the current value local function show_num() -- enforce limits - -- min 0 - if e.value < 0 then - for i = 1, #digits do digits[i] = 0 end - e.value = 0 - -- min configured - elseif (type(args.min) == "number") and (e.value < args.min) then - -- cap at min + if (type(args.min) == "number") and (e.value < args.min) then e.value = args.min set_digits() + elseif e.value < 0 then + e.value = 0 + set_digits() else - -- max printable if string.len(util.sprintf(fmt, e.value)) > args.width then - -- max out to all 9s + -- max printable exceeded, so max out to all 9s for i = 1, #digits do digits[i] = 9 end update_value() - -- max configured elseif (type(args.max) == "number") and (e.value > args.max) then - -- cap at max e.value = args.max set_digits() + else + set_digits() end end @@ -142,8 +138,6 @@ local function spinbox(args) ---@param val number number to show function e.set_value(val) e.value = val - - set_digits() show_num() end @@ -163,6 +157,10 @@ local function spinbox(args) show_num() end + -- default to zero, init digits table + e.value = 0 + set_digits() + return e.get() end diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 2e5fdb0..860fa1b 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -145,12 +145,18 @@ function coordinator.new_session(id, in_queue, out_queue, facility) for i = 1, #self.units do local unit = self.units[i] ---@type reactor_unit + + local auto_ctl = { + unit.get_control_inf().lim_br10 / 10 + } + status[unit.get_id()] = { unit.get_reactor_status(), unit.get_rtu_statuses(), unit.get_annunciator(), unit.get_alarms(), - unit.get_state() + unit.get_state(), + auto_ctl } end diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 076036e..073c1f2 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -301,6 +301,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) local public = {} -- ADD/LINK DEVICES -- + --#region -- link the PLC ---@param plc_session plc_session_struct @@ -365,7 +366,10 @@ function unit.new(for_reactor, num_boilers, num_turbines) util.filter_table(self.redstone, function (s) return s.get_session_id() ~= session end) end + --#endregion + -- AUTO CONTROL -- + --#region -- engage automatic control function public.a_engage() @@ -410,6 +414,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) end end + --#endregion + -- UPDATE SESSION -- -- update (iterate) this unit @@ -501,7 +507,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- set the automatic control max burn rate for this unit ---@param limit number burn rate limit for auto control function public.set_burn_limit(limit) - if limit >= 0 then + if limit > 0 then self.db.control.lim_br10 = math.floor(limit * 10) if self.plc_i ~= nil then diff --git a/supervisor/startup.lua b/supervisor/startup.lua index a07c1e3..f05eaf8 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.9.4" +local SUPERVISOR_VERSION = "beta-v0.9.5" local print = util.print local println = util.println From 8abac3fdcb2c26873ed6f2228ef58019cb601ae9 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 23 Jan 2023 15:10:41 -0500 Subject: [PATCH 473/587] refactoring and adjusted spinbox and hazard button elements --- coordinator/coordinator.lua | 12 ++--- coordinator/iocontrol.lua | 13 ++--- coordinator/sounder.lua | 2 +- coordinator/startup.lua | 2 +- coordinator/ui/components/imatrix.lua | 2 +- coordinator/ui/components/unit_detail.lua | 4 +- coordinator/ui/components/unit_overview.lua | 2 +- graphics/elements/controls/hazard_button.lua | 7 +-- .../elements/controls/spinbox_numeric.lua | 52 ++++++++++++++----- 9 files changed, 60 insertions(+), 36 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 13430e0..227b020 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -426,9 +426,9 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa if packet.length == 3 then local cmd = packet.data[1] local unit_id = packet.data[2] - local ack = packet.data[3] + local ack = packet.data[3] == true - local unit = iocontrol.get_db().units[unit_id] ---@type ioctl_entry + local unit = iocontrol.get_db().units[unit_id] ---@type ioctl_unit if unit ~= nil then if cmd == UNIT_COMMANDS.SCRAM then @@ -444,14 +444,12 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa elseif cmd == UNIT_COMMANDS.ACK_ALL_ALARMS then unit.ack_alarms_ack(ack) elseif cmd == UNIT_COMMANDS.SET_GROUP then - process.sv_assign(unit_id, ack) - elseif cmd == UNIT_COMMANDS.SET_LIMIT then - process.sv_limit(unit_id, ack) + ---@todo how is this going to be handled? else - log.debug(util.c("received command ack with unknown command ", cmd)) + log.debug(util.c("received unit command ack with unknown command ", cmd)) end else - log.debug(util.c("received command ack with unknown unit ", unit_id)) + log.debug(util.c("received unit command ack with unknown unit ", unit_id)) end else log.debug("SCADA_CRDN unit command ack packet length mismatch") diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index f37f9d2..0d0aa35 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -21,9 +21,7 @@ local io = {} ---@param comms coord_comms comms reference ---@diagnostic disable-next-line: redefined-local function iocontrol.init(conf, comms) - -- pass IO control here since it can't be require'd due to a require loop - process.init(io, comms) - + ---@class ioctl_facility io.facility = { auto_active = false, scram = false, @@ -50,7 +48,7 @@ function iocontrol.init(conf, comms) local function ack(alarm) process.ack_alarm(i, alarm) end local function reset(alarm) process.reset_alarm(i, alarm) end - ---@class ioctl_entry + ---@class ioctl_unit local entry = { unit_id = i, ---@type integer @@ -140,6 +138,9 @@ function iocontrol.init(conf, comms) table.insert(io.units, entry) end + + -- pass IO control here since it can't be require'd due to a require loop + process.init(io, comms) end -- populate facility structure builds @@ -180,7 +181,7 @@ end function iocontrol.record_unit_builds(builds) -- note: if not all units and RTUs are connected, some will be nil for id, build in pairs(builds) do - local unit = io.units[id] ---@type ioctl_entry + local unit = io.units[id] ---@type ioctl_unit if type(build) ~= "table" then log.error(util.c("corrupted unit builds provided, unit ", id, " not a table")) @@ -330,7 +331,7 @@ function iocontrol.update_unit_statuses(statuses) -- get all unit statuses for i = 1, #statuses do local log_header = util.c("iocontrol.update_unit_statuses[unit ", i, "]: ") - local unit = io.units[i] ---@type ioctl_entry + local unit = io.units[i] ---@type ioctl_unit local status = statuses[i] if type(status) ~= "table" or #status ~= 6 then diff --git a/coordinator/sounder.lua b/coordinator/sounder.lua index 594d6c3..57174fb 100644 --- a/coordinator/sounder.lua +++ b/coordinator/sounder.lua @@ -306,7 +306,7 @@ function sounder.eval(units) if units ~= nil then -- check all alarms for all units for i = 1, #units do - local unit = units[i] ---@type ioctl_entry + local unit = units[i] ---@type ioctl_unit for id = 1, #unit.alarms do alarms[id] = alarms[id] or (unit.alarms[id] == ALARM_STATE.TRIPPED) end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 1f1a829..bda6ba0 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.8" +local COORDINATOR_VERSION = "beta-v0.8.9" local print = util.print local println = util.println diff --git a/coordinator/ui/components/imatrix.lua b/coordinator/ui/components/imatrix.lua index abd81b0..627ea94 100644 --- a/coordinator/ui/components/imatrix.lua +++ b/coordinator/ui/components/imatrix.lua @@ -38,7 +38,7 @@ local function new_view(root, x, y, data, ps, id) local label_fg_bg = cpair(colors.gray, colors.lightGray) local lu_col = cpair(colors.gray, colors.gray) - local status = StateIndicator{parent=rect,x=11,y=1,states=style.imatrix.states,value=1,min_width=12} + local status = StateIndicator{parent=rect,x=10,y=1,states=style.imatrix.states,value=1,min_width=14} local energy = PowerIndicator{parent=rect,x=7,y=3,lu_colors=lu_col,label="Energy: ",format="%8.2f",value=0,width=26,fg_bg=text_fg_bg} local capacity = PowerIndicator{parent=rect,x=7,y=4,lu_colors=lu_col,label="Capacity:",format="%8.2f",value=0,width=26,fg_bg=text_fg_bg} local input = PowerIndicator{parent=rect,x=7,y=5,lu_colors=lu_col,label="Input: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg_bg} diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index a35bf9a..f7f5957 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -59,7 +59,7 @@ local waste_opts = { ---@param parent graphics_element parent ---@param id integer local function init(parent, id) - local unit = iocontrol.get_db().units[id] ---@type ioctl_entry + local unit = iocontrol.get_db().units[id] ---@type ioctl_unit local r_ps = unit.reactor_ps local b_ps = unit.boiler_ps_tbl local t_ps = unit.turbine_ps_tbl @@ -329,7 +329,7 @@ local function init(parent, id) ---------------------- local burn_control = Div{parent=main,x=12,y=28,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} - local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg} + local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg} TextBox{parent=burn_control,x=9,y=2,text="mB/t"} local set_burn = function () unit.set_burn(burn_rate.get_value()) end diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 71a2d87..56166ae 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -24,7 +24,7 @@ local pipe = core.graphics.pipe ---@param parent graphics_element parent ---@param x integer top left x ---@param y integer top left y ----@param unit ioctl_entry unit database entry +---@param unit ioctl_unit unit database entry local function make(parent, x, y, unit) local height = 0 local num_boilers = #unit.boiler_data_tbl diff --git a/graphics/elements/controls/hazard_button.lua b/graphics/elements/controls/hazard_button.lua index 51edfb8..dec01d2 100644 --- a/graphics/elements/controls/hazard_button.lua +++ b/graphics/elements/controls/hazard_button.lua @@ -1,6 +1,7 @@ -- Hazard-bordered Button Graphics Element local tcd = require("scada-common.tcallbackdsp") +local util = require("scada-common.util") local core = require("graphics.core") local element = require("graphics.element") @@ -42,7 +43,7 @@ local function hazard_button(args) e.window.setTextColor(accent) e.window.setBackgroundColor(args.fg_bg.bkg) e.window.setCursorPos(1, 1) - e.window.write("\x99\x89\x89\x89\x89\x89\x89\x89\x99") + e.window.write("\x99" .. util.strrep("\x89", args.width - 2) .. "\x99") -- center left e.window.setCursorPos(1, 2) @@ -53,14 +54,14 @@ local function hazard_button(args) -- center right e.window.setTextColor(args.fg_bg.bkg) e.window.setBackgroundColor(accent) - e.window.setCursorPos(9, 2) + e.window.setCursorPos(args.width, 2) e.window.write("\x99") -- bottom e.window.setTextColor(accent) e.window.setBackgroundColor(args.fg_bg.bkg) e.window.setCursorPos(1, 3) - e.window.write("\x99\x98\x98\x98\x98\x98\x98\x98\x99") + e.window.write("\x99" .. util.strrep("\x98", args.width - 2) .. "\x99") end -- on request timeout: recursively calls itself to double flash button text diff --git a/graphics/elements/controls/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua index f76ad21..088d847 100644 --- a/graphics/elements/controls/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -11,6 +11,7 @@ local element = require("graphics.element") ---@field whole_num_precision integer number of whole number digits ---@field fractional_precision integer number of fractional digits ---@field arrow_fg_bg cpair arrow foreground/background colors +---@field arrow_disable? color color when disabled (default light gray) ---@field parent graphics_element ---@field id? string element id ---@field x? integer 1 if omitted @@ -29,8 +30,17 @@ local function spinbox(args) assert(util.is_int(wn_prec), "graphics.element.controls.spinbox_numeric: whole number precision must be an integer") assert(util.is_int(fr_prec), "graphics.element.controls.spinbox_numeric: fractional precision must be an integer") - local fmt = "%" .. (wn_prec + fr_prec + 1) .. "." .. fr_prec .. "f" - local fmt_init = "%0" .. (wn_prec + fr_prec + 1) .. "." .. fr_prec .. "f" + local fmt = "" + local fmt_init = "" + + if fr_prec > 0 then + fmt = "%" .. (wn_prec + fr_prec + 1) .. "." .. fr_prec .. "f" + fmt_init = "%0" .. (wn_prec + fr_prec + 1) .. "." .. fr_prec .. "f" + else + fmt = "%" .. wn_prec .. "d" + fmt_init = "%0" .. wn_prec .. "d" + end + local dec_point_x = args.whole_num_precision + 1 assert(type(args.arrow_fg_bg) == "table", "graphics.element.spinbox_numeric: arrow_fg_bg is a required field") @@ -43,22 +53,26 @@ local function spinbox(args) local e = element.new(args) -- set initial value - e.value = args.default or 0.0 + e.value = args.default or 0 -- draw the arrows - e.window.setBackgroundColor(args.arrow_fg_bg.bkg) - e.window.setTextColor(args.arrow_fg_bg.fgd) - e.window.setCursorPos(1, 1) - e.window.write(util.strrep("\x1e", wn_prec)) - e.window.setCursorPos(1, 3) - e.window.write(util.strrep("\x1f", wn_prec)) - if fr_prec > 0 then - e.window.setCursorPos(1 + wn_prec, 1) - e.window.write(" " .. util.strrep("\x1e", fr_prec)) - e.window.setCursorPos(1 + wn_prec, 3) - e.window.write(" " .. util.strrep("\x1f", fr_prec)) + local function draw_arrows(color) + e.window.setBackgroundColor(args.arrow_fg_bg.bkg) + e.window.setTextColor(color) + e.window.setCursorPos(1, 1) + e.window.write(util.strrep("\x1e", wn_prec)) + e.window.setCursorPos(1, 3) + e.window.write(util.strrep("\x1f", wn_prec)) + if fr_prec > 0 then + e.window.setCursorPos(1 + wn_prec, 1) + e.window.write(" " .. util.strrep("\x1e", fr_prec)) + e.window.setCursorPos(1 + wn_prec, 3) + e.window.write(" " .. util.strrep("\x1f", fr_prec)) + end end + draw_arrows(args.arrow_fg_bg.fgd) + -- populate digits from current value local function set_digits() local initial_str = util.sprintf(fmt_init, e.value) @@ -157,6 +171,16 @@ local function spinbox(args) show_num() end + -- enable this input + function e.enable() + draw_arrows(args.arrow_fg_bg.fgd) + end + + -- disable this input + function e.disable() + draw_arrows(args.arrow_disable or colors.lightGray) + end + -- default to zero, init digits table e.value = 0 set_digits() From e808ee2be05f736880f08c6e4fce8f9477308d70 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 23 Jan 2023 20:47:45 -0500 Subject: [PATCH 474/587] #137 save/recall waste configuration with config file --- coordinator/coordinator.lua | 8 ++++++-- coordinator/process.lua | 29 +++++++++++++++++++++++++++++ coordinator/startup.lua | 2 +- supervisor/session/unit.lua | 3 +++ supervisor/startup.lua | 2 +- 5 files changed, 40 insertions(+), 4 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 227b020..a0fe10f 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -70,7 +70,9 @@ function coordinator.configure_monitors(num_units) end -- attempt to load settings - settings.load("/coord.settings") + if not settings.load("/coord.settings") then + log.warning("configure_monitors(): failed to load coordinator settings file (may not exist yet)") + end --------------------- -- PRIMARY DISPLAY -- @@ -143,7 +145,9 @@ function coordinator.configure_monitors(num_units) end settings.set("UNIT_DISPLAYS", unit_displays) - settings.save("/coord.settings") + if not settings.save("/coord.settings") then + log.warning("configure_monitors(): failed to save coordinator settings file") + end for i = 1, #unit_displays do monitors.unit_displays[i] = ppm.get_periph(unit_displays[i]) diff --git a/coordinator/process.lua b/coordinator/process.lua index d404e06..963e2c7 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -23,6 +23,21 @@ local self = { function process.init(iocontrol, comms) self.io = iocontrol self.comms = comms + + -- load settings + if not settings.load("/coord.settings") then + log.error("process.init(): failed to load coordinator settings file") + end + + local waste_mode = settings.get("WASTE_MODES") ---@type table|nil + + if type(waste_mode) == "table" then + for id = 1, math.min(#waste_mode, self.io.facility.num_units) do + self.comms.send_unit_command(UNIT_COMMANDS.SET_WASTE, id, waste_mode[id]) + end + + log.info("PROCESS: loaded waste mode settings from coord.settings") + end end -- start reactor @@ -62,6 +77,20 @@ end function process.set_waste(id, mode) self.comms.send_command(UNIT_COMMANDS.SET_WASTE, id, mode) log.debug(util.c("UNIT[", id, "]: SET WASTE = ", mode)) + + local waste_mode = settings.get("WASTE_MODES") ---@type table|nil + + if type(waste_mode) ~= "table" then + waste_mode = {} + end + + waste_mode[id] = mode + + settings.set("WASTE_MODES", waste_mode) + + if not settings.save("/coord.settings") then + log.error("process.set_waste(): failed to save coordinator settings file") + end end -- acknowledge all alarms diff --git a/coordinator/startup.lua b/coordinator/startup.lua index bda6ba0..b41c73b 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.9" +local COORDINATOR_VERSION = "beta-v0.8.10" local print = util.print local println = util.println diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 073c1f2..7b5e08d 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -322,6 +322,9 @@ function unit.new(for_reactor, num_boilers, num_turbines) ---@param rs_unit unit_session function public.add_redstone(rs_unit) table.insert(self.redstone, rs_unit) + + -- send or re-send waste settings + public.set_waste(self.waste_mode) end -- link a turbine RTU session diff --git a/supervisor/startup.lua b/supervisor/startup.lua index f05eaf8..f4f28da 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.9.5" +local SUPERVISOR_VERSION = "beta-v0.9.6" local print = util.print local println = util.println From e9562a140c652ad5c3d33ccd0094639b2a84e66f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 26 Jan 2023 18:26:26 -0500 Subject: [PATCH 475/587] #143 #103 #101 #102 work in progress auto control, added coordinator controls, save/auto load configuration, auto enable/disable on reactor PLC for auto control (untested) --- coordinator/coordinator.lua | 41 +++- coordinator/iocontrol.lua | 57 +++++- coordinator/process.lua | 140 ++++++++++++-- coordinator/ui/components/processctl.lua | 128 +++++++++++-- coordinator/ui/layout/main_view.lua | 2 +- graphics/elements/controls/hazard_button.lua | 6 +- reactor-plc/plc.lua | 102 +++++++++- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- scada-common/comms.lua | 95 ++++++---- scada-common/types.lua | 12 ++ scada-common/util.lua | 7 + supervisor/session/coordinator.lua | 64 +++++-- supervisor/session/facility.lua | 190 +++++++++++++++---- supervisor/session/plc.lua | 54 +++++- supervisor/session/unit.lua | 7 + supervisor/startup.lua | 2 +- 17 files changed, 750 insertions(+), 161 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index a0fe10f..b13f4a2 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -2,10 +2,10 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local ppm = require("scada-common.ppm") local util = require("scada-common.util") -local process = require("coordinator.process") local apisessions = require("coordinator.apisessions") local iocontrol = require("coordinator.iocontrol") +local process = require("coordinator.process") local dialog = require("coordinator.ui.dialog") @@ -20,6 +20,7 @@ local ESTABLISH_ACK = comms.ESTABLISH_ACK local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES local UNIT_COMMANDS = comms.UNIT_COMMANDS +local FAC_COMMANDS = comms.FAC_COMMANDS local coordinator = {} @@ -313,11 +314,25 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa return self.sv_linked end + -- send a facility command + ---@param cmd FAC_COMMANDS command + function public.send_fac_command(cmd) + _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.FAC_CMD, { cmd }) + end + + -- send the auto process control configuration with a start command + ---@param config coord_auto_config configuration + function public.send_auto_start(config) + _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.FAC_CMD, { + FAC_COMMANDS.START, config.mode, config.burn_target, config.charge_target, config.gen_target, config.limits + }) + end + -- send a unit command ---@param cmd UNIT_COMMANDS command ---@param unit integer unit ID - ---@param option any? optional options (like burn rate) - function public.send_command(cmd, unit, option) + ---@param option any? optional option options for the optional options (like burn rate) (does option still look like a word?) + function public.send_unit_command(cmd, unit, option) _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.UNIT_CMD, { cmd, unit, option }) end @@ -412,6 +427,26 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa end elseif packet.type == SCADA_CRDN_TYPES.FAC_CMD then -- facility command acknowledgement + if packet.length >= 2 then + local cmd = packet.data[1] + local ack = packet.data[2] == true + + if cmd == FAC_COMMANDS.SCRAM_ALL then + iocontrol.get_db().facility.scram_ack(ack) + elseif cmd == FAC_COMMANDS.STOP then + iocontrol.get_db().facility.stop_ack(ack) + elseif cmd == FAC_COMMANDS.START then + if packet.length == 7 then + process.start_ack_handle({ table.unpack(packet.data, 2) }) + else + log.debug("SCADA_CRDN process start (with configuration) ack echo packet length mismatch") + end + else + log.debug(util.c("received facility command ack with unknown command ", cmd)) + end + else + log.debug("SCADA_CRDN facility command ack packet length mismatch") + end elseif packet.type == SCADA_CRDN_TYPES.UNIT_BUILDS then -- record builds if iocontrol.record_unit_builds(packet.data) then diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 0d0aa35..7d8444b 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -24,9 +24,17 @@ function iocontrol.init(conf, comms) ---@class ioctl_facility io.facility = { auto_active = false, - scram = false, + auto_ramping = false, + auto_scram = false, + auto_scram_cause = "ok", ---@type auto_scram_cause + + num_units = conf.num_units, ---@type integer + + save_cfg_ack = function (success) end, ---@param success boolean + start_ack = function (success) end, ---@param success boolean + stop_ack = function (success) end, ---@param success boolean + scram_ack = function (success) end, ---@param success boolean - num_units = conf.num_units, ---@type integer ps = psil.create(), induction_ps_tbl = {}, @@ -69,7 +77,6 @@ function iocontrol.init(conf, comms) set_waste = function (mode) process.set_waste(i, mode) end, ---@param mode integer waste processing mode set_group = function (grp) process.set_group(i, grp) end, ---@param grp integer|0 group ID or 0 - set_limit = function (lim) process.set_limit(i, lim) end, ---@param lim number burn rate limit start_ack = function (success) end, ---@param success boolean scram_ack = function (success) end, ---@param success boolean @@ -195,7 +202,7 @@ function iocontrol.record_unit_builds(builds) -- reactor build if type(build.reactor) == "table" then - unit.reactor_data.mek_struct = build.reactor + unit.reactor_data.mek_struct = build.reactor ---@type mek_struct for key, val in pairs(unit.reactor_data.mek_struct) do unit.reactor_ps.publish(key, val) end @@ -257,11 +264,38 @@ function iocontrol.update_facility_status(status) else local fac = io.facility + -- auto control status information + + local ctl_status = status[1] + + if type(ctl_status) == "table" then + fac.auto_active = ctl_status[1] > 0 + fac.auto_ramping = ctl_status[2] + fac.auto_scram = ctl_status[3] + fac.auto_scram_cause = ctl_status[4] + + fac.ps.publish("auto_active", fac.auto_active) + fac.ps.publish("auto_ramping", fac.auto_ramping) + fac.ps.publish("auto_scram", fac.auto_scram) + fac.ps.publish("auto_scram_cause", fac.auto_scram_cause) + else + log.debug(log_header .. "control status not a table") + end + -- RTU statuses - local rtu_statuses = status[1] + local rtu_statuses = status[2] if type(rtu_statuses) == "table" then + -- power statistics + if type(rtu_statuses.power) == "table" then + fac.ps.publish("avg_charge", rtu_statuses.power[1]) + fac.ps.publish("avg_inflow", rtu_statuses.power[2]) + fac.ps.publish("avg_outflow", rtu_statuses.power[3]) + else + log.debug(log_header .. "power statistics list not a table") + end + -- induction matricies statuses if type(rtu_statuses.induction) == "table" then for id = 1, #fac.induction_ps_tbl do @@ -328,6 +362,8 @@ function iocontrol.update_unit_statuses(statuses) log.debug("iocontrol.update_unit_statuses: number of provided unit statuses does not match expected number of units") return false else + local burn_rate_sum = 0.0 + -- get all unit statuses for i = 1, #statuses do local log_header = util.c("iocontrol.update_unit_statuses[unit ", i, "]: ") @@ -369,6 +405,11 @@ function iocontrol.update_unit_statuses(statuses) unit.reactor_data.rps_status = rps_status ---@type rps_status unit.reactor_data.mek_status = mek_status ---@type mek_status + -- if status hasn't been received, mek_status = {} + if type(unit.reactor_data.mek_status.act_burn_rate) == "number" then + burn_rate_sum = burn_rate_sum + unit.reactor_data.mek_status.act_burn_rate + end + if unit.reactor_data.mek_status.status then unit.reactor_ps.publish("computed_status", 5) -- running else @@ -596,8 +637,8 @@ function iocontrol.update_unit_statuses(statuses) local auto_ctl_state = status[6] if type(auto_ctl_state) == "table" then - if #auto_ctl_state == 1 then - unit.reactor_ps.publish("burn_limit", auto_ctl_state[1]) + if #auto_ctl_state == 0 then + ---@todo else log.debug(log_header .. "auto control state length mismatch") end @@ -606,6 +647,8 @@ function iocontrol.update_unit_statuses(statuses) end end + io.facility.ps.publish("burn_sum", burn_rate_sum) + -- update alarm sounder sounder.eval(io.units) end diff --git a/coordinator/process.lua b/coordinator/process.lua index 963e2c7..e1d067f 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -1,16 +1,28 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") +local types = require("scada-common.types") local util = require("scada-common.util") +local FAC_COMMANDS = comms.FAC_COMMANDS local UNIT_COMMANDS = comms.UNIT_COMMANDS +local PROCESS = types.PROCESS + ---@class process_controller local process = {} local self = { io = nil, ---@type ioctl - comms = nil ---@type coord_comms + comms = nil, ---@type coord_comms + ---@class coord_auto_config + config = { + mode = 0, ---@type PROCESS + burn_target = 0.0, + charge_target = 0.0, + gen_target = 0.0, + limits = {} ---@type table + } } -------------------------- @@ -24,11 +36,37 @@ function process.init(iocontrol, comms) self.io = iocontrol self.comms = comms + for i = 1, self.io.facility.num_units do + self.config.limits[i] = 0.1 + end + -- load settings if not settings.load("/coord.settings") then log.error("process.init(): failed to load coordinator settings file") end + local config = settings.get("PROCESS") ---@type coord_auto_config|nil + + if type(config) == "table" then + self.config.mode = config.mode + self.config.burn_target = config.burn_target + self.config.charge_target = config.charge_target + self.config.gen_target = config.gen_target + self.config.limits = config.limits + + self.io.facility.ps.publish("process_mode", self.config.mode) + self.io.facility.ps.publish("process_burn_target", self.config.burn_target) + self.io.facility.ps.publish("process_charge_target", self.config.charge_target) + self.io.facility.ps.publish("process_gen_target", self.config.gen_target) + + for id = 1, math.min(#self.config.limits, self.io.facility.num_units) do + local unit = self.io.units[id] ---@type ioctl_unit + unit.reactor_ps.publish("burn_limit", self.config.limits[id]) + end + + log.info("PROCESS: loaded auto control settings from coord.settings") + end + local waste_mode = settings.get("WASTE_MODES") ---@type table|nil if type(waste_mode) == "table" then @@ -44,7 +82,7 @@ end ---@param id integer unit ID function process.start(id) self.io.units[id].control_state = true - self.comms.send_command(UNIT_COMMANDS.START, id) + self.comms.send_unit_command(UNIT_COMMANDS.START, id) log.debug(util.c("UNIT[", id, "]: START")) end @@ -52,14 +90,14 @@ end ---@param id integer unit ID function process.scram(id) self.io.units[id].control_state = false - self.comms.send_command(UNIT_COMMANDS.SCRAM, id) + self.comms.send_unit_command(UNIT_COMMANDS.SCRAM, id) log.debug(util.c("UNIT[", id, "]: SCRAM")) end -- reset reactor protection system ---@param id integer unit ID function process.reset_rps(id) - self.comms.send_command(UNIT_COMMANDS.RESET_RPS, id) + self.comms.send_unit_command(UNIT_COMMANDS.RESET_RPS, id) log.debug(util.c("UNIT[", id, "]: RESET RPS")) end @@ -67,7 +105,7 @@ end ---@param id integer unit ID ---@param rate number burn rate function process.set_rate(id, rate) - self.comms.send_command(UNIT_COMMANDS.SET_BURN, id, rate) + self.comms.send_unit_command(UNIT_COMMANDS.SET_BURN, id, rate) log.debug(util.c("UNIT[", id, "]: SET BURN = ", rate)) end @@ -75,7 +113,7 @@ end ---@param id integer unit ID ---@param mode integer waste mode function process.set_waste(id, mode) - self.comms.send_command(UNIT_COMMANDS.SET_WASTE, id, mode) + self.comms.send_unit_command(UNIT_COMMANDS.SET_WASTE, id, mode) log.debug(util.c("UNIT[", id, "]: SET WASTE = ", mode)) local waste_mode = settings.get("WASTE_MODES") ---@type table|nil @@ -96,7 +134,7 @@ end -- acknowledge all alarms ---@param id integer unit ID function process.ack_all_alarms(id) - self.comms.send_command(UNIT_COMMANDS.ACK_ALL_ALARMS, id) + self.comms.send_unit_command(UNIT_COMMANDS.ACK_ALL_ALARMS, id) log.debug(util.c("UNIT[", id, "]: ACK ALL ALARMS")) end @@ -104,7 +142,7 @@ end ---@param id integer unit ID ---@param alarm integer alarm ID function process.ack_alarm(id, alarm) - self.comms.send_command(UNIT_COMMANDS.ACK_ALARM, id, alarm) + self.comms.send_unit_command(UNIT_COMMANDS.ACK_ALARM, id, alarm) log.debug(util.c("UNIT[", id, "]: ACK ALARM ", alarm)) end @@ -112,7 +150,7 @@ end ---@param id integer unit ID ---@param alarm integer alarm ID function process.reset_alarm(id, alarm) - self.comms.send_command(UNIT_COMMANDS.RESET_ALARM, id, alarm) + self.comms.send_unit_command(UNIT_COMMANDS.RESET_ALARM, id, alarm) log.debug(util.c("UNIT[", id, "]: RESET ALARM ", alarm)) end @@ -120,16 +158,86 @@ end ---@param unit_id integer unit ID ---@param group_id integer|0 group ID or 0 for independent function process.set_group(unit_id, group_id) - self.comms.send_command(UNIT_COMMANDS.SET_GROUP, unit_id, group_id) + self.comms.send_unit_command(UNIT_COMMANDS.SET_GROUP, unit_id, group_id) log.debug(util.c("UNIT[", unit_id, "]: SET GROUP ", group_id)) end --- set the burn rate limit ----@param id integer unit ID ----@param limit number burn rate limit -function process.set_limit(id, limit) - self.comms.send_command(UNIT_COMMANDS.SET_LIMIT, id, limit) - log.debug(util.c("UNIT[", id, "]: SET LIMIT = ", limit)) +-------------------------- +-- AUTO PROCESS CONTROL -- +-------------------------- + +-- facility SCRAM command +function process.fac_scram() + self.comms.send_fac_command(FAC_COMMANDS.SCRAM_ALL) + log.debug("FAC: SCRAM ALL") +end + +-- stop automatic process control +function process.stop_auto() + self.comms.send_fac_command(FAC_COMMANDS.STOP) + log.debug("FAC: STOP AUTO") +end + +-- start automatic process control +function process.start_auto() + self.comms.send_auto_start(self.config) + log.debug("FAC: START AUTO") +end + +-- save process control settings +---@param mode PROCESS control mode +---@param burn_target number burn rate target +---@param charge_target number charge target +---@param gen_target number generation rate target +---@param limits table unit burn rate limits +function process.save(mode, burn_target, charge_target, gen_target, limits) + -- attempt to load settings + if not settings.load("/coord.settings") then + log.warning("process.save(): failed to load coordinator settings file") + end + + -- config table + self.config = { + mode = mode, + burn_target = burn_target, + charge_target = charge_target, + gen_target = gen_target, + limits = limits + } + + -- save config + settings.set("PROCESS", self.config) + local saved = settings.save("/coord.settings") + + if not saved then + log.warning("process.save(): failed to save coordinator settings file") + end + + log.debug("saved = " .. util.strval(saved)) + + self.io.facility.save_cfg_ack(saved) +end + +-- handle a start command acknowledgement +---@param response table ack and configuration reply +function process.start_ack_handle(response) + local ack = response[1] + + self.config.mode = response[2] + self.config.burn_target = response[3] + self.config.charge_target = response[4] + self.config.gen_target = response[5] + + for i = 1, #response[6] do + self.config.limits[i] = response[6][i] + end + + self.io.facility.ps.publish("auto_mode", self.config.mode) + self.io.facility.ps.publish("burn_target", self.config.burn_target) + self.io.facility.ps.publish("charge_target", self.config.charge_target) + self.io.facility.ps.publish("gen_target", self.config.gen_target) + + self.io.facility.start_ack(ack) end -------------------------- diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index d8dece4..91f888d 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -1,6 +1,8 @@ +local tcd = require("scada-common.tcallbackdsp") local util = require("scada-common.util") local iocontrol = require("coordinator.iocontrol") +local process = require("coordinator.process") local style = require("coordinator.ui.style") @@ -36,29 +38,127 @@ local function new_view(root, x, y) local facility = iocontrol.get_db().facility local units = iocontrol.get_db().units - local bw_fg_bg = cpair(colors.black, colors.white) + local bw_fg_bg = cpair(colors.black, colors.white) + local hzd_fg_bg = cpair(colors.white, colors.gray) + local dis_colors = cpair(colors.white, colors.lightGray) - local proc = Div{parent=root,width=60,height=24,x=x,y=y} + local main = Div{parent=root,width=80,height=24,x=x,y=y} - local limits = Div{parent=proc,width=40,height=24,x=30,y=1} + local scram = HazardButton{parent=main,x=1,y=1,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=process.fac_scram,fg_bg=hzd_fg_bg} + + facility.scram_ack = scram.on_response + + --------------------- + -- process control -- + --------------------- + + local proc = Div{parent=main,width=54,height=24,x=27,y=1} + + ----------------------------- + -- process control targets -- + ----------------------------- + + local targets = Div{parent=proc,width=31,height=24,x=1,y=1} + + local burn_tag = Div{parent=targets,x=1,y=1,width=8,height=4,fg_bg=cpair(colors.black,colors.purple)} + TextBox{parent=burn_tag,x=2,y=2,text="Burn Target",width=7,height=2} + + local burn_target = Div{parent=targets,x=9,y=1,width=23,height=3,fg_bg=cpair(colors.gray,colors.white)} + local b_target = SpinboxNumeric{parent=burn_target,x=11,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg} + TextBox{parent=burn_target,x=18,y=2,text="mB/t"} + local burn_sum = DataIndicator{parent=targets,x=9,y=4,label="",format="%18.1f",value=0,unit="mB/t",commas=true,lu_colors=cpair(colors.black,colors.black),width=23,fg_bg=cpair(colors.black,colors.brown)} + + facility.ps.subscribe("process_burn_target", b_target.set_value) + facility.ps.subscribe("burn_sum", burn_sum.update) + + local chg_tag = Div{parent=targets,x=1,y=6,width=8,height=4,fg_bg=cpair(colors.black,colors.purple)} + TextBox{parent=chg_tag,x=2,y=2,text="Charge Target",width=7,height=2} + + local chg_target = Div{parent=targets,x=9,y=6,width=23,height=3,fg_bg=cpair(colors.gray,colors.white)} + local c_target = SpinboxNumeric{parent=chg_target,x=2,y=1,whole_num_precision=15,fractional_precision=0,min=0,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg} + TextBox{parent=chg_target,x=18,y=2,text="kFE"} + local cur_charge = DataIndicator{parent=targets,x=9,y=9,label="",format="%19d",value=0,unit="kFE",commas=true,lu_colors=cpair(colors.black,colors.black),width=23,fg_bg=cpair(colors.black,colors.brown)} + + facility.ps.subscribe("process_charge_target", c_target.set_value) + facility.induction_ps_tbl[1].subscribe("energy", function (j) cur_charge.update(util.joules_to_fe(j) / 1000) end) + + local gen_tag = Div{parent=targets,x=1,y=11,width=8,height=4,fg_bg=cpair(colors.black,colors.purple)} + TextBox{parent=gen_tag,x=2,y=2,text="Gen. Target",width=7,height=2} + + local gen_target = Div{parent=targets,x=9,y=11,width=23,height=3,fg_bg=cpair(colors.gray,colors.white)} + local g_target = SpinboxNumeric{parent=gen_target,x=8,y=1,whole_num_precision=9,fractional_precision=0,min=0,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg} + TextBox{parent=gen_target,x=18,y=2,text="kFE/t"} + local cur_gen = DataIndicator{parent=targets,x=9,y=14,label="",format="%17d",value=0,unit="kFE/t",commas=true,lu_colors=cpair(colors.black,colors.black),width=23,fg_bg=cpair(colors.black,colors.brown)} + + facility.ps.subscribe("process_gen_target", g_target.set_value) + facility.induction_ps_tbl[1].subscribe("last_input", function (j) cur_gen.update(util.joules_to_fe(j) / 1000) end) + + ----------------- + -- unit limits -- + ----------------- + + local limit_div = Div{parent=proc,width=40,height=19,x=34,y=6} + + local rate_limits = {} for i = 1, facility.num_units do - local unit = units[i] ---@type ioctl_entry + local unit = units[i] ---@type ioctl_unit - local _y = ((i - 1) * 4) + 1 + local _y = ((i - 1) * 5) + 1 - TextBox{parent=limits,x=1,y=_y+1,text="Unit "..i} - - local lim_ctl = Div{parent=limits,x=8,y=_y,width=20,height=3,fg_bg=cpair(colors.gray,colors.white)} - local burn_rate = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,max=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg} - - unit.reactor_ps.subscribe("max_burn", burn_rate.set_max) - unit.reactor_ps.subscribe("burn_limit", burn_rate.set_value) + local unit_tag = Div{parent=limit_div,x=1,y=_y,width=8,height=4,fg_bg=cpair(colors.black,colors.lightBlue)} + TextBox{parent=unit_tag,x=2,y=2,text="Unit "..i.." Limit",width=7,height=2} + local lim_ctl = Div{parent=limit_div,x=9,y=_y,width=14,height=3,fg_bg=cpair(colors.gray,colors.white)} + rate_limits[i] = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg} TextBox{parent=lim_ctl,x=9,y=2,text="mB/t"} - local set_burn = function () unit.set_limit(burn_rate.get_value()) end - PushButton{parent=lim_ctl,x=14,y=2,text="SAVE",min_width=6,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} + unit.reactor_ps.subscribe("max_burn", rate_limits[i].set_max) + unit.reactor_ps.subscribe("burn_limit", rate_limits[i].set_value) + + local cur_burn = DataIndicator{parent=limit_div,x=9,y=_y+3,label="",format="%7.1f",value=0,unit="mB/t",commas=false,lu_colors=cpair(colors.black,colors.black),width=14,fg_bg=cpair(colors.black,colors.brown)} + + unit.reactor_ps.subscribe("act_burn_rate", cur_burn.update) + end + + ------------------------- + -- controls and status -- + ------------------------- + + local ctl_opts = { "Regulated", "Burn Rate", "Charge Level", "Generation Rate" } + local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.purple,colors.black),radio_bg=colors.gray} + + facility.ps.subscribe("process_mode", mode.set_value) + + local u_stat = Rectangle{parent=proc,border=border(1,colors.gray,true),thin=true,width=31,height=4,x=1,y=16,fg_bg=bw_fg_bg} + local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=31,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=bw_fg_bg} + local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data",width=31,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} + + local auto_controls = Div{parent=proc,x=1,y=20,width=31,height=5,fg_bg=cpair(colors.gray,colors.white)} + + -- save the automatic process control configuration without starting + local function _save_cfg() + local limits = {} + for i = 1, #rate_limits do limits[i] = rate_limits[i].get_value() end + + process.save(mode.get_value(), b_target.get_value(), c_target.get_value(), g_target.get_value(), limits) + end + + -- start automatic control after saving process control settings + local function _start_auto() + _save_cfg() + process.start_auto() + end + + local save = HazardButton{parent=auto_controls,x=2,y=2,text="SAVE",accent=colors.purple,dis_colors=dis_colors,callback=_save_cfg,fg_bg=hzd_fg_bg} + local start = HazardButton{parent=auto_controls,x=13,y=2,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=_start_auto,fg_bg=hzd_fg_bg} + local stop = HazardButton{parent=auto_controls,x=23,y=2,text="STOP",accent=colors.orange,dis_colors=dis_colors,callback=process.stop_auto,fg_bg=hzd_fg_bg} + + facility.start_ack = start.on_response + facility.stop_ack = stop.on_response + + function facility.save_cfg_ack(ack) + tcd.dispatch(0.2, function () save.on_response(ack) end) end end diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index 8609f73..59ac653 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -83,7 +83,7 @@ local function init(monitor) -- testing ---@fixme remove test code - ColorMap{parent=main,x=2,y=(main.height()-1)} + ColorMap{parent=main,x=132,y=(main.height()-1)} local audio = Div{parent=main,width=34,height=15,x=95,y=cnc_y_start} diff --git a/graphics/elements/controls/hazard_button.lua b/graphics/elements/controls/hazard_button.lua index dec01d2..0b59df6 100644 --- a/graphics/elements/controls/hazard_button.lua +++ b/graphics/elements/controls/hazard_button.lua @@ -145,9 +145,6 @@ local function hazard_button(args) ---@diagnostic disable-next-line: unused-local function e.handle_touch(event) if e.enabled then - -- call the touch callback - args.callback() - -- change text color to indicate clicked e.window.setTextColor(args.accent) e.window.setCursorPos(3, 2) @@ -160,6 +157,9 @@ local function hazard_button(args) -- 1.5 second timeout tcd.dispatch(1.5, on_timeout) + + -- call the touch callback + args.callback() end end diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index afcd255..468d190 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -13,6 +13,7 @@ local DEVICE_TYPES = comms.DEVICE_TYPES local ESTABLISH_ACK = comms.ESTABLISH_ACK local RPLC_TYPES = comms.RPLC_TYPES local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES +local AUTO_ACK = comms.PLC_AUTO_ACK local print = util.print local println = util.println @@ -24,6 +25,8 @@ local println_ts = util.println_ts local PCALL_SCRAM_MSG = "pcall: Scram requires the reactor to be active." local PCALL_START_MSG = "pcall: Reactor is already active." +local AUTO_TOGGLE_DELAY_MS = 5000 + -- RPS SAFETY CONSTANTS local MAX_DAMAGE_PERCENT = 90 @@ -61,6 +64,7 @@ function plc.rps_init(reactor, is_formed) reactor = reactor, state = { false, false, false, false, false, false, false, false, false, false, false, false }, reactor_enabled = false, + enabled_at = 0, formed = is_formed, force_disabled = false, tripped = false, @@ -215,7 +219,7 @@ function plc.rps_init(reactor, is_formed) self.state[state_keys.sys_fail] = true end - -- SCRAM the reactor now + -- SCRAM the reactor now (blocks waiting for server tick) ---@return boolean success function public.scram() log.info("RPS: reactor SCRAM") @@ -226,11 +230,12 @@ function plc.rps_init(reactor, is_formed) return false else self.reactor_enabled = false + self.last_runtime = util.time_ms() - self.enabled_at return true end end - -- start the reactor + -- start the reactor now (blocks waiting for server tick) ---@return boolean success function public.activate() if not self.tripped then @@ -241,6 +246,7 @@ function plc.rps_init(reactor, is_formed) log.error("RPS: failed reactor start") else self.reactor_enabled = true + self.enabled_at = util.time_ms() return true end else @@ -250,6 +256,21 @@ function plc.rps_init(reactor, is_formed) return false end + -- automatic control activate/re-activate + ---@return boolean success + function public.auto_activate() + -- clear automatic SCRAM if it was the cause + if self.tripped and self.trip_cause == "automatic" then + self.state[state_keys.automatic] = true + self.trip_cause = rps_status_t.ok + self.tripped = false + + log.debug("RPS: cleared automatic SCRAM for re-activation") + end + + return public.activate() + end + -- check all safety conditions ---@return boolean tripped, rps_status_t trip_status, boolean first_trip function public.check() @@ -324,7 +345,8 @@ function plc.rps_init(reactor, is_formed) self.tripped = true self.trip_cause = status - -- in the case that the reactor is detected to be active, it will be scrammed shortly after this in the main RPS loop if we don't here + -- in the case that the reactor is detected to be active, + -- it will be scrammed shortly after this in the main RPS loop if we don't here if self.formed then if not self.force_disabled then public.scram() @@ -348,6 +370,10 @@ function plc.rps_init(reactor, is_formed) function public.is_formed() return self.formed end function public.is_force_disabled() return self.force_disabled end + -- get the runtime of the reactor if active, or the last runtime if disabled + ---@return integer runtime time since last enable + function public.get_runtime() return util.trinary(self.reactor_enabled, util.time_ms() - self.enabled_at, self.last_runtime) end + -- reset the RPS ---@param quiet? boolean true to suppress the info log message function public.reset(quiet) @@ -383,8 +409,8 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co reactor = reactor, scrammed = false, linked = false, - status_cache = nil, resend_build = false, + status_cache = nil, max_burn_rate = nil } @@ -532,9 +558,9 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co -- general ack ---@param msg_type RPLC_TYPES - ---@param succeeded boolean - local function _send_ack(msg_type, succeeded) - _send(msg_type, { succeeded }) + ---@param status boolean|integer + local function _send_ack(msg_type, status) + _send(msg_type, { status }) end -- send structure properties (these should not change, server will cache these) @@ -587,6 +613,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co self.reactor = reactor self.status_cache = nil self.resend_build = true + self.max_burn_rate = nil end -- unlink from the server @@ -731,7 +758,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co log.debug("sent out structure again, did supervisor miss it?") elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then -- set the burn rate - if packet.length == 2 then + if (packet.length == 2) and (type(packet.data[1]) == "number") then local success = false local burn_rate = math.floor(packet.data[1] * 10) / 10 local ramp = packet.data[2] @@ -759,7 +786,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co _send_ack(packet.type, success) else - log.debug("RPLC set burn rate packet length mismatch") + log.debug("RPLC set burn rate packet length mismatch or non-numeric burn rate") end elseif packet.type == RPLC_TYPES.RPS_ENABLE then -- enable the reactor @@ -779,6 +806,63 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co -- reset the RPS status rps.reset() _send_ack(packet.type, true) + elseif packet.type == RPLC_TYPES.AUTO_BURN_RATE then + -- automatic control requested a new burn rate + if (packet.length == 2) and (type(packet.data[1]) == "number") then + local ack = AUTO_ACK.FAIL + local burn_rate = math.floor(packet.data[1] * 10) / 10 + local ramp = packet.data[2] + + -- if no known max burn rate, check again + if self.max_burn_rate == nil then + self.max_burn_rate = self.reactor.getMaxBurnRate() + end + + -- if we know our max burn rate, update current burn rate setpoint if in range + if self.max_burn_rate ~= ppm.ACCESS_FAULT then + if burn_rate < 0.1 then + if rps.is_active() then + if rps.get_runtime() > AUTO_TOGGLE_DELAY_MS then + -- auto scram to disable + if rps.scram() then + ack = AUTO_ACK.ZERO_DIS_OK + self.auto_last_disable = util.time_ms() + end + else + -- too soon to disable + ack = AUTO_ACK.ZERO_DIS_WAIT + end + else + ack = AUTO_ACK.ZERO_DIS_OK + end + elseif burn_rate <= self.max_burn_rate then + if not rps.is_active() then + -- activate the reactor + if not rps.auto_activate() then + log.debug("automatic reactor activation failed") + end + end + + -- if active, set/ramp burn rate + if rps.is_active() then + if ramp then + setpoints.burn_rate_en = true + setpoints.burn_rate = burn_rate + ack = AUTO_ACK.RAMP_SET_OK + else + self.reactor.setBurnRate(burn_rate) + ack = util.trinary(self.reactor.__p_is_faulted(), AUTO_ACK.FAIL, AUTO_ACK.DIRECT_SET_OK) + end + end + else + log.debug(burn_rate .. " rate outside of 0 < x <= " .. self.max_burn_rate) + end + end + + _send_ack(packet.type, ack) + else + log.debug("RPLC set automatic burn rate packet length mismatch or non-numeric burn rate") + end else log.warning("received unknown RPLC packet type " .. packet.type) end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 5e0dca5..427f8f7 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.10" +local R_PLC_VERSION = "beta-v0.10.0" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index ec5fc0a..2188549 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.9.10" +local RTU_VERSION = "beta-v0.9.11" local rtu_t = types.rtu_t diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 2abc533..6fed4d5 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -12,7 +12,7 @@ local rtu_t = types.rtu_t local insert = table.insert -comms.version = "1.1.1" +comms.version = "1.1.2" ---@alias PROTOCOLS integer local PROTOCOLS = { @@ -23,14 +23,6 @@ local PROTOCOLS = { COORD_API = 4 -- data/control packets for pocket computers to/from coordinators } ----@alias DEVICE_TYPES integer -local DEVICE_TYPES = { - PLC = 0, -- PLC device type for establish - RTU = 1, -- RTU device type for establish - SV = 2, -- supervisor device type for establish - CRDN = 3 -- coordinator device type for establish -} - ---@alias RPLC_TYPES integer local RPLC_TYPES = { STATUS = 0, -- reactor/system status @@ -41,7 +33,8 @@ local RPLC_TYPES = { RPS_ASCRAM = 5, -- SCRAM reactor (automatic request) RPS_STATUS = 6, -- RPS status RPS_ALARM = 7, -- RPS alarm broadcast - RPS_RESET = 8 -- clear RPS trip (if in bad state, will trip immediately) + RPS_RESET = 8, -- clear RPS trip (if in bad state, will trip immediately) + AUTO_BURN_RATE = 9 -- set an automatic burn rate, PLC will respond with status, enable toggle speed limited } ---@alias SCADA_MGMT_TYPES integer @@ -53,13 +46,6 @@ local SCADA_MGMT_TYPES = { RTU_DEV_REMOUNT = 4 -- RTU multiblock possbily changed (formed, unformed) due to PPM remount } ----@alias ESTABLISH_ACK integer -local ESTABLISH_ACK = { - ALLOW = 0, -- link approved - DENY = 1, -- link denied - COLLISION = 2 -- link denied due to existing active link -} - ---@alias SCADA_CRDN_TYPES integer local SCADA_CRDN_TYPES = { FAC_BUILDS = 0, -- facility RTU builds @@ -70,25 +56,26 @@ local SCADA_CRDN_TYPES = { UNIT_CMD = 5 -- command a reactor unit } ----@alias UNIT_COMMANDS integer -local UNIT_COMMANDS = { - SCRAM = 0, -- SCRAM the reactor - START = 1, -- start the reactor - RESET_RPS = 2, -- reset the RPS - SET_BURN = 3, -- set the burn rate - SET_WASTE = 4, -- set the waste processing mode - ACK_ALL_ALARMS = 5, -- ack all active alarms - ACK_ALARM = 6, -- ack a particular alarm - RESET_ALARM = 7, -- reset a particular alarm - SET_GROUP = 8, -- assign this unit to a group - SET_LIMIT = 9 -- set this unit maximum auto burn rate -} - ---@alias CAPI_TYPES integer local CAPI_TYPES = { ESTABLISH = 0 -- initial greeting } +---@alias ESTABLISH_ACK integer +local ESTABLISH_ACK = { + ALLOW = 0, -- link approved + DENY = 1, -- link denied + COLLISION = 2 -- link denied due to existing active link +} + +---@alias DEVICE_TYPES integer +local DEVICE_TYPES = { + PLC = 0, -- PLC device type for establish + RTU = 1, -- RTU device type for establish + SV = 2, -- supervisor device type for establish + CRDN = 3 -- coordinator device type for establish +} + ---@alias RTU_UNIT_TYPES integer local RTU_UNIT_TYPES = { REDSTONE = 0, -- redstone I/O @@ -100,16 +87,51 @@ local RTU_UNIT_TYPES = { ENV_DETECTOR = 6 -- environment detector } +---@alias PLC_AUTO_ACK integer +local PLC_AUTO_ACK = { + FAIL = 0, -- failed to set burn rate/burn rate invalid + DIRECT_SET_OK = 1, -- successfully set burn rate + RAMP_SET_OK = 2, -- successfully started burn rate ramping + ZERO_DIS_OK = 3, -- successfully disabled reactor with < 0.1 burn rate + ZERO_DIS_WAIT = 4 -- too soon to disable reactor with < 0.1 burn rate +} + +---@alias FAC_COMMANDS integer +local FAC_COMMANDS = { + SCRAM_ALL = 0, -- SCRAM all reactors + STOP = 1, -- stop automatic control + START = 2 -- start automatic control +} + +---@alias UNIT_COMMANDS integer +local UNIT_COMMANDS = { + SCRAM = 0, -- SCRAM the reactor + START = 1, -- start the reactor + RESET_RPS = 2, -- reset the RPS + SET_BURN = 3, -- set the burn rate + SET_WASTE = 4, -- set the waste processing mode + ACK_ALL_ALARMS = 5, -- ack all active alarms + ACK_ALARM = 6, -- ack a particular alarm + RESET_ALARM = 7, -- reset a particular alarm + SET_GROUP = 8 -- assign this unit to a group +} + comms.PROTOCOLS = PROTOCOLS -comms.DEVICE_TYPES = DEVICE_TYPES + comms.RPLC_TYPES = RPLC_TYPES -comms.ESTABLISH_ACK = ESTABLISH_ACK comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES comms.SCADA_CRDN_TYPES = SCADA_CRDN_TYPES -comms.UNIT_COMMANDS = UNIT_COMMANDS comms.CAPI_TYPES = CAPI_TYPES + +comms.ESTABLISH_ACK = ESTABLISH_ACK +comms.DEVICE_TYPES = DEVICE_TYPES comms.RTU_UNIT_TYPES = RTU_UNIT_TYPES +comms.PLC_AUTO_ACK = PLC_AUTO_ACK + +comms.UNIT_COMMANDS = UNIT_COMMANDS +comms.FAC_COMMANDS = FAC_COMMANDS + ---@alias packet scada_packet|modbus_packet|rplc_packet|mgmt_packet|crdn_packet|capi_packet ---@alias frame modbus_frame|rplc_frame|mgmt_frame|crdn_frame|capi_frame @@ -308,9 +330,10 @@ function comms.rplc_packet() self.type == RPLC_TYPES.RPS_ENABLE or self.type == RPLC_TYPES.RPS_SCRAM or self.type == RPLC_TYPES.RPS_ASCRAM or - self.type == RPLC_TYPES.RPS_ALARM or self.type == RPLC_TYPES.RPS_STATUS or - self.type == RPLC_TYPES.RPS_RESET + self.type == RPLC_TYPES.RPS_ALARM or + self.type == RPLC_TYPES.RPS_RESET or + self.type == RPLC_TYPES.AUTO_BURN_RATE end -- make an RPLC packet diff --git a/scada-common/types.lua b/scada-common/types.lua index cfdfcad..d4db1e9 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -35,6 +35,15 @@ types.TRI_FAIL = { FULL = 2 } +---@alias PROCESS integer +types.PROCESS = { + INACTIVE = 0, + SIMPLE = 1, + BURN_RATE = 2, + CHARGE = 3, + GEN_RATE = 4 +} + ---@alias WASTE_MODE integer types.WASTE_MODE = { AUTO = 1, @@ -164,6 +173,9 @@ types.ALARM_STATE = { ---| "sys_fail" ---| "force_disabled" +---@alias auto_scram_cause +---| "ok" + ---@alias rtu_t string types.rtu_t = { redstone = "redstone", diff --git a/scada-common/util.lua b/scada-common/util.lua index 979172e..8b88758 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -202,6 +202,13 @@ function util.is_int(x) return type(x) == "number" and x == math.floor(x) end +-- get the sign of a number +---@param x number value +---@return integer sign (-1 for < 0, 1 otherwise) +function util.sign(x) + return util.trinary(x < 0, -1, 1) +end + -- round a number to an integer ---@return integer rounded function util.round(x) diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 860fa1b..c1d3047 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -11,6 +11,7 @@ local PROTOCOLS = comms.PROTOCOLS local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES local UNIT_COMMANDS = comms.UNIT_COMMANDS +local FAC_COMMANDS = comms.FAC_COMMANDS local SV_Q_CMDS = svqtypes.SV_Q_CMDS local SV_Q_DATA = svqtypes.SV_Q_DATA @@ -133,6 +134,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility) -- send facility status local function _send_fac_status() local status = { + facility.get_control_status(), facility.get_rtu_statuses() } @@ -146,9 +148,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility) for i = 1, #self.units do local unit = self.units[i] ---@type reactor_unit - local auto_ctl = { - unit.get_control_inf().lim_br10 / 10 - } + local auto_ctl = {} status[unit.get_id()] = { unit.get_reactor_status(), @@ -208,6 +208,37 @@ function coordinator.new_session(id, in_queue, out_queue, facility) if pkt.type == SCADA_CRDN_TYPES.FAC_BUILDS then -- acknowledgement to coordinator receiving builds self.acks.fac_builds = true + elseif pkt.type == SCADA_CRDN_TYPES.FAC_CMD then + if pkt.length >= 1 then + local cmd = pkt.data[1] + + if cmd == FAC_COMMANDS.SCRAM_ALL then + facility.scram_all() + _send(SCADA_CRDN_TYPES.FAC_CMD, { cmd, true }) + elseif cmd == FAC_COMMANDS.STOP then + facility.auto_stop() + _send(SCADA_CRDN_TYPES.FAC_CMD, { cmd, true }) + elseif cmd == FAC_COMMANDS.START then + if pkt.length == 6 then + ---@type coord_auto_config + local config = { + mode = pkt.data[2], + burn_target = pkt.data[3], + charge_target = pkt.data[4], + gen_target = pkt.data[5], + limits = pkt.data[6] + } + + _send(SCADA_CRDN_TYPES.FAC_CMD, { cmd, table.unpack(facility.auto_start(config)) }) + else + log.debug(log_header .. "CRDN auto start (with configuration) packet length mismatch") + end + else + log.debug(log_header .. "CRDN facility command unknown") + end + else + log.debug(log_header .. "CRDN facility command packet length mismatch") + end elseif pkt.type == SCADA_CRDN_TYPES.UNIT_BUILDS then -- acknowledgement to coordinator receiving builds self.acks.unit_builds = true @@ -234,13 +265,13 @@ function coordinator.new_session(id, in_queue, out_queue, facility) if pkt.length == 3 then self.out_q.push_data(SV_Q_DATA.SET_BURN, data) else - log.debug(log_header .. "CRDN command unit burn rate missing option") + log.debug(log_header .. "CRDN unit command burn rate missing option") end elseif cmd == UNIT_COMMANDS.SET_WASTE then if pkt.length == 3 then unit.set_waste(pkt.data[3]) else - log.debug(log_header .. "CRDN command unit set waste missing option") + log.debug(log_header .. "CRDN unit command set waste missing option") end elseif cmd == UNIT_COMMANDS.ACK_ALL_ALARMS then unit.ack_all() @@ -249,36 +280,29 @@ function coordinator.new_session(id, in_queue, out_queue, facility) if pkt.length == 3 then unit.ack_alarm(pkt.data[3]) else - log.debug(log_header .. "CRDN command unit ack alarm missing alarm id") + log.debug(log_header .. "CRDN unit command ack alarm missing alarm id") end elseif cmd == UNIT_COMMANDS.RESET_ALARM then if pkt.length == 3 then unit.reset_alarm(pkt.data[3]) else - log.debug(log_header .. "CRDN command unit reset alarm missing alarm id") + log.debug(log_header .. "CRDN unit command reset alarm missing alarm id") end elseif cmd == UNIT_COMMANDS.SET_GROUP then if pkt.length == 3 then facility.set_group(unit.get_id(), pkt.data[3]) _send(SCADA_CRDN_TYPES.UNIT_CMD, { cmd, uid, pkt.data[3] }) else - log.debug(log_header .. "CRDN command unit set group missing group id") - end - elseif cmd == UNIT_COMMANDS.SET_LIMIT then - if pkt.length == 3 then - unit.set_burn_limit(pkt.data[3]) - _send(SCADA_CRDN_TYPES.UNIT_CMD, { cmd, uid, pkt.data[3] }) - else - log.debug(log_header .. "CRDN command unit set limit missing group id") + log.debug(log_header .. "CRDN unit command set group missing group id") end else - log.debug(log_header .. "CRDN command unknown") + log.debug(log_header .. "CRDN unit command unknown") end else - log.debug(log_header .. "CRDN command unit invalid") + log.debug(log_header .. "CRDN unit command invalid") end else - log.debug(log_header .. "CRDN command unit packet length mismatch") + log.debug(log_header .. "CRDN unit command packet length mismatch") end else log.debug(log_header .. "handler received unexpected SCADA_CRDN packet type " .. pkt.type) @@ -331,6 +355,8 @@ function coordinator.new_session(id, in_queue, out_queue, facility) self.retry_times.builds_packet = util.time() + RETRY_PERIOD _send_fac_builds() _send_unit_builds() + else + log.warning(log_header .. "unsupported command received in in_queue (this is a bug)") end elseif message.qtype == mqueue.TYPE.DATA then -- instruction with body @@ -339,6 +365,8 @@ function coordinator.new_session(id, in_queue, out_queue, facility) if cmd.key == CRD_S_DATA.CMD_ACK then local ack = cmd.val ---@type coord_ack _send(SCADA_CRDN_TYPES.UNIT_CMD, { ack.cmd, ack.unit, ack.ack }) + else + log.warning(log_header .. "unsupported data command received in in_queue (this is a bug)") end end end diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index d7ceadc..d554fca 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -1,12 +1,12 @@ local log = require("scada-common.log") local rsio = require("scada-common.rsio") +local types = require("scada-common.types") local util = require("scada-common.util") local rsctl = require("supervisor.session.rsctl") local unit = require("supervisor.session.unit") -local HEATING_WATER = 20000 -local HEATING_SODIUM = 200000 +local PROCESS = types.PROCESS -- 7.14 kJ per blade for 1 mB of fissile fuel
-- 2856 FE per blade per 1 mB, 285.6 FE per blade per 0.1 mB (minimum) @@ -15,15 +15,6 @@ local POWER_PER_BLADE = util.joules_to_fe(7140) local MAX_CHARGE = 0.99 local RE_ENABLE_CHARGE = 0.95 ----@alias PROCESS integer -local PROCESS = { - INACTIVE = 1, - SIMPLE = 2, - CHARGE = 3, - GEN_RATE = 4, - BURN_RATE = 5 -} - local AUTO_SCRAM = { NONE = 0, MATRIX_DC = 1, @@ -31,7 +22,7 @@ local AUTO_SCRAM = { } local charge_Kp = 1.0 -local charge_Ki = 0.0 +local charge_Ki = 0.00001 local charge_Kd = 0.0 local rate_Kp = 1.0 @@ -41,8 +32,6 @@ local rate_Kd = 0.0 ---@class facility_management local facility = {} -facility.PROCESS_MODES = PROCESS - -- create a new facility management object ---@param num_reactors integer number of reactor units ---@param cooling_conf table cooling configurations of reactor units @@ -54,9 +43,11 @@ function facility.new(num_reactors, cooling_conf) -- process control mode = PROCESS.INACTIVE, last_mode = PROCESS.INACTIVE, - burn_target = 0.0, -- burn rate target for aggregate burn mode + mode_set = PROCESS.SIMPLE, + max_burn_combined = 0.0, -- maximum burn rate to clamp at + burn_target = 0.1, -- burn rate target for aggregate burn mode charge_target = 0, -- FE charge target - charge_rate = 0, -- FE/t charge rate target + gen_rate_target = 0, -- FE/t charge rate target group_map = { 0, 0, 0, 0 }, -- units -> group IDs prio_defs = { {}, {}, {}, {} }, -- priority definitions (each level is a table of units) ascram = false, @@ -67,6 +58,7 @@ function facility.new(num_reactors, cooling_conf) initial_ramp = true, waiting_on_ramp = false, accumulator = 0.0, + saturated = false, last_error = 0.0, last_time = 0.0, -- statistics @@ -214,6 +206,8 @@ function facility.new(num_reactors, cooling_conf) if state_changed then if self.last_mode == PROCESS.INACTIVE then local blade_count = 0 + self.max_burn_combined = 0.0 + for i = 1, #self.prio_defs do table.sort(self.prio_defs[i], ---@param a reactor_unit @@ -224,6 +218,7 @@ function facility.new(num_reactors, cooling_conf) for _, u in pairs(self.prio_defs[i]) do blade_count = blade_count + u.get_db().blade_count u.a_engage() + self.max_burn_combined = self.max_burn_combined + (u.get_control_inf().lim_br10 / 10.0) end end @@ -247,6 +242,18 @@ function facility.new(num_reactors, cooling_conf) if state_changed then self.time_start = now end + elseif self.mode == PROCESS.BURN_RATE then + -- a total aggregate burn rate + if state_changed then + -- nothing special to do + elseif self.waiting_on_ramp and _all_units_ramped() then + self.waiting_on_ramp = false + self.time_start = now + end + + if not self.waiting_on_ramp then + _allocate_burn_rate(self.burn_target, self.initial_ramp) + end elseif self.mode == PROCESS.CHARGE then -- target a level of charge local error = (self.charge_target - avg_charge) / self.charge_conversion @@ -261,23 +268,32 @@ function facility.new(num_reactors, cooling_conf) end if not self.waiting_on_ramp then - self.accumulator = self.accumulator + (avg_charge / self.charge_conversion) + if not self.saturated then + self.accumulator = self.accumulator + ((avg_charge / self.charge_conversion) * (now - self.last_time)) + end local runtime = now - self.time_start - local integral = self.accumulator / runtime - local derivative = (error - self.last_error) / (now - self.last_time) + local integral = self.accumulator + -- local derivative = (error - self.last_error) / (now - self.last_time) local P = (charge_Kp * error) local I = (charge_Ki * integral) - local D = (charge_Kd * derivative) + local D = 0 -- (charge_Kd * derivative) local setpoint = P + I + D + + -- round setpoint -> setpoint rounded (sp_r) local sp_r = util.round(setpoint * 10.0) / 10.0 - log.debug(util.sprintf("PROC_CHRG[%f] { CHRG[%f] ERR[%f] INT[%f] => SP[%f] SP_R[%f] <= P[%f] I[%f] D[%d] }", - runtime, avg_charge, error, integral, setpoint, sp_r, P, I, D)) + -- clamp at range -> setpoint clamped (sp_c) + local sp_c = math.max(0, math.min(sp_r, self.max_burn_combined)) - _allocate_burn_rate(sp_r, self.initial_ramp) + self.saturated = sp_r ~= sp_c + + log.debug(util.sprintf("PROC_CHRG[%f] { CHRG[%f] ERR[%f] INT[%f] => SP[%f] SP_C[%f] <= P[%f] I[%f] D[%d] }", + runtime, avg_charge, error, integral, setpoint, sp_c, P, I, D)) + + _allocate_burn_rate(sp_c, self.initial_ramp) if self.initial_ramp then self.waiting_on_ramp = true @@ -285,7 +301,7 @@ function facility.new(num_reactors, cooling_conf) end elseif self.mode == PROCESS.GEN_RATE then -- target a rate of generation - local error = (self.charge_rate - avg_inflow) / self.charge_conversion + local error = (self.gen_rate_target - avg_inflow) / self.charge_conversion local setpoint = 0.0 if state_changed then @@ -303,36 +319,32 @@ function facility.new(num_reactors, cooling_conf) end if not self.waiting_on_ramp then - self.accumulator = self.accumulator + (avg_inflow / self.charge_conversion) + if not self.saturated then + self.accumulator = self.accumulator + ((avg_inflow / self.charge_conversion) * (now - self.last_time)) + end local runtime = util.time_s() - self.time_start - local integral = self.accumulator / runtime - local derivative = (error - self.last_error) / (now - self.last_time) + local integral = self.accumulator + -- local derivative = (error - self.last_error) / (now - self.last_time) local P = (rate_Kp * error) local I = (rate_Ki * integral) - local D = (rate_Kd * derivative) + local D = 0 -- (rate_Kd * derivative) setpoint = P + I + D + -- round setpoint -> setpoint rounded (sp_r) local sp_r = util.round(setpoint * 10.0) / 10.0 - log.debug(util.sprintf("PROC_RATE[%f] { RATE[%f] ERR[%f] INT[%f] => SP[%f] SP_R[%f] <= P[%f] I[%f] D[%f] }", - runtime, avg_inflow, error, integral, setpoint, sp_r, P, I, D)) + -- clamp at range -> setpoint clamped (sp_c) + local sp_c = math.max(0, math.min(sp_r, self.max_burn_combined)) - _allocate_burn_rate(sp_r, false) - end - elseif self.mode == PROCESS.BURN_RATE then - -- a total aggregate burn rate - if state_changed then - -- nothing special to do - elseif self.waiting_on_ramp and _all_units_ramped() then - self.waiting_on_ramp = false - self.time_start = now - end + self.saturated = sp_r ~= sp_c - if not self.waiting_on_ramp then - _allocate_burn_rate(self.burn_target, self.initial_ramp) + log.debug(util.sprintf("PROC_RATE[%f] { RATE[%f] ERR[%f] INT[%f] => SP[%f] SP_C[%f] <= P[%f] I[%f] D[%f] }", + runtime, avg_inflow, error, integral, setpoint, sp_c, P, I, D)) + + _allocate_burn_rate(sp_c, false) end end @@ -387,6 +399,83 @@ function facility.new(num_reactors, cooling_conf) end end + -- COMMANDS -- + + -- SCRAM all reactor units + function public.scram_all() + for i = 1, #self.units do + local u = self.units[i] ---@type reactor_unit + u.scram() + end + end + + -- stop auto control + function public.auto_stop() + self.mode = PROCESS.INACTIVE + end + + -- set automatic control configuration and start the process + ---@param config coord_auto_config configuration + ---@return table response ready state (successfully started) and current configuration (after updating) + function public.auto_start(config) + local ready = false + + -- load up current limits + local limits = {} + for i = 1, num_reactors do + local u = self.units[i] ---@type reactor_unit + limits[i] = u.get_control_inf().lim_br10 * 10 + end + + -- only allow changes if not running + if self.mode == PROCESS.INACTIVE then + if (type(config.mode) == "number") and (config.mode > PROCESS.INACTIVE) and (config.mode <= PROCESS.SIMPLE) then + self.mode_set = config.mode + end + + if (type(config.burn_target) == "number") and config.burn_target >= 0.1 then + self.burn_target = config.burn_target + log.debug("SET BURN TARGET " .. config.burn_target) + end + + if (type(config.charge_target) == "number") and config.charge_target >= 0 then + self.charge_target = config.charge_target + log.debug("SET CHARGE TARGET " .. config.charge_target) + end + + if (type(config.gen_target) == "number") and config.gen_target >= 0 then + self.gen_rate_target = config.gen_target + log.debug("SET RATE TARGET " .. config.gen_target) + end + + if (type(config.limits) == "table") and (#config.limits == num_reactors) then + for i = 1, num_reactors do + local limit = config.limits[i] + + if (type(limit) == "number") and (limit >= 0.1) then + limits[i] = limit + self.units[i].set_burn_limit(limit) + log.debug("SET UNIT " .. i .. " LIMIT " .. limit) + end + end + end + + ready = self.mode_set > 0 + + if (self.mode_set == PROCESS.CHARGE) and (self.charge_target <= 0) then + ready = false + elseif (self.mode_set == PROCESS.GEN_RATE) and (self.gen_rate_target <= 0) then + ready = false + elseif (self.mode_set == PROCESS.BURN_RATE) and (self.burn_target <= 0.1) then + ready = false + end + + if ready then self.mode = self.mode_set end + end + + return { ready, self.mode_set, self.burn_target, self.charge_target, self.gen_rate_target, limits } + end + -- SETTINGS -- -- set the automatic control group of a unit @@ -424,10 +513,27 @@ function facility.new(num_reactors, cooling_conf) return build end + -- get automatic process control status + function public.get_control_status() + return { + self.mode, + self.waiting_on_ramp, + self.ascram, + self.ascram_reason + } + end + -- get RTU statuses function public.get_rtu_statuses() local status = {} + -- power averages from induction matricies + status.power = { + self.avg_charge, + self.avg_inflow, + self.avg_outflow + } + -- status of induction matricies (including tanks) status.induction = {} for i = 1, #self.induction do diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 9027e5f..ecd6cc6 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -10,6 +10,7 @@ local plc = {} local PROTOCOLS = comms.PROTOCOLS local RPLC_TYPES = comms.RPLC_TYPES local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES +local PLC_AUTO_ACK = comms.PLC_AUTO_ACK local UNIT_COMMANDS = comms.UNIT_COMMANDS @@ -19,8 +20,9 @@ local print_ts = util.print_ts local println_ts = util.println_ts -- retry time constants in ms -local INITIAL_WAIT = 1500 -local RETRY_PERIOD = 1000 +local INITIAL_WAIT = 1500 +local INITIAL_AUTO_WAIT = 1000 +local RETRY_PERIOD = 1000 local PLC_S_CMDS = { SCRAM = 1, @@ -440,6 +442,21 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) cmd = UNIT_COMMANDS.RESET_RPS, ack = ack }) + elseif pkt.type == RPLC_TYPES.AUTO_BURN_RATE then + if pkt.length == 1 then + local ack = pkt.data[1] + + self.acks.burn_rate = ack ~= PLC_AUTO_ACK.FAIL + + if ack == PLC_AUTO_ACK.FAIL then + elseif ack == PLC_AUTO_ACK.DIRECT_SET_OK then + elseif ack == PLC_AUTO_ACK.RAMP_SET_OK then + elseif ack == PLC_AUTO_ACK.ZERO_DIS_OK then + elseif ack == PLC_AUTO_ACK.ZERO_DIS_WAIT then + end + else + log.debug(log_header .. "RPLC automatic burn rate ack packet length mismatch") + end else log.debug(log_header .. "handler received unsupported RPLC packet type " .. pkt.type) end @@ -517,6 +534,11 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) ---@param engage boolean true to engage the lockout function public.auto_lock(engage) self.auto_lock = engage + + -- stop retrying a burn rate command + if engage then + self.acks.burn_rate = true + end end -- set the burn rate on behalf of automatic control @@ -583,6 +605,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) self.acks.rps_reset = false self.retry_times.rps_reset_req = util.time() + INITIAL_WAIT _send(RPLC_TYPES.RPS_RESET, {}) + else + log.warning(log_header .. "unsupported command received in in_queue (this is a bug)") end elseif message.qtype == mqueue.TYPE.DATA then -- instruction with body @@ -613,13 +637,20 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) end elseif cmd.key == PLC_S_DATA.AUTO_BURN_RATE then -- set automatic burn rate - cmd.val = math.floor(cmd.val * 10) / 10 -- round to 10ths place - if cmd.val > 0 and cmd.val <= self.sDB.mek_struct.max_burn then - self.commanded_burn_rate = cmd.val - self.acks.burn_rate = false - self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + if self.auto_lock then + cmd.val = math.floor(cmd.val * 10) / 10 -- round to 10ths place + if cmd.val > 0 and cmd.val <= self.sDB.mek_struct.max_burn then + self.commanded_burn_rate = cmd.val + + -- this is only for manual control, only retry auto ramps + self.acks.burn_rate = not self.ramping_rate + self.retry_times.burn_rate_req = util.time() + INITIAL_AUTO_WAIT + + _send(RPLC_TYPES.AUTO_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + end end + else + log.warning(log_header .. "unsupported data command received in in_queue (this is a bug)") end end end @@ -685,7 +716,12 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) if not self.acks.burn_rate then if rtimes.burn_rate_req - util.time() <= 0 then - _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + if self.auto_lock then + _send(RPLC_TYPES.AUTO_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + else + _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + end + rtimes.burn_rate_req = util.time() + RETRY_PERIOD end end diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 7b5e08d..e084fed 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -450,6 +450,13 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- OPERATIONS -- + -- queue a command to SCRAM the reactor + function public.scram() + if self.plc_s ~= nil then + self.plc_s.in_queue.push_command(PLC_S_CMDS.SCRAM) + end + end + -- acknowledge all alarms (if possible) function public.ack_all() for i = 1, #self.db.alarm_states do diff --git a/supervisor/startup.lua b/supervisor/startup.lua index f4f28da..b284a35 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.9.6" +local SUPERVISOR_VERSION = "beta-v0.9.7" local print = util.print local println = util.println From fe71615c12f05a75eadff3426f9e562eb0db5ae5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 1 Feb 2023 21:55:02 -0500 Subject: [PATCH 476/587] #101 #102 work on bugfixes; disable unit controls while in auto mode --- coordinator/iocontrol.lua | 25 ++++-- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 51 ++++++++++-- graphics/elements/controls/push_button.lua | 27 ++++++- supervisor/session/facility.lua | 90 +++++++++++++++------- supervisor/session/unit.lua | 1 + supervisor/session/unitlogic.lua | 4 +- supervisor/startup.lua | 2 +- 8 files changed, 155 insertions(+), 47 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 7d8444b..da12a50 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -116,6 +116,8 @@ function iocontrol.init(conf, comms) ALARM_STATE.INACTIVE -- turbine trip }, + annunciator = {}, ---@type annunciator + reactor_ps = psil.create(), reactor_data = {}, ---@type reactor_db @@ -272,12 +274,23 @@ function iocontrol.update_facility_status(status) fac.auto_active = ctl_status[1] > 0 fac.auto_ramping = ctl_status[2] fac.auto_scram = ctl_status[3] - fac.auto_scram_cause = ctl_status[4] + fac.status_line_1 = ctl_status[4] + fac.status_line_2 = ctl_status[5] fac.ps.publish("auto_active", fac.auto_active) fac.ps.publish("auto_ramping", fac.auto_ramping) fac.ps.publish("auto_scram", fac.auto_scram) - fac.ps.publish("auto_scram_cause", fac.auto_scram_cause) + fac.ps.publish("status_line_1", fac.status_line_1) + fac.ps.publish("status_line_2", fac.status_line_2) + + local group_map = ctl_status[6] + + if (type(group_map) == "table") and (#group_map == fac.num_units) then + local names = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } + for i = 1, #group_map do + io.units[i].reactor_ps.publish("auto_group", names[group_map[i] + 1]) + end + end else log.debug(log_header .. "control status not a table") end @@ -556,14 +569,14 @@ function iocontrol.update_unit_statuses(statuses) -- annunciator - local annunciator = status[3] ---@type annunciator + unit.annunciator = status[3] - if type(annunciator) ~= "table" then - annunciator = {} + if type(unit.annunciator) ~= "table" then + unit.annunciator = {} log.debug(log_header .. "annunciator state not a table") end - for key, val in pairs(annunciator) do + for key, val in pairs(unit.annunciator) do if key == "TurbineTrip" then -- split up turbine trip table for all turbines and a general OR combination local trips = val diff --git a/coordinator/startup.lua b/coordinator/startup.lua index b41c73b..c1a8232 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.10" +local COORDINATOR_VERSION = "beta-v0.8.11" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index f7f5957..c3950e9 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -160,7 +160,6 @@ local function init(parent, id) local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(colors.green,colors.red)} local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=cpair(colors.white,colors.gray)} local r_active = IndicatorLight{parent=annunciator,label="Active",colors=cpair(colors.green,colors.gray)} - ---@todo auto control as info sent here local r_auto = IndicatorLight{parent=annunciator,label="Automatic Control",colors=cpair(colors.blue,colors.gray)} annunciator.line_break() @@ -171,6 +170,7 @@ local function init(parent, id) r_ps.subscribe("PLCOnline", plc_online.update) r_ps.subscribe("PLCHeartbeat", plc_hbeat.update) r_ps.subscribe("status", r_active.update) + r_ps.subscribe("AutoControl", r_auto.update) annunciator.line_break() @@ -328,18 +328,18 @@ local function init(parent, id) -- reactor controls -- ---------------------- + local dis_colors = cpair(colors.white, colors.lightGray) + local burn_control = Div{parent=main,x=12,y=28,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg} TextBox{parent=burn_control,x=9,y=2,text="mB/t"} local set_burn = function () unit.set_burn(burn_rate.get_value()) end - PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} + local set_burn_btn = PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),dis_fg_bg=dis_colors,callback=set_burn} r_ps.subscribe("burn_rate", burn_rate.set_value) r_ps.subscribe("max_burn", burn_rate.set_max) - local dis_colors = cpair(colors.white, colors.lightGray) - local start = HazardButton{parent=main,x=2,y=28,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=hzd_fg_bg} local ack_a = HazardButton{parent=main,x=12,y=32,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=unit.ack_alarms,fg_bg=hzd_fg_bg} local scram = HazardButton{parent=main,x=2,y=32,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=hzd_fg_bg} @@ -352,7 +352,9 @@ local function init(parent, id) local function start_button_en_check() if (unit.reactor_data ~= nil) and (unit.reactor_data.mek_status ~= nil) then - local can_start = (not unit.reactor_data.mek_status.status) and (not unit.reactor_data.rps_tripped) + local can_start = (not unit.reactor_data.mek_status.status) and + (not unit.reactor_data.rps_tripped) and + (not unit.annunciator.AutoControl) if can_start then start.enable() else start.disable() end end end @@ -455,22 +457,55 @@ local function init(parent, id) local ctl_opts = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } - RadioButton{parent=auto_div,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.blue,colors.white),radio_bg=colors.gray} + local group = RadioButton{parent=auto_div,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.blue,colors.white),radio_bg=colors.gray} auto_div.line_break() - PushButton{parent=auto_div,text="SET",x=4,min_width=5,fg_bg=cpair(colors.black,colors.white),active_fg_bg=cpair(colors.white,colors.gray),callback=function()end} + local function set_group() unit.set_group(group.get_value() - 1) end + + local set_grp_btn = PushButton{parent=auto_div,text="SET",x=4,min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),dis_fg_bg=cpair(colors.gray,colors.white),callback=set_group} auto_div.line_break() TextBox{parent=auto_div,text="Prio. Group",height=1,width=11,fg_bg=style.label} local auto_grp = TextBox{parent=auto_div,text="Manual",height=1,width=11,fg_bg=bw_fg_bg} + r_ps.subscribe("auto_group", auto_grp.set_value) + auto_div.line_break() - local a_prm = IndicatorLight{parent=auto_div,label="Active",x=2,colors=cpair(colors.green,colors.gray)} + local a_act = IndicatorLight{parent=auto_div,label="Active",x=2,colors=cpair(colors.green,colors.gray)} local a_stb = IndicatorLight{parent=auto_div,label="Standby",x=2,colors=cpair(colors.white,colors.gray)} + r_ps.subscribe("status", function (active) + if unit.annunciator.AutoControl then + a_act.update(active) + a_stb.update(not active) + else + a_act.update(false) + a_stb.update(false) + end + end) + + -- enable and disable controls based on auto control state (start button is handled separately) + r_ps.subscribe("AutoControl", function (auto_active) + start_button_en_check() + + if auto_active then + burn_rate.disable() + set_burn_btn.disable() + set_grp_btn.disable() + a_act.update(unit.reactor_data.mek_status.status == true) + a_stb.update(unit.reactor_data.mek_status.status == false) + else + burn_rate.enable() + set_burn_btn.enable() + set_grp_btn.enable() + a_act.update(false) + a_stb.update(false) + end + end) + return main end diff --git a/graphics/elements/controls/push_button.lua b/graphics/elements/controls/push_button.lua index 018b0e9..8cb89c9 100644 --- a/graphics/elements/controls/push_button.lua +++ b/graphics/elements/controls/push_button.lua @@ -10,6 +10,7 @@ local element = require("graphics.element") ---@field callback function function to call on touch ---@field min_width? integer text length + 2 if omitted ---@field active_fg_bg? cpair foreground/background colors when pressed +---@field dis_fg_bg? cpair foreground/background colors when disabled ---@field parent graphics_element ---@field id? string element id ---@field x? integer 1 if omitted @@ -61,8 +62,10 @@ local function push_button(args) -- show as unpressed in 0.25 seconds tcd.dispatch(0.25, function () e.value = false - e.window.setTextColor(e.fg_bg.fgd) - e.window.setBackgroundColor(e.fg_bg.bkg) + if e.enabled then + e.window.setTextColor(e.fg_bg.fgd) + e.window.setBackgroundColor(e.fg_bg.bkg) + end draw() end) end @@ -78,6 +81,26 @@ local function push_button(args) if val then e.handle_touch(core.events.touch("", 1, 1)) end end + -- show butten as enabled + function e.enable() + if args.dis_fg_bg ~= nil then + e.value = false + e.window.setTextColor(e.fg_bg.fgd) + e.window.setBackgroundColor(e.fg_bg.bkg) + draw() + end + end + + -- show button as disabled + function e.disable() + if args.dis_fg_bg ~= nil then + e.value = false + e.window.setTextColor(args.dis_fg_bg.fgd) + e.window.setBackgroundColor(args.dis_fg_bg.bkg) + draw() + end + end + -- initial draw draw() diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index d554fca..7486346 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -40,6 +40,7 @@ function facility.new(num_reactors, cooling_conf) units = {}, induction = {}, redstone = {}, + status_text = { "IDLE", "control inactive" }, -- process control mode = PROCESS.INACTIVE, last_mode = PROCESS.INACTIVE, @@ -103,36 +104,42 @@ function facility.new(num_reactors, cooling_conf) local unallocated = math.floor(burn_rate * 10) -- go through alll priority groups - for i = 1, #self.prio_defs and (unallocated > 0) do + for i = 1, #self.prio_defs do local units = self.prio_defs[i] - local split = math.floor(unallocated / #units) - local splits = {} - for u = 1, #units do splits[u] = split end - splits[#units] = splits[#units] + (unallocated % #units) + if #units > 0 then + local split = math.floor(unallocated / #units) - -- go through all reactor units in this group - for u = 1, #units do - local ctl = units[u].get_control_inf() ---@type unit_control - local last = ctl.br10 + local splits = {} + for u = 1, #units do splits[u] = split end + splits[#units] = splits[#units] + (unallocated % #units) - if splits[u] <= ctl.lim_br10 then - ctl.br10 = splits[u] - else - ctl.br10 = ctl.lim_br10 + -- go through all reactor units in this group + for u = 1, #units do + local ctl = units[u].get_control_inf() ---@type unit_control + local last = ctl.br10 - if u < #units then - local remaining = #units - u - split = math.floor(unallocated / remaining) - for x = (u + 1), #units do splits[x] = split end - splits[#units] = splits[#units] + (unallocated % remaining) + if splits[u] <= ctl.lim_br10 then + ctl.br10 = splits[u] + else + ctl.br10 = ctl.lim_br10 + + if u < #units then + local remaining = #units - u + split = math.floor(unallocated / remaining) + for x = (u + 1), #units do splits[x] = split end + splits[#units] = splits[#units] + (unallocated % remaining) + end end + + unallocated = unallocated - ctl.br10 + + if last ~= ctl.br10 then units[u].a_commit_br10(ramp) end end - - unallocated = unallocated - ctl.br10 - - if last ~= ctl.br10 then units[u].a_commit_br10(ramp) end end + + -- stop if fully allocated + if unallocated <= 0 then break end end end @@ -216,19 +223,24 @@ function facility.new(num_reactors, cooling_conf) ) for _, u in pairs(self.prio_defs[i]) do - blade_count = blade_count + u.get_db().blade_count + blade_count = blade_count + u.get_control_inf().blade_count u.a_engage() self.max_burn_combined = self.max_burn_combined + (u.get_control_inf().lim_br10 / 10.0) end end self.charge_conversion = blade_count * POWER_PER_BLADE + + log.debug(util.c("FAC: starting auto control: chg_conv = ", self.charge_conversion, ", blade_count = ", blade_count, ", max_burn = ", self.max_burn_combined)) elseif self.mode == PROCESS.INACTIVE then for i = 1, #self.prio_defs do for _, u in pairs(self.prio_defs[i]) do u.a_disengage() end end + + self.status_text = { "IDLE", "control disengaged" } + log.debug("FAC: disengaging auto control (now inactive)") end self.initial_ramp = true @@ -241,30 +253,43 @@ function facility.new(num_reactors, cooling_conf) -- run units at their last configured set point if state_changed then self.time_start = now + ---@todo will still need to ramp? + log.debug(util.c("FAC: SIMPLE mode first call completed")) end elseif self.mode == PROCESS.BURN_RATE then -- a total aggregate burn rate if state_changed then -- nothing special to do + self.status_text = { "BURN RATE MODE", "starting up" } + log.debug(util.c("FAC: BURN_RATE mode first call completed")) elseif self.waiting_on_ramp and _all_units_ramped() then self.waiting_on_ramp = false self.time_start = now + self.status_text = { "BURN RATE MODE", "running" } + log.debug(util.c("FAC: BURN_RATE mode initial ramp completed")) end if not self.waiting_on_ramp then _allocate_burn_rate(self.burn_target, self.initial_ramp) + + if self.initial_ramp then + self.status_text = { "BURN RATE MODE", "ramping reactors" } + self.waiting_on_ramp = true + log.debug(util.c("FAC: BURN_RATE mode allocated initial ramp")) + end end elseif self.mode == PROCESS.CHARGE then -- target a level of charge local error = (self.charge_target - avg_charge) / self.charge_conversion if state_changed then - -- nothing special to do + log.debug(util.c("FAC: CHARGE mode first call completed")) elseif self.waiting_on_ramp and _all_units_ramped() then self.waiting_on_ramp = false self.time_start = now self.accumulator = 0 + log.debug(util.c("FAC: CHARGE mode initial ramp completed")) end if not self.waiting_on_ramp then @@ -311,11 +336,13 @@ function facility.new(num_reactors, cooling_conf) local sp_r = util.round(setpoint * 10.0) / 10.0 _allocate_burn_rate(sp_r, true) + log.debug(util.c("FAC: GEN_RATE mode first call completed")) elseif self.waiting_on_ramp and _all_units_ramped() then self.waiting_on_ramp = false self.time_start = now self.accumulator = 0 + log.debug(util.c("FAC: GEN_RATE mode initial ramp completed")) end if not self.waiting_on_ramp then @@ -346,6 +373,9 @@ function facility.new(num_reactors, cooling_conf) _allocate_burn_rate(sp_c, false) end + elseif self.mode ~= PROCESS.INACTIVE then + log.error(util.c("FAC: unsupported process mode ", self.mode, ", switching to inactive")) + self.mode = PROCESS.INACTIVE end ------------------------------ @@ -389,6 +419,9 @@ function facility.new(num_reactors, cooling_conf) self.ascram = true end end + + -- update last mode + self.last_mode = self.mode end -- call the update function of all units in the facility @@ -429,8 +462,9 @@ function facility.new(num_reactors, cooling_conf) -- only allow changes if not running if self.mode == PROCESS.INACTIVE then - if (type(config.mode) == "number") and (config.mode > PROCESS.INACTIVE) and (config.mode <= PROCESS.SIMPLE) then + if (type(config.mode) == "number") and (config.mode > PROCESS.INACTIVE) and (config.mode <= PROCESS.GEN_RATE) then self.mode_set = config.mode + log.debug("SET MODE " .. config.mode) end if (type(config.burn_target) == "number") and config.burn_target >= 0.1 then @@ -489,7 +523,7 @@ function facility.new(num_reactors, cooling_conf) util.filter_table(self.prio_defs[old_group], function (u) return u.get_id() ~= unit_id end) end - self.group_map[unit] = group + self.group_map[unit_id] = group -- add to group if not independent if group > 0 then @@ -519,7 +553,9 @@ function facility.new(num_reactors, cooling_conf) self.mode, self.waiting_on_ramp, self.ascram, - self.ascram_reason + self.status_text[1], + self.status_text[2], + self.group_map } end diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index e084fed..9ae4277 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -387,6 +387,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) self.db.annunciator.AutoControl = false if self.plc_i ~= nil then self.plc_i.auto_lock(false) + self.db.control.br10 = 0 end end diff --git a/supervisor/session/unitlogic.lua b/supervisor/session/unitlogic.lua index 0531038..2924e6f 100644 --- a/supervisor/session/unitlogic.lua +++ b/supervisor/session/unitlogic.lua @@ -194,7 +194,7 @@ function logic.update_annunciator(self) local max_water_return_rate = 0 -- recompute blade count on the chance that it may have changed - self.db.blade_count = 0 + self.db.control.blade_count = 0 -- go through turbines for stats and online for i = 1, #self.turbines do @@ -204,7 +204,7 @@ function logic.update_annunciator(self) total_flow_rate = total_flow_rate + turbine.state.flow_rate total_input_rate = total_input_rate + turbine.state.steam_input_rate max_water_return_rate = max_water_return_rate + turbine.build.max_water_output - self.db.blade_count = self.db.blade_count + turbine.build.blades + self.db.control.blade_count = self.db.control.blade_count + turbine.build.blades self.db.annunciator.TurbineOnline[session.get_device_idx()] = true end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index b284a35..ce7f377 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.9.7" +local SUPERVISOR_VERSION = "beta-v0.9.8" local print = util.print local println = util.println From 846f9685ad7815286b06d59855da233d58d2a1c5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 2 Feb 2023 20:17:23 -0500 Subject: [PATCH 477/587] #148 fixed burn rate ramping, adjusted auto burn rate ramping --- reactor-plc/plc.lua | 17 ++++++++++++++--- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 8 ++++---- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 468d190..7381c00 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -824,9 +824,12 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co if rps.is_active() then if rps.get_runtime() > AUTO_TOGGLE_DELAY_MS then -- auto scram to disable + log.debug("AUTO: stopping the reactor to meet 0.0 burn rate") if rps.scram() then ack = AUTO_ACK.ZERO_DIS_OK self.auto_last_disable = util.time_ms() + else + log.debug("AUTO: automatic reactor stop failed") end else -- too soon to disable @@ -838,24 +841,32 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co elseif burn_rate <= self.max_burn_rate then if not rps.is_active() then -- activate the reactor - if not rps.auto_activate() then - log.debug("automatic reactor activation failed") + log.debug("AUTO: activating the reactor") + if rps.auto_activate() then + self.reactor.setBurnRate(0.1) + if self.reactor.__p_is_faulted() then + log.debug("AUTO: failed to reset burn rate on auto activation") + end + else + log.debug("AUTO: automatic reactor activation failed") end end -- if active, set/ramp burn rate if rps.is_active() then if ramp then + log.debug(util.c("AUTO: setting burn rate ramp to ", burn_rate)) setpoints.burn_rate_en = true setpoints.burn_rate = burn_rate ack = AUTO_ACK.RAMP_SET_OK else + log.debug(util.c("AUTO: setting burn rate directly to ", burn_rate)) self.reactor.setBurnRate(burn_rate) ack = util.trinary(self.reactor.__p_is_faulted(), AUTO_ACK.FAIL, AUTO_ACK.DIRECT_SET_OK) end end else - log.debug(burn_rate .. " rate outside of 0 < x <= " .. self.max_burn_rate) + log.debug(util.c(burn_rate, " rate outside of 0 < x <= ", self.max_burn_rate)) end end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 427f8f7..cc7dbba 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.10.0" +local R_PLC_VERSION = "beta-v0.10.1" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index b1f35d0..d503bcf 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -601,7 +601,7 @@ function threads.thread__setpoint_control(smem) ---@diagnostic disable-next-line: need-check-nil reactor.setBurnRate(setpoints.burn_rate) else - log.debug("starting burn rate ramp from " .. last_sp_burn .. "mB/t to " .. setpoints.burn_rate .. "mB/t") + log.debug("starting burn rate ramp from " .. last_sp_burn .. " mB/t to " .. setpoints.burn_rate .. " mB/t") running = true end @@ -623,19 +623,19 @@ function threads.thread__setpoint_control(smem) local current_burn_rate = reactor.getBurnRate() -- we yielded, check enable again - if setpoints.burn_rate_en and (current_burn_rate ~= ppm.ACCESS_FAULT) and (current_burn_rate ~= setpoints.burn_rate) then + if setpoints.burn_rate_en and (type(current_burn_rate) == "number") and (current_burn_rate ~= setpoints.burn_rate) then -- calculate new burn rate local new_burn_rate = current_burn_rate if setpoints.burn_rate > current_burn_rate then -- need to ramp up - local new_burn_rate = current_burn_rate + (BURN_RATE_RAMP_mB_s * min_elapsed_s) + new_burn_rate = current_burn_rate + (BURN_RATE_RAMP_mB_s * min_elapsed_s) if new_burn_rate > setpoints.burn_rate then new_burn_rate = setpoints.burn_rate end else -- need to ramp down - local new_burn_rate = current_burn_rate - (BURN_RATE_RAMP_mB_s * min_elapsed_s) + new_burn_rate = current_burn_rate - (BURN_RATE_RAMP_mB_s * min_elapsed_s) if new_burn_rate < setpoints.burn_rate then new_burn_rate = setpoints.burn_rate end From 5721231ffd4aed6f3fdd1978942003f911967ffd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 2 Feb 2023 22:04:26 -0500 Subject: [PATCH 478/587] #148 fixed burn rate ramping again for real this time --- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 42 +++++++++++++++++------------------------ 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index cc7dbba..2ac2d6f 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.10.1" +local R_PLC_VERSION = "beta-v0.10.2" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index d503bcf..dea3955 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -578,8 +578,6 @@ function threads.thread__setpoint_control(smem) local last_update = util.time() local running = false - local last_sp_burn = 0.0 - -- do not use the actual elapsed time, it could spike -- we do not want to have big jumps as that is what we are trying to avoid in the first place local min_elapsed_s = SP_CTRL_SLEEP / 1000.0 @@ -591,23 +589,23 @@ function threads.thread__setpoint_control(smem) -- get reactor, may have changed do to disconnect/reconnect local reactor = plc_dev.reactor - if plc_state.init_ok and not plc_state.no_reactor then + if plc_state.init_ok and (not plc_state.no_reactor) then -- check if we should start ramping - if setpoints.burn_rate_en and setpoints.burn_rate ~= last_sp_burn then - if rps.is_active() then - if math.abs(setpoints.burn_rate - last_sp_burn) <= 5 then - -- update without ramp if <= 5 mB/t change - log.debug("setting burn rate directly to " .. setpoints.burn_rate .. "mB/t") + if setpoints.burn_rate_en then +---@diagnostic disable-next-line: need-check-nil + local cur_burn_rate = reactor.getBurnRate() + + if (setpoints.burn_rate ~= cur_burn_rate) and rps.is_active() then + -- update without ramp if <= 2.5 mB/t change + running = math.abs(setpoints.burn_rate - cur_burn_rate) > 2.5 + + if running then + log.debug("SPCTL: starting burn rate ramp from " .. cur_burn_rate .. " mB/t to " .. setpoints.burn_rate .. " mB/t") + else + log.debug("SPCTL: setting burn rate directly to " .. setpoints.burn_rate .. "mB/t") ---@diagnostic disable-next-line: need-check-nil reactor.setBurnRate(setpoints.burn_rate) - else - log.debug("starting burn rate ramp from " .. last_sp_burn .. " mB/t to " .. setpoints.burn_rate .. " mB/t") - running = true end - - last_sp_burn = setpoints.burn_rate - else - last_sp_burn = 0.0 end end @@ -630,25 +628,19 @@ function threads.thread__setpoint_control(smem) if setpoints.burn_rate > current_burn_rate then -- need to ramp up new_burn_rate = current_burn_rate + (BURN_RATE_RAMP_mB_s * min_elapsed_s) - if new_burn_rate > setpoints.burn_rate then - new_burn_rate = setpoints.burn_rate - end + if new_burn_rate > setpoints.burn_rate then new_burn_rate = setpoints.burn_rate end else -- need to ramp down new_burn_rate = current_burn_rate - (BURN_RATE_RAMP_mB_s * min_elapsed_s) - if new_burn_rate < setpoints.burn_rate then - new_burn_rate = setpoints.burn_rate - end + if new_burn_rate < setpoints.burn_rate then new_burn_rate = setpoints.burn_rate end end + running = running or (new_burn_rate ~= setpoints.burn_rate) + -- set the burn rate ---@diagnostic disable-next-line: need-check-nil reactor.setBurnRate(new_burn_rate) - - running = running or (new_burn_rate ~= setpoints.burn_rate) end - else - last_sp_burn = 0.0 end end end From eb8aab175fe40908953b7b3700e06c65e763648a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 2 Feb 2023 22:51:21 -0500 Subject: [PATCH 479/587] #148 okay turns out that variable was important, ramping now works as intended, correctly --- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 2ac2d6f..f12bfc0 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.10.2" +local R_PLC_VERSION = "beta-v0.10.3" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index dea3955..69d2ff1 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -578,6 +578,8 @@ function threads.thread__setpoint_control(smem) local last_update = util.time() local running = false + local last_burn_sp = 0.0 + -- do not use the actual elapsed time, it could spike -- we do not want to have big jumps as that is what we are trying to avoid in the first place local min_elapsed_s = SP_CTRL_SLEEP / 1000.0 @@ -591,18 +593,20 @@ function threads.thread__setpoint_control(smem) if plc_state.init_ok and (not plc_state.no_reactor) then -- check if we should start ramping - if setpoints.burn_rate_en then + if setpoints.burn_rate_en and (setpoints.burn_rate ~= last_burn_sp) then ---@diagnostic disable-next-line: need-check-nil local cur_burn_rate = reactor.getBurnRate() - if (setpoints.burn_rate ~= cur_burn_rate) and rps.is_active() then + if (type(cur_burn_rate) == "number") and (setpoints.burn_rate ~= cur_burn_rate) and rps.is_active() then + last_burn_sp = setpoints.burn_rate + -- update without ramp if <= 2.5 mB/t change running = math.abs(setpoints.burn_rate - cur_burn_rate) > 2.5 if running then - log.debug("SPCTL: starting burn rate ramp from " .. cur_burn_rate .. " mB/t to " .. setpoints.burn_rate .. " mB/t") + log.debug(util.c("SPCTL: starting burn rate ramp from ", cur_burn_rate, " mB/t to ", setpoints.burn_rate, " mB/t")) else - log.debug("SPCTL: setting burn rate directly to " .. setpoints.burn_rate .. "mB/t") + log.debug(util.c("SPCTL: setting burn rate directly to ", setpoints.burn_rate, " mB/t")) ---@diagnostic disable-next-line: need-check-nil reactor.setBurnRate(setpoints.burn_rate) end @@ -641,8 +645,19 @@ function threads.thread__setpoint_control(smem) ---@diagnostic disable-next-line: need-check-nil reactor.setBurnRate(new_burn_rate) end + else + log.debug("SPCTL: ramping aborted (reactor inactive)") + setpoints.burn_rate_en = false end end + elseif setpoints.burn_rate_en then + log.debug(util.c("SPCTL: ramping completed (setpoint of ", setpoints.burn_rate, " mB/t)")) + setpoints.burn_rate_en = false + end + + -- if ramping completed or was aborted, reset last burn setpoint so that if it is requested again it will be re-attempted + if not setpoints.burn_rate_en then + last_burn_sp = 0 end end From 2e78aa895db9b7f23afec1bc86d9f8076092a68d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 2 Feb 2023 22:58:51 -0500 Subject: [PATCH 480/587] #101 #102 burn rate process mode functional --- coordinator/iocontrol.lua | 22 +++++---- coordinator/startup.lua | 2 +- coordinator/ui/components/processctl.lua | 51 +++++++++++++++++++++ coordinator/ui/components/unit_detail.lua | 17 +++---- coordinator/ui/components/unit_overview.lua | 2 - coordinator/ui/layout/main_view.lua | 11 +++-- supervisor/session/facility.lua | 25 +++++++++- supervisor/session/plc.lua | 1 + supervisor/session/unit.lua | 17 +++++-- supervisor/session/unitlogic.lua | 31 +++++++++++++ supervisor/startup.lua | 2 +- 11 files changed, 148 insertions(+), 33 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index da12a50..ecf677d 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -23,9 +23,11 @@ local io = {} function iocontrol.init(conf, comms) ---@class ioctl_facility io.facility = { + auto_ready = false, auto_active = false, auto_ramping = false, auto_scram = false, + ---@todo not currently used or set auto_scram_cause = "ok", ---@type auto_scram_cause num_units = conf.num_units, ---@type integer @@ -271,19 +273,21 @@ function iocontrol.update_facility_status(status) local ctl_status = status[1] if type(ctl_status) == "table" then - fac.auto_active = ctl_status[1] > 0 - fac.auto_ramping = ctl_status[2] - fac.auto_scram = ctl_status[3] - fac.status_line_1 = ctl_status[4] - fac.status_line_2 = ctl_status[5] + fac.auto_ready = ctl_status[1] + fac.auto_active = ctl_status[2] > 0 + fac.auto_ramping = ctl_status[3] + fac.auto_scram = ctl_status[4] + fac.status_line_1 = ctl_status[5] + fac.status_line_2 = ctl_status[6] + fac.ps.publish("auto_ready", fac.auto_ready) fac.ps.publish("auto_active", fac.auto_active) fac.ps.publish("auto_ramping", fac.auto_ramping) fac.ps.publish("auto_scram", fac.auto_scram) fac.ps.publish("status_line_1", fac.status_line_1) fac.ps.publish("status_line_2", fac.status_line_2) - local group_map = ctl_status[6] + local group_map = ctl_status[7] if (type(group_map) == "table") and (#group_map == fac.num_units) then local names = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } @@ -634,10 +638,12 @@ function iocontrol.update_unit_statuses(statuses) local unit_state = status[5] if type(unit_state) == "table" then - if #unit_state == 3 then + if #unit_state == 5 then unit.reactor_ps.publish("U_StatusLine1", unit_state[1]) unit.reactor_ps.publish("U_StatusLine2", unit_state[2]) - unit.reactor_ps.publish("U_WasteMode", unit_state[3]) + unit.reactor_ps.publish("U_WasteMode", unit_state[3]) + unit.reactor_ps.publish("U_AutoReady", unit_state[4]) + unit.reactor_ps.publish("U_AutoDegraded", unit_state[5]) else log.debug(log_header .. "unit state length mismatch") end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index c1a8232..0546344 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.11" +local COORDINATOR_VERSION = "beta-v0.8.12" local print = util.print local println = util.println diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 91f888d..9687ccf 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -30,6 +30,8 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN local cpair = core.graphics.cpair local border = core.graphics.border +local period = core.flasher.PERIOD + -- new process control view ---@param root graphics_element parent ---@param x integer top left x @@ -48,6 +50,22 @@ local function new_view(root, x, y) facility.scram_ack = scram.on_response + local auto_act = IndicatorLight{parent=main,y=5,label="Auto Active",colors=cpair(colors.green,colors.gray)} + local auto_ramp = IndicatorLight{parent=main,label="Auto Ramping",colors=cpair(colors.white,colors.gray),flash=true,period=period.BLINK_250_MS} + local auto_scram = IndicatorLight{parent=main,label="Auto SCRAM",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + + facility.ps.subscribe("auto_active", auto_act.update) + facility.ps.subscribe("auto_ramping", auto_ramp.update) + facility.ps.subscribe("auto_scram", auto_scram.update) + + main.line_break() + + local _ = IndicatorLight{parent=main,label="Unit Off-line",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_1000_MS} + local _ = IndicatorLight{parent=main,label="Unit RPS Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local _ = IndicatorLight{parent=main,label="Unit Critical Alarm",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local _ = IndicatorLight{parent=main,label="High Charge Level",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + + --------------------- -- process control -- --------------------- @@ -134,6 +152,9 @@ local function new_view(root, x, y) local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=31,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=bw_fg_bg} local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data",width=31,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} + facility.ps.subscribe("status_line_1", stat_line_1.set_value) + facility.ps.subscribe("status_line_2", stat_line_2.set_value) + local auto_controls = Div{parent=proc,x=1,y=20,width=31,height=5,fg_bg=cpair(colors.gray,colors.white)} -- save the automatic process control configuration without starting @@ -160,6 +181,36 @@ local function new_view(root, x, y) function facility.save_cfg_ack(ack) tcd.dispatch(0.2, function () save.on_response(ack) end) end + + facility.ps.subscribe("auto_ready", function (ready) + if ready and (not facility.auto_active) then start.enable() else start.disable() end + end) + + facility.ps.subscribe("auto_active", function (active) + if active then + b_target.disable() + c_target.disable() + g_target.disable() + + mode.disable() + start.disable() + + for i = 1, #rate_limits do + rate_limits[i].disable() + end + else + b_target.enable() + c_target.enable() + g_target.enable() + + mode.enable() + if facility.auto_ready then start.enable() end + + for i = 1, #rate_limits do + rate_limits[i].enable() + end + end + end) end return new_view diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index c3950e9..cb87894 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -474,17 +474,14 @@ local function init(parent, id) auto_div.line_break() - local a_act = IndicatorLight{parent=auto_div,label="Active",x=2,colors=cpair(colors.green,colors.gray)} - local a_stb = IndicatorLight{parent=auto_div,label="Standby",x=2,colors=cpair(colors.white,colors.gray)} + local a_rdy = IndicatorLight{parent=auto_div,label="Ready",x=2,colors=cpair(colors.green,colors.gray)} + local a_stb = IndicatorLight{parent=auto_div,label="Standby",x=2,colors=cpair(colors.white,colors.gray),flash=true,period=period.BLINK_1000_MS} + r_ps.subscribe("U_AutoReady", a_rdy.update) + + -- update standby indicator r_ps.subscribe("status", function (active) - if unit.annunciator.AutoControl then - a_act.update(active) - a_stb.update(not active) - else - a_act.update(false) - a_stb.update(false) - end + a_stb.update(unit.annunciator.AutoControl and (not active)) end) -- enable and disable controls based on auto control state (start button is handled separately) @@ -495,13 +492,11 @@ local function init(parent, id) burn_rate.disable() set_burn_btn.disable() set_grp_btn.disable() - a_act.update(unit.reactor_data.mek_status.status == true) a_stb.update(unit.reactor_data.mek_status.status == false) else burn_rate.enable() set_burn_btn.enable() set_grp_btn.enable() - a_act.update(false) a_stb.update(false) end end) diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 56166ae..b28b43f 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -16,8 +16,6 @@ local TextBox = require("graphics.elements.textbox") local TEXT_ALIGN = core.graphics.TEXT_ALIGN -local cpair = core.graphics.cpair -local border = core.graphics.border local pipe = core.graphics.pipe -- make a new unit overview window diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index 59ac653..f1b68ad 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -49,26 +49,31 @@ local function init(monitor) local uo_1, uo_2, uo_3, uo_4 ---@type graphics_element local cnc_y_start = 3 + local row_1_height = 0 -- unit overviews if facility.num_units >= 1 then uo_1 = unit_overview(main, 2, 3, units[1]) - cnc_y_start = cnc_y_start + uo_1.height() + 1 + row_1_height = uo_1.height() end if facility.num_units >= 2 then uo_2 = unit_overview(main, 84, 3, units[2]) + row_1_height = math.max(row_1_height, uo_2.height()) end + cnc_y_start = cnc_y_start + row_1_height + 1 + if facility.num_units >= 3 then -- base offset 3, spacing 1, max height of units 1 and 2 - local row_2_offset = 3 + 1 + math.max(uo_1.height(), uo_2.height()) + local row_2_offset = cnc_y_start uo_3 = unit_overview(main, 2, row_2_offset, units[3]) - cnc_y_start = cnc_y_start + uo_3.height() + 1 + cnc_y_start = row_2_offset + uo_3.height() + 1 if facility.num_units == 4 then uo_4 = unit_overview(main, 84, row_2_offset, units[4]) + cnc_y_start = math.max(cnc_y_start, row_2_offset + uo_4.height() + 1) end end diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index 7486346..ebd417a 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -40,8 +40,9 @@ function facility.new(num_reactors, cooling_conf) units = {}, induction = {}, redstone = {}, - status_text = { "IDLE", "control inactive" }, + status_text = { "START UP", "initializing..." }, -- process control + units_ready = false, mode = PROCESS.INACTIVE, last_mode = PROCESS.INACTIVE, mode_set = PROCESS.SIMPLE, @@ -234,7 +235,10 @@ function facility.new(num_reactors, cooling_conf) log.debug(util.c("FAC: starting auto control: chg_conv = ", self.charge_conversion, ", blade_count = ", blade_count, ", max_burn = ", self.max_burn_combined)) elseif self.mode == PROCESS.INACTIVE then for i = 1, #self.prio_defs do + -- SCRAM reactors and disengage auto control + -- use manual SCRAM since inactive was requested, and automatic SCRAM trips an alarm for _, u in pairs(self.prio_defs[i]) do + u.scram() u.a_disengage() end end @@ -249,7 +253,21 @@ function facility.new(num_reactors, cooling_conf) self.initial_ramp = false end - if self.mode == PROCESS.SIMPLE then + if self.mode == PROCESS.INACTIVE then + -- check if we are ready to start when that time comes + self.units_ready = true + for i = 1, #self.prio_defs do + for _, u in pairs(self.prio_defs[i]) do + self.units_ready = self.units_ready and u.get_control_inf().ready + end + end + + if self.units_ready then + self.status_text = { "IDLE", "control disengaged" } + else + self.status_text = { "NOT READY", "assigned units not ready" } + end + elseif self.mode == PROCESS.SIMPLE then -- run units at their last configured set point if state_changed then self.time_start = now @@ -504,6 +522,8 @@ function facility.new(num_reactors, cooling_conf) ready = false end + ready = ready and self.units_ready + if ready then self.mode = self.mode_set end end @@ -550,6 +570,7 @@ function facility.new(num_reactors, cooling_conf) -- get automatic process control status function public.get_control_status() return { + self.units_ready, self.mode, self.waiting_on_ramp, self.ascram, diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index ecd6cc6..c36d449 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -448,6 +448,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) self.acks.burn_rate = ack ~= PLC_AUTO_ACK.FAIL + ---@todo implement error handling here if ack == PLC_AUTO_ACK.FAIL then elseif ack == PLC_AUTO_ACK.DIRECT_SET_OK then elseif ack == PLC_AUTO_ACK.RAMP_SET_OK then diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 9ae4277..742c5ce 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -174,6 +174,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- fields for facility control ---@class unit_control control = { + ready = false, + degraded = false, blade_count = 0, br10 = 0, lim_br10 = 0 @@ -398,7 +400,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) if self.plc_i ~= nil then self.plc_i.auto_set_burn(self.db.control.br10 / 10, ramp) - if ramp then self.ramp_target_br10 = self.db.control.br10 / 10 end + if ramp then self.ramp_target_br10 = self.db.control.br10 end end end end @@ -407,8 +409,9 @@ function unit.new(for_reactor, num_boilers, num_turbines) ---@return boolean complete function public.a_ramp_complete() if self.plc_i ~= nil then - return (math.floor(self.plc_i.get_db().mek_status.burn_rate * 10) == self.ramp_target_br10) or (self.ramp_target_br10 == 0) - else return false end + local cur_rate = math.floor(self.plc_i.get_db().mek_status.burn_rate * 10) + return (cur_rate == self.ramp_target_br10) or (self.ramp_target_br10 == 0) + else return true end end -- perform an automatic SCRAM @@ -428,6 +431,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) if self.plc_s ~= nil and not self.plc_s.open then self.plc_s = nil self.plc_i = nil + self.db.control.br10 = 0 self.db.control.lim_br10 = 0 end @@ -436,6 +440,9 @@ function unit.new(for_reactor, num_boilers, num_turbines) _unlink_disconnected_units(self.turbines) _unlink_disconnected_units(self.redstone) + -- update degraded state for auto control + self.db.control.degraded = (#self.boilers ~= num_boilers) or (#self.turbines ~= num_turbines) or (self.plc_i == nil) + -- update deltas _dt__compute_all() @@ -606,9 +613,9 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- get information required for automatic reactor control function public.get_control_inf() return self.db.control end - -- get unit state (currently only waste mode) + -- get unit state function public.get_state() - return { self.status_text[1], self.status_text[2], self.waste_mode } + return { self.status_text[1], self.status_text[2], self.waste_mode, self.db.control.ready, self.db.control.degraded } end -- get the reactor ID diff --git a/supervisor/session/unitlogic.lua b/supervisor/session/unitlogic.lua index 2924e6f..4354fde 100644 --- a/supervisor/session/unitlogic.lua +++ b/supervisor/session/unitlogic.lua @@ -38,9 +38,17 @@ function logic.update_annunciator(self) -- check PLC status self.db.annunciator.PLCOnline = self.plc_i ~= nil + local plc_ready = self.db.annunciator.PLCOnline + if self.db.annunciator.PLCOnline then local plc_db = self.plc_i.get_db() + -- update ready state + -- - can't be tripped + -- - must have received status at least once + -- - must have received struct at least once + plc_ready = (not plc_db.rps_tripped) and (plc_db.last_status_update > 0) and (plc_db.mek_struct.length > 0) + -- update auto control limit if (self.db.control.lim_br10 == 0) or ((self.db.control.lim_br10 / 10) > plc_db.mek_struct.max_burn) then self.db.control.lim_br10 = math.floor(plc_db.mek_struct.max_burn * 10) @@ -106,6 +114,8 @@ function logic.update_annunciator(self) -- BOILERS -- ------------- + local boilers_ready = num_boilers == #self.boilers + -- clear boiler online flags for i = 1, num_boilers do self.db.annunciator.BoilerOnline[i] = false end @@ -119,6 +129,14 @@ function logic.update_annunciator(self) local session = self.boilers[i] ---@type unit_session local boiler = session.get_db() ---@type boilerv_session_db + -- update ready state + -- - must be formed + -- - must have received build, state, and tanks at least once + boilers_ready = boilers_ready and boiler.formed and + (boiler.build.last_update > 0) and + (boiler.state.last_update > 0) and + (boiler.tanks.last_update > 0) + total_boil_rate = total_boil_rate + boiler.state.boil_rate boiler_steam_dt_sum = _get_dt(DT_KEYS.BoilerSteam .. self.boilers[i].get_device_idx()) boiler_water_dt_sum = _get_dt(DT_KEYS.BoilerWater .. self.boilers[i].get_device_idx()) @@ -185,6 +203,8 @@ function logic.update_annunciator(self) -- TURBINES -- -------------- + local turbines_ready = num_turbines == #self.turbines + -- clear turbine online flags for i = 1, num_turbines do self.db.annunciator.TurbineOnline[i] = false end @@ -201,6 +221,14 @@ function logic.update_annunciator(self) local session = self.turbines[i] ---@type unit_session local turbine = session.get_db() ---@type turbinev_session_db + -- update ready state + -- - must be formed + -- - must have received build, state, and tanks at least once + turbines_ready = turbines_ready and turbine.formed and + (turbine.build.last_update > 0) and + (turbine.state.last_update > 0) and + (turbine.tanks.last_update > 0) + total_flow_rate = total_flow_rate + turbine.state.flow_rate total_input_rate = total_input_rate + turbine.state.steam_input_rate max_water_return_rate = max_water_return_rate + turbine.build.max_water_output @@ -257,6 +285,9 @@ function logic.update_annunciator(self) local has_steam = db.state.steam_input_rate > 0 or db.tanks.steam_fill > 0.01 self.db.annunciator.TurbineTrip[turbine.get_device_idx()] = has_steam and db.state.flow_rate == 0 end + + -- update auto control ready state for this unit + self.db.control.ready = plc_ready and boilers_ready and turbines_ready end -- update an alarm state given conditions diff --git a/supervisor/startup.lua b/supervisor/startup.lua index ce7f377..f579ec2 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.9.8" +local SUPERVISOR_VERSION = "beta-v0.9.9" local print = util.print local println = util.println From 53e4576547c209c0f065abd103e3612faedf9907 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 2 Feb 2023 23:07:09 -0500 Subject: [PATCH 481/587] some coordinator code cleanup and refactoring --- coordinator/iocontrol.lua | 48 +++---- coordinator/process.lua | 2 +- coordinator/renderer.lua | 3 +- coordinator/sounder.lua | 2 +- coordinator/startup.lua | 2 +- coordinator/ui/components/boiler.lua | 4 +- coordinator/ui/components/imatrix.lua | 3 +- coordinator/ui/components/processctl.lua | 8 +- coordinator/ui/components/reactor.lua | 6 +- coordinator/ui/components/turbine.lua | 3 +- coordinator/ui/components/unit_detail.lua | 136 ++++++++++---------- coordinator/ui/components/unit_overview.lua | 4 +- 12 files changed, 109 insertions(+), 112 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index ecf677d..d1d59a9 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -120,7 +120,7 @@ function iocontrol.init(conf, comms) annunciator = {}, ---@type annunciator - reactor_ps = psil.create(), + unit_ps = psil.create(), reactor_data = {}, ---@type reactor_db boiler_ps_tbl = {}, @@ -208,12 +208,12 @@ function iocontrol.record_unit_builds(builds) if type(build.reactor) == "table" then unit.reactor_data.mek_struct = build.reactor ---@type mek_struct for key, val in pairs(unit.reactor_data.mek_struct) do - unit.reactor_ps.publish(key, val) + unit.unit_ps.publish(key, val) end if (type(unit.reactor_data.mek_struct.length) == "number") and (unit.reactor_data.mek_struct.length ~= 0) and (type(unit.reactor_data.mek_struct.width) == "number") and (unit.reactor_data.mek_struct.width ~= 0) then - unit.reactor_ps.publish("size", { unit.reactor_data.mek_struct.length, unit.reactor_data.mek_struct.width }) + unit.unit_ps.publish("size", { unit.reactor_data.mek_struct.length, unit.reactor_data.mek_struct.width }) end end @@ -292,7 +292,7 @@ function iocontrol.update_facility_status(status) if (type(group_map) == "table") and (#group_map == fac.num_units) then local names = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } for i = 1, #group_map do - io.units[i].reactor_ps.publish("auto_group", names[group_map[i] + 1]) + io.units[i].unit_ps.publish("auto_group", names[group_map[i] + 1]) end end else @@ -402,7 +402,7 @@ function iocontrol.update_unit_statuses(statuses) end if #reactor_status == 0 then - unit.reactor_ps.publish("computed_status", 1) -- disconnected + unit.unit_ps.publish("computed_status", 1) -- disconnected elseif #reactor_status == 3 then local mek_status = reactor_status[1] local rps_status = reactor_status[2] @@ -428,36 +428,36 @@ function iocontrol.update_unit_statuses(statuses) end if unit.reactor_data.mek_status.status then - unit.reactor_ps.publish("computed_status", 5) -- running + unit.unit_ps.publish("computed_status", 5) -- running else if unit.reactor_data.no_reactor then - unit.reactor_ps.publish("computed_status", 3) -- faulted + unit.unit_ps.publish("computed_status", 3) -- faulted elseif not unit.reactor_data.formed then - unit.reactor_ps.publish("computed_status", 2) -- multiblock not formed + unit.unit_ps.publish("computed_status", 2) -- multiblock not formed elseif unit.reactor_data.rps_status.force_dis then - unit.reactor_ps.publish("computed_status", 7) -- reactor force disabled + unit.unit_ps.publish("computed_status", 7) -- reactor force disabled elseif unit.reactor_data.rps_tripped and unit.reactor_data.rps_trip_cause ~= "manual" then - unit.reactor_ps.publish("computed_status", 6) -- SCRAM + unit.unit_ps.publish("computed_status", 6) -- SCRAM else - unit.reactor_ps.publish("computed_status", 4) -- disabled + unit.unit_ps.publish("computed_status", 4) -- disabled end end for key, val in pairs(unit.reactor_data) do if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then - unit.reactor_ps.publish(key, val) + unit.unit_ps.publish(key, val) end end if type(unit.reactor_data.rps_status) == "table" then for key, val in pairs(unit.reactor_data.rps_status) do - unit.reactor_ps.publish(key, val) + unit.unit_ps.publish(key, val) end end if type(unit.reactor_data.mek_status) == "table" then for key, val in pairs(unit.reactor_data.mek_status) do - unit.reactor_ps.publish(key, val) + unit.unit_ps.publish(key, val) end end else @@ -591,7 +591,7 @@ function iocontrol.update_unit_statuses(statuses) unit.turbine_ps_tbl[id].publish(key, trips[id]) end - unit.reactor_ps.publish("TurbineTrip", any) + unit.unit_ps.publish("TurbineTrip", any) elseif key == "BoilerOnline" or key == "HeatingRateLow" or key == "WaterLevelLow" then -- split up array for all boilers for id = 1, #val do @@ -607,7 +607,7 @@ function iocontrol.update_unit_statuses(statuses) log.error(log_header .. "unrecognized table found in annunciator list, this is a bug", true) else -- non-table fields - unit.reactor_ps.publish(key, val) + unit.unit_ps.publish(key, val) end end @@ -622,11 +622,11 @@ function iocontrol.update_unit_statuses(statuses) unit.alarms[id] = state if state == types.ALARM_STATE.TRIPPED or state == types.ALARM_STATE.ACKED then - unit.reactor_ps.publish("Alarm_" .. id, 2) + unit.unit_ps.publish("Alarm_" .. id, 2) elseif state == types.ALARM_STATE.RING_BACK then - unit.reactor_ps.publish("Alarm_" .. id, 3) + unit.unit_ps.publish("Alarm_" .. id, 3) else - unit.reactor_ps.publish("Alarm_" .. id, 1) + unit.unit_ps.publish("Alarm_" .. id, 1) end end else @@ -639,11 +639,11 @@ function iocontrol.update_unit_statuses(statuses) if type(unit_state) == "table" then if #unit_state == 5 then - unit.reactor_ps.publish("U_StatusLine1", unit_state[1]) - unit.reactor_ps.publish("U_StatusLine2", unit_state[2]) - unit.reactor_ps.publish("U_WasteMode", unit_state[3]) - unit.reactor_ps.publish("U_AutoReady", unit_state[4]) - unit.reactor_ps.publish("U_AutoDegraded", unit_state[5]) + unit.unit_ps.publish("U_StatusLine1", unit_state[1]) + unit.unit_ps.publish("U_StatusLine2", unit_state[2]) + unit.unit_ps.publish("U_WasteMode", unit_state[3]) + unit.unit_ps.publish("U_AutoReady", unit_state[4]) + unit.unit_ps.publish("U_AutoDegraded", unit_state[5]) else log.debug(log_header .. "unit state length mismatch") end diff --git a/coordinator/process.lua b/coordinator/process.lua index e1d067f..f509a7b 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -61,7 +61,7 @@ function process.init(iocontrol, comms) for id = 1, math.min(#self.config.limits, self.io.facility.num_units) do local unit = self.io.units[id] ---@type ioctl_unit - unit.reactor_ps.publish("burn_limit", self.config.limits[id]) + unit.unit_ps.publish("burn_limit", self.config.limits[id]) end log.info("PROCESS: loaded auto control settings from coord.settings") diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index e66fb99..0d99db3 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -1,11 +1,12 @@ local log = require("scada-common.log") -local flasher = require("graphics.flasher") local style = require("coordinator.ui.style") local main_view = require("coordinator.ui.layout.main_view") local unit_view = require("coordinator.ui.layout.unit_view") +local flasher = require("graphics.flasher") + local renderer = {} -- render engine diff --git a/coordinator/sounder.lua b/coordinator/sounder.lua index 57174fb..ff0ec17 100644 --- a/coordinator/sounder.lua +++ b/coordinator/sounder.lua @@ -2,9 +2,9 @@ -- Alarm Sounder -- +local log = require("scada-common.log") local types = require("scada-common.types") local util = require("scada-common.util") -local log = require("scada-common.log") local ALARM = types.ALARM local ALARM_STATE = types.ALARM_STATE diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 0546344..3025eb3 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.12" +local COORDINATOR_VERSION = "beta-v0.8.13" local print = util.print local println = util.println diff --git a/coordinator/ui/components/boiler.lua b/coordinator/ui/components/boiler.lua index ee463a9..c4a433b 100644 --- a/coordinator/ui/components/boiler.lua +++ b/coordinator/ui/components/boiler.lua @@ -1,7 +1,7 @@ -local core = require("graphics.core") - local style = require("coordinator.ui.style") +local core = require("graphics.core") + local Rectangle = require("graphics.elements.rectangle") local TextBox = require("graphics.elements.textbox") diff --git a/coordinator/ui/components/imatrix.lua b/coordinator/ui/components/imatrix.lua index 627ea94..62d7dbf 100644 --- a/coordinator/ui/components/imatrix.lua +++ b/coordinator/ui/components/imatrix.lua @@ -1,8 +1,9 @@ -local core = require("graphics.core") local util = require("scada-common.util") local style = require("coordinator.ui.style") +local core = require("graphics.core") + local Div = require("graphics.elements.div") local Rectangle = require("graphics.elements.rectangle") local TextBox = require("graphics.elements.textbox") diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 9687ccf..4bda49f 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -12,8 +12,6 @@ local Div = require("graphics.elements.div") local Rectangle = require("graphics.elements.rectangle") local TextBox = require("graphics.elements.textbox") -local AlarmLight = require("graphics.elements.indicators.alight") -local CoreMap = require("graphics.elements.indicators.coremap") local DataIndicator = require("graphics.elements.indicators.data") local IndicatorLight = require("graphics.elements.indicators.light") local TriIndicatorLight = require("graphics.elements.indicators.trilight") @@ -131,12 +129,12 @@ local function new_view(root, x, y) rate_limits[i] = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg} TextBox{parent=lim_ctl,x=9,y=2,text="mB/t"} - unit.reactor_ps.subscribe("max_burn", rate_limits[i].set_max) - unit.reactor_ps.subscribe("burn_limit", rate_limits[i].set_value) + unit.unit_ps.subscribe("max_burn", rate_limits[i].set_max) + unit.unit_ps.subscribe("burn_limit", rate_limits[i].set_value) local cur_burn = DataIndicator{parent=limit_div,x=9,y=_y+3,label="",format="%7.1f",value=0,unit="mB/t",commas=false,lu_colors=cpair(colors.black,colors.black),width=14,fg_bg=cpair(colors.black,colors.brown)} - unit.reactor_ps.subscribe("act_burn_rate", cur_burn.update) + unit.unit_ps.subscribe("act_burn_rate", cur_burn.update) end ------------------------- diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua index d81d662..f103d76 100644 --- a/coordinator/ui/components/reactor.lua +++ b/coordinator/ui/components/reactor.lua @@ -1,9 +1,7 @@ -local util = require("scada-common.util") +local style = require("coordinator.ui.style") local core = require("graphics.core") -local style = require("coordinator.ui.style") - local Rectangle = require("graphics.elements.rectangle") local TextBox = require("graphics.elements.textbox") @@ -11,8 +9,6 @@ local DataIndicator = require("graphics.elements.indicators.data") local HorizontalBar = require("graphics.elements.indicators.hbar") local StateIndicator = require("graphics.elements.indicators.state") -local TEXT_ALIGN = core.graphics.TEXT_ALIGN - local cpair = core.graphics.cpair local border = core.graphics.border diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua index 5577bf3..db6959c 100644 --- a/coordinator/ui/components/turbine.lua +++ b/coordinator/ui/components/turbine.lua @@ -1,8 +1,9 @@ -local core = require("graphics.core") local util = require("scada-common.util") local style = require("coordinator.ui.style") +local core = require("graphics.core") + local Rectangle = require("graphics.elements.rectangle") local DataIndicator = require("graphics.elements.indicators.data") diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index cb87894..cfaf840 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -60,7 +60,7 @@ local waste_opts = { ---@param id integer local function init(parent, id) local unit = iocontrol.get_db().units[id] ---@type ioctl_unit - local r_ps = unit.reactor_ps + local u_ps = unit.unit_ps local b_ps = unit.boiler_ps_tbl local t_ps = unit.turbine_ps_tbl @@ -77,16 +77,16 @@ local function init(parent, id) ----------------------------- local core_map = CoreMap{parent=main,x=2,y=3,reactor_l=18,reactor_w=18} - r_ps.subscribe("temp", core_map.update) - r_ps.subscribe("size", function (s) core_map.resize(s[1], s[2]) end) + u_ps.subscribe("temp", core_map.update) + u_ps.subscribe("size", function (s) core_map.resize(s[1], s[2]) end) TextBox{parent=main,x=12,y=22,text="Heating Rate",height=1,width=12,fg_bg=style.label} local heating_r = DataIndicator{parent=main,x=12,label="",format="%14.0f",value=0,unit="mB/t",commas=true,lu_colors=lu_cpair,width=19,fg_bg=bw_fg_bg} - r_ps.subscribe("heating_rate", heating_r.update) + u_ps.subscribe("heating_rate", heating_r.update) TextBox{parent=main,x=12,y=25,text="Commanded Burn Rate",height=1,width=19,fg_bg=style.label} local burn_r = DataIndicator{parent=main,x=12,label="",format="%14.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=19,fg_bg=bw_fg_bg} - r_ps.subscribe("burn_rate", burn_r.update) + u_ps.subscribe("burn_rate", burn_r.update) TextBox{parent=main,text="F",x=2,y=22,width=1,height=1,fg_bg=style.label} TextBox{parent=main,text="C",x=4,y=22,width=1,height=1,fg_bg=style.label} @@ -100,12 +100,12 @@ local function init(parent, id) local hcool = VerticalBar{parent=main,x=8,y=23,fg_bg=cpair(colors.white,colors.gray),height=4,width=1} local waste = VerticalBar{parent=main,x=10,y=23,fg_bg=cpair(colors.brown,colors.gray),height=4,width=1} - r_ps.subscribe("fuel_fill", fuel.update) - r_ps.subscribe("ccool_fill", ccool.update) - r_ps.subscribe("hcool_fill", hcool.update) - r_ps.subscribe("waste_fill", waste.update) + u_ps.subscribe("fuel_fill", fuel.update) + u_ps.subscribe("ccool_fill", ccool.update) + u_ps.subscribe("hcool_fill", hcool.update) + u_ps.subscribe("waste_fill", waste.update) - r_ps.subscribe("ccool_type", function (type) + u_ps.subscribe("ccool_type", function (type) if type == "mekanism:sodium" then ccool.recolor(cpair(colors.lightBlue, colors.gray)) else @@ -113,7 +113,7 @@ local function init(parent, id) end end) - r_ps.subscribe("hcool_type", function (type) + u_ps.subscribe("hcool_type", function (type) if type == "mekanism:superheated_sodium" then hcool.recolor(cpair(colors.orange, colors.gray)) else @@ -123,15 +123,15 @@ local function init(parent, id) TextBox{parent=main,x=32,y=22,text="Core Temp",height=1,width=9,fg_bg=style.label} local core_temp = DataIndicator{parent=main,x=32,label="",format="%11.2f",value=0,unit="K",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} - r_ps.subscribe("temp", core_temp.update) + u_ps.subscribe("temp", core_temp.update) TextBox{parent=main,x=32,y=25,text="Burn Rate",height=1,width=9,fg_bg=style.label} local act_burn_r = DataIndicator{parent=main,x=32,label="",format="%8.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} - r_ps.subscribe("act_burn_rate", act_burn_r.update) + u_ps.subscribe("act_burn_rate", act_burn_r.update) TextBox{parent=main,x=32,y=28,text="Damage",height=1,width=6,fg_bg=style.label} local damage_p = DataIndicator{parent=main,x=32,label="",format="%11.0f",value=0,unit="%",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} - r_ps.subscribe("damage", damage_p.update) + u_ps.subscribe("damage", damage_p.update) ---@todo radiation monitor TextBox{parent=main,x=32,y=31,text="Radiation",height=1,width=21,fg_bg=style.label} @@ -145,8 +145,8 @@ local function init(parent, id) local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=33,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=bw_fg_bg} local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data",width=33,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} - r_ps.subscribe("U_StatusLine1", stat_line_1.set_value) - r_ps.subscribe("U_StatusLine2", stat_line_2.set_value) + u_ps.subscribe("U_StatusLine1", stat_line_1.set_value) + u_ps.subscribe("U_StatusLine2", stat_line_2.set_value) ----------------- -- annunciator -- @@ -167,10 +167,10 @@ local function init(parent, id) ---@todo radiation monitor local rad_mon = IndicatorLight{parent=annunciator,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)} - r_ps.subscribe("PLCOnline", plc_online.update) - r_ps.subscribe("PLCHeartbeat", plc_hbeat.update) - r_ps.subscribe("status", r_active.update) - r_ps.subscribe("AutoControl", r_auto.update) + u_ps.subscribe("PLCOnline", plc_online.update) + u_ps.subscribe("PLCHeartbeat", plc_hbeat.update) + u_ps.subscribe("status", r_active.update) + u_ps.subscribe("AutoControl", r_auto.update) annunciator.line_break() @@ -187,17 +187,17 @@ local function init(parent, id) local r_wloc = IndicatorLight{parent=annunciator,label="Waste Line Occlusion",colors=cpair(colors.yellow,colors.gray)} local r_hsrt = IndicatorLight{parent=annunciator,label="Startup Rate High",colors=cpair(colors.yellow,colors.gray)} - r_ps.subscribe("ReactorSCRAM", r_scram.update) - r_ps.subscribe("ManualReactorSCRAM", r_mscrm.update) - r_ps.subscribe("AutoReactorSCRAM", r_ascrm.update) - r_ps.subscribe("RCPTrip", r_rtrip.update) - r_ps.subscribe("RCSFlowLow", r_cflow.update) - r_ps.subscribe("CoolantLevelLow", r_clow.update) - r_ps.subscribe("ReactorTempHigh", r_temp.update) - r_ps.subscribe("ReactorHighDeltaT", r_rhdt.update) - r_ps.subscribe("FuelInputRateLow", r_firl.update) - r_ps.subscribe("WasteLineOcclusion", r_wloc.update) - r_ps.subscribe("HighStartupRate", r_hsrt.update) + u_ps.subscribe("ReactorSCRAM", r_scram.update) + u_ps.subscribe("ManualReactorSCRAM", r_mscrm.update) + u_ps.subscribe("AutoReactorSCRAM", r_ascrm.update) + u_ps.subscribe("RCPTrip", r_rtrip.update) + u_ps.subscribe("RCSFlowLow", r_cflow.update) + u_ps.subscribe("CoolantLevelLow", r_clow.update) + u_ps.subscribe("ReactorTempHigh", r_temp.update) + u_ps.subscribe("ReactorHighDeltaT", r_rhdt.update) + u_ps.subscribe("FuelInputRateLow", r_firl.update) + u_ps.subscribe("WasteLineOcclusion", r_wloc.update) + u_ps.subscribe("HighStartupRate", r_hsrt.update) -- RPS annunciator panel @@ -216,16 +216,16 @@ local function init(parent, id) local rps_tmo = IndicatorLight{parent=rps_annunc,label="Timeout",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} local rps_sfl = IndicatorLight{parent=rps_annunc,label="System Failure",colors=cpair(colors.orange,colors.gray),flash=true,period=period.BLINK_500_MS} - r_ps.subscribe("rps_tripped", rps_trp.update) - r_ps.subscribe("dmg_crit", rps_dmg.update) - r_ps.subscribe("ex_hcool", rps_exh.update) - r_ps.subscribe("ex_waste", rps_exw.update) - r_ps.subscribe("high_temp", rps_tmp.update) - r_ps.subscribe("no_fuel", rps_nof.update) - r_ps.subscribe("no_cool", rps_noc.update) - r_ps.subscribe("fault", rps_flt.update) - r_ps.subscribe("timeout", rps_tmo.update) - r_ps.subscribe("sys_fail", rps_sfl.update) + u_ps.subscribe("rps_tripped", rps_trp.update) + u_ps.subscribe("dmg_crit", rps_dmg.update) + u_ps.subscribe("ex_hcool", rps_exh.update) + u_ps.subscribe("ex_waste", rps_exw.update) + u_ps.subscribe("high_temp", rps_tmp.update) + u_ps.subscribe("no_fuel", rps_nof.update) + u_ps.subscribe("no_cool", rps_noc.update) + u_ps.subscribe("fault", rps_flt.update) + u_ps.subscribe("timeout", rps_tmo.update) + u_ps.subscribe("sys_fail", rps_sfl.update) -- cooling annunciator panel @@ -240,11 +240,11 @@ local function init(parent, id) local c_mwrf = IndicatorLight{parent=rcs_annunc,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} local c_tbnt = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - r_ps.subscribe("CoolantFeedMismatch", c_cfm.update) - r_ps.subscribe("BoilRateMismatch", c_brm.update) - r_ps.subscribe("SteamFeedMismatch", c_sfm.update) - r_ps.subscribe("MaxWaterReturnFeed", c_mwrf.update) - r_ps.subscribe("TurbineTrip", c_tbnt.update) + u_ps.subscribe("CoolantFeedMismatch", c_cfm.update) + u_ps.subscribe("BoilRateMismatch", c_brm.update) + u_ps.subscribe("SteamFeedMismatch", c_sfm.update) + u_ps.subscribe("MaxWaterReturnFeed", c_mwrf.update) + u_ps.subscribe("TurbineTrip", c_tbnt.update) rcs_annunc.line_break() @@ -337,8 +337,8 @@ local function init(parent, id) local set_burn = function () unit.set_burn(burn_rate.get_value()) end local set_burn_btn = PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),dis_fg_bg=dis_colors,callback=set_burn} - r_ps.subscribe("burn_rate", burn_rate.set_value) - r_ps.subscribe("max_burn", burn_rate.set_max) + u_ps.subscribe("burn_rate", burn_rate.set_value) + u_ps.subscribe("max_burn", burn_rate.set_max) local start = HazardButton{parent=main,x=2,y=28,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=hzd_fg_bg} local ack_a = HazardButton{parent=main,x=12,y=32,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=unit.ack_alarms,fg_bg=hzd_fg_bg} @@ -359,9 +359,9 @@ local function init(parent, id) end end - r_ps.subscribe("status", start_button_en_check) - r_ps.subscribe("rps_tripped", start_button_en_check) - r_ps.subscribe("rps_tripped", function (active) if active then reset.enable() else reset.disable() end end) + u_ps.subscribe("status", start_button_en_check) + u_ps.subscribe("rps_tripped", start_button_en_check) + u_ps.subscribe("rps_tripped", function (active) if active then reset.enable() else reset.disable() end end) TextBox{parent=main,text="WASTE PROCESSING",fg_bg=cpair(colors.black,colors.brown),alignment=TEXT_ALIGN.CENTER,width=33,height=1,x=46,y=48} local waste_proc = Rectangle{parent=main,border=border(1,colors.brown,true),thin=true,width=33,height=3,x=46,y=49} @@ -369,7 +369,7 @@ local function init(parent, id) local waste_mode = MultiButton{parent=waste_div,x=1,y=1,options=waste_opts,callback=unit.set_waste,min_width=6} - r_ps.subscribe("U_WasteMode", waste_mode.set_value) + u_ps.subscribe("U_WasteMode", waste_mode.set_value) ---------------------- -- alarm management -- @@ -392,20 +392,20 @@ local function init(parent, id) local a_clt = AlarmLight{parent=alarm_panel,x=6,label="RCS Transient",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS} local a_tbt = AlarmLight{parent=alarm_panel,x=6,label="Turbine Trip",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} - r_ps.subscribe("Alarm_1", a_brc.update) - r_ps.subscribe("Alarm_2", a_rad.update) - r_ps.subscribe("Alarm_4", a_dmg.update) + u_ps.subscribe("Alarm_1", a_brc.update) + u_ps.subscribe("Alarm_2", a_rad.update) + u_ps.subscribe("Alarm_4", a_dmg.update) - r_ps.subscribe("Alarm_3", a_rcl.update) - r_ps.subscribe("Alarm_5", a_rcd.update) - r_ps.subscribe("Alarm_6", a_rot.update) - r_ps.subscribe("Alarm_7", a_rht.update) - r_ps.subscribe("Alarm_8", a_rwl.update) - r_ps.subscribe("Alarm_9", a_rwh.update) + u_ps.subscribe("Alarm_3", a_rcl.update) + u_ps.subscribe("Alarm_5", a_rcd.update) + u_ps.subscribe("Alarm_6", a_rot.update) + u_ps.subscribe("Alarm_7", a_rht.update) + u_ps.subscribe("Alarm_8", a_rwl.update) + u_ps.subscribe("Alarm_9", a_rwh.update) - r_ps.subscribe("Alarm_10", a_rps.update) - r_ps.subscribe("Alarm_11", a_clt.update) - r_ps.subscribe("Alarm_12", a_tbt.update) + u_ps.subscribe("Alarm_10", a_rps.update) + u_ps.subscribe("Alarm_11", a_clt.update) + u_ps.subscribe("Alarm_12", a_tbt.update) -- ack's and resets @@ -470,22 +470,22 @@ local function init(parent, id) TextBox{parent=auto_div,text="Prio. Group",height=1,width=11,fg_bg=style.label} local auto_grp = TextBox{parent=auto_div,text="Manual",height=1,width=11,fg_bg=bw_fg_bg} - r_ps.subscribe("auto_group", auto_grp.set_value) + u_ps.subscribe("auto_group", auto_grp.set_value) auto_div.line_break() local a_rdy = IndicatorLight{parent=auto_div,label="Ready",x=2,colors=cpair(colors.green,colors.gray)} local a_stb = IndicatorLight{parent=auto_div,label="Standby",x=2,colors=cpair(colors.white,colors.gray),flash=true,period=period.BLINK_1000_MS} - r_ps.subscribe("U_AutoReady", a_rdy.update) + u_ps.subscribe("U_AutoReady", a_rdy.update) -- update standby indicator - r_ps.subscribe("status", function (active) + u_ps.subscribe("status", function (active) a_stb.update(unit.annunciator.AutoControl and (not active)) end) -- enable and disable controls based on auto control state (start button is handled separately) - r_ps.subscribe("AutoControl", function (auto_active) + u_ps.subscribe("AutoControl", function (auto_active) start_button_en_check() if auto_active then diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index b28b43f..3846be4 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -43,13 +43,13 @@ local function make(parent, x, y, unit) local root = Div{parent=parent,x=x,y=y,width=80,height=height} -- unit header message - TextBox{parent=root,text="Unit #" .. unit.unit_id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + TextBox{parent=root,text="Unit #"..unit.unit_id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} ------------- -- REACTOR -- ------------- - reactor_view(root, 1, 3, unit.reactor_data, unit.reactor_ps) + reactor_view(root, 1, 3, unit.reactor_data, unit.unit_ps) if num_boilers > 0 then local coolant_pipes = {} From 72791d042b767ba964a2eabc3cb64091ddedba67 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 3 Feb 2023 15:19:00 -0500 Subject: [PATCH 482/587] #149 validate display sizes on startup --- coordinator/renderer.lua | 24 +++++++++++++++++++++ coordinator/startup.lua | 18 +++++++++++++--- coordinator/ui/components/processctl.lua | 2 ++ coordinator/ui/components/unit_overview.lua | 2 ++ 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 0d99db3..51816f6 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -1,4 +1,5 @@ local log = require("scada-common.log") +local util = require("scada-common.util") local style = require("coordinator.ui.style") @@ -85,6 +86,29 @@ function renderer.reset(recolor) end end +-- check main display width +---@return boolean width_okay +function renderer.validate_main_display_width() + local w, _ = engine.monitors.primary.getSize() + return w == 164 +end + +-- check display sizes +---@return boolean valid all unit display dimensions OK +function renderer.validate_unit_display_sizes() + local valid = true + + for id, monitor in pairs(engine.monitors.unit_displays) do + local w, h = monitor.getSize() + if w ~= 79 or h ~= 52 then + log.warning(util.c("unit ", id, " display resolution not 79 wide by 52 tall: ", w, ", ", h)) + valid = false + end + end + + return valid +end + -- initialize the dmesg output window function renderer.init_dmesg() local disp_x, disp_y = engine.monitors.primary.getSize() diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 3025eb3..d173eb1 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.13" +local COORDINATOR_VERSION = "beta-v0.8.14" local print = util.print local println = util.println @@ -84,13 +84,25 @@ local function main() return end - log.info("monitors ready, dmesg output incoming...") - -- init renderer renderer.set_displays(monitors) renderer.reset(config.RECOLOR) + + if not renderer.validate_main_display_width() then + println("boot> main display must be 8 blocks wide") + log.fatal("main display not wide enough") + return + elseif not renderer.validate_unit_display_sizes() then + println("boot> one or more unit display dimensions incorrect; they must be 4x4 blocks") + log.fatal("unit display dimensions incorrect") + return + end + renderer.init_dmesg() + -- lets get started! + log.info("monitors ready, dmesg output incoming...") + log_graphics("displays connected and reset") log_sys("system start on " .. os.date("%c")) log_boot("starting " .. COORDINATOR_VERSION) diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 4bda49f..922799b 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -35,6 +35,8 @@ local period = core.flasher.PERIOD ---@param x integer top left x ---@param y integer top left y local function new_view(root, x, y) + assert(root.height() >= (y + 24), "main display not of sufficient vertical resolution (add an additional row of monitors)") + local facility = iocontrol.get_db().facility local units = iocontrol.get_db().units diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 3846be4..672c6ea 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -39,6 +39,8 @@ local function make(parent, x, y, unit) height = 25 end + assert(parent.height() >= (y + height), "main display not of sufficient vertical resolution (add an additional row of monitors)") + -- bounding box div local root = Div{parent=parent,x=x,y=y,width=80,height=height} From a117d5ee97a7a48a08a30cf58d2a8fe84daad0dd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 3 Feb 2023 16:40:58 -0500 Subject: [PATCH 483/587] #150 save and automatically set priority groups, added checks to set waste and set group commands, restore waste mode control if operation failed --- coordinator/iocontrol.lua | 1 + coordinator/process.lua | 31 +++++++++++++++++++++-- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 3 ++- supervisor/session/coordinator.lua | 4 +-- supervisor/startup.lua | 2 +- 6 files changed, 36 insertions(+), 7 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index d1d59a9..2502027 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -292,6 +292,7 @@ function iocontrol.update_facility_status(status) if (type(group_map) == "table") and (#group_map == fac.num_units) then local names = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } for i = 1, #group_map do + io.units[i].unit_ps.publish("auto_group_id", group_map[i] + 1) io.units[i].unit_ps.publish("auto_group", names[group_map[i] + 1]) end end diff --git a/coordinator/process.lua b/coordinator/process.lua index f509a7b..11871e7 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -70,12 +70,22 @@ function process.init(iocontrol, comms) local waste_mode = settings.get("WASTE_MODES") ---@type table|nil if type(waste_mode) == "table" then - for id = 1, math.min(#waste_mode, self.io.facility.num_units) do - self.comms.send_unit_command(UNIT_COMMANDS.SET_WASTE, id, waste_mode[id]) + for id, mode in pairs(waste_mode) do + self.comms.send_unit_command(UNIT_COMMANDS.SET_WASTE, id, mode) end log.info("PROCESS: loaded waste mode settings from coord.settings") end + + local prio_groups = settings.get("PRIORITY_GROUPS") ---@type table|nil + + if type(prio_groups) == "table" then + for id, group in pairs(prio_groups) do + self.comms.send_unit_command(UNIT_COMMANDS.SET_GROUP, id, group) + end + + log.info("PROCESS: loaded priority groups settings from coord.settings") + end end -- start reactor @@ -113,6 +123,9 @@ end ---@param id integer unit ID ---@param mode integer waste mode function process.set_waste(id, mode) + -- publish so that if it fails then it gets reset + self.io.units[id].unit_ps.publish("U_WasteMode", mode) + self.comms.send_unit_command(UNIT_COMMANDS.SET_WASTE, id, mode) log.debug(util.c("UNIT[", id, "]: SET WASTE = ", mode)) @@ -160,6 +173,20 @@ end function process.set_group(unit_id, group_id) self.comms.send_unit_command(UNIT_COMMANDS.SET_GROUP, unit_id, group_id) log.debug(util.c("UNIT[", unit_id, "]: SET GROUP ", group_id)) + + local prio_groups = settings.get("PRIORITY_GROUPS") ---@type table|nil + + if type(prio_groups) ~= "table" then + prio_groups = {} + end + + prio_groups[unit_id] = group_id + + settings.set("PRIORITY_GROUPS", prio_groups) + + if not settings.save("/coord.settings") then + log.error("process.set_group(): failed to save coordinator settings file") + end end -------------------------- diff --git a/coordinator/startup.lua b/coordinator/startup.lua index d173eb1..3052ed0 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.14" +local COORDINATOR_VERSION = "beta-v0.8.15" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index cfaf840..ccb3266 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -459,10 +459,11 @@ local function init(parent, id) local group = RadioButton{parent=auto_div,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.blue,colors.white),radio_bg=colors.gray} + u_ps.subscribe("auto_group_id", group.set_value) + auto_div.line_break() local function set_group() unit.set_group(group.get_value() - 1) end - local set_grp_btn = PushButton{parent=auto_div,text="SET",x=4,min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),dis_fg_bg=cpair(colors.gray,colors.white),callback=set_group} auto_div.line_break() diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index c1d3047..b146a1d 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -268,7 +268,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility) log.debug(log_header .. "CRDN unit command burn rate missing option") end elseif cmd == UNIT_COMMANDS.SET_WASTE then - if pkt.length == 3 then + if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] > 0) and (pkt.data[3] <= 4) then unit.set_waste(pkt.data[3]) else log.debug(log_header .. "CRDN unit command set waste missing option") @@ -289,7 +289,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility) log.debug(log_header .. "CRDN unit command reset alarm missing alarm id") end elseif cmd == UNIT_COMMANDS.SET_GROUP then - if pkt.length == 3 then + if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] >= 0) and (pkt.data[3] <= 4) then facility.set_group(unit.get_id(), pkt.data[3]) _send(SCADA_CRDN_TYPES.UNIT_CMD, { cmd, uid, pkt.data[3] }) else diff --git a/supervisor/startup.lua b/supervisor/startup.lua index f579ec2..3832894 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.9.9" +local SUPERVISOR_VERSION = "beta-v0.9.10" local print = util.print local println = util.println From ba8bfb6e14561b4b675296c9a7b37946ad92aeda Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 3 Feb 2023 21:05:21 -0500 Subject: [PATCH 484/587] #101 fixed averages and display them --- coordinator/iocontrol.lua | 6 ++--- coordinator/startup.lua | 2 +- coordinator/ui/components/imatrix.lua | 33 +++++++++++++++++---------- scada-common/util.lua | 2 +- supervisor/session/facility.lua | 24 +++++++++---------- supervisor/startup.lua | 2 +- 6 files changed, 39 insertions(+), 30 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 2502027..d5b0a54 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -307,9 +307,9 @@ function iocontrol.update_facility_status(status) if type(rtu_statuses) == "table" then -- power statistics if type(rtu_statuses.power) == "table" then - fac.ps.publish("avg_charge", rtu_statuses.power[1]) - fac.ps.publish("avg_inflow", rtu_statuses.power[2]) - fac.ps.publish("avg_outflow", rtu_statuses.power[3]) + fac.induction_ps_tbl[1].publish("avg_charge", rtu_statuses.power[1]) + fac.induction_ps_tbl[1].publish("avg_inflow", rtu_statuses.power[2]) + fac.induction_ps_tbl[1].publish("avg_outflow", rtu_statuses.power[3]) else log.debug(log_header .. "power statistics list not a table") end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 3052ed0..f8d3406 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.15" +local COORDINATOR_VERSION = "beta-v0.8.16" local print = util.print local println = util.println diff --git a/coordinator/ui/components/imatrix.lua b/coordinator/ui/components/imatrix.lua index 62d7dbf..c14e910 100644 --- a/coordinator/ui/components/imatrix.lua +++ b/coordinator/ui/components/imatrix.lua @@ -24,16 +24,17 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN ---@param y integer top left y ---@param data imatrix_session_db matrix data ---@param ps psil ps interface +---@param id number? matrix ID local function new_view(root, x, y, data, ps, id) local title = "INDUCTION MATRIX" if type(id) == "number" then title = title .. id end - local matrix = Div{parent=root,fg_bg=style.root,width=33,height=21,x=x,y=y} + local matrix = Div{parent=root,fg_bg=style.root,width=33,height=25,x=x,y=y} TextBox{parent=matrix,text=" ",width=33,height=1,x=1,y=1,fg_bg=cpair(colors.lightGray,colors.gray)} TextBox{parent=matrix,text=title,alignment=TEXT_ALIGN.CENTER,width=33,height=1,x=1,y=2,fg_bg=cpair(colors.lightGray,colors.gray)} - local rect = Rectangle{parent=matrix,border=border(1, colors.gray, true),width=33,height=18,x=1,y=3} + local rect = Rectangle{parent=matrix,border=border(1,colors.gray,true),width=33,height=22,x=1,y=3} local text_fg_bg = cpair(colors.black, colors.lightGray) local label_fg_bg = cpair(colors.gray, colors.lightGray) @@ -45,31 +46,39 @@ local function new_view(root, x, y, data, ps, id) local input = PowerIndicator{parent=rect,x=7,y=5,lu_colors=lu_col,label="Input: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg_bg} local output = PowerIndicator{parent=rect,x=7,y=6,lu_colors=lu_col,label="Output: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg_bg} + local avg_chg = PowerIndicator{parent=rect,x=7,y=8,lu_colors=lu_col,label="Avg. Chg:",format="%8.2f",value=0,width=26,fg_bg=text_fg_bg} + local avg_in = PowerIndicator{parent=rect,x=7,y=9,lu_colors=lu_col,label="Avg. In: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg_bg} + local avg_out = PowerIndicator{parent=rect,x=7,y=10,lu_colors=lu_col,label="Avg. Out:",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg_bg} + ps.subscribe("computed_status", status.update) ps.subscribe("energy", function (val) energy.update(util.joules_to_fe(val)) end) ps.subscribe("max_energy", function (val) capacity.update(util.joules_to_fe(val)) end) ps.subscribe("last_input", function (val) input.update(util.joules_to_fe(val)) end) ps.subscribe("last_output", function (val) output.update(util.joules_to_fe(val)) end) - local fill = DataIndicator{parent=rect,x=11,y=8,lu_colors=lu_col,label="Fill:",unit="%",format="%8.2f",value=0,width=18,fg_bg=text_fg_bg} + ps.subscribe("avg_charge", avg_chg.update) + ps.subscribe("avg_inflow", avg_in.update) + ps.subscribe("avg_outflow", avg_out.update) - local cells = DataIndicator{parent=rect,x=11,y=10,lu_colors=lu_col,label="Cells: ",format="%7d",value=0,width=18,fg_bg=text_fg_bg} - local providers = DataIndicator{parent=rect,x=11,y=11,lu_colors=lu_col,label="Providers:",format="%7d",value=0,width=18,fg_bg=text_fg_bg} + local fill = DataIndicator{parent=rect,x=11,y=12,lu_colors=lu_col,label="Fill:",unit="%",format="%8.2f",value=0,width=18,fg_bg=text_fg_bg} - TextBox{parent=rect,text="Transfer Capacity",x=11,y=13,height=1,width=17,fg_bg=label_fg_bg} - local trans_cap = PowerIndicator{parent=rect,x=19,y=14,lu_colors=lu_col,label="",format="%5.2f",rate=true,value=0,width=12,fg_bg=text_fg_bg} + local cells = DataIndicator{parent=rect,x=11,y=14,lu_colors=lu_col,label="Cells: ",format="%7d",value=0,width=18,fg_bg=text_fg_bg} + local providers = DataIndicator{parent=rect,x=11,y=15,lu_colors=lu_col,label="Providers:",format="%7d",value=0,width=18,fg_bg=text_fg_bg} + + TextBox{parent=rect,text="Transfer Capacity",x=11,y=17,height=1,width=17,fg_bg=label_fg_bg} + local trans_cap = PowerIndicator{parent=rect,x=19,y=18,lu_colors=lu_col,label="",format="%5.2f",rate=true,value=0,width=12,fg_bg=text_fg_bg} ps.subscribe("cells", cells.update) ps.subscribe("providers", providers.update) ps.subscribe("energy_fill", function (val) fill.update(val * 100) end) ps.subscribe("transfer_cap", function (val) trans_cap.update(util.joules_to_fe(val)) end) - local charge = VerticalBar{parent=rect,x=2,y=2,fg_bg=cpair(colors.green,colors.gray),height=13,width=4} - local in_cap = VerticalBar{parent=rect,x=7,y=8,fg_bg=cpair(colors.red,colors.gray),height=7,width=1} - local out_cap = VerticalBar{parent=rect,x=9,y=8,fg_bg=cpair(colors.blue,colors.gray),height=7,width=1} + local charge = VerticalBar{parent=rect,x=2,y=2,fg_bg=cpair(colors.green,colors.gray),height=17,width=4} + local in_cap = VerticalBar{parent=rect,x=7,y=12,fg_bg=cpair(colors.red,colors.gray),height=7,width=1} + local out_cap = VerticalBar{parent=rect,x=9,y=12,fg_bg=cpair(colors.blue,colors.gray),height=7,width=1} - TextBox{parent=rect,text="FILL",x=2,y=16,height=1,width=4,fg_bg=text_fg_bg} - TextBox{parent=rect,text="I/O",x=7,y=16,height=1,width=3,fg_bg=text_fg_bg} + TextBox{parent=rect,text="FILL",x=2,y=20,height=1,width=4,fg_bg=text_fg_bg} + TextBox{parent=rect,text="I/O",x=7,y=20,height=1,width=3,fg_bg=text_fg_bg} local function calc_saturation(val) if (type(data.build) == "table") and (type(data.build.transfer_cap) == "number") and (data.build.transfer_cap > 0) then diff --git a/scada-common/util.lua b/scada-common/util.lua index 8b88758..56cacd7 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -253,7 +253,7 @@ function util.mov_avg(length, default) function public.compute() local sum = 0 for i = 1, length do sum = sum + data[i] end - return sum + return sum / length end public.reset(default) diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index ebd417a..a63dfd2 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -65,9 +65,9 @@ function facility.new(num_reactors, cooling_conf) last_time = 0.0, -- statistics im_stat_init = false, - avg_charge = util.mov_avg(10, 0.0), - avg_inflow = util.mov_avg(10, 0.0), - avg_outflow = util.mov_avg(10, 0.0) + avg_charge = util.mov_avg(20, 0.0), + avg_inflow = util.mov_avg(20, 0.0), + avg_outflow = util.mov_avg(20, 0.0) } -- create units @@ -185,14 +185,14 @@ function facility.new(num_reactors, cooling_conf) if (db.state.last_update > 0) and (db.tanks.last_update > 0) then if self.im_stat_init then - self.avg_charge.record(db.tanks.energy, db.tanks.last_update) - self.avg_inflow.record(db.state.last_input, db.state.last_update) - self.avg_outflow.record(db.state.last_output, db.state.last_update) + self.avg_charge.record(util.joules_to_fe(db.tanks.energy), db.tanks.last_update) + self.avg_inflow.record(util.joules_to_fe(db.state.last_input), db.state.last_update) + self.avg_outflow.record(util.joules_to_fe(db.state.last_output), db.state.last_update) else self.im_stat_init = true - self.avg_charge.reset(db.tanks.energy) - self.avg_inflow.reset(db.state.last_input) - self.avg_outflow.reset(db.state.last_output) + self.avg_charge.reset(util.joules_to_fe(db.tanks.energy)) + self.avg_inflow.reset(util.joules_to_fe(db.state.last_input)) + self.avg_outflow.reset(util.joules_to_fe(db.state.last_output)) end end else @@ -586,9 +586,9 @@ function facility.new(num_reactors, cooling_conf) -- power averages from induction matricies status.power = { - self.avg_charge, - self.avg_inflow, - self.avg_outflow + self.avg_charge.compute(), + self.avg_inflow.compute(), + self.avg_outflow.compute() } -- status of induction matricies (including tanks) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 3832894..ae879f4 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.9.10" +local SUPERVISOR_VERSION = "beta-v0.9.11" local print = util.print local println = util.println From b5c70b0d3729e7c59bf834ff606d79f60e445445 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Feb 2023 13:47:00 -0500 Subject: [PATCH 485/587] fixed process controller assuming ramp complete if burn rate setpoint was identical to setpoint before process control start --- coordinator/iocontrol.lua | 21 ++- coordinator/startup.lua | 2 +- coordinator/ui/components/processctl.lua | 17 +- coordinator/ui/components/unit_detail.lua | 21 ++- coordinator/ui/layout/main_view.lua | 48 +++--- reactor-plc/plc.lua | 7 +- reactor-plc/startup.lua | 3 +- rtu/startup.lua | 6 +- scada-common/comms.lua | 12 +- scada-common/log.lua | 197 +++++++++++----------- supervisor/session/facility.lua | 7 + supervisor/session/plc.lua | 38 ++++- supervisor/session/unit.lua | 3 +- supervisor/startup.lua | 2 +- 14 files changed, 225 insertions(+), 159 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index d5b0a54..914f7ee 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -23,6 +23,8 @@ local io = {} function iocontrol.init(conf, comms) ---@class ioctl_facility io.facility = { + all_sys_ok = false, + auto_ready = false, auto_active = false, auto_ramping = false, @@ -273,13 +275,15 @@ function iocontrol.update_facility_status(status) local ctl_status = status[1] if type(ctl_status) == "table" then - fac.auto_ready = ctl_status[1] - fac.auto_active = ctl_status[2] > 0 - fac.auto_ramping = ctl_status[3] - fac.auto_scram = ctl_status[4] - fac.status_line_1 = ctl_status[5] - fac.status_line_2 = ctl_status[6] + fac.all_sys_ok = ctl_status[1] + fac.auto_ready = ctl_status[2] + fac.auto_active = ctl_status[3] > 0 + fac.auto_ramping = ctl_status[4] + fac.auto_scram = ctl_status[5] + fac.status_line_1 = ctl_status[6] + fac.status_line_2 = ctl_status[7] + fac.ps.publish("all_sys_ok", fac.all_sys_ok) fac.ps.publish("auto_ready", fac.auto_ready) fac.ps.publish("auto_active", fac.auto_active) fac.ps.publish("auto_ramping", fac.auto_ramping) @@ -287,12 +291,13 @@ function iocontrol.update_facility_status(status) fac.ps.publish("status_line_1", fac.status_line_1) fac.ps.publish("status_line_2", fac.status_line_2) - local group_map = ctl_status[7] + local group_map = ctl_status[8] if (type(group_map) == "table") and (#group_map == fac.num_units) then local names = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } for i = 1, #group_map do - io.units[i].unit_ps.publish("auto_group_id", group_map[i] + 1) + io.units[i].a_group = group_map[i] + io.units[i].unit_ps.publish("auto_group_id", group_map[i]) io.units[i].unit_ps.publish("auto_group", names[group_map[i] + 1]) end end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index f8d3406..e52c1b9 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.16" +local COORDINATOR_VERSION = "beta-v0.8.17" local print = util.print local println = util.println diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 922799b..0181a4d 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -50,10 +50,21 @@ local function new_view(root, x, y) facility.scram_ack = scram.on_response - local auto_act = IndicatorLight{parent=main,y=5,label="Auto Active",colors=cpair(colors.green,colors.gray)} - local auto_ramp = IndicatorLight{parent=main,label="Auto Ramping",colors=cpair(colors.white,colors.gray),flash=true,period=period.BLINK_250_MS} - local auto_scram = IndicatorLight{parent=main,label="Auto SCRAM",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local all_ok = IndicatorLight{parent=main,y=5,label="Unit Systems Online",colors=cpair(colors.green,colors.red)} + local ind_mat = IndicatorLight{parent=main,label="Induction Matrix",colors=cpair(colors.green,colors.gray)} + local rad_mon = IndicatorLight{parent=main,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)} + facility.ps.subscribe("all_sys_ok", all_ok.update) + facility.induction_ps_tbl[1].subscribe("computed_status", function (status) ind_mat.update(status > 1) end) + + main.line_break() + + local auto_ready = IndicatorLight{parent=main,label="Configured Units Ready",colors=cpair(colors.green,colors.red)} + local auto_act = IndicatorLight{parent=main,label="Process Active",colors=cpair(colors.green,colors.gray)} + local auto_ramp = IndicatorLight{parent=main,label="Process Ramping",colors=cpair(colors.white,colors.gray),flash=true,period=period.BLINK_250_MS} + local auto_scram = IndicatorLight{parent=main,label="Automatic SCRAM",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + + facility.ps.subscribe("auto_ready", auto_ready.update) facility.ps.subscribe("auto_active", auto_act.update) facility.ps.subscribe("auto_ramping", auto_ramp.update) facility.ps.subscribe("auto_scram", auto_scram.update) diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index ccb3266..99990e8 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -354,7 +354,7 @@ local function init(parent, id) if (unit.reactor_data ~= nil) and (unit.reactor_data.mek_status ~= nil) then local can_start = (not unit.reactor_data.mek_status.status) and (not unit.reactor_data.rps_tripped) and - (not unit.annunciator.AutoControl) + (unit.a_group == 0) if can_start then start.enable() else start.disable() end end end @@ -459,7 +459,7 @@ local function init(parent, id) local group = RadioButton{parent=auto_div,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.blue,colors.white),radio_bg=colors.gray} - u_ps.subscribe("auto_group_id", group.set_value) + u_ps.subscribe("auto_group_id", function (gid) group.set_value(gid + 1) end) auto_div.line_break() @@ -485,18 +485,27 @@ local function init(parent, id) a_stb.update(unit.annunciator.AutoControl and (not active)) end) + -- enable and disable controls based on group assignment + u_ps.subscribe("auto_group_id", function (gid) + start_button_en_check() + + if gid == 0 then + burn_rate.enable() + set_burn_btn.enable() + else + burn_rate.disable() + set_burn_btn.disable() + end + end) + -- enable and disable controls based on auto control state (start button is handled separately) u_ps.subscribe("AutoControl", function (auto_active) start_button_en_check() if auto_active then - burn_rate.disable() - set_burn_btn.disable() set_grp_btn.disable() a_stb.update(unit.reactor_data.mek_status.status == false) else - burn_rate.enable() - set_burn_btn.enable() set_grp_btn.enable() a_stb.update(false) end diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index f1b68ad..cf41366 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -88,33 +88,33 @@ local function init(monitor) -- testing ---@fixme remove test code - ColorMap{parent=main,x=132,y=(main.height()-1)} + ColorMap{parent=main,x=98,y=(main.height()-1)} - local audio = Div{parent=main,width=34,height=15,x=95,y=cnc_y_start} + local audio = Div{parent=main,width=23,height=23,x=107,y=cnc_y_start} - PushButton{parent=audio,x=1,y=1,text="TEST 1",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_1} - PushButton{parent=audio,x=1,text="TEST 2",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_2} - PushButton{parent=audio,x=1,text="TEST 3",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_3} - PushButton{parent=audio,x=1,text="TEST 4",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_4} - PushButton{parent=audio,x=1,text="TEST 5",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_5} - PushButton{parent=audio,x=1,text="TEST 6",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_6} - PushButton{parent=audio,x=1,text="TEST 7",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_7} - PushButton{parent=audio,x=1,text="TEST 8",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_8} - PushButton{parent=audio,x=1,text="STOP",min_width=8,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.stop} - PushButton{parent=audio,x=1,text="PSCALE",min_width=8,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_power_scale} + PushButton{parent=audio,x=16,y=1,text="TEST 1",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_1} + PushButton{parent=audio,x=16,text="TEST 2",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_2} + PushButton{parent=audio,x=16,text="TEST 3",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_3} + PushButton{parent=audio,x=16,text="TEST 4",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_4} + PushButton{parent=audio,x=16,text="TEST 5",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_5} + PushButton{parent=audio,x=16,text="TEST 6",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_6} + PushButton{parent=audio,x=16,text="TEST 7",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_7} + PushButton{parent=audio,x=16,text="TEST 8",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_8} + PushButton{parent=audio,x=16,text="STOP",min_width=8,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.stop} + PushButton{parent=audio,x=16,text="PSCALE",min_width=8,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_power_scale} - SwitchButton{parent=audio,x=11,y=1,text="CONTAINMENT BREACH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_breach} - SwitchButton{parent=audio,x=11,text="CONTAINMENT RADIATION",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rad} - SwitchButton{parent=audio,x=11,text="REACTOR LOST",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_lost} - SwitchButton{parent=audio,x=11,text="CRITICAL DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_crit} - SwitchButton{parent=audio,x=11,text="REACTOR DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_dmg} - SwitchButton{parent=audio,x=11,text="REACTOR OVER TEMP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_overtemp} - SwitchButton{parent=audio,x=11,text="REACTOR HIGH TEMP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_hightemp} - SwitchButton{parent=audio,x=11,text="REACTOR WASTE LEAK",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_wasteleak} - SwitchButton{parent=audio,x=11,text="REACTOR WASTE HIGH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_highwaste} - SwitchButton{parent=audio,x=11,text="RPS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rps} - SwitchButton{parent=audio,x=11,text="RCS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rcs} - SwitchButton{parent=audio,x=11,text="TURBINE TRIP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_turbinet} + SwitchButton{parent=audio,x=1,y=12,text="CONTAINMENT BREACH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_breach} + SwitchButton{parent=audio,x=1,text="CONTAINMENT RADIATION",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rad} + SwitchButton{parent=audio,x=1,text="REACTOR LOST",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_lost} + SwitchButton{parent=audio,x=1,text="CRITICAL DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_crit} + SwitchButton{parent=audio,x=1,text="REACTOR DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_dmg} + SwitchButton{parent=audio,x=1,text="REACTOR OVER TEMP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_overtemp} + SwitchButton{parent=audio,x=1,text="REACTOR HIGH TEMP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_hightemp} + SwitchButton{parent=audio,x=1,text="REACTOR WASTE LEAK",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_wasteleak} + SwitchButton{parent=audio,x=1,text="REACTOR WASTE HIGH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_highwaste} + SwitchButton{parent=audio,x=1,text="RPS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rps} + SwitchButton{parent=audio,x=1,text="RCS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rcs} + SwitchButton{parent=audio,x=1,text="TURBINE TRIP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_turbinet} local imatrix_1 = imatrix(main, 131, cnc_y_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1]) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 7381c00..57c8471 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -68,7 +68,7 @@ function plc.rps_init(reactor, is_formed) formed = is_formed, force_disabled = false, tripped = false, - trip_cause = "" ---@type rps_trip_cause + trip_cause = "ok" ---@type rps_trip_cause } ---@class rps @@ -410,6 +410,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co scrammed = false, linked = false, resend_build = false, + auto_ack_token = 0, status_cache = nil, max_burn_rate = nil } @@ -656,6 +657,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co (not self.scrammed), -- requested control state no_reactor, -- no reactor peripheral connected formed, -- reactor formed + self.auto_ack_token, -- token to indicate auto command has been received before this status update heating_rate, -- heating rate mek_data -- mekanism status data } @@ -808,10 +810,11 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co _send_ack(packet.type, true) elseif packet.type == RPLC_TYPES.AUTO_BURN_RATE then -- automatic control requested a new burn rate - if (packet.length == 2) and (type(packet.data[1]) == "number") then + if (packet.length == 3) and (type(packet.data[1]) == "number") and (type(packet.data[3]) == "number") then local ack = AUTO_ACK.FAIL local burn_rate = math.floor(packet.data[1] * 10) / 10 local ramp = packet.data[2] + self.auto_ack_token = packet.data[3] -- if no known max burn rate, check again if self.max_burn_rate == nil then diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index f12bfc0..8ac0de2 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.10.3" +local R_PLC_VERSION = "beta-v0.10.4" local print = util.print local println = util.println @@ -169,6 +169,7 @@ local function main() log.debug("init> running without networking") end +---@diagnostic disable-next-line: param-type-mismatch util.push_event("clock_start") println("boot> completed") diff --git a/rtu/startup.lua b/rtu/startup.lua index 2188549..dec02ab 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.9.11" +local RTU_VERSION = "beta-v0.9.12" local rtu_t = types.rtu_t @@ -362,6 +362,10 @@ local function main() table.insert(units, rtu_unit) + if not formed then + log.debug(util.c("configure> device '", name, "' is not formed")) + end + local for_message = "facility" if for_reactor > 0 then for_message = util.c("reactor ", for_reactor) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 6fed4d5..b1a68f7 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -12,7 +12,7 @@ local rtu_t = types.rtu_t local insert = table.insert -comms.version = "1.1.2" +comms.version = "1.2.0" ---@alias PROTOCOLS integer local PROTOCOLS = { @@ -140,11 +140,11 @@ function comms.scada_packet() local self = { modem_msg_in = nil, valid = false, - raw = nil, - seq_num = nil, - protocol = nil, - length = nil, - payload = nil + raw = { -1, -1, {} }, + seq_num = -1, + protocol = -1, + length = 0, + payload = {} } ---@class scada_packet diff --git a/scada-common/log.lua b/scada-common/log.lua index 2aeb714..cc6dd0a 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -110,73 +110,40 @@ function log.dmesg(msg, tag, tag_color) local t_stamp = string.format("%12.2f", os.clock()) local out = _log_sys.dmesg_out - local out_w, out_h = out.getSize() - local lines = { msg } + if out ~= nil then + local out_w, out_h = out.getSize() - -- wrap if needed - if string.len(msg) > out_w then - local remaining = true - local s_start = 1 - local s_end = out_w - local i = 1 + local lines = { msg } - lines = {} + -- wrap if needed + if string.len(msg) > out_w then + local remaining = true + local s_start = 1 + local s_end = out_w + local i = 1 - while remaining do - local line = string.sub(msg, s_start, s_end) + lines = {} - if line == "" then - remaining = false - else - lines[i] = line + while remaining do + local line = string.sub(msg, s_start, s_end) - s_start = s_end + 1 - s_end = s_end + out_w - i = i + 1 + if line == "" then + remaining = false + else + lines[i] = line + + s_start = s_end + 1 + s_end = s_end + out_w + i = i + 1 + end end end - end - -- start output with tag and time, assuming we have enough width for this to be on one line - local cur_x, cur_y = out.getCursorPos() + -- start output with tag and time, assuming we have enough width for this to be on one line + local cur_x, cur_y = out.getCursorPos() - if cur_x > 1 then - if cur_y == out_h then - out.scroll(1) - out.setCursorPos(1, cur_y) - else - out.setCursorPos(1, cur_y + 1) - end - end - - -- colored time - local initial_color = out.getTextColor() - out.setTextColor(colors.white) - out.write("[") - out.setTextColor(colors.lightGray) - out.write(t_stamp) - ts_coord.x2, ts_coord.y = out.getCursorPos() - ts_coord.x2 = ts_coord.x2 - 1 - out.setTextColor(colors.white) - out.write("] ") - - -- print optionally colored tag - if tag ~= "" then - out.write("[") - if tag_color then out.setTextColor(tag_color) end - out.write(tag) - out.setTextColor(colors.white) - out.write("] ") - end - - out.setTextColor(initial_color) - - -- output message - for i = 1, #lines do - cur_x, cur_y = out.getCursorPos() - - if i > 1 and cur_x > 1 then + if cur_x > 1 then if cur_y == out_h then out.scroll(1) out.setCursorPos(1, cur_y) @@ -185,10 +152,46 @@ function log.dmesg(msg, tag, tag_color) end end - out.write(lines[i]) - end + -- colored time + local initial_color = out.getTextColor() + out.setTextColor(colors.white) + out.write("[") + out.setTextColor(colors.lightGray) + out.write(t_stamp) + ts_coord.x2, ts_coord.y = out.getCursorPos() + ts_coord.x2 = ts_coord.x2 - 1 + out.setTextColor(colors.white) + out.write("] ") - _log(util.c("[", t_stamp, "] [", tag, "] ", msg)) + -- print optionally colored tag + if tag ~= "" then + out.write("[") + if tag_color then out.setTextColor(tag_color) end + out.write(tag) + out.setTextColor(colors.white) + out.write("] ") + end + + out.setTextColor(initial_color) + + -- output message + for i = 1, #lines do + cur_x, cur_y = out.getCursorPos() + + if i > 1 and cur_x > 1 then + if cur_y == out_h then + out.scroll(1) + out.setCursorPos(1, cur_y) + else + out.setCursorPos(1, cur_y + 1) + end + end + + out.write(lines[i]) + end + + _log(util.c("[", t_stamp, "] [", tag, "] ", msg)) + end return ts_coord end @@ -204,52 +207,56 @@ function log.dmesg_working(msg, tag, tag_color) local out = _log_sys.dmesg_out local width = (ts_coord.x2 - ts_coord.x1) + 1 - local initial_color = out.getTextColor() + if out ~= nil then + local initial_color = out.getTextColor() - local counter = 0 + local counter = 0 - local function update(sec_remaining) - local time = util.sprintf("%ds", sec_remaining) - local available = width - (string.len(time) + 2) - local progress = "" + local function update(sec_remaining) + local time = util.sprintf("%ds", sec_remaining) + local available = width - (string.len(time) + 2) + local progress = "" - out.setCursorPos(ts_coord.x1, ts_coord.y) - out.write(" ") + out.setCursorPos(ts_coord.x1, ts_coord.y) + out.write(" ") - if counter % 4 == 0 then - progress = "|" - elseif counter % 4 == 1 then - progress = "/" - elseif counter % 4 == 2 then - progress = "-" - elseif counter % 4 == 3 then - progress = "\\" + if counter % 4 == 0 then + progress = "|" + elseif counter % 4 == 1 then + progress = "/" + elseif counter % 4 == 2 then + progress = "-" + elseif counter % 4 == 3 then + progress = "\\" + end + + out.setTextColor(colors.blue) + out.write(progress) + out.setTextColor(colors.lightGray) + out.write(util.spaces(available) .. time) + out.setTextColor(initial_color) + + counter = counter + 1 end - out.setTextColor(colors.blue) - out.write(progress) - out.setTextColor(colors.lightGray) - out.write(util.spaces(available) .. time) - out.setTextColor(initial_color) + local function done(ok) + out.setCursorPos(ts_coord.x1, ts_coord.y) - counter = counter + 1 - end + if ok or ok == nil then + out.setTextColor(colors.green) + out.write(util.pad("DONE", width)) + else + out.setTextColor(colors.red) + out.write(util.pad("FAIL", width)) + end - local function done(ok) - out.setCursorPos(ts_coord.x1, ts_coord.y) - - if ok or ok == nil then - out.setTextColor(colors.green) - out.write(util.pad("DONE", width)) - else - out.setTextColor(colors.red) - out.write(util.pad("FAIL", width)) + out.setTextColor(initial_color) end - out.setTextColor(initial_color) + return update, done + else + return function () end, function () end end - - return update, done end -- log debug messages diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index a63dfd2..af3e98b 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -41,6 +41,7 @@ function facility.new(num_reactors, cooling_conf) induction = {}, redstone = {}, status_text = { "START UP", "initializing..." }, + all_sys_ok = false, -- process control units_ready = false, mode = PROCESS.INACTIVE, @@ -199,6 +200,11 @@ function facility.new(num_reactors, cooling_conf) self.im_stat_init = false end + self.all_sys_ok = true + for i = 1, #self.units do + self.all_sys_ok = self.all_sys_ok and not self.units[i].get_control_inf().degraded + end + ------------------------- -- Run Process Control -- ------------------------- @@ -570,6 +576,7 @@ function facility.new(num_reactors, cooling_conf) -- get automatic process control status function public.get_control_status() return { + self.all_sys_ok, self.units_ready, self.mode, self.waiting_on_ramp, diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index c36d449..bf9037e 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -59,6 +59,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) out_q = out_queue, commanded_state = false, commanded_burn_rate = 0.0, + auto_cmd_token = 0, ramping_rate = false, auto_scram = false, auto_lock = false, @@ -92,6 +93,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) -- session database ---@class reactor_db sDB = { + auto_ack_token = 0, last_status_update = 0, control_state = false, no_reactor = false, @@ -305,18 +307,19 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) -- handle packet by type if pkt.type == RPLC_TYPES.STATUS then -- status packet received, update data - if pkt.length >= 4 then + if pkt.length >= 5 then self.sDB.last_status_update = pkt.data[1] self.sDB.control_state = pkt.data[2] self.sDB.no_reactor = pkt.data[3] self.sDB.formed = pkt.data[4] + self.sDB.auto_ack_token = pkt.data[5] if not self.sDB.no_reactor and self.sDB.formed then - self.sDB.mek_status.heating_rate = pkt.data[5] or 0.0 + self.sDB.mek_status.heating_rate = pkt.data[6] or 0.0 -- attempt to read mek_data table - if pkt.data[6] ~= nil then - local status = pcall(_copy_status, pkt.data[6]) + if pkt.data[7] ~= nil then + local status = pcall(_copy_status, pkt.data[7]) if status then -- copied in status data OK self.received_status_cache = true @@ -496,6 +499,11 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) -- get the session database function public.get_db() return self.sDB end + -- check if ramping is completed by first verifying auto command token ack + function public.is_ramp_complete() + return (self.sDB.auto_ack_token == self.auto_cmd_token) and (self.commanded_burn_rate == self.sDB.mek_status.act_burn_rate) + end + -- get the reactor structure function public.get_struct() if self.received_struct then @@ -618,6 +626,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) cmd.val = math.floor(cmd.val * 10) / 10 -- round to 10ths place if cmd.val > 0 and cmd.val <= self.sDB.mek_struct.max_burn then self.commanded_burn_rate = cmd.val + self.auto_cmd_token = 0 self.ramping_rate = false self.acks.burn_rate = false self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT @@ -630,6 +639,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) cmd.val = math.floor(cmd.val * 10) / 10 -- round to 10ths place if cmd.val > 0 and cmd.val <= self.sDB.mek_struct.max_burn then self.commanded_burn_rate = cmd.val + self.auto_cmd_token = 0 self.ramping_rate = true self.acks.burn_rate = false self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT @@ -640,14 +650,15 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) -- set automatic burn rate if self.auto_lock then cmd.val = math.floor(cmd.val * 10) / 10 -- round to 10ths place - if cmd.val > 0 and cmd.val <= self.sDB.mek_struct.max_burn then + if cmd.val >= 0 and cmd.val <= self.sDB.mek_struct.max_burn then + self.auto_cmd_token = util.time_ms() self.commanded_burn_rate = cmd.val -- this is only for manual control, only retry auto ramps self.acks.burn_rate = not self.ramping_rate self.retry_times.burn_rate_req = util.time() + INITIAL_AUTO_WAIT - _send(RPLC_TYPES.AUTO_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + _send(RPLC_TYPES.AUTO_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate, self.auto_cmd_token }) end end else @@ -717,10 +728,19 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) if not self.acks.burn_rate then if rtimes.burn_rate_req - util.time() <= 0 then - if self.auto_lock then - _send(RPLC_TYPES.AUTO_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) - else + if self.auto_cmd_token > 0 then + if self.auto_lock then + _send(RPLC_TYPES.AUTO_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate, self.auto_cmd_token }) + log.debug("retried auto burn rate?") + else + -- would have been an auto command, but disengaged, so stop retrying + self.acks.burn_rate = true + end + elseif not self.auto_lock then _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + else + -- shouldn't be in this state, just pretend it was acknowledged + self.acks.burn_rate = true end rtimes.burn_rate_req = util.time() + RETRY_PERIOD diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 742c5ce..aa0c5b7 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -409,8 +409,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) ---@return boolean complete function public.a_ramp_complete() if self.plc_i ~= nil then - local cur_rate = math.floor(self.plc_i.get_db().mek_status.burn_rate * 10) - return (cur_rate == self.ramp_target_br10) or (self.ramp_target_br10 == 0) + return self.plc_i.is_ramp_complete() else return true end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index ae879f4..2a1eb32 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.9.11" +local SUPERVISOR_VERSION = "beta-v0.9.12" local print = util.print local println = util.println From 3e74d6c998b7952a97559a7ccbe861bf5d678272 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 5 Feb 2023 02:07:54 -0500 Subject: [PATCH 486/587] #101 initial coordinator control interface completed --- coordinator/startup.lua | 2 +- coordinator/ui/components/processctl.lua | 30 ++++++++++++++++++++---- coordinator/ui/layout/main_view.lua | 2 +- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index e52c1b9..859d1d0 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.17" +local COORDINATOR_VERSION = "beta-v0.8.18" local print = util.print local println = util.println diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 0181a4d..517cbb2 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -44,7 +44,7 @@ local function new_view(root, x, y) local hzd_fg_bg = cpair(colors.white, colors.gray) local dis_colors = cpair(colors.white, colors.lightGray) - local main = Div{parent=root,width=80,height=24,x=x,y=y} + local main = Div{parent=root,width=104,height=24,x=x,y=y} local scram = HazardButton{parent=main,x=1,y=1,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=process.fac_scram,fg_bg=hzd_fg_bg} @@ -81,7 +81,7 @@ local function new_view(root, x, y) -- process control -- --------------------- - local proc = Div{parent=main,width=54,height=24,x=27,y=1} + local proc = Div{parent=main,width=78,height=24,x=27,y=1} ----------------------------- -- process control targets -- @@ -126,7 +126,7 @@ local function new_view(root, x, y) -- unit limits -- ----------------- - local limit_div = Div{parent=proc,width=40,height=19,x=34,y=6} + local limit_div = Div{parent=proc,width=21,height=19,x=34,y=6} local rate_limits = {} @@ -140,7 +140,7 @@ local function new_view(root, x, y) local lim_ctl = Div{parent=limit_div,x=9,y=_y,width=14,height=3,fg_bg=cpair(colors.gray,colors.white)} rate_limits[i] = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg} - TextBox{parent=lim_ctl,x=9,y=2,text="mB/t"} + TextBox{parent=lim_ctl,x=9,y=2,text="mB/t",width=4,height=1} unit.unit_ps.subscribe("max_burn", rate_limits[i].set_max) unit.unit_ps.subscribe("burn_limit", rate_limits[i].set_value) @@ -150,6 +150,28 @@ local function new_view(root, x, y) unit.unit_ps.subscribe("act_burn_rate", cur_burn.update) end + ------------------- + -- unit statuses -- + ------------------- + + local stat_div = Div{parent=proc,width=38,height=19,x=57,y=6} + + for i = 1, facility.num_units do + local unit = units[i] ---@type ioctl_unit + + local _y = ((i - 1) * 5) + 1 + + local unit_tag = Div{parent=stat_div,x=1,y=_y,width=8,height=4,fg_bg=cpair(colors.black,colors.lightBlue)} + TextBox{parent=unit_tag,x=2,y=2,text="Unit "..i.." Status",width=7,height=2} + + local lights = Div{parent=stat_div,x=9,y=_y,width=12,height=4,fg_bg=bw_fg_bg} + local ready = IndicatorLight{parent=lights,x=2,y=2,label="Ready",colors=cpair(colors.green,colors.gray)} + local degraded = IndicatorLight{parent=lights,x=2,y=3,label="Degraded",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + + unit.unit_ps.subscribe("U_AutoReady", ready.update) + unit.unit_ps.subscribe("U_AutoDegraded", degraded.update) + end + ------------------------- -- controls and status -- ------------------------- diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index cf41366..127761c 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -88,7 +88,7 @@ local function init(monitor) -- testing ---@fixme remove test code - ColorMap{parent=main,x=98,y=(main.height()-1)} + -- ColorMap{parent=main,x=98,y=(main.height()-1)} local audio = Div{parent=main,width=23,height=23,x=107,y=cnc_y_start} From c77993d3a01b4ca7cc247b68377e6002881185d2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 5 Feb 2023 12:15:41 -0500 Subject: [PATCH 487/587] bottom align process control panel and induction matrix view --- .vscode/settings.json | 3 +++ coordinator/startup.lua | 2 +- coordinator/ui/components/imatrix.lua | 2 +- coordinator/ui/layout/main_view.lua | 19 +++++++++++++------ 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 0cc34d6..6f2ebaa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,5 +15,8 @@ "periphemu", "mekanismEnergyHelper", "_HOST" + ], + "Lua.diagnostics.disable": [ + "duplicate-set-field" ] } diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 859d1d0..d9404f0 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.18" +local COORDINATOR_VERSION = "beta-v0.9.0" local print = util.print local println = util.println diff --git a/coordinator/ui/components/imatrix.lua b/coordinator/ui/components/imatrix.lua index c14e910..2910fbb 100644 --- a/coordinator/ui/components/imatrix.lua +++ b/coordinator/ui/components/imatrix.lua @@ -29,7 +29,7 @@ local function new_view(root, x, y, data, ps, id) local title = "INDUCTION MATRIX" if type(id) == "number" then title = title .. id end - local matrix = Div{parent=root,fg_bg=style.root,width=33,height=25,x=x,y=y} + local matrix = Div{parent=root,fg_bg=style.root,width=33,height=24,x=x,y=y} TextBox{parent=matrix,text=" ",width=33,height=1,x=1,y=1,fg_bg=cpair(colors.lightGray,colors.gray)} TextBox{parent=matrix,text=title,alignment=TEXT_ALIGN.CENTER,width=33,height=1,x=1,y=2,fg_bg=cpair(colors.lightGray,colors.gray)} diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index 127761c..b200f44 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -77,20 +77,27 @@ local function init(monitor) end end - -- command & control + -- command & control - TextBox{parent=main,y=cnc_y_start,text=util.strrep("\x8c", header.width()),alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=cpair(colors.lightGray,colors.gray)} + cnc_y_start = cnc_y_start - cnc_y_start = cnc_y_start + 2 + -- induction matrix and process control interfaces are 24 tall + space needed for divider + local cnc_bottom_align_start = main.height() - 26 - local process = process_ctl(main, 2, cnc_y_start) + assert(cnc_bottom_align_start >= cnc_y_start, "main display not of sufficient vertical resolution (add an additional row of monitors)") + + TextBox{parent=main,y=cnc_bottom_align_start,text=util.strrep("\x8c", header.width()),alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=cpair(colors.lightGray,colors.gray)} + + cnc_bottom_align_start = cnc_bottom_align_start + 2 + + local process = process_ctl(main, 2, cnc_bottom_align_start) -- testing ---@fixme remove test code -- ColorMap{parent=main,x=98,y=(main.height()-1)} - local audio = Div{parent=main,width=23,height=23,x=107,y=cnc_y_start} + local audio = Div{parent=main,width=23,height=23,x=107,y=cnc_bottom_align_start} PushButton{parent=audio,x=16,y=1,text="TEST 1",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_1} PushButton{parent=audio,x=16,text="TEST 2",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_2} @@ -116,7 +123,7 @@ local function init(monitor) SwitchButton{parent=audio,x=1,text="RCS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rcs} SwitchButton{parent=audio,x=1,text="TURBINE TRIP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_turbinet} - local imatrix_1 = imatrix(main, 131, cnc_y_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1]) + local imatrix_1 = imatrix(main, 131, cnc_bottom_align_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1]) return main end From 1100051585a61fc24749934cc1de75014a0ade60 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 5 Feb 2023 13:04:42 -0500 Subject: [PATCH 488/587] #151 improved RCS alarm behavior --- supervisor/session/facility.lua | 2 +- supervisor/session/plc.lua | 1 - supervisor/session/unit.lua | 6 ++++-- supervisor/session/unitlogic.lua | 16 +++++++--------- supervisor/startup.lua | 2 +- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index af3e98b..3245ac0 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -524,7 +524,7 @@ function facility.new(num_reactors, cooling_conf) ready = false elseif (self.mode_set == PROCESS.GEN_RATE) and (self.gen_rate_target <= 0) then ready = false - elseif (self.mode_set == PROCESS.BURN_RATE) and (self.burn_target <= 0.1) then + elseif (self.mode_set == PROCESS.BURN_RATE) and (self.burn_target < 0.1) then ready = false end diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index bf9037e..3cf6468 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -731,7 +731,6 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) if self.auto_cmd_token > 0 then if self.auto_lock then _send(RPLC_TYPES.AUTO_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate, self.auto_cmd_token }) - log.debug("retried auto burn rate?") else -- would have been an auto command, but disengaged, so stop retrying self.acks.burn_rate = true diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index aa0c5b7..ca2d640 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -26,6 +26,7 @@ local IO = rsio.IO local FLOW_STABILITY_DELAY_MS = 15000 local DT_KEYS = { + ReactorBurnR = "RBR", ReactorTemp = "RTP", ReactorFuel = "RFL", ReactorWaste = "RWS", @@ -86,7 +87,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) status_text = { "UNKNOWN", "awaiting connection..." }, -- logic for alarms had_reactor = false, - start_ms = 0, + last_rate_change_ms = 0, plc_cache = { active = false, ok = false, @@ -245,6 +246,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) local last_update_s = plc_db.last_status_update / 1000.0 + _compute_dt(DT_KEYS.ReactorBurnR, plc_db.mek_status.act_burn_rate, last_update_s) _compute_dt(DT_KEYS.ReactorTemp, plc_db.mek_status.temp, last_update_s) _compute_dt(DT_KEYS.ReactorFuel, plc_db.mek_status.fuel, last_update_s) _compute_dt(DT_KEYS.ReactorWaste, plc_db.mek_status.waste, last_update_s) @@ -409,7 +411,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) ---@return boolean complete function public.a_ramp_complete() if self.plc_i ~= nil then - return self.plc_i.is_ramp_complete() + return self.plc_i.is_ramp_complete() or (self.plc_i.get_status().act_burn_rate == 0 and self.db.control.br10 == 0) else return true end end diff --git a/supervisor/session/unitlogic.lua b/supervisor/session/unitlogic.lua index 4354fde..8043a4b 100644 --- a/supervisor/session/unitlogic.lua +++ b/supervisor/session/unitlogic.lua @@ -54,11 +54,9 @@ function logic.update_annunciator(self) self.db.control.lim_br10 = math.floor(plc_db.mek_struct.max_burn * 10) end - -- record reactor start time (some alarms are delayed during reactor heatup) - if self.start_ms == 0 and plc_db.mek_status.status then - self.start_ms = util.time_ms() - elseif not plc_db.mek_status.status then - self.start_ms = 0 + -- some alarms wait until the burn rate has stabilized, so keep track of that + if math.abs(_get_dt(DT_KEYS.ReactorBurnR)) > 0 then + self.last_rate_change_ms = util.time_ms() end -- record reactor stats @@ -237,8 +235,8 @@ function logic.update_annunciator(self) self.db.annunciator.TurbineOnline[session.get_device_idx()] = true end - -- check for boil rate mismatch (either between reactor and turbine or boiler and turbine) - self.db.annunciator.BoilRateMismatch = math.abs(total_boil_rate - total_input_rate) > 4 + -- check for boil rate mismatch (> 4% error) either between reactor and turbine or boiler and turbine + self.db.annunciator.BoilRateMismatch = math.abs(total_boil_rate - total_input_rate) > (0.04 * total_boil_rate) -- check for steam feed mismatch and max return rate local sfmismatch = math.abs(total_flow_rate - total_input_rate) > 10 @@ -436,7 +434,7 @@ function logic.update_alarms(self) -- annunciator indicators for these states may not indicate a real issue when: -- > flow is ramping up right after reactor start -- > flow is ramping down after reactor shutdown - if (util.time_ms() - self.start_ms > self.defs.FLOW_STABILITY_DELAY_MS) and plc_cache.active then + if ((util.time_ms() - self.last_rate_change_ms) > self.defs.FLOW_STABILITY_DELAY_MS) and plc_cache.active then rcs_trans = rcs_trans or annunc.BoilRateMismatch or annunc.CoolantFeedMismatch or annunc.SteamFeedMismatch end @@ -517,7 +515,7 @@ function logic.update_status_text(self) self.status_text[2] = "insufficient fuel input rate" elseif self.db.annunciator.WasteLineOcclusion then self.status_text[2] = "insufficient waste output rate" - elseif (util.time_ms() - self.start_ms) <= self.defs.FLOW_STABILITY_DELAY_MS then + elseif (util.time_ms() - self.last_rate_change_ms) <= self.defs.FLOW_STABILITY_DELAY_MS then if self.num_turbines > 1 then self.status_text[2] = "turbines spinning up" else diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 2a1eb32..8b2e4e8 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.9.12" +local SUPERVISOR_VERSION = "beta-v0.9.13" local print = util.print local println = util.println From 1d3a1672c80c67f796ff98aac96572cd64237501 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 7 Feb 2023 00:32:50 -0500 Subject: [PATCH 489/587] #102 #21 auto control loop with induction matrix and unit alarm checks and handling --- .gitignore | 1 + coordinator/iocontrol.lua | 17 +-- coordinator/startup.lua | 2 +- coordinator/ui/components/processctl.lua | 4 +- reactor-plc/plc.lua | 17 +++ reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 1 + rtu/startup.lua | 2 +- scada-common/comms.lua | 52 +++---- scada-common/types.lua | 5 +- supervisor/session/facility.lua | 153 ++++++++++++++++----- supervisor/session/plc.lua | 30 +++- supervisor/session/unit.lua | 166 ++++++++++++++++------- supervisor/session/unitlogic.lua | 6 +- supervisor/startup.lua | 2 +- 15 files changed, 327 insertions(+), 133 deletions(-) diff --git a/.gitignore b/.gitignore index 200fed8..17ae161 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ _notes/ +program.sh \ No newline at end of file diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 914f7ee..51399a6 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -28,9 +28,8 @@ function iocontrol.init(conf, comms) auto_ready = false, auto_active = false, auto_ramping = false, + auto_saturated = false, auto_scram = false, - ---@todo not currently used or set - auto_scram_cause = "ok", ---@type auto_scram_cause num_units = conf.num_units, ---@type integer @@ -274,24 +273,26 @@ function iocontrol.update_facility_status(status) local ctl_status = status[1] - if type(ctl_status) == "table" then + if type(ctl_status) == "table" and (#ctl_status == 9) then fac.all_sys_ok = ctl_status[1] fac.auto_ready = ctl_status[2] fac.auto_active = ctl_status[3] > 0 fac.auto_ramping = ctl_status[4] - fac.auto_scram = ctl_status[5] - fac.status_line_1 = ctl_status[6] - fac.status_line_2 = ctl_status[7] + fac.auto_saturated = ctl_status[5] + fac.auto_scram = ctl_status[6] + fac.status_line_1 = ctl_status[7] + fac.status_line_2 = ctl_status[8] fac.ps.publish("all_sys_ok", fac.all_sys_ok) fac.ps.publish("auto_ready", fac.auto_ready) fac.ps.publish("auto_active", fac.auto_active) fac.ps.publish("auto_ramping", fac.auto_ramping) + fac.ps.publish("auto_saturated", fac.auto_saturated) fac.ps.publish("auto_scram", fac.auto_scram) fac.ps.publish("status_line_1", fac.status_line_1) fac.ps.publish("status_line_2", fac.status_line_2) - local group_map = ctl_status[8] + local group_map = ctl_status[9] if (type(group_map) == "table") and (#group_map == fac.num_units) then local names = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } @@ -302,7 +303,7 @@ function iocontrol.update_facility_status(status) end end else - log.debug(log_header .. "control status not a table") + log.debug(log_header .. "control status not a table or length mismatch") end -- RTU statuses diff --git a/coordinator/startup.lua b/coordinator/startup.lua index d9404f0..74ae70a 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.0" +local COORDINATOR_VERSION = "beta-v0.9.1" local print = util.print local println = util.println diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 517cbb2..9b9b2a3 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -62,11 +62,13 @@ local function new_view(root, x, y) local auto_ready = IndicatorLight{parent=main,label="Configured Units Ready",colors=cpair(colors.green,colors.red)} local auto_act = IndicatorLight{parent=main,label="Process Active",colors=cpair(colors.green,colors.gray)} local auto_ramp = IndicatorLight{parent=main,label="Process Ramping",colors=cpair(colors.white,colors.gray),flash=true,period=period.BLINK_250_MS} + local auto_sat = IndicatorLight{parent=main,label="Max Burn Rate",colors=cpair(colors.yellow,colors.gray)} local auto_scram = IndicatorLight{parent=main,label="Automatic SCRAM",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} facility.ps.subscribe("auto_ready", auto_ready.update) facility.ps.subscribe("auto_active", auto_act.update) facility.ps.subscribe("auto_ramping", auto_ramp.update) + facility.ps.subscribe("auto_saturated", auto_sat.update) facility.ps.subscribe("auto_scram", auto_scram.update) main.line_break() @@ -176,7 +178,7 @@ local function new_view(root, x, y) -- controls and status -- ------------------------- - local ctl_opts = { "Regulated", "Burn Rate", "Charge Level", "Generation Rate" } + local ctl_opts = { "Monitored Max Burn", "Combined Burn Rate", "Charge Level", "Generation Rate" } local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.purple,colors.black),radio_bg=colors.gray} facility.ps.subscribe("process_mode", mode.set_value) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 57c8471..a23ad3f 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -387,6 +387,19 @@ function plc.rps_init(reactor, is_formed) if not quiet then log.info("RPS: reset") end end + -- reset the automatic and timeout trip flags, then clear trip if that was the trip cause + function public.auto_reset() + self.state[state_keys.automatic] = false + self.state[state_keys.timeout] = false + + if self.trip_cause == rps_status_t.automatic or self.trip_cause == rps_status_t.timeout then + self.trip_cause = rps_status_t.ok + self.tripped = false + end + + log.info("RPS: auto reset") + end + return public end @@ -808,6 +821,10 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co -- reset the RPS status rps.reset() _send_ack(packet.type, true) + elseif packet.type == RPLC_TYPES.RPS_AUTO_RESET then + -- reset automatic SCRAM and timeout trips + rps.auto_reset() + _send_ack(packet.type, true) elseif packet.type == RPLC_TYPES.AUTO_BURN_RATE then -- automatic control requested a new burn rate if (packet.length == 3) and (type(packet.data[1]) == "number") and (type(packet.data[3]) == "number") then diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 8ac0de2..207fec6 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.10.4" +local R_PLC_VERSION = "beta-v0.10.5" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 69d2ff1..1c3c29f 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -266,6 +266,7 @@ function threads.thread__main(smem, init) -- this thread cannot be slept because it will miss events (namely "terminate" otherwise) if not plc_state.shutdown then log.info("main thread restarting now...") +---@diagnostic disable-next-line: param-type-mismatch util.push_event("clock_start") end end diff --git a/rtu/startup.lua b/rtu/startup.lua index dec02ab..975784d 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.9.12" +local RTU_VERSION = "beta-v0.9.13" local rtu_t = types.rtu_t diff --git a/scada-common/comms.lua b/scada-common/comms.lua index b1a68f7..1234c31 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -12,7 +12,7 @@ local rtu_t = types.rtu_t local insert = table.insert -comms.version = "1.2.0" +comms.version = "1.3.0" ---@alias PROTOCOLS integer local PROTOCOLS = { @@ -34,7 +34,8 @@ local RPLC_TYPES = { RPS_STATUS = 6, -- RPS status RPS_ALARM = 7, -- RPS alarm broadcast RPS_RESET = 8, -- clear RPS trip (if in bad state, will trip immediately) - AUTO_BURN_RATE = 9 -- set an automatic burn rate, PLC will respond with status, enable toggle speed limited + RPS_AUTO_RESET = 9, -- clear RPS trip if it is just a timeout or auto scram + AUTO_BURN_RATE = 10 -- set an automatic burn rate, PLC will respond with status, enable toggle speed limited } ---@alias SCADA_MGMT_TYPES integer @@ -223,12 +224,12 @@ end function comms.modbus_packet() local self = { frame = nil, - raw = nil, - txn_id = nil, - length = nil, - unit_id = nil, - func_code = nil, - data = nil + raw = {}, + txn_id = -1, + length = 0, + unit_id = -1, + func_code = 0, + data = {} } ---@class modbus_packet @@ -312,11 +313,11 @@ end function comms.rplc_packet() local self = { frame = nil, - raw = nil, - id = nil, - type = nil, - length = nil, - body = nil + raw = {}, + id = 0, + type = -1, + length = 0, + data = {} } ---@class rplc_packet @@ -333,6 +334,7 @@ function comms.rplc_packet() self.type == RPLC_TYPES.RPS_STATUS or self.type == RPLC_TYPES.RPS_ALARM or self.type == RPLC_TYPES.RPS_RESET or + self.type == RPLC_TYPES.RPS_AUTO_RESET or self.type == RPLC_TYPES.AUTO_BURN_RATE end @@ -411,10 +413,10 @@ end function comms.mgmt_packet() local self = { frame = nil, - raw = nil, - type = nil, - length = nil, - data = nil + raw = {}, + type = -1, + length = 0, + data = {} } ---@class mgmt_packet @@ -500,10 +502,10 @@ end function comms.crdn_packet() local self = { frame = nil, - raw = nil, - type = nil, - length = nil, - data = nil + raw = {}, + type = -1, + length = 0, + data = {} } ---@class crdn_packet @@ -590,10 +592,10 @@ end function comms.capi_packet() local self = { frame = nil, - raw = nil, - type = nil, - length = nil, - data = nil + raw = {}, + type = -1, + length = 0, + data = {} } ---@class capi_packet diff --git a/scada-common/types.lua b/scada-common/types.lua index d4db1e9..eaa355c 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -37,6 +37,8 @@ types.TRI_FAIL = { ---@alias PROCESS integer types.PROCESS = { + UNIT_ALARM_IDLE = -2, + MATRIX_FAULT_IDLE = -1, INACTIVE = 0, SIMPLE = 1, BURN_RATE = 2, @@ -173,9 +175,6 @@ types.ALARM_STATE = { ---| "sys_fail" ---| "force_disabled" ----@alias auto_scram_cause ----| "ok" - ---@alias rtu_t string types.rtu_t = { redstone = "redstone", diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index 3245ac0..9d82477 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -12,13 +12,14 @@ local PROCESS = types.PROCESS -- 2856 FE per blade per 1 mB, 285.6 FE per blade per 0.1 mB (minimum) local POWER_PER_BLADE = util.joules_to_fe(7140) -local MAX_CHARGE = 0.99 +local HIGH_CHARGE = 1.0 local RE_ENABLE_CHARGE = 0.95 local AUTO_SCRAM = { NONE = 0, MATRIX_DC = 1, - MATRIX_FILL = 2 + MATRIX_FILL = 2, + CRIT_ALARM = 3 } local charge_Kp = 1.0 @@ -46,6 +47,7 @@ function facility.new(num_reactors, cooling_conf) units_ready = false, mode = PROCESS.INACTIVE, last_mode = PROCESS.INACTIVE, + return_mode = PROCESS.INACTIVE, mode_set = PROCESS.SIMPLE, max_burn_combined = 0.0, -- maximum burn rate to clamp at burn_target = 0.1, -- burn rate target for aggregate burn mode @@ -53,6 +55,7 @@ function facility.new(num_reactors, cooling_conf) gen_rate_target = 0, -- FE/t charge rate target group_map = { 0, 0, 0, 0 }, -- units -> group IDs prio_defs = { {}, {}, {}, {} }, -- priority definitions (each level is a table of units) + at_max_burn = false, ascram = false, ascram_reason = AUTO_SCRAM.NONE, -- closed loop control @@ -102,6 +105,7 @@ function facility.new(num_reactors, cooling_conf) -- split a burn rate among the reactors ---@param burn_rate number burn rate assignment ---@param ramp boolean true to ramp, false to set right away + ---@return integer unallocated local function _allocate_burn_rate(burn_rate, ramp) local unallocated = math.floor(burn_rate * 10) @@ -117,32 +121,38 @@ function facility.new(num_reactors, cooling_conf) splits[#units] = splits[#units] + (unallocated % #units) -- go through all reactor units in this group - for u = 1, #units do - local ctl = units[u].get_control_inf() ---@type unit_control + for id = 1, #units do + local u = units[id] ---@type reactor_unit + + local ctl = u.get_control_inf() + local lim_br10 = u.a_get_effective_limit() + local last = ctl.br10 - if splits[u] <= ctl.lim_br10 then - ctl.br10 = splits[u] + if splits[id] <= lim_br10 then + ctl.br10 = splits[id] else - ctl.br10 = ctl.lim_br10 + ctl.br10 = lim_br10 - if u < #units then - local remaining = #units - u + if id < #units then + local remaining = #units - id split = math.floor(unallocated / remaining) - for x = (u + 1), #units do splits[x] = split end + for x = (id + 1), #units do splits[x] = split end splits[#units] = splits[#units] + (unallocated % remaining) end end - unallocated = unallocated - ctl.br10 + unallocated = math.max(0, unallocated - ctl.br10) - if last ~= ctl.br10 then units[u].a_commit_br10(ramp) end + if last ~= ctl.br10 then + log.debug("unit " .. id .. ": set to " .. ctl.br10 .. " (was " .. last .. ")") + u.a_commit_br10(ramp) + end end end - - -- stop if fully allocated - if unallocated <= 0 then break end end + + return unallocated end -- PUBLIC FUNCTIONS -- @@ -215,10 +225,18 @@ function facility.new(num_reactors, cooling_conf) local now = util.time_s() local state_changed = self.mode ~= self.last_mode + local next_mode = self.mode -- once auto control is started, sort the priority sublists by limits if state_changed then + self.saturated = false + + log.debug("FAC: state changed from " .. self.last_mode .. " to " .. self.mode) + if self.last_mode == PROCESS.INACTIVE then + ---@todo change this to be a reset button + if self.mode ~= PROCESS.MATRIX_FAULT_IDLE then self.ascram = false end + local blade_count = 0 self.max_burn_combined = 0.0 @@ -259,27 +277,31 @@ function facility.new(num_reactors, cooling_conf) self.initial_ramp = false end - if self.mode == PROCESS.INACTIVE then - -- check if we are ready to start when that time comes - self.units_ready = true - for i = 1, #self.prio_defs do - for _, u in pairs(self.prio_defs[i]) do - self.units_ready = self.units_ready and u.get_control_inf().ready - end + -- update unit ready state + self.units_ready = true + for i = 1, #self.prio_defs do + for _, u in pairs(self.prio_defs[i]) do + self.units_ready = self.units_ready and u.get_control_inf().ready end + end + -- perform mode-specific operations + if self.mode == PROCESS.INACTIVE then if self.units_ready then self.status_text = { "IDLE", "control disengaged" } else self.status_text = { "NOT READY", "assigned units not ready" } end elseif self.mode == PROCESS.SIMPLE then - -- run units at their last configured set point + -- run units at their limits if state_changed then self.time_start = now - ---@todo will still need to ramp? + self.saturated = true + self.status_text = { "MONITORED MODE", "running reactors at limit" } log.debug(util.c("FAC: SIMPLE mode first call completed")) end + + _allocate_burn_rate(self.max_burn_combined, true) elseif self.mode == PROCESS.BURN_RATE then -- a total aggregate burn rate if state_changed then @@ -294,7 +316,8 @@ function facility.new(num_reactors, cooling_conf) end if not self.waiting_on_ramp then - _allocate_burn_rate(self.burn_target, self.initial_ramp) + local unallocated = _allocate_burn_rate(self.burn_target, true) + self.saturated = self.burn_target == self.max_burn_combined or unallocated > 0 if self.initial_ramp then self.status_text = { "BURN RATE MODE", "ramping reactors" } @@ -397,16 +420,27 @@ function facility.new(num_reactors, cooling_conf) _allocate_burn_rate(sp_c, false) end + elseif self.mode == PROCESS.MATRIX_FAULT_IDLE then + -- exceeded charge, wait until condition clears + if self.ascram_reason == AUTO_SCRAM.NONE then + next_mode = self.return_mode + log.info("FAC: exiting matrix fault idle state due to fault resolution") + elseif self.ascram_reason == AUTO_SCRAM.CRIT_ALARM then + next_mode = PROCESS.INACTIVE + log.info("FAC: exiting matrix fault idle state due to critical unit alarm") + end + elseif self.mode == PROCESS.UNIT_ALARM_IDLE then + -- do nothing, wait for user to confirm (stop and reset) elseif self.mode ~= PROCESS.INACTIVE then log.error(util.c("FAC: unsupported process mode ", self.mode, ", switching to inactive")) - self.mode = PROCESS.INACTIVE + next_mode = PROCESS.INACTIVE end ------------------------------ -- Evaluate Automatic SCRAM -- ------------------------------ - if self.mode ~= PROCESS.INACTIVE then + if (self.mode ~= PROCESS.INACTIVE) and (self.mode ~= PROCESS.UNIT_ALARM_IDLE) then local scram = false if self.induction[1] ~= nil then @@ -415,37 +449,93 @@ function facility.new(num_reactors, cooling_conf) if self.ascram_reason == AUTO_SCRAM.MATRIX_DC then self.ascram_reason = AUTO_SCRAM.NONE + log.info("FAC: cleared automatic SCRAM trip due to prior induction matrix disconnect") end - if (db.tanks.energy_fill > MAX_CHARGE) or + if (db.tanks.energy_fill >= HIGH_CHARGE) or (self.ascram_reason == AUTO_SCRAM.MATRIX_FILL and db.tanks.energy_fill > RE_ENABLE_CHARGE) then scram = true + if self.mode ~= PROCESS.MATRIX_FAULT_IDLE then + self.return_mode = self.mode + next_mode = PROCESS.MATRIX_FAULT_IDLE + end + if self.ascram_reason == AUTO_SCRAM.NONE then self.ascram_reason = AUTO_SCRAM.MATRIX_FILL end + elseif self.ascram_reason == AUTO_SCRAM.MATRIX_FILL then + log.info("FAC: charge state of induction matrix entered acceptable range <= " .. (RE_ENABLE_CHARGE * 100) .. "%") + self.ascram_reason = AUTO_SCRAM.NONE + end + + for i = 1, #self.units do + local u = self.units[i] ---@type reactor_unit + + if u.has_critical_alarm() then + scram = true + + if self.ascram_reason == AUTO_SCRAM.NONE then + self.ascram_reason = AUTO_SCRAM.CRIT_ALARM + end + + next_mode = PROCESS.UNIT_ALARM_IDLE + + log.info("FAC: emergency exit of process control due to critical unit alarm") + break + end end else scram = true + + if self.mode ~= PROCESS.MATRIX_FAULT_IDLE then + self.return_mode = self.mode + next_mode = PROCESS.MATRIX_FAULT_IDLE + end + if self.ascram_reason == AUTO_SCRAM.NONE then self.ascram_reason = AUTO_SCRAM.MATRIX_DC end end -- SCRAM all units - if not self.ascram and scram then + if (not self.ascram) and scram then for i = 1, #self.prio_defs do for _, u in pairs(self.prio_defs[i]) do u.a_scram() end end - self.ascram = true + if self.ascram_reason == AUTO_SCRAM.MATRIX_DC then + log.info("FAC: automatic SCRAM due to induction matrix disconnection") + self.status_text = { "AUTOMATIC SCRAM", "induction matrix disconnected" } + elseif self.ascram_reason == AUTO_SCRAM.MATRIX_FILL then + log.info("FAC: automatic SCRAM due to induction matrix high charge") + self.status_text = { "AUTOMATIC SCRAM", "induction matrix fill high" } + elseif self.ascram_reason == AUTO_SCRAM.CRIT_ALARM then + log.info("FAC: automatic SCRAM due to critical unit alarm") + self.status_text = { "AUTOMATIC SCRAM", "critical unit alarm tripped" } + else + log.error(util.c("FAC: automatic SCRAM reason (", self.ascram_reason, ") not set to a known value")) + end + end + + self.ascram = scram + + -- clear PLC SCRAM if we should + if not self.ascram then + self.ascram_reason = AUTO_SCRAM.NONE + + for i = 1, #self.units do + local u = self.units[i] ---@type reactor_unit + u.a_cond_rps_reset() + end end end - -- update last mode + -- update last mode and set next mode self.last_mode = self.mode + self.mode = next_mode end -- call the update function of all units in the facility @@ -580,6 +670,7 @@ function facility.new(num_reactors, cooling_conf) self.units_ready, self.mode, self.waiting_on_ramp, + self.at_max_burn or self.saturated, self.ascram, self.status_text[1], self.status_text[2], diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 3cf6468..45e8bae 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -28,7 +28,8 @@ local PLC_S_CMDS = { SCRAM = 1, ASCRAM = 2, ENABLE = 3, - RPS_RESET = 4 + RPS_RESET = 4, + RPS_AUTO_RESET = 5 } local PLC_S_DATA = { @@ -445,18 +446,29 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) cmd = UNIT_COMMANDS.RESET_RPS, ack = ack }) + elseif pkt.type == RPLC_TYPES.RPS_AUTO_RESET then + -- RPS auto control reset acknowledgement + local ack = _get_ack(pkt) + if ack then + self.auto_scram = false + else + log.debug(log_header .. "RPS auto reset failed") + end elseif pkt.type == RPLC_TYPES.AUTO_BURN_RATE then if pkt.length == 1 then local ack = pkt.data[1] - self.acks.burn_rate = ack ~= PLC_AUTO_ACK.FAIL - - ---@todo implement error handling here if ack == PLC_AUTO_ACK.FAIL then - elseif ack == PLC_AUTO_ACK.DIRECT_SET_OK then - elseif ack == PLC_AUTO_ACK.RAMP_SET_OK then - elseif ack == PLC_AUTO_ACK.ZERO_DIS_OK then + self.acks.burn_rate = false + log.debug(log_header .. "RPLC automatic burn rate set fail") + elseif ack == PLC_AUTO_ACK.DIRECT_SET_OK or ack == PLC_AUTO_ACK.RAMP_SET_OK or ack == PLC_AUTO_ACK.ZERO_DIS_OK then + self.acks.burn_rate = true elseif ack == PLC_AUTO_ACK.ZERO_DIS_WAIT then + self.acks.burn_rate = false + log.debug(log_header .. "RPLC automatic burn rate too soon to disable at 0 mB/t") + else + self.acks.burn_rate = false + log.debug(log_header .. "RPLC automatic burn rate ack unknown") end else log.debug(log_header .. "RPLC automatic burn rate ack packet length mismatch") @@ -614,6 +626,10 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) self.acks.rps_reset = false self.retry_times.rps_reset_req = util.time() + INITIAL_WAIT _send(RPLC_TYPES.RPS_RESET, {}) + elseif cmd == PLC_S_CMDS.RPS_AUTO_RESET then + if self.auto_scram or self.sDB.rps_status.timeout then + _send(RPLC_TYPES.RPS_AUTO_RESET, {}) + end else log.warning(log_header .. "unsupported command received in in_queue (this is a bug)") end diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index ca2d640..fc22ed6 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -375,55 +375,6 @@ function unit.new(for_reactor, num_boilers, num_turbines) --#endregion - -- AUTO CONTROL -- - --#region - - -- engage automatic control - function public.a_engage() - self.db.annunciator.AutoControl = true - if self.plc_i ~= nil then - self.plc_i.auto_lock(true) - end - end - - -- disengage automatic control - function public.a_disengage() - self.db.annunciator.AutoControl = false - if self.plc_i ~= nil then - self.plc_i.auto_lock(false) - self.db.control.br10 = 0 - end - end - - -- set the automatic burn rate based on the last set br10 - ---@param ramp boolean true to ramp to rate, false to set right away - function public.a_commit_br10(ramp) - if self.db.annunciator.AutoControl then - if self.plc_i ~= nil then - self.plc_i.auto_set_burn(self.db.control.br10 / 10, ramp) - - if ramp then self.ramp_target_br10 = self.db.control.br10 end - end - end - end - - -- check if ramping is complete (burn rate is same as target) - ---@return boolean complete - function public.a_ramp_complete() - if self.plc_i ~= nil then - return self.plc_i.is_ramp_complete() or (self.plc_i.get_status().act_burn_rate == 0 and self.db.control.br10 == 0) - else return true end - end - - -- perform an automatic SCRAM - function public.a_scram() - if self.plc_s ~= nil then - self.plc_s.in_queue.push_command(PLC_S_CMDS.ASCRAM) - end - end - - --#endregion - -- UPDATE SESSION -- -- update (iterate) this unit @@ -444,6 +395,32 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- update degraded state for auto control self.db.control.degraded = (#self.boilers ~= num_boilers) or (#self.turbines ~= num_turbines) or (self.plc_i == nil) + -- check boilers formed/faulted + for i = 1, #self.boilers do + local sess = self.boilers[i] ---@type unit_session + local boiler = sess.get_db() ---@type boilerv_session_db + if sess.is_faulted() or not boiler.formed then + self.db.control.degraded = true + end + end + + -- check turbines formed/faulted + for i = 1, #self.turbines do + local sess = self.turbines[i] ---@type unit_session + local turbine = sess.get_db() ---@type turbinev_session_db + if sess.is_faulted() or not turbine.formed then + self.db.control.degraded = true + end + end + + -- check plc formed/faulted + if self.plc_i ~= nil then + local rps = self.plc_i.get_rps() + if rps.fault or rps.sys_fail then + self.db.control.degraded = true + end + end + -- update deltas _dt__compute_all() @@ -457,7 +434,82 @@ function unit.new(for_reactor, num_boilers, num_turbines) logic.update_status_text(self) end + -- AUTO CONTROL OPERATIONS -- + --#region + + -- engage automatic control + function public.a_engage() + self.db.annunciator.AutoControl = true + if self.plc_i ~= nil then + self.plc_i.auto_lock(true) + end + end + + -- disengage automatic control + function public.a_disengage() + self.db.annunciator.AutoControl = false + if self.plc_i ~= nil then + self.plc_i.auto_lock(false) + self.db.control.br10 = 0 + end + end + + -- get the actual limit of this unit + -- + -- if it is degraded or not ready, the limit will be 0 + ---@return integer lim_br10 + function public.a_get_effective_limit() + if not self.db.control.ready or self.db.control.degraded or self.plc_cache.rps_trip then + self.db.control.br10 = 0 + return 0 + else + return self.db.control.lim_br10 + end + end + + -- set the automatic burn rate based on the last set br10 + ---@param ramp boolean true to ramp to rate, false to set right away + function public.a_commit_br10(ramp) + if self.db.annunciator.AutoControl then + if self.plc_i ~= nil then + self.plc_i.auto_set_burn(self.db.control.br10 / 10, ramp) + + if ramp then self.ramp_target_br10 = self.db.control.br10 end + end + end + end + + -- check if ramping is complete (burn rate is same as target) + ---@return boolean complete + function public.a_ramp_complete() + if self.plc_i ~= nil then + return self.plc_i.is_ramp_complete() or + (self.plc_i.get_status().act_burn_rate == 0 and self.db.control.br10 == 0) or + public.a_get_effective_limit() == 0 + else return true end + end + + -- perform an automatic SCRAM + function public.a_scram() + if self.plc_s ~= nil then + self.plc_s.in_queue.push_command(PLC_S_CMDS.ASCRAM) + end + end + + -- queue a command to clear timeout/auto-scram if set + function public.a_cond_rps_reset() + if self.plc_s ~= nil and self.plc_i ~= nil then + local rps = self.plc_i.get_rps() + if rps.timeout or rps.automatic then + self.plc_s.in_queue.push_command(PLC_S_CMDS.RPS_AUTO_RESET) + end + end + end + + --#endregion + -- OPERATIONS -- + --#region -- queue a command to SCRAM the reactor function public.scram() @@ -537,7 +589,21 @@ function unit.new(for_reactor, num_boilers, num_turbines) end end + --#endregion + -- READ STATES/PROPERTIES -- + --#region + + -- check if a critical alarm is tripped + function public.has_critical_alarm() + for _, data in pairs(self.alarms) do + if data.tier == PRIO.CRITICAL and (data.state == AISTATE.TRIPPED or data.state == AISTATE.ACKED) then + return true + end + end + + return false + end -- get build properties of all machines function public.get_build() @@ -622,6 +688,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- get the reactor ID function public.get_id() return self.r_id end + --#endregion + return public end diff --git a/supervisor/session/unitlogic.lua b/supervisor/session/unitlogic.lua index 8043a4b..ab96a11 100644 --- a/supervisor/session/unitlogic.lua +++ b/supervisor/session/unitlogic.lua @@ -516,11 +516,7 @@ function logic.update_status_text(self) elseif self.db.annunciator.WasteLineOcclusion then self.status_text[2] = "insufficient waste output rate" elseif (util.time_ms() - self.last_rate_change_ms) <= self.defs.FLOW_STABILITY_DELAY_MS then - if self.num_turbines > 1 then - self.status_text[2] = "turbines spinning up" - else - self.status_text[2] = "turbine spinning up" - end + self.status_text[2] = "awaiting flow stability" else self.status_text[2] = "system nominal" end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 8b2e4e8..79282c0 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.9.13" +local SUPERVISOR_VERSION = "beta-v0.9.14" local print = util.print local println = util.println From 6c09772a74b0e1e753e856bd1a642551ab3ecc8f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 7 Feb 2023 17:31:22 -0500 Subject: [PATCH 490/587] #76 added trusted connection ranges for modem messages --- coordinator/config.lua | 2 ++ coordinator/coordinator.lua | 19 +++++++++++-------- coordinator/startup.lua | 6 ++++-- reactor-plc/config.lua | 2 ++ reactor-plc/plc.lua | 31 +++++++++++++++++-------------- reactor-plc/startup.lua | 5 +++-- rtu/config.lua | 15 +++++++++++---- rtu/rtu.lua | 29 ++++++++++++++++------------- rtu/startup.lua | 6 ++++-- scada-common/comms.lua | 18 ++++++++++++++++++ supervisor/config.lua | 2 ++ supervisor/startup.lua | 5 +++-- supervisor/supervisor.lua | 31 ++++++++++++++++--------------- 13 files changed, 109 insertions(+), 62 deletions(-) diff --git a/coordinator/config.lua b/coordinator/config.lua index 983c7d9..b88593f 100644 --- a/coordinator/config.lua +++ b/coordinator/config.lua @@ -6,6 +6,8 @@ config.SCADA_SV_PORT = 16100 config.SCADA_SV_LISTEN = 16101 -- listen port for SCADA coordinator API access config.SCADA_API_LISTEN = 16200 +-- max trusted modem message distance (0 to disable check) +config.TRUSTED_RANGE = 0 -- expected number of reactor units, used only to require that number of unit monitors config.NUM_UNITS = 4 diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index b13f4a2..7df62d0 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -191,13 +191,14 @@ function coordinator.log_comms_connecting(message) end -- coordinator communications ----@param version string ----@param modem table ----@param sv_port integer ----@param sv_listen integer ----@param api_listen integer +---@param version string coordinator version +---@param modem table modem device +---@param sv_port integer port of configured supervisor +---@param sv_listen integer listening port for supervisor replys +---@param api_listen integer listening port for pocket API +---@param range integer trusted device connection range ---@param sv_watchdog watchdog -function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_watchdog) +function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range, sv_watchdog) local self = { sv_linked = false, sv_seq_num = 0, @@ -209,6 +210,8 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa ---@class coord_comms local public = {} + comms.set_trusted_range(range) + -- PRIVATE FUNCTIONS -- -- configure modem channels @@ -512,8 +515,8 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa ---@class facility_conf local conf = { - num_units = config[1], - defs = {} -- boilers and turbines + num_units = config[1], ---@type integer + defs = {} -- boilers and turbines } if (#config - 1) == (conf.num_units * 2) then diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 74ae70a..e789f38 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.1" +local COORDINATOR_VERSION = "beta-v0.9.2" local print = util.print local println = util.println @@ -41,6 +41,7 @@ local cfv = util.new_validator() cfv.assert_port(config.SCADA_SV_PORT) cfv.assert_port(config.SCADA_SV_LISTEN) cfv.assert_port(config.SCADA_API_LISTEN) +cfv.assert_type_int(config.TRUSTED_RANGE) cfv.assert_type_int(config.NUM_UNITS) cfv.assert_type_bool(config.RECOLOR) cfv.assert_type_num(config.SOUNDER_VOLUME) @@ -146,7 +147,8 @@ local function main() log.debug("boot> conn watchdog created") -- 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) + local coord_comms = coordinator.comms(COORDINATOR_VERSION, modem, config.SCADA_SV_PORT, config.SCADA_SV_LISTEN, + config.SCADA_API_LISTEN, config.TRUSTED_RANGE, conn_watchdog) log.debug("boot> comms init") log_comms("comms initialized") diff --git a/reactor-plc/config.lua b/reactor-plc/config.lua index 2e0eed3..f1a33a0 100644 --- a/reactor-plc/config.lua +++ b/reactor-plc/config.lua @@ -8,6 +8,8 @@ config.REACTOR_ID = 1 config.SERVER_PORT = 16000 -- port to listen to incoming packets FROM server config.LISTEN_PORT = 14001 +-- max trusted modem message distance (0 to disable check) +config.TRUSTED_RANGE = 0 -- log path config.LOG_PATH = "/log.txt" -- log mode diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index a23ad3f..71f17e4 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -404,15 +404,16 @@ function plc.rps_init(reactor, is_formed) end -- Reactor PLC Communications ----@param id integer ----@param version string ----@param modem table ----@param local_port integer ----@param server_port integer ----@param reactor table ----@param rps rps ----@param conn_watchdog watchdog -function plc.comms(id, version, modem, local_port, server_port, reactor, rps, conn_watchdog) +---@param id integer reactor ID +---@param version string PLC version +---@param modem table modem device +---@param local_port integer local listening port +---@param server_port integer remote server port +---@param range integer trusted device connection range +---@param reactor table reactor device +---@param rps rps RPS reference +---@param conn_watchdog watchdog watchdog reference +function plc.comms(id, version, modem, local_port, server_port, range, reactor, rps, conn_watchdog) local self = { seq_num = 0, r_seq_num = nil, @@ -428,6 +429,13 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co max_burn_rate = nil } + ---@class plc_comms + local public = {} + + comms.set_trusted_range(range) + + -- PRIVATE FUNCTIONS -- + -- configure modem channels local function _conf_channels() self.modem.closeAll() @@ -436,11 +444,6 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co _conf_channels() - ---@class plc_comms - local public = {} - - -- PRIVATE FUNCTIONS -- - -- send an RPLC packet ---@param msg_type RPLC_TYPES ---@param msg table diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 207fec6..b4ffb89 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.10.5" +local R_PLC_VERSION = "beta-v0.10.6" local print = util.print local println = util.println @@ -31,6 +31,7 @@ cfv.assert_type_bool(config.NETWORKED) cfv.assert_type_int(config.REACTOR_ID) cfv.assert_port(config.SERVER_PORT) cfv.assert_port(config.LISTEN_PORT) +cfv.assert_type_int(config.TRUSTED_RANGE) cfv.assert_type_str(config.LOG_PATH) cfv.assert_type_int(config.LOG_MODE) assert(cfv.valid(), "bad config file: missing/invalid fields") @@ -162,7 +163,7 @@ local function main() -- 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) + config.TRUSTED_RANGE, smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog) log.debug("init> comms init") else println("boot> starting in offline mode") diff --git a/rtu/config.lua b/rtu/config.lua index 80f9e9e..50122ea 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -6,13 +6,15 @@ local config = {} config.SERVER_PORT = 16000 -- port to listen to incoming packets FROM server config.LISTEN_PORT = 15001 +-- max trusted modem message distance (0 to disable check) +config.TRUSTED_RANGE = 0 -- log path config.LOG_PATH = "/log.txt" -- log mode -- 0 = APPEND (adds to existing file on start) -- 1 = NEW (replaces existing file on start) config.LOG_MODE = 0 --- RTU peripheral devices (name: side/network device name) +-- RTU peripheral devices (named: side/network device name) config.RTU_DEVICES = { { name = "boilerValve_0", @@ -33,17 +35,22 @@ config.RTU_REDSTONE = { -- { -- port = rsio.IO.WASTE_PO, -- side = "top", - -- bundled_color = colors.blue + -- bundled_color = colors.red -- }, -- { -- port = rsio.IO.WASTE_PU, -- side = "top", - -- bundled_color = colors.cyan + -- bundled_color = colors.orange + -- }, + -- { + -- port = rsio.IO.WASTE_POPL, + -- side = "top", + -- bundled_color = colors.yellow -- }, -- { -- port = rsio.IO.WASTE_AM, -- side = "top", - -- bundled_color = colors.purple + -- bundled_color = colors.lime -- } -- } -- } diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 2831cf4..eb75d1a 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -160,12 +160,13 @@ function rtu.init_unit() end -- RTU Communications ----@param version string ----@param modem table ----@param local_port integer ----@param server_port integer ----@param conn_watchdog watchdog -function rtu.comms(version, modem, local_port, server_port, conn_watchdog) +---@param version string RTU version +---@param modem table modem device +---@param local_port integer local listening port +---@param server_port integer remote server port +---@param range integer trusted device connection range +---@param conn_watchdog watchdog watchdog reference +function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog) local self = { version = version, seq_num = 0, @@ -177,6 +178,15 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog) conn_watchdog = conn_watchdog } + ---@class rtu_comms + local public = {} + + local insert = table.insert + + comms.set_trusted_range(range) + + -- PRIVATE FUNCTIONS -- + -- configure modem channels local function _conf_channels() self.modem.closeAll() @@ -185,13 +195,6 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog) _conf_channels() - ---@class rtu_comms - local public = {} - - local insert = table.insert - - -- PRIVATE FUNCTIONS -- - -- send a scada management packet ---@param msg_type SCADA_MGMT_TYPES ---@param msg table diff --git a/rtu/startup.lua b/rtu/startup.lua index 975784d..392c039 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.9.13" +local RTU_VERSION = "beta-v0.10.0" local rtu_t = types.rtu_t @@ -42,6 +42,7 @@ local cfv = util.new_validator() cfv.assert_port(config.SERVER_PORT) cfv.assert_port(config.LISTEN_PORT) +cfv.assert_type_int(config.TRUSTED_RANGE) cfv.assert_type_str(config.LOG_PATH) cfv.assert_type_int(config.LOG_MODE) cfv.assert_type_table(config.RTU_DEVICES) @@ -390,7 +391,8 @@ local function main() 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) + smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, + config.TRUSTED_RANGE, smem_sys.conn_watchdog) log.debug("boot> comms init") -- init threads diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 1234c31..672d4e2 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -12,6 +12,8 @@ local rtu_t = types.rtu_t local insert = table.insert +local max_distance = nil + comms.version = "1.3.0" ---@alias PROTOCOLS integer @@ -136,6 +138,17 @@ comms.FAC_COMMANDS = FAC_COMMANDS ---@alias packet scada_packet|modbus_packet|rplc_packet|mgmt_packet|crdn_packet|capi_packet ---@alias frame modbus_frame|rplc_frame|mgmt_frame|crdn_frame|capi_frame +-- configure the maximum allowable message receive distance
+-- packets received with distances greater than this will be silently discarded +---@param distance integer max modem message distance (less than 1 disables the limit) +function comms.set_trusted_range(distance) + if distance < 1 then + max_distance = nil + else + max_distance = distance + end +end + -- generic SCADA packet object function comms.scada_packet() local self = { @@ -181,6 +194,10 @@ function comms.scada_packet() self.raw = self.modem_msg_in.msg + if (type(max_distance) == "number") and (distance > max_distance) then + -- outside of maximum allowable transmission distance + -- log.debug("comms.scada_packet.receive(): discarding packet with distance " .. distance .. " outside of trusted range") + else if type(self.raw) == "table" then if #self.raw >= 3 then self.seq_num = self.raw[1] @@ -196,6 +213,7 @@ function comms.scada_packet() self.valid = type(self.seq_num) == "number" and type(self.protocol) == "number" and type(self.payload) == "table" + end end return self.valid diff --git a/supervisor/config.lua b/supervisor/config.lua index b98ceb2..a7ceb7e 100644 --- a/supervisor/config.lua +++ b/supervisor/config.lua @@ -4,6 +4,8 @@ local config = {} config.SCADA_DEV_LISTEN = 16000 -- listen port for SCADA supervisor access by coordinators config.SCADA_SV_LISTEN = 16100 +-- max trusted modem message distance (0 to disable check) +config.TRUSTED_RANGE = 0 -- expected number of reactors config.NUM_REACTORS = 4 -- expected number of boilers/turbines for each reactor diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 79282c0..bb27074 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.9.14" +local SUPERVISOR_VERSION = "beta-v0.10.0" local print = util.print local println = util.println @@ -29,6 +29,7 @@ local cfv = util.new_validator() cfv.assert_port(config.SCADA_DEV_LISTEN) cfv.assert_port(config.SCADA_SV_LISTEN) +cfv.assert_type_int(config.TRUSTED_RANGE) cfv.assert_type_int(config.NUM_REACTORS) cfv.assert_type_table(config.REACTOR_COOLING) cfv.assert_type_str(config.LOG_PATH) @@ -81,7 +82,7 @@ local function main() -- start comms, open all channels local superv_comms = supervisor.comms(SUPERVISOR_VERSION, config.NUM_REACTORS, config.REACTOR_COOLING, modem, - config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN) + config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN, config.TRUSTED_RANGE) -- base loop clock (6.67Hz, 3 ticks) local MAIN_CLOCK = 0.15 diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index b86e0e6..12f5291 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -9,9 +9,7 @@ local supervisor = {} local PROTOCOLS = comms.PROTOCOLS local DEVICE_TYPES = comms.DEVICE_TYPES local ESTABLISH_ACK = comms.ESTABLISH_ACK -local RPLC_TYPES = comms.RPLC_TYPES local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES -local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES local print = util.print local println = util.println @@ -19,13 +17,14 @@ local print_ts = util.print_ts local println_ts = util.println_ts -- supervisory controller communications ----@param version string ----@param num_reactors integer ----@param cooling_conf table ----@param modem table ----@param dev_listen integer ----@param coord_listen integer -function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen, coord_listen) +---@param version string supervisor version +---@param num_reactors integer number of reactors +---@param cooling_conf table cooling configuration table +---@param modem table modem device +---@param dev_listen integer listening port for PLC/RTU devices +---@param coord_listen integer listening port for coordinator +---@param range integer trusted device connection range +function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen, coord_listen, range) local self = { version = version, num_reactors = num_reactors, @@ -38,6 +37,8 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen ---@class superv_comms local public = {} + comms.set_trusted_range(range) + -- PRIVATE FUNCTIONS -- -- configure modem channels @@ -205,12 +206,12 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen if plc_id == false then -- reactor already has a PLC assigned - log.debug(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) + log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.COLLISION }) else -- got an ID; assigned to a reactor successfully - println(util.c("reactor ", reactor_id, " PLC (", firmware_v, ") [:", r_port, "] \xbb connected")) - log.debug(util.c("PLC_ESTABLISH: link accepted for PLC (", firmware_v, ") [:", r_port, "] connected with session ID ", plc_id)) + println(util.c("PLC (", firmware_v, ") [:", r_port, "] \xbb reactor ", reactor_id, " connected")) + log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [:", r_port, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id)) _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW }) end else @@ -224,7 +225,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen local s_id = svsessions.establish_rtu_session(l_port, r_port, rtu_advert, firmware_v) println(util.c("RTU (", firmware_v, ") [:", r_port, "] \xbb connected")) - log.debug(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [:", r_port, "] connected with session ID ", s_id)) + log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [:", r_port, "] connected with session ID ", s_id)) _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW }) else log.debug("RTU_ESTABLISH: packet length mismatch") @@ -286,8 +287,8 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen table.insert(config, cooling_conf[i].TURBINES) end - println(util.c("coordinator (",firmware_v, ") [:", r_port, "] \xbb connected")) - log.debug(util.c("CRDN_ESTABLISH: coordinator (",firmware_v, ") [:", r_port, "] connected with session ID ", s_id)) + println(util.c("CRD (",firmware_v, ") [:", r_port, "] \xbb connected")) + log.info(util.c("CRDN_ESTABLISH: coordinator (",firmware_v, ") [:", r_port, "] connected with session ID ", s_id)) _send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW, config }) else log.debug("CRDN_ESTABLISH: denied new coordinator due to already being connected to another coordinator") From 678dafa62f6457c59be5dc9cea8ecd3eed5de375 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 7 Feb 2023 17:51:55 -0500 Subject: [PATCH 491/587] #152 supervisor cleanups and improvements to alarms --- supervisor/session/coordinator.lua | 2 +- supervisor/session/rtu.lua | 2 +- supervisor/session/svsessions.lua | 10 +++++---- supervisor/session/unit.lua | 36 ++++++++++++++++++++++++++---- supervisor/session/unitlogic.lua | 14 +++++++++--- supervisor/startup.lua | 2 +- 6 files changed, 52 insertions(+), 14 deletions(-) diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index b146a1d..03b0eb7 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -380,7 +380,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility) -- exit if connection was closed if not self.connected then - println("connection to coordinator " .. id .. " closed by remote host") + println("connection to coordinator closed by remote host") log.info(log_header .. "session closed by remote host") return self.connected end diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index b5a72b2..01a32c3 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -332,7 +332,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility) -- exit if connection was closed if not self.connected then - println(log_header .. "connection to RTU closed by remote host") + println("RTU connection " .. id .. " closed by remote host") log.info(log_header .. "session closed by remote host") return self.connected end diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 10b5b96..04e2f5e 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -135,7 +135,7 @@ local function _shutdown(session) end end - log.debug("closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) + log.debug(util.c("closed ", session.s_type, " session ", session.instance.get_id(), " on remote port ", session.r_port)) end -- close connections @@ -158,7 +158,8 @@ local function _check_watchdogs(sessions, timer_event) if session.open then local triggered = session.instance.check_wd(timer_event) if triggered then - log.debug("watchdog closing session " .. session.instance.get_id() .. " on remote port " .. session.r_port .. "...") + log.debug(util.c("watchdog closing ", session.s_type, " session ", session.instance.get_id(), + " on remote port ", session.r_port, "...")) _shutdown(session) end end @@ -171,7 +172,8 @@ local function _free_closed(sessions) local f = function (session) return session.open end local on_delete = function (session) - log.debug("free'ing closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port) + log.debug(util.c("free'ing closed ", session.s_type, " session ", session.instance.get_id(), + " on remote port ", session.r_port)) end util.filter_table(sessions, f, on_delete) @@ -296,7 +298,7 @@ function svsessions.establish_plc_session(local_port, remote_port, for_reactor, local units = self.facility.get_units() units[for_reactor].link_plc_session(plc_s) - log.debug("established new PLC session to " .. remote_port .. " with ID " .. self.next_plc_id) + log.debug(util.c("established new PLC session to ", remote_port, " with ID ", self.next_plc_id, " for reactor ", for_reactor)) self.next_plc_id = self.next_plc_id + 1 diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index fc22ed6..d3665c4 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -88,11 +88,39 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- logic for alarms had_reactor = false, last_rate_change_ms = 0, + ---@type rps_status + last_rps_trips = { + dmg_crit = false, + high_temp = false, + no_cool = false, + ex_waste = false, + ex_hcool = false, + no_fuel = false, + fault = false, + timeout = false, + manual = false, + automatic = false, + sys_fail = false, + force_dis = false + }, plc_cache = { active = false, ok = false, - rps_trip = false, - rps_status = {}, ---@type rps_status + ---@type rps_status + rps_status = { + dmg_crit = false, + high_temp = false, + no_cool = false, + ex_waste = false, + ex_hcool = false, + no_fuel = false, + fault = false, + timeout = false, + manual = false, + automatic = false, + sys_fail = false, + force_dis = false + }, damage = 0, temp = 0, waste = 0 @@ -118,11 +146,11 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- waste >85% ReactorHighWaste = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 2, id = ALARM.ReactorHighWaste, tier = PRIO.TIMELY }, -- RPS trip occured - RPSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.RPSTransient, tier = PRIO.URGENT }, + RPSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 1, id = ALARM.RPSTransient, tier = PRIO.URGENT }, -- BoilRateMismatch, CoolantFeedMismatch, SteamFeedMismatch, MaxWaterReturnFeed RCSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 5, id = ALARM.RCSTransient, tier = PRIO.TIMELY }, -- "It's just a routine turbin' trip!" -Bill Gibson, "The China Syndrome" - TurbineTrip = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.TurbineTrip, tier = PRIO.URGENT } + TurbineTrip = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 1, id = ALARM.TurbineTrip, tier = PRIO.URGENT } }, ---@class unit_db db = { diff --git a/supervisor/session/unitlogic.lua b/supervisor/session/unitlogic.lua index ab96a11..369f761 100644 --- a/supervisor/session/unitlogic.lua +++ b/supervisor/session/unitlogic.lua @@ -397,10 +397,12 @@ function logic.update_alarms(self) _update_alarm_state(self, plc_cache.damage >= 100, self.alarms.CriticalDamage) -- Reactor Damage - _update_alarm_state(self, plc_cache.damage > 0, self.alarms.ReactorDamage) + local rps_dmg_90 = plc_cache.rps_status.dmg_crit and not self.last_rps_trips.dmg_crit + _update_alarm_state(self, (plc_cache.damage > 0) or rps_dmg_90, self.alarms.ReactorDamage) -- Over-Temperature - _update_alarm_state(self, plc_cache.temp >= 1200, self.alarms.ReactorOverTemp) + local rps_high_temp = plc_cache.rps_status.high_temp and not self.last_rps_trips.high_temp + _update_alarm_state(self, (plc_cache.temp >= 1200) or rps_high_temp, self.alarms.ReactorOverTemp) -- High Temperature _update_alarm_state(self, plc_cache.temp > 1150, self.alarms.ReactorHighTemp) @@ -409,7 +411,8 @@ function logic.update_alarms(self) _update_alarm_state(self, plc_cache.waste >= 0.99, self.alarms.ReactorWasteLeak) -- High Waste - _update_alarm_state(self, plc_cache.waste > 0.50, self.alarms.ReactorHighWaste) + local rps_high_waste = plc_cache.rps_status.ex_waste and not self.last_rps_trips.ex_waste + _update_alarm_state(self, (plc_cache.waste > 0.50) or rps_high_waste, self.alarms.ReactorHighWaste) -- RPS Transient (excludes timeouts and manual trips) local rps_alarm = false @@ -444,6 +447,11 @@ function logic.update_alarms(self) local any_trip = false for i = 1, #annunc.TurbineTrip do any_trip = any_trip or annunc.TurbineTrip[i] end _update_alarm_state(self, any_trip, self.alarms.TurbineTrip) + + -- update last trips table + for key, val in pairs(plc_cache.rps_status) do + self.last_rps_trips[key] = val + end end -- update the two unit status text messages diff --git a/supervisor/startup.lua b/supervisor/startup.lua index bb27074..ee39614 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.10.0" +local SUPERVISOR_VERSION = "beta-v0.10.1" local print = util.print local println = util.println From 07ee79216343d776f50e56dbffe92ce8c92fb6be Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 7 Feb 2023 18:44:34 -0500 Subject: [PATCH 492/587] #153 facility alarm acknowledge button --- coordinator/coordinator.lua | 2 ++ coordinator/iocontrol.lua | 11 +++++---- coordinator/process.lua | 22 +++++++++++------- coordinator/startup.lua | 2 +- coordinator/ui/components/processctl.lua | 4 +++- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- scada-common/comms.lua | 29 ++++++++++++------------ supervisor/session/coordinator.lua | 3 +++ supervisor/session/facility.lua | 8 +++++++ supervisor/startup.lua | 2 +- 11 files changed, 55 insertions(+), 32 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 7df62d0..26410e6 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -444,6 +444,8 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range else log.debug("SCADA_CRDN process start (with configuration) ack echo packet length mismatch") end + elseif cmd == FAC_COMMANDS.ACK_ALL_ALARMS then + iocontrol.get_db().facility.ack_alarms_ack(ack) else log.debug(util.c("received facility command ack with unknown command ", cmd)) end diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 51399a6..1fe04f4 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -31,12 +31,13 @@ function iocontrol.init(conf, comms) auto_saturated = false, auto_scram = false, - num_units = conf.num_units, ---@type integer + num_units = conf.num_units, ---@type integer - save_cfg_ack = function (success) end, ---@param success boolean - start_ack = function (success) end, ---@param success boolean - stop_ack = function (success) end, ---@param success boolean - scram_ack = function (success) end, ---@param success boolean + save_cfg_ack = function (success) end, ---@param success boolean + start_ack = function (success) end, ---@param success boolean + stop_ack = function (success) end, ---@param success boolean + scram_ack = function (success) end, ---@param success boolean + ack_alarms_ack = function (success) end, ---@param success boolean ps = psil.create(), diff --git a/coordinator/process.lua b/coordinator/process.lua index 11871e7..b3a6bb3 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -17,11 +17,11 @@ local self = { comms = nil, ---@type coord_comms ---@class coord_auto_config config = { - mode = 0, ---@type PROCESS + mode = PROCESS.INACTIVE, burn_target = 0.0, charge_target = 0.0, gen_target = 0.0, - limits = {} ---@type table + limits = {} } } @@ -88,6 +88,18 @@ function process.init(iocontrol, comms) end end +-- facility SCRAM command +function process.fac_scram() + self.comms.send_fac_command(FAC_COMMANDS.SCRAM_ALL) + log.debug("FAC: SCRAM ALL") +end + +-- facility alarm acknowledge command +function process.fac_ack_alarms() + self.comms.send_fac_command(FAC_COMMANDS.ACK_ALL_ALARMS) + log.debug("FAC: ACK ALL ALARMS") +end + -- start reactor ---@param id integer unit ID function process.start(id) @@ -193,12 +205,6 @@ end -- AUTO PROCESS CONTROL -- -------------------------- --- facility SCRAM command -function process.fac_scram() - self.comms.send_fac_command(FAC_COMMANDS.SCRAM_ALL) - log.debug("FAC: SCRAM ALL") -end - -- stop automatic process control function process.stop_auto() self.comms.send_fac_command(FAC_COMMANDS.STOP) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index e789f38..4d71b67 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.2" +local COORDINATOR_VERSION = "beta-v0.9.3" local print = util.print local println = util.println diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 9b9b2a3..5d7484e 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -47,8 +47,10 @@ local function new_view(root, x, y) local main = Div{parent=root,width=104,height=24,x=x,y=y} local scram = HazardButton{parent=main,x=1,y=1,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=process.fac_scram,fg_bg=hzd_fg_bg} + local ack_a = HazardButton{parent=main,x=16,y=1,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=process.fac_ack_alarms,fg_bg=hzd_fg_bg} facility.scram_ack = scram.on_response + facility.ack_alarms_ack = ack_a.on_response local all_ok = IndicatorLight{parent=main,y=5,label="Unit Systems Online",colors=cpair(colors.green,colors.red)} local ind_mat = IndicatorLight{parent=main,label="Induction Matrix",colors=cpair(colors.green,colors.gray)} @@ -208,7 +210,7 @@ local function new_view(root, x, y) local save = HazardButton{parent=auto_controls,x=2,y=2,text="SAVE",accent=colors.purple,dis_colors=dis_colors,callback=_save_cfg,fg_bg=hzd_fg_bg} local start = HazardButton{parent=auto_controls,x=13,y=2,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=_start_auto,fg_bg=hzd_fg_bg} - local stop = HazardButton{parent=auto_controls,x=23,y=2,text="STOP",accent=colors.orange,dis_colors=dis_colors,callback=process.stop_auto,fg_bg=hzd_fg_bg} + local stop = HazardButton{parent=auto_controls,x=23,y=2,text="STOP",accent=colors.red,dis_colors=dis_colors,callback=process.stop_auto,fg_bg=hzd_fg_bg} facility.start_ack = start.on_response facility.stop_ack = stop.on_response diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index b4ffb89..c8f8b23 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.10.6" +local R_PLC_VERSION = "beta-v0.10.7" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index 392c039..557bf28 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.10.0" +local RTU_VERSION = "beta-v0.10.1" local rtu_t = types.rtu_t diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 672d4e2..94bfd15 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -14,7 +14,7 @@ local insert = table.insert local max_distance = nil -comms.version = "1.3.0" +comms.version = "1.3.1" ---@alias PROTOCOLS integer local PROTOCOLS = { @@ -103,7 +103,8 @@ local PLC_AUTO_ACK = { local FAC_COMMANDS = { SCRAM_ALL = 0, -- SCRAM all reactors STOP = 1, -- stop automatic control - START = 2 -- start automatic control + START = 2, -- start automatic control + ACK_ALL_ALARMS = 3 -- acknowledge all alarms on all units } ---@alias UNIT_COMMANDS integer @@ -198,21 +199,21 @@ function comms.scada_packet() -- outside of maximum allowable transmission distance -- log.debug("comms.scada_packet.receive(): discarding packet with distance " .. distance .. " outside of trusted range") else - if type(self.raw) == "table" then - if #self.raw >= 3 then - self.seq_num = self.raw[1] - self.protocol = self.raw[2] + if type(self.raw) == "table" then + if #self.raw >= 3 then + self.seq_num = self.raw[1] + self.protocol = self.raw[2] - -- element 3 must be a table - if type(self.raw[3]) == "table" then - self.length = #self.raw[3] - self.payload = self.raw[3] + -- element 3 must be a table + if type(self.raw[3]) == "table" then + self.length = #self.raw[3] + self.payload = self.raw[3] + end end - end - self.valid = type(self.seq_num) == "number" and - type(self.protocol) == "number" and - type(self.payload) == "table" + self.valid = type(self.seq_num) == "number" and + type(self.protocol) == "number" and + type(self.payload) == "table" end end diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 03b0eb7..9075d2a 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -233,6 +233,9 @@ function coordinator.new_session(id, in_queue, out_queue, facility) else log.debug(log_header .. "CRDN auto start (with configuration) packet length mismatch") end + elseif cmd == FAC_COMMANDS.ACK_ALL_ALARMS then + facility.ack_all() + _send(SCADA_CRDN_TYPES.FAC_CMD, { cmd, true }) else log.debug(log_header .. "CRDN facility command unknown") end diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index 9d82477..fddb467 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -556,6 +556,14 @@ function facility.new(num_reactors, cooling_conf) end end + -- ack all alarms on all reactor units + function public.ack_all() + for i = 1, #self.units do + local u = self.units[i] ---@type reactor_unit + u.ack_all() + end + end + -- stop auto control function public.auto_stop() self.mode = PROCESS.INACTIVE diff --git a/supervisor/startup.lua b/supervisor/startup.lua index ee39614..831727b 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.10.1" +local SUPERVISOR_VERSION = "beta-v0.10.2" local print = util.print local println = util.println From ee739c214df277fc062633e189244c655802c684 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 7 Feb 2023 23:47:58 -0500 Subject: [PATCH 493/587] #19 gen rate target process control working, some tweaks will be needed as I term is unstable due to limiting decimal precision --- supervisor/session/facility.lua | 142 +++++++++++++++++++++----------- supervisor/session/unit.lua | 2 + supervisor/startup.lua | 2 +- 3 files changed, 95 insertions(+), 51 deletions(-) diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index fddb467..b9db2b2 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -12,6 +12,8 @@ local PROCESS = types.PROCESS -- 2856 FE per blade per 1 mB, 285.6 FE per blade per 0.1 mB (minimum) local POWER_PER_BLADE = util.joules_to_fe(7140) +local FLOW_STABILITY_DELAY_S = unit.FLOW_STABILITY_DELAY_MS / 1000 + local HIGH_CHARGE = 1.0 local RE_ENABLE_CHARGE = 0.95 @@ -23,12 +25,12 @@ local AUTO_SCRAM = { } local charge_Kp = 1.0 -local charge_Ki = 0.00001 +local charge_Ki = 0.0 local charge_Kd = 0.0 -local rate_Kp = 1.0 -local rate_Ki = 0.00001 -local rate_Kd = 0.0 +local rate_Kp = 0.75 +local rate_Ki = 0.01 +local rate_Kd = -0.10 ---@class facility_management local facility = {} @@ -51,8 +53,8 @@ function facility.new(num_reactors, cooling_conf) mode_set = PROCESS.SIMPLE, max_burn_combined = 0.0, -- maximum burn rate to clamp at burn_target = 0.1, -- burn rate target for aggregate burn mode - charge_target = 0, -- FE charge target - gen_rate_target = 0, -- FE/t charge rate target + charge_setpoint = 0, -- FE charge target setpoint + gen_rate_setpoint = 0, -- FE/t charge rate target setpoint group_map = { 0, 0, 0, 0 }, -- units -> group IDs prio_defs = { {}, {}, {}, {} }, -- priority definitions (each level is a table of units) at_max_burn = false, @@ -63,15 +65,17 @@ function facility.new(num_reactors, cooling_conf) time_start = 0.0, initial_ramp = true, waiting_on_ramp = false, + waiting_on_stable = false, accumulator = 0.0, saturated = false, + last_update = 0, last_error = 0.0, last_time = 0.0, -- statistics im_stat_init = false, - avg_charge = util.mov_avg(20, 0.0), - avg_inflow = util.mov_avg(20, 0.0), - avg_outflow = util.mov_avg(20, 0.0) + avg_charge = util.mov_avg(10, 0.0), + avg_inflow = util.mov_avg(10, 0.0), + avg_outflow = util.mov_avg(10, 0.0) } -- create units @@ -190,15 +194,20 @@ function facility.new(num_reactors, cooling_conf) _unlink_disconnected_units(self.redstone) -- calculate moving averages for induction matrix + local charge_update = 0 + local rate_update = 0 if self.induction[1] ~= nil then local matrix = self.induction[1] ---@type unit_session local db = matrix.get_db() ---@type imatrix_session_db - if (db.state.last_update > 0) and (db.tanks.last_update > 0) then + charge_update = db.tanks.last_update + rate_update = db.state.last_update + + if (charge_update > 0) and (rate_update > 0) then if self.im_stat_init then - self.avg_charge.record(util.joules_to_fe(db.tanks.energy), db.tanks.last_update) - self.avg_inflow.record(util.joules_to_fe(db.state.last_input), db.state.last_update) - self.avg_outflow.record(util.joules_to_fe(db.state.last_output), db.state.last_update) + self.avg_charge.record(util.joules_to_fe(db.tanks.energy), charge_update) + self.avg_inflow.record(util.joules_to_fe(db.state.last_input), rate_update) + self.avg_outflow.record(util.joules_to_fe(db.state.last_output), rate_update) else self.im_stat_init = true self.avg_charge.reset(util.joules_to_fe(db.tanks.energy)) @@ -273,6 +282,7 @@ function facility.new(num_reactors, cooling_conf) self.initial_ramp = true self.waiting_on_ramp = false + self.waiting_on_stable = false else self.initial_ramp = false end @@ -327,9 +337,11 @@ function facility.new(num_reactors, cooling_conf) end elseif self.mode == PROCESS.CHARGE then -- target a level of charge - local error = (self.charge_target - avg_charge) / self.charge_conversion + -- convert to kFE to make constants not microscopic + local error = (self.charge_setpoint - avg_charge) / 1000000 if state_changed then + self.last_time = now log.debug(util.c("FAC: CHARGE mode first call completed")) elseif self.waiting_on_ramp and _all_units_ramped() then self.waiting_on_ramp = false @@ -341,7 +353,7 @@ function facility.new(num_reactors, cooling_conf) if not self.waiting_on_ramp then if not self.saturated then - self.accumulator = self.accumulator + ((avg_charge / self.charge_conversion) * (now - self.last_time)) + self.accumulator = self.accumulator + (error * (now - self.last_time)) end local runtime = now - self.time_start @@ -367,59 +379,89 @@ function facility.new(num_reactors, cooling_conf) _allocate_burn_rate(sp_c, self.initial_ramp) + self.last_time = now + if self.initial_ramp then self.waiting_on_ramp = true end end elseif self.mode == PROCESS.GEN_RATE then -- target a rate of generation - local error = (self.gen_rate_target - avg_inflow) / self.charge_conversion - local setpoint = 0.0 + -- convert to MFE to make constants not microscopic + local error = (self.gen_rate_setpoint - avg_inflow) / 1000000 + local output = 0.0 if state_changed then -- estimate an initial setpoint - setpoint = error / self.charge_conversion + output = (error * 1000000) / self.charge_conversion - local sp_r = util.round(setpoint * 10.0) / 10.0 + local out_r = util.round(output * 10.0) / 10.0 - _allocate_burn_rate(sp_r, true) - log.debug(util.c("FAC: GEN_RATE mode first call completed")) - elseif self.waiting_on_ramp and _all_units_ramped() then - self.waiting_on_ramp = false + log.debug(util.c("FAC: initial burn rate for gen rate setpoint is " .. out_r)) - self.time_start = now - self.accumulator = 0 - log.debug(util.c("FAC: GEN_RATE mode initial ramp completed")) - end + _allocate_burn_rate(out_r, true) - if not self.waiting_on_ramp then + self.waiting_on_ramp = true + + self.status_text = { "GENERATION MODE", "starting up" } + elseif self.waiting_on_ramp then + if _all_units_ramped() then + self.waiting_on_ramp = false + self.waiting_on_stable = true + + self.time_start = now + + self.status_text = { "GENERATION MODE", "holding ramped rate" } + log.debug("FAC: GEN_RATE mode initial ramp completed, holding for stablization time") + end + elseif self.waiting_on_stable then + if (now - self.time_start) > FLOW_STABILITY_DELAY_S then + self.waiting_on_stable = false + + self.time_start = now + self.last_time = now + self.last_error = 0 + self.accumulator = 0 + + self.status_text = { "GENERATION MODE", "running control loop" } + log.debug("FAC: GEN_RATE mode initial hold completed") + end + elseif self.last_update ~= rate_update then if not self.saturated then - self.accumulator = self.accumulator + ((avg_inflow / self.charge_conversion) * (now - self.last_time)) + self.accumulator = self.accumulator + (error * (now - self.last_time)) end local runtime = util.time_s() - self.time_start local integral = self.accumulator - -- local derivative = (error - self.last_error) / (now - self.last_time) + local derivative = (error - self.last_error) / (now - self.last_time) local P = (rate_Kp * error) local I = (rate_Ki * integral) - local D = 0 -- (rate_Kd * derivative) + local D = (rate_Kd * derivative) - setpoint = P + I + D + -- velocity (rate) (derivative of charge level => rate) feed forward + local FF = self.gen_rate_setpoint / self.charge_conversion - -- round setpoint -> setpoint rounded (sp_r) - local sp_r = util.round(setpoint * 10.0) / 10.0 + output = P + I + D + FF - -- clamp at range -> setpoint clamped (sp_c) - local sp_c = math.max(0, math.min(sp_r, self.max_burn_combined)) + -- round output -> output rounded (sp_r) + local out_r = util.round(output * 10.0) / 10.0 - self.saturated = sp_r ~= sp_c + -- clamp at range -> output clamped (sp_c) + local out_c = math.max(0, math.min(out_r, self.max_burn_combined)) - log.debug(util.sprintf("PROC_RATE[%f] { RATE[%f] ERR[%f] INT[%f] => SP[%f] SP_C[%f] <= P[%f] I[%f] D[%f] }", - runtime, avg_inflow, error, integral, setpoint, sp_c, P, I, D)) + self.saturated = out_r ~= out_c - _allocate_burn_rate(sp_c, false) + log.debug(util.sprintf("GEN_RATE[%f] { RATE[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%f] }", + runtime, avg_inflow, error, integral, output, out_c, P, I, D)) + + _allocate_burn_rate(out_c, false) + + self.last_time = now + self.last_error = error end + + self.last_update = rate_update elseif self.mode == PROCESS.MATRIX_FAULT_IDLE then -- exceeded charge, wait until condition clears if self.ascram_reason == AUTO_SCRAM.NONE then @@ -586,22 +628,22 @@ function facility.new(num_reactors, cooling_conf) if self.mode == PROCESS.INACTIVE then if (type(config.mode) == "number") and (config.mode > PROCESS.INACTIVE) and (config.mode <= PROCESS.GEN_RATE) then self.mode_set = config.mode - log.debug("SET MODE " .. config.mode) + log.debug("SET MODE " .. self.mode_set) end if (type(config.burn_target) == "number") and config.burn_target >= 0.1 then self.burn_target = config.burn_target - log.debug("SET BURN TARGET " .. config.burn_target) + log.debug("SET BURN TARGET " .. self.burn_target) end if (type(config.charge_target) == "number") and config.charge_target >= 0 then - self.charge_target = config.charge_target - log.debug("SET CHARGE TARGET " .. config.charge_target) + self.charge_setpoint = config.charge_target + log.debug("SET CHARGE TARGET " .. self.charge_setpoint) end if (type(config.gen_target) == "number") and config.gen_target >= 0 then - self.gen_rate_target = config.gen_target - log.debug("SET RATE TARGET " .. config.gen_target) + self.gen_rate_setpoint = config.gen_target * 1000 -- convert kFE to FE + log.debug("SET RATE TARGET " .. self.gen_rate_setpoint) end if (type(config.limits) == "table") and (#config.limits == num_reactors) then @@ -618,9 +660,9 @@ function facility.new(num_reactors, cooling_conf) ready = self.mode_set > 0 - if (self.mode_set == PROCESS.CHARGE) and (self.charge_target <= 0) then + if (self.mode_set == PROCESS.CHARGE) and (self.charge_setpoint <= 0) then ready = false - elseif (self.mode_set == PROCESS.GEN_RATE) and (self.gen_rate_target <= 0) then + elseif (self.mode_set == PROCESS.GEN_RATE) and (self.gen_rate_setpoint <= 0) then ready = false elseif (self.mode_set == PROCESS.BURN_RATE) and (self.burn_target < 0.1) then ready = false @@ -631,7 +673,7 @@ function facility.new(num_reactors, cooling_conf) if ready then self.mode = self.mode_set end end - return { ready, self.mode_set, self.burn_target, self.charge_target, self.gen_rate_target, limits } + return { ready, self.mode_set, self.burn_target, self.charge_setpoint, self.gen_rate_setpoint, limits } end -- SETTINGS -- @@ -677,7 +719,7 @@ function facility.new(num_reactors, cooling_conf) self.all_sys_ok, self.units_ready, self.mode, - self.waiting_on_ramp, + self.waiting_on_ramp or self.waiting_on_stable, self.at_max_burn or self.saturated, self.ascram, self.status_text[1], diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index d3665c4..32debf9 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -50,6 +50,8 @@ local AISTATE = { RING_BACK_TRIPPING = 5 } +unit.FLOW_STABILITY_DELAY_MS = FLOW_STABILITY_DELAY_MS + ---@class alarm_def ---@field state ALARM_INT_STATE internal alarm state ---@field trip_time integer time (ms) when first tripped diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 831727b..1025f8a 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.10.2" +local SUPERVISOR_VERSION = "beta-v0.10.3" local print = util.print local println = util.println From 37f73194946b07aa644817bca2052b2e8700d9a6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 8 Feb 2023 20:26:13 -0500 Subject: [PATCH 494/587] #154 increased auto burn rate precision --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 4 +- reactor-plc/plc.lua | 36 ++++++++---------- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- scada-common/comms.lua | 5 +-- supervisor/session/facility.lua | 46 ++++++++++------------- supervisor/session/plc.lua | 5 +-- supervisor/session/unit.lua | 32 ++++++++-------- supervisor/session/unitlogic.lua | 4 +- supervisor/startup.lua | 2 +- 11 files changed, 61 insertions(+), 79 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 4d71b67..5a160c4 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.3" +local COORDINATOR_VERSION = "beta-v0.9.4" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 99990e8..17d064f 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -85,7 +85,7 @@ local function init(parent, id) u_ps.subscribe("heating_rate", heating_r.update) TextBox{parent=main,x=12,y=25,text="Commanded Burn Rate",height=1,width=19,fg_bg=style.label} - local burn_r = DataIndicator{parent=main,x=12,label="",format="%14.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=19,fg_bg=bw_fg_bg} + local burn_r = DataIndicator{parent=main,x=12,label="",format="%14.2f",value=0,unit="mB/t",lu_colors=lu_cpair,width=19,fg_bg=bw_fg_bg} u_ps.subscribe("burn_rate", burn_r.update) TextBox{parent=main,text="F",x=2,y=22,width=1,height=1,fg_bg=style.label} @@ -126,7 +126,7 @@ local function init(parent, id) u_ps.subscribe("temp", core_temp.update) TextBox{parent=main,x=32,y=25,text="Burn Rate",height=1,width=9,fg_bg=style.label} - local act_burn_r = DataIndicator{parent=main,x=32,label="",format="%8.1f",value=0,unit="mB/t",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} + local act_burn_r = DataIndicator{parent=main,x=32,label="",format="%8.2f",value=0,unit="mB/t",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} u_ps.subscribe("act_burn_rate", act_burn_r.update) TextBox{parent=main,x=32,y=28,text="Damage",height=1,width=6,fg_bg=style.label} diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 71f17e4..6be0a84 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -25,8 +25,6 @@ local println_ts = util.println_ts local PCALL_SCRAM_MSG = "pcall: Scram requires the reactor to be active." local PCALL_START_MSG = "pcall: Reactor is already active." -local AUTO_TOGGLE_DELAY_MS = 5000 - -- RPS SAFETY CONSTANTS local MAX_DAMAGE_PERCENT = 90 @@ -832,7 +830,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, -- automatic control requested a new burn rate if (packet.length == 3) and (type(packet.data[1]) == "number") and (type(packet.data[3]) == "number") then local ack = AUTO_ACK.FAIL - local burn_rate = math.floor(packet.data[1] * 10) / 10 + local burn_rate = math.floor(packet.data[1] * 100) / 100 local ramp = packet.data[2] self.auto_ack_token = packet.data[3] @@ -843,20 +841,15 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, -- if we know our max burn rate, update current burn rate setpoint if in range if self.max_burn_rate ~= ppm.ACCESS_FAULT then - if burn_rate < 0.1 then + if burn_rate < 0.01 then if rps.is_active() then - if rps.get_runtime() > AUTO_TOGGLE_DELAY_MS then - -- auto scram to disable - log.debug("AUTO: stopping the reactor to meet 0.0 burn rate") - if rps.scram() then - ack = AUTO_ACK.ZERO_DIS_OK - self.auto_last_disable = util.time_ms() - else - log.debug("AUTO: automatic reactor stop failed") - end + -- auto scram to disable + log.debug("AUTO: stopping the reactor to meet 0.0 burn rate") + if rps.scram() then + ack = AUTO_ACK.ZERO_DIS_OK + self.auto_last_disable = util.time_ms() else - -- too soon to disable - ack = AUTO_ACK.ZERO_DIS_WAIT + log.debug("AUTO: automatic reactor stop failed") end else ack = AUTO_ACK.ZERO_DIS_OK @@ -865,13 +858,14 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, if not rps.is_active() then -- activate the reactor log.debug("AUTO: activating the reactor") - if rps.auto_activate() then - self.reactor.setBurnRate(0.1) - if self.reactor.__p_is_faulted() then - log.debug("AUTO: failed to reset burn rate on auto activation") - end + + self.reactor.setBurnRate(0.01) + if self.reactor.__p_is_faulted() then + log.debug("AUTO: failed to reset burn rate for auto activation") else - log.debug("AUTO: automatic reactor activation failed") + if not rps.auto_activate() then + log.debug("AUTO: automatic reactor activation failed") + end end end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index c8f8b23..b0374c9 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.10.7" +local R_PLC_VERSION = "beta-v0.10.8" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index 557bf28..19c201b 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.10.1" +local RTU_VERSION = "beta-v0.10.2" local rtu_t = types.rtu_t diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 94bfd15..6b1918a 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -14,7 +14,7 @@ local insert = table.insert local max_distance = nil -comms.version = "1.3.1" +comms.version = "1.3.2" ---@alias PROTOCOLS integer local PROTOCOLS = { @@ -95,8 +95,7 @@ local PLC_AUTO_ACK = { FAIL = 0, -- failed to set burn rate/burn rate invalid DIRECT_SET_OK = 1, -- successfully set burn rate RAMP_SET_OK = 2, -- successfully started burn rate ramping - ZERO_DIS_OK = 3, -- successfully disabled reactor with < 0.1 burn rate - ZERO_DIS_WAIT = 4 -- too soon to disable reactor with < 0.1 burn rate + ZERO_DIS_OK = 3 -- successfully disabled reactor with < 0.01 burn rate } ---@alias FAC_COMMANDS integer diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index b9db2b2..498652f 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -111,7 +111,7 @@ function facility.new(num_reactors, cooling_conf) ---@param ramp boolean true to ramp, false to set right away ---@return integer unallocated local function _allocate_burn_rate(burn_rate, ramp) - local unallocated = math.floor(burn_rate * 10) + local unallocated = math.floor(burn_rate * 100) -- go through alll priority groups for i = 1, #self.prio_defs do @@ -129,14 +129,14 @@ function facility.new(num_reactors, cooling_conf) local u = units[id] ---@type reactor_unit local ctl = u.get_control_inf() - local lim_br10 = u.a_get_effective_limit() + local lim_br100 = u.a_get_effective_limit() - local last = ctl.br10 + local last = ctl.br100 - if splits[id] <= lim_br10 then - ctl.br10 = splits[id] + if splits[id] <= lim_br100 then + ctl.br100 = splits[id] else - ctl.br10 = lim_br10 + ctl.br100 = lim_br100 if id < #units then local remaining = #units - id @@ -146,11 +146,11 @@ function facility.new(num_reactors, cooling_conf) end end - unallocated = math.max(0, unallocated - ctl.br10) + unallocated = math.max(0, unallocated - ctl.br100) - if last ~= ctl.br10 then - log.debug("unit " .. id .. ": set to " .. ctl.br10 .. " (was " .. last .. ")") - u.a_commit_br10(ramp) + if last ~= ctl.br100 then + log.debug("unit " .. id .. ": set to " .. ctl.br100 .. " (was " .. last .. ")") + u.a_commit_br100(ramp) end end end @@ -253,13 +253,13 @@ function facility.new(num_reactors, cooling_conf) table.sort(self.prio_defs[i], ---@param a reactor_unit ---@param b reactor_unit - function (a, b) return a.get_control_inf().lim_br10 < b.get_control_inf().lim_br10 end + function (a, b) return a.get_control_inf().lim_br100 < b.get_control_inf().lim_br100 end ) for _, u in pairs(self.prio_defs[i]) do blade_count = blade_count + u.get_control_inf().blade_count u.a_engage() - self.max_burn_combined = self.max_burn_combined + (u.get_control_inf().lim_br10 / 10.0) + self.max_burn_combined = self.max_burn_combined + (u.get_control_inf().lim_br100 / 100.0) end end @@ -366,13 +366,10 @@ function facility.new(num_reactors, cooling_conf) local setpoint = P + I + D - -- round setpoint -> setpoint rounded (sp_r) - local sp_r = util.round(setpoint * 10.0) / 10.0 - -- clamp at range -> setpoint clamped (sp_c) - local sp_c = math.max(0, math.min(sp_r, self.max_burn_combined)) + local sp_c = math.max(0, math.min(setpoint, self.max_burn_combined)) - self.saturated = sp_r ~= sp_c + self.saturated = setpoint ~= sp_c log.debug(util.sprintf("PROC_CHRG[%f] { CHRG[%f] ERR[%f] INT[%f] => SP[%f] SP_C[%f] <= P[%f] I[%f] D[%d] }", runtime, avg_charge, error, integral, setpoint, sp_c, P, I, D)) @@ -395,11 +392,9 @@ function facility.new(num_reactors, cooling_conf) -- estimate an initial setpoint output = (error * 1000000) / self.charge_conversion - local out_r = util.round(output * 10.0) / 10.0 + log.debug(util.c("FAC: initial burn rate for gen rate is " .. output)) - log.debug(util.c("FAC: initial burn rate for gen rate setpoint is " .. out_r)) - - _allocate_burn_rate(out_r, true) + _allocate_burn_rate(output, true) self.waiting_on_ramp = true @@ -444,13 +439,10 @@ function facility.new(num_reactors, cooling_conf) output = P + I + D + FF - -- round output -> output rounded (sp_r) - local out_r = util.round(output * 10.0) / 10.0 - -- clamp at range -> output clamped (sp_c) - local out_c = math.max(0, math.min(out_r, self.max_burn_combined)) + local out_c = math.max(0, math.min(output, self.max_burn_combined)) - self.saturated = out_r ~= out_c + self.saturated = output ~= out_c log.debug(util.sprintf("GEN_RATE[%f] { RATE[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%f] }", runtime, avg_inflow, error, integral, output, out_c, P, I, D)) @@ -621,7 +613,7 @@ function facility.new(num_reactors, cooling_conf) local limits = {} for i = 1, num_reactors do local u = self.units[i] ---@type reactor_unit - limits[i] = u.get_control_inf().lim_br10 * 10 + limits[i] = u.get_control_inf().lim_br100 * 100 end -- only allow changes if not running diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 45e8bae..7947526 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -463,9 +463,6 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) log.debug(log_header .. "RPLC automatic burn rate set fail") elseif ack == PLC_AUTO_ACK.DIRECT_SET_OK or ack == PLC_AUTO_ACK.RAMP_SET_OK or ack == PLC_AUTO_ACK.ZERO_DIS_OK then self.acks.burn_rate = true - elseif ack == PLC_AUTO_ACK.ZERO_DIS_WAIT then - self.acks.burn_rate = false - log.debug(log_header .. "RPLC automatic burn rate too soon to disable at 0 mB/t") else self.acks.burn_rate = false log.debug(log_header .. "RPLC automatic burn rate ack unknown") @@ -665,7 +662,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) elseif cmd.key == PLC_S_DATA.AUTO_BURN_RATE then -- set automatic burn rate if self.auto_lock then - cmd.val = math.floor(cmd.val * 10) / 10 -- round to 10ths place + cmd.val = math.floor(cmd.val * 100) / 100 -- round to 100ths place if cmd.val >= 0 and cmd.val <= self.sDB.mek_struct.max_burn then self.auto_cmd_token = util.time_ms() self.commanded_burn_rate = cmd.val diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 32debf9..5c220a4 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -77,7 +77,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) boilers = {}, redstone = {}, -- auto control - ramp_target_br10 = 0, + ramp_target_br100 = 0, -- state tracking deltas = {}, last_heartbeat = 0, @@ -208,8 +208,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) ready = false, degraded = false, blade_count = 0, - br10 = 0, - lim_br10 = 0 + br100 = 0, + lim_br100 = 0 } } } @@ -413,8 +413,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) if self.plc_s ~= nil and not self.plc_s.open then self.plc_s = nil self.plc_i = nil - self.db.control.br10 = 0 - self.db.control.lim_br10 = 0 + self.db.control.br100 = 0 + self.db.control.lim_br100 = 0 end -- unlink RTU unit sessions if they are closed @@ -480,31 +480,31 @@ function unit.new(for_reactor, num_boilers, num_turbines) self.db.annunciator.AutoControl = false if self.plc_i ~= nil then self.plc_i.auto_lock(false) - self.db.control.br10 = 0 + self.db.control.br100 = 0 end end -- get the actual limit of this unit -- -- if it is degraded or not ready, the limit will be 0 - ---@return integer lim_br10 + ---@return integer lim_br100 function public.a_get_effective_limit() if not self.db.control.ready or self.db.control.degraded or self.plc_cache.rps_trip then - self.db.control.br10 = 0 + self.db.control.br100 = 0 return 0 else - return self.db.control.lim_br10 + return self.db.control.lim_br100 end end - -- set the automatic burn rate based on the last set br10 + -- set the automatic burn rate based on the last set burn rate in 100ths ---@param ramp boolean true to ramp to rate, false to set right away - function public.a_commit_br10(ramp) + function public.a_commit_br100(ramp) if self.db.annunciator.AutoControl then if self.plc_i ~= nil then - self.plc_i.auto_set_burn(self.db.control.br10 / 10, ramp) + self.plc_i.auto_set_burn(self.db.control.br100 / 100, ramp) - if ramp then self.ramp_target_br10 = self.db.control.br10 end + if ramp then self.ramp_target_br100 = self.db.control.br100 end end end end @@ -514,7 +514,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) function public.a_ramp_complete() if self.plc_i ~= nil then return self.plc_i.is_ramp_complete() or - (self.plc_i.get_status().act_burn_rate == 0 and self.db.control.br10 == 0) or + (self.plc_i.get_status().act_burn_rate == 0 and self.db.control.br100 == 0) or public.a_get_effective_limit() == 0 else return true end end @@ -609,11 +609,11 @@ function unit.new(for_reactor, num_boilers, num_turbines) ---@param limit number burn rate limit for auto control function public.set_burn_limit(limit) if limit > 0 then - self.db.control.lim_br10 = math.floor(limit * 10) + self.db.control.lim_br100 = math.floor(limit * 100) if self.plc_i ~= nil then if limit > self.plc_i.get_struct().max_burn then - self.db.control.lim_br10 = math.floor(self.plc_i.get_struct().max_burn * 10) + self.db.control.lim_br100 = math.floor(self.plc_i.get_struct().max_burn * 100) end end end diff --git a/supervisor/session/unitlogic.lua b/supervisor/session/unitlogic.lua index 369f761..84f7c58 100644 --- a/supervisor/session/unitlogic.lua +++ b/supervisor/session/unitlogic.lua @@ -50,8 +50,8 @@ function logic.update_annunciator(self) plc_ready = (not plc_db.rps_tripped) and (plc_db.last_status_update > 0) and (plc_db.mek_struct.length > 0) -- update auto control limit - if (self.db.control.lim_br10 == 0) or ((self.db.control.lim_br10 / 10) > plc_db.mek_struct.max_burn) then - self.db.control.lim_br10 = math.floor(plc_db.mek_struct.max_burn * 10) + if (self.db.control.lim_br100 == 0) or ((self.db.control.lim_br100 / 100) > plc_db.mek_struct.max_burn) then + self.db.control.lim_br100 = math.floor(plc_db.mek_struct.max_burn * 100) end -- some alarms wait until the burn rate has stabilized, so keep track of that diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 1025f8a..29963ec 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.10.3" +local SUPERVISOR_VERSION = "beta-v0.10.4" local print = util.print local println = util.println From 44d5cec1f804b3ed5d33c2e96c694c73b16bc279 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 9 Feb 2023 22:52:10 -0500 Subject: [PATCH 495/587] #19 decent rate PID gains, fixed blade counting and added checks, bugfix with PLC reconnects not being in auto mode, logging cleanups --- coordinator/startup.lua | 2 +- coordinator/ui/components/processctl.lua | 2 +- coordinator/ui/components/reactor.lua | 2 +- reactor-plc/plc.lua | 4 +- reactor-plc/startup.lua | 2 +- scada-common/types.lua | 8 +- supervisor/session/facility.lua | 162 +++++++++++++---------- supervisor/session/unit.lua | 1 + supervisor/startup.lua | 2 +- 9 files changed, 104 insertions(+), 81 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 5a160c4..fcdab12 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.4" +local COORDINATOR_VERSION = "beta-v0.9.5" local print = util.print local println = util.println diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 5d7484e..2f56136 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -124,7 +124,7 @@ local function new_view(root, x, y) local cur_gen = DataIndicator{parent=targets,x=9,y=14,label="",format="%17d",value=0,unit="kFE/t",commas=true,lu_colors=cpair(colors.black,colors.black),width=23,fg_bg=cpair(colors.black,colors.brown)} facility.ps.subscribe("process_gen_target", g_target.set_value) - facility.induction_ps_tbl[1].subscribe("last_input", function (j) cur_gen.update(util.joules_to_fe(j) / 1000) end) + facility.induction_ps_tbl[1].subscribe("last_input", function (j) cur_gen.update(util.round(util.joules_to_fe(j) / 1000)) end) ----------------- -- unit limits -- diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua index f103d76..5dc9f23 100644 --- a/coordinator/ui/components/reactor.lua +++ b/coordinator/ui/components/reactor.lua @@ -26,7 +26,7 @@ local function new_view(root, x, y, data, ps) local status = StateIndicator{parent=reactor,x=6,y=1,states=style.reactor.states,value=1,min_width=16} local core_temp = DataIndicator{parent=reactor,x=2,y=3,lu_colors=lu_col,label="Core Temp:",unit="K",format="%10.2f",value=0,width=26,fg_bg=text_fg_bg} - local burn_r = DataIndicator{parent=reactor,x=2,y=4,lu_colors=lu_col,label="Burn Rate:",unit="mB/t",format="%10.1f",value=0,width=26,fg_bg=text_fg_bg} + local burn_r = DataIndicator{parent=reactor,x=2,y=4,lu_colors=lu_col,label="Burn Rate:",unit="mB/t",format="%10.2f",value=0,width=26,fg_bg=text_fg_bg} local heating_r = DataIndicator{parent=reactor,x=2,y=5,lu_colors=lu_col,label="Heating:",unit="mB/t",format="%12.0f",value=0,commas=true,width=26,fg_bg=text_fg_bg} ps.subscribe("computed_status", status.update) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 6be0a84..4eecdf4 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -393,9 +393,9 @@ function plc.rps_init(reactor, is_formed) if self.trip_cause == rps_status_t.automatic or self.trip_cause == rps_status_t.timeout then self.trip_cause = rps_status_t.ok self.tripped = false - end - log.info("RPS: auto reset") + log.info("RPS: auto reset") + end end return public diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index b0374c9..d390a19 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.10.8" +local R_PLC_VERSION = "beta-v0.10.9" local print = util.print local println = util.println diff --git a/scada-common/types.lua b/scada-common/types.lua index eaa355c..462a7a5 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -37,13 +37,13 @@ types.TRI_FAIL = { ---@alias PROCESS integer types.PROCESS = { - UNIT_ALARM_IDLE = -2, - MATRIX_FAULT_IDLE = -1, INACTIVE = 0, - SIMPLE = 1, + MAX_BURN = 1, BURN_RATE = 2, CHARGE = 3, - GEN_RATE = 4 + GEN_RATE = 4, + MATRIX_FAULT_IDLE = 5, + UNIT_ALARM_IDLE = 6 } ---@alias WASTE_MODE integer diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index 498652f..37431cc 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -24,13 +24,19 @@ local AUTO_SCRAM = { CRIT_ALARM = 3 } +local START_STATUS = { + OK = 0, + NO_UNITS = 1, + BLADE_MISMATCH = 2 +} + local charge_Kp = 1.0 local charge_Ki = 0.0 local charge_Kd = 0.0 -local rate_Kp = 0.75 -local rate_Ki = 0.01 -local rate_Kd = -0.10 +local rate_Kp = 2.45 +local rate_Ki = 0.2825 -- 0.1825, 0.2825 +local rate_Kd = -1.0 ---@class facility_management local facility = {} @@ -50,7 +56,8 @@ function facility.new(num_reactors, cooling_conf) mode = PROCESS.INACTIVE, last_mode = PROCESS.INACTIVE, return_mode = PROCESS.INACTIVE, - mode_set = PROCESS.SIMPLE, + mode_set = PROCESS.MAX_BURN, + start_fail = START_STATUS.OK, max_burn_combined = 0.0, -- maximum burn rate to clamp at burn_target = 0.1, -- burn rate target for aggregate burn mode charge_setpoint = 0, -- FE charge target setpoint @@ -149,7 +156,7 @@ function facility.new(num_reactors, cooling_conf) unallocated = math.max(0, unallocated - ctl.br100) if last ~= ctl.br100 then - log.debug("unit " .. id .. ": set to " .. ctl.br100 .. " (was " .. last .. ")") + log.debug("unit " .. u.get_id() .. ": set to " .. ctl.br100 .. " (was " .. last .. ")") u.a_commit_br100(ramp) end end @@ -193,9 +200,11 @@ function facility.new(num_reactors, cooling_conf) _unlink_disconnected_units(self.induction) _unlink_disconnected_units(self.redstone) - -- calculate moving averages for induction matrix + -- current state for process control local charge_update = 0 local rate_update = 0 + + -- calculate moving averages for induction matrix if self.induction[1] ~= nil then local matrix = self.induction[1] ---@type unit_session local db = matrix.get_db() ---@type imatrix_session_db @@ -243,10 +252,11 @@ function facility.new(num_reactors, cooling_conf) log.debug("FAC: state changed from " .. self.last_mode .. " to " .. self.mode) if self.last_mode == PROCESS.INACTIVE then - ---@todo change this to be a reset button + self.start_fail = START_STATUS.OK + if self.mode ~= PROCESS.MATRIX_FAULT_IDLE then self.ascram = false end - local blade_count = 0 + local blade_count = nil self.max_burn_combined = 0.0 for i = 1, #self.prio_defs do @@ -257,15 +267,32 @@ function facility.new(num_reactors, cooling_conf) ) for _, u in pairs(self.prio_defs[i]) do - blade_count = blade_count + u.get_control_inf().blade_count - u.a_engage() + local u_blade_count = u.get_control_inf().blade_count + + if blade_count == nil then + blade_count = u_blade_count + elseif u_blade_count ~= blade_count then + log.warning("FAC: cannot start auto control with inconsistent blade counts") + next_mode = PROCESS.INACTIVE + self.start_fail = START_STATUS.BLADE_MISMATCH + end + + if self.start_fail == START_STATUS.OK then u.a_engage() end + self.max_burn_combined = self.max_burn_combined + (u.get_control_inf().lim_br100 / 100.0) end end - self.charge_conversion = blade_count * POWER_PER_BLADE + if blade_count == nil then + -- no units + log.warning("FAC: cannot start process control with 0 units assigned") + next_mode = PROCESS.INACTIVE + self.start_fail = START_STATUS.NO_UNITS + else + self.charge_conversion = blade_count * POWER_PER_BLADE - log.debug(util.c("FAC: starting auto control: chg_conv = ", self.charge_conversion, ", blade_count = ", blade_count, ", max_burn = ", self.max_burn_combined)) + log.debug(util.c("FAC: starting auto control: chg_conv = ", self.charge_conversion, ", blade_count = ", blade_count, ", max_burn = ", self.max_burn_combined)) + end elseif self.mode == PROCESS.INACTIVE then for i = 1, #self.prio_defs do -- SCRAM reactors and disengage auto control @@ -276,8 +303,7 @@ function facility.new(num_reactors, cooling_conf) end end - self.status_text = { "IDLE", "control disengaged" } - log.debug("FAC: disengaging auto control (now inactive)") + log.info("FAC: disengaging auto control (now inactive)") end self.initial_ramp = true @@ -297,44 +323,36 @@ function facility.new(num_reactors, cooling_conf) -- perform mode-specific operations if self.mode == PROCESS.INACTIVE then - if self.units_ready then - self.status_text = { "IDLE", "control disengaged" } - else + if not self.units_ready then self.status_text = { "NOT READY", "assigned units not ready" } + elseif self.start_fail == START_STATUS.NO_UNITS then + self.status_text = { "START FAILED", "no units assigned" } + elseif self.start_fail == START_STATUS.BLADE_MISMATCH then + self.status_text = { "START FAILED", "turbine blade count mismatch" } + else + self.status_text = { "IDLE", "control disengaged" } end - elseif self.mode == PROCESS.SIMPLE then + elseif self.mode == PROCESS.MAX_BURN then -- run units at their limits if state_changed then self.time_start = now self.saturated = true + self.status_text = { "MONITORED MODE", "running reactors at limit" } - log.debug(util.c("FAC: SIMPLE mode first call completed")) + log.info(util.c("FAC: MAX_BURN process mode started")) end _allocate_burn_rate(self.max_burn_combined, true) elseif self.mode == PROCESS.BURN_RATE then -- a total aggregate burn rate if state_changed then - -- nothing special to do - self.status_text = { "BURN RATE MODE", "starting up" } - log.debug(util.c("FAC: BURN_RATE mode first call completed")) - elseif self.waiting_on_ramp and _all_units_ramped() then - self.waiting_on_ramp = false self.time_start = now self.status_text = { "BURN RATE MODE", "running" } - log.debug(util.c("FAC: BURN_RATE mode initial ramp completed")) + log.info(util.c("FAC: BURN_RATE process mode started")) end - if not self.waiting_on_ramp then - local unallocated = _allocate_burn_rate(self.burn_target, true) - self.saturated = self.burn_target == self.max_burn_combined or unallocated > 0 - - if self.initial_ramp then - self.status_text = { "BURN RATE MODE", "ramping reactors" } - self.waiting_on_ramp = true - log.debug(util.c("FAC: BURN_RATE mode allocated initial ramp")) - end - end + local unallocated = _allocate_burn_rate(self.burn_target, true) + self.saturated = self.burn_target == self.max_burn_combined or unallocated > 0 elseif self.mode == PROCESS.CHARGE then -- target a level of charge -- convert to kFE to make constants not microscopic @@ -343,38 +361,45 @@ function facility.new(num_reactors, cooling_conf) if state_changed then self.last_time = now log.debug(util.c("FAC: CHARGE mode first call completed")) - elseif self.waiting_on_ramp and _all_units_ramped() then - self.waiting_on_ramp = false + elseif self.waiting_on_ramp then + if _all_units_ramped() then + self.waiting_on_ramp = false - self.time_start = now - self.accumulator = 0 - log.debug(util.c("FAC: CHARGE mode initial ramp completed")) + self.time_start = now + self.last_time = now + self.last_error = 0 + self.accumulator = 0 + + self.status_text = { "CHARGE MODE", "running control loop" } + log.info(util.c("FAC: CHARGE mode initial ramp completed")) + end end - if not self.waiting_on_ramp then - if not self.saturated then + if not self.waiting_on_ramp and self.last_update ~= rate_update then + -- stop accumulator when saturated to avoid windup, deadband accumulator at 1kFE to avoid windup + if (not self.saturated) and (math.abs(error) >= 0.001) then self.accumulator = self.accumulator + (error * (now - self.last_time)) end local runtime = now - self.time_start local integral = self.accumulator - -- local derivative = (error - self.last_error) / (now - self.last_time) + local derivative = (error - self.last_error) / (now - self.last_time) local P = (charge_Kp * error) local I = (charge_Ki * integral) - local D = 0 -- (charge_Kd * derivative) + local D = (charge_Kd * derivative) - local setpoint = P + I + D + local output = P + I + D - -- clamp at range -> setpoint clamped (sp_c) - local sp_c = math.max(0, math.min(setpoint, self.max_burn_combined)) + -- clamp at range -> output clamped (out_c) + local out_c = math.max(0, math.min(output, self.max_burn_combined)) - self.saturated = setpoint ~= sp_c + self.saturated = output ~= out_c - log.debug(util.sprintf("PROC_CHRG[%f] { CHRG[%f] ERR[%f] INT[%f] => SP[%f] SP_C[%f] <= P[%f] I[%f] D[%d] }", - runtime, avg_charge, error, integral, setpoint, sp_c, P, I, D)) + log.debug(util.sprintf("PROC_CHRG[%f] { CHRG[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%d] }", + runtime, avg_charge, error, integral, output, out_c, P, I, D)) - _allocate_burn_rate(sp_c, self.initial_ramp) + _allocate_burn_rate(out_c, self.initial_ramp) self.last_time = now @@ -382,23 +407,21 @@ function facility.new(num_reactors, cooling_conf) self.waiting_on_ramp = true end end + + self.last_update = charge_update elseif self.mode == PROCESS.GEN_RATE then -- target a rate of generation - -- convert to MFE to make constants not microscopic - local error = (self.gen_rate_setpoint - avg_inflow) / 1000000 - local output = 0.0 - if state_changed then - -- estimate an initial setpoint - output = (error * 1000000) / self.charge_conversion + -- estimate an initial output + local output = self.gen_rate_setpoint / self.charge_conversion - log.debug(util.c("FAC: initial burn rate for gen rate is " .. output)) - - _allocate_burn_rate(output, true) + local unallocated = _allocate_burn_rate(output, true) + self.saturated = output >= self.max_burn_combined or unallocated > 0 self.waiting_on_ramp = true self.status_text = { "GENERATION MODE", "starting up" } + log.info(util.c("FAC: GEN_RATE process mode initial ramp started (initial target is ", output, " mB/t)")) elseif self.waiting_on_ramp then if _all_units_ramped() then self.waiting_on_ramp = false @@ -407,7 +430,7 @@ function facility.new(num_reactors, cooling_conf) self.time_start = now self.status_text = { "GENERATION MODE", "holding ramped rate" } - log.debug("FAC: GEN_RATE mode initial ramp completed, holding for stablization time") + log.info("FAC: GEN_RATE process mode initial ramp completed, holding for stablization time") end elseif self.waiting_on_stable then if (now - self.time_start) > FLOW_STABILITY_DELAY_S then @@ -419,14 +442,18 @@ function facility.new(num_reactors, cooling_conf) self.accumulator = 0 self.status_text = { "GENERATION MODE", "running control loop" } - log.debug("FAC: GEN_RATE mode initial hold completed") + log.info("FAC: GEN_RATE process mode initial hold completed, starting PID control") end elseif self.last_update ~= rate_update then + -- convert to MFE (in rounded kFE) to make constants not microscopic + local error = util.round((self.gen_rate_setpoint - avg_inflow) / 1000) / 1000 + + -- stop accumulator when saturated to avoid windup, deadband accumulator at 1kFE to avoid windup if not self.saturated then self.accumulator = self.accumulator + (error * (now - self.last_time)) end - local runtime = util.time_s() - self.time_start + local runtime = now - self.time_start local integral = self.accumulator local derivative = (error - self.last_error) / (now - self.last_time) @@ -437,7 +464,7 @@ function facility.new(num_reactors, cooling_conf) -- velocity (rate) (derivative of charge level => rate) feed forward local FF = self.gen_rate_setpoint / self.charge_conversion - output = P + I + D + FF + local output = P + I + D + FF -- clamp at range -> output clamped (sp_c) local out_c = math.max(0, math.min(output, self.max_burn_combined)) @@ -620,22 +647,18 @@ function facility.new(num_reactors, cooling_conf) if self.mode == PROCESS.INACTIVE then if (type(config.mode) == "number") and (config.mode > PROCESS.INACTIVE) and (config.mode <= PROCESS.GEN_RATE) then self.mode_set = config.mode - log.debug("SET MODE " .. self.mode_set) end if (type(config.burn_target) == "number") and config.burn_target >= 0.1 then self.burn_target = config.burn_target - log.debug("SET BURN TARGET " .. self.burn_target) end if (type(config.charge_target) == "number") and config.charge_target >= 0 then self.charge_setpoint = config.charge_target - log.debug("SET CHARGE TARGET " .. self.charge_setpoint) end if (type(config.gen_target) == "number") and config.gen_target >= 0 then - self.gen_rate_setpoint = config.gen_target * 1000 -- convert kFE to FE - log.debug("SET RATE TARGET " .. self.gen_rate_setpoint) + self.gen_rate_setpoint = config.gen_target * 1000 -- convert kFE to FE end if (type(config.limits) == "table") and (#config.limits == num_reactors) then @@ -645,7 +668,6 @@ function facility.new(num_reactors, cooling_conf) if (type(limit) == "number") and (limit >= 0.1) then limits[i] = limit self.units[i].set_burn_limit(limit) - log.debug("SET UNIT " .. i .. " LIMIT " .. limit) end end end diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 5c220a4..459ef4f 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -531,6 +531,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) if self.plc_s ~= nil and self.plc_i ~= nil then local rps = self.plc_i.get_rps() if rps.timeout or rps.automatic then + self.plc_i.auto_lock(true) -- if it timed out/restarted, auto lock was lost, so re-lock it self.plc_s.in_queue.push_command(PLC_S_CMDS.RPS_AUTO_RESET) end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 29963ec..704459c 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.10.4" +local SUPERVISOR_VERSION = "beta-v0.10.5" local print = util.print local println = util.println From da9eead2d5001c5996e297e2bfe511e905fd9a4b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 10 Feb 2023 20:26:25 -0500 Subject: [PATCH 496/587] #19 #156 gain changes for generation rate control, fixed plc ready checks --- supervisor/session/facility.lua | 12 +++++++----- supervisor/session/unitlogic.lua | 3 ++- supervisor/startup.lua | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index 37431cc..17f67ba 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -35,7 +35,7 @@ local charge_Ki = 0.0 local charge_Kd = 0.0 local rate_Kp = 2.45 -local rate_Ki = 0.2825 -- 0.1825, 0.2825 +local rate_Ki = 0.4825 local rate_Kd = -1.0 ---@class facility_management @@ -271,8 +271,8 @@ function facility.new(num_reactors, cooling_conf) if blade_count == nil then blade_count = u_blade_count - elseif u_blade_count ~= blade_count then - log.warning("FAC: cannot start auto control with inconsistent blade counts") + elseif (u_blade_count ~= blade_count) and (self.mode == PROCESS.GEN_RATE) then + log.warning("FAC: cannot start GEN_RATE process with inconsistent unit blade counts") next_mode = PROCESS.INACTIVE self.start_fail = START_STATUS.BLADE_MISMATCH end @@ -314,9 +314,11 @@ function facility.new(num_reactors, cooling_conf) end -- update unit ready state + local assign_count = 0 self.units_ready = true for i = 1, #self.prio_defs do for _, u in pairs(self.prio_defs[i]) do + assign_count = assign_count + 1 self.units_ready = self.units_ready and u.get_control_inf().ready end end @@ -325,8 +327,8 @@ function facility.new(num_reactors, cooling_conf) if self.mode == PROCESS.INACTIVE then if not self.units_ready then self.status_text = { "NOT READY", "assigned units not ready" } - elseif self.start_fail == START_STATUS.NO_UNITS then - self.status_text = { "START FAILED", "no units assigned" } + elseif self.start_fail == START_STATUS.NO_UNITS and assign_count == 0 then + self.status_text = { "START FAILED", "no units were assigned" } elseif self.start_fail == START_STATUS.BLADE_MISMATCH then self.status_text = { "START FAILED", "turbine blade count mismatch" } else diff --git a/supervisor/session/unitlogic.lua b/supervisor/session/unitlogic.lua index 84f7c58..11bdacd 100644 --- a/supervisor/session/unitlogic.lua +++ b/supervisor/session/unitlogic.lua @@ -47,7 +47,8 @@ function logic.update_annunciator(self) -- - can't be tripped -- - must have received status at least once -- - must have received struct at least once - plc_ready = (not plc_db.rps_tripped) and (plc_db.last_status_update > 0) and (plc_db.mek_struct.length > 0) + plc_ready = plc_db.formed and (not plc_db.no_reactor) and (not plc_db.rps_tripped) and + (next(self.plc_i.get_status()) ~= nil) and (next(self.plc_i.get_struct()) ~= nil) -- update auto control limit if (self.db.control.lim_br100 == 0) or ((self.db.control.lim_br100 / 100) > plc_db.mek_struct.max_burn) then diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 704459c..44cad9d 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.10.5" +local SUPERVISOR_VERSION = "beta-v0.10.6" local print = util.print local println = util.println From ff1bd02739b0fa66c4a77a666dbda9edb609328b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 11 Feb 2023 00:21:00 -0500 Subject: [PATCH 497/587] #20 process target charge level --- coordinator/startup.lua | 2 +- coordinator/ui/components/processctl.lua | 8 ++-- supervisor/session/facility.lua | 54 +++++++++--------------- supervisor/startup.lua | 2 +- 4 files changed, 26 insertions(+), 40 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index fcdab12..d095d67 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.5" +local COORDINATOR_VERSION = "beta-v0.9.6" local print = util.print local println = util.println diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 2f56136..3a37a8f 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -64,7 +64,7 @@ local function new_view(root, x, y) local auto_ready = IndicatorLight{parent=main,label="Configured Units Ready",colors=cpair(colors.green,colors.red)} local auto_act = IndicatorLight{parent=main,label="Process Active",colors=cpair(colors.green,colors.gray)} local auto_ramp = IndicatorLight{parent=main,label="Process Ramping",colors=cpair(colors.white,colors.gray),flash=true,period=period.BLINK_250_MS} - local auto_sat = IndicatorLight{parent=main,label="Max Burn Rate",colors=cpair(colors.yellow,colors.gray)} + local auto_sat = IndicatorLight{parent=main,label="Min/Max Burn Rate",colors=cpair(colors.yellow,colors.gray)} local auto_scram = IndicatorLight{parent=main,label="Automatic SCRAM",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} facility.ps.subscribe("auto_ready", auto_ready.update) @@ -109,11 +109,11 @@ local function new_view(root, x, y) local chg_target = Div{parent=targets,x=9,y=6,width=23,height=3,fg_bg=cpair(colors.gray,colors.white)} local c_target = SpinboxNumeric{parent=chg_target,x=2,y=1,whole_num_precision=15,fractional_precision=0,min=0,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg} - TextBox{parent=chg_target,x=18,y=2,text="kFE"} - local cur_charge = DataIndicator{parent=targets,x=9,y=9,label="",format="%19d",value=0,unit="kFE",commas=true,lu_colors=cpair(colors.black,colors.black),width=23,fg_bg=cpair(colors.black,colors.brown)} + TextBox{parent=chg_target,x=18,y=2,text="MFE"} + local cur_charge = DataIndicator{parent=targets,x=9,y=9,label="",format="%19d",value=0,unit="MFE",commas=true,lu_colors=cpair(colors.black,colors.black),width=23,fg_bg=cpair(colors.black,colors.brown)} facility.ps.subscribe("process_charge_target", c_target.set_value) - facility.induction_ps_tbl[1].subscribe("energy", function (j) cur_charge.update(util.joules_to_fe(j) / 1000) end) + facility.induction_ps_tbl[1].subscribe("energy", function (j) cur_charge.update(util.joules_to_fe(j) / 1000000) end) local gen_tag = Div{parent=targets,x=1,y=11,width=8,height=4,fg_bg=cpair(colors.black,colors.purple)} TextBox{parent=gen_tag,x=2,y=2,text="Gen. Target",width=7,height=2} diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index 17f67ba..69b8a45 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -30,9 +30,9 @@ local START_STATUS = { BLADE_MISMATCH = 2 } -local charge_Kp = 1.0 +local charge_Kp = 0.275 local charge_Ki = 0.0 -local charge_Kd = 0.0 +local charge_Kd = 4.5 local rate_Kp = 2.45 local rate_Ki = 0.4825 @@ -80,9 +80,9 @@ function facility.new(num_reactors, cooling_conf) last_time = 0.0, -- statistics im_stat_init = false, - avg_charge = util.mov_avg(10, 0.0), - avg_inflow = util.mov_avg(10, 0.0), - avg_outflow = util.mov_avg(10, 0.0) + avg_charge = util.mov_avg(3, 0.0), + avg_inflow = util.mov_avg(6, 0.0), + avg_outflow = util.mov_avg(6, 0.0) } -- create units @@ -290,8 +290,6 @@ function facility.new(num_reactors, cooling_conf) self.start_fail = START_STATUS.NO_UNITS else self.charge_conversion = blade_count * POWER_PER_BLADE - - log.debug(util.c("FAC: starting auto control: chg_conv = ", self.charge_conversion, ", blade_count = ", blade_count, ", max_burn = ", self.max_burn_combined)) end elseif self.mode == PROCESS.INACTIVE then for i = 1, #self.prio_defs do @@ -357,29 +355,20 @@ function facility.new(num_reactors, cooling_conf) self.saturated = self.burn_target == self.max_burn_combined or unallocated > 0 elseif self.mode == PROCESS.CHARGE then -- target a level of charge - -- convert to kFE to make constants not microscopic - local error = (self.charge_setpoint - avg_charge) / 1000000 - if state_changed then + self.time_start = now self.last_time = now - log.debug(util.c("FAC: CHARGE mode first call completed")) - elseif self.waiting_on_ramp then - if _all_units_ramped() then - self.waiting_on_ramp = false + self.last_error = 0 + self.accumulator = 0 - self.time_start = now - self.last_time = now - self.last_error = 0 - self.accumulator = 0 + self.status_text = { "CHARGE MODE", "running control loop" } + log.info(util.c("FAC: CHARGE mode starting PID control")) + elseif self.last_update ~= charge_update then + -- convert to kFE to make constants not microscopic + local error = util.round((self.charge_setpoint - avg_charge) / 1000) / 1000 - self.status_text = { "CHARGE MODE", "running control loop" } - log.info(util.c("FAC: CHARGE mode initial ramp completed")) - end - end - - if not self.waiting_on_ramp and self.last_update ~= rate_update then - -- stop accumulator when saturated to avoid windup, deadband accumulator at 1kFE to avoid windup - if (not self.saturated) and (math.abs(error) >= 0.001) then + -- stop accumulator when saturated to avoid windup + if not self.saturated then self.accumulator = self.accumulator + (error * (now - self.last_time)) end @@ -398,16 +387,13 @@ function facility.new(num_reactors, cooling_conf) self.saturated = output ~= out_c - log.debug(util.sprintf("PROC_CHRG[%f] { CHRG[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%d] }", + log.debug(util.sprintf("CHARGE[%f] { CHRG[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%d] }", runtime, avg_charge, error, integral, output, out_c, P, I, D)) - _allocate_burn_rate(out_c, self.initial_ramp) + _allocate_burn_rate(out_c, true) self.last_time = now - - if self.initial_ramp then - self.waiting_on_ramp = true - end + self.last_error = error end self.last_update = charge_update @@ -450,7 +436,7 @@ function facility.new(num_reactors, cooling_conf) -- convert to MFE (in rounded kFE) to make constants not microscopic local error = util.round((self.gen_rate_setpoint - avg_inflow) / 1000) / 1000 - -- stop accumulator when saturated to avoid windup, deadband accumulator at 1kFE to avoid windup + -- stop accumulator when saturated to avoid windup if not self.saturated then self.accumulator = self.accumulator + (error * (now - self.last_time)) end @@ -656,7 +642,7 @@ function facility.new(num_reactors, cooling_conf) end if (type(config.charge_target) == "number") and config.charge_target >= 0 then - self.charge_setpoint = config.charge_target + self.charge_setpoint = config.charge_target * 1000000 -- convert MFE to FE end if (type(config.gen_target) == "number") and config.gen_target >= 0 then diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 44cad9d..fc4f90d 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.10.6" +local SUPERVISOR_VERSION = "beta-v0.10.7" local print = util.print local println = util.println From 42ff61a8a18449a91363b746ea22011ae38c707d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 11 Feb 2023 14:27:29 -0500 Subject: [PATCH 498/587] #155 gen rate mode pausing on units no longer being ready --- scada-common/types.lua | 14 +++++++- supervisor/session/facility.lua | 60 ++++++++++++++++++++++++++++----- supervisor/session/unit.lua | 1 + supervisor/startup.lua | 2 +- 4 files changed, 66 insertions(+), 11 deletions(-) diff --git a/scada-common/types.lua b/scada-common/types.lua index 462a7a5..2973383 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -43,7 +43,19 @@ types.PROCESS = { CHARGE = 3, GEN_RATE = 4, MATRIX_FAULT_IDLE = 5, - UNIT_ALARM_IDLE = 6 + UNIT_ALARM_IDLE = 6, + GEN_RATE_FAULT_IDLE = 7 +} + +types.PROCESS_NAMES = { + "INACTIVE", + "MAX_BURN", + "BURN_RATE", + "CHARGE", + "GEN_RATE", + "MATRIX_FAULT_IDLE", + "UNIT_ALARM_IDLE", + "GEN_RATE_FAULT_IDLE" } ---@alias WASTE_MODE integer diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index 69b8a45..ab658e4 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -7,6 +7,7 @@ local rsctl = require("supervisor.session.rsctl") local unit = require("supervisor.session.unit") local PROCESS = types.PROCESS +local PROCESS_NAMES = types.PROCESS_NAMES -- 7.14 kJ per blade for 1 mB of fissile fuel
-- 2856 FE per blade per 1 mB, 285.6 FE per blade per 0.1 mB (minimum) @@ -21,7 +22,8 @@ local AUTO_SCRAM = { NONE = 0, MATRIX_DC = 1, MATRIX_FILL = 2, - CRIT_ALARM = 3 + CRIT_ALARM = 3, + GEN_FAULT = 4 } local START_STATUS = { @@ -116,11 +118,12 @@ function facility.new(num_reactors, cooling_conf) -- split a burn rate among the reactors ---@param burn_rate number burn rate assignment ---@param ramp boolean true to ramp, false to set right away - ---@return integer unallocated - local function _allocate_burn_rate(burn_rate, ramp) + ---@param abort_on_fault boolean? true to exit if one device has an effective burn rate different than its limit + ---@return integer unallocated_br100, boolean? aborted + local function _allocate_burn_rate(burn_rate, ramp, abort_on_fault) local unallocated = math.floor(burn_rate * 100) - -- go through alll priority groups + -- go through all priority groups for i = 1, #self.prio_defs do local units = self.prio_defs[i] @@ -138,6 +141,11 @@ function facility.new(num_reactors, cooling_conf) local ctl = u.get_control_inf() local lim_br100 = u.a_get_effective_limit() + if abort_on_fault and (lim_br100 ~= ctl.lim_br100) then + -- effective limit differs from set limit, unit is degraded + return unallocated, true + end + local last = ctl.br100 if splits[id] <= lim_br100 then @@ -163,7 +171,7 @@ function facility.new(num_reactors, cooling_conf) end end - return unallocated + return unallocated, false end -- PUBLIC FUNCTIONS -- @@ -249,12 +257,15 @@ function facility.new(num_reactors, cooling_conf) if state_changed then self.saturated = false - log.debug("FAC: state changed from " .. self.last_mode .. " to " .. self.mode) + log.debug("FAC: state changed from " .. PROCESS_NAMES[self.last_mode + 1] .. " to " .. PROCESS_NAMES[self.mode + 1]) - if self.last_mode == PROCESS.INACTIVE then + if (self.last_mode == PROCESS.INACTIVE) or (self.last_mode == PROCESS.GEN_RATE_FAULT_IDLE) then self.start_fail = START_STATUS.OK - if self.mode ~= PROCESS.MATRIX_FAULT_IDLE then self.ascram = false end + if (self.mode ~= PROCESS.MATRIX_FAULT_IDLE) and (self.mode ~= PROCESS.UNIT_ALARM_IDLE) then + -- auto clear ASCRAM + self.ascram = false + end local blade_count = nil self.max_burn_combined = 0.0 @@ -462,7 +473,12 @@ function facility.new(num_reactors, cooling_conf) log.debug(util.sprintf("GEN_RATE[%f] { RATE[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%f] }", runtime, avg_inflow, error, integral, output, out_c, P, I, D)) - _allocate_burn_rate(out_c, false) + local _, fault = _allocate_burn_rate(out_c, false) + + if fault then + log.info("FAC: one or more units degraded, pausing GEN_RATE process control") + next_mode = PROCESS.GEN_RATE_FAULT_IDLE + end self.last_time = now self.last_error = error @@ -480,6 +496,13 @@ function facility.new(num_reactors, cooling_conf) end elseif self.mode == PROCESS.UNIT_ALARM_IDLE then -- do nothing, wait for user to confirm (stop and reset) + elseif self.mode == PROCESS.GEN_RATE_FAULT_IDLE then + -- system faulted (degraded/not ready) while running generation rate mode + -- mode will need to be fully restarted once everything is OK to re-ramp to feed-forward + if self.units_ready then + log.info("FAC: system ready after faulting out of GEN_RATE process mode, switching back...") + next_mode = PROCESS.GEN_RATE + end elseif self.mode ~= PROCESS.INACTIVE then log.error(util.c("FAC: unsupported process mode ", self.mode, ", switching to inactive")) next_mode = PROCESS.INACTIVE @@ -492,6 +515,10 @@ function facility.new(num_reactors, cooling_conf) if (self.mode ~= PROCESS.INACTIVE) and (self.mode ~= PROCESS.UNIT_ALARM_IDLE) then local scram = false + if self.units_ready and self.ascram_reason == AUTO_SCRAM.GEN_FAULT then + self.ascram_reason = AUTO_SCRAM.NONE + end + if self.induction[1] ~= nil then local matrix = self.induction[1] ---@type unit_session local db = matrix.get_db() ---@type imatrix_session_db @@ -534,6 +561,17 @@ function facility.new(num_reactors, cooling_conf) break end end + + if (self.mode == PROCESS.GEN_RATE) and (not self.units_ready) then + -- system not ready, will need to restart GEN_RATE mode + scram = true + + if self.ascram_reason == AUTO_SCRAM.NONE then + self.ascram_reason = AUTO_SCRAM.GEN_FAULT + end + + next_mode = PROCESS.GEN_RATE_FAULT_IDLE + end else scram = true @@ -564,6 +602,9 @@ function facility.new(num_reactors, cooling_conf) elseif self.ascram_reason == AUTO_SCRAM.CRIT_ALARM then log.info("FAC: automatic SCRAM due to critical unit alarm") self.status_text = { "AUTOMATIC SCRAM", "critical unit alarm tripped" } + elseif self.ascram_reason == AUTO_SCRAM.GEN_FAULT then + log.info("FAC: automatic SCRAM due to unit problem while in GEN_RATE mode, will resume once all units are ready") + self.status_text = { "GENERATION MODE IDLE", "paused: system not ready" } else log.error(util.c("FAC: automatic SCRAM reason (", self.ascram_reason, ") not set to a known value")) end @@ -575,6 +616,7 @@ function facility.new(num_reactors, cooling_conf) if not self.ascram then self.ascram_reason = AUTO_SCRAM.NONE + -- do not reset while in gen rate, we have to exit this mode first for i = 1, #self.units do local u = self.units[i] ---@type reactor_unit u.a_cond_rps_reset() diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 459ef4f..e39e0aa 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -522,6 +522,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- perform an automatic SCRAM function public.a_scram() if self.plc_s ~= nil then + self.db.control.br100 = 0 self.plc_s.in_queue.push_command(PLC_S_CMDS.ASCRAM) end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index fc4f90d..348151b 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.10.7" +local SUPERVISOR_VERSION = "beta-v0.11.0" local print = util.print local println = util.println From 4d40d08a7a96f17e2a333dccc62e70c9a886f980 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 Feb 2023 13:06:44 -0500 Subject: [PATCH 499/587] #157 fixed bug with RTU remount messages --- rtu/startup.lua | 25 +++++++++++++++++++------ rtu/threads.lua | 28 ++++++++++++++++++++-------- supervisor/session/rtu.lua | 2 +- supervisor/session/rtu/boilerv.lua | 2 ++ supervisor/session/rtu/imatrix.lua | 2 ++ supervisor/session/rtu/sps.lua | 2 ++ supervisor/session/rtu/turbinev.lua | 2 ++ supervisor/startup.lua | 2 +- 8 files changed, 49 insertions(+), 16 deletions(-) diff --git a/rtu/startup.lua b/rtu/startup.lua index 19c201b..22f6f2d 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.10.2" +local RTU_VERSION = "beta-v0.10.3" local rtu_t = types.rtu_t @@ -222,11 +222,13 @@ local function main() ---@class rtu_unit_registry_entry local unit = { + uid = 0, name = "redstone_io", type = rtu_t.redstone, index = entry_idx, reactor = io_reactor, device = capabilities, -- use device field for redstone ports + is_multiblock = false, formed = nil, ---@type boolean|nil rtu = rs_rtu, ---@type rtu_device|rtu_rs_device modbus_io = modbus.new(rs_rtu, false), @@ -237,6 +239,8 @@ local function main() table.insert(units, unit) log.debug(util.c("init> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for reactor ", io_reactor)) + + unit.uid = #units end end @@ -267,9 +271,10 @@ local function main() local device = ppm.get_periph(name) local type = nil - local rtu_iface = nil ---@type rtu_device + local rtu_iface = nil ---@type rtu_device local rtu_type = "" - local formed = nil ---@type boolean|nil + local is_multiblock = false + local formed = nil ---@type boolean|nil if device == nil then local message = util.c("configure> '", name, "' not found, using placeholder") @@ -286,6 +291,7 @@ local function main() -- boiler multiblock rtu_type = rtu_t.boiler_valve rtu_iface = boilerv_rtu.new(device) + is_multiblock = true formed = device.isFormed() if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then @@ -297,6 +303,7 @@ local function main() -- turbine multiblock rtu_type = rtu_t.turbine_valve rtu_iface = turbinev_rtu.new(device) + is_multiblock = true formed = device.isFormed() if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then @@ -308,6 +315,7 @@ local function main() -- induction matrix multiblock rtu_type = rtu_t.induction_matrix rtu_iface = imatrix_rtu.new(device) + is_multiblock = true formed = device.isFormed() if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then @@ -319,6 +327,7 @@ local function main() -- SPS multiblock rtu_type = rtu_t.sps rtu_iface = sps_rtu.new(device) + is_multiblock = true formed = device.isFormed() if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then @@ -347,15 +356,17 @@ local function main() ---@class rtu_unit_registry_entry local rtu_unit = { + uid = 0, name = name, type = rtu_type, index = index, reactor = for_reactor, device = device, - formed = formed, - rtu = rtu_iface, ---@type rtu_device|rtu_rs_device + is_multiblock = is_multiblock, + formed = formed, ---@type boolean|nil + rtu = rtu_iface, ---@type rtu_device|rtu_rs_device modbus_io = modbus.new(rtu_iface, true), - pkt_queue = mqueue.new(), ---@type mqueue|nil + pkt_queue = mqueue.new(), ---@type mqueue|nil thread = nil } @@ -373,6 +384,8 @@ local function main() end log.debug(util.c("configure> initialized RTU unit #", #units, ": ", name, " (", rtu_type, ") [", index, "] for ", for_message)) + + rtu_unit.uid = #units end -- we made it through all that trusting-user-to-write-a-config-file chaos diff --git a/rtu/threads.lua b/rtu/threads.lua index 8027f34..7af184e 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -157,16 +157,20 @@ function threads.thread__main(smem) if unit.type == rtu_t.boiler_valve then unit.rtu = boilerv_rtu.new(device) - unit.formed = true + -- if not formed, indexing the multiblock functions would have resulted in a PPM fault + unit.formed = util.trinary(device.__p_is_faulted(), false, nil) elseif unit.type == rtu_t.turbine_valve then unit.rtu = turbinev_rtu.new(device) - unit.formed = true + -- if not formed, indexing the multiblock functions would have resulted in a PPM fault + unit.formed = util.trinary(device.__p_is_faulted(), false, nil) elseif unit.type == rtu_t.induction_matrix then unit.rtu = imatrix_rtu.new(device) - unit.formed = true + -- if not formed, indexing the multiblock functions would have resulted in a PPM fault + unit.formed = util.trinary(device.__p_is_faulted(), false, nil) elseif unit.type == rtu_t.sps then unit.rtu = sps_rtu.new(device) - unit.formed = true + -- if not formed, indexing the multiblock functions would have resulted in a PPM fault + unit.formed = util.trinary(device.__p_is_faulted(), false, nil) elseif unit.type == rtu_t.sna then unit.rtu = sna_rtu.new(device) elseif unit.type == rtu_t.env_detector then @@ -175,6 +179,10 @@ function threads.thread__main(smem) log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true) end + if unit.is_multiblock and (unit.formed == false) then + log.info(util.c("assuming ", unit.name, " is not formed due to PPM faults while initializing")) + end + unit.modbus_io = modbus.new(unit.rtu, true) println_ts("reconnected the " .. unit.type .. " on interface " .. unit.name) @@ -183,7 +191,7 @@ function threads.thread__main(smem) if resend_advert then rtu_comms.send_advertisement(units) else - rtu_comms.send_remounted(unit.index) + rtu_comms.send_remounted(unit.uid) end end end @@ -342,9 +350,13 @@ function threads.thread__unit_comms(smem, unit) end -- check if multiblock is still formed if this is a multiblock - if (type(unit.formed) == "boolean") and (util.time() - last_f_check > 1000) then + if unit.is_multiblock and (util.time_ms() - last_f_check > 250) then local is_formed = unit.device.isFormed() + last_f_check = util.time_ms() + + if unit.formed == nil then unit.formed = is_formed end + if (not unit.formed) and is_formed then -- newly re-formed local iface = ppm.get_iface(unit.device) @@ -384,13 +396,13 @@ function threads.thread__unit_comms(smem, unit) log.error("illegal remount of non-multiblock RTU attempted for " .. short_name, true) end - rtu_comms.send_remounted(unit.index) + rtu_comms.send_remounted(unit.uid) else -- fully lost the peripheral now :( log.error(util.c(unit.name, " lost (failed reconnect)")) end - log.info("reconnected the " .. unit.type .. " on interface " .. unit.name) + log.info(util.c("reconnected the ", unit.type, " on interface ", unit.name)) else log.error("failed to get interface of previously connected RTU unit " .. detail_name, true) end diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 01a32c3..607f4f6 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -265,7 +265,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility) _handle_advertisement() elseif pkt.type == SCADA_MGMT_TYPES.RTU_DEV_REMOUNT then if pkt.length == 1 then - local unit_id = pkt[1] + local unit_id = pkt.data[1] if self.units[unit_id] ~= nil then local unit = self.units[unit_id] ---@type unit_session unit.invalidate_cache() diff --git a/supervisor/session/rtu/boilerv.lua b/supervisor/session/rtu/boilerv.lua index d35517d..fe61372 100644 --- a/supervisor/session/rtu/boilerv.lua +++ b/supervisor/session/rtu/boilerv.lua @@ -137,6 +137,8 @@ function boilerv.new(session_id, unit_id, advert, out_queue) -- load in data if correct length if m_pkt.length == 1 then self.db.formed = m_pkt.data[1] + + if not self.db.formed then self.has_build = false end else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua index ab5704c..00e14f7 100644 --- a/supervisor/session/rtu/imatrix.lua +++ b/supervisor/session/rtu/imatrix.lua @@ -124,6 +124,8 @@ function imatrix.new(session_id, unit_id, advert, out_queue) -- load in data if correct length if m_pkt.length == 1 then self.db.formed = m_pkt.data[1] + + if not self.db.formed then self.has_build = false end else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua index f389049..4cd7fb1 100644 --- a/supervisor/session/rtu/sps.lua +++ b/supervisor/session/rtu/sps.lua @@ -129,6 +129,8 @@ function sps.new(session_id, unit_id, advert, out_queue) -- load in data if correct length if m_pkt.length == 1 then self.db.formed = m_pkt.data[1] + + if not self.db.formed then self.has_build = false end else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua index d1bba6b..5662b70 100644 --- a/supervisor/session/rtu/turbinev.lua +++ b/supervisor/session/rtu/turbinev.lua @@ -166,6 +166,8 @@ function turbinev.new(session_id, unit_id, advert, out_queue) -- load in data if correct length if m_pkt.length == 1 then self.db.formed = m_pkt.data[1] + + if not self.db.formed then self.has_build = false end else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 348151b..2ae0df3 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.11.0" +local SUPERVISOR_VERSION = "beta-v0.11.1" local print = util.print local println = util.println From 9784b4e1653b6eb8d880a3c67a3f1da8f6c3aa01 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 13 Feb 2023 12:27:22 -0500 Subject: [PATCH 500/587] #146 increased timeout times and added to config files --- coordinator/config.lua | 9 +++------ coordinator/startup.lua | 9 +++++---- reactor-plc/config.lua | 4 ++++ reactor-plc/startup.lua | 9 ++++++--- rtu/config.lua | 6 +++++- rtu/startup.lua | 6 ++++-- supervisor/config.lua | 6 ++++++ supervisor/session/coordinator.lua | 13 +++++++------ supervisor/session/plc.lua | 18 +++++++++--------- supervisor/session/rtu.lua | 15 ++++++++------- supervisor/session/svsessions.lua | 8 +++++--- supervisor/startup.lua | 8 +++++++- 12 files changed, 69 insertions(+), 42 deletions(-) diff --git a/coordinator/config.lua b/coordinator/config.lua index b88593f..c293fa2 100644 --- a/coordinator/config.lua +++ b/coordinator/config.lua @@ -8,11 +8,13 @@ config.SCADA_SV_LISTEN = 16101 config.SCADA_API_LISTEN = 16200 -- max trusted modem message distance (0 to disable check) config.TRUSTED_RANGE = 0 +-- time in seconds (>= 1) before assuming a remote device is no longer active +config.COMMS_TIMEOUT = 5 -- expected number of reactor units, used only to require that number of unit monitors config.NUM_UNITS = 4 --- graphics color +-- override default display colors (prettier in my opinion) config.RECOLOR = true -- alarm sounder volume (0.0 to 3.0, 1.0 being standard max volume, this is the option given to to speaker.play()) @@ -29,9 +31,4 @@ config.LOG_PATH = "/log.txt" -- 1 = NEW (replaces existing file on start) config.LOG_MODE = 0 --- crypto config -config.SECURE = true --- must be common between all devices -config.PASSWORD = "testpassword!" - return config diff --git a/coordinator/startup.lua b/coordinator/startup.lua index d095d67..46f907a 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.6" +local COORDINATOR_VERSION = "beta-v0.9.7" local print = util.print local println = util.println @@ -42,14 +42,15 @@ cfv.assert_port(config.SCADA_SV_PORT) cfv.assert_port(config.SCADA_SV_LISTEN) cfv.assert_port(config.SCADA_API_LISTEN) cfv.assert_type_int(config.TRUSTED_RANGE) +cfv.assert_type_num(config.COMMS_TIMEOUT) +cfv.assert_min(config.COMMS_TIMEOUT, 1) cfv.assert_type_int(config.NUM_UNITS) cfv.assert_type_bool(config.RECOLOR) cfv.assert_type_num(config.SOUNDER_VOLUME) cfv.assert_type_bool(config.TIME_24_HOUR) cfv.assert_type_str(config.LOG_PATH) cfv.assert_type_int(config.LOG_MODE) -cfv.assert_type_bool(config.SECURE) -cfv.assert_type_str(config.PASSWORD) + assert(cfv.valid(), "bad config file: missing/invalid fields") ---------------------------------------- @@ -142,7 +143,7 @@ local function main() end -- create connection watchdog - local conn_watchdog = util.new_watchdog(5) + local conn_watchdog = util.new_watchdog(config.COMMS_TIMEOUT) conn_watchdog.cancel() log.debug("boot> conn watchdog created") diff --git a/reactor-plc/config.lua b/reactor-plc/config.lua index f1a33a0..adc9b35 100644 --- a/reactor-plc/config.lua +++ b/reactor-plc/config.lua @@ -4,12 +4,16 @@ local config = {} config.NETWORKED = true -- unique reactor ID config.REACTOR_ID = 1 + -- port to send packets TO server config.SERVER_PORT = 16000 -- port to listen to incoming packets FROM server config.LISTEN_PORT = 14001 -- max trusted modem message distance (0 to disable check) config.TRUSTED_RANGE = 0 +-- time in seconds (>= 1) before assuming a remote device is no longer active +config.COMMS_TIMEOUT = 5 + -- log path config.LOG_PATH = "/log.txt" -- log mode diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index d390a19..e78b1b5 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.10.9" +local R_PLC_VERSION = "beta-v0.10.10" local print = util.print local println = util.println @@ -32,8 +32,11 @@ cfv.assert_type_int(config.REACTOR_ID) cfv.assert_port(config.SERVER_PORT) cfv.assert_port(config.LISTEN_PORT) cfv.assert_type_int(config.TRUSTED_RANGE) +cfv.assert_type_num(config.COMMS_TIMEOUT) +cfv.assert_min(config.COMMS_TIMEOUT, 1) cfv.assert_type_str(config.LOG_PATH) cfv.assert_type_int(config.LOG_MODE) + assert(cfv.valid(), "bad config file: missing/invalid fields") ---------------------------------------- @@ -157,8 +160,8 @@ local function main() log.debug("init> rps init") if __shared_memory.networked then - -- comms watchdog, 3 second timeout - smem_sys.conn_watchdog = util.new_watchdog(3) + -- comms watchdog + smem_sys.conn_watchdog = util.new_watchdog(config.COMMS_TIMEOUT) log.debug("init> conn watchdog started") -- start comms diff --git a/rtu/config.lua b/rtu/config.lua index 50122ea..afd94cf 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -6,14 +6,18 @@ local config = {} config.SERVER_PORT = 16000 -- port to listen to incoming packets FROM server config.LISTEN_PORT = 15001 --- max trusted modem message distance (0 to disable check) +-- max trusted modem message distance (< 1 to disable check) config.TRUSTED_RANGE = 0 +-- time in seconds (>= 1) before assuming a remote device is no longer active +config.COMMS_TIMEOUT = 5 + -- log path config.LOG_PATH = "/log.txt" -- log mode -- 0 = APPEND (adds to existing file on start) -- 1 = NEW (replaces existing file on start) config.LOG_MODE = 0 + -- RTU peripheral devices (named: side/network device name) config.RTU_DEVICES = { { diff --git a/rtu/startup.lua b/rtu/startup.lua index 22f6f2d..d036203 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.10.3" +local RTU_VERSION = "beta-v0.10.4" local rtu_t = types.rtu_t @@ -43,6 +43,8 @@ local cfv = util.new_validator() cfv.assert_port(config.SERVER_PORT) cfv.assert_port(config.LISTEN_PORT) cfv.assert_type_int(config.TRUSTED_RANGE) +cfv.assert_type_num(config.COMMS_TIMEOUT) +cfv.assert_min(config.COMMS_TIMEOUT, 1) cfv.assert_type_str(config.LOG_PATH) cfv.assert_type_int(config.LOG_MODE) cfv.assert_type_table(config.RTU_DEVICES) @@ -400,7 +402,7 @@ local function main() if configure() then -- start connection watchdog - smem_sys.conn_watchdog = util.new_watchdog(5) + smem_sys.conn_watchdog = util.new_watchdog(config.COMMS_TIMEOUT) log.debug("boot> conn watchdog started") -- setup comms diff --git a/supervisor/config.lua b/supervisor/config.lua index a7ceb7e..bbf4175 100644 --- a/supervisor/config.lua +++ b/supervisor/config.lua @@ -6,6 +6,11 @@ config.SCADA_DEV_LISTEN = 16000 config.SCADA_SV_LISTEN = 16100 -- max trusted modem message distance (0 to disable check) config.TRUSTED_RANGE = 0 +-- time in seconds (>= 1) before assuming a remote device is no longer active +config.PLC_TIMEOUT = 5 +config.RTU_TIMEOUT = 5 +config.CRD_TIMEOUT = 5 + -- expected number of reactors config.NUM_REACTORS = 4 -- expected number of boilers/turbines for each reactor @@ -15,6 +20,7 @@ config.REACTOR_COOLING = { { BOILERS = 1, TURBINES = 1 }, -- reactor unit 3 { BOILERS = 1, TURBINES = 1 } -- reactor unit 4 } + -- log path config.LOG_PATH = "/log.txt" -- log mode diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 9075d2a..d17659b 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -42,11 +42,12 @@ local PERIODICS = { } -- coordinator supervisor session ----@param id integer ----@param in_queue mqueue ----@param out_queue mqueue ----@param facility facility -function coordinator.new_session(id, in_queue, out_queue, facility) +---@param id integer session ID +---@param in_queue mqueue in message queue +---@param out_queue mqueue out message queue +---@param timeout number communications timeout +---@param facility facility facility data table +function coordinator.new_session(id, in_queue, out_queue, timeout, facility) local log_header = "crdn_session(" .. id .. "): " local self = { @@ -57,7 +58,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility) seq_num = 0, r_seq_num = nil, connected = true, - conn_watchdog = util.new_watchdog(5), + conn_watchdog = util.new_watchdog(timeout), last_rtt = 0, -- periodic messages periodics = { diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 7947526..7c07825 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -46,15 +46,15 @@ local PERIODICS = { } -- PLC supervisor session ----@param id integer ----@param for_reactor integer ----@param in_queue mqueue ----@param out_queue mqueue -function plc.new_session(id, for_reactor, in_queue, out_queue) +---@param id integer session ID +---@param for_reactor integer reactor ID +---@param in_queue mqueue in message queue +---@param out_queue mqueue out message queue +---@param timeout number communications timeout +function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) local log_header = "plc_session(" .. id .. "): " local self = { - id = id, for_reactor = for_reactor, in_q = in_queue, out_q = out_queue, @@ -70,7 +70,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) connected = true, received_struct = false, received_status_cache = false, - plc_conn_watchdog = util.new_watchdog(3), + plc_conn_watchdog = util.new_watchdog(timeout), last_rtt = 0, -- periodic messages periodics = { @@ -248,7 +248,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) local s_pkt = comms.scada_packet() local r_pkt = comms.rplc_packet() - r_pkt.make(self.id, msg_type, msg) + r_pkt.make(for_reactor, msg_type, msg) s_pkt.make(self.seq_num, PROTOCOLS.RPLC, r_pkt.raw_sendable()) self.out_q.push_packet(s_pkt) @@ -503,7 +503,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) -- PUBLIC FUNCTIONS -- -- get the session ID - function public.get_id() return self.id end + function public.get_id() return id end -- get the session database function public.get_db() return self.sDB end diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 607f4f6..573a5ac 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -32,12 +32,13 @@ local PERIODICS = { } -- create a new RTU session ----@param id integer ----@param in_queue mqueue ----@param out_queue mqueue ----@param advertisement table ----@param facility facility -function rtu.new_session(id, in_queue, out_queue, advertisement, facility) +---@param id integer session ID +---@param in_queue mqueue in message queue +---@param out_queue mqueue out message queue +---@param timeout number communications timeout +---@param advertisement table RTU device advertisement +---@param facility facility facility data table +function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facility) local log_header = "rtu_session(" .. id .. "): " local self = { @@ -50,7 +51,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility) seq_num = 0, r_seq_num = nil, connected = true, - rtu_conn_watchdog = util.new_watchdog(3), + rtu_conn_watchdog = util.new_watchdog(timeout), last_rtt = 0, -- periodic messages periodics = { diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 04e2f5e..e7ce735 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -2,6 +2,8 @@ local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") local util = require("scada-common.util") +local config = require("supervisor.config") + local facility = require("supervisor.session.facility") local svqtypes = require("supervisor.session.svqtypes") @@ -292,7 +294,7 @@ function svsessions.establish_plc_session(local_port, remote_port, for_reactor, instance = nil ---@type plc_session } - plc_s.instance = plc.new_session(self.next_plc_id, for_reactor, plc_s.in_queue, plc_s.out_queue) + plc_s.instance = plc.new_session(self.next_plc_id, for_reactor, plc_s.in_queue, plc_s.out_queue, config.PLC_TIMEOUT) table.insert(self.plc_sessions, plc_s) local units = self.facility.get_units() @@ -329,7 +331,7 @@ function svsessions.establish_rtu_session(local_port, remote_port, advertisement instance = nil ---@type rtu_session } - rtu_s.instance = rtu.new_session(self.next_rtu_id, rtu_s.in_queue, rtu_s.out_queue, advertisement, self.facility) + rtu_s.instance = rtu.new_session(self.next_rtu_id, rtu_s.in_queue, rtu_s.out_queue, config.RTU_TIMEOUT, advertisement, self.facility) table.insert(self.rtu_sessions, rtu_s) log.debug("established new RTU session to " .. remote_port .. " with ID " .. self.next_rtu_id) @@ -359,7 +361,7 @@ function svsessions.establish_coord_session(local_port, remote_port, version) instance = nil ---@type coord_session } - coord_s.instance = coordinator.new_session(self.next_coord_id, coord_s.in_queue, coord_s.out_queue, self.facility) + coord_s.instance = coordinator.new_session(self.next_coord_id, coord_s.in_queue, coord_s.out_queue, config.CRD_TIMEOUT, self.facility) table.insert(self.coord_sessions, coord_s) log.debug("established new coordinator session to " .. remote_port .. " with ID " .. self.next_coord_id) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 2ae0df3..037b62b 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.11.1" +local SUPERVISOR_VERSION = "beta-v0.11.2" local print = util.print local println = util.println @@ -30,6 +30,12 @@ local cfv = util.new_validator() cfv.assert_port(config.SCADA_DEV_LISTEN) cfv.assert_port(config.SCADA_SV_LISTEN) cfv.assert_type_int(config.TRUSTED_RANGE) +cfv.assert_type_num(config.PLC_TIMEOUT) +cfv.assert_min(config.PLC_TIMEOUT, 1) +cfv.assert_type_num(config.RTU_TIMEOUT) +cfv.assert_min(config.RTU_TIMEOUT, 1) +cfv.assert_type_num(config.CRD_TIMEOUT) +cfv.assert_min(config.CRD_TIMEOUT, 1) cfv.assert_type_int(config.NUM_REACTORS) cfv.assert_type_table(config.REACTOR_COOLING) cfv.assert_type_str(config.LOG_PATH) From fdf75350c0c7be08e262881486be9a3a1fe8289f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 13 Feb 2023 12:29:59 -0500 Subject: [PATCH 501/587] #146 increased minimum timeout --- coordinator/config.lua | 2 +- coordinator/startup.lua | 2 +- reactor-plc/config.lua | 2 +- reactor-plc/startup.lua | 2 +- rtu/config.lua | 2 +- rtu/startup.lua | 2 +- supervisor/config.lua | 2 +- supervisor/startup.lua | 6 +++--- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/coordinator/config.lua b/coordinator/config.lua index c293fa2..a29bb46 100644 --- a/coordinator/config.lua +++ b/coordinator/config.lua @@ -8,7 +8,7 @@ config.SCADA_SV_LISTEN = 16101 config.SCADA_API_LISTEN = 16200 -- max trusted modem message distance (0 to disable check) config.TRUSTED_RANGE = 0 --- time in seconds (>= 1) before assuming a remote device is no longer active +-- time in seconds (>= 2) before assuming a remote device is no longer active config.COMMS_TIMEOUT = 5 -- expected number of reactor units, used only to require that number of unit monitors diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 46f907a..8aa0d79 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -43,7 +43,7 @@ cfv.assert_port(config.SCADA_SV_LISTEN) cfv.assert_port(config.SCADA_API_LISTEN) cfv.assert_type_int(config.TRUSTED_RANGE) cfv.assert_type_num(config.COMMS_TIMEOUT) -cfv.assert_min(config.COMMS_TIMEOUT, 1) +cfv.assert_min(config.COMMS_TIMEOUT, 2) cfv.assert_type_int(config.NUM_UNITS) cfv.assert_type_bool(config.RECOLOR) cfv.assert_type_num(config.SOUNDER_VOLUME) diff --git a/reactor-plc/config.lua b/reactor-plc/config.lua index adc9b35..f3cf0f6 100644 --- a/reactor-plc/config.lua +++ b/reactor-plc/config.lua @@ -11,7 +11,7 @@ config.SERVER_PORT = 16000 config.LISTEN_PORT = 14001 -- max trusted modem message distance (0 to disable check) config.TRUSTED_RANGE = 0 --- time in seconds (>= 1) before assuming a remote device is no longer active +-- time in seconds (>= 2) before assuming a remote device is no longer active config.COMMS_TIMEOUT = 5 -- log path diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index e78b1b5..584afbe 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -33,7 +33,7 @@ cfv.assert_port(config.SERVER_PORT) cfv.assert_port(config.LISTEN_PORT) cfv.assert_type_int(config.TRUSTED_RANGE) cfv.assert_type_num(config.COMMS_TIMEOUT) -cfv.assert_min(config.COMMS_TIMEOUT, 1) +cfv.assert_min(config.COMMS_TIMEOUT, 2) cfv.assert_type_str(config.LOG_PATH) cfv.assert_type_int(config.LOG_MODE) diff --git a/rtu/config.lua b/rtu/config.lua index afd94cf..96b26ee 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -8,7 +8,7 @@ config.SERVER_PORT = 16000 config.LISTEN_PORT = 15001 -- max trusted modem message distance (< 1 to disable check) config.TRUSTED_RANGE = 0 --- time in seconds (>= 1) before assuming a remote device is no longer active +-- time in seconds (>= 2) before assuming a remote device is no longer active config.COMMS_TIMEOUT = 5 -- log path diff --git a/rtu/startup.lua b/rtu/startup.lua index d036203..76203a3 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -44,7 +44,7 @@ cfv.assert_port(config.SERVER_PORT) cfv.assert_port(config.LISTEN_PORT) cfv.assert_type_int(config.TRUSTED_RANGE) cfv.assert_type_num(config.COMMS_TIMEOUT) -cfv.assert_min(config.COMMS_TIMEOUT, 1) +cfv.assert_min(config.COMMS_TIMEOUT, 2) cfv.assert_type_str(config.LOG_PATH) cfv.assert_type_int(config.LOG_MODE) cfv.assert_type_table(config.RTU_DEVICES) diff --git a/supervisor/config.lua b/supervisor/config.lua index bbf4175..47d530d 100644 --- a/supervisor/config.lua +++ b/supervisor/config.lua @@ -6,7 +6,7 @@ config.SCADA_DEV_LISTEN = 16000 config.SCADA_SV_LISTEN = 16100 -- max trusted modem message distance (0 to disable check) config.TRUSTED_RANGE = 0 --- time in seconds (>= 1) before assuming a remote device is no longer active +-- time in seconds (>= 2) before assuming a remote device is no longer active config.PLC_TIMEOUT = 5 config.RTU_TIMEOUT = 5 config.CRD_TIMEOUT = 5 diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 037b62b..e04f6b6 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -31,11 +31,11 @@ cfv.assert_port(config.SCADA_DEV_LISTEN) cfv.assert_port(config.SCADA_SV_LISTEN) cfv.assert_type_int(config.TRUSTED_RANGE) cfv.assert_type_num(config.PLC_TIMEOUT) -cfv.assert_min(config.PLC_TIMEOUT, 1) +cfv.assert_min(config.PLC_TIMEOUT, 2) cfv.assert_type_num(config.RTU_TIMEOUT) -cfv.assert_min(config.RTU_TIMEOUT, 1) +cfv.assert_min(config.RTU_TIMEOUT, 2) cfv.assert_type_num(config.CRD_TIMEOUT) -cfv.assert_min(config.CRD_TIMEOUT, 1) +cfv.assert_min(config.CRD_TIMEOUT, 2) cfv.assert_type_int(config.NUM_REACTORS) cfv.assert_type_table(config.REACTOR_COOLING) cfv.assert_type_str(config.LOG_PATH) From ccd9f4b6ccad5163a104425f55d9c0d47cff7019 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 13 Feb 2023 18:08:32 -0500 Subject: [PATCH 502/587] #158 fixed race conditions and cleaned up ascram logic --- supervisor/session/facility.lua | 126 +++++++++++++++----------------- supervisor/session/plc.lua | 36 ++++----- supervisor/startup.lua | 2 +- 3 files changed, 78 insertions(+), 86 deletions(-) diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index ab658e4..b9b5ccd 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -69,6 +69,12 @@ function facility.new(num_reactors, cooling_conf) at_max_burn = false, ascram = false, ascram_reason = AUTO_SCRAM.NONE, + ascram_status = { + matrix_dc = false, + matrix_fill = false, + crit_alarm = false, + gen_fault = false + }, -- closed loop control charge_conversion = 1.0, time_start = 0.0, @@ -512,111 +518,95 @@ function facility.new(num_reactors, cooling_conf) -- Evaluate Automatic SCRAM -- ------------------------------ + local astatus = self.ascram_status + if (self.mode ~= PROCESS.INACTIVE) and (self.mode ~= PROCESS.UNIT_ALARM_IDLE) then - local scram = false - - if self.units_ready and self.ascram_reason == AUTO_SCRAM.GEN_FAULT then - self.ascram_reason = AUTO_SCRAM.NONE - end - if self.induction[1] ~= nil then local matrix = self.induction[1] ---@type unit_session local db = matrix.get_db() ---@type imatrix_session_db - if self.ascram_reason == AUTO_SCRAM.MATRIX_DC then - self.ascram_reason = AUTO_SCRAM.NONE - log.info("FAC: cleared automatic SCRAM trip due to prior induction matrix disconnect") + -- clear matrix disconnected + if astatus.matrix_dc then + astatus.matrix_dc = false + log.info("FAC: induction matrix reconnected, clearing ASCRAM condition") end - if (db.tanks.energy_fill >= HIGH_CHARGE) or - (self.ascram_reason == AUTO_SCRAM.MATRIX_FILL and db.tanks.energy_fill > RE_ENABLE_CHARGE) then - scram = true + -- check matrix fill too high + local was_fill = astatus.matrix_fill + astatus.matrix_fill = (db.tanks.energy_fill >= HIGH_CHARGE) or (astatus.matrix_fill and db.tanks.energy_fill > RE_ENABLE_CHARGE) - if self.mode ~= PROCESS.MATRIX_FAULT_IDLE then - self.return_mode = self.mode - next_mode = PROCESS.MATRIX_FAULT_IDLE - end - - if self.ascram_reason == AUTO_SCRAM.NONE then - self.ascram_reason = AUTO_SCRAM.MATRIX_FILL - end - elseif self.ascram_reason == AUTO_SCRAM.MATRIX_FILL then + if was_fill and not astatus.matrix_fill then log.info("FAC: charge state of induction matrix entered acceptable range <= " .. (RE_ENABLE_CHARGE * 100) .. "%") - self.ascram_reason = AUTO_SCRAM.NONE end + -- system not ready, will need to restart GEN_RATE mode + -- clears when we enter the fault waiting state + astatus.gen_fault = self.mode == PROCESS.GEN_RATE and not self.units_ready + + -- check for critical unit alarms for i = 1, #self.units do local u = self.units[i] ---@type reactor_unit if u.has_critical_alarm() then - scram = true - - if self.ascram_reason == AUTO_SCRAM.NONE then - self.ascram_reason = AUTO_SCRAM.CRIT_ALARM - end - - next_mode = PROCESS.UNIT_ALARM_IDLE - - log.info("FAC: emergency exit of process control due to critical unit alarm") + log.info(util.c("FAC: emergency exit of process control due to critical unit alarm (unit ", u.get_id(), ")")) break end end - - if (self.mode == PROCESS.GEN_RATE) and (not self.units_ready) then - -- system not ready, will need to restart GEN_RATE mode - scram = true - - if self.ascram_reason == AUTO_SCRAM.NONE then - self.ascram_reason = AUTO_SCRAM.GEN_FAULT - end - - next_mode = PROCESS.GEN_RATE_FAULT_IDLE - end else - scram = true - - if self.mode ~= PROCESS.MATRIX_FAULT_IDLE then - self.return_mode = self.mode - next_mode = PROCESS.MATRIX_FAULT_IDLE - end - - if self.ascram_reason == AUTO_SCRAM.NONE then - self.ascram_reason = AUTO_SCRAM.MATRIX_DC - end + astatus.matrix_dc = true end - -- SCRAM all units - if (not self.ascram) and scram then + -- log.debug(util.c("dc: ", astatus.matrix_dc, " fill: ", astatus.matrix_fill, " crit: ", astatus.crit_alarm, " gen: ", astatus.gen_fault)) + + local scram = astatus.matrix_dc or astatus.matrix_fill or astatus.crit_alarm or astatus.gen_fault + + if scram and not self.ascram then + -- SCRAM all units for i = 1, #self.prio_defs do for _, u in pairs(self.prio_defs[i]) do u.a_scram() end end - if self.ascram_reason == AUTO_SCRAM.MATRIX_DC then - log.info("FAC: automatic SCRAM due to induction matrix disconnection") - self.status_text = { "AUTOMATIC SCRAM", "induction matrix disconnected" } - elseif self.ascram_reason == AUTO_SCRAM.MATRIX_FILL then - log.info("FAC: automatic SCRAM due to induction matrix high charge") - self.status_text = { "AUTOMATIC SCRAM", "induction matrix fill high" } - elseif self.ascram_reason == AUTO_SCRAM.CRIT_ALARM then - log.info("FAC: automatic SCRAM due to critical unit alarm") + if astatus.crit_alarm then + -- highest priority alarm + next_mode = PROCESS.UNIT_ALARM_IDLE + self.ascram_reason = AUTO_SCRAM.CRIT_ALARM self.status_text = { "AUTOMATIC SCRAM", "critical unit alarm tripped" } - elseif self.ascram_reason == AUTO_SCRAM.GEN_FAULT then - log.info("FAC: automatic SCRAM due to unit problem while in GEN_RATE mode, will resume once all units are ready") + + log.info("FAC: automatic SCRAM due to critical unit alarm") + elseif astatus.matrix_dc then + next_mode = PROCESS.MATRIX_FAULT_IDLE + self.ascram_reason = AUTO_SCRAM.MATRIX_DC + self.status_text = { "AUTOMATIC SCRAM", "induction matrix disconnected" } + + if self.mode ~= PROCESS.MATRIX_FAULT_IDLE then self.return_mode = self.mode end + + log.info("FAC: automatic SCRAM due to induction matrix disconnection") + elseif astatus.matrix_fill then + next_mode = PROCESS.MATRIX_FAULT_IDLE + self.ascram_reason = AUTO_SCRAM.MATRIX_FILL + self.status_text = { "AUTOMATIC SCRAM", "induction matrix fill high" } + + if self.mode ~= PROCESS.MATRIX_FAULT_IDLE then self.return_mode = self.mode end + + log.info("FAC: automatic SCRAM due to induction matrix high charge") + elseif astatus.gen_fault then + -- lowest priority alarm + next_mode = PROCESS.GEN_RATE_FAULT_IDLE + self.ascram_reason = AUTO_SCRAM.GEN_FAULT self.status_text = { "GENERATION MODE IDLE", "paused: system not ready" } - else - log.error(util.c("FAC: automatic SCRAM reason (", self.ascram_reason, ") not set to a known value")) + + log.info("FAC: automatic SCRAM due to unit problem while in GEN_RATE mode, will resume once all units are ready") end end self.ascram = scram - -- clear PLC SCRAM if we should if not self.ascram then self.ascram_reason = AUTO_SCRAM.NONE - -- do not reset while in gen rate, we have to exit this mode first + -- reset PLC RPS trips if we should for i = 1, #self.units do local u = self.units[i] ---@type reactor_unit u.a_cond_rps_reset() diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 7c07825..4d29c3c 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -62,7 +62,6 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) commanded_burn_rate = 0.0, auto_cmd_token = 0, ramping_rate = false, - auto_scram = false, auto_lock = false, -- connection properties seq_num = 0, @@ -82,12 +81,14 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) struct_req = (util.time() + 500), status_req = (util.time() + 500), scram_req = 0, + ascram_req = 0, burn_rate_req = 0, rps_reset_req = 0 }, -- command acknowledgements acks = { scram = true, + ascram = true, burn_rate = true, rps_reset = true }, @@ -398,7 +399,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) -- automatic SCRAM acknowledgement local ack = _get_ack(pkt) if ack then - self.acks.scram = true + self.acks.ascram = true self.sDB.control_state = false elseif ack == false then log.debug(log_header .. " automatic SCRAM failed!") @@ -449,9 +450,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) elseif pkt.type == RPLC_TYPES.RPS_AUTO_RESET then -- RPS auto control reset acknowledgement local ack = _get_ack(pkt) - if ack then - self.auto_scram = false - else + if not ack then log.debug(log_header .. "RPS auto reset failed") end elseif pkt.type == RPLC_TYPES.AUTO_BURN_RATE then @@ -608,23 +607,22 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) end elseif cmd == PLC_S_CMDS.SCRAM then -- SCRAM reactor - self.auto_scram = false self.acks.scram = false self.retry_times.scram_req = util.time() + INITIAL_WAIT _send(RPLC_TYPES.RPS_SCRAM, {}) elseif cmd == PLC_S_CMDS.ASCRAM then -- SCRAM reactor - self.auto_scram = true - self.acks.scram = false - self.retry_times.scram_req = util.time() + INITIAL_WAIT + self.acks.ascram = false + self.retry_times.ascram_req = util.time() + INITIAL_WAIT _send(RPLC_TYPES.RPS_ASCRAM, {}) elseif cmd == PLC_S_CMDS.RPS_RESET then -- reset RPS + self.acks.ascram = true self.acks.rps_reset = false self.retry_times.rps_reset_req = util.time() + INITIAL_WAIT _send(RPLC_TYPES.RPS_RESET, {}) elseif cmd == PLC_S_CMDS.RPS_AUTO_RESET then - if self.auto_scram or self.sDB.rps_status.timeout then + if self.sDB.rps_status.automatic or self.sDB.rps_status.timeout then _send(RPLC_TYPES.RPS_AUTO_RESET, {}) end else @@ -763,17 +761,21 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) -- SCRAM request retry if not self.acks.scram then - if rtimes.scram_req - util.time() <= 0 then - if self.auto_scram then - _send(RPLC_TYPES.RPS_ASCRAM, {}) - else - _send(RPLC_TYPES.RPS_SCRAM, {}) - end - + if rtimes.scram_req - util.time() <= 0 then + _send(RPLC_TYPES.RPS_SCRAM, {}) rtimes.scram_req = util.time() + RETRY_PERIOD end end + -- automatic SCRAM request retry + + if not self.acks.ascram then + if rtimes.ascram_req - util.time() <= 0 then + _send(RPLC_TYPES.RPS_ASCRAM, {}) + rtimes.ascram_req = util.time() + RETRY_PERIOD + end + end + -- RPS reset request retry if not self.acks.rps_reset then diff --git a/supervisor/startup.lua b/supervisor/startup.lua index e04f6b6..62c93ff 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.11.2" +local SUPERVISOR_VERSION = "beta-v0.11.3" local print = util.print local println = util.println From 2affe1b31ca5b25a5adef330b6c5378dd1f4be0a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 13 Feb 2023 18:20:48 -0500 Subject: [PATCH 503/587] #139 emergency coolant enabled on RPS low coolant --- scada-common/rsio.lua | 19 +++++++++++++------ supervisor/session/unit.lua | 21 ++++++++++++++++----- supervisor/startup.lua | 2 +- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 15bb258..7736a90 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -68,7 +68,10 @@ local IO_PORT = { R_EXCESS_WS = 18, -- active high, if the reactor has excess waste R_INSUFF_FUEL = 19, -- active high, if the reactor has insufficent fuel R_PLC_FAULT = 20, -- active high, if the reactor PLC reports a device access fault - R_PLC_TIMEOUT = 21 -- active high, if the reactor PLC has not been heard from + R_PLC_TIMEOUT = 21, -- active high, if the reactor PLC has not been heard from + + -- unit outputs + U_EMER_COOL = 22 -- active low, emergency coolant control } rsio.IO_LVL = IO_LVL @@ -104,7 +107,8 @@ function rsio.to_string(port) "R_EXCESS_WS", "R_INSUFF_FUEL", "R_PLC_FAULT", - "R_PLC_TIMEOUT" + "R_PLC_TIMEOUT", + "U_EMER_COOL" } if util.is_int(port) and port > 0 and port <= #names then @@ -164,7 +168,9 @@ local RS_DIO_MAP = { -- R_PLC_FAULT { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_PLC_TIMEOUT - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT } + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + -- U_EMER_COOL + { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT } } -- get the mode of a port @@ -192,7 +198,8 @@ function rsio.get_io_mode(port) IO_MODE.DIGITAL_OUT, -- R_EXCESS_WS IO_MODE.DIGITAL_OUT, -- R_INSUFF_FUEL IO_MODE.DIGITAL_OUT, -- R_PLC_FAULT - IO_MODE.DIGITAL_OUT -- R_PLC_TIMEOUT + IO_MODE.DIGITAL_OUT, -- R_PLC_TIMEOUT + IO_MODE.DIGITAL_OUT -- U_EMER_COOL } if util.is_int(port) and port > 0 and port <= #modes then @@ -212,7 +219,7 @@ local RS_SIDES = rs.getSides() ---@param port IO_PORT ---@return boolean valid function rsio.is_valid_port(port) - return util.is_int(port) and (port > 0) and (port <= IO_PORT.R_PLC_TIMEOUT) + return util.is_int(port) and (port > 0) and (port <= IO_PORT.U_EMER_COOL) end -- check if a side is valid @@ -261,7 +268,7 @@ end ---@param active boolean ---@return IO_LVL|false function rsio.digital_write_active(port, active) - if (not util.is_int(port)) or (port < IO_PORT.F_ALARM) or (port > IO_PORT.R_PLC_TIMEOUT) then + if (not util.is_int(port)) or (port < IO_PORT.F_ALARM) or (port > IO_PORT.U_EMER_COOL) then return false else return RS_DIO_MAP[port]._out(active) diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index e39e0aa..6efaf45 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -108,6 +108,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) plc_cache = { active = false, ok = false, + rps_trip = false, ---@type rps_status rps_status = { dmg_crit = false, @@ -315,11 +316,12 @@ function unit.new(for_reactor, num_boilers, num_turbines) local __rs_w = rs_rtu_io_ctl.digital_write local __rs_r = rs_rtu_io_ctl.digital_read - -- waste valves - local waste_pu = { open = function () __rs_w(IO.WASTE_PU, true) end, close = function () __rs_w(IO.WASTE_PU, false) end } - local waste_sna = { open = function () __rs_w(IO.WASTE_PO, true) end, close = function () __rs_w(IO.WASTE_PO, false) end } - local waste_po = { open = function () __rs_w(IO.WASTE_POPL, true) end, close = function () __rs_w(IO.WASTE_POPL, false) end } - local waste_sps = { open = function () __rs_w(IO.WASTE_AM, true) end, close = function () __rs_w(IO.WASTE_AM, false) end } + -- valves + local waste_pu = { open = function () __rs_w(IO.WASTE_PU, true) end, close = function () __rs_w(IO.WASTE_PU, false) end } + local waste_sna = { open = function () __rs_w(IO.WASTE_PO, true) end, close = function () __rs_w(IO.WASTE_PO, false) end } + local waste_po = { open = function () __rs_w(IO.WASTE_POPL, true) end, close = function () __rs_w(IO.WASTE_POPL, false) end } + local waste_sps = { open = function () __rs_w(IO.WASTE_AM, true) end, close = function () __rs_w(IO.WASTE_AM, false) end } + local emer_cool = { open = function () __rs_w(IO.U_EMER_COOL, true) end, close = function () __rs_w(IO.U_EMER_COOL, false) end } --#endregion @@ -462,6 +464,15 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- update status text logic.update_status_text(self) + + -- check if emergency coolant is needed + if self.plc_cache.rps_status.no_cool then + emer_cool.open() + elseif not self.plc_cache.rps_trip then + -- can't turn off on sufficient coolant level since it might drop again + -- turn off once system is OK again + emer_cool.close() + end end -- AUTO CONTROL OPERATIONS -- diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 62c93ff..16bc38b 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.11.3" +local SUPERVISOR_VERSION = "beta-v0.11.4" local print = util.print local println = util.println From 5751c320b1b68cee538f920dfe8ce8e252f18397 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 13 Feb 2023 18:53:00 -0500 Subject: [PATCH 504/587] only report not formed if its a multiblock --- rtu/startup.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtu/startup.lua b/rtu/startup.lua index 76203a3..7bfa330 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.10.4" +local RTU_VERSION = "beta-v0.10.5" local rtu_t = types.rtu_t @@ -376,7 +376,7 @@ local function main() table.insert(units, rtu_unit) - if not formed then + if is_multiblock and not formed then log.debug(util.c("configure> device '", name, "' is not formed")) end From ef27da8daf771ab35d4e23d347fa79571052321a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 13 Feb 2023 18:53:24 -0500 Subject: [PATCH 505/587] fixed incorrect text for boiler status on coordinator --- coordinator/startup.lua | 2 +- coordinator/ui/style.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 8aa0d79..0d285fc 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.7" +local COORDINATOR_VERSION = "beta-v0.9.8" local print = util.print local println = util.println diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index 24ade2e..ca584f4 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -75,11 +75,11 @@ style.boiler = { }, { color = cpair(colors.black, colors.orange), - text = "RTU FAULT" + text = "NOT FORMED" }, { color = cpair(colors.black, colors.orange), - text = "NOT FORMED" + text = "RTU FAULT" }, { color = cpair(colors.white, colors.gray), From 1fe2acb5c5b4f69ba43d5db74c8d78f1ac1fa86e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 13 Feb 2023 22:11:31 -0500 Subject: [PATCH 506/587] #144 added radiation monitor integration; displays, unit alarms, connection states, other bugfixes --- coordinator/iocontrol.lua | 70 ++++++++++++------ coordinator/startup.lua | 2 +- coordinator/ui/components/processctl.lua | 21 ++++-- coordinator/ui/components/unit_detail.lua | 11 +-- graphics/element.lua | 1 + graphics/elements/indicators/rad.lua | 88 +++++++++++++++++++++++ scada-common/types.lua | 4 ++ supervisor/session/coordinator.lua | 5 +- supervisor/session/facility.lua | 23 +++++- supervisor/session/rtu.lua | 5 ++ supervisor/session/rtu/envd.lua | 2 +- supervisor/session/unit.lua | 29 ++++++-- supervisor/session/unitlogic.lua | 13 +++- supervisor/startup.lua | 2 +- 14 files changed, 224 insertions(+), 52 deletions(-) create mode 100644 graphics/elements/indicators/rad.lua diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 1fe04f4..eda923c 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -31,13 +31,15 @@ function iocontrol.init(conf, comms) auto_saturated = false, auto_scram = false, - num_units = conf.num_units, ---@type integer + radiation = { radiation = 0, unit = "nSv" }, ---@type radiation_reading - save_cfg_ack = function (success) end, ---@param success boolean - start_ack = function (success) end, ---@param success boolean - stop_ack = function (success) end, ---@param success boolean - scram_ack = function (success) end, ---@param success boolean - ack_alarms_ack = function (success) end, ---@param success boolean + num_units = conf.num_units, ---@type integer + + save_cfg_ack = function (success) end, ---@param success boolean + start_ack = function (success) end, ---@param success boolean + stop_ack = function (success) end, ---@param success boolean + scram_ack = function (success) end, ---@param success boolean + ack_alarms_ack = function (success) end, ---@param success boolean ps = psil.create(), @@ -62,7 +64,8 @@ function iocontrol.init(conf, comms) ---@class ioctl_unit local entry = { - unit_id = i, ---@type integer + ---@type integer + unit_id = i, num_boilers = 0, num_turbines = 0, @@ -70,8 +73,9 @@ function iocontrol.init(conf, comms) control_state = false, burn_rate_cmd = 0.0, waste_control = 0, + radiation = { radiation = 0, unit = "nSv" }, ---@type radiation_reading - a_group = 0, -- auto control group + a_group = 0, -- auto control group start = function () process.start(i) end, scram = function () process.scram(i) end, @@ -370,6 +374,24 @@ function iocontrol.update_facility_status(status) else log.debug(log_header .. "induction matrix list not a table") end + + -- environment detector status + if type(rtu_statuses.rad_mon) == "table" then + if #rtu_statuses.rad_mon > 0 then + local rad_mon = rtu_statuses.rad_mon[1] + local rtu_faulted = rad_mon[1] ---@type boolean + fac.radiation = rad_mon[2] ---@type number + + fac.ps.publish("RadMonOnline", util.trinary(rtu_faulted, 2, 3)) + fac.ps.publish("radiation", fac.radiation) + else + fac.radiation = { radiation = 0, unit = "nSv" } + fac.ps.publish("RadMonOnline", 1) + end + else + log.debug(log_header .. "radiation monitor list not a table") + return false + end end end @@ -575,6 +597,24 @@ function iocontrol.update_unit_statuses(statuses) log.debug(log_header .. "turbine list not a table") return false end + + -- environment detector status + if type(rtu_statuses.rad_mon) == "table" then + if #rtu_statuses.rad_mon > 0 then + local rad_mon = rtu_statuses.rad_mon[1] + local rtu_faulted = rad_mon[1] ---@type boolean + unit.radiation = rad_mon[2] ---@type number + + unit.unit_ps.publish("RadMonOnline", util.trinary(rtu_faulted, 2, 3)) + unit.unit_ps.publish("radiation", unit.radiation) + else + unit.radiation = { radiation = 0, unit = "nSv" } + unit.unit_ps.publish("RadMonOnline", 1) + end + else + log.debug(log_header .. "radiation monitor list not a table") + return false + end else log.debug(log_header .. "rtu list not a table") end @@ -658,20 +698,6 @@ function iocontrol.update_unit_statuses(statuses) else log.debug(log_header .. "unit state not a table") end - - -- auto control state fields - - local auto_ctl_state = status[6] - - if type(auto_ctl_state) == "table" then - if #auto_ctl_state == 0 then - ---@todo - else - log.debug(log_header .. "auto control state length mismatch") - end - else - log.debug(log_header .. "auto control state not a table") - end end io.facility.ps.publish("burn_sum", burn_rate_sum) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 0d285fc..7fcd9a0 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.8" +local COORDINATOR_VERSION = "beta-v0.9.9" local print = util.print local println = util.println diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 3a37a8f..5d18c91 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -14,6 +14,7 @@ local TextBox = require("graphics.elements.textbox") local DataIndicator = require("graphics.elements.indicators.data") local IndicatorLight = require("graphics.elements.indicators.light") +local RadIndicator = require("graphics.elements.indicators.rad") local TriIndicatorLight = require("graphics.elements.indicators.trilight") local VerticalBar = require("graphics.elements.indicators.vbar") @@ -42,6 +43,7 @@ local function new_view(root, x, y) local bw_fg_bg = cpair(colors.black, colors.white) local hzd_fg_bg = cpair(colors.white, colors.gray) + local lu_cpair = cpair(colors.gray, colors.gray) local dis_colors = cpair(colors.white, colors.lightGray) local main = Div{parent=root,width=104,height=24,x=x,y=y} @@ -52,12 +54,13 @@ local function new_view(root, x, y) facility.scram_ack = scram.on_response facility.ack_alarms_ack = ack_a.on_response - local all_ok = IndicatorLight{parent=main,y=5,label="Unit Systems Online",colors=cpair(colors.green,colors.red)} + local all_ok = IndicatorLight{parent=main,y=5,label="Unit Systems Online",colors=cpair(colors.green,colors.red)} local ind_mat = IndicatorLight{parent=main,label="Induction Matrix",colors=cpair(colors.green,colors.gray)} - local rad_mon = IndicatorLight{parent=main,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)} + local rad_mon = TriIndicatorLight{parent=main,label="Radiation Monitor",c1=colors.gray,c2=colors.yellow,c3=colors.green} facility.ps.subscribe("all_sys_ok", all_ok.update) facility.induction_ps_tbl[1].subscribe("computed_status", function (status) ind_mat.update(status > 1) end) + facility.ps.subscribe("RadMonOnline", rad_mon.update) main.line_break() @@ -65,21 +68,25 @@ local function new_view(root, x, y) local auto_act = IndicatorLight{parent=main,label="Process Active",colors=cpair(colors.green,colors.gray)} local auto_ramp = IndicatorLight{parent=main,label="Process Ramping",colors=cpair(colors.white,colors.gray),flash=true,period=period.BLINK_250_MS} local auto_sat = IndicatorLight{parent=main,label="Min/Max Burn Rate",colors=cpair(colors.yellow,colors.gray)} - local auto_scram = IndicatorLight{parent=main,label="Automatic SCRAM",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} facility.ps.subscribe("auto_ready", auto_ready.update) facility.ps.subscribe("auto_active", auto_act.update) facility.ps.subscribe("auto_ramping", auto_ramp.update) facility.ps.subscribe("auto_saturated", auto_sat.update) - facility.ps.subscribe("auto_scram", auto_scram.update) main.line_break() - local _ = IndicatorLight{parent=main,label="Unit Off-line",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_1000_MS} - local _ = IndicatorLight{parent=main,label="Unit RPS Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local auto_scram = IndicatorLight{parent=main,label="Automatic SCRAM",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local _ = IndicatorLight{parent=main,label="Matrix Disconnected",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_250_MS} + local _ = IndicatorLight{parent=main,label="Matrix Charge High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} local _ = IndicatorLight{parent=main,label="Unit Critical Alarm",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - local _ = IndicatorLight{parent=main,label="High Charge Level",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local _ = IndicatorLight{parent=main,label="Gen. Control Fault",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + facility.ps.subscribe("auto_scram", auto_scram.update) + + TextBox{parent=main,y=23,text="Radiation",height=1,width=21,fg_bg=style.label} + local radiation = RadIndicator{parent=main,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} + facility.ps.subscribe("radiation", radiation.update) --------------------- -- process control -- diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 17d064f..b08a319 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -16,6 +16,7 @@ local AlarmLight = require("graphics.elements.indicators.alight") local CoreMap = require("graphics.elements.indicators.coremap") local DataIndicator = require("graphics.elements.indicators.data") local IndicatorLight = require("graphics.elements.indicators.light") +local RadIndicator = require("graphics.elements.indicators.rad") local TriIndicatorLight = require("graphics.elements.indicators.trilight") local VerticalBar = require("graphics.elements.indicators.vbar") @@ -133,9 +134,9 @@ local function init(parent, id) local damage_p = DataIndicator{parent=main,x=32,label="",format="%11.0f",value=0,unit="%",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} u_ps.subscribe("damage", damage_p.update) - ---@todo radiation monitor TextBox{parent=main,x=32,y=31,text="Radiation",height=1,width=21,fg_bg=style.label} - DataIndicator{parent=main,x=32,label="",format="%7.2f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} + local radiation = RadIndicator{parent=main,x=32,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} + u_ps.subscribe("radiation", radiation.update) ------------------- -- system status -- @@ -164,13 +165,13 @@ local function init(parent, id) annunciator.line_break() - ---@todo radiation monitor - local rad_mon = IndicatorLight{parent=annunciator,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)} + local rad_mon = TriIndicatorLight{parent=annunciator,label="Radiation Monitor",c1=colors.gray,c2=colors.yellow,c3=colors.green} u_ps.subscribe("PLCOnline", plc_online.update) u_ps.subscribe("PLCHeartbeat", plc_hbeat.update) u_ps.subscribe("status", r_active.update) u_ps.subscribe("AutoControl", r_auto.update) + u_ps.subscribe("RadMonOnline", rad_mon.update) annunciator.line_break() @@ -213,7 +214,7 @@ local function init(parent, id) local rps_nof = IndicatorLight{parent=rps_annunc,label="No Fuel",colors=cpair(colors.yellow,colors.gray)} local rps_noc = IndicatorLight{parent=rps_annunc,label="Coolant Level Low Low",colors=cpair(colors.yellow,colors.gray)} local rps_flt = IndicatorLight{parent=rps_annunc,label="PPM Fault",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} - local rps_tmo = IndicatorLight{parent=rps_annunc,label="Timeout",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} + local rps_tmo = IndicatorLight{parent=rps_annunc,label="Connection Timeout",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} local rps_sfl = IndicatorLight{parent=rps_annunc,label="System Failure",colors=cpair(colors.orange,colors.gray),flash=true,period=period.BLINK_500_MS} u_ps.subscribe("rps_tripped", rps_trp.update) diff --git a/graphics/element.lua b/graphics/element.lua index fdd0bfe..5f32060 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -34,6 +34,7 @@ local element = {} ---|icon_indicator_args ---|indicator_light_args ---|power_indicator_args +---|rad_indicator_args ---|state_indicator_args ---|tristate_indicator_light_args ---|vbar_args diff --git a/graphics/elements/indicators/rad.lua b/graphics/elements/indicators/rad.lua new file mode 100644 index 0000000..a27d0a4 --- /dev/null +++ b/graphics/elements/indicators/rad.lua @@ -0,0 +1,88 @@ +-- Radiation Indicator Graphics Element + +local util = require("scada-common.util") + +local element = require("graphics.element") + +---@class rad_indicator_args +---@field label string indicator label +---@field format string data format (lua string format) +---@field commas? boolean whether to use commas if a number is given (default to false) +---@field lu_colors? cpair label foreground color (a), unit foreground color (b) +---@field value any default value +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field width integer length +---@field fg_bg? cpair foreground/background colors + +-- new radiation indicator +---@param args rad_indicator_args +---@return graphics_element element, element_id id +local function rad(args) + assert(type(args.label) == "string", "graphics.elements.indicators.rad: label is a required field") + assert(type(args.format) == "string", "graphics.elements.indicators.rad: format is a required field") + assert(util.is_int(args.width), "graphics.elements.indicators.rad: width is a required field") + + -- single line + args.height = 1 + + -- create new graphics element base object + local e = element.new(args) + + -- label color + if args.lu_colors ~= nil then + e.window.setTextColor(args.lu_colors.color_a) + end + + -- write label + e.window.setCursorPos(1, 1) + e.window.write(args.label) + + local label_len = string.len(args.label) + local data_start = 1 + local clear_width = args.width + + if label_len > 0 then + data_start = data_start + (label_len + 1) + clear_width = args.width - (label_len + 1) + end + + -- on state change + ---@param value any new value + function e.on_update(value) + e.value = value.radiation + + -- clear old data and label + e.window.setCursorPos(data_start, 1) + e.window.write(util.spaces(clear_width)) + + -- write data + local data_str = util.sprintf(args.format, e.value) + e.window.setCursorPos(data_start, 1) + e.window.setTextColor(e.fg_bg.fgd) + if args.commas then + e.window.write(util.comma_format(data_str)) + else + e.window.write(data_str) + end + + -- write unit + if args.lu_colors ~= nil then + e.window.setTextColor(args.lu_colors.color_b) + end + e.window.write(" " .. value.unit) + end + + -- set the value + ---@param val any new value + function e.set_value(val) e.on_update(val) end + + -- initial value draw + e.on_update({ radiation = 0, unit = "nSv" }) + + return e.get() +end + +return rad diff --git a/scada-common/types.lua b/scada-common/types.lua index 2973383..31ef14b 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -11,6 +11,10 @@ local types = {} ---@field name string ---@field amount integer +---@class radiation_reading +---@field radiation number +---@field unit string + ---@class coordinate ---@field x integer ---@field y integer diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index d17659b..e49e065 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -149,15 +149,12 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) for i = 1, #self.units do local unit = self.units[i] ---@type reactor_unit - local auto_ctl = {} - status[unit.get_id()] = { unit.get_reactor_status(), unit.get_rtu_statuses(), unit.get_annunciator(), unit.get_alarms(), - unit.get_state(), - auto_ctl + unit.get_state() } end diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index b9b5ccd..864a0fe 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -49,8 +49,9 @@ local facility = {} function facility.new(num_reactors, cooling_conf) local self = { units = {}, - induction = {}, redstone = {}, + induction = {}, + envd = {}, status_text = { "START UP", "initializing..." }, all_sys_ok = false, -- process control @@ -199,11 +200,18 @@ function facility.new(num_reactors, cooling_conf) table.insert(self.induction, imatrix) end + -- link an environment detector RTU session + ---@param envd unit_session + function public.add_envd(envd) + table.insert(self.envd, envd) + end + -- purge devices associated with the given RTU session ID ---@param session integer RTU session ID function public.purge_rtu_devices(session) util.filter_table(self.redstone, function (s) return s.get_session_id() ~= session end) util.filter_table(self.induction, function (s) return s.get_session_id() ~= session end) + util.filter_table(self.envd, function (s) return s.get_session_id() ~= session end) end -- UPDATE -- @@ -211,8 +219,9 @@ function facility.new(num_reactors, cooling_conf) -- update (iterate) the facility management function public.update() -- unlink RTU unit sessions if they are closed - _unlink_disconnected_units(self.induction) _unlink_disconnected_units(self.redstone) + _unlink_disconnected_units(self.induction) + _unlink_disconnected_units(self.envd) -- current state for process control local charge_update = 0 @@ -785,7 +794,15 @@ function facility.new(num_reactors, cooling_conf) } end - ---@todo other RTU statuses + -- radiation monitors (environment detectors) + status.rad_mon = {} + for i = 1, #self.envd do + local envd = self.envd[i] ---@type unit_session + status.rad_mon[envd.get_device_idx()] = { + envd.is_faulted(), + envd.get_db().radiation + } + end return status end diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 573a5ac..35eaf87 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -136,6 +136,10 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili -- turbine (Mekanism 10.1+) unit = svrs_turbinev.new(id, i, unit_advert, self.modbus_q) if type(unit) ~= "nil" then target_unit.add_turbine(unit) end + elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then + -- environment detector + unit = svrs_envd.new(id, i, unit_advert, self.modbus_q) + if type(unit) ~= "nil" then target_unit.add_envd(unit) end else log.error(util.c(log_header, "bad advertisement: encountered unsupported reactor-specific RTU type ", type_string)) end @@ -157,6 +161,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then -- environment detector unit = svrs_envd.new(id, i, unit_advert, self.modbus_q) + if type(unit) ~= "nil" then facility.add_envd(unit) end else log.error(util.c(log_header, "bad advertisement: encountered unsupported reactor-independent RTU type ", type_string)) end diff --git a/supervisor/session/rtu/envd.lua b/supervisor/session/rtu/envd.lua index 4148b7d..7fb7c26 100644 --- a/supervisor/session/rtu/envd.lua +++ b/supervisor/session/rtu/envd.lua @@ -44,7 +44,7 @@ function envd.new(session_id, unit_id, advert, out_queue) ---@class envd_session_db db = { last_update = 0, - radiation = {}, + radiation = { radiation = 0, unit = "nSv" }, ---@type radiation_reading radiation_raw = 0 } } diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 6efaf45..bb95fcc 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -73,9 +73,10 @@ function unit.new(for_reactor, num_boilers, num_turbines) num_turbines = num_turbines, types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE }, defs = { FLOW_STABILITY_DELAY_MS = FLOW_STABILITY_DELAY_MS }, - turbines = {}, - boilers = {}, redstone = {}, + boilers = {}, + turbines = {}, + envd = {}, -- auto control ramp_target_br100 = 0, -- state tracking @@ -397,12 +398,19 @@ function unit.new(for_reactor, num_boilers, num_turbines) end end + -- link an environment detector RTU session + ---@param envd unit_session + function public.add_envd(envd) + table.insert(self.envd, envd) + end + -- purge devices associated with the given RTU session ID ---@param session integer RTU session ID function public.purge_rtu_devices(session) - util.filter_table(self.turbines, function (s) return s.get_session_id() ~= session end) - util.filter_table(self.boilers, function (s) return s.get_session_id() ~= session end) util.filter_table(self.redstone, function (s) return s.get_session_id() ~= session end) + util.filter_table(self.boilers, function (s) return s.get_session_id() ~= session end) + util.filter_table(self.turbines, function (s) return s.get_session_id() ~= session end) + util.filter_table(self.envd, function (s) return s.get_session_id() ~= session end) end --#endregion @@ -420,9 +428,10 @@ function unit.new(for_reactor, num_boilers, num_turbines) end -- unlink RTU unit sessions if they are closed + _unlink_disconnected_units(self.redstone) _unlink_disconnected_units(self.boilers) _unlink_disconnected_units(self.turbines) - _unlink_disconnected_units(self.redstone) + _unlink_disconnected_units(self.envd) -- update degraded state for auto control self.db.control.degraded = (#self.boilers ~= num_boilers) or (#self.turbines ~= num_turbines) or (self.plc_i == nil) @@ -709,7 +718,15 @@ function unit.new(for_reactor, num_boilers, num_turbines) } end - ---@todo other RTU statuses + -- radiation monitors (environment detectors) + status.rad_mon = {} + for i = 1, #self.envd do + local envd = self.envd[i] ---@type unit_session + status.rad_mon[envd.get_device_idx()] = { + envd.is_faulted(), + envd.get_db().radiation + } + end return status end diff --git a/supervisor/session/unitlogic.lua b/supervisor/session/unitlogic.lua index 11bdacd..3da3652 100644 --- a/supervisor/session/unitlogic.lua +++ b/supervisor/session/unitlogic.lua @@ -16,6 +16,11 @@ local aistate_string = { "RING_BACK_TRIPPING" } +-- background radiation 0.0000001 Sv/h (99.99 nSv/h) +-- "green tint" radiation 0.00001 Sv/h (10 uSv/h) +-- damaging radiation 0.00006 Sv/h (60 uSv/h) +local RADIATION_ALARM_LEVEL = 0.00005 -- 50 uSv/h, not yet damaging but this isn't good + ---@class unit_logic_extension local logic = {} @@ -388,8 +393,12 @@ function logic.update_alarms(self) _update_alarm_state(self, (not plc_cache.ok) and (plc_cache.damage > 99), self.alarms.ContainmentBreach) -- Containment Radiation - ---@todo containment radiation alarm - _update_alarm_state(self, false, self.alarms.ContainmentRadiation) + local rad_alarm = false + for i = 1, #self.envd do + rad_alarm = self.envd[i].get_db().radiation_raw > RADIATION_ALARM_LEVEL + break + end + _update_alarm_state(self, rad_alarm, self.alarms.ContainmentRadiation) -- Reactor Lost _update_alarm_state(self, self.had_reactor and self.plc_i == nil, self.alarms.ReactorLost) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 16bc38b..4ff3169 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.11.4" +local SUPERVISOR_VERSION = "beta-v0.11.5" local print = util.print local println = util.println From 655213e174f7331b33f93a4629d9ec32ea840074 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 13 Feb 2023 22:11:45 -0500 Subject: [PATCH 507/587] updated license --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 7cd46cf..e6824b5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Mikayla Fischler +Copyright © 2022 - 2023 Mikayla Fischler Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 9d5a55bf58d2757a2dc049bf90c764793e1d1dbf Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 13 Feb 2023 22:14:47 -0500 Subject: [PATCH 508/587] fixed the commit just now that broke status data to coordinator --- coordinator/iocontrol.lua | 2 +- coordinator/startup.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index eda923c..77b878e 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -417,7 +417,7 @@ function iocontrol.update_unit_statuses(statuses) local unit = io.units[i] ---@type ioctl_unit local status = statuses[i] - if type(status) ~= "table" or #status ~= 6 then + if type(status) ~= "table" or #status ~= 5 then log.debug(log_header .. "invalid status entry in unit statuses (not a table or invalid length)") return false end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 7fcd9a0..e7ed010 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.9" +local COORDINATOR_VERSION = "beta-v0.9.10" local print = util.print local println = util.println From 8ebdf2686bfd2eaea5b4db59c6d1b04ea98079b1 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 14 Feb 2023 15:15:34 -0500 Subject: [PATCH 509/587] #118 created constructors for basic types --- coordinator/iocontrol.lua | 4 ++-- coordinator/startup.lua | 2 +- scada-common/types.lua | 35 +++++++++++++++++++++++++++-- supervisor/session/plc.lua | 5 +++-- supervisor/session/rtu/boilerv.lua | 12 +++++----- supervisor/session/rtu/envd.lua | 2 +- supervisor/session/rtu/imatrix.lua | 4 ++-- supervisor/session/rtu/sna.lua | 4 ++-- supervisor/session/rtu/sps.lua | 8 +++---- supervisor/session/rtu/turbinev.lua | 6 ++--- supervisor/startup.lua | 2 +- 11 files changed, 58 insertions(+), 26 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 77b878e..af27ab5 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -31,7 +31,7 @@ function iocontrol.init(conf, comms) auto_saturated = false, auto_scram = false, - radiation = { radiation = 0, unit = "nSv" }, ---@type radiation_reading + radiation = types.new_zero_radiation_reading(), num_units = conf.num_units, ---@type integer @@ -73,7 +73,7 @@ function iocontrol.init(conf, comms) control_state = false, burn_rate_cmd = 0.0, waste_control = 0, - radiation = { radiation = 0, unit = "nSv" }, ---@type radiation_reading + radiation = types.new_zero_radiation_reading(), a_group = 0, -- auto control group diff --git a/coordinator/startup.lua b/coordinator/startup.lua index e7ed010..73df848 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.10" +local COORDINATOR_VERSION = "beta-v0.9.11" local print = util.print local println = util.println diff --git a/scada-common/types.lua b/scada-common/types.lua index 31ef14b..41946eb 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -11,15 +11,46 @@ local types = {} ---@field name string ---@field amount integer +-- create a new tank fluid +---@param n string name +---@param a integer amount +---@return radiation_reading +function types.new_tank_fluid(n, a) return { name = n, amount = a } end + +-- create a new empty tank fluid +---@return tank_fluid +function types.new_empty_gas() return { type = "mekanism:empty_gas", amount = 0 } end + ---@class radiation_reading ---@field radiation number ---@field unit string +-- create a new radiation reading +---@param r number radiaiton level +---@param u string radiation unit +---@return radiation_reading +function types.new_radiation_reading(r, u) return { radiation = r, unit = u } end + +-- create a new zeroed radiation reading +---@return radiation_reading +function types.new_zero_radiation_reading() return { radiation = 0, unit = "nSv" } end + ---@class coordinate ---@field x integer ---@field y integer ---@field z integer +-- create a new coordinate +---@param x integer +---@param y integer +---@param z integer +---@return coordinate +function types.new_coordinate(x, y, z) return { x = x, y = y, z = z } end + +-- create a new zero coordinate +---@return coordinate +function types.new_zero_coordinate() return { x = 0, y = 0, z = 0 } end + ---@class rtu_advertisement ---@field type integer ---@field index integer @@ -47,7 +78,7 @@ types.PROCESS = { CHARGE = 3, GEN_RATE = 4, MATRIX_FAULT_IDLE = 5, - UNIT_ALARM_IDLE = 6, + SYSTEM_ALARM_IDLE = 6, GEN_RATE_FAULT_IDLE = 7 } @@ -58,7 +89,7 @@ types.PROCESS_NAMES = { "CHARGE", "GEN_RATE", "MATRIX_FAULT_IDLE", - "UNIT_ALARM_IDLE", + "SYSTEM_ALARM_IDLE", "GEN_RATE_FAULT_IDLE" } diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 4d29c3c..f78c6e6 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -1,6 +1,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") +local types = require("scada-common.types") local util = require("scada-common.util") local svqtypes = require("supervisor.session.svqtypes") @@ -149,8 +150,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) length = 0, width = 0, height = 0, - min_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate - max_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate + min_pos = types.new_zero_coordinate(), + max_pos = types.new_zero_coordinate(), heat_cap = 0, fuel_asm = 0, fuel_sa = 0, diff --git a/supervisor/session/rtu/boilerv.lua b/supervisor/session/rtu/boilerv.lua index fe61372..2f3b5b5 100644 --- a/supervisor/session/rtu/boilerv.lua +++ b/supervisor/session/rtu/boilerv.lua @@ -62,8 +62,8 @@ function boilerv.new(session_id, unit_id, advert, out_queue) length = 0, width = 0, height = 0, - min_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate - max_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate + min_pos = types.new_zero_coordinate(), + max_pos = types.new_zero_coordinate(), boil_cap = 0.0, steam_cap = 0, water_cap = 0, @@ -80,16 +80,16 @@ function boilerv.new(session_id, unit_id, advert, out_queue) }, tanks = { last_update = 0, - steam = { type = "mekanism:empty_gas", amount = 0 }, ---@type tank_fluid + steam = types.new_empty_gas(), steam_need = 0, steam_fill = 0.0, - water = { type = "mekanism:empty_gas", amount = 0 }, ---@type tank_fluid + water = types.new_empty_gas(), water_need = 0, water_fill = 0.0, - hcool = { type = "mekanism:empty_gas", amount = 0 }, ---@type tank_fluid + hcool = types.new_empty_gas(), hcool_need = 0, hcool_fill = 0.0, - ccool = { type = "mekanism:empty_gas", amount = 0 }, ---@type tank_fluid + ccool = types.new_empty_gas(), ccool_need = 0, ccool_fill = 0.0 } diff --git a/supervisor/session/rtu/envd.lua b/supervisor/session/rtu/envd.lua index 7fb7c26..bc65476 100644 --- a/supervisor/session/rtu/envd.lua +++ b/supervisor/session/rtu/envd.lua @@ -44,7 +44,7 @@ function envd.new(session_id, unit_id, advert, out_queue) ---@class envd_session_db db = { last_update = 0, - radiation = { radiation = 0, unit = "nSv" }, ---@type radiation_reading + radiation = types.new_zero_radiation_reading(), radiation_raw = 0 } } diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua index 00e14f7..3ab5d4a 100644 --- a/supervisor/session/rtu/imatrix.lua +++ b/supervisor/session/rtu/imatrix.lua @@ -62,8 +62,8 @@ function imatrix.new(session_id, unit_id, advert, out_queue) length = 0, width = 0, height = 0, - min_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate - max_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate + min_pos = types.new_zero_coordinate(), + max_pos = types.new_zero_coordinate(), max_energy = 0, transfer_cap = 0, cells = 0, diff --git a/supervisor/session/rtu/sna.lua b/supervisor/session/rtu/sna.lua index 40014d3..cd3415f 100644 --- a/supervisor/session/rtu/sna.lua +++ b/supervisor/session/rtu/sna.lua @@ -64,10 +64,10 @@ function sna.new(session_id, unit_id, advert, out_queue) }, tanks = { last_update = 0, - input = {}, ---@type tank_fluid + input = types.new_empty_gas(), input_need = 0, input_fill = 0.0, - output = {}, ---@type tank_fluid + output = types.new_empty_gas(), output_need = 0, output_fill = 0.0 } diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua index 4cd7fb1..3a6f4c3 100644 --- a/supervisor/session/rtu/sps.lua +++ b/supervisor/session/rtu/sps.lua @@ -62,8 +62,8 @@ function sps.new(session_id, unit_id, advert, out_queue) length = 0, width = 0, height = 0, - min_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate - max_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate + min_pos = types.new_zero_coordinate(), + max_pos = types.new_zero_coordinate(), coils = 0, input_cap = 0, output_cap = 0, @@ -75,10 +75,10 @@ function sps.new(session_id, unit_id, advert, out_queue) }, tanks = { last_update = 0, - input = {}, ---@type tank_fluid + input = types.new_empty_gas(), input_need = 0, input_fill = 0.0, - output = {}, ---@type tank_fluid + output = types.new_empty_gas(), output_need = 0, output_fill = 0.0, energy = 0, diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua index 5662b70..0ac793a 100644 --- a/supervisor/session/rtu/turbinev.lua +++ b/supervisor/session/rtu/turbinev.lua @@ -74,8 +74,8 @@ function turbinev.new(session_id, unit_id, advert, out_queue) length = 0, width = 0, height = 0, - min_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate - max_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate + min_pos = types.new_zero_coordinate(), + max_pos = types.new_zero_coordinate(), blades = 0, coils = 0, vents = 0, @@ -96,7 +96,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue) }, tanks = { last_update = 0, - steam = { type = "mekanism:empty_gas", amount = 0 }, ---@type tank_fluid + steam = types.new_empty_gas(), steam_need = 0, steam_fill = 0.0, energy = 0, diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 4ff3169..164624d 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.11.5" +local SUPERVISOR_VERSION = "beta-v0.11.6" local print = util.print local println = util.println From 199ce53f52e5b26b92708f899d544c3f57e59dd2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 14 Feb 2023 22:55:40 -0500 Subject: [PATCH 510/587] #160 #161 linked up ASCRAM lights and added ASCRAM radiation condition --- coordinator/iocontrol.lua | 37 +++++++++++---- coordinator/startup.lua | 2 +- coordinator/ui/components/processctl.lua | 18 +++++--- coordinator/ui/components/unit_detail.lua | 2 +- supervisor/session/facility.lua | 56 +++++++++++++++++------ supervisor/startup.lua | 2 +- 6 files changed, 85 insertions(+), 32 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index af27ab5..44f35c8 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -29,7 +29,16 @@ function iocontrol.init(conf, comms) auto_active = false, auto_ramping = false, auto_saturated = false, + auto_scram = false, + ---@type ascram_status + ascram_status = { + matrix_dc = false, + matrix_fill = false, + crit_alarm = false, + radiation = false, + gen_fault = false + }, radiation = types.new_zero_radiation_reading(), @@ -278,15 +287,22 @@ function iocontrol.update_facility_status(status) local ctl_status = status[1] - if type(ctl_status) == "table" and (#ctl_status == 9) then + if type(ctl_status) == "table" and (#ctl_status == 14) then fac.all_sys_ok = ctl_status[1] fac.auto_ready = ctl_status[2] fac.auto_active = ctl_status[3] > 0 fac.auto_ramping = ctl_status[4] fac.auto_saturated = ctl_status[5] + fac.auto_scram = ctl_status[6] - fac.status_line_1 = ctl_status[7] - fac.status_line_2 = ctl_status[8] + fac.ascram_status.matrix_dc = ctl_status[7] + fac.ascram_status.matrix_fill = ctl_status[8] + fac.ascram_status.crit_alarm = ctl_status[9] + fac.ascram_status.radiation = ctl_status[10] + fac.ascram_status.gen_fault = ctl_status[11] + + fac.status_line_1 = ctl_status[12] + fac.status_line_2 = ctl_status[13] fac.ps.publish("all_sys_ok", fac.all_sys_ok) fac.ps.publish("auto_ready", fac.auto_ready) @@ -294,10 +310,15 @@ function iocontrol.update_facility_status(status) fac.ps.publish("auto_ramping", fac.auto_ramping) fac.ps.publish("auto_saturated", fac.auto_saturated) fac.ps.publish("auto_scram", fac.auto_scram) + fac.ps.publish("as_matrix_dc", fac.ascram_status.matrix_dc) + fac.ps.publish("as_matrix_fill", fac.ascram_status.matrix_fill) + fac.ps.publish("as_crit_alarm", fac.ascram_status.crit_alarm) + fac.ps.publish("as_radiation", fac.ascram_status.radiation) + fac.ps.publish("as_gen_fault", fac.ascram_status.gen_fault) fac.ps.publish("status_line_1", fac.status_line_1) fac.ps.publish("status_line_2", fac.status_line_2) - local group_map = ctl_status[9] + local group_map = ctl_status[14] if (type(group_map) == "table") and (#group_map == fac.num_units) then local names = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } @@ -382,11 +403,11 @@ function iocontrol.update_facility_status(status) local rtu_faulted = rad_mon[1] ---@type boolean fac.radiation = rad_mon[2] ---@type number - fac.ps.publish("RadMonOnline", util.trinary(rtu_faulted, 2, 3)) + fac.ps.publish("rad_computed_status", util.trinary(rtu_faulted, 2, 3)) fac.ps.publish("radiation", fac.radiation) else fac.radiation = { radiation = 0, unit = "nSv" } - fac.ps.publish("RadMonOnline", 1) + fac.ps.publish("rad_computed_status", 1) end else log.debug(log_header .. "radiation monitor list not a table") @@ -605,11 +626,11 @@ function iocontrol.update_unit_statuses(statuses) local rtu_faulted = rad_mon[1] ---@type boolean unit.radiation = rad_mon[2] ---@type number - unit.unit_ps.publish("RadMonOnline", util.trinary(rtu_faulted, 2, 3)) + unit.unit_ps.publish("rad_computed_status", util.trinary(rtu_faulted, 2, 3)) unit.unit_ps.publish("radiation", unit.radiation) else unit.radiation = { radiation = 0, unit = "nSv" } - unit.unit_ps.publish("RadMonOnline", 1) + unit.unit_ps.publish("rad_computed_status", 1) end else log.debug(log_header .. "radiation monitor list not a table") diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 73df848..872b3ce 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.11" +local COORDINATOR_VERSION = "beta-v0.9.12" local print = util.print local println = util.println diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 5d18c91..98d04dd 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -60,7 +60,7 @@ local function new_view(root, x, y) facility.ps.subscribe("all_sys_ok", all_ok.update) facility.induction_ps_tbl[1].subscribe("computed_status", function (status) ind_mat.update(status > 1) end) - facility.ps.subscribe("RadMonOnline", rad_mon.update) + facility.ps.subscribe("rad_computed_status", rad_mon.update) main.line_break() @@ -76,13 +76,19 @@ local function new_view(root, x, y) main.line_break() - local auto_scram = IndicatorLight{parent=main,label="Automatic SCRAM",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - local _ = IndicatorLight{parent=main,label="Matrix Disconnected",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_250_MS} - local _ = IndicatorLight{parent=main,label="Matrix Charge High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - local _ = IndicatorLight{parent=main,label="Unit Critical Alarm",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - local _ = IndicatorLight{parent=main,label="Gen. Control Fault",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local auto_scram = IndicatorLight{parent=main,label="Automatic SCRAM",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local matrix_dc = IndicatorLight{parent=main,label="Matrix Disconnected",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} + local matrix_fill = IndicatorLight{parent=main,label="Matrix Charge High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_500_MS} + local unit_crit = IndicatorLight{parent=main,label="Unit Critical Alarm",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local fac_rad_h = IndicatorLight{parent=main,label="Facility Radiation High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local gen_fault = IndicatorLight{parent=main,label="Gen. Control Fault",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} facility.ps.subscribe("auto_scram", auto_scram.update) + facility.ps.subscribe("as_matrix_dc", matrix_dc.update) + facility.ps.subscribe("as_matrix_fill", matrix_fill.update) + facility.ps.subscribe("as_crit_alarm", unit_crit.update) + facility.ps.subscribe("as_radiation", fac_rad_h.update) + facility.ps.subscribe("as_gen_fault", gen_fault.update) TextBox{parent=main,y=23,text="Radiation",height=1,width=21,fg_bg=style.label} local radiation = RadIndicator{parent=main,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index b08a319..ad66532 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -171,7 +171,7 @@ local function init(parent, id) u_ps.subscribe("PLCHeartbeat", plc_hbeat.update) u_ps.subscribe("status", r_active.update) u_ps.subscribe("AutoControl", r_auto.update) - u_ps.subscribe("RadMonOnline", rad_mon.update) + u_ps.subscribe("rad_computed_status", rad_mon.update) annunciator.line_break() diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index 864a0fe..0818210 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -15,6 +15,11 @@ local POWER_PER_BLADE = util.joules_to_fe(7140) local FLOW_STABILITY_DELAY_S = unit.FLOW_STABILITY_DELAY_MS / 1000 +-- background radiation 0.0000001 Sv/h (99.99 nSv/h) +-- "green tint" radiation 0.00001 Sv/h (10 uSv/h) +-- damaging radiation 0.00006 Sv/h (60 uSv/h) +local RADIATION_ALARM_LEVEL = 0.00001 + local HIGH_CHARGE = 1.0 local RE_ENABLE_CHARGE = 0.95 @@ -23,7 +28,8 @@ local AUTO_SCRAM = { MATRIX_DC = 1, MATRIX_FILL = 2, CRIT_ALARM = 3, - GEN_FAULT = 4 + RADIATION = 4, + GEN_FAULT = 5 } local START_STATUS = { @@ -70,10 +76,12 @@ function facility.new(num_reactors, cooling_conf) at_max_burn = false, ascram = false, ascram_reason = AUTO_SCRAM.NONE, + ---@class ascram_status ascram_status = { matrix_dc = false, matrix_fill = false, crit_alarm = false, + radiation = false, gen_fault = false }, -- closed loop control @@ -277,7 +285,7 @@ function facility.new(num_reactors, cooling_conf) if (self.last_mode == PROCESS.INACTIVE) or (self.last_mode == PROCESS.GEN_RATE_FAULT_IDLE) then self.start_fail = START_STATUS.OK - if (self.mode ~= PROCESS.MATRIX_FAULT_IDLE) and (self.mode ~= PROCESS.UNIT_ALARM_IDLE) then + if (self.mode ~= PROCESS.MATRIX_FAULT_IDLE) and (self.mode ~= PROCESS.SYSTEM_ALARM_IDLE) then -- auto clear ASCRAM self.ascram = false end @@ -488,12 +496,7 @@ function facility.new(num_reactors, cooling_conf) log.debug(util.sprintf("GEN_RATE[%f] { RATE[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%f] }", runtime, avg_inflow, error, integral, output, out_c, P, I, D)) - local _, fault = _allocate_burn_rate(out_c, false) - - if fault then - log.info("FAC: one or more units degraded, pausing GEN_RATE process control") - next_mode = PROCESS.GEN_RATE_FAULT_IDLE - end + _allocate_burn_rate(out_c, false) self.last_time = now self.last_error = error @@ -509,7 +512,7 @@ function facility.new(num_reactors, cooling_conf) next_mode = PROCESS.INACTIVE log.info("FAC: exiting matrix fault idle state due to critical unit alarm") end - elseif self.mode == PROCESS.UNIT_ALARM_IDLE then + elseif self.mode == PROCESS.SYSTEM_ALARM_IDLE then -- do nothing, wait for user to confirm (stop and reset) elseif self.mode == PROCESS.GEN_RATE_FAULT_IDLE then -- system faulted (degraded/not ready) while running generation rate mode @@ -529,7 +532,7 @@ function facility.new(num_reactors, cooling_conf) local astatus = self.ascram_status - if (self.mode ~= PROCESS.INACTIVE) and (self.mode ~= PROCESS.UNIT_ALARM_IDLE) then + if (self.mode ~= PROCESS.INACTIVE) and (self.mode ~= PROCESS.SYSTEM_ALARM_IDLE) then if self.induction[1] ~= nil then local matrix = self.induction[1] ---@type unit_session local db = matrix.get_db() ---@type imatrix_session_db @@ -548,10 +551,6 @@ function facility.new(num_reactors, cooling_conf) log.info("FAC: charge state of induction matrix entered acceptable range <= " .. (RE_ENABLE_CHARGE * 100) .. "%") end - -- system not ready, will need to restart GEN_RATE mode - -- clears when we enter the fault waiting state - astatus.gen_fault = self.mode == PROCESS.GEN_RATE and not self.units_ready - -- check for critical unit alarms for i = 1, #self.units do local u = self.units[i] ---@type reactor_unit @@ -561,6 +560,21 @@ function facility.new(num_reactors, cooling_conf) break end end + + -- check for facility radiation + if self.envd[1] ~= nil then + local envd = self.envd[1] ---@type unit_session + local e_db = envd.get_db() ---@type envd_session_db + + astatus.radiation = e_db.radiation_raw > RADIATION_ALARM_LEVEL + else + -- don't clear, if it is true then we lost it with high radiation, so just keep alarming + -- operator can restart the system or hit the stop/reset button + end + + -- system not ready, will need to restart GEN_RATE mode + -- clears when we enter the fault waiting state + astatus.gen_fault = self.mode == PROCESS.GEN_RATE and not self.units_ready else astatus.matrix_dc = true end @@ -579,11 +593,17 @@ function facility.new(num_reactors, cooling_conf) if astatus.crit_alarm then -- highest priority alarm - next_mode = PROCESS.UNIT_ALARM_IDLE + next_mode = PROCESS.SYSTEM_ALARM_IDLE self.ascram_reason = AUTO_SCRAM.CRIT_ALARM self.status_text = { "AUTOMATIC SCRAM", "critical unit alarm tripped" } log.info("FAC: automatic SCRAM due to critical unit alarm") + elseif astatus.radiation then + next_mode = PROCESS.SYSTEM_ALARM_IDLE + self.ascram_reason = AUTO_SCRAM.RADIATION + self.status_text = { "AUTOMATIC SCRAM", "facility radiation high" } + + log.info("FAC: automatic SCRAM due to high facility radiation") elseif astatus.matrix_dc then next_mode = PROCESS.MATRIX_FAULT_IDLE self.ascram_reason = AUTO_SCRAM.MATRIX_DC @@ -758,6 +778,7 @@ function facility.new(num_reactors, cooling_conf) -- get automatic process control status function public.get_control_status() + local astat = self.ascram_status return { self.all_sys_ok, self.units_ready, @@ -765,6 +786,11 @@ function facility.new(num_reactors, cooling_conf) self.waiting_on_ramp or self.waiting_on_stable, self.at_max_burn or self.saturated, self.ascram, + astat.matrix_dc, + astat.matrix_fill, + astat.crit_alarm, + astat.radiation, + astat.gen_fault or self.mode == PROCESS.GEN_RATE_FAULT_IDLE, self.status_text[1], self.status_text[2], self.group_map diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 164624d..3e92d15 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.11.6" +local SUPERVISOR_VERSION = "beta-v0.11.7" local print = util.print local println = util.println From 2babd67198973cb6db28fefd40dc13aa47016df5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 15 Feb 2023 19:52:28 -0500 Subject: [PATCH 511/587] #162 #168 status indicator for emergency coolant, display number of connected RTUs, added RCS hardware fault and radiation warning indicators --- coordinator/iocontrol.lua | 17 +++++--- coordinator/startup.lua | 2 +- coordinator/ui/components/processctl.lua | 6 ++- coordinator/ui/components/unit_detail.lua | 50 +++++++++++------------ graphics/elements/indicators/rad.lua | 3 +- scada-common/util.lua | 2 +- supervisor/session/facility.lua | 10 +++++ supervisor/session/svsessions.lua | 7 +++- supervisor/session/unit.lua | 17 +++++--- supervisor/session/unitlogic.lua | 33 +++++++++++++++ supervisor/startup.lua | 2 +- 11 files changed, 105 insertions(+), 44 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 44f35c8..b79166f 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -23,7 +23,9 @@ local io = {} function iocontrol.init(conf, comms) ---@class ioctl_facility io.facility = { + num_units = conf.num_units, ---@type integer all_sys_ok = false, + rtu_count = 0, auto_ready = false, auto_active = false, @@ -42,8 +44,6 @@ function iocontrol.init(conf, comms) radiation = types.new_zero_radiation_reading(), - num_units = conf.num_units, ---@type integer - save_cfg_ack = function (success) end, ---@param success boolean start_ack = function (success) end, ---@param success boolean stop_ack = function (success) end, ---@param success boolean @@ -336,7 +336,12 @@ function iocontrol.update_facility_status(status) local rtu_statuses = status[2] + fac.rtu_count = 0 if type(rtu_statuses) == "table" then + -- connected RTU count + fac.rtu_count = rtu_statuses.count + fac.ps.publish("rtu_count", fac.rtu_count) + -- power statistics if type(rtu_statuses.power) == "table" then fac.induction_ps_tbl[1].publish("avg_charge", rtu_statuses.power[1]) @@ -406,13 +411,15 @@ function iocontrol.update_facility_status(status) fac.ps.publish("rad_computed_status", util.trinary(rtu_faulted, 2, 3)) fac.ps.publish("radiation", fac.radiation) else - fac.radiation = { radiation = 0, unit = "nSv" } + fac.radiation = types.new_zero_radiation_reading() fac.ps.publish("rad_computed_status", 1) end else log.debug(log_header .. "radiation monitor list not a table") return false end + else + log.debug(log_header .. "rtu statuses not a table") end end @@ -626,11 +633,9 @@ function iocontrol.update_unit_statuses(statuses) local rtu_faulted = rad_mon[1] ---@type boolean unit.radiation = rad_mon[2] ---@type number - unit.unit_ps.publish("rad_computed_status", util.trinary(rtu_faulted, 2, 3)) unit.unit_ps.publish("radiation", unit.radiation) else - unit.radiation = { radiation = 0, unit = "nSv" } - unit.unit_ps.publish("rad_computed_status", 1) + unit.radiation = types.new_zero_radiation_reading() end else log.debug(log_header .. "radiation monitor list not a table") diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 872b3ce..c146477 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.12" +local COORDINATOR_VERSION = "beta-v0.9.13" local print = util.print local println = util.println diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 98d04dd..17b3e66 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -90,10 +90,14 @@ local function new_view(root, x, y) facility.ps.subscribe("as_radiation", fac_rad_h.update) facility.ps.subscribe("as_gen_fault", gen_fault.update) - TextBox{parent=main,y=23,text="Radiation",height=1,width=21,fg_bg=style.label} + TextBox{parent=main,y=23,text="Radiation",height=1,width=13,fg_bg=style.label} local radiation = RadIndicator{parent=main,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} facility.ps.subscribe("radiation", radiation.update) + TextBox{parent=main,x=15,y=23,text="Linked RTUs",height=1,width=11,fg_bg=style.label} + local rtu_count = DataIndicator{parent=main,x=15,y=24,label="",format="%11d",value=0,lu_colors=lu_cpair,width=11,fg_bg=bw_fg_bg} + facility.ps.subscribe("rtu_count", rtu_count.update) + --------------------- -- process control -- --------------------- diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index ad66532..f5716b5 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -157,28 +157,29 @@ local function init(parent, id) local annunciator = Div{parent=main,width=23,height=18,x=22,y=3} - -- connectivity/basic state + -- connectivity local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(colors.green,colors.red)} local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=cpair(colors.white,colors.gray)} - local r_active = IndicatorLight{parent=annunciator,label="Active",colors=cpair(colors.green,colors.gray)} - local r_auto = IndicatorLight{parent=annunciator,label="Automatic Control",colors=cpair(colors.blue,colors.gray)} - - annunciator.line_break() - - local rad_mon = TriIndicatorLight{parent=annunciator,label="Radiation Monitor",c1=colors.gray,c2=colors.yellow,c3=colors.green} + local rad_mon = TriIndicatorLight{parent=annunciator,label="Radiation Monitor",c1=colors.gray,c2=colors.yellow,c3=colors.green} u_ps.subscribe("PLCOnline", plc_online.update) u_ps.subscribe("PLCHeartbeat", plc_hbeat.update) - u_ps.subscribe("status", r_active.update) - u_ps.subscribe("AutoControl", r_auto.update) - u_ps.subscribe("rad_computed_status", rad_mon.update) + u_ps.subscribe("RadiationMonitor", rad_mon.update) annunciator.line_break() - -- non-RPS reactor annunciator panel + -- operating state + local r_active = IndicatorLight{parent=annunciator,label="Active",colors=cpair(colors.green,colors.gray)} + local r_auto = IndicatorLight{parent=annunciator,label="Automatic Control",colors=cpair(colors.white,colors.gray)} + + u_ps.subscribe("status", r_active.update) + u_ps.subscribe("AutoControl", r_auto.update) + + -- main unit transient/warning annunciator panel local r_scram = IndicatorLight{parent=annunciator,label="Reactor SCRAM",colors=cpair(colors.red,colors.gray)} local r_mscrm = IndicatorLight{parent=annunciator,label="Manual Reactor SCRAM",colors=cpair(colors.red,colors.gray)} local r_ascrm = IndicatorLight{parent=annunciator,label="Auto Reactor SCRAM",colors=cpair(colors.red,colors.gray)} + local rad_wrn = IndicatorLight{parent=annunciator,label="Radiation Warning",colors=cpair(colors.yellow,colors.gray)} local r_rtrip = IndicatorLight{parent=annunciator,label="RCP Trip",colors=cpair(colors.red,colors.gray)} local r_cflow = IndicatorLight{parent=annunciator,label="RCS Flow Low",colors=cpair(colors.yellow,colors.gray)} local r_clow = IndicatorLight{parent=annunciator,label="Coolant Level Low",colors=cpair(colors.yellow,colors.gray)} @@ -191,6 +192,7 @@ local function init(parent, id) u_ps.subscribe("ReactorSCRAM", r_scram.update) u_ps.subscribe("ManualReactorSCRAM", r_mscrm.update) u_ps.subscribe("AutoReactorSCRAM", r_ascrm.update) + u_ps.subscribe("RadiationWarning", rad_wrn.update) u_ps.subscribe("RCPTrip", r_rtrip.update) u_ps.subscribe("RCSFlowLow", r_cflow.update) u_ps.subscribe("CoolantLevelLow", r_clow.update) @@ -233,14 +235,18 @@ local function init(parent, id) TextBox{parent=main,text="REACTOR COOLANT SYSTEM",fg_bg=cpair(colors.black,colors.blue),alignment=TEXT_ALIGN.CENTER,width=33,height=1,x=46,y=22} local rcs = Rectangle{parent=main,border=border(1,colors.blue,true),thin=true,width=33,height=24,x=46,y=23} local rcs_annunc = Div{parent=rcs,width=27,height=22,x=2,y=1} - local rcs_tags = Div{parent=rcs,width=2,height=22,x=29,y=1} + local rcs_tags = Div{parent=rcs,width=2,height=13,x=29,y=9} - local c_cfm = IndicatorLight{parent=rcs_annunc,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} - local c_brm = IndicatorLight{parent=rcs_annunc,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)} - local c_sfm = IndicatorLight{parent=rcs_annunc,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} - local c_mwrf = IndicatorLight{parent=rcs_annunc,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} - local c_tbnt = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local c_flt = IndicatorLight{parent=rcs_annunc,label="RCS Hardware Fault",colors=cpair(colors.yellow,colors.gray)} + local c_emg = TriIndicatorLight{parent=rcs_annunc,label="Emergency Coolant",c1=colors.gray,c2=colors.white,c3=colors.yellow} + local c_cfm = IndicatorLight{parent=rcs_annunc,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_brm = IndicatorLight{parent=rcs_annunc,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_sfm = IndicatorLight{parent=rcs_annunc,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_mwrf = IndicatorLight{parent=rcs_annunc,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} + local c_tbnt = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + u_ps.subscribe("RCSFault", c_flt.update) + u_ps.subscribe("EmergencyCoolant", c_emg.update) u_ps.subscribe("CoolantFeedMismatch", c_cfm.update) u_ps.subscribe("BoilRateMismatch", c_brm.update) u_ps.subscribe("SteamFeedMismatch", c_sfm.update) @@ -252,7 +258,7 @@ local function init(parent, id) -- boiler annunciator panel(s) if unit.num_boilers > 0 then - TextBox{parent=rcs_tags,x=1,y=7,text="B1",width=2,height=1,fg_bg=bw_fg_bg} + TextBox{parent=rcs_tags,x=1,text="B1",width=2,height=1,fg_bg=bw_fg_bg} local b1_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=cpair(colors.red,colors.gray)} b_ps[1].subscribe("WasterLevelLow", b1_wll.update) @@ -273,7 +279,7 @@ local function init(parent, id) -- turbine annunciator panels if unit.num_boilers == 0 then - TextBox{parent=rcs_tags,y=7,text="T1",width=2,height=1,fg_bg=bw_fg_bg} + TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg} else rcs_tags.line_break() rcs_annunc.line_break() @@ -292,9 +298,6 @@ local function init(parent, id) t_ps[1].subscribe("TurbineTrip", t1_trp.update) if unit.num_turbines > 1 then - rcs_tags.line_break() - rcs_annunc.line_break() - TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg} local t2_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} t_ps[2].subscribe("SteamDumpOpen", function (val) t2_sdo.update(val + 1) end) @@ -309,9 +312,6 @@ local function init(parent, id) end if unit.num_turbines > 2 then - rcs_tags.line_break() - rcs_annunc.line_break() - TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg} local t3_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} t_ps[3].subscribe("SteamDumpOpen", function (val) t3_sdo.update(val + 1) end) diff --git a/graphics/elements/indicators/rad.lua b/graphics/elements/indicators/rad.lua index a27d0a4..86ec856 100644 --- a/graphics/elements/indicators/rad.lua +++ b/graphics/elements/indicators/rad.lua @@ -1,5 +1,6 @@ -- Radiation Indicator Graphics Element +local types = require("scada-common.types") local util = require("scada-common.util") local element = require("graphics.element") @@ -80,7 +81,7 @@ local function rad(args) function e.set_value(val) e.on_update(val) end -- initial value draw - e.on_update({ radiation = 0, unit = "nSv" }) + e.on_update(types.new_zero_radiation_reading()) return e.get() end diff --git a/scada-common/util.lua b/scada-common/util.lua index 56cacd7..afe3c59 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -14,7 +14,7 @@ util.TICK_TIME_MS = 50 --#region -- trinary operator ----@param cond boolean condition +---@param cond boolean|nil condition ---@param a any return if true ---@param b any return if false ---@return any value diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index 0818210..b621e67 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -60,6 +60,7 @@ function facility.new(num_reactors, cooling_conf) envd = {}, status_text = { "START UP", "initializing..." }, all_sys_ok = false, + rtu_conn_count = 0, -- process control units_ready = false, mode = PROCESS.INACTIVE, @@ -224,6 +225,12 @@ function facility.new(num_reactors, cooling_conf) -- UPDATE -- + -- supervisor sessions reporting the list of active RTU sessions + ---@param rtu_sessions table session list of all connected RTUs + function public.report_rtus(rtu_sessions) + self.rtu_conn_count = #rtu_sessions + end + -- update (iterate) the facility management function public.update() -- unlink RTU unit sessions if they are closed @@ -801,6 +808,9 @@ function facility.new(num_reactors, cooling_conf) function public.get_rtu_statuses() local status = {} + -- total count of all connected RTUs in the facility + status.count = self.rtu_conn_count + -- power averages from induction matricies status.power = { self.avg_charge.compute(), diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index e7ce735..91457c7 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -144,7 +144,7 @@ end ---@param sessions table local function _close(sessions) for i = 1, #sessions do - local session = sessions[i] ---@type plc_session_struct + local session = sessions[i] ---@type plc_session_struct|rtu_session_struct if session.open then _shutdown(session) end @@ -156,7 +156,7 @@ end ---@param timer_event number local function _check_watchdogs(sessions, timer_event) for i = 1, #sessions do - local session = sessions[i] ---@type plc_session_struct + local session = sessions[i] ---@type plc_session_struct|rtu_session_struct if session.open then local triggered = session.instance.check_wd(timer_event) if triggered then @@ -400,6 +400,9 @@ function svsessions.iterate_all() -- iterate coordinator sessions _iterate(self.coord_sessions) + -- report RTU sessions to facility + self.facility.report_rtus(self.rtu_sessions) + -- iterate facility self.facility.update() diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index bb95fcc..98cdd5b 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -163,10 +163,12 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- reactor PLCOnline = false, PLCHeartbeat = false, -- alternate true/false to blink, each time there is a keep_alive + RadiationMonitor = 1, AutoControl = false, ReactorSCRAM = false, ManualReactorSCRAM = false, AutoReactorSCRAM = false, + RadiationWarning = false, RCPTrip = false, RCSFlowLow = false, CoolantLevelLow = false, @@ -175,16 +177,19 @@ function unit.new(for_reactor, num_boilers, num_turbines) FuelInputRateLow = false, WasteLineOcclusion = false, HighStartupRate = false, - -- boiler + -- cooling + RCSFault = false, + EmergencyCoolant = 1, + CoolantFeedMismatch = false, + BoilRateMismatch = false, + SteamFeedMismatch = false, + MaxWaterReturnFeed = false, + -- boilers BoilerOnline = {}, HeatingRateLow = {}, WaterLevelLow = {}, - BoilRateMismatch = false, - CoolantFeedMismatch = false, - -- turbine + -- turbines TurbineOnline = {}, - SteamFeedMismatch = false, - MaxWaterReturnFeed = false, SteamDumpOpen = {}, TurbineOverSpeed = {}, TurbineTrip = {} diff --git a/supervisor/session/unitlogic.lua b/supervisor/session/unitlogic.lua index 3da3652..647def8 100644 --- a/supervisor/session/unitlogic.lua +++ b/supervisor/session/unitlogic.lua @@ -1,4 +1,5 @@ local log = require("scada-common.log") +local rsio = require("scada-common.rsio") local types = require("scada-common.types") local util = require("scada-common.util") @@ -7,6 +8,8 @@ local ALARM_STATE = types.ALARM_STATE local TRI_FAIL = types.TRI_FAIL local DUMPING_MODE = types.DUMPING_MODE +local IO = rsio.IO + local aistate_string = { "INACTIVE", "TRIPPING", @@ -19,6 +22,7 @@ local aistate_string = { -- background radiation 0.0000001 Sv/h (99.99 nSv/h) -- "green tint" radiation 0.00001 Sv/h (10 uSv/h) -- damaging radiation 0.00006 Sv/h (60 uSv/h) +local RADIATION_ALERT_LEVEL = 0.00001 -- 10 uSv/h local RADIATION_ALARM_LEVEL = 0.00005 -- 50 uSv/h, not yet damaging but this isn't good ---@class unit_logic_extension @@ -33,6 +37,8 @@ function logic.update_annunciator(self) local num_boilers = self.num_boilers local num_turbines = self.num_turbines + self.db.annunciator.RCSFault = false + -- variables for boiler, or reactor if no boilers used local total_boil_rate = 0.0 @@ -114,6 +120,29 @@ function logic.update_annunciator(self) self.plc_cache.ok = false end + --------------- + -- MISC RTUs -- + --------------- + + self.db.annunciator.RadiationMonitor = 1 + self.db.annunciator.RadiationWarning = false + for i = 1, #self.envd do + local envd = self.envd[i] ---@type unit_session + self.db.annunciator.RadiationMonitor = util.trinary(envd.is_faulted(), 2, 3) + self.db.annunciator.RadiationWarning = envd.get_db().radiation_raw > RADIATION_ALERT_LEVEL + break + end + + self.db.annunciator.EmergencyCoolant = 1 + for i = 1, #self.redstone do + local db = self.redstone[i].get_db() ---@type redstone_session_db + local io = db.io[IO.U_EMER_COOL] ---@type rs_db_dig_io|nil + if io ~= nil then + self.db.annunciator.EmergencyCoolant = util.trinary(io.read(), 3, 2) + break + end + end + ------------- -- BOILERS -- ------------- @@ -133,6 +162,8 @@ function logic.update_annunciator(self) local session = self.boilers[i] ---@type unit_session local boiler = session.get_db() ---@type boilerv_session_db + self.db.annunciator.RCSFault = self.db.annunciator.RCSFault or (not boiler.formed) or session.is_faulted() + -- update ready state -- - must be formed -- - must have received build, state, and tanks at least once @@ -225,6 +256,8 @@ function logic.update_annunciator(self) local session = self.turbines[i] ---@type unit_session local turbine = session.get_db() ---@type turbinev_session_db + self.db.annunciator.RCSFault = self.db.annunciator.RCSFault or (not turbine.formed) or session.is_faulted() + -- update ready state -- - must be formed -- - must have received build, state, and tanks at least once diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 3e92d15..40f4659 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.11.7" +local SUPERVISOR_VERSION = "beta-v0.11.8" local print = util.print local println = util.println From 5e65ca636ebd3c257d9c6e93dd2dd106c48eebce Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 15 Feb 2023 19:59:58 -0500 Subject: [PATCH 512/587] #164 reporting comms version mismatches --- coordinator/coordinator.lua | 41 +++++++++++++++++++++++++++++++------ coordinator/startup.lua | 4 ++-- reactor-plc/plc.lua | 31 ++++++++++++++++++++-------- reactor-plc/startup.lua | 2 +- rtu/rtu.lua | 18 +++++++++++++--- rtu/startup.lua | 2 +- scada-common/comms.lua | 5 +++-- supervisor/startup.lua | 2 +- supervisor/supervisor.lua | 8 ++++---- 9 files changed, 84 insertions(+), 29 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 26410e6..ff8bcee 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -204,7 +204,8 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range sv_seq_num = 0, sv_r_seq_num = nil, modem = modem, - connected = false + connected = false, + last_est_ack = ESTABLISH_ACK.ALLOW } ---@class coord_comms @@ -312,6 +313,16 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range if terminated then coordinator.log_comms("supervisor connection attempt cancelled by user") + elseif not self.sv_linked then + if self.last_est_ack == ESTABLISH_ACK.DENY then + coordinator.log_comms("supervisor connection attempt denied") + elseif self.last_est_ack == ESTABLISH_ACK.COLLISION then + coordinator.log_comms("supervisor connection failed due to collision") + elseif self.last_est_ack == ESTABLISH_ACK.BAD_VERSION then + coordinator.log_comms("supervisor connection failed due to version mismatch") + else + coordinator.log_comms("supervisor connection failed with no valid response") + end end return self.sv_linked @@ -538,12 +549,30 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range log.error("invalid supervisor configuration table received, establish failed") end else - log.debug("supervisor connection denied") + log.debug("SCADA_MGMT establish packet reply (len = 2) unsupported") end - elseif packet.length == 1 and packet.data[1] == ESTABLISH_ACK.DENY then - log.debug("supervisor connection denied") - elseif packet.length == 1 and packet.data[1] == ESTABLISH_ACK.COLLISION then - log.debug("supervisor connection denied due to collision") + + self.last_est_ack = est_ack + elseif packet.length == 1 then + local est_ack = packet.data[1] + + if est_ack == ESTABLISH_ACK.DENY then + if self.last_est_ack ~= est_ack then + log.debug("supervisor connection denied") + end + elseif est_ack == ESTABLISH_ACK.COLLISION then + if self.last_est_ack ~= est_ack then + log.debug("supervisor connection denied due to collision") + end + elseif est_ack == ESTABLISH_ACK.BAD_VERSION then + if self.last_est_ack ~= est_ack then + log.info("supervisor comms version mismatch") + end + else + log.debug("SCADA_MGMT establish packet reply (len = 1) unsupported") + end + + self.last_est_ack = est_ack else log.debug("SCADA_MGMT establish packet length mismatch") end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index c146477..51d8b6a 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.13" +local COORDINATOR_VERSION = "beta-v0.9.14" local print = util.print local println = util.println @@ -167,7 +167,7 @@ local function main() -- 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_sys("supervisor connection failed, shutting down...") log.fatal("failed to connect to supervisor") return false end diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 4eecdf4..5e7297c 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -421,6 +421,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, reactor = reactor, scrammed = false, linked = false, + last_est_ack = ESTABLISH_ACK.ALLOW, resend_build = false, auto_ack_token = 0, status_cache = nil, @@ -917,12 +918,18 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, elseif est_ack == ESTABLISH_ACK.COLLISION then println_ts("received unsolicited link collision, unlinking") log.warning("unsolicited establish request collision") + elseif est_ack == ESTABLISH_ACK.BAD_VERSION then + println_ts("received unsolicited link version mismatch, unlinking") + log.warning("unsolicited establish request version mismatch") else println_ts("invalid unsolicited link response") log.error("unsolicited unknown establish request response") end self.linked = est_ack == ESTABLISH_ACK.ALLOW + + -- clear this since this is for something that was unsolicited + self.last_est_ack = ESTABLISH_ACK.ALLOW else log.debug("SCADA_MGMT establish packet length mismatch") end @@ -968,18 +975,24 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, public.send_status(plc_state.no_reactor, plc_state.reactor_formed) log.debug("sent initial status data") - elseif est_ack == ESTABLISH_ACK.DENY then - println_ts("link request denied, retrying...") - log.debug("establish request denied") - elseif est_ack == ESTABLISH_ACK.COLLISION then - println_ts("reactor PLC ID collision (check config), retrying...") - log.warning("establish request collision") - else - println_ts("invalid link response, bad channel? retrying...") - log.error("unknown establish request response") + elseif self.last_est_ack ~= est_ack then + if est_ack == ESTABLISH_ACK.DENY then + println_ts("link request denied, retrying...") + log.debug("establish request denied") + elseif est_ack == ESTABLISH_ACK.COLLISION then + println_ts("reactor PLC ID collision (check config), retrying...") + log.warning("establish request collision") + elseif est_ack == ESTABLISH_ACK.BAD_VERSION then + println_ts("supervisor version mismatch (try updating), retrying...") + log.warning("establish request version mismatch") + else + println_ts("invalid link response, bad channel? retrying...") + log.error("unknown establish request response") + end end self.linked = est_ack == ESTABLISH_ACK.ALLOW + self.last_est_ack = est_ack else log.debug("SCADA_MGMT establish packet length mismatch") end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 584afbe..52b7229 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.10.10" +local R_PLC_VERSION = "beta-v0.10.11" local print = util.print local println = util.println diff --git a/rtu/rtu.lua b/rtu/rtu.lua index eb75d1a..ff81659 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -175,7 +175,8 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog modem = modem, s_port = server_port, l_port = local_port, - conn_watchdog = conn_watchdog + conn_watchdog = conn_watchdog, + last_est_ack = ESTABLISH_ACK.ALLOW } ---@class rtu_comms @@ -414,10 +415,21 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog log.info("supervisor connection established") else -- establish denied + if est_ack ~= self.last_est_ack then + if est_ack == ESTABLISH_ACK.BAD_VERSION then + -- version mismatch + println_ts("supervisor comms version mismatch (try updating), retrying...") + log.warning("supervisor connection denied due to comms version mismatch") + else + println_ts("supervisor connection denied, retrying...") + log.warning("supervisor connection denied") + end + end + public.unlink(rtu_state) - println_ts("supervisor connection denied") - log.warning("supervisor connection denied by remote host") end + + self.last_est_ack = est_ack else log.debug("SCADA_MGMT establish packet length mismatch") end diff --git a/rtu/startup.lua b/rtu/startup.lua index 7bfa330..375e93f 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.10.5" +local RTU_VERSION = "beta-v0.10.6" local rtu_t = types.rtu_t diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 6b1918a..ad7d460 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -14,7 +14,7 @@ local insert = table.insert local max_distance = nil -comms.version = "1.3.2" +comms.version = "1.3.3" ---@alias PROTOCOLS integer local PROTOCOLS = { @@ -68,7 +68,8 @@ local CAPI_TYPES = { local ESTABLISH_ACK = { ALLOW = 0, -- link approved DENY = 1, -- link denied - COLLISION = 2 -- link denied due to existing active link + COLLISION = 2, -- link denied due to existing active link + BAD_VERSION = 3 -- link denied due to comms version mismatch } ---@alias DEVICE_TYPES integer diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 40f4659..1c4b0d0 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.11.8" +local SUPERVISOR_VERSION = "beta-v0.11.9" local print = util.print local println = util.println diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 12f5291..5cadad4 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -170,8 +170,8 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen session.in_queue.push_packet(packet) else -- unknown session, force a re-link - log.debug("PLC_EST: no session but not an establish, force relink") - _send_dev_establish((packet.scada_frame.seq_num() + 1), r_port, { ESTABLISH_ACK.DENY }) + log.debug("PLC_ESTABLISH: no session but not an establish, forcing relink") + _send_dev_establish(packet.scada_frame.seq_num() + 1, r_port, { ESTABLISH_ACK.DENY }) end elseif protocol == PROTOCOLS.SCADA_MGMT then -- look for an associated session @@ -194,7 +194,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen if comms_v ~= comms.version then log.debug(util.c("dropping establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) - _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) + _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.BAD_VERSION }) return end @@ -269,7 +269,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen if comms_v ~= comms.version then log.debug(util.c("dropping establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) - _send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) + _send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.BAD_VERSION }) return elseif dev_type ~= DEVICE_TYPES.CRDN then log.debug(util.c("illegal establish packet for device ", dev_type, " on CRDN listening channel")) From c18e7ef4d0f0a65c6f0a314807888632bd94ad70 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 16 Feb 2023 20:48:40 -0500 Subject: [PATCH 513/587] display turbine generation as rate instead of charge --- coordinator/startup.lua | 2 +- coordinator/ui/components/turbine.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 51d8b6a..639a47b 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.14" +local COORDINATOR_VERSION = "beta-v0.9.15" local print = util.print local println = util.println diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua index db6959c..ef09cee 100644 --- a/coordinator/ui/components/turbine.lua +++ b/coordinator/ui/components/turbine.lua @@ -26,7 +26,7 @@ local function new_view(root, x, y, ps) local lu_col = cpair(colors.gray, colors.gray) local status = StateIndicator{parent=turbine,x=7,y=1,states=style.turbine.states,value=1,min_width=12} - local prod_rate = PowerIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",format="%10.2f",value=0,width=16,fg_bg=text_fg_bg} + local prod_rate = PowerIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",format="%10.2f",value=0,rate=true,width=16,fg_bg=text_fg_bg} local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=0,commas=true,width=16,fg_bg=text_fg_bg} ps.subscribe("computed_status", status.update) From 9f95801bfc445f4a6811e16b5bdad0a06383ca4a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 18 Feb 2023 17:36:44 -0500 Subject: [PATCH 514/587] moved supervisor unit/facility files out of sessions folder --- supervisor/{session => }/facility.lua | 2 ++ supervisor/session/svsessions.lua | 2 +- supervisor/startup.lua | 2 +- supervisor/{session => }/unit.lua | 3 ++- supervisor/{session => }/unitlogic.lua | 0 5 files changed, 6 insertions(+), 3 deletions(-) rename supervisor/{session => }/facility.lua (99%) rename supervisor/{session => }/unit.lua (99%) rename supervisor/{session => }/unitlogic.lua (100%) diff --git a/supervisor/session/facility.lua b/supervisor/facility.lua similarity index 99% rename from supervisor/session/facility.lua rename to supervisor/facility.lua index b621e67..cff8780 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/facility.lua @@ -3,6 +3,8 @@ local rsio = require("scada-common.rsio") local types = require("scada-common.types") local util = require("scada-common.util") +local unit = require("supervisor.unit") + local rsctl = require("supervisor.session.rsctl") local unit = require("supervisor.session.unit") diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 91457c7..b70096e 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -3,8 +3,8 @@ local mqueue = require("scada-common.mqueue") local util = require("scada-common.util") local config = require("supervisor.config") +local facility = require("supervisor.facility") -local facility = require("supervisor.session.facility") local svqtypes = require("supervisor.session.svqtypes") local coordinator = require("supervisor.session.coordinator") diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 1c4b0d0..191ae28 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.11.9" +local SUPERVISOR_VERSION = "beta-v0.11.10" local print = util.print local println = util.println diff --git a/supervisor/session/unit.lua b/supervisor/unit.lua similarity index 99% rename from supervisor/session/unit.lua rename to supervisor/unit.lua index 98cdd5b..a18282d 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/unit.lua @@ -3,7 +3,8 @@ local rsio = require("scada-common.rsio") local types = require("scada-common.types") local util = require("scada-common.util") -local logic = require("supervisor.session.unitlogic") +local logic = require("supervisor.unitlogic") + local plc = require("supervisor.session.plc") local rsctl = require("supervisor.session.rsctl") diff --git a/supervisor/session/unitlogic.lua b/supervisor/unitlogic.lua similarity index 100% rename from supervisor/session/unitlogic.lua rename to supervisor/unitlogic.lua From caa6cc81b1872dd7cacd67449bd0ad79eaa9cabd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 18 Feb 2023 17:37:28 -0500 Subject: [PATCH 515/587] #163 changed formed/faulted display priority on coordinator for RTUs --- coordinator/iocontrol.lua | 16 ++++++++-------- coordinator/startup.lua | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index b79166f..30bb6e5 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -548,16 +548,16 @@ function iocontrol.update_unit_statuses(statuses) unit.boiler_ps_tbl[id].publish("formed", data.formed) unit.boiler_ps_tbl[id].publish("faulted", rtu_faulted) - if data.formed then - if rtu_faulted then - unit.boiler_ps_tbl[id].publish("computed_status", 3) -- faulted - elseif data.state.boil_rate > 0 then + if rtu_faulted then + unit.boiler_ps_tbl[id].publish("computed_status", 3) -- faulted + elseif data.formed then + if data.state.boil_rate > 0 then unit.boiler_ps_tbl[id].publish("computed_status", 5) -- active else unit.boiler_ps_tbl[id].publish("computed_status", 4) -- idle end else - unit.boiler_ps_tbl[id].publish("computed_status", 2) -- not formed + unit.boiler_ps_tbl[id].publish("computed_status", 2) -- not formed end for key, val in pairs(unit.boiler_data_tbl[id].state) do @@ -596,11 +596,11 @@ function iocontrol.update_unit_statuses(statuses) unit.turbine_ps_tbl[id].publish("formed", data.formed) unit.turbine_ps_tbl[id].publish("faulted", rtu_faulted) - if data.formed then + if rtu_faulted then + unit.turbine_ps_tbl[id].publish("computed_status", 3) -- faulted + elseif data.formed then if data.tanks.energy_fill >= 0.99 then unit.turbine_ps_tbl[id].publish("computed_status", 6) -- trip - elseif rtu_faulted then - unit.turbine_ps_tbl[id].publish("computed_status", 3) -- faulted elseif data.state.flow_rate < 100 then unit.turbine_ps_tbl[id].publish("computed_status", 4) -- idle else diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 639a47b..1623615 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.15" +local COORDINATOR_VERSION = "beta-v0.9.16" local print = util.print local println = util.println From cc5ea0dbb0b951ccaccd6f61e6fdd929d47f6192 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 00:14:27 -0500 Subject: [PATCH 516/587] #159 linked up redstone I/O --- rtu/startup.lua | 2 +- scada-common/rsio.lua | 101 +++++++++++++++++++++++---------------- supervisor/facility.lua | 44 +++++++++++++++-- supervisor/startup.lua | 2 +- supervisor/unit.lua | 44 +++++++++++------ supervisor/unitlogic.lua | 71 +++++++++++++++++++++++++++ 6 files changed, 201 insertions(+), 63 deletions(-) diff --git a/rtu/startup.lua b/rtu/startup.lua index 375e93f..bbe5ee8 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.10.6" +local RTU_VERSION = "beta-v0.11.0" local rtu_t = types.rtu_t diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 7736a90..ed17a3f 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -11,7 +11,7 @@ local rsio = {} -- RS I/O CONSTANTS -- ---------------------- ----@alias IO_LVL integer +---@enum IO_LVL I/O logic level local IO_LVL = { DISCONNECT = -1, -- use for RTU session to indicate this RTU is not connected to this port LOW = 0, @@ -19,13 +19,13 @@ local IO_LVL = { FLOATING = 2 -- use for RTU session to indicate this RTU is connected but not yet read } ----@alias IO_DIR integer +---@enum IO_DIR I/O direction local IO_DIR = { IN = 0, OUT = 1 } ----@alias IO_MODE integer +---@enum IO_MODE I/O mode (digital/analog input/output) local IO_MODE = { DIGITAL_IN = 0, DIGITAL_OUT = 1, @@ -33,45 +33,50 @@ local IO_MODE = { ANALOG_OUT = 3 } ----@alias IO_PORT integer +---@enum IO_PORT redstone I/O logic port local IO_PORT = { -- digital inputs -- -- facility F_SCRAM = 1, -- active low, facility-wide scram + F_ACK = 2, -- active high, facility alarm acknowledge -- reactor - R_SCRAM = 2, -- active low, reactor scram - R_ENABLE = 3, -- active high, reactor enable + R_SCRAM = 3, -- active low, reactor scram + R_RESET = 4, -- active high, reactor RPS reset + R_ENABLE = 5, -- active high, reactor enable + + -- unit + U_ACK = 6, -- active high, unit alarm acknowledge -- digital outputs -- -- facility - F_ALARM = 4, -- active high, facility safety alarm + F_ALARM = 7, -- active high, facility alarm (any high priority unit alarm) -- waste - WASTE_PU = 5, -- active low, waste -> plutonium -> pellets route - WASTE_PO = 6, -- active low, waste -> polonium route - WASTE_POPL = 7, -- active low, polonium -> pellets route - WASTE_AM = 8, -- active low, polonium -> anti-matter route + WASTE_PU = 8, -- active low, waste -> plutonium -> pellets route + WASTE_PO = 9, -- active low, waste -> polonium route + WASTE_POPL = 10, -- active low, polonium -> pellets route + WASTE_AM = 11, -- active low, polonium -> anti-matter route -- reactor - R_ALARM = 9, -- active high, reactor safety alarm - R_SCRAMMED = 10, -- active high, if the reactor is scrammed - R_AUTO_SCRAM = 11, -- active high, if the reactor was automatically scrammed R_ACTIVE = 12, -- active high, if the reactor is active R_AUTO_CTRL = 13, -- active high, if the reactor burn rate is automatic - R_DMG_CRIT = 14, -- active high, if the reactor damage is critical - R_HIGH_TEMP = 15, -- active high, if the reactor is at a high temperature - R_NO_COOLANT = 16, -- active high, if the reactor has no coolant - R_EXCESS_HC = 17, -- active high, if the reactor has excess heated coolant - R_EXCESS_WS = 18, -- active high, if the reactor has excess waste - R_INSUFF_FUEL = 19, -- active high, if the reactor has insufficent fuel - R_PLC_FAULT = 20, -- active high, if the reactor PLC reports a device access fault - R_PLC_TIMEOUT = 21, -- active high, if the reactor PLC has not been heard from + R_SCRAMMED = 14, -- active high, if the reactor is scrammed + R_AUTO_SCRAM = 15, -- active high, if the reactor was automatically scrammed + R_DMG_CRIT = 16, -- active high, if the reactor damage is critical + R_HIGH_TEMP = 17, -- active high, if the reactor is at a high temperature + R_NO_COOLANT = 18, -- active high, if the reactor has no coolant + R_EXCESS_HC = 19, -- active high, if the reactor has excess heated coolant + R_EXCESS_WS = 20, -- active high, if the reactor has excess waste + R_INSUFF_FUEL = 21, -- active high, if the reactor has insufficent fuel + R_PLC_FAULT = 22, -- active high, if the reactor PLC reports a device access fault + R_PLC_TIMEOUT = 23, -- active high, if the reactor PLC has not been heard from -- unit outputs - U_EMER_COOL = 22 -- active low, emergency coolant control + U_ALARM = 24, -- active high, unit alarm + U_EMER_COOL = 25 -- active low, emergency coolant control } rsio.IO_LVL = IO_LVL @@ -88,18 +93,20 @@ rsio.IO = IO_PORT function rsio.to_string(port) local names = { "F_SCRAM", + "F_ACK", "R_SCRAM", + "R_RESET", "R_ENABLE", + "U_ACK", "F_ALARM", "WASTE_PU", "WASTE_PO", "WASTE_POPL", "WASTE_AM", - "R_ALARM", - "R_SCRAMMED", - "R_AUTO_SCRAM", "R_ACTIVE", "R_AUTO_CTRL", + "R_SCRAMMED", + "R_AUTO_SCRAM", "R_DMG_CRIT", "R_HIGH_TEMP", "R_NO_COOLANT", @@ -108,6 +115,7 @@ function rsio.to_string(port) "R_INSUFF_FUEL", "R_PLC_FAULT", "R_PLC_TIMEOUT", + "U_ALARM", "U_EMER_COOL" } @@ -129,12 +137,22 @@ local function _O_ACTIVE_LOW(active) if active then return IO_LVL.LOW else retur local RS_DIO_MAP = { -- F_SCRAM { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN }, + -- F_ACK + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN }, + -- R_SCRAM { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN }, + -- R_RESET + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN }, -- R_ENABLE { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN }, + + -- U_ACK + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN }, + -- F_ALARM { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + -- WASTE_PU { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, -- WASTE_PO @@ -143,16 +161,15 @@ local RS_DIO_MAP = { { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, -- WASTE_AM { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, - -- R_ALARM + + -- R_ACTIVE + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + -- R_AUTO_CTRL { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_SCRAMMED { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_AUTO_SCRAM { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- R_ACTIVE - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- R_AUTO_CTRL - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_DMG_CRIT { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_HIGH_TEMP @@ -169,6 +186,9 @@ local RS_DIO_MAP = { { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_PLC_TIMEOUT { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + + -- U_ALARM + { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- U_EMER_COOL { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT } } @@ -179,18 +199,20 @@ local RS_DIO_MAP = { function rsio.get_io_mode(port) local modes = { IO_MODE.DIGITAL_IN, -- F_SCRAM + IO_MODE.DIGITAL_IN, -- F_ACK IO_MODE.DIGITAL_IN, -- R_SCRAM + IO_MODE.DIGITAL_IN, -- R_RESET IO_MODE.DIGITAL_IN, -- R_ENABLE + IO_MODE.DIGITAL_IN, -- U_ACK IO_MODE.DIGITAL_OUT, -- F_ALARM IO_MODE.DIGITAL_OUT, -- WASTE_PU IO_MODE.DIGITAL_OUT, -- WASTE_PO IO_MODE.DIGITAL_OUT, -- WASTE_POPL IO_MODE.DIGITAL_OUT, -- WASTE_AM - IO_MODE.DIGITAL_OUT, -- R_ALARM - IO_MODE.DIGITAL_OUT, -- R_SCRAMMED - IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM IO_MODE.DIGITAL_OUT, -- R_ACTIVE IO_MODE.DIGITAL_OUT, -- R_AUTO_CTRL + IO_MODE.DIGITAL_OUT, -- R_SCRAMMED + IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM IO_MODE.DIGITAL_OUT, -- R_DMG_CRIT IO_MODE.DIGITAL_OUT, -- R_HIGH_TEMP IO_MODE.DIGITAL_OUT, -- R_NO_COOLANT @@ -199,6 +221,7 @@ function rsio.get_io_mode(port) IO_MODE.DIGITAL_OUT, -- R_INSUFF_FUEL IO_MODE.DIGITAL_OUT, -- R_PLC_FAULT IO_MODE.DIGITAL_OUT, -- R_PLC_TIMEOUT + IO_MODE.DIGITAL_OUT, -- U_ALARM IO_MODE.DIGITAL_OUT -- U_EMER_COOL } @@ -249,11 +272,7 @@ end ---@param rs_value boolean ---@return IO_LVL function rsio.digital_read(rs_value) - if rs_value then - return IO_LVL.HIGH - else - return IO_LVL.LOW - end + if rs_value then return IO_LVL.HIGH else return IO_LVL.LOW end end -- get redstone boolean output value corresponding to a digital I/O level @@ -280,7 +299,7 @@ end ---@param level IO_LVL ---@return boolean|nil function rsio.digital_is_active(port, level) - if (not util.is_int(port)) or (port > IO_PORT.R_ENABLE) then + if (not util.is_int(port)) or (port > IO_PORT.U_ACK) then return nil elseif level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then return nil @@ -310,7 +329,7 @@ end ---@return number rs_value scaled redstone reading (0 to 15) function rsio.analog_write(value, min, max) local scaled_value = (value - min) / (max - min) - return scaled_value * 15 + return math.floor(scaled_value * 15) end return rsio diff --git a/supervisor/facility.lua b/supervisor/facility.lua index cff8780..1b2634c 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -6,11 +6,12 @@ local util = require("scada-common.util") local unit = require("supervisor.unit") local rsctl = require("supervisor.session.rsctl") -local unit = require("supervisor.session.unit") local PROCESS = types.PROCESS local PROCESS_NAMES = types.PROCESS_NAMES +local IO = rsio.IO + -- 7.14 kJ per blade for 1 mB of fissile fuel
-- 2856 FE per blade per 1 mB, 285.6 FE per blade per 0.1 mB (minimum) local POWER_PER_BLADE = util.joules_to_fe(7140) @@ -57,12 +58,15 @@ local facility = {} function facility.new(num_reactors, cooling_conf) local self = { units = {}, + status_text = { "START UP", "initializing..." }, + all_sys_ok = false, + -- rtus + rtu_conn_count = 0, redstone = {}, induction = {}, envd = {}, - status_text = { "START UP", "initializing..." }, - all_sys_ok = false, - rtu_conn_count = 0, + -- redstone I/O control + io_ctl = nil, ---@type rs_controller -- process control units_ready = false, mode = PROCESS.INACTIVE, @@ -111,7 +115,7 @@ function facility.new(num_reactors, cooling_conf) end -- init redstone RTU I/O controller - local rs_rtu_io_ctl = rsctl.new(self.redstone) + self.io_ctl = rsctl.new(self.redstone) -- unlink disconnected units ---@param sessions table @@ -655,6 +659,36 @@ function facility.new(num_reactors, cooling_conf) -- update last mode and set next mode self.last_mode = self.mode self.mode = next_mode + + ------------------------- + -- Handle Redstone I/O -- + ------------------------- + + if #self.redstone > 0 then + -- handle facility SCRAM + if self.io_ctl.digital_read(IO.F_SCRAM) then + for i = 1, #self.units do + local u = self.units[i] ---@type reactor_unit + u.cond_scram() + end + end + + -- handle facility ack + if self.io_ctl.digital_read(IO.F_ACK) then public.ack_all() end + + -- update facility alarm output + local has_alarm = false + for i = 1, #self.units do + local u = self.units[i] ---@type reactor_unit + + if u.has_critical_alarm() then + has_alarm = true + return + end + end + + self.io_ctl.digital_write(IO.F_ALARM, has_alarm) + end end -- call the update function of all units in the facility diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 191ae28..4289b17 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.11.10" +local SUPERVISOR_VERSION = "beta-v0.11.11" local print = util.print local println = util.println diff --git a/supervisor/unit.lua b/supervisor/unit.lua index a18282d..1eff099 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -74,10 +74,14 @@ function unit.new(for_reactor, num_boilers, num_turbines) num_turbines = num_turbines, types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE }, defs = { FLOW_STABILITY_DELAY_MS = FLOW_STABILITY_DELAY_MS }, + -- rtus redstone = {}, boilers = {}, turbines = {}, envd = {}, + -- redstone control + io_ctl = nil, ---@type rs_controller + valves = {}, ---@type unit_valves -- auto control ramp_target_br100 = 0, -- state tracking @@ -151,7 +155,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- waste >85% ReactorHighWaste = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 2, id = ALARM.ReactorHighWaste, tier = PRIO.TIMELY }, -- RPS trip occured - RPSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 1, id = ALARM.RPSTransient, tier = PRIO.URGENT }, + RPSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 1, id = ALARM.RPSTransient, tier = PRIO.TIMELY }, -- BoilRateMismatch, CoolantFeedMismatch, SteamFeedMismatch, MaxWaterReturnFeed RCSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 5, id = ALARM.RCSTransient, tier = PRIO.TIMELY }, -- "It's just a routine turbin' trip!" -Bill Gibson, "The China Syndrome" @@ -223,7 +227,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) } -- init redstone RTU I/O controller - local rs_rtu_io_ctl = rsctl.new(self.redstone) + self.io_ctl = rsctl.new(self.redstone) -- init boiler table fields for _ = 1, num_boilers do @@ -272,10 +276,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- get the delta t of a value ---@param key string value key - ---@return number - function self._get_dt(key) - if self.deltas[key] then return self.deltas[key].dt else return 0.0 end - end + ---@return number value value or 0 if not known + function self._get_dt(key) if self.deltas[key] then return self.deltas[key].dt else return 0.0 end end -- update all delta computations local function _dt__compute_all() @@ -320,8 +322,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) --#region redstone I/O - local __rs_w = rs_rtu_io_ctl.digital_write - local __rs_r = rs_rtu_io_ctl.digital_read + local __rs_w = self.io_ctl.digital_write + local __rs_r = self.io_ctl.digital_read -- valves local waste_pu = { open = function () __rs_w(IO.WASTE_PU, true) end, close = function () __rs_w(IO.WASTE_PU, false) end } @@ -330,6 +332,15 @@ function unit.new(for_reactor, num_boilers, num_turbines) local waste_sps = { open = function () __rs_w(IO.WASTE_AM, true) end, close = function () __rs_w(IO.WASTE_AM, false) end } local emer_cool = { open = function () __rs_w(IO.U_EMER_COOL, true) end, close = function () __rs_w(IO.U_EMER_COOL, false) end } + ---@class unit_valves + self.valves = { + waste_pu = waste_pu, + waste_sna = waste_sna, + waste_po = waste_po, + waste_sps = waste_sps, + emer_cool = emer_cool + } + --#endregion -- unlink disconnected units @@ -480,13 +491,9 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- update status text logic.update_status_text(self) - -- check if emergency coolant is needed - if self.plc_cache.rps_status.no_cool then - emer_cool.open() - elseif not self.plc_cache.rps_trip then - -- can't turn off on sufficient coolant level since it might drop again - -- turn off once system is OK again - emer_cool.close() + -- handle redstone I/O + if #self.redstone > 0 then + logic.handle_redstone(self) end end @@ -576,6 +583,13 @@ function unit.new(for_reactor, num_boilers, num_turbines) end end + -- queue a SCRAM command only if a manual SCRAM has not already occured + function public.cond_scram() + if self.plc_s ~= nil and not self.plc_cache.rps_status.manual then + self.plc_s.in_queue.push_command(PLC_S_CMDS.SCRAM) + end + end + -- acknowledge all alarms (if possible) function public.ack_all() for i = 1, #self.db.alarm_states do diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 647def8..6cece31 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -3,6 +3,8 @@ local rsio = require("scada-common.rsio") local types = require("scada-common.types") local util = require("scada-common.util") +local plc = require("supervisor.session.plc") + local ALARM_STATE = types.ALARM_STATE local TRI_FAIL = types.TRI_FAIL @@ -10,6 +12,8 @@ local DUMPING_MODE = types.DUMPING_MODE local IO = rsio.IO +local PLC_S_CMDS = plc.PLC_S_CMDS + local aistate_string = { "INACTIVE", "TRIPPING", @@ -620,4 +624,71 @@ function logic.update_status_text(self) end end +-- handle unit redstone I/O +---@param self _unit_self unit instance +function logic.handle_redstone(self) + -- reactor controls + if self.plc_s ~= nil then + if (not self.plc_cache.rps_status.manual) and self.io_ctl.digital_read(IO.R_SCRAM) then + -- reactor SCRAM requested but not yet done; perform it + self.plc_s.in_queue.push_command(PLC_S_CMDS.SCRAM) + end + + if self.plc_cache.rps_trip and self.io_ctl.digital_read(IO.R_RESET) then + -- reactor RPS reset requested but not yet done; perform it + self.plc_s.in_queue.push_command(PLC_S_CMDS.RPS_RESET) + end + + if (not self.db.annunciator.AutoControl) and (not self.plc_cache.active) and + (not self.plc_cache.rps_trip) and self.io_ctl.digital_read(IO.R_ACTIVE) then + -- reactor enable requested and allowable, but not yet done; perform it + self.plc_s.in_queue.push_command(PLC_S_CMDS.ENABLE) + end + end + + -- check for request to ack all alarms + if self.io_ctl.digital_read(IO.U_ACK) then + for i = 1, #self.db.alarm_states do + if self.db.alarm_states[i] == ALARM_STATE.TRIPPED then + self.db.alarm_states[i] = ALARM_STATE.ACKED + end + end + end + + -- write reactor status outputs + self.io_ctl.digital_write(IO.R_ACTIVE, self.plc_cache.active) + self.io_ctl.digital_write(IO.R_AUTO_CTRL, self.db.annunciator.AutoControl) + self.io_ctl.digital_write(IO.R_SCRAMMED, self.plc_cache.rps_trip) + self.io_ctl.digital_write(IO.R_AUTO_SCRAM, self.plc_cache.rps_status.automatic) + self.io_ctl.digital_write(IO.R_DMG_CRIT, self.plc_cache.rps_status.dmg_crit) + self.io_ctl.digital_write(IO.R_HIGH_TEMP, self.plc_cache.rps_status.high_temp) + self.io_ctl.digital_write(IO.R_NO_COOLANT, self.plc_cache.rps_status.no_cool) + self.io_ctl.digital_write(IO.R_EXCESS_HC, self.plc_cache.rps_status.ex_hcool) + self.io_ctl.digital_write(IO.R_EXCESS_WS, self.plc_cache.rps_status.ex_waste) + self.io_ctl.digital_write(IO.R_INSUFF_FUEL, self.plc_cache.rps_status.no_fuel) + self.io_ctl.digital_write(IO.R_PLC_FAULT, self.plc_cache.rps_status.fault) + self.io_ctl.digital_write(IO.R_PLC_TIMEOUT, self.plc_cache.rps_status.timeout) + + -- write unit outputs + + local has_alarm = false + for i = 1, #self.db.alarm_states do + if self.db.alarm_states[i] == ALARM_STATE.TRIPPED then + has_alarm = true + break + end + end + + self.io_ctl.digital_write(IO.U_ALARM, has_alarm) + + -- check if emergency coolant is needed + if self.plc_cache.rps_status.no_cool then + self.valves.emer_cool.open() + elseif not self.plc_cache.rps_trip then + -- can't turn off on sufficient coolant level since it might drop again + -- turn off once system is OK again + self.valves.emer_cool.close() + end +end + return logic From 35dfd61df19d31182dd7941dd18ac46768fae387 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 12:20:16 -0500 Subject: [PATCH 517/587] #169 startup rate high; also changed how clearing ASCRAM status and updating indicators works --- scada-common/types.lua | 15 +++++- supervisor/facility.lua | 109 +++++++++++++++++++++------------------ supervisor/startup.lua | 2 +- supervisor/unitlogic.lua | 18 ++++++- 4 files changed, 89 insertions(+), 55 deletions(-) diff --git a/scada-common/types.lua b/scada-common/types.lua index 41946eb..23a4006 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -8,7 +8,7 @@ local types = {} -- CLASSES -- ---@class tank_fluid ----@field name string +---@field name fluid ---@field amount integer -- create a new tank fluid @@ -222,6 +222,19 @@ types.ALARM_STATE = { ---| "sys_fail" ---| "force_disabled" +---@alias fluid +---| "mekanism:empty_gas" +---| "minecraft:water" +---| "mekanism:sodium" +---| "mekanism:superheated_sodium" + +types.fluid = { + empty_gas = "mekanism:empty_gas", + water = "minecraft:water", + sodium = "mekanism:sodium", + superheated_sodium = "mekanism:superheated_sodium" +} + ---@alias rtu_t string types.rtu_t = { redstone = "redstone", diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 1b2634c..2dd07d5 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -301,6 +301,7 @@ function facility.new(num_reactors, cooling_conf) if (self.mode ~= PROCESS.MATRIX_FAULT_IDLE) and (self.mode ~= PROCESS.SYSTEM_ALARM_IDLE) then -- auto clear ASCRAM self.ascram = false + self.ascram_reason = AUTO_SCRAM.NONE end local blade_count = nil @@ -372,12 +373,18 @@ function facility.new(num_reactors, cooling_conf) if self.mode == PROCESS.INACTIVE then if not self.units_ready then self.status_text = { "NOT READY", "assigned units not ready" } - elseif self.start_fail == START_STATUS.NO_UNITS and assign_count == 0 then - self.status_text = { "START FAILED", "no units were assigned" } - elseif self.start_fail == START_STATUS.BLADE_MISMATCH then - self.status_text = { "START FAILED", "turbine blade count mismatch" } else - self.status_text = { "IDLE", "control disengaged" } + -- clear ASCRAM once ready + self.ascram = false + self.ascram_reason = AUTO_SCRAM.NONE + + if self.start_fail == START_STATUS.NO_UNITS and assign_count == 0 then + self.status_text = { "START FAILED", "no units were assigned" } + elseif self.start_fail == START_STATUS.BLADE_MISMATCH then + self.status_text = { "START FAILED", "turbine blade count mismatch" } + else + self.status_text = { "IDLE", "control disengaged" } + end end elseif self.mode == PROCESS.MAX_BURN then -- run units at their limits @@ -522,7 +529,7 @@ function facility.new(num_reactors, cooling_conf) next_mode = self.return_mode log.info("FAC: exiting matrix fault idle state due to fault resolution") elseif self.ascram_reason == AUTO_SCRAM.CRIT_ALARM then - next_mode = PROCESS.INACTIVE + next_mode = PROCESS.SYSTEM_ALARM_IDLE log.info("FAC: exiting matrix fault idle state due to critical unit alarm") end elseif self.mode == PROCESS.SYSTEM_ALARM_IDLE then @@ -545,55 +552,54 @@ function facility.new(num_reactors, cooling_conf) local astatus = self.ascram_status - if (self.mode ~= PROCESS.INACTIVE) and (self.mode ~= PROCESS.SYSTEM_ALARM_IDLE) then - if self.induction[1] ~= nil then - local matrix = self.induction[1] ---@type unit_session - local db = matrix.get_db() ---@type imatrix_session_db + if self.induction[1] ~= nil then + local matrix = self.induction[1] ---@type unit_session + local db = matrix.get_db() ---@type imatrix_session_db - -- clear matrix disconnected - if astatus.matrix_dc then - astatus.matrix_dc = false - log.info("FAC: induction matrix reconnected, clearing ASCRAM condition") - end - - -- check matrix fill too high - local was_fill = astatus.matrix_fill - astatus.matrix_fill = (db.tanks.energy_fill >= HIGH_CHARGE) or (astatus.matrix_fill and db.tanks.energy_fill > RE_ENABLE_CHARGE) - - if was_fill and not astatus.matrix_fill then - log.info("FAC: charge state of induction matrix entered acceptable range <= " .. (RE_ENABLE_CHARGE * 100) .. "%") - end - - -- check for critical unit alarms - for i = 1, #self.units do - local u = self.units[i] ---@type reactor_unit - - if u.has_critical_alarm() then - log.info(util.c("FAC: emergency exit of process control due to critical unit alarm (unit ", u.get_id(), ")")) - break - end - end - - -- check for facility radiation - if self.envd[1] ~= nil then - local envd = self.envd[1] ---@type unit_session - local e_db = envd.get_db() ---@type envd_session_db - - astatus.radiation = e_db.radiation_raw > RADIATION_ALARM_LEVEL - else - -- don't clear, if it is true then we lost it with high radiation, so just keep alarming - -- operator can restart the system or hit the stop/reset button - end - - -- system not ready, will need to restart GEN_RATE mode - -- clears when we enter the fault waiting state - astatus.gen_fault = self.mode == PROCESS.GEN_RATE and not self.units_ready - else - astatus.matrix_dc = true + -- clear matrix disconnected + if astatus.matrix_dc then + astatus.matrix_dc = false + log.info("FAC: induction matrix reconnected, clearing ASCRAM condition") end - -- log.debug(util.c("dc: ", astatus.matrix_dc, " fill: ", astatus.matrix_fill, " crit: ", astatus.crit_alarm, " gen: ", astatus.gen_fault)) + -- check matrix fill too high + local was_fill = astatus.matrix_fill + astatus.matrix_fill = (db.tanks.energy_fill >= HIGH_CHARGE) or (astatus.matrix_fill and db.tanks.energy_fill > RE_ENABLE_CHARGE) + if was_fill and not astatus.matrix_fill then + log.info("FAC: charge state of induction matrix entered acceptable range <= " .. (RE_ENABLE_CHARGE * 100) .. "%") + end + + -- check for critical unit alarms + astatus.crit_alarm = false + for i = 1, #self.units do + local u = self.units[i] ---@type reactor_unit + + if u.has_critical_alarm() then + astatus.crit_alarm = true + break + end + end + + -- check for facility radiation + if self.envd[1] ~= nil then + local envd = self.envd[1] ---@type unit_session + local e_db = envd.get_db() ---@type envd_session_db + + astatus.radiation = e_db.radiation_raw > RADIATION_ALARM_LEVEL + else + -- don't clear, if it is true then we lost it with high radiation, so just keep alarming + -- operator can restart the system or hit the stop/reset button + end + + -- system not ready, will need to restart GEN_RATE mode + -- clears when we enter the fault waiting state + astatus.gen_fault = self.mode == PROCESS.GEN_RATE and not self.units_ready + else + astatus.matrix_dc = true + end + + if (self.mode ~= PROCESS.INACTIVE) and (self.mode ~= PROCESS.SYSTEM_ALARM_IDLE) then local scram = astatus.matrix_dc or astatus.matrix_fill or astatus.crit_alarm or astatus.gen_fault if scram and not self.ascram then @@ -611,6 +617,7 @@ function facility.new(num_reactors, cooling_conf) self.status_text = { "AUTOMATIC SCRAM", "critical unit alarm tripped" } log.info("FAC: automatic SCRAM due to critical unit alarm") + log.warning("FAC: emergency exit of process control due to critical unit alarm") elseif astatus.radiation then next_mode = PROCESS.SYSTEM_ALARM_IDLE self.ascram_reason = AUTO_SCRAM.RADIATION diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 4289b17..8097f83 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.11.11" +local SUPERVISOR_VERSION = "beta-v0.11.12" local print = util.print local println = util.println diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 6cece31..ec27d14 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -109,12 +109,26 @@ function logic.update_annunciator(self) self.db.annunciator.AutoReactorSCRAM = plc_db.rps_trip_cause == types.rps_status_t.automatic self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.no_cool) self.db.annunciator.RCSFlowLow = plc_db.mek_status.ccool_fill < 0.75 or plc_db.mek_status.hcool_fill > 0.25 + self.db.annunciator.CoolantLevelLow = plc_db.mek_status.ccool_fill < 0.5 self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > 1000 self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > 100 self.db.annunciator.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < -1.0 or plc_db.mek_status.fuel_fill <= 0.01 self.db.annunciator.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 1.0 or plc_db.mek_status.waste_fill >= 0.85 - ---@todo this is dependent on setup, i.e. how much coolant is buffered and the turbine setup - self.db.annunciator.HighStartupRate = not plc_db.mek_status.status and plc_db.mek_status.burn_rate > 40 + + -- this warning applies when no coolant is buffered (which we can't easily determine without running) + --[[ + logic is that each tick, the heating rate worth of coolant steps between: + reactor tank + reactor heated coolant outflow tube + boiler/turbine tank + reactor cooled coolant return tube + so if there is a tick where coolant is no longer present in the reactor, then bad things happen. + such as when a burn rate consumes half the coolant in the tank, meaning that: + 50% at some point will be in the boiler, and 50% in a tube, so that leaves 0% in the reactor + ]]-- + local heating_rate_conv = util.trinary(plc_db.mek_status.ccool_type == types.fluid.sodium, 200000, 20000) + local high_rate = (plc_db.mek_status.ccool_amnt / (plc_db.mek_status.burn_rate * heating_rate_conv)) < 4 + self.db.annunciator.HighStartupRate = not plc_db.mek_status.status and high_rate -- if no boilers, use reactor heating rate to check for boil rate mismatch if num_boilers == 0 then From 052b2f38483f55d1d4fbc6491e640a8895824232 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 12:37:07 -0500 Subject: [PATCH 518/587] improved RCS flow low detection --- supervisor/startup.lua | 2 +- supervisor/unitlogic.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 8097f83..b380410 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.11.12" +local SUPERVISOR_VERSION = "beta-v0.11.13" local print = util.print local println = util.println diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index ec27d14..0709428 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -108,7 +108,7 @@ function logic.update_annunciator(self) self.db.annunciator.ManualReactorSCRAM = plc_db.rps_trip_cause == types.rps_status_t.manual self.db.annunciator.AutoReactorSCRAM = plc_db.rps_trip_cause == types.rps_status_t.automatic self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.no_cool) - self.db.annunciator.RCSFlowLow = plc_db.mek_status.ccool_fill < 0.75 or plc_db.mek_status.hcool_fill > 0.25 + self.db.annunciator.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < -2.0 self.db.annunciator.CoolantLevelLow = plc_db.mek_status.ccool_fill < 0.5 self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > 1000 self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > 100 From 72eb2432cc507208c88a44bd5aacc3444e22e7e3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 12:54:02 -0500 Subject: [PATCH 519/587] #117 installation files, first pass --- .vscode/settings.json | 3 +- imgen.py | 95 ++++++++++++ install_manifest.json | 189 ++++++++++++++++++++++++ installer.lua | 326 ++++++++++++++++++++++++++++++++++++++++++ pocket/startup.lua | 11 ++ 5 files changed, 623 insertions(+), 1 deletion(-) create mode 100644 imgen.py create mode 100644 install_manifest.json create mode 100644 installer.lua diff --git a/.vscode/settings.json b/.vscode/settings.json index 6f2ebaa..70230fe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,7 +14,8 @@ "read", "periphemu", "mekanismEnergyHelper", - "_HOST" + "_HOST", + "http" ], "Lua.diagnostics.disable": [ "duplicate-set-field" diff --git a/imgen.py b/imgen.py new file mode 100644 index 0000000..56193ca --- /dev/null +++ b/imgen.py @@ -0,0 +1,95 @@ +import json +import os + +# list files in a directory +def list_files(path): + list = [] + + for (root, dirs, files) in os.walk(path): + for f in files: + list.append(root[2:] + "/" + f) + + return list + +# get size of all files in a directory +def dir_size(path): + total = 0 + + for (root, dirs, files) in os.walk(path): + for f in files: + total += os.path.getsize(root + "/" + f) + + return total + +# get the version of an application at the provided path +def get_version(path, is_comms = False): + ver = "" + string = "comms.version = \"" + + if not is_comms: + path = path + "/startup.lua" + string = "_VERSION = \"" + + f = open(path, "r") + + for line in f: + pos = line.find(string) + if pos >= 0: + ver = line[(pos + len(string)):(len(line) - 2)] + break + + f.close() + + return ver + +# installation manifest +manifest = { + "versions" : { + "bootloader" : get_version("."), + "comms" : get_version("./scada-common/comms.lua", True), + "reactor-plc" : get_version("./reactor-plc"), + "rtu" : get_version("./rtu"), + "supervisor" : get_version("./supervisor"), + "coordinator" : get_version("./coordinator"), + "pocket" : get_version("./pocket") + }, + "files" : { + # common files + "system" : [ "initenv.lua", "startup.lua" ], + "common" : list_files("./scada-common"), + "graphics" : list_files("./graphics"), + "lockbox" : list_files("./lockbox"), + # platform files + "reactor-plc" : list_files("./reactor-plc"), + "rtu" : list_files("./rtu"), + "supervisor" : list_files("./supervisor"), + "coordinator" : list_files("./coordinator"), + "pocket" : list_files("./pocket"), + }, + "depends" : { + "reactor-plc" : [ "system", "common" ], + "rtu" : [ "system", "common" ], + "supervisor" : [ "system", "common" ], + "coordinator" : [ "system", "common", "graphics" ], + "pocket" : [ "system", "common", "graphics" ] + }, + "sizes" : { + # common files + "system" : os.path.getsize("initenv.lua") + os.path.getsize("startup.lua"), + "common" : dir_size("./scada-common"), + "graphics" : dir_size("./graphics"), + "lockbox" : dir_size("./lockbox"), + # platform files + "reactor-plc" : dir_size("./reactor-plc"), + "rtu" : dir_size("./rtu"), + "supervisor" : dir_size("./supervisor"), + "coordinator" : dir_size("./coordinator"), + "pocket" : dir_size("./pocket"), + } +} + +f = open("install_manifest.json", "w") + +json.dump(manifest, f) + +f.close() diff --git a/install_manifest.json b/install_manifest.json new file mode 100644 index 0000000..58131e8 --- /dev/null +++ b/install_manifest.json @@ -0,0 +1,189 @@ +{ + "versions": { + "bootloader": "0.2", + "comms": "1.3.3", + "reactor-plc": "beta-v0.10.11", + "rtu": "beta-v0.11.0", + "supervisor": "beta-v0.11.13", + "coordinator": "beta-v0.9.16", + "pocket": "alpha-v0.0.0" + }, + "files": { + "system": [ + "initenv.lua", + "startup.lua" + ], + "common": [ + "scada-common/crypto.lua", + "scada-common/ppm.lua", + "scada-common/comms.lua", + "scada-common/psil.lua", + "scada-common/tcallbackdsp.lua", + "scada-common/rsio.lua", + "scada-common/mqueue.lua", + "scada-common/crash.lua", + "scada-common/log.lua", + "scada-common/types.lua", + "scada-common/util.lua" + ], + "graphics": [ + "graphics/element.lua", + "graphics/flasher.lua", + "graphics/core.lua", + "graphics/elements/textbox.lua", + "graphics/elements/displaybox.lua", + "graphics/elements/pipenet.lua", + "graphics/elements/rectangle.lua", + "graphics/elements/div.lua", + "graphics/elements/tiling.lua", + "graphics/elements/colormap.lua", + "graphics/elements/indicators/alight.lua", + "graphics/elements/indicators/icon.lua", + "graphics/elements/indicators/power.lua", + "graphics/elements/indicators/rad.lua", + "graphics/elements/indicators/state.lua", + "graphics/elements/indicators/light.lua", + "graphics/elements/indicators/vbar.lua", + "graphics/elements/indicators/coremap.lua", + "graphics/elements/indicators/data.lua", + "graphics/elements/indicators/hbar.lua", + "graphics/elements/indicators/trilight.lua", + "graphics/elements/controls/switch_button.lua", + "graphics/elements/controls/spinbox_numeric.lua", + "graphics/elements/controls/hazard_button.lua", + "graphics/elements/controls/push_button.lua", + "graphics/elements/controls/radio_button.lua", + "graphics/elements/controls/multi_button.lua", + "graphics/elements/animations/waiting.lua" + ], + "lockbox": [ + "lockbox/init.lua", + "lockbox/LICENSE", + "lockbox/kdf/pbkdf2.lua", + "lockbox/util/bit.lua", + "lockbox/util/array.lua", + "lockbox/util/stream.lua", + "lockbox/util/queue.lua", + "lockbox/digest/sha2_224.lua", + "lockbox/digest/sha1.lua", + "lockbox/digest/sha2_256.lua", + "lockbox/cipher/aes128.lua", + "lockbox/cipher/aes256.lua", + "lockbox/cipher/aes192.lua", + "lockbox/cipher/mode/ofb.lua", + "lockbox/cipher/mode/cbc.lua", + "lockbox/cipher/mode/ctr.lua", + "lockbox/cipher/mode/cfb.lua", + "lockbox/mac/hmac.lua", + "lockbox/padding/ansix923.lua", + "lockbox/padding/pkcs7.lua", + "lockbox/padding/zero.lua", + "lockbox/padding/isoiec7816.lua" + ], + "reactor-plc": [ + "reactor-plc/threads.lua", + "reactor-plc/plc.lua", + "reactor-plc/config.lua", + "reactor-plc/startup.lua" + ], + "rtu": [ + "rtu/threads.lua", + "rtu/rtu.lua", + "rtu/modbus.lua", + "rtu/config.lua", + "rtu/startup.lua", + "rtu/dev/sps_rtu.lua", + "rtu/dev/envd_rtu.lua", + "rtu/dev/boilerv_rtu.lua", + "rtu/dev/redstone_rtu.lua", + "rtu/dev/sna_rtu.lua", + "rtu/dev/imatrix_rtu.lua", + "rtu/dev/turbinev_rtu.lua" + ], + "supervisor": [ + "supervisor/supervisor.lua", + "supervisor/unit.lua", + "supervisor/config.lua", + "supervisor/startup.lua", + "supervisor/unitlogic.lua", + "supervisor/facility.lua", + "supervisor/session/coordinator.lua", + "supervisor/session/svqtypes.lua", + "supervisor/session/svsessions.lua", + "supervisor/session/rtu.lua", + "supervisor/session/plc.lua", + "supervisor/session/rsctl.lua", + "supervisor/session/rtu/boilerv.lua", + "supervisor/session/rtu/txnctrl.lua", + "supervisor/session/rtu/unit_session.lua", + "supervisor/session/rtu/turbinev.lua", + "supervisor/session/rtu/envd.lua", + "supervisor/session/rtu/imatrix.lua", + "supervisor/session/rtu/sps.lua", + "supervisor/session/rtu/qtypes.lua", + "supervisor/session/rtu/sna.lua", + "supervisor/session/rtu/redstone.lua" + ], + "coordinator": [ + "coordinator/coordinator.lua", + "coordinator/renderer.lua", + "coordinator/iocontrol.lua", + "coordinator/sounder.lua", + "coordinator/config.lua", + "coordinator/startup.lua", + "coordinator/apisessions.lua", + "coordinator/process.lua", + "coordinator/ui/dialog.lua", + "coordinator/ui/style.lua", + "coordinator/ui/layout/main_view.lua", + "coordinator/ui/layout/unit_view.lua", + "coordinator/ui/components/reactor.lua", + "coordinator/ui/components/processctl.lua", + "coordinator/ui/components/unit_overview.lua", + "coordinator/ui/components/boiler.lua", + "coordinator/ui/components/unit_detail.lua", + "coordinator/ui/components/imatrix.lua", + "coordinator/ui/components/unit_waiting.lua", + "coordinator/ui/components/turbine.lua" + ], + "pocket": [ + "pocket/config.lua", + "pocket/startup.lua" + ] + }, + "depends": { + "reactor-plc": [ + "system", + "common" + ], + "rtu": [ + "system", + "common" + ], + "supervisor": [ + "system", + "common" + ], + "coordinator": [ + "system", + "common", + "graphics" + ], + "pocket": [ + "system", + "common", + "graphics" + ] + }, + "sizes": { + "system": 1982, + "common": 88049, + "graphics": 99360, + "lockbox": 100797, + "reactor-plc": 75956, + "rtu": 81511, + "supervisor": 261745, + "coordinator": 180622, + "pocket": 335 + } +} \ No newline at end of file diff --git a/installer.lua b/installer.lua new file mode 100644 index 0000000..fa863e1 --- /dev/null +++ b/installer.lua @@ -0,0 +1,326 @@ +-- +-- ComputerCraft Mekanism SCADA System Installer Utility +-- + +--[[ + +Copyright © 2023 Mikayla Fischler + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the “Software”), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +]]-- + +local util = require("scada-common.util") + +local print = util.print +local println = util.println + +local VERSION = "v0.1" + +local install_dir = "/.install-cache" +local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/devel/" +local install_manifest = repo_path .. "install_manifest.json" + +local opts = { ... } +local mode = nil +local app = nil + +-- +-- get and validate command line options +-- + +println("-- CC Mekanism SCADA Installer " .. VERSION .. " --") + +if #opts == 0 or opts[1] == "help" or #opts ~= 2 then + println("note: only modifies files that are part of the device application") + println("usage: installer ") + println("") + println(" install - fresh install, overwrites config") + println(" update - update files EXCEPT for config/logs") + println(" remove - delete files EXCEPT for config/logs") + println(" purge - delete files INCLUDING config/logs") + println("") + println(" reactor-plc - reactor PLC firmware") + println(" rtu - RTU firmware") + println(" supervisor - supervisor server application") + println(" coordinator - coordinator application") + println(" pocket - pocket application") + return +else + for _, v in pairs({ "install", "update", "remove", "purge" }) do + if opts[1] == v then + mode = v + break + end + end + + if mode == nil then + println("unrecognized mode") + return + end + + for _, v in pairs({ "reactor-plc", "rtu", "supervisor", "coordinator", "pocket" }) do + if opts[2] == v then + app = v + break + end + end + + if app == nil then + println("unrecognized application") + return + end +end + +-- +-- run selected mode +-- + +if mode == "install" or mode == "update" then + ------------------------- + -- GET REMOTE MANIFEST -- + ------------------------- + + local response, error = http.get(install_manifest) + + if response == nil then + println("failed to get installation manifest from GitHub, cannot update or install") + println(util.c("http error ", error)) + return + end + + local ok, manifest = pcall(function () return textutils.unserializeJSON(response.readAll()) end) + + if not ok then + println("error parsing remote version manifest") + return + end + + ------------------------ + -- GET LOCAL MANIFEST -- + ------------------------ + + local imfile = fs.open("install_manifest.json") + local local_ok, local_manifest = pcall(function () return textutils.unserializeJSON(imfile.readAll()) end) + imfile.close() + + local local_app_version = nil + local local_comms_version = nil + local local_boot_version = nil + + if not local_ok and mode == "update" then + println("warning: failed to load local installation information") + local_app_version = local_manifest.versions[app] + local_comms_version = local_manifest.versions.comms + local_boot_version = local_manifest.versions.bootloader + end + + local remote_app_version = manifest.versions[app] + local remote_comms_version = manifest.versions.comms + local remote_boot_version = manifest.versions.bootloader + + if mode == "install" then + println("installing " .. app .. " files...") + elseif mode == "update" then + println("updating " .. app .. " files... (keeping old config.lua)") + end + + if local_boot_version ~= nil then + if local_boot_version ~= remote_boot_version then + println("[bootldr] updating " .. local_boot_version .. " => " .. remote_boot_version) + end + else + println("[bootldr] fresh install of " .. remote_boot_version) + end + + if local_comms_version ~= nil then + if local_comms_version ~= remote_comms_version then + println("[comms] updating " .. local_comms_version .. " => " .. remote_comms_version) + println("[comms] other devices on the network will require an update") + end + else + println("[comms] fresh install of " .. remote_comms_version) + end + + if local_app_version ~= nil then + if local_app_version ~= remote_app_version then + println("[" .. app .. "] updating " .. local_app_version .. " => " .. remote_app_version) + end + else + println("[" .. app .. "] fresh install of " .. remote_app_version) + end + + -------------------------- + -- START INSTALL/UPDATE -- + -------------------------- + + local space_required = 0 + local space_available = fs.getFreeSpace("/") + + local single_file_mode = false + local file_list = manifest.files + local size_list = manifest.sizes + local dependencies = manifest.depends[app] + local config_file = app .. "/config.lua" + + for _, dependency in pairs(dependencies) do + local size = size_list[dependency] + space_required = space_required + size + end + + if space_available < space_required then + single_file_mode = true + println("WARNING: Insuffienct space available for a full download!") + println("Files will be downloaded one by one, so if you are replacing a current install this will not be a problem unless installation fails.") + println("Do you wish to continue? (y/N)") + + local confirm = read() + if confirm ~= "y" or confirm ~= "Y" then + println("installation cancelled") + return + end + end + +---@diagnostic disable-next-line: undefined-field + os.sleep(2) + + local success = true + + if not single_file_mode then + if fs.exists(install_dir) then + fs.delete(install_dir) + fs.makeDir(install_dir) + end + + for _, dependency in pairs(dependencies) do + local files = file_list[dependency] + for _, file in pairs(files) do + println("get: " .. file) + local dl, err_c = http.get(repo_path .. file) + + if dl == nil then + println("get: error " .. err_c) + success = false + break + else + local handle = fs.open(install_dir .. "/" .. file, "w") + handle.write(dl.readAll()) + handle.close() + end + end + end + + if success then + for _, dependency in pairs(dependencies) do + local files = file_list[dependency] + for _, file in pairs(files) do + if mode == "install" or file ~= config_file then + fs.move(install_dir .. "/" .. file, file) + end + end + end + end + + fs.delete(install_dir) + + if success then + -- if we made it here, then none of the file system functions threw exceptions + -- that means everything is OK + if mode == "install" then + println("installation completed successfully") + else + println("update completed successfully") + end + else + if mode == "install" then + println("installation failed") + else + println("update failed, existing files unmodified") + end + end + else + for _, dependency in pairs(dependencies) do + local files = file_list[dependency] + for _, file in pairs(files) do + println("get: " .. file) + local dl, err_c = http.get(repo_path .. file) + + if dl == nil then + println("get: error " .. err_c) + success = false + break + else + local handle = fs.open("/" .. file, "w") + handle.write(dl.readAll()) + handle.close() + end + end + end + + if success then + -- if we made it here, then none of the file system functions threw exceptions + -- that means everything is OK + if mode == "install" then + println("installation completed successfully") + else + println("update completed successfully") + end + else + if mode == "install" then + println("installation failed, files may have been skipped") + else + println("update failed, files may have been skipped") + end + end + end +elseif mode == "remove" or mode == "purge" then + local imfile = fs.open("install_manifest.json") + local ok, manifest = pcall(function () return textutils.unserializeJSON(imfile.readAll()) end) + imfile.close() + + if not ok then + println("error parsing local version manifest") + return + elseif mode == "remove" then + println("removing all " .. app .. " files except for config.lua and log.txt...") + elseif mode == "purge" then + println("purging all " .. app .. " files including config.lua and log.txt...") + end + +---@diagnostic disable-next-line: undefined-field + os.sleep(2) + + local file_list = manifest.files + local dependencies = manifest.depends[app] + local config_file = app .. "/config.lua" + + -- delete all files except config unless purging + for _, dependency in pairs(dependencies) do + local files = file_list[dependency] + for _, file in pairs(files) do + if mode == "purge" or file ~= config_file then + fs.delete(file) + println("deleted " .. file) + end + end + end + + -- delete log file if purging + if mode == "purge" then + println("deleting log file '" .. config_file .. "'...") + local config = require(config_file) + fs.delete(config.LOG_PATH) + println("deleted " .. config.LOG_PATH) + end + + println("done!") +end diff --git a/pocket/startup.lua b/pocket/startup.lua index 40a0777..032bba9 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -3,3 +3,14 @@ -- require("/initenv").init_env() + +local util = require("scada-common.util") + +local POCKET_VERSION = "alpha-v0.0.0" + +local print = util.print +local println = util.println +local print_ts = util.print_ts +local println_ts = util.println_ts + +println("Sorry, this isn't written yet :(") From 1dea5b1b7a8e59bb3cba81f76974873392b06fff Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 12:56:53 -0500 Subject: [PATCH 520/587] #117 removed util dependency from installer, whoops --- installer.lua | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/installer.lua b/installer.lua index fa863e1..a69fde7 100644 --- a/installer.lua +++ b/installer.lua @@ -19,12 +19,10 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]]-- -local util = require("scada-common.util") +local function println(message) print(tostring(message)) end +local function print(message) term.write(tostring(message)) end -local print = util.print -local println = util.println - -local VERSION = "v0.1" +local VERSION = "v0.2" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/devel/" @@ -94,7 +92,7 @@ if mode == "install" or mode == "update" then if response == nil then println("failed to get installation manifest from GitHub, cannot update or install") - println(util.c("http error ", error)) + println("http error " .. error) return end From 0493f572a2ee51489b51d5f73b226c1a3faf7226 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 17:15:26 -0500 Subject: [PATCH 521/587] #117 installer v0.3 with colors and fixes --- installer.lua | 82 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 12 deletions(-) diff --git a/installer.lua b/installer.lua index a69fde7..cae4eed 100644 --- a/installer.lua +++ b/installer.lua @@ -22,7 +22,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local VERSION = "v0.2" +local VERSION = "v0.3" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/devel/" @@ -107,16 +107,24 @@ if mode == "install" or mode == "update" then -- GET LOCAL MANIFEST -- ------------------------ - local imfile = fs.open("install_manifest.json") - local local_ok, local_manifest = pcall(function () return textutils.unserializeJSON(imfile.readAll()) end) - imfile.close() + local imfile = fs.open("install_manifest.json", "r") + local local_ok = false + local local_manifest = {} + + if imfile ~= nil then + local_ok, local_manifest = pcall(function () return textutils.unserializeJSON(imfile.readAll()) end) + imfile.close() + end local local_app_version = nil local local_comms_version = nil local local_boot_version = nil if not local_ok and mode == "update" then + term.setTextColor(colors.yellow) println("warning: failed to load local installation information") + term.setTextColor(colors.white) + local_app_version = local_manifest.versions[app] local_comms_version = local_manifest.versions.comms local_boot_version = local_manifest.versions.bootloader @@ -126,27 +134,53 @@ if mode == "install" or mode == "update" then local remote_comms_version = manifest.versions.comms local remote_boot_version = manifest.versions.bootloader + term.setTextColor(colors.green) if mode == "install" then println("installing " .. app .. " files...") elseif mode == "update" then println("updating " .. app .. " files... (keeping old config.lua)") end + term.setTextColor(colors.white) if local_boot_version ~= nil then if local_boot_version ~= remote_boot_version then - println("[bootldr] updating " .. local_boot_version .. " => " .. remote_boot_version) + print("[bootldr] updating ") + term.setTextColor(colors.blue) + print(local_boot_version) + term.setTextColor(colors.white) + print(" => ") + term.setTextColor(colors.blue) + println(remote_boot_version) + term.setTextColor(colors.white) end else - println("[bootldr] fresh install of " .. remote_boot_version) + println("[bootldr] new install of ") + term.setTextColor(colors.blue) + println(remote_boot_version) + term.setTextColor(colors.white) end if local_comms_version ~= nil then if local_comms_version ~= remote_comms_version then - println("[comms] updating " .. local_comms_version .. " => " .. remote_comms_version) - println("[comms] other devices on the network will require an update") + print("[comms] updating ") + term.setTextColor(colors.blue) + print(local_comms_version) + term.setTextColor(colors.white) + print(" => ") + term.setTextColor(colors.blue) + println(remote_comms_version) + term.setTextColor(colors.white) + + print("[comms] ") + term.setTextColor(colors.yellow) + println("other devices on the network will require an update") + term.setTextColor(colors.white) end else - println("[comms] fresh install of " .. remote_comms_version) + println("[comms] new install of ") + term.setTextColor(colors.blue) + println(remote_comms_version) + term.setTextColor(colors.white) end if local_app_version ~= nil then @@ -170,6 +204,8 @@ if mode == "install" or mode == "update" then local dependencies = manifest.depends[app] local config_file = app .. "/config.lua" + table.insert(dependencies, app) + for _, dependency in pairs(dependencies) do local size = size_list[dependency] space_required = space_required + size @@ -177,7 +213,9 @@ if mode == "install" or mode == "update" then if space_available < space_required then single_file_mode = true + term.setTextColor(colors.red) println("WARNING: Insuffienct space available for a full download!") + term.setTextColor(colors.white) println("Files will be downloaded one by one, so if you are replacing a current install this will not be a problem unless installation fails.") println("Do you wish to continue? (y/N)") @@ -199,13 +237,15 @@ if mode == "install" or mode == "update" then fs.makeDir(install_dir) end + term.setTextColor(colors.lightGray) for _, dependency in pairs(dependencies) do local files = file_list[dependency] for _, file in pairs(files) do - println("get: " .. file) + println("get: " .. file) local dl, err_c = http.get(repo_path .. file) if dl == nil then + term.setTextColor(colors.red) println("get: error " .. err_c) success = false break @@ -231,6 +271,8 @@ if mode == "install" or mode == "update" then fs.delete(install_dir) if success then + term.setTextColor(colors.green) + -- if we made it here, then none of the file system functions threw exceptions -- that means everything is OK if mode == "install" then @@ -240,8 +282,10 @@ if mode == "install" or mode == "update" then end else if mode == "install" then + term.setTextColor(colors.red) println("installation failed") else + term.setTextColor(colors.orange) println("update failed, existing files unmodified") end end @@ -249,7 +293,7 @@ if mode == "install" or mode == "update" then for _, dependency in pairs(dependencies) do local files = file_list[dependency] for _, file in pairs(files) do - println("get: " .. file) + println("get: " .. file) local dl, err_c = http.get(repo_path .. file) if dl == nil then @@ -267,12 +311,14 @@ if mode == "install" or mode == "update" then if success then -- if we made it here, then none of the file system functions threw exceptions -- that means everything is OK + term.setTextColor(colors.green) if mode == "install" then println("installation completed successfully") else println("update completed successfully") end else + term.setTextColor(colors.red) if mode == "install" then println("installation failed, files may have been skipped") else @@ -286,9 +332,14 @@ elseif mode == "remove" or mode == "purge" then imfile.close() if not ok then + term.setTextColor(colors.red) println("error parsing local version manifest") + term.setTextColor(colors.white) return - elseif mode == "remove" then + end + + term.setTextColor(colors.orange) + if mode == "remove" then println("removing all " .. app .. " files except for config.lua and log.txt...") elseif mode == "purge" then println("purging all " .. app .. " files including config.lua and log.txt...") @@ -301,6 +352,10 @@ elseif mode == "remove" or mode == "purge" then local dependencies = manifest.depends[app] local config_file = app .. "/config.lua" + table.insert(dependencies, app) + + term.setTextColor(colors.lightGray) + -- delete all files except config unless purging for _, dependency in pairs(dependencies) do local files = file_list[dependency] @@ -320,5 +375,8 @@ elseif mode == "remove" or mode == "purge" then println("deleted " .. config.LOG_PATH) end + term.setTextColor(colors.green) println("done!") end + +term.setTextColor(colors.white) From df57e1859e3662440b70142b7e1a58fa22510b51 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 18:49:04 -0500 Subject: [PATCH 522/587] #117 installer v0.4 with package version checking for skips, fixes to file overwriting --- installer.lua | 152 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 99 insertions(+), 53 deletions(-) diff --git a/installer.lua b/installer.lua index cae4eed..cb4f284 100644 --- a/installer.lua +++ b/installer.lua @@ -22,7 +22,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local VERSION = "v0.3" +local VERSION = "v0.4" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/devel/" @@ -99,7 +99,7 @@ if mode == "install" or mode == "update" then local ok, manifest = pcall(function () return textutils.unserializeJSON(response.readAll()) end) if not ok then - println("error parsing remote version manifest") + println("error parsing remote installation manifest") return end @@ -148,25 +148,40 @@ if mode == "install" or mode == "update" then term.setTextColor(colors.blue) print(local_boot_version) term.setTextColor(colors.white) - print(" => ") + print(" \xbb ") term.setTextColor(colors.blue) println(remote_boot_version) term.setTextColor(colors.white) end else - println("[bootldr] new install of ") + print("[bootldr] new install of ") term.setTextColor(colors.blue) println(remote_boot_version) term.setTextColor(colors.white) end + if local_app_version ~= nil then + if local_app_version ~= remote_app_version then + print("[" .. app .. "] updating ") + term.setTextColor(colors.blue) + print(local_app_version) + term.setTextColor(colors.white) + print(" \xbb ") + term.setTextColor(colors.blue) + println(remote_app_version) + term.setTextColor(colors.white) + end + else + println("[" .. app .. "] fresh install of " .. remote_app_version) + end + if local_comms_version ~= nil then if local_comms_version ~= remote_comms_version then print("[comms] updating ") term.setTextColor(colors.blue) print(local_comms_version) term.setTextColor(colors.white) - print(" => ") + print(" \xbb ") term.setTextColor(colors.blue) println(remote_comms_version) term.setTextColor(colors.white) @@ -177,20 +192,12 @@ if mode == "install" or mode == "update" then term.setTextColor(colors.white) end else - println("[comms] new install of ") + print("[comms] new install of ") term.setTextColor(colors.blue) println(remote_comms_version) term.setTextColor(colors.white) end - if local_app_version ~= nil then - if local_app_version ~= remote_app_version then - println("[" .. app .. "] updating " .. local_app_version .. " => " .. remote_app_version) - end - else - println("[" .. app .. "] fresh install of " .. remote_app_version) - end - -------------------------- -- START INSTALL/UPDATE -- -------------------------- @@ -237,32 +244,59 @@ if mode == "install" or mode == "update" then fs.makeDir(install_dir) end - term.setTextColor(colors.lightGray) for _, dependency in pairs(dependencies) do - local files = file_list[dependency] - for _, file in pairs(files) do - println("get: " .. file) - local dl, err_c = http.get(repo_path .. file) + if (dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version) then + -- skip system package if unchanged, skip app package if not changed + -- skip packages that have no version if app version didn't change + print("skipping download of unchanged package ") + term.setTextColor(colors.blue) + println(dependency) + else + print("downloading package ") + term.setTextColor(colors.blue) + println(dependency) - if dl == nil then - term.setTextColor(colors.red) - println("get: error " .. err_c) - success = false - break - else - local handle = fs.open(install_dir .. "/" .. file, "w") - handle.write(dl.readAll()) - handle.close() + term.setTextColor(colors.lightGray) + local files = file_list[dependency] + for _, file in pairs(files) do + println("get: " .. file) + local dl, err_c = http.get(repo_path .. file) + + if dl == nil then + term.setTextColor(colors.red) + println("get: error " .. err_c) + success = false + break + else + local handle = fs.open(install_dir .. "/" .. file, "w") + handle.write(dl.readAll()) + handle.close() + end end end end if success then for _, dependency in pairs(dependencies) do - local files = file_list[dependency] - for _, file in pairs(files) do - if mode == "install" or file ~= config_file then - fs.move(install_dir .. "/" .. file, file) + if (dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version) then + -- skip system package if unchanged, skip app package if not changed + -- skip packages that have no version if app version didn't change + print("skipping install of unchanged package ") + term.setTextColor(colors.blue) + println(dependency) + else + print("installing package ") + term.setTextColor(colors.blue) + println(dependency) + + term.setTextColor(colors.lightGray) + local files = file_list[dependency] + for _, file in pairs(files) do + if mode == "install" or file ~= config_file then + local temp_file = install_dir .. "/" .. file + if fs.exists(temp_file) then fs.delete(temp_file) end + fs.move(temp_file, file) + end end end end @@ -291,19 +325,32 @@ if mode == "install" or mode == "update" then end else for _, dependency in pairs(dependencies) do - local files = file_list[dependency] - for _, file in pairs(files) do - println("get: " .. file) - local dl, err_c = http.get(repo_path .. file) + if (dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version) then + -- skip system package if unchanged, skip app package if not changed + -- skip packages that have no version if app version didn't change + print("skipping install of unchanged package ") + term.setTextColor(colors.blue) + println(dependency) + else + print("installing package ") + term.setTextColor(colors.blue) + println(dependency) - if dl == nil then - println("get: error " .. err_c) - success = false - break - else - local handle = fs.open("/" .. file, "w") - handle.write(dl.readAll()) - handle.close() + term.setTextColor(colors.lightGray) + local files = file_list[dependency] + for _, file in pairs(files) do + println("get: " .. file) + local dl, err_c = http.get(repo_path .. file) + + if dl == nil then + println("get: error " .. err_c) + success = false + break + else + local handle = fs.open("/" .. file, "w") + handle.write(dl.readAll()) + handle.close() + end end end end @@ -333,7 +380,7 @@ elseif mode == "remove" or mode == "purge" then if not ok then term.setTextColor(colors.red) - println("error parsing local version manifest") + println("error parsing local installation manifest") term.setTextColor(colors.white) return end @@ -356,6 +403,13 @@ elseif mode == "remove" or mode == "purge" then term.setTextColor(colors.lightGray) + -- delete log file if purging + if mode == "purge" then + local config = require(config_file) + fs.delete(config.LOG_PATH) + println("deleted log file " .. config.LOG_PATH) + end + -- delete all files except config unless purging for _, dependency in pairs(dependencies) do local files = file_list[dependency] @@ -367,14 +421,6 @@ elseif mode == "remove" or mode == "purge" then end end - -- delete log file if purging - if mode == "purge" then - println("deleting log file '" .. config_file .. "'...") - local config = require(config_file) - fs.delete(config.LOG_PATH) - println("deleted " .. config.LOG_PATH) - end - term.setTextColor(colors.green) println("done!") end From 726d15b48f359f98760f36756bdce58168db963c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 18:52:21 -0500 Subject: [PATCH 523/587] #117 installer v0.5 fixed colors and move not working --- installer.lua | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/installer.lua b/installer.lua index cb4f284..c76a469 100644 --- a/installer.lua +++ b/installer.lua @@ -22,7 +22,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local VERSION = "v0.4" +local VERSION = "v0.5" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/devel/" @@ -172,7 +172,9 @@ if mode == "install" or mode == "update" then term.setTextColor(colors.white) end else - println("[" .. app .. "] fresh install of " .. remote_app_version) + term.setTextColor(colors.blue) + println(remote_app_version) + term.setTextColor(colors.white) end if local_comms_version ~= nil then @@ -248,10 +250,12 @@ if mode == "install" or mode == "update" then if (dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version) then -- skip system package if unchanged, skip app package if not changed -- skip packages that have no version if app version didn't change + term.setTextColor(colors.white) print("skipping download of unchanged package ") term.setTextColor(colors.blue) println(dependency) else + term.setTextColor(colors.white) print("downloading package ") term.setTextColor(colors.blue) println(dependency) @@ -281,10 +285,12 @@ if mode == "install" or mode == "update" then if (dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version) then -- skip system package if unchanged, skip app package if not changed -- skip packages that have no version if app version didn't change + term.setTextColor(colors.white) print("skipping install of unchanged package ") term.setTextColor(colors.blue) println(dependency) else + term.setTextColor(colors.white) print("installing package ") term.setTextColor(colors.blue) println(dependency) @@ -294,7 +300,7 @@ if mode == "install" or mode == "update" then for _, file in pairs(files) do if mode == "install" or file ~= config_file then local temp_file = install_dir .. "/" .. file - if fs.exists(temp_file) then fs.delete(temp_file) end + if fs.exists(file) then fs.delete(file) end fs.move(temp_file, file) end end @@ -328,10 +334,12 @@ if mode == "install" or mode == "update" then if (dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version) then -- skip system package if unchanged, skip app package if not changed -- skip packages that have no version if app version didn't change + term.setTextColor(colors.white) print("skipping install of unchanged package ") term.setTextColor(colors.blue) println(dependency) else + term.setTextColor(colors.white) print("installing package ") term.setTextColor(colors.blue) println(dependency) From fa6524d93409d38214712f3a34d93534fc1c8584 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 19:14:47 -0500 Subject: [PATCH 524/587] #117 installer v0.6 saving manifest after operation, checking install state before proceeding --- installer.lua | 75 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 13 deletions(-) diff --git a/installer.lua b/installer.lua index c76a469..38ecdf7 100644 --- a/installer.lua +++ b/installer.lua @@ -22,7 +22,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local VERSION = "v0.5" +local VERSION = "v0.6" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/devel/" @@ -32,6 +32,29 @@ local opts = { ... } local mode = nil local app = nil +local function write_install_manifest(manifest, dependencies) + local versions = {} + for key, value in pairs(manifest.versions) do + local is_dependency = false + for _, dependency in pairs(dependencies) do + if key == "bootloader" and dependency == "system" then + is_dependency = true + break + end + end + + if key == app or is_dependency then + versions[key] = value + end + end + + manifest.versions = versions + + local imfile = fs.open("install_manifest.json", "w") + imfile.write(textutils.serializeJSON(manifest)) + imfile.close() +end + -- -- get and validate command line options -- @@ -128,6 +151,11 @@ if mode == "install" or mode == "update" then local_app_version = local_manifest.versions[app] local_comms_version = local_manifest.versions.comms local_boot_version = local_manifest.versions.bootloader + elseif local_manifest.versions[app] == nil then + term.setTextColor(colors.red) + println("another application is already installed, please purge it before installing a new application") + term.setTextColor(colors.white) + return end local remote_app_version = manifest.versions[app] @@ -172,6 +200,7 @@ if mode == "install" or mode == "update" then term.setTextColor(colors.white) end else + print("[" .. app .. "] new install of ") term.setTextColor(colors.blue) println(remote_app_version) term.setTextColor(colors.white) @@ -247,7 +276,7 @@ if mode == "install" or mode == "update" then end for _, dependency in pairs(dependencies) do - if (dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version) then + if mode == "update" and ((dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version)) then -- skip system package if unchanged, skip app package if not changed -- skip packages that have no version if app version didn't change term.setTextColor(colors.white) @@ -282,7 +311,7 @@ if mode == "install" or mode == "update" then if success then for _, dependency in pairs(dependencies) do - if (dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version) then + if mode == "update" and ((dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version)) then -- skip system package if unchanged, skip app package if not changed -- skip packages that have no version if app version didn't change term.setTextColor(colors.white) @@ -311,10 +340,10 @@ if mode == "install" or mode == "update" then fs.delete(install_dir) if success then - term.setTextColor(colors.green) - -- if we made it here, then none of the file system functions threw exceptions -- that means everything is OK + write_install_manifest(manifest, dependencies) + term.setTextColor(colors.green) if mode == "install" then println("installation completed successfully") else @@ -331,7 +360,7 @@ if mode == "install" or mode == "update" then end else for _, dependency in pairs(dependencies) do - if (dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version) then + if mode == "update" and ((dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version)) then -- skip system package if unchanged, skip app package if not changed -- skip packages that have no version if app version didn't change term.setTextColor(colors.white) @@ -366,6 +395,7 @@ if mode == "install" or mode == "update" then if success then -- if we made it here, then none of the file system functions threw exceptions -- that means everything is OK + write_install_manifest(manifest, dependencies) term.setTextColor(colors.green) if mode == "install" then println("installation completed successfully") @@ -382,15 +412,25 @@ if mode == "install" or mode == "update" then end end elseif mode == "remove" or mode == "purge" then - local imfile = fs.open("install_manifest.json") - local ok, manifest = pcall(function () return textutils.unserializeJSON(imfile.readAll()) end) - imfile.close() + local imfile = fs.open("install_manifest.json", "r") + local ok = false + local manifest = {} + + if imfile ~= nil then + ok, manifest = pcall(function () return textutils.unserializeJSON(imfile.readAll()) end) + imfile.close() + end if not ok then term.setTextColor(colors.red) println("error parsing local installation manifest") term.setTextColor(colors.white) return + elseif manifest.versions[app] == nil then + term.setTextColor(colors.red) + println(app .. " is not installed") + term.setTextColor(colors.white) + return end term.setTextColor(colors.orange) @@ -414,8 +454,10 @@ elseif mode == "remove" or mode == "purge" then -- delete log file if purging if mode == "purge" then local config = require(config_file) - fs.delete(config.LOG_PATH) - println("deleted log file " .. config.LOG_PATH) + if fs.exists(config.LOG_PATH) then + fs.delete(config.LOG_PATH) + println("deleted log file " .. config.LOG_PATH) + end end -- delete all files except config unless purging @@ -423,12 +465,19 @@ elseif mode == "remove" or mode == "purge" then local files = file_list[dependency] for _, file in pairs(files) do if mode == "purge" or file ~= config_file then - fs.delete(file) - println("deleted " .. file) + if fs.exists(file) then + fs.delete(file) + println("deleted " .. file) + end end end end + if mode == "purge" then + fs.delete("install_manifest.json") + println("deleted install_manifest.json") + end + term.setTextColor(colors.green) println("done!") end From 960c016f4c07dbca245d7465da45a255732ec10a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 19:18:06 -0500 Subject: [PATCH 525/587] #117 installer v0.7 fixed bug with checking local manifest --- installer.lua | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/installer.lua b/installer.lua index 38ecdf7..accacd0 100644 --- a/installer.lua +++ b/installer.lua @@ -22,7 +22,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local VERSION = "v0.6" +local VERSION = "v0.7" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/devel/" @@ -143,19 +143,23 @@ if mode == "install" or mode == "update" then local local_comms_version = nil local local_boot_version = nil - if not local_ok and mode == "update" then - term.setTextColor(colors.yellow) - println("warning: failed to load local installation information") - term.setTextColor(colors.white) - + if not local_ok then + if mode == "update" then + term.setTextColor(colors.yellow) + println("warning: failed to load local installation information") + term.setTextColor(colors.white) + end + else local_app_version = local_manifest.versions[app] local_comms_version = local_manifest.versions.comms local_boot_version = local_manifest.versions.bootloader - elseif local_manifest.versions[app] == nil then - term.setTextColor(colors.red) - println("another application is already installed, please purge it before installing a new application") - term.setTextColor(colors.white) - return + + if local_manifest.versions[app] == nil then + term.setTextColor(colors.red) + println("another application is already installed, please purge it before installing a new application") + term.setTextColor(colors.white) + return + end end local remote_app_version = manifest.versions[app] From bc38a9ea275ff108b450b1f8bb6f78f46da7d2b6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 19:30:03 -0500 Subject: [PATCH 526/587] #117 installer v0.8 fixed purge, added check --- installer.lua => ccmsi.lua | 69 +++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 4 deletions(-) rename installer.lua => ccmsi.lua (89%) diff --git a/installer.lua b/ccmsi.lua similarity index 89% rename from installer.lua rename to ccmsi.lua index accacd0..96ff5a9 100644 --- a/installer.lua +++ b/ccmsi.lua @@ -22,7 +22,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local VERSION = "v0.7" +local VERSION = "v0.8" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/devel/" @@ -63,8 +63,9 @@ println("-- CC Mekanism SCADA Installer " .. VERSION .. " --") if #opts == 0 or opts[1] == "help" or #opts ~= 2 then println("note: only modifies files that are part of the device application") - println("usage: installer ") + println("usage: ccmsi ") println("") + println(" check - check latest versions avilable") println(" install - fresh install, overwrites config") println(" update - update files EXCEPT for config/logs") println(" remove - delete files EXCEPT for config/logs") @@ -106,7 +107,7 @@ end -- run selected mode -- -if mode == "install" or mode == "update" then +if mode == "check" then ------------------------- -- GET REMOTE MANIFEST -- ------------------------- @@ -122,7 +123,9 @@ if mode == "install" or mode == "update" then local ok, manifest = pcall(function () return textutils.unserializeJSON(response.readAll()) end) if not ok then + term.setTextColor(colors.red) println("error parsing remote installation manifest") + term.setTextColor(colors.white) return end @@ -139,6 +142,64 @@ if mode == "install" or mode == "update" then imfile.close() end + if not local_ok then + term.setTextColor(colors.yellow) + println("warning: failed to load local installation information") + term.setTextColor(colors.white) + end + + for key, value in pairs(manifest.versions) do + term.setTextColor(colors.white) + print("[" .. key .. "]" ) + term.setTextColor(colors.blue) + print(value) + term.setTextColor(colors.lightGray) + if local_manifest.versions[key] ~= nil then + print(" (current ") + term.setTextColor(colors.blue) + print(value) + term.setTextColor(colors.white) + println(")") + else + println(" (not installed)") + end + end +elseif mode == "install" or mode == "update" then + ------------------------- + -- GET REMOTE MANIFEST -- + ------------------------- + + local response, error = http.get(install_manifest) + + if response == nil then + term.setTextColor(colors.red) + println("failed to get installation manifest from GitHub, cannot update or install") + println("http error " .. error) + term.setTextColor(colors.white) + return + end + + local ok, manifest = pcall(function () return textutils.unserializeJSON(response.readAll()) end) + + if not ok then + term.setTextColor(colors.red) + println("error parsing remote installation manifest") + term.setTextColor(colors.white) + end + + ------------------------ + -- GET LOCAL MANIFEST -- + ------------------------ + + local imfile = fs.open("install_manifest.json", "r") + local local_ok = false + local local_manifest = {} + + if imfile ~= nil then + local_ok, local_manifest = pcall(function () return textutils.unserializeJSON(imfile.readAll()) end) + imfile.close() + end + local local_app_version = nil local local_comms_version = nil local local_boot_version = nil @@ -457,7 +518,7 @@ elseif mode == "remove" or mode == "purge" then -- delete log file if purging if mode == "purge" then - local config = require(config_file) + local config = require(app .. ".config") if fs.exists(config.LOG_PATH) then fs.delete(config.LOG_PATH) println("deleted log file " .. config.LOG_PATH) From 950ad2931f38991b920eade213ccc20e40e5b4f2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 19:41:32 -0500 Subject: [PATCH 527/587] #117 installer v0.8a fixes to deletion of directories and check command --- ccmsi.lua | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index 96ff5a9..af6c99f 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -22,7 +22,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local VERSION = "v0.8" +local VERSION = "v0.8a" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/devel/" @@ -78,7 +78,7 @@ if #opts == 0 or opts[1] == "help" or #opts ~= 2 then println(" pocket - pocket application") return else - for _, v in pairs({ "install", "update", "remove", "purge" }) do + for _, v in pairs({ "check", "install", "update", "remove", "purge" }) do if opts[1] == v then mode = v break @@ -97,7 +97,7 @@ else end end - if app == nil then + if app == nil and mode ~= "check" then println("unrecognized application") return end @@ -115,8 +115,10 @@ if mode == "check" then local response, error = http.get(install_manifest) if response == nil then + term.setTextColor(colors.red) println("failed to get installation manifest from GitHub, cannot update or install") println("http error " .. error) + term.setTextColor(colors.white) return end @@ -536,6 +538,36 @@ elseif mode == "remove" or mode == "purge" then end end end + + if mode == "purge" or dependency ~= app then + local folder = files[1] + while true do + local dir = fs.getDir(folder) + if dir == "" or dir == ".." then + break + else + folder = dir + end + end + + fs.delete(folder) + println("deleted directory " .. folder) + elseif dependency == app then + local folder = files[1] + while true do + local dir = fs.getDir(folder) + if dir == "" or dir == ".." or dir == app then + break + else + folder = dir + end + end + + if folder ~= app then + fs.delete(folder) + println("deleted app subdirectory " .. folder) + end + end end if mode == "purge" then From e6632c3bd939680c1e289563e05022d16068bcbe Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 19:56:12 -0500 Subject: [PATCH 528/587] #117 installer v0.8b fixed to folder deletion, check command, and preserving comms version in manifest --- ccmsi.lua | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index af6c99f..0100354 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -22,7 +22,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local VERSION = "v0.8a" +local VERSION = "v0.8b" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/devel/" @@ -32,6 +32,9 @@ local opts = { ... } local mode = nil local app = nil +-- record the local installation manifest +---@param manifest table +---@param dependencies table local function write_install_manifest(manifest, dependencies) local versions = {} for key, value in pairs(manifest.versions) do @@ -43,7 +46,7 @@ local function write_install_manifest(manifest, dependencies) end end - if key == app or is_dependency then + if key == app or key == "comms" or is_dependency then versions[key] = value end end @@ -61,7 +64,7 @@ end println("-- CC Mekanism SCADA Installer " .. VERSION .. " --") -if #opts == 0 or opts[1] == "help" or #opts ~= 2 then +if #opts == 0 or opts[1] == "help" then println("note: only modifies files that are part of the device application") println("usage: ccmsi ") println("") @@ -151,18 +154,19 @@ if mode == "check" then end for key, value in pairs(manifest.versions) do - term.setTextColor(colors.white) - print("[" .. key .. "]" ) + term.setTextColor(colors.purple) + print(string.format("%-14s", "[" .. key .. "]")) term.setTextColor(colors.blue) print(value) - term.setTextColor(colors.lightGray) - if local_manifest.versions[key] ~= nil then + if local_ok and (local_manifest.versions[key] ~= nil) then + term.setTextColor(colors.white) print(" (current ") term.setTextColor(colors.blue) print(value) term.setTextColor(colors.white) println(")") else + term.setTextColor(colors.lightGray) println(" (not installed)") end end @@ -553,19 +557,20 @@ elseif mode == "remove" or mode == "purge" then fs.delete(folder) println("deleted directory " .. folder) elseif dependency == app then - local folder = files[1] - while true do - local dir = fs.getDir(folder) - if dir == "" or dir == ".." or dir == app then - break - else - folder = dir + for _, folder in pairs(files) do + while true do + local dir = fs.getDir(folder) + if dir == "" or dir == ".." or dir == app then + break + else + folder = dir + end end - end - if folder ~= app then - fs.delete(folder) - println("deleted app subdirectory " .. folder) + if folder ~= app and fs.exists(folder) then + fs.delete(folder) + println("deleted app subdirectory " .. folder) + end end end end From 279a40e335bc7cd39c777ce8cb1a0f805b701057 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 20:17:03 -0500 Subject: [PATCH 529/587] #117 installer v0.9a added support for different targets --- ccmsi.lua | 56 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index 0100354..eb59195 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -22,11 +22,10 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local VERSION = "v0.8b" +local VERSION = "v0.9a" local install_dir = "/.install-cache" -local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/devel/" -local install_manifest = repo_path .. "install_manifest.json" +local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/" local opts = { ... } local mode = nil @@ -65,20 +64,31 @@ end println("-- CC Mekanism SCADA Installer " .. VERSION .. " --") if #opts == 0 or opts[1] == "help" then - println("note: only modifies files that are part of the device application") - println("usage: ccmsi ") + println("usage: ccmsi ") println("") + term.setTextColor(colors.lightGray) println(" check - check latest versions avilable") + term.setTextColor(colors.yellow) + println(" ccmsi check for target") + term.setTextColor(colors.lightGray) println(" install - fresh install, overwrites config") println(" update - update files EXCEPT for config/logs") println(" remove - delete files EXCEPT for config/logs") println(" purge - delete files INCLUDING config/logs") + term.setTextColor(colors.white) println("") + term.setTextColor(colors.lightGray) println(" reactor-plc - reactor PLC firmware") println(" rtu - RTU firmware") println(" supervisor - supervisor server application") println(" coordinator - coordinator application") println(" pocket - pocket application") + term.setTextColor(colors.white) + println("") + term.setTextColor(colors.yellow) + println(" second parameter instead of app when used with check") + term.setTextColor(colors.lightGray) + println(" target GitHub tag or branch name instead of main") return else for _, v in pairs({ "check", "install", "update", "remove", "purge" }) do @@ -115,6 +125,9 @@ if mode == "check" then -- GET REMOTE MANIFEST -- ------------------------- + if opts[2] then repo_path = repo_path .. opts[2] .. "/" else repo_path = repo_path .. "main/" end + local install_manifest = repo_path .. "install_manifest.json" + local response, error = http.get(install_manifest) if response == nil then @@ -175,6 +188,9 @@ elseif mode == "install" or mode == "update" then -- GET REMOTE MANIFEST -- ------------------------- + if opts[3] then repo_path = repo_path .. opts[3] .. "/" else repo_path = repo_path .. "main/" end + local install_manifest = repo_path .. "install_manifest.json" + local response, error = http.get(install_manifest) if response == nil then @@ -508,7 +524,7 @@ elseif mode == "remove" or mode == "purge" then if mode == "remove" then println("removing all " .. app .. " files except for config.lua and log.txt...") elseif mode == "purge" then - println("purging all " .. app .. " files including config.lua and log.txt...") + println("purging all " .. app .. " files...") end ---@diagnostic disable-next-line: undefined-field @@ -523,11 +539,21 @@ elseif mode == "remove" or mode == "purge" then term.setTextColor(colors.lightGray) -- delete log file if purging - if mode == "purge" then - local config = require(app .. ".config") - if fs.exists(config.LOG_PATH) then - fs.delete(config.LOG_PATH) - println("deleted log file " .. config.LOG_PATH) + if mode == "purge" and fs.exists(config_file) then + local log_deleted = pcall(function () + local config = require(app .. ".config") + if fs.exists(config.LOG_PATH) then + fs.delete(config.LOG_PATH) + println("deleted log file " .. config.LOG_PATH) + end + end) + + if not log_deleted then + term.setTextColor(colors.red) + println("failed to delete log file") + term.setTextColor(colors.white) +---@diagnostic disable-next-line: undefined-field + os.sleep(1) end end @@ -554,8 +580,10 @@ elseif mode == "remove" or mode == "purge" then end end - fs.delete(folder) - println("deleted directory " .. folder) + if fs.isDir(folder) then + fs.delete(folder) + println("deleted directory " .. folder) + end elseif dependency == app then for _, folder in pairs(files) do while true do @@ -567,7 +595,7 @@ elseif mode == "remove" or mode == "purge" then end end - if folder ~= app and fs.exists(folder) then + if folder ~= app and fs.isDir(folder) then fs.delete(folder) println("deleted app subdirectory " .. folder) end From 632e96c8b3820c86829bbf642b25095b3f34de47 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 20:43:39 -0500 Subject: [PATCH 530/587] #117 installer v0.9b cleanup and improvements to check list --- ccmsi.lua | 105 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 73 insertions(+), 32 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index eb59195..437e051 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -3,7 +3,6 @@ -- --[[ - Copyright © 2023 Mikayla Fischler Permission is hereby granted, free of charge, to any person obtaining a copy of this software and @@ -16,13 +15,12 @@ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE A IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ]]-- local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local VERSION = "v0.9a" +local VERSION = "v0.9b" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/" @@ -45,9 +43,7 @@ local function write_install_manifest(manifest, dependencies) end end - if key == app or key == "comms" or is_dependency then - versions[key] = value - end + if key == app or key == "comms" or is_dependency then versions[key] = value end end manifest.versions = versions @@ -86,9 +82,10 @@ if #opts == 0 or opts[1] == "help" then term.setTextColor(colors.white) println("") term.setTextColor(colors.yellow) - println(" second parameter instead of app when used with check") + println(" second parameter when used with check") term.setTextColor(colors.lightGray) - println(" target GitHub tag or branch name instead of main") + println(" note: defaults to main") + println(" target GitHub tag or branch name") return else for _, v in pairs({ "check", "install", "update", "remove", "purge" }) do @@ -131,9 +128,10 @@ if mode == "check" then local response, error = http.get(install_manifest) if response == nil then - term.setTextColor(colors.red) + term.setTextColor(colors.orange) println("failed to get installation manifest from GitHub, cannot update or install") - println("http error " .. error) + term.setTextColor(colors.red) + println("HTTP error: " .. error) term.setTextColor(colors.white) return end @@ -162,25 +160,37 @@ if mode == "check" then if not local_ok then term.setTextColor(colors.yellow) - println("warning: failed to load local installation information") + println("failed to load local installation information") term.setTextColor(colors.white) end + -- list all versions for key, value in pairs(manifest.versions) do term.setTextColor(colors.purple) print(string.format("%-14s", "[" .. key .. "]")) - term.setTextColor(colors.blue) - print(value) if local_ok and (local_manifest.versions[key] ~= nil) then - term.setTextColor(colors.white) - print(" (current ") term.setTextColor(colors.blue) - print(value) - term.setTextColor(colors.white) - println(")") + print(local_manifest.versions[key]) + if value ~= local_manifest.versions[key] then + term.setTextColor(colors.white) + print(" (") + term.setTextColor(colors.cyan) + print(value) + term.setTextColor(colors.white) + println(" available)") + else + term.setTextColor(colors.green) + print(" (up to date)") + end else term.setTextColor(colors.lightGray) - println(" (not installed)") + println("not installed") + term.setTextColor(colors.white) + print(" (") + term.setTextColor(colors.cyan) + print(value) + term.setTextColor(colors.white) + println(" available)") end end elseif mode == "install" or mode == "update" then @@ -194,9 +204,10 @@ elseif mode == "install" or mode == "update" then local response, error = http.get(install_manifest) if response == nil then - term.setTextColor(colors.red) + term.setTextColor(colors.orange) println("failed to get installation manifest from GitHub, cannot update or install") - println("http error " .. error) + term.setTextColor(colors.red) + println("HTTP error: " .. error) term.setTextColor(colors.white) return end @@ -226,6 +237,7 @@ elseif mode == "install" or mode == "update" then local local_comms_version = nil local local_boot_version = nil + -- try to find local versions if not local_ok then if mode == "update" then term.setTextColor(colors.yellow) @@ -257,6 +269,7 @@ elseif mode == "install" or mode == "update" then end term.setTextColor(colors.white) + -- display bootloader version change information if local_boot_version ~= nil then if local_boot_version ~= remote_boot_version then print("[bootldr] updating ") @@ -267,6 +280,11 @@ elseif mode == "install" or mode == "update" then term.setTextColor(colors.blue) println(remote_boot_version) term.setTextColor(colors.white) + elseif mode == "install" then + print("[bootldr] reinstalling ") + term.setTextColor(colors.blue) + print(local_boot_version) + term.setTextColor(colors.white) end else print("[bootldr] new install of ") @@ -275,6 +293,7 @@ elseif mode == "install" or mode == "update" then term.setTextColor(colors.white) end + -- display app version change information if local_app_version ~= nil then if local_app_version ~= remote_app_version then print("[" .. app .. "] updating ") @@ -285,6 +304,11 @@ elseif mode == "install" or mode == "update" then term.setTextColor(colors.blue) println(remote_app_version) term.setTextColor(colors.white) + elseif mode == "install" then + print("[" .. app .. "] reinstalling ") + term.setTextColor(colors.blue) + print(local_app_version) + term.setTextColor(colors.white) end else print("[" .. app .. "] new install of ") @@ -293,6 +317,7 @@ elseif mode == "install" or mode == "update" then term.setTextColor(colors.white) end + -- display comms version change information if local_comms_version ~= nil then if local_comms_version ~= remote_comms_version then print("[comms] updating ") @@ -303,11 +328,15 @@ elseif mode == "install" or mode == "update" then term.setTextColor(colors.blue) println(remote_comms_version) term.setTextColor(colors.white) - print("[comms] ") term.setTextColor(colors.yellow) println("other devices on the network will require an update") term.setTextColor(colors.white) + elseif mode == "install" then + print("[comms] reinstalling ") + term.setTextColor(colors.blue) + print(local_comms_version) + term.setTextColor(colors.white) end else print("[comms] new install of ") @@ -336,10 +365,11 @@ elseif mode == "install" or mode == "update" then space_required = space_required + size end + -- check space constraints if space_available < space_required then single_file_mode = true - term.setTextColor(colors.red) - println("WARNING: Insuffienct space available for a full download!") + term.setTextColor(colors.yellow) + println("WARNING: Insufficient space available for a full download!") term.setTextColor(colors.white) println("Files will be downloaded one by one, so if you are replacing a current install this will not be a problem unless installation fails.") println("Do you wish to continue? (y/N)") @@ -362,6 +392,7 @@ elseif mode == "install" or mode == "update" then fs.makeDir(install_dir) end + -- download all dependencies for _, dependency in pairs(dependencies) do if mode == "update" and ((dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version)) then -- skip system package if unchanged, skip app package if not changed @@ -379,12 +410,12 @@ elseif mode == "install" or mode == "update" then term.setTextColor(colors.lightGray) local files = file_list[dependency] for _, file in pairs(files) do - println("get: " .. file) - local dl, err_c = http.get(repo_path .. file) + println("GET: " .. file) + local dl, err = http.get(repo_path .. file) if dl == nil then term.setTextColor(colors.red) - println("get: error " .. err_c) + println("GET: HTTP Error " .. err) success = false break else @@ -396,6 +427,7 @@ elseif mode == "install" or mode == "update" then end end + -- copy in downloaded files (installation) if success then for _, dependency in pairs(dependencies) do if mode == "update" and ((dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version)) then @@ -446,6 +478,7 @@ elseif mode == "install" or mode == "update" then end end else + -- go through all files and replace one by one for _, dependency in pairs(dependencies) do if mode == "update" and ((dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version)) then -- skip system package if unchanged, skip app package if not changed @@ -463,11 +496,11 @@ elseif mode == "install" or mode == "update" then term.setTextColor(colors.lightGray) local files = file_list[dependency] for _, file in pairs(files) do - println("get: " .. file) - local dl, err_c = http.get(repo_path .. file) + println("GET: " .. file) + local dl, err = http.get(repo_path .. file) if dl == nil then - println("get: error " .. err_c) + println("GET: HTTP Error " .. err) success = false break else @@ -513,7 +546,7 @@ elseif mode == "remove" or mode == "purge" then println("error parsing local installation manifest") term.setTextColor(colors.white) return - elseif manifest.versions[app] == nil then + elseif mode == "remove" and manifest.versions[app] == nil then term.setTextColor(colors.red) println(app .. " is not installed") term.setTextColor(colors.white) @@ -551,7 +584,7 @@ elseif mode == "remove" or mode == "purge" then if not log_deleted then term.setTextColor(colors.red) println("failed to delete log file") - term.setTextColor(colors.white) + term.setTextColor(colors.lightGray) ---@diagnostic disable-next-line: undefined-field os.sleep(1) end @@ -569,6 +602,7 @@ elseif mode == "remove" or mode == "purge" then end end + -- delete folders that we should be deleteing if mode == "purge" or dependency ~= app then local folder = files[1] while true do @@ -603,9 +637,16 @@ elseif mode == "remove" or mode == "purge" then end end + -- only delete manifest if purging if mode == "purge" then fs.delete("install_manifest.json") println("deleted install_manifest.json") + else + -- remove all data from versions list to show nothing is installed + manifest.versions = {} + imfile = fs.open("install_manifest.json", "w") + imfile.write(textutils.serializeJSON(manifest)) + imfile.close() end term.setTextColor(colors.green) From d74a2db8e9bf3e4db6b0694d6726e252741c9a29 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 20:45:48 -0500 Subject: [PATCH 531/587] #117 installer v0.9c fixes to check list --- ccmsi.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index 437e051..400d9e9 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -20,7 +20,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local VERSION = "v0.9b" +local VERSION = "v0.9c" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/" @@ -180,17 +180,17 @@ if mode == "check" then println(" available)") else term.setTextColor(colors.green) - print(" (up to date)") + println(" (up to date)") end else term.setTextColor(colors.lightGray) - println("not installed") + print("not installed") term.setTextColor(colors.white) - print(" (") + print(" (latest ") term.setTextColor(colors.cyan) print(value) term.setTextColor(colors.white) - println(" available)") + println(")") end end elseif mode == "install" or mode == "update" then From 00263b2feb0c60e72eb14e84fa1980a82fe16751 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 21:52:43 -0500 Subject: [PATCH 532/587] #117 installer v0.9d prevent updating when installation isn't present --- ccmsi.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index 400d9e9..a100c4a 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -20,7 +20,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local VERSION = "v0.9c" +local VERSION = "v0.9d" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/" @@ -240,9 +240,10 @@ elseif mode == "install" or mode == "update" then -- try to find local versions if not local_ok then if mode == "update" then - term.setTextColor(colors.yellow) - println("warning: failed to load local installation information") + term.setTextColor(colors.red) + println("failed to load local installation information, cannot update") term.setTextColor(colors.white) + return end else local_app_version = local_manifest.versions[app] From c9526ba601f3a0ca4cba339d635233a50370013f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 21:55:32 -0500 Subject: [PATCH 533/587] #117 installer v0.9e fixed missing newlines on reinstalling message --- ccmsi.lua | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index a100c4a..3c59ae2 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -20,7 +20,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local VERSION = "v0.9d" +local VERSION = "v0.9e" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/" @@ -284,7 +284,7 @@ elseif mode == "install" or mode == "update" then elseif mode == "install" then print("[bootldr] reinstalling ") term.setTextColor(colors.blue) - print(local_boot_version) + println(local_boot_version) term.setTextColor(colors.white) end else @@ -308,7 +308,7 @@ elseif mode == "install" or mode == "update" then elseif mode == "install" then print("[" .. app .. "] reinstalling ") term.setTextColor(colors.blue) - print(local_app_version) + println(local_app_version) term.setTextColor(colors.white) end else @@ -336,7 +336,7 @@ elseif mode == "install" or mode == "update" then elseif mode == "install" then print("[comms] reinstalling ") term.setTextColor(colors.blue) - print(local_comms_version) + println(local_comms_version) term.setTextColor(colors.white) end else @@ -411,12 +411,12 @@ elseif mode == "install" or mode == "update" then term.setTextColor(colors.lightGray) local files = file_list[dependency] for _, file in pairs(files) do - println("GET: " .. file) + println("GET " .. file) local dl, err = http.get(repo_path .. file) if dl == nil then term.setTextColor(colors.red) - println("GET: HTTP Error " .. err) + println("GET HTTP Error " .. err) success = false break else @@ -497,11 +497,11 @@ elseif mode == "install" or mode == "update" then term.setTextColor(colors.lightGray) local files = file_list[dependency] for _, file in pairs(files) do - println("GET: " .. file) + println("GET " .. file) local dl, err = http.get(repo_path .. file) if dl == nil then - println("GET: HTTP Error " .. err) + println("GET HTTP Error " .. err) success = false break else From c4f6c1b289f30031b31d542ef9e150169130521b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 22:41:32 -0500 Subject: [PATCH 534/587] #159 fixed RTU facility level redstone linking --- install_manifest.json | 4 ++-- rtu/startup.lua | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/install_manifest.json b/install_manifest.json index 58131e8..07f929a 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -3,7 +3,7 @@ "bootloader": "0.2", "comms": "1.3.3", "reactor-plc": "beta-v0.10.11", - "rtu": "beta-v0.11.0", + "rtu": "beta-v0.11.1", "supervisor": "beta-v0.11.13", "coordinator": "beta-v0.9.16", "pocket": "alpha-v0.0.0" @@ -181,7 +181,7 @@ "graphics": 99360, "lockbox": 100797, "reactor-plc": 75956, - "rtu": 81511, + "rtu": 81676, "supervisor": 261745, "coordinator": 180622, "pocket": 335 diff --git a/rtu/startup.lua b/rtu/startup.lua index bbe5ee8..bb74421 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.11.0" +local RTU_VERSION = "beta-v0.11.1" local rtu_t = types.rtu_t @@ -131,8 +131,8 @@ local function main() local io_reactor = rtu_redstone[entry_idx].for_reactor ---@type integer -- CHECK: reactor ID must be >= to 1 - 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")) + 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 >= 0")) return false end From 1be57aaf13faeb7acd306c34f17f8627bfff18a8 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 20 Feb 2023 00:49:37 -0500 Subject: [PATCH 535/587] #140 partial build packet updates --- coordinator/coordinator.lua | 12 ++++-- coordinator/startup.lua | 2 +- install_manifest.json | 10 ++--- supervisor/facility.lua | 13 +++--- supervisor/session/coordinator.lua | 53 +++++++++++++++++++------ supervisor/session/plc.lua | 2 +- supervisor/session/rtu.lua | 8 ++-- supervisor/session/rtu/boilerv.lua | 10 ++--- supervisor/session/rtu/imatrix.lua | 10 ++--- supervisor/session/rtu/sna.lua | 10 ++--- supervisor/session/rtu/sps.lua | 10 ++--- supervisor/session/rtu/turbinev.lua | 10 ++--- supervisor/session/rtu/unit_session.lua | 2 +- supervisor/session/svqtypes.lua | 5 ++- supervisor/session/svsessions.lua | 19 ++++----- supervisor/startup.lua | 2 +- supervisor/unit.lua | 31 ++++++++++----- 17 files changed, 128 insertions(+), 81 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index ff8bcee..c62bbe1 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -465,11 +465,15 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range end elseif packet.type == SCADA_CRDN_TYPES.UNIT_BUILDS then -- record builds - if iocontrol.record_unit_builds(packet.data) then - -- acknowledge receipt of builds - _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.UNIT_BUILDS, {}) + if packet.length == 1 then + if iocontrol.record_unit_builds(packet.data[1]) then + -- acknowledge receipt of builds + _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.UNIT_BUILDS, {}) + else + log.error("received invalid UNIT_BUILDS packet") + end else - log.error("received invalid UNIT_BUILDS packet") + log.debug("UNIT_BUILDS packet length mismatch") end elseif packet.type == SCADA_CRDN_TYPES.UNIT_STATUSES then -- update statuses diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 1623615..85670b4 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.16" +local COORDINATOR_VERSION = "beta-v0.10.0" local print = util.print local println = util.println diff --git a/install_manifest.json b/install_manifest.json index 07f929a..910a948 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -4,8 +4,8 @@ "comms": "1.3.3", "reactor-plc": "beta-v0.10.11", "rtu": "beta-v0.11.1", - "supervisor": "beta-v0.11.13", - "coordinator": "beta-v0.9.16", + "supervisor": "beta-v0.12.0", + "coordinator": "beta-v0.10.0", "pocket": "alpha-v0.0.0" }, "files": { @@ -180,10 +180,10 @@ "common": 88049, "graphics": 99360, "lockbox": 100797, - "reactor-plc": 75956, + "reactor-plc": 75915, "rtu": 81676, - "supervisor": 261745, - "coordinator": 180622, + "supervisor": 265030, + "coordinator": 180849, "pocket": 335 } } \ No newline at end of file diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 2dd07d5..fe9db20 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -814,13 +814,16 @@ function facility.new(num_reactors, cooling_conf) -- READ STATES/PROPERTIES -- -- get build properties of all machines - function public.get_build() + ---@param inc_imatrix boolean? true/nil to include induction matrix build, false to exclude + function public.get_build(inc_imatrix) local build = {} - build.induction = {} - for i = 1, #self.induction do - local matrix = self.induction[i] ---@type unit_session - build.induction[matrix.get_device_idx()] = { matrix.get_db().formed, matrix.get_db().build } + if inc_imatrix ~= false then + build.induction = {} + for i = 1, #self.induction do + local matrix = self.induction[i] ---@type unit_session + build.induction[matrix.get_device_idx()] = { matrix.get_db().formed, matrix.get_db().build } + end end return build diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index e49e065..ab829f4 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -12,6 +12,7 @@ local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES local UNIT_COMMANDS = comms.UNIT_COMMANDS local FAC_COMMANDS = comms.FAC_COMMANDS +local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES local SV_Q_CMDS = svqtypes.SV_Q_CMDS local SV_Q_DATA = svqtypes.SV_Q_DATA @@ -24,13 +25,15 @@ local println_ts = util.println_ts -- retry time constants in ms local INITIAL_WAIT = 1500 local RETRY_PERIOD = 1000 +local PARTIAL_RETRY_PERIOD = 2000 local CRD_S_CMDS = { - RESEND_BUILDS = 1 } local CRD_S_DATA = { - CMD_ACK = 1 + CMD_ACK = 1, + RESEND_PLC_BUILD = 2, + RESEND_RTU_BUILD = 3 } coordinator.CRD_S_CMDS = CRD_S_CMDS @@ -129,7 +132,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) builds[unit.get_id()] = unit.get_build() end - _send(SCADA_CRDN_TYPES.UNIT_BUILDS, builds) + _send(SCADA_CRDN_TYPES.UNIT_BUILDS, { builds }) end -- send facility status @@ -350,15 +353,6 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) _handle_packet(message.message) elseif message.qtype == mqueue.TYPE.COMMAND then -- handle instruction - local cmd = message.message - if cmd == CRD_S_CMDS.RESEND_BUILDS then - -- re-send builds - self.retry_times.builds_packet = util.time() + RETRY_PERIOD - _send_fac_builds() - _send_unit_builds() - else - log.warning(log_header .. "unsupported command received in in_queue (this is a bug)") - end elseif message.qtype == mqueue.TYPE.DATA then -- instruction with body local cmd = message.message ---@type queue_data @@ -366,6 +360,41 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) if cmd.key == CRD_S_DATA.CMD_ACK then local ack = cmd.val ---@type coord_ack _send(SCADA_CRDN_TYPES.UNIT_CMD, { ack.cmd, ack.unit, ack.ack }) + elseif cmd.key == CRD_S_DATA.RESEND_PLC_BUILD then + -- re-send PLC build + -- retry logic will be kept as-is, so as long as no retry is needed, this will be a small update + self.retry_times.builds_packet = util.time() + PARTIAL_RETRY_PERIOD + self.acks.unit_builds = false + + local unit_id = cmd.val + local builds = {} + + local unit = self.units[unit_id] ---@type reactor_unit + builds[unit_id] = unit.get_build(true, false, false) + + _send(SCADA_CRDN_TYPES.UNIT_BUILDS, { builds }) + elseif cmd.key == CRD_S_DATA.RESEND_RTU_BUILD then + local unit_id = cmd.val.unit + if unit_id > 0 then + -- re-send unit RTU builds + -- retry logic will be kept as-is, so as long as no retry is needed, this will be a small update + self.retry_times.u_builds_packet = util.time() + PARTIAL_RETRY_PERIOD + self.acks.unit_builds = false + + local builds = {} + + local unit = self.units[unit_id] ---@type reactor_unit + builds[unit_id] = unit.get_build(false, cmd.val.type == RTU_UNIT_TYPES.BOILER_VALVE, cmd.val.type == RTU_UNIT_TYPES.TURBINE_VALVE) + + _send(SCADA_CRDN_TYPES.UNIT_BUILDS, { builds }) + else + -- re-send facility RTU builds + -- retry logic will be kept as-is, so as long as no retry is needed, this will be a small update + self.retry_times.f_builds_packet = util.time() + PARTIAL_RETRY_PERIOD + self.acks.fac_builds = false + + _send(SCADA_CRDN_TYPES.FAC_BUILDS, { facility.get_build(cmd.val.type == RTU_UNIT_TYPES.IMATRIX) }) + end else log.warning(log_header .. "unsupported data command received in in_queue (this is a bug)") end diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index f78c6e6..3d3270b 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -342,7 +342,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) if status then -- copied in structure data OK self.received_struct = true - self.out_q.push_command(svqtypes.SV_Q_CMDS.BUILD_CHANGED) + self.out_q.push_data(svqtypes.SV_Q_DATA.PLC_BUILD_CHANGED, for_reactor) else -- error copying structure data log.error(log_header .. "failed to parse struct packet data") diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 35eaf87..1db89cb 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -385,12 +385,12 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili _send_modbus(msg.message) elseif msg.qtype == mqueue.TYPE.COMMAND then -- handle instruction - local cmd = msg.message - if cmd == unit_session.RTU_US_CMDS.BUILD_CHANGED then - self.out_q.push_command(svqtypes.SV_Q_CMDS.BUILD_CHANGED) - end elseif msg.qtype == mqueue.TYPE.DATA then -- instruction with body + local cmd = msg.message ---@type queue_data + if cmd.key == unit_session.RTU_US_DATA.BUILD_CHANGED then + self.out_q.push_data(svqtypes.SV_Q_DATA.RTU_BUILD_CHANGED, cmd.val) + end end end end diff --git a/supervisor/session/rtu/boilerv.lua b/supervisor/session/rtu/boilerv.lua index 2f3b5b5..0cc4945 100644 --- a/supervisor/session/rtu/boilerv.lua +++ b/supervisor/session/rtu/boilerv.lua @@ -32,10 +32,10 @@ local PERIODICS = { } -- create a new boilerv rtu session runner ----@param session_id integer ----@param unit_id integer ----@param advert rtu_advertisement ----@param out_queue mqueue +---@param session_id integer RTU session ID +---@param unit_id integer RTU unit ID +---@param advert rtu_advertisement RTU advertisement table +---@param out_queue mqueue RTU unit message out queue function boilerv.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPES.BOILER_VALVE then @@ -161,7 +161,7 @@ function boilerv.new(session_id, unit_id, advert, out_queue) self.db.build.max_boil_rate = m_pkt.data[12] self.has_build = true - out_queue.push_command(unit_session.RTU_US_CMDS.BUILD_CHANGED) + out_queue.push_data(unit_session.RTU_US_DATA.BUILD_CHANGED, { unit = advert.reactor, type = advert.type }) else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua index 3ab5d4a..607c22d 100644 --- a/supervisor/session/rtu/imatrix.lua +++ b/supervisor/session/rtu/imatrix.lua @@ -32,10 +32,10 @@ local PERIODICS = { } -- create a new imatrix rtu session runner ----@param session_id integer ----@param unit_id integer ----@param advert rtu_advertisement ----@param out_queue mqueue +---@param session_id integer RTU session ID +---@param unit_id integer RTU unit ID +---@param advert rtu_advertisement RTU advertisement table +---@param out_queue mqueue RTU unit message out queue function imatrix.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPES.IMATRIX then @@ -145,7 +145,7 @@ function imatrix.new(session_id, unit_id, advert, out_queue) self.db.build.providers = m_pkt.data[9] self.has_build = true - out_queue.push_command(unit_session.RTU_US_CMDS.BUILD_CHANGED) + out_queue.push_data(unit_session.RTU_US_DATA.BUILD_CHANGED, { unit = advert.reactor, type = advert.type }) else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end diff --git a/supervisor/session/rtu/sna.lua b/supervisor/session/rtu/sna.lua index cd3415f..e2a667e 100644 --- a/supervisor/session/rtu/sna.lua +++ b/supervisor/session/rtu/sna.lua @@ -29,10 +29,10 @@ local PERIODICS = { } -- create a new sna rtu session runner ----@param session_id integer ----@param unit_id integer ----@param advert rtu_advertisement ----@param out_queue mqueue +---@param session_id integer RTU session ID +---@param unit_id integer RTU unit ID +---@param advert rtu_advertisement RTU advertisement table +---@param out_queue mqueue RTU unit message out queue function sna.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPES.SNA then @@ -113,7 +113,7 @@ function sna.new(session_id, unit_id, advert, out_queue) self.db.build.output_cap = m_pkt.data[2] self.has_build = true - out_queue.push_command(unit_session.RTU_US_CMDS.BUILD_CHANGED) + out_queue.push_data(unit_session.RTU_US_DATA.BUILD_CHANGED, { unit = advert.reactor, type = advert.type }) else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua index 3a6f4c3..9b07f3e 100644 --- a/supervisor/session/rtu/sps.lua +++ b/supervisor/session/rtu/sps.lua @@ -32,10 +32,10 @@ local PERIODICS = { } -- create a new sps rtu session runner ----@param session_id integer ----@param unit_id integer ----@param advert rtu_advertisement ----@param out_queue mqueue +---@param session_id integer RTU session ID +---@param unit_id integer RTU unit ID +---@param advert rtu_advertisement RTU advertisement table +---@param out_queue mqueue RTU unit message out queue function sps.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPES.SPS then @@ -150,7 +150,7 @@ function sps.new(session_id, unit_id, advert, out_queue) self.db.build.max_energy = m_pkt.data[9] self.has_build = true - out_queue.push_command(unit_session.RTU_US_CMDS.BUILD_CHANGED) + out_queue.push_data(unit_session.RTU_US_DATA.BUILD_CHANGED, { unit = advert.reactor, type = advert.type }) else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua index 0ac793a..3f8357f 100644 --- a/supervisor/session/rtu/turbinev.lua +++ b/supervisor/session/rtu/turbinev.lua @@ -44,10 +44,10 @@ local PERIODICS = { } -- create a new turbinev rtu session runner ----@param session_id integer ----@param unit_id integer ----@param advert rtu_advertisement ----@param out_queue mqueue +---@param session_id integer RTU session ID +---@param unit_id integer RTU unit ID +---@param advert rtu_advertisement RTU advertisement table +---@param out_queue mqueue RTU unit message out queue function turbinev.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPES.TURBINE_VALVE then @@ -192,7 +192,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue) self.db.build.max_water_output = m_pkt.data[15] self.has_build = true - out_queue.push_command(unit_session.RTU_US_CMDS.BUILD_CHANGED) + out_queue.push_data(unit_session.RTU_US_DATA.BUILD_CHANGED, { unit = advert.reactor, type = advert.type }) else log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") end diff --git a/supervisor/session/rtu/unit_session.lua b/supervisor/session/rtu/unit_session.lua index f6e4297..27b21c0 100644 --- a/supervisor/session/rtu/unit_session.lua +++ b/supervisor/session/rtu/unit_session.lua @@ -13,10 +13,10 @@ local MODBUS_FCODE = types.MODBUS_FCODE local MODBUS_EXCODE = types.MODBUS_EXCODE local RTU_US_CMDS = { - BUILD_CHANGED = 1 } local RTU_US_DATA = { + BUILD_CHANGED = 1 } unit_session.RTU_US_CMDS = RTU_US_CMDS diff --git a/supervisor/session/svqtypes.lua b/supervisor/session/svqtypes.lua index 09ef4f1..6503d59 100644 --- a/supervisor/session/svqtypes.lua +++ b/supervisor/session/svqtypes.lua @@ -1,7 +1,6 @@ local svqtypes = {} local SV_Q_CMDS = { - BUILD_CHANGED = 1 } local SV_Q_DATA = { @@ -10,7 +9,9 @@ local SV_Q_DATA = { RESET_RPS = 3, SET_BURN = 4, __END_PLC_CMDS__ = 5, - CRDN_ACK = 6 + CRDN_ACK = 6, + PLC_BUILD_CHANGED = 7, + RTU_BUILD_CHANGED = 8 } ---@class coord_ack diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index b70096e..b7c8ef5 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -62,11 +62,6 @@ local function _sv_handle_outq(session) self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) elseif msg.qtype == mqueue.TYPE.COMMAND then -- handle instruction/notification - local cmd = msg.message - if (cmd == SV_Q_CMDS.BUILD_CHANGED) and (svsessions.get_coord_session() ~= nil) then - -- notify coordinator that a build has changed - svsessions.get_coord_session().in_queue.push_command(CRD_S_CMDS.RESEND_BUILDS) - end elseif msg.qtype == mqueue.TYPE.DATA then -- instruction/notification with body local cmd = msg.message ---@type queue_data @@ -89,11 +84,17 @@ local function _sv_handle_outq(session) end end else - if cmd.key == SV_Q_DATA.CRDN_ACK then - -- ack to be sent to coordinator - local crd_s = svsessions.get_coord_session() - if crd_s ~= nil then + local crd_s = svsessions.get_coord_session() + if crd_s ~= nil then + if cmd.key == SV_Q_DATA.CRDN_ACK then + -- ack to be sent to coordinator crd_s.in_queue.push_data(CRD_S_DATA.CMD_ACK, cmd.val) + elseif cmd.key == SV_Q_DATA.PLC_BUILD_CHANGED then + -- a PLC build has changed + crd_s.in_queue.push_data(CRD_S_DATA.RESEND_PLC_BUILD, cmd.val) + elseif cmd.key == SV_Q_DATA.RTU_BUILD_CHANGED then + -- an RTU build has changed + crd_s.in_queue.push_data(CRD_S_DATA.RESEND_RTU_BUILD, cmd.val) end end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index b380410..8e8246e 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.11.13" +local SUPERVISOR_VERSION = "beta-v0.12.0" local print = util.print local println = util.println diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 1eff099..258b297 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -678,23 +678,32 @@ function unit.new(for_reactor, num_boilers, num_turbines) end -- get build properties of all machines - function public.get_build() + ---@param inc_plc boolean? true/nil to include PLC build, false to exclude + ---@param inc_boilers boolean? true/nil to include boiler builds, false to exclude + ---@param inc_turbines boolean? true/nil to include turbine builds, false to exclude + function public.get_build(inc_plc, inc_boilers, inc_turbines) local build = {} - if self.plc_i ~= nil then - build.reactor = self.plc_i.get_struct() + if inc_plc ~= false then + if self.plc_i ~= nil then + build.reactor = self.plc_i.get_struct() + end end - build.boilers = {} - for i = 1, #self.boilers do - local boiler = self.boilers[i] ---@type unit_session - build.boilers[boiler.get_device_idx()] = { boiler.get_db().formed, boiler.get_db().build } + if inc_boilers ~= false then + build.boilers = {} + for i = 1, #self.boilers do + local boiler = self.boilers[i] ---@type unit_session + build.boilers[boiler.get_device_idx()] = { boiler.get_db().formed, boiler.get_db().build } + end end - build.turbines = {} - for i = 1, #self.turbines do - local turbine = self.turbines[i] ---@type unit_session - build.turbines[turbine.get_device_idx()] = { turbine.get_db().formed, turbine.get_db().build } + if inc_turbines ~= false then + build.turbines = {} + for i = 1, #self.turbines do + local turbine = self.turbines[i] ---@type unit_session + build.turbines[turbine.get_device_idx()] = { turbine.get_db().formed, turbine.get_db().build } + end end return build From 8df67245c58388e5d6e631de2cb26e33a53a551f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 20 Feb 2023 12:08:51 -0500 Subject: [PATCH 536/587] #171 unit auto SCRAM and improvements to emergency coolant control --- install_manifest.json | 12 +++---- reactor-plc/plc.lua | 4 +-- reactor-plc/startup.lua | 2 +- scada-common/rsio.lua | 2 +- supervisor/startup.lua | 2 +- supervisor/unit.lua | 32 ++++++++++------- supervisor/unitlogic.lua | 78 ++++++++++++++++++++++++++++++++++++---- 7 files changed, 102 insertions(+), 30 deletions(-) diff --git a/install_manifest.json b/install_manifest.json index 910a948..b7201b0 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -2,9 +2,9 @@ "versions": { "bootloader": "0.2", "comms": "1.3.3", - "reactor-plc": "beta-v0.10.11", + "reactor-plc": "beta-v0.11.0", "rtu": "beta-v0.11.1", - "supervisor": "beta-v0.12.0", + "supervisor": "beta-v0.12.1", "coordinator": "beta-v0.10.0", "pocket": "alpha-v0.0.0" }, @@ -177,12 +177,12 @@ }, "sizes": { "system": 1982, - "common": 88049, + "common": 88021, "graphics": 99360, "lockbox": 100797, - "reactor-plc": 75915, - "rtu": 81676, - "supervisor": 265030, + "reactor-plc": 75902, + "rtu": 81679, + "supervisor": 267633, "coordinator": 180849, "pocket": 335 } diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 5e7297c..f6bbe5d 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -29,7 +29,7 @@ local PCALL_START_MSG = "pcall: Reactor is already active." local MAX_DAMAGE_PERCENT = 90 local MAX_DAMAGE_TEMPERATURE = 1200 -local MIN_COOLANT_FILL = 0.02 +local MIN_COOLANT_FILL = 0.10 local MAX_WASTE_FILL = 0.8 local MAX_HEATED_COLLANT_FILL = 0.95 @@ -206,7 +206,7 @@ function plc.rps_init(reactor, is_formed) self.state[state_keys.manual] = true end - -- automatic SCRAM commanded by supervisor/coordinator + -- automatic SCRAM commanded by supervisor function public.trip_auto() self.state[state_keys.automatic] = true end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 52b7229..4380c33 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.10.11" +local R_PLC_VERSION = "beta-v0.11.0" local print = util.print local println = util.println diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index ed17a3f..79ced10 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -299,7 +299,7 @@ end ---@param level IO_LVL ---@return boolean|nil function rsio.digital_is_active(port, level) - if (not util.is_int(port)) or (port > IO_PORT.U_ACK) then + if not util.is_int(port) then return nil elseif level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then return nil diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 8e8246e..fa51cba 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.12.0" +local SUPERVISOR_VERSION = "beta-v0.12.1" local print = util.print local println = util.println diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 258b297..9b0849c 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -82,7 +82,10 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- redstone control io_ctl = nil, ---@type rs_controller valves = {}, ---@type unit_valves + emcool_opened = false, -- auto control + auto_engaged = false, + auto_was_alarmed = false, ramp_target_br100 = 0, -- state tracking deltas = {}, @@ -141,25 +144,25 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- radiation monitor alarm for this unit ContainmentRadiation = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ContainmentRadiation, tier = PRIO.CRITICAL }, -- reactor offline after being online - ReactorLost = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorLost, tier = PRIO.URGENT }, + ReactorLost = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorLost, tier = PRIO.TIMELY }, -- damage >100% CriticalDamage = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.CriticalDamage, tier = PRIO.CRITICAL }, -- reactor damage increasing ReactorDamage = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorDamage, tier = PRIO.EMERGENCY }, -- reactor >1200K ReactorOverTemp = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorOverTemp, tier = PRIO.URGENT }, - -- reactor >1100K - ReactorHighTemp = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 2, id = ALARM.ReactorHighTemp, tier = PRIO.TIMELY }, + -- reactor >1150K + ReactorHighTemp = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 1, id = ALARM.ReactorHighTemp, tier = PRIO.TIMELY }, -- waste = 100% ReactorWasteLeak = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorWasteLeak, tier = PRIO.EMERGENCY }, -- waste >85% - ReactorHighWaste = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 2, id = ALARM.ReactorHighWaste, tier = PRIO.TIMELY }, + ReactorHighWaste = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 2, id = ALARM.ReactorHighWaste, tier = PRIO.URGENT }, -- RPS trip occured - RPSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 1, id = ALARM.RPSTransient, tier = PRIO.TIMELY }, + RPSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 2, id = ALARM.RPSTransient, tier = PRIO.TIMELY }, -- BoilRateMismatch, CoolantFeedMismatch, SteamFeedMismatch, MaxWaterReturnFeed RCSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 5, id = ALARM.RCSTransient, tier = PRIO.TIMELY }, -- "It's just a routine turbin' trip!" -Bill Gibson, "The China Syndrome" - TurbineTrip = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 1, id = ALARM.TurbineTrip, tier = PRIO.URGENT } + TurbineTrip = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 2, id = ALARM.TurbineTrip, tier = PRIO.URGENT } }, ---@class unit_db db = { @@ -488,12 +491,17 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- update alarm status logic.update_alarms(self) + -- if in auto mode, SCRAM on certain alarms + logic.update_auto_safety(public, self) + -- update status text logic.update_status_text(self) -- handle redstone I/O if #self.redstone > 0 then logic.handle_redstone(self) + elseif not self.plc_cache.rps_trip then + self.emcool_opened = false end end @@ -502,7 +510,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- engage automatic control function public.a_engage() - self.db.annunciator.AutoControl = true + self.auto_engaged = true if self.plc_i ~= nil then self.plc_i.auto_lock(true) end @@ -510,7 +518,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- disengage automatic control function public.a_disengage() - self.db.annunciator.AutoControl = false + self.auto_engaged = false if self.plc_i ~= nil then self.plc_i.auto_lock(false) self.db.control.br100 = 0 @@ -533,7 +541,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- set the automatic burn rate based on the last set burn rate in 100ths ---@param ramp boolean true to ramp to rate, false to set right away function public.a_commit_br100(ramp) - if self.db.annunciator.AutoControl then + if self.auto_engaged then if self.plc_i ~= nil then self.plc_i.auto_set_burn(self.db.control.br100 / 100, ramp) @@ -562,7 +570,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- queue a command to clear timeout/auto-scram if set function public.a_cond_rps_reset() - if self.plc_s ~= nil and self.plc_i ~= nil then + if self.plc_s ~= nil and self.plc_i ~= nil and (not self.auto_was_alarmed) and (not self.emcool_opened) then local rps = self.plc_i.get_rps() if rps.timeout or rps.automatic then self.plc_i.auto_lock(true) -- if it timed out/restarted, auto lock was lost, so re-lock it @@ -668,8 +676,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- check if a critical alarm is tripped function public.has_critical_alarm() - for _, data in pairs(self.alarms) do - if data.tier == PRIO.CRITICAL and (data.state == AISTATE.TRIPPED or data.state == AISTATE.ACKED) then + for _, alarm in pairs(self.alarms) do + if alarm.tier == PRIO.CRITICAL and (alarm.state == AISTATE.TRIPPED or alarm.state == AISTATE.ACKED) then return true end end diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 0709428..836dad2 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -5,6 +5,7 @@ local util = require("scada-common.util") local plc = require("supervisor.session.plc") +local PRIO = types.ALARM_PRIORITY local ALARM_STATE = types.ALARM_STATE local TRI_FAIL = types.TRI_FAIL @@ -50,6 +51,8 @@ function logic.update_annunciator(self) -- REACTOR -- ------------- + self.db.annunciator.AutoControl = self.auto_engaged + -- check PLC status self.db.annunciator.PLCOnline = self.plc_i ~= nil @@ -109,7 +112,7 @@ function logic.update_annunciator(self) self.db.annunciator.AutoReactorSCRAM = plc_db.rps_trip_cause == types.rps_status_t.automatic self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.no_cool) self.db.annunciator.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < -2.0 - self.db.annunciator.CoolantLevelLow = plc_db.mek_status.ccool_fill < 0.5 + self.db.annunciator.CoolantLevelLow = plc_db.mek_status.ccool_fill < 0.4 self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > 1000 self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > 100 self.db.annunciator.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < -1.0 or plc_db.mek_status.fuel_fill <= 0.01 @@ -515,6 +518,37 @@ function logic.update_alarms(self) end end +-- update the internal automatic safety control performed while in auto control mode +---@param public reactor_unit reactor unit public functions +---@param self _unit_self unit instance +function logic.update_auto_safety(public, self) + local AISTATE = self.types.AISTATE + + if self.auto_engaged then + local alarmed = false + + for _, alarm in pairs(self.alarms) do + if alarm.tier <= PRIO.URGENT and (alarm.state == AISTATE.TRIPPED or alarm.state == AISTATE.ACKED) then + if not self.auto_was_alarmed then + log.info(util.c("UNIT ", self.r_id, " AUTO SCRAM due to ALARM ", alarm.id, " (", types.alarm_string[alarm.id], ") [PRIORITY ", + types.alarm_prio_string[alarm.tier + 1],"]")) + end + + alarmed = true + break + end + end + + if alarmed and not self.plc_cache.rps_status.automatic then + public.a_scram() + end + + self.auto_was_alarmed = alarmed + else + self.auto_was_alarmed = false + end +end + -- update the two unit status text messages ---@param self _unit_self unit instance function logic.update_status_text(self) @@ -570,6 +604,8 @@ function logic.update_status_text(self) self.status_text = { "WASTE LEVEL HIGH", "waste accumulating in reactor" } elseif is_active(self.alarms.TurbineTrip) then self.status_text = { "TURBINE TRIP", "turbine stall occured" } + elseif self.emcool_opened then + self.status_text = { "EMERGENCY COOLANT OPENED", "reset RPS to close valve" } -- connection dependent states elseif self.plc_i ~= nil then local plc_db = self.plc_i.get_db() @@ -641,6 +677,15 @@ end -- handle unit redstone I/O ---@param self _unit_self unit instance function logic.handle_redstone(self) + local AISTATE = self.types.AISTATE + + -- check if an alarm is active (tripped or ack'd) + ---@param alarm table alarm entry + ---@return boolean active + local function is_active(alarm) + return alarm.state == AISTATE.TRIPPED or alarm.state == AISTATE.ACKED + end + -- reactor controls if self.plc_s ~= nil then if (not self.plc_cache.rps_status.manual) and self.io_ctl.digital_read(IO.R_SCRAM) then @@ -653,7 +698,7 @@ function logic.handle_redstone(self) self.plc_s.in_queue.push_command(PLC_S_CMDS.RPS_RESET) end - if (not self.db.annunciator.AutoControl) and (not self.plc_cache.active) and + if (not self.auto_engaged) and (not self.plc_cache.active) and (not self.plc_cache.rps_trip) and self.io_ctl.digital_read(IO.R_ACTIVE) then -- reactor enable requested and allowable, but not yet done; perform it self.plc_s.in_queue.push_command(PLC_S_CMDS.ENABLE) @@ -671,7 +716,7 @@ function logic.handle_redstone(self) -- write reactor status outputs self.io_ctl.digital_write(IO.R_ACTIVE, self.plc_cache.active) - self.io_ctl.digital_write(IO.R_AUTO_CTRL, self.db.annunciator.AutoControl) + self.io_ctl.digital_write(IO.R_AUTO_CTRL, self.auto_engaged) self.io_ctl.digital_write(IO.R_SCRAMMED, self.plc_cache.rps_trip) self.io_ctl.digital_write(IO.R_AUTO_SCRAM, self.plc_cache.rps_status.automatic) self.io_ctl.digital_write(IO.R_DMG_CRIT, self.plc_cache.rps_status.dmg_crit) @@ -695,13 +740,32 @@ function logic.handle_redstone(self) self.io_ctl.digital_write(IO.U_ALARM, has_alarm) - -- check if emergency coolant is needed - if self.plc_cache.rps_status.no_cool then - self.valves.emer_cool.open() - elseif not self.plc_cache.rps_trip then + ----------------------- + -- Emergency Coolant -- + ----------------------- + + local enable_emer_cool = self.plc_cache.rps_status.no_cool or + (self.auto_engaged and self.db.annunciator.CoolantLevelLow and is_active(self.alarms.ReactorOverTemp)) + + if not self.plc_cache.rps_trip then -- can't turn off on sufficient coolant level since it might drop again -- turn off once system is OK again + -- if auto control is engaged, alarm check will SCRAM on reactor over temp so that's covered self.valves.emer_cool.close() + + if self.db.annunciator.EmergencyCoolant > 1 and self.emcool_opened then + log.info(util.c("UNIT ", self.r_id, " emergency coolant valve closed")) + end + + self.emcool_opened = false + elseif enable_emer_cool or self.emcool_opened then + self.valves.emer_cool.open() + + if self.db.annunciator.EmergencyCoolant > 1 and not self.emcool_opened then + log.info(util.c("UNIT ", self.r_id, " emergency coolant valve opened")) + end + + self.emcool_opened = true end end From e2d2a0f1dccd22bf0565d30af7f935962aa62c0f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 20 Feb 2023 14:50:20 -0500 Subject: [PATCH 537/587] #172 fixed bug with full builds not being sent --- coordinator/coordinator.lua | 17 ++++++++++++++++- coordinator/startup.lua | 2 +- install_manifest.json | 16 ++++++++-------- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- scada-common/comms.lua | 18 ++++++++++-------- supervisor/session/coordinator.lua | 29 +++++++++++++++++++++++++---- supervisor/startup.lua | 2 +- 8 files changed, 63 insertions(+), 25 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index c62bbe1..0e23b4f 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -422,7 +422,22 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range -- handle packet if protocol == PROTOCOLS.SCADA_CRDN then if self.sv_linked then - if packet.type == SCADA_CRDN_TYPES.FAC_BUILDS then + if packet.type == SCADA_CRDN_TYPES.INITIAL_BUILDS then + if packet.length == 2 then + -- record builds + local fac_builds = iocontrol.record_facility_builds(packet.data[1]) + local unit_builds = iocontrol.record_unit_builds(packet.data[2]) + + if fac_builds and unit_builds then + -- acknowledge receipt of builds + _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.INITIAL_BUILDS, {}) + else + log.error("received invalid INITIAL_BUILDS packet") + end + else + log.debug("INITIAL_BUILDS packet length mismatch") + end + elseif packet.type == SCADA_CRDN_TYPES.FAC_BUILDS then if packet.length == 1 then -- record facility builds if iocontrol.record_facility_builds(packet.data[1]) then diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 85670b4..aebb5dd 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.10.0" +local COORDINATOR_VERSION = "beta-v0.10.1" local print = util.print local println = util.println diff --git a/install_manifest.json b/install_manifest.json index b7201b0..f68183c 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1,11 +1,11 @@ { "versions": { "bootloader": "0.2", - "comms": "1.3.3", - "reactor-plc": "beta-v0.11.0", - "rtu": "beta-v0.11.1", - "supervisor": "beta-v0.12.1", - "coordinator": "beta-v0.10.0", + "comms": "1.4.0", + "reactor-plc": "beta-v0.11.1", + "rtu": "beta-v0.11.2", + "supervisor": "beta-v0.12.2", + "coordinator": "beta-v0.10.1", "pocket": "alpha-v0.0.0" }, "files": { @@ -177,13 +177,13 @@ }, "sizes": { "system": 1982, - "common": 88021, + "common": 88163, "graphics": 99360, "lockbox": 100797, "reactor-plc": 75902, "rtu": 81679, - "supervisor": 267633, - "coordinator": 180849, + "supervisor": 268416, + "coordinator": 181783, "pocket": 335 } } \ No newline at end of file diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 4380c33..9fefc1d 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.11.0" +local R_PLC_VERSION = "beta-v0.11.1" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index bb74421..418921c 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.11.1" +local RTU_VERSION = "beta-v0.11.2" local rtu_t = types.rtu_t diff --git a/scada-common/comms.lua b/scada-common/comms.lua index ad7d460..b275622 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -14,7 +14,7 @@ local insert = table.insert local max_distance = nil -comms.version = "1.3.3" +comms.version = "1.4.0" ---@alias PROTOCOLS integer local PROTOCOLS = { @@ -51,12 +51,13 @@ local SCADA_MGMT_TYPES = { ---@alias SCADA_CRDN_TYPES integer local SCADA_CRDN_TYPES = { - FAC_BUILDS = 0, -- facility RTU builds - FAC_STATUS = 1, -- state of facility and facility devices - FAC_CMD = 2, -- faility command - UNIT_BUILDS = 3, -- build of each reactor unit (reactor + RTUs) - UNIT_STATUSES = 4, -- state of each of the reactor units - UNIT_CMD = 5 -- command a reactor unit + INITIAL_BUILDS = 0, -- initial, complete builds packet to the coordinator + FAC_BUILDS = 1, -- facility RTU builds + FAC_STATUS = 2, -- state of facility and facility devices + FAC_CMD = 3, -- faility command + UNIT_BUILDS = 4, -- build of each reactor unit (reactor + RTUs) + UNIT_STATUSES = 5, -- state of each of the reactor units + UNIT_CMD = 6 -- command a reactor unit } ---@alias CAPI_TYPES integer @@ -532,7 +533,8 @@ function comms.crdn_packet() -- check that type is known local function _crdn_type_valid() - return self.type == SCADA_CRDN_TYPES.FAC_BUILDS or + return self.type == SCADA_CRDN_TYPES.INITIAL_BUILDS or + self.type == SCADA_CRDN_TYPES.FAC_BUILDS or self.type == SCADA_CRDN_TYPES.FAC_STATUS or self.type == SCADA_CRDN_TYPES.FAC_CMD or self.type == SCADA_CRDN_TYPES.UNIT_BUILDS or diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index ab829f4..55bbcc1 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -71,11 +71,13 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) }, -- when to next retry one of these messages retry_times = { + builds_packet = 0, f_builds_packet = 0, u_builds_packet = 0 }, -- message acknowledgements acks = { + builds = false, fac_builds = false, unit_builds = false } @@ -115,16 +117,25 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) self.seq_num = self.seq_num + 1 end + -- send both facility and unit builds + local function _send_all_builds() + local unit_builds = {} + + for i = 1, #self.units do + local unit = self.units[i] ---@type reactor_unit + unit_builds[unit.get_id()] = unit.get_build() + end + + _send(SCADA_CRDN_TYPES.INITIAL_BUILDS, { facility.get_build(), unit_builds }) + end + -- send facility builds local function _send_fac_builds() - self.acks.fac_builds = false _send(SCADA_CRDN_TYPES.FAC_BUILDS, { facility.get_build() }) end -- send unit builds local function _send_unit_builds() - self.acks.unit_builds = false - local builds = {} for i = 1, #self.units do @@ -206,7 +217,10 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) end elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_CRDN then - if pkt.type == SCADA_CRDN_TYPES.FAC_BUILDS then + if pkt.type == SCADA_CRDN_TYPES.INITIAL_BUILDS then + -- acknowledgement to coordinator receiving builds + self.acks.builds = true + elseif pkt.type == SCADA_CRDN_TYPES.FAC_BUILDS then -- acknowledgement to coordinator receiving builds self.acks.fac_builds = true elseif pkt.type == SCADA_CRDN_TYPES.FAC_CMD then @@ -450,6 +464,13 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) -- builds packet retries + if not self.acks.builds then + if rtimes.builds_packet - util.time() <= 0 then + _send_all_builds() + rtimes.builds_packet = util.time() + RETRY_PERIOD + end + end + if not self.acks.fac_builds then if rtimes.f_builds_packet - util.time() <= 0 then _send_fac_builds() diff --git a/supervisor/startup.lua b/supervisor/startup.lua index fa51cba..5cba7f7 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.12.1" +local SUPERVISOR_VERSION = "beta-v0.12.2" local print = util.print local println = util.println From 34cac6a8b8070f958e2dd66d1cba963acb43b0c9 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 21 Feb 2023 10:31:05 -0500 Subject: [PATCH 538/587] #118 cleanup started of scada-common --- reactor-plc/startup.lua | 1 - reactor-plc/threads.lua | 1 - scada-common/comms.lua | 68 ++++++++++++------ scada-common/crypto.lua | 12 ++-- scada-common/log.lua | 43 ++++++------ scada-common/mqueue.lua | 7 +- scada-common/ppm.lua | 125 +++++++++++++++++----------------- scada-common/psil.lua | 4 +- scada-common/rsio.lua | 25 +++++-- scada-common/tcallbackdsp.lua | 7 +- scada-common/types.lua | 97 +++++++++++++------------- scada-common/util.lua | 91 ++++++++++++++----------- supervisor/unitlogic.lua | 14 ++-- 13 files changed, 273 insertions(+), 222 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 9fefc1d..90d9207 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -173,7 +173,6 @@ local function main() log.debug("init> running without networking") end ----@diagnostic disable-next-line: param-type-mismatch util.push_event("clock_start") println("boot> completed") diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 1c3c29f..69d2ff1 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -266,7 +266,6 @@ function threads.thread__main(smem, init) -- this thread cannot be slept because it will miss events (namely "terminate" otherwise) if not plc_state.shutdown then log.info("main thread restarting now...") ----@diagnostic disable-next-line: param-type-mismatch util.push_event("clock_start") end end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index b275622..14f929c 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -16,7 +16,7 @@ local max_distance = nil comms.version = "1.4.0" ----@alias PROTOCOLS integer +---@enum PROTOCOLS local PROTOCOLS = { MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol RPLC = 1, -- reactor PLC protocol @@ -25,7 +25,7 @@ local PROTOCOLS = { COORD_API = 4 -- data/control packets for pocket computers to/from coordinators } ----@alias RPLC_TYPES integer +---@enum RPLC_TYPES local RPLC_TYPES = { STATUS = 0, -- reactor/system status MEK_STRUCT = 1, -- mekanism build structure @@ -40,7 +40,7 @@ local RPLC_TYPES = { AUTO_BURN_RATE = 10 -- set an automatic burn rate, PLC will respond with status, enable toggle speed limited } ----@alias SCADA_MGMT_TYPES integer +---@enum SCADA_MGMT_TYPES local SCADA_MGMT_TYPES = { ESTABLISH = 0, -- establish new connection KEEP_ALIVE = 1, -- keep alive packet w/ RTT @@ -49,7 +49,7 @@ local SCADA_MGMT_TYPES = { RTU_DEV_REMOUNT = 4 -- RTU multiblock possbily changed (formed, unformed) due to PPM remount } ----@alias SCADA_CRDN_TYPES integer +---@enum SCADA_CRDN_TYPES local SCADA_CRDN_TYPES = { INITIAL_BUILDS = 0, -- initial, complete builds packet to the coordinator FAC_BUILDS = 1, -- facility RTU builds @@ -60,12 +60,11 @@ local SCADA_CRDN_TYPES = { UNIT_CMD = 6 -- command a reactor unit } ----@alias CAPI_TYPES integer +---@enum CAPI_TYPES local CAPI_TYPES = { - ESTABLISH = 0 -- initial greeting } ----@alias ESTABLISH_ACK integer +---@enum ESTABLISH_ACK local ESTABLISH_ACK = { ALLOW = 0, -- link approved DENY = 1, -- link denied @@ -73,7 +72,7 @@ local ESTABLISH_ACK = { BAD_VERSION = 3 -- link denied due to comms version mismatch } ----@alias DEVICE_TYPES integer +---@enum DEVICE_TYPES local DEVICE_TYPES = { PLC = 0, -- PLC device type for establish RTU = 1, -- RTU device type for establish @@ -81,7 +80,7 @@ local DEVICE_TYPES = { CRDN = 3 -- coordinator device type for establish } ----@alias RTU_UNIT_TYPES integer +---@enum RTU_UNIT_TYPES local RTU_UNIT_TYPES = { REDSTONE = 0, -- redstone I/O BOILER_VALVE = 1, -- boiler mekanism 10.1+ @@ -92,7 +91,7 @@ local RTU_UNIT_TYPES = { ENV_DETECTOR = 6 -- environment detector } ----@alias PLC_AUTO_ACK integer +---@enum PLC_AUTO_ACK local PLC_AUTO_ACK = { FAIL = 0, -- failed to set burn rate/burn rate invalid DIRECT_SET_OK = 1, -- successfully set burn rate @@ -100,7 +99,7 @@ local PLC_AUTO_ACK = { ZERO_DIS_OK = 3 -- successfully disabled reactor with < 0.01 burn rate } ----@alias FAC_COMMANDS integer +---@enum FAC_COMMANDS local FAC_COMMANDS = { SCRAM_ALL = 0, -- SCRAM all reactors STOP = 1, -- stop automatic control @@ -108,7 +107,7 @@ local FAC_COMMANDS = { ACK_ALL_ALARMS = 3 -- acknowledge all alarms on all units } ----@alias UNIT_COMMANDS integer +---@enum UNIT_COMMANDS local UNIT_COMMANDS = { SCRAM = 0, -- SCRAM the reactor START = 1, -- start the reactor @@ -152,6 +151,7 @@ function comms.set_trusted_range(distance) end -- generic SCADA packet object +---@nodiscard function comms.scada_packet() local self = { modem_msg_in = nil, @@ -180,11 +180,12 @@ function comms.scada_packet() end -- parse in a modem message as a SCADA packet - ---@param side string - ---@param sender integer - ---@param reply_to integer - ---@param message any - ---@param distance integer + ---@param side string modem side + ---@param sender integer sender port + ---@param reply_to integer reply port + ---@param message any message body + ---@param distance integer transmission distance + ---@return boolean valid valid message received function public.receive(side, sender, reply_to, message, distance) self.modem_msg_in = { iface = side, @@ -223,24 +224,34 @@ function comms.scada_packet() -- public accessors -- + ---@nodiscard function public.modem_event() return self.modem_msg_in end + ---@nodiscard function public.raw_sendable() return self.raw end + ---@nodiscard function public.local_port() return self.modem_msg_in.s_port end + ---@nodiscard function public.remote_port() return self.modem_msg_in.r_port end + ---@nodiscard function public.is_valid() return self.valid end + ---@nodiscard function public.seq_num() return self.seq_num end + ---@nodiscard function public.protocol() return self.protocol end + ---@nodiscard function public.length() return self.length end + ---@nodiscard function public.data() return self.payload end return public end --- MODBUS packet +-- MODBUS packet
-- modeled after MODBUS TCP packet +---@nodiscard function comms.modbus_packet() local self = { frame = nil, @@ -309,9 +320,11 @@ function comms.modbus_packet() end -- get raw to send + ---@nodiscard function public.raw_sendable() return self.raw end -- get this packet as a frame with an immutable relation to this object + ---@nodiscard function public.get() ---@class modbus_frame local frame = { @@ -330,6 +343,7 @@ function comms.modbus_packet() end -- reactor PLC packet +---@nodiscard function comms.rplc_packet() local self = { frame = nil, @@ -410,9 +424,11 @@ function comms.rplc_packet() end -- get raw to send + ---@nodiscard function public.raw_sendable() return self.raw end -- get this packet as a frame with an immutable relation to this object + ---@nodiscard function public.get() ---@class rplc_frame local frame = { @@ -430,6 +446,7 @@ function comms.rplc_packet() end -- SCADA management packet +---@nodiscard function comms.mgmt_packet() local self = { frame = nil, @@ -500,9 +517,11 @@ function comms.mgmt_packet() end -- get raw to send + ---@nodiscard function public.raw_sendable() return self.raw end -- get this packet as a frame with an immutable relation to this object + ---@nodiscard function public.get() ---@class mgmt_frame local frame = { @@ -519,6 +538,7 @@ function comms.mgmt_packet() end -- SCADA coordinator packet +---@nodiscard function comms.crdn_packet() local self = { frame = nil, @@ -532,6 +552,7 @@ function comms.crdn_packet() local public = {} -- check that type is known + ---@nodiscard local function _crdn_type_valid() return self.type == SCADA_CRDN_TYPES.INITIAL_BUILDS or self.type == SCADA_CRDN_TYPES.FAC_BUILDS or @@ -590,9 +611,11 @@ function comms.crdn_packet() end -- get raw to send + ---@nodiscard function public.raw_sendable() return self.raw end -- get this packet as a frame with an immutable relation to this object + ---@nodiscard function public.get() ---@class crdn_frame local frame = { @@ -609,7 +632,8 @@ function comms.crdn_packet() end -- coordinator API (CAPI) packet --- @todo +---@todo implement for pocket access +---@nodiscard function comms.capi_packet() local self = { frame = nil, @@ -623,7 +647,7 @@ function comms.capi_packet() local public = {} local function _capi_type_valid() - -- @todo + ---@todo return false end @@ -675,9 +699,11 @@ function comms.capi_packet() end -- get raw to send + ---@nodiscard function public.raw_sendable() return self.raw end -- get this packet as a frame with an immutable relation to this object + ---@nodiscard function public.get() ---@class capi_frame local frame = { @@ -694,6 +720,7 @@ function comms.capi_packet() end -- convert rtu_t to RTU unit type +---@nodiscard ---@param type rtu_t ---@return RTU_UNIT_TYPES|nil function comms.rtu_t_to_unit_type(type) @@ -717,6 +744,7 @@ function comms.rtu_t_to_unit_type(type) end -- convert RTU unit type to rtu_t +---@nodiscard ---@param utype RTU_UNIT_TYPES ---@return rtu_t|nil function comms.advert_type_to_rtu_t(utype) diff --git a/scada-common/crypto.lua b/scada-common/crypto.lua index 6424bcb..a1053bf 100644 --- a/scada-common/crypto.lua +++ b/scada-common/crypto.lua @@ -70,6 +70,7 @@ function crypto.init(password, server_port) end -- encrypt plaintext +---@nodiscard ---@param plaintext string ---@return table initial_value, string ciphertext function crypto.encrypt(plaintext) @@ -113,6 +114,7 @@ function crypto.encrypt(plaintext) end -- decrypt ciphertext +---@nodiscard ---@param iv string CTR initial value ---@param ciphertext string ciphertext hex ---@return string plaintext @@ -135,6 +137,7 @@ function crypto.decrypt(iv, ciphertext) end -- generate HMAC of message +---@nodiscard ---@param message_hex string initial value concatenated with ciphertext function crypto.hmac(message_hex) local start = util.time() @@ -201,11 +204,12 @@ function crypto.secure_modem(modem) end -- parse in a modem message as a network packet - ---@param side string - ---@param sender integer - ---@param reply_to integer + ---@nodiscard + ---@param side string modem side + ---@param sender integer sender port + ---@param reply_to integer reply port ---@param message any encrypted packet sent with secure_modem.transmit - ---@param distance integer + ---@param distance integer transmission distance ---@return string side, integer sender, integer reply_to, any plaintext_message, integer distance function public.receive(side, sender, reply_to, message, distance) local body = "" diff --git a/scada-common/log.lua b/scada-common/log.lua index cc6dd0a..30f785d 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -18,7 +18,7 @@ log.MODE = MODE -- whether to log debug messages or not local LOG_DEBUG = true -local _log_sys = { +local log_sys = { path = "/log.txt", mode = MODE.APPEND, file = nil, @@ -33,27 +33,25 @@ local free_space = fs.getFreeSpace ---@param write_mode MODE ---@param dmesg_redirect? table terminal/window to direct dmesg to function log.init(path, write_mode, dmesg_redirect) - _log_sys.path = path - _log_sys.mode = write_mode + log_sys.path = path + log_sys.mode = write_mode - if _log_sys.mode == MODE.APPEND then - _log_sys.file = fs.open(path, "a") + if log_sys.mode == MODE.APPEND then + log_sys.file = fs.open(path, "a") else - _log_sys.file = fs.open(path, "w") + log_sys.file = fs.open(path, "w") end if dmesg_redirect then - _log_sys.dmesg_out = dmesg_redirect + log_sys.dmesg_out = dmesg_redirect else - _log_sys.dmesg_out = term.current() + log_sys.dmesg_out = term.current() end end -- direct dmesg output to a monitor/window ---@param window table window or terminal reference -function log.direct_dmesg(window) - _log_sys.dmesg_out = window -end +function log.direct_dmesg(window) log_sys.dmesg_out = window end -- private log write function ---@param msg string @@ -64,8 +62,8 @@ local function _log(msg) -- attempt to write log local status, result = pcall(function () - _log_sys.file.writeLine(stamped) - _log_sys.file.flush() + log_sys.file.writeLine(stamped) + log_sys.file.flush() end) -- if we don't have space, we need to create a new log file @@ -80,18 +78,18 @@ local function _log(msg) end end - if out_of_space or (free_space(_log_sys.path) < 100) then + if out_of_space or (free_space(log_sys.path) < 100) then -- delete the old log file before opening a new one - _log_sys.file.close() - fs.delete(_log_sys.path) + log_sys.file.close() + fs.delete(log_sys.path) -- re-init logger and pass dmesg_out so that it doesn't change - log.init(_log_sys.path, _log_sys.mode, _log_sys.dmesg_out) + log.init(log_sys.path, log_sys.mode, log_sys.dmesg_out) -- leave a message - _log_sys.file.writeLine(time_stamp .. "recycled log file") - _log_sys.file.writeLine(stamped) - _log_sys.file.flush() + log_sys.file.writeLine(time_stamp .. "recycled log file") + log_sys.file.writeLine(stamped) + log_sys.file.flush() end end @@ -109,7 +107,7 @@ function log.dmesg(msg, tag, tag_color) tag = util.strval(tag) local t_stamp = string.format("%12.2f", os.clock()) - local out = _log_sys.dmesg_out + local out = log_sys.dmesg_out if out ~= nil then local out_w, out_h = out.getSize() @@ -197,6 +195,7 @@ function log.dmesg(msg, tag, tag_color) end -- print a dmesg message, but then show remaining seconds instead of timestamp +---@nodiscard ---@param msg string message ---@param tag? string log tag ---@param tag_color? integer log tag color @@ -204,7 +203,7 @@ end function log.dmesg_working(msg, tag, tag_color) local ts_coord = log.dmesg(msg, tag, tag_color) - local out = _log_sys.dmesg_out + local out = log_sys.dmesg_out local width = (ts_coord.x2 - ts_coord.x1) + 1 if out ~= nil then diff --git a/scada-common/mqueue.lua b/scada-common/mqueue.lua index 22bae5d..b48e4ad 100644 --- a/scada-common/mqueue.lua +++ b/scada-common/mqueue.lua @@ -4,7 +4,7 @@ local mqueue = {} ----@alias MQ_TYPE integer +---@enum MQ_TYPE local TYPE = { COMMAND = 0, DATA = 1, @@ -14,6 +14,7 @@ local TYPE = { mqueue.TYPE = TYPE -- create a new message queue +---@nodiscard function mqueue.new() local queue = {} @@ -35,10 +36,13 @@ function mqueue.new() function public.length() return #queue end -- check if queue is empty + ---@nodiscard ---@return boolean is_empty function public.empty() return #queue == 0 end -- check if queue has contents + ---@nodiscard + ---@return boolean has_contents function public.ready() return #queue ~= 0 end -- push a new item onto the queue @@ -68,6 +72,7 @@ function mqueue.new() end -- get an item off the queue + ---@nodiscard ---@return queue_item|nil function public.pop() if #queue > 0 then diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index f69de1e..fe9e026 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -24,7 +24,7 @@ ppm.VIRTUAL_DEVICE_TYPE = VIRTUAL_DEVICE_TYPE local REPORT_FREQUENCY = 20 -- log every 20 faults per function -local _ppm_sys = { +local ppm_sys = { mounts = {}, next_vid = 0, auto_cf = false, @@ -34,11 +34,9 @@ local _ppm_sys = { mute = false } --- wrap peripheral calls with lua protected call as we don't want a disconnect to crash a program ---- ----also provides peripheral-specific fault checks (auto-clear fault defaults to true) ---- ----assumes iface is a valid peripheral +-- wrap peripheral calls with lua protected call as we don't want a disconnect to crash a program
+-- also provides peripheral-specific fault checks (auto-clear fault defaults to true)
+-- assumes iface is a valid peripheral ---@param iface string CC peripheral interface local function peri_init(iface) local self = { @@ -68,7 +66,7 @@ local function peri_init(iface) if status then -- auto fault clear if self.auto_cf then self.faulted = false end - if _ppm_sys.auto_cf then _ppm_sys.faulted = false end + if ppm_sys.auto_cf then ppm_sys.faulted = false end self.fault_counts[key] = 0 @@ -80,10 +78,10 @@ local function peri_init(iface) self.faulted = true self.last_fault = result - _ppm_sys.faulted = true - _ppm_sys.last_fault = result + ppm_sys.faulted = true + ppm_sys.last_fault = result - if not _ppm_sys.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then + 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 faults]" @@ -95,7 +93,7 @@ local function peri_init(iface) self.fault_counts[key] = self.fault_counts[key] + 1 if result == "Terminated" then - _ppm_sys.terminate = true + ppm_sys.terminate = true end return ACCESS_FAULT @@ -136,10 +134,10 @@ local function peri_init(iface) self.faulted = true self.last_fault = UNDEFINED_FIELD - _ppm_sys.faulted = true - _ppm_sys.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 + 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]" @@ -169,48 +167,35 @@ end -- REPORTING -- -- silence error prints -function ppm.disable_reporting() - _ppm_sys.mute = true -end +function ppm.disable_reporting() ppm_sys.mute = true end -- allow error prints -function ppm.enable_reporting() - _ppm_sys.mute = false -end +function ppm.enable_reporting() ppm_sys.mute = false end -- FAULT MEMORY -- -- enable automatically clearing fault flag -function ppm.enable_afc() - _ppm_sys.auto_cf = true -end +function ppm.enable_afc() ppm_sys.auto_cf = true end -- disable automatically clearing fault flag -function ppm.disable_afc() - _ppm_sys.auto_cf = false -end +function ppm.disable_afc() ppm_sys.auto_cf = false end -- clear fault flag -function ppm.clear_fault() - _ppm_sys.faulted = false -end +function ppm.clear_fault() ppm_sys.faulted = false end -- check fault flag -function ppm.is_faulted() - return _ppm_sys.faulted -end +---@nodiscard +function ppm.is_faulted() return ppm_sys.faulted end -- get the last fault message -function ppm.get_last_fault() - return _ppm_sys.last_fault -end +---@nodiscard +function ppm.get_last_fault() return ppm_sys.last_fault end -- TERMINATION -- -- if a caught error was a termination request -function ppm.should_terminate() - return _ppm_sys.terminate -end +---@nodiscard +function ppm.should_terminate() return ppm_sys.terminate end -- MOUNTING -- @@ -218,12 +203,12 @@ end function ppm.mount_all() local ifaces = peripheral.getNames() - _ppm_sys.mounts = {} + ppm_sys.mounts = {} for i = 1, #ifaces do - _ppm_sys.mounts[ifaces[i]] = peri_init(ifaces[i]) + ppm_sys.mounts[ifaces[i]] = peri_init(ifaces[i]) - log.info(util.c("PPM: found a ", _ppm_sys.mounts[ifaces[i]].type, " (", ifaces[i], ")")) + log.info(util.c("PPM: found a ", ppm_sys.mounts[ifaces[i]].type, " (", ifaces[i], ")")) end if #ifaces == 0 then @@ -232,6 +217,7 @@ function ppm.mount_all() end -- mount a particular device +---@nodiscard ---@param iface string CC peripheral interface ---@return string|nil type, table|nil device function ppm.mount(iface) @@ -241,10 +227,10 @@ function ppm.mount(iface) for i = 1, #ifaces do if iface == ifaces[i] then - _ppm_sys.mounts[iface] = peri_init(iface) + ppm_sys.mounts[iface] = peri_init(iface) - pm_type = _ppm_sys.mounts[iface].type - pm_dev = _ppm_sys.mounts[iface].dev + pm_type = ppm_sys.mounts[iface].type + pm_dev = ppm_sys.mounts[iface].dev log.info(util.c("PPM: mount(", iface, ") -> found a ", pm_type)) break @@ -255,26 +241,27 @@ function ppm.mount(iface) end -- mount a virtual, placeholder device (specifically designed for RTU startup with missing devices) +---@nodiscard ---@return string type, table device function ppm.mount_virtual() - local iface = "ppm_vdev_" .. _ppm_sys.next_vid + local iface = "ppm_vdev_" .. ppm_sys.next_vid - _ppm_sys.mounts[iface] = peri_init("__virtual__") - _ppm_sys.next_vid = _ppm_sys.next_vid + 1 + ppm_sys.mounts[iface] = peri_init("__virtual__") + ppm_sys.next_vid = ppm_sys.next_vid + 1 log.info(util.c("PPM: mount_virtual() -> allocated new virtual device ", iface)) - return _ppm_sys.mounts[iface].type, _ppm_sys.mounts[iface].dev + return ppm_sys.mounts[iface].type, ppm_sys.mounts[iface].dev end -- manually unmount a peripheral from the PPM ---@param device table device table function ppm.unmount(device) if device then - for side, data in pairs(_ppm_sys.mounts) do + for side, data in pairs(ppm_sys.mounts) do if data.dev == device then log.warning(util.c("PPM: manually unmounted ", data.type, " mounted to ", side)) - _ppm_sys.mounts[side] = nil + ppm_sys.mounts[side] = nil break end end @@ -282,6 +269,7 @@ function ppm.unmount(device) end -- handle peripheral_detach event +---@nodiscard ---@param iface string CC peripheral interface ---@return string|nil type, table|nil device function ppm.handle_unmount(iface) @@ -289,7 +277,7 @@ function ppm.handle_unmount(iface) local pm_type = nil -- what got disconnected? - local lost_dev = _ppm_sys.mounts[iface] + local lost_dev = ppm_sys.mounts[iface] if lost_dev then pm_type = lost_dev.type @@ -300,7 +288,7 @@ function ppm.handle_unmount(iface) log.error(util.c("PPM: lost device unknown to the PPM mounted to ", iface)) end - _ppm_sys.mounts[iface] = nil + ppm_sys.mounts[iface] = nil return pm_type, pm_dev end @@ -308,23 +296,26 @@ end -- GENERAL ACCESSORS -- -- list all available peripherals +---@nodiscard ---@return table names function ppm.list_avail() return peripheral.getNames() end -- list mounted peripherals +---@nodiscard ---@return table mounts function ppm.list_mounts() - return _ppm_sys.mounts + return ppm_sys.mounts end -- get a mounted peripheral side/interface by device table +---@nodiscard ---@param device table device table ---@return string|nil iface CC peripheral interface function ppm.get_iface(device) if device then - for side, data in pairs(_ppm_sys.mounts) do + for side, data in pairs(ppm_sys.mounts) do if data.dev == device then return side end end end @@ -333,30 +324,33 @@ function ppm.get_iface(device) end -- get a mounted peripheral by side/interface +---@nodiscard ---@param iface string CC peripheral interface ---@return table|nil device function table function ppm.get_periph(iface) - if _ppm_sys.mounts[iface] then - return _ppm_sys.mounts[iface].dev + if ppm_sys.mounts[iface] then + return ppm_sys.mounts[iface].dev else return nil end end -- get a mounted peripheral type by side/interface +---@nodiscard ---@param iface string CC peripheral interface ---@return string|nil type function ppm.get_type(iface) - if _ppm_sys.mounts[iface] then - return _ppm_sys.mounts[iface].type + if ppm_sys.mounts[iface] then + return ppm_sys.mounts[iface].type else return nil end end -- get all mounted peripherals by type +---@nodiscard ---@param name string type name ---@return table devices device function tables function ppm.get_all_devices(name) local devices = {} - for _, data in pairs(_ppm_sys.mounts) do + for _, data in pairs(ppm_sys.mounts) do if data.type == name then table.insert(devices, data.dev) end @@ -366,12 +360,13 @@ function ppm.get_all_devices(name) end -- get a mounted peripheral by type (if multiple, returns the first) +---@nodiscard ---@param name string type name ---@return table|nil device function table function ppm.get_device(name) local device = nil - for side, data in pairs(_ppm_sys.mounts) do + for _, data in pairs(ppm_sys.mounts) do if data.type == name then device = data.dev break @@ -384,20 +379,21 @@ end -- SPECIFIC DEVICE ACCESSORS -- -- get the fission reactor (if multiple, returns the first) +---@nodiscard ---@return table|nil reactor function table function ppm.get_fission_reactor() return ppm.get_device("fissionReactorLogicAdapter") end --- get the wireless modem (if multiple, returns the first) --- +-- get the wireless modem (if multiple, returns the first)
-- if this is in a CraftOS emulated environment, wired modems will be used instead +---@nodiscard ---@return table|nil modem function table function ppm.get_wireless_modem() local w_modem = nil local emulated_env = periphemu ~= nil - for _, device in pairs(_ppm_sys.mounts) do + for _, device in pairs(ppm_sys.mounts) do if device.type == "modem" and (emulated_env or device.dev.isWireless()) then w_modem = device.dev break @@ -408,11 +404,12 @@ function ppm.get_wireless_modem() end -- list all connected monitors +---@nodiscard ---@return table monitors function ppm.get_monitor_list() local list = {} - for iface, device in pairs(_ppm_sys.mounts) do + for iface, device in pairs(ppm_sys.mounts) do if device.type == "monitor" then list[iface] = device end diff --git a/scada-common/psil.lua b/scada-common/psil.lua index ddadf36..c21b2cf 100644 --- a/scada-common/psil.lua +++ b/scada-common/psil.lua @@ -5,6 +5,7 @@ local psil = {} -- instantiate a new PSI layer +---@nodiscard function psil.create() local self = { ic = {} @@ -19,8 +20,7 @@ function psil.create() ---@class psil local public = {} - -- subscribe to a data object in the interconnect - -- + -- subscribe to a data object in the interconnect
-- will call func() right away if a value is already avaliable ---@param key string data key ---@param func function function to call on change diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 79ced10..6b33a24 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -89,6 +89,7 @@ rsio.IO = IO_PORT ----------------------- -- port to string +---@nodiscard ---@param port IO_PORT function rsio.to_string(port) local names = { @@ -194,6 +195,7 @@ local RS_DIO_MAP = { } -- get the mode of a port +---@nodiscard ---@param port IO_PORT ---@return IO_MODE function rsio.get_io_mode(port) @@ -239,6 +241,7 @@ end local RS_SIDES = rs.getSides() -- check if a port is valid +---@nodiscard ---@param port IO_PORT ---@return boolean valid function rsio.is_valid_port(port) @@ -246,6 +249,7 @@ function rsio.is_valid_port(port) end -- check if a side is valid +---@nodiscard ---@param side string ---@return boolean valid function rsio.is_valid_side(side) @@ -258,6 +262,7 @@ function rsio.is_valid_side(side) end -- check if a color is a valid single color +---@nodiscard ---@param color integer ---@return boolean valid function rsio.is_color(color) @@ -269,22 +274,25 @@ end ----------------- -- get digital I/O level reading from a redstone boolean input value ----@param rs_value boolean +---@nodiscard +---@param rs_value boolean raw value from redstone ---@return IO_LVL function rsio.digital_read(rs_value) if rs_value then return IO_LVL.HIGH else return IO_LVL.LOW end end -- get redstone boolean output value corresponding to a digital I/O level ----@param level IO_LVL +---@nodiscard +---@param level IO_LVL logic level ---@return boolean function rsio.digital_write(level) return level == IO_LVL.HIGH end -- returns the level corresponding to active ----@param port IO_PORT ----@param active boolean +---@nodiscard +---@param port IO_PORT port (to determine active high/low) +---@param active boolean state to convert to logic level ---@return IO_LVL|false function rsio.digital_write_active(port, active) if (not util.is_int(port)) or (port < IO_PORT.F_ALARM) or (port > IO_PORT.U_EMER_COOL) then @@ -295,9 +303,10 @@ function rsio.digital_write_active(port, active) end -- returns true if the level corresponds to active ----@param port IO_PORT ----@param level IO_LVL ----@return boolean|nil +---@nodiscard +---@param port IO_PORT port (to determine active low/high) +---@param level IO_LVL logic level +---@return boolean|nil state true for active, false for inactive, or nil if invalid port or level provided function rsio.digital_is_active(port, level) if not util.is_int(port) then return nil @@ -313,6 +322,7 @@ end ---------------- -- read an analog value scaled from min to max +---@nodiscard ---@param rs_value number redstone reading (0 to 15) ---@param min number minimum of range ---@param max number maximum of range @@ -323,6 +333,7 @@ function rsio.analog_read(rs_value, min, max) end -- write an analog value from the provided scale range +---@nodiscard ---@param value number value to write (from min to max range) ---@param min number minimum of range ---@param max number maximum of range diff --git a/scada-common/tcallbackdsp.lua b/scada-common/tcallbackdsp.lua index 52f55da..3f8f07a 100644 --- a/scada-common/tcallbackdsp.lua +++ b/scada-common/tcallbackdsp.lua @@ -19,8 +19,6 @@ function tcallbackdsp.dispatch(time, f) duration = time, expiry = time + util.time_s() } - - -- log.debug(util.c("TCD: queued callback for ", f, " [timer: ", timer, "]")) end -- request a function to be called after the specified time, aborting any registered instances of that function reference @@ -45,8 +43,6 @@ function tcallbackdsp.dispatch_unique(time, f) duration = time, expiry = time + util.time_s() } - - -- log.debug(util.c("TCD: queued callback for ", f, " [timer: ", timer, "]")) end -- abort a requested callback @@ -72,8 +68,7 @@ function tcallbackdsp.handle(event) end end --- identify any overdo callbacks --- +-- identify any overdo callbacks
-- prints to log debug output function tcallbackdsp.diagnostics() for timer, entry in pairs(registry) do diff --git a/scada-common/types.lua b/scada-common/types.lua index 23a4006..cf5f85f 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -12,12 +12,14 @@ local types = {} ---@field amount integer -- create a new tank fluid +---@nodiscard ---@param n string name ---@param a integer amount ---@return radiation_reading function types.new_tank_fluid(n, a) return { name = n, amount = a } end -- create a new empty tank fluid +---@nodiscard ---@return tank_fluid function types.new_empty_gas() return { type = "mekanism:empty_gas", amount = 0 } end @@ -26,12 +28,14 @@ function types.new_empty_gas() return { type = "mekanism:empty_gas", amount = 0 ---@field unit string -- create a new radiation reading +---@nodiscard ---@param r number radiaiton level ---@param u string radiation unit ---@return radiation_reading function types.new_radiation_reading(r, u) return { radiation = r, unit = u } end -- create a new zeroed radiation reading +---@nodiscard ---@return radiation_reading function types.new_zero_radiation_reading() return { radiation = 0, unit = "nSv" } end @@ -41,6 +45,7 @@ function types.new_zero_radiation_reading() return { radiation = 0, unit = "nSv" ---@field z integer -- create a new coordinate +---@nodiscard ---@param x integer ---@param y integer ---@param z integer @@ -48,11 +53,12 @@ function types.new_zero_radiation_reading() return { radiation = 0, unit = "nSv" function types.new_coordinate(x, y, z) return { x = x, y = y, z = z } end -- create a new zero coordinate +---@nodiscard ---@return coordinate function types.new_zero_coordinate() return { x = 0, y = 0, z = 0 } end ---@class rtu_advertisement ----@field type integer +---@field type RTU_UNIT_TYPES ---@field index integer ---@field reactor integer ---@field rsio table|nil @@ -62,15 +68,16 @@ function types.new_zero_coordinate() return { x = 0, y = 0, z = 0 } end ---@alias color integer -- ENUMERATION TYPES -- +--#region ----@alias TRI_FAIL integer +---@enum TRI_FAIL types.TRI_FAIL = { OK = 0, PARTIAL = 1, FULL = 2 } ----@alias PROCESS integer +---@enum PROCESS types.PROCESS = { INACTIVE = 0, MAX_BURN = 1, @@ -93,7 +100,7 @@ types.PROCESS_NAMES = { "GEN_RATE_FAULT_IDLE" } ----@alias WASTE_MODE integer +---@enum WASTE_MODE types.WASTE_MODE = { AUTO = 1, PLUTONIUM = 2, @@ -101,7 +108,7 @@ types.WASTE_MODE = { ANTI_MATTER = 4 } ----@alias ALARM integer +---@enum ALARM types.ALARM = { ContainmentBreach = 1, ContainmentRadiation = 2, @@ -117,7 +124,7 @@ types.ALARM = { TurbineTrip = 12 } -types.alarm_string = { +types.ALARM_NAMES = { "ContainmentBreach", "ContainmentRadiation", "ReactorLost", @@ -132,7 +139,7 @@ types.alarm_string = { "TurbineTrip" } ----@alias ALARM_PRIORITY integer +---@enum ALARM_PRIORITY types.ALARM_PRIORITY = { CRITICAL = 0, EMERGENCY = 1, @@ -140,30 +147,14 @@ types.ALARM_PRIORITY = { TIMELY = 3 } -types.alarm_prio_string = { +types.ALARM_PRIORITY_NAMES = { "CRITICAL", "EMERGENCY", "URGENT", "TIMELY" } --- map alarms to alarm priority -types.ALARM_PRIO_MAP = { - types.ALARM_PRIORITY.CRITICAL, - types.ALARM_PRIORITY.CRITICAL, - types.ALARM_PRIORITY.URGENT, - types.ALARM_PRIORITY.CRITICAL, - types.ALARM_PRIORITY.EMERGENCY, - types.ALARM_PRIORITY.EMERGENCY, - types.ALARM_PRIORITY.TIMELY, - types.ALARM_PRIORITY.EMERGENCY, - types.ALARM_PRIORITY.TIMELY, - types.ALARM_PRIORITY.URGENT, - types.ALARM_PRIORITY.TIMELY, - types.ALARM_PRIORITY.URGENT -} - ----@alias ALARM_STATE integer +---@enum ALARM_STATE types.ALARM_STATE = { INACTIVE = 0, TRIPPED = 1, @@ -171,7 +162,10 @@ types.ALARM_STATE = { RING_BACK = 3 } +--#endregion + -- STRING TYPES -- +--#region ---@alias os_event ---| "alarm" @@ -206,21 +200,7 @@ types.ALARM_STATE = { ---| "websocket_failure" ---| "websocket_message" ---| "websocket_success" - ----@alias rps_trip_cause ----| "ok" ----| "dmg_crit" ----| "high_temp" ----| "no_coolant" ----| "full_waste" ----| "heated_coolant_backup" ----| "no_fuel" ----| "fault" ----| "timeout" ----| "manual" ----| "automatic" ----| "sys_fail" ----| "force_disabled" +---| "clock_start" custom, added for reactor PLC ---@alias fluid ---| "mekanism:empty_gas" @@ -246,6 +226,21 @@ types.rtu_t = { env_detector = "environment_detector" } +---@alias rps_trip_cause +---| "ok" +---| "dmg_crit" +---| "high_temp" +---| "no_coolant" +---| "full_waste" +---| "heated_coolant_backup" +---| "no_fuel" +---| "fault" +---| "timeout" +---| "manual" +---| "automatic" +---| "sys_fail" +---| "force_disabled" + ---@alias rps_status_t rps_trip_cause types.rps_status_t = { ok = "ok", @@ -263,18 +258,24 @@ types.rps_status_t = { force_disabled = "force_disabled" } --- turbine steam dumping modes ----@alias DUMPING_MODE string +---@alias DUMPING_MODE +---| "IDLE" +---| "DUMPING" +---| "DUMPING_EXCESS" + types.DUMPING_MODE = { IDLE = "IDLE", DUMPING = "DUMPING", DUMPING_EXCESS = "DUMPING_EXCESS" } --- MODBUS +--#endregion --- modbus function codes ----@alias MODBUS_FCODE integer +-- MODBUS -- +--#region + +-- MODBUS function codes +---@enum MODBUS_FCODE types.MODBUS_FCODE = { READ_COILS = 0x01, READ_DISCRETE_INPUTS = 0x02, @@ -287,8 +288,8 @@ types.MODBUS_FCODE = { ERROR_FLAG = 0x80 } --- modbus exception codes ----@alias MODBUS_EXCODE integer +-- MODBUS exception codes +---@enum MODBUS_EXCODE types.MODBUS_EXCODE = { ILLEGAL_FUNCTION = 0x01, ILLEGAL_DATA_ADDR = 0x02, @@ -302,4 +303,6 @@ types.MODBUS_EXCODE = { GATEWAY_TARGET_TIMEOUT = 0x0B } +--#endregion + return types diff --git a/scada-common/util.lua b/scada-common/util.lua index afe3c59..2913e9f 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -14,6 +14,7 @@ util.TICK_TIME_MS = 50 --#region -- trinary operator +---@nodiscard ---@param cond boolean|nil condition ---@param a any return if true ---@param b any return if false @@ -57,6 +58,7 @@ end --#region -- get a value as a string +---@nodiscard ---@param val any ---@return string function util.strval(val) @@ -69,6 +71,7 @@ function util.strval(val) end -- repeat a string n times +---@nodiscard ---@param str string ---@param n integer ---@return string @@ -81,6 +84,7 @@ function util.strrep(str, n) end -- repeat a space n times +---@nodiscard ---@param n integer ---@return string function util.spaces(n) @@ -88,6 +92,7 @@ function util.spaces(n) end -- pad text to a minimum width +---@nodiscard ---@param str string text ---@param n integer minimum width ---@return string @@ -100,6 +105,7 @@ function util.pad(str, n) end -- wrap a string into a table of lines, supporting single dash splits +---@nodiscard ---@param str string ---@param limit integer line limit ---@return table lines @@ -147,13 +153,12 @@ function util.strwrap(str, limit) end -- concatenation with built-in to string +---@nodiscard ---@vararg any ---@return string function util.concat(...) local str = "" - for _, v in ipairs(arg) do - str = str .. util.strval(v) - end + for _, v in ipairs(arg) do str = str .. util.strval(v) end return str end @@ -161,15 +166,16 @@ end util.c = util.concat -- sprintf implementation +---@nodiscard ---@param format string ---@vararg any function util.sprintf(format, ...) return string.format(format, table.unpack(arg)) end --- format a number string with commas as the thousands separator --- +-- format a number string with commas as the thousands separator
-- subtracts from spaces at the start if present for each comma used +---@nodiscard ---@param num string number string ---@return string function util.comma_format(num) @@ -196,6 +202,7 @@ end --#region -- is a value an integer +---@nodiscard ---@param x any value ---@return boolean is_integer if the number is an integer function util.is_int(x) @@ -203,6 +210,7 @@ function util.is_int(x) end -- get the sign of a number +---@nodiscard ---@param x number value ---@return integer sign (-1 for < 0, 1 otherwise) function util.sign(x) @@ -210,12 +218,14 @@ function util.sign(x) end -- round a number to an integer +---@nodiscard ---@return integer rounded function util.round(x) return math.floor(x + 0.5) end -- get a new moving average object +---@nodiscard ---@param length integer history length ---@param default number value to fill history with for first call to compute() function util.mov_avg(length, default) @@ -249,6 +259,7 @@ function util.mov_avg(length, default) end -- compute the moving average + ---@nodiscard ---@return number average function public.compute() local sum = 0 @@ -264,6 +275,7 @@ end -- TIME -- -- current time +---@nodiscard ---@return integer milliseconds function util.time_ms() ---@diagnostic disable-next-line: undefined-field @@ -271,6 +283,7 @@ function util.time_ms() end -- current time +---@nodiscard ---@return number seconds function util.time_s() ---@diagnostic disable-next-line: undefined-field @@ -278,10 +291,9 @@ function util.time_s() end -- current time +---@nodiscard ---@return integer milliseconds -function util.time() - return util.time_ms() -end +function util.time() return util.time_ms() end --#endregion @@ -289,6 +301,7 @@ end --#region -- OS pull event raw wrapper with types +---@nodiscard ---@param target_event? string event to wait for ---@return os_event event, any param1, any param2, any param3, any param4, any param5 function util.pull_event(target_event) @@ -309,6 +322,7 @@ function util.push_event(event, param1, param2, param3, param4, param5) end -- start an OS timer +---@nodiscard ---@param t number timer duration in seconds ---@return integer timer ID function util.start_timer(t) @@ -336,14 +350,12 @@ function util.psleep(t) pcall(os.sleep, t) end --- no-op to provide a brief pause (1 tick) to yield ---- +-- no-op to provide a brief pause (1 tick) to yield
--- EVENT_CONSUMER: this function consumes events -function util.nop() - util.psleep(0.05) -end +function util.nop() util.psleep(0.05) end -- attempt to maintain a minimum loop timing (duration of execution) +---@nodiscard ---@param target_timing integer minimum amount of milliseconds to wait for ---@param last_update integer millisecond time of last update ---@return integer time_now @@ -351,9 +363,7 @@ end 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 - if sleep_for >= 50 then - util.psleep(sleep_for / 1000.0) - end + if sleep_for >= 50 then util.psleep(sleep_for / 1000.0) end return util.time() end @@ -362,8 +372,7 @@ end -- TABLE UTILITIES -- --#region --- delete elements from a table if the passed function returns false when passed a table element --- +-- delete elements from a table if the passed function returns false when passed a table element
-- put briefly: deletes elements that return false, keeps elements that return true ---@param t table table to remove elements from ---@param f function should return false to delete an element when passed the element: f(elem) = true|false @@ -388,6 +397,7 @@ function util.filter_table(t, f, on_delete) end -- check if a table contains the provided element +---@nodiscard ---@param t table table to check ---@param element any element to check for function util.table_contains(t, element) @@ -404,11 +414,13 @@ end --#region -- convert Joules to FE +---@nodiscard ---@param J number Joules ---@return number FE Forge Energy function util.joules_to_fe(J) return (J * 0.4) end -- convert FE to Joules +---@nodiscard ---@param FE number Forge Energy ---@return number J Joules function util.fe_to_joules(FE) return (FE * 2.5) end @@ -418,10 +430,11 @@ local function MFE(fe) return fe / 1000000.0 end local function GFE(fe) return fe / 1000000000.0 end local function TFE(fe) return fe / 1000000000000.0 end local function PFE(fe) return fe / 1000000000000000.0 end -local function EFE(fe) return fe / 1000000000000000000.0 end -- if you accomplish this please touch grass -local function ZFE(fe) return fe / 1000000000000000000000.0 end -- please stop +local function EFE(fe) return fe / 1000000000000000000.0 end -- if you accomplish this please touch grass +local function ZFE(fe) return fe / 1000000000000000000000.0 end -- please stop -- format a power value into XXX.XX UNIT format (FE, kFE, MFE, GFE, TFE, PFE, EFE, ZFE) +---@nodiscard ---@param fe number forge energy value ---@param combine_label? boolean if a label should be included in the string itself ---@param format? string format override @@ -430,9 +443,7 @@ function util.power_format(fe, combine_label, format) local unit local value - if type(format) ~= "string" then - format = "%.2f" - end + if type(format) ~= "string" then format = "%.2f" end if fe < 1000.0 then unit = "FE" @@ -474,10 +485,10 @@ end -- WATCHDOG -- --- ComputerCraft OS Timer based Watchdog +-- OS timer based watchdog
+-- triggers a timer event if not fed within 'timeout' seconds +---@nodiscard ---@param timeout number timeout duration ---- ---- triggers a timer event if not fed within 'timeout' seconds function util.new_watchdog(timeout) local self = { timeout = timeout, @@ -487,10 +498,10 @@ function util.new_watchdog(timeout) ---@class watchdog local public = {} + -- check if a timer is this watchdog + ---@nodiscard ---@param timer number timer event timer ID - function public.is_timer(timer) - return self.wd_timer == timer - end + function public.is_timer(timer) return self.wd_timer == timer end -- satiate the beast function public.feed() @@ -512,10 +523,10 @@ end -- LOOP CLOCK -- --- ComputerCraft OS Timer based Loop Clock +-- OS timer based loop clock
+-- fires a timer event at the specified period, does not start at construct time +---@nodiscard ---@param period number clock period ---- ---- fires a timer event at the specified period, does not start at construct time function util.new_clock(period) local self = { period = period, @@ -525,24 +536,22 @@ function util.new_clock(period) ---@class clock local public = {} + -- check if a timer is this clock + ---@nodiscard ---@param timer number timer event timer ID - function public.is_clock(timer) - return self.timer == timer - end + function public.is_clock(timer) return self.timer == timer end -- start the clock - function public.start() - self.timer = util.start_timer(self.period) - end + function public.start() self.timer = util.start_timer(self.period) end return public end -- FIELD VALIDATOR -- --- create a new type validator --- +-- create a new type validator
-- can execute sequential checks and check valid() to see if it is still valid +---@nodiscard function util.new_validator() local valid = true @@ -565,6 +574,8 @@ function util.new_validator() function public.assert_port(port) valid = valid and type(port) == "number" and port >= 0 and port <= 65535 end + -- check if all assertions passed successfully + ---@nodiscard function public.valid() return valid end return public diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 836dad2..8322892 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -367,8 +367,8 @@ local function _update_alarm_state(self, tripped, alarm) else alarm.state = AISTATE.TRIPPED self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED - log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): TRIPPED [PRIORITY ", - types.alarm_prio_string[alarm.tier + 1],"]")) + log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): TRIPPED [PRIORITY ", + types.ALARM_PRIORITY_NAMES[alarm.tier + 1],"]")) end else alarm.trip_time = util.time_ms() @@ -381,8 +381,8 @@ local function _update_alarm_state(self, tripped, alarm) if elapsed > (alarm.hold_time * 1000) then alarm.state = AISTATE.TRIPPED self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED - log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): TRIPPED [PRIORITY ", - types.alarm_prio_string[alarm.tier + 1],"]")) + log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): TRIPPED [PRIORITY ", + types.ALARM_PRIORITY_NAMES[alarm.tier + 1],"]")) end elseif int_state == AISTATE.RING_BACK_TRIPPING then alarm.trip_time = 0 @@ -432,7 +432,7 @@ local function _update_alarm_state(self, tripped, alarm) -- check for state change if alarm.state ~= int_state then local change_str = util.c(aistate_string[int_state + 1], " -> ", aistate_string[alarm.state + 1]) - log.debug(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): ", change_str)) + log.debug(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): ", change_str)) end end @@ -530,8 +530,8 @@ function logic.update_auto_safety(public, self) for _, alarm in pairs(self.alarms) do if alarm.tier <= PRIO.URGENT and (alarm.state == AISTATE.TRIPPED or alarm.state == AISTATE.ACKED) then if not self.auto_was_alarmed then - log.info(util.c("UNIT ", self.r_id, " AUTO SCRAM due to ALARM ", alarm.id, " (", types.alarm_string[alarm.id], ") [PRIORITY ", - types.alarm_prio_string[alarm.tier + 1],"]")) + log.info(util.c("UNIT ", self.r_id, " AUTO SCRAM due to ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], ") [PRIORITY ", + types.ALARM_PRIORITY_NAMES[alarm.tier + 1],"]")) end alarmed = true From 6e0dde3f30afcdc399f63ce65cbe3a87d12dcebc Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 21 Feb 2023 11:05:57 -0500 Subject: [PATCH 539/587] #118 refactoring of comms types --- coordinator/coordinator.lua | 98 +++++++-------- coordinator/iocontrol.lua | 3 - coordinator/process.lua | 32 ++--- reactor-plc/plc.lua | 66 +++++----- rtu/rtu.lua | 42 +++---- scada-common/comms.lua | 156 ++++++++++++------------ scada-common/types.lua | 2 +- supervisor/facility.lua | 2 +- supervisor/session/coordinator.lua | 100 +++++++-------- supervisor/session/plc.lua | 88 ++++++------- supervisor/session/rtu.lua | 48 ++++---- supervisor/session/rtu/boilerv.lua | 4 +- supervisor/session/rtu/envd.lua | 4 +- supervisor/session/rtu/imatrix.lua | 4 +- supervisor/session/rtu/redstone.lua | 4 +- supervisor/session/rtu/sna.lua | 4 +- supervisor/session/rtu/sps.lua | 4 +- supervisor/session/rtu/turbinev.lua | 4 +- supervisor/session/rtu/unit_session.lua | 4 +- supervisor/supervisor.lua | 42 +++---- 20 files changed, 354 insertions(+), 357 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 0e23b4f..d67a6c9 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -14,13 +14,13 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts -local PROTOCOLS = comms.PROTOCOLS -local DEVICE_TYPES = comms.DEVICE_TYPES +local PROTOCOL = comms.PROTOCOL +local DEVICE_TYPE = comms.DEVICE_TYPE local ESTABLISH_ACK = comms.ESTABLISH_ACK -local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES -local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES -local UNIT_COMMANDS = comms.UNIT_COMMANDS -local FAC_COMMANDS = comms.FAC_COMMANDS +local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE +local SCADA_CRDN_TYPE = comms.SCADA_CRDN_TYPE +local UNIT_COMMAND = comms.UNIT_COMMAND +local FAC_COMMAND = comms.FAC_COMMAND local coordinator = {} @@ -225,15 +225,15 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range _conf_channels() -- send a packet to the supervisor - ---@param msg_type SCADA_MGMT_TYPES|SCADA_CRDN_TYPES + ---@param msg_type SCADA_MGMT_TYPE|SCADA_CRDN_TYPE ---@param msg table local function _send_sv(protocol, msg_type, msg) local s_pkt = comms.scada_packet() local pkt = nil ---@type mgmt_packet|crdn_packet - if protocol == PROTOCOLS.SCADA_MGMT then + if protocol == PROTOCOL.SCADA_MGMT then pkt = comms.mgmt_packet() - elseif protocol == PROTOCOLS.SCADA_CRDN then + elseif protocol == PROTOCOL.SCADA_CRDN then pkt = comms.crdn_packet() else return @@ -248,13 +248,13 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range -- attempt connection establishment local function _send_establish() - _send_sv(PROTOCOLS.SCADA_MGMT, SCADA_MGMT_TYPES.ESTABLISH, { comms.version, version, DEVICE_TYPES.CRDN }) + _send_sv(PROTOCOL.SCADA_MGMT, SCADA_MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.CRDN }) end -- keep alive ack ---@param srv_time integer local function _send_keep_alive_ack(srv_time) - _send_sv(PROTOCOLS.SCADA_MGMT, SCADA_MGMT_TYPES.KEEP_ALIVE, { srv_time, util.time() }) + _send_sv(PROTOCOL.SCADA_MGMT, SCADA_MGMT_TYPE.KEEP_ALIVE, { srv_time, util.time() }) end -- PUBLIC FUNCTIONS -- @@ -271,7 +271,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range function public.close() sv_watchdog.cancel() self.sv_linked = false - _send_sv(PROTOCOLS.SCADA_MGMT, SCADA_MGMT_TYPES.CLOSE, {}) + _send_sv(PROTOCOL.SCADA_MGMT, SCADA_MGMT_TYPE.CLOSE, {}) end -- attempt to connect to the subervisor @@ -300,7 +300,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range elseif event == "modem_message" then -- handle message local packet = public.parse_packet(p1, p2, p3, p4, p5) - if packet ~= nil and packet.type == SCADA_MGMT_TYPES.ESTABLISH then + if packet ~= nil and packet.type == SCADA_MGMT_TYPE.ESTABLISH then public.handle_packet(packet) end elseif event == "terminate" then @@ -329,25 +329,25 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range end -- send a facility command - ---@param cmd FAC_COMMANDS command + ---@param cmd FAC_COMMAND command function public.send_fac_command(cmd) - _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.FAC_CMD, { cmd }) + _send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.FAC_CMD, { cmd }) end -- send the auto process control configuration with a start command ---@param config coord_auto_config configuration function public.send_auto_start(config) - _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.FAC_CMD, { - FAC_COMMANDS.START, config.mode, config.burn_target, config.charge_target, config.gen_target, config.limits + _send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.FAC_CMD, { + FAC_COMMAND.START, config.mode, config.burn_target, config.charge_target, config.gen_target, config.limits }) end -- send a unit command - ---@param cmd UNIT_COMMANDS command + ---@param cmd UNIT_COMMAND command ---@param unit integer unit ID ---@param option any? optional option options for the optional options (like burn rate) (does option still look like a word?) function public.send_unit_command(cmd, unit, option) - _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.UNIT_CMD, { cmd, unit, option }) + _send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.UNIT_CMD, { cmd, unit, option }) end -- parse a packet @@ -366,19 +366,19 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range if s_pkt.is_valid() then -- get as SCADA management packet - if s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then + if s_pkt.protocol() == PROTOCOL.SCADA_MGMT then local mgmt_pkt = comms.mgmt_packet() if mgmt_pkt.decode(s_pkt) then pkt = mgmt_pkt.get() end -- get as coordinator packet - elseif s_pkt.protocol() == PROTOCOLS.SCADA_CRDN then + elseif s_pkt.protocol() == PROTOCOL.SCADA_CRDN then local crdn_pkt = comms.crdn_packet() if crdn_pkt.decode(s_pkt) then pkt = crdn_pkt.get() end -- get as coordinator API packet - elseif s_pkt.protocol() == PROTOCOLS.COORD_API then + elseif s_pkt.protocol() == PROTOCOL.COORD_API then local capi_pkt = comms.capi_packet() if capi_pkt.decode(s_pkt) then pkt = capi_pkt.get() @@ -399,7 +399,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range local l_port = packet.scada_frame.local_port() if l_port == api_listen then - if protocol == PROTOCOLS.COORD_API then + if protocol == PROTOCOL.COORD_API then ---@diagnostic disable-next-line: param-type-mismatch apisessions.handle_packet(packet) else @@ -420,9 +420,9 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range sv_watchdog.feed() -- handle packet - if protocol == PROTOCOLS.SCADA_CRDN then + if protocol == PROTOCOL.SCADA_CRDN then if self.sv_linked then - if packet.type == SCADA_CRDN_TYPES.INITIAL_BUILDS then + if packet.type == SCADA_CRDN_TYPE.INITIAL_BUILDS then if packet.length == 2 then -- record builds local fac_builds = iocontrol.record_facility_builds(packet.data[1]) @@ -430,47 +430,47 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range if fac_builds and unit_builds then -- acknowledge receipt of builds - _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.INITIAL_BUILDS, {}) + _send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.INITIAL_BUILDS, {}) else log.error("received invalid INITIAL_BUILDS packet") end else log.debug("INITIAL_BUILDS packet length mismatch") end - elseif packet.type == SCADA_CRDN_TYPES.FAC_BUILDS then + elseif packet.type == SCADA_CRDN_TYPE.FAC_BUILDS then if packet.length == 1 then -- record facility builds if iocontrol.record_facility_builds(packet.data[1]) then -- acknowledge receipt of builds - _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.FAC_BUILDS, {}) + _send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.FAC_BUILDS, {}) else log.error("received invalid FAC_BUILDS packet") end else log.debug("FAC_BUILDS packet length mismatch") end - elseif packet.type == SCADA_CRDN_TYPES.FAC_STATUS then + elseif packet.type == SCADA_CRDN_TYPE.FAC_STATUS then -- update facility status if not iocontrol.update_facility_status(packet.data) then log.error("received invalid FAC_STATUS packet") end - elseif packet.type == SCADA_CRDN_TYPES.FAC_CMD then + elseif packet.type == SCADA_CRDN_TYPE.FAC_CMD then -- facility command acknowledgement if packet.length >= 2 then local cmd = packet.data[1] local ack = packet.data[2] == true - if cmd == FAC_COMMANDS.SCRAM_ALL then + if cmd == FAC_COMMAND.SCRAM_ALL then iocontrol.get_db().facility.scram_ack(ack) - elseif cmd == FAC_COMMANDS.STOP then + elseif cmd == FAC_COMMAND.STOP then iocontrol.get_db().facility.stop_ack(ack) - elseif cmd == FAC_COMMANDS.START then + elseif cmd == FAC_COMMAND.START then if packet.length == 7 then process.start_ack_handle({ table.unpack(packet.data, 2) }) else log.debug("SCADA_CRDN process start (with configuration) ack echo packet length mismatch") end - elseif cmd == FAC_COMMANDS.ACK_ALL_ALARMS then + elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then iocontrol.get_db().facility.ack_alarms_ack(ack) else log.debug(util.c("received facility command ack with unknown command ", cmd)) @@ -478,24 +478,24 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range else log.debug("SCADA_CRDN facility command ack packet length mismatch") end - elseif packet.type == SCADA_CRDN_TYPES.UNIT_BUILDS then + elseif packet.type == SCADA_CRDN_TYPE.UNIT_BUILDS then -- record builds if packet.length == 1 then if iocontrol.record_unit_builds(packet.data[1]) then -- acknowledge receipt of builds - _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.UNIT_BUILDS, {}) + _send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.UNIT_BUILDS, {}) else log.error("received invalid UNIT_BUILDS packet") end else log.debug("UNIT_BUILDS packet length mismatch") end - elseif packet.type == SCADA_CRDN_TYPES.UNIT_STATUSES then + elseif packet.type == SCADA_CRDN_TYPE.UNIT_STATUSES then -- update statuses if not iocontrol.update_unit_statuses(packet.data) then log.error("received invalid UNIT_STATUSES packet") end - elseif packet.type == SCADA_CRDN_TYPES.UNIT_CMD then + elseif packet.type == SCADA_CRDN_TYPE.UNIT_CMD then -- unit command acknowledgement if packet.length == 3 then local cmd = packet.data[1] @@ -505,19 +505,19 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range local unit = iocontrol.get_db().units[unit_id] ---@type ioctl_unit if unit ~= nil then - if cmd == UNIT_COMMANDS.SCRAM then + if cmd == UNIT_COMMAND.SCRAM then unit.scram_ack(ack) - elseif cmd == UNIT_COMMANDS.START then + elseif cmd == UNIT_COMMAND.START then unit.start_ack(ack) - elseif cmd == UNIT_COMMANDS.RESET_RPS then + elseif cmd == UNIT_COMMAND.RESET_RPS then unit.reset_rps_ack(ack) - elseif cmd == UNIT_COMMANDS.SET_BURN then + elseif cmd == UNIT_COMMAND.SET_BURN then unit.set_burn_ack(ack) - elseif cmd == UNIT_COMMANDS.SET_WASTE then + elseif cmd == UNIT_COMMAND.SET_WASTE then unit.set_waste_ack(ack) - elseif cmd == UNIT_COMMANDS.ACK_ALL_ALARMS then + elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then unit.ack_alarms_ack(ack) - elseif cmd == UNIT_COMMANDS.SET_GROUP then + elseif cmd == UNIT_COMMAND.SET_GROUP then ---@todo how is this going to be handled? else log.debug(util.c("received unit command ack with unknown command ", cmd)) @@ -534,8 +534,8 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range else log.debug("discarding SCADA_CRDN packet before linked") end - elseif protocol == PROTOCOLS.SCADA_MGMT then - if packet.type == SCADA_MGMT_TYPES.ESTABLISH then + elseif protocol == PROTOCOL.SCADA_MGMT then + if packet.type == SCADA_MGMT_TYPE.ESTABLISH then -- connection with supervisor established if packet.length == 2 then local est_ack = packet.data[1] @@ -596,7 +596,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range log.debug("SCADA_MGMT establish packet length mismatch") end elseif self.sv_linked then - if packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + if packet.type == SCADA_MGMT_TYPE.KEEP_ALIVE then -- keep alive request received, echo back if packet.length == 1 then local timestamp = packet.data[1] @@ -614,7 +614,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range else log.debug("SCADA keep alive packet length mismatch") end - elseif packet.type == SCADA_MGMT_TYPES.CLOSE then + elseif packet.type == SCADA_MGMT_TYPE.CLOSE then -- handle session close sv_watchdog.cancel() self.sv_linked = false diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 30bb6e5..b66ea80 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -1,4 +1,3 @@ -local comms = require("scada-common.comms") local log = require("scada-common.log") local psil = require("scada-common.psil") local types = require("scada-common.types") @@ -7,8 +6,6 @@ local util = require("scada-common.util") local process = require("coordinator.process") local sounder = require("coordinator.sounder") -local UNIT_COMMANDS = comms.UNIT_COMMANDS - local ALARM_STATE = types.ALARM_STATE local iocontrol = {} diff --git a/coordinator/process.lua b/coordinator/process.lua index b3a6bb3..e3604bf 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -4,8 +4,8 @@ local log = require("scada-common.log") local types = require("scada-common.types") local util = require("scada-common.util") -local FAC_COMMANDS = comms.FAC_COMMANDS -local UNIT_COMMANDS = comms.UNIT_COMMANDS +local FAC_COMMAND = comms.FAC_COMMAND +local UNIT_COMMAND = comms.UNIT_COMMAND local PROCESS = types.PROCESS @@ -71,7 +71,7 @@ function process.init(iocontrol, comms) if type(waste_mode) == "table" then for id, mode in pairs(waste_mode) do - self.comms.send_unit_command(UNIT_COMMANDS.SET_WASTE, id, mode) + self.comms.send_unit_command(UNIT_COMMAND.SET_WASTE, id, mode) end log.info("PROCESS: loaded waste mode settings from coord.settings") @@ -81,7 +81,7 @@ function process.init(iocontrol, comms) if type(prio_groups) == "table" then for id, group in pairs(prio_groups) do - self.comms.send_unit_command(UNIT_COMMANDS.SET_GROUP, id, group) + self.comms.send_unit_command(UNIT_COMMAND.SET_GROUP, id, group) end log.info("PROCESS: loaded priority groups settings from coord.settings") @@ -90,13 +90,13 @@ end -- facility SCRAM command function process.fac_scram() - self.comms.send_fac_command(FAC_COMMANDS.SCRAM_ALL) + self.comms.send_fac_command(FAC_COMMAND.SCRAM_ALL) log.debug("FAC: SCRAM ALL") end -- facility alarm acknowledge command function process.fac_ack_alarms() - self.comms.send_fac_command(FAC_COMMANDS.ACK_ALL_ALARMS) + self.comms.send_fac_command(FAC_COMMAND.ACK_ALL_ALARMS) log.debug("FAC: ACK ALL ALARMS") end @@ -104,7 +104,7 @@ end ---@param id integer unit ID function process.start(id) self.io.units[id].control_state = true - self.comms.send_unit_command(UNIT_COMMANDS.START, id) + self.comms.send_unit_command(UNIT_COMMAND.START, id) log.debug(util.c("UNIT[", id, "]: START")) end @@ -112,14 +112,14 @@ end ---@param id integer unit ID function process.scram(id) self.io.units[id].control_state = false - self.comms.send_unit_command(UNIT_COMMANDS.SCRAM, id) + self.comms.send_unit_command(UNIT_COMMAND.SCRAM, id) log.debug(util.c("UNIT[", id, "]: SCRAM")) end -- reset reactor protection system ---@param id integer unit ID function process.reset_rps(id) - self.comms.send_unit_command(UNIT_COMMANDS.RESET_RPS, id) + self.comms.send_unit_command(UNIT_COMMAND.RESET_RPS, id) log.debug(util.c("UNIT[", id, "]: RESET RPS")) end @@ -127,7 +127,7 @@ end ---@param id integer unit ID ---@param rate number burn rate function process.set_rate(id, rate) - self.comms.send_unit_command(UNIT_COMMANDS.SET_BURN, id, rate) + self.comms.send_unit_command(UNIT_COMMAND.SET_BURN, id, rate) log.debug(util.c("UNIT[", id, "]: SET BURN = ", rate)) end @@ -138,7 +138,7 @@ function process.set_waste(id, mode) -- publish so that if it fails then it gets reset self.io.units[id].unit_ps.publish("U_WasteMode", mode) - self.comms.send_unit_command(UNIT_COMMANDS.SET_WASTE, id, mode) + self.comms.send_unit_command(UNIT_COMMAND.SET_WASTE, id, mode) log.debug(util.c("UNIT[", id, "]: SET WASTE = ", mode)) local waste_mode = settings.get("WASTE_MODES") ---@type table|nil @@ -159,7 +159,7 @@ end -- acknowledge all alarms ---@param id integer unit ID function process.ack_all_alarms(id) - self.comms.send_unit_command(UNIT_COMMANDS.ACK_ALL_ALARMS, id) + self.comms.send_unit_command(UNIT_COMMAND.ACK_ALL_ALARMS, id) log.debug(util.c("UNIT[", id, "]: ACK ALL ALARMS")) end @@ -167,7 +167,7 @@ end ---@param id integer unit ID ---@param alarm integer alarm ID function process.ack_alarm(id, alarm) - self.comms.send_unit_command(UNIT_COMMANDS.ACK_ALARM, id, alarm) + self.comms.send_unit_command(UNIT_COMMAND.ACK_ALARM, id, alarm) log.debug(util.c("UNIT[", id, "]: ACK ALARM ", alarm)) end @@ -175,7 +175,7 @@ end ---@param id integer unit ID ---@param alarm integer alarm ID function process.reset_alarm(id, alarm) - self.comms.send_unit_command(UNIT_COMMANDS.RESET_ALARM, id, alarm) + self.comms.send_unit_command(UNIT_COMMAND.RESET_ALARM, id, alarm) log.debug(util.c("UNIT[", id, "]: RESET ALARM ", alarm)) end @@ -183,7 +183,7 @@ end ---@param unit_id integer unit ID ---@param group_id integer|0 group ID or 0 for independent function process.set_group(unit_id, group_id) - self.comms.send_unit_command(UNIT_COMMANDS.SET_GROUP, unit_id, group_id) + self.comms.send_unit_command(UNIT_COMMAND.SET_GROUP, unit_id, group_id) log.debug(util.c("UNIT[", unit_id, "]: SET GROUP ", group_id)) local prio_groups = settings.get("PRIORITY_GROUPS") ---@type table|nil @@ -207,7 +207,7 @@ end -- stop automatic process control function process.stop_auto() - self.comms.send_fac_command(FAC_COMMANDS.STOP) + self.comms.send_fac_command(FAC_COMMAND.STOP) log.debug("FAC: STOP AUTO") end diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index f6bbe5d..5c00465 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -8,11 +8,11 @@ local plc = {} local rps_status_t = types.rps_status_t -local PROTOCOLS = comms.PROTOCOLS -local DEVICE_TYPES = comms.DEVICE_TYPES +local PROTOCOL = comms.PROTOCOL +local DEVICE_TYPE = comms.DEVICE_TYPE local ESTABLISH_ACK = comms.ESTABLISH_ACK -local RPLC_TYPES = comms.RPLC_TYPES -local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES +local RPLC_TYPE = comms.RPLC_TYPE +local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE local AUTO_ACK = comms.PLC_AUTO_ACK local print = util.print @@ -444,28 +444,28 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, _conf_channels() -- send an RPLC packet - ---@param msg_type RPLC_TYPES + ---@param msg_type RPLC_TYPE ---@param msg table local function _send(msg_type, msg) local s_pkt = comms.scada_packet() local r_pkt = comms.rplc_packet() r_pkt.make(id, msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOLS.RPLC, r_pkt.raw_sendable()) + s_pkt.make(self.seq_num, PROTOCOL.RPLC, r_pkt.raw_sendable()) self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) self.seq_num = self.seq_num + 1 end -- send a SCADA management packet - ---@param msg_type SCADA_MGMT_TYPES + ---@param msg_type SCADA_MGMT_TYPE ---@param msg table local function _send_mgmt(msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() m_pkt.make(msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) + s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) self.seq_num = self.seq_num + 1 @@ -569,11 +569,11 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, -- keep alive ack ---@param srv_time integer local function _send_keep_alive_ack(srv_time) - _send_mgmt(SCADA_MGMT_TYPES.KEEP_ALIVE, { srv_time, util.time() }) + _send_mgmt(SCADA_MGMT_TYPE.KEEP_ALIVE, { srv_time, util.time() }) end -- general ack - ---@param msg_type RPLC_TYPES + ---@param msg_type RPLC_TYPE ---@param status boolean|integer local function _send_ack(msg_type, status) _send(msg_type, { status }) @@ -605,7 +605,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, parallel.waitForAll(table.unpack(tasks)) if not self.reactor.__p_is_faulted() then - _send(RPLC_TYPES.MEK_STRUCT, mek_data) + _send(RPLC_TYPE.MEK_STRUCT, mek_data) self.resend_build = false else log.error("failed to send structure: PPM fault") @@ -643,12 +643,12 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, function public.close() conn_watchdog.cancel() public.unlink() - _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) + _send_mgmt(SCADA_MGMT_TYPE.CLOSE, {}) end -- attempt to establish link with supervisor function public.send_link_req() - _send_mgmt(SCADA_MGMT_TYPES.ESTABLISH, { comms.version, version, DEVICE_TYPES.PLC, id }) + _send_mgmt(SCADA_MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PLC, id }) end -- send live status information @@ -677,7 +677,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, mek_data -- mekanism status data } - _send(RPLC_TYPES.STATUS, sys_status) + _send(RPLC_TYPE.STATUS, sys_status) if self.resend_build then _send_struct() @@ -688,7 +688,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, -- send reactor protection system status function public.send_rps_status() if self.linked then - _send(RPLC_TYPES.RPS_STATUS, { rps.is_tripped(), rps.get_trip_cause(), table.unpack(rps.status()) }) + _send(RPLC_TYPE.RPS_STATUS, { rps.is_tripped(), rps.get_trip_cause(), table.unpack(rps.status()) }) end end @@ -701,7 +701,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, table.unpack(rps.status()) } - _send(RPLC_TYPES.RPS_ALARM, rps_alarm) + _send(RPLC_TYPE.RPS_ALARM, rps_alarm) end end @@ -721,13 +721,13 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, if s_pkt.is_valid() then -- get as RPLC packet - if s_pkt.protocol() == PROTOCOLS.RPLC then + if s_pkt.protocol() == PROTOCOL.RPLC then local rplc_pkt = comms.rplc_packet() if rplc_pkt.decode(s_pkt) then pkt = rplc_pkt.get() end -- get as SCADA management packet - elseif s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then + elseif s_pkt.protocol() == PROTOCOL.SCADA_MGMT then local mgmt_pkt = comms.mgmt_packet() if mgmt_pkt.decode(s_pkt) then pkt = mgmt_pkt.get() @@ -762,18 +762,18 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, local protocol = packet.scada_frame.protocol() -- handle packet - if protocol == PROTOCOLS.RPLC then + if protocol == PROTOCOL.RPLC then if self.linked then - if packet.type == RPLC_TYPES.STATUS then + if packet.type == RPLC_TYPE.STATUS then -- request of full status, clear cache first self.status_cache = nil public.send_status(plc_state.no_reactor, plc_state.reactor_formed) log.debug("sent out status cache again, did supervisor miss it?") - elseif packet.type == RPLC_TYPES.MEK_STRUCT then + elseif packet.type == RPLC_TYPE.MEK_STRUCT then -- request for physical structure _send_struct() log.debug("sent out structure again, did supervisor miss it?") - elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then + elseif packet.type == RPLC_TYPE.MEK_BURN_RATE then -- set the burn rate if (packet.length == 2) and (type(packet.data[1]) == "number") then local success = false @@ -805,29 +805,29 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, else log.debug("RPLC set burn rate packet length mismatch or non-numeric burn rate") end - elseif packet.type == RPLC_TYPES.RPS_ENABLE then + elseif packet.type == RPLC_TYPE.RPS_ENABLE then -- enable the reactor self.scrammed = false _send_ack(packet.type, rps.activate()) - elseif packet.type == RPLC_TYPES.RPS_SCRAM then + elseif packet.type == RPLC_TYPE.RPS_SCRAM then -- disable the reactor per manual request self.scrammed = true rps.trip_manual() _send_ack(packet.type, true) - elseif packet.type == RPLC_TYPES.RPS_ASCRAM then + elseif packet.type == RPLC_TYPE.RPS_ASCRAM then -- disable the reactor per automatic request self.scrammed = true rps.trip_auto() _send_ack(packet.type, true) - elseif packet.type == RPLC_TYPES.RPS_RESET then + elseif packet.type == RPLC_TYPE.RPS_RESET then -- reset the RPS status rps.reset() _send_ack(packet.type, true) - elseif packet.type == RPLC_TYPES.RPS_AUTO_RESET then + elseif packet.type == RPLC_TYPE.RPS_AUTO_RESET then -- reset automatic SCRAM and timeout trips rps.auto_reset() _send_ack(packet.type, true) - elseif packet.type == RPLC_TYPES.AUTO_BURN_RATE then + elseif packet.type == RPLC_TYPE.AUTO_BURN_RATE then -- automatic control requested a new burn rate if (packet.length == 3) and (type(packet.data[1]) == "number") and (type(packet.data[3]) == "number") then local ack = AUTO_ACK.FAIL @@ -898,9 +898,9 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, else log.debug("discarding RPLC packet before linked") end - elseif protocol == PROTOCOLS.SCADA_MGMT then + elseif protocol == PROTOCOL.SCADA_MGMT then if self.linked then - if packet.type == SCADA_MGMT_TYPES.ESTABLISH then + if packet.type == SCADA_MGMT_TYPE.ESTABLISH then -- link request confirmation if packet.length == 1 then log.debug("received unsolicited establish response") @@ -933,7 +933,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, else log.debug("SCADA_MGMT establish packet length mismatch") end - elseif packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + elseif packet.type == SCADA_MGMT_TYPE.KEEP_ALIVE then -- keep alive request received, echo back if packet.length == 1 and type(packet.data[1]) == "number" then local timestamp = packet.data[1] @@ -949,7 +949,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, else log.debug("SCADA_MGMT keep alive packet length/type mismatch") end - elseif packet.type == SCADA_MGMT_TYPES.CLOSE then + elseif packet.type == SCADA_MGMT_TYPE.CLOSE then -- handle session close conn_watchdog.cancel() public.unlink() @@ -958,7 +958,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, else log.warning("received unsupported SCADA_MGMT packet type " .. packet.type) end - elseif packet.type == SCADA_MGMT_TYPES.ESTABLISH then + elseif packet.type == SCADA_MGMT_TYPE.ESTABLISH then -- link request confirmation if packet.length == 1 then local est_ack = packet.data[1] diff --git a/rtu/rtu.lua b/rtu/rtu.lua index ff81659..feaaf38 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -7,11 +7,11 @@ local modbus = require("rtu.modbus") local rtu = {} -local PROTOCOLS = comms.PROTOCOLS -local DEVICE_TYPES = comms.DEVICE_TYPES +local PROTOCOL = comms.PROTOCOL +local DEVICE_TYPE = comms.DEVICE_TYPE local ESTABLISH_ACK = comms.ESTABLISH_ACK -local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES -local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE +local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE local print = util.print local println = util.println @@ -197,14 +197,14 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog _conf_channels() -- send a scada management packet - ---@param msg_type SCADA_MGMT_TYPES + ---@param msg_type SCADA_MGMT_TYPE ---@param msg table local function _send(msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() m_pkt.make(msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) + s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) self.seq_num = self.seq_num + 1 @@ -213,7 +213,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog -- keep alive ack ---@param srv_time integer local function _send_keep_alive_ack(srv_time) - _send(SCADA_MGMT_TYPES.KEEP_ALIVE, { srv_time, util.time() }) + _send(SCADA_MGMT_TYPE.KEEP_ALIVE, { srv_time, util.time() }) end -- generate device advertisement table @@ -233,7 +233,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog unit.reactor } - if type == RTU_UNIT_TYPES.REDSTONE then + if type == RTU_UNIT_TYPE.REDSTONE then insert(advert, unit.device) end @@ -250,7 +250,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog ---@param m_pkt modbus_packet function public.send_modbus(m_pkt) local s_pkt = comms.scada_packet() - s_pkt.make(self.seq_num, PROTOCOLS.MODBUS_TCP, m_pkt.raw_sendable()) + s_pkt.make(self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable()) self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) self.seq_num = self.seq_num + 1 end @@ -275,25 +275,25 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog function public.close(rtu_state) self.conn_watchdog.cancel() public.unlink(rtu_state) - _send(SCADA_MGMT_TYPES.CLOSE, {}) + _send(SCADA_MGMT_TYPE.CLOSE, {}) end -- send establish request (includes advertisement) ---@param units table function public.send_establish(units) - _send(SCADA_MGMT_TYPES.ESTABLISH, { comms.version, self.version, DEVICE_TYPES.RTU, _generate_advertisement(units) }) + _send(SCADA_MGMT_TYPE.ESTABLISH, { comms.version, self.version, DEVICE_TYPE.RTU, _generate_advertisement(units) }) end -- send capability advertisement ---@param units table function public.send_advertisement(units) - _send(SCADA_MGMT_TYPES.RTU_ADVERT, _generate_advertisement(units)) + _send(SCADA_MGMT_TYPE.RTU_ADVERT, _generate_advertisement(units)) end -- notify that a peripheral was remounted ---@param unit_index integer RTU unit ID function public.send_remounted(unit_index) - _send(SCADA_MGMT_TYPES.RTU_DEV_REMOUNT, { unit_index }) + _send(SCADA_MGMT_TYPE.RTU_DEV_REMOUNT, { unit_index }) end -- parse a MODBUS/SCADA packet @@ -312,13 +312,13 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog if s_pkt.is_valid() then -- get as MODBUS TCP packet - if s_pkt.protocol() == PROTOCOLS.MODBUS_TCP then + if s_pkt.protocol() == PROTOCOL.MODBUS_TCP then local m_pkt = comms.modbus_packet() if m_pkt.decode(s_pkt) then pkt = m_pkt.get() end -- get as SCADA management packet - elseif s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then + elseif s_pkt.protocol() == PROTOCOL.SCADA_MGMT then local mgmt_pkt = comms.mgmt_packet() if mgmt_pkt.decode(s_pkt) then pkt = mgmt_pkt.get() @@ -352,7 +352,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog local protocol = packet.scada_frame.protocol() - if protocol == PROTOCOLS.MODBUS_TCP then + if protocol == PROTOCOL.MODBUS_TCP then if rtu_state.linked then local return_code = false ---@diagnostic disable-next-line: param-type-mismatch @@ -401,9 +401,9 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog else log.debug("discarding MODBUS packet before linked") end - elseif protocol == PROTOCOLS.SCADA_MGMT then + elseif protocol == PROTOCOL.SCADA_MGMT then -- SCADA management packet - if packet.type == SCADA_MGMT_TYPES.ESTABLISH then + if packet.type == SCADA_MGMT_TYPE.ESTABLISH then if packet.length == 1 then local est_ack = packet.data[1] @@ -434,7 +434,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog log.debug("SCADA_MGMT establish packet length mismatch") end elseif rtu_state.linked then - if packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + if packet.type == SCADA_MGMT_TYPE.KEEP_ALIVE then -- keep alive request received, echo back if packet.length == 1 and type(packet.data[1]) == "number" then local timestamp = packet.data[1] @@ -450,13 +450,13 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog else log.debug("SCADA_MGMT keep alive packet length/type mismatch") end - elseif packet.type == SCADA_MGMT_TYPES.CLOSE then + elseif packet.type == SCADA_MGMT_TYPE.CLOSE then -- close connection self.conn_watchdog.cancel() public.unlink(rtu_state) println_ts("server connection closed by remote host") log.warning("server connection closed by remote host") - elseif packet.type == SCADA_MGMT_TYPES.RTU_ADVERT then + elseif packet.type == SCADA_MGMT_TYPE.RTU_ADVERT then -- request for capabilities again public.send_advertisement(units) else diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 14f929c..0e0dd0e 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -16,8 +16,8 @@ local max_distance = nil comms.version = "1.4.0" ----@enum PROTOCOLS -local PROTOCOLS = { +---@enum PROTOCOL +local PROTOCOL = { MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol RPLC = 1, -- reactor PLC protocol SCADA_MGMT = 2, -- SCADA supervisor management, device advertisements, etc @@ -25,8 +25,8 @@ local PROTOCOLS = { COORD_API = 4 -- data/control packets for pocket computers to/from coordinators } ----@enum RPLC_TYPES -local RPLC_TYPES = { +---@enum RPLC_TYPE +local RPLC_TYPE = { STATUS = 0, -- reactor/system status MEK_STRUCT = 1, -- mekanism build structure MEK_BURN_RATE = 2, -- set burn rate @@ -40,8 +40,8 @@ local RPLC_TYPES = { AUTO_BURN_RATE = 10 -- set an automatic burn rate, PLC will respond with status, enable toggle speed limited } ----@enum SCADA_MGMT_TYPES -local SCADA_MGMT_TYPES = { +---@enum SCADA_MGMT_TYPE +local SCADA_MGMT_TYPE = { ESTABLISH = 0, -- establish new connection KEEP_ALIVE = 1, -- keep alive packet w/ RTT CLOSE = 2, -- close a connection @@ -49,8 +49,8 @@ local SCADA_MGMT_TYPES = { RTU_DEV_REMOUNT = 4 -- RTU multiblock possbily changed (formed, unformed) due to PPM remount } ----@enum SCADA_CRDN_TYPES -local SCADA_CRDN_TYPES = { +---@enum SCADA_CRDN_TYPE +local SCADA_CRDN_TYPE = { INITIAL_BUILDS = 0, -- initial, complete builds packet to the coordinator FAC_BUILDS = 1, -- facility RTU builds FAC_STATUS = 2, -- state of facility and facility devices @@ -60,8 +60,8 @@ local SCADA_CRDN_TYPES = { UNIT_CMD = 6 -- command a reactor unit } ----@enum CAPI_TYPES -local CAPI_TYPES = { +---@enum CAPI_TYPE +local CAPI_TYPE = { } ---@enum ESTABLISH_ACK @@ -72,16 +72,16 @@ local ESTABLISH_ACK = { BAD_VERSION = 3 -- link denied due to comms version mismatch } ----@enum DEVICE_TYPES -local DEVICE_TYPES = { +---@enum DEVICE_TYPE +local DEVICE_TYPE = { PLC = 0, -- PLC device type for establish RTU = 1, -- RTU device type for establish SV = 2, -- supervisor device type for establish CRDN = 3 -- coordinator device type for establish } ----@enum RTU_UNIT_TYPES -local RTU_UNIT_TYPES = { +---@enum RTU_UNIT_TYPE +local RTU_UNIT_TYPE = { REDSTONE = 0, -- redstone I/O BOILER_VALVE = 1, -- boiler mekanism 10.1+ TURBINE_VALVE = 2, -- turbine, mekanism 10.1+ @@ -99,16 +99,16 @@ local PLC_AUTO_ACK = { ZERO_DIS_OK = 3 -- successfully disabled reactor with < 0.01 burn rate } ----@enum FAC_COMMANDS -local FAC_COMMANDS = { +---@enum FAC_COMMAND +local FAC_COMMAND = { SCRAM_ALL = 0, -- SCRAM all reactors STOP = 1, -- stop automatic control START = 2, -- start automatic control ACK_ALL_ALARMS = 3 -- acknowledge all alarms on all units } ----@enum UNIT_COMMANDS -local UNIT_COMMANDS = { +---@enum UNIT_COMMAND +local UNIT_COMMAND = { SCRAM = 0, -- SCRAM the reactor START = 1, -- start the reactor RESET_RPS = 2, -- reset the RPS @@ -120,26 +120,26 @@ local UNIT_COMMANDS = { SET_GROUP = 8 -- assign this unit to a group } -comms.PROTOCOLS = PROTOCOLS +comms.PROTOCOL = PROTOCOL -comms.RPLC_TYPES = RPLC_TYPES -comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES -comms.SCADA_CRDN_TYPES = SCADA_CRDN_TYPES -comms.CAPI_TYPES = CAPI_TYPES +comms.RPLC_TYPE = RPLC_TYPE +comms.SCADA_MGMT_TYPE = SCADA_MGMT_TYPE +comms.SCADA_CRDN_TYPE = SCADA_CRDN_TYPE +comms.CAPI_TYPE = CAPI_TYPE comms.ESTABLISH_ACK = ESTABLISH_ACK -comms.DEVICE_TYPES = DEVICE_TYPES -comms.RTU_UNIT_TYPES = RTU_UNIT_TYPES +comms.DEVICE_TYPE = DEVICE_TYPE +comms.RTU_UNIT_TYPE = RTU_UNIT_TYPE comms.PLC_AUTO_ACK = PLC_AUTO_ACK -comms.UNIT_COMMANDS = UNIT_COMMANDS -comms.FAC_COMMANDS = FAC_COMMANDS +comms.UNIT_COMMAND = UNIT_COMMAND +comms.FAC_COMMAND = FAC_COMMAND ---@alias packet scada_packet|modbus_packet|rplc_packet|mgmt_packet|crdn_packet|capi_packet ---@alias frame modbus_frame|rplc_frame|mgmt_frame|crdn_frame|capi_frame --- configure the maximum allowable message receive distance
+-- configure the maximum allowable message receive distance
-- packets received with distances greater than this will be silently discarded ---@param distance integer max modem message distance (less than 1 disables the limit) function comms.set_trusted_range(distance) @@ -168,7 +168,7 @@ function comms.scada_packet() -- make a SCADA packet ---@param seq_num integer - ---@param protocol PROTOCOLS + ---@param protocol PROTOCOL ---@param payload table function public.make(seq_num, protocol, payload) self.valid = true @@ -296,7 +296,7 @@ function comms.modbus_packet() if frame then self.frame = frame - if frame.protocol() == PROTOCOLS.MODBUS_TCP then + if frame.protocol() == PROTOCOL.MODBUS_TCP then local size_ok = frame.length() >= 3 if size_ok then @@ -359,22 +359,22 @@ function comms.rplc_packet() -- check that type is known local function _rplc_type_valid() - return self.type == RPLC_TYPES.STATUS or - self.type == RPLC_TYPES.MEK_STRUCT or - self.type == RPLC_TYPES.MEK_BURN_RATE or - self.type == RPLC_TYPES.RPS_ENABLE or - self.type == RPLC_TYPES.RPS_SCRAM or - self.type == RPLC_TYPES.RPS_ASCRAM or - self.type == RPLC_TYPES.RPS_STATUS or - self.type == RPLC_TYPES.RPS_ALARM or - self.type == RPLC_TYPES.RPS_RESET or - self.type == RPLC_TYPES.RPS_AUTO_RESET or - self.type == RPLC_TYPES.AUTO_BURN_RATE + return self.type == RPLC_TYPE.STATUS or + self.type == RPLC_TYPE.MEK_STRUCT or + self.type == RPLC_TYPE.MEK_BURN_RATE or + self.type == RPLC_TYPE.RPS_ENABLE or + self.type == RPLC_TYPE.RPS_SCRAM or + self.type == RPLC_TYPE.RPS_ASCRAM or + self.type == RPLC_TYPE.RPS_STATUS or + self.type == RPLC_TYPE.RPS_ALARM or + self.type == RPLC_TYPE.RPS_RESET or + self.type == RPLC_TYPE.RPS_AUTO_RESET or + self.type == RPLC_TYPE.AUTO_BURN_RATE end -- make an RPLC packet ---@param id integer - ---@param packet_type RPLC_TYPES + ---@param packet_type RPLC_TYPE ---@param data table function public.make(id, packet_type, data) if type(data) == "table" then @@ -401,7 +401,7 @@ function comms.rplc_packet() if frame then self.frame = frame - if frame.protocol() == PROTOCOLS.RPLC then + if frame.protocol() == PROTOCOL.RPLC then local ok = frame.length() >= 2 if ok then @@ -461,16 +461,16 @@ function comms.mgmt_packet() -- check that type is known local function _scada_type_valid() - return self.type == SCADA_MGMT_TYPES.ESTABLISH or - self.type == SCADA_MGMT_TYPES.KEEP_ALIVE or - self.type == SCADA_MGMT_TYPES.CLOSE or - self.type == SCADA_MGMT_TYPES.REMOTE_LINKED or - self.type == SCADA_MGMT_TYPES.RTU_ADVERT or - self.type == SCADA_MGMT_TYPES.RTU_DEV_REMOUNT + return self.type == SCADA_MGMT_TYPE.ESTABLISH or + self.type == SCADA_MGMT_TYPE.KEEP_ALIVE or + self.type == SCADA_MGMT_TYPE.CLOSE or + self.type == SCADA_MGMT_TYPE.REMOTE_LINKED or + self.type == SCADA_MGMT_TYPE.RTU_ADVERT or + self.type == SCADA_MGMT_TYPE.RTU_DEV_REMOUNT end -- make a SCADA management packet - ---@param packet_type SCADA_MGMT_TYPES + ---@param packet_type SCADA_MGMT_TYPE ---@param data table function public.make(packet_type, data) if type(data) == "table" then @@ -496,7 +496,7 @@ function comms.mgmt_packet() if frame then self.frame = frame - if frame.protocol() == PROTOCOLS.SCADA_MGMT then + if frame.protocol() == PROTOCOL.SCADA_MGMT then local ok = frame.length() >= 1 if ok then @@ -554,17 +554,17 @@ function comms.crdn_packet() -- check that type is known ---@nodiscard local function _crdn_type_valid() - return self.type == SCADA_CRDN_TYPES.INITIAL_BUILDS or - self.type == SCADA_CRDN_TYPES.FAC_BUILDS or - self.type == SCADA_CRDN_TYPES.FAC_STATUS or - self.type == SCADA_CRDN_TYPES.FAC_CMD or - self.type == SCADA_CRDN_TYPES.UNIT_BUILDS or - self.type == SCADA_CRDN_TYPES.UNIT_STATUSES or - self.type == SCADA_CRDN_TYPES.UNIT_CMD + return self.type == SCADA_CRDN_TYPE.INITIAL_BUILDS or + self.type == SCADA_CRDN_TYPE.FAC_BUILDS or + self.type == SCADA_CRDN_TYPE.FAC_STATUS or + self.type == SCADA_CRDN_TYPE.FAC_CMD or + self.type == SCADA_CRDN_TYPE.UNIT_BUILDS or + self.type == SCADA_CRDN_TYPE.UNIT_STATUSES or + self.type == SCADA_CRDN_TYPE.UNIT_CMD end -- make a coordinator packet - ---@param packet_type SCADA_CRDN_TYPES + ---@param packet_type SCADA_CRDN_TYPE ---@param data table function public.make(packet_type, data) if type(data) == "table" then @@ -590,7 +590,7 @@ function comms.crdn_packet() if frame then self.frame = frame - if frame.protocol() == PROTOCOLS.SCADA_CRDN then + if frame.protocol() == PROTOCOL.SCADA_CRDN then local ok = frame.length() >= 1 if ok then @@ -652,7 +652,7 @@ function comms.capi_packet() end -- make a coordinator API packet - ---@param packet_type CAPI_TYPES + ---@param packet_type CAPI_TYPE ---@param data table function public.make(packet_type, data) if type(data) == "table" then @@ -678,7 +678,7 @@ function comms.capi_packet() if frame then self.frame = frame - if frame.protocol() == PROTOCOLS.COORD_API then + if frame.protocol() == PROTOCOL.COORD_API then local ok = frame.length() >= 1 if ok then @@ -722,22 +722,22 @@ end -- convert rtu_t to RTU unit type ---@nodiscard ---@param type rtu_t ----@return RTU_UNIT_TYPES|nil +---@return RTU_UNIT_TYPE|nil function comms.rtu_t_to_unit_type(type) if type == rtu_t.redstone then - return RTU_UNIT_TYPES.REDSTONE + return RTU_UNIT_TYPE.REDSTONE elseif type == rtu_t.boiler_valve then - return RTU_UNIT_TYPES.BOILER_VALVE + return RTU_UNIT_TYPE.BOILER_VALVE elseif type == rtu_t.turbine_valve then - return RTU_UNIT_TYPES.TURBINE_VALVE + return RTU_UNIT_TYPE.TURBINE_VALVE elseif type == rtu_t.induction_matrix then - return RTU_UNIT_TYPES.IMATRIX + return RTU_UNIT_TYPE.IMATRIX elseif type == rtu_t.sps then - return RTU_UNIT_TYPES.SPS + return RTU_UNIT_TYPE.SPS elseif type == rtu_t.sna then - return RTU_UNIT_TYPES.SNA + return RTU_UNIT_TYPE.SNA elseif type == rtu_t.env_detector then - return RTU_UNIT_TYPES.ENV_DETECTOR + return RTU_UNIT_TYPE.ENV_DETECTOR end return nil @@ -745,22 +745,22 @@ end -- convert RTU unit type to rtu_t ---@nodiscard ----@param utype RTU_UNIT_TYPES +---@param utype RTU_UNIT_TYPE ---@return rtu_t|nil function comms.advert_type_to_rtu_t(utype) - if utype == RTU_UNIT_TYPES.REDSTONE then + if utype == RTU_UNIT_TYPE.REDSTONE then return rtu_t.redstone - elseif utype == RTU_UNIT_TYPES.BOILER_VALVE then + elseif utype == RTU_UNIT_TYPE.BOILER_VALVE then return rtu_t.boiler_valve - elseif utype == RTU_UNIT_TYPES.TURBINE_VALVE then + elseif utype == RTU_UNIT_TYPE.TURBINE_VALVE then return rtu_t.turbine_valve - elseif utype == RTU_UNIT_TYPES.IMATRIX then + elseif utype == RTU_UNIT_TYPE.IMATRIX then return rtu_t.induction_matrix - elseif utype == RTU_UNIT_TYPES.SPS then + elseif utype == RTU_UNIT_TYPE.SPS then return rtu_t.sps - elseif utype == RTU_UNIT_TYPES.SNA then + elseif utype == RTU_UNIT_TYPE.SNA then return rtu_t.sna - elseif utype == RTU_UNIT_TYPES.ENV_DETECTOR then + elseif utype == RTU_UNIT_TYPE.ENV_DETECTOR then return rtu_t.env_detector end diff --git a/scada-common/types.lua b/scada-common/types.lua index cf5f85f..88473e3 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -58,7 +58,7 @@ function types.new_coordinate(x, y, z) return { x = x, y = y, z = z } end function types.new_zero_coordinate() return { x = 0, y = 0, z = 0 } end ---@class rtu_advertisement ----@field type RTU_UNIT_TYPES +---@field type RTU_UNIT_TYPE ---@field index integer ---@field reactor integer ---@field rsio table|nil diff --git a/supervisor/facility.lua b/supervisor/facility.lua index fe9db20..1a86c40 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -12,7 +12,7 @@ local PROCESS_NAMES = types.PROCESS_NAMES local IO = rsio.IO --- 7.14 kJ per blade for 1 mB of fissile fuel
+-- 7.14 kJ per blade for 1 mB of fissile fuel
-- 2856 FE per blade per 1 mB, 285.6 FE per blade per 0.1 mB (minimum) local POWER_PER_BLADE = util.joules_to_fe(7140) diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 55bbcc1..40231ad 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -7,12 +7,12 @@ local svqtypes = require("supervisor.session.svqtypes") local coordinator = {} -local PROTOCOLS = comms.PROTOCOLS -local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES -local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES -local UNIT_COMMANDS = comms.UNIT_COMMANDS -local FAC_COMMANDS = comms.FAC_COMMANDS -local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local PROTOCOL = comms.PROTOCOL +local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE +local SCADA_CRDN_TYPE = comms.SCADA_CRDN_TYPE +local UNIT_COMMAND = comms.UNIT_COMMAND +local FAC_COMMAND = comms.FAC_COMMAND +local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE local SV_Q_CMDS = svqtypes.SV_Q_CMDS local SV_Q_DATA = svqtypes.SV_Q_DATA @@ -90,28 +90,28 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) end -- send a CRDN packet - ---@param msg_type SCADA_CRDN_TYPES + ---@param msg_type SCADA_CRDN_TYPE ---@param msg table local function _send(msg_type, msg) local s_pkt = comms.scada_packet() local c_pkt = comms.crdn_packet() c_pkt.make(msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOLS.SCADA_CRDN, c_pkt.raw_sendable()) + s_pkt.make(self.seq_num, PROTOCOL.SCADA_CRDN, c_pkt.raw_sendable()) self.out_q.push_packet(s_pkt) self.seq_num = self.seq_num + 1 end -- send a SCADA management packet - ---@param msg_type SCADA_MGMT_TYPES + ---@param msg_type SCADA_MGMT_TYPE ---@param msg table local function _send_mgmt(msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() m_pkt.make(msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) + s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) self.out_q.push_packet(s_pkt) self.seq_num = self.seq_num + 1 @@ -126,12 +126,12 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) unit_builds[unit.get_id()] = unit.get_build() end - _send(SCADA_CRDN_TYPES.INITIAL_BUILDS, { facility.get_build(), unit_builds }) + _send(SCADA_CRDN_TYPE.INITIAL_BUILDS, { facility.get_build(), unit_builds }) end -- send facility builds local function _send_fac_builds() - _send(SCADA_CRDN_TYPES.FAC_BUILDS, { facility.get_build() }) + _send(SCADA_CRDN_TYPE.FAC_BUILDS, { facility.get_build() }) end -- send unit builds @@ -143,7 +143,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) builds[unit.get_id()] = unit.get_build() end - _send(SCADA_CRDN_TYPES.UNIT_BUILDS, { builds }) + _send(SCADA_CRDN_TYPE.UNIT_BUILDS, { builds }) end -- send facility status @@ -153,7 +153,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) facility.get_rtu_statuses() } - _send(SCADA_CRDN_TYPES.FAC_STATUS, status) + _send(SCADA_CRDN_TYPE.FAC_STATUS, status) end -- send unit statuses @@ -172,7 +172,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) } end - _send(SCADA_CRDN_TYPES.UNIT_STATUSES, status) + _send(SCADA_CRDN_TYPE.UNIT_STATUSES, status) end -- handle a packet @@ -192,8 +192,8 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) self.conn_watchdog.feed() -- process packet - if pkt.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then - if pkt.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + if pkt.scada_frame.protocol() == PROTOCOL.SCADA_MGMT then + if pkt.type == SCADA_MGMT_TYPE.KEEP_ALIVE then -- keep alive reply if pkt.length == 2 then local srv_start = pkt.data[1] @@ -210,30 +210,30 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) else log.debug(log_header .. "SCADA keep alive packet length mismatch") end - elseif pkt.type == SCADA_MGMT_TYPES.CLOSE then + elseif pkt.type == SCADA_MGMT_TYPE.CLOSE then -- close the session _close() else log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) end - elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_CRDN then - if pkt.type == SCADA_CRDN_TYPES.INITIAL_BUILDS then + elseif pkt.scada_frame.protocol() == PROTOCOL.SCADA_CRDN then + if pkt.type == SCADA_CRDN_TYPE.INITIAL_BUILDS then -- acknowledgement to coordinator receiving builds self.acks.builds = true - elseif pkt.type == SCADA_CRDN_TYPES.FAC_BUILDS then + elseif pkt.type == SCADA_CRDN_TYPE.FAC_BUILDS then -- acknowledgement to coordinator receiving builds self.acks.fac_builds = true - elseif pkt.type == SCADA_CRDN_TYPES.FAC_CMD then + elseif pkt.type == SCADA_CRDN_TYPE.FAC_CMD then if pkt.length >= 1 then local cmd = pkt.data[1] - if cmd == FAC_COMMANDS.SCRAM_ALL then + if cmd == FAC_COMMAND.SCRAM_ALL then facility.scram_all() - _send(SCADA_CRDN_TYPES.FAC_CMD, { cmd, true }) - elseif cmd == FAC_COMMANDS.STOP then + _send(SCADA_CRDN_TYPE.FAC_CMD, { cmd, true }) + elseif cmd == FAC_COMMAND.STOP then facility.auto_stop() - _send(SCADA_CRDN_TYPES.FAC_CMD, { cmd, true }) - elseif cmd == FAC_COMMANDS.START then + _send(SCADA_CRDN_TYPE.FAC_CMD, { cmd, true }) + elseif cmd == FAC_COMMAND.START then if pkt.length == 6 then ---@type coord_auto_config local config = { @@ -244,23 +244,23 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) limits = pkt.data[6] } - _send(SCADA_CRDN_TYPES.FAC_CMD, { cmd, table.unpack(facility.auto_start(config)) }) + _send(SCADA_CRDN_TYPE.FAC_CMD, { cmd, table.unpack(facility.auto_start(config)) }) else log.debug(log_header .. "CRDN auto start (with configuration) packet length mismatch") end - elseif cmd == FAC_COMMANDS.ACK_ALL_ALARMS then + elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then facility.ack_all() - _send(SCADA_CRDN_TYPES.FAC_CMD, { cmd, true }) + _send(SCADA_CRDN_TYPE.FAC_CMD, { cmd, true }) else log.debug(log_header .. "CRDN facility command unknown") end else log.debug(log_header .. "CRDN facility command packet length mismatch") end - elseif pkt.type == SCADA_CRDN_TYPES.UNIT_BUILDS then + elseif pkt.type == SCADA_CRDN_TYPE.UNIT_BUILDS then -- acknowledgement to coordinator receiving builds self.acks.unit_builds = true - elseif pkt.type == SCADA_CRDN_TYPES.UNIT_CMD then + elseif pkt.type == SCADA_CRDN_TYPE.UNIT_CMD then if pkt.length >= 2 then -- get command and unit id local cmd = pkt.data[1] @@ -273,43 +273,43 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) if util.is_int(uid) and uid > 0 and uid <= #self.units then local unit = self.units[uid] ---@type reactor_unit - if cmd == UNIT_COMMANDS.START then + if cmd == UNIT_COMMAND.START then self.out_q.push_data(SV_Q_DATA.START, data) - elseif cmd == UNIT_COMMANDS.SCRAM then + elseif cmd == UNIT_COMMAND.SCRAM then self.out_q.push_data(SV_Q_DATA.SCRAM, data) - elseif cmd == UNIT_COMMANDS.RESET_RPS then + elseif cmd == UNIT_COMMAND.RESET_RPS then self.out_q.push_data(SV_Q_DATA.RESET_RPS, data) - elseif cmd == UNIT_COMMANDS.SET_BURN then + elseif cmd == UNIT_COMMAND.SET_BURN then if pkt.length == 3 then self.out_q.push_data(SV_Q_DATA.SET_BURN, data) else log.debug(log_header .. "CRDN unit command burn rate missing option") end - elseif cmd == UNIT_COMMANDS.SET_WASTE then + elseif cmd == UNIT_COMMAND.SET_WASTE then if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] > 0) and (pkt.data[3] <= 4) then unit.set_waste(pkt.data[3]) else log.debug(log_header .. "CRDN unit command set waste missing option") end - elseif cmd == UNIT_COMMANDS.ACK_ALL_ALARMS then + elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then unit.ack_all() - _send(SCADA_CRDN_TYPES.UNIT_CMD, { cmd, uid, true }) - elseif cmd == UNIT_COMMANDS.ACK_ALARM then + _send(SCADA_CRDN_TYPE.UNIT_CMD, { cmd, uid, true }) + elseif cmd == UNIT_COMMAND.ACK_ALARM then if pkt.length == 3 then unit.ack_alarm(pkt.data[3]) else log.debug(log_header .. "CRDN unit command ack alarm missing alarm id") end - elseif cmd == UNIT_COMMANDS.RESET_ALARM then + elseif cmd == UNIT_COMMAND.RESET_ALARM then if pkt.length == 3 then unit.reset_alarm(pkt.data[3]) else log.debug(log_header .. "CRDN unit command reset alarm missing alarm id") end - elseif cmd == UNIT_COMMANDS.SET_GROUP then + elseif cmd == UNIT_COMMAND.SET_GROUP then if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] >= 0) and (pkt.data[3] <= 4) then facility.set_group(unit.get_id(), pkt.data[3]) - _send(SCADA_CRDN_TYPES.UNIT_CMD, { cmd, uid, pkt.data[3] }) + _send(SCADA_CRDN_TYPE.UNIT_CMD, { cmd, uid, pkt.data[3] }) else log.debug(log_header .. "CRDN unit command set group missing group id") end @@ -342,7 +342,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) -- close the connection function public.close() _close() - _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) + _send_mgmt(SCADA_MGMT_TYPE.CLOSE, {}) println("connection to coordinator " .. id .. " closed by server") log.info(log_header .. "session closed by server") end @@ -373,7 +373,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) if cmd.key == CRD_S_DATA.CMD_ACK then local ack = cmd.val ---@type coord_ack - _send(SCADA_CRDN_TYPES.UNIT_CMD, { ack.cmd, ack.unit, ack.ack }) + _send(SCADA_CRDN_TYPE.UNIT_CMD, { ack.cmd, ack.unit, ack.ack }) elseif cmd.key == CRD_S_DATA.RESEND_PLC_BUILD then -- re-send PLC build -- retry logic will be kept as-is, so as long as no retry is needed, this will be a small update @@ -386,7 +386,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) local unit = self.units[unit_id] ---@type reactor_unit builds[unit_id] = unit.get_build(true, false, false) - _send(SCADA_CRDN_TYPES.UNIT_BUILDS, { builds }) + _send(SCADA_CRDN_TYPE.UNIT_BUILDS, { builds }) elseif cmd.key == CRD_S_DATA.RESEND_RTU_BUILD then local unit_id = cmd.val.unit if unit_id > 0 then @@ -398,16 +398,16 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) local builds = {} local unit = self.units[unit_id] ---@type reactor_unit - builds[unit_id] = unit.get_build(false, cmd.val.type == RTU_UNIT_TYPES.BOILER_VALVE, cmd.val.type == RTU_UNIT_TYPES.TURBINE_VALVE) + builds[unit_id] = unit.get_build(false, cmd.val.type == RTU_UNIT_TYPE.BOILER_VALVE, cmd.val.type == RTU_UNIT_TYPE.TURBINE_VALVE) - _send(SCADA_CRDN_TYPES.UNIT_BUILDS, { builds }) + _send(SCADA_CRDN_TYPE.UNIT_BUILDS, { builds }) else -- re-send facility RTU builds -- retry logic will be kept as-is, so as long as no retry is needed, this will be a small update self.retry_times.f_builds_packet = util.time() + PARTIAL_RETRY_PERIOD self.acks.fac_builds = false - _send(SCADA_CRDN_TYPES.FAC_BUILDS, { facility.get_build(cmd.val.type == RTU_UNIT_TYPES.IMATRIX) }) + _send(SCADA_CRDN_TYPE.FAC_BUILDS, { facility.get_build(cmd.val.type == RTU_UNIT_TYPE.IMATRIX) }) end else log.warning(log_header .. "unsupported data command received in in_queue (this is a bug)") @@ -441,7 +441,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) periodics.keep_alive = periodics.keep_alive + elapsed if periodics.keep_alive >= PERIODICS.KEEP_ALIVE then - _send_mgmt(SCADA_MGMT_TYPES.KEEP_ALIVE, { util.time() }) + _send_mgmt(SCADA_MGMT_TYPE.KEEP_ALIVE, { util.time() }) periodics.keep_alive = 0 end diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 3d3270b..b311369 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -8,12 +8,12 @@ local svqtypes = require("supervisor.session.svqtypes") local plc = {} -local PROTOCOLS = comms.PROTOCOLS -local RPLC_TYPES = comms.RPLC_TYPES -local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES +local PROTOCOL = comms.PROTOCOL +local RPLC_TYPE = comms.RPLC_TYPE +local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE local PLC_AUTO_ACK = comms.PLC_AUTO_ACK -local UNIT_COMMANDS = comms.UNIT_COMMANDS +local UNIT_COMMAND = comms.UNIT_COMMAND local print = util.print local println = util.println @@ -244,28 +244,28 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) end -- send an RPLC packet - ---@param msg_type RPLC_TYPES + ---@param msg_type RPLC_TYPE ---@param msg table local function _send(msg_type, msg) local s_pkt = comms.scada_packet() local r_pkt = comms.rplc_packet() r_pkt.make(for_reactor, msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOLS.RPLC, r_pkt.raw_sendable()) + s_pkt.make(self.seq_num, PROTOCOL.RPLC, r_pkt.raw_sendable()) self.out_q.push_packet(s_pkt) self.seq_num = self.seq_num + 1 end -- send a SCADA management packet - ---@param msg_type SCADA_MGMT_TYPES + ---@param msg_type SCADA_MGMT_TYPE ---@param msg table local function _send_mgmt(msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() m_pkt.make(msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) + s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) self.out_q.push_packet(s_pkt) self.seq_num = self.seq_num + 1 @@ -297,7 +297,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) end -- process packet - if pkt.scada_frame.protocol() == PROTOCOLS.RPLC then + if pkt.scada_frame.protocol() == PROTOCOL.RPLC then -- check reactor ID if pkt.id ~= for_reactor then log.warning(log_header .. "RPLC packet with ID not matching reactor ID: reactor " .. self.for_reactor .. " != " .. pkt.id) @@ -308,7 +308,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) self.plc_conn_watchdog.feed() -- handle packet by type - if pkt.type == RPLC_TYPES.STATUS then + if pkt.type == RPLC_TYPE.STATUS then -- status packet received, update data if pkt.length >= 5 then self.sDB.last_status_update = pkt.data[1] @@ -335,7 +335,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) else log.debug(log_header .. "RPLC status packet length mismatch") end - elseif pkt.type == RPLC_TYPES.MEK_STRUCT then + elseif pkt.type == RPLC_TYPE.MEK_STRUCT then -- received reactor structure, record it if pkt.length == 14 then local status = pcall(_copy_struct, pkt.data) @@ -350,7 +350,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) else log.debug(log_header .. "RPLC struct packet length mismatch") end - elseif pkt.type == RPLC_TYPES.MEK_BURN_RATE then + elseif pkt.type == RPLC_TYPE.MEK_BURN_RATE then -- burn rate acknowledgement local ack = _get_ack(pkt) if ack then @@ -362,10 +362,10 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) -- send acknowledgement to coordinator self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { unit = self.for_reactor, - cmd = UNIT_COMMANDS.SET_BURN, + cmd = UNIT_COMMAND.SET_BURN, ack = ack }) - elseif pkt.type == RPLC_TYPES.RPS_ENABLE then + elseif pkt.type == RPLC_TYPE.RPS_ENABLE then -- enable acknowledgement local ack = _get_ack(pkt) if ack then @@ -377,10 +377,10 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) -- send acknowledgement to coordinator self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { unit = self.for_reactor, - cmd = UNIT_COMMANDS.START, + cmd = UNIT_COMMAND.START, ack = ack }) - elseif pkt.type == RPLC_TYPES.RPS_SCRAM then + elseif pkt.type == RPLC_TYPE.RPS_SCRAM then -- manual SCRAM acknowledgement local ack = _get_ack(pkt) if ack then @@ -393,10 +393,10 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) -- send acknowledgement to coordinator self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { unit = self.for_reactor, - cmd = UNIT_COMMANDS.SCRAM, + cmd = UNIT_COMMAND.SCRAM, ack = ack }) - elseif pkt.type == RPLC_TYPES.RPS_ASCRAM then + elseif pkt.type == RPLC_TYPE.RPS_ASCRAM then -- automatic SCRAM acknowledgement local ack = _get_ack(pkt) if ack then @@ -405,7 +405,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) elseif ack == false then log.debug(log_header .. " automatic SCRAM failed!") end - elseif pkt.type == RPLC_TYPES.RPS_STATUS then + elseif pkt.type == RPLC_TYPE.RPS_STATUS then -- RPS status packet received, copy data if pkt.length == 14 then local status = pcall(_copy_rps_status, pkt.data) @@ -418,7 +418,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) else log.debug(log_header .. "RPLC RPS status packet length mismatch") end - elseif pkt.type == RPLC_TYPES.RPS_ALARM then + elseif pkt.type == RPLC_TYPE.RPS_ALARM then -- RPS alarm if pkt.length == 13 then local status = pcall(_copy_rps_status, { true, table.unpack(pkt.data) }) @@ -431,7 +431,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) else log.debug(log_header .. "RPLC RPS alarm packet length mismatch") end - elseif pkt.type == RPLC_TYPES.RPS_RESET then + elseif pkt.type == RPLC_TYPE.RPS_RESET then -- RPS reset acknowledgement local ack = _get_ack(pkt) if ack then @@ -445,16 +445,16 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) -- send acknowledgement to coordinator self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { unit = self.for_reactor, - cmd = UNIT_COMMANDS.RESET_RPS, + cmd = UNIT_COMMAND.RESET_RPS, ack = ack }) - elseif pkt.type == RPLC_TYPES.RPS_AUTO_RESET then + elseif pkt.type == RPLC_TYPE.RPS_AUTO_RESET then -- RPS auto control reset acknowledgement local ack = _get_ack(pkt) if not ack then log.debug(log_header .. "RPS auto reset failed") end - elseif pkt.type == RPLC_TYPES.AUTO_BURN_RATE then + elseif pkt.type == RPLC_TYPE.AUTO_BURN_RATE then if pkt.length == 1 then local ack = pkt.data[1] @@ -473,8 +473,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) else log.debug(log_header .. "handler received unsupported RPLC packet type " .. pkt.type) end - elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then - if pkt.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + elseif pkt.scada_frame.protocol() == PROTOCOL.SCADA_MGMT then + if pkt.type == SCADA_MGMT_TYPE.KEEP_ALIVE then -- keep alive reply if pkt.length == 2 then local srv_start = pkt.data[1] @@ -491,7 +491,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) else log.debug(log_header .. "SCADA keep alive packet length mismatch") end - elseif pkt.type == SCADA_MGMT_TYPES.CLOSE then + elseif pkt.type == SCADA_MGMT_TYPE.CLOSE then -- close the session _close() else @@ -575,7 +575,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) -- close the connection function public.close() _close() - _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) + _send_mgmt(SCADA_MGMT_TYPE.CLOSE, {}) println("connection to reactor " .. self.for_reactor .. " PLC closed by server") log.info(log_header .. "session closed by server") end @@ -604,27 +604,27 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) if cmd == PLC_S_CMDS.ENABLE then -- enable reactor if not self.auto_lock then - _send(RPLC_TYPES.RPS_ENABLE, {}) + _send(RPLC_TYPE.RPS_ENABLE, {}) end elseif cmd == PLC_S_CMDS.SCRAM then -- SCRAM reactor self.acks.scram = false self.retry_times.scram_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.RPS_SCRAM, {}) + _send(RPLC_TYPE.RPS_SCRAM, {}) elseif cmd == PLC_S_CMDS.ASCRAM then -- SCRAM reactor self.acks.ascram = false self.retry_times.ascram_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.RPS_ASCRAM, {}) + _send(RPLC_TYPE.RPS_ASCRAM, {}) elseif cmd == PLC_S_CMDS.RPS_RESET then -- reset RPS self.acks.ascram = true self.acks.rps_reset = false self.retry_times.rps_reset_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.RPS_RESET, {}) + _send(RPLC_TYPE.RPS_RESET, {}) elseif cmd == PLC_S_CMDS.RPS_AUTO_RESET then if self.sDB.rps_status.automatic or self.sDB.rps_status.timeout then - _send(RPLC_TYPES.RPS_AUTO_RESET, {}) + _send(RPLC_TYPE.RPS_AUTO_RESET, {}) end else log.warning(log_header .. "unsupported command received in in_queue (this is a bug)") @@ -642,7 +642,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) self.ramping_rate = false self.acks.burn_rate = false self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + _send(RPLC_TYPE.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) end end elseif cmd.key == PLC_S_DATA.RAMP_BURN_RATE then @@ -655,7 +655,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) self.ramping_rate = true self.acks.burn_rate = false self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + _send(RPLC_TYPE.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) end end elseif cmd.key == PLC_S_DATA.AUTO_BURN_RATE then @@ -670,7 +670,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) self.acks.burn_rate = not self.ramping_rate self.retry_times.burn_rate_req = util.time() + INITIAL_AUTO_WAIT - _send(RPLC_TYPES.AUTO_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate, self.auto_cmd_token }) + _send(RPLC_TYPE.AUTO_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate, self.auto_cmd_token }) end end else @@ -705,7 +705,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) periodics.keep_alive = periodics.keep_alive + elapsed if periodics.keep_alive >= PERIODICS.KEEP_ALIVE then - _send_mgmt(SCADA_MGMT_TYPES.KEEP_ALIVE, { util.time() }) + _send_mgmt(SCADA_MGMT_TYPE.KEEP_ALIVE, { util.time() }) periodics.keep_alive = 0 end @@ -722,7 +722,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) if not self.received_struct then if rtimes.struct_req - util.time() <= 0 then - _send(RPLC_TYPES.MEK_STRUCT, {}) + _send(RPLC_TYPE.MEK_STRUCT, {}) rtimes.struct_req = util.time() + RETRY_PERIOD end end @@ -731,7 +731,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) if not self.received_status_cache then if rtimes.status_req - util.time() <= 0 then - _send(RPLC_TYPES.MEK_STATUS, {}) + _send(RPLC_TYPE.MEK_STATUS, {}) rtimes.status_req = util.time() + RETRY_PERIOD end end @@ -742,13 +742,13 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) if rtimes.burn_rate_req - util.time() <= 0 then if self.auto_cmd_token > 0 then if self.auto_lock then - _send(RPLC_TYPES.AUTO_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate, self.auto_cmd_token }) + _send(RPLC_TYPE.AUTO_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate, self.auto_cmd_token }) else -- would have been an auto command, but disengaged, so stop retrying self.acks.burn_rate = true end elseif not self.auto_lock then - _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + _send(RPLC_TYPE.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) else -- shouldn't be in this state, just pretend it was acknowledged self.acks.burn_rate = true @@ -763,7 +763,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) if not self.acks.scram then if rtimes.scram_req - util.time() <= 0 then - _send(RPLC_TYPES.RPS_SCRAM, {}) + _send(RPLC_TYPE.RPS_SCRAM, {}) rtimes.scram_req = util.time() + RETRY_PERIOD end end @@ -772,7 +772,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) if not self.acks.ascram then if rtimes.ascram_req - util.time() <= 0 then - _send(RPLC_TYPES.RPS_ASCRAM, {}) + _send(RPLC_TYPE.RPS_ASCRAM, {}) rtimes.ascram_req = util.time() + RETRY_PERIOD end end @@ -781,7 +781,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) if not self.acks.rps_reset then if rtimes.rps_reset_req - util.time() <= 0 then - _send(RPLC_TYPES.RPS_RESET, {}) + _send(RPLC_TYPE.RPS_RESET, {}) rtimes.rps_reset_req = util.time() + RETRY_PERIOD end end diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 1db89cb..44d8cec 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -18,9 +18,9 @@ local svrs_turbinev = require("supervisor.session.rtu.turbinev") local rtu = {} -local PROTOCOLS = comms.PROTOCOLS -local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES -local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local PROTOCOL = comms.PROTOCOL +local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE +local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE local print = util.print local println = util.println @@ -99,7 +99,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili advert_validator.assert_type_int(unit_advert.index) advert_validator.assert_type_int(unit_advert.reactor) - if u_type == RTU_UNIT_TYPES.REDSTONE then + if u_type == RTU_UNIT_TYPE.REDSTONE then advert_validator.assert_type_table(unit_advert.rsio) end @@ -124,19 +124,19 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili if unit_advert.reactor > 0 then local target_unit = self.fac_units[unit_advert.reactor] ---@type reactor_unit - if u_type == RTU_UNIT_TYPES.REDSTONE then + if u_type == RTU_UNIT_TYPE.REDSTONE then -- redstone unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q) if type(unit) ~= "nil" then target_unit.add_redstone(unit) end - elseif u_type == RTU_UNIT_TYPES.BOILER_VALVE then + elseif u_type == RTU_UNIT_TYPE.BOILER_VALVE then -- boiler (Mekanism 10.1+) unit = svrs_boilerv.new(id, i, unit_advert, self.modbus_q) if type(unit) ~= "nil" then target_unit.add_boiler(unit) end - elseif u_type == RTU_UNIT_TYPES.TURBINE_VALVE then + elseif u_type == RTU_UNIT_TYPE.TURBINE_VALVE then -- turbine (Mekanism 10.1+) unit = svrs_turbinev.new(id, i, unit_advert, self.modbus_q) if type(unit) ~= "nil" then target_unit.add_turbine(unit) end - elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then + elseif u_type == RTU_UNIT_TYPE.ENV_DETECTOR then -- environment detector unit = svrs_envd.new(id, i, unit_advert, self.modbus_q) if type(unit) ~= "nil" then target_unit.add_envd(unit) end @@ -144,21 +144,21 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili log.error(util.c(log_header, "bad advertisement: encountered unsupported reactor-specific RTU type ", type_string)) end else - if u_type == RTU_UNIT_TYPES.REDSTONE then + if u_type == RTU_UNIT_TYPE.REDSTONE then -- redstone unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q) if type(unit) ~= "nil" then facility.add_redstone(unit) end - elseif u_type == RTU_UNIT_TYPES.IMATRIX then + elseif u_type == RTU_UNIT_TYPE.IMATRIX then -- induction matrix unit = svrs_imatrix.new(id, i, unit_advert, self.modbus_q) if type(unit) ~= "nil" then facility.add_imatrix(unit) end - elseif u_type == RTU_UNIT_TYPES.SPS then + elseif u_type == RTU_UNIT_TYPE.SPS then -- super-critical phase shifter unit = svrs_sps.new(id, i, unit_advert, self.modbus_q) - elseif u_type == RTU_UNIT_TYPES.SNA then + elseif u_type == RTU_UNIT_TYPE.SNA then -- solar neutron activator unit = svrs_sna.new(id, i, unit_advert, self.modbus_q) - elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then + elseif u_type == RTU_UNIT_TYPE.ENV_DETECTOR then -- environment detector unit = svrs_envd.new(id, i, unit_advert, self.modbus_q) if type(unit) ~= "nil" then facility.add_envd(unit) end @@ -194,21 +194,21 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili local function _send_modbus(m_pkt) local s_pkt = comms.scada_packet() - s_pkt.make(self.seq_num, PROTOCOLS.MODBUS_TCP, m_pkt.raw_sendable()) + s_pkt.make(self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable()) self.out_q.push_packet(s_pkt) self.seq_num = self.seq_num + 1 end -- send a SCADA management packet - ---@param msg_type SCADA_MGMT_TYPES + ---@param msg_type SCADA_MGMT_TYPE ---@param msg table local function _send_mgmt(msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() m_pkt.make(msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) + s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) self.out_q.push_packet(s_pkt) self.seq_num = self.seq_num + 1 @@ -231,15 +231,15 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili self.rtu_conn_watchdog.feed() -- process packet - if pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then + if pkt.scada_frame.protocol() == PROTOCOL.MODBUS_TCP then if self.units[pkt.unit_id] ~= nil then local unit = self.units[pkt.unit_id] ---@type unit_session ---@diagnostic disable-next-line: param-type-mismatch unit.handle_packet(pkt) end - elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then + elseif pkt.scada_frame.protocol() == PROTOCOL.SCADA_MGMT then -- handle management packet - if pkt.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + if pkt.type == SCADA_MGMT_TYPE.KEEP_ALIVE then -- keep alive reply if pkt.length == 2 then local srv_start = pkt.data[1] @@ -256,10 +256,10 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili else log.debug(log_header .. "SCADA keep alive packet length mismatch") end - elseif pkt.type == SCADA_MGMT_TYPES.CLOSE then + elseif pkt.type == SCADA_MGMT_TYPE.CLOSE then -- close the session _close() - elseif pkt.type == SCADA_MGMT_TYPES.RTU_ADVERT then + elseif pkt.type == SCADA_MGMT_TYPE.RTU_ADVERT then -- RTU unit advertisement log.debug(log_header .. "received updated advertisement") @@ -269,7 +269,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili -- handle advertisement; this will re-create all unit sub-sessions _handle_advertisement() - elseif pkt.type == SCADA_MGMT_TYPES.RTU_DEV_REMOUNT then + elseif pkt.type == SCADA_MGMT_TYPE.RTU_DEV_REMOUNT then if pkt.length == 1 then local unit_id = pkt.data[1] if self.units[unit_id] ~= nil then @@ -299,7 +299,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili -- close the connection function public.close() _close() - _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) + _send_mgmt(SCADA_MGMT_TYPE.CLOSE, {}) println(log_header .. "connection to RTU closed by server") log.info(log_header .. "session closed by server") end @@ -365,7 +365,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili periodics.keep_alive = periodics.keep_alive + elapsed if periodics.keep_alive >= PERIODICS.KEEP_ALIVE then - _send_mgmt(SCADA_MGMT_TYPES.KEEP_ALIVE, { util.time() }) + _send_mgmt(SCADA_MGMT_TYPE.KEEP_ALIVE, { util.time() }) periodics.keep_alive = 0 end diff --git a/supervisor/session/rtu/boilerv.lua b/supervisor/session/rtu/boilerv.lua index 0cc4945..65b00b6 100644 --- a/supervisor/session/rtu/boilerv.lua +++ b/supervisor/session/rtu/boilerv.lua @@ -7,7 +7,7 @@ local unit_session = require("supervisor.session.rtu.unit_session") local boilerv = {} -local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE local MODBUS_FCODE = types.MODBUS_FCODE local TXN_TYPES = { @@ -38,7 +38,7 @@ local PERIODICS = { ---@param out_queue mqueue RTU unit message out queue function boilerv.new(session_id, unit_id, advert, out_queue) -- type check - if advert.type ~= RTU_UNIT_TYPES.BOILER_VALVE then + if advert.type ~= RTU_UNIT_TYPE.BOILER_VALVE then log.error("attempt to instantiate boilerv RTU for type '" .. advert.type .. "'. this is a bug.") return nil end diff --git a/supervisor/session/rtu/envd.lua b/supervisor/session/rtu/envd.lua index bc65476..adee4aa 100644 --- a/supervisor/session/rtu/envd.lua +++ b/supervisor/session/rtu/envd.lua @@ -7,7 +7,7 @@ local unit_session = require("supervisor.session.rtu.unit_session") local envd = {} -local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE local MODBUS_FCODE = types.MODBUS_FCODE local TXN_TYPES = { @@ -29,7 +29,7 @@ local PERIODICS = { ---@param out_queue mqueue function envd.new(session_id, unit_id, advert, out_queue) -- type check - if advert.type ~= RTU_UNIT_TYPES.ENV_DETECTOR then + if advert.type ~= RTU_UNIT_TYPE.ENV_DETECTOR then log.error("attempt to instantiate envd RTU for type '" .. advert.type .. "'. this is a bug.") return nil end diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua index 607c22d..7d1ba18 100644 --- a/supervisor/session/rtu/imatrix.lua +++ b/supervisor/session/rtu/imatrix.lua @@ -7,7 +7,7 @@ local unit_session = require("supervisor.session.rtu.unit_session") local imatrix = {} -local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE local MODBUS_FCODE = types.MODBUS_FCODE local TXN_TYPES = { @@ -38,7 +38,7 @@ local PERIODICS = { ---@param out_queue mqueue RTU unit message out queue function imatrix.new(session_id, unit_id, advert, out_queue) -- type check - if advert.type ~= RTU_UNIT_TYPES.IMATRIX then + if advert.type ~= RTU_UNIT_TYPE.IMATRIX then log.error("attempt to instantiate imatrix RTU for type '" .. advert.type .. "'. this is a bug.") return nil end diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index a286f9c..48c8329 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -9,7 +9,7 @@ local unit_session = require("supervisor.session.rtu.unit_session") local redstone = {} -local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE local MODBUS_FCODE = types.MODBUS_FCODE local IO_PORT = rsio.IO @@ -53,7 +53,7 @@ local PERIODICS = { ---@param out_queue mqueue function redstone.new(session_id, unit_id, advert, out_queue) -- type check - if advert.type ~= RTU_UNIT_TYPES.REDSTONE then + if advert.type ~= RTU_UNIT_TYPE.REDSTONE then log.error("attempt to instantiate redstone RTU for type '" .. advert.type .. "'. this is a bug.") return nil end diff --git a/supervisor/session/rtu/sna.lua b/supervisor/session/rtu/sna.lua index e2a667e..454a587 100644 --- a/supervisor/session/rtu/sna.lua +++ b/supervisor/session/rtu/sna.lua @@ -7,7 +7,7 @@ local unit_session = require("supervisor.session.rtu.unit_session") local sna = {} -local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE local MODBUS_FCODE = types.MODBUS_FCODE local TXN_TYPES = { @@ -35,7 +35,7 @@ local PERIODICS = { ---@param out_queue mqueue RTU unit message out queue function sna.new(session_id, unit_id, advert, out_queue) -- type check - if advert.type ~= RTU_UNIT_TYPES.SNA then + if advert.type ~= RTU_UNIT_TYPE.SNA then log.error("attempt to instantiate sna RTU for type '" .. advert.type .. "'. this is a bug.") return nil end diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua index 9b07f3e..effd0d0 100644 --- a/supervisor/session/rtu/sps.lua +++ b/supervisor/session/rtu/sps.lua @@ -7,7 +7,7 @@ local unit_session = require("supervisor.session.rtu.unit_session") local sps = {} -local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE local MODBUS_FCODE = types.MODBUS_FCODE local TXN_TYPES = { @@ -38,7 +38,7 @@ local PERIODICS = { ---@param out_queue mqueue RTU unit message out queue function sps.new(session_id, unit_id, advert, out_queue) -- type check - if advert.type ~= RTU_UNIT_TYPES.SPS then + if advert.type ~= RTU_UNIT_TYPE.SPS then log.error("attempt to instantiate sps RTU for type '" .. advert.type .. "'. this is a bug.") return nil end diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua index 3f8357f..f0d8741 100644 --- a/supervisor/session/rtu/turbinev.lua +++ b/supervisor/session/rtu/turbinev.lua @@ -9,7 +9,7 @@ local unit_session = require("supervisor.session.rtu.unit_session") local turbinev = {} -local RTU_UNIT_TYPES = comms.RTU_UNIT_TYPES +local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE local DUMPING_MODE = types.DUMPING_MODE local MODBUS_FCODE = types.MODBUS_FCODE @@ -50,7 +50,7 @@ local PERIODICS = { ---@param out_queue mqueue RTU unit message out queue function turbinev.new(session_id, unit_id, advert, out_queue) -- type check - if advert.type ~= RTU_UNIT_TYPES.TURBINE_VALVE then + if advert.type ~= RTU_UNIT_TYPE.TURBINE_VALVE then log.error("attempt to instantiate turbinev RTU for type '" .. advert.type .. "'. this is a bug.") return nil end diff --git a/supervisor/session/rtu/unit_session.lua b/supervisor/session/rtu/unit_session.lua index 27b21c0..069de28 100644 --- a/supervisor/session/rtu/unit_session.lua +++ b/supervisor/session/rtu/unit_session.lua @@ -8,7 +8,7 @@ local txnctrl = require("supervisor.session.rtu.txnctrl") local unit_session = {} -local PROTOCOLS = comms.PROTOCOLS +local PROTOCOL = comms.PROTOCOL local MODBUS_FCODE = types.MODBUS_FCODE local MODBUS_EXCODE = types.MODBUS_EXCODE @@ -72,7 +72,7 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t ---@param m_pkt modbus_frame MODBUS packet ---@return integer|false txn_type, integer txn_id transaction type or false on error/busy, transaction ID function protected.try_resolve(m_pkt) - if m_pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then + if m_pkt.scada_frame.protocol() == PROTOCOL.MODBUS_TCP then if m_pkt.unit_id == self.unit_id then local txn_type = self.transaction_controller.resolve(m_pkt.txn_id) local txn_tag = " (" .. util.strval(self.txn_tags[txn_type]) .. ")" diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 5cadad4..f28dd66 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -6,10 +6,10 @@ local svsessions = require("supervisor.session.svsessions") local supervisor = {} -local PROTOCOLS = comms.PROTOCOLS -local DEVICE_TYPES = comms.DEVICE_TYPES +local PROTOCOL = comms.PROTOCOL +local DEVICE_TYPE = comms.DEVICE_TYPE local ESTABLISH_ACK = comms.ESTABLISH_ACK -local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES +local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE local print = util.print local println = util.println @@ -60,8 +60,8 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() - m_pkt.make(SCADA_MGMT_TYPES.ESTABLISH, msg) - s_pkt.make(seq_id, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) + m_pkt.make(SCADA_MGMT_TYPE.ESTABLISH, msg) + s_pkt.make(seq_id, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) self.modem.transmit(dest, self.dev_listen, s_pkt.raw_sendable()) end @@ -74,8 +74,8 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen local s_pkt = comms.scada_packet() local c_pkt = comms.mgmt_packet() - c_pkt.make(SCADA_MGMT_TYPES.ESTABLISH, msg) - s_pkt.make(seq_id, PROTOCOLS.SCADA_MGMT, c_pkt.raw_sendable()) + c_pkt.make(SCADA_MGMT_TYPE.ESTABLISH, msg) + s_pkt.make(seq_id, PROTOCOL.SCADA_MGMT, c_pkt.raw_sendable()) self.modem.transmit(dest, self.coord_listen, s_pkt.raw_sendable()) end @@ -107,25 +107,25 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen if s_pkt.is_valid() then -- get as MODBUS TCP packet - if s_pkt.protocol() == PROTOCOLS.MODBUS_TCP then + if s_pkt.protocol() == PROTOCOL.MODBUS_TCP then local m_pkt = comms.modbus_packet() if m_pkt.decode(s_pkt) then pkt = m_pkt.get() end -- get as RPLC packet - elseif s_pkt.protocol() == PROTOCOLS.RPLC then + elseif s_pkt.protocol() == PROTOCOL.RPLC then local rplc_pkt = comms.rplc_packet() if rplc_pkt.decode(s_pkt) then pkt = rplc_pkt.get() end -- get as SCADA management packet - elseif s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then + elseif s_pkt.protocol() == PROTOCOL.SCADA_MGMT then local mgmt_pkt = comms.mgmt_packet() if mgmt_pkt.decode(s_pkt) then pkt = mgmt_pkt.get() end -- get as coordinator packet - elseif s_pkt.protocol() == PROTOCOLS.SCADA_CRDN then + elseif s_pkt.protocol() == PROTOCOL.SCADA_CRDN then local crdn_pkt = comms.crdn_packet() if crdn_pkt.decode(s_pkt) then pkt = crdn_pkt.get() @@ -148,7 +148,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen -- device (RTU/PLC) listening channel if l_port == self.dev_listen then - if protocol == PROTOCOLS.MODBUS_TCP then + if protocol == PROTOCOL.MODBUS_TCP then -- look for an associated session local session = svsessions.find_rtu_session(r_port) @@ -160,7 +160,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen -- any other packet should be session related, discard it log.debug("discarding MODBUS_TCP packet without a known session") end - elseif protocol == PROTOCOLS.RPLC then + elseif protocol == PROTOCOL.RPLC then -- look for an associated session local session = svsessions.find_plc_session(r_port) @@ -173,7 +173,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen log.debug("PLC_ESTABLISH: no session but not an establish, forcing relink") _send_dev_establish(packet.scada_frame.seq_num() + 1, r_port, { ESTABLISH_ACK.DENY }) end - elseif protocol == PROTOCOLS.SCADA_MGMT then + elseif protocol == PROTOCOL.SCADA_MGMT then -- look for an associated session local session = svsessions.find_device_session(r_port) @@ -181,7 +181,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen if session ~= nil then -- pass the packet onto the session handler session.in_queue.push_packet(packet) - elseif packet.type == SCADA_MGMT_TYPES.ESTABLISH then + elseif packet.type == SCADA_MGMT_TYPE.ESTABLISH then -- establish a new session local next_seq_id = packet.scada_frame.seq_num() + 1 @@ -198,7 +198,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen return end - if dev_type == DEVICE_TYPES.PLC then + if dev_type == DEVICE_TYPE.PLC then -- PLC linking request if packet.length == 4 and type(packet.data[4]) == "number" then local reactor_id = packet.data[4] @@ -218,7 +218,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type") _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) end - elseif dev_type == DEVICE_TYPES.RTU then + elseif dev_type == DEVICE_TYPE.RTU then if packet.length == 4 then -- this is an RTU advertisement for a new session local rtu_advert = packet.data[4] @@ -251,12 +251,12 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen -- look for an associated session local session = svsessions.find_coord_session(r_port) - if protocol == PROTOCOLS.SCADA_MGMT then + if protocol == PROTOCOL.SCADA_MGMT then -- SCADA management packet if session ~= nil then -- pass the packet onto the session handler session.in_queue.push_packet(packet) - elseif packet.type == SCADA_MGMT_TYPES.ESTABLISH then + elseif packet.type == SCADA_MGMT_TYPE.ESTABLISH then -- establish a new session local next_seq_id = packet.scada_frame.seq_num() + 1 @@ -271,7 +271,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen " (expected v", comms.version, ")")) _send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.BAD_VERSION }) return - elseif dev_type ~= DEVICE_TYPES.CRDN then + elseif dev_type ~= DEVICE_TYPE.CRDN then log.debug(util.c("illegal establish packet for device ", dev_type, " on CRDN listening channel")) _send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) return @@ -302,7 +302,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen -- any other packet should be session related, discard it log.debug(r_port .. "->" .. l_port .. ": discarding SCADA_MGMT packet without a known session") end - elseif protocol == PROTOCOLS.SCADA_CRDN then + elseif protocol == PROTOCOL.SCADA_CRDN then -- coordinator packet if session ~= nil then -- pass the packet onto the session handler From 7c64a66dd391617c00f1aa5c6d2d2aad07d4d09a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 21 Feb 2023 11:29:04 -0500 Subject: [PATCH 540/587] #118 refactored rps_status_t --- reactor-plc/plc.lua | 44 ++++++++++++++++++++-------------------- scada-common/types.lua | 33 +++++++++++++++--------------- supervisor/unitlogic.lua | 8 ++++---- 3 files changed, 42 insertions(+), 43 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 5c00465..3368047 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -6,7 +6,7 @@ local util = require("scada-common.util") local plc = {} -local rps_status_t = types.rps_status_t +local RPS_TRIP_CAUSE = types.RPS_TRIP_CAUSE local PROTOCOL = comms.PROTOCOL local DEVICE_TYPE = comms.DEVICE_TYPE @@ -260,7 +260,7 @@ function plc.rps_init(reactor, is_formed) -- clear automatic SCRAM if it was the cause if self.tripped and self.trip_cause == "automatic" then self.state[state_keys.automatic] = true - self.trip_cause = rps_status_t.ok + self.trip_cause = RPS_TRIP_CAUSE.OK self.tripped = false log.debug("RPS: cleared automatic SCRAM for re-activation") @@ -270,9 +270,9 @@ function plc.rps_init(reactor, is_formed) end -- check all safety conditions - ---@return boolean tripped, rps_status_t trip_status, boolean first_trip + ---@return boolean tripped, rps_trip_cause trip_status, boolean first_trip function public.check() - local status = rps_status_t.ok + local status = RPS_TRIP_CAUSE.OK local was_tripped = self.tripped local first_trip = false @@ -298,47 +298,47 @@ function plc.rps_init(reactor, is_formed) status = self.trip_cause elseif self.state[state_keys.sys_fail] then log.warning("RPS: system failure, reactor not formed") - status = rps_status_t.sys_fail + status = RPS_TRIP_CAUSE.SYS_FAIL elseif self.state[state_keys.force_disabled] then log.warning("RPS: reactor was force disabled") - status = rps_status_t.force_disabled + status = RPS_TRIP_CAUSE.FORCE_DISABLED elseif self.state[state_keys.dmg_crit] then log.warning("RPS: damage critical") - status = rps_status_t.dmg_crit + status = RPS_TRIP_CAUSE.DMG_CRIT elseif self.state[state_keys.high_temp] then log.warning("RPS: high temperature") - status = rps_status_t.high_temp + status = RPS_TRIP_CAUSE.HIGH_TEMP elseif self.state[state_keys.no_coolant] then log.warning("RPS: no coolant") - status = rps_status_t.no_coolant + status = RPS_TRIP_CAUSE.NO_COOLANT elseif self.state[state_keys.ex_waste] then log.warning("RPS: full waste") - status = rps_status_t.ex_waste + status = RPS_TRIP_CAUSE.EX_WASTE elseif self.state[state_keys.ex_hcoolant] then log.warning("RPS: heated coolant backup") - status = rps_status_t.ex_hcoolant + status = RPS_TRIP_CAUSE.EX_HCOOLANT elseif self.state[state_keys.no_fuel] then log.warning("RPS: no fuel") - status = rps_status_t.no_fuel + status = RPS_TRIP_CAUSE.NO_FUEL elseif self.state[state_keys.fault] then log.warning("RPS: reactor access fault") - status = rps_status_t.fault + status = RPS_TRIP_CAUSE.FAULT elseif self.state[state_keys.timeout] then log.warning("RPS: supervisor connection timeout") - status = rps_status_t.timeout + status = RPS_TRIP_CAUSE.TIMEOUT elseif self.state[state_keys.manual] then log.warning("RPS: manual SCRAM requested") - status = rps_status_t.manual + status = RPS_TRIP_CAUSE.MANUAL elseif self.state[state_keys.automatic] then log.warning("RPS: automatic SCRAM requested") - status = rps_status_t.automatic + status = RPS_TRIP_CAUSE.AUTOMATIC else self.tripped = false - self.trip_cause = rps_status_t.ok + self.trip_cause = RPS_TRIP_CAUSE.OK end -- if a new trip occured... - if (not was_tripped) and (status ~= rps_status_t.ok) then + if (not was_tripped) and (status ~= RPS_TRIP_CAUSE.OK) then first_trip = true self.tripped = true self.trip_cause = status @@ -376,7 +376,7 @@ function plc.rps_init(reactor, is_formed) ---@param quiet? boolean true to suppress the info log message function public.reset(quiet) self.tripped = false - self.trip_cause = rps_status_t.ok + self.trip_cause = RPS_TRIP_CAUSE.OK for i = 1, #self.state do self.state[i] = false @@ -390,8 +390,8 @@ function plc.rps_init(reactor, is_formed) self.state[state_keys.automatic] = false self.state[state_keys.timeout] = false - if self.trip_cause == rps_status_t.automatic or self.trip_cause == rps_status_t.timeout then - self.trip_cause = rps_status_t.ok + if self.trip_cause == RPS_TRIP_CAUSE.AUTOMATIC or self.trip_cause == RPS_TRIP_CAUSE.TIMEOUT then + self.trip_cause = RPS_TRIP_CAUSE.OK self.tripped = false log.info("RPS: auto reset") @@ -693,7 +693,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, end -- send reactor protection system alarm - ---@param cause rps_status_t reactor protection system status + ---@param cause rps_trip_cause reactor protection system status function public.send_rps_alarm(cause) if self.linked then local rps_alarm = { diff --git a/scada-common/types.lua b/scada-common/types.lua index 88473e3..4d7f6f6 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -231,8 +231,8 @@ types.rtu_t = { ---| "dmg_crit" ---| "high_temp" ---| "no_coolant" ----| "full_waste" ----| "heated_coolant_backup" +---| "ex_waste" +---| "ex_heated_coolant" ---| "no_fuel" ---| "fault" ---| "timeout" @@ -241,21 +241,20 @@ types.rtu_t = { ---| "sys_fail" ---| "force_disabled" ----@alias rps_status_t rps_trip_cause -types.rps_status_t = { - ok = "ok", - dmg_crit = "dmg_crit", - high_temp = "high_temp", - no_coolant = "no_coolant", - ex_waste = "full_waste", - ex_hcoolant = "heated_coolant_backup", - no_fuel = "no_fuel", - fault = "fault", - timeout = "timeout", - manual = "manual", - automatic = "automatic", - sys_fail = "sys_fail", - force_disabled = "force_disabled" +types.RPS_TRIP_CAUSE = { + OK = "ok", + DMG_CRIT = "dmg_crit", + HIGH_TEMP = "high_temp", + NO_COOLANT = "no_coolant", + EX_WASTE = "ex_waste", + EX_HCOOLANT = "ex_heated_coolant", + NO_FUEL = "no_fuel", + FAULT = "fault", + TIMEOUT = "timeout", + MANUAL = "manual", + AUTOMATIC = "automatic", + SYS_FAIL = "sys_fail", + FORCE_DISABLED = "force_disabled" } ---@alias DUMPING_MODE diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 8322892..d4e1d68 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -108,8 +108,8 @@ function logic.update_annunciator(self) -- update other annunciator fields self.db.annunciator.ReactorSCRAM = plc_db.rps_tripped - self.db.annunciator.ManualReactorSCRAM = plc_db.rps_trip_cause == types.rps_status_t.manual - self.db.annunciator.AutoReactorSCRAM = plc_db.rps_trip_cause == types.rps_status_t.automatic + self.db.annunciator.ManualReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.MANUAL + self.db.annunciator.AutoReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.AUTOMATIC self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.no_cool) self.db.annunciator.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < -2.0 self.db.annunciator.CoolantLevelLow = plc_db.mek_status.ccool_fill < 0.4 @@ -636,9 +636,9 @@ function logic.update_status_text(self) cause = "core temperature high" elseif plc_db.rps_trip_cause == "no_coolant" then cause = "insufficient coolant" - elseif plc_db.rps_trip_cause == "full_waste" then + elseif plc_db.rps_trip_cause == "ex_waste" then cause = "excess waste" - elseif plc_db.rps_trip_cause == "heated_coolant_backup" then + elseif plc_db.rps_trip_cause == "ex_heated_coolant" then cause = "excess heated coolant" elseif plc_db.rps_trip_cause == "no_fuel" then cause = "insufficient fuel" From a07086907e2edecb59e542c54ead54d97beccbde Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 21 Feb 2023 11:30:49 -0500 Subject: [PATCH 541/587] #118 refactored DUMPING_MODE --- scada-common/types.lua | 2 +- supervisor/session/rtu/turbinev.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scada-common/types.lua b/scada-common/types.lua index 4d7f6f6..1437a56 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -257,7 +257,7 @@ types.RPS_TRIP_CAUSE = { FORCE_DISABLED = "force_disabled" } ----@alias DUMPING_MODE +---@alias dumping_mode ---| "IDLE" ---| "DUMPING" ---| "DUMPING_EXCESS" diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua index f0d8741..5e95ee4 100644 --- a/supervisor/session/rtu/turbinev.lua +++ b/supervisor/session/rtu/turbinev.lua @@ -92,7 +92,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue) flow_rate = 0, prod_rate = 0, steam_input_rate = 0, - dumping_mode = DUMPING_MODE.IDLE ---@type DUMPING_MODE + dumping_mode = DUMPING_MODE.IDLE ---@type dumping_mode }, tanks = { last_update = 0, @@ -123,7 +123,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue) end -- set the dumping mode - ---@param mode DUMPING_MODE + ---@param mode dumping_mode local function _set_dump_mode(mode) -- write holding register 1 self.session.send_request(TXN_TYPES.SET_DUMP, MODBUS_FCODE.WRITE_SINGLE_HOLD_REG, { 1, mode }) From 7247d8a828e4b78de01b9002c72f6f1281326966 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 21 Feb 2023 11:32:56 -0500 Subject: [PATCH 542/587] #118 refactored fluid --- scada-common/types.lua | 10 +++++----- supervisor/unitlogic.lua | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scada-common/types.lua b/scada-common/types.lua index 1437a56..c452154 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -208,11 +208,11 @@ types.ALARM_STATE = { ---| "mekanism:sodium" ---| "mekanism:superheated_sodium" -types.fluid = { - empty_gas = "mekanism:empty_gas", - water = "minecraft:water", - sodium = "mekanism:sodium", - superheated_sodium = "mekanism:superheated_sodium" +types.FLUID = { + EMPTY_GAS = "mekanism:empty_gas", + WATER = "minecraft:water", + SODIUM = "mekanism:sodium", + SUPERHEATED_SODIUM = "mekanism:superheated_sodium" } ---@alias rtu_t string diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index d4e1d68..4ee66b1 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -129,7 +129,7 @@ function logic.update_annunciator(self) such as when a burn rate consumes half the coolant in the tank, meaning that: 50% at some point will be in the boiler, and 50% in a tube, so that leaves 0% in the reactor ]]-- - local heating_rate_conv = util.trinary(plc_db.mek_status.ccool_type == types.fluid.sodium, 200000, 20000) + local heating_rate_conv = util.trinary(plc_db.mek_status.ccool_type == types.FLUID.SODIUM, 200000, 20000) local high_rate = (plc_db.mek_status.ccool_amnt / (plc_db.mek_status.burn_rate * heating_rate_conv)) < 4 self.db.annunciator.HighStartupRate = not plc_db.mek_status.status and high_rate From 424097973da270767b5e6a59825ee3b2294d1202 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 21 Feb 2023 12:27:16 -0500 Subject: [PATCH 543/587] #118 refactored RTU unit types --- reactor-plc/threads.lua | 15 ++++--- rtu/rtu.lua | 8 ++-- rtu/startup.lua | 67 ++++++++++++++++------------- rtu/threads.lua | 64 ++++++++++++++------------- scada-common/comms.lua | 63 --------------------------- scada-common/types.lua | 53 ++++++++++++++++++----- supervisor/session/coordinator.lua | 3 +- supervisor/session/rtu.lua | 6 +-- supervisor/session/rtu/boilerv.lua | 5 +-- supervisor/session/rtu/envd.lua | 5 +-- supervisor/session/rtu/imatrix.lua | 5 +-- supervisor/session/rtu/redstone.lua | 6 +-- supervisor/session/rtu/sna.lua | 5 +-- supervisor/session/rtu/sps.lua | 5 +-- supervisor/session/rtu/turbinev.lua | 5 +-- 15 files changed, 146 insertions(+), 169 deletions(-) diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 69d2ff1..4ea34f2 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -31,7 +31,8 @@ local MQ__COMM_CMD = { ---@param smem plc_shared_memory ---@param init function function threads.thread__main(smem, init) - local public = {} ---@class thread + ---@class parallel_thread + local public = {} -- execute thread function public.exec() @@ -277,7 +278,8 @@ end -- RPS operation thread ---@param smem plc_shared_memory function threads.thread__rps(smem) - local public = {} ---@class thread + ---@class parallel_thread + local public = {} -- execute thread function public.exec() @@ -416,7 +418,8 @@ end -- communications sender thread ---@param smem plc_shared_memory function threads.thread__comms_tx(smem) - local public = {} ---@class thread + ---@class parallel_thread + local public = {} -- execute thread function public.exec() @@ -490,7 +493,8 @@ end -- communications handler thread ---@param smem plc_shared_memory function threads.thread__comms_rx(smem) - local public = {} ---@class thread + ---@class parallel_thread + local public = {} -- execute thread function public.exec() @@ -564,7 +568,8 @@ end -- apply setpoints ---@param smem plc_shared_memory function threads.thread__setpoint_control(smem) - local public = {} ---@class thread + ---@class parallel_thread + local public = {} -- execute thread function public.exec() diff --git a/rtu/rtu.lua b/rtu/rtu.lua index feaaf38..3e5cf9a 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -1,6 +1,7 @@ local comms = require("scada-common.comms") local ppm = require("scada-common.ppm") local log = require("scada-common.log") +local types = require("scada-common.types") local util = require("scada-common.util") local modbus = require("rtu.modbus") @@ -11,7 +12,7 @@ local PROTOCOL = comms.PROTOCOL local DEVICE_TYPE = comms.DEVICE_TYPE local ESTABLISH_ACK = comms.ESTABLISH_ACK local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE -local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE +local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local print = util.print local println = util.println @@ -223,12 +224,11 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog local advertisement = {} for i = 1, #units do - local unit = units[i] --@type rtu_unit_registry_entry - local type = comms.rtu_t_to_unit_type(unit.type) + local unit = units[i] ---@type rtu_unit_registry_entry if type ~= nil then local advert = { - type, + unit.type, unit.index, unit.reactor } diff --git a/rtu/startup.lua b/rtu/startup.lua index 418921c..00fb4b8 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -27,7 +27,7 @@ local turbinev_rtu = require("rtu.dev.turbinev_rtu") local RTU_VERSION = "beta-v0.11.2" -local rtu_t = types.rtu_t +local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local print = util.print local println = util.println @@ -151,7 +151,7 @@ local function main() -- check for duplicate entries for i = 1, #units do 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_UNIT_TYPE.REDSTONE then -- duplicate entry local message = util.c("configure> skipping definition block #", entry_idx, " for reactor ", io_reactor, " with already defined redstone I/O") @@ -224,23 +224,28 @@ local function main() ---@class rtu_unit_registry_entry local unit = { - uid = 0, - name = "redstone_io", - type = rtu_t.redstone, - index = entry_idx, - reactor = io_reactor, - device = capabilities, -- use device field for redstone ports - is_multiblock = false, - formed = nil, ---@type boolean|nil - rtu = rs_rtu, ---@type rtu_device|rtu_rs_device + uid = 0, ---@type integer + name = "redstone_io", ---@type string + type = RTU_UNIT_TYPE.REDSTONE, ---@type RTU_UNIT_TYPE + index = entry_idx, ---@type integer + reactor = io_reactor, ---@type integer + device = capabilities, ---@type table use device field for redstone ports + is_multiblock = false, ---@type boolean + 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 + pkt_queue = nil, ---@type mqueue|nil + thread = nil ---@type parallel_thread|nil } table.insert(units, unit) - log.debug(util.c("init> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for reactor ", io_reactor)) + local for_message = "facility" + if io_reactor > 0 then + for_message = util.c("reactor ", io_reactor) + end + + log.debug(util.c("configure> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for ", for_message)) unit.uid = #units end @@ -274,7 +279,7 @@ local function main() local type = nil local rtu_iface = nil ---@type rtu_device - local rtu_type = "" + local rtu_type = nil ---@type RTU_UNIT_TYPE local is_multiblock = false local formed = nil ---@type boolean|nil @@ -291,7 +296,7 @@ local function main() if type == "boilerValve" then -- boiler multiblock - rtu_type = rtu_t.boiler_valve + rtu_type = RTU_UNIT_TYPE.BOILER_VALVE rtu_iface = boilerv_rtu.new(device) is_multiblock = true formed = device.isFormed() @@ -303,7 +308,7 @@ local function main() end elseif type == "turbineValve" then -- turbine multiblock - rtu_type = rtu_t.turbine_valve + rtu_type = RTU_UNIT_TYPE.TURBINE_VALVE rtu_iface = turbinev_rtu.new(device) is_multiblock = true formed = device.isFormed() @@ -315,7 +320,7 @@ local function main() end elseif type == "inductionPort" then -- induction matrix multiblock - rtu_type = rtu_t.induction_matrix + rtu_type = RTU_UNIT_TYPE.IMATRIX rtu_iface = imatrix_rtu.new(device) is_multiblock = true formed = device.isFormed() @@ -327,7 +332,7 @@ local function main() end elseif type == "spsPort" then -- SPS multiblock - rtu_type = rtu_t.sps + rtu_type = RTU_UNIT_TYPE.SPS rtu_iface = sps_rtu.new(device) is_multiblock = true formed = device.isFormed() @@ -339,15 +344,15 @@ local function main() end elseif type == "solarNeutronActivator" then -- SNA - rtu_type = rtu_t.sna + rtu_type = RTU_UNIT_TYPE.SNA rtu_iface = sna_rtu.new(device) elseif type == "environmentDetector" then -- advanced peripherals environment detector - rtu_type = rtu_t.env_detector + rtu_type = RTU_UNIT_TYPE.ENV_DETECTOR rtu_iface = envd_rtu.new(device) elseif type == ppm.VIRTUAL_DEVICE_TYPE then -- placeholder device - rtu_type = "virtual" + rtu_type = RTU_UNIT_TYPE.VIRTUAL rtu_iface = rtu.init_unit().interface() else local message = util.c("configure> device '", name, "' is not a known type (", type, ")") @@ -358,18 +363,18 @@ local function main() ---@class rtu_unit_registry_entry local rtu_unit = { - uid = 0, - name = name, - type = rtu_type, - index = index, - reactor = for_reactor, - device = device, - is_multiblock = is_multiblock, + uid = 0, ---@type integer + name = name, ---@type string + type = rtu_type, ---@type RTU_UNIT_TYPE + index = index, ---@type integer + reactor = for_reactor, ---@type integer + device = device, ---@type table + is_multiblock = is_multiblock, ---@type boolean formed = formed, ---@type boolean|nil 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 + thread = nil ---@type parallel_thread|nil } rtu_unit.thread = threads.thread__unit_comms(__shared_memory, rtu_unit) @@ -385,7 +390,7 @@ local function main() for_message = util.c("reactor ", for_reactor) end - log.debug(util.c("configure> initialized RTU unit #", #units, ": ", name, " (", rtu_type, ") [", index, "] for ", for_message)) + log.debug(util.c("configure> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ") [", index, "] for ", for_message)) rtu_unit.uid = #units end diff --git a/rtu/threads.lua b/rtu/threads.lua index 7af184e..152a4b2 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -15,7 +15,7 @@ local modbus = require("rtu.modbus") local threads = {} -local rtu_t = types.rtu_t +local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local print = util.print local println = util.println @@ -28,7 +28,8 @@ local COMMS_SLEEP = 100 -- (100ms, 2 ticks) -- main thread ---@param smem rtu_shared_memory function threads.thread__main(smem) - local public = {} ---@class thread + ---@class parallel_thread + local public = {} -- execute thread function public.exec() @@ -93,8 +94,9 @@ function threads.thread__main(smem) -- we are going to let the PPM prevent crashes -- return fault flags/codes to MODBUS queries local unit = units[i] - println_ts(util.c("lost the ", unit.type, " on interface ", unit.name)) - log.warning(util.c("lost the ", unit.type, " unit peripheral on interface ", unit.name)) + local type_name = types.rtu_type_to_string(unit.type) + println_ts(util.c("lost the ", type_name, " on interface ", unit.name)) + log.warning(util.c("lost the ", type_name, " unit peripheral on interface ", unit.name)) break end end @@ -129,51 +131,51 @@ function threads.thread__main(smem) -- found, re-link unit.device = device - if unit.type == "virtual" then + if unit.type == RTU_UNIT_TYPE.VIRTUAL then resend_advert = true if type == "boilerValve" then -- boiler multiblock - unit.type = rtu_t.boiler_valve + unit.type = RTU_UNIT_TYPE.BOILER_VALVE elseif type == "turbineValve" then -- turbine multiblock - unit.type = rtu_t.turbine_valve + unit.type = RTU_UNIT_TYPE.TURBINE_VALVE elseif type == "inductionPort" then -- induction matrix multiblock - unit.type = rtu_t.induction_matrix + unit.type = RTU_UNIT_TYPE.IMATRIX elseif type == "spsPort" then -- SPS multiblock - unit.type = rtu_t.sps + unit.type = RTU_UNIT_TYPE.SPS elseif type == "solarNeutronActivator" then -- SNA - unit.type = rtu_t.sna + unit.type = RTU_UNIT_TYPE.SNA elseif type == "environmentDetector" then -- advanced peripherals environment detector - unit.type = rtu_t.env_detector + unit.type = RTU_UNIT_TYPE.ENV_DETECTOR else resend_advert = false log.error(util.c("virtual device '", unit.name, "' cannot init to an unknown type (", type, ")")) end end - if unit.type == rtu_t.boiler_valve then + if unit.type == RTU_UNIT_TYPE.BOILER_VALVE then unit.rtu = boilerv_rtu.new(device) -- if not formed, indexing the multiblock functions would have resulted in a PPM fault unit.formed = util.trinary(device.__p_is_faulted(), false, nil) - elseif unit.type == rtu_t.turbine_valve then + elseif unit.type == RTU_UNIT_TYPE.TURBINE_VALVE then unit.rtu = turbinev_rtu.new(device) -- if not formed, indexing the multiblock functions would have resulted in a PPM fault unit.formed = util.trinary(device.__p_is_faulted(), false, nil) - elseif unit.type == rtu_t.induction_matrix then + elseif unit.type == RTU_UNIT_TYPE.IMATRIX then unit.rtu = imatrix_rtu.new(device) -- if not formed, indexing the multiblock functions would have resulted in a PPM fault unit.formed = util.trinary(device.__p_is_faulted(), false, nil) - elseif unit.type == rtu_t.sps then + elseif unit.type == RTU_UNIT_TYPE.SPS then unit.rtu = sps_rtu.new(device) -- if not formed, indexing the multiblock functions would have resulted in a PPM fault unit.formed = util.trinary(device.__p_is_faulted(), false, nil) - elseif unit.type == rtu_t.sna then + elseif unit.type == RTU_UNIT_TYPE.SNA then unit.rtu = sna_rtu.new(device) - elseif unit.type == rtu_t.env_detector then + elseif unit.type == RTU_UNIT_TYPE.ENV_DETECTOR then unit.rtu = envd_rtu.new(device) else log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true) @@ -185,8 +187,10 @@ function threads.thread__main(smem) unit.modbus_io = modbus.new(unit.rtu, true) - println_ts("reconnected the " .. unit.type .. " on interface " .. unit.name) - log.info("reconnected the " .. unit.type .. " on interface " .. unit.name) + local type_name = types.rtu_type_to_string(unit.type) + local message = util.c("reconnected the ", type_name, " on interface ", unit.name) + println_ts(message) + log.info(message) if resend_advert then rtu_comms.send_advertisement(units) @@ -231,7 +235,8 @@ end -- communications handler thread ---@param smem rtu_shared_memory function threads.thread__comms(smem) - local public = {} ---@class thread + ---@class parallel_thread + local public = {} -- execute thread function public.exec() @@ -304,11 +309,12 @@ end ---@param smem rtu_shared_memory ---@param unit rtu_unit_registry_entry function threads.thread__unit_comms(smem, unit) - local public = {} ---@class thread + ---@class parallel_thread + local public = {} -- execute thread function public.exec() - log.debug("rtu unit thread start -> " .. unit.type .. "(" .. unit.name .. ")") + log.debug(util.c("rtu unit thread start -> ", types.rtu_type_to_string(unit.type), "(", unit.name, ")")) -- load in from shared memory local rtu_state = smem.rtu_state @@ -319,8 +325,8 @@ function threads.thread__unit_comms(smem, unit) local last_f_check = 0 - local detail_name = util.c(unit.type, " (", unit.name, ") [", unit.index, "] for reactor ", unit.reactor) - local short_name = util.c(unit.type, " (", unit.name, ")") + local detail_name = util.c(types.rtu_type_to_string(unit.type), " (", unit.name, ") [", unit.index, "] for reactor ", unit.reactor) + local short_name = util.c(types.rtu_type_to_string(unit.type), " (", unit.name, ")") if packet_queue == nil then log.error("rtu unit thread created without a message queue, exiting...", true) @@ -368,25 +374,25 @@ function threads.thread__unit_comms(smem, unit) local type, device = ppm.mount(iface) if device ~= nil then - if type == "boilerValve" and unit.type == rtu_t.boiler_valve then + if type == "boilerValve" and unit.type == RTU_UNIT_TYPE.BOILER_VALVE then -- boiler multiblock unit.device = device unit.rtu = boilerv_rtu.new(device) unit.formed = device.isFormed() unit.modbus_io = modbus.new(unit.rtu, true) - elseif type == "turbineValve" and unit.type == rtu_t.turbine_valve then + elseif type == "turbineValve" and unit.type == RTU_UNIT_TYPE.TURBINE_VALVE then -- turbine multiblock unit.device = device unit.rtu = turbinev_rtu.new(device) unit.formed = device.isFormed() unit.modbus_io = modbus.new(unit.rtu, true) - elseif type == "inductionPort" and unit.type == rtu_t.induction_matrix then + elseif type == "inductionPort" and unit.type == RTU_UNIT_TYPE.IMATRIX then -- induction matrix multiblock unit.device = device unit.rtu = imatrix_rtu.new(device) unit.formed = device.isFormed() unit.modbus_io = modbus.new(unit.rtu, true) - elseif type == "spsPort" and unit.type == rtu_t.sps then + elseif type == "spsPort" and unit.type == RTU_UNIT_TYPE.SPS then -- SPS multiblock unit.device = device unit.rtu = sps_rtu.new(device) @@ -433,7 +439,7 @@ function threads.thread__unit_comms(smem, unit) end if not rtu_state.shutdown then - log.info(util.c("rtu unit thread ", unit.type, "(", unit.name, " restarting in 5 seconds...")) + log.info(util.c("rtu unit thread ", types.rtu_type_to_string(unit.type), "(", unit.name, " restarting in 5 seconds...")) util.psleep(5) end end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 0e0dd0e..1556203 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -3,13 +3,10 @@ -- local log = require("scada-common.log") -local types = require("scada-common.types") ---@class comms local comms = {} -local rtu_t = types.rtu_t - local insert = table.insert local max_distance = nil @@ -80,17 +77,6 @@ local DEVICE_TYPE = { CRDN = 3 -- coordinator device type for establish } ----@enum RTU_UNIT_TYPE -local RTU_UNIT_TYPE = { - REDSTONE = 0, -- redstone I/O - BOILER_VALVE = 1, -- boiler mekanism 10.1+ - TURBINE_VALVE = 2, -- turbine, mekanism 10.1+ - IMATRIX = 3, -- induction matrix - SPS = 4, -- SPS - SNA = 5, -- SNA - ENV_DETECTOR = 6 -- environment detector -} - ---@enum PLC_AUTO_ACK local PLC_AUTO_ACK = { FAIL = 0, -- failed to set burn rate/burn rate invalid @@ -129,7 +115,6 @@ comms.CAPI_TYPE = CAPI_TYPE comms.ESTABLISH_ACK = ESTABLISH_ACK comms.DEVICE_TYPE = DEVICE_TYPE -comms.RTU_UNIT_TYPE = RTU_UNIT_TYPE comms.PLC_AUTO_ACK = PLC_AUTO_ACK @@ -719,52 +704,4 @@ function comms.capi_packet() return public end --- convert rtu_t to RTU unit type ----@nodiscard ----@param type rtu_t ----@return RTU_UNIT_TYPE|nil -function comms.rtu_t_to_unit_type(type) - if type == rtu_t.redstone then - return RTU_UNIT_TYPE.REDSTONE - elseif type == rtu_t.boiler_valve then - return RTU_UNIT_TYPE.BOILER_VALVE - elseif type == rtu_t.turbine_valve then - return RTU_UNIT_TYPE.TURBINE_VALVE - elseif type == rtu_t.induction_matrix then - return RTU_UNIT_TYPE.IMATRIX - elseif type == rtu_t.sps then - return RTU_UNIT_TYPE.SPS - elseif type == rtu_t.sna then - return RTU_UNIT_TYPE.SNA - elseif type == rtu_t.env_detector then - return RTU_UNIT_TYPE.ENV_DETECTOR - end - - return nil -end - --- convert RTU unit type to rtu_t ----@nodiscard ----@param utype RTU_UNIT_TYPE ----@return rtu_t|nil -function comms.advert_type_to_rtu_t(utype) - if utype == RTU_UNIT_TYPE.REDSTONE then - return rtu_t.redstone - elseif utype == RTU_UNIT_TYPE.BOILER_VALVE then - return rtu_t.boiler_valve - elseif utype == RTU_UNIT_TYPE.TURBINE_VALVE then - return rtu_t.turbine_valve - elseif utype == RTU_UNIT_TYPE.IMATRIX then - return rtu_t.induction_matrix - elseif utype == RTU_UNIT_TYPE.SPS then - return rtu_t.sps - elseif utype == RTU_UNIT_TYPE.SNA then - return rtu_t.sna - elseif utype == RTU_UNIT_TYPE.ENV_DETECTOR then - return rtu_t.env_detector - end - - return nil -end - return comms diff --git a/scada-common/types.lua b/scada-common/types.lua index c452154..c9a4d91 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -70,6 +70,48 @@ function types.new_zero_coordinate() return { x = 0, y = 0, z = 0 } end -- ENUMERATION TYPES -- --#region +---@enum RTU_UNIT_TYPE +types.RTU_UNIT_TYPE = { + VIRTUAL = 0, -- virtual device + REDSTONE = 1, -- redstone I/O + BOILER_VALVE = 2, -- boiler mekanism 10.1+ + TURBINE_VALVE = 3, -- turbine, mekanism 10.1+ + IMATRIX = 4, -- induction matrix + SPS = 5, -- SPS + SNA = 6, -- SNA + ENV_DETECTOR = 7 -- environment detector +} + +types.RTU_UNIT_NAMES = { + "redstone", + "boiler_valve", + "turbine_valve", + "induction_matrix", + "sps", + "sna", + "environment_detector" +} + +-- safe conversion of RTU UNIT TYPE to string +---@nodiscard +---@param utype RTU_UNIT_TYPE +---@return string +function types.rtu_type_to_string(utype) + if utype == types.RTU_UNIT_TYPE.VIRTUAL then + return "virtual" + elseif utype == types.RTU_UNIT_TYPE.REDSTONE or + utype == types.RTU_UNIT_TYPE.BOILER_VALVE or + utype == types.RTU_UNIT_TYPE.TURBINE_VALVE or + utype == types.RTU_UNIT_TYPE.IMATRIX or + utype == types.RTU_UNIT_TYPE.SPS or + utype == types.RTU_UNIT_TYPE.SNA or + utype == types.RTU_UNIT_TYPE.ENV_DETECTOR then + return types.RTU_UNIT_NAMES[utype] + else + return "" + end +end + ---@enum TRI_FAIL types.TRI_FAIL = { OK = 0, @@ -215,17 +257,6 @@ types.FLUID = { SUPERHEATED_SODIUM = "mekanism:superheated_sodium" } ----@alias rtu_t string -types.rtu_t = { - redstone = "redstone", - boiler_valve = "boiler_valve", - turbine_valve = "turbine_valve", - induction_matrix = "induction_matrix", - sps = "sps", - sna = "sna", - env_detector = "environment_detector" -} - ---@alias rps_trip_cause ---| "ok" ---| "dmg_crit" diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 40231ad..3b3f231 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -1,6 +1,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") +local types = require("scada-common.types") local util = require("scada-common.util") local svqtypes = require("supervisor.session.svqtypes") @@ -12,7 +13,7 @@ local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE local SCADA_CRDN_TYPE = comms.SCADA_CRDN_TYPE local UNIT_COMMAND = comms.UNIT_COMMAND local FAC_COMMAND = comms.FAC_COMMAND -local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE +local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local SV_Q_CMDS = svqtypes.SV_Q_CMDS local SV_Q_DATA = svqtypes.SV_Q_DATA diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 44d8cec..a0fe916 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -1,7 +1,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") -local rsio = require("scada-common.rsio") +local types = require("scada-common.types") local util = require("scada-common.util") local svqtypes = require("supervisor.session.svqtypes") @@ -20,7 +20,7 @@ local rtu = {} local PROTOCOL = comms.PROTOCOL local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE -local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE +local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local print = util.print local println = util.println @@ -113,7 +113,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili end local type_string = util.strval(u_type) - if type(u_type) == "number" then type_string = util.strval(comms.advert_type_to_rtu_t(u_type)) end + if type(u_type) == "number" then type_string = types.rtu_type_to_string(u_type) end -- create unit by type diff --git a/supervisor/session/rtu/boilerv.lua b/supervisor/session/rtu/boilerv.lua index 65b00b6..58043dd 100644 --- a/supervisor/session/rtu/boilerv.lua +++ b/supervisor/session/rtu/boilerv.lua @@ -1,4 +1,3 @@ -local comms = require("scada-common.comms") local log = require("scada-common.log") local types = require("scada-common.types") local util = require("scada-common.util") @@ -7,7 +6,7 @@ local unit_session = require("supervisor.session.rtu.unit_session") local boilerv = {} -local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE +local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local MODBUS_FCODE = types.MODBUS_FCODE local TXN_TYPES = { @@ -39,7 +38,7 @@ local PERIODICS = { function boilerv.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPE.BOILER_VALVE then - log.error("attempt to instantiate boilerv RTU for type '" .. advert.type .. "'. this is a bug.") + log.error("attempt to instantiate boilerv RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.") return nil end diff --git a/supervisor/session/rtu/envd.lua b/supervisor/session/rtu/envd.lua index adee4aa..626aecb 100644 --- a/supervisor/session/rtu/envd.lua +++ b/supervisor/session/rtu/envd.lua @@ -1,4 +1,3 @@ -local comms = require("scada-common.comms") local log = require("scada-common.log") local types = require("scada-common.types") local util = require("scada-common.util") @@ -7,7 +6,7 @@ local unit_session = require("supervisor.session.rtu.unit_session") local envd = {} -local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE +local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local MODBUS_FCODE = types.MODBUS_FCODE local TXN_TYPES = { @@ -30,7 +29,7 @@ local PERIODICS = { function envd.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPE.ENV_DETECTOR then - log.error("attempt to instantiate envd RTU for type '" .. advert.type .. "'. this is a bug.") + log.error("attempt to instantiate envd RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.") return nil end diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua index 7d1ba18..fcb616d 100644 --- a/supervisor/session/rtu/imatrix.lua +++ b/supervisor/session/rtu/imatrix.lua @@ -1,4 +1,3 @@ -local comms = require("scada-common.comms") local log = require("scada-common.log") local types = require("scada-common.types") local util = require("scada-common.util") @@ -7,7 +6,7 @@ local unit_session = require("supervisor.session.rtu.unit_session") local imatrix = {} -local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE +local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local MODBUS_FCODE = types.MODBUS_FCODE local TXN_TYPES = { @@ -39,7 +38,7 @@ local PERIODICS = { function imatrix.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPE.IMATRIX then - log.error("attempt to instantiate imatrix RTU for type '" .. advert.type .. "'. this is a bug.") + log.error("attempt to instantiate imatrix RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.") return nil end diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index 48c8329..50764c4 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -1,6 +1,4 @@ -local comms = require("scada-common.comms") local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") local rsio = require("scada-common.rsio") local types = require("scada-common.types") local util = require("scada-common.util") @@ -9,7 +7,7 @@ local unit_session = require("supervisor.session.rtu.unit_session") local redstone = {} -local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE +local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local MODBUS_FCODE = types.MODBUS_FCODE local IO_PORT = rsio.IO @@ -54,7 +52,7 @@ local PERIODICS = { function redstone.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPE.REDSTONE then - log.error("attempt to instantiate redstone RTU for type '" .. advert.type .. "'. this is a bug.") + log.error("attempt to instantiate redstone RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.") return nil end diff --git a/supervisor/session/rtu/sna.lua b/supervisor/session/rtu/sna.lua index 454a587..085db14 100644 --- a/supervisor/session/rtu/sna.lua +++ b/supervisor/session/rtu/sna.lua @@ -1,4 +1,3 @@ -local comms = require("scada-common.comms") local log = require("scada-common.log") local types = require("scada-common.types") local util = require("scada-common.util") @@ -7,7 +6,7 @@ local unit_session = require("supervisor.session.rtu.unit_session") local sna = {} -local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE +local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local MODBUS_FCODE = types.MODBUS_FCODE local TXN_TYPES = { @@ -36,7 +35,7 @@ local PERIODICS = { function sna.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPE.SNA then - log.error("attempt to instantiate sna RTU for type '" .. advert.type .. "'. this is a bug.") + log.error("attempt to instantiate sna RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.") return nil end diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua index effd0d0..7d74acf 100644 --- a/supervisor/session/rtu/sps.lua +++ b/supervisor/session/rtu/sps.lua @@ -1,4 +1,3 @@ -local comms = require("scada-common.comms") local log = require("scada-common.log") local types = require("scada-common.types") local util = require("scada-common.util") @@ -7,7 +6,7 @@ local unit_session = require("supervisor.session.rtu.unit_session") local sps = {} -local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE +local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local MODBUS_FCODE = types.MODBUS_FCODE local TXN_TYPES = { @@ -39,7 +38,7 @@ local PERIODICS = { function sps.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPE.SPS then - log.error("attempt to instantiate sps RTU for type '" .. advert.type .. "'. this is a bug.") + log.error("attempt to instantiate sps RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.") return nil end diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua index 5e95ee4..b8e7228 100644 --- a/supervisor/session/rtu/turbinev.lua +++ b/supervisor/session/rtu/turbinev.lua @@ -1,4 +1,3 @@ -local comms = require("scada-common.comms") local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") local types = require("scada-common.types") @@ -9,7 +8,7 @@ local unit_session = require("supervisor.session.rtu.unit_session") local turbinev = {} -local RTU_UNIT_TYPE = comms.RTU_UNIT_TYPE +local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local DUMPING_MODE = types.DUMPING_MODE local MODBUS_FCODE = types.MODBUS_FCODE @@ -51,7 +50,7 @@ local PERIODICS = { function turbinev.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPE.TURBINE_VALVE then - log.error("attempt to instantiate turbinev RTU for type '" .. advert.type .. "'. this is a bug.") + log.error("attempt to instantiate turbinev RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.") return nil end From 82ea35168b7c2bbb884f9ae43fb5f1fd04a6140f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 21 Feb 2023 12:40:34 -0500 Subject: [PATCH 544/587] #118 type cleanup --- coordinator/ui/components/unit_detail.lua | 6 +-- scada-common/types.lua | 52 ++++++++++++++--------- supervisor/facility.lua | 2 +- supervisor/unit.lua | 14 +++--- supervisor/unitlogic.lua | 10 ++--- 5 files changed, 49 insertions(+), 35 deletions(-) diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index f5716b5..8fb7c38 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -287,7 +287,7 @@ local function init(parent, id) end local t1_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} - t_ps[1].subscribe("SteamDumpOpen", function (val) t1_sdo.update(val + 1) end) + t_ps[1].subscribe("SteamDumpOpen", t1_sdo.update) TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg} local t1_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} @@ -300,7 +300,7 @@ local function init(parent, id) if unit.num_turbines > 1 then TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg} local t2_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} - t_ps[2].subscribe("SteamDumpOpen", function (val) t2_sdo.update(val + 1) end) + t_ps[2].subscribe("SteamDumpOpen", t2_sdo.update) TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg} local t2_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} @@ -314,7 +314,7 @@ local function init(parent, id) if unit.num_turbines > 2 then TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg} local t3_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} - t_ps[3].subscribe("SteamDumpOpen", function (val) t3_sdo.update(val + 1) end) + t_ps[3].subscribe("SteamDumpOpen", t3_sdo.update) TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg} local t3_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} diff --git a/scada-common/types.lua b/scada-common/types.lua index c9a4d91..8cc31f8 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -114,21 +114,21 @@ end ---@enum TRI_FAIL types.TRI_FAIL = { - OK = 0, - PARTIAL = 1, - FULL = 2 + OK = 1, + PARTIAL = 2, + FULL = 3 } ---@enum PROCESS types.PROCESS = { - INACTIVE = 0, - MAX_BURN = 1, - BURN_RATE = 2, - CHARGE = 3, - GEN_RATE = 4, - MATRIX_FAULT_IDLE = 5, - SYSTEM_ALARM_IDLE = 6, - GEN_RATE_FAULT_IDLE = 7 + INACTIVE = 1, + MAX_BURN = 2, + BURN_RATE = 3, + CHARGE = 4, + GEN_RATE = 5, + MATRIX_FAULT_IDLE = 6, + SYSTEM_ALARM_IDLE = 7, + GEN_RATE_FAULT_IDLE = 8 } types.PROCESS_NAMES = { @@ -150,6 +150,13 @@ types.WASTE_MODE = { ANTI_MATTER = 4 } +types.WASTE_MODE_NAMES = { + "AUTO", + "PLUTONIUM", + "POLONIUM", + "ANTI_MATTER" +} + ---@enum ALARM types.ALARM = { ContainmentBreach = 1, @@ -183,10 +190,10 @@ types.ALARM_NAMES = { ---@enum ALARM_PRIORITY types.ALARM_PRIORITY = { - CRITICAL = 0, - EMERGENCY = 1, - URGENT = 2, - TIMELY = 3 + CRITICAL = 1, + EMERGENCY = 2, + URGENT = 3, + TIMELY = 4 } types.ALARM_PRIORITY_NAMES = { @@ -198,10 +205,17 @@ types.ALARM_PRIORITY_NAMES = { ---@enum ALARM_STATE types.ALARM_STATE = { - INACTIVE = 0, - TRIPPED = 1, - ACKED = 2, - RING_BACK = 3 + INACTIVE = 1, + TRIPPED = 2, + ACKED = 3, + RING_BACK = 4 +} + +types.ALARM_STATE_NAMES = { + "INACTIVE", + "TRIPPED", + "ACKED", + "RING_BACK" } --#endregion diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 1a86c40..24f03c2 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -293,7 +293,7 @@ function facility.new(num_reactors, cooling_conf) if state_changed then self.saturated = false - log.debug("FAC: state changed from " .. PROCESS_NAMES[self.last_mode + 1] .. " to " .. PROCESS_NAMES[self.mode + 1]) + log.debug("FAC: state changed from " .. PROCESS_NAMES[self.last_mode] .. " to " .. PROCESS_NAMES[self.mode]) if (self.last_mode == PROCESS.INACTIVE) or (self.last_mode == PROCESS.GEN_RATE_FAULT_IDLE) then self.start_fail = START_STATUS.OK diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 9b0849c..b809cbb 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -41,14 +41,14 @@ local DT_KEYS = { TurbinePower = "TPR" } ----@alias ALARM_INT_STATE integer +---@enum ALARM_INT_STATE local AISTATE = { - INACTIVE = 0, - TRIPPING = 1, - TRIPPED = 2, - ACKED = 3, - RING_BACK = 4, - RING_BACK_TRIPPING = 5 + INACTIVE = 1, + TRIPPING = 2, + TRIPPED = 3, + ACKED = 4, + RING_BACK = 5, + RING_BACK_TRIPPING = 6 } unit.FLOW_STABILITY_DELAY_MS = FLOW_STABILITY_DELAY_MS diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 4ee66b1..9249193 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -15,7 +15,7 @@ local IO = rsio.IO local PLC_S_CMDS = plc.PLC_S_CMDS -local aistate_string = { +local AISTATE_NAMES = { "INACTIVE", "TRIPPING", "TRIPPED", @@ -368,7 +368,7 @@ local function _update_alarm_state(self, tripped, alarm) alarm.state = AISTATE.TRIPPED self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): TRIPPED [PRIORITY ", - types.ALARM_PRIORITY_NAMES[alarm.tier + 1],"]")) + types.ALARM_PRIORITY_NAMES[alarm.tier],"]")) end else alarm.trip_time = util.time_ms() @@ -382,7 +382,7 @@ local function _update_alarm_state(self, tripped, alarm) alarm.state = AISTATE.TRIPPED self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): TRIPPED [PRIORITY ", - types.ALARM_PRIORITY_NAMES[alarm.tier + 1],"]")) + types.ALARM_PRIORITY_NAMES[alarm.tier],"]")) end elseif int_state == AISTATE.RING_BACK_TRIPPING then alarm.trip_time = 0 @@ -431,7 +431,7 @@ local function _update_alarm_state(self, tripped, alarm) -- check for state change if alarm.state ~= int_state then - local change_str = util.c(aistate_string[int_state + 1], " -> ", aistate_string[alarm.state + 1]) + local change_str = util.c(AISTATE_NAMES[int_state], " -> ", AISTATE_NAMES[alarm.state]) log.debug(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): ", change_str)) end end @@ -531,7 +531,7 @@ function logic.update_auto_safety(public, self) if alarm.tier <= PRIO.URGENT and (alarm.state == AISTATE.TRIPPED or alarm.state == AISTATE.ACKED) then if not self.auto_was_alarmed then log.info(util.c("UNIT ", self.r_id, " AUTO SCRAM due to ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], ") [PRIORITY ", - types.ALARM_PRIORITY_NAMES[alarm.tier + 1],"]")) + types.ALARM_PRIORITY_NAMES[alarm.tier],"]")) end alarmed = true From ce0198f3892b53b1b25c57e9ec414a2a9a1742aa Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 21 Feb 2023 16:57:33 -0500 Subject: [PATCH 545/587] #118 PLC code cleanup --- reactor-plc/plc.lua | 68 ++++++++++++++++++++--------------------- reactor-plc/startup.lua | 7 ++--- reactor-plc/threads.lua | 19 +++++++----- scada-common/comms.lua | 10 +++--- 4 files changed, 54 insertions(+), 50 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 3368047..af3383d 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -25,7 +25,7 @@ local println_ts = util.println_ts local PCALL_SCRAM_MSG = "pcall: Scram requires the reactor to be active." local PCALL_START_MSG = "pcall: Reactor is already active." --- RPS SAFETY CONSTANTS +--#region RPS SAFETY CONSTANTS local MAX_DAMAGE_PERCENT = 90 local MAX_DAMAGE_TEMPERATURE = 1200 @@ -33,13 +33,12 @@ local MIN_COOLANT_FILL = 0.10 local MAX_WASTE_FILL = 0.8 local MAX_HEATED_COLLANT_FILL = 0.95 --- END RPS SAFETY CONSTANTS +--#endregion END RPS SAFETY CONSTANTS ---- RPS: Reactor Protection System ---- ---- identifies dangerous states and SCRAMs reactor if warranted ---- ---- autonomous from main SCADA supervisor/coordinator control +-- RPS: Reactor Protection System
+-- identifies dangerous states and SCRAMs reactor if warranted
+-- autonomous from main SCADA supervisor/coordinator control +---@nodiscard ---@param reactor table ---@param is_formed boolean function plc.rps_init(reactor, is_formed) @@ -270,6 +269,7 @@ function plc.rps_init(reactor, is_formed) end -- check all safety conditions + ---@nodiscard ---@return boolean tripped, rps_trip_cause trip_status, boolean first_trip function public.check() local status = RPS_TRIP_CAUSE.OK @@ -359,16 +359,23 @@ function plc.rps_init(reactor, is_formed) return self.tripped, status, first_trip end + ---@nodiscard function public.status() return self.state end + ---@nodiscard function public.is_tripped() return self.tripped end + ---@nodiscard function public.get_trip_cause() return self.trip_cause end + ---@nodiscard function public.is_active() return self.reactor_enabled end + ---@nodiscard function public.is_formed() return self.formed end + ---@nodiscard function public.is_force_disabled() return self.force_disabled end -- get the runtime of the reactor if active, or the last runtime if disabled + ---@nodiscard ---@return integer runtime time since last enable function public.get_runtime() return util.trinary(self.reactor_enabled, util.time_ms() - self.enabled_at, self.last_runtime) end @@ -402,6 +409,7 @@ function plc.rps_init(reactor, is_formed) end -- Reactor PLC Communications +---@nodiscard ---@param id integer reactor ID ---@param version string PLC version ---@param modem table modem device @@ -416,8 +424,6 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, seq_num = 0, r_seq_num = nil, modem = modem, - s_port = server_port, - l_port = local_port, reactor = reactor, scrammed = false, linked = false, @@ -428,9 +434,6 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, max_burn_rate = nil } - ---@class plc_comms - local public = {} - comms.set_trusted_range(range) -- PRIVATE FUNCTIONS -- @@ -438,7 +441,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, -- configure modem channels local function _conf_channels() self.modem.closeAll() - self.modem.open(self.l_port) + self.modem.open(local_port) end _conf_channels() @@ -453,7 +456,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, r_pkt.make(id, msg_type, msg) s_pkt.make(self.seq_num, PROTOCOL.RPLC, r_pkt.raw_sendable()) - self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) + self.modem.transmit(server_port, local_port, s_pkt.raw_sendable()) self.seq_num = self.seq_num + 1 end @@ -467,7 +470,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, m_pkt.make(msg_type, msg) s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) - self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) + self.modem.transmit(server_port, local_port, s_pkt.raw_sendable()) self.seq_num = self.seq_num + 1 end @@ -614,6 +617,9 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, -- PUBLIC FUNCTIONS -- + ---@class plc_comms + local public = {} + -- reconnect a newly connected modem ---@param modem table ---@diagnostic disable-next-line: redefined-local @@ -679,9 +685,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, _send(RPLC_TYPE.STATUS, sys_status) - if self.resend_build then - _send_struct() - end + if self.resend_build then _send_struct() end end end @@ -696,12 +700,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, ---@param cause rps_trip_cause reactor protection system status function public.send_rps_alarm(cause) if self.linked then - local rps_alarm = { - cause, - table.unpack(rps.status()) - } - - _send(RPLC_TYPE.RPS_ALARM, rps_alarm) + _send(RPLC_TYPE.RPS_ALARM, { cause, table.unpack(rps.status()) }) end end @@ -745,7 +744,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, ---@param plc_state plc_state PLC state ---@param setpoints setpoints setpoint control table function public.handle_packet(packet, plc_state, setpoints) - if packet ~= nil and packet.scada_frame.local_port() == self.l_port then + if packet.scada_frame.local_port() == local_port then -- check sequence number if self.r_seq_num == nil then self.r_seq_num = packet.scada_frame.seq_num() @@ -848,9 +847,8 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, log.debug("AUTO: stopping the reactor to meet 0.0 burn rate") if rps.scram() then ack = AUTO_ACK.ZERO_DIS_OK - self.auto_last_disable = util.time_ms() else - log.debug("AUTO: automatic reactor stop failed") + log.warning("AUTO: automatic reactor stop failed") end else ack = AUTO_ACK.ZERO_DIS_OK @@ -862,10 +860,10 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, self.reactor.setBurnRate(0.01) if self.reactor.__p_is_faulted() then - log.debug("AUTO: failed to reset burn rate for auto activation") + log.warning("AUTO: failed to reset burn rate for auto activation") else if not rps.auto_activate() then - log.debug("AUTO: automatic reactor activation failed") + log.warning("AUTO: automatic reactor activation failed") end end end @@ -965,7 +963,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, if est_ack == ESTABLISH_ACK.ALLOW then println_ts("linked!") - log.debug("supervisor establish request approved") + log.info("supervisor establish request approved, PLC is linked") -- reset remote sequence number and cache self.r_seq_num = nil @@ -978,16 +976,16 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, elseif self.last_est_ack ~= est_ack then if est_ack == ESTABLISH_ACK.DENY then println_ts("link request denied, retrying...") - log.debug("establish request denied") + log.info("supervisor establish request denied, retrying") elseif est_ack == ESTABLISH_ACK.COLLISION then println_ts("reactor PLC ID collision (check config), retrying...") - log.warning("establish request collision") + log.warning("establish request collision, retrying") elseif est_ack == ESTABLISH_ACK.BAD_VERSION then println_ts("supervisor version mismatch (try updating), retrying...") - log.warning("establish request version mismatch") + log.warning("establish request version mismatch, retrying") else println_ts("invalid link response, bad channel? retrying...") - log.error("unknown establish request response") + log.error("unknown establish request response, retrying") end end @@ -1006,7 +1004,9 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, end end + ---@nodiscard function public.is_scrammed() return self.scrammed end + ---@nodiscard function public.is_linked() return self.linked end return public diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 90d9207..e55f0df 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -145,8 +145,7 @@ local function main() plc_state.no_modem = true end - -- PLC init - --- + -- PLC init
--- EVENT_CONSUMER: this function consumes events local function init() if plc_state.init_ok then @@ -170,13 +169,13 @@ local function main() log.debug("init> comms init") else println("boot> starting in offline mode") - log.debug("init> running without networking") + log.info("init> running without networking") end util.push_event("clock_start") println("boot> completed") - log.debug("init> boot completed") + log.info("init> boot completed") else println("boot> system in degraded state, awaiting devices...") log.warning("init> booted in a degraded state, awaiting peripheral connections...") diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 4ea34f2..d2708fd 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -28,6 +28,7 @@ local MQ__COMM_CMD = { } -- main thread +---@nodiscard ---@param smem plc_shared_memory ---@param init function function threads.thread__main(smem, init) @@ -45,9 +46,9 @@ function threads.thread__main(smem, init) local loop_clock = util.new_clock(MAIN_CLOCK) -- load in from shared memory - local networked = smem.networked - local plc_state = smem.plc_state - local plc_dev = smem.plc_dev + local networked = smem.networked + local plc_state = smem.plc_state + local plc_dev = smem.plc_dev -- event loop while true do @@ -276,6 +277,7 @@ function threads.thread__main(smem, init) end -- RPS operation thread +---@nodiscard ---@param smem plc_shared_memory function threads.thread__rps(smem) ---@class parallel_thread @@ -298,10 +300,10 @@ function threads.thread__rps(smem) -- thread loop while true do -- get plc_sys fields (may have been set late due to degraded boot) - local rps = smem.plc_sys.rps - local plc_comms = smem.plc_sys.plc_comms + local rps = smem.plc_sys.rps + local plc_comms = smem.plc_sys.plc_comms -- get reactor, may have changed do to disconnect/reconnect - local reactor = plc_dev.reactor + local reactor = plc_dev.reactor -- RPS checks if plc_state.init_ok then @@ -416,6 +418,7 @@ function threads.thread__rps(smem) end -- communications sender thread +---@nodiscard ---@param smem plc_shared_memory function threads.thread__comms_tx(smem) ---@class parallel_thread @@ -491,6 +494,7 @@ function threads.thread__comms_tx(smem) end -- communications handler thread +---@nodiscard ---@param smem plc_shared_memory function threads.thread__comms_rx(smem) ---@class parallel_thread @@ -565,7 +569,8 @@ function threads.thread__comms_rx(smem) return public end --- apply setpoints +-- ramp control outputs to desired setpoints +---@nodiscard ---@param smem plc_shared_memory function threads.thread__setpoint_control(smem) ---@class parallel_thread diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 1556203..0c2ccbe 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -334,7 +334,7 @@ function comms.rplc_packet() frame = nil, raw = {}, id = 0, - type = -1, + type = 0, ---@type RPLC_TYPE length = 0, data = {} } @@ -436,7 +436,7 @@ function comms.mgmt_packet() local self = { frame = nil, raw = {}, - type = -1, + type = 0, ---@type SCADA_MGMT_TYPE length = 0, data = {} } @@ -528,7 +528,7 @@ function comms.crdn_packet() local self = { frame = nil, raw = {}, - type = -1, + type = 0, ---@type SCADA_CRDN_TYPE length = 0, data = {} } @@ -617,13 +617,13 @@ function comms.crdn_packet() end -- coordinator API (CAPI) packet ----@todo implement for pocket access +---@todo implement for pocket access, set enum type for self.type ---@nodiscard function comms.capi_packet() local self = { frame = nil, raw = {}, - type = -1, + type = 0, length = 0, data = {} } From 79494f0587dae39fa5abc7c2b84e8ab6f4af78f3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 21 Feb 2023 23:50:43 -0500 Subject: [PATCH 546/587] #118 RTU/PLC code cleanup --- reactor-plc/plc.lua | 146 +++++++++++++++++++-------------------- reactor-plc/startup.lua | 24 +++---- rtu/dev/boilerv_rtu.lua | 1 + rtu/dev/envd_rtu.lua | 1 + rtu/dev/imatrix_rtu.lua | 1 + rtu/dev/redstone_rtu.lua | 7 +- rtu/dev/sna_rtu.lua | 3 +- rtu/dev/sps_rtu.lua | 3 +- rtu/dev/turbinev_rtu.lua | 1 + rtu/modbus.lua | 93 +++++++++++++------------ rtu/rtu.lua | 67 ++++++++---------- rtu/startup.lua | 40 +++++++---- rtu/threads.lua | 41 ++++++----- scada-common/comms.lua | 2 +- 14 files changed, 222 insertions(+), 208 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index af3383d..68750e0 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -58,24 +58,20 @@ function plc.rps_init(reactor, is_formed) } local self = { - reactor = reactor, state = { false, false, false, false, false, false, false, false, false, false, false, false }, reactor_enabled = false, enabled_at = 0, formed = is_formed, force_disabled = false, tripped = false, - trip_cause = "ok" ---@type rps_trip_cause + trip_cause = "ok" ---@type rps_trip_cause } - ---@class rps - local public = {} - -- PRIVATE FUNCTIONS -- -- set reactor access fault flag local function _set_fault() - if self.reactor.__p_last_fault() ~= "Terminated" then + if reactor.__p_last_fault() ~= "Terminated" then self.state[state_keys.fault] = true end end @@ -87,7 +83,7 @@ function plc.rps_init(reactor, is_formed) -- check if the reactor is formed local function _is_formed() - local formed = self.reactor.isFormed() + local formed = reactor.isFormed() if formed == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() @@ -102,7 +98,7 @@ function plc.rps_init(reactor, is_formed) -- check if the reactor is force disabled local function _is_force_disabled() - local disabled = self.reactor.isForceDisabled() + local disabled = reactor.isForceDisabled() if disabled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() @@ -117,7 +113,7 @@ function plc.rps_init(reactor, is_formed) -- check for critical damage local function _damage_critical() - local damage_percent = self.reactor.getDamagePercent() + local damage_percent = reactor.getDamagePercent() if damage_percent == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() @@ -129,7 +125,7 @@ function plc.rps_init(reactor, is_formed) -- check if the reactor is at a critically high temperature local function _high_temp() -- mekanism: MAX_DAMAGE_TEMPERATURE = 1_200 - local temp = self.reactor.getTemperature() + local temp = reactor.getTemperature() if temp == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() @@ -140,7 +136,7 @@ function plc.rps_init(reactor, is_formed) -- check if there is no coolant (<2% filled) local function _no_coolant() - local coolant_filled = self.reactor.getCoolantFilledPercentage() + local coolant_filled = reactor.getCoolantFilledPercentage() if coolant_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() @@ -151,7 +147,7 @@ function plc.rps_init(reactor, is_formed) -- check for excess waste (>80% filled) local function _excess_waste() - local w_filled = self.reactor.getWasteFilledPercentage() + local w_filled = reactor.getWasteFilledPercentage() if w_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() @@ -162,7 +158,7 @@ function plc.rps_init(reactor, is_formed) -- check for heated coolant backup (>95% filled) local function _excess_heated_coolant() - local hc_filled = self.reactor.getHeatedCoolantFilledPercentage() + local hc_filled = reactor.getHeatedCoolantFilledPercentage() if hc_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() @@ -173,7 +169,7 @@ function plc.rps_init(reactor, is_formed) -- check if there is no fuel local function _insufficient_fuel() - local fuel = self.reactor.getFuel() + local fuel = reactor.getFuel() if fuel == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() @@ -184,10 +180,13 @@ function plc.rps_init(reactor, is_formed) -- PUBLIC FUNCTIONS -- + ---@class rps + local public = {} + -- re-link a reactor after a peripheral re-connect ----@diagnostic disable-next-line: redefined-local - function public.reconnect_reactor(reactor) - self.reactor = reactor + ---@param new_reactor table reconnected reactor + function public.reconnect_reactor(new_reactor) + reactor = new_reactor end -- trip for lost peripheral @@ -221,8 +220,8 @@ function plc.rps_init(reactor, is_formed) function public.scram() log.info("RPS: reactor SCRAM") - self.reactor.scram() - if self.reactor.__p_is_faulted() and (self.reactor.__p_last_fault() ~= PCALL_SCRAM_MSG) then + reactor.scram() + if reactor.__p_is_faulted() and (reactor.__p_last_fault() ~= PCALL_SCRAM_MSG) then log.error("RPS: failed reactor SCRAM") return false else @@ -238,8 +237,8 @@ function plc.rps_init(reactor, is_formed) if not self.tripped then log.info("RPS: reactor start") - self.reactor.activate() - if self.reactor.__p_is_faulted() and (self.reactor.__p_last_fault() ~= PCALL_START_MSG) then + reactor.activate() + if reactor.__p_is_faulted() and (reactor.__p_last_fault() ~= PCALL_START_MSG) then log.error("RPS: failed reactor start") else self.reactor_enabled = true @@ -423,8 +422,6 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, local self = { seq_num = 0, r_seq_num = nil, - modem = modem, - reactor = reactor, scrammed = false, linked = false, last_est_ack = ESTABLISH_ACK.ALLOW, @@ -440,8 +437,8 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, -- configure modem channels local function _conf_channels() - self.modem.closeAll() - self.modem.open(local_port) + modem.closeAll() + modem.open(local_port) end _conf_channels() @@ -456,7 +453,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, r_pkt.make(id, msg_type, msg) s_pkt.make(self.seq_num, PROTOCOL.RPLC, r_pkt.raw_sendable()) - self.modem.transmit(server_port, local_port, s_pkt.raw_sendable()) + modem.transmit(server_port, local_port, s_pkt.raw_sendable()) self.seq_num = self.seq_num + 1 end @@ -470,7 +467,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, m_pkt.make(msg_type, msg) s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) - self.modem.transmit(server_port, local_port, s_pkt.raw_sendable()) + modem.transmit(server_port, local_port, s_pkt.raw_sendable()) self.seq_num = self.seq_num + 1 end @@ -503,21 +500,21 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, } local tasks = { - function () data_table[1] = self.reactor.getStatus() end, - function () data_table[2] = self.reactor.getBurnRate() end, - function () data_table[3] = self.reactor.getActualBurnRate() end, - function () data_table[4] = self.reactor.getTemperature() end, - function () data_table[5] = self.reactor.getDamagePercent() end, - function () data_table[6] = self.reactor.getBoilEfficiency() end, - function () data_table[7] = self.reactor.getEnvironmentalLoss() end, - function () fuel = self.reactor.getFuel() end, - function () data_table[9] = self.reactor.getFuelFilledPercentage() end, - function () waste = self.reactor.getWaste() end, - function () data_table[11] = self.reactor.getWasteFilledPercentage() end, - function () coolant = self.reactor.getCoolant() end, - function () data_table[14] = self.reactor.getCoolantFilledPercentage() end, - function () hcoolant = self.reactor.getHeatedCoolant() end, - function () data_table[17] = self.reactor.getHeatedCoolantFilledPercentage() end + function () data_table[1] = reactor.getStatus() end, + function () data_table[2] = reactor.getBurnRate() end, + function () data_table[3] = reactor.getActualBurnRate() end, + function () data_table[4] = reactor.getTemperature() end, + function () data_table[5] = reactor.getDamagePercent() end, + function () data_table[6] = reactor.getBoilEfficiency() end, + function () data_table[7] = reactor.getEnvironmentalLoss() end, + function () fuel = reactor.getFuel() end, + function () data_table[9] = reactor.getFuelFilledPercentage() end, + function () waste = reactor.getWaste() end, + function () data_table[11] = reactor.getWasteFilledPercentage() end, + function () coolant = reactor.getCoolant() end, + function () data_table[14] = reactor.getCoolantFilledPercentage() end, + function () hcoolant = reactor.getHeatedCoolant() end, + function () data_table[17] = reactor.getHeatedCoolantFilledPercentage() end } parallel.waitForAll(table.unpack(tasks)) @@ -540,7 +537,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, data_table[16] = hcoolant.amount end - return data_table, self.reactor.__p_is_faulted() + return data_table, reactor.__p_is_faulted() end -- update the status cache if changed @@ -590,24 +587,24 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, local mek_data = { false, 0, 0, 0, min_pos, max_pos, 0, 0, 0, 0, 0, 0, 0, 0 } local tasks = { - function () mek_data[1] = self.reactor.getLength() end, - function () mek_data[2] = self.reactor.getWidth() end, - function () mek_data[3] = self.reactor.getHeight() end, - function () mek_data[4] = self.reactor.getMinPos() end, - function () mek_data[5] = self.reactor.getMaxPos() end, - function () mek_data[6] = self.reactor.getHeatCapacity() end, - function () mek_data[7] = self.reactor.getFuelAssemblies() end, - function () mek_data[8] = self.reactor.getFuelSurfaceArea() end, - function () mek_data[9] = self.reactor.getFuelCapacity() end, - function () mek_data[10] = self.reactor.getWasteCapacity() end, - function () mek_data[11] = self.reactor.getCoolantCapacity() end, - function () mek_data[12] = self.reactor.getHeatedCoolantCapacity() end, - function () mek_data[13] = self.reactor.getMaxBurnRate() end + function () mek_data[1] = reactor.getLength() end, + function () mek_data[2] = reactor.getWidth() end, + function () mek_data[3] = reactor.getHeight() end, + function () mek_data[4] = reactor.getMinPos() end, + function () mek_data[5] = reactor.getMaxPos() end, + function () mek_data[6] = reactor.getHeatCapacity() end, + function () mek_data[7] = reactor.getFuelAssemblies() end, + function () mek_data[8] = reactor.getFuelSurfaceArea() end, + function () mek_data[9] = reactor.getFuelCapacity() end, + function () mek_data[10] = reactor.getWasteCapacity() end, + function () mek_data[11] = reactor.getCoolantCapacity() end, + function () mek_data[12] = reactor.getHeatedCoolantCapacity() end, + function () mek_data[13] = reactor.getMaxBurnRate() end } parallel.waitForAll(table.unpack(tasks)) - if not self.reactor.__p_is_faulted() then + if not reactor.__p_is_faulted() then _send(RPLC_TYPE.MEK_STRUCT, mek_data) self.resend_build = false else @@ -621,18 +618,16 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, local public = {} -- reconnect a newly connected modem - ---@param modem table ----@diagnostic disable-next-line: redefined-local - function public.reconnect_modem(modem) - self.modem = modem + ---@param new_modem table + function public.reconnect_modem(new_modem) + modem = new_modem _conf_channels() end -- reconnect a newly connected reactor - ---@param reactor table ----@diagnostic disable-next-line: redefined-local - function public.reconnect_reactor(reactor) - self.reactor = reactor + ---@param new_reactor table + function public.reconnect_reactor(new_reactor) + reactor = new_reactor self.status_cache = nil self.resend_build = true self.max_burn_rate = nil @@ -670,7 +665,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, mek_data = self.status_cache end - heating_rate = self.reactor.getHeatingRate() + heating_rate = reactor.getHeatingRate() end local sys_status = { @@ -705,6 +700,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, end -- parse an RPLC packet + ---@nodiscard ---@param side string ---@param sender integer ---@param reply_to integer @@ -762,6 +758,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, -- handle packet if protocol == PROTOCOL.RPLC then + ---@cast packet rplc_frame if self.linked then if packet.type == RPLC_TYPE.STATUS then -- request of full status, clear cache first @@ -781,7 +778,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, -- if no known max burn rate, check again if self.max_burn_rate == nil then - self.max_burn_rate = self.reactor.getMaxBurnRate() + self.max_burn_rate = reactor.getMaxBurnRate() end -- if we know our max burn rate, update current burn rate setpoint if in range @@ -792,8 +789,8 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, setpoints.burn_rate = burn_rate success = true else - self.reactor.setBurnRate(burn_rate) - success = not self.reactor.__p_is_faulted() + reactor.setBurnRate(burn_rate) + success = not reactor.__p_is_faulted() end else log.debug(burn_rate .. " rate outside of 0 < x <= " .. self.max_burn_rate) @@ -836,7 +833,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, -- if no known max burn rate, check again if self.max_burn_rate == nil then - self.max_burn_rate = self.reactor.getMaxBurnRate() + self.max_burn_rate = reactor.getMaxBurnRate() end -- if we know our max burn rate, update current burn rate setpoint if in range @@ -858,8 +855,8 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, -- activate the reactor log.debug("AUTO: activating the reactor") - self.reactor.setBurnRate(0.01) - if self.reactor.__p_is_faulted() then + reactor.setBurnRate(0.01) + if reactor.__p_is_faulted() then log.warning("AUTO: failed to reset burn rate for auto activation") else if not rps.auto_activate() then @@ -877,8 +874,8 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, ack = AUTO_ACK.RAMP_SET_OK else log.debug(util.c("AUTO: setting burn rate directly to ", burn_rate)) - self.reactor.setBurnRate(burn_rate) - ack = util.trinary(self.reactor.__p_is_faulted(), AUTO_ACK.FAIL, AUTO_ACK.DIRECT_SET_OK) + reactor.setBurnRate(burn_rate) + ack = util.trinary(reactor.__p_is_faulted(), AUTO_ACK.FAIL, AUTO_ACK.DIRECT_SET_OK) end end else @@ -897,6 +894,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, log.debug("discarding RPLC packet before linked") end elseif protocol == PROTOCOL.SCADA_MGMT then + ---@cast packet mgmt_frame if self.linked then if packet.type == SCADA_MGMT_TYPE.ESTABLISH then -- link request confirmation diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index e55f0df..8b66b1a 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,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.11.1" +local R_PLC_VERSION = "v0.12.0" local print = util.print local println = util.println @@ -116,15 +116,15 @@ local function main() -- we need a reactor, can at least do some things even if it isn't formed though if smem_dev.reactor == nil then - println("boot> fission reactor not found"); - log.warning("no reactor on startup") + println("init> fission reactor not found"); + log.warning("init> no reactor on startup") plc_state.init_ok = false plc_state.degraded = true plc_state.no_reactor = true elseif not smem_dev.reactor.isFormed() then - println("boot> fission reactor not formed"); - log.warning("reactor logic adapter present, but reactor is not formed") + println("init> fission reactor not formed"); + log.warning("init> reactor logic adapter present, but reactor is not formed") plc_state.degraded = true plc_state.reactor_formed = false @@ -132,8 +132,8 @@ local function main() -- 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") + println("init> wireless modem not found") + log.warning("init> 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 @@ -168,17 +168,17 @@ local function main() config.TRUSTED_RANGE, smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog) log.debug("init> comms init") else - println("boot> starting in offline mode") + println("init> starting in offline mode") log.info("init> running without networking") end util.push_event("clock_start") - println("boot> completed") - log.info("init> boot completed") + println("init> completed") + log.info("init> startup completed") else - println("boot> system in degraded state, awaiting devices...") - log.warning("init> booted in a degraded state, awaiting peripheral connections...") + println("init> system in degraded state, awaiting devices...") + log.warning("init> started in a degraded state, awaiting peripheral connections...") end end diff --git a/rtu/dev/boilerv_rtu.lua b/rtu/dev/boilerv_rtu.lua index ed7cdb5..b93d412 100644 --- a/rtu/dev/boilerv_rtu.lua +++ b/rtu/dev/boilerv_rtu.lua @@ -3,6 +3,7 @@ local rtu = require("rtu.rtu") local boilerv_rtu = {} -- create new boiler (mek 10.1+) device +---@nodiscard ---@param boiler table function boilerv_rtu.new(boiler) local unit = rtu.init_unit() diff --git a/rtu/dev/envd_rtu.lua b/rtu/dev/envd_rtu.lua index c09ee0c..ba4758a 100644 --- a/rtu/dev/envd_rtu.lua +++ b/rtu/dev/envd_rtu.lua @@ -3,6 +3,7 @@ local rtu = require("rtu.rtu") local envd_rtu = {} -- create new environment detector device +---@nodiscard ---@param envd table function envd_rtu.new(envd) local unit = rtu.init_unit() diff --git a/rtu/dev/imatrix_rtu.lua b/rtu/dev/imatrix_rtu.lua index 6e99453..29405b8 100644 --- a/rtu/dev/imatrix_rtu.lua +++ b/rtu/dev/imatrix_rtu.lua @@ -3,6 +3,7 @@ local rtu = require("rtu.rtu") local imatrix_rtu = {} -- create new induction matrix (mek 10.1+) device +---@nodiscard ---@param imatrix table function imatrix_rtu.new(imatrix) local unit = rtu.init_unit() diff --git a/rtu/dev/redstone_rtu.lua b/rtu/dev/redstone_rtu.lua index 13ca83b..da7db6b 100644 --- a/rtu/dev/redstone_rtu.lua +++ b/rtu/dev/redstone_rtu.lua @@ -1,7 +1,7 @@ -local rtu = require("rtu.rtu") - local rsio = require("scada-common.rsio") +local rtu = require("rtu.rtu") + local redstone_rtu = {} local IO_LVL = rsio.IO_LVL @@ -10,14 +10,15 @@ local digital_read = rsio.digital_read local digital_write = rsio.digital_write -- create new redstone device +---@nodiscard function redstone_rtu.new() local unit = rtu.init_unit() -- get RTU interface local interface = unit.interface() + -- extends rtu_device; fields added manually to please Lua diagnostics ---@class rtu_rs_device - --- extends rtu_device; fields added manually to please Lua diagnostics local public = { io_count = interface.io_count, read_coil = interface.read_coil, diff --git a/rtu/dev/sna_rtu.lua b/rtu/dev/sna_rtu.lua index a4c250f..0339794 100644 --- a/rtu/dev/sna_rtu.lua +++ b/rtu/dev/sna_rtu.lua @@ -2,7 +2,8 @@ local rtu = require("rtu.rtu") local sna_rtu = {} --- create new solar neutron activator (sna) device +-- create new solar neutron activator (SNA) device +---@nodiscard ---@param sna table function sna_rtu.new(sna) local unit = rtu.init_unit() diff --git a/rtu/dev/sps_rtu.lua b/rtu/dev/sps_rtu.lua index 3b7fdf1..ba0a18c 100644 --- a/rtu/dev/sps_rtu.lua +++ b/rtu/dev/sps_rtu.lua @@ -2,7 +2,8 @@ local rtu = require("rtu.rtu") local sps_rtu = {} --- create new super-critical phase shifter (sps) device +-- create new super-critical phase shifter (SPS) device +---@nodiscard ---@param sps table function sps_rtu.new(sps) local unit = rtu.init_unit() diff --git a/rtu/dev/turbinev_rtu.lua b/rtu/dev/turbinev_rtu.lua index 191427d..eba310c 100644 --- a/rtu/dev/turbinev_rtu.lua +++ b/rtu/dev/turbinev_rtu.lua @@ -3,6 +3,7 @@ local rtu = require("rtu.rtu") local turbinev_rtu = {} -- create new turbine (mek 10.1+) device +---@nodiscard ---@param turbine table function turbinev_rtu.new(turbine) local unit = rtu.init_unit() diff --git a/rtu/modbus.lua b/rtu/modbus.lua index 5411f37..20c5939 100644 --- a/rtu/modbus.lua +++ b/rtu/modbus.lua @@ -7,22 +7,15 @@ local MODBUS_FCODE = types.MODBUS_FCODE local MODBUS_EXCODE = types.MODBUS_EXCODE -- new modbus comms handler object +---@nodiscard ---@param rtu_dev rtu_device|rtu_rs_device RTU device ---@param use_parallel_read boolean whether or not to use parallel calls when reading function modbus.new(rtu_dev, use_parallel_read) - local self = { - rtu = rtu_dev, - use_parallel = use_parallel_read - } - - ---@class modbus - local public = {} - local insert = table.insert - -- read a span of coils (digital outputs) - -- + -- read a span of coils (digital outputs)
-- returns a table of readings or a MODBUS_EXCODE error code + ---@nodiscard ---@param c_addr_start integer ---@param count integer ---@return boolean ok, table|MODBUS_EXCODE readings @@ -30,20 +23,20 @@ function modbus.new(rtu_dev, use_parallel_read) local tasks = {} local readings = {} ---@type table|MODBUS_EXCODE local access_fault = false - local _, coils, _, _ = self.rtu.io_count() + local _, coils, _, _ = rtu_dev.io_count() local return_ok = ((c_addr_start + count) <= (coils + 1)) and (count > 0) if return_ok then for i = 1, count do local addr = c_addr_start + i - 1 - if self.use_parallel then + if use_parallel_read then insert(tasks, function () - local reading, fault = self.rtu.read_coil(addr) + local reading, fault = rtu_dev.read_coil(addr) if fault then access_fault = true else readings[i] = reading end end) else - readings[i], access_fault = self.rtu.read_coil(addr) + readings[i], access_fault = rtu_dev.read_coil(addr) if access_fault then return_ok = false @@ -54,7 +47,7 @@ function modbus.new(rtu_dev, use_parallel_read) end -- run parallel tasks if configured - if self.use_parallel then + if use_parallel_read then parallel.waitForAll(table.unpack(tasks)) end @@ -69,9 +62,9 @@ function modbus.new(rtu_dev, use_parallel_read) return return_ok, readings end - -- read a span of discrete inputs (digital inputs) - -- + -- read a span of discrete inputs (digital inputs)
-- returns a table of readings or a MODBUS_EXCODE error code + ---@nodiscard ---@param di_addr_start integer ---@param count integer ---@return boolean ok, table|MODBUS_EXCODE readings @@ -79,20 +72,20 @@ function modbus.new(rtu_dev, use_parallel_read) local tasks = {} local readings = {} ---@type table|MODBUS_EXCODE local access_fault = false - local discrete_inputs, _, _, _ = self.rtu.io_count() + local discrete_inputs, _, _, _ = rtu_dev.io_count() local return_ok = ((di_addr_start + count) <= (discrete_inputs + 1)) and (count > 0) if return_ok then for i = 1, count do local addr = di_addr_start + i - 1 - if self.use_parallel then + if use_parallel_read then insert(tasks, function () - local reading, fault = self.rtu.read_di(addr) + local reading, fault = rtu_dev.read_di(addr) if fault then access_fault = true else readings[i] = reading end end) else - readings[i], access_fault = self.rtu.read_di(addr) + readings[i], access_fault = rtu_dev.read_di(addr) if access_fault then return_ok = false @@ -103,7 +96,7 @@ function modbus.new(rtu_dev, use_parallel_read) end -- run parallel tasks if configured - if self.use_parallel then + if use_parallel_read then parallel.waitForAll(table.unpack(tasks)) end @@ -118,9 +111,9 @@ function modbus.new(rtu_dev, use_parallel_read) return return_ok, readings end - -- read a span of holding registers (analog outputs) - -- + -- read a span of holding registers (analog outputs)
-- returns a table of readings or a MODBUS_EXCODE error code + ---@nodiscard ---@param hr_addr_start integer ---@param count integer ---@return boolean ok, table|MODBUS_EXCODE readings @@ -128,20 +121,20 @@ function modbus.new(rtu_dev, use_parallel_read) local tasks = {} local readings = {} ---@type table|MODBUS_EXCODE local access_fault = false - local _, _, _, hold_regs = self.rtu.io_count() + local _, _, _, hold_regs = rtu_dev.io_count() local return_ok = ((hr_addr_start + count) <= (hold_regs + 1)) and (count > 0) if return_ok then for i = 1, count do local addr = hr_addr_start + i - 1 - if self.use_parallel then + if use_parallel_read then insert(tasks, function () - local reading, fault = self.rtu.read_holding_reg(addr) + local reading, fault = rtu_dev.read_holding_reg(addr) if fault then access_fault = true else readings[i] = reading end end) else - readings[i], access_fault = self.rtu.read_holding_reg(addr) + readings[i], access_fault = rtu_dev.read_holding_reg(addr) if access_fault then return_ok = false @@ -152,7 +145,7 @@ function modbus.new(rtu_dev, use_parallel_read) end -- run parallel tasks if configured - if self.use_parallel then + if use_parallel_read then parallel.waitForAll(table.unpack(tasks)) end @@ -167,9 +160,9 @@ function modbus.new(rtu_dev, use_parallel_read) return return_ok, readings end - -- read a span of input registers (analog inputs) - -- + -- read a span of input registers (analog inputs)
-- returns a table of readings or a MODBUS_EXCODE error code + ---@nodiscard ---@param ir_addr_start integer ---@param count integer ---@return boolean ok, table|MODBUS_EXCODE readings @@ -177,20 +170,20 @@ function modbus.new(rtu_dev, use_parallel_read) local tasks = {} local readings = {} ---@type table|MODBUS_EXCODE local access_fault = false - local _, _, input_regs, _ = self.rtu.io_count() + local _, _, input_regs, _ = rtu_dev.io_count() local return_ok = ((ir_addr_start + count) <= (input_regs + 1)) and (count > 0) if return_ok then for i = 1, count do local addr = ir_addr_start + i - 1 - if self.use_parallel then + if use_parallel_read then insert(tasks, function () - local reading, fault = self.rtu.read_input_reg(addr) + local reading, fault = rtu_dev.read_input_reg(addr) if fault then access_fault = true else readings[i] = reading end end) else - readings[i], access_fault = self.rtu.read_input_reg(addr) + readings[i], access_fault = rtu_dev.read_input_reg(addr) if access_fault then return_ok = false @@ -201,7 +194,7 @@ function modbus.new(rtu_dev, use_parallel_read) end -- run parallel tasks if configured - if self.use_parallel then + if use_parallel_read then parallel.waitForAll(table.unpack(tasks)) end @@ -217,16 +210,17 @@ function modbus.new(rtu_dev, use_parallel_read) end -- write a single coil (digital output) + ---@nodiscard ---@param c_addr integer ---@param value any ---@return boolean ok, MODBUS_EXCODE local function _5_write_single_coil(c_addr, value) local response = nil - local _, coils, _, _ = self.rtu.io_count() + local _, coils, _, _ = rtu_dev.io_count() local return_ok = c_addr <= coils if return_ok then - local access_fault = self.rtu.write_coil(c_addr, value) + local access_fault = rtu_dev.write_coil(c_addr, value) if access_fault then return_ok = false @@ -240,16 +234,17 @@ function modbus.new(rtu_dev, use_parallel_read) end -- write a single holding register (analog output) + ---@nodiscard ---@param hr_addr integer ---@param value any ---@return boolean ok, MODBUS_EXCODE local function _6_write_single_holding_register(hr_addr, value) local response = nil - local _, _, _, hold_regs = self.rtu.io_count() + local _, _, _, hold_regs = rtu_dev.io_count() local return_ok = hr_addr <= hold_regs if return_ok then - local access_fault = self.rtu.write_holding_reg(hr_addr, value) + local access_fault = rtu_dev.write_holding_reg(hr_addr, value) if access_fault then return_ok = false @@ -263,19 +258,20 @@ function modbus.new(rtu_dev, use_parallel_read) end -- write multiple coils (digital outputs) + ---@nodiscard ---@param c_addr_start integer ---@param values any ---@return boolean ok, MODBUS_EXCODE local function _15_write_multiple_coils(c_addr_start, values) local response = nil - local _, coils, _, _ = self.rtu.io_count() + local _, coils, _, _ = rtu_dev.io_count() local count = #values local return_ok = ((c_addr_start + count) <= (coils + 1)) and (count > 0) if return_ok then for i = 1, count do local addr = c_addr_start + i - 1 - local access_fault = self.rtu.write_coil(addr, values[i]) + local access_fault = rtu_dev.write_coil(addr, values[i]) if access_fault then return_ok = false @@ -291,19 +287,20 @@ function modbus.new(rtu_dev, use_parallel_read) end -- write multiple holding registers (analog outputs) + ---@nodiscard ---@param hr_addr_start integer ---@param values any ---@return boolean ok, MODBUS_EXCODE local function _16_write_multiple_holding_registers(hr_addr_start, values) local response = nil - local _, _, _, hold_regs = self.rtu.io_count() + local _, _, _, hold_regs = rtu_dev.io_count() local count = #values local return_ok = ((hr_addr_start + count) <= (hold_regs + 1)) and (count > 0) if return_ok then for i = 1, count do local addr = hr_addr_start + i - 1 - local access_fault = self.rtu.write_holding_reg(addr, values[i]) + local access_fault = rtu_dev.write_holding_reg(addr, values[i]) if access_fault then return_ok = false @@ -318,7 +315,11 @@ function modbus.new(rtu_dev, use_parallel_read) return return_ok, response end + ---@class modbus + local public = {} + -- validate a request without actually executing it + ---@nodiscard ---@param packet modbus_frame ---@return boolean return_code, modbus_packet reply function public.check_request(packet) @@ -360,6 +361,7 @@ function modbus.new(rtu_dev, use_parallel_read) end -- handle a MODBUS TCP packet and generate a reply + ---@nodiscard ---@param packet modbus_frame ---@return boolean return_code, modbus_packet reply function public.handle_packet(packet) @@ -420,6 +422,7 @@ function modbus.new(rtu_dev, use_parallel_read) end -- return a SERVER_DEVICE_BUSY error reply +---@nodiscard ---@param packet modbus_frame MODBUS packet frame ---@return modbus_packet reply function modbus.reply__srv_device_busy(packet) @@ -432,6 +435,7 @@ function modbus.reply__srv_device_busy(packet) end -- return a NEG_ACKNOWLEDGE error reply +---@nodiscard ---@param packet modbus_frame MODBUS packet frame ---@return modbus_packet reply function modbus.reply__neg_ack(packet) @@ -444,6 +448,7 @@ function modbus.reply__neg_ack(packet) end -- return a GATEWAY_PATH_UNAVAILABLE error reply +---@nodiscard ---@param packet modbus_frame MODBUS packet frame ---@return modbus_packet reply function modbus.reply__gw_unavailable(packet) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 3e5cf9a..e2c9b44 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -19,7 +19,8 @@ local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts --- create a new RTU +-- create a new RTU unit +---@nodiscard function rtu.init_unit() local self = { discrete_inputs = {}, @@ -153,14 +154,13 @@ function rtu.init_unit() -- public RTU device access -- get the public interface to this RTU - function protected.interface() - return public - end + function protected.interface() return public end return protected end -- RTU Communications +---@nodiscard ---@param version string RTU version ---@param modem table modem device ---@param local_port integer local listening port @@ -169,20 +169,12 @@ end ---@param conn_watchdog watchdog watchdog reference function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog) local self = { - version = version, seq_num = 0, r_seq_num = nil, txn_id = 0, - modem = modem, - s_port = server_port, - l_port = local_port, - conn_watchdog = conn_watchdog, last_est_ack = ESTABLISH_ACK.ALLOW } - ---@class rtu_comms - local public = {} - local insert = table.insert comms.set_trusted_range(range) @@ -191,8 +183,8 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog -- configure modem channels local function _conf_channels() - self.modem.closeAll() - self.modem.open(self.l_port) + modem.closeAll() + modem.open(local_port) end _conf_channels() @@ -207,7 +199,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog m_pkt.make(msg_type, msg) s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) - self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) + modem.transmit(server_port, local_port, s_pkt.raw_sendable()) self.seq_num = self.seq_num + 1 end @@ -218,6 +210,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog end -- generate device advertisement table + ---@nodiscard ---@param units table ---@return table advertisement local function _generate_advertisement(units) @@ -227,11 +220,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog local unit = units[i] ---@type rtu_unit_registry_entry if type ~= nil then - local advert = { - unit.type, - unit.index, - unit.reactor - } + local advert = { unit.type, unit.index, unit.reactor } if type == RTU_UNIT_TYPE.REDSTONE then insert(advert, unit.device) @@ -246,20 +235,22 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog -- PUBLIC FUNCTIONS -- + ---@class rtu_comms + local public = {} + -- send a MODBUS TCP packet ---@param m_pkt modbus_packet function public.send_modbus(m_pkt) local s_pkt = comms.scada_packet() s_pkt.make(self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable()) - self.modem.transmit(self.s_port, self.l_port, s_pkt.raw_sendable()) + modem.transmit(server_port, local_port, s_pkt.raw_sendable()) self.seq_num = self.seq_num + 1 end -- reconnect a newly connected modem - ---@param modem table ----@diagnostic disable-next-line: redefined-local - function public.reconnect_modem(modem) - self.modem = modem + ---@param new_modem table + function public.reconnect_modem(new_modem) + modem = new_modem _conf_channels() end @@ -273,7 +264,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog -- close the connection to the server ---@param rtu_state rtu_state function public.close(rtu_state) - self.conn_watchdog.cancel() + conn_watchdog.cancel() public.unlink(rtu_state) _send(SCADA_MGMT_TYPE.CLOSE, {}) end @@ -281,7 +272,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog -- send establish request (includes advertisement) ---@param units table function public.send_establish(units) - _send(SCADA_MGMT_TYPE.ESTABLISH, { comms.version, self.version, DEVICE_TYPE.RTU, _generate_advertisement(units) }) + _send(SCADA_MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.RTU, _generate_advertisement(units) }) end -- send capability advertisement @@ -297,6 +288,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog end -- parse a MODBUS/SCADA packet + ---@nodiscard ---@param side string ---@param sender integer ---@param reply_to integer @@ -333,10 +325,10 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog -- handle a MODBUS/SCADA packet ---@param packet modbus_frame|mgmt_frame - ---@param units table + ---@param units table RTU units ---@param rtu_state rtu_state function public.handle_packet(packet, units, rtu_state) - if packet ~= nil and packet.scada_frame.local_port() == self.l_port then + if packet.scada_frame.local_port() == local_port then -- check sequence number if self.r_seq_num == nil then self.r_seq_num = packet.scada_frame.seq_num() @@ -348,14 +340,14 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog end -- feed watchdog on valid sequence number - self.conn_watchdog.feed() + conn_watchdog.feed() local protocol = packet.scada_frame.protocol() if protocol == PROTOCOL.MODBUS_TCP then + ---@cast packet modbus_frame if rtu_state.linked then local return_code = false ----@diagnostic disable-next-line: param-type-mismatch local reply = modbus.reply__neg_ack(packet) -- handle MODBUS instruction @@ -365,20 +357,17 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog if unit.name == "redstone_io" then -- immediately execute redstone RTU requests ----@diagnostic disable-next-line: param-type-mismatch return_code, reply = unit.modbus_io.handle_packet(packet) if not return_code then log.warning("requested MODBUS operation failed" .. unit_dbg_tag) end else -- check validity then pass off to unit comms thread ----@diagnostic disable-next-line: param-type-mismatch return_code, reply = unit.modbus_io.check_request(packet) if return_code then -- check if there are more than 3 active transactions -- still queue the packet, but this may indicate a problem if unit.pkt_queue.length() > 3 then ----@diagnostic disable-next-line: param-type-mismatch reply = modbus.reply__srv_device_busy(packet) log.debug("queueing new request with " .. unit.pkt_queue.length() .. " transactions already in the queue" .. unit_dbg_tag) @@ -392,7 +381,6 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog end else -- unit ID out of range? ----@diagnostic disable-next-line: param-type-mismatch reply = modbus.reply__gw_unavailable(packet) log.error("received MODBUS packet for non-existent unit") end @@ -402,6 +390,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog log.debug("discarding MODBUS packet before linked") end elseif protocol == PROTOCOL.SCADA_MGMT then + ---@cast packet mgmt_frame -- SCADA management packet if packet.type == SCADA_MGMT_TYPE.ESTABLISH then if packet.length == 1 then @@ -419,10 +408,10 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog if est_ack == ESTABLISH_ACK.BAD_VERSION then -- version mismatch println_ts("supervisor comms version mismatch (try updating), retrying...") - log.warning("supervisor connection denied due to comms version mismatch") + log.warning("supervisor connection denied due to comms version mismatch, retrying") else println_ts("supervisor connection denied, retrying...") - log.warning("supervisor connection denied") + log.warning("supervisor connection denied, retrying") end end @@ -452,13 +441,13 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog end elseif packet.type == SCADA_MGMT_TYPE.CLOSE then -- close connection - self.conn_watchdog.cancel() + conn_watchdog.cancel() public.unlink(rtu_state) println_ts("server connection closed by remote host") log.warning("server connection closed by remote host") elseif packet.type == SCADA_MGMT_TYPE.RTU_ADVERT then -- request for capabilities again - public.send_advertisement(units) + public.send_advertisement(units) else -- not supported log.warning("received unsupported SCADA_MGMT message type " .. packet.type) diff --git a/rtu/startup.lua b/rtu/startup.lua index 00fb4b8..a97658c 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "beta-v0.11.2" +local RTU_VERSION = "v0.12.0" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE @@ -132,13 +132,17 @@ local function main() -- CHECK: reactor ID must be >= to 1 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 >= 0")) + local message = util.c("configure> redstone entry #", entry_idx, " : ", io_reactor, " isn't an integer >= 0") + println(message) + log.fatal(message) return false end -- CHECK: io table exists if type(io_table) ~= "table" then - println(util.c("configure> redstone entry #", entry_idx, " no IO table found")) + local message = util.c("configure> redstone entry #", entry_idx, " no IO table found") + println(message) + log.fatal(message) return false end @@ -148,7 +152,7 @@ local function main() local continue = true - -- check for duplicate entries + -- CHECK: no duplicate entries for i = 1, #units do local unit = units[i] ---@type rtu_unit_registry_entry if unit.reactor == io_reactor and unit.type == RTU_UNIT_TYPE.REDSTONE then @@ -181,7 +185,7 @@ local function main() 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) + log.fatal(message) return false else -- link redstone in RTU @@ -245,7 +249,7 @@ local function main() for_message = util.c("reactor ", io_reactor) end - log.debug(util.c("configure> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for ", for_message)) + log.info(util.c("configure> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for ", for_message)) unit.uid = #units end @@ -259,25 +263,31 @@ local function main() -- CHECK: name is a string if type(name) ~= "string" then - println(util.c("configure> device entry #", i, ": device ", name, " isn't a string")) + local message = util.c("configure> device entry #", i, ": device ", name, " isn't a string") + println(message) + log.fatal(message) return false end -- CHECK: index is an integer >= 1 if (not util.is_int(index)) or (index <= 0) then - println(util.c("configure> device entry #", i, ": index ", index, " isn't an integer >= 1")) + local message = util.c("configure> device entry #", i, ": index ", index, " isn't an integer >= 1") + println(message) + log.fatal(message) return false end -- CHECK: reactor is an integer >= 0 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 >= 0")) + local message = util.c("configure> device entry #", i, ": reactor ", for_reactor, " isn't an integer >= 0") + println(message) + log.fatal(message) return false end local device = ppm.get_periph(name) - local type = nil + local type = nil ---@type string|nil local rtu_iface = nil ---@type rtu_device local rtu_type = nil ---@type RTU_UNIT_TYPE local is_multiblock = false @@ -382,7 +392,7 @@ local function main() table.insert(units, rtu_unit) if is_multiblock and not formed then - log.debug(util.c("configure> device '", name, "' is not formed")) + log.info(util.c("configure> device '", name, "' is not formed")) end local for_message = "facility" @@ -390,7 +400,7 @@ local function main() for_message = util.c("reactor ", for_reactor) end - log.debug(util.c("configure> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ") [", index, "] for ", for_message)) + log.info(util.c("configure> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ") [", index, "] for ", for_message)) rtu_unit.uid = #units end @@ -408,12 +418,12 @@ local function main() if configure() then -- start connection watchdog smem_sys.conn_watchdog = util.new_watchdog(config.COMMS_TIMEOUT) - log.debug("boot> conn watchdog started") + log.debug("startup> conn watchdog started") -- setup comms smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, config.TRUSTED_RANGE, smem_sys.conn_watchdog) - log.debug("boot> comms init") + log.debug("startup> comms init") -- init threads local main_thread = threads.thread__main(__shared_memory) @@ -427,6 +437,8 @@ local function main() end end + log.info("startup> completed") + -- run threads parallel.waitForAll(table.unpack(_threads)) else diff --git a/rtu/threads.lua b/rtu/threads.lua index 152a4b2..6b06eb0 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -1,17 +1,17 @@ -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local ppm = require("scada-common.ppm") -local types = require("scada-common.types") -local util = require("scada-common.util") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local ppm = require("scada-common.ppm") +local types = require("scada-common.types") +local util = require("scada-common.util") -local boilerv_rtu = require("rtu.dev.boilerv_rtu") -local envd_rtu = require("rtu.dev.envd_rtu") -local imatrix_rtu = require("rtu.dev.imatrix_rtu") -local sna_rtu = require("rtu.dev.sna_rtu") -local sps_rtu = require("rtu.dev.sps_rtu") -local turbinev_rtu = require("rtu.dev.turbinev_rtu") +local boilerv_rtu = require("rtu.dev.boilerv_rtu") +local envd_rtu = require("rtu.dev.envd_rtu") +local imatrix_rtu = require("rtu.dev.imatrix_rtu") +local sna_rtu = require("rtu.dev.sna_rtu") +local sps_rtu = require("rtu.dev.sps_rtu") +local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local modbus = require("rtu.modbus") +local modbus = require("rtu.modbus") local threads = {} @@ -26,6 +26,7 @@ local MAIN_CLOCK = 2 -- (2Hz, 40 ticks) local COMMS_SLEEP = 100 -- (100ms, 2 ticks) -- main thread +---@nodiscard ---@param smem rtu_shared_memory function threads.thread__main(smem) ---@class parallel_thread @@ -114,9 +115,9 @@ function threads.thread__main(smem) rtu_comms.reconnect_modem(rtu_dev.modem) println_ts("wireless modem reconnected.") - log.info("comms modem reconnected.") + log.info("comms modem reconnected") else - log.info("wired modem reconnected.") + log.info("wired modem reconnected") end else -- relink lost peripheral to correct unit entry @@ -233,6 +234,7 @@ function threads.thread__main(smem) end -- communications handler thread +---@nodiscard ---@param smem rtu_shared_memory function threads.thread__comms(smem) ---@class parallel_thread @@ -243,13 +245,13 @@ function threads.thread__comms(smem) log.debug("comms thread start") -- load in from shared memory - local rtu_state = smem.rtu_state - local rtu_comms = smem.rtu_sys.rtu_comms - local units = smem.rtu_sys.units + local rtu_state = smem.rtu_state + local rtu_comms = smem.rtu_sys.rtu_comms + local units = smem.rtu_sys.units - local comms_queue = smem.q.mq_comms + local comms_queue = smem.q.mq_comms - local last_update = util.time() + local last_update = util.time() -- thread loop while true do @@ -306,6 +308,7 @@ function threads.thread__comms(smem) end -- per-unit communications handler thread +---@nodiscard ---@param smem rtu_shared_memory ---@param unit rtu_unit_registry_entry function threads.thread__unit_comms(smem, unit) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 0c2ccbe..ad7c0aa 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -244,7 +244,7 @@ function comms.modbus_packet() txn_id = -1, length = 0, unit_id = -1, - func_code = 0, + func_code = 0x80, data = {} } From 4340518ecf137c84ba7aa3d00b0737c3ca128a8b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 22 Feb 2023 23:09:47 -0500 Subject: [PATCH 547/587] #118 coordinator code cleanup --- coordinator/apisessions.lua | 12 +- coordinator/coordinator.lua | 69 ++- coordinator/iocontrol.lua | 655 ++++++++++---------- coordinator/process.lua | 61 +- coordinator/renderer.lua | 10 +- coordinator/sounder.lua | 9 +- coordinator/startup.lua | 43 +- coordinator/ui/components/boiler.lua | 1 + coordinator/ui/components/imatrix.lua | 1 + coordinator/ui/components/processctl.lua | 4 +- coordinator/ui/components/reactor.lua | 7 +- coordinator/ui/components/turbine.lua | 1 + coordinator/ui/components/unit_detail.lua | 15 +- coordinator/ui/components/unit_overview.lua | 13 +- coordinator/ui/components/unit_waiting.lua | 3 +- coordinator/ui/dialog.lua | 5 +- coordinator/ui/layout/main_view.lua | 7 +- coordinator/ui/layout/unit_view.lua | 3 +- 18 files changed, 484 insertions(+), 435 deletions(-) diff --git a/coordinator/apisessions.lua b/coordinator/apisessions.lua index 3c14c08..268052e 100644 --- a/coordinator/apisessions.lua +++ b/coordinator/apisessions.lua @@ -4,13 +4,17 @@ local apisessions = {} function apisessions.handle_packet(packet) end -function apisessions.check_all_watchdogs() -end - -function apisessions.close_all() +-- attempt to identify which session's watchdog timer fired +---@param timer_event number +function apisessions.check_all_watchdogs(timer_event) end +-- delete all closed sessions function apisessions.free_all_closed() end +-- close all open connections +function apisessions.close_all() +end + return apisessions diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index d67a6c9..e4bb283 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -25,6 +25,7 @@ local FAC_COMMAND = comms.FAC_COMMAND local coordinator = {} -- request the user to select a monitor +---@nodiscard ---@param names table available monitors ---@return boolean|string|nil local function ask_monitor(names) @@ -64,9 +65,11 @@ function coordinator.configure_monitors(num_units) end -- we need a certain number of monitors (1 per unit + 1 primary display) - if #names < num_units + 1 then - println("not enough monitors connected (need " .. num_units + 1 .. ")") - log.warning("insufficient monitors present (need " .. num_units + 1 .. ")") + local num_displays_needed = num_units + 1 + if #names < num_displays_needed then + local message = "not enough monitors connected (need " .. num_displays_needed .. ")" + println(message) + log.warning(message) return false end @@ -125,7 +128,6 @@ function coordinator.configure_monitors(num_units) else -- make sure all displays are connected for i = 1, num_units do ----@diagnostic disable-next-line: need-check-nil local display = unit_displays[i] if not util.table_contains(names, display) then @@ -183,14 +185,19 @@ 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 +-- log a message for communications connecting, providing access to progress indication control functions +---@nodiscard ---@param message string ---@return function update, function done function coordinator.log_comms_connecting(message) ----@diagnostic disable-next-line: return-type-mismatch - return log_dmesg(message, "COMMS", true) + local update, done = log_dmesg(message, "COMMS", true) + ---@cast update function + ---@cast done function + return update, done end -- coordinator communications +---@nodiscard ---@param version string coordinator version ---@param modem table modem device ---@param sv_port integer port of configured supervisor @@ -203,23 +210,19 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range sv_linked = false, sv_seq_num = 0, sv_r_seq_num = nil, - modem = modem, connected = false, last_est_ack = ESTABLISH_ACK.ALLOW } - ---@class coord_comms - local public = {} - comms.set_trusted_range(range) -- PRIVATE FUNCTIONS -- -- configure modem channels local function _conf_channels() - self.modem.closeAll() - self.modem.open(sv_listen) - self.modem.open(api_listen) + modem.closeAll() + modem.open(sv_listen) + modem.open(api_listen) end _conf_channels() @@ -242,7 +245,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range pkt.make(msg_type, msg) s_pkt.make(self.sv_seq_num, protocol, pkt.raw_sendable()) - self.modem.transmit(sv_port, sv_listen, s_pkt.raw_sendable()) + modem.transmit(sv_port, sv_listen, s_pkt.raw_sendable()) self.sv_seq_num = self.sv_seq_num + 1 end @@ -259,11 +262,13 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range -- PUBLIC FUNCTIONS -- + ---@class coord_comms + local public = {} + -- reconnect a newly connected modem - ---@param modem table ----@diagnostic disable-next-line: redefined-local - function public.reconnect_modem(modem) - self.modem = modem + ---@param new_modem table + function public.reconnect_modem(new_modem) + modem = new_modem _conf_channels() end @@ -275,6 +280,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range end -- attempt to connect to the subervisor + ---@nodiscard ---@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 @@ -400,7 +406,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range if l_port == api_listen then if protocol == PROTOCOL.COORD_API then ----@diagnostic disable-next-line: param-type-mismatch + ---@cast packet capi_frame apisessions.handle_packet(packet) else log.debug("illegal packet type " .. protocol .. " on api listening channel", true) @@ -421,6 +427,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range -- handle packet if protocol == PROTOCOL.SCADA_CRDN then + ---@cast packet crdn_frame if self.sv_linked then if packet.type == SCADA_CRDN_TYPE.INITIAL_BUILDS then if packet.length == 2 then @@ -432,7 +439,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range -- acknowledge receipt of builds _send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.INITIAL_BUILDS, {}) else - log.error("received invalid INITIAL_BUILDS packet") + log.debug("received invalid INITIAL_BUILDS packet") end else log.debug("INITIAL_BUILDS packet length mismatch") @@ -444,7 +451,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range -- acknowledge receipt of builds _send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.FAC_BUILDS, {}) else - log.error("received invalid FAC_BUILDS packet") + log.debug("received invalid FAC_BUILDS packet") end else log.debug("FAC_BUILDS packet length mismatch") @@ -452,7 +459,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range elseif packet.type == SCADA_CRDN_TYPE.FAC_STATUS then -- update facility status if not iocontrol.update_facility_status(packet.data) then - log.error("received invalid FAC_STATUS packet") + log.debug("received invalid FAC_STATUS packet") end elseif packet.type == SCADA_CRDN_TYPE.FAC_CMD then -- facility command acknowledgement @@ -485,7 +492,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range -- acknowledge receipt of builds _send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.UNIT_BUILDS, {}) else - log.error("received invalid UNIT_BUILDS packet") + log.debug("received invalid UNIT_BUILDS packet") end else log.debug("UNIT_BUILDS packet length mismatch") @@ -518,7 +525,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then unit.ack_alarms_ack(ack) elseif cmd == UNIT_COMMAND.SET_GROUP then - ---@todo how is this going to be handled? + -- UI will be updated to display current group if changed successfully else log.debug(util.c("received unit command ack with unknown command ", cmd)) end @@ -535,6 +542,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range log.debug("discarding SCADA_CRDN packet before linked") end elseif protocol == PROTOCOL.SCADA_MGMT then + ---@cast packet mgmt_frame if packet.type == SCADA_MGMT_TYPE.ESTABLISH then -- connection with supervisor established if packet.length == 2 then @@ -562,10 +570,10 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range self.sv_linked = true else - log.error("invalid supervisor configuration definitions received, establish failed") + log.debug("invalid supervisor configuration definitions received, establish failed") end else - log.error("invalid supervisor configuration table received, establish failed") + log.debug("invalid supervisor configuration table received, establish failed") end else log.debug("SCADA_MGMT establish packet reply (len = 2) unsupported") @@ -577,11 +585,11 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range if est_ack == ESTABLISH_ACK.DENY then if self.last_est_ack ~= est_ack then - log.debug("supervisor connection denied") + log.info("supervisor connection denied") end elseif est_ack == ESTABLISH_ACK.COLLISION then if self.last_est_ack ~= est_ack then - log.debug("supervisor connection denied due to collision") + log.info("supervisor connection denied due to collision") end elseif est_ack == ESTABLISH_ACK.BAD_VERSION then if self.last_est_ack ~= est_ack then @@ -619,9 +627,9 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range sv_watchdog.cancel() self.sv_linked = false println_ts("server connection closed by remote host") - log.warning("server connection closed by remote host") + log.info("server connection closed by remote host") else - log.warning("received unknown SCADA_MGMT packet type " .. packet.type) + log.debug("received unknown SCADA_MGMT packet type " .. packet.type) end else log.debug("discarding non-link SCADA_MGMT packet before linked") @@ -636,6 +644,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range end -- check if the coordinator is still linked to the supervisor + ---@nodiscard function public.is_linked() return self.sv_linked end return public diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index b66ea80..ea243cd 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -1,3 +1,7 @@ +-- +-- I/O Control for Supervisor/Coordinator Integration +-- + local log = require("scada-common.log") local psil = require("scada-common.psil") local types = require("scada-common.types") @@ -16,7 +20,6 @@ local io = {} -- initialize the coordinator IO controller ---@param conf facility_conf configuration ---@param comms coord_comms comms reference ----@diagnostic disable-next-line: redefined-local function iocontrol.init(conf, comms) ---@class ioctl_facility io.facility = { @@ -41,11 +44,11 @@ function iocontrol.init(conf, comms) radiation = types.new_zero_radiation_reading(), - save_cfg_ack = function (success) end, ---@param success boolean - start_ack = function (success) end, ---@param success boolean - stop_ack = function (success) end, ---@param success boolean - scram_ack = function (success) end, ---@param success boolean - ack_alarms_ack = function (success) end, ---@param success boolean + save_cfg_ack = function (success) end, ---@param success boolean + start_ack = function (success) end, ---@param success boolean + stop_ack = function (success) end, ---@param success boolean + scram_ack = function (success) end, ---@param success boolean + ack_alarms_ack = function (success) end, ---@param success boolean ps = psil.create(), @@ -56,7 +59,7 @@ function iocontrol.init(conf, comms) env_d_data = {} } - -- create induction tables (max 1 per unit, preferably 1 total) + -- create induction tables (currently only 1 is supported) for _ = 1, conf.num_units do local data = {} ---@type imatrix_session_db table.insert(io.facility.induction_ps_tbl, psil.create()) @@ -170,6 +173,8 @@ end ---@param build table ---@return boolean valid function iocontrol.record_facility_builds(build) + local valid = true + if type(build) == "table" then local fac = io.facility @@ -187,96 +192,103 @@ function iocontrol.record_facility_builds(build) end else log.debug(util.c("iocontrol.record_facility_builds: invalid induction matrix id ", id)) + valid = false end end end else - log.error("facility builds not a table") - return false + log.debug("facility builds not a table") + valid = false end - return true + return valid end -- populate unit structure builds ---@param builds table ---@return boolean valid function iocontrol.record_unit_builds(builds) + local valid = true + -- note: if not all units and RTUs are connected, some will be nil for id, build in pairs(builds) do local unit = io.units[id] ---@type ioctl_unit + local log_header = util.c("iocontrol.record_unit_builds[UNIT ", id, "]: ") + if type(build) ~= "table" then - log.error(util.c("corrupted unit builds provided, unit ", id, " not a table")) - return false + log.debug(log_header .. "build not a table") + valid = false elseif type(unit) ~= "table" then - log.error(util.c("corrupted unit builds provided, invalid unit ", id)) - return false - end + log.debug(log_header .. "invalid unit id") + valid = false + else + -- reactor build + if type(build.reactor) == "table" then + unit.reactor_data.mek_struct = build.reactor ---@type mek_struct + for key, val in pairs(unit.reactor_data.mek_struct) do + unit.unit_ps.publish(key, val) + end - local log_header = util.c("iocontrol.record_unit_builds[unit ", id, "]: ") - - -- reactor build - if type(build.reactor) == "table" then - unit.reactor_data.mek_struct = build.reactor ---@type mek_struct - for key, val in pairs(unit.reactor_data.mek_struct) do - unit.unit_ps.publish(key, val) - end - - if (type(unit.reactor_data.mek_struct.length) == "number") and (unit.reactor_data.mek_struct.length ~= 0) and - (type(unit.reactor_data.mek_struct.width) == "number") and (unit.reactor_data.mek_struct.width ~= 0) then - unit.unit_ps.publish("size", { unit.reactor_data.mek_struct.length, unit.reactor_data.mek_struct.width }) - end - end - - -- boiler builds - if type(build.boilers) == "table" then - for b_id, boiler in pairs(build.boilers) do - if type(unit.boiler_data_tbl[b_id]) == "table" then - unit.boiler_data_tbl[b_id].formed = boiler[1] ---@type boolean - unit.boiler_data_tbl[b_id].build = boiler[2] ---@type table - - unit.boiler_ps_tbl[b_id].publish("formed", boiler[1]) - - for key, val in pairs(unit.boiler_data_tbl[b_id].build) do - unit.boiler_ps_tbl[b_id].publish(key, val) - end - else - log.debug(util.c(log_header, "invalid boiler id ", b_id)) + if (type(unit.reactor_data.mek_struct.length) == "number") and (unit.reactor_data.mek_struct.length ~= 0) and + (type(unit.reactor_data.mek_struct.width) == "number") and (unit.reactor_data.mek_struct.width ~= 0) then + unit.unit_ps.publish("size", { unit.reactor_data.mek_struct.length, unit.reactor_data.mek_struct.width }) end end - end - -- turbine builds - if type(build.turbines) == "table" then - for t_id, turbine in pairs(build.turbines) do - if type(unit.turbine_data_tbl[t_id]) == "table" then - unit.turbine_data_tbl[t_id].formed = turbine[1] ---@type boolean - unit.turbine_data_tbl[t_id].build = turbine[2] ---@type table + -- boiler builds + if type(build.boilers) == "table" then + for b_id, boiler in pairs(build.boilers) do + if type(unit.boiler_data_tbl[b_id]) == "table" then + unit.boiler_data_tbl[b_id].formed = boiler[1] ---@type boolean + unit.boiler_data_tbl[b_id].build = boiler[2] ---@type table - unit.turbine_ps_tbl[t_id].publish("formed", turbine[1]) + unit.boiler_ps_tbl[b_id].publish("formed", boiler[1]) - for key, val in pairs(unit.turbine_data_tbl[t_id].build) do - unit.turbine_ps_tbl[t_id].publish(key, val) + for key, val in pairs(unit.boiler_data_tbl[b_id].build) do + unit.boiler_ps_tbl[b_id].publish(key, val) + end + else + log.debug(util.c(log_header, "invalid boiler id ", b_id)) + valid = false + end + end + end + + -- turbine builds + if type(build.turbines) == "table" then + for t_id, turbine in pairs(build.turbines) do + if type(unit.turbine_data_tbl[t_id]) == "table" then + unit.turbine_data_tbl[t_id].formed = turbine[1] ---@type boolean + unit.turbine_data_tbl[t_id].build = turbine[2] ---@type table + + unit.turbine_ps_tbl[t_id].publish("formed", turbine[1]) + + for key, val in pairs(unit.turbine_data_tbl[t_id].build) do + unit.turbine_ps_tbl[t_id].publish(key, val) + end + else + log.debug(util.c(log_header, "invalid turbine id ", t_id)) + valid = false end - else - log.debug(util.c(log_header, "invalid turbine id ", t_id)) end end end end - return true + return valid end -- update facility status ---@param status table ---@return boolean valid function iocontrol.update_facility_status(status) + local valid = true local log_header = util.c("iocontrol.update_facility_status: ") + if type(status) ~= "table" then - log.debug(log_header .. "status not a table") - return false + log.debug(util.c(log_header, "status not a table")) + valid = false else local fac = io.facility @@ -284,10 +296,17 @@ function iocontrol.update_facility_status(status) local ctl_status = status[1] - if type(ctl_status) == "table" and (#ctl_status == 14) then + if type(ctl_status) == "table" and #ctl_status == 14 then fac.all_sys_ok = ctl_status[1] fac.auto_ready = ctl_status[2] - fac.auto_active = ctl_status[3] > 0 + + if type(ctl_status[3]) == "number" then + fac.auto_active = ctl_status[3] > 1 + else + fac.auto_active = false + valid = false + end + fac.auto_ramping = ctl_status[4] fac.auto_saturated = ctl_status[5] @@ -327,6 +346,7 @@ function iocontrol.update_facility_status(status) end else log.debug(log_header .. "control status not a table or length mismatch") + valid = false end -- RTU statuses @@ -334,10 +354,10 @@ function iocontrol.update_facility_status(status) local rtu_statuses = status[2] fac.rtu_count = 0 + if type(rtu_statuses) == "table" then -- connected RTU count fac.rtu_count = rtu_statuses.count - fac.ps.publish("rtu_count", fac.rtu_count) -- power statistics if type(rtu_statuses.power) == "table" then @@ -346,6 +366,7 @@ function iocontrol.update_facility_status(status) fac.induction_ps_tbl[1].publish("avg_outflow", rtu_statuses.power[3]) else log.debug(log_header .. "power statistics list not a table") + valid = false end -- induction matricies statuses @@ -371,16 +392,16 @@ function iocontrol.update_facility_status(status) if data.formed then if rtu_faulted then - fac.induction_ps_tbl[id].publish("computed_status", 3) -- faulted + fac.induction_ps_tbl[id].publish("computed_status", 3) -- faulted elseif data.tanks.energy_fill >= 0.99 then - fac.induction_ps_tbl[id].publish("computed_status", 6) -- full + fac.induction_ps_tbl[id].publish("computed_status", 6) -- full elseif data.tanks.energy_fill <= 0.01 then - fac.induction_ps_tbl[id].publish("computed_status", 5) -- empty + fac.induction_ps_tbl[id].publish("computed_status", 5) -- empty else - fac.induction_ps_tbl[id].publish("computed_status", 4) -- on-line + fac.induction_ps_tbl[id].publish("computed_status", 4) -- on-line end else - fac.induction_ps_tbl[id].publish("computed_status", 2) -- not formed + fac.induction_ps_tbl[id].publish("computed_status", 2) -- not formed end for key, val in pairs(fac.induction_data_tbl[id].state) do @@ -396,6 +417,7 @@ function iocontrol.update_facility_status(status) end else log.debug(log_header .. "induction matrix list not a table") + valid = false end -- environment detector status @@ -413,313 +435,324 @@ function iocontrol.update_facility_status(status) end else log.debug(log_header .. "radiation monitor list not a table") - return false + valid = false end else log.debug(log_header .. "rtu statuses not a table") + valid = false end + + fac.ps.publish("rtu_count", fac.rtu_count) end - return true + return valid end -- update unit statuses ---@param statuses table ---@return boolean valid function iocontrol.update_unit_statuses(statuses) + local valid = true + if type(statuses) ~= "table" then log.debug("iocontrol.update_unit_statuses: unit statuses not a table") - return false + valid = false elseif #statuses ~= #io.units then log.debug("iocontrol.update_unit_statuses: number of provided unit statuses does not match expected number of units") - return false + valid = false else local burn_rate_sum = 0.0 -- get all unit statuses for i = 1, #statuses do local log_header = util.c("iocontrol.update_unit_statuses[unit ", i, "]: ") + local unit = io.units[i] ---@type ioctl_unit local status = statuses[i] if type(status) ~= "table" or #status ~= 5 then log.debug(log_header .. "invalid status entry in unit statuses (not a table or invalid length)") - return false - end - - -- reactor PLC status - - local reactor_status = status[1] - - if type(reactor_status) ~= "table" then - reactor_status = {} - log.debug(log_header .. "reactor status not a table") - end - - if #reactor_status == 0 then - unit.unit_ps.publish("computed_status", 1) -- disconnected - elseif #reactor_status == 3 then - local mek_status = reactor_status[1] - local rps_status = reactor_status[2] - local gen_status = reactor_status[3] - - if #gen_status == 6 then - unit.reactor_data.last_status_update = gen_status[1] - unit.reactor_data.control_state = gen_status[2] - unit.reactor_data.rps_tripped = gen_status[3] - unit.reactor_data.rps_trip_cause = gen_status[4] - unit.reactor_data.no_reactor = gen_status[5] - unit.reactor_data.formed = gen_status[6] - else - log.debug(log_header .. "reactor general status length mismatch") - end - - unit.reactor_data.rps_status = rps_status ---@type rps_status - unit.reactor_data.mek_status = mek_status ---@type mek_status - - -- if status hasn't been received, mek_status = {} - if type(unit.reactor_data.mek_status.act_burn_rate) == "number" then - burn_rate_sum = burn_rate_sum + unit.reactor_data.mek_status.act_burn_rate - end - - if unit.reactor_data.mek_status.status then - unit.unit_ps.publish("computed_status", 5) -- running - else - if unit.reactor_data.no_reactor then - unit.unit_ps.publish("computed_status", 3) -- faulted - elseif not unit.reactor_data.formed then - unit.unit_ps.publish("computed_status", 2) -- multiblock not formed - elseif unit.reactor_data.rps_status.force_dis then - unit.unit_ps.publish("computed_status", 7) -- reactor force disabled - elseif unit.reactor_data.rps_tripped and unit.reactor_data.rps_trip_cause ~= "manual" then - unit.unit_ps.publish("computed_status", 6) -- SCRAM - else - unit.unit_ps.publish("computed_status", 4) -- disabled - end - end - - for key, val in pairs(unit.reactor_data) do - if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then - unit.unit_ps.publish(key, val) - end - end - - if type(unit.reactor_data.rps_status) == "table" then - for key, val in pairs(unit.reactor_data.rps_status) do - unit.unit_ps.publish(key, val) - end - end - - if type(unit.reactor_data.mek_status) == "table" then - for key, val in pairs(unit.reactor_data.mek_status) do - unit.unit_ps.publish(key, val) - end - end + valid = false else - log.debug(log_header .. "reactor status length mismatch") - end + -- reactor PLC status + local reactor_status = status[1] - -- RTU statuses + if type(reactor_status) ~= "table" then + reactor_status = {} + log.debug(log_header .. "reactor status not a table") + end - local rtu_statuses = status[2] + if #reactor_status == 0 then + unit.unit_ps.publish("computed_status", 1) -- disconnected + elseif #reactor_status == 3 then + local mek_status = reactor_status[1] + local rps_status = reactor_status[2] + local gen_status = reactor_status[3] - if type(rtu_statuses) == "table" then - -- boiler statuses - if type(rtu_statuses.boilers) == "table" then - for id = 1, #unit.boiler_ps_tbl do - if rtu_statuses.boilers[i] == nil then - -- disconnected - unit.boiler_ps_tbl[id].publish("computed_status", 1) + if #gen_status == 6 then + unit.reactor_data.last_status_update = gen_status[1] + unit.reactor_data.control_state = gen_status[2] + unit.reactor_data.rps_tripped = gen_status[3] + unit.reactor_data.rps_trip_cause = gen_status[4] + unit.reactor_data.no_reactor = gen_status[5] + unit.reactor_data.formed = gen_status[6] + else + log.debug(log_header .. "reactor general status length mismatch") + end + + unit.reactor_data.rps_status = rps_status ---@type rps_status + unit.reactor_data.mek_status = mek_status ---@type mek_status + + -- if status hasn't been received, mek_status = {} + if type(unit.reactor_data.mek_status.act_burn_rate) == "number" then + burn_rate_sum = burn_rate_sum + unit.reactor_data.mek_status.act_burn_rate + end + + if unit.reactor_data.mek_status.status then + unit.unit_ps.publish("computed_status", 5) -- running + else + if unit.reactor_data.no_reactor then + unit.unit_ps.publish("computed_status", 3) -- faulted + elseif not unit.reactor_data.formed then + unit.unit_ps.publish("computed_status", 2) -- multiblock not formed + elseif unit.reactor_data.rps_status.force_dis then + unit.unit_ps.publish("computed_status", 7) -- reactor force disabled + elseif unit.reactor_data.rps_tripped and unit.reactor_data.rps_trip_cause ~= "manual" then + unit.unit_ps.publish("computed_status", 6) -- SCRAM + else + unit.unit_ps.publish("computed_status", 4) -- disabled end end - for id, boiler in pairs(rtu_statuses.boilers) do - if type(unit.boiler_data_tbl[id]) == "table" then - local rtu_faulted = boiler[1] ---@type boolean - unit.boiler_data_tbl[id].formed = boiler[2] ---@type boolean - unit.boiler_data_tbl[id].state = boiler[3] ---@type table - unit.boiler_data_tbl[id].tanks = boiler[4] ---@type table + for key, val in pairs(unit.reactor_data) do + if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then + unit.unit_ps.publish(key, val) + end + end - local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db + if type(unit.reactor_data.rps_status) == "table" then + for key, val in pairs(unit.reactor_data.rps_status) do + unit.unit_ps.publish(key, val) + end + end - unit.boiler_ps_tbl[id].publish("formed", data.formed) - unit.boiler_ps_tbl[id].publish("faulted", rtu_faulted) + if type(unit.reactor_data.mek_status) == "table" then + for key, val in pairs(unit.reactor_data.mek_status) do + unit.unit_ps.publish(key, val) + end + end + else + log.debug(log_header .. "reactor status length mismatch") + valid = false + end - if rtu_faulted then - unit.boiler_ps_tbl[id].publish("computed_status", 3) -- faulted - elseif data.formed then - if data.state.boil_rate > 0 then - unit.boiler_ps_tbl[id].publish("computed_status", 5) -- active + -- RTU statuses + local rtu_statuses = status[2] + + if type(rtu_statuses) == "table" then + -- boiler statuses + if type(rtu_statuses.boilers) == "table" then + for id = 1, #unit.boiler_ps_tbl do + if rtu_statuses.boilers[i] == nil then + -- disconnected + unit.boiler_ps_tbl[id].publish("computed_status", 1) + end + end + + for id, boiler in pairs(rtu_statuses.boilers) do + if type(unit.boiler_data_tbl[id]) == "table" then + local rtu_faulted = boiler[1] ---@type boolean + unit.boiler_data_tbl[id].formed = boiler[2] ---@type boolean + unit.boiler_data_tbl[id].state = boiler[3] ---@type table + unit.boiler_data_tbl[id].tanks = boiler[4] ---@type table + + local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db + + unit.boiler_ps_tbl[id].publish("formed", data.formed) + unit.boiler_ps_tbl[id].publish("faulted", rtu_faulted) + + if rtu_faulted then + unit.boiler_ps_tbl[id].publish("computed_status", 3) -- faulted + elseif data.formed then + if data.state.boil_rate > 0 then + unit.boiler_ps_tbl[id].publish("computed_status", 5) -- active + else + unit.boiler_ps_tbl[id].publish("computed_status", 4) -- idle + end else - unit.boiler_ps_tbl[id].publish("computed_status", 4) -- idle + unit.boiler_ps_tbl[id].publish("computed_status", 2) -- not formed + end + + for key, val in pairs(unit.boiler_data_tbl[id].state) do + unit.boiler_ps_tbl[id].publish(key, val) + end + + for key, val in pairs(unit.boiler_data_tbl[id].tanks) do + unit.boiler_ps_tbl[id].publish(key, val) end else - unit.boiler_ps_tbl[id].publish("computed_status", 2) -- not formed + log.debug(util.c(log_header, "invalid boiler id ", id)) + valid = false end - - for key, val in pairs(unit.boiler_data_tbl[id].state) do - unit.boiler_ps_tbl[id].publish(key, val) - end - - for key, val in pairs(unit.boiler_data_tbl[id].tanks) do - unit.boiler_ps_tbl[id].publish(key, val) - end - else - log.debug(util.c(log_header, "invalid boiler id ", id)) - end - end - else - log.debug(log_header .. "boiler list not a table") - end - - -- turbine statuses - if type(rtu_statuses.turbines) == "table" then - for id = 1, #unit.turbine_ps_tbl do - if rtu_statuses.turbines[i] == nil then - -- disconnected - unit.turbine_ps_tbl[id].publish("computed_status", 1) end + else + log.debug(log_header .. "boiler list not a table") + valid = false end - for id, turbine in pairs(rtu_statuses.turbines) do - if type(unit.turbine_data_tbl[id]) == "table" then - local rtu_faulted = turbine[1] ---@type boolean - unit.turbine_data_tbl[id].formed = turbine[2] ---@type boolean - unit.turbine_data_tbl[id].state = turbine[3] ---@type table - unit.turbine_data_tbl[id].tanks = turbine[4] ---@type table + -- turbine statuses + if type(rtu_statuses.turbines) == "table" then + for id = 1, #unit.turbine_ps_tbl do + if rtu_statuses.turbines[i] == nil then + -- disconnected + unit.turbine_ps_tbl[id].publish("computed_status", 1) + end + end - local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db + for id, turbine in pairs(rtu_statuses.turbines) do + if type(unit.turbine_data_tbl[id]) == "table" then + local rtu_faulted = turbine[1] ---@type boolean + unit.turbine_data_tbl[id].formed = turbine[2] ---@type boolean + unit.turbine_data_tbl[id].state = turbine[3] ---@type table + unit.turbine_data_tbl[id].tanks = turbine[4] ---@type table - unit.turbine_ps_tbl[id].publish("formed", data.formed) - unit.turbine_ps_tbl[id].publish("faulted", rtu_faulted) + local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db - if rtu_faulted then - unit.turbine_ps_tbl[id].publish("computed_status", 3) -- faulted - elseif data.formed then - if data.tanks.energy_fill >= 0.99 then - unit.turbine_ps_tbl[id].publish("computed_status", 6) -- trip - elseif data.state.flow_rate < 100 then - unit.turbine_ps_tbl[id].publish("computed_status", 4) -- idle + unit.turbine_ps_tbl[id].publish("formed", data.formed) + unit.turbine_ps_tbl[id].publish("faulted", rtu_faulted) + + if rtu_faulted then + unit.turbine_ps_tbl[id].publish("computed_status", 3) -- faulted + elseif data.formed then + if data.tanks.energy_fill >= 0.99 then + unit.turbine_ps_tbl[id].publish("computed_status", 6) -- trip + elseif data.state.flow_rate < 100 then + unit.turbine_ps_tbl[id].publish("computed_status", 4) -- idle + else + unit.turbine_ps_tbl[id].publish("computed_status", 5) -- active + end else - unit.turbine_ps_tbl[id].publish("computed_status", 5) -- active + unit.turbine_ps_tbl[id].publish("computed_status", 2) -- not formed + end + + for key, val in pairs(unit.turbine_data_tbl[id].state) do + unit.turbine_ps_tbl[id].publish(key, val) + end + + for key, val in pairs(unit.turbine_data_tbl[id].tanks) do + unit.turbine_ps_tbl[id].publish(key, val) end else - unit.turbine_ps_tbl[id].publish("computed_status", 2) -- not formed + log.debug(util.c(log_header, "invalid turbine id ", id)) + valid = false end + end + else + log.debug(log_header .. "turbine list not a table") + valid = false + end - for key, val in pairs(unit.turbine_data_tbl[id].state) do - unit.turbine_ps_tbl[id].publish(key, val) - end + -- environment detector status + if type(rtu_statuses.rad_mon) == "table" then + if #rtu_statuses.rad_mon > 0 then + local rad_mon = rtu_statuses.rad_mon[1] + local rtu_faulted = rad_mon[1] ---@type boolean + unit.radiation = rad_mon[2] ---@type number - for key, val in pairs(unit.turbine_data_tbl[id].tanks) do - unit.turbine_ps_tbl[id].publish(key, val) - end + unit.unit_ps.publish("radiation", unit.radiation) else - log.debug(util.c(log_header, "invalid turbine id ", id)) + unit.radiation = types.new_zero_radiation_reading() + end + else + log.debug(log_header .. "radiation monitor list not a table") + valid = false + end + else + log.debug(log_header .. "rtu list not a table") + valid = false + end + + -- annunciator + unit.annunciator = status[3] + + if type(unit.annunciator) ~= "table" then + unit.annunciator = {} + log.debug(log_header .. "annunciator state not a table") + valid = false + end + + for key, val in pairs(unit.annunciator) do + if key == "TurbineTrip" then + -- split up turbine trip table for all turbines and a general OR combination + local trips = val + local any = false + + for id = 1, #trips do + any = any or trips[id] + unit.turbine_ps_tbl[id].publish(key, trips[id]) + end + + unit.unit_ps.publish("TurbineTrip", any) + elseif key == "BoilerOnline" or key == "HeatingRateLow" or key == "WaterLevelLow" then + -- split up array for all boilers + for id = 1, #val do + unit.boiler_ps_tbl[id].publish(key, val[id]) + end + elseif key == "TurbineOnline" or key == "SteamDumpOpen" or key == "TurbineOverSpeed" then + -- split up array for all turbines + for id = 1, #val do + unit.turbine_ps_tbl[id].publish(key, val[id]) + end + elseif type(val) == "table" then + -- we missed one of the tables? + log.debug(log_header .. "unrecognized table found in annunciator list, this is a bug") + valid = false + else + -- non-table fields + unit.unit_ps.publish(key, val) + end + end + + -- alarms + local alarm_states = status[4] + + if type(alarm_states) == "table" then + for id = 1, #alarm_states do + local state = alarm_states[id] + + unit.alarms[id] = state + + if state == types.ALARM_STATE.TRIPPED or state == types.ALARM_STATE.ACKED then + unit.unit_ps.publish("Alarm_" .. id, 2) + elseif state == types.ALARM_STATE.RING_BACK then + unit.unit_ps.publish("Alarm_" .. id, 3) + else + unit.unit_ps.publish("Alarm_" .. id, 1) end end else - log.debug(log_header .. "turbine list not a table") - return false + log.debug(log_header .. "alarm states not a table") + valid = false end - -- environment detector status - if type(rtu_statuses.rad_mon) == "table" then - if #rtu_statuses.rad_mon > 0 then - local rad_mon = rtu_statuses.rad_mon[1] - local rtu_faulted = rad_mon[1] ---@type boolean - unit.radiation = rad_mon[2] ---@type number + -- unit state fields + local unit_state = status[5] - unit.unit_ps.publish("radiation", unit.radiation) + if type(unit_state) == "table" then + if #unit_state == 5 then + unit.unit_ps.publish("U_StatusLine1", unit_state[1]) + unit.unit_ps.publish("U_StatusLine2", unit_state[2]) + unit.unit_ps.publish("U_WasteMode", unit_state[3]) + unit.unit_ps.publish("U_AutoReady", unit_state[4]) + unit.unit_ps.publish("U_AutoDegraded", unit_state[5]) else - unit.radiation = types.new_zero_radiation_reading() + log.debug(log_header .. "unit state length mismatch") + valid = false end else - log.debug(log_header .. "radiation monitor list not a table") - return false + log.debug(log_header .. "unit state not a table") + valid = false end - else - log.debug(log_header .. "rtu list not a table") - end - - -- annunciator - - unit.annunciator = status[3] - - if type(unit.annunciator) ~= "table" then - unit.annunciator = {} - log.debug(log_header .. "annunciator state not a table") - end - - for key, val in pairs(unit.annunciator) do - if key == "TurbineTrip" then - -- split up turbine trip table for all turbines and a general OR combination - local trips = val - local any = false - - for id = 1, #trips do - any = any or trips[id] - unit.turbine_ps_tbl[id].publish(key, trips[id]) - end - - unit.unit_ps.publish("TurbineTrip", any) - elseif key == "BoilerOnline" or key == "HeatingRateLow" or key == "WaterLevelLow" then - -- split up array for all boilers - for id = 1, #val do - unit.boiler_ps_tbl[id].publish(key, val[id]) - end - elseif key == "TurbineOnline" or key == "SteamDumpOpen" or key == "TurbineOverSpeed" then - -- split up array for all turbines - for id = 1, #val do - unit.turbine_ps_tbl[id].publish(key, val[id]) - end - elseif type(val) == "table" then - -- we missed one of the tables? - log.error(log_header .. "unrecognized table found in annunciator list, this is a bug", true) - else - -- non-table fields - unit.unit_ps.publish(key, val) - end - end - - -- alarms - - local alarm_states = status[4] - - if type(alarm_states) == "table" then - for id = 1, #alarm_states do - local state = alarm_states[id] - - unit.alarms[id] = state - - if state == types.ALARM_STATE.TRIPPED or state == types.ALARM_STATE.ACKED then - unit.unit_ps.publish("Alarm_" .. id, 2) - elseif state == types.ALARM_STATE.RING_BACK then - unit.unit_ps.publish("Alarm_" .. id, 3) - else - unit.unit_ps.publish("Alarm_" .. id, 1) - end - end - else - log.debug(log_header .. "alarm states not a table") - end - - -- unit state fields - - local unit_state = status[5] - - if type(unit_state) == "table" then - if #unit_state == 5 then - unit.unit_ps.publish("U_StatusLine1", unit_state[1]) - unit.unit_ps.publish("U_StatusLine2", unit_state[2]) - unit.unit_ps.publish("U_WasteMode", unit_state[3]) - unit.unit_ps.publish("U_AutoReady", unit_state[4]) - unit.unit_ps.publish("U_AutoDegraded", unit_state[5]) - else - log.debug(log_header .. "unit state length mismatch") - end - else - log.debug(log_header .. "unit state not a table") end end @@ -729,7 +762,7 @@ function iocontrol.update_unit_statuses(statuses) sounder.eval(io.units) end - return true + return valid end -- get the IO controller database diff --git a/coordinator/process.lua b/coordinator/process.lua index e3604bf..1e318ed 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -1,3 +1,6 @@ +-- +-- Process Control Management +-- local comms = require("scada-common.comms") local log = require("scada-common.log") @@ -30,11 +33,11 @@ local self = { -------------------------- -- initialize the process controller ----@param iocontrol ioctl ----@diagnostic disable-next-line: redefined-local -function process.init(iocontrol, comms) +---@param iocontrol ioctl iocontrl system +---@param coord_comms coord_comms coordinator communications +function process.init(iocontrol, coord_comms) self.io = iocontrol - self.comms = comms + self.comms = coord_comms for i = 1, self.io.facility.num_units do self.config.limits[i] = 0.1 @@ -91,13 +94,13 @@ end -- facility SCRAM command function process.fac_scram() self.comms.send_fac_command(FAC_COMMAND.SCRAM_ALL) - log.debug("FAC: SCRAM ALL") + log.debug("PROCESS: FAC SCRAM ALL") end -- facility alarm acknowledge command function process.fac_ack_alarms() self.comms.send_fac_command(FAC_COMMAND.ACK_ALL_ALARMS) - log.debug("FAC: ACK ALL ALARMS") + log.debug("PROCESS: FAC ACK ALL ALARMS") end -- start reactor @@ -105,7 +108,7 @@ end function process.start(id) self.io.units[id].control_state = true self.comms.send_unit_command(UNIT_COMMAND.START, id) - log.debug(util.c("UNIT[", id, "]: START")) + log.debug(util.c("PROCESS: UNIT[", id, "] START")) end -- SCRAM reactor @@ -113,14 +116,14 @@ end function process.scram(id) self.io.units[id].control_state = false self.comms.send_unit_command(UNIT_COMMAND.SCRAM, id) - log.debug(util.c("UNIT[", id, "]: SCRAM")) + log.debug(util.c("PROCESS: UNIT[", id, "] SCRAM")) end -- reset reactor protection system ---@param id integer unit ID function process.reset_rps(id) self.comms.send_unit_command(UNIT_COMMAND.RESET_RPS, id) - log.debug(util.c("UNIT[", id, "]: RESET RPS")) + log.debug(util.c("PROCESS: UNIT[", id, "] RESET RPS")) end -- set burn rate @@ -128,7 +131,7 @@ end ---@param rate number burn rate function process.set_rate(id, rate) self.comms.send_unit_command(UNIT_COMMAND.SET_BURN, id, rate) - log.debug(util.c("UNIT[", id, "]: SET BURN = ", rate)) + log.debug(util.c("PROCESS: UNIT[", id, "] SET BURN ", rate)) end -- set waste mode @@ -139,13 +142,11 @@ function process.set_waste(id, mode) self.io.units[id].unit_ps.publish("U_WasteMode", mode) self.comms.send_unit_command(UNIT_COMMAND.SET_WASTE, id, mode) - log.debug(util.c("UNIT[", id, "]: SET WASTE = ", mode)) + log.debug(util.c("PROCESS: UNIT[", id, "] SET WASTE ", mode)) local waste_mode = settings.get("WASTE_MODES") ---@type table|nil - if type(waste_mode) ~= "table" then - waste_mode = {} - end + if type(waste_mode) ~= "table" then waste_mode = {} end waste_mode[id] = mode @@ -160,7 +161,7 @@ end ---@param id integer unit ID function process.ack_all_alarms(id) self.comms.send_unit_command(UNIT_COMMAND.ACK_ALL_ALARMS, id) - log.debug(util.c("UNIT[", id, "]: ACK ALL ALARMS")) + log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALL ALARMS")) end -- acknowledge an alarm @@ -168,7 +169,7 @@ end ---@param alarm integer alarm ID function process.ack_alarm(id, alarm) self.comms.send_unit_command(UNIT_COMMAND.ACK_ALARM, id, alarm) - log.debug(util.c("UNIT[", id, "]: ACK ALARM ", alarm)) + log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALARM ", alarm)) end -- reset an alarm @@ -176,7 +177,7 @@ end ---@param alarm integer alarm ID function process.reset_alarm(id, alarm) self.comms.send_unit_command(UNIT_COMMAND.RESET_ALARM, id, alarm) - log.debug(util.c("UNIT[", id, "]: RESET ALARM ", alarm)) + log.debug(util.c("PROCESS: UNIT[", id, "] RESET ALARM ", alarm)) end -- assign a unit to a group @@ -184,13 +185,11 @@ end ---@param group_id integer|0 group ID or 0 for independent function process.set_group(unit_id, group_id) self.comms.send_unit_command(UNIT_COMMAND.SET_GROUP, unit_id, group_id) - log.debug(util.c("UNIT[", unit_id, "]: SET GROUP ", group_id)) + log.debug(util.c("PROCESS: UNIT[", unit_id, "] SET GROUP ", group_id)) local prio_groups = settings.get("PRIORITY_GROUPS") ---@type table|nil - if type(prio_groups) ~= "table" then - prio_groups = {} - end + if type(prio_groups) ~= "table" then prio_groups = {} end prio_groups[unit_id] = group_id @@ -208,13 +207,13 @@ end -- stop automatic process control function process.stop_auto() self.comms.send_fac_command(FAC_COMMAND.STOP) - log.debug("FAC: STOP AUTO") + log.debug("PROCESS: STOP AUTO CTL") end -- start automatic process control function process.start_auto() self.comms.send_auto_start(self.config) - log.debug("FAC: START AUTO") + log.debug("PROCESS: START AUTO CTL") end -- save process control settings @@ -246,8 +245,6 @@ function process.save(mode, burn_target, charge_target, gen_target, limits) log.warning("process.save(): failed to save coordinator settings file") end - log.debug("saved = " .. util.strval(saved)) - self.io.facility.save_cfg_ack(saved) end @@ -273,18 +270,4 @@ function process.start_ack_handle(response) self.io.facility.start_ack(ack) end --------------------------- --- SUPERVISOR RESPONSES -- --------------------------- - --- acknowledgement from the supervisor to assign a unit to a group -function process.sv_assign(unit_id, group_id) - self.io.units[unit_id].group = group_id -end - --- acknowledgement from the supervisor to assign a unit a burn rate limit -function process.sv_limit(unit_id, limit) - self.io.units[unit_id].limit = limit -end - return process diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 51816f6..4003d71 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -1,3 +1,7 @@ +-- +-- Graphics Rendering Control +-- + local log = require("scada-common.log") local util = require("scada-common.util") @@ -56,6 +60,7 @@ function renderer.set_displays(monitors) end -- check if the renderer is configured to use a given monitor peripheral +---@nodiscard ---@param periph table peripheral ---@return boolean is_used function renderer.is_monitor_used(periph) @@ -87,6 +92,7 @@ function renderer.reset(recolor) end -- check main display width +---@nodiscard ---@return boolean width_okay function renderer.validate_main_display_width() local w, _ = engine.monitors.primary.getSize() @@ -94,6 +100,7 @@ function renderer.validate_main_display_width() end -- check display sizes +---@nodiscard ---@return boolean valid all unit display dimensions OK function renderer.validate_unit_display_sizes() local valid = true @@ -101,7 +108,7 @@ function renderer.validate_unit_display_sizes() for id, monitor in pairs(engine.monitors.unit_displays) do local w, h = monitor.getSize() if w ~= 79 or h ~= 52 then - log.warning(util.c("unit ", id, " display resolution not 79 wide by 52 tall: ", w, ", ", h)) + log.warning(util.c("RENDERER: unit ", id, " display resolution not 79 wide by 52 tall: ", w, ", ", h)) valid = false end end @@ -171,6 +178,7 @@ function renderer.close_ui() end -- is the UI ready? +---@nodiscard ---@return boolean ready function renderer.ui_ready() return engine.ui_ready end diff --git a/coordinator/sounder.lua b/coordinator/sounder.lua index ff0ec17..373b8f1 100644 --- a/coordinator/sounder.lua +++ b/coordinator/sounder.lua @@ -14,7 +14,7 @@ local sounder = {} local _2_PI = 2 * math.pi -- 2 whole pies, hope you're hungry local _DRATE = 48000 -- 48kHz audio -local _MAX_VAL = 127/2 -- max signed integer in this 8-bit audio +local _MAX_VAL = 127 / 2 -- max signed integer in this 8-bit audio local _MAX_SAMPLES = 0x20000 -- 128 * 1024 samples local _05s_SAMPLES = 24000 -- half a second worth of samples @@ -26,7 +26,8 @@ local alarm_ctl = { playing = false, num_active = 0, next_block = 1, - quad_buffer = { {}, {}, {}, {} } -- split audio up into 0.5s samples so specific components can be ended quicker + -- split audio up into 0.5s samples so specific components can be ended quicker + quad_buffer = { {}, {}, {}, {} } } -- sounds modeled after https://www.e2s.com/references-and-guidelines/listen-and-download-alarm-tones @@ -52,6 +53,7 @@ local TONES = { } -- calculate how many samples are in the given number of milliseconds +---@nodiscard ---@param ms integer milliseconds ---@return integer samples local function ms_to_samples(ms) return math.floor(ms * 48) end @@ -224,6 +226,7 @@ end --#endregion -- hard audio limiter +---@nodiscard ---@param output number output level ---@return number limited -128.0 to 127.0 local function limit(output) @@ -454,7 +457,7 @@ function sounder.test_power_scale() end end - log.debug("power rescale test took " .. (util.time_ms() - start) .. "ms") + log.debug("SOUNDER: power rescale test took " .. (util.time_ms() - start) .. "ms") end --#endregion diff --git a/coordinator/startup.lua b/coordinator/startup.lua index aebb5dd..8af8567 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.10.1" +local COORDINATOR_VERSION = "v0.11.0" local print = util.print local println = util.println @@ -81,7 +81,7 @@ local function main() -- setup monitors local configured, monitors = coordinator.configure_monitors(config.NUM_UNITS) if not configured or monitors == nil then - println("boot> monitor setup failed") + println("startup> monitor setup failed") log.fatal("monitor configuration failed") return end @@ -91,11 +91,11 @@ local function main() renderer.reset(config.RECOLOR) if not renderer.validate_main_display_width() then - println("boot> main display must be 8 blocks wide") + println("startup> main display must be 8 blocks wide") log.fatal("main display not wide enough") return elseif not renderer.validate_unit_display_sizes() then - println("boot> one or more unit display dimensions incorrect; they must be 4x4 blocks") + println("startup> one or more unit display dimensions incorrect; they must be 4x4 blocks") log.fatal("unit display dimensions incorrect") return end @@ -116,7 +116,7 @@ local function main() local speaker = ppm.get_device("speaker") if speaker == nil then log_boot("annunciator alarm speaker not found") - println("boot> speaker not found") + println("startup> speaker not found") log.fatal("no annunciator alarm speaker found") return else @@ -135,7 +135,7 @@ local function main() local modem = ppm.get_wireless_modem() if modem == nil then log_comms("wireless modem not found") - println("boot> wireless modem not found") + println("startup> wireless modem not found") log.fatal("no wireless modem on startup") return else @@ -145,12 +145,12 @@ local function main() -- create connection watchdog local conn_watchdog = util.new_watchdog(config.COMMS_TIMEOUT) conn_watchdog.cancel() - log.debug("boot> conn watchdog created") + log.debug("startup> conn watchdog created") -- 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, config.TRUSTED_RANGE, conn_watchdog) - log.debug("boot> comms init") + log.debug("startup> comms init") log_comms("comms initialized") -- base loop clock (2Hz, 10 ticks) @@ -176,7 +176,7 @@ local function main() end if not init_connect_sv() then - println("boot> failed to connect to supervisor") + println("startup> failed to connect to supervisor") log_sys("system shutdown") return else @@ -199,7 +199,7 @@ local function main() renderer.close_ui() log_graphics(util.c("UI crashed: ", message)) println_ts("UI crashed") - log.fatal(util.c("ui crashed with error ", message)) + log.fatal(util.c("GUI crashed with error ", message)) else log_graphics("first UI draw took " .. (util.time_ms() - draw_start) .. "ms") @@ -223,7 +223,7 @@ local function main() if ui_ok then -- start connection watchdog conn_watchdog.feed() - log.debug("boot> conn watchdog started") + log.debug("startup> conn watchdog started") log_sys("system started successfully") end @@ -243,7 +243,6 @@ local function main() no_modem = true log_sys("comms modem disconnected") println_ts("wireless modem disconnected!") - log.error("comms modem disconnected!") -- close out UI renderer.close_ui() @@ -252,20 +251,21 @@ local function main() 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 - println_ts("lost a configured monitor, system will now exit") - log_sys("lost a configured monitor, system will now exit") + local msg = "lost a configured monitor, system will now exit" + println_ts(msg) + log_sys(msg) break else log_sys("lost unused monitor, ignoring") end elseif type == "speaker" then - println_ts("lost alarm sounder speaker") - log_sys("lost alarm sounder speaker") + local msg = "lost alarm sounder speaker" + println_ts(msg) + log_sys(msg) end end elseif event == "peripheral" then @@ -291,8 +291,9 @@ local function main() elseif type == "monitor" then -- not supported, system will exit on loss of in-use monitors elseif type == "speaker" then - println_ts("alarm sounder speaker reconnected") - log_sys("alarm sounder speaker reconnected") + local msg = "alarm sounder speaker reconnected" + println_ts(msg) + log_sys(msg) sounder.reconnect(device) end end @@ -301,7 +302,7 @@ local function main() -- main loop tick -- free any closed sessions - --apisessions.free_all_closed() + apisessions.free_all_closed() -- update date and time string for main display iocontrol.get_db().facility.ps.publish("date_time", os.date(date_format)) @@ -326,7 +327,7 @@ local function main() -- a non-clock/main watchdog timer event --check API watchdogs - --apisessions.check_all_watchdogs(param1) + apisessions.check_all_watchdogs(param1) -- notify timer callback dispatcher tcallbackdsp.handle(param1) diff --git a/coordinator/ui/components/boiler.lua b/coordinator/ui/components/boiler.lua index c4a433b..cc4e93b 100644 --- a/coordinator/ui/components/boiler.lua +++ b/coordinator/ui/components/boiler.lua @@ -13,6 +13,7 @@ local cpair = core.graphics.cpair local border = core.graphics.border -- new boiler view +---@nodiscard ---@param root graphics_element parent ---@param x integer top left x ---@param y integer top left y diff --git a/coordinator/ui/components/imatrix.lua b/coordinator/ui/components/imatrix.lua index 2910fbb..e60a126 100644 --- a/coordinator/ui/components/imatrix.lua +++ b/coordinator/ui/components/imatrix.lua @@ -19,6 +19,7 @@ local border = core.graphics.border local TEXT_ALIGN = core.graphics.TEXT_ALIGN -- new induction matrix view +---@nodiscard ---@param root graphics_element parent ---@param x integer top left x ---@param y integer top left y diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 17b3e66..82ce614 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -16,11 +16,8 @@ local DataIndicator = require("graphics.elements.indicators.data") local IndicatorLight = require("graphics.elements.indicators.light") local RadIndicator = require("graphics.elements.indicators.rad") local TriIndicatorLight = require("graphics.elements.indicators.trilight") -local VerticalBar = require("graphics.elements.indicators.vbar") local HazardButton = require("graphics.elements.controls.hazard_button") -local MultiButton = require("graphics.elements.controls.multi_button") -local PushButton = require("graphics.elements.controls.push_button") local RadioButton = require("graphics.elements.controls.radio_button") local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") @@ -32,6 +29,7 @@ local border = core.graphics.border local period = core.flasher.PERIOD -- new process control view +---@nodiscard ---@param root graphics_element parent ---@param x integer top left x ---@param y integer top left y diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua index 5dc9f23..139a8d3 100644 --- a/coordinator/ui/components/reactor.lua +++ b/coordinator/ui/components/reactor.lua @@ -1,3 +1,5 @@ +local types = require("scada-common.types") + local style = require("coordinator.ui.style") local core = require("graphics.core") @@ -13,6 +15,7 @@ local cpair = core.graphics.cpair local border = core.graphics.border -- create new reactor view +---@nodiscard ---@param root graphics_element parent ---@param x integer top left x ---@param y integer top left y @@ -47,7 +50,7 @@ local function new_view(root, x, y, data, ps) local waste = HorizontalBar{parent=reactor_fills,x=8,y=5,show_percent=true,bar_fg_bg=cpair(colors.brown,colors.gray),height=1,width=14} ps.subscribe("ccool_type", function (type) - if type == "mekanism:sodium" then + if type == types.FLUID.SODIUM then ccool.recolor(cpair(colors.lightBlue, colors.gray)) else ccool.recolor(cpair(colors.blue, colors.gray)) @@ -55,7 +58,7 @@ local function new_view(root, x, y, data, ps) end) ps.subscribe("hcool_type", function (type) - if type == "mekanism:superheated_sodium" then + if type == types.FLUID.SUPERHEATED_SODIUM then hcool.recolor(cpair(colors.orange, colors.gray)) else hcool.recolor(cpair(colors.white, colors.gray)) diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua index ef09cee..c6caec9 100644 --- a/coordinator/ui/components/turbine.lua +++ b/coordinator/ui/components/turbine.lua @@ -15,6 +15,7 @@ local cpair = core.graphics.cpair local border = core.graphics.border -- new turbine view +---@nodiscard ---@param root graphics_element parent ---@param x integer top left x ---@param y integer top left y diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 8fb7c38..82b01ec 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -57,6 +57,7 @@ local waste_opts = { } -- create a unit view +---@nodiscard ---@param parent graphics_element parent ---@param id integer local function init(parent, id) @@ -237,13 +238,13 @@ local function init(parent, id) local rcs_annunc = Div{parent=rcs,width=27,height=22,x=2,y=1} local rcs_tags = Div{parent=rcs,width=2,height=13,x=29,y=9} - local c_flt = IndicatorLight{parent=rcs_annunc,label="RCS Hardware Fault",colors=cpair(colors.yellow,colors.gray)} - local c_emg = TriIndicatorLight{parent=rcs_annunc,label="Emergency Coolant",c1=colors.gray,c2=colors.white,c3=colors.yellow} - local c_cfm = IndicatorLight{parent=rcs_annunc,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} - local c_brm = IndicatorLight{parent=rcs_annunc,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)} - local c_sfm = IndicatorLight{parent=rcs_annunc,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} - local c_mwrf = IndicatorLight{parent=rcs_annunc,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} - local c_tbnt = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local c_flt = IndicatorLight{parent=rcs_annunc,label="RCS Hardware Fault",colors=cpair(colors.yellow,colors.gray)} + local c_emg = TriIndicatorLight{parent=rcs_annunc,label="Emergency Coolant",c1=colors.gray,c2=colors.white,c3=colors.yellow} + local c_cfm = IndicatorLight{parent=rcs_annunc,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_brm = IndicatorLight{parent=rcs_annunc,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_sfm = IndicatorLight{parent=rcs_annunc,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_mwrf = IndicatorLight{parent=rcs_annunc,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} + local c_tbnt = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} u_ps.subscribe("RCSFault", c_flt.update) u_ps.subscribe("EmergencyCoolant", c_emg.update) diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 672c6ea..5062116 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -19,6 +19,7 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN local pipe = core.graphics.pipe -- make a new unit overview window +---@nodiscard ---@param parent graphics_element parent ---@param x integer top left x ---@param y integer top left y @@ -51,7 +52,7 @@ local function make(parent, x, y, unit) -- REACTOR -- ------------- - reactor_view(root, 1, 3, unit.reactor_data, unit.unit_ps) + local _ = reactor_view(root, 1, 3, unit.reactor_data, unit.unit_ps) if num_boilers > 0 then local coolant_pipes = {} @@ -101,16 +102,16 @@ local function make(parent, x, y, unit) local steam_pipes_b = {} if no_boilers then - table.insert(steam_pipes_b, pipe(0, 1, 3, 1, colors.white)) -- steam to turbine 1 - table.insert(steam_pipes_b, pipe(0, 2, 3, 2, colors.blue)) -- water to turbine 1 + table.insert(steam_pipes_b, pipe(0, 1, 3, 1, colors.white)) -- steam to turbine 1 + table.insert(steam_pipes_b, pipe(0, 2, 3, 2, colors.blue)) -- water to turbine 1 if num_turbines >= 2 then - table.insert(steam_pipes_b, pipe(1, 2, 3, 9, colors.white)) -- steam to turbine 2 - table.insert(steam_pipes_b, pipe(2, 3, 3, 10, colors.blue)) -- water to turbine 2 + table.insert(steam_pipes_b, pipe(1, 2, 3, 9, colors.white)) -- steam to turbine 2 + table.insert(steam_pipes_b, pipe(2, 3, 3, 10, colors.blue)) -- water to turbine 2 end if num_turbines >= 3 then - table.insert(steam_pipes_b, pipe(1, 9, 3, 17, colors.white)) -- steam boiler 1 to turbine 1 junction end + table.insert(steam_pipes_b, pipe(1, 9, 3, 17, colors.white)) -- steam boiler 1 to turbine 1 junction end table.insert(steam_pipes_b, pipe(2, 10, 3, 18, colors.blue)) -- water boiler 1 to turbine 1 junction start end else diff --git a/coordinator/ui/components/unit_waiting.lua b/coordinator/ui/components/unit_waiting.lua index f7fd7ec..8c29f1a 100644 --- a/coordinator/ui/components/unit_waiting.lua +++ b/coordinator/ui/components/unit_waiting.lua @@ -1,5 +1,5 @@ -- --- Reactor Unit SCADA Coordinator GUI +-- Reactor Unit Waiting Spinner -- local style = require("coordinator.ui.style") @@ -16,6 +16,7 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN local cpair = core.graphics.cpair -- create a unit waiting view +---@nodiscard ---@param parent graphics_element parent ---@param y integer y offset local function init(parent, y) diff --git a/coordinator/ui/dialog.lua b/coordinator/ui/dialog.lua index 4c2a522..676ae2b 100644 --- a/coordinator/ui/dialog.lua +++ b/coordinator/ui/dialog.lua @@ -3,13 +3,11 @@ local completion = require("cc.completion") local util = require("scada-common.util") local print = util.print -local println = util.println -local print_ts = util.print_ts -local println_ts = util.println_ts local dialog = {} -- ask the user yes or no +---@nodiscard ---@param question string ---@param default boolean ---@return boolean|nil @@ -36,6 +34,7 @@ function dialog.ask_y_n(question, default) end -- ask the user for an input within a set of options +---@nodiscard ---@param options table ---@param cancel string ---@return boolean|string|nil diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index b200f44..d1397d8 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -30,6 +30,7 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN local cpair = core.graphics.cpair -- create new main view +---@nodiscard ---@param monitor table main viewscreen local function init(monitor) local facility = iocontrol.get_db().facility @@ -77,7 +78,7 @@ local function init(monitor) end end - -- command & control + -- command & control cnc_y_start = cnc_y_start @@ -90,7 +91,7 @@ local function init(monitor) cnc_bottom_align_start = cnc_bottom_align_start + 2 - local process = process_ctl(main, 2, cnc_bottom_align_start) + local _ = process_ctl(main, 2, cnc_bottom_align_start) -- testing ---@fixme remove test code @@ -123,7 +124,7 @@ local function init(monitor) SwitchButton{parent=audio,x=1,text="RCS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rcs} SwitchButton{parent=audio,x=1,text="TURBINE TRIP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_turbinet} - local imatrix_1 = imatrix(main, 131, cnc_bottom_align_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1]) + local _ = imatrix(main, 131, cnc_bottom_align_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1]) return main end diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index 1c5fddf..c6ca828 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -9,12 +9,13 @@ local unit_detail = require("coordinator.ui.components.unit_detail") local DisplayBox = require("graphics.elements.displaybox") -- create a unit view +---@nodiscard ---@param monitor table ---@param id integer local function init(monitor, id) local main = DisplayBox{window=monitor,fg_bg=style.root} - unit_detail(main, id) + local _ = unit_detail(main, id) return main end From 225ed7baa19436a32a372c0d80d63d1bf61a2feb Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 22 Feb 2023 23:20:59 -0500 Subject: [PATCH 548/587] #118 removed some coordinator nodiscard tags --- coordinator/ui/components/boiler.lua | 1 - coordinator/ui/components/imatrix.lua | 1 - coordinator/ui/components/processctl.lua | 1 - coordinator/ui/components/reactor.lua | 1 - coordinator/ui/components/turbine.lua | 1 - coordinator/ui/components/unit_detail.lua | 1 - coordinator/ui/components/unit_overview.lua | 3 +-- coordinator/ui/components/unit_waiting.lua | 1 - coordinator/ui/layout/main_view.lua | 5 ++--- coordinator/ui/layout/unit_view.lua | 3 +-- 10 files changed, 4 insertions(+), 14 deletions(-) diff --git a/coordinator/ui/components/boiler.lua b/coordinator/ui/components/boiler.lua index cc4e93b..c4a433b 100644 --- a/coordinator/ui/components/boiler.lua +++ b/coordinator/ui/components/boiler.lua @@ -13,7 +13,6 @@ local cpair = core.graphics.cpair local border = core.graphics.border -- new boiler view ----@nodiscard ---@param root graphics_element parent ---@param x integer top left x ---@param y integer top left y diff --git a/coordinator/ui/components/imatrix.lua b/coordinator/ui/components/imatrix.lua index e60a126..2910fbb 100644 --- a/coordinator/ui/components/imatrix.lua +++ b/coordinator/ui/components/imatrix.lua @@ -19,7 +19,6 @@ local border = core.graphics.border local TEXT_ALIGN = core.graphics.TEXT_ALIGN -- new induction matrix view ----@nodiscard ---@param root graphics_element parent ---@param x integer top left x ---@param y integer top left y diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 82ce614..26944e0 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -29,7 +29,6 @@ local border = core.graphics.border local period = core.flasher.PERIOD -- new process control view ----@nodiscard ---@param root graphics_element parent ---@param x integer top left x ---@param y integer top left y diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua index 139a8d3..a17fc75 100644 --- a/coordinator/ui/components/reactor.lua +++ b/coordinator/ui/components/reactor.lua @@ -15,7 +15,6 @@ local cpair = core.graphics.cpair local border = core.graphics.border -- create new reactor view ----@nodiscard ---@param root graphics_element parent ---@param x integer top left x ---@param y integer top left y diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua index c6caec9..ef09cee 100644 --- a/coordinator/ui/components/turbine.lua +++ b/coordinator/ui/components/turbine.lua @@ -15,7 +15,6 @@ local cpair = core.graphics.cpair local border = core.graphics.border -- new turbine view ----@nodiscard ---@param root graphics_element parent ---@param x integer top left x ---@param y integer top left y diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 82b01ec..f507682 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -57,7 +57,6 @@ local waste_opts = { } -- create a unit view ----@nodiscard ---@param parent graphics_element parent ---@param id integer local function init(parent, id) diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 5062116..e5a07f9 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -19,7 +19,6 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN local pipe = core.graphics.pipe -- make a new unit overview window ----@nodiscard ---@param parent graphics_element parent ---@param x integer top left x ---@param y integer top left y @@ -52,7 +51,7 @@ local function make(parent, x, y, unit) -- REACTOR -- ------------- - local _ = reactor_view(root, 1, 3, unit.reactor_data, unit.unit_ps) + reactor_view(root, 1, 3, unit.reactor_data, unit.unit_ps) if num_boilers > 0 then local coolant_pipes = {} diff --git a/coordinator/ui/components/unit_waiting.lua b/coordinator/ui/components/unit_waiting.lua index 8c29f1a..3b1a846 100644 --- a/coordinator/ui/components/unit_waiting.lua +++ b/coordinator/ui/components/unit_waiting.lua @@ -16,7 +16,6 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN local cpair = core.graphics.cpair -- create a unit waiting view ----@nodiscard ---@param parent graphics_element parent ---@param y integer y offset local function init(parent, y) diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index d1397d8..94d25f8 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -30,7 +30,6 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN local cpair = core.graphics.cpair -- create new main view ----@nodiscard ---@param monitor table main viewscreen local function init(monitor) local facility = iocontrol.get_db().facility @@ -91,7 +90,7 @@ local function init(monitor) cnc_bottom_align_start = cnc_bottom_align_start + 2 - local _ = process_ctl(main, 2, cnc_bottom_align_start) + process_ctl(main, 2, cnc_bottom_align_start) -- testing ---@fixme remove test code @@ -124,7 +123,7 @@ local function init(monitor) SwitchButton{parent=audio,x=1,text="RCS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rcs} SwitchButton{parent=audio,x=1,text="TURBINE TRIP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_turbinet} - local _ = imatrix(main, 131, cnc_bottom_align_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1]) + imatrix(main, 131, cnc_bottom_align_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1]) return main end diff --git a/coordinator/ui/layout/unit_view.lua b/coordinator/ui/layout/unit_view.lua index c6ca828..1c5fddf 100644 --- a/coordinator/ui/layout/unit_view.lua +++ b/coordinator/ui/layout/unit_view.lua @@ -9,13 +9,12 @@ local unit_detail = require("coordinator.ui.components.unit_detail") local DisplayBox = require("graphics.elements.displaybox") -- create a unit view ----@nodiscard ---@param monitor table ---@param id integer local function init(monitor, id) local main = DisplayBox{window=monitor,fg_bg=style.root} - local _ = unit_detail(main, id) + unit_detail(main, id) return main end From 38ac552613df4a66c1e00fa9a43be0ad53f06f43 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 24 Feb 2023 19:50:01 -0500 Subject: [PATCH 549/587] #118 graphics cleanup --- graphics/core.lua | 10 +++++++--- graphics/element.lua | 21 +++++++++++++++++++-- graphics/elements/displaybox.lua | 1 + graphics/elements/div.lua | 1 + graphics/elements/indicators/alight.lua | 1 + graphics/elements/indicators/coremap.lua | 1 + graphics/elements/indicators/data.lua | 1 + graphics/elements/indicators/hbar.lua | 1 + graphics/elements/indicators/icon.lua | 1 + graphics/elements/indicators/light.lua | 1 + graphics/elements/indicators/power.lua | 1 + graphics/elements/indicators/rad.lua | 1 + graphics/elements/indicators/state.lua | 1 + graphics/elements/indicators/trilight.lua | 1 + graphics/elements/indicators/vbar.lua | 1 + graphics/elements/rectangle.lua | 2 +- graphics/elements/tiling.lua | 2 +- graphics/flasher.lua | 6 ++---- 18 files changed, 43 insertions(+), 11 deletions(-) diff --git a/graphics/core.lua b/graphics/core.lua index 510e31d..98c8ed5 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -16,6 +16,7 @@ local events = {} ---@field y integer -- create a new touch event definition +---@nodiscard ---@param monitor string ---@param x integer ---@param y integer @@ -32,7 +33,7 @@ core.events = events local graphics = {} ----@alias TEXT_ALIGN integer +---@enum TEXT_ALIGN graphics.TEXT_ALIGN = { LEFT = 1, CENTER = 2, @@ -47,6 +48,7 @@ graphics.TEXT_ALIGN = { ---@alias element_id string|integer -- create a new border definition +---@nodiscard ---@param width integer border width ---@param color color border color ---@param even? boolean whether to pad width extra to account for rectangular pixels, defaults to false @@ -66,6 +68,7 @@ end ---@field h integer -- create a new graphics frame definition +---@nodiscard ---@param x integer ---@param y integer ---@param w integer @@ -91,6 +94,7 @@ end ---@field blit_bkg string -- create a new color pair definition +---@nodiscard ---@param a color ---@param b color ---@return cpair @@ -120,9 +124,9 @@ end ---@field thin boolean true for 1 subpixel, false (default) for 2 ---@field align_tr boolean false to align bottom left (default), true to align top right --- create a new pipe --- +-- create a new pipe
-- note: pipe coordinate origin is (0, 0) +---@nodiscard ---@param x1 integer starting x, origin is 0 ---@param y1 integer starting y, origin is 0 ---@param x2 integer ending x, origin is 0 diff --git a/graphics/element.lua b/graphics/element.lua index 5f32060..8aa3ce9 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -47,6 +47,7 @@ local element = {} ---|tiling_args -- a base graphics element, should not be created on its own +---@nodiscard ---@param args graphics_args arguments function element.new(args) local self = { @@ -172,6 +173,7 @@ function element.new(args) end -- get value + ---@nodiscard function protected.get_value() return protected.value end @@ -218,6 +220,7 @@ function element.new(args) end -- get public interface + ---@nodiscard ---@return graphics_element element, element_id id function protected.get() return public, self.id end @@ -246,11 +249,13 @@ function element.new(args) ---------------------- -- get the window object + ---@nodiscard function public.window() return protected.window end -- CHILD ELEMENTS -- -- add a child element + ---@nodiscard ---@param key string|nil id ---@param child graphics_template ---@return integer|string key @@ -271,6 +276,7 @@ function element.new(args) end -- get a child element + ---@nodiscard ---@return graphics_element function public.get_child(key) return self.children[key] end @@ -279,6 +285,7 @@ function element.new(args) function public.remove(key) self.children[key] = nil end -- attempt to get a child element by ID (does not include this element itself) + ---@nodiscard ---@param id element_id ---@return graphics_element|nil element function public.get_element_by_id(id) @@ -297,39 +304,49 @@ function element.new(args) -- AUTO-PLACEMENT -- -- skip a line for automatically placed elements - function public.line_break() self.next_y = self.next_y + 1 end + function public.line_break() + self.next_y = self.next_y + 1 + end -- PROPERTIES -- -- get the foreground/background colors + ---@nodiscard ---@return cpair fg_bg - function public.get_fg_bg() return protected.fg_bg end + function public.get_fg_bg() + return protected.fg_bg + end -- get element x + ---@nodiscard ---@return integer x function public.get_x() return protected.frame.x end -- get element y + ---@nodiscard ---@return integer y function public.get_y() return protected.frame.y end -- get element width + ---@nodiscard ---@return integer width function public.width() return protected.frame.w end -- get element height + ---@nodiscard ---@return integer height function public.height() return protected.frame.h end -- get the element value + ---@nodiscard ---@return any value function public.get_value() return protected.get_value() diff --git a/graphics/elements/displaybox.lua b/graphics/elements/displaybox.lua index 0d7fd47..c7e5c9f 100644 --- a/graphics/elements/displaybox.lua +++ b/graphics/elements/displaybox.lua @@ -12,6 +12,7 @@ local element = require("graphics.element") ---@field fg_bg? cpair foreground/background colors -- new root display box +---@nodiscard ---@param args displaybox_args local function displaybox(args) -- create new graphics element base object diff --git a/graphics/elements/div.lua b/graphics/elements/div.lua index 59e3a1e..5eeef71 100644 --- a/graphics/elements/div.lua +++ b/graphics/elements/div.lua @@ -13,6 +13,7 @@ local element = require("graphics.element") ---@field fg_bg? cpair foreground/background colors -- new div element +---@nodiscard ---@param args div_args ---@return graphics_element element, element_id id local function div(args) diff --git a/graphics/elements/indicators/alight.lua b/graphics/elements/indicators/alight.lua index eea103a..8bb8fa6 100644 --- a/graphics/elements/indicators/alight.lua +++ b/graphics/elements/indicators/alight.lua @@ -20,6 +20,7 @@ local flasher = require("graphics.flasher") ---@field fg_bg? cpair foreground/background colors -- new alarm indicator light +---@nodiscard ---@param args alarm_indicator_light ---@return graphics_element element, element_id id local function alarm_indicator_light(args) diff --git a/graphics/elements/indicators/coremap.lua b/graphics/elements/indicators/coremap.lua index 0ca72e1..c50348b 100644 --- a/graphics/elements/indicators/coremap.lua +++ b/graphics/elements/indicators/coremap.lua @@ -14,6 +14,7 @@ local element = require("graphics.element") ---@field y? integer 1 if omitted -- new core map box +---@nodiscard ---@param args core_map_args ---@return graphics_element element, element_id id local function core_map(args) diff --git a/graphics/elements/indicators/data.lua b/graphics/elements/indicators/data.lua index d19fab0..66d45dc 100644 --- a/graphics/elements/indicators/data.lua +++ b/graphics/elements/indicators/data.lua @@ -19,6 +19,7 @@ local element = require("graphics.element") ---@field fg_bg? cpair foreground/background colors -- new data indicator +---@nodiscard ---@param args data_indicator_args ---@return graphics_element element, element_id id local function data(args) diff --git a/graphics/elements/indicators/hbar.lua b/graphics/elements/indicators/hbar.lua index a05cdb6..2d9b110 100644 --- a/graphics/elements/indicators/hbar.lua +++ b/graphics/elements/indicators/hbar.lua @@ -17,6 +17,7 @@ local element = require("graphics.element") ---@field fg_bg? cpair foreground/background colors -- new horizontal bar +---@nodiscard ---@param args hbar_args ---@return graphics_element element, element_id id local function hbar(args) diff --git a/graphics/elements/indicators/icon.lua b/graphics/elements/indicators/icon.lua index 0c71d29..f31479d 100644 --- a/graphics/elements/indicators/icon.lua +++ b/graphics/elements/indicators/icon.lua @@ -20,6 +20,7 @@ local element = require("graphics.element") ---@field fg_bg? cpair foreground/background colors -- new icon indicator +---@nodiscard ---@param args icon_indicator_args ---@return graphics_element element, element_id id local function icon(args) diff --git a/graphics/elements/indicators/light.lua b/graphics/elements/indicators/light.lua index 3695553..e764ad9 100644 --- a/graphics/elements/indicators/light.lua +++ b/graphics/elements/indicators/light.lua @@ -18,6 +18,7 @@ local flasher = require("graphics.flasher") ---@field fg_bg? cpair foreground/background colors -- new indicator light +---@nodiscard ---@param args indicator_light_args ---@return graphics_element element, element_id id local function indicator_light(args) diff --git a/graphics/elements/indicators/power.lua b/graphics/elements/indicators/power.lua index e76f690..1d727ae 100644 --- a/graphics/elements/indicators/power.lua +++ b/graphics/elements/indicators/power.lua @@ -18,6 +18,7 @@ local element = require("graphics.element") ---@field fg_bg? cpair foreground/background colors -- new power indicator +---@nodiscard ---@param args power_indicator_args ---@return graphics_element element, element_id id local function power(args) diff --git a/graphics/elements/indicators/rad.lua b/graphics/elements/indicators/rad.lua index 86ec856..2e4ad56 100644 --- a/graphics/elements/indicators/rad.lua +++ b/graphics/elements/indicators/rad.lua @@ -19,6 +19,7 @@ local element = require("graphics.element") ---@field fg_bg? cpair foreground/background colors -- new radiation indicator +---@nodiscard ---@param args rad_indicator_args ---@return graphics_element element, element_id id local function rad(args) diff --git a/graphics/elements/indicators/state.lua b/graphics/elements/indicators/state.lua index 386910c..10d081b 100644 --- a/graphics/elements/indicators/state.lua +++ b/graphics/elements/indicators/state.lua @@ -20,6 +20,7 @@ local element = require("graphics.element") ---@field fg_bg? cpair foreground/background colors -- new state indicator +---@nodiscard ---@param args state_indicator_args ---@return graphics_element element, element_id id local function state_indicator(args) diff --git a/graphics/elements/indicators/trilight.lua b/graphics/elements/indicators/trilight.lua index 2c61fb7..543ebf5 100644 --- a/graphics/elements/indicators/trilight.lua +++ b/graphics/elements/indicators/trilight.lua @@ -20,6 +20,7 @@ local flasher = require("graphics.flasher") ---@field fg_bg? cpair foreground/background colors -- new tri-state indicator light +---@nodiscard ---@param args tristate_indicator_light_args ---@return graphics_element element, element_id id local function tristate_indicator_light(args) diff --git a/graphics/elements/indicators/vbar.lua b/graphics/elements/indicators/vbar.lua index be7d9e4..fe7f9bc 100644 --- a/graphics/elements/indicators/vbar.lua +++ b/graphics/elements/indicators/vbar.lua @@ -15,6 +15,7 @@ local element = require("graphics.element") ---@field fg_bg? cpair foreground/background colors -- new vertical bar +---@nodiscard ---@param args vbar_args ---@return graphics_element element, element_id id local function vbar(args) diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua index 09c20da..6422cbc 100644 --- a/graphics/elements/rectangle.lua +++ b/graphics/elements/rectangle.lua @@ -144,7 +144,7 @@ local function rectangle(args) e.window.blit(spaces, blit_fg, blit_bg_top_bot) end else - if (args.thin == true) then + if args.thin == true then e.window.blit(p_s, blit_fg_sides, blit_bg_sides) else e.window.blit(p_s, blit_fg, blit_bg_sides) diff --git a/graphics/elements/tiling.lua b/graphics/elements/tiling.lua index 86af96d..a97438a 100644 --- a/graphics/elements/tiling.lua +++ b/graphics/elements/tiling.lua @@ -60,7 +60,7 @@ local function tiling(args) -- create pattern for y = start_y, inner_height + (start_y - 1) do e.window.setCursorPos(start_x, y) - for x = 1, inner_width do + for _ = 1, inner_width do if alternator then if even then e.window.blit(" ", "00", fill_a .. fill_a) diff --git a/graphics/flasher.lua b/graphics/flasher.lua index b5eed69..0a3d9ea 100644 --- a/graphics/flasher.lua +++ b/graphics/flasher.lua @@ -21,8 +21,7 @@ local active = false local registry = { {}, {}, {} } -- one registry table per period local callback_counter = 0 --- blink registered indicators --- +-- blink registered indicators
-- this assumes it is called every 250ms, it does no checking of time on its own local function callback_250ms() if active then @@ -55,8 +54,7 @@ function flasher.clear() registry = { {}, {}, {} } end --- register a function to be called on the selected blink period --- +-- register a function to be called on the selected blink period
-- times are not strictly enforced, but all with a given period will be set at the same time ---@param f function function to call each period ---@param period PERIOD time period option (1, 2, or 3) From b7895080cb01c6906052cf944a5f42718f8a6287 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 24 Feb 2023 23:36:16 -0500 Subject: [PATCH 550/587] #118 supervisor cleanup --- supervisor/facility.lua | 28 +++--- supervisor/session/coordinator.lua | 23 ++--- supervisor/session/plc.lua | 59 ++++++------ supervisor/session/rsctl.lua | 1 + supervisor/session/rtu.lua | 18 ++-- supervisor/session/rtu/boilerv.lua | 2 + supervisor/session/rtu/envd.lua | 2 + supervisor/session/rtu/imatrix.lua | 2 + supervisor/session/rtu/redstone.lua | 6 ++ supervisor/session/rtu/sna.lua | 2 + supervisor/session/rtu/sps.lua | 4 +- supervisor/session/rtu/turbinev.lua | 2 + supervisor/session/rtu/txnctrl.lua | 7 +- supervisor/session/rtu/unit_session.lua | 26 ++++-- supervisor/session/svsessions.lua | 39 +++++--- supervisor/startup.lua | 10 +-- supervisor/supervisor.lua | 115 +++++++++++++----------- supervisor/unit.lua | 41 +++++---- supervisor/unitlogic.lua | 10 ++- 19 files changed, 241 insertions(+), 156 deletions(-) diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 24f03c2..db56aec 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -53,6 +53,7 @@ local rate_Kd = -1.0 local facility = {} -- create a new facility management object +---@nodiscard ---@param num_reactors integer number of reactor units ---@param cooling_conf table cooling configurations of reactor units function facility.new(num_reactors, cooling_conf) @@ -124,6 +125,7 @@ function facility.new(num_reactors, cooling_conf) end -- check if all auto-controlled units completed ramping + ---@nodiscard local function _all_units_ramped() local all_ramped = true @@ -185,10 +187,7 @@ function facility.new(num_reactors, cooling_conf) unallocated = math.max(0, unallocated - ctl.br100) - if last ~= ctl.br100 then - log.debug("unit " .. u.get_id() .. ": set to " .. ctl.br100 .. " (was " .. last .. ")") - u.a_commit_br100(ramp) - end + if last ~= ctl.br100 then u.a_commit_br100(ramp) end end end end @@ -426,7 +425,7 @@ function facility.new(num_reactors, cooling_conf) self.accumulator = self.accumulator + (error * (now - self.last_time)) end - local runtime = now - self.time_start + -- local runtime = now - self.time_start local integral = self.accumulator local derivative = (error - self.last_error) / (now - self.last_time) @@ -441,8 +440,8 @@ function facility.new(num_reactors, cooling_conf) self.saturated = output ~= out_c - log.debug(util.sprintf("CHARGE[%f] { CHRG[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%d] }", - runtime, avg_charge, error, integral, output, out_c, P, I, D)) + -- log.debug(util.sprintf("CHARGE[%f] { CHRG[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%d] }", + -- runtime, avg_charge, error, integral, output, out_c, P, I, D)) _allocate_burn_rate(out_c, true) @@ -495,7 +494,7 @@ function facility.new(num_reactors, cooling_conf) self.accumulator = self.accumulator + (error * (now - self.last_time)) end - local runtime = now - self.time_start + -- local runtime = now - self.time_start local integral = self.accumulator local derivative = (error - self.last_error) / (now - self.last_time) @@ -513,8 +512,8 @@ function facility.new(num_reactors, cooling_conf) self.saturated = output ~= out_c - log.debug(util.sprintf("GEN_RATE[%f] { RATE[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%f] }", - runtime, avg_inflow, error, integral, output, out_c, P, I, D)) + -- log.debug(util.sprintf("GEN_RATE[%f] { RATE[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%f] }", + -- runtime, avg_inflow, error, integral, output, out_c, P, I, D)) _allocate_burn_rate(out_c, false) @@ -814,6 +813,7 @@ function facility.new(num_reactors, cooling_conf) -- READ STATES/PROPERTIES -- -- get build properties of all machines + ---@nodiscard ---@param inc_imatrix boolean? true/nil to include induction matrix build, false to exclude function public.get_build(inc_imatrix) local build = {} @@ -830,6 +830,7 @@ function facility.new(num_reactors, cooling_conf) end -- get automatic process control status + ---@nodiscard function public.get_control_status() local astat = self.ascram_status return { @@ -851,6 +852,7 @@ function facility.new(num_reactors, cooling_conf) end -- get RTU statuses + ---@nodiscard function public.get_rtu_statuses() local status = {} @@ -889,9 +891,9 @@ function facility.new(num_reactors, cooling_conf) return status end - function public.get_units() - return self.units - end + -- get the units in this facility + ---@nodiscard + function public.get_units() return self.units end return public end diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 3b3f231..1402993 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -13,6 +13,7 @@ local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE local SCADA_CRDN_TYPE = comms.SCADA_CRDN_TYPE local UNIT_COMMAND = comms.UNIT_COMMAND local FAC_COMMAND = comms.FAC_COMMAND + local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local SV_Q_CMDS = svqtypes.SV_Q_CMDS @@ -46,6 +47,7 @@ local PERIODICS = { } -- coordinator supervisor session +---@nodiscard ---@param id integer session ID ---@param in_queue mqueue in message queue ---@param out_queue mqueue out message queue @@ -55,8 +57,6 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) local log_header = "crdn_session(" .. id .. "): " local self = { - in_q = in_queue, - out_q = out_queue, units = facility.get_units(), -- connection properties seq_num = 0, @@ -100,7 +100,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) c_pkt.make(msg_type, msg) s_pkt.make(self.seq_num, PROTOCOL.SCADA_CRDN, c_pkt.raw_sendable()) - self.out_q.push_packet(s_pkt) + out_queue.push_packet(s_pkt) self.seq_num = self.seq_num + 1 end @@ -114,7 +114,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) m_pkt.make(msg_type, msg) s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) - self.out_q.push_packet(s_pkt) + out_queue.push_packet(s_pkt) self.seq_num = self.seq_num + 1 end @@ -275,14 +275,14 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) local unit = self.units[uid] ---@type reactor_unit if cmd == UNIT_COMMAND.START then - self.out_q.push_data(SV_Q_DATA.START, data) + out_queue.push_data(SV_Q_DATA.START, data) elseif cmd == UNIT_COMMAND.SCRAM then - self.out_q.push_data(SV_Q_DATA.SCRAM, data) + out_queue.push_data(SV_Q_DATA.SCRAM, data) elseif cmd == UNIT_COMMAND.RESET_RPS then - self.out_q.push_data(SV_Q_DATA.RESET_RPS, data) + out_queue.push_data(SV_Q_DATA.RESET_RPS, data) elseif cmd == UNIT_COMMAND.SET_BURN then if pkt.length == 3 then - self.out_q.push_data(SV_Q_DATA.SET_BURN, data) + out_queue.push_data(SV_Q_DATA.SET_BURN, data) else log.debug(log_header .. "CRDN unit command burn rate missing option") end @@ -333,9 +333,11 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) local public = {} -- get the session ID + ---@nodiscard function public.get_id() return id end -- check if a timer matches this session's watchdog + ---@nodiscard function public.check_wd(timer) return self.conn_watchdog.is_timer(timer) and self.connected end @@ -349,6 +351,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) end -- iterate the session + ---@nodiscard ---@return boolean connected function public.iterate() if self.connected then @@ -358,9 +361,9 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) local handle_start = util.time() - while self.in_q.ready() and self.connected do + while in_queue.ready() and self.connected do -- get a new message to process - local message = self.in_q.pop() + local message = in_queue.pop() if message ~= nil then if message.qtype == mqueue.TYPE.PACKET then diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index b311369..676263c 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -12,7 +12,6 @@ local PROTOCOL = comms.PROTOCOL local RPLC_TYPE = comms.RPLC_TYPE local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE local PLC_AUTO_ACK = comms.PLC_AUTO_ACK - local UNIT_COMMAND = comms.UNIT_COMMAND local print = util.print @@ -47,18 +46,16 @@ local PERIODICS = { } -- PLC supervisor session +---@nodiscard ---@param id integer session ID ----@param for_reactor integer reactor ID +---@param reactor_id integer reactor ID ---@param in_queue mqueue in message queue ---@param out_queue mqueue out message queue ---@param timeout number communications timeout -function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) +function plc.new_session(id, reactor_id, in_queue, out_queue, timeout) local log_header = "plc_session(" .. id .. "): " local self = { - for_reactor = for_reactor, - in_q = in_queue, - out_q = out_queue, commanded_state = false, commanded_burn_rate = 0.0, auto_cmd_token = 0, @@ -250,10 +247,10 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) local s_pkt = comms.scada_packet() local r_pkt = comms.rplc_packet() - r_pkt.make(for_reactor, msg_type, msg) + r_pkt.make(reactor_id, msg_type, msg) s_pkt.make(self.seq_num, PROTOCOL.RPLC, r_pkt.raw_sendable()) - self.out_q.push_packet(s_pkt) + out_queue.push_packet(s_pkt) self.seq_num = self.seq_num + 1 end @@ -267,11 +264,12 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) m_pkt.make(msg_type, msg) s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) - self.out_q.push_packet(s_pkt) + out_queue.push_packet(s_pkt) self.seq_num = self.seq_num + 1 end -- get an ACK status + ---@nodiscard ---@param pkt rplc_frame ---@return boolean|nil ack local function _get_ack(pkt) @@ -299,8 +297,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) -- process packet if pkt.scada_frame.protocol() == PROTOCOL.RPLC then -- check reactor ID - if pkt.id ~= for_reactor then - log.warning(log_header .. "RPLC packet with ID not matching reactor ID: reactor " .. self.for_reactor .. " != " .. pkt.id) + if pkt.id ~= reactor_id then + log.warning(log_header .. "RPLC packet with ID not matching reactor ID: reactor " .. reactor_id .. " != " .. pkt.id) return end @@ -342,7 +340,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) if status then -- copied in structure data OK self.received_struct = true - self.out_q.push_data(svqtypes.SV_Q_DATA.PLC_BUILD_CHANGED, for_reactor) + out_queue.push_data(svqtypes.SV_Q_DATA.PLC_BUILD_CHANGED, reactor_id) else -- error copying structure data log.error(log_header .. "failed to parse struct packet data") @@ -360,8 +358,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) end -- send acknowledgement to coordinator - self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { - unit = self.for_reactor, + out_queue.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { + unit = reactor_id, cmd = UNIT_COMMAND.SET_BURN, ack = ack }) @@ -375,8 +373,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) end -- send acknowledgement to coordinator - self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { - unit = self.for_reactor, + out_queue.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { + unit = reactor_id, cmd = UNIT_COMMAND.START, ack = ack }) @@ -391,8 +389,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) end -- send acknowledgement to coordinator - self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { - unit = self.for_reactor, + out_queue.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { + unit = reactor_id, cmd = UNIT_COMMAND.SCRAM, ack = ack }) @@ -443,8 +441,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) end -- send acknowledgement to coordinator - self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { - unit = self.for_reactor, + out_queue.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { + unit = reactor_id, cmd = UNIT_COMMAND.RESET_RPS, ack = ack }) @@ -503,17 +501,22 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) -- PUBLIC FUNCTIONS -- -- get the session ID + ---@nodiscard function public.get_id() return id end -- get the session database + ---@nodiscard function public.get_db() return self.sDB end -- check if ramping is completed by first verifying auto command token ack + ---@nodiscard function public.is_ramp_complete() return (self.sDB.auto_ack_token == self.auto_cmd_token) and (self.commanded_burn_rate == self.sDB.mek_status.act_burn_rate) end -- get the reactor structure + ---@nodiscard + ---@return mek_struct|table struct struct or empty table function public.get_struct() if self.received_struct then return self.sDB.mek_struct @@ -523,6 +526,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) end -- get the reactor status + ---@nodiscard + ---@return mek_status|table struct status or empty table function public.get_status() if self.received_status_cache then return self.sDB.mek_status @@ -532,11 +537,13 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) end -- get the reactor RPS status + ---@nodiscard function public.get_rps() return self.sDB.rps_status end -- get the general status information + ---@nodiscard function public.get_general_status() return { self.sDB.last_status_update, @@ -564,10 +571,11 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) ---@param ramp boolean true to ramp, false to not function public.auto_set_burn(rate, ramp) self.ramping_rate = ramp - self.in_q.push_data(PLC_S_DATA.AUTO_BURN_RATE, rate) + in_queue.push_data(PLC_S_DATA.AUTO_BURN_RATE, rate) end -- check if a timer matches this session's watchdog + ---@nodiscard function public.check_wd(timer) return self.plc_conn_watchdog.is_timer(timer) and self.connected end @@ -576,11 +584,12 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) function public.close() _close() _send_mgmt(SCADA_MGMT_TYPE.CLOSE, {}) - println("connection to reactor " .. self.for_reactor .. " PLC closed by server") + println("connection to reactor " .. reactor_id .. " PLC closed by server") log.info(log_header .. "session closed by server") end -- iterate the session + ---@nodiscard ---@return boolean connected function public.iterate() if self.connected then @@ -590,9 +599,9 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) local handle_start = util.time() - while self.in_q.ready() and self.connected do + while in_queue.ready() and self.connected do -- get a new message to process - local message = self.in_q.pop() + local message = in_queue.pop() if message ~= nil then if message.qtype == mqueue.TYPE.PACKET then @@ -688,7 +697,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout) -- exit if connection was closed if not self.connected then - println("connection to reactor " .. self.for_reactor .. " PLC closed by remote host") + println("connection to reactor " .. reactor_id .. " PLC closed by remote host") log.info(log_header .. "session closed by remote host") return self.connected end diff --git a/supervisor/session/rsctl.lua b/supervisor/session/rsctl.lua index 1544cc3..fb17efe 100644 --- a/supervisor/session/rsctl.lua +++ b/supervisor/session/rsctl.lua @@ -5,6 +5,7 @@ local rsctl = {} -- create a new redstone RTU I/O controller +---@nodiscard ---@param redstone_rtus table redstone RTU sessions function rsctl.new(redstone_rtus) ---@class rs_controller diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index a0fe916..9d178fe 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -32,6 +32,7 @@ local PERIODICS = { } -- create a new RTU session +---@nodiscard ---@param id integer session ID ---@param in_queue mqueue in message queue ---@param out_queue mqueue out message queue @@ -42,8 +43,6 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili local log_header = "rtu_session(" .. id .. "): " local self = { - in_q = in_queue, - out_q = out_queue, modbus_q = mqueue.new(), advert = advertisement, fac_units = facility.get_units(), @@ -196,7 +195,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili s_pkt.make(self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable()) - self.out_q.push_packet(s_pkt) + out_queue.push_packet(s_pkt) self.seq_num = self.seq_num + 1 end @@ -210,7 +209,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili m_pkt.make(msg_type, msg) s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) - self.out_q.push_packet(s_pkt) + out_queue.push_packet(s_pkt) self.seq_num = self.seq_num + 1 end @@ -262,10 +261,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili elseif pkt.type == SCADA_MGMT_TYPE.RTU_ADVERT then -- RTU unit advertisement log.debug(log_header .. "received updated advertisement") - - -- copy advertisement and remove version tag self.advert = pkt.data - table.remove(self.advert, 1) -- handle advertisement; this will re-create all unit sub-sessions _handle_advertisement() @@ -291,6 +287,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili function public.get_id() return id end -- check if a timer matches this session's watchdog + ---@nodiscard ---@param timer number function public.check_wd(timer) return self.rtu_conn_watchdog.is_timer(timer) and self.connected @@ -305,6 +302,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili end -- iterate the session + ---@nodiscard ---@return boolean connected function public.iterate() if self.connected then @@ -314,9 +312,9 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili local handle_start = util.time() - while self.in_q.ready() and self.connected do + while in_queue.ready() and self.connected do -- get a new message to process - local msg = self.in_q.pop() + local msg = in_queue.pop() if msg ~= nil then if msg.qtype == mqueue.TYPE.PACKET then @@ -389,7 +387,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili -- instruction with body local cmd = msg.message ---@type queue_data if cmd.key == unit_session.RTU_US_DATA.BUILD_CHANGED then - self.out_q.push_data(svqtypes.SV_Q_DATA.RTU_BUILD_CHANGED, cmd.val) + out_queue.push_data(svqtypes.SV_Q_DATA.RTU_BUILD_CHANGED, cmd.val) end end end diff --git a/supervisor/session/rtu/boilerv.lua b/supervisor/session/rtu/boilerv.lua index 58043dd..2f3c231 100644 --- a/supervisor/session/rtu/boilerv.lua +++ b/supervisor/session/rtu/boilerv.lua @@ -31,6 +31,7 @@ local PERIODICS = { } -- create a new boilerv rtu session runner +---@nodiscard ---@param session_id integer RTU session ID ---@param unit_id integer RTU unit ID ---@param advert rtu_advertisement RTU advertisement table @@ -238,6 +239,7 @@ function boilerv.new(session_id, unit_id, advert, out_queue) end -- get the unit session database + ---@nodiscard function public.get_db() return self.db end return public diff --git a/supervisor/session/rtu/envd.lua b/supervisor/session/rtu/envd.lua index 626aecb..3b4b666 100644 --- a/supervisor/session/rtu/envd.lua +++ b/supervisor/session/rtu/envd.lua @@ -22,6 +22,7 @@ local PERIODICS = { } -- create a new environment detector rtu session runner +---@nodiscard ---@param session_id integer ---@param unit_id integer ---@param advert rtu_advertisement @@ -99,6 +100,7 @@ function envd.new(session_id, unit_id, advert, out_queue) end -- get the unit session database + ---@nodiscard function public.get_db() return self.db end return public diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua index fcb616d..0b120b4 100644 --- a/supervisor/session/rtu/imatrix.lua +++ b/supervisor/session/rtu/imatrix.lua @@ -31,6 +31,7 @@ local PERIODICS = { } -- create a new imatrix rtu session runner +---@nodiscard ---@param session_id integer RTU session ID ---@param unit_id integer RTU unit ID ---@param advert rtu_advertisement RTU advertisement table @@ -212,6 +213,7 @@ function imatrix.new(session_id, unit_id, advert, out_queue) end -- get the unit session database + ---@nodiscard function public.get_db() return self.db end return public diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index 50764c4..7c813a2 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -45,6 +45,7 @@ local PERIODICS = { ---@field req IO_LVL -- create a new redstone rtu session runner +---@nodiscard ---@param session_id integer ---@param unit_id integer ---@param advert rtu_advertisement @@ -118,6 +119,7 @@ function redstone.new(session_id, unit_id, advert, out_queue) ---@class rs_db_dig_io local io_f = { + ---@nodiscard read = function () return rsio.digital_is_active(port, self.phy_io.digital_in[port].phy) end, ---@param active boolean write = function (active) end @@ -132,6 +134,7 @@ function redstone.new(session_id, unit_id, advert, out_queue) ---@class rs_db_dig_io local io_f = { + ---@nodiscard read = function () return rsio.digital_is_active(port, self.phy_io.digital_out[port].phy) end, ---@param active boolean write = function (active) @@ -149,6 +152,7 @@ function redstone.new(session_id, unit_id, advert, out_queue) ---@class rs_db_ana_io local io_f = { + ---@nodiscard ---@return integer read = function () return self.phy_io.analog_in[port].phy end, ---@param value integer @@ -164,6 +168,7 @@ function redstone.new(session_id, unit_id, advert, out_queue) ---@class rs_db_ana_io local io_f = { + ---@nodiscard ---@return integer read = function () return self.phy_io.analog_out[port].phy end, ---@param value integer @@ -378,6 +383,7 @@ function redstone.new(session_id, unit_id, advert, out_queue) end -- get the unit session database + ---@nodiscard function public.get_db() return self.db end return public diff --git a/supervisor/session/rtu/sna.lua b/supervisor/session/rtu/sna.lua index 085db14..006222b 100644 --- a/supervisor/session/rtu/sna.lua +++ b/supervisor/session/rtu/sna.lua @@ -28,6 +28,7 @@ local PERIODICS = { } -- create a new sna rtu session runner +---@nodiscard ---@param session_id integer RTU session ID ---@param unit_id integer RTU unit ID ---@param advert rtu_advertisement RTU advertisement table @@ -175,6 +176,7 @@ function sna.new(session_id, unit_id, advert, out_queue) end -- get the unit session database + ---@nodiscard function public.get_db() return self.db end return public diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua index 7d74acf..da036cd 100644 --- a/supervisor/session/rtu/sps.lua +++ b/supervisor/session/rtu/sps.lua @@ -31,6 +31,7 @@ local PERIODICS = { } -- create a new sps rtu session runner +---@nodiscard ---@param session_id integer RTU session ID ---@param unit_id integer RTU unit ID ---@param advert rtu_advertisement RTU advertisement table @@ -112,7 +113,7 @@ function sps.new(session_id, unit_id, advert, out_queue) -- query the tanks of the device local function _request_tanks() -- read input registers 11 through 19 (start = 11, count = 9) - self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 10, 12 }) + self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 11, 9 }) end -- PUBLIC FUNCTIONS -- @@ -222,6 +223,7 @@ function sps.new(session_id, unit_id, advert, out_queue) end -- get the unit session database + ---@nodiscard function public.get_db() return self.db end return public diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua index b8e7228..4cf32c4 100644 --- a/supervisor/session/rtu/turbinev.lua +++ b/supervisor/session/rtu/turbinev.lua @@ -43,6 +43,7 @@ local PERIODICS = { } -- create a new turbinev rtu session runner +---@nodiscard ---@param session_id integer RTU session ID ---@param unit_id integer RTU unit ID ---@param advert rtu_advertisement RTU advertisement table @@ -309,6 +310,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue) end -- get the unit session database + ---@nodiscard function public.get_db() return self.db end return public diff --git a/supervisor/session/rtu/txnctrl.lua b/supervisor/session/rtu/txnctrl.lua index a1f3b4b..9766bab 100644 --- a/supervisor/session/rtu/txnctrl.lua +++ b/supervisor/session/rtu/txnctrl.lua @@ -6,9 +6,10 @@ local util = require("scada-common.util") local txnctrl = {} -local TIMEOUT = 2000 -- 2000ms max wait +local TIMEOUT = 2000 -- 2000ms max wait -- create a new transaction controller +---@nodiscard function txnctrl.new() local self = { list = {}, @@ -22,16 +23,19 @@ function txnctrl.new() local remove = table.remove -- get the length of the transaction list + ---@nodiscard function public.length() return #self.list end -- check if there are no active transactions + ---@nodiscard function public.empty() return #self.list == 0 end -- create a new transaction of the given type + ---@nodiscard ---@param txn_type integer ---@return integer txn_id function public.create(txn_type) @@ -49,6 +53,7 @@ function txnctrl.new() end -- mark a transaction as resolved to get its transaction type + ---@nodiscard ---@param txn_id integer ---@return integer txn_type function public.resolve(txn_id) diff --git a/supervisor/session/rtu/unit_session.lua b/supervisor/session/rtu/unit_session.lua index 069de28..700f9b1 100644 --- a/supervisor/session/rtu/unit_session.lua +++ b/supervisor/session/rtu/unit_session.lua @@ -23,6 +23,7 @@ unit_session.RTU_US_CMDS = RTU_US_CMDS unit_session.RTU_US_DATA = RTU_US_DATA -- create a new unit session runner +---@nodiscard ---@param session_id integer RTU session ID ---@param unit_id integer MODBUS unit ID ---@param advert rtu_advertisement RTU advertisement for this unit @@ -31,12 +32,8 @@ unit_session.RTU_US_DATA = RTU_US_DATA ---@param txn_tags table transaction log tags function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_tags) local self = { - log_tag = log_tag, - txn_tags = txn_tags, - unit_id = unit_id, device_index = advert.index, reactor = advert.reactor, - out_q = out_queue, transaction_controller = txnctrl.new(), connected = true, device_fail = false @@ -61,21 +58,22 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t local m_pkt = comms.modbus_packet() local txn_id = self.transaction_controller.create(txn_type) - m_pkt.make(txn_id, self.unit_id, f_code, register_param) + m_pkt.make(txn_id, unit_id, f_code, register_param) - self.out_q.push_packet(m_pkt) + out_queue.push_packet(m_pkt) return txn_id end -- try to resolve a MODBUS transaction + ---@nodiscard ---@param m_pkt modbus_frame MODBUS packet ---@return integer|false txn_type, integer txn_id transaction type or false on error/busy, transaction ID function protected.try_resolve(m_pkt) if m_pkt.scada_frame.protocol() == PROTOCOL.MODBUS_TCP then - if m_pkt.unit_id == self.unit_id then + if m_pkt.unit_id == unit_id then local txn_type = self.transaction_controller.resolve(m_pkt.txn_id) - local txn_tag = " (" .. util.strval(self.txn_tags[txn_type]) .. ")" + local txn_tag = " (" .. util.strval(txn_tags[txn_type]) .. ")" if bit.band(m_pkt.func_code, MODBUS_FCODE.ERROR_FLAG) ~= 0 then -- transaction incomplete or failed @@ -135,26 +133,35 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t end -- get the public interface + ---@nodiscard function protected.get() return public end -- PUBLIC FUNCTIONS -- -- get the unit ID + ---@nodiscard function public.get_session_id() return session_id end -- get the unit ID - function public.get_unit_id() return self.unit_id end + ---@nodiscard + function public.get_unit_id() return unit_id end -- get the device index + ---@nodiscard function public.get_device_idx() return self.device_index end -- get the reactor ID + ---@nodiscard function public.get_reactor() return self.reactor end -- get the command queue + ---@nodiscard function public.get_cmd_queue() return protected.in_q end -- close this unit + ---@nodiscard function public.close() self.connected = false end -- check if this unit is connected + ---@nodiscard function public.is_connected() return self.connected end -- check if this unit is faulted + ---@nodiscard function public.is_faulted() return self.device_fail end -- PUBLIC TEMPLATE FUNCTIONS -- @@ -179,6 +186,7 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t end -- get the unit session database + ---@nodiscard function public.get_db() log.debug("template unit_session.get_db() called", true) return {} diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index b7c8ef5..76fb6d1 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -183,9 +183,10 @@ local function _free_closed(sessions) end -- find a session by remote port +---@nodiscard ---@param list table ---@param port integer ----@return plc_session_struct|rtu_session_struct|nil +---@return plc_session_struct|rtu_session_struct|coord_session_struct|nil local function _find_session(list, port) for i = 1, #list do if list[i].r_port == port then return list[i] end @@ -212,54 +213,63 @@ function svsessions.relink_modem(modem) end -- find an RTU session by the remote port +---@nodiscard ---@param remote_port integer ---@return rtu_session_struct|nil function svsessions.find_rtu_session(remote_port) -- check RTU sessions ----@diagnostic disable-next-line: return-type-mismatch - return _find_session(self.rtu_sessions, remote_port) + local session = _find_session(self.rtu_sessions, remote_port) + ---@cast session rtu_session_struct + return session end -- find a PLC session by the remote port +---@nodiscard ---@param remote_port integer ---@return plc_session_struct|nil function svsessions.find_plc_session(remote_port) -- check PLC sessions ----@diagnostic disable-next-line: return-type-mismatch - return _find_session(self.plc_sessions, remote_port) + local session = _find_session(self.plc_sessions, remote_port) + ---@cast session plc_session_struct + return session end -- find a PLC/RTU session by the remote port +---@nodiscard ---@param remote_port integer ---@return plc_session_struct|rtu_session_struct|nil function svsessions.find_device_session(remote_port) -- check RTU sessions - local s = _find_session(self.rtu_sessions, remote_port) + local session = _find_session(self.rtu_sessions, remote_port) -- check PLC sessions - if s == nil then s = _find_session(self.plc_sessions, remote_port) end + if session == nil then session = _find_session(self.plc_sessions, remote_port) end + ---@cast session plc_session_struct|rtu_session_struct|nil - return s + return session end --- find a coordinator session by the remote port --- +-- find a coordinator session by the remote port
-- only one coordinator is allowed, but this is kept to be consistent with all other session tables +---@nodiscard ---@param remote_port integer ----@return nil +---@return coord_session_struct|nil function svsessions.find_coord_session(remote_port) -- check coordinator sessions ----@diagnostic disable-next-line: return-type-mismatch - return _find_session(self.coord_sessions, remote_port) + local session = _find_session(self.coord_sessions, remote_port) + ---@cast session coord_session_struct + return session end -- get the a coordinator session if exists +---@nodiscard ---@return coord_session_struct|nil function svsessions.get_coord_session() return self.coord_sessions[1] end -- get a session by reactor ID +---@nodiscard ---@param reactor integer ---@return plc_session_struct|nil session function svsessions.get_reactor_session(reactor) @@ -275,6 +285,7 @@ function svsessions.get_reactor_session(reactor) end -- establish a new PLC session +---@nodiscard ---@param local_port integer ---@param remote_port integer ---@param for_reactor integer @@ -314,6 +325,7 @@ function svsessions.establish_plc_session(local_port, remote_port, for_reactor, end -- establish a new RTU session +---@nodiscard ---@param local_port integer ---@param remote_port integer ---@param advertisement table @@ -344,6 +356,7 @@ function svsessions.establish_rtu_session(local_port, remote_port, advertisement end -- establish a new coordinator session +---@nodiscard ---@param local_port integer ---@param remote_port integer ---@param version string diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 5cba7f7..e537572 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.12.2" +local SUPERVISOR_VERSION = "v0.13.0" local print = util.print local println = util.println @@ -81,7 +81,7 @@ local function main() local modem = ppm.get_wireless_modem() if modem == nil then - println("boot> wireless modem not found") + println("startup> wireless modem not found") log.fatal("no wireless modem on startup") return end @@ -110,7 +110,7 @@ local function main() -- we only care if this is our wireless modem if device == modem then println_ts("wireless modem disconnected!") - log.error("comms modem disconnected!") + log.warning("comms modem disconnected") else log.warning("non-comms modem disconnected") end @@ -127,9 +127,9 @@ local function main() superv_comms.reconnect_modem(modem) println_ts("wireless modem reconnected.") - log.info("comms modem reconnected.") + log.info("comms modem reconnected") else - log.info("wired modem reconnected.") + log.info("wired modem reconnected") end end end diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index f28dd66..848dfc3 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -17,6 +17,7 @@ local print_ts = util.print_ts local println_ts = util.println_ts -- supervisory controller communications +---@nodiscard ---@param version string supervisor version ---@param num_reactors integer number of reactors ---@param cooling_conf table cooling configuration table @@ -26,32 +27,24 @@ local println_ts = util.println_ts ---@param range integer trusted device connection range function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen, coord_listen, range) local self = { - version = version, - num_reactors = num_reactors, - modem = modem, - dev_listen = dev_listen, - coord_listen = coord_listen, - reactor_struct_cache = nil + last_est_acks = {} } - ---@class superv_comms - local public = {} - comms.set_trusted_range(range) -- PRIVATE FUNCTIONS -- -- configure modem channels local function _conf_channels() - self.modem.closeAll() - self.modem.open(self.dev_listen) - self.modem.open(self.coord_listen) + modem.closeAll() + modem.open(dev_listen) + modem.open(coord_listen) end _conf_channels() -- link modem to svsessions - svsessions.init(self.modem, num_reactors, cooling_conf) + svsessions.init(modem, num_reactors, cooling_conf) -- send an establish request response to a PLC/RTU ---@param dest integer @@ -63,7 +56,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen m_pkt.make(SCADA_MGMT_TYPE.ESTABLISH, msg) s_pkt.make(seq_id, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) - self.modem.transmit(dest, self.dev_listen, s_pkt.raw_sendable()) + modem.transmit(dest, dev_listen, s_pkt.raw_sendable()) end -- send coordinator connection establish response @@ -77,21 +70,24 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen c_pkt.make(SCADA_MGMT_TYPE.ESTABLISH, msg) s_pkt.make(seq_id, PROTOCOL.SCADA_MGMT, c_pkt.raw_sendable()) - self.modem.transmit(dest, self.coord_listen, s_pkt.raw_sendable()) + modem.transmit(dest, coord_listen, s_pkt.raw_sendable()) end -- PUBLIC FUNCTIONS -- + ---@class superv_comms + local public = {} + -- reconnect a newly connected modem - ---@param modem table ----@diagnostic disable-next-line: redefined-local - function public.reconnect_modem(modem) - self.modem = modem - svsessions.relink_modem(self.modem) + ---@param new_modem table + function public.reconnect_modem(new_modem) + modem = new_modem + svsessions.relink_modem(new_modem) _conf_channels() end -- parse a packet + ---@nodiscard ---@param side string ---@param sender integer ---@param reply_to integer @@ -147,8 +143,9 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen local protocol = packet.scada_frame.protocol() -- device (RTU/PLC) listening channel - if l_port == self.dev_listen then + if l_port == dev_listen then if protocol == PROTOCOL.MODBUS_TCP then + ---@cast packet modbus_frame -- look for an associated session local session = svsessions.find_rtu_session(r_port) @@ -161,6 +158,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen log.debug("discarding MODBUS_TCP packet without a known session") end elseif protocol == PROTOCOL.RPLC then + ---@cast packet rplc_frame -- look for an associated session local session = svsessions.find_plc_session(r_port) @@ -174,6 +172,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen _send_dev_establish(packet.scada_frame.seq_num() + 1, r_port, { ESTABLISH_ACK.DENY }) end elseif protocol == PROTOCOL.SCADA_MGMT then + ---@cast packet mgmt_frame -- look for an associated session local session = svsessions.find_device_session(r_port) @@ -192,13 +191,13 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen local dev_type = packet.data[3] if comms_v ~= comms.version then - log.debug(util.c("dropping establish packet with incorrect comms version v", comms_v, - " (expected v", comms.version, ")")) - _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.BAD_VERSION }) - return - end + if self.last_est_acks[r_port] ~= ESTABLISH_ACK.BAD_VERSION then + log.info(util.c("dropping device establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + self.last_est_acks[r_port] = ESTABLISH_ACK.BAD_VERSION + end - if dev_type == DEVICE_TYPE.PLC then + _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.BAD_VERSION }) + elseif dev_type == DEVICE_TYPE.PLC then -- PLC linking request if packet.length == 4 and type(packet.data[4]) == "number" then local reactor_id = packet.data[4] @@ -206,13 +205,19 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen if plc_id == false then -- reactor already has a PLC assigned - log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) + if self.last_est_acks[r_port] ~= ESTABLISH_ACK.COLLISION then + log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) + self.last_est_acks[r_port] = ESTABLISH_ACK.COLLISION + end + _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.COLLISION }) else -- got an ID; assigned to a reactor successfully println(util.c("PLC (", firmware_v, ") [:", r_port, "] \xbb reactor ", reactor_id, " connected")) log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [:", r_port, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id)) + _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW }) + self.last_est_acks[r_port] = ESTABLISH_ACK.ALLOW end else log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type") @@ -226,6 +231,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen println(util.c("RTU (", firmware_v, ") [:", r_port, "] \xbb connected")) log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [:", r_port, "] connected with session ID ", s_id)) + _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW }) else log.debug("RTU_ESTABLISH: packet length mismatch") @@ -247,11 +253,12 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen log.debug("illegal packet type " .. protocol .. " on device listening channel") end -- coordinator listening channel - elseif l_port == self.coord_listen then + elseif l_port == coord_listen then -- look for an associated session local session = svsessions.find_coord_session(r_port) if protocol == PROTOCOL.SCADA_MGMT then + ---@cast packet mgmt_frame -- SCADA management packet if session ~= nil then -- pass the packet onto the session handler @@ -267,32 +274,39 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen local dev_type = packet.data[3] if comms_v ~= comms.version then - log.debug(util.c("dropping establish packet with incorrect comms version v", comms_v, - " (expected v", comms.version, ")")) + if self.last_est_acks[r_port] ~= ESTABLISH_ACK.BAD_VERSION then + log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + self.last_est_acks[r_port] = ESTABLISH_ACK.BAD_VERSION + end + _send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.BAD_VERSION }) - return elseif dev_type ~= DEVICE_TYPE.CRDN then log.debug(util.c("illegal establish packet for device ", dev_type, " on CRDN listening channel")) _send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) - return - end - - -- this is an attempt to establish a new session - local s_id = svsessions.establish_coord_session(l_port, r_port, firmware_v) - - if s_id ~= false then - local config = { self.num_reactors } - for i = 1, #cooling_conf do - table.insert(config, cooling_conf[i].BOILERS) - table.insert(config, cooling_conf[i].TURBINES) - end - - println(util.c("CRD (",firmware_v, ") [:", r_port, "] \xbb connected")) - log.info(util.c("CRDN_ESTABLISH: coordinator (",firmware_v, ") [:", r_port, "] connected with session ID ", s_id)) - _send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW, config }) else - log.debug("CRDN_ESTABLISH: denied new coordinator due to already being connected to another coordinator") - _send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.COLLISION }) + -- this is an attempt to establish a new session + local s_id = svsessions.establish_coord_session(l_port, r_port, firmware_v) + + if s_id ~= false then + local config = { num_reactors } + for i = 1, #cooling_conf do + table.insert(config, cooling_conf[i].BOILERS) + table.insert(config, cooling_conf[i].TURBINES) + end + + println(util.c("CRD (",firmware_v, ") [:", r_port, "] \xbb connected")) + log.info(util.c("CRDN_ESTABLISH: coordinator (",firmware_v, ") [:", r_port, "] connected with session ID ", s_id)) + + _send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW, config }) + self.last_est_acks[r_port] = ESTABLISH_ACK.ALLOW + else + if self.last_est_acks[r_port] ~= ESTABLISH_ACK.COLLISION then + log.info("CRDN_ESTABLISH: denied new coordinator due to already being connected to another coordinator") + self.last_est_acks[r_port] = ESTABLISH_ACK.COLLISION + end + + _send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.COLLISION }) + end end else log.debug("CRDN_ESTABLISH: establish packet length mismatch") @@ -303,6 +317,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen log.debug(r_port .. "->" .. l_port .. ": discarding SCADA_MGMT packet without a known session") end elseif protocol == PROTOCOL.SCADA_CRDN then + ---@cast packet crdn_frame -- coordinator packet if session ~= nil then -- pass the packet onto the session handler diff --git a/supervisor/unit.lua b/supervisor/unit.lua index b809cbb..b79036e 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -11,15 +11,14 @@ local rsctl = require("supervisor.session.rsctl") ---@class reactor_control_unit local unit = {} -local WASTE_MODE = types.WASTE_MODE - -local ALARM = types.ALARM -local PRIO = types.ALARM_PRIORITY -local ALARM_STATE = types.ALARM_STATE - -local TRI_FAIL = types.TRI_FAIL +local WASTE_MODE = types.WASTE_MODE local DUMPING_MODE = types.DUMPING_MODE +local ALARM = types.ALARM +local PRIO = types.ALARM_PRIORITY +local ALARM_STATE = types.ALARM_STATE +local TRI_FAIL = types.TRI_FAIL + local PLC_S_CMDS = plc.PLC_S_CMDS local IO = rsio.IO @@ -61,13 +60,14 @@ unit.FLOW_STABILITY_DELAY_MS = FLOW_STABILITY_DELAY_MS ---@field tier integer alarm urgency tier (0 = highest) -- create a new reactor unit ----@param for_reactor integer reactor unit number +---@nodiscard +---@param reactor_id integer reactor unit number ---@param num_boilers integer number of boilers expected ---@param num_turbines integer number of turbines expected -function unit.new(for_reactor, num_boilers, num_turbines) +function unit.new(reactor_id, num_boilers, num_turbines) ---@class _unit_self local self = { - r_id = for_reactor, + r_id = reactor_id, plc_s = nil, ---@class plc_session_struct plc_i = nil, ---@class plc_session num_boilers = num_boilers, @@ -278,6 +278,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) local function _reset_dt(key) self.deltas[key] = nil end -- get the delta t of a value + ---@nodiscard ---@param key string value key ---@return number value value or 0 if not known function self._get_dt(key) if self.deltas[key] then return self.deltas[key].dt else return 0.0 end end @@ -326,7 +327,6 @@ function unit.new(for_reactor, num_boilers, num_turbines) --#region redstone I/O local __rs_w = self.io_ctl.digital_write - local __rs_r = self.io_ctl.digital_read -- valves local waste_pu = { open = function () __rs_w(IO.WASTE_PU, true) end, close = function () __rs_w(IO.WASTE_PU, false) end } @@ -525,9 +525,9 @@ function unit.new(for_reactor, num_boilers, num_turbines) end end - -- get the actual limit of this unit - -- + -- get the actual limit of this unit
-- if it is degraded or not ready, the limit will be 0 + ---@nodiscard ---@return integer lim_br100 function public.a_get_effective_limit() if not self.db.control.ready or self.db.control.degraded or self.plc_cache.rps_trip then @@ -551,6 +551,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) end -- check if ramping is complete (burn rate is same as target) + ---@nodiscard ---@return boolean complete function public.a_ramp_complete() if self.plc_i ~= nil then @@ -610,7 +611,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- acknowledge an alarm (if possible) ---@param id ALARM alarm ID function public.ack_alarm(id) - if (type(id) == "number") and (self.db.alarm_states[id] == ALARM_STATE.TRIPPED) then + if type(id) == "number" and self.db.alarm_states[id] == ALARM_STATE.TRIPPED then self.db.alarm_states[id] = ALARM_STATE.ACKED end end @@ -618,7 +619,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- reset an alarm (if possible) ---@param id ALARM alarm ID function public.reset_alarm(id) - if (type(id) == "number") and (self.db.alarm_states[id] == ALARM_STATE.RING_BACK) then + if type(id) == "number" and self.db.alarm_states[id] == ALARM_STATE.RING_BACK then self.db.alarm_states[id] = ALARM_STATE.INACTIVE end end @@ -675,6 +676,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) --#region -- check if a critical alarm is tripped + ---@nodiscard + ---@return boolean tripped function public.has_critical_alarm() for _, alarm in pairs(self.alarms) do if alarm.tier == PRIO.CRITICAL and (alarm.state == AISTATE.TRIPPED or alarm.state == AISTATE.ACKED) then @@ -686,6 +689,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) end -- get build properties of all machines + ---@nodiscard ---@param inc_plc boolean? true/nil to include PLC build, false to exclude ---@param inc_boilers boolean? true/nil to include boiler builds, false to exclude ---@param inc_turbines boolean? true/nil to include turbine builds, false to exclude @@ -718,6 +722,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) end -- get reactor status + ---@nodiscard function public.get_reactor_status() local status = {} if self.plc_i ~= nil then @@ -728,6 +733,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) end -- get RTU statuses + ---@nodiscard function public.get_rtu_statuses() local status = {} @@ -769,20 +775,25 @@ function unit.new(for_reactor, num_boilers, num_turbines) end -- get the annunciator status + ---@nodiscard function public.get_annunciator() return self.db.annunciator end -- get the alarm states + ---@nodiscard function public.get_alarms() return self.db.alarm_states end -- get information required for automatic reactor control + ---@nodiscard function public.get_control_inf() return self.db.control end -- get unit state + ---@nodiscard function public.get_state() return { self.status_text[1], self.status_text[2], self.waste_mode, self.db.control.ready, self.db.control.degraded } end -- get the reactor ID + ---@nodiscard function public.get_id() return self.r_id end --#endregion diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 9249193..1e701a8 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -5,12 +5,12 @@ local util = require("scada-common.util") local plc = require("supervisor.session.plc") -local PRIO = types.ALARM_PRIORITY -local ALARM_STATE = types.ALARM_STATE - local TRI_FAIL = types.TRI_FAIL local DUMPING_MODE = types.DUMPING_MODE +local PRIO = types.ALARM_PRIORITY +local ALARM_STATE = types.ALARM_STATE + local IO = rsio.IO local PLC_S_CMDS = plc.PLC_S_CMDS @@ -555,6 +555,7 @@ function logic.update_status_text(self) local AISTATE = self.types.AISTATE -- check if an alarm is active (tripped or ack'd) + ---@nodiscard ---@param alarm table alarm entry ---@return boolean active local function is_active(alarm) @@ -670,7 +671,7 @@ function logic.update_status_text(self) end end else - self.status_text = { "Reactor Off-line", "awaiting connection..." } + self.status_text = { "REACTOR OFF-LINE", "awaiting connection..." } end end @@ -680,6 +681,7 @@ function logic.handle_redstone(self) local AISTATE = self.types.AISTATE -- check if an alarm is active (tripped or ack'd) + ---@nodiscard ---@param alarm table alarm entry ---@return boolean active local function is_active(alarm) From 16d6372d7bad5b053956f7e742d9ba766b10ed82 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 24 Feb 2023 23:59:39 -0500 Subject: [PATCH 551/587] #118 bugfixes with cleanup --- install_manifest.json | 20 ++++++++++---------- rtu/rtu.lua | 4 ++-- rtu/startup.lua | 2 +- scada-common/types.lua | 16 ++++++++-------- supervisor/facility.lua | 2 +- supervisor/startup.lua | 2 +- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/install_manifest.json b/install_manifest.json index f68183c..1f0e40f 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -2,10 +2,10 @@ "versions": { "bootloader": "0.2", "comms": "1.4.0", - "reactor-plc": "beta-v0.11.1", - "rtu": "beta-v0.11.2", - "supervisor": "beta-v0.12.2", - "coordinator": "beta-v0.10.1", + "reactor-plc": "v0.12.0", + "rtu": "v0.12.1", + "supervisor": "v0.13.1", + "coordinator": "v0.11.0", "pocket": "alpha-v0.0.0" }, "files": { @@ -177,13 +177,13 @@ }, "sizes": { "system": 1982, - "common": 88163, - "graphics": 99360, + "common": 88565, + "graphics": 99858, "lockbox": 100797, - "reactor-plc": 75902, - "rtu": 81679, - "supervisor": 268416, - "coordinator": 181783, + "reactor-plc": 75621, + "rtu": 85496, + "supervisor": 270182, + "coordinator": 183279, "pocket": 335 } } \ No newline at end of file diff --git a/rtu/rtu.lua b/rtu/rtu.lua index e2c9b44..1344a3e 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -219,10 +219,10 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog for i = 1, #units do local unit = units[i] ---@type rtu_unit_registry_entry - if type ~= nil then + if unit.type ~= nil then local advert = { unit.type, unit.index, unit.reactor } - if type == RTU_UNIT_TYPE.REDSTONE then + if unit.type == RTU_UNIT_TYPE.REDSTONE then insert(advert, unit.device) end diff --git a/rtu/startup.lua b/rtu/startup.lua index a97658c..aff3a22 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "v0.12.0" +local RTU_VERSION = "v0.12.1" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE diff --git a/scada-common/types.lua b/scada-common/types.lua index 8cc31f8..7ad0eb8 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -121,14 +121,14 @@ types.TRI_FAIL = { ---@enum PROCESS types.PROCESS = { - INACTIVE = 1, - MAX_BURN = 2, - BURN_RATE = 3, - CHARGE = 4, - GEN_RATE = 5, - MATRIX_FAULT_IDLE = 6, - SYSTEM_ALARM_IDLE = 7, - GEN_RATE_FAULT_IDLE = 8 + INACTIVE = 0, + MAX_BURN = 1, + BURN_RATE = 2, + CHARGE = 3, + GEN_RATE = 4, + MATRIX_FAULT_IDLE = 5, + SYSTEM_ALARM_IDLE = 6, + GEN_RATE_FAULT_IDLE = 7 } types.PROCESS_NAMES = { diff --git a/supervisor/facility.lua b/supervisor/facility.lua index db56aec..21da1a1 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -292,7 +292,7 @@ function facility.new(num_reactors, cooling_conf) if state_changed then self.saturated = false - log.debug("FAC: state changed from " .. PROCESS_NAMES[self.last_mode] .. " to " .. PROCESS_NAMES[self.mode]) + log.debug("FAC: state changed from " .. PROCESS_NAMES[self.last_mode + 1] .. " to " .. PROCESS_NAMES[self.mode + 1]) if (self.last_mode == PROCESS.INACTIVE) or (self.last_mode == PROCESS.GEN_RATE_FAULT_IDLE) then self.start_fail = START_STATUS.OK diff --git a/supervisor/startup.lua b/supervisor/startup.lua index e537572..0682994 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "v0.13.0" +local SUPERVISOR_VERSION = "v0.13.1" local print = util.print local println = util.println From 4f285cf2b597ff5db3128320a94edb0f91d82e2c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 25 Feb 2023 02:25:35 -0500 Subject: [PATCH 552/587] #118 safety/constants common file --- reactor-plc/plc.lua | 25 +++++--------- scada-common/constants.lua | 71 ++++++++++++++++++++++++++++++++++++++ supervisor/facility.lua | 17 ++++----- supervisor/unit.lua | 7 ---- supervisor/unitlogic.lua | 45 ++++++++++++------------ 5 files changed, 108 insertions(+), 57 deletions(-) create mode 100644 scada-common/constants.lua diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 68750e0..06ff298 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -1,4 +1,5 @@ local comms = require("scada-common.comms") +local const = require("scada-common.constants") local log = require("scada-common.log") local ppm = require("scada-common.ppm") local types = require("scada-common.types") @@ -15,6 +16,8 @@ local RPLC_TYPE = comms.RPLC_TYPE local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE local AUTO_ACK = comms.PLC_AUTO_ACK +local RPS_LIMITS = const.RPS_LIMITS + local print = util.print local println = util.println local print_ts = util.print_ts @@ -25,16 +28,6 @@ local println_ts = util.println_ts local PCALL_SCRAM_MSG = "pcall: Scram requires the reactor to be active." local PCALL_START_MSG = "pcall: Reactor is already active." ---#region RPS SAFETY CONSTANTS - -local MAX_DAMAGE_PERCENT = 90 -local MAX_DAMAGE_TEMPERATURE = 1200 -local MIN_COOLANT_FILL = 0.10 -local MAX_WASTE_FILL = 0.8 -local MAX_HEATED_COLLANT_FILL = 0.95 - ---#endregion END RPS SAFETY CONSTANTS - -- RPS: Reactor Protection System
-- identifies dangerous states and SCRAMs reactor if warranted
-- autonomous from main SCADA supervisor/coordinator control @@ -118,7 +111,7 @@ function plc.rps_init(reactor, is_formed) -- lost the peripheral or terminated, handled later _set_fault() elseif not self.state[state_keys.dmg_crit] then - self.state[state_keys.dmg_crit] = damage_percent >= MAX_DAMAGE_PERCENT + self.state[state_keys.dmg_crit] = damage_percent >= RPS_LIMITS.MAX_DAMAGE_PERCENT end end @@ -130,7 +123,7 @@ function plc.rps_init(reactor, is_formed) -- lost the peripheral or terminated, handled later _set_fault() elseif not self.state[state_keys.high_temp] then - self.state[state_keys.high_temp] = temp >= MAX_DAMAGE_TEMPERATURE + self.state[state_keys.high_temp] = temp >= RPS_LIMITS.MAX_DAMAGE_TEMPERATURE end end @@ -141,7 +134,7 @@ function plc.rps_init(reactor, is_formed) -- lost the peripheral or terminated, handled later _set_fault() elseif not self.state[state_keys.no_coolant] then - self.state[state_keys.no_coolant] = coolant_filled < MIN_COOLANT_FILL + self.state[state_keys.no_coolant] = coolant_filled < RPS_LIMITS.MIN_COOLANT_FILL end end @@ -152,7 +145,7 @@ function plc.rps_init(reactor, is_formed) -- lost the peripheral or terminated, handled later _set_fault() elseif not self.state[state_keys.ex_waste] then - self.state[state_keys.ex_waste] = w_filled > MAX_WASTE_FILL + self.state[state_keys.ex_waste] = w_filled > RPS_LIMITS.MAX_WASTE_FILL end end @@ -163,7 +156,7 @@ function plc.rps_init(reactor, is_formed) -- lost the peripheral or terminated, handled later _set_fault() elseif not self.state[state_keys.ex_hcoolant] then - self.state[state_keys.ex_hcoolant] = hc_filled > MAX_HEATED_COLLANT_FILL + self.state[state_keys.ex_hcoolant] = hc_filled > RPS_LIMITS.MAX_HEATED_COLLANT_FILL end end @@ -174,7 +167,7 @@ function plc.rps_init(reactor, is_formed) -- lost the peripheral or terminated, handled later _set_fault() elseif not self.state[state_keys.no_fuel] then - self.state[state_keys.no_fuel] = fuel == 0 + self.state[state_keys.no_fuel] = fuel <= RPS_LIMITS.NO_FUEL_FILL end end diff --git a/scada-common/constants.lua b/scada-common/constants.lua new file mode 100644 index 0000000..c10722d --- /dev/null +++ b/scada-common/constants.lua @@ -0,0 +1,71 @@ +-- +-- System and Safety Constants +-- + +-- Notes on Radiation +-- - background radiation 0.0000001 Sv/h (99.99 nSv/h) +-- - "green tint" radiation 0.00001 Sv/h (10 uSv/h) +-- - damaging radiation 0.00006 Sv/h (60 uSv/h) + +local constants = {} + +--#region Reactor Protection System (on the PLC) Limits + +local rps = {} + +rps.MAX_DAMAGE_PERCENT = 90 -- damage >= 90% +rps.MAX_DAMAGE_TEMPERATURE = 1200 -- temp >= 1200K +rps.MIN_COOLANT_FILL = 0.10 -- fill < 10% +rps.MAX_WASTE_FILL = 0.8 -- fill > 80% +rps.MAX_HEATED_COLLANT_FILL = 0.95 -- fill > 95% +rps.NO_FUEL_FILL = 0.0 -- fill <= 0% + +constants.RPS_LIMITS = rps + +--#endregion + +--#region Annunciator Limits + +local annunc = {} + +annunc.RCSFlowLow = -2.0 -- flow < -2.0 mB/s +annunc.CoolantLevelLow = 0.4 -- fill < 40% +annunc.ReactorTempHigh = 1000 -- temp > 1000K +annunc.ReactorHighDeltaT = 50 -- rate > 50K/s +annunc.FuelLevelLow = 0.05 -- fill <= 5% +annunc.WasteLevelHigh = 0.85 -- fill >= 85% +annunc.SteamFeedMismatch = 10 -- ±10mB difference between total coolant flow and total steam input rate +annunc.RadiationWarning = 0.00001 -- 10 uSv/h + +constants.ANNUNCIATOR_LIMITS = annunc + +--#endregion + +--#region Supervisor Alarm Limits + +local alarms = {} + +-- unit alarms + +alarms.HIGH_TEMP = 1150 -- temp >= 1150K +alarms.HIGH_WASTE = 0.5 -- fill > 50% +alarms.HIGH_RADIATION = 0.00005 -- 50 uSv/h, not yet damaging but this isn't good + +-- facility alarms + +alarms.CHARGE_HIGH = 1.0 -- once at or above 100% charge +alarms.CHARGE_RE_ENABLE = 0.95 -- once below 95% charge +alarms.FAC_HIGH_RAD = 0.00001 -- 10 uSv/h + +constants.ALARM_LIMITS = alarms + +--#endregion + +--#region Supervisor Constants + +-- milliseconds until turbine flow is assumed to be stable enough to enable coolant checks +constants.FLOW_STABILITY_DELAY_MS = 15000 + +--#endregion + +return constants diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 21da1a1..106c4d7 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -1,3 +1,4 @@ +local const = require("scada-common.constants") local log = require("scada-common.log") local rsio = require("scada-common.rsio") local types = require("scada-common.types") @@ -16,15 +17,9 @@ local IO = rsio.IO -- 2856 FE per blade per 1 mB, 285.6 FE per blade per 0.1 mB (minimum) local POWER_PER_BLADE = util.joules_to_fe(7140) -local FLOW_STABILITY_DELAY_S = unit.FLOW_STABILITY_DELAY_MS / 1000 +local FLOW_STABILITY_DELAY_S = const.FLOW_STABILITY_DELAY_MS / 1000 --- background radiation 0.0000001 Sv/h (99.99 nSv/h) --- "green tint" radiation 0.00001 Sv/h (10 uSv/h) --- damaging radiation 0.00006 Sv/h (60 uSv/h) -local RADIATION_ALARM_LEVEL = 0.00001 - -local HIGH_CHARGE = 1.0 -local RE_ENABLE_CHARGE = 0.95 +local ALARM_LIMS = const.ALARM_LIMITS local AUTO_SCRAM = { NONE = 0, @@ -563,10 +558,10 @@ function facility.new(num_reactors, cooling_conf) -- check matrix fill too high local was_fill = astatus.matrix_fill - astatus.matrix_fill = (db.tanks.energy_fill >= HIGH_CHARGE) or (astatus.matrix_fill and db.tanks.energy_fill > RE_ENABLE_CHARGE) + astatus.matrix_fill = (db.tanks.energy_fill >= ALARM_LIMS.CHARGE_HIGH) or (astatus.matrix_fill and db.tanks.energy_fill > ALARM_LIMS.CHARGE_RE_ENABLE) if was_fill and not astatus.matrix_fill then - log.info("FAC: charge state of induction matrix entered acceptable range <= " .. (RE_ENABLE_CHARGE * 100) .. "%") + log.info("FAC: charge state of induction matrix entered acceptable range <= " .. (ALARM_LIMS.CHARGE_RE_ENABLE * 100) .. "%") end -- check for critical unit alarms @@ -585,7 +580,7 @@ function facility.new(num_reactors, cooling_conf) local envd = self.envd[1] ---@type unit_session local e_db = envd.get_db() ---@type envd_session_db - astatus.radiation = e_db.radiation_raw > RADIATION_ALARM_LEVEL + astatus.radiation = e_db.radiation_raw > ALARM_LIMS.FAC_HIGH_RAD else -- don't clear, if it is true then we lost it with high radiation, so just keep alarming -- operator can restart the system or hit the stop/reset button diff --git a/supervisor/unit.lua b/supervisor/unit.lua index b79036e..c3f9482 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -12,8 +12,6 @@ local rsctl = require("supervisor.session.rsctl") local unit = {} local WASTE_MODE = types.WASTE_MODE -local DUMPING_MODE = types.DUMPING_MODE - local ALARM = types.ALARM local PRIO = types.ALARM_PRIORITY local ALARM_STATE = types.ALARM_STATE @@ -23,8 +21,6 @@ local PLC_S_CMDS = plc.PLC_S_CMDS local IO = rsio.IO -local FLOW_STABILITY_DELAY_MS = 15000 - local DT_KEYS = { ReactorBurnR = "RBR", ReactorTemp = "RTP", @@ -50,8 +46,6 @@ local AISTATE = { RING_BACK_TRIPPING = 6 } -unit.FLOW_STABILITY_DELAY_MS = FLOW_STABILITY_DELAY_MS - ---@class alarm_def ---@field state ALARM_INT_STATE internal alarm state ---@field trip_time integer time (ms) when first tripped @@ -73,7 +67,6 @@ function unit.new(reactor_id, num_boilers, num_turbines) num_boilers = num_boilers, num_turbines = num_turbines, types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE }, - defs = { FLOW_STABILITY_DELAY_MS = FLOW_STABILITY_DELAY_MS }, -- rtus redstone = {}, boilers = {}, diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 1e701a8..3a55c82 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -1,3 +1,4 @@ +local const = require("scada-common.constants") local log = require("scada-common.log") local rsio = require("scada-common.rsio") local types = require("scada-common.types") @@ -5,11 +6,10 @@ local util = require("scada-common.util") local plc = require("supervisor.session.plc") -local TRI_FAIL = types.TRI_FAIL +local TRI_FAIL = types.TRI_FAIL local DUMPING_MODE = types.DUMPING_MODE - -local PRIO = types.ALARM_PRIORITY -local ALARM_STATE = types.ALARM_STATE +local PRIO = types.ALARM_PRIORITY +local ALARM_STATE = types.ALARM_STATE local IO = rsio.IO @@ -24,11 +24,10 @@ local AISTATE_NAMES = { "RING_BACK_TRIPPING" } --- background radiation 0.0000001 Sv/h (99.99 nSv/h) --- "green tint" radiation 0.00001 Sv/h (10 uSv/h) --- damaging radiation 0.00006 Sv/h (60 uSv/h) -local RADIATION_ALERT_LEVEL = 0.00001 -- 10 uSv/h -local RADIATION_ALARM_LEVEL = 0.00005 -- 50 uSv/h, not yet damaging but this isn't good +local FLOW_STABILITY_DELAY_MS = const.FLOW_STABILITY_DELAY_MS + +local ANNUNC_LIMS = const.ANNUNCIATOR_LIMITS +local ALARM_LIMS = const.ALARM_LIMITS ---@class unit_logic_extension local logic = {} @@ -111,12 +110,12 @@ function logic.update_annunciator(self) self.db.annunciator.ManualReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.MANUAL self.db.annunciator.AutoReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.AUTOMATIC self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.no_cool) - self.db.annunciator.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < -2.0 - self.db.annunciator.CoolantLevelLow = plc_db.mek_status.ccool_fill < 0.4 - self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > 1000 - self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > 100 - self.db.annunciator.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < -1.0 or plc_db.mek_status.fuel_fill <= 0.01 - self.db.annunciator.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 1.0 or plc_db.mek_status.waste_fill >= 0.85 + self.db.annunciator.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < ANNUNC_LIMS.RCSFlowLow + self.db.annunciator.CoolantLevelLow = plc_db.mek_status.ccool_fill < ANNUNC_LIMS.CoolantLevelLow + self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > ANNUNC_LIMS.ReactorTempHigh + self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > ANNUNC_LIMS.ReactorHighDeltaT + self.db.annunciator.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < -1.0 or plc_db.mek_status.fuel_fill <= ANNUNC_LIMS.FuelLevelLow + self.db.annunciator.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 1.0 or plc_db.mek_status.waste_fill >= ANNUNC_LIMS.WasteLevelHigh -- this warning applies when no coolant is buffered (which we can't easily determine without running) --[[ @@ -150,7 +149,7 @@ function logic.update_annunciator(self) for i = 1, #self.envd do local envd = self.envd[i] ---@type unit_session self.db.annunciator.RadiationMonitor = util.trinary(envd.is_faulted(), 2, 3) - self.db.annunciator.RadiationWarning = envd.get_db().radiation_raw > RADIATION_ALERT_LEVEL + self.db.annunciator.RadiationWarning = envd.get_db().radiation_raw > ANNUNC_LIMS.RadiationWarning break end @@ -299,7 +298,7 @@ function logic.update_annunciator(self) self.db.annunciator.BoilRateMismatch = math.abs(total_boil_rate - total_input_rate) > (0.04 * total_boil_rate) -- check for steam feed mismatch and max return rate - local sfmismatch = math.abs(total_flow_rate - total_input_rate) > 10 + local sfmismatch = math.abs(total_flow_rate - total_input_rate) > ANNUNC_LIMS.SteamFeedMismatch sfmismatch = sfmismatch or boiler_steam_dt_sum > 2.0 or boiler_water_dt_sum < -2.0 self.db.annunciator.SteamFeedMismatch = sfmismatch self.db.annunciator.MaxWaterReturnFeed = max_water_return_rate == total_flow_rate and total_flow_rate ~= 0 @@ -449,7 +448,7 @@ function logic.update_alarms(self) -- Containment Radiation local rad_alarm = false for i = 1, #self.envd do - rad_alarm = self.envd[i].get_db().radiation_raw > RADIATION_ALARM_LEVEL + rad_alarm = self.envd[i].get_db().radiation_raw > ALARM_LIMS.HIGH_RADIATION break end _update_alarm_state(self, rad_alarm, self.alarms.ContainmentRadiation) @@ -469,14 +468,14 @@ function logic.update_alarms(self) _update_alarm_state(self, (plc_cache.temp >= 1200) or rps_high_temp, self.alarms.ReactorOverTemp) -- High Temperature - _update_alarm_state(self, plc_cache.temp > 1150, self.alarms.ReactorHighTemp) + _update_alarm_state(self, plc_cache.temp >= ALARM_LIMS.HIGH_TEMP, self.alarms.ReactorHighTemp) -- Waste Leak - _update_alarm_state(self, plc_cache.waste >= 0.99, self.alarms.ReactorWasteLeak) + _update_alarm_state(self, plc_cache.waste >= 1.0, self.alarms.ReactorWasteLeak) -- High Waste local rps_high_waste = plc_cache.rps_status.ex_waste and not self.last_rps_trips.ex_waste - _update_alarm_state(self, (plc_cache.waste > 0.50) or rps_high_waste, self.alarms.ReactorHighWaste) + _update_alarm_state(self, (plc_cache.waste > ALARM_LIMS.HIGH_WASTE) or rps_high_waste, self.alarms.ReactorHighWaste) -- RPS Transient (excludes timeouts and manual trips) local rps_alarm = false @@ -501,7 +500,7 @@ function logic.update_alarms(self) -- annunciator indicators for these states may not indicate a real issue when: -- > flow is ramping up right after reactor start -- > flow is ramping down after reactor shutdown - if ((util.time_ms() - self.last_rate_change_ms) > self.defs.FLOW_STABILITY_DELAY_MS) and plc_cache.active then + if ((util.time_ms() - self.last_rate_change_ms) > FLOW_STABILITY_DELAY_MS) and plc_cache.active then rcs_trans = rcs_trans or annunc.BoilRateMismatch or annunc.CoolantFeedMismatch or annunc.SteamFeedMismatch end @@ -621,7 +620,7 @@ function logic.update_status_text(self) self.status_text[2] = "insufficient fuel input rate" elseif self.db.annunciator.WasteLineOcclusion then self.status_text[2] = "insufficient waste output rate" - elseif (util.time_ms() - self.last_rate_change_ms) <= self.defs.FLOW_STABILITY_DELAY_MS then + elseif (util.time_ms() - self.last_rate_change_ms) <= FLOW_STABILITY_DELAY_MS then self.status_text[2] = "awaiting flow stability" else self.status_text[2] = "system nominal" From 446fff04da3ce8189cb070939bd18cb61a3222e7 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 25 Feb 2023 12:07:25 -0500 Subject: [PATCH 553/587] #118 PLC RPS fuel check fixed --- reactor-plc/plc.lua | 2 +- reactor-plc/startup.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 06ff298..973ed66 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -162,7 +162,7 @@ function plc.rps_init(reactor, is_formed) -- check if there is no fuel local function _insufficient_fuel() - local fuel = reactor.getFuel() + local fuel = reactor.getFuelFilledPercentage() if fuel == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 8b66b1a..6eecad0 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,7 @@ local config = require("reactor-plc.config") local plc = require("reactor-plc.plc") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v0.12.0" +local R_PLC_VERSION = "v0.12.1" local print = util.print local println = util.println From 7508acb1a7b2a27bb54ca5f05bb46ece882e2e6c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 25 Feb 2023 12:20:03 -0500 Subject: [PATCH 554/587] #174 fixed sounder not resuming on supervisor reconnect with same alarm states --- coordinator/sounder.lua | 3 +++ coordinator/startup.lua | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/coordinator/sounder.lua b/coordinator/sounder.lua index 373b8f1..556fbbd 100644 --- a/coordinator/sounder.lua +++ b/coordinator/sounder.lua @@ -296,6 +296,9 @@ end function sounder.reconnect(speaker) alarm_ctl.speaker = speaker alarm_ctl.playing = false + alarm_ctl.next_block = 1 + alarm_ctl.num_active = 0 + for id = 1, #TONES do TONES[id].active = false end end -- check alarm state to enable/disable alarms diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 8af8567..ef035e7 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "v0.11.0" +local COORDINATOR_VERSION = "v0.11.1" local print = util.print local println = util.println @@ -314,9 +314,10 @@ local function main() log_comms(msg) println_ts(msg) - -- close connection and UI + -- close connection, UI, and stop sounder coord_comms.close() renderer.close_ui() + sounder.stop() if not no_modem then -- try to re-connect to the supervisor @@ -341,9 +342,10 @@ local function main() if not coord_comms.is_linked() then log_comms("supervisor closed connection") - -- close connection and UI + -- close connection, UI, and stop sounder coord_comms.close() renderer.close_ui() + sounder.stop() if not no_modem then -- try to re-connect to the supervisor From bd1625c42e638a7e459f580b38969890e4f5ebce Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 25 Feb 2023 12:51:37 -0500 Subject: [PATCH 555/587] #166 removed sounder test code from GUI --- coordinator/startup.lua | 2 +- coordinator/ui/layout/main_view.lua | 31 ----------------------------- 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index ef035e7..0beed29 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "v0.11.1" +local COORDINATOR_VERSION = "v0.11.2" local print = util.print local println = util.println diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index 94d25f8..d9a726f 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -92,37 +92,6 @@ local function init(monitor) process_ctl(main, 2, cnc_bottom_align_start) - -- testing - ---@fixme remove test code - - -- ColorMap{parent=main,x=98,y=(main.height()-1)} - - local audio = Div{parent=main,width=23,height=23,x=107,y=cnc_bottom_align_start} - - PushButton{parent=audio,x=16,y=1,text="TEST 1",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_1} - PushButton{parent=audio,x=16,text="TEST 2",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_2} - PushButton{parent=audio,x=16,text="TEST 3",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_3} - PushButton{parent=audio,x=16,text="TEST 4",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_4} - PushButton{parent=audio,x=16,text="TEST 5",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_5} - PushButton{parent=audio,x=16,text="TEST 6",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_6} - PushButton{parent=audio,x=16,text="TEST 7",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_7} - PushButton{parent=audio,x=16,text="TEST 8",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_8} - PushButton{parent=audio,x=16,text="STOP",min_width=8,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.stop} - PushButton{parent=audio,x=16,text="PSCALE",min_width=8,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_power_scale} - - SwitchButton{parent=audio,x=1,y=12,text="CONTAINMENT BREACH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_breach} - SwitchButton{parent=audio,x=1,text="CONTAINMENT RADIATION",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rad} - SwitchButton{parent=audio,x=1,text="REACTOR LOST",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_lost} - SwitchButton{parent=audio,x=1,text="CRITICAL DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_crit} - SwitchButton{parent=audio,x=1,text="REACTOR DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_dmg} - SwitchButton{parent=audio,x=1,text="REACTOR OVER TEMP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_overtemp} - SwitchButton{parent=audio,x=1,text="REACTOR HIGH TEMP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_hightemp} - SwitchButton{parent=audio,x=1,text="REACTOR WASTE LEAK",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_wasteleak} - SwitchButton{parent=audio,x=1,text="REACTOR WASTE HIGH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_highwaste} - SwitchButton{parent=audio,x=1,text="RPS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rps} - SwitchButton{parent=audio,x=1,text="RCS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rcs} - SwitchButton{parent=audio,x=1,text="TURBINE TRIP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_turbinet} - imatrix(main, 131, cnc_bottom_align_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1]) return main From 523ac91c3bce3920f045167003d385f0274d063a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 25 Feb 2023 13:25:23 -0500 Subject: [PATCH 556/587] fixed coordinator RCS annunciator dimensions --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 4 ++-- install_manifest.json | 15 ++++++++------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 0beed29..e378c73 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "v0.11.2" +local COORDINATOR_VERSION = "v0.11.3" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index f507682..a16d0d0 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -234,8 +234,8 @@ local function init(parent, id) TextBox{parent=main,text="REACTOR COOLANT SYSTEM",fg_bg=cpair(colors.black,colors.blue),alignment=TEXT_ALIGN.CENTER,width=33,height=1,x=46,y=22} local rcs = Rectangle{parent=main,border=border(1,colors.blue,true),thin=true,width=33,height=24,x=46,y=23} - local rcs_annunc = Div{parent=rcs,width=27,height=22,x=2,y=1} - local rcs_tags = Div{parent=rcs,width=2,height=13,x=29,y=9} + local rcs_annunc = Div{parent=rcs,width=27,height=23,x=2,y=1} + local rcs_tags = Div{parent=rcs,width=2,height=14,x=29,y=9} local c_flt = IndicatorLight{parent=rcs_annunc,label="RCS Hardware Fault",colors=cpair(colors.yellow,colors.gray)} local c_emg = TriIndicatorLight{parent=rcs_annunc,label="Emergency Coolant",c1=colors.gray,c2=colors.white,c3=colors.yellow} diff --git a/install_manifest.json b/install_manifest.json index 1f0e40f..ad1b53c 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -2,10 +2,10 @@ "versions": { "bootloader": "0.2", "comms": "1.4.0", - "reactor-plc": "v0.12.0", + "reactor-plc": "v0.12.1", "rtu": "v0.12.1", "supervisor": "v0.13.1", - "coordinator": "v0.11.0", + "coordinator": "v0.11.3", "pocket": "alpha-v0.0.0" }, "files": { @@ -20,6 +20,7 @@ "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", + "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", @@ -177,13 +178,13 @@ }, "sizes": { "system": 1982, - "common": 88565, + "common": 90500, "graphics": 99858, "lockbox": 100797, - "reactor-plc": 75621, - "rtu": 85496, - "supervisor": 270182, - "coordinator": 183279, + "reactor-plc": 75545, + "rtu": 83090, + "supervisor": 269955, + "coordinator": 179370, "pocket": 335 } } \ No newline at end of file From fbb992ff122648065b5f9db2b36679f8659a26e3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 25 Feb 2023 14:11:40 -0500 Subject: [PATCH 557/587] #173 dump excess steam on opening emergency coolant --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 2 +- install_manifest.json | 10 +++--- rtu/dev/turbinev_rtu.lua | 2 +- rtu/startup.lua | 2 +- supervisor/startup.lua | 2 +- supervisor/unit.lua | 2 +- supervisor/unitlogic.lua | 38 +++++++++++++++++++---- 8 files changed, 43 insertions(+), 17 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index e378c73..75e0bb5 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "v0.11.3" +local COORDINATOR_VERSION = "v0.11.4" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index a16d0d0..d1babd1 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -238,7 +238,7 @@ local function init(parent, id) local rcs_tags = Div{parent=rcs,width=2,height=14,x=29,y=9} local c_flt = IndicatorLight{parent=rcs_annunc,label="RCS Hardware Fault",colors=cpair(colors.yellow,colors.gray)} - local c_emg = TriIndicatorLight{parent=rcs_annunc,label="Emergency Coolant",c1=colors.gray,c2=colors.white,c3=colors.yellow} + local c_emg = TriIndicatorLight{parent=rcs_annunc,label="Emergency Coolant",c1=colors.gray,c2=colors.white,c3=colors.green} local c_cfm = IndicatorLight{parent=rcs_annunc,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} local c_brm = IndicatorLight{parent=rcs_annunc,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)} local c_sfm = IndicatorLight{parent=rcs_annunc,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} diff --git a/install_manifest.json b/install_manifest.json index ad1b53c..0db383e 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -3,9 +3,9 @@ "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.1", - "rtu": "v0.12.1", - "supervisor": "v0.13.1", - "coordinator": "v0.11.3", + "rtu": "v0.12.2", + "supervisor": "v0.13.2", + "coordinator": "v0.11.4", "pocket": "alpha-v0.0.0" }, "files": { @@ -183,8 +183,8 @@ "lockbox": 100797, "reactor-plc": 75545, "rtu": 83090, - "supervisor": 269955, - "coordinator": 179370, + "supervisor": 271106, + "coordinator": 179369, "pocket": 335 } } \ No newline at end of file diff --git a/rtu/dev/turbinev_rtu.lua b/rtu/dev/turbinev_rtu.lua index eba310c..89b3ae0 100644 --- a/rtu/dev/turbinev_rtu.lua +++ b/rtu/dev/turbinev_rtu.lua @@ -47,7 +47,7 @@ function turbinev_rtu.new(turbine) unit.connect_input_reg(turbine.getEnergyFilledPercentage) -- holding registers -- - unit.connect_holding_reg(turbine.setDumpingMode, turbine.getDumpingMode) + unit.connect_holding_reg(turbine.getDumpingMode, turbine.setDumpingMode) return unit.interface() end diff --git a/rtu/startup.lua b/rtu/startup.lua index aff3a22..ef7a2df 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "v0.12.1" +local RTU_VERSION = "v0.12.2" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 0682994..01f2fee 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "v0.13.1" +local SUPERVISOR_VERSION = "v0.13.2" local print = util.print local println = util.println diff --git a/supervisor/unit.lua b/supervisor/unit.lua index c3f9482..ffecfb3 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -523,7 +523,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) ---@nodiscard ---@return integer lim_br100 function public.a_get_effective_limit() - if not self.db.control.ready or self.db.control.degraded or self.plc_cache.rps_trip then + if (not self.db.control.ready) or self.db.control.degraded or self.plc_cache.rps_trip then self.db.control.br100 = 0 return 0 else diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 3a55c82..81d6a17 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -1,16 +1,20 @@ -local const = require("scada-common.constants") -local log = require("scada-common.log") -local rsio = require("scada-common.rsio") -local types = require("scada-common.types") -local util = require("scada-common.util") +local const = require("scada-common.constants") +local log = require("scada-common.log") +local rsio = require("scada-common.rsio") +local types = require("scada-common.types") +local util = require("scada-common.util") -local plc = require("supervisor.session.plc") +local plc = require("supervisor.session.plc") + +local qtypes = require("supervisor.session.rtu.qtypes") local TRI_FAIL = types.TRI_FAIL local DUMPING_MODE = types.DUMPING_MODE local PRIO = types.ALARM_PRIORITY local ALARM_STATE = types.ALARM_STATE +local TBV_RTU_S_DATA = qtypes.TBV_RTU_S_DATA + local IO = rsio.IO local PLC_S_CMDS = plc.PLC_S_CMDS @@ -754,16 +758,38 @@ function logic.handle_redstone(self) -- if auto control is engaged, alarm check will SCRAM on reactor over temp so that's covered self.valves.emer_cool.close() + -- set turbines to not dump steam + for i = 1, #self.turbines do + local session = self.turbines[i] ---@type unit_session + local turbine = session.get_db() ---@type turbinev_session_db + + if turbine.state.dumping_mode ~= DUMPING_MODE.IDLE then + session.get_cmd_queue().push_data(TBV_RTU_S_DATA.SET_DUMP_MODE, DUMPING_MODE.IDLE) + end + end + if self.db.annunciator.EmergencyCoolant > 1 and self.emcool_opened then log.info(util.c("UNIT ", self.r_id, " emergency coolant valve closed")) + log.info(util.c("UNIT ", self.r_id, " turbines set to not dump steam")) end self.emcool_opened = false elseif enable_emer_cool or self.emcool_opened then self.valves.emer_cool.open() + -- set turbines to dump excess steam + for i = 1, #self.turbines do + local session = self.turbines[i] ---@type unit_session + local turbine = session.get_db() ---@type turbinev_session_db + + if turbine.state.dumping_mode ~= DUMPING_MODE.DUMPING_EXCESS then + session.get_cmd_queue().push_data(TBV_RTU_S_DATA.SET_DUMP_MODE, DUMPING_MODE.DUMPING_EXCESS) + end + end + if self.db.annunciator.EmergencyCoolant > 1 and not self.emcool_opened then log.info(util.c("UNIT ", self.r_id, " emergency coolant valve opened")) + log.info(util.c("UNIT ", self.r_id, " turbines set to dump excess steam")) end self.emcool_opened = true From b150072234f12b54760235047cf58add38de8d3c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Feb 2023 14:17:35 -0500 Subject: [PATCH 558/587] #177 correctly set Water Level Low --- scada-common/constants.lua | 1 + supervisor/startup.lua | 2 +- supervisor/unit.lua | 2 +- supervisor/unitlogic.lua | 30 ++++++++++++++++-------------- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/scada-common/constants.lua b/scada-common/constants.lua index c10722d..902e187 100644 --- a/scada-common/constants.lua +++ b/scada-common/constants.lua @@ -34,6 +34,7 @@ annunc.ReactorTempHigh = 1000 -- temp > 1000K annunc.ReactorHighDeltaT = 50 -- rate > 50K/s annunc.FuelLevelLow = 0.05 -- fill <= 5% annunc.WasteLevelHigh = 0.85 -- fill >= 85% +annunc.WaterLevelLow = 0.4 -- fill < 40% annunc.SteamFeedMismatch = 10 -- ±10mB difference between total coolant flow and total steam input rate annunc.RadiationWarning = 0.00001 -- 10 uSv/h diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 01f2fee..6123014 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "v0.13.2" +local SUPERVISOR_VERSION = "v0.13.3" local print = util.print local println = util.println diff --git a/supervisor/unit.lua b/supervisor/unit.lua index ffecfb3..4d7f4e2 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -144,7 +144,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) ReactorDamage = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorDamage, tier = PRIO.EMERGENCY }, -- reactor >1200K ReactorOverTemp = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorOverTemp, tier = PRIO.URGENT }, - -- reactor >1150K + -- reactor >=1150K ReactorHighTemp = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 1, id = ALARM.ReactorHighTemp, tier = PRIO.TIMELY }, -- waste = 100% ReactorWasteLeak = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorWasteLeak, tier = PRIO.EMERGENCY }, diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 81d6a17..823c581 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -185,6 +185,7 @@ function logic.update_annunciator(self) for i = 1, #self.boilers do local session = self.boilers[i] ---@type unit_session local boiler = session.get_db() ---@type boilerv_session_db + local idx = session.get_device_idx() self.db.annunciator.RCSFault = self.db.annunciator.RCSFault or (not boiler.formed) or session.is_faulted() @@ -197,10 +198,11 @@ function logic.update_annunciator(self) (boiler.tanks.last_update > 0) total_boil_rate = total_boil_rate + boiler.state.boil_rate - boiler_steam_dt_sum = _get_dt(DT_KEYS.BoilerSteam .. self.boilers[i].get_device_idx()) - boiler_water_dt_sum = _get_dt(DT_KEYS.BoilerWater .. self.boilers[i].get_device_idx()) + boiler_steam_dt_sum = _get_dt(DT_KEYS.BoilerSteam .. idx) + boiler_water_dt_sum = _get_dt(DT_KEYS.BoilerWater .. idx) - self.db.annunciator.BoilerOnline[session.get_device_idx()] = true + self.db.annunciator.BoilerOnline[idx] = true + self.db.annunciator.WaterLevelLow[idx] = boiler.tanks.water_fill < ANNUNC_LIMS.WaterLevelLow end -- check heating rate low @@ -452,7 +454,7 @@ function logic.update_alarms(self) -- Containment Radiation local rad_alarm = false for i = 1, #self.envd do - rad_alarm = self.envd[i].get_db().radiation_raw > ALARM_LIMS.HIGH_RADIATION + rad_alarm = self.envd[i].get_db().radiation_raw >= ALARM_LIMS.HIGH_RADIATION break end _update_alarm_state(self, rad_alarm, self.alarms.ContainmentRadiation) @@ -569,7 +571,7 @@ function logic.update_status_text(self) if is_active(self.alarms.ContainmentBreach) then -- boom? or was boom disabled if self.plc_i ~= nil and self.plc_i.get_rps().force_dis then - self.status_text = { "REACTOR FORCE DISABLED", "meltdown would have occured" } + self.status_text = { "REACTOR FORCE DISABLED", "meltdown would have occurred" } else self.status_text = { "CORE MELTDOWN", "reactor destroyed" } end @@ -594,10 +596,6 @@ function logic.update_status_text(self) end elseif is_active(self.alarms.ContainmentRadiation) then self.status_text = { "RADIATION DETECTED", "radiation levels above normal" } - -- elseif is_active(self.alarms.RPSTransient) then - -- RPS status handled when checking reactor status - elseif is_active(self.alarms.RCSTransient) then - self.status_text = { "RCS TRANSIENT", "check coolant system" } elseif is_active(self.alarms.ReactorOverTemp) then self.status_text = { "CORE OVER TEMP", "reactor core temperature >=1200K" } elseif is_active(self.alarms.ReactorWasteLeak) then @@ -607,7 +605,11 @@ function logic.update_status_text(self) elseif is_active(self.alarms.ReactorHighWaste) then self.status_text = { "WASTE LEVEL HIGH", "waste accumulating in reactor" } elseif is_active(self.alarms.TurbineTrip) then - self.status_text = { "TURBINE TRIP", "turbine stall occured" } + self.status_text = { "TURBINE TRIP", "turbine stall occurred" } + elseif is_active(self.alarms.RCSTransient) then + self.status_text = { "RCS TRANSIENT", "check coolant system" } + -- elseif is_active(self.alarms.RPSTransient) then + -- RPS status handled when checking reactor status elseif self.emcool_opened then self.status_text = { "EMERGENCY COOLANT OPENED", "reset RPS to close valve" } -- connection dependent states @@ -635,7 +637,7 @@ function logic.update_status_text(self) if plc_db.rps_trip_cause == "ok" then -- hmm... elseif plc_db.rps_trip_cause == "dmg_crit" then - cause = "core damage critical" + cause = "core damage high" elseif plc_db.rps_trip_cause == "high_temp" then cause = "core temperature high" elseif plc_db.rps_trip_cause == "no_coolant" then @@ -694,18 +696,18 @@ function logic.handle_redstone(self) -- reactor controls if self.plc_s ~= nil then if (not self.plc_cache.rps_status.manual) and self.io_ctl.digital_read(IO.R_SCRAM) then - -- reactor SCRAM requested but not yet done; perform it + -- reactor SCRAM requested but not yet done; perform it self.plc_s.in_queue.push_command(PLC_S_CMDS.SCRAM) end if self.plc_cache.rps_trip and self.io_ctl.digital_read(IO.R_RESET) then - -- reactor RPS reset requested but not yet done; perform it + -- reactor RPS reset requested but not yet done; perform it self.plc_s.in_queue.push_command(PLC_S_CMDS.RPS_RESET) end if (not self.auto_engaged) and (not self.plc_cache.active) and (not self.plc_cache.rps_trip) and self.io_ctl.digital_read(IO.R_ACTIVE) then - -- reactor enable requested and allowable, but not yet done; perform it + -- reactor enable requested and allowable, but not yet done; perform it self.plc_s.in_queue.push_command(PLC_S_CMDS.ENABLE) end end From 2b8f71fc433480a4cbe94060c7a6ed4e6a5bb0ce Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Feb 2023 14:22:25 -0500 Subject: [PATCH 559/587] status message cleanup and some updated comments --- coordinator/sounder.lua | 2 +- coordinator/startup.lua | 2 +- coordinator/ui/components/processctl.lua | 2 +- coordinator/ui/components/unit_detail.lua | 2 +- install_manifest.json | 10 +++++----- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/coordinator/sounder.lua b/coordinator/sounder.lua index 556fbbd..6eafb8d 100644 --- a/coordinator/sounder.lua +++ b/coordinator/sounder.lua @@ -329,7 +329,7 @@ function sounder.eval(units) if alarms[ALARM.CriticalDamage] then new_states[T_660Hz_Int_125ms] = true else - -- EMERGENCY level alarms + -- EMERGENCY level alarms + URGENT over temp if alarms[ALARM.ReactorDamage] or alarms[ALARM.ReactorOverTemp] or alarms[ALARM.ReactorWasteLeak] then new_states[T_544Hz_440Hz_Alt] = true -- URGENT level turbine trip diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 75e0bb5..0502a8a 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "v0.11.4" +local COORDINATOR_VERSION = "v0.11.5" local print = util.print local println = util.println diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 26944e0..5b8d8ae 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -201,7 +201,7 @@ local function new_view(root, x, y) local u_stat = Rectangle{parent=proc,border=border(1,colors.gray,true),thin=true,width=31,height=4,x=1,y=16,fg_bg=bw_fg_bg} local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=31,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=bw_fg_bg} - local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data",width=31,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} + local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data...",width=31,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} facility.ps.subscribe("status_line_1", stat_line_1.set_value) facility.ps.subscribe("status_line_2", stat_line_2.set_value) diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index d1babd1..05e675d 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -144,7 +144,7 @@ local function init(parent, id) local u_stat = Rectangle{parent=main,border=border(1,colors.gray,true),thin=true,width=33,height=4,x=46,y=3,fg_bg=bw_fg_bg} local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=33,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=bw_fg_bg} - local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data",width=33,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} + local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data...",width=33,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} u_ps.subscribe("U_StatusLine1", stat_line_1.set_value) u_ps.subscribe("U_StatusLine2", stat_line_2.set_value) diff --git a/install_manifest.json b/install_manifest.json index 0db383e..10b3be2 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -4,8 +4,8 @@ "comms": "1.4.0", "reactor-plc": "v0.12.1", "rtu": "v0.12.2", - "supervisor": "v0.13.2", - "coordinator": "v0.11.4", + "supervisor": "v0.13.3", + "coordinator": "v0.11.5", "pocket": "alpha-v0.0.0" }, "files": { @@ -178,13 +178,13 @@ }, "sizes": { "system": 1982, - "common": 90500, + "common": 90550, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 83090, - "supervisor": 271106, - "coordinator": 179369, + "supervisor": 271184, + "coordinator": 179394, "pocket": 335 } } \ No newline at end of file From 523d478739077b114b1b8c3225a31a6e89228124 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Feb 2023 14:49:16 -0500 Subject: [PATCH 560/587] changed trip time warning to 750ms --- coordinator/coordinator.lua | 4 ++-- coordinator/startup.lua | 2 +- install_manifest.json | 8 ++++---- reactor-plc/plc.lua | 4 ++-- reactor-plc/startup.lua | 2 +- rtu/rtu.lua | 4 ++-- rtu/startup.lua | 2 +- supervisor/session/coordinator.lua | 4 ++-- supervisor/session/plc.lua | 4 ++-- supervisor/session/rtu.lua | 4 ++-- supervisor/startup.lua | 2 +- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index e4bb283..37b7503 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -610,8 +610,8 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range local timestamp = packet.data[1] local trip_time = util.time() - timestamp - if trip_time > 500 then - log.warning("coord KEEP_ALIVE trip time > 500ms (" .. trip_time .. "ms)") + if trip_time > 750 then + log.warning("coord KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)") end -- log.debug("coord RTT = " .. trip_time .. "ms") diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 0502a8a..a35a0f4 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "v0.11.5" +local COORDINATOR_VERSION = "v0.11.6" local print = util.print local println = util.println diff --git a/install_manifest.json b/install_manifest.json index 10b3be2..ea81b8d 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -2,10 +2,10 @@ "versions": { "bootloader": "0.2", "comms": "1.4.0", - "reactor-plc": "v0.12.1", - "rtu": "v0.12.2", - "supervisor": "v0.13.3", - "coordinator": "v0.11.5", + "reactor-plc": "v0.12.2", + "rtu": "v0.12.3", + "supervisor": "v0.13.4", + "coordinator": "v0.11.6", "pocket": "alpha-v0.0.0" }, "files": { diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 973ed66..1d9b52c 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -928,8 +928,8 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, local timestamp = packet.data[1] local trip_time = util.time() - timestamp - if trip_time > 500 then - log.warning("PLC KEEP_ALIVE trip time > 500ms (" .. trip_time .. "ms)") + if trip_time > 750 then + log.warning("PLC KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)") end -- log.debug("RPLC RTT = " .. trip_time .. "ms") diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 6eecad0..f8229e5 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,7 @@ local config = require("reactor-plc.config") local plc = require("reactor-plc.plc") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v0.12.1" +local R_PLC_VERSION = "v0.12.2" local print = util.print local println = util.println diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 1344a3e..a1b9fbd 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -429,8 +429,8 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog local timestamp = packet.data[1] local trip_time = util.time() - timestamp - if trip_time > 500 then - log.warning("RTU KEEP_ALIVE trip time > 500ms (" .. trip_time .. "ms)") + if trip_time > 750 then + log.warning("RTU KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)") end -- log.debug("RTU RTT = " .. trip_time .. "ms") diff --git a/rtu/startup.lua b/rtu/startup.lua index ef7a2df..34f0c65 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "v0.12.2" +local RTU_VERSION = "v0.12.3" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 1402993..44dbefe 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -202,8 +202,8 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility) local srv_now = util.time() self.last_rtt = srv_now - srv_start - if self.last_rtt > 500 then - log.warning(log_header .. "COORD KEEP_ALIVE round trip time > 500ms (" .. self.last_rtt .. "ms)") + if self.last_rtt > 750 then + log.warning(log_header .. "COORD KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)") end -- log.debug(log_header .. "COORD RTT = " .. self.last_rtt .. "ms") diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 676263c..ae4a087 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -480,8 +480,8 @@ function plc.new_session(id, reactor_id, in_queue, out_queue, timeout) local srv_now = util.time() self.last_rtt = srv_now - srv_start - if self.last_rtt > 500 then - log.warning(log_header .. "PLC KEEP_ALIVE round trip time > 500ms (" .. self.last_rtt .. "ms)") + if self.last_rtt > 750 then + log.warning(log_header .. "PLC KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)") end -- log.debug(log_header .. "PLC RTT = " .. self.last_rtt .. "ms") diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 9d178fe..d265cad 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -246,8 +246,8 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili local srv_now = util.time() self.last_rtt = srv_now - srv_start - if self.last_rtt > 500 then - log.warning(log_header .. "RTU KEEP_ALIVE round trip time > 500ms (" .. self.last_rtt .. "ms)") + if self.last_rtt > 750 then + log.warning(log_header .. "RTU KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)") end -- log.debug(log_header .. "RTU RTT = " .. self.last_rtt .. "ms") diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 6123014..1117ff7 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "v0.13.3" +local SUPERVISOR_VERSION = "v0.13.4" local print = util.print local println = util.println From ae3315e4a00cf0ff81441f1d822cf628b64b2cea Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 27 Feb 2023 23:51:26 -0500 Subject: [PATCH 561/587] #180 include manifest size in sizes --- ccmsi.lua | 6 +- imgen.py | 103 ++++++++++++----------- install_manifest.json | 191 +----------------------------------------- 3 files changed, 60 insertions(+), 240 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index 3c59ae2..7173795 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -20,7 +20,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local VERSION = "v0.9e" +local VERSION = "v0.9f" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/" @@ -350,7 +350,7 @@ elseif mode == "install" or mode == "update" then -- START INSTALL/UPDATE -- -------------------------- - local space_required = 0 + local space_required = manifest.sizes.manifest local space_available = fs.getFreeSpace("/") local single_file_mode = false @@ -372,7 +372,7 @@ elseif mode == "install" or mode == "update" then term.setTextColor(colors.yellow) println("WARNING: Insufficient space available for a full download!") term.setTextColor(colors.white) - println("Files will be downloaded one by one, so if you are replacing a current install this will not be a problem unless installation fails.") + println("Files can be downloaded one by one, so if you are replacing a current install this will not be a problem unless installation fails.") println("Do you wish to continue? (y/N)") local confirm = read() diff --git a/imgen.py b/imgen.py index 56193ca..e6c6ec0 100644 --- a/imgen.py +++ b/imgen.py @@ -42,54 +42,63 @@ def get_version(path, is_comms = False): return ver -# installation manifest -manifest = { - "versions" : { - "bootloader" : get_version("."), - "comms" : get_version("./scada-common/comms.lua", True), - "reactor-plc" : get_version("./reactor-plc"), - "rtu" : get_version("./rtu"), - "supervisor" : get_version("./supervisor"), - "coordinator" : get_version("./coordinator"), - "pocket" : get_version("./pocket") - }, - "files" : { - # common files - "system" : [ "initenv.lua", "startup.lua" ], - "common" : list_files("./scada-common"), - "graphics" : list_files("./graphics"), - "lockbox" : list_files("./lockbox"), - # platform files - "reactor-plc" : list_files("./reactor-plc"), - "rtu" : list_files("./rtu"), - "supervisor" : list_files("./supervisor"), - "coordinator" : list_files("./coordinator"), - "pocket" : list_files("./pocket"), - }, - "depends" : { - "reactor-plc" : [ "system", "common" ], - "rtu" : [ "system", "common" ], - "supervisor" : [ "system", "common" ], - "coordinator" : [ "system", "common", "graphics" ], - "pocket" : [ "system", "common", "graphics" ] - }, - "sizes" : { - # common files - "system" : os.path.getsize("initenv.lua") + os.path.getsize("startup.lua"), - "common" : dir_size("./scada-common"), - "graphics" : dir_size("./graphics"), - "lockbox" : dir_size("./lockbox"), - # platform files - "reactor-plc" : dir_size("./reactor-plc"), - "rtu" : dir_size("./rtu"), - "supervisor" : dir_size("./supervisor"), - "coordinator" : dir_size("./coordinator"), - "pocket" : dir_size("./pocket"), +# generate installation manifest object +def make_manifest(size): + manifest = { + "versions" : { + "bootloader" : get_version("."), + "comms" : get_version("./scada-common/comms.lua", True), + "reactor-plc" : get_version("./reactor-plc"), + "rtu" : get_version("./rtu"), + "supervisor" : get_version("./supervisor"), + "coordinator" : get_version("./coordinator"), + "pocket" : get_version("./pocket") + }, + "files" : { + # common files + "system" : [ "initenv.lua", "startup.lua" ], + "common" : list_files("./scada-common"), + "graphics" : list_files("./graphics"), + "lockbox" : list_files("./lockbox"), + # platform files + "reactor-plc" : list_files("./reactor-plc"), + "rtu" : list_files("./rtu"), + "supervisor" : list_files("./supervisor"), + "coordinator" : list_files("./coordinator"), + "pocket" : list_files("./pocket"), + }, + "depends" : { + "reactor-plc" : [ "system", "common" ], + "rtu" : [ "system", "common" ], + "supervisor" : [ "system", "common" ], + "coordinator" : [ "system", "common", "graphics" ], + "pocket" : [ "system", "common", "graphics" ] + }, + "sizes" : { + # manifest file estimate + "manifest" : size, + # common files + "system" : os.path.getsize("initenv.lua") + os.path.getsize("startup.lua"), + "common" : dir_size("./scada-common"), + "graphics" : dir_size("./graphics"), + "lockbox" : dir_size("./lockbox"), + # platform files + "reactor-plc" : dir_size("./reactor-plc"), + "rtu" : dir_size("./rtu"), + "supervisor" : dir_size("./supervisor"), + "coordinator" : dir_size("./coordinator"), + "pocket" : dir_size("./pocket"), + } } -} + return manifest + +# write initial manifest with placeholder size f = open("install_manifest.json", "w") - -json.dump(manifest, f) - +json.dump(make_manifest("-----"), f) +f.close() + +# calculate file size then regenerate with embedded size +f = open("install_manifest.json", "w") +json.dump(make_manifest(os.path.getsize("install_manifest.json")), f) f.close() diff --git a/install_manifest.json b/install_manifest.json index ea81b8d..48814c8 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1,190 +1 @@ -{ - "versions": { - "bootloader": "0.2", - "comms": "1.4.0", - "reactor-plc": "v0.12.2", - "rtu": "v0.12.3", - "supervisor": "v0.13.4", - "coordinator": "v0.11.6", - "pocket": "alpha-v0.0.0" - }, - "files": { - "system": [ - "initenv.lua", - "startup.lua" - ], - "common": [ - "scada-common/crypto.lua", - "scada-common/ppm.lua", - "scada-common/comms.lua", - "scada-common/psil.lua", - "scada-common/tcallbackdsp.lua", - "scada-common/rsio.lua", - "scada-common/constants.lua", - "scada-common/mqueue.lua", - "scada-common/crash.lua", - "scada-common/log.lua", - "scada-common/types.lua", - "scada-common/util.lua" - ], - "graphics": [ - "graphics/element.lua", - "graphics/flasher.lua", - "graphics/core.lua", - "graphics/elements/textbox.lua", - "graphics/elements/displaybox.lua", - "graphics/elements/pipenet.lua", - "graphics/elements/rectangle.lua", - "graphics/elements/div.lua", - "graphics/elements/tiling.lua", - "graphics/elements/colormap.lua", - "graphics/elements/indicators/alight.lua", - "graphics/elements/indicators/icon.lua", - "graphics/elements/indicators/power.lua", - "graphics/elements/indicators/rad.lua", - "graphics/elements/indicators/state.lua", - "graphics/elements/indicators/light.lua", - "graphics/elements/indicators/vbar.lua", - "graphics/elements/indicators/coremap.lua", - "graphics/elements/indicators/data.lua", - "graphics/elements/indicators/hbar.lua", - "graphics/elements/indicators/trilight.lua", - "graphics/elements/controls/switch_button.lua", - "graphics/elements/controls/spinbox_numeric.lua", - "graphics/elements/controls/hazard_button.lua", - "graphics/elements/controls/push_button.lua", - "graphics/elements/controls/radio_button.lua", - "graphics/elements/controls/multi_button.lua", - "graphics/elements/animations/waiting.lua" - ], - "lockbox": [ - "lockbox/init.lua", - "lockbox/LICENSE", - "lockbox/kdf/pbkdf2.lua", - "lockbox/util/bit.lua", - "lockbox/util/array.lua", - "lockbox/util/stream.lua", - "lockbox/util/queue.lua", - "lockbox/digest/sha2_224.lua", - "lockbox/digest/sha1.lua", - "lockbox/digest/sha2_256.lua", - "lockbox/cipher/aes128.lua", - "lockbox/cipher/aes256.lua", - "lockbox/cipher/aes192.lua", - "lockbox/cipher/mode/ofb.lua", - "lockbox/cipher/mode/cbc.lua", - "lockbox/cipher/mode/ctr.lua", - "lockbox/cipher/mode/cfb.lua", - "lockbox/mac/hmac.lua", - "lockbox/padding/ansix923.lua", - "lockbox/padding/pkcs7.lua", - "lockbox/padding/zero.lua", - "lockbox/padding/isoiec7816.lua" - ], - "reactor-plc": [ - "reactor-plc/threads.lua", - "reactor-plc/plc.lua", - "reactor-plc/config.lua", - "reactor-plc/startup.lua" - ], - "rtu": [ - "rtu/threads.lua", - "rtu/rtu.lua", - "rtu/modbus.lua", - "rtu/config.lua", - "rtu/startup.lua", - "rtu/dev/sps_rtu.lua", - "rtu/dev/envd_rtu.lua", - "rtu/dev/boilerv_rtu.lua", - "rtu/dev/redstone_rtu.lua", - "rtu/dev/sna_rtu.lua", - "rtu/dev/imatrix_rtu.lua", - "rtu/dev/turbinev_rtu.lua" - ], - "supervisor": [ - "supervisor/supervisor.lua", - "supervisor/unit.lua", - "supervisor/config.lua", - "supervisor/startup.lua", - "supervisor/unitlogic.lua", - "supervisor/facility.lua", - "supervisor/session/coordinator.lua", - "supervisor/session/svqtypes.lua", - "supervisor/session/svsessions.lua", - "supervisor/session/rtu.lua", - "supervisor/session/plc.lua", - "supervisor/session/rsctl.lua", - "supervisor/session/rtu/boilerv.lua", - "supervisor/session/rtu/txnctrl.lua", - "supervisor/session/rtu/unit_session.lua", - "supervisor/session/rtu/turbinev.lua", - "supervisor/session/rtu/envd.lua", - "supervisor/session/rtu/imatrix.lua", - "supervisor/session/rtu/sps.lua", - "supervisor/session/rtu/qtypes.lua", - "supervisor/session/rtu/sna.lua", - "supervisor/session/rtu/redstone.lua" - ], - "coordinator": [ - "coordinator/coordinator.lua", - "coordinator/renderer.lua", - "coordinator/iocontrol.lua", - "coordinator/sounder.lua", - "coordinator/config.lua", - "coordinator/startup.lua", - "coordinator/apisessions.lua", - "coordinator/process.lua", - "coordinator/ui/dialog.lua", - "coordinator/ui/style.lua", - "coordinator/ui/layout/main_view.lua", - "coordinator/ui/layout/unit_view.lua", - "coordinator/ui/components/reactor.lua", - "coordinator/ui/components/processctl.lua", - "coordinator/ui/components/unit_overview.lua", - "coordinator/ui/components/boiler.lua", - "coordinator/ui/components/unit_detail.lua", - "coordinator/ui/components/imatrix.lua", - "coordinator/ui/components/unit_waiting.lua", - "coordinator/ui/components/turbine.lua" - ], - "pocket": [ - "pocket/config.lua", - "pocket/startup.lua" - ] - }, - "depends": { - "reactor-plc": [ - "system", - "common" - ], - "rtu": [ - "system", - "common" - ], - "supervisor": [ - "system", - "common" - ], - "coordinator": [ - "system", - "common", - "graphics" - ], - "pocket": [ - "system", - "common", - "graphics" - ] - }, - "sizes": { - "system": 1982, - "common": 90550, - "graphics": 99858, - "lockbox": 100797, - "reactor-plc": 75545, - "rtu": 83090, - "supervisor": 271184, - "coordinator": 179394, - "pocket": 335 - } -} \ No newline at end of file +{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.2", "rtu": "v0.12.3", "supervisor": "v0.13.4", "coordinator": "v0.11.6", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 0, "system": 1982, "common": 90550, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 271184, "coordinator": 179394, "pocket": 335}} \ No newline at end of file From 0d7fde635d6128a4273679f85584406f0327507a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 27 Feb 2023 23:52:18 -0500 Subject: [PATCH 562/587] updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index db4ee4d..440c734 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ TBD, I am planning on AES symmetric encryption for security + HMAC to prevent re This is somewhat important here as otherwise anyone can just control your setup, which is undeseriable. Unlike normal Minecraft PVP chaos, it would be very difficult to identify who is messing with your system, as with an Ender Modem they can do it from effectively anywhere and the server operators would have to check every computer's filesystem to find suspicious code. -The only other possible security mitigation for commanding (no effect on monitoring) is to enforce a maximum authorized transmission range (which I will probably also do, or maybe fall back to), as modem message events contain the transmission distance. +The other security mitigation for commanding (no effect on monitoring) is to enforce a maximum authorized transmission range, which has been added as a configurable feature. ## Known Issues From 3f15ae6b6ff427d7816a3630762f3b9d19fbf169 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 27 Feb 2023 23:59:46 -0500 Subject: [PATCH 563/587] #179 remove recolor option from coordinator config --- coordinator/config.lua | 3 --- coordinator/renderer.lua | 34 +++++++++++----------------------- coordinator/startup.lua | 5 ++--- coordinator/ui/style.lua | 3 +++ 4 files changed, 16 insertions(+), 29 deletions(-) diff --git a/coordinator/config.lua b/coordinator/config.lua index a29bb46..052bba4 100644 --- a/coordinator/config.lua +++ b/coordinator/config.lua @@ -14,9 +14,6 @@ config.COMMS_TIMEOUT = 5 -- expected number of reactor units, used only to require that number of unit monitors config.NUM_UNITS = 4 --- override default display colors (prettier in my opinion) -config.RECOLOR = true - -- alarm sounder volume (0.0 to 3.0, 1.0 being standard max volume, this is the option given to to speaker.play()) -- note: alarm sine waves are at half saturation, so that multiple will be required to reach full scale config.SOUNDER_VOLUME = 1.0 diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 4003d71..edf0de8 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -27,29 +27,18 @@ local ui = { unit_layouts = {} } --- reset a display to the "default", but set text scale to 0.5 +-- init a display to the "default", but set text scale to 0.5 ---@param monitor table monitor ----@param recolor? boolean override default color palette -local function _reset_display(monitor, recolor) +local function _init_display(monitor) monitor.setTextScale(0.5) monitor.setTextColor(colors.white) monitor.setBackgroundColor(colors.black) monitor.clear() monitor.setCursorPos(1, 1) - if recolor then - -- set overridden colors - for i = 1, #style.colors do - monitor.setPaletteColor(style.colors[i].c, style.colors[i].hex) - end - else - -- reset all colors - for _, val in pairs(colors) do - -- colors api has constants and functions, just get color constants - if type(val) == "number" then - monitor.setPaletteColor(val, term.nativePaletteColor(val)) - end - end + -- set overridden colors + for i = 1, #style.colors do + monitor.setPaletteColor(style.colors[i].c, style.colors[i].hex) end end @@ -79,15 +68,14 @@ function renderer.is_monitor_used(periph) return false end --- reset all displays in use by the renderer ----@param recolor? boolean true to use color palette from style -function renderer.reset(recolor) - -- reset primary monitor - _reset_display(engine.monitors.primary, recolor) +-- init all displays in use by the renderer +function renderer.init_displays() + -- init primary monitor + _init_display(engine.monitors.primary) - -- reset unit displays + -- init unit displays for _, monitor in pairs(engine.monitors.unit_displays) do - _reset_display(monitor, recolor) + _init_display(monitor) end end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index a35a0f4..1a23719 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "v0.11.6" +local COORDINATOR_VERSION = "v0.11.7" local print = util.print local println = util.println @@ -45,7 +45,6 @@ cfv.assert_type_int(config.TRUSTED_RANGE) cfv.assert_type_num(config.COMMS_TIMEOUT) cfv.assert_min(config.COMMS_TIMEOUT, 2) cfv.assert_type_int(config.NUM_UNITS) -cfv.assert_type_bool(config.RECOLOR) cfv.assert_type_num(config.SOUNDER_VOLUME) cfv.assert_type_bool(config.TIME_24_HOUR) cfv.assert_type_str(config.LOG_PATH) @@ -88,7 +87,7 @@ local function main() -- init renderer renderer.set_displays(monitors) - renderer.reset(config.RECOLOR) + renderer.init_displays() if not renderer.validate_main_display_width() then println("startup> main display must be 8 blocks wide") diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index ca584f4..4b4bf46 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -1,3 +1,6 @@ +-- +-- Graphics Style Options +-- local core = require("graphics.core") From 58cf383c914532585c2c45057ad5e2c8d6d82055 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 1 Mar 2023 22:37:28 -0500 Subject: [PATCH 564/587] #185 disable auto mode changing if auto mode is active regardless of assignment --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 12 +++++++----- install_manifest.json | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 1a23719..066f477 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "v0.11.7" +local COORDINATOR_VERSION = "v0.11.8" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 05e675d..bfc0a22 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -61,6 +61,7 @@ local waste_opts = { ---@param id integer local function init(parent, id) local unit = iocontrol.get_db().units[id] ---@type ioctl_unit + local f_ps = iocontrol.get_db().facility.ps local u_ps = unit.unit_ps local b_ps = unit.boiler_ps_tbl local t_ps = unit.turbine_ps_tbl @@ -504,12 +505,13 @@ local function init(parent, id) start_button_en_check() if auto_active then - set_grp_btn.disable() a_stb.update(unit.reactor_data.mek_status.status == false) - else - set_grp_btn.enable() - a_stb.update(false) - end + else a_stb.update(false) end + end) + + -- can't change group if auto is engaged regardless of if this unit is part of auto control + f_ps.subscribe("auto_active", function (auto_active) + if auto_active then set_grp_btn.disable() else set_grp_btn.enable() end end) return main diff --git a/install_manifest.json b/install_manifest.json index 48814c8..5dec597 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.2", "rtu": "v0.12.3", "supervisor": "v0.13.4", "coordinator": "v0.11.6", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 0, "system": 1982, "common": 90550, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 271184, "coordinator": 179394, "pocket": 335}} \ No newline at end of file +{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.2", "rtu": "v0.12.3", "supervisor": "v0.13.4", "coordinator": "v0.11.8", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 0, "system": 1982, "common": 90550, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 271184, "coordinator": 179004, "pocket": 335}} \ No newline at end of file From 11115633cfb122407db2792c9e987ff6fc23c043 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 2 Mar 2023 22:29:50 -0500 Subject: [PATCH 565/587] #186 fixed manifest size in install_manifest.json, fixed unit display not connected prompt, added message about bad cooling config --- coordinator/coordinator.lua | 14 ++++++++++---- coordinator/startup.lua | 2 +- imgen.py | 4 +++- install_manifest.json | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 37b7503..40f8253 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -117,7 +117,7 @@ function coordinator.configure_monitors(num_units) while display == nil and #names > 0 do -- lets get a monitor - println("please select monitor for unit " .. i) + println("please select monitor for unit #" .. i) display = ask_monitor(names) end @@ -131,7 +131,8 @@ function coordinator.configure_monitors(num_units) local display = unit_displays[i] if not util.table_contains(names, display) then - local response = dialog.ask_y_n("unit display " .. i .. " is not connected, would you like to change it?", true) + println("unit #" .. i .. " display is not connected") + local response = dialog.ask_y_n("would you like to change it", true) if response == false then return false end display = nil end @@ -210,6 +211,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range sv_linked = false, sv_seq_num = 0, sv_r_seq_num = nil, + sv_config_err = false, connected = false, last_est_ack = ESTABLISH_ACK.ALLOW } @@ -295,7 +297,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range clock.start() - while (util.time_s() - start) < timeout_s and not self.sv_linked do + while (util.time_s() - start) < timeout_s and (not self.sv_linked) and (not self.sv_config_err) do local event, p1, p2, p3, p4, p5 = util.pull_event() if event == "timer" and clock.is_clock(p1) then @@ -319,6 +321,8 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range if terminated then coordinator.log_comms("supervisor connection attempt cancelled by user") + elseif self.sv_config_err then + coordinator.log_comms("supervisor cooling configuration invalid, check supervisor config file") elseif not self.sv_linked then if self.last_est_ack == ESTABLISH_ACK.DENY then coordinator.log_comms("supervisor connection attempt denied") @@ -569,8 +573,10 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range iocontrol.init(conf, public) self.sv_linked = true + self.sv_config_err = false else - log.debug("invalid supervisor configuration definitions received, establish failed") + self.sv_config_err = true + log.warning("invalid supervisor configuration definitions received, establish failed") end else log.debug("invalid supervisor configuration table received, establish failed") diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 066f477..b6a8f10 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "v0.11.8" +local COORDINATOR_VERSION = "v0.11.9" local print = util.print local println = util.println diff --git a/imgen.py b/imgen.py index e6c6ec0..02bcff7 100644 --- a/imgen.py +++ b/imgen.py @@ -98,7 +98,9 @@ f = open("install_manifest.json", "w") json.dump(make_manifest("-----"), f) f.close() +manifest_size = os.path.getsize("install_manifest.json") + # calculate file size then regenerate with embedded size f = open("install_manifest.json", "w") -json.dump(make_manifest(os.path.getsize("install_manifest.json")), f) +json.dump(make_manifest(manifest_size), f) f.close() diff --git a/install_manifest.json b/install_manifest.json index 5dec597..dcdc18c 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.2", "rtu": "v0.12.3", "supervisor": "v0.13.4", "coordinator": "v0.11.8", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 0, "system": 1982, "common": 90550, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 271184, "coordinator": 179004, "pocket": 335}} \ No newline at end of file +{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.2", "rtu": "v0.12.3", "supervisor": "v0.13.4", "coordinator": "v0.11.9", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4626, "system": 1982, "common": 90549, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 271184, "coordinator": 179375, "pocket": 335}} \ No newline at end of file From 0e5113918c99b1c9d77d2c18533a144d255383f9 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 01:37:15 -0500 Subject: [PATCH 566/587] #186 improved sv config validation, changed waste high thresholds, fixed monitored max burn not showing as active, fixed redstone R_ENABLE and U_ALARM, changed RPS high waste trip to 95% --- coordinator/iocontrol.lua | 3 ++- coordinator/startup.lua | 2 +- install_manifest.json | 2 +- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- scada-common/comms.lua | 4 ++-- scada-common/constants.lua | 6 +++--- scada-common/rsio.lua | 4 ++-- supervisor/startup.lua | 11 +++++++---- supervisor/unitlogic.lua | 4 ++-- 10 files changed, 22 insertions(+), 18 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index ea243cd..fc54fb3 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -11,6 +11,7 @@ local process = require("coordinator.process") local sounder = require("coordinator.sounder") local ALARM_STATE = types.ALARM_STATE +local PROCESS = types.PROCESS local iocontrol = {} @@ -301,7 +302,7 @@ function iocontrol.update_facility_status(status) fac.auto_ready = ctl_status[2] if type(ctl_status[3]) == "number" then - fac.auto_active = ctl_status[3] > 1 + fac.auto_active = ctl_status[3] > PROCESS.INACTIVE else fac.auto_active = false valid = false diff --git a/coordinator/startup.lua b/coordinator/startup.lua index b6a8f10..7d6e39c 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "v0.11.9" +local COORDINATOR_VERSION = "v0.11.10" local print = util.print local println = util.println diff --git a/install_manifest.json b/install_manifest.json index dcdc18c..0a218b8 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.2", "rtu": "v0.12.3", "supervisor": "v0.13.4", "coordinator": "v0.11.9", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4626, "system": 1982, "common": 90549, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 271184, "coordinator": 179375, "pocket": 335}} \ No newline at end of file +{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.5", "coordinator": "v0.11.10", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4627, "system": 1982, "common": 90588, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 271405, "coordinator": 179421, "pocket": 335}} \ No newline at end of file diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index f8229e5..a41a86e 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,7 @@ local config = require("reactor-plc.config") local plc = require("reactor-plc.plc") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v0.12.2" +local R_PLC_VERSION = "v0.12.3" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index 34f0c65..52e08a1 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "v0.12.3" +local RTU_VERSION = "v0.12.4" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE diff --git a/scada-common/comms.lua b/scada-common/comms.lua index ad7c0aa..497c3b3 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -141,9 +141,9 @@ function comms.scada_packet() local self = { modem_msg_in = nil, valid = false, - raw = { -1, -1, {} }, + raw = { -1, PROTOCOL.SCADA_MGMT, {} }, seq_num = -1, - protocol = -1, + protocol = PROTOCOL.SCADA_MGMT, length = 0, payload = {} } diff --git a/scada-common/constants.lua b/scada-common/constants.lua index 902e187..18272f5 100644 --- a/scada-common/constants.lua +++ b/scada-common/constants.lua @@ -16,7 +16,7 @@ local rps = {} rps.MAX_DAMAGE_PERCENT = 90 -- damage >= 90% rps.MAX_DAMAGE_TEMPERATURE = 1200 -- temp >= 1200K rps.MIN_COOLANT_FILL = 0.10 -- fill < 10% -rps.MAX_WASTE_FILL = 0.8 -- fill > 80% +rps.MAX_WASTE_FILL = 0.95 -- fill > 95% rps.MAX_HEATED_COLLANT_FILL = 0.95 -- fill > 95% rps.NO_FUEL_FILL = 0.0 -- fill <= 0% @@ -33,7 +33,7 @@ annunc.CoolantLevelLow = 0.4 -- fill < 40% annunc.ReactorTempHigh = 1000 -- temp > 1000K annunc.ReactorHighDeltaT = 50 -- rate > 50K/s annunc.FuelLevelLow = 0.05 -- fill <= 5% -annunc.WasteLevelHigh = 0.85 -- fill >= 85% +annunc.WasteLevelHigh = 0.80 -- fill >= 80% annunc.WaterLevelLow = 0.4 -- fill < 40% annunc.SteamFeedMismatch = 10 -- ±10mB difference between total coolant flow and total steam input rate annunc.RadiationWarning = 0.00001 -- 10 uSv/h @@ -49,7 +49,7 @@ local alarms = {} -- unit alarms alarms.HIGH_TEMP = 1150 -- temp >= 1150K -alarms.HIGH_WASTE = 0.5 -- fill > 50% +alarms.HIGH_WASTE = 0.85 -- fill > 85% alarms.HIGH_RADIATION = 0.00005 -- 50 uSv/h, not yet damaging but this isn't good -- facility alarms diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 6b33a24..42e27aa 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -52,7 +52,7 @@ local IO_PORT = { -- digital outputs -- -- facility - F_ALARM = 7, -- active high, facility alarm (any high priority unit alarm) + F_ALARM = 7, -- active high, facility-wide alarm (any high priority unit alarm) -- waste WASTE_PU = 8, -- active low, waste -> plutonium -> pellets route @@ -75,7 +75,7 @@ local IO_PORT = { R_PLC_TIMEOUT = 23, -- active high, if the reactor PLC has not been heard from -- unit outputs - U_ALARM = 24, -- active high, unit alarm + U_ALARM = 24, -- active high, unit alarm U_EMER_COOL = 25 -- active low, emergency coolant control } diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 1117ff7..743efa6 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "v0.13.4" +local SUPERVISOR_VERSION = "v0.13.5" local print = util.print local println = util.println @@ -43,15 +43,18 @@ cfv.assert_type_int(config.LOG_MODE) assert(cfv.valid(), "bad config file: missing/invalid fields") +cfv.assert_eq(#config.REACTOR_COOLING, config.NUM_REACTORS) +assert(cfv.valid(), "config: number of cooling configs different than number of units") + for i = 1, config.NUM_REACTORS do cfv.assert_type_table(config.REACTOR_COOLING[i]) - assert(cfv.valid(), "missing cooling entry for reactor " .. i) + assert(cfv.valid(), "config: missing cooling entry for reactor " .. i) cfv.assert_type_int(config.REACTOR_COOLING[i].BOILERS) cfv.assert_type_int(config.REACTOR_COOLING[i].TURBINES) - assert(cfv.valid(), "missing boilers/turbines for reactor " .. i) + assert(cfv.valid(), "config: missing boilers/turbines for reactor " .. i) cfv.assert_min(config.REACTOR_COOLING[i].BOILERS, 0) cfv.assert_min(config.REACTOR_COOLING[i].TURBINES, 1) - assert(cfv.valid(), "bad number of boilers/turbines for reactor " .. i) + assert(cfv.valid(), "config: bad number of boilers/turbines for reactor " .. i) end ---------------------------------------- diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 823c581..56e6460 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -706,7 +706,7 @@ function logic.handle_redstone(self) end if (not self.auto_engaged) and (not self.plc_cache.active) and - (not self.plc_cache.rps_trip) and self.io_ctl.digital_read(IO.R_ACTIVE) then + (not self.plc_cache.rps_trip) and self.io_ctl.digital_read(IO.R_ENABLE) then -- reactor enable requested and allowable, but not yet done; perform it self.plc_s.in_queue.push_command(PLC_S_CMDS.ENABLE) end @@ -739,7 +739,7 @@ function logic.handle_redstone(self) local has_alarm = false for i = 1, #self.db.alarm_states do - if self.db.alarm_states[i] == ALARM_STATE.TRIPPED then + if self.db.alarm_states[i] == ALARM_STATE.TRIPPED or self.db.alarm_states[i] == ALARM_STATE.ACKED then has_alarm = true break end From b12f3206e2121938745ada642a2b4b00c3fc9372 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 02:05:36 -0500 Subject: [PATCH 567/587] #186 additional messages for radiation alarm/warning with added urgency/level-specific messages --- install_manifest.json | 2 +- scada-common/constants.lua | 16 +++++++++++----- supervisor/startup.lua | 2 +- supervisor/unit.lua | 1 + supervisor/unitlogic.lua | 25 +++++++++++++++++++++++-- 5 files changed, 37 insertions(+), 9 deletions(-) diff --git a/install_manifest.json b/install_manifest.json index 0a218b8..72629dd 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.5", "coordinator": "v0.11.10", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4627, "system": 1982, "common": 90588, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 271405, "coordinator": 179421, "pocket": 335}} \ No newline at end of file +{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.6", "coordinator": "v0.11.10", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4627, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 272656, "coordinator": 179421, "pocket": 335}} \ No newline at end of file diff --git a/scada-common/constants.lua b/scada-common/constants.lua index 18272f5..15b6ae9 100644 --- a/scada-common/constants.lua +++ b/scada-common/constants.lua @@ -2,11 +2,6 @@ -- System and Safety Constants -- --- Notes on Radiation --- - background radiation 0.0000001 Sv/h (99.99 nSv/h) --- - "green tint" radiation 0.00001 Sv/h (10 uSv/h) --- - damaging radiation 0.00006 Sv/h (60 uSv/h) - local constants = {} --#region Reactor Protection System (on the PLC) Limits @@ -67,6 +62,17 @@ constants.ALARM_LIMITS = alarms -- milliseconds until turbine flow is assumed to be stable enough to enable coolant checks constants.FLOW_STABILITY_DELAY_MS = 15000 +-- Notes on Radiation +-- - background radiation 0.0000001 Sv/h (99.99 nSv/h) +-- - "green tint" radiation 0.00001 Sv/h (10 uSv/h) +-- - damaging radiation 0.00006 Sv/h (60 uSv/h) +constants.LOW_RADIATION = 0.00001 +constants.HAZARD_RADIATION = 0.00006 +constants.HIGH_RADIATION = 0.001 +constants.VERY_HIGH_RADIATION = 0.1 +constants.SEVERE_RADIATION = 8.0 +constants.EXTREME_RADIATION = 100.0 + --#endregion return constants diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 743efa6..272a5e6 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "v0.13.5" +local SUPERVISOR_VERSION = "v0.13.6" local print = util.print local println = util.println diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 4d7f4e2..7432182 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -83,6 +83,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) -- state tracking deltas = {}, last_heartbeat = 0, + last_radiation = 0, damage_initial = 0, damage_start = 0, damage_last = 0, diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 56e6460..b5fa35e 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -454,7 +454,8 @@ function logic.update_alarms(self) -- Containment Radiation local rad_alarm = false for i = 1, #self.envd do - rad_alarm = self.envd[i].get_db().radiation_raw >= ALARM_LIMS.HIGH_RADIATION + self.last_radiation = self.envd[i].get_db().radiation_raw + rad_alarm = self.last_radiation >= ALARM_LIMS.HIGH_RADIATION break end _update_alarm_state(self, rad_alarm, self.alarms.ContainmentRadiation) @@ -595,7 +596,21 @@ function logic.update_status_text(self) self.status_text[2] = "estimating time to critical..." end elseif is_active(self.alarms.ContainmentRadiation) then - self.status_text = { "RADIATION DETECTED", "radiation levels above normal" } + self.status_text[1] = "RADIATION DETECTED" + + if self.last_radiation >= const.EXTREME_RADIATION then + self.status_text[2] = "extremely high radiation level" + elseif self.last_radiation >= const.SEVERE_RADIATION then + self.status_text[2] = "severely high radiation level" + elseif self.last_radiation >= const.VERY_HIGH_RADIATION then + self.status_text[2] = "very high level of radiation" + elseif self.last_radiation >= const.HIGH_RADIATION then + self.status_text[2] = "high level of radiation" + elseif self.last_radiation >= const.HAZARD_RADIATION then + self.status_text[2] = "hazardous level of radiation" + else + self.status_text[2] = "elevated level of radiation" + end elseif is_active(self.alarms.ReactorOverTemp) then self.status_text = { "CORE OVER TEMP", "reactor core temperature >=1200K" } elseif is_active(self.alarms.ReactorWasteLeak) then @@ -663,6 +678,9 @@ function logic.update_status_text(self) end self.status_text = { "RPS SCRAM", cause } + elseif self.db.annunciator.RadiationWarning then + -- elevated, non-hazardous level of radiation is low priority, so display it now if everything else was fine + self.status_text = { "RADIATION DETECTED", "elevated level of radiation" } else self.status_text[1] = "IDLE" @@ -675,6 +693,9 @@ function logic.update_status_text(self) self.status_text[2] = "core hot" end end + elseif self.db.annunciator.RadiationWarning then + -- in case PLC was disconnected but radiation is present + self.status_text = { "RADIATION DETECTED", "elevated level of radiation" } else self.status_text = { "REACTOR OFF-LINE", "awaiting connection..." } end From d01a6d548f725a39e5559171bdf07c8002b73ce6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 11:40:06 -0500 Subject: [PATCH 568/587] #186 fixed radiation warning condition --- install_manifest.json | 2 +- supervisor/startup.lua | 2 +- supervisor/unitlogic.lua | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/install_manifest.json b/install_manifest.json index 72629dd..18d0625 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.6", "coordinator": "v0.11.10", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4627, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 272656, "coordinator": 179421, "pocket": 335}} \ No newline at end of file +{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.7", "coordinator": "v0.11.10", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4627, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 272657, "coordinator": 179421, "pocket": 335}} \ No newline at end of file diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 272a5e6..54a3886 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "v0.13.6" +local SUPERVISOR_VERSION = "v0.13.7" local print = util.print local println = util.println diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index b5fa35e..a1eeacb 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -153,7 +153,7 @@ function logic.update_annunciator(self) for i = 1, #self.envd do local envd = self.envd[i] ---@type unit_session self.db.annunciator.RadiationMonitor = util.trinary(envd.is_faulted(), 2, 3) - self.db.annunciator.RadiationWarning = envd.get_db().radiation_raw > ANNUNC_LIMS.RadiationWarning + self.db.annunciator.RadiationWarning = envd.get_db().radiation_raw >= ANNUNC_LIMS.RadiationWarning break end From f7828dd05b19269d466934550eb01c3efe1d62c3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 11:46:59 -0500 Subject: [PATCH 569/587] #186 F_ALARM use emergency+ level --- install_manifest.json | 2 +- supervisor/facility.lua | 7 ++++--- supervisor/startup.lua | 2 +- supervisor/unit.lua | 7 ++++--- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/install_manifest.json b/install_manifest.json index 18d0625..3aaead3 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.7", "coordinator": "v0.11.10", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4627, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 272657, "coordinator": 179421, "pocket": 335}} \ No newline at end of file +{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.8", "coordinator": "v0.11.10", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4627, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 272880, "coordinator": 179421, "pocket": 335}} \ No newline at end of file diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 106c4d7..0d3db0c 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -10,6 +10,7 @@ local rsctl = require("supervisor.session.rsctl") local PROCESS = types.PROCESS local PROCESS_NAMES = types.PROCESS_NAMES +local PRIO = types.ALARM_PRIORITY local IO = rsio.IO @@ -569,7 +570,7 @@ function facility.new(num_reactors, cooling_conf) for i = 1, #self.units do local u = self.units[i] ---@type reactor_unit - if u.has_critical_alarm() then + if u.has_alarm_min_prio(PRIO.CRITICAL) then astatus.crit_alarm = true break end @@ -677,12 +678,12 @@ function facility.new(num_reactors, cooling_conf) -- handle facility ack if self.io_ctl.digital_read(IO.F_ACK) then public.ack_all() end - -- update facility alarm output + -- update facility alarm output (check if emergency+ alarms are active) local has_alarm = false for i = 1, #self.units do local u = self.units[i] ---@type reactor_unit - if u.has_critical_alarm() then + if u.has_alarm_min_prio(PRIO.EMERGENCY) then has_alarm = true return end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 54a3886..39590ab 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "v0.13.7" +local SUPERVISOR_VERSION = "v0.13.8" local print = util.print local println = util.println diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 7432182..15a3093 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -669,12 +669,13 @@ function unit.new(reactor_id, num_boilers, num_turbines) -- READ STATES/PROPERTIES -- --#region - -- check if a critical alarm is tripped + -- check if an alarm of at least a certain priority level is tripped ---@nodiscard + ---@param min_prio ALARM_PRIORITY alarms with this priority or higher will be checked ---@return boolean tripped - function public.has_critical_alarm() + function public.has_alarm_min_prio(min_prio) for _, alarm in pairs(self.alarms) do - if alarm.tier == PRIO.CRITICAL and (alarm.state == AISTATE.TRIPPED or alarm.state == AISTATE.ACKED) then + if alarm.tier <= min_prio and (alarm.state == AISTATE.TRIPPED or alarm.state == AISTATE.ACKED) then return true end end From 3586d335a6e05433de612c08f6d9e72150a5a165 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 12:27:38 -0500 Subject: [PATCH 570/587] #186 don't includes assigned monitors in list of monitors to assign --- coordinator/coordinator.lua | 26 +++++++++++++++++++------- coordinator/startup.lua | 2 +- install_manifest.json | 2 +- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 40f8253..821c82a 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -47,6 +47,7 @@ end -- configure monitor layout ---@param num_units integer number of units expected +---@return boolean success, monitors_struct? monitors function coordinator.configure_monitors(num_units) ---@class monitors_struct local monitors = { @@ -58,10 +59,12 @@ function coordinator.configure_monitors(num_units) local monitors_avail = ppm.get_monitor_list() local names = {} + local available = {} -- get all interface names for iface, _ in pairs(monitors_avail) do table.insert(names, iface) + table.insert(available, iface) end -- we need a certain number of monitors (1 per unit + 1 primary display) @@ -76,6 +79,15 @@ function coordinator.configure_monitors(num_units) -- attempt to load settings if not settings.load("/coord.settings") then log.warning("configure_monitors(): failed to load coordinator settings file (may not exist yet)") + else + local _primary = settings.get("PRIMARY_DISPLAY") + local _unitd = settings.get("UNIT_DISPLAYS") + + -- filter out already assigned monitors + util.filter_table(available, function (x) return x ~= _primary end) + if type(_unitd) == "table" then + util.filter_table(available, function (x) return not util.table_contains(_unitd, x) end) + end end --------------------- @@ -91,15 +103,15 @@ function coordinator.configure_monitors(num_units) iface_primary_display = nil end - while iface_primary_display == nil and #names > 0 do + while iface_primary_display == nil and #available > 0 do -- lets get a monitor - iface_primary_display = ask_monitor(names) + iface_primary_display = ask_monitor(available) end if type(iface_primary_display) ~= "string" then return false end settings.set("PRIMARY_DISPLAY", iface_primary_display) - util.filter_table(names, function (x) return x ~= iface_primary_display end) + util.filter_table(available, function (x) return x ~= iface_primary_display end) monitors.primary = ppm.get_periph(iface_primary_display) monitors.primary_name = iface_primary_display @@ -115,10 +127,10 @@ function coordinator.configure_monitors(num_units) for i = 1, num_units do local display = nil - while display == nil and #names > 0 do + while display == nil and #available > 0 do -- lets get a monitor println("please select monitor for unit #" .. i) - display = ask_monitor(names) + display = ask_monitor(available) end if display == false then return false end @@ -137,9 +149,9 @@ function coordinator.configure_monitors(num_units) display = nil end - while display == nil and #names > 0 do + while display == nil and #available > 0 do -- lets get a monitor - display = ask_monitor(names) + display = ask_monitor(available) end if display == false then return false end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 7d6e39c..7751612 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "v0.11.10" +local COORDINATOR_VERSION = "v0.11.11" local print = util.print local println = util.println diff --git a/install_manifest.json b/install_manifest.json index 3aaead3..e186503 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.8", "coordinator": "v0.11.10", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4627, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 272880, "coordinator": 179421, "pocket": 335}} \ No newline at end of file +{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.8", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4627, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 272880, "coordinator": 179964, "pocket": 335}} \ No newline at end of file From 94fb02a46b3a127b14c1ed0ecac7dad0f1f36cb2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 12:44:37 -0500 Subject: [PATCH 571/587] #186 fixed ccmsi install/update insufficient space confirm --- ccmsi.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index 7173795..fa47ac1 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -20,7 +20,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local VERSION = "v0.9f" +local VERSION = "v0.9g" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/" @@ -376,7 +376,7 @@ elseif mode == "install" or mode == "update" then println("Do you wish to continue? (y/N)") local confirm = read() - if confirm ~= "y" or confirm ~= "Y" then + if confirm ~= "y" and confirm ~= "Y" then println("installation cancelled") return end From be8e8d767cdf4a807963eb4dd79650afed02025e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 12:49:05 -0500 Subject: [PATCH 572/587] #186 fixed ccmsi insufficient space update overwriting config --- ccmsi.lua | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index fa47ac1..c2d5d5c 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -20,7 +20,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local VERSION = "v0.9g" +local VERSION = "v0.9h" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/" @@ -497,17 +497,19 @@ elseif mode == "install" or mode == "update" then term.setTextColor(colors.lightGray) local files = file_list[dependency] for _, file in pairs(files) do - println("GET " .. file) - local dl, err = http.get(repo_path .. file) + if mode == "install" or file ~= config_file then + println("GET " .. file) + local dl, err = http.get(repo_path .. file) - if dl == nil then - println("GET HTTP Error " .. err) - success = false - break - else - local handle = fs.open("/" .. file, "w") - handle.write(dl.readAll()) - handle.close() + if dl == nil then + println("GET HTTP Error " .. err) + success = false + break + else + local handle = fs.open("/" .. file, "w") + handle.write(dl.readAll()) + handle.close() + end end end end From 8c236eca8515485bbbd0f47a8814abed89ee8454 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 13:38:41 -0500 Subject: [PATCH 573/587] #186 fixed bug with facility update returning, improved damage status message --- install_manifest.json | 2 +- supervisor/facility.lua | 2 +- supervisor/startup.lua | 2 +- supervisor/unit.lua | 1 + supervisor/unitlogic.lua | 27 +++++++++++++++++++-------- 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/install_manifest.json b/install_manifest.json index e186503..91b1f9d 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.8", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4627, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 272880, "coordinator": 179964, "pocket": 335}} \ No newline at end of file +{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.9", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4627, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 273485, "coordinator": 179964, "pocket": 335}} \ No newline at end of file diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 0d3db0c..f37de26 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -685,7 +685,7 @@ function facility.new(num_reactors, cooling_conf) if u.has_alarm_min_prio(PRIO.EMERGENCY) then has_alarm = true - return + break end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 39590ab..4f1706f 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "v0.13.8" +local SUPERVISOR_VERSION = "v0.13.9" local print = util.print local println = util.println diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 15a3093..8bdb911 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -84,6 +84,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) deltas = {}, last_heartbeat = 0, last_radiation = 0, + damage_decreasing = false, damage_initial = 0, damage_start = 0, damage_last = 0, diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index a1eeacb..8d5540d 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -93,10 +93,12 @@ function logic.update_annunciator(self) -- track damage if plc_db.mek_status.damage > 0 then if self.damage_start == 0 then + self.damage_decreasing = false self.damage_start = util.time_s() self.damage_initial = plc_db.mek_status.damage end else + self.damage_decreasing = false self.damage_start = 0 self.damage_initial = 0 self.damage_last = 0 @@ -584,16 +586,25 @@ function logic.update_status_text(self) self.status_text[1] = "CONTAINMENT TAKING DAMAGE" if self.plc_cache.damage >= 100 then self.status_text[2] = "damage critical" - elseif (self.plc_cache.damage - self.damage_initial) > 0 then - if self.plc_cache.damage > self.damage_last then - self.damage_last = self.plc_cache.damage - local rate = (self.plc_cache.damage - self.damage_initial) / (util.time_s() - self.damage_start) - self.damage_est_last = (100 - self.plc_cache.damage) / rate - end + elseif self.plc_cache.damage < self.damage_last then + self.damage_decreasing = true + self.status_text = { "CONTAINMENT TOOK DAMAGE", "damage level lowering..." } + elseif (not self.damage_decreasing) or (self.plc_cache.damage > self.damage_last) then + self.damage_decreasing = false - self.status_text[2] = util.c("damage critical in ", util.sprintf("%.1f", self.damage_est_last), "s") + if (self.plc_cache.damage - self.damage_initial) > 0 then + if self.plc_cache.damage > self.damage_last then + self.damage_last = self.plc_cache.damage + local rate = (self.plc_cache.damage - self.damage_initial) / (util.time_s() - self.damage_start) + self.damage_est_last = (100 - self.plc_cache.damage) / rate + end + + self.status_text[2] = util.c("damage critical in ", util.sprintf("%.1f", self.damage_est_last), "s") + else + self.status_text[2] = "estimating time to critical..." + end else - self.status_text[2] = "estimating time to critical..." + self.status_text = { "CONTAINMENT TOOK DAMAGE", "damage level lowering..." } end elseif is_active(self.alarms.ContainmentRadiation) then self.status_text[1] = "RADIATION DETECTED" From 2e9f52dc8917df4677075b1be1f6f47002e768ff Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 14:13:47 -0500 Subject: [PATCH 574/587] #187 added installer version to manifest --- ccmsi.lua | 10 ++++++++-- imgen.py | 14 +++++++------- install_manifest.json | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index c2d5d5c..e1e5d98 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -20,7 +20,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local VERSION = "v0.9h" +local CCMSI_VERSION = "v0.9i" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/" @@ -57,7 +57,7 @@ end -- get and validate command line options -- -println("-- CC Mekanism SCADA Installer " .. VERSION .. " --") +println("-- CC Mekanism SCADA Installer " .. CCMSI_VERSION .. " --") if #opts == 0 or opts[1] == "help" then println("usage: ccmsi ") @@ -258,6 +258,12 @@ elseif mode == "install" or mode == "update" then end end + if manifest.versions.installer ~= local_manifest.versions.installer then + term.setTextColor(colors.orange) + println("a newer version of the installer is available, consider downloading it") + term.setTextColor(colors.white) + end + local remote_app_version = manifest.versions[app] local remote_comms_version = manifest.versions.comms local remote_boot_version = manifest.versions.bootloader diff --git a/imgen.py b/imgen.py index 02bcff7..faab46a 100644 --- a/imgen.py +++ b/imgen.py @@ -27,7 +27,6 @@ def get_version(path, is_comms = False): string = "comms.version = \"" if not is_comms: - path = path + "/startup.lua" string = "_VERSION = \"" f = open(path, "r") @@ -46,13 +45,14 @@ def get_version(path, is_comms = False): def make_manifest(size): manifest = { "versions" : { - "bootloader" : get_version("."), + "installer" : get_version("./ccmsi.lua"), + "bootloader" : get_version("./startup.lua"), "comms" : get_version("./scada-common/comms.lua", True), - "reactor-plc" : get_version("./reactor-plc"), - "rtu" : get_version("./rtu"), - "supervisor" : get_version("./supervisor"), - "coordinator" : get_version("./coordinator"), - "pocket" : get_version("./pocket") + "reactor-plc" : get_version("./reactor-plc/startup.lua"), + "rtu" : get_version("./rtu/startup.lua"), + "supervisor" : get_version("./supervisor/startup.lua"), + "coordinator" : get_version("./coordinator/startup.lua"), + "pocket" : get_version("./pocket/startup.lua") }, "files" : { # common files diff --git a/install_manifest.json b/install_manifest.json index 91b1f9d..b7f3409 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.9", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4627, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 273485, "coordinator": 179964, "pocket": 335}} \ No newline at end of file +{"versions": {"installer": "v0.9i", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.9", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4649, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 273485, "coordinator": 179964, "pocket": 335}} \ No newline at end of file From d494abe8af13eb348d94c16c19085bcf5259b3f1 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 14:19:17 -0500 Subject: [PATCH 575/587] #187 improved installer version check --- ccmsi.lua | 15 +++++++++------ install_manifest.json | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index e1e5d98..b0d231f 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -20,7 +20,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local CCMSI_VERSION = "v0.9i" +local CCMSI_VERSION = "v0.9j" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/" @@ -164,6 +164,8 @@ if mode == "check" then term.setTextColor(colors.white) end + local_manifest.versions.installer = CCMSI_VERSION + -- list all versions for key, value in pairs(manifest.versions) do term.setTextColor(colors.purple) @@ -256,12 +258,13 @@ elseif mode == "install" or mode == "update" then term.setTextColor(colors.white) return end - end - if manifest.versions.installer ~= local_manifest.versions.installer then - term.setTextColor(colors.orange) - println("a newer version of the installer is available, consider downloading it") - term.setTextColor(colors.white) + local_manifest.versions.installer = CCMSI_VERSION + if manifest.versions.installer ~= CCMSI_VERSION then + term.setTextColor(colors.yellow) + println("a newer version of the installer is available, consider downloading it") + term.setTextColor(colors.white) + end end local remote_app_version = manifest.versions[app] diff --git a/install_manifest.json b/install_manifest.json index b7f3409..75b1e01 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"installer": "v0.9i", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.9", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4649, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 273485, "coordinator": 179964, "pocket": 335}} \ No newline at end of file +{"versions": {"installer": "v0.9j", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.9", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4649, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 273485, "coordinator": 179964, "pocket": 335}} \ No newline at end of file From 57ccb73efe47d204fd7dc3e22824ede90d701298 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 14:21:42 -0500 Subject: [PATCH 576/587] #187 installer bugfix --- ccmsi.lua | 6 +++--- install_manifest.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index b0d231f..3ace596 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -20,7 +20,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local CCMSI_VERSION = "v0.9j" +local CCMSI_VERSION = "v0.9k" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/" @@ -162,10 +162,10 @@ if mode == "check" then term.setTextColor(colors.yellow) println("failed to load local installation information") term.setTextColor(colors.white) + else + local_manifest.versions.installer = CCMSI_VERSION end - local_manifest.versions.installer = CCMSI_VERSION - -- list all versions for key, value in pairs(manifest.versions) do term.setTextColor(colors.purple) diff --git a/install_manifest.json b/install_manifest.json index 75b1e01..50d7ad5 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"installer": "v0.9j", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.9", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4649, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 273485, "coordinator": 179964, "pocket": 335}} \ No newline at end of file +{"versions": {"installer": "v0.9k", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.9", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4649, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 273485, "coordinator": 179964, "pocket": 335}} \ No newline at end of file From 5b7a11d157c88861eab1b771294131e227ee0df3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 14:38:42 -0500 Subject: [PATCH 577/587] #187 installer bugfix --- ccmsi.lua | 4 +++- install_manifest.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index 3ace596..526628f 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -20,7 +20,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local CCMSI_VERSION = "v0.9k" +local CCMSI_VERSION = "v0.9l" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/" @@ -162,6 +162,8 @@ if mode == "check" then term.setTextColor(colors.yellow) println("failed to load local installation information") term.setTextColor(colors.white) + + local_manifest.versions = { installer = CCMSI_VERSION } else local_manifest.versions.installer = CCMSI_VERSION end diff --git a/install_manifest.json b/install_manifest.json index 50d7ad5..fae2000 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"installer": "v0.9k", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.9", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4649, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 273485, "coordinator": 179964, "pocket": 335}} \ No newline at end of file +{"versions": {"installer": "v0.9l", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.9", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4649, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 273485, "coordinator": 179964, "pocket": 335}} \ No newline at end of file From 9a500d53d8d92a0f168f5f85d58add10ee75c23f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 14:40:49 -0500 Subject: [PATCH 578/587] #187 installer bugfix --- ccmsi.lua | 6 +++--- install_manifest.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index 526628f..efb23ef 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -20,7 +20,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local CCMSI_VERSION = "v0.9l" +local CCMSI_VERSION = "v0.9m" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/" @@ -163,7 +163,7 @@ if mode == "check" then println("failed to load local installation information") term.setTextColor(colors.white) - local_manifest.versions = { installer = CCMSI_VERSION } + local_manifest = { versions = { installer = CCMSI_VERSION } } else local_manifest.versions.installer = CCMSI_VERSION end @@ -172,7 +172,7 @@ if mode == "check" then for key, value in pairs(manifest.versions) do term.setTextColor(colors.purple) print(string.format("%-14s", "[" .. key .. "]")) - if local_ok and (local_manifest.versions[key] ~= nil) then + if key == "installer" or (local_ok and (local_manifest.versions[key] ~= nil)) then term.setTextColor(colors.blue) print(local_manifest.versions[key]) if value ~= local_manifest.versions[key] then diff --git a/install_manifest.json b/install_manifest.json index fae2000..9699096 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"installer": "v0.9l", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.9", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4649, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 273485, "coordinator": 179964, "pocket": 335}} \ No newline at end of file +{"versions": {"installer": "v0.9m", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.9", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4649, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 273485, "coordinator": 179964, "pocket": 335}} \ No newline at end of file From 0279ecdec9688a2a49685c8e301a63fe551b9a52 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 19:49:56 -0500 Subject: [PATCH 579/587] #186 second attempt at improving damage status text --- install_manifest.json | 2 +- supervisor/startup.lua | 2 +- supervisor/unitlogic.lua | 10 ++++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/install_manifest.json b/install_manifest.json index 9699096..0d7324d 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"installer": "v0.9m", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.9", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4649, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 273485, "coordinator": 179964, "pocket": 335}} \ No newline at end of file +{"versions": {"installer": "v0.9m", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.10", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4650, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 273746, "coordinator": 179964, "pocket": 335}} \ No newline at end of file diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 4f1706f..cdf7eb7 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "v0.13.9" +local SUPERVISOR_VERSION = "v0.13.10" local print = util.print local println = util.println diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 8d5540d..46a311d 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -586,15 +586,19 @@ function logic.update_status_text(self) self.status_text[1] = "CONTAINMENT TAKING DAMAGE" if self.plc_cache.damage >= 100 then self.status_text[2] = "damage critical" - elseif self.plc_cache.damage < self.damage_last then + elseif (self.plc_cache.damage < self.damage_last) or ((self.plc_cache.damage - self.damage_initial) < 0) then self.damage_decreasing = true self.status_text = { "CONTAINMENT TOOK DAMAGE", "damage level lowering..." } + + -- reset damage estimation data in case it goes back up again + self.damage_initial = self.plc_cache.damage + self.damage_start = util.time_s() + self.damage_est_last = 0 elseif (not self.damage_decreasing) or (self.plc_cache.damage > self.damage_last) then self.damage_decreasing = false if (self.plc_cache.damage - self.damage_initial) > 0 then if self.plc_cache.damage > self.damage_last then - self.damage_last = self.plc_cache.damage local rate = (self.plc_cache.damage - self.damage_initial) / (util.time_s() - self.damage_start) self.damage_est_last = (100 - self.plc_cache.damage) / rate end @@ -606,6 +610,8 @@ function logic.update_status_text(self) else self.status_text = { "CONTAINMENT TOOK DAMAGE", "damage level lowering..." } end + + self.damage_last = self.plc_cache.damage elseif is_active(self.alarms.ContainmentRadiation) then self.status_text[1] = "RADIATION DETECTED" From edb5d8b96ffdb2f67e8b1720728dd9537d6479e5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 21:19:35 -0500 Subject: [PATCH 580/587] #186 different steam feed mismatch and RCS flow low tolerances for water vs sodium cooling --- install_manifest.json | 2 +- scada-common/constants.lua | 23 ++++++++++++++--------- supervisor/startup.lua | 2 +- supervisor/unitlogic.lua | 8 ++++++-- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/install_manifest.json b/install_manifest.json index 0d7324d..462476f 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"installer": "v0.9m", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.10", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4650, "system": 1982, "common": 90797, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 273746, "coordinator": 179964, "pocket": 335}} \ No newline at end of file +{"versions": {"installer": "v0.9m", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.11", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4650, "system": 1982, "common": 91075, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 274131, "coordinator": 179964, "pocket": 335}} \ No newline at end of file diff --git a/scada-common/constants.lua b/scada-common/constants.lua index 15b6ae9..7c661ee 100644 --- a/scada-common/constants.lua +++ b/scada-common/constants.lua @@ -23,15 +23,20 @@ constants.RPS_LIMITS = rps local annunc = {} -annunc.RCSFlowLow = -2.0 -- flow < -2.0 mB/s -annunc.CoolantLevelLow = 0.4 -- fill < 40% -annunc.ReactorTempHigh = 1000 -- temp > 1000K -annunc.ReactorHighDeltaT = 50 -- rate > 50K/s -annunc.FuelLevelLow = 0.05 -- fill <= 5% -annunc.WasteLevelHigh = 0.80 -- fill >= 80% -annunc.WaterLevelLow = 0.4 -- fill < 40% -annunc.SteamFeedMismatch = 10 -- ±10mB difference between total coolant flow and total steam input rate -annunc.RadiationWarning = 0.00001 -- 10 uSv/h +annunc.RCSFlowLow_H2O = -3.2 -- flow < -3.2 mB/s +annunc.RCSFlowLow_NA = -2.0 -- flow < -2.0 mB/s +annunc.CoolantLevelLow = 0.4 -- fill < 40% +annunc.ReactorTempHigh = 1000 -- temp > 1000K +annunc.ReactorHighDeltaT = 50 -- rate > 50K/s +annunc.FuelLevelLow = 0.05 -- fill <= 5% +annunc.WasteLevelHigh = 0.80 -- fill >= 80% +annunc.WaterLevelLow = 0.4 -- fill < 40% +annunc.SteamFeedMismatch = 10 -- ±10mB difference between total coolant flow and total steam input rate +annunc.SFM_MaxSteamDT_H20 = 2.0 -- flow > 2.0 mB/s +annunc.SFM_MinWaterDT_H20 = -3.0 -- flow < -3.0 mB/s +annunc.SFM_MaxSteamDT_NA = 2.0 -- flow > 2.0 mB/s +annunc.SFM_MinWaterDT_NA = -2.0 -- flow < -2.0 mB/s +annunc.RadiationWarning = 0.00001 -- 10 uSv/h constants.ANNUNCIATOR_LIMITS = annunc diff --git a/supervisor/startup.lua b/supervisor/startup.lua index cdf7eb7..51069d9 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "v0.13.10" +local SUPERVISOR_VERSION = "v0.13.11" local print = util.print local println = util.println diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 46a311d..c6d0aec 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -111,12 +111,14 @@ function logic.update_annunciator(self) self.last_heartbeat = plc_db.last_status_update end + local flow_low = util.trinary(plc_db.mek_status.ccool_type == types.FLUID.SODIUM, ANNUNC_LIMS.RCSFlowLow_NA, ANNUNC_LIMS.RCSFlowLow_H2O) + -- update other annunciator fields self.db.annunciator.ReactorSCRAM = plc_db.rps_tripped self.db.annunciator.ManualReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.MANUAL self.db.annunciator.AutoReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.AUTOMATIC self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.no_cool) - self.db.annunciator.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < ANNUNC_LIMS.RCSFlowLow + self.db.annunciator.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < flow_low self.db.annunciator.CoolantLevelLow = plc_db.mek_status.ccool_fill < ANNUNC_LIMS.CoolantLevelLow self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > ANNUNC_LIMS.ReactorTempHigh self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > ANNUNC_LIMS.ReactorHighDeltaT @@ -306,8 +308,10 @@ function logic.update_annunciator(self) self.db.annunciator.BoilRateMismatch = math.abs(total_boil_rate - total_input_rate) > (0.04 * total_boil_rate) -- check for steam feed mismatch and max return rate + local steam_dt_max = util.trinary(num_boilers > 0, ANNUNC_LIMS.SFM_MaxSteamDT_H20, ANNUNC_LIMS.SFM_MaxSteamDT_NA) + local water_dt_min = util.trinary(num_boilers > 0, ANNUNC_LIMS.SFM_MinWaterDT_H20, ANNUNC_LIMS.SFM_MinWaterDT_NA) local sfmismatch = math.abs(total_flow_rate - total_input_rate) > ANNUNC_LIMS.SteamFeedMismatch - sfmismatch = sfmismatch or boiler_steam_dt_sum > 2.0 or boiler_water_dt_sum < -2.0 + sfmismatch = sfmismatch or boiler_steam_dt_sum > steam_dt_max or boiler_water_dt_sum < water_dt_min self.db.annunciator.SteamFeedMismatch = sfmismatch self.db.annunciator.MaxWaterReturnFeed = max_water_return_rate == total_flow_rate and total_flow_rate ~= 0 From 85a95329628f325afe82f09ac121aa407408b4fd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 21:35:54 -0500 Subject: [PATCH 581/587] #186 fixed incorrect constant usage, add RCS flow low to flow stability holdoff when not using a boiler --- install_manifest.json | 2 +- supervisor/startup.lua | 2 +- supervisor/unitlogic.lua | 11 +++++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/install_manifest.json b/install_manifest.json index 462476f..56b8bcf 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"installer": "v0.9m", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.11", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4650, "system": 1982, "common": 91075, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 274131, "coordinator": 179964, "pocket": 335}} \ No newline at end of file +{"versions": {"installer": "v0.9m", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.12", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4650, "system": 1982, "common": 91075, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 274269, "coordinator": 179964, "pocket": 335}} \ No newline at end of file diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 51069d9..bb5b0b4 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "v0.13.11" +local SUPERVISOR_VERSION = "v0.13.12" local print = util.print local println = util.println diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index c6d0aec..3ded653 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -308,8 +308,8 @@ function logic.update_annunciator(self) self.db.annunciator.BoilRateMismatch = math.abs(total_boil_rate - total_input_rate) > (0.04 * total_boil_rate) -- check for steam feed mismatch and max return rate - local steam_dt_max = util.trinary(num_boilers > 0, ANNUNC_LIMS.SFM_MaxSteamDT_H20, ANNUNC_LIMS.SFM_MaxSteamDT_NA) - local water_dt_min = util.trinary(num_boilers > 0, ANNUNC_LIMS.SFM_MinWaterDT_H20, ANNUNC_LIMS.SFM_MinWaterDT_NA) + local steam_dt_max = util.trinary(num_boilers == 0, ANNUNC_LIMS.SFM_MaxSteamDT_H20, ANNUNC_LIMS.SFM_MaxSteamDT_NA) + local water_dt_min = util.trinary(num_boilers == 0, ANNUNC_LIMS.SFM_MinWaterDT_H20, ANNUNC_LIMS.SFM_MinWaterDT_NA) local sfmismatch = math.abs(total_flow_rate - total_input_rate) > ANNUNC_LIMS.SteamFeedMismatch sfmismatch = sfmismatch or boiler_steam_dt_sum > steam_dt_max or boiler_water_dt_sum < water_dt_min self.db.annunciator.SteamFeedMismatch = sfmismatch @@ -508,13 +508,16 @@ function logic.update_alarms(self) for i = 1, #annunc.WaterLevelLow do any_low = any_low or annunc.WaterLevelLow[i] end for i = 1, #annunc.TurbineOverSpeed do any_over = any_over or annunc.TurbineOverSpeed[i] end - local rcs_trans = any_low or any_over or annunc.RCPTrip or annunc.RCSFlowLow or annunc.MaxWaterReturnFeed + local rcs_trans = any_low or any_over or annunc.RCPTrip or annunc.MaxWaterReturnFeed + + -- only care about RCS flow low early with boilers + if self.num_boilers > 0 then rcs_trans = rcs_trans or annunc.RCSFlowLow end -- annunciator indicators for these states may not indicate a real issue when: -- > flow is ramping up right after reactor start -- > flow is ramping down after reactor shutdown if ((util.time_ms() - self.last_rate_change_ms) > FLOW_STABILITY_DELAY_MS) and plc_cache.active then - rcs_trans = rcs_trans or annunc.BoilRateMismatch or annunc.CoolantFeedMismatch or annunc.SteamFeedMismatch + rcs_trans = rcs_trans or annunc.RCSFlowLow or annunc.BoilRateMismatch or annunc.CoolantFeedMismatch or annunc.SteamFeedMismatch end _update_alarm_state(self, rcs_trans, self.alarms.RCSTransient) From c9f1bddb369bd2bfaf1465901928a490e6ea146a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 21:55:40 -0500 Subject: [PATCH 582/587] #188 refactored RPS dmg_crit to dmg_high --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 4 +-- install_manifest.json | 2 +- reactor-plc/plc.lua | 18 +++++------ reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- scada-common/rsio.lua | 8 ++--- scada-common/types.lua | 4 +-- supervisor/session/plc.lua | 4 +-- supervisor/startup.lua | 2 +- supervisor/unit.lua | 4 +-- supervisor/unitlogic.lua | 39 ++++++++++++----------- 12 files changed, 46 insertions(+), 45 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 7751612..985afae 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "v0.11.11" +local COORDINATOR_VERSION = "v0.11.12" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index bfc0a22..f4f633e 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -210,7 +210,7 @@ local function init(parent, id) local rps_annunc = Div{parent=rps,width=31,height=10,x=2,y=1} local rps_trp = IndicatorLight{parent=rps_annunc,label="RPS Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - local rps_dmg = IndicatorLight{parent=rps_annunc,label="Damage Critical",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local rps_dmg = IndicatorLight{parent=rps_annunc,label="Damage High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} local rps_exh = IndicatorLight{parent=rps_annunc,label="Excess Heated Coolant",colors=cpair(colors.yellow,colors.gray)} local rps_exw = IndicatorLight{parent=rps_annunc,label="Excess Waste",colors=cpair(colors.yellow,colors.gray)} local rps_tmp = IndicatorLight{parent=rps_annunc,label="Core Temperature High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} @@ -221,7 +221,7 @@ local function init(parent, id) local rps_sfl = IndicatorLight{parent=rps_annunc,label="System Failure",colors=cpair(colors.orange,colors.gray),flash=true,period=period.BLINK_500_MS} u_ps.subscribe("rps_tripped", rps_trp.update) - u_ps.subscribe("dmg_crit", rps_dmg.update) + u_ps.subscribe("dmg_high", rps_dmg.update) u_ps.subscribe("ex_hcool", rps_exh.update) u_ps.subscribe("ex_waste", rps_exw.update) u_ps.subscribe("high_temp", rps_tmp.update) diff --git a/install_manifest.json b/install_manifest.json index 56b8bcf..793d9c9 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"installer": "v0.9m", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.3", "rtu": "v0.12.4", "supervisor": "v0.13.12", "coordinator": "v0.11.11", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4650, "system": 1982, "common": 91075, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75545, "rtu": 82913, "supervisor": 274269, "coordinator": 179964, "pocket": 335}} \ No newline at end of file +{"versions": {"installer": "v0.9m", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.4", "rtu": "v0.12.5", "supervisor": "v0.13.13", "coordinator": "v0.11.12", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4650, "system": 1982, "common": 91071, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75535, "rtu": 83729, "supervisor": 274484, "coordinator": 179960, "pocket": 335}} \ No newline at end of file diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 1d9b52c..0f82522 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -36,7 +36,7 @@ local PCALL_START_MSG = "pcall: Reactor is already active." ---@param is_formed boolean function plc.rps_init(reactor, is_formed) local state_keys = { - dmg_crit = 1, + dmg_high = 1, high_temp = 2, no_coolant = 3, ex_waste = 4, @@ -104,14 +104,14 @@ function plc.rps_init(reactor, is_formed) end end - -- check for critical damage - local function _damage_critical() + -- check for high damage + local function _damage_high() local damage_percent = reactor.getDamagePercent() if damage_percent == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() - elseif not self.state[state_keys.dmg_crit] then - self.state[state_keys.dmg_crit] = damage_percent >= RPS_LIMITS.MAX_DAMAGE_PERCENT + elseif not self.state[state_keys.dmg_high] then + self.state[state_keys.dmg_high] = damage_percent >= RPS_LIMITS.MAX_DAMAGE_PERCENT end end @@ -273,7 +273,7 @@ function plc.rps_init(reactor, is_formed) parallel.waitForAll( _is_formed, _is_force_disabled, - _damage_critical, + _damage_high, _high_temp, _no_coolant, _excess_waste, @@ -294,9 +294,9 @@ function plc.rps_init(reactor, is_formed) elseif self.state[state_keys.force_disabled] then log.warning("RPS: reactor was force disabled") status = RPS_TRIP_CAUSE.FORCE_DISABLED - elseif self.state[state_keys.dmg_crit] then - log.warning("RPS: damage critical") - status = RPS_TRIP_CAUSE.DMG_CRIT + elseif self.state[state_keys.dmg_high] then + log.warning("RPS: damage level high") + status = RPS_TRIP_CAUSE.DMG_HIGH elseif self.state[state_keys.high_temp] then log.warning("RPS: high temperature") status = RPS_TRIP_CAUSE.HIGH_TEMP diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index a41a86e..332ffd3 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,7 @@ local config = require("reactor-plc.config") local plc = require("reactor-plc.plc") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v0.12.3" +local R_PLC_VERSION = "v0.12.4" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index 52e08a1..4248fc2 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "v0.12.4" +local RTU_VERSION = "v0.12.5" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 42e27aa..f8f484c 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -65,7 +65,7 @@ local IO_PORT = { R_AUTO_CTRL = 13, -- active high, if the reactor burn rate is automatic R_SCRAMMED = 14, -- active high, if the reactor is scrammed R_AUTO_SCRAM = 15, -- active high, if the reactor was automatically scrammed - R_DMG_CRIT = 16, -- active high, if the reactor damage is critical + R_DMG_HIGH = 16, -- active high, if the reactor damage is high R_HIGH_TEMP = 17, -- active high, if the reactor is at a high temperature R_NO_COOLANT = 18, -- active high, if the reactor has no coolant R_EXCESS_HC = 19, -- active high, if the reactor has excess heated coolant @@ -108,7 +108,7 @@ function rsio.to_string(port) "R_AUTO_CTRL", "R_SCRAMMED", "R_AUTO_SCRAM", - "R_DMG_CRIT", + "R_DMG_HIGH", "R_HIGH_TEMP", "R_NO_COOLANT", "R_EXCESS_HC", @@ -171,7 +171,7 @@ local RS_DIO_MAP = { { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_AUTO_SCRAM { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- R_DMG_CRIT + -- R_DMG_HIGH { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_HIGH_TEMP { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, @@ -215,7 +215,7 @@ function rsio.get_io_mode(port) IO_MODE.DIGITAL_OUT, -- R_AUTO_CTRL IO_MODE.DIGITAL_OUT, -- R_SCRAMMED IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM - IO_MODE.DIGITAL_OUT, -- R_DMG_CRIT + IO_MODE.DIGITAL_OUT, -- R_DMG_HIGH IO_MODE.DIGITAL_OUT, -- R_HIGH_TEMP IO_MODE.DIGITAL_OUT, -- R_NO_COOLANT IO_MODE.DIGITAL_OUT, -- R_EXCESS_HC diff --git a/scada-common/types.lua b/scada-common/types.lua index 7ad0eb8..1194e1d 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -273,7 +273,7 @@ types.FLUID = { ---@alias rps_trip_cause ---| "ok" ----| "dmg_crit" +---| "dmg_high" ---| "high_temp" ---| "no_coolant" ---| "ex_waste" @@ -288,7 +288,7 @@ types.FLUID = { types.RPS_TRIP_CAUSE = { OK = "ok", - DMG_CRIT = "dmg_crit", + DMG_HIGH = "dmg_high", HIGH_TEMP = "high_temp", NO_COOLANT = "no_coolant", EX_WASTE = "ex_waste", diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index ae4a087..1a1f665 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -102,7 +102,7 @@ function plc.new_session(id, reactor_id, in_queue, out_queue, timeout) rps_trip_cause = "ok", ---@type rps_trip_cause ---@class rps_status rps_status = { - dmg_crit = false, + dmg_high = false, high_temp = false, no_cool = false, ex_waste = false, @@ -169,7 +169,7 @@ function plc.new_session(id, reactor_id, in_queue, out_queue, timeout) local function _copy_rps_status(rps_status) self.sDB.rps_tripped = rps_status[1] self.sDB.rps_trip_cause = rps_status[2] - self.sDB.rps_status.dmg_crit = rps_status[3] + self.sDB.rps_status.dmg_high = rps_status[3] self.sDB.rps_status.high_temp = rps_status[4] self.sDB.rps_status.no_cool = rps_status[5] self.sDB.rps_status.ex_waste = rps_status[6] diff --git a/supervisor/startup.lua b/supervisor/startup.lua index bb5b0b4..169b87e 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "v0.13.12" +local SUPERVISOR_VERSION = "v0.13.13" local print = util.print local println = util.println diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 8bdb911..b6f82ec 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -96,7 +96,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) last_rate_change_ms = 0, ---@type rps_status last_rps_trips = { - dmg_crit = false, + dmg_high = false, high_temp = false, no_cool = false, ex_waste = false, @@ -115,7 +115,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) rps_trip = false, ---@type rps_status rps_status = { - dmg_crit = false, + dmg_high = false, high_temp = false, no_cool = false, ex_waste = false, diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 3ded653..8dd51a8 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -8,10 +8,11 @@ local plc = require("supervisor.session.plc") local qtypes = require("supervisor.session.rtu.qtypes") -local TRI_FAIL = types.TRI_FAIL -local DUMPING_MODE = types.DUMPING_MODE -local PRIO = types.ALARM_PRIORITY -local ALARM_STATE = types.ALARM_STATE +local RPS_TRIP_CAUSE = types.RPS_TRIP_CAUSE +local TRI_FAIL = types.TRI_FAIL +local DUMPING_MODE = types.DUMPING_MODE +local PRIO = types.ALARM_PRIORITY +local ALARM_STATE = types.ALARM_STATE local TBV_RTU_S_DATA = qtypes.TBV_RTU_S_DATA @@ -473,7 +474,7 @@ function logic.update_alarms(self) _update_alarm_state(self, plc_cache.damage >= 100, self.alarms.CriticalDamage) -- Reactor Damage - local rps_dmg_90 = plc_cache.rps_status.dmg_crit and not self.last_rps_trips.dmg_crit + local rps_dmg_90 = plc_cache.rps_status.dmg_high and not self.last_rps_trips.dmg_high _update_alarm_state(self, (plc_cache.damage > 0) or rps_dmg_90, self.alarms.ReactorDamage) -- Over-Temperature @@ -673,31 +674,31 @@ function logic.update_status_text(self) elseif plc_db.rps_tripped then local cause = "unknown" - if plc_db.rps_trip_cause == "ok" then + if plc_db.rps_trip_cause == RPS_TRIP_CAUSE.OK then -- hmm... - elseif plc_db.rps_trip_cause == "dmg_crit" then + elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.DMG_HIGH then cause = "core damage high" - elseif plc_db.rps_trip_cause == "high_temp" then + elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.HIGH_TEMP then cause = "core temperature high" - elseif plc_db.rps_trip_cause == "no_coolant" then + elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.NO_COOLANT then cause = "insufficient coolant" - elseif plc_db.rps_trip_cause == "ex_waste" then + elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.EX_WASTE then cause = "excess waste" - elseif plc_db.rps_trip_cause == "ex_heated_coolant" then + elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.EX_HCOOLANT then cause = "excess heated coolant" - elseif plc_db.rps_trip_cause == "no_fuel" then + elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.NO_FUEL then cause = "insufficient fuel" - elseif plc_db.rps_trip_cause == "fault" then + elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.FAULT then cause = "hardware fault" - elseif plc_db.rps_trip_cause == "timeout" then + elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.TIMEOUT then cause = "connection timed out" - elseif plc_db.rps_trip_cause == "manual" then + elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.MANUAL then cause = "manual operator SCRAM" - elseif plc_db.rps_trip_cause == "automatic" then + elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.AUTOMATIC then cause = "automated system SCRAM" - elseif plc_db.rps_trip_cause == "sys_fail" then + elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.SYS_FAIL then cause = "PLC system failure" - elseif plc_db.rps_trip_cause == "force_disabled" then + elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.FORCE_DISABLED then cause = "reactor force disabled" end @@ -771,7 +772,7 @@ function logic.handle_redstone(self) self.io_ctl.digital_write(IO.R_AUTO_CTRL, self.auto_engaged) self.io_ctl.digital_write(IO.R_SCRAMMED, self.plc_cache.rps_trip) self.io_ctl.digital_write(IO.R_AUTO_SCRAM, self.plc_cache.rps_status.automatic) - self.io_ctl.digital_write(IO.R_DMG_CRIT, self.plc_cache.rps_status.dmg_crit) + self.io_ctl.digital_write(IO.R_DMG_HIGH, self.plc_cache.rps_status.dmg_high) self.io_ctl.digital_write(IO.R_HIGH_TEMP, self.plc_cache.rps_status.high_temp) self.io_ctl.digital_write(IO.R_NO_COOLANT, self.plc_cache.rps_status.no_cool) self.io_ctl.digital_write(IO.R_EXCESS_HC, self.plc_cache.rps_status.ex_hcool) From 83dc1064f7950f182272911251af628d553fbeb6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 22:19:53 -0500 Subject: [PATCH 583/587] #188 refactored RPS no_cool to low_cool --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 6 +++--- install_manifest.json | 2 +- reactor-plc/plc.lua | 18 +++++++++--------- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- scada-common/rsio.lua | 8 ++++---- scada-common/types.lua | 4 ++-- supervisor/session/plc.lua | 4 ++-- supervisor/startup.lua | 2 +- supervisor/unit.lua | 4 ++-- supervisor/unitlogic.lua | 8 ++++---- 12 files changed, 31 insertions(+), 31 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 985afae..e954710 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "v0.11.12" +local COORDINATOR_VERSION = "v0.11.13" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index f4f633e..87f86de 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -210,12 +210,12 @@ local function init(parent, id) local rps_annunc = Div{parent=rps,width=31,height=10,x=2,y=1} local rps_trp = IndicatorLight{parent=rps_annunc,label="RPS Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - local rps_dmg = IndicatorLight{parent=rps_annunc,label="Damage High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local rps_dmg = IndicatorLight{parent=rps_annunc,label="Damage Level High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} local rps_exh = IndicatorLight{parent=rps_annunc,label="Excess Heated Coolant",colors=cpair(colors.yellow,colors.gray)} local rps_exw = IndicatorLight{parent=rps_annunc,label="Excess Waste",colors=cpair(colors.yellow,colors.gray)} local rps_tmp = IndicatorLight{parent=rps_annunc,label="Core Temperature High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} local rps_nof = IndicatorLight{parent=rps_annunc,label="No Fuel",colors=cpair(colors.yellow,colors.gray)} - local rps_noc = IndicatorLight{parent=rps_annunc,label="Coolant Level Low Low",colors=cpair(colors.yellow,colors.gray)} + local rps_loc = IndicatorLight{parent=rps_annunc,label="Coolant Level Low Low",colors=cpair(colors.yellow,colors.gray)} local rps_flt = IndicatorLight{parent=rps_annunc,label="PPM Fault",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} local rps_tmo = IndicatorLight{parent=rps_annunc,label="Connection Timeout",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} local rps_sfl = IndicatorLight{parent=rps_annunc,label="System Failure",colors=cpair(colors.orange,colors.gray),flash=true,period=period.BLINK_500_MS} @@ -226,7 +226,7 @@ local function init(parent, id) u_ps.subscribe("ex_waste", rps_exw.update) u_ps.subscribe("high_temp", rps_tmp.update) u_ps.subscribe("no_fuel", rps_nof.update) - u_ps.subscribe("no_cool", rps_noc.update) + u_ps.subscribe("low_cool", rps_loc.update) u_ps.subscribe("fault", rps_flt.update) u_ps.subscribe("timeout", rps_tmo.update) u_ps.subscribe("sys_fail", rps_sfl.update) diff --git a/install_manifest.json b/install_manifest.json index 793d9c9..694ae3b 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"installer": "v0.9m", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.4", "rtu": "v0.12.5", "supervisor": "v0.13.13", "coordinator": "v0.11.12", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4650, "system": 1982, "common": 91071, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75535, "rtu": 83729, "supervisor": 274484, "coordinator": 179960, "pocket": 335}} \ No newline at end of file +{"versions": {"installer": "v0.9m", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.5", "rtu": "v0.12.6", "supervisor": "v0.13.14", "coordinator": "v0.11.13", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4650, "system": 1982, "common": 91083, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75536, "rtu": 83729, "supervisor": 274492, "coordinator": 179967, "pocket": 335}} \ No newline at end of file diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 0f82522..b100407 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -38,7 +38,7 @@ function plc.rps_init(reactor, is_formed) local state_keys = { dmg_high = 1, high_temp = 2, - no_coolant = 3, + low_coolant = 3, ex_waste = 4, ex_hcoolant = 5, no_fuel = 6, @@ -127,14 +127,14 @@ function plc.rps_init(reactor, is_formed) end end - -- check if there is no coolant (<2% filled) - local function _no_coolant() + -- check if there is very low coolant + local function _low_coolant() local coolant_filled = reactor.getCoolantFilledPercentage() if coolant_filled == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() - elseif not self.state[state_keys.no_coolant] then - self.state[state_keys.no_coolant] = coolant_filled < RPS_LIMITS.MIN_COOLANT_FILL + elseif not self.state[state_keys.low_coolant] then + self.state[state_keys.low_coolant] = coolant_filled < RPS_LIMITS.MIN_COOLANT_FILL end end @@ -275,7 +275,7 @@ function plc.rps_init(reactor, is_formed) _is_force_disabled, _damage_high, _high_temp, - _no_coolant, + _low_coolant, _excess_waste, _excess_heated_coolant, _insufficient_fuel @@ -300,9 +300,9 @@ function plc.rps_init(reactor, is_formed) elseif self.state[state_keys.high_temp] then log.warning("RPS: high temperature") status = RPS_TRIP_CAUSE.HIGH_TEMP - elseif self.state[state_keys.no_coolant] then - log.warning("RPS: no coolant") - status = RPS_TRIP_CAUSE.NO_COOLANT + elseif self.state[state_keys.low_coolant] then + log.warning("RPS: low coolant") + status = RPS_TRIP_CAUSE.LOW_COOLANT elseif self.state[state_keys.ex_waste] then log.warning("RPS: full waste") status = RPS_TRIP_CAUSE.EX_WASTE diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 332ffd3..03474eb 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,7 @@ local config = require("reactor-plc.config") local plc = require("reactor-plc.plc") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v0.12.4" +local R_PLC_VERSION = "v0.12.5" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index 4248fc2..7f3be68 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "v0.12.5" +local RTU_VERSION = "v0.12.6" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index f8f484c..6698836 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -67,7 +67,7 @@ local IO_PORT = { R_AUTO_SCRAM = 15, -- active high, if the reactor was automatically scrammed R_DMG_HIGH = 16, -- active high, if the reactor damage is high R_HIGH_TEMP = 17, -- active high, if the reactor is at a high temperature - R_NO_COOLANT = 18, -- active high, if the reactor has no coolant + R_LOW_COOLANT = 18, -- active high, if the reactor has very low coolant R_EXCESS_HC = 19, -- active high, if the reactor has excess heated coolant R_EXCESS_WS = 20, -- active high, if the reactor has excess waste R_INSUFF_FUEL = 21, -- active high, if the reactor has insufficent fuel @@ -110,7 +110,7 @@ function rsio.to_string(port) "R_AUTO_SCRAM", "R_DMG_HIGH", "R_HIGH_TEMP", - "R_NO_COOLANT", + "R_LOW_COOLANT", "R_EXCESS_HC", "R_EXCESS_WS", "R_INSUFF_FUEL", @@ -175,7 +175,7 @@ local RS_DIO_MAP = { { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_HIGH_TEMP { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- R_NO_COOLANT + -- R_LOW_COOLANT { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_EXCESS_HC { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, @@ -217,7 +217,7 @@ function rsio.get_io_mode(port) IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM IO_MODE.DIGITAL_OUT, -- R_DMG_HIGH IO_MODE.DIGITAL_OUT, -- R_HIGH_TEMP - IO_MODE.DIGITAL_OUT, -- R_NO_COOLANT + IO_MODE.DIGITAL_OUT, -- R_LOW_COOLANT IO_MODE.DIGITAL_OUT, -- R_EXCESS_HC IO_MODE.DIGITAL_OUT, -- R_EXCESS_WS IO_MODE.DIGITAL_OUT, -- R_INSUFF_FUEL diff --git a/scada-common/types.lua b/scada-common/types.lua index 1194e1d..02e8d32 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -275,7 +275,7 @@ types.FLUID = { ---| "ok" ---| "dmg_high" ---| "high_temp" ----| "no_coolant" +---| "low_coolant" ---| "ex_waste" ---| "ex_heated_coolant" ---| "no_fuel" @@ -290,7 +290,7 @@ types.RPS_TRIP_CAUSE = { OK = "ok", DMG_HIGH = "dmg_high", HIGH_TEMP = "high_temp", - NO_COOLANT = "no_coolant", + LOW_COOLANT = "low_coolant", EX_WASTE = "ex_waste", EX_HCOOLANT = "ex_heated_coolant", NO_FUEL = "no_fuel", diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 1a1f665..8a4d255 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -104,7 +104,7 @@ function plc.new_session(id, reactor_id, in_queue, out_queue, timeout) rps_status = { dmg_high = false, high_temp = false, - no_cool = false, + low_cool = false, ex_waste = false, ex_hcool = false, no_fuel = false, @@ -171,7 +171,7 @@ function plc.new_session(id, reactor_id, in_queue, out_queue, timeout) self.sDB.rps_trip_cause = rps_status[2] self.sDB.rps_status.dmg_high = rps_status[3] self.sDB.rps_status.high_temp = rps_status[4] - self.sDB.rps_status.no_cool = rps_status[5] + self.sDB.rps_status.low_cool = rps_status[5] self.sDB.rps_status.ex_waste = rps_status[6] self.sDB.rps_status.ex_hcool = rps_status[7] self.sDB.rps_status.no_fuel = rps_status[8] diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 169b87e..21c9059 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "v0.13.13" +local SUPERVISOR_VERSION = "v0.13.14" local print = util.print local println = util.println diff --git a/supervisor/unit.lua b/supervisor/unit.lua index b6f82ec..f218aeb 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -98,7 +98,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) last_rps_trips = { dmg_high = false, high_temp = false, - no_cool = false, + low_cool = false, ex_waste = false, ex_hcool = false, no_fuel = false, @@ -117,7 +117,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) rps_status = { dmg_high = false, high_temp = false, - no_cool = false, + low_cool = false, ex_waste = false, ex_hcool = false, no_fuel = false, diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 8dd51a8..969f2e0 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -118,7 +118,7 @@ function logic.update_annunciator(self) self.db.annunciator.ReactorSCRAM = plc_db.rps_tripped self.db.annunciator.ManualReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.MANUAL self.db.annunciator.AutoReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.AUTOMATIC - self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.no_cool) + self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.low_cool) self.db.annunciator.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < flow_low self.db.annunciator.CoolantLevelLow = plc_db.mek_status.ccool_fill < ANNUNC_LIMS.CoolantLevelLow self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > ANNUNC_LIMS.ReactorTempHigh @@ -680,7 +680,7 @@ function logic.update_status_text(self) cause = "core damage high" elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.HIGH_TEMP then cause = "core temperature high" - elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.NO_COOLANT then + elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.LOW_COOLANT then cause = "insufficient coolant" elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.EX_WASTE then cause = "excess waste" @@ -774,7 +774,7 @@ function logic.handle_redstone(self) self.io_ctl.digital_write(IO.R_AUTO_SCRAM, self.plc_cache.rps_status.automatic) self.io_ctl.digital_write(IO.R_DMG_HIGH, self.plc_cache.rps_status.dmg_high) self.io_ctl.digital_write(IO.R_HIGH_TEMP, self.plc_cache.rps_status.high_temp) - self.io_ctl.digital_write(IO.R_NO_COOLANT, self.plc_cache.rps_status.no_cool) + self.io_ctl.digital_write(IO.R_LOW_COOLANT, self.plc_cache.rps_status.low_cool) self.io_ctl.digital_write(IO.R_EXCESS_HC, self.plc_cache.rps_status.ex_hcool) self.io_ctl.digital_write(IO.R_EXCESS_WS, self.plc_cache.rps_status.ex_waste) self.io_ctl.digital_write(IO.R_INSUFF_FUEL, self.plc_cache.rps_status.no_fuel) @@ -797,7 +797,7 @@ function logic.handle_redstone(self) -- Emergency Coolant -- ----------------------- - local enable_emer_cool = self.plc_cache.rps_status.no_cool or + local enable_emer_cool = self.plc_cache.rps_status.low_cool or (self.auto_engaged and self.db.annunciator.CoolantLevelLow and is_active(self.alarms.ReactorOverTemp)) if not self.plc_cache.rps_trip then From 9eddab2c2304aa62b301b17dcfe3044c06cbd8c6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 22:32:13 -0500 Subject: [PATCH 584/587] #188 refactored RPS dmg_high to high_dmg --- coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 2 +- install_manifest.json | 2 +- reactor-plc/plc.lua | 16 ++++++++-------- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- scada-common/rsio.lua | 8 ++++---- scada-common/types.lua | 4 ++-- supervisor/session/plc.lua | 4 ++-- supervisor/startup.lua | 2 +- supervisor/unit.lua | 4 ++-- supervisor/unitlogic.lua | 6 +++--- 12 files changed, 27 insertions(+), 27 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index e954710..0b192a4 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "v0.11.13" +local COORDINATOR_VERSION = "v0.12.0" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 87f86de..b6fc8af 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -221,7 +221,7 @@ local function init(parent, id) local rps_sfl = IndicatorLight{parent=rps_annunc,label="System Failure",colors=cpair(colors.orange,colors.gray),flash=true,period=period.BLINK_500_MS} u_ps.subscribe("rps_tripped", rps_trp.update) - u_ps.subscribe("dmg_high", rps_dmg.update) + u_ps.subscribe("high_dmg", rps_dmg.update) u_ps.subscribe("ex_hcool", rps_exh.update) u_ps.subscribe("ex_waste", rps_exw.update) u_ps.subscribe("high_temp", rps_tmp.update) diff --git a/install_manifest.json b/install_manifest.json index 694ae3b..5533f55 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"installer": "v0.9m", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v0.12.5", "rtu": "v0.12.6", "supervisor": "v0.13.14", "coordinator": "v0.11.13", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4650, "system": 1982, "common": 91083, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75536, "rtu": 83729, "supervisor": 274492, "coordinator": 179967, "pocket": 335}} \ No newline at end of file +{"versions": {"installer": "v0.9m", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v1.0.0", "rtu": "v0.13.0", "supervisor": "v0.14.0", "coordinator": "v0.12.0", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4647, "system": 1982, "common": 91083, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75529, "rtu": 83729, "supervisor": 274491, "coordinator": 179966, "pocket": 335}} \ No newline at end of file diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index b100407..dce6a73 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -36,7 +36,7 @@ local PCALL_START_MSG = "pcall: Reactor is already active." ---@param is_formed boolean function plc.rps_init(reactor, is_formed) local state_keys = { - dmg_high = 1, + high_dmg = 1, high_temp = 2, low_coolant = 3, ex_waste = 4, @@ -105,13 +105,13 @@ function plc.rps_init(reactor, is_formed) end -- check for high damage - local function _damage_high() + local function _high_damage() local damage_percent = reactor.getDamagePercent() if damage_percent == ppm.ACCESS_FAULT then -- lost the peripheral or terminated, handled later _set_fault() - elseif not self.state[state_keys.dmg_high] then - self.state[state_keys.dmg_high] = damage_percent >= RPS_LIMITS.MAX_DAMAGE_PERCENT + elseif not self.state[state_keys.high_dmg] then + self.state[state_keys.high_dmg] = damage_percent >= RPS_LIMITS.MAX_DAMAGE_PERCENT end end @@ -273,7 +273,7 @@ function plc.rps_init(reactor, is_formed) parallel.waitForAll( _is_formed, _is_force_disabled, - _damage_high, + _high_damage, _high_temp, _low_coolant, _excess_waste, @@ -294,9 +294,9 @@ function plc.rps_init(reactor, is_formed) elseif self.state[state_keys.force_disabled] then log.warning("RPS: reactor was force disabled") status = RPS_TRIP_CAUSE.FORCE_DISABLED - elseif self.state[state_keys.dmg_high] then - log.warning("RPS: damage level high") - status = RPS_TRIP_CAUSE.DMG_HIGH + elseif self.state[state_keys.high_dmg] then + log.warning("RPS: high damage") + status = RPS_TRIP_CAUSE.HIGH_DMG elseif self.state[state_keys.high_temp] then log.warning("RPS: high temperature") status = RPS_TRIP_CAUSE.HIGH_TEMP diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 03474eb..5ad5ca4 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -14,7 +14,7 @@ local config = require("reactor-plc.config") local plc = require("reactor-plc.plc") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v0.12.5" +local R_PLC_VERSION = "v1.0.0" local print = util.print local println = util.println diff --git a/rtu/startup.lua b/rtu/startup.lua index 7f3be68..df79496 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "v0.12.6" +local RTU_VERSION = "v0.13.0" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 6698836..29acfd2 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -65,7 +65,7 @@ local IO_PORT = { R_AUTO_CTRL = 13, -- active high, if the reactor burn rate is automatic R_SCRAMMED = 14, -- active high, if the reactor is scrammed R_AUTO_SCRAM = 15, -- active high, if the reactor was automatically scrammed - R_DMG_HIGH = 16, -- active high, if the reactor damage is high + R_HIGH_DMG = 16, -- active high, if the reactor damage is high R_HIGH_TEMP = 17, -- active high, if the reactor is at a high temperature R_LOW_COOLANT = 18, -- active high, if the reactor has very low coolant R_EXCESS_HC = 19, -- active high, if the reactor has excess heated coolant @@ -108,7 +108,7 @@ function rsio.to_string(port) "R_AUTO_CTRL", "R_SCRAMMED", "R_AUTO_SCRAM", - "R_DMG_HIGH", + "R_HIGH_DMG", "R_HIGH_TEMP", "R_LOW_COOLANT", "R_EXCESS_HC", @@ -171,7 +171,7 @@ local RS_DIO_MAP = { { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_AUTO_SCRAM { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- R_DMG_HIGH + -- R_HIGH_DMG { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, -- R_HIGH_TEMP { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, @@ -215,7 +215,7 @@ function rsio.get_io_mode(port) IO_MODE.DIGITAL_OUT, -- R_AUTO_CTRL IO_MODE.DIGITAL_OUT, -- R_SCRAMMED IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM - IO_MODE.DIGITAL_OUT, -- R_DMG_HIGH + IO_MODE.DIGITAL_OUT, -- R_HIGH_DMG IO_MODE.DIGITAL_OUT, -- R_HIGH_TEMP IO_MODE.DIGITAL_OUT, -- R_LOW_COOLANT IO_MODE.DIGITAL_OUT, -- R_EXCESS_HC diff --git a/scada-common/types.lua b/scada-common/types.lua index 02e8d32..9beb1e6 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -273,7 +273,7 @@ types.FLUID = { ---@alias rps_trip_cause ---| "ok" ----| "dmg_high" +---| "high_dmg" ---| "high_temp" ---| "low_coolant" ---| "ex_waste" @@ -288,7 +288,7 @@ types.FLUID = { types.RPS_TRIP_CAUSE = { OK = "ok", - DMG_HIGH = "dmg_high", + HIGH_DMG = "high_dmg", HIGH_TEMP = "high_temp", LOW_COOLANT = "low_coolant", EX_WASTE = "ex_waste", diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 8a4d255..3bba325 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -102,7 +102,7 @@ function plc.new_session(id, reactor_id, in_queue, out_queue, timeout) rps_trip_cause = "ok", ---@type rps_trip_cause ---@class rps_status rps_status = { - dmg_high = false, + high_dmg = false, high_temp = false, low_cool = false, ex_waste = false, @@ -169,7 +169,7 @@ function plc.new_session(id, reactor_id, in_queue, out_queue, timeout) local function _copy_rps_status(rps_status) self.sDB.rps_tripped = rps_status[1] self.sDB.rps_trip_cause = rps_status[2] - self.sDB.rps_status.dmg_high = rps_status[3] + self.sDB.rps_status.high_dmg = rps_status[3] self.sDB.rps_status.high_temp = rps_status[4] self.sDB.rps_status.low_cool = rps_status[5] self.sDB.rps_status.ex_waste = rps_status[6] diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 21c9059..2d0c577 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "v0.13.14" +local SUPERVISOR_VERSION = "v0.14.0" local print = util.print local println = util.println diff --git a/supervisor/unit.lua b/supervisor/unit.lua index f218aeb..abdcbb0 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -96,7 +96,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) last_rate_change_ms = 0, ---@type rps_status last_rps_trips = { - dmg_high = false, + high_dmg = false, high_temp = false, low_cool = false, ex_waste = false, @@ -115,7 +115,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) rps_trip = false, ---@type rps_status rps_status = { - dmg_high = false, + high_dmg = false, high_temp = false, low_cool = false, ex_waste = false, diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 969f2e0..4aa41d2 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -474,7 +474,7 @@ function logic.update_alarms(self) _update_alarm_state(self, plc_cache.damage >= 100, self.alarms.CriticalDamage) -- Reactor Damage - local rps_dmg_90 = plc_cache.rps_status.dmg_high and not self.last_rps_trips.dmg_high + local rps_dmg_90 = plc_cache.rps_status.high_dmg and not self.last_rps_trips.high_dmg _update_alarm_state(self, (plc_cache.damage > 0) or rps_dmg_90, self.alarms.ReactorDamage) -- Over-Temperature @@ -676,7 +676,7 @@ function logic.update_status_text(self) if plc_db.rps_trip_cause == RPS_TRIP_CAUSE.OK then -- hmm... - elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.DMG_HIGH then + elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.HIGH_DMG then cause = "core damage high" elseif plc_db.rps_trip_cause == RPS_TRIP_CAUSE.HIGH_TEMP then cause = "core temperature high" @@ -772,7 +772,7 @@ function logic.handle_redstone(self) self.io_ctl.digital_write(IO.R_AUTO_CTRL, self.auto_engaged) self.io_ctl.digital_write(IO.R_SCRAMMED, self.plc_cache.rps_trip) self.io_ctl.digital_write(IO.R_AUTO_SCRAM, self.plc_cache.rps_status.automatic) - self.io_ctl.digital_write(IO.R_DMG_HIGH, self.plc_cache.rps_status.dmg_high) + self.io_ctl.digital_write(IO.R_HIGH_DMG, self.plc_cache.rps_status.high_dmg) self.io_ctl.digital_write(IO.R_HIGH_TEMP, self.plc_cache.rps_status.high_temp) self.io_ctl.digital_write(IO.R_LOW_COOLANT, self.plc_cache.rps_status.low_cool) self.io_ctl.digital_write(IO.R_EXCESS_HC, self.plc_cache.rps_status.ex_hcool) From 2a681d1d37db3084e79c17f8a8f44e61bad32a3e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Mar 2023 23:01:24 -0500 Subject: [PATCH 585/587] disable debug prints and update ccmsi version for release --- ccmsi.lua | 2 +- install_manifest.json | 2 +- scada-common/log.lua | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index efb23ef..f728d01 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -20,7 +20,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local CCMSI_VERSION = "v0.9m" +local CCMSI_VERSION = "v1.0" local install_dir = "/.install-cache" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/" diff --git a/install_manifest.json b/install_manifest.json index 5533f55..80859f2 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"installer": "v0.9m", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v1.0.0", "rtu": "v0.13.0", "supervisor": "v0.14.0", "coordinator": "v0.12.0", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4647, "system": 1982, "common": 91083, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75529, "rtu": 83729, "supervisor": 274491, "coordinator": 179966, "pocket": 335}} \ No newline at end of file +{"versions": {"installer": "v1.0", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v1.0.0", "rtu": "v0.13.0", "supervisor": "v0.14.0", "coordinator": "v0.12.0", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4646, "system": 1982, "common": 91084, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75529, "rtu": 83729, "supervisor": 274491, "coordinator": 179966, "pocket": 335}} \ No newline at end of file diff --git a/scada-common/log.lua b/scada-common/log.lua index 30f785d..424bf55 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -16,7 +16,7 @@ local MODE = { log.MODE = MODE -- whether to log debug messages or not -local LOG_DEBUG = true +local LOG_DEBUG = false local log_sys = { path = "/log.txt", From 66deabcf5d2bcf676c1e2aa36e7cf5a780e31402 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 5 Mar 2023 11:52:03 -0500 Subject: [PATCH 586/587] HIGH CHARGE on induction matrix is now yellow not red --- coordinator/startup.lua | 2 +- coordinator/ui/style.lua | 2 +- install_manifest.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 0b192a4..e3eac0a 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "v0.12.0" +local COORDINATOR_VERSION = "v0.12.1" local print = util.print local println = util.println diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index 4b4bf46..74923f2 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -149,7 +149,7 @@ style.imatrix = { text = "LOW CHARGE" }, { - color = cpair(colors.black, colors.red), + color = cpair(colors.black, colors.yellow), text = "HIGH CHARGE" }, } diff --git a/install_manifest.json b/install_manifest.json index 80859f2..a874c59 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"installer": "v1.0", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v1.0.0", "rtu": "v0.13.0", "supervisor": "v0.14.0", "coordinator": "v0.12.0", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4646, "system": 1982, "common": 91084, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75529, "rtu": 83729, "supervisor": 274491, "coordinator": 179966, "pocket": 335}} \ No newline at end of file +{"versions": {"installer": "v1.0", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v1.0.0", "rtu": "v0.13.0", "supervisor": "v0.14.0", "coordinator": "v0.12.1", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4646, "system": 1982, "common": 91084, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75529, "rtu": 82913, "supervisor": 274491, "coordinator": 179969, "pocket": 335}} \ No newline at end of file From 8b1e7cb9333fcbf428eeabf575ac0cd8b6e6e5a3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 5 Mar 2023 12:35:36 -0500 Subject: [PATCH 587/587] added energy bar to turbine overview --- coordinator/startup.lua | 2 +- coordinator/ui/components/turbine.lua | 8 +++++++- install_manifest.json | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index e3eac0a..39629fd 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "v0.12.1" +local COORDINATOR_VERSION = "v0.12.2" local print = util.print local println = util.println diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua index ef09cee..e4d6967 100644 --- a/coordinator/ui/components/turbine.lua +++ b/coordinator/ui/components/turbine.lua @@ -5,6 +5,7 @@ local style = require("coordinator.ui.style") local core = require("graphics.core") local Rectangle = require("graphics.elements.rectangle") +local TextBox = require("graphics.elements.textbox") local DataIndicator = require("graphics.elements.indicators.data") local PowerIndicator = require("graphics.elements.indicators.power") @@ -33,9 +34,14 @@ local function new_view(root, x, y, ps) ps.subscribe("prod_rate", function (val) prod_rate.update(util.joules_to_fe(val)) end) ps.subscribe("flow_rate", flow_rate.update) - local steam = VerticalBar{parent=turbine,x=2,y=1,fg_bg=cpair(colors.white,colors.gray),height=5,width=2} + local steam = VerticalBar{parent=turbine,x=2,y=1,fg_bg=cpair(colors.white,colors.gray),height=4,width=1} + local energy = VerticalBar{parent=turbine,x=3,y=1,fg_bg=cpair(colors.green,colors.gray),height=4,width=1} + + TextBox{parent=turbine,text="S",x=2,y=5,height=1,width=1,fg_bg=text_fg_bg} + TextBox{parent=turbine,text="E",x=3,y=5,height=1,width=1,fg_bg=text_fg_bg} ps.subscribe("steam_fill", steam.update) + ps.subscribe("energy_fill", energy.update) end return new_view diff --git a/install_manifest.json b/install_manifest.json index a874c59..78bb226 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"installer": "v1.0", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v1.0.0", "rtu": "v0.13.0", "supervisor": "v0.14.0", "coordinator": "v0.12.1", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4646, "system": 1982, "common": 91084, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75529, "rtu": 82913, "supervisor": 274491, "coordinator": 179969, "pocket": 335}} \ No newline at end of file +{"versions": {"installer": "v1.0", "bootloader": "0.2", "comms": "1.4.0", "reactor-plc": "v1.0.0", "rtu": "v0.13.0", "supervisor": "v0.14.0", "coordinator": "v0.12.2", "pocket": "alpha-v0.0.0"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/threads.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua"], "rtu": ["rtu/threads.lua", "rtu/rtu.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/apisessions.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/unit_waiting.lua", "coordinator/ui/components/turbine.lua"], "pocket": ["pocket/config.lua", "pocket/startup.lua"]}, "depends": {"reactor-plc": ["system", "common"], "rtu": ["system", "common"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 4646, "system": 1982, "common": 91084, "graphics": 99858, "lockbox": 100797, "reactor-plc": 75529, "rtu": 82913, "supervisor": 274491, "coordinator": 180346, "pocket": 335}} \ No newline at end of file