From 19869416af1739c9be57466ba71127e8ed08921b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 23 Mar 2024 00:26:58 -0400 Subject: [PATCH] #434 #454 PPM improvements and undefined function overhaul --- reactor-plc/plc.lua | 6 +-- reactor-plc/startup.lua | 10 +---- rtu/dev/boilerv_rtu.lua | 67 +++++++++++++++------------------- rtu/dev/dynamicv_rtu.lua | 33 ++++++----------- rtu/dev/imatrix_rtu.lua | 41 ++++++++------------- rtu/dev/sps_rtu.lua | 51 +++++++++++--------------- rtu/dev/turbinev_rtu.lua | 65 ++++++++++++++------------------- rtu/rtu.lua | 35 +++++++++++++----- rtu/startup.lua | 15 ++++---- rtu/threads.lua | 79 +++++----------------------------------- scada-common/ppm.lua | 47 ++++++++++++++++-------- scada-common/util.lua | 2 +- 12 files changed, 186 insertions(+), 265 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 560f3ba..1a6b32f 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -144,9 +144,9 @@ function plc.rps_init(reactor, is_formed) local function _check_and_handle_ppm_call(result) if result == ppm.ACCESS_FAULT then _set_fault() - elseif result == ppm.UNDEFINED_FIELD then - _set_fault() - self.formed = false + + -- if undefined, then the reactor isn't formed + if reactor.__p_last_fault() == ppm.UNDEFINED_FIELD then self.formed = false end else return true end return false diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index ea51bf6..63167c7 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc") local renderer = require("reactor-plc.renderer") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v1.7.0" +local R_PLC_VERSION = "v1.7.1" local println = util.println local println_ts = util.println_ts @@ -144,13 +144,6 @@ local function main() println("init> fission reactor is not formed") log.warning("init> reactor logic adapter present, but reactor is not formed") - plc_state.degraded = true - plc_state.reactor_formed = false - elseif smem_dev.reactor.getStatus() == ppm.UNDEFINED_FIELD then - -- reactor formed after ppm.mount_all was called - println("init> fission reactor was not formed") - log.warning("init> reactor reported formed, but multiblock functions are not available") - plc_state.degraded = true plc_state.reactor_formed = false end @@ -185,6 +178,7 @@ local function main() local message plc_state.fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode) + -- ...or not if not plc_state.fp_ok then println_ts(util.c("UI error: ", message)) println("init> running without front panel") diff --git a/rtu/dev/boilerv_rtu.lua b/rtu/dev/boilerv_rtu.lua index cb4f701..24ffff1 100644 --- a/rtu/dev/boilerv_rtu.lua +++ b/rtu/dev/boilerv_rtu.lua @@ -9,58 +9,49 @@ local boilerv_rtu = {} function boilerv_rtu.new(boiler) local unit = rtu.init_unit(boiler) - -- disable auto fault clearing - boiler.__p_clear_fault() - boiler.__p_disable_afc() - -- discrete inputs -- - unit.connect_di(boiler.isFormed) + unit.connect_di("isFormed") -- coils -- -- none -- input registers -- -- multiblock properties - 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) + unit.connect_input_reg("getLength") + unit.connect_input_reg("getWidth") + unit.connect_input_reg("getHeight") + unit.connect_input_reg("getMinPos") + unit.connect_input_reg("getMaxPos") -- 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) + unit.connect_input_reg("getBoilCapacity") + unit.connect_input_reg("getSteamCapacity") + unit.connect_input_reg("getWaterCapacity") + unit.connect_input_reg("getHeatedCoolantCapacity") + unit.connect_input_reg("getCooledCoolantCapacity") + unit.connect_input_reg("getSuperheaters") + unit.connect_input_reg("getMaxBoilRate") -- current state - unit.connect_input_reg(boiler.getTemperature) - unit.connect_input_reg(boiler.getBoilRate) - unit.connect_input_reg(boiler.getEnvironmentalLoss) + unit.connect_input_reg("getTemperature") + unit.connect_input_reg("getBoilRate") + unit.connect_input_reg("getEnvironmentalLoss") -- 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) + unit.connect_input_reg("getSteam") + unit.connect_input_reg("getSteamNeeded") + unit.connect_input_reg("getSteamFilledPercentage") + unit.connect_input_reg("getWater") + unit.connect_input_reg("getWaterNeeded") + unit.connect_input_reg("getWaterFilledPercentage") + unit.connect_input_reg("getHeatedCoolant") + unit.connect_input_reg("getHeatedCoolantNeeded") + unit.connect_input_reg("getHeatedCoolantFilledPercentage") + unit.connect_input_reg("getCooledCoolant") + unit.connect_input_reg("getCooledCoolantNeeded") + unit.connect_input_reg("getCooledCoolantFilledPercentage") -- holding registers -- -- none - -- check if any calls faulted - local faulted = boiler.__p_is_faulted() - boiler.__p_clear_fault() - boiler.__p_enable_afc() - - return unit.interface(), faulted + return unit.interface(), false end return boilerv_rtu diff --git a/rtu/dev/dynamicv_rtu.lua b/rtu/dev/dynamicv_rtu.lua index 987e366..4ef10cb 100644 --- a/rtu/dev/dynamicv_rtu.lua +++ b/rtu/dev/dynamicv_rtu.lua @@ -9,12 +9,8 @@ local dynamicv_rtu = {} function dynamicv_rtu.new(dynamic_tank) local unit = rtu.init_unit(dynamic_tank) - -- disable auto fault clearing - dynamic_tank.__p_clear_fault() - dynamic_tank.__p_disable_afc() - -- discrete inputs -- - unit.connect_di(dynamic_tank.isFormed) + unit.connect_di("isFormed") -- coils -- unit.connect_coil(function () dynamic_tank.incrementContainerEditMode() end, function () end) @@ -22,27 +18,22 @@ function dynamicv_rtu.new(dynamic_tank) -- input registers -- -- multiblock properties - unit.connect_input_reg(dynamic_tank.getLength) - unit.connect_input_reg(dynamic_tank.getWidth) - unit.connect_input_reg(dynamic_tank.getHeight) - unit.connect_input_reg(dynamic_tank.getMinPos) - unit.connect_input_reg(dynamic_tank.getMaxPos) + unit.connect_input_reg("getLength") + unit.connect_input_reg("getWidth") + unit.connect_input_reg("getHeight") + unit.connect_input_reg("getMinPos") + unit.connect_input_reg("getMaxPos") -- build properties - unit.connect_input_reg(dynamic_tank.getTankCapacity) - unit.connect_input_reg(dynamic_tank.getChemicalTankCapacity) + unit.connect_input_reg("getTankCapacity") + unit.connect_input_reg("getChemicalTankCapacity") -- tanks/containers - unit.connect_input_reg(dynamic_tank.getStored) - unit.connect_input_reg(dynamic_tank.getFilledPercentage) + unit.connect_input_reg("getStored") + unit.connect_input_reg("getFilledPercentage") -- holding registers -- - unit.connect_holding_reg(dynamic_tank.getContainerEditMode, dynamic_tank.setContainerEditMode) + unit.connect_holding_reg("getContainerEditMode", "setContainerEditMode") - -- check if any calls faulted - local faulted = dynamic_tank.__p_is_faulted() - dynamic_tank.__p_clear_fault() - dynamic_tank.__p_enable_afc() - - return unit.interface(), faulted + return unit.interface(), false end return dynamicv_rtu diff --git a/rtu/dev/imatrix_rtu.lua b/rtu/dev/imatrix_rtu.lua index f3b7f5c..61de630 100644 --- a/rtu/dev/imatrix_rtu.lua +++ b/rtu/dev/imatrix_rtu.lua @@ -9,45 +9,36 @@ local imatrix_rtu = {} function imatrix_rtu.new(imatrix) local unit = rtu.init_unit(imatrix) - -- disable auto fault clearing - imatrix.__p_clear_fault() - imatrix.__p_disable_afc() - -- discrete inputs -- - unit.connect_di(imatrix.isFormed) + unit.connect_di("isFormed") -- coils -- -- none -- input registers -- -- multiblock properties - 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) + unit.connect_input_reg("getLength") + unit.connect_input_reg("getWidth") + unit.connect_input_reg("getHeight") + unit.connect_input_reg("getMinPos") + unit.connect_input_reg("getMaxPos") -- build properties - unit.connect_input_reg(imatrix.getMaxEnergy) - unit.connect_input_reg(imatrix.getTransferCap) - unit.connect_input_reg(imatrix.getInstalledCells) - unit.connect_input_reg(imatrix.getInstalledProviders) + unit.connect_input_reg("getMaxEnergy") + unit.connect_input_reg("getTransferCap") + unit.connect_input_reg("getInstalledCells") + unit.connect_input_reg("getInstalledProviders") -- I/O rates - unit.connect_input_reg(imatrix.getLastInput) - unit.connect_input_reg(imatrix.getLastOutput) + unit.connect_input_reg("getLastInput") + unit.connect_input_reg("getLastOutput") -- tanks - unit.connect_input_reg(imatrix.getEnergy) - unit.connect_input_reg(imatrix.getEnergyNeeded) - unit.connect_input_reg(imatrix.getEnergyFilledPercentage) + unit.connect_input_reg("getEnergy") + unit.connect_input_reg("getEnergyNeeded") + unit.connect_input_reg("getEnergyFilledPercentage") -- holding registers -- -- none - -- check if any calls faulted - local faulted = imatrix.__p_is_faulted() - imatrix.__p_clear_fault() - imatrix.__p_enable_afc() - - return unit.interface(), faulted + return unit.interface(), false end return imatrix_rtu diff --git a/rtu/dev/sps_rtu.lua b/rtu/dev/sps_rtu.lua index 448dc48..14a6e07 100644 --- a/rtu/dev/sps_rtu.lua +++ b/rtu/dev/sps_rtu.lua @@ -9,50 +9,41 @@ local sps_rtu = {} function sps_rtu.new(sps) local unit = rtu.init_unit(sps) - -- disable auto fault clearing - sps.__p_clear_fault() - sps.__p_disable_afc() - -- discrete inputs -- - unit.connect_di(sps.isFormed) + unit.connect_di("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) + unit.connect_input_reg("getLength") + unit.connect_input_reg("getWidth") + unit.connect_input_reg("getHeight") + unit.connect_input_reg("getMinPos") + unit.connect_input_reg("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) + unit.connect_input_reg("getCoils") + unit.connect_input_reg("getInputCapacity") + unit.connect_input_reg("getOutputCapacity") + unit.connect_input_reg("getMaxEnergy") -- current state - unit.connect_input_reg(sps.getProcessRate) + unit.connect_input_reg("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) + unit.connect_input_reg("getInput") + unit.connect_input_reg("getInputNeeded") + unit.connect_input_reg("getInputFilledPercentage") + unit.connect_input_reg("getOutput") + unit.connect_input_reg("getOutputNeeded") + unit.connect_input_reg("getOutputFilledPercentage") + unit.connect_input_reg("getEnergy") + unit.connect_input_reg("getEnergyNeeded") + unit.connect_input_reg("getEnergyFilledPercentage") -- holding registers -- -- none - -- check if any calls faulted - local faulted = sps.__p_is_faulted() - sps.__p_clear_fault() - sps.__p_enable_afc() - - return unit.interface(), faulted + return unit.interface(), false end return sps_rtu diff --git a/rtu/dev/turbinev_rtu.lua b/rtu/dev/turbinev_rtu.lua index 4bfad81..5770ff2 100644 --- a/rtu/dev/turbinev_rtu.lua +++ b/rtu/dev/turbinev_rtu.lua @@ -9,12 +9,8 @@ local turbinev_rtu = {} function turbinev_rtu.new(turbine) local unit = rtu.init_unit(turbine) - -- disable auto fault clearing - turbine.__p_clear_fault() - turbine.__p_disable_afc() - -- discrete inputs -- - unit.connect_di(turbine.isFormed) + unit.connect_di("isFormed") -- coils -- unit.connect_coil(function () turbine.incrementDumpingMode() end, function () end) @@ -22,44 +18,39 @@ function turbinev_rtu.new(turbine) -- input registers -- -- multiblock properties - 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) + unit.connect_input_reg("getLength") + unit.connect_input_reg("getWidth") + unit.connect_input_reg("getHeight") + unit.connect_input_reg("getMinPos") + unit.connect_input_reg("getMaxPos") -- 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.getMaxEnergy) - unit.connect_input_reg(turbine.getMaxFlowRate) - unit.connect_input_reg(turbine.getMaxProduction) - unit.connect_input_reg(turbine.getMaxWaterOutput) + unit.connect_input_reg("getBlades") + unit.connect_input_reg("getCoils") + unit.connect_input_reg("getVents") + unit.connect_input_reg("getDispersers") + unit.connect_input_reg("getCondensers") + unit.connect_input_reg("getSteamCapacity") + unit.connect_input_reg("getMaxEnergy") + unit.connect_input_reg("getMaxFlowRate") + unit.connect_input_reg("getMaxProduction") + unit.connect_input_reg("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) + unit.connect_input_reg("getFlowRate") + unit.connect_input_reg("getProductionRate") + unit.connect_input_reg("getLastSteamInputRate") + unit.connect_input_reg("getDumpingMode") -- tanks/containers - 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) + unit.connect_input_reg("getSteam") + unit.connect_input_reg("getSteamNeeded") + unit.connect_input_reg("getSteamFilledPercentage") + unit.connect_input_reg("getEnergy") + unit.connect_input_reg("getEnergyNeeded") + unit.connect_input_reg("getEnergyFilledPercentage") -- holding registers -- - unit.connect_holding_reg(turbine.getDumpingMode, turbine.setDumpingMode) + unit.connect_holding_reg("getDumpingMode", "setDumpingMode") - -- check if any calls faulted - local faulted = turbine.__p_is_faulted() - turbine.__p_clear_fault() - turbine.__p_enable_afc() - - return unit.interface(), faulted + return unit.interface(), false end return turbinev_rtu diff --git a/rtu/rtu.lua b/rtu/rtu.lua index ab13bbd..803c9aa 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -92,6 +92,8 @@ function rtu.init_unit(device) local insert = table.insert + local stub = function () log.warning("tried to call an RTU function stub") end + ---@class rtu_device local public = {} @@ -113,13 +115,26 @@ function rtu.init_unit(device) return self.io_count_cache[1], self.io_count_cache[2], self.io_count_cache[3], self.io_count_cache[4] end + -- pass a function through or generate one to call a function by name from the device + ---@param f function|string function or device function name + local function _as_func(f) + if type(f) == "string" then + local name = f + if device then + f = function (...) return device[name](...) end + else f = stub end + end + + return f + end + -- discrete inputs: single bit read-only -- connect discrete input - ---@param f function + ---@param f function|string function or function name ---@return integer count count of discrete inputs function protected.connect_di(f) - insert(self.discrete_inputs, { read = f }) + insert(self.discrete_inputs, { read = _as_func(f) }) _count_io() return #self.discrete_inputs end @@ -135,11 +150,11 @@ function rtu.init_unit(device) -- coils: single bit read-write -- connect coil - ---@param f_read function - ---@param f_write function + ---@param f_read function|string function or function name + ---@param f_write function|string function or function name ---@return integer count count of coils function protected.connect_coil(f_read, f_write) - insert(self.coils, { read = f_read, write = f_write }) + insert(self.coils, { read = _as_func(f_read), write = _as_func(f_write) }) _count_io() return #self.coils end @@ -164,10 +179,10 @@ function rtu.init_unit(device) -- input registers: multi-bit read-only -- connect input register - ---@param f function + ---@param f function|string function or function name ---@return integer count count of input registers function protected.connect_input_reg(f) - insert(self.input_regs, { read = f }) + insert(self.input_regs, { read = _as_func(f) }) _count_io() return #self.input_regs end @@ -183,11 +198,11 @@ function rtu.init_unit(device) -- holding registers: multi-bit read-write -- connect holding register - ---@param f_read function - ---@param f_write function + ---@param f_read function|string function or function name + ---@param f_write function|string function or function name ---@return integer count count of holding registers function protected.connect_holding_reg(f_read, f_write) - insert(self.holding_regs, { read = f_read, write = f_write }) + insert(self.holding_regs, { read = _as_func(f_read), write = _as_func(f_write) }) _count_io() return #self.holding_regs end diff --git a/rtu/startup.lua b/rtu/startup.lua index 4b92756..d16751f 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -31,7 +31,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 = "v1.8.0" +local RTU_VERSION = "v1.9.0" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE @@ -342,7 +342,7 @@ local function main() is_multiblock = true formed = device.isFormed() - if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then + if formed == ppm.ACCESS_FAULT then println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed boiler multiblock")) return false @@ -357,7 +357,7 @@ local function main() is_multiblock = true formed = device.isFormed() - if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then + if formed == ppm.ACCESS_FAULT then println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed turbine multiblock")) return false @@ -377,7 +377,7 @@ local function main() is_multiblock = true formed = device.isFormed() - if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then + if formed == ppm.ACCESS_FAULT then println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed dynamic tank multiblock")) return false @@ -391,7 +391,7 @@ local function main() is_multiblock = true formed = device.isFormed() - if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then + if formed == ppm.ACCESS_FAULT then println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed induction matrix multiblock")) return false @@ -405,7 +405,7 @@ local function main() is_multiblock = true formed = device.isFormed() - if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then + if formed == ppm.ACCESS_FAULT then println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed SPS multiblock")) return false @@ -471,7 +471,8 @@ local function main() for_message = util.c("reactor ", for_reactor) end - log.info(util.c("sys_config> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ") [", index, "] for ", for_message)) + local index_str = util.trinary(index ~= nil, util.c(" [", index, "]"), "") + log.info(util.c("sys_config> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ")", index_str, " for ", for_message)) rtu_unit.uid = #units diff --git a/rtu/threads.lua b/rtu/threads.lua index f3147e7..011b617 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -517,82 +517,23 @@ function threads.thread__unit_comms(smem, unit) -- check if multiblock is still formed if this is a multiblock 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() + local is_formed = unit.device.isFormed() + if unit.formed == nil then unit.formed = is_formed if is_formed then unit.hw_state = UNIT_HW_STATE.OK end + elseif not unit.formed then + unit.hw_state = UNIT_HW_STATE.UNFORMED end - if not unit.formed then unit.hw_state = UNIT_HW_STATE.UNFORMED end - - if (not unit.formed) and is_formed 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) - local faulted = false - - if device ~= nil then - if type == "boilerValve" and unit.type == RTU_UNIT_TYPE.BOILER_VALVE then - -- boiler multiblock - unit.device = device - unit.rtu, faulted = boilerv_rtu.new(device) - unit.formed = device.isFormed() - unit.modbus_io = modbus.new(unit.rtu, true) - elseif type == "turbineValve" and unit.type == RTU_UNIT_TYPE.TURBINE_VALVE then - -- turbine multiblock - unit.device = device - unit.rtu, faulted = turbinev_rtu.new(device) - unit.formed = device.isFormed() - unit.modbus_io = modbus.new(unit.rtu, true) - elseif type == "dynamicValve" and unit.type == RTU_UNIT_TYPE.DYNAMIC_VALVE then - -- dynamic tank multiblock - unit.device = device - unit.rtu, faulted = dynamicv_rtu.new(device) - unit.formed = device.isFormed() - unit.modbus_io = modbus.new(unit.rtu, true) - elseif type == "inductionPort" and unit.type == RTU_UNIT_TYPE.IMATRIX then - -- induction matrix multiblock - unit.device = device - unit.rtu, faulted = imatrix_rtu.new(device) - unit.formed = device.isFormed() - unit.modbus_io = modbus.new(unit.rtu, true) - elseif type == "spsPort" and unit.type == RTU_UNIT_TYPE.SPS then - -- SPS multiblock - unit.device = device - unit.rtu, faulted = 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 or type change attempted for " .. short_name, true) - end - - if unit.formed and faulted then - -- something is still wrong = can't mark as formed yet - unit.formed = false - unit.hw_state = UNIT_HW_STATE.UNFORMED - log.info(util.c("assuming ", unit.name, " is not formed due to PPM faults while initializing")) - else - unit.hw_state = UNIT_HW_STATE.OK - rtu_comms.send_remounted(unit.uid) - end - - local type_name = types.rtu_type_to_string(unit.type) - log.info(util.c("reconnected the ", type_name, " on interface ", unit.name)) - else - -- fully lost the peripheral now :( - log.error(util.c(unit.name, " lost (failed reconnect)")) - end - else - log.error("failed to get interface of previously connected RTU unit " .. detail_name, true) - end + if (is_formed == true) and not unit.formed then + unit.hw_state = UNIT_HW_STATE.OK + log.info(util.c(detail_name, " is now formed")) + rtu_comms.send_remounted(unit.uid) + elseif (is_formed == false) and unit.formed then + log.warning(util.c(detail_name, " is no longer formed")) end unit.formed = is_formed diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 0d02663..34a6a60 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -51,11 +51,13 @@ local function peri_init(iface) self.device = peripheral.wrap(iface) end - -- initialization process (re-map) - - for key, func in pairs(self.device) do - self.fault_counts[key] = 0 - self.device[key] = function (...) + -- create a protected version of a peripheral function call + ---@nodiscard + ---@param key string function name + ---@param func function function + ---@return function method protected version of the function + local function protect_peri_function(key, func) + return function (...) local return_table = table.pack(pcall(func, ...)) local status = return_table[1] @@ -85,20 +87,24 @@ local function peri_init(iface) count_str = " [" .. self.fault_counts[key] .. " total faults]" end - log.error(util.c("PPM: protected ", key, "() -> ", result, count_str)) + log.error(util.c("PPM: [@", iface, "] protected ", key, "() -> ", result, count_str)) end self.fault_counts[key] = self.fault_counts[key] + 1 - if result == "Terminated" then - ppm_sys.terminate = true - end + if result == "Terminated" then ppm_sys.terminate = true end - return ACCESS_FAULT + return ACCESS_FAULT, result end end end + -- initialization process (re-map) + for key, func in pairs(self.device) do + self.fault_counts[key] = 0 + self.device[key] = protect_peri_function(key, func) + end + -- fault management & monitoring functions local function clear_fault() self.faulted = false end @@ -131,12 +137,21 @@ local function peri_init(iface) local mt = { __index = function (_, key) + -- try to find the function in case it was added (multiblock formed) + local funcs = peripheral.wrap(iface) + if (type(funcs) == "table") and (type(funcs[key]) == "function") then + -- add this function then return it + self.device[key] = protect_peri_function(key, funcs[key]) + log.info(util.c("PPM: [@", iface, "] initialized previously undefined field ", key, "()")) + + return self.device[key] + end + + -- function still missing, return an undefined function handler + -- note: code should avoid storing functions for multiblocks and instead try to index them again return (function () -- 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 + if self.fault_counts[key] == nil then self.fault_counts[key] = 0 end -- function failed self.faulted = true @@ -151,12 +166,12 @@ local function peri_init(iface) count_str = " [" .. self.fault_counts[key] .. " total calls]" end - log.error(util.c("PPM: caught undefined function ", key, "()", count_str)) + log.error(util.c("PPM: [@", iface, "] caught undefined function ", key, "()", count_str)) end self.fault_counts[key] = self.fault_counts[key] + 1 - return UNDEFINED_FIELD + return ACCESS_FAULT, UNDEFINED_FIELD end) end } diff --git a/scada-common/util.lua b/scada-common/util.lua index ebca4a2..395be22 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -22,7 +22,7 @@ local t_pack = table.pack local util = {} -- scada-common version -util.version = "1.1.19" +util.version = "1.2.0" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50