#412 updates to RSIO for induction matrix low, high, and analog charge level

This commit is contained in:
Mikayla Fischler 2024-04-20 16:32:18 -04:00
parent a786404092
commit d9efd5b8d2
9 changed files with 226 additions and 199 deletions

View File

@ -2,6 +2,7 @@
-- Configuration GUI -- Configuration GUI
-- --
local constants = require("scada-common.constants")
local log = require("scada-common.log") local log = require("scada-common.log")
local ppm = require("scada-common.ppm") local ppm = require("scada-common.ppm")
local rsio = require("scada-common.rsio") local rsio = require("scada-common.rsio")
@ -39,39 +40,42 @@ local CENTER = core.ALIGN.CENTER
local RIGHT = core.ALIGN.RIGHT local RIGHT = core.ALIGN.RIGHT
-- rsio port descriptions -- rsio port descriptions
local PORT_DESC = { local PORT_DESC_MAP = {
"Facility SCRAM", { IO.F_SCRAM, "Facility SCRAM" },
"Facility Acknowledge", { IO.F_ACK, "Facility Acknowledge" },
"Reactor SCRAM", { IO.R_SCRAM, "Reactor SCRAM" },
"Reactor RPS Reset", { IO.R_RESET, "Reactor RPS Reset" },
"Reactor Enable", { IO.R_ENABLE, "Reactor Enable" },
"Unit Acknowledge", { IO.U_ACK, "Unit Acknowledge" },
"Facility Alarm (high prio)", { IO.F_ALARM, "Facility Alarm (high prio)" },
"Facility Alarm (any)", { IO.F_ALARM_ANY, "Facility Alarm (any)" },
"Waste Plutonium Valve", { IO.F_MATRIX_LOW, "Induction Matrix < " .. (100 * constants.RS_THRESHOLDS.IMATRIX_CHARGE_LOW) .. "%" },
"Waste Polonium Valve", { IO.F_MATRIX_HIGH, "Induction Matrix > " .. (100 * constants.RS_THRESHOLDS.IMATRIX_CHARGE_HIGH) .. "%" },
"Waste Po Pellets Valve", { IO.F_MATRIX_CHG, "Induction Matrix Charge %" },
"Waste Antimatter Valve", { IO.WASTE_PU, "Waste Plutonium Valve" },
"Reactor Active", { IO.WASTE_PO, "Waste Polonium Valve" },
"Reactor in Auto Control", { IO.WASTE_POPL, "Waste Po Pellets Valve" },
"RPS Tripped", { IO.WASTE_AM, "Waste Antimatter Valve" },
"RPS Auto SCRAM", { IO.R_ACTIVE, "Reactor Active" },
"RPS High Damage", { IO.R_AUTO_CTRL, "Reactor in Auto Control" },
"RPS High Temperature", { IO.R_SCRAMMED, "RPS Tripped" },
"RPS Low Coolant", { IO.R_AUTO_SCRAM, "RPS Auto SCRAM" },
"RPS Excess Heated Coolant", { IO.R_HIGH_DMG, "RPS High Damage" },
"RPS Excess Waste", { IO.R_HIGH_TEMP, "RPS High Temperature" },
"RPS Insufficient Fuel", { IO.R_LOW_COOLANT, "RPS Low Coolant" },
"RPS PLC Fault", { IO.R_EXCESS_HC, "RPS Excess Heated Coolant" },
"RPS Supervisor Timeout", { IO.R_EXCESS_WS, "RPS Excess Waste" },
"Unit Alarm", { IO.R_INSUFF_FUEL, "RPS Insufficient Fuel" },
"Unit Emergency Cool. Valve" { IO.R_PLC_FAULT, "RPS PLC Fault" },
{ IO.R_PLC_TIMEOUT, "RPS Supervisor Timeout" },
{ IO.U_ALARM, "Unit Alarm" },
{ IO.U_EMER_COOL, "Unit Emergency Cool. Valve" }
} }
-- designation (0 = facility, 1 = unit) -- designation (0 = facility, 1 = unit)
local PORT_DSGN = { [-1] = 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } local PORT_DSGN = { [-1] = 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }
assert(#PORT_DESC == rsio.NUM_PORTS) assert(#PORT_DESC_MAP == rsio.NUM_PORTS)
assert(#PORT_DSGN == rsio.NUM_PORTS) assert(#PORT_DSGN == rsio.NUM_PORTS)
-- changes to the config data/format to let the user know -- changes to the config data/format to let the user know
@ -1167,14 +1171,17 @@ local function config_view(display)
PushButton{parent=all_w_macro,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">ALL_WASTE",callback=function()new_rs(-1)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.black)} PushButton{parent=all_w_macro,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">ALL_WASTE",callback=function()new_rs(-1)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.black)}
TextBox{parent=all_w_macro,x=16,y=1,width=5,height=1,text="[n/a]",fg_bg=cpair(colors.lightGray,colors.white)} TextBox{parent=all_w_macro,x=16,y=1,width=5,height=1,text="[n/a]",fg_bg=cpair(colors.lightGray,colors.white)}
TextBox{parent=all_w_macro,x=22,y=1,height=1,text="Create all 4 waste entries",fg_bg=cpair(colors.gray,colors.white)} TextBox{parent=all_w_macro,x=22,y=1,height=1,text="Create all 4 waste entries",fg_bg=cpair(colors.gray,colors.white)}
for i = 1, rsio.NUM_PORTS do for i = 1, rsio.NUM_PORTS do
local name = rsio.to_string(i) local p = PORT_DESC_MAP[i][1]
local io_dir = util.trinary(rsio.get_io_dir(i) == rsio.IO_DIR.IN, "[in]", "[out]") local name = rsio.to_string(p)
local btn_color = util.trinary(rsio.get_io_dir(i) == rsio.IO_DIR.IN, colors.yellow, colors.lightBlue) local io_dir = util.trinary(rsio.get_io_dir(p) == rsio.IO_DIR.IN, "[in]", "[out]")
local btn_color = util.trinary(rsio.get_io_dir(p) == rsio.IO_DIR.IN, colors.yellow, colors.lightBlue)
local entry = Div{parent=rs_ports,height=1} local entry = Div{parent=rs_ports,height=1}
PushButton{parent=entry,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">"..name,callback=function()new_rs(i)end,fg_bg=cpair(colors.black,btn_color),active_fg_bg=cpair(colors.white,colors.black)} PushButton{parent=entry,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">"..name,callback=function()new_rs(p)end,fg_bg=cpair(colors.black,btn_color),active_fg_bg=cpair(colors.white,colors.black)}
TextBox{parent=entry,x=16,y=1,width=5,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)} TextBox{parent=entry,x=16,y=1,width=5,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)}
TextBox{parent=entry,x=22,y=1,height=1,text=PORT_DESC[i],fg_bg=cpair(colors.gray,colors.white)} TextBox{parent=entry,x=22,y=1,height=1,text=PORT_DESC_MAP[i][2],fg_bg=cpair(colors.gray,colors.white)}
end end
PushButton{parent=rs_c_2,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=rs_c_2,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}

View File

@ -31,7 +31,7 @@ local sna_rtu = require("rtu.dev.sna_rtu")
local sps_rtu = require("rtu.dev.sps_rtu") local sps_rtu = require("rtu.dev.sps_rtu")
local turbinev_rtu = require("rtu.dev.turbinev_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu")
local RTU_VERSION = "v1.9.4" local RTU_VERSION = "v1.9.5"
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE

View File

@ -66,6 +66,18 @@ constants.ALARM_LIMITS = alarms
--#endregion --#endregion
--#region Supervisor Redstone Activation Thresholds
---@class _rs_threshold_constants
local rs = {}
rs.IMATRIX_CHARGE_LOW = 0.05 -- activation threshold (less than) for F_MATRIX_LOW
rs.IMATRIX_CHARGE_HIGH = 0.95 -- activation threshold (greater than) for F_MATRIX_HIGH
constants.RS_THRESHOLDS = rs
--#endregion
--#region Supervisor Constants --#region Supervisor Constants
-- milliseconds until coolant flow is assumed to be stable enough to enable certain coolant checks -- milliseconds until coolant flow is assumed to be stable enough to enable certain coolant checks

View File

@ -52,6 +52,8 @@ local IO_PORT = {
-- facility -- facility
F_ALARM = 7, -- active high, facility-wide alarm (any high priority unit alarm) F_ALARM = 7, -- active high, facility-wide alarm (any high priority unit alarm)
F_ALARM_ANY = 8, -- active high, any alarm regardless of priority F_ALARM_ANY = 8, -- active high, any alarm regardless of priority
F_MATRIX_LOW = 27, -- active high, induction matrix charge less than
F_MATRIX_HIGH = 28, -- active high, induction matrix charge high
-- waste -- waste
WASTE_PU = 9, -- active low, waste -> plutonium -> pellets route WASTE_PU = 9, -- active low, waste -> plutonium -> pellets route
@ -75,17 +77,27 @@ local IO_PORT = {
-- unit outputs -- unit outputs
U_ALARM = 25, -- active high, unit alarm U_ALARM = 25, -- active high, unit alarm
U_EMER_COOL = 26 -- active low, emergency coolant control U_EMER_COOL = 26, -- active low, emergency coolant control
-- analog outputs --
-- facility
F_MATRIX_CHG = 29 -- analog charge level of the induction matrix
} }
rsio.IO_LVL = IO_LVL rsio.IO_LVL = IO_LVL
rsio.IO_DIR = IO_DIR rsio.IO_DIR = IO_DIR
rsio.IO_MODE = IO_MODE rsio.IO_MODE = IO_MODE
rsio.IO = IO_PORT rsio.IO = IO_PORT
rsio.NUM_PORTS = IO_PORT.U_EMER_COOL
rsio.NUM_PORTS = 29
rsio.NUM_DIG_PORTS = 28
rsio.NUM_ANA_PORTS = 1
-- self checks -- self checks
assert(rsio.NUM_PORTS == (rsio.NUM_DIG_PORTS + rsio.NUM_ANA_PORTS), "port counts inconsistent")
local dup_chk = {} local dup_chk = {}
for _, v in pairs(IO_PORT) do for _, v in pairs(IO_PORT) do
assert(dup_chk[v] ~= true, "duplicate in port list") assert(dup_chk[v] ~= true, "duplicate in port list")
@ -96,64 +108,45 @@ assert(#dup_chk == rsio.NUM_PORTS, "port list malformed")
--#endregion --#endregion
--#region Utility Functions --#region Utility Functions and Attribute Tables
local PORT_NAMES = { local IO = IO_PORT
"F_SCRAM",
"F_ACK",
"R_SCRAM",
"R_RESET",
"R_ENABLE",
"U_ACK",
"F_ALARM",
"F_ALARM_ANY",
"WASTE_PU",
"WASTE_PO",
"WASTE_POPL",
"WASTE_AM",
"R_ACTIVE",
"R_AUTO_CTRL",
"R_SCRAMMED",
"R_AUTO_SCRAM",
"R_HIGH_DMG",
"R_HIGH_TEMP",
"R_LOW_COOLANT",
"R_EXCESS_HC",
"R_EXCESS_WS",
"R_INSUFF_FUEL",
"R_PLC_FAULT",
"R_PLC_TIMEOUT",
"U_ALARM",
"U_EMER_COOL"
}
-- list of all port names
local PORT_NAMES = {}
for k, v in pairs(IO) do PORT_NAMES[v] = k end
-- list of all port I/O modes
local MODES = { local MODES = {
IO_MODE.DIGITAL_IN, -- F_SCRAM [IO.F_SCRAM] = IO_MODE.DIGITAL_IN,
IO_MODE.DIGITAL_IN, -- F_ACK [IO.F_ACK] = IO_MODE.DIGITAL_IN,
IO_MODE.DIGITAL_IN, -- R_SCRAM [IO.R_SCRAM] = IO_MODE.DIGITAL_IN,
IO_MODE.DIGITAL_IN, -- R_RESET [IO.R_RESET] = IO_MODE.DIGITAL_IN,
IO_MODE.DIGITAL_IN, -- R_ENABLE [IO.R_ENABLE] = IO_MODE.DIGITAL_IN,
IO_MODE.DIGITAL_IN, -- U_ACK [IO.U_ACK] = IO_MODE.DIGITAL_IN,
IO_MODE.DIGITAL_OUT, -- F_ALARM [IO.F_ALARM] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- F_ALARM_ANY [IO.F_ALARM_ANY] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- WASTE_PU [IO.F_MATRIX_LOW] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- WASTE_PO [IO.F_MATRIX_HIGH] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- WASTE_POPL [IO.WASTE_PU] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- WASTE_AM [IO.WASTE_PO] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_ACTIVE [IO.WASTE_POPL] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_AUTO_CTRL [IO.WASTE_AM] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_SCRAMMED [IO.R_ACTIVE] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM [IO.R_AUTO_CTRL] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_HIGH_DMG [IO.R_SCRAMMED] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_HIGH_TEMP [IO.R_AUTO_SCRAM] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_LOW_COOLANT [IO.R_HIGH_DMG] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_EXCESS_HC [IO.R_HIGH_TEMP] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_EXCESS_WS [IO.R_LOW_COOLANT] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_INSUFF_FUEL [IO.R_EXCESS_HC] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_PLC_FAULT [IO.R_EXCESS_WS] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_PLC_TIMEOUT [IO.R_INSUFF_FUEL] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- U_ALARM [IO.R_PLC_FAULT] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT -- U_EMER_COOL [IO.R_PLC_TIMEOUT] = IO_MODE.DIGITAL_OUT,
[IO.U_ALARM] = IO_MODE.DIGITAL_OUT,
[IO.U_EMER_COOL] = IO_MODE.DIGITAL_OUT,
[IO.F_MATRIX_CHG] = IO_MODE.ANALOG_OUT
} }
assert(rsio.NUM_PORTS == #PORT_NAMES, "port names length incorrect") assert(rsio.NUM_PORTS == #PORT_NAMES, "port names length incorrect")
@ -179,74 +172,51 @@ local function _O_ACTIVE_LOW(active) if active then return IO_LVL.LOW else retur
-- I/O mappings to I/O function and I/O mode -- I/O mappings to I/O function and I/O mode
local RS_DIO_MAP = { local RS_DIO_MAP = {
-- F_SCRAM [IO.F_SCRAM] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN },
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN }, [IO.F_ACK] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
-- F_ACK
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
-- R_SCRAM [IO.R_SCRAM] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN },
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN }, [IO.R_RESET] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
-- R_RESET [IO.R_ENABLE] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
-- R_ENABLE
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
-- U_ACK [IO.U_ACK] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
-- F_ALARM [IO.F_ALARM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.F_ALARM_ANY] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- F_ALARM_ANY [IO.F_MATRIX_LOW] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.F_MATRIX_HIGH] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- WASTE_PU [IO.WASTE_PU] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, [IO.WASTE_PO] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
-- WASTE_PO [IO.WASTE_POPL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, [IO.WASTE_AM] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
-- WASTE_POPL
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
-- WASTE_AM
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
-- R_ACTIVE [IO.R_ACTIVE] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.R_AUTO_CTRL] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_AUTO_CTRL [IO.R_SCRAMMED] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.R_AUTO_SCRAM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_SCRAMMED [IO.R_HIGH_DMG] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.R_HIGH_TEMP] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_AUTO_SCRAM [IO.R_LOW_COOLANT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.R_EXCESS_HC] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_HIGH_DMG [IO.R_EXCESS_WS] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.R_INSUFF_FUEL] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_HIGH_TEMP [IO.R_PLC_FAULT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.R_PLC_TIMEOUT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_LOW_COOLANT
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_EXCESS_HC
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_EXCESS_WS
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_INSUFF_FUEL
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_PLC_FAULT
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_PLC_TIMEOUT
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- U_ALARM [IO.U_ALARM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.U_EMER_COOL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }
-- U_EMER_COOL
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }
} }
assert(rsio.NUM_PORTS == #RS_DIO_MAP, "RS_DIO_MAP length incorrect") assert(rsio.NUM_DIG_PORTS == #RS_DIO_MAP, "RS_DIO_MAP length incorrect")
-- get the I/O direction of a port -- get the I/O direction of a port
---@nodiscard ---@nodiscard
---@param port IO_PORT ---@param port IO_PORT
---@return IO_DIR ---@return IO_DIR
function rsio.get_io_dir(port) function rsio.get_io_dir(port)
if rsio.is_valid_port(port) then return RS_DIO_MAP[port].mode if rsio.is_valid_port(port) then
return util.trinary(MODES[port] == IO_MODE.DIGITAL_OUT or MODES[port] == IO_MODE.ANALOG_OUT, IO_DIR.OUT, IO_DIR.IN)
else return IO_DIR.IN end else return IO_DIR.IN end
end end
@ -310,6 +280,13 @@ end
--#region Digital I/O --#region Digital I/O
-- check if a port is digital
---@nodiscard
---@param port IO_PORT
function rsio.is_digital(port)
return rsio.is_valid_port(port) and (MODES[port] == IO_MODE.DIGITAL_IN or MODES[port] == IO_MODE.DIGITAL_OUT)
end
-- get digital I/O level reading from a redstone boolean input value -- get digital I/O level reading from a redstone boolean input value
---@nodiscard ---@nodiscard
---@param rs_value boolean raw value from redstone ---@param rs_value boolean raw value from redstone
@ -330,7 +307,7 @@ function rsio.digital_write(level) return level == IO_LVL.HIGH end
---@param active boolean state to convert to logic level ---@param active boolean state to convert to logic level
---@return IO_LVL|false ---@return IO_LVL|false
function rsio.digital_write_active(port, active) 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 if not rsio.is_digital(port) then
return false return false
else else
return RS_DIO_MAP[port]._out(active) return RS_DIO_MAP[port]._out(active)
@ -343,9 +320,7 @@ end
---@param level IO_LVL logic level ---@param level IO_LVL logic level
---@return boolean|nil state true for active, false for inactive, or nil if invalid port or level provided ---@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) function rsio.digital_is_active(port, level)
if not util.is_int(port) then if (not rsio.is_digital(port)) or level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then
return nil
elseif level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then
return nil return nil
else else
return RS_DIO_MAP[port]._in(level) return RS_DIO_MAP[port]._in(level)
@ -356,6 +331,13 @@ end
--#region Analog I/O --#region Analog I/O
-- check if a port is analog
---@nodiscard
---@param port IO_PORT
function rsio.is_analog(port)
return rsio.is_valid_port(port) and (MODES[port] == IO_MODE.ANALOG_IN or MODES[port] == IO_MODE.ANALOG_OUT)
end
-- read an analog value scaled from min to max -- read an analog value scaled from min to max
---@nodiscard ---@nodiscard
---@param rs_value number redstone reading (0 to 15) ---@param rs_value number redstone reading (0 to 15)
@ -372,7 +354,7 @@ end
---@param value number value to write (from min to max range) ---@param value number value to write (from min to max range)
---@param min number minimum of range ---@param min number minimum of range
---@param max number maximum of range ---@param max number maximum of range
---@return number rs_value scaled redstone reading (0 to 15) ---@return integer rs_value scaled redstone reading (0 to 15)
function rsio.analog_write(value, min, max) function rsio.analog_write(value, min, max)
local scaled_value = (value - min) / (max - min) local scaled_value = (value - min) / (max - min)
return math.floor(scaled_value * 15) return math.floor(scaled_value * 15)

View File

@ -22,7 +22,7 @@ local t_pack = table.pack
local util = {} local util = {}
-- scada-common version -- scada-common version
util.version = "1.2.2" util.version = "1.3.0"
util.TICK_TIME_S = 0.05 util.TICK_TIME_S = 0.05
util.TICK_TIME_MS = 50 util.TICK_TIME_MS = 50

View File

@ -300,8 +300,8 @@ function facility.new(config, cooling_conf)
-- calculate moving averages for induction matrix -- calculate moving averages for induction matrix
if self.induction[1] ~= nil then if self.induction[1] ~= nil then
local matrix = self.induction[1] ---@type unit_session local matrix = self.induction[1] ---@type unit_session
local db = matrix.get_db() ---@type imatrix_session_db local db = matrix.get_db() ---@type imatrix_session_db
charge_update = db.tanks.last_update charge_update = db.tanks.last_update
rate_update = db.state.last_update rate_update = db.state.last_update
@ -774,6 +774,16 @@ function facility.new(config, cooling_conf)
self.io_ctl.digital_write(IO.F_ALARM, has_prio_alarm) self.io_ctl.digital_write(IO.F_ALARM, has_prio_alarm)
self.io_ctl.digital_write(IO.F_ALARM_ANY, has_any_alarm) self.io_ctl.digital_write(IO.F_ALARM_ANY, has_any_alarm)
-- update induction matrix related outputs
if self.induction[1] ~= nil then
local matrix = self.induction[1] ---@type unit_session
local db = matrix.get_db() ---@type imatrix_session_db
self.io_ctl.digital_write(IO.F_MATRIX_LOW, db.tanks.energy_fill < const.RS_THRESHOLDS.IMATRIX_CHARGE_LOW)
self.io_ctl.digital_write(IO.F_MATRIX_HIGH, db.tanks.energy_fill > const.RS_THRESHOLDS.IMATRIX_CHARGE_HIGH)
self.io_ctl.analog_write(IO.F_MATRIX_CHG, db.tanks.energy_fill, 0, 1)
end
end end
--#endregion --#endregion

View File

@ -2,6 +2,8 @@
-- Redstone RTU Session I/O Controller -- Redstone RTU Session I/O Controller
-- --
local rsio = require("scada-common.rsio")
local rsctl = {} local rsctl = {}
-- create a new redstone RTU I/O controller -- create a new redstone RTU I/O controller
@ -16,7 +18,7 @@ function rsctl.new(redstone_rtus)
---@return boolean ---@return boolean
function public.is_connected(port) function public.is_connected(port)
for i = 1, #redstone_rtus do for i = 1, #redstone_rtus do
local db = redstone_rtus[i].get_db() ---@type redstone_session_db local db = redstone_rtus[i].get_db() ---@type redstone_session_db
if db.io[port] ~= nil then return true end if db.io[port] ~= nil then return true end
end end
@ -28,8 +30,8 @@ function rsctl.new(redstone_rtus)
---@param value boolean ---@param value boolean
function public.digital_write(port, value) function public.digital_write(port, value)
for i = 1, #redstone_rtus do for i = 1, #redstone_rtus do
local db = redstone_rtus[i].get_db() ---@type redstone_session_db local db = redstone_rtus[i].get_db() ---@type redstone_session_db
local io = db.io[port] ---@type rs_db_dig_io|nil local io = db.io[port] ---@type rs_db_dig_io|nil
if io ~= nil then io.write(value) end if io ~= nil then io.write(value) end
end end
end end
@ -40,12 +42,25 @@ function rsctl.new(redstone_rtus)
---@return boolean|nil ---@return boolean|nil
function public.digital_read(port) function public.digital_read(port)
for i = 1, #redstone_rtus do for i = 1, #redstone_rtus do
local db = redstone_rtus[i].get_db() ---@type redstone_session_db local db = redstone_rtus[i].get_db() ---@type redstone_session_db
local io = db.io[port] ---@type rs_db_dig_io|nil local io = db.io[port] ---@type rs_db_dig_io|nil
if io ~= nil then return io.read() end if io ~= nil then return io.read() end
end end
end end
-- write to an analog redstone port (applies to all RTUs)
---@param port IO_PORT
---@param value number value
---@param min number minimum value for scaling 0 to 15
---@param max number maximum value for scaling 0 to 15
function public.analog_write(port, value, min, max)
for i = 1, #redstone_rtus do
local db = redstone_rtus[i].get_db() ---@type redstone_session_db
local io = db.io[port] ---@type rs_db_ana_io|nil
if io ~= nil then io.write(rsio.analog_write(value, min, max)) end
end
end
return public return public
end end

View File

@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor")
local svsessions = require("supervisor.session.svsessions") local svsessions = require("supervisor.session.svsessions")
local SUPERVISOR_VERSION = "v1.3.6" local SUPERVISOR_VERSION = "v1.3.7"
local println = util.println local println = util.println
local println_ts = util.println_ts local println_ts = util.println_ts

View File

@ -1,16 +1,28 @@
require("/initenv").init_env() require("/initenv").init_env()
local rsio = require("scada-common.rsio") local rsio = require("scada-common.rsio")
local util = require("scada-common.util") local util = require("scada-common.util")
local testutils = require("test.testutils") local testutils = require("test.testutils")
local IO = rsio.IO
local IO_LVL = rsio.IO_LVL
local IO_MODE = rsio.IO_MODE
local print = util.print local print = util.print
local println = util.println local println = util.println
local IO = rsio.IO -- list of inverted digital signals<br>
local IO_LVL = rsio.IO_LVL -- just using the key for a quick lookup, value need to be not nil
local IO_MODE = rsio.IO_MODE local DIG_INV = {
[IO.F_SCRAM] = 0,
[IO.R_SCRAM] = 0,
[IO.WASTE_PU] = 0,
[IO.WASTE_PO] = 0,
[IO.WASTE_POPL] = 0,
[IO.WASTE_AM] = 0,
[IO.U_EMER_COOL] = 0
}
println("starting RSIO tester") println("starting RSIO tester")
println("") println("")
@ -50,8 +62,8 @@ testutils.pause()
println(">>> checking invalid ports:") println(">>> checking invalid ports:")
testutils.test_func("rsio.to_string", rsio.to_string, { -1, 100, false }, "") testutils.test_func("rsio.to_string", rsio.to_string, { -1, 100, false }, "UNKNOWN")
testutils.test_func_nil("rsio.to_string", rsio.to_string, "") testutils.test_func_nil("rsio.to_string", rsio.to_string, "UNKNOWN")
testutils.test_func("rsio.get_io_mode", rsio.get_io_mode, { -1, 100, false }, IO_MODE.ANALOG_IN) testutils.test_func("rsio.get_io_mode", rsio.get_io_mode, { -1, 100, false }, IO_MODE.ANALOG_IN)
testutils.test_func_nil("rsio.get_io_mode", rsio.get_io_mode, IO_MODE.ANALOG_IN) testutils.test_func_nil("rsio.get_io_mode", rsio.get_io_mode, IO_MODE.ANALOG_IN)
@ -100,46 +112,35 @@ println(">>> checking port I/O:")
print("rsio.digital_is_active(...): ") print("rsio.digital_is_active(...): ")
-- check input ports -- check all digital ports
assert(rsio.digital_is_active(IO.F_SCRAM, IO_LVL.LOW) == true, "IO_F_SCRAM_HIGH") for i = 1, rsio.NUM_PORTS do
assert(rsio.digital_is_active(IO.F_SCRAM, IO_LVL.HIGH) == false, "IO_F_SCRAM_LOW") if rsio.get_io_mode(i) == IO_MODE.DIGITAL_IN or rsio.get_io_mode(i) == IO_MODE.DIGITAL_OUT then
assert(rsio.digital_is_active(IO.R_SCRAM, IO_LVL.LOW) == true, "IO_R_SCRAM_HIGH") local high = DIG_INV[i] == nil
assert(rsio.digital_is_active(IO.R_SCRAM, IO_LVL.HIGH) == false, "IO_R_SCRAM_LOW") assert(rsio.digital_is_active(i, IO_LVL.LOW) == not high, "IO_" .. rsio.to_string(i) .. "_LOW")
assert(rsio.digital_is_active(IO.R_ENABLE, IO_LVL.LOW) == false, "IO_R_ENABLE_HIGH") assert(rsio.digital_is_active(i, IO_LVL.HIGH) == high, "IO_" .. rsio.to_string(i) .. "_HIGH")
assert(rsio.digital_is_active(IO.R_ENABLE, IO_LVL.HIGH) == true, "IO_R_ENABLE_LOW") end
end
-- non-inputs should always return LOW assert(rsio.digital_is_active(IO.F_MATRIX_CHG, IO_LVL.LOW) == nil, "ANA_DIG_READ_LOW")
assert(rsio.digital_is_active(IO.F_ALARM, IO_LVL.LOW) == false, "IO_OUT_READ_LOW") assert(rsio.digital_is_active(IO.F_MATRIX_CHG, IO_LVL.HIGH) == nil, "ANA_DIG_READ_HIGH")
assert(rsio.digital_is_active(IO.F_ALARM, IO_LVL.HIGH) == false, "IO_OUT_READ_HIGH")
println("PASS") println("PASS")
-- check output ports -- check digital write
print("rsio.digital_write(...): ") print("rsio.digital_write_active(...): ")
-- check output ports -- check all digital ports
assert(rsio.digital_write_active(IO.F_ALARM, true) == IO_LVL.LOW, "IO_F_ALARM_LOW") for i = 1, rsio.NUM_PORTS do
assert(rsio.digital_write_active(IO.F_ALARM, true) == IO_LVL.HIGH, "IO_F_ALARM_HIGH") if rsio.get_io_mode(i) == IO_MODE.DIGITAL_IN or rsio.get_io_mode(i) == IO_MODE.DIGITAL_OUT then
assert(rsio.digital_write_active(IO.WASTE_PU, true) == IO_LVL.HIGH, "IO_WASTE_PU_HIGH") local high = DIG_INV[i] == nil
assert(rsio.digital_write_active(IO.WASTE_PU, true) == IO_LVL.LOW, "IO_WASTE_PU_LOW") assert(rsio.digital_write_active(i, not high) == IO_LVL.LOW, "IO_" .. rsio.to_string(i) .. "_LOW")
assert(rsio.digital_write_active(IO.WASTE_PO, true) == IO_LVL.HIGH, "IO_WASTE_PO_HIGH") assert(rsio.digital_write_active(i, high) == IO_LVL.HIGH, "IO_" .. rsio.to_string(i) .. "_HIGH")
assert(rsio.digital_write_active(IO.WASTE_PO, true) == IO_LVL.LOW, "IO_WASTE_PO_LOW") end
assert(rsio.digital_write_active(IO.WASTE_POPL, true) == IO_LVL.HIGH, "IO_WASTE_POPL_HIGH")
assert(rsio.digital_write_active(IO.WASTE_POPL, true) == IO_LVL.LOW, "IO_WASTE_POPL_LOW")
assert(rsio.digital_write_active(IO.WASTE_AM, true) == IO_LVL.HIGH, "IO_WASTE_AM_HIGH")
assert(rsio.digital_write_active(IO.WASTE_AM, true) == IO_LVL.LOW, "IO_WASTE_AM_LOW")
-- check all reactor output ports (all are active high)
for i = IO.R_ALARM, (IO.R_PLC_TIMEOUT - IO.R_ALARM + 1) do
assert(rsio.to_string(i) ~= "", "REACTOR_IO_BAD_PORT")
assert(rsio.digital_write_active(i, false) == IO_LVL.LOW, "IO_" .. rsio.to_string(i) .. "_LOW")
assert(rsio.digital_write_active(i, true) == IO_LVL.HIGH, "IO_" .. rsio.to_string(i) .. "_HIGH")
end end
-- non-outputs should always return false assert(rsio.digital_write_active(IO.F_MATRIX_CHG, true) == false, "ANA_DIG_WRITE_TRUE")
assert(rsio.digital_write_active(IO.F_SCRAM, false) == IO_LVL.LOW, "IO_IN_WRITE_FALSE") assert(rsio.digital_write_active(IO.F_MATRIX_CHG, false) == false, "ANA_DIG_WRITE_FALSE")
assert(rsio.digital_write_active(IO.F_SCRAM, true) == IO_LVL.LOW, "IO_IN_WRITE_TRUE")
println("PASS") println("PASS")