#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
--
local constants = require("scada-common.constants")
local log = require("scada-common.log")
local ppm = require("scada-common.ppm")
local rsio = require("scada-common.rsio")
@ -39,39 +40,42 @@ local CENTER = core.ALIGN.CENTER
local RIGHT = core.ALIGN.RIGHT
-- rsio port descriptions
local PORT_DESC = {
"Facility SCRAM",
"Facility Acknowledge",
"Reactor SCRAM",
"Reactor RPS Reset",
"Reactor Enable",
"Unit Acknowledge",
"Facility Alarm (high prio)",
"Facility Alarm (any)",
"Waste Plutonium Valve",
"Waste Polonium Valve",
"Waste Po Pellets Valve",
"Waste Antimatter Valve",
"Reactor Active",
"Reactor in Auto Control",
"RPS Tripped",
"RPS Auto SCRAM",
"RPS High Damage",
"RPS High Temperature",
"RPS Low Coolant",
"RPS Excess Heated Coolant",
"RPS Excess Waste",
"RPS Insufficient Fuel",
"RPS PLC Fault",
"RPS Supervisor Timeout",
"Unit Alarm",
"Unit Emergency Cool. Valve"
local PORT_DESC_MAP = {
{ IO.F_SCRAM, "Facility SCRAM" },
{ IO.F_ACK, "Facility Acknowledge" },
{ IO.R_SCRAM, "Reactor SCRAM" },
{ IO.R_RESET, "Reactor RPS Reset" },
{ IO.R_ENABLE, "Reactor Enable" },
{ IO.U_ACK, "Unit Acknowledge" },
{ IO.F_ALARM, "Facility Alarm (high prio)" },
{ IO.F_ALARM_ANY, "Facility Alarm (any)" },
{ IO.F_MATRIX_LOW, "Induction Matrix < " .. (100 * constants.RS_THRESHOLDS.IMATRIX_CHARGE_LOW) .. "%" },
{ IO.F_MATRIX_HIGH, "Induction Matrix > " .. (100 * constants.RS_THRESHOLDS.IMATRIX_CHARGE_HIGH) .. "%" },
{ IO.F_MATRIX_CHG, "Induction Matrix Charge %" },
{ IO.WASTE_PU, "Waste Plutonium Valve" },
{ IO.WASTE_PO, "Waste Polonium Valve" },
{ IO.WASTE_POPL, "Waste Po Pellets Valve" },
{ IO.WASTE_AM, "Waste Antimatter Valve" },
{ IO.R_ACTIVE, "Reactor Active" },
{ IO.R_AUTO_CTRL, "Reactor in Auto Control" },
{ IO.R_SCRAMMED, "RPS Tripped" },
{ IO.R_AUTO_SCRAM, "RPS Auto SCRAM" },
{ IO.R_HIGH_DMG, "RPS High Damage" },
{ IO.R_HIGH_TEMP, "RPS High Temperature" },
{ IO.R_LOW_COOLANT, "RPS Low Coolant" },
{ IO.R_EXCESS_HC, "RPS Excess Heated Coolant" },
{ IO.R_EXCESS_WS, "RPS Excess Waste" },
{ IO.R_INSUFF_FUEL, "RPS Insufficient Fuel" },
{ 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)
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)
-- 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)}
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)}
for i = 1, rsio.NUM_PORTS do
local name = rsio.to_string(i)
local io_dir = util.trinary(rsio.get_io_dir(i) == rsio.IO_DIR.IN, "[in]", "[out]")
local btn_color = util.trinary(rsio.get_io_dir(i) == rsio.IO_DIR.IN, colors.yellow, colors.lightBlue)
local p = PORT_DESC_MAP[i][1]
local name = rsio.to_string(p)
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}
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=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
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 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_HW_STATE = databus.RTU_UNIT_HW_STATE

View File

@ -66,6 +66,18 @@ constants.ALARM_LIMITS = alarms
--#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
-- 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
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_MATRIX_LOW = 27, -- active high, induction matrix charge less than
F_MATRIX_HIGH = 28, -- active high, induction matrix charge high
-- waste
WASTE_PU = 9, -- active low, waste -> plutonium -> pellets route
@ -75,17 +77,27 @@ local IO_PORT = {
-- unit outputs
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_DIR = IO_DIR
rsio.IO_MODE = IO_MODE
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
assert(rsio.NUM_PORTS == (rsio.NUM_DIG_PORTS + rsio.NUM_ANA_PORTS), "port counts inconsistent")
local dup_chk = {}
for _, v in pairs(IO_PORT) do
assert(dup_chk[v] ~= true, "duplicate in port list")
@ -96,64 +108,45 @@ assert(#dup_chk == rsio.NUM_PORTS, "port list malformed")
--#endregion
--#region Utility Functions
--#region Utility Functions and Attribute Tables
local PORT_NAMES = {
"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"
}
local IO = IO_PORT
-- 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 = {
IO_MODE.DIGITAL_IN, -- F_SCRAM
IO_MODE.DIGITAL_IN, -- F_ACK
IO_MODE.DIGITAL_IN, -- R_SCRAM
IO_MODE.DIGITAL_IN, -- R_RESET
IO_MODE.DIGITAL_IN, -- R_ENABLE
IO_MODE.DIGITAL_IN, -- U_ACK
IO_MODE.DIGITAL_OUT, -- F_ALARM
IO_MODE.DIGITAL_OUT, -- F_ALARM_ANY
IO_MODE.DIGITAL_OUT, -- WASTE_PU
IO_MODE.DIGITAL_OUT, -- WASTE_PO
IO_MODE.DIGITAL_OUT, -- WASTE_POPL
IO_MODE.DIGITAL_OUT, -- WASTE_AM
IO_MODE.DIGITAL_OUT, -- R_ACTIVE
IO_MODE.DIGITAL_OUT, -- R_AUTO_CTRL
IO_MODE.DIGITAL_OUT, -- R_SCRAMMED
IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM
IO_MODE.DIGITAL_OUT, -- R_HIGH_DMG
IO_MODE.DIGITAL_OUT, -- R_HIGH_TEMP
IO_MODE.DIGITAL_OUT, -- R_LOW_COOLANT
IO_MODE.DIGITAL_OUT, -- R_EXCESS_HC
IO_MODE.DIGITAL_OUT, -- R_EXCESS_WS
IO_MODE.DIGITAL_OUT, -- R_INSUFF_FUEL
IO_MODE.DIGITAL_OUT, -- R_PLC_FAULT
IO_MODE.DIGITAL_OUT, -- R_PLC_TIMEOUT
IO_MODE.DIGITAL_OUT, -- U_ALARM
IO_MODE.DIGITAL_OUT -- U_EMER_COOL
[IO.F_SCRAM] = IO_MODE.DIGITAL_IN,
[IO.F_ACK] = IO_MODE.DIGITAL_IN,
[IO.R_SCRAM] = IO_MODE.DIGITAL_IN,
[IO.R_RESET] = IO_MODE.DIGITAL_IN,
[IO.R_ENABLE] = IO_MODE.DIGITAL_IN,
[IO.U_ACK] = IO_MODE.DIGITAL_IN,
[IO.F_ALARM] = IO_MODE.DIGITAL_OUT,
[IO.F_ALARM_ANY] = IO_MODE.DIGITAL_OUT,
[IO.F_MATRIX_LOW] = IO_MODE.DIGITAL_OUT,
[IO.F_MATRIX_HIGH] = IO_MODE.DIGITAL_OUT,
[IO.WASTE_PU] = IO_MODE.DIGITAL_OUT,
[IO.WASTE_PO] = IO_MODE.DIGITAL_OUT,
[IO.WASTE_POPL] = IO_MODE.DIGITAL_OUT,
[IO.WASTE_AM] = IO_MODE.DIGITAL_OUT,
[IO.R_ACTIVE] = IO_MODE.DIGITAL_OUT,
[IO.R_AUTO_CTRL] = IO_MODE.DIGITAL_OUT,
[IO.R_SCRAMMED] = IO_MODE.DIGITAL_OUT,
[IO.R_AUTO_SCRAM] = IO_MODE.DIGITAL_OUT,
[IO.R_HIGH_DMG] = IO_MODE.DIGITAL_OUT,
[IO.R_HIGH_TEMP] = IO_MODE.DIGITAL_OUT,
[IO.R_LOW_COOLANT] = IO_MODE.DIGITAL_OUT,
[IO.R_EXCESS_HC] = IO_MODE.DIGITAL_OUT,
[IO.R_EXCESS_WS] = IO_MODE.DIGITAL_OUT,
[IO.R_INSUFF_FUEL] = IO_MODE.DIGITAL_OUT,
[IO.R_PLC_FAULT] = IO_MODE.DIGITAL_OUT,
[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")
@ -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
local RS_DIO_MAP = {
-- F_SCRAM
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN },
-- F_ACK
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
[IO.F_SCRAM] = { _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 },
-- R_SCRAM
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN },
-- R_RESET
{ _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 },
[IO.R_SCRAM] = { _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 },
[IO.R_ENABLE] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
-- U_ACK
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
[IO.U_ACK] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
-- F_ALARM
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- F_ALARM_ANY
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
[IO.F_ALARM] = { _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 },
[IO.F_MATRIX_LOW] = { _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
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
-- WASTE_PO
{ _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 },
[IO.WASTE_PU] = { _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 },
[IO.WASTE_POPL] = { _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 },
-- R_ACTIVE
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_AUTO_CTRL
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_SCRAMMED
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_AUTO_SCRAM
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_HIGH_DMG
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_HIGH_TEMP
{ _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 },
[IO.R_ACTIVE] = { _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 },
[IO.R_SCRAMMED] = { _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 },
[IO.R_HIGH_DMG] = { _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 },
[IO.R_LOW_COOLANT] = { _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 },
[IO.R_EXCESS_WS] = { _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 },
[IO.R_PLC_FAULT] = { _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 },
-- U_ALARM
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- U_EMER_COOL
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }
[IO.U_ALARM] = { _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 }
}
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
---@nodiscard
---@param port IO_PORT
---@return IO_DIR
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
end
@ -310,6 +280,13 @@ end
--#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
---@nodiscard
---@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
---@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
if not rsio.is_digital(port) then
return false
else
return RS_DIO_MAP[port]._out(active)
@ -343,9 +320,7 @@ end
---@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
elseif level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then
if (not rsio.is_digital(port)) or level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then
return nil
else
return RS_DIO_MAP[port]._in(level)
@ -356,6 +331,13 @@ end
--#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
---@nodiscard
---@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 min number minimum 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)
local scaled_value = (value - min) / (max - min)
return math.floor(scaled_value * 15)

View File

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

View File

@ -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_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
--#endregion

View File

@ -2,6 +2,8 @@
-- Redstone RTU Session I/O Controller
--
local rsio = require("scada-common.rsio")
local rsctl = {}
-- create a new redstone RTU I/O controller
@ -46,6 +48,19 @@ function rsctl.new(redstone_rtus)
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
end

View File

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

View File

@ -5,13 +5,25 @@ local util = require("scada-common.util")
local testutils = require("test.testutils")
local print = util.print
local println = util.println
local IO = rsio.IO
local IO_LVL = rsio.IO_LVL
local IO_MODE = rsio.IO_MODE
local print = util.print
local println = util.println
-- list of inverted digital signals<br>
-- just using the key for a quick lookup, value need to be not nil
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("")
@ -50,8 +62,8 @@ testutils.pause()
println(">>> checking invalid ports:")
testutils.test_func("rsio.to_string", rsio.to_string, { -1, 100, false }, "")
testutils.test_func_nil("rsio.to_string", rsio.to_string, "")
testutils.test_func("rsio.to_string", rsio.to_string, { -1, 100, false }, "UNKNOWN")
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_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(...): ")
-- check input ports
assert(rsio.digital_is_active(IO.F_SCRAM, IO_LVL.LOW) == true, "IO_F_SCRAM_HIGH")
assert(rsio.digital_is_active(IO.F_SCRAM, IO_LVL.HIGH) == false, "IO_F_SCRAM_LOW")
assert(rsio.digital_is_active(IO.R_SCRAM, IO_LVL.LOW) == true, "IO_R_SCRAM_HIGH")
assert(rsio.digital_is_active(IO.R_SCRAM, IO_LVL.HIGH) == false, "IO_R_SCRAM_LOW")
assert(rsio.digital_is_active(IO.R_ENABLE, IO_LVL.LOW) == false, "IO_R_ENABLE_HIGH")
assert(rsio.digital_is_active(IO.R_ENABLE, IO_LVL.HIGH) == true, "IO_R_ENABLE_LOW")
-- check all digital ports
for i = 1, rsio.NUM_PORTS do
if rsio.get_io_mode(i) == IO_MODE.DIGITAL_IN or rsio.get_io_mode(i) == IO_MODE.DIGITAL_OUT then
local high = DIG_INV[i] == nil
assert(rsio.digital_is_active(i, IO_LVL.LOW) == not high, "IO_" .. rsio.to_string(i) .. "_LOW")
assert(rsio.digital_is_active(i, IO_LVL.HIGH) == high, "IO_" .. rsio.to_string(i) .. "_HIGH")
end
end
-- non-inputs should always return LOW
assert(rsio.digital_is_active(IO.F_ALARM, IO_LVL.LOW) == false, "IO_OUT_READ_LOW")
assert(rsio.digital_is_active(IO.F_ALARM, IO_LVL.HIGH) == false, "IO_OUT_READ_HIGH")
assert(rsio.digital_is_active(IO.F_MATRIX_CHG, IO_LVL.LOW) == nil, "ANA_DIG_READ_LOW")
assert(rsio.digital_is_active(IO.F_MATRIX_CHG, IO_LVL.HIGH) == nil, "ANA_DIG_READ_HIGH")
println("PASS")
-- check output ports
-- check digital write
print("rsio.digital_write(...): ")
print("rsio.digital_write_active(...): ")
-- check output ports
assert(rsio.digital_write_active(IO.F_ALARM, true) == IO_LVL.LOW, "IO_F_ALARM_LOW")
assert(rsio.digital_write_active(IO.F_ALARM, true) == IO_LVL.HIGH, "IO_F_ALARM_HIGH")
assert(rsio.digital_write_active(IO.WASTE_PU, true) == IO_LVL.HIGH, "IO_WASTE_PU_HIGH")
assert(rsio.digital_write_active(IO.WASTE_PU, true) == IO_LVL.LOW, "IO_WASTE_PU_LOW")
assert(rsio.digital_write_active(IO.WASTE_PO, true) == IO_LVL.HIGH, "IO_WASTE_PO_HIGH")
assert(rsio.digital_write_active(IO.WASTE_PO, true) == IO_LVL.LOW, "IO_WASTE_PO_LOW")
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")
-- check all digital ports
for i = 1, rsio.NUM_PORTS do
if rsio.get_io_mode(i) == IO_MODE.DIGITAL_IN or rsio.get_io_mode(i) == IO_MODE.DIGITAL_OUT then
local high = DIG_INV[i] == nil
assert(rsio.digital_write_active(i, not high) == IO_LVL.LOW, "IO_" .. rsio.to_string(i) .. "_LOW")
assert(rsio.digital_write_active(i, high) == IO_LVL.HIGH, "IO_" .. rsio.to_string(i) .. "_HIGH")
end
end
-- non-outputs should always return false
assert(rsio.digital_write_active(IO.F_SCRAM, false) == IO_LVL.LOW, "IO_IN_WRITE_FALSE")
assert(rsio.digital_write_active(IO.F_SCRAM, true) == IO_LVL.LOW, "IO_IN_WRITE_TRUE")
assert(rsio.digital_write_active(IO.F_MATRIX_CHG, true) == false, "ANA_DIG_WRITE_TRUE")
assert(rsio.digital_write_active(IO.F_MATRIX_CHG, false) == false, "ANA_DIG_WRITE_FALSE")
println("PASS")