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 = {} }