#33 lua module/require architecture changeover

This commit is contained in:
Mikayla Fischler
2022-05-04 13:37:01 -04:00
parent 7bcb260712
commit b575899d46
33 changed files with 679 additions and 518 deletions

View File

@@ -1,4 +1,6 @@
-- #REQUIRES util.lua
local util = require("scada-common.util")
local alarm = {}
SEVERITY = {
INFO = 0, -- basic info message
@@ -9,7 +11,27 @@ SEVERITY = {
EMERGENCY = 5 -- critical safety alarm
}
function scada_alarm(severity, device, message)
alarm.SEVERITY = SEVERITY
alarm.severity_to_string = function (severity)
if severity == SEVERITY.INFO then
return "INFO"
elseif severity == SEVERITY.WARNING then
return "WARNING"
elseif severity == SEVERITY.ALERT then
return "ALERT"
elseif severity == SEVERITY.FACILITY then
return "FACILITY"
elseif severity == SEVERITY.SAFETY then
return "SAFETY"
elseif severity == SEVERITY.EMERGENCY then
return "EMERGENCY"
else
return "UNKNOWN"
end
end
alarm.scada_alarm = function (severity, device, message)
local self = {
time = util.time(),
ts_string = os.date("[%H:%M:%S]"),
@@ -19,7 +41,7 @@ function scada_alarm(severity, device, message)
}
local format = function ()
return self.ts_string .. " [" .. severity_to_string(self.severity) .. "] (" .. self.device ") >> " .. self.message
return self.ts_string .. " [" .. alarm.severity_to_string(self.severity) .. "] (" .. self.device ") >> " .. self.message
end
local properties = function ()
@@ -37,20 +59,4 @@ function scada_alarm(severity, device, message)
}
end
function severity_to_string(severity)
if severity == SEVERITY.INFO then
return "INFO"
elseif severity == SEVERITY.WARNING then
return "WARNING"
elseif severity == SEVERITY.ALERT then
return "ALERT"
elseif severity == SEVERITY.FACILITY then
return "FACILITY"
elseif severity == SEVERITY.SAFETY then
return "SAFETY"
elseif severity == SEVERITY.EMERGENCY then
return "EMERGENCY"
else
return "UNKNOWN"
end
end
return alarm

View File

@@ -1,4 +1,10 @@
PROTOCOLS = {
--
-- Communications
--
local comms = {}
local PROTOCOLS = {
MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol
RPLC = 1, -- reactor PLC protocol
SCADA_MGMT = 2, -- SCADA supervisor management, device advertisements, etc
@@ -6,7 +12,7 @@ PROTOCOLS = {
COORD_API = 4 -- data/control packets for pocket computers to/from coordinators
}
RPLC_TYPES = {
local RPLC_TYPES = {
KEEP_ALIVE = 0, -- keep alive packets
LINK_REQ = 1, -- linking requests
STATUS = 2, -- reactor/system status
@@ -19,13 +25,13 @@ RPLC_TYPES = {
ISS_CLEAR = 9 -- clear ISS trip (if in bad state, will trip immediately)
}
RPLC_LINKING = {
local RPLC_LINKING = {
ALLOW = 0, -- link approved
DENY = 1, -- link denied
COLLISION = 2 -- link denied due to existing active link
}
SCADA_MGMT_TYPES = {
local SCADA_MGMT_TYPES = {
PING = 0, -- generic ping
CLOSE = 1, -- close a connection
REMOTE_LINKED = 2, -- remote device linked
@@ -33,15 +39,21 @@ SCADA_MGMT_TYPES = {
RTU_HEARTBEAT = 4 -- RTU heartbeat
}
RTU_ADVERT_TYPES = {
local RTU_ADVERT_TYPES = {
BOILER = 0, -- boiler
TURBINE = 1, -- turbine
IMATRIX = 2, -- induction matrix
REDSTONE = 3 -- redstone I/O
}
comms.PROTOCOLS = PROTOCOLS
comms.RPLC_TYPES = RPLC_TYPES
comms.RPLC_LINKING = RPLC_LINKING
comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES
comms.RTU_ADVERT_TYPES = RTU_ADVERT_TYPES
-- generic SCADA packet object
function scada_packet()
comms.scada_packet = function ()
local self = {
modem_msg_in = nil,
valid = false,
@@ -124,7 +136,7 @@ end
-- MODBUS packet
-- modeled after MODBUS TCP packet
function modbus_packet()
comms.modbus_packet = function ()
local self = {
frame = nil,
raw = nil,
@@ -165,11 +177,11 @@ function modbus_packet()
return size_ok
else
log._debug("attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true)
log.debug("attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true)
return false
end
else
log._debug("nil frame encountered", true)
log.debug("nil frame encountered", true)
return false
end
end
@@ -201,7 +213,7 @@ function modbus_packet()
end
-- reactor PLC packet
function rplc_packet()
comms.rplc_packet = function ()
local self = {
frame = nil,
raw = nil,
@@ -256,11 +268,11 @@ function rplc_packet()
return ok
else
log._debug("attempted RPLC parse of incorrect protocol " .. frame.protocol(), true)
log.debug("attempted RPLC parse of incorrect protocol " .. frame.protocol(), true)
return false
end
else
log._debug("nil frame encountered", true)
log.debug("nil frame encountered", true)
return false
end
end
@@ -291,7 +303,7 @@ function rplc_packet()
end
-- SCADA management packet
function mgmt_packet()
comms.mgmt_packet = function ()
local self = {
frame = nil,
raw = nil,
@@ -339,11 +351,11 @@ function mgmt_packet()
return ok
else
log._debug("attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true)
log.debug("attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true)
return false
end
else
log._debug("nil frame encountered", true)
log.debug("nil frame encountered", true)
return false
end
end
@@ -374,7 +386,7 @@ end
-- SCADA coordinator packet
-- @todo
function coord_packet()
comms.coord_packet = function ()
local self = {
frame = nil,
raw = nil,
@@ -418,11 +430,11 @@ function coord_packet()
return ok
else
log._debug("attempted COORD_DATA parse of incorrect protocol " .. frame.protocol(), true)
log.debug("attempted COORD_DATA parse of incorrect protocol " .. frame.protocol(), true)
return false
end
else
log._debug("nil frame encountered", true)
log.debug("nil frame encountered", true)
return false
end
end
@@ -453,7 +465,7 @@ end
-- coordinator API (CAPI) packet
-- @todo
function capi_packet()
comms.capi_packet = function ()
local self = {
frame = nil,
raw = nil,
@@ -497,11 +509,11 @@ function capi_packet()
return ok
else
log._debug("attempted COORD_API parse of incorrect protocol " .. frame.protocol(), true)
log.debug("attempted COORD_API parse of incorrect protocol " .. frame.protocol(), true)
return false
end
else
log._debug("nil frame encountered", true)
log.debug("nil frame encountered", true)
return false
end
end
@@ -529,3 +541,5 @@ function capi_packet()
get = get
}
end
return comms

View File

@@ -2,14 +2,17 @@
-- File System Logger
--
-- we use extra short abbreviations since computer craft screens are very small
-- underscores are used since some of these names are used elsewhere (e.g. 'debug' is a lua table)
local log = {}
MODE = {
-- we use extra short abbreviations since computer craft screens are very small
local MODE = {
APPEND = 0,
NEW = 1
}
log.MODE = MODE
local LOG_DEBUG = true
local log_path = "/log.txt"
@@ -50,7 +53,7 @@ local _log = function (msg)
end
end
function init(path, write_mode)
log.init = function (path, write_mode)
log_path = path
mode = write_mode
@@ -61,7 +64,7 @@ function init(path, write_mode)
end
end
function _debug(msg, trace)
log.debug = function (msg, trace)
if LOG_DEBUG then
local dbg_info = ""
@@ -80,15 +83,15 @@ function _debug(msg, trace)
end
end
function _info(msg)
log.info = function (msg)
_log("[INF] " .. msg)
end
function _warning(msg)
log.warning = function (msg)
_log("[WRN] " .. msg)
end
function _error(msg, trace)
log.error = function (msg, trace)
local dbg_info = ""
if trace then
@@ -105,6 +108,8 @@ function _error(msg, trace)
_log("[ERR] " .. dbg_info .. msg)
end
function _fatal(msg)
log.fatal = function (msg)
_log("[FTL] " .. msg)
end
return log

View File

@@ -2,13 +2,17 @@
-- Message Queue
--
TYPE = {
local mqueue = {}
local TYPE = {
COMMAND = 0,
DATA = 1,
PACKET = 2
}
function new()
mqueue.TYPE = TYPE
mqueue.new = function ()
local queue = {}
local length = function ()
@@ -57,3 +61,5 @@ function new()
pop = pop
}
end
return mqueue

View File

@@ -1,10 +1,14 @@
-- #REQUIRES log.lua
local log = require("scada-common.log")
--
-- Protected Peripheral Manager
--
ACCESS_FAULT = nil
local ppm = {}
local ACCESS_FAULT = nil
ppm.ACCESS_FAULT = ACCESS_FAULT
----------------------------
-- PRIVATE DATA/FUNCTIONS --
@@ -46,7 +50,7 @@ local peri_init = function (iface)
_ppm_sys.faulted = true
if not _ppm_sys.mute then
log._error("PPM: protected " .. key .. "() -> " .. result)
log.error("PPM: protected " .. key .. "() -> " .. result)
end
if result == "Terminated" then
@@ -88,48 +92,48 @@ end
-- REPORTING --
-- silence error prints
function disable_reporting()
ppm.disable_reporting = function ()
_ppm_sys.mute = true
end
-- allow error prints
function enable_reporting()
ppm.enable_reporting = function ()
_ppm_sys.mute = false
end
-- FAULT MEMORY --
-- enable automatically clearing fault flag
function enable_afc()
ppm.enable_afc = function ()
_ppm_sys.auto_cf = true
end
-- disable automatically clearing fault flag
function disable_afc()
ppm.disable_afc = function ()
_ppm_sys.auto_cf = false
end
-- check fault flag
function is_faulted()
ppm.is_faulted = function ()
return _ppm_sys.faulted
end
-- clear fault flag
function clear_fault()
ppm.clear_fault = function ()
_ppm_sys.faulted = false
end
-- TERMINATION --
-- if a caught error was a termination request
function should_terminate()
ppm.should_terminate = function ()
return _ppm_sys.terminate
end
-- MOUNTING --
-- mount all available peripherals (clears mounts first)
function mount_all()
ppm.mount_all = function ()
local ifaces = peripheral.getNames()
_ppm_sys.mounts = {}
@@ -137,23 +141,23 @@ function mount_all()
for i = 1, #ifaces do
_ppm_sys.mounts[ifaces[i]] = peri_init(ifaces[i])
log._info("PPM: found a " .. _ppm_sys.mounts[ifaces[i]].type .. " (" .. ifaces[i] .. ")")
log.info("PPM: found a " .. _ppm_sys.mounts[ifaces[i]].type .. " (" .. ifaces[i] .. ")")
end
if #ifaces == 0 then
log._warning("PPM: mount_all() -> no devices found")
log.warning("PPM: mount_all() -> no devices found")
end
end
-- mount a particular device
function mount(iface)
ppm.mount = function (iface)
local ifaces = peripheral.getNames()
local pm_dev = nil
local pm_type = nil
for i = 1, #ifaces do
if iface == ifaces[i] then
log._info("PPM: mount(" .. iface .. ") -> found a " .. peripheral.getType(iface))
log.info("PPM: mount(" .. iface .. ") -> found a " .. peripheral.getType(iface))
_ppm_sys.mounts[iface] = peri_init(iface)
@@ -167,15 +171,15 @@ function mount(iface)
end
-- handle peripheral_detach event
function handle_unmount(iface)
ppm.handle_unmount = function (iface)
-- what got disconnected?
local lost_dev = _ppm_sys.mounts[iface]
if lost_dev then
local type = lost_dev.type
log._warning("PPM: lost device " .. type .. " mounted to " .. iface)
log.warning("PPM: lost device " .. type .. " mounted to " .. iface)
else
log._error("PPM: lost device unknown to the PPM mounted to " .. iface)
log.error("PPM: lost device unknown to the PPM mounted to " .. iface)
end
return lost_dev
@@ -184,31 +188,31 @@ end
-- GENERAL ACCESSORS --
-- list all available peripherals
function list_avail()
ppm.list_avail = function ()
return peripheral.getNames()
end
-- list mounted peripherals
function list_mounts()
ppm.list_mounts = function ()
return _ppm_sys.mounts
end
-- get a mounted peripheral by side/interface
function get_periph(iface)
ppm.get_periph = function (iface)
if _ppm_sys.mounts[iface] then
return _ppm_sys.mounts[iface].dev
else return nil end
end
-- get a mounted peripheral type by side/interface
function get_type(iface)
ppm.get_type = function (iface)
if _ppm_sys.mounts[iface] then
return _ppm_sys.mounts[iface].type
else return nil end
end
-- get all mounted peripherals by type
function get_all_devices(name)
ppm.get_all_devices = function (name)
local devices = {}
for side, data in pairs(_ppm_sys.mounts) do
@@ -221,7 +225,7 @@ function get_all_devices(name)
end
-- get a mounted peripheral by type (if multiple, returns the first)
function get_device(name)
ppm.get_device = function (name)
local device = nil
for side, data in pairs(_ppm_sys.mounts) do
@@ -237,12 +241,12 @@ end
-- SPECIFIC DEVICE ACCESSORS --
-- get the fission reactor (if multiple, returns the first)
function get_fission_reactor()
return get_device("fissionReactor")
ppm.get_fission_reactor = function ()
return ppm.get_device("fissionReactor")
end
-- get the wireless modem (if multiple, returns the first)
function get_wireless_modem()
ppm.get_wireless_modem = function ()
local w_modem = nil
for side, device in pairs(_ppm_sys.mounts) do
@@ -256,6 +260,8 @@ function get_wireless_modem()
end
-- list all connected monitors
function list_monitors()
return get_all_devices("monitor")
ppm.list_monitors = function ()
return ppm.get_all_devices("monitor")
end
return ppm

View File

@@ -1,21 +1,27 @@
IO_LVL = {
--
-- Redstone I/O
--
local rsio = {}
local IO_LVL = {
LOW = 0,
HIGH = 1
}
IO_DIR = {
local IO_DIR = {
IN = 0,
OUT = 1
}
IO_MODE = {
local IO_MODE = {
DIGITAL_OUT = 0,
DIGITAL_IN = 1,
ANALOG_OUT = 2,
ANALOG_IN = 3
}
RS_IO = {
local RS_IO = {
-- digital inputs --
-- facility
@@ -53,7 +59,12 @@ RS_IO = {
A_T_FLOW_RATE = 21 -- turbine flow rate percentage
}
function to_string(channel)
rsio.IO_LVL = IO_LVL
rsio.IO_DIR = IO_DIR
rsio.IO_MODE = IO_MODE
rsio.IO = RS_IO
rsio.to_string = function (channel)
local names = {
"F_SCRAM",
"F_AE2_LIVE",
@@ -85,11 +96,11 @@ function to_string(channel)
end
end
function is_valid_channel(channel)
rsio.is_valid_channel = function (channel)
return channel ~= nil and channel > 0 and channel <= RS_IO.A_T_FLOW_RATE
end
function is_valid_side(side)
rsio.is_valid_side = function (side)
if side ~= nil then
for _, s in pairs(rs.getSides()) do
if s == side then return true end
@@ -98,7 +109,7 @@ function is_valid_side(side)
return false
end
function is_color(color)
rsio.is_color = function (color)
return (color > 0) and (bit.band(color, (color - 1)) == 0);
end
@@ -149,7 +160,7 @@ local RS_DIO_MAP = {
{ _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }
}
function get_io_mode(channel)
rsio.get_io_mode = function (channel)
local modes = {
IO_MODE.DIGITAL_IN, -- F_SCRAM
IO_MODE.DIGITAL_IN, -- F_AE2_LIVE
@@ -182,7 +193,7 @@ function get_io_mode(channel)
end
-- get digital IO level reading
function digital_read(rs_value)
rsio.digital_read = function (rs_value)
if rs_value then
return IO_LVL.HIGH
else
@@ -191,7 +202,7 @@ function digital_read(rs_value)
end
-- returns the level corresponding to active
function digital_write(channel, active)
rsio.digital_write = function (channel, active)
if channel < RS_IO.WASTE_PO or channel > RS_IO.R_PLC_TIMEOUT then
return IO_LVL.LOW
else
@@ -200,10 +211,12 @@ function digital_write(channel, active)
end
-- returns true if the level corresponds to active
function digital_is_active(channel, level)
rsio.digital_is_active = function (channel, level)
if channel > RS_IO.R_ENABLE or channel > RS_IO.R_PLC_TIMEOUT then
return false
else
return RS_DIO_MAP[channel]._f(level)
end
end
return rsio

View File

@@ -1,6 +1,10 @@
--
-- Global Types
--
rtu_t = {
local types = {}
types.rtu_t = {
redstone = "redstone",
boiler = "boiler",
boiler_valve = "boiler_valve",
@@ -10,7 +14,7 @@ rtu_t = {
induction_matrix = "induction_matrix"
}
iss_status_t = {
types.iss_status_t = {
ok = "ok",
dmg_crit = "dmg_crit",
ex_hcoolant = "heated_coolant_backup",
@@ -24,7 +28,7 @@ iss_status_t = {
-- MODBUS
-- modbus function codes
local MODBUS_FCODE = {
types.MODBUS_FCODE = {
READ_COILS = 0x01,
READ_DISCRETE_INPUTS = 0x02,
READ_MUL_HOLD_REGS = 0x03,
@@ -37,7 +41,7 @@ local MODBUS_FCODE = {
}
-- modbus exception codes
local MODBUS_EXCODE = {
types.MODBUS_EXCODE = {
ILLEGAL_FUNCTION = 0x01,
ILLEGAL_DATA_ADDR = 0x02,
ILLEGAL_DATA_VALUE = 0x03,
@@ -49,3 +53,5 @@ local MODBUS_EXCODE = {
GATEWAY_PATH_UNAVAILABLE = 0x0A,
GATEWAY_TARGET_TIMEOUT = 0x0B
}
return types

View File

@@ -1,70 +1,69 @@
local util = {}
-- PRINT --
-- we are overwriting 'print' so save it first
local _print = print
-- print
function print(message)
util.print = function (message)
term.write(message)
end
-- print line
function println(message)
_print(message)
util.println = function (message)
print(message)
end
-- timestamped print
function print_ts(message)
util.print_ts = function (message)
term.write(os.date("[%H:%M:%S] ") .. message)
end
-- timestamped print line
function println_ts(message)
_print(os.date("[%H:%M:%S] ") .. message)
util.println_ts = function (message)
print(os.date("[%H:%M:%S] ") .. message)
end
-- TIME --
function time_ms()
util.time_ms = function ()
return os.epoch('local')
end
function time_s()
util.time_s = function ()
return os.epoch('local') / 1000
end
function time()
return time_ms()
util.time = function ()
return util.time_ms()
end
-- PARALLELIZATION --
-- protected sleep call so we still are in charge of catching termination
function psleep(t)
util.psleep = function (t)
pcall(os.sleep, t)
end
-- no-op to provide a brief pause (and a yield)
-- EVENT_CONSUMER: this function consumes events
function nop()
psleep(0.05)
util.nop = function ()
util.psleep(0.05)
end
-- attempt to maintain a minimum loop timing (duration of execution)
function adaptive_delay(target_timing, last_update)
local sleep_for = target_timing - (time() - last_update)
util.adaptive_delay = function (target_timing, last_update)
local sleep_for = target_timing - (util.time() - last_update)
-- only if >50ms since worker loops already yield 0.05s
if sleep_for >= 50 then
psleep(sleep_for / 1000.0)
util.psleep(sleep_for / 1000.0)
end
return time()
return util.time()
end
-- WATCHDOG --
-- ComputerCraft OS Timer based Watchdog
-- triggers a timer event if not fed within 'timeout' seconds
function new_watchdog(timeout)
util.new_watchdog = function (timeout)
local self = {
_timeout = timeout,
_wd_timer = os.startTimer(timeout)
@@ -93,3 +92,5 @@ function new_watchdog(timeout)
cancel = cancel
}
end
return util