From 36557fc34551d9fad3499c178af9116f9f5476bf Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 21 Sep 2022 15:53:51 -0400 Subject: [PATCH] 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()