2022-05-04 17:37:01 +00:00
|
|
|
--
|
|
|
|
-- Redstone I/O
|
|
|
|
--
|
|
|
|
|
2022-06-05 20:51:38 +00:00
|
|
|
local util = require("scada-common.util")
|
|
|
|
|
2022-12-01 04:31:14 +00:00
|
|
|
---@class rsio
|
2022-05-04 17:37:01 +00:00
|
|
|
local rsio = {}
|
|
|
|
|
2022-05-06 15:11:53 +00:00
|
|
|
----------------------
|
|
|
|
-- RS I/O CONSTANTS --
|
|
|
|
----------------------
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
---@alias IO_LVL integer
|
2022-05-04 17:37:01 +00:00
|
|
|
local IO_LVL = {
|
2022-12-01 04:31:14 +00:00
|
|
|
DISCONNECT = -1, -- use for RTU session to indicate this RTU is not connected to this port
|
2022-03-15 15:58:08 +00:00
|
|
|
LOW = 0,
|
2022-05-15 00:27:06 +00:00
|
|
|
HIGH = 1,
|
2022-12-01 04:31:14 +00:00
|
|
|
FLOATING = 2 -- use for RTU session to indicate this RTU is connected but not yet read
|
2022-03-15 15:58:08 +00:00
|
|
|
}
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
---@alias IO_DIR integer
|
2022-05-04 17:37:01 +00:00
|
|
|
local IO_DIR = {
|
2022-03-23 19:36:14 +00:00
|
|
|
IN = 0,
|
|
|
|
OUT = 1
|
|
|
|
}
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
---@alias IO_MODE integer
|
2022-05-04 17:37:01 +00:00
|
|
|
local IO_MODE = {
|
2022-05-16 14:38:47 +00:00
|
|
|
DIGITAL_IN = 0,
|
|
|
|
DIGITAL_OUT = 1,
|
|
|
|
ANALOG_IN = 2,
|
|
|
|
ANALOG_OUT = 3
|
2022-03-23 19:36:14 +00:00
|
|
|
}
|
|
|
|
|
2022-12-01 04:31:14 +00:00
|
|
|
---@alias IO_PORT integer
|
|
|
|
local IO_PORT = {
|
2022-02-08 20:42:06 +00:00
|
|
|
-- digital inputs --
|
|
|
|
|
|
|
|
-- facility
|
2022-03-15 15:58:08 +00:00
|
|
|
F_SCRAM = 1, -- active low, facility-wide scram
|
2022-02-08 20:42:06 +00:00
|
|
|
|
|
|
|
-- reactor
|
2022-05-16 14:38:47 +00:00
|
|
|
R_SCRAM = 2, -- active low, reactor scram
|
|
|
|
R_ENABLE = 3, -- active high, reactor enable
|
2022-02-08 20:42:06 +00:00
|
|
|
|
|
|
|
-- digital outputs --
|
|
|
|
|
2022-05-16 14:38:47 +00:00
|
|
|
-- facility
|
|
|
|
F_ALARM = 4, -- active high, facility safety alarm
|
|
|
|
|
2022-02-08 20:42:06 +00:00
|
|
|
-- waste
|
2022-12-01 04:31:14 +00:00
|
|
|
WASTE_PU = 5, -- active low, waste -> plutonium -> pellets route
|
|
|
|
WASTE_PO = 6, -- active low, waste -> polonium route
|
|
|
|
WASTE_POPL = 7, -- active low, polonium -> pellets route
|
|
|
|
WASTE_AM = 8, -- active low, polonium -> anti-matter route
|
2022-02-08 20:42:06 +00:00
|
|
|
|
|
|
|
-- reactor
|
2022-12-01 04:31:14 +00:00
|
|
|
R_ALARM = 9, -- active high, reactor safety alarm
|
|
|
|
R_SCRAMMED = 10, -- active high, if the reactor is scrammed
|
|
|
|
R_AUTO_SCRAM = 11, -- active high, if the reactor was automatically scrammed
|
|
|
|
R_ACTIVE = 12, -- active high, if the reactor is active
|
|
|
|
R_AUTO_CTRL = 13, -- active high, if the reactor burn rate is automatic
|
|
|
|
R_DMG_CRIT = 14, -- active high, if the reactor damage is critical
|
|
|
|
R_HIGH_TEMP = 15, -- active high, if the reactor is at a high temperature
|
|
|
|
R_NO_COOLANT = 16, -- active high, if the reactor has no coolant
|
|
|
|
R_EXCESS_HC = 17, -- active high, if the reactor has excess heated coolant
|
|
|
|
R_EXCESS_WS = 18, -- active high, if the reactor has excess waste
|
|
|
|
R_INSUFF_FUEL = 19, -- active high, if the reactor has insufficent fuel
|
|
|
|
R_PLC_FAULT = 20, -- active high, if the reactor PLC reports a device access fault
|
2023-02-13 23:20:48 +00:00
|
|
|
R_PLC_TIMEOUT = 21, -- active high, if the reactor PLC has not been heard from
|
|
|
|
|
|
|
|
-- unit outputs
|
|
|
|
U_EMER_COOL = 22 -- active low, emergency coolant control
|
2022-02-08 20:42:06 +00:00
|
|
|
}
|
2022-03-15 15:58:08 +00:00
|
|
|
|
2022-05-04 17:37:01 +00:00
|
|
|
rsio.IO_LVL = IO_LVL
|
|
|
|
rsio.IO_DIR = IO_DIR
|
|
|
|
rsio.IO_MODE = IO_MODE
|
2022-12-01 04:31:14 +00:00
|
|
|
rsio.IO = IO_PORT
|
2022-05-04 17:37:01 +00:00
|
|
|
|
2022-05-06 15:11:53 +00:00
|
|
|
-----------------------
|
|
|
|
-- UTILITY FUNCTIONS --
|
|
|
|
-----------------------
|
|
|
|
|
2022-12-01 04:31:14 +00:00
|
|
|
-- port to string
|
|
|
|
---@param port IO_PORT
|
|
|
|
function rsio.to_string(port)
|
2022-03-23 19:36:14 +00:00
|
|
|
local names = {
|
|
|
|
"F_SCRAM",
|
|
|
|
"R_SCRAM",
|
|
|
|
"R_ENABLE",
|
2022-05-16 14:38:47 +00:00
|
|
|
"F_ALARM",
|
2022-03-23 19:36:14 +00:00
|
|
|
"WASTE_PU",
|
2022-12-01 04:31:14 +00:00
|
|
|
"WASTE_PO",
|
|
|
|
"WASTE_POPL",
|
2022-03-23 19:36:14 +00:00
|
|
|
"WASTE_AM",
|
2022-05-16 14:38:47 +00:00
|
|
|
"R_ALARM",
|
2022-03-23 19:36:14 +00:00
|
|
|
"R_SCRAMMED",
|
|
|
|
"R_AUTO_SCRAM",
|
|
|
|
"R_ACTIVE",
|
|
|
|
"R_AUTO_CTRL",
|
|
|
|
"R_DMG_CRIT",
|
|
|
|
"R_HIGH_TEMP",
|
|
|
|
"R_NO_COOLANT",
|
|
|
|
"R_EXCESS_HC",
|
|
|
|
"R_EXCESS_WS",
|
|
|
|
"R_INSUFF_FUEL",
|
2022-05-16 14:38:47 +00:00
|
|
|
"R_PLC_FAULT",
|
2023-02-13 23:20:48 +00:00
|
|
|
"R_PLC_TIMEOUT",
|
|
|
|
"U_EMER_COOL"
|
2022-03-23 19:36:14 +00:00
|
|
|
}
|
|
|
|
|
2022-12-01 04:31:14 +00:00
|
|
|
if util.is_int(port) and port > 0 and port <= #names then
|
|
|
|
return names[port]
|
2022-03-23 19:36:14 +00:00
|
|
|
else
|
|
|
|
return ""
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-05-06 15:11:53 +00:00
|
|
|
local _B_AND = bit.band
|
2022-03-23 19:36:14 +00:00
|
|
|
|
2022-12-01 04:31:14 +00:00
|
|
|
local function _I_ACTIVE_HIGH(level) return level == IO_LVL.HIGH end
|
|
|
|
local function _I_ACTIVE_LOW(level) return level == IO_LVL.LOW end
|
|
|
|
local function _O_ACTIVE_HIGH(active) if active then return IO_LVL.HIGH else return IO_LVL.LOW end end
|
|
|
|
local function _O_ACTIVE_LOW(active) if active then return IO_LVL.LOW else return IO_LVL.HIGH end end
|
2022-03-15 15:58:08 +00:00
|
|
|
|
2022-03-23 19:36:14 +00:00
|
|
|
-- I/O mappings to I/O function and I/O mode
|
2022-03-15 15:58:08 +00:00
|
|
|
local RS_DIO_MAP = {
|
|
|
|
-- F_SCRAM
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN },
|
2022-03-15 15:58:08 +00:00
|
|
|
-- R_SCRAM
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN },
|
2022-03-15 15:58:08 +00:00
|
|
|
-- R_ENABLE
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
|
2022-05-16 14:38:47 +00:00
|
|
|
-- F_ALARM
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
2022-03-15 15:58:08 +00:00
|
|
|
-- WASTE_PU
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _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 },
|
2022-03-15 15:58:08 +00:00
|
|
|
-- WASTE_AM
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
2022-05-16 14:38:47 +00:00
|
|
|
-- R_ALARM
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
2022-03-15 15:58:08 +00:00
|
|
|
-- R_SCRAMMED
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
2022-03-15 15:58:08 +00:00
|
|
|
-- R_AUTO_SCRAM
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
2022-03-15 15:58:08 +00:00
|
|
|
-- R_ACTIVE
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
2022-03-15 15:58:08 +00:00
|
|
|
-- R_AUTO_CTRL
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
2022-03-15 15:58:08 +00:00
|
|
|
-- R_DMG_CRIT
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
2022-03-15 15:58:08 +00:00
|
|
|
-- R_HIGH_TEMP
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
2022-03-15 15:58:08 +00:00
|
|
|
-- R_NO_COOLANT
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
2022-03-15 15:58:08 +00:00
|
|
|
-- R_EXCESS_HC
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
2022-03-15 15:58:08 +00:00
|
|
|
-- R_EXCESS_WS
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
2022-03-15 15:58:08 +00:00
|
|
|
-- R_INSUFF_FUEL
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
2022-05-16 14:38:47 +00:00
|
|
|
-- R_PLC_FAULT
|
2022-12-01 04:31:14 +00:00
|
|
|
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
2022-03-15 15:58:08 +00:00
|
|
|
-- R_PLC_TIMEOUT
|
2023-02-13 23:20:48 +00:00
|
|
|
{ _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 }
|
2022-03-15 15:58:08 +00:00
|
|
|
}
|
|
|
|
|
2022-12-01 04:31:14 +00:00
|
|
|
-- get the mode of a port
|
|
|
|
---@param port IO_PORT
|
2022-05-10 21:06:27 +00:00
|
|
|
---@return IO_MODE
|
2022-12-01 04:31:14 +00:00
|
|
|
function rsio.get_io_mode(port)
|
2022-03-23 19:36:14 +00:00
|
|
|
local modes = {
|
|
|
|
IO_MODE.DIGITAL_IN, -- F_SCRAM
|
|
|
|
IO_MODE.DIGITAL_IN, -- R_SCRAM
|
|
|
|
IO_MODE.DIGITAL_IN, -- R_ENABLE
|
2022-05-16 14:38:47 +00:00
|
|
|
IO_MODE.DIGITAL_OUT, -- F_ALARM
|
2022-03-23 19:36:14 +00:00
|
|
|
IO_MODE.DIGITAL_OUT, -- WASTE_PU
|
2022-12-01 04:31:14 +00:00
|
|
|
IO_MODE.DIGITAL_OUT, -- WASTE_PO
|
|
|
|
IO_MODE.DIGITAL_OUT, -- WASTE_POPL
|
2022-03-23 19:36:14 +00:00
|
|
|
IO_MODE.DIGITAL_OUT, -- WASTE_AM
|
2022-05-16 14:38:47 +00:00
|
|
|
IO_MODE.DIGITAL_OUT, -- R_ALARM
|
2022-03-23 19:36:14 +00:00
|
|
|
IO_MODE.DIGITAL_OUT, -- R_SCRAMMED
|
|
|
|
IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM
|
|
|
|
IO_MODE.DIGITAL_OUT, -- R_ACTIVE
|
|
|
|
IO_MODE.DIGITAL_OUT, -- R_AUTO_CTRL
|
|
|
|
IO_MODE.DIGITAL_OUT, -- R_DMG_CRIT
|
|
|
|
IO_MODE.DIGITAL_OUT, -- R_HIGH_TEMP
|
|
|
|
IO_MODE.DIGITAL_OUT, -- R_NO_COOLANT
|
|
|
|
IO_MODE.DIGITAL_OUT, -- R_EXCESS_HC
|
|
|
|
IO_MODE.DIGITAL_OUT, -- R_EXCESS_WS
|
|
|
|
IO_MODE.DIGITAL_OUT, -- R_INSUFF_FUEL
|
2022-05-16 14:38:47 +00:00
|
|
|
IO_MODE.DIGITAL_OUT, -- R_PLC_FAULT
|
2023-02-13 23:20:48 +00:00
|
|
|
IO_MODE.DIGITAL_OUT, -- R_PLC_TIMEOUT
|
|
|
|
IO_MODE.DIGITAL_OUT -- U_EMER_COOL
|
2022-03-23 19:36:14 +00:00
|
|
|
}
|
|
|
|
|
2022-12-01 04:31:14 +00:00
|
|
|
if util.is_int(port) and port > 0 and port <= #modes then
|
|
|
|
return modes[port]
|
2022-03-23 19:36:14 +00:00
|
|
|
else
|
|
|
|
return IO_MODE.ANALOG_IN
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-05-06 15:11:53 +00:00
|
|
|
--------------------
|
|
|
|
-- GENERIC CHECKS --
|
|
|
|
--------------------
|
|
|
|
|
|
|
|
local RS_SIDES = rs.getSides()
|
|
|
|
|
2022-12-01 04:31:14 +00:00
|
|
|
-- check if a port is valid
|
|
|
|
---@param port IO_PORT
|
2022-05-10 21:06:27 +00:00
|
|
|
---@return boolean valid
|
2022-12-01 04:31:14 +00:00
|
|
|
function rsio.is_valid_port(port)
|
2023-02-13 23:20:48 +00:00
|
|
|
return util.is_int(port) and (port > 0) and (port <= IO_PORT.U_EMER_COOL)
|
2022-05-06 15:11:53 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- check if a side is valid
|
2022-05-10 21:06:27 +00:00
|
|
|
---@param side string
|
|
|
|
---@return boolean valid
|
2022-05-31 20:09:06 +00:00
|
|
|
function rsio.is_valid_side(side)
|
2022-05-06 15:11:53 +00:00
|
|
|
if side ~= nil then
|
|
|
|
for i = 0, #RS_SIDES do
|
|
|
|
if RS_SIDES[i] == side then return true end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
-- check if a color is a valid single color
|
2022-05-10 21:06:27 +00:00
|
|
|
---@param color integer
|
|
|
|
---@return boolean valid
|
2022-05-31 20:09:06 +00:00
|
|
|
function rsio.is_color(color)
|
2022-10-20 17:59:35 +00:00
|
|
|
return util.is_int(color) and (color > 0) and (_B_AND(color, (color - 1)) == 0)
|
2022-05-06 15:11:53 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-----------------
|
|
|
|
-- DIGITAL I/O --
|
|
|
|
-----------------
|
|
|
|
|
2022-12-01 04:31:14 +00:00
|
|
|
-- get digital I/O level reading from a redstone boolean input value
|
2022-05-10 21:06:27 +00:00
|
|
|
---@param rs_value boolean
|
|
|
|
---@return IO_LVL
|
2022-05-31 20:09:06 +00:00
|
|
|
function rsio.digital_read(rs_value)
|
2022-03-15 15:58:08 +00:00
|
|
|
if rs_value then
|
|
|
|
return IO_LVL.HIGH
|
|
|
|
else
|
|
|
|
return IO_LVL.LOW
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-12-01 04:31:14 +00:00
|
|
|
-- get redstone boolean output value corresponding to a digital I/O level
|
2022-05-27 22:10:06 +00:00
|
|
|
---@param level IO_LVL
|
|
|
|
---@return boolean
|
2022-12-01 04:31:14 +00:00
|
|
|
function rsio.digital_write(level)
|
|
|
|
return level == IO_LVL.HIGH
|
|
|
|
end
|
|
|
|
|
|
|
|
-- returns the level corresponding to active
|
|
|
|
---@param port IO_PORT
|
|
|
|
---@param active boolean
|
|
|
|
---@return IO_LVL|false
|
|
|
|
function rsio.digital_write_active(port, active)
|
2023-02-13 23:20:48 +00:00
|
|
|
if (not util.is_int(port)) or (port < IO_PORT.F_ALARM) or (port > IO_PORT.U_EMER_COOL) then
|
2022-05-27 22:10:06 +00:00
|
|
|
return false
|
2022-03-15 15:58:08 +00:00
|
|
|
else
|
2022-12-01 04:31:14 +00:00
|
|
|
return RS_DIO_MAP[port]._out(active)
|
2022-03-15 15:58:08 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-03-23 19:36:14 +00:00
|
|
|
-- returns true if the level corresponds to active
|
2022-12-01 04:31:14 +00:00
|
|
|
---@param port IO_PORT
|
2022-05-10 21:06:27 +00:00
|
|
|
---@param level IO_LVL
|
2022-12-01 04:31:14 +00:00
|
|
|
---@return boolean|nil
|
|
|
|
function rsio.digital_is_active(port, level)
|
|
|
|
if (not util.is_int(port)) or (port > IO_PORT.R_ENABLE) then
|
|
|
|
return nil
|
|
|
|
elseif level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then
|
|
|
|
return nil
|
2022-03-15 15:58:08 +00:00
|
|
|
else
|
2022-12-01 04:31:14 +00:00
|
|
|
return RS_DIO_MAP[port]._in(level)
|
2022-03-15 15:58:08 +00:00
|
|
|
end
|
|
|
|
end
|
2022-05-04 17:37:01 +00:00
|
|
|
|
2022-05-16 14:38:47 +00:00
|
|
|
----------------
|
|
|
|
-- ANALOG I/O --
|
|
|
|
----------------
|
|
|
|
|
|
|
|
-- read an analog value scaled from min to max
|
|
|
|
---@param rs_value number redstone reading (0 to 15)
|
|
|
|
---@param min number minimum of range
|
|
|
|
---@param max number maximum of range
|
|
|
|
---@return number value scaled reading (min to max)
|
2022-05-31 20:09:06 +00:00
|
|
|
function rsio.analog_read(rs_value, min, max)
|
2022-05-16 14:38:47 +00:00
|
|
|
local value = rs_value / 15
|
|
|
|
return (value * (max - min)) + min
|
|
|
|
end
|
|
|
|
|
|
|
|
-- write an analog value from the provided scale range
|
|
|
|
---@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)
|
2022-05-31 20:09:06 +00:00
|
|
|
function rsio.analog_write(value, min, max)
|
2022-05-16 14:38:47 +00:00
|
|
|
local scaled_value = (value - min) / (max - min)
|
|
|
|
return scaled_value * 15
|
|
|
|
end
|
|
|
|
|
2022-05-04 17:37:01 +00:00
|
|
|
return rsio
|