cc-mek-scada/scada-common/rsio.lua

347 lines
10 KiB
Lua
Raw Normal View History

--
-- Redstone I/O
--
2022-06-05 20:51:38 +00:00
local util = require("scada-common.util")
---@class rsio
local rsio = {}
2022-05-06 15:11:53 +00:00
----------------------
-- RS I/O CONSTANTS --
----------------------
2023-02-19 05:14:27 +00:00
---@enum IO_LVL I/O logic level
local IO_LVL = {
DISCONNECT = -1, -- use for RTU session to indicate this RTU is not connected to this port
LOW = 0,
2022-05-15 00:27:06 +00:00
HIGH = 1,
FLOATING = 2 -- use for RTU session to indicate this RTU is connected but not yet read
}
2023-02-19 05:14:27 +00:00
---@enum IO_DIR I/O direction
local IO_DIR = {
2022-03-23 19:36:14 +00:00
IN = 0,
OUT = 1
}
2023-02-19 05:14:27 +00:00
---@enum IO_MODE I/O mode (digital/analog input/output)
local IO_MODE = {
DIGITAL_IN = 0,
DIGITAL_OUT = 1,
ANALOG_IN = 2,
ANALOG_OUT = 3
2022-03-23 19:36:14 +00:00
}
2023-02-19 05:14:27 +00:00
---@enum IO_PORT redstone I/O logic port
local IO_PORT = {
2022-02-08 20:42:06 +00:00
-- digital inputs --
-- facility
F_SCRAM = 1, -- active low, facility-wide scram
2023-02-19 05:14:27 +00:00
F_ACK = 2, -- active high, facility alarm acknowledge
2022-02-08 20:42:06 +00:00
-- reactor
2023-02-19 05:14:27 +00:00
R_SCRAM = 3, -- active low, reactor scram
R_RESET = 4, -- active high, reactor RPS reset
R_ENABLE = 5, -- active high, reactor enable
-- unit
U_ACK = 6, -- active high, unit alarm acknowledge
2022-02-08 20:42:06 +00:00
-- digital outputs --
-- facility
F_ALARM = 7, -- active high, facility-wide alarm (any high priority unit alarm)
2022-02-08 20:42:06 +00:00
-- waste
2023-02-19 05:14:27 +00:00
WASTE_PU = 8, -- active low, waste -> plutonium -> pellets route
WASTE_PO = 9, -- active low, waste -> polonium route
WASTE_POPL = 10, -- active low, polonium -> pellets route
WASTE_AM = 11, -- active low, polonium -> anti-matter route
2022-02-08 20:42:06 +00:00
-- reactor
R_ACTIVE = 12, -- active high, if the reactor is active
R_AUTO_CTRL = 13, -- active high, if the reactor burn rate is automatic
2023-02-19 05:14:27 +00:00
R_SCRAMMED = 14, -- active high, if the reactor is scrammed
R_AUTO_SCRAM = 15, -- active high, if the reactor was automatically scrammed
R_HIGH_DMG = 16, -- active high, if the reactor damage is high
2023-02-19 05:14:27 +00:00
R_HIGH_TEMP = 17, -- active high, if the reactor is at a high temperature
R_LOW_COOLANT = 18, -- active high, if the reactor has very low coolant
2023-02-19 05:14:27 +00:00
R_EXCESS_HC = 19, -- active high, if the reactor has excess heated coolant
R_EXCESS_WS = 20, -- active high, if the reactor has excess waste
R_INSUFF_FUEL = 21, -- active high, if the reactor has insufficent fuel
R_PLC_FAULT = 22, -- active high, if the reactor PLC reports a device access fault
R_PLC_TIMEOUT = 23, -- active high, if the reactor PLC has not been heard from
-- unit outputs
U_ALARM = 24, -- active high, unit alarm
2023-02-19 05:14:27 +00:00
U_EMER_COOL = 25 -- active low, emergency coolant control
2022-02-08 20:42:06 +00:00
}
rsio.IO_LVL = IO_LVL
rsio.IO_DIR = IO_DIR
rsio.IO_MODE = IO_MODE
rsio.IO = IO_PORT
2022-05-06 15:11:53 +00:00
-----------------------
-- UTILITY FUNCTIONS --
-----------------------
-- port to string
2023-02-21 15:31:05 +00:00
---@nodiscard
---@param port IO_PORT
function rsio.to_string(port)
2022-03-23 19:36:14 +00:00
local names = {
"F_SCRAM",
2023-02-19 05:14:27 +00:00
"F_ACK",
2022-03-23 19:36:14 +00:00
"R_SCRAM",
2023-02-19 05:14:27 +00:00
"R_RESET",
2022-03-23 19:36:14 +00:00
"R_ENABLE",
2023-02-19 05:14:27 +00:00
"U_ACK",
"F_ALARM",
2022-03-23 19:36:14 +00:00
"WASTE_PU",
"WASTE_PO",
"WASTE_POPL",
2022-03-23 19:36:14 +00:00
"WASTE_AM",
"R_ACTIVE",
"R_AUTO_CTRL",
2023-02-19 05:14:27 +00:00
"R_SCRAMMED",
"R_AUTO_SCRAM",
"R_HIGH_DMG",
2022-03-23 19:36:14 +00:00
"R_HIGH_TEMP",
"R_LOW_COOLANT",
2022-03-23 19:36:14 +00:00
"R_EXCESS_HC",
"R_EXCESS_WS",
"R_INSUFF_FUEL",
"R_PLC_FAULT",
"R_PLC_TIMEOUT",
2023-02-19 05:14:27 +00:00
"U_ALARM",
"U_EMER_COOL"
2022-03-23 19:36: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
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-23 19:36:14 +00:00
-- 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 },
2023-02-19 05:14:27 +00:00
-- 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 },
2023-02-19 05:14:27 +00:00
-- 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 },
2023-02-19 05:14:27 +00:00
-- 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 },
2023-02-19 05:14:27 +00:00
-- 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 },
2023-02-19 05:14:27 +00:00
-- 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 },
2023-02-19 05:14:27 +00:00
-- 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 }
}
-- get the mode of a port
2023-02-21 15:31:05 +00:00
---@nodiscard
---@param port IO_PORT
2022-05-10 21:06:27 +00:00
---@return IO_MODE
function rsio.get_io_mode(port)
2022-03-23 19:36:14 +00:00
local modes = {
IO_MODE.DIGITAL_IN, -- F_SCRAM
2023-02-19 05:14:27 +00:00
IO_MODE.DIGITAL_IN, -- F_ACK
2022-03-23 19:36:14 +00:00
IO_MODE.DIGITAL_IN, -- R_SCRAM
2023-02-19 05:14:27 +00:00
IO_MODE.DIGITAL_IN, -- R_RESET
2022-03-23 19:36:14 +00:00
IO_MODE.DIGITAL_IN, -- R_ENABLE
2023-02-19 05:14:27 +00:00
IO_MODE.DIGITAL_IN, -- U_ACK
IO_MODE.DIGITAL_OUT, -- F_ALARM
2022-03-23 19:36:14 +00:00
IO_MODE.DIGITAL_OUT, -- WASTE_PU
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
IO_MODE.DIGITAL_OUT, -- R_ACTIVE
IO_MODE.DIGITAL_OUT, -- R_AUTO_CTRL
2023-02-19 05:14:27 +00:00
IO_MODE.DIGITAL_OUT, -- R_SCRAMMED
IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM
IO_MODE.DIGITAL_OUT, -- R_HIGH_DMG
2022-03-23 19:36:14 +00:00
IO_MODE.DIGITAL_OUT, -- R_HIGH_TEMP
IO_MODE.DIGITAL_OUT, -- R_LOW_COOLANT
2022-03-23 19:36:14 +00:00
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
2023-02-19 05:14:27 +00:00
IO_MODE.DIGITAL_OUT, -- U_ALARM
IO_MODE.DIGITAL_OUT -- U_EMER_COOL
2022-03-23 19:36: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()
-- check if a port is valid
2023-02-21 15:31:05 +00:00
---@nodiscard
---@param port IO_PORT
2022-05-10 21:06:27 +00:00
---@return boolean valid
function rsio.is_valid_port(port)
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
2023-02-21 15:31:05 +00:00
---@nodiscard
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
2023-02-21 15:31:05 +00:00
---@nodiscard
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 --
-----------------
-- get digital I/O level reading from a redstone boolean input value
2023-02-21 15:31:05 +00:00
---@nodiscard
---@param rs_value boolean raw value from redstone
2022-05-10 21:06:27 +00:00
---@return IO_LVL
2022-05-31 20:09:06 +00:00
function rsio.digital_read(rs_value)
2023-02-19 05:14:27 +00:00
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
2023-02-21 15:31:05 +00:00
---@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
2023-02-21 15:31:05 +00:00
---@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
return false
else
return RS_DIO_MAP[port]._out(active)
end
end
2022-03-23 19:36:14 +00:00
-- returns true if the level corresponds to active
2023-02-21 15:31:05 +00:00
---@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
elseif level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then
return nil
else
return RS_DIO_MAP[port]._in(level)
end
end
----------------
-- ANALOG I/O --
----------------
-- read an analog value scaled from min to max
2023-02-21 15:31:05 +00:00
---@nodiscard
---@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)
local value = rs_value / 15
return (value * (max - min)) + min
end
-- write an analog value from the provided scale range
2023-02-21 15:31:05 +00:00
---@nodiscard
---@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)
local scaled_value = (value - min) / (max - min)
2023-02-19 05:14:27 +00:00
return math.floor(scaled_value * 15)
end
return rsio