diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 9fefc1d..90d9207 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -173,7 +173,6 @@ local function main() log.debug("init> running without networking") end ----@diagnostic disable-next-line: param-type-mismatch util.push_event("clock_start") println("boot> completed") diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 1c3c29f..69d2ff1 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -266,7 +266,6 @@ function threads.thread__main(smem, init) -- this thread cannot be slept because it will miss events (namely "terminate" otherwise) if not plc_state.shutdown then log.info("main thread restarting now...") ----@diagnostic disable-next-line: param-type-mismatch util.push_event("clock_start") end end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index b275622..14f929c 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -16,7 +16,7 @@ local max_distance = nil comms.version = "1.4.0" ----@alias PROTOCOLS integer +---@enum PROTOCOLS local PROTOCOLS = { MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol RPLC = 1, -- reactor PLC protocol @@ -25,7 +25,7 @@ local PROTOCOLS = { COORD_API = 4 -- data/control packets for pocket computers to/from coordinators } ----@alias RPLC_TYPES integer +---@enum RPLC_TYPES local RPLC_TYPES = { STATUS = 0, -- reactor/system status MEK_STRUCT = 1, -- mekanism build structure @@ -40,7 +40,7 @@ local RPLC_TYPES = { AUTO_BURN_RATE = 10 -- set an automatic burn rate, PLC will respond with status, enable toggle speed limited } ----@alias SCADA_MGMT_TYPES integer +---@enum SCADA_MGMT_TYPES local SCADA_MGMT_TYPES = { ESTABLISH = 0, -- establish new connection KEEP_ALIVE = 1, -- keep alive packet w/ RTT @@ -49,7 +49,7 @@ local SCADA_MGMT_TYPES = { RTU_DEV_REMOUNT = 4 -- RTU multiblock possbily changed (formed, unformed) due to PPM remount } ----@alias SCADA_CRDN_TYPES integer +---@enum SCADA_CRDN_TYPES local SCADA_CRDN_TYPES = { INITIAL_BUILDS = 0, -- initial, complete builds packet to the coordinator FAC_BUILDS = 1, -- facility RTU builds @@ -60,12 +60,11 @@ local SCADA_CRDN_TYPES = { UNIT_CMD = 6 -- command a reactor unit } ----@alias CAPI_TYPES integer +---@enum CAPI_TYPES local CAPI_TYPES = { - ESTABLISH = 0 -- initial greeting } ----@alias ESTABLISH_ACK integer +---@enum ESTABLISH_ACK local ESTABLISH_ACK = { ALLOW = 0, -- link approved DENY = 1, -- link denied @@ -73,7 +72,7 @@ local ESTABLISH_ACK = { BAD_VERSION = 3 -- link denied due to comms version mismatch } ----@alias DEVICE_TYPES integer +---@enum DEVICE_TYPES local DEVICE_TYPES = { PLC = 0, -- PLC device type for establish RTU = 1, -- RTU device type for establish @@ -81,7 +80,7 @@ local DEVICE_TYPES = { CRDN = 3 -- coordinator device type for establish } ----@alias RTU_UNIT_TYPES integer +---@enum RTU_UNIT_TYPES local RTU_UNIT_TYPES = { REDSTONE = 0, -- redstone I/O BOILER_VALVE = 1, -- boiler mekanism 10.1+ @@ -92,7 +91,7 @@ local RTU_UNIT_TYPES = { ENV_DETECTOR = 6 -- environment detector } ----@alias PLC_AUTO_ACK integer +---@enum PLC_AUTO_ACK local PLC_AUTO_ACK = { FAIL = 0, -- failed to set burn rate/burn rate invalid DIRECT_SET_OK = 1, -- successfully set burn rate @@ -100,7 +99,7 @@ local PLC_AUTO_ACK = { ZERO_DIS_OK = 3 -- successfully disabled reactor with < 0.01 burn rate } ----@alias FAC_COMMANDS integer +---@enum FAC_COMMANDS local FAC_COMMANDS = { SCRAM_ALL = 0, -- SCRAM all reactors STOP = 1, -- stop automatic control @@ -108,7 +107,7 @@ local FAC_COMMANDS = { ACK_ALL_ALARMS = 3 -- acknowledge all alarms on all units } ----@alias UNIT_COMMANDS integer +---@enum UNIT_COMMANDS local UNIT_COMMANDS = { SCRAM = 0, -- SCRAM the reactor START = 1, -- start the reactor @@ -152,6 +151,7 @@ function comms.set_trusted_range(distance) end -- generic SCADA packet object +---@nodiscard function comms.scada_packet() local self = { modem_msg_in = nil, @@ -180,11 +180,12 @@ function comms.scada_packet() end -- parse in a modem message as a SCADA packet - ---@param side string - ---@param sender integer - ---@param reply_to integer - ---@param message any - ---@param distance integer + ---@param side string modem side + ---@param sender integer sender port + ---@param reply_to integer reply port + ---@param message any message body + ---@param distance integer transmission distance + ---@return boolean valid valid message received function public.receive(side, sender, reply_to, message, distance) self.modem_msg_in = { iface = side, @@ -223,24 +224,34 @@ function comms.scada_packet() -- public accessors -- + ---@nodiscard function public.modem_event() return self.modem_msg_in end + ---@nodiscard function public.raw_sendable() return self.raw end + ---@nodiscard function public.local_port() return self.modem_msg_in.s_port end + ---@nodiscard function public.remote_port() return self.modem_msg_in.r_port end + ---@nodiscard function public.is_valid() return self.valid end + ---@nodiscard function public.seq_num() return self.seq_num end + ---@nodiscard function public.protocol() return self.protocol end + ---@nodiscard function public.length() return self.length end + ---@nodiscard function public.data() return self.payload end return public end --- MODBUS packet +-- MODBUS packet
-- modeled after MODBUS TCP packet +---@nodiscard function comms.modbus_packet() local self = { frame = nil, @@ -309,9 +320,11 @@ function comms.modbus_packet() end -- get raw to send + ---@nodiscard function public.raw_sendable() return self.raw end -- get this packet as a frame with an immutable relation to this object + ---@nodiscard function public.get() ---@class modbus_frame local frame = { @@ -330,6 +343,7 @@ function comms.modbus_packet() end -- reactor PLC packet +---@nodiscard function comms.rplc_packet() local self = { frame = nil, @@ -410,9 +424,11 @@ function comms.rplc_packet() end -- get raw to send + ---@nodiscard function public.raw_sendable() return self.raw end -- get this packet as a frame with an immutable relation to this object + ---@nodiscard function public.get() ---@class rplc_frame local frame = { @@ -430,6 +446,7 @@ function comms.rplc_packet() end -- SCADA management packet +---@nodiscard function comms.mgmt_packet() local self = { frame = nil, @@ -500,9 +517,11 @@ function comms.mgmt_packet() end -- get raw to send + ---@nodiscard function public.raw_sendable() return self.raw end -- get this packet as a frame with an immutable relation to this object + ---@nodiscard function public.get() ---@class mgmt_frame local frame = { @@ -519,6 +538,7 @@ function comms.mgmt_packet() end -- SCADA coordinator packet +---@nodiscard function comms.crdn_packet() local self = { frame = nil, @@ -532,6 +552,7 @@ function comms.crdn_packet() local public = {} -- check that type is known + ---@nodiscard local function _crdn_type_valid() return self.type == SCADA_CRDN_TYPES.INITIAL_BUILDS or self.type == SCADA_CRDN_TYPES.FAC_BUILDS or @@ -590,9 +611,11 @@ function comms.crdn_packet() end -- get raw to send + ---@nodiscard function public.raw_sendable() return self.raw end -- get this packet as a frame with an immutable relation to this object + ---@nodiscard function public.get() ---@class crdn_frame local frame = { @@ -609,7 +632,8 @@ function comms.crdn_packet() end -- coordinator API (CAPI) packet --- @todo +---@todo implement for pocket access +---@nodiscard function comms.capi_packet() local self = { frame = nil, @@ -623,7 +647,7 @@ function comms.capi_packet() local public = {} local function _capi_type_valid() - -- @todo + ---@todo return false end @@ -675,9 +699,11 @@ function comms.capi_packet() end -- get raw to send + ---@nodiscard function public.raw_sendable() return self.raw end -- get this packet as a frame with an immutable relation to this object + ---@nodiscard function public.get() ---@class capi_frame local frame = { @@ -694,6 +720,7 @@ function comms.capi_packet() end -- convert rtu_t to RTU unit type +---@nodiscard ---@param type rtu_t ---@return RTU_UNIT_TYPES|nil function comms.rtu_t_to_unit_type(type) @@ -717,6 +744,7 @@ function comms.rtu_t_to_unit_type(type) end -- convert RTU unit type to rtu_t +---@nodiscard ---@param utype RTU_UNIT_TYPES ---@return rtu_t|nil function comms.advert_type_to_rtu_t(utype) diff --git a/scada-common/crypto.lua b/scada-common/crypto.lua index 6424bcb..a1053bf 100644 --- a/scada-common/crypto.lua +++ b/scada-common/crypto.lua @@ -70,6 +70,7 @@ function crypto.init(password, server_port) end -- encrypt plaintext +---@nodiscard ---@param plaintext string ---@return table initial_value, string ciphertext function crypto.encrypt(plaintext) @@ -113,6 +114,7 @@ function crypto.encrypt(plaintext) end -- decrypt ciphertext +---@nodiscard ---@param iv string CTR initial value ---@param ciphertext string ciphertext hex ---@return string plaintext @@ -135,6 +137,7 @@ function crypto.decrypt(iv, ciphertext) end -- generate HMAC of message +---@nodiscard ---@param message_hex string initial value concatenated with ciphertext function crypto.hmac(message_hex) local start = util.time() @@ -201,11 +204,12 @@ function crypto.secure_modem(modem) end -- parse in a modem message as a network packet - ---@param side string - ---@param sender integer - ---@param reply_to integer + ---@nodiscard + ---@param side string modem side + ---@param sender integer sender port + ---@param reply_to integer reply port ---@param message any encrypted packet sent with secure_modem.transmit - ---@param distance integer + ---@param distance integer transmission distance ---@return string side, integer sender, integer reply_to, any plaintext_message, integer distance function public.receive(side, sender, reply_to, message, distance) local body = "" diff --git a/scada-common/log.lua b/scada-common/log.lua index cc6dd0a..30f785d 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -18,7 +18,7 @@ log.MODE = MODE -- whether to log debug messages or not local LOG_DEBUG = true -local _log_sys = { +local log_sys = { path = "/log.txt", mode = MODE.APPEND, file = nil, @@ -33,27 +33,25 @@ local free_space = fs.getFreeSpace ---@param write_mode MODE ---@param dmesg_redirect? table terminal/window to direct dmesg to function log.init(path, write_mode, dmesg_redirect) - _log_sys.path = path - _log_sys.mode = write_mode + log_sys.path = path + log_sys.mode = write_mode - if _log_sys.mode == MODE.APPEND then - _log_sys.file = fs.open(path, "a") + if log_sys.mode == MODE.APPEND then + log_sys.file = fs.open(path, "a") else - _log_sys.file = fs.open(path, "w") + log_sys.file = fs.open(path, "w") end if dmesg_redirect then - _log_sys.dmesg_out = dmesg_redirect + log_sys.dmesg_out = dmesg_redirect else - _log_sys.dmesg_out = term.current() + log_sys.dmesg_out = term.current() end end -- direct dmesg output to a monitor/window ---@param window table window or terminal reference -function log.direct_dmesg(window) - _log_sys.dmesg_out = window -end +function log.direct_dmesg(window) log_sys.dmesg_out = window end -- private log write function ---@param msg string @@ -64,8 +62,8 @@ local function _log(msg) -- attempt to write log local status, result = pcall(function () - _log_sys.file.writeLine(stamped) - _log_sys.file.flush() + log_sys.file.writeLine(stamped) + log_sys.file.flush() end) -- if we don't have space, we need to create a new log file @@ -80,18 +78,18 @@ local function _log(msg) end end - if out_of_space or (free_space(_log_sys.path) < 100) then + if out_of_space or (free_space(log_sys.path) < 100) then -- delete the old log file before opening a new one - _log_sys.file.close() - fs.delete(_log_sys.path) + log_sys.file.close() + fs.delete(log_sys.path) -- re-init logger and pass dmesg_out so that it doesn't change - log.init(_log_sys.path, _log_sys.mode, _log_sys.dmesg_out) + log.init(log_sys.path, log_sys.mode, log_sys.dmesg_out) -- leave a message - _log_sys.file.writeLine(time_stamp .. "recycled log file") - _log_sys.file.writeLine(stamped) - _log_sys.file.flush() + log_sys.file.writeLine(time_stamp .. "recycled log file") + log_sys.file.writeLine(stamped) + log_sys.file.flush() end end @@ -109,7 +107,7 @@ function log.dmesg(msg, tag, tag_color) tag = util.strval(tag) local t_stamp = string.format("%12.2f", os.clock()) - local out = _log_sys.dmesg_out + local out = log_sys.dmesg_out if out ~= nil then local out_w, out_h = out.getSize() @@ -197,6 +195,7 @@ function log.dmesg(msg, tag, tag_color) end -- print a dmesg message, but then show remaining seconds instead of timestamp +---@nodiscard ---@param msg string message ---@param tag? string log tag ---@param tag_color? integer log tag color @@ -204,7 +203,7 @@ end function log.dmesg_working(msg, tag, tag_color) local ts_coord = log.dmesg(msg, tag, tag_color) - local out = _log_sys.dmesg_out + local out = log_sys.dmesg_out local width = (ts_coord.x2 - ts_coord.x1) + 1 if out ~= nil then diff --git a/scada-common/mqueue.lua b/scada-common/mqueue.lua index 22bae5d..b48e4ad 100644 --- a/scada-common/mqueue.lua +++ b/scada-common/mqueue.lua @@ -4,7 +4,7 @@ local mqueue = {} ----@alias MQ_TYPE integer +---@enum MQ_TYPE local TYPE = { COMMAND = 0, DATA = 1, @@ -14,6 +14,7 @@ local TYPE = { mqueue.TYPE = TYPE -- create a new message queue +---@nodiscard function mqueue.new() local queue = {} @@ -35,10 +36,13 @@ function mqueue.new() function public.length() return #queue end -- check if queue is empty + ---@nodiscard ---@return boolean is_empty function public.empty() return #queue == 0 end -- check if queue has contents + ---@nodiscard + ---@return boolean has_contents function public.ready() return #queue ~= 0 end -- push a new item onto the queue @@ -68,6 +72,7 @@ function mqueue.new() end -- get an item off the queue + ---@nodiscard ---@return queue_item|nil function public.pop() if #queue > 0 then diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index f69de1e..fe9e026 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -24,7 +24,7 @@ ppm.VIRTUAL_DEVICE_TYPE = VIRTUAL_DEVICE_TYPE local REPORT_FREQUENCY = 20 -- log every 20 faults per function -local _ppm_sys = { +local ppm_sys = { mounts = {}, next_vid = 0, auto_cf = false, @@ -34,11 +34,9 @@ local _ppm_sys = { mute = false } --- wrap peripheral calls with lua protected call as we don't want a disconnect to crash a program ---- ----also provides peripheral-specific fault checks (auto-clear fault defaults to true) ---- ----assumes iface is a valid peripheral +-- wrap peripheral calls with lua protected call as we don't want a disconnect to crash a program
+-- also provides peripheral-specific fault checks (auto-clear fault defaults to true)
+-- assumes iface is a valid peripheral ---@param iface string CC peripheral interface local function peri_init(iface) local self = { @@ -68,7 +66,7 @@ local function peri_init(iface) if status then -- auto fault clear if self.auto_cf then self.faulted = false end - if _ppm_sys.auto_cf then _ppm_sys.faulted = false end + if ppm_sys.auto_cf then ppm_sys.faulted = false end self.fault_counts[key] = 0 @@ -80,10 +78,10 @@ local function peri_init(iface) self.faulted = true self.last_fault = result - _ppm_sys.faulted = true - _ppm_sys.last_fault = result + ppm_sys.faulted = true + ppm_sys.last_fault = result - if not _ppm_sys.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then + if not ppm_sys.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then local count_str = "" if self.fault_counts[key] > 0 then count_str = " [" .. self.fault_counts[key] .. " total faults]" @@ -95,7 +93,7 @@ local function peri_init(iface) self.fault_counts[key] = self.fault_counts[key] + 1 if result == "Terminated" then - _ppm_sys.terminate = true + ppm_sys.terminate = true end return ACCESS_FAULT @@ -136,10 +134,10 @@ local function peri_init(iface) self.faulted = true self.last_fault = UNDEFINED_FIELD - _ppm_sys.faulted = true - _ppm_sys.last_fault = UNDEFINED_FIELD + ppm_sys.faulted = true + ppm_sys.last_fault = UNDEFINED_FIELD - if not _ppm_sys.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then + if not ppm_sys.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then local count_str = "" if self.fault_counts[key] > 0 then count_str = " [" .. self.fault_counts[key] .. " total calls]" @@ -169,48 +167,35 @@ end -- REPORTING -- -- silence error prints -function ppm.disable_reporting() - _ppm_sys.mute = true -end +function ppm.disable_reporting() ppm_sys.mute = true end -- allow error prints -function ppm.enable_reporting() - _ppm_sys.mute = false -end +function ppm.enable_reporting() ppm_sys.mute = false end -- FAULT MEMORY -- -- enable automatically clearing fault flag -function ppm.enable_afc() - _ppm_sys.auto_cf = true -end +function ppm.enable_afc() ppm_sys.auto_cf = true end -- disable automatically clearing fault flag -function ppm.disable_afc() - _ppm_sys.auto_cf = false -end +function ppm.disable_afc() ppm_sys.auto_cf = false end -- clear fault flag -function ppm.clear_fault() - _ppm_sys.faulted = false -end +function ppm.clear_fault() ppm_sys.faulted = false end -- check fault flag -function ppm.is_faulted() - return _ppm_sys.faulted -end +---@nodiscard +function ppm.is_faulted() return ppm_sys.faulted end -- get the last fault message -function ppm.get_last_fault() - return _ppm_sys.last_fault -end +---@nodiscard +function ppm.get_last_fault() return ppm_sys.last_fault end -- TERMINATION -- -- if a caught error was a termination request -function ppm.should_terminate() - return _ppm_sys.terminate -end +---@nodiscard +function ppm.should_terminate() return ppm_sys.terminate end -- MOUNTING -- @@ -218,12 +203,12 @@ end function ppm.mount_all() local ifaces = peripheral.getNames() - _ppm_sys.mounts = {} + ppm_sys.mounts = {} for i = 1, #ifaces do - _ppm_sys.mounts[ifaces[i]] = peri_init(ifaces[i]) + ppm_sys.mounts[ifaces[i]] = peri_init(ifaces[i]) - log.info(util.c("PPM: found a ", _ppm_sys.mounts[ifaces[i]].type, " (", ifaces[i], ")")) + log.info(util.c("PPM: found a ", ppm_sys.mounts[ifaces[i]].type, " (", ifaces[i], ")")) end if #ifaces == 0 then @@ -232,6 +217,7 @@ function ppm.mount_all() end -- mount a particular device +---@nodiscard ---@param iface string CC peripheral interface ---@return string|nil type, table|nil device function ppm.mount(iface) @@ -241,10 +227,10 @@ function ppm.mount(iface) for i = 1, #ifaces do if iface == ifaces[i] then - _ppm_sys.mounts[iface] = peri_init(iface) + ppm_sys.mounts[iface] = peri_init(iface) - pm_type = _ppm_sys.mounts[iface].type - pm_dev = _ppm_sys.mounts[iface].dev + pm_type = ppm_sys.mounts[iface].type + pm_dev = ppm_sys.mounts[iface].dev log.info(util.c("PPM: mount(", iface, ") -> found a ", pm_type)) break @@ -255,26 +241,27 @@ function ppm.mount(iface) end -- mount a virtual, placeholder device (specifically designed for RTU startup with missing devices) +---@nodiscard ---@return string type, table device function ppm.mount_virtual() - local iface = "ppm_vdev_" .. _ppm_sys.next_vid + local iface = "ppm_vdev_" .. ppm_sys.next_vid - _ppm_sys.mounts[iface] = peri_init("__virtual__") - _ppm_sys.next_vid = _ppm_sys.next_vid + 1 + ppm_sys.mounts[iface] = peri_init("__virtual__") + ppm_sys.next_vid = ppm_sys.next_vid + 1 log.info(util.c("PPM: mount_virtual() -> allocated new virtual device ", iface)) - return _ppm_sys.mounts[iface].type, _ppm_sys.mounts[iface].dev + return ppm_sys.mounts[iface].type, ppm_sys.mounts[iface].dev end -- manually unmount a peripheral from the PPM ---@param device table device table function ppm.unmount(device) if device then - for side, data in pairs(_ppm_sys.mounts) do + for side, data in pairs(ppm_sys.mounts) do if data.dev == device then log.warning(util.c("PPM: manually unmounted ", data.type, " mounted to ", side)) - _ppm_sys.mounts[side] = nil + ppm_sys.mounts[side] = nil break end end @@ -282,6 +269,7 @@ function ppm.unmount(device) end -- handle peripheral_detach event +---@nodiscard ---@param iface string CC peripheral interface ---@return string|nil type, table|nil device function ppm.handle_unmount(iface) @@ -289,7 +277,7 @@ function ppm.handle_unmount(iface) local pm_type = nil -- what got disconnected? - local lost_dev = _ppm_sys.mounts[iface] + local lost_dev = ppm_sys.mounts[iface] if lost_dev then pm_type = lost_dev.type @@ -300,7 +288,7 @@ function ppm.handle_unmount(iface) log.error(util.c("PPM: lost device unknown to the PPM mounted to ", iface)) end - _ppm_sys.mounts[iface] = nil + ppm_sys.mounts[iface] = nil return pm_type, pm_dev end @@ -308,23 +296,26 @@ end -- GENERAL ACCESSORS -- -- list all available peripherals +---@nodiscard ---@return table names function ppm.list_avail() return peripheral.getNames() end -- list mounted peripherals +---@nodiscard ---@return table mounts function ppm.list_mounts() - return _ppm_sys.mounts + return ppm_sys.mounts end -- get a mounted peripheral side/interface by device table +---@nodiscard ---@param device table device table ---@return string|nil iface CC peripheral interface function ppm.get_iface(device) if device then - for side, data in pairs(_ppm_sys.mounts) do + for side, data in pairs(ppm_sys.mounts) do if data.dev == device then return side end end end @@ -333,30 +324,33 @@ function ppm.get_iface(device) end -- get a mounted peripheral by side/interface +---@nodiscard ---@param iface string CC peripheral interface ---@return table|nil device function table function ppm.get_periph(iface) - if _ppm_sys.mounts[iface] then - return _ppm_sys.mounts[iface].dev + if ppm_sys.mounts[iface] then + return ppm_sys.mounts[iface].dev else return nil end end -- get a mounted peripheral type by side/interface +---@nodiscard ---@param iface string CC peripheral interface ---@return string|nil type function ppm.get_type(iface) - if _ppm_sys.mounts[iface] then - return _ppm_sys.mounts[iface].type + if ppm_sys.mounts[iface] then + return ppm_sys.mounts[iface].type else return nil end end -- get all mounted peripherals by type +---@nodiscard ---@param name string type name ---@return table devices device function tables function ppm.get_all_devices(name) local devices = {} - for _, data in pairs(_ppm_sys.mounts) do + for _, data in pairs(ppm_sys.mounts) do if data.type == name then table.insert(devices, data.dev) end @@ -366,12 +360,13 @@ function ppm.get_all_devices(name) end -- get a mounted peripheral by type (if multiple, returns the first) +---@nodiscard ---@param name string type name ---@return table|nil device function table function ppm.get_device(name) local device = nil - for side, data in pairs(_ppm_sys.mounts) do + for _, data in pairs(ppm_sys.mounts) do if data.type == name then device = data.dev break @@ -384,20 +379,21 @@ end -- SPECIFIC DEVICE ACCESSORS -- -- get the fission reactor (if multiple, returns the first) +---@nodiscard ---@return table|nil reactor function table function ppm.get_fission_reactor() return ppm.get_device("fissionReactorLogicAdapter") end --- get the wireless modem (if multiple, returns the first) --- +-- get the wireless modem (if multiple, returns the first)
-- if this is in a CraftOS emulated environment, wired modems will be used instead +---@nodiscard ---@return table|nil modem function table function ppm.get_wireless_modem() local w_modem = nil local emulated_env = periphemu ~= nil - for _, device in pairs(_ppm_sys.mounts) do + for _, device in pairs(ppm_sys.mounts) do if device.type == "modem" and (emulated_env or device.dev.isWireless()) then w_modem = device.dev break @@ -408,11 +404,12 @@ function ppm.get_wireless_modem() end -- list all connected monitors +---@nodiscard ---@return table monitors function ppm.get_monitor_list() local list = {} - for iface, device in pairs(_ppm_sys.mounts) do + for iface, device in pairs(ppm_sys.mounts) do if device.type == "monitor" then list[iface] = device end diff --git a/scada-common/psil.lua b/scada-common/psil.lua index ddadf36..c21b2cf 100644 --- a/scada-common/psil.lua +++ b/scada-common/psil.lua @@ -5,6 +5,7 @@ local psil = {} -- instantiate a new PSI layer +---@nodiscard function psil.create() local self = { ic = {} @@ -19,8 +20,7 @@ function psil.create() ---@class psil local public = {} - -- subscribe to a data object in the interconnect - -- + -- subscribe to a data object in the interconnect
-- will call func() right away if a value is already avaliable ---@param key string data key ---@param func function function to call on change diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 79ced10..6b33a24 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -89,6 +89,7 @@ rsio.IO = IO_PORT ----------------------- -- port to string +---@nodiscard ---@param port IO_PORT function rsio.to_string(port) local names = { @@ -194,6 +195,7 @@ local RS_DIO_MAP = { } -- get the mode of a port +---@nodiscard ---@param port IO_PORT ---@return IO_MODE function rsio.get_io_mode(port) @@ -239,6 +241,7 @@ end local RS_SIDES = rs.getSides() -- check if a port is valid +---@nodiscard ---@param port IO_PORT ---@return boolean valid function rsio.is_valid_port(port) @@ -246,6 +249,7 @@ function rsio.is_valid_port(port) end -- check if a side is valid +---@nodiscard ---@param side string ---@return boolean valid function rsio.is_valid_side(side) @@ -258,6 +262,7 @@ function rsio.is_valid_side(side) end -- check if a color is a valid single color +---@nodiscard ---@param color integer ---@return boolean valid function rsio.is_color(color) @@ -269,22 +274,25 @@ end ----------------- -- get digital I/O level reading from a redstone boolean input value ----@param rs_value boolean +---@nodiscard +---@param rs_value boolean raw value from redstone ---@return IO_LVL function rsio.digital_read(rs_value) if rs_value then return IO_LVL.HIGH else return IO_LVL.LOW end end -- get redstone boolean output value corresponding to a digital I/O level ----@param level IO_LVL +---@nodiscard +---@param level IO_LVL logic level ---@return boolean function rsio.digital_write(level) return level == IO_LVL.HIGH end -- returns the level corresponding to active ----@param port IO_PORT ----@param active boolean +---@nodiscard +---@param port IO_PORT port (to determine active high/low) +---@param active boolean state to convert to logic level ---@return IO_LVL|false function rsio.digital_write_active(port, active) if (not util.is_int(port)) or (port < IO_PORT.F_ALARM) or (port > IO_PORT.U_EMER_COOL) then @@ -295,9 +303,10 @@ function rsio.digital_write_active(port, active) end -- returns true if the level corresponds to active ----@param port IO_PORT ----@param level IO_LVL ----@return boolean|nil +---@nodiscard +---@param port IO_PORT port (to determine active low/high) +---@param level IO_LVL logic level +---@return boolean|nil state true for active, false for inactive, or nil if invalid port or level provided function rsio.digital_is_active(port, level) if not util.is_int(port) then return nil @@ -313,6 +322,7 @@ end ---------------- -- read an analog value scaled from min to max +---@nodiscard ---@param rs_value number redstone reading (0 to 15) ---@param min number minimum of range ---@param max number maximum of range @@ -323,6 +333,7 @@ function rsio.analog_read(rs_value, min, max) end -- write an analog value from the provided scale range +---@nodiscard ---@param value number value to write (from min to max range) ---@param min number minimum of range ---@param max number maximum of range diff --git a/scada-common/tcallbackdsp.lua b/scada-common/tcallbackdsp.lua index 52f55da..3f8f07a 100644 --- a/scada-common/tcallbackdsp.lua +++ b/scada-common/tcallbackdsp.lua @@ -19,8 +19,6 @@ function tcallbackdsp.dispatch(time, f) duration = time, expiry = time + util.time_s() } - - -- log.debug(util.c("TCD: queued callback for ", f, " [timer: ", timer, "]")) end -- request a function to be called after the specified time, aborting any registered instances of that function reference @@ -45,8 +43,6 @@ function tcallbackdsp.dispatch_unique(time, f) duration = time, expiry = time + util.time_s() } - - -- log.debug(util.c("TCD: queued callback for ", f, " [timer: ", timer, "]")) end -- abort a requested callback @@ -72,8 +68,7 @@ function tcallbackdsp.handle(event) end end --- identify any overdo callbacks --- +-- identify any overdo callbacks
-- prints to log debug output function tcallbackdsp.diagnostics() for timer, entry in pairs(registry) do diff --git a/scada-common/types.lua b/scada-common/types.lua index 23a4006..cf5f85f 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -12,12 +12,14 @@ local types = {} ---@field amount integer -- create a new tank fluid +---@nodiscard ---@param n string name ---@param a integer amount ---@return radiation_reading function types.new_tank_fluid(n, a) return { name = n, amount = a } end -- create a new empty tank fluid +---@nodiscard ---@return tank_fluid function types.new_empty_gas() return { type = "mekanism:empty_gas", amount = 0 } end @@ -26,12 +28,14 @@ function types.new_empty_gas() return { type = "mekanism:empty_gas", amount = 0 ---@field unit string -- create a new radiation reading +---@nodiscard ---@param r number radiaiton level ---@param u string radiation unit ---@return radiation_reading function types.new_radiation_reading(r, u) return { radiation = r, unit = u } end -- create a new zeroed radiation reading +---@nodiscard ---@return radiation_reading function types.new_zero_radiation_reading() return { radiation = 0, unit = "nSv" } end @@ -41,6 +45,7 @@ function types.new_zero_radiation_reading() return { radiation = 0, unit = "nSv" ---@field z integer -- create a new coordinate +---@nodiscard ---@param x integer ---@param y integer ---@param z integer @@ -48,11 +53,12 @@ function types.new_zero_radiation_reading() return { radiation = 0, unit = "nSv" function types.new_coordinate(x, y, z) return { x = x, y = y, z = z } end -- create a new zero coordinate +---@nodiscard ---@return coordinate function types.new_zero_coordinate() return { x = 0, y = 0, z = 0 } end ---@class rtu_advertisement ----@field type integer +---@field type RTU_UNIT_TYPES ---@field index integer ---@field reactor integer ---@field rsio table|nil @@ -62,15 +68,16 @@ function types.new_zero_coordinate() return { x = 0, y = 0, z = 0 } end ---@alias color integer -- ENUMERATION TYPES -- +--#region ----@alias TRI_FAIL integer +---@enum TRI_FAIL types.TRI_FAIL = { OK = 0, PARTIAL = 1, FULL = 2 } ----@alias PROCESS integer +---@enum PROCESS types.PROCESS = { INACTIVE = 0, MAX_BURN = 1, @@ -93,7 +100,7 @@ types.PROCESS_NAMES = { "GEN_RATE_FAULT_IDLE" } ----@alias WASTE_MODE integer +---@enum WASTE_MODE types.WASTE_MODE = { AUTO = 1, PLUTONIUM = 2, @@ -101,7 +108,7 @@ types.WASTE_MODE = { ANTI_MATTER = 4 } ----@alias ALARM integer +---@enum ALARM types.ALARM = { ContainmentBreach = 1, ContainmentRadiation = 2, @@ -117,7 +124,7 @@ types.ALARM = { TurbineTrip = 12 } -types.alarm_string = { +types.ALARM_NAMES = { "ContainmentBreach", "ContainmentRadiation", "ReactorLost", @@ -132,7 +139,7 @@ types.alarm_string = { "TurbineTrip" } ----@alias ALARM_PRIORITY integer +---@enum ALARM_PRIORITY types.ALARM_PRIORITY = { CRITICAL = 0, EMERGENCY = 1, @@ -140,30 +147,14 @@ types.ALARM_PRIORITY = { TIMELY = 3 } -types.alarm_prio_string = { +types.ALARM_PRIORITY_NAMES = { "CRITICAL", "EMERGENCY", "URGENT", "TIMELY" } --- map alarms to alarm priority -types.ALARM_PRIO_MAP = { - types.ALARM_PRIORITY.CRITICAL, - types.ALARM_PRIORITY.CRITICAL, - types.ALARM_PRIORITY.URGENT, - types.ALARM_PRIORITY.CRITICAL, - types.ALARM_PRIORITY.EMERGENCY, - types.ALARM_PRIORITY.EMERGENCY, - types.ALARM_PRIORITY.TIMELY, - types.ALARM_PRIORITY.EMERGENCY, - types.ALARM_PRIORITY.TIMELY, - types.ALARM_PRIORITY.URGENT, - types.ALARM_PRIORITY.TIMELY, - types.ALARM_PRIORITY.URGENT -} - ----@alias ALARM_STATE integer +---@enum ALARM_STATE types.ALARM_STATE = { INACTIVE = 0, TRIPPED = 1, @@ -171,7 +162,10 @@ types.ALARM_STATE = { RING_BACK = 3 } +--#endregion + -- STRING TYPES -- +--#region ---@alias os_event ---| "alarm" @@ -206,21 +200,7 @@ types.ALARM_STATE = { ---| "websocket_failure" ---| "websocket_message" ---| "websocket_success" - ----@alias rps_trip_cause ----| "ok" ----| "dmg_crit" ----| "high_temp" ----| "no_coolant" ----| "full_waste" ----| "heated_coolant_backup" ----| "no_fuel" ----| "fault" ----| "timeout" ----| "manual" ----| "automatic" ----| "sys_fail" ----| "force_disabled" +---| "clock_start" custom, added for reactor PLC ---@alias fluid ---| "mekanism:empty_gas" @@ -246,6 +226,21 @@ types.rtu_t = { env_detector = "environment_detector" } +---@alias rps_trip_cause +---| "ok" +---| "dmg_crit" +---| "high_temp" +---| "no_coolant" +---| "full_waste" +---| "heated_coolant_backup" +---| "no_fuel" +---| "fault" +---| "timeout" +---| "manual" +---| "automatic" +---| "sys_fail" +---| "force_disabled" + ---@alias rps_status_t rps_trip_cause types.rps_status_t = { ok = "ok", @@ -263,18 +258,24 @@ types.rps_status_t = { force_disabled = "force_disabled" } --- turbine steam dumping modes ----@alias DUMPING_MODE string +---@alias DUMPING_MODE +---| "IDLE" +---| "DUMPING" +---| "DUMPING_EXCESS" + types.DUMPING_MODE = { IDLE = "IDLE", DUMPING = "DUMPING", DUMPING_EXCESS = "DUMPING_EXCESS" } --- MODBUS +--#endregion --- modbus function codes ----@alias MODBUS_FCODE integer +-- MODBUS -- +--#region + +-- MODBUS function codes +---@enum MODBUS_FCODE types.MODBUS_FCODE = { READ_COILS = 0x01, READ_DISCRETE_INPUTS = 0x02, @@ -287,8 +288,8 @@ types.MODBUS_FCODE = { ERROR_FLAG = 0x80 } --- modbus exception codes ----@alias MODBUS_EXCODE integer +-- MODBUS exception codes +---@enum MODBUS_EXCODE types.MODBUS_EXCODE = { ILLEGAL_FUNCTION = 0x01, ILLEGAL_DATA_ADDR = 0x02, @@ -302,4 +303,6 @@ types.MODBUS_EXCODE = { GATEWAY_TARGET_TIMEOUT = 0x0B } +--#endregion + return types diff --git a/scada-common/util.lua b/scada-common/util.lua index afe3c59..2913e9f 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -14,6 +14,7 @@ util.TICK_TIME_MS = 50 --#region -- trinary operator +---@nodiscard ---@param cond boolean|nil condition ---@param a any return if true ---@param b any return if false @@ -57,6 +58,7 @@ end --#region -- get a value as a string +---@nodiscard ---@param val any ---@return string function util.strval(val) @@ -69,6 +71,7 @@ function util.strval(val) end -- repeat a string n times +---@nodiscard ---@param str string ---@param n integer ---@return string @@ -81,6 +84,7 @@ function util.strrep(str, n) end -- repeat a space n times +---@nodiscard ---@param n integer ---@return string function util.spaces(n) @@ -88,6 +92,7 @@ function util.spaces(n) end -- pad text to a minimum width +---@nodiscard ---@param str string text ---@param n integer minimum width ---@return string @@ -100,6 +105,7 @@ function util.pad(str, n) end -- wrap a string into a table of lines, supporting single dash splits +---@nodiscard ---@param str string ---@param limit integer line limit ---@return table lines @@ -147,13 +153,12 @@ function util.strwrap(str, limit) end -- concatenation with built-in to string +---@nodiscard ---@vararg any ---@return string function util.concat(...) local str = "" - for _, v in ipairs(arg) do - str = str .. util.strval(v) - end + for _, v in ipairs(arg) do str = str .. util.strval(v) end return str end @@ -161,15 +166,16 @@ end util.c = util.concat -- sprintf implementation +---@nodiscard ---@param format string ---@vararg any function util.sprintf(format, ...) return string.format(format, table.unpack(arg)) end --- format a number string with commas as the thousands separator --- +-- format a number string with commas as the thousands separator
-- subtracts from spaces at the start if present for each comma used +---@nodiscard ---@param num string number string ---@return string function util.comma_format(num) @@ -196,6 +202,7 @@ end --#region -- is a value an integer +---@nodiscard ---@param x any value ---@return boolean is_integer if the number is an integer function util.is_int(x) @@ -203,6 +210,7 @@ function util.is_int(x) end -- get the sign of a number +---@nodiscard ---@param x number value ---@return integer sign (-1 for < 0, 1 otherwise) function util.sign(x) @@ -210,12 +218,14 @@ function util.sign(x) end -- round a number to an integer +---@nodiscard ---@return integer rounded function util.round(x) return math.floor(x + 0.5) end -- get a new moving average object +---@nodiscard ---@param length integer history length ---@param default number value to fill history with for first call to compute() function util.mov_avg(length, default) @@ -249,6 +259,7 @@ function util.mov_avg(length, default) end -- compute the moving average + ---@nodiscard ---@return number average function public.compute() local sum = 0 @@ -264,6 +275,7 @@ end -- TIME -- -- current time +---@nodiscard ---@return integer milliseconds function util.time_ms() ---@diagnostic disable-next-line: undefined-field @@ -271,6 +283,7 @@ function util.time_ms() end -- current time +---@nodiscard ---@return number seconds function util.time_s() ---@diagnostic disable-next-line: undefined-field @@ -278,10 +291,9 @@ function util.time_s() end -- current time +---@nodiscard ---@return integer milliseconds -function util.time() - return util.time_ms() -end +function util.time() return util.time_ms() end --#endregion @@ -289,6 +301,7 @@ end --#region -- OS pull event raw wrapper with types +---@nodiscard ---@param target_event? string event to wait for ---@return os_event event, any param1, any param2, any param3, any param4, any param5 function util.pull_event(target_event) @@ -309,6 +322,7 @@ function util.push_event(event, param1, param2, param3, param4, param5) end -- start an OS timer +---@nodiscard ---@param t number timer duration in seconds ---@return integer timer ID function util.start_timer(t) @@ -336,14 +350,12 @@ function util.psleep(t) pcall(os.sleep, t) end --- no-op to provide a brief pause (1 tick) to yield ---- +-- no-op to provide a brief pause (1 tick) to yield
--- EVENT_CONSUMER: this function consumes events -function util.nop() - util.psleep(0.05) -end +function util.nop() util.psleep(0.05) end -- attempt to maintain a minimum loop timing (duration of execution) +---@nodiscard ---@param target_timing integer minimum amount of milliseconds to wait for ---@param last_update integer millisecond time of last update ---@return integer time_now @@ -351,9 +363,7 @@ end function util.adaptive_delay(target_timing, last_update) local sleep_for = target_timing - (util.time() - last_update) -- only if >50ms since worker loops already yield 0.05s - if sleep_for >= 50 then - util.psleep(sleep_for / 1000.0) - end + if sleep_for >= 50 then util.psleep(sleep_for / 1000.0) end return util.time() end @@ -362,8 +372,7 @@ end -- TABLE UTILITIES -- --#region --- delete elements from a table if the passed function returns false when passed a table element --- +-- delete elements from a table if the passed function returns false when passed a table element
-- put briefly: deletes elements that return false, keeps elements that return true ---@param t table table to remove elements from ---@param f function should return false to delete an element when passed the element: f(elem) = true|false @@ -388,6 +397,7 @@ function util.filter_table(t, f, on_delete) end -- check if a table contains the provided element +---@nodiscard ---@param t table table to check ---@param element any element to check for function util.table_contains(t, element) @@ -404,11 +414,13 @@ end --#region -- convert Joules to FE +---@nodiscard ---@param J number Joules ---@return number FE Forge Energy function util.joules_to_fe(J) return (J * 0.4) end -- convert FE to Joules +---@nodiscard ---@param FE number Forge Energy ---@return number J Joules function util.fe_to_joules(FE) return (FE * 2.5) end @@ -418,10 +430,11 @@ local function MFE(fe) return fe / 1000000.0 end local function GFE(fe) return fe / 1000000000.0 end local function TFE(fe) return fe / 1000000000000.0 end local function PFE(fe) return fe / 1000000000000000.0 end -local function EFE(fe) return fe / 1000000000000000000.0 end -- if you accomplish this please touch grass -local function ZFE(fe) return fe / 1000000000000000000000.0 end -- please stop +local function EFE(fe) return fe / 1000000000000000000.0 end -- if you accomplish this please touch grass +local function ZFE(fe) return fe / 1000000000000000000000.0 end -- please stop -- format a power value into XXX.XX UNIT format (FE, kFE, MFE, GFE, TFE, PFE, EFE, ZFE) +---@nodiscard ---@param fe number forge energy value ---@param combine_label? boolean if a label should be included in the string itself ---@param format? string format override @@ -430,9 +443,7 @@ function util.power_format(fe, combine_label, format) local unit local value - if type(format) ~= "string" then - format = "%.2f" - end + if type(format) ~= "string" then format = "%.2f" end if fe < 1000.0 then unit = "FE" @@ -474,10 +485,10 @@ end -- WATCHDOG -- --- ComputerCraft OS Timer based Watchdog +-- OS timer based watchdog
+-- triggers a timer event if not fed within 'timeout' seconds +---@nodiscard ---@param timeout number timeout duration ---- ---- triggers a timer event if not fed within 'timeout' seconds function util.new_watchdog(timeout) local self = { timeout = timeout, @@ -487,10 +498,10 @@ function util.new_watchdog(timeout) ---@class watchdog local public = {} + -- check if a timer is this watchdog + ---@nodiscard ---@param timer number timer event timer ID - function public.is_timer(timer) - return self.wd_timer == timer - end + function public.is_timer(timer) return self.wd_timer == timer end -- satiate the beast function public.feed() @@ -512,10 +523,10 @@ end -- LOOP CLOCK -- --- ComputerCraft OS Timer based Loop Clock +-- OS timer based loop clock
+-- fires a timer event at the specified period, does not start at construct time +---@nodiscard ---@param period number clock period ---- ---- fires a timer event at the specified period, does not start at construct time function util.new_clock(period) local self = { period = period, @@ -525,24 +536,22 @@ function util.new_clock(period) ---@class clock local public = {} + -- check if a timer is this clock + ---@nodiscard ---@param timer number timer event timer ID - function public.is_clock(timer) - return self.timer == timer - end + function public.is_clock(timer) return self.timer == timer end -- start the clock - function public.start() - self.timer = util.start_timer(self.period) - end + function public.start() self.timer = util.start_timer(self.period) end return public end -- FIELD VALIDATOR -- --- create a new type validator --- +-- create a new type validator
-- can execute sequential checks and check valid() to see if it is still valid +---@nodiscard function util.new_validator() local valid = true @@ -565,6 +574,8 @@ function util.new_validator() function public.assert_port(port) valid = valid and type(port) == "number" and port >= 0 and port <= 65535 end + -- check if all assertions passed successfully + ---@nodiscard function public.valid() return valid end return public diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 836dad2..8322892 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -367,8 +367,8 @@ local function _update_alarm_state(self, tripped, alarm) else alarm.state = AISTATE.TRIPPED self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED - log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): TRIPPED [PRIORITY ", - types.alarm_prio_string[alarm.tier + 1],"]")) + log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): TRIPPED [PRIORITY ", + types.ALARM_PRIORITY_NAMES[alarm.tier + 1],"]")) end else alarm.trip_time = util.time_ms() @@ -381,8 +381,8 @@ local function _update_alarm_state(self, tripped, alarm) if elapsed > (alarm.hold_time * 1000) then alarm.state = AISTATE.TRIPPED self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED - log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): TRIPPED [PRIORITY ", - types.alarm_prio_string[alarm.tier + 1],"]")) + log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): TRIPPED [PRIORITY ", + types.ALARM_PRIORITY_NAMES[alarm.tier + 1],"]")) end elseif int_state == AISTATE.RING_BACK_TRIPPING then alarm.trip_time = 0 @@ -432,7 +432,7 @@ local function _update_alarm_state(self, tripped, alarm) -- check for state change if alarm.state ~= int_state then local change_str = util.c(aistate_string[int_state + 1], " -> ", aistate_string[alarm.state + 1]) - log.debug(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): ", change_str)) + log.debug(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): ", change_str)) end end @@ -530,8 +530,8 @@ function logic.update_auto_safety(public, self) for _, alarm in pairs(self.alarms) do if alarm.tier <= PRIO.URGENT and (alarm.state == AISTATE.TRIPPED or alarm.state == AISTATE.ACKED) then if not self.auto_was_alarmed then - log.info(util.c("UNIT ", self.r_id, " AUTO SCRAM due to ALARM ", alarm.id, " (", types.alarm_string[alarm.id], ") [PRIORITY ", - types.alarm_prio_string[alarm.tier + 1],"]")) + log.info(util.c("UNIT ", self.r_id, " AUTO SCRAM due to ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], ") [PRIORITY ", + types.ALARM_PRIORITY_NAMES[alarm.tier + 1],"]")) end alarmed = true