#159 linked up redstone I/O

This commit is contained in:
Mikayla Fischler 2023-02-19 00:14:27 -05:00
parent caa6cc81b1
commit cc5ea0dbb0
6 changed files with 201 additions and 63 deletions

View File

@ -25,7 +25,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 = "beta-v0.10.6"
local RTU_VERSION = "beta-v0.11.0"
local rtu_t = types.rtu_t

View File

@ -11,7 +11,7 @@ local rsio = {}
-- RS I/O CONSTANTS --
----------------------
---@alias IO_LVL integer
---@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,
@ -19,13 +19,13 @@ local IO_LVL = {
FLOATING = 2 -- use for RTU session to indicate this RTU is connected but not yet read
}
---@alias IO_DIR integer
---@enum IO_DIR I/O direction
local IO_DIR = {
IN = 0,
OUT = 1
}
---@alias IO_MODE integer
---@enum IO_MODE I/O mode (digital/analog input/output)
local IO_MODE = {
DIGITAL_IN = 0,
DIGITAL_OUT = 1,
@ -33,45 +33,50 @@ local IO_MODE = {
ANALOG_OUT = 3
}
---@alias IO_PORT integer
---@enum IO_PORT redstone I/O logic port
local IO_PORT = {
-- digital inputs --
-- facility
F_SCRAM = 1, -- active low, facility-wide scram
F_ACK = 2, -- active high, facility alarm acknowledge
-- reactor
R_SCRAM = 2, -- active low, reactor scram
R_ENABLE = 3, -- active high, reactor enable
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
-- digital outputs --
-- facility
F_ALARM = 4, -- active high, facility safety alarm
F_ALARM = 7, -- active high, facility alarm (any high priority unit alarm)
-- waste
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
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
-- reactor
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
R_PLC_TIMEOUT = 21, -- active high, if the reactor PLC has not been heard from
R_SCRAMMED = 14, -- active high, if the reactor is scrammed
R_AUTO_SCRAM = 15, -- active high, if the reactor was automatically scrammed
R_DMG_CRIT = 16, -- active high, if the reactor damage is critical
R_HIGH_TEMP = 17, -- active high, if the reactor is at a high temperature
R_NO_COOLANT = 18, -- active high, if the reactor has no coolant
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_EMER_COOL = 22 -- active low, emergency coolant control
U_ALARM = 24, -- active high, unit alarm
U_EMER_COOL = 25 -- active low, emergency coolant control
}
rsio.IO_LVL = IO_LVL
@ -88,18 +93,20 @@ rsio.IO = IO_PORT
function rsio.to_string(port)
local names = {
"F_SCRAM",
"F_ACK",
"R_SCRAM",
"R_RESET",
"R_ENABLE",
"U_ACK",
"F_ALARM",
"WASTE_PU",
"WASTE_PO",
"WASTE_POPL",
"WASTE_AM",
"R_ALARM",
"R_SCRAMMED",
"R_AUTO_SCRAM",
"R_ACTIVE",
"R_AUTO_CTRL",
"R_SCRAMMED",
"R_AUTO_SCRAM",
"R_DMG_CRIT",
"R_HIGH_TEMP",
"R_NO_COOLANT",
@ -108,6 +115,7 @@ function rsio.to_string(port)
"R_INSUFF_FUEL",
"R_PLC_FAULT",
"R_PLC_TIMEOUT",
"U_ALARM",
"U_EMER_COOL"
}
@ -129,12 +137,22 @@ local function _O_ACTIVE_LOW(active) if active then return IO_LVL.LOW else retur
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 },
-- 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 },
-- 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 },
-- WASTE_PU
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
-- WASTE_PO
@ -143,16 +161,15 @@ local RS_DIO_MAP = {
{ _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_ALARM
-- 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_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_DMG_CRIT
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_HIGH_TEMP
@ -169,6 +186,9 @@ local RS_DIO_MAP = {
{ _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
{ _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 }
}
@ -179,18 +199,20 @@ local RS_DIO_MAP = {
function rsio.get_io_mode(port)
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, -- 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_ALARM
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_SCRAMMED
IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM
IO_MODE.DIGITAL_OUT, -- R_DMG_CRIT
IO_MODE.DIGITAL_OUT, -- R_HIGH_TEMP
IO_MODE.DIGITAL_OUT, -- R_NO_COOLANT
@ -199,6 +221,7 @@ function rsio.get_io_mode(port)
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
}
@ -249,11 +272,7 @@ end
---@param rs_value boolean
---@return IO_LVL
function rsio.digital_read(rs_value)
if rs_value then
return IO_LVL.HIGH
else
return IO_LVL.LOW
end
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
@ -280,7 +299,7 @@ end
---@param level IO_LVL
---@return boolean|nil
function rsio.digital_is_active(port, level)
if (not util.is_int(port)) or (port > IO_PORT.R_ENABLE) then
if (not util.is_int(port)) or (port > IO_PORT.U_ACK) then
return nil
elseif level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then
return nil
@ -310,7 +329,7 @@ end
---@return number rs_value scaled redstone reading (0 to 15)
function rsio.analog_write(value, min, max)
local scaled_value = (value - min) / (max - min)
return scaled_value * 15
return math.floor(scaled_value * 15)
end
return rsio

View File

@ -6,11 +6,12 @@ local util = require("scada-common.util")
local unit = require("supervisor.unit")
local rsctl = require("supervisor.session.rsctl")
local unit = require("supervisor.session.unit")
local PROCESS = types.PROCESS
local PROCESS_NAMES = types.PROCESS_NAMES
local IO = rsio.IO
-- 7.14 kJ per blade for 1 mB of fissile fuel<br/>
-- 2856 FE per blade per 1 mB, 285.6 FE per blade per 0.1 mB (minimum)
local POWER_PER_BLADE = util.joules_to_fe(7140)
@ -57,12 +58,15 @@ local facility = {}
function facility.new(num_reactors, cooling_conf)
local self = {
units = {},
status_text = { "START UP", "initializing..." },
all_sys_ok = false,
-- rtus
rtu_conn_count = 0,
redstone = {},
induction = {},
envd = {},
status_text = { "START UP", "initializing..." },
all_sys_ok = false,
rtu_conn_count = 0,
-- redstone I/O control
io_ctl = nil, ---@type rs_controller
-- process control
units_ready = false,
mode = PROCESS.INACTIVE,
@ -111,7 +115,7 @@ function facility.new(num_reactors, cooling_conf)
end
-- init redstone RTU I/O controller
local rs_rtu_io_ctl = rsctl.new(self.redstone)
self.io_ctl = rsctl.new(self.redstone)
-- unlink disconnected units
---@param sessions table
@ -655,6 +659,36 @@ function facility.new(num_reactors, cooling_conf)
-- update last mode and set next mode
self.last_mode = self.mode
self.mode = next_mode
-------------------------
-- Handle Redstone I/O --
-------------------------
if #self.redstone > 0 then
-- handle facility SCRAM
if self.io_ctl.digital_read(IO.F_SCRAM) then
for i = 1, #self.units do
local u = self.units[i] ---@type reactor_unit
u.cond_scram()
end
end
-- handle facility ack
if self.io_ctl.digital_read(IO.F_ACK) then public.ack_all() end
-- update facility alarm output
local has_alarm = false
for i = 1, #self.units do
local u = self.units[i] ---@type reactor_unit
if u.has_critical_alarm() then
has_alarm = true
return
end
end
self.io_ctl.digital_write(IO.F_ALARM, has_alarm)
end
end
-- call the update function of all units in the facility

View File

@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions")
local config = require("supervisor.config")
local supervisor = require("supervisor.supervisor")
local SUPERVISOR_VERSION = "beta-v0.11.10"
local SUPERVISOR_VERSION = "beta-v0.11.11"
local print = util.print
local println = util.println

View File

@ -74,10 +74,14 @@ function unit.new(for_reactor, num_boilers, num_turbines)
num_turbines = num_turbines,
types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE },
defs = { FLOW_STABILITY_DELAY_MS = FLOW_STABILITY_DELAY_MS },
-- rtus
redstone = {},
boilers = {},
turbines = {},
envd = {},
-- redstone control
io_ctl = nil, ---@type rs_controller
valves = {}, ---@type unit_valves
-- auto control
ramp_target_br100 = 0,
-- state tracking
@ -151,7 +155,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
-- waste >85%
ReactorHighWaste = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 2, id = ALARM.ReactorHighWaste, tier = PRIO.TIMELY },
-- RPS trip occured
RPSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 1, id = ALARM.RPSTransient, tier = PRIO.URGENT },
RPSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 1, id = ALARM.RPSTransient, tier = PRIO.TIMELY },
-- BoilRateMismatch, CoolantFeedMismatch, SteamFeedMismatch, MaxWaterReturnFeed
RCSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 5, id = ALARM.RCSTransient, tier = PRIO.TIMELY },
-- "It's just a routine turbin' trip!" -Bill Gibson, "The China Syndrome"
@ -223,7 +227,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
}
-- init redstone RTU I/O controller
local rs_rtu_io_ctl = rsctl.new(self.redstone)
self.io_ctl = rsctl.new(self.redstone)
-- init boiler table fields
for _ = 1, num_boilers do
@ -272,10 +276,8 @@ function unit.new(for_reactor, num_boilers, num_turbines)
-- get the delta t of a value
---@param key string value key
---@return number
function self._get_dt(key)
if self.deltas[key] then return self.deltas[key].dt else return 0.0 end
end
---@return number value value or 0 if not known
function self._get_dt(key) if self.deltas[key] then return self.deltas[key].dt else return 0.0 end end
-- update all delta computations
local function _dt__compute_all()
@ -320,8 +322,8 @@ function unit.new(for_reactor, num_boilers, num_turbines)
--#region redstone I/O
local __rs_w = rs_rtu_io_ctl.digital_write
local __rs_r = rs_rtu_io_ctl.digital_read
local __rs_w = self.io_ctl.digital_write
local __rs_r = self.io_ctl.digital_read
-- valves
local waste_pu = { open = function () __rs_w(IO.WASTE_PU, true) end, close = function () __rs_w(IO.WASTE_PU, false) end }
@ -330,6 +332,15 @@ function unit.new(for_reactor, num_boilers, num_turbines)
local waste_sps = { open = function () __rs_w(IO.WASTE_AM, true) end, close = function () __rs_w(IO.WASTE_AM, false) end }
local emer_cool = { open = function () __rs_w(IO.U_EMER_COOL, true) end, close = function () __rs_w(IO.U_EMER_COOL, false) end }
---@class unit_valves
self.valves = {
waste_pu = waste_pu,
waste_sna = waste_sna,
waste_po = waste_po,
waste_sps = waste_sps,
emer_cool = emer_cool
}
--#endregion
-- unlink disconnected units
@ -480,13 +491,9 @@ function unit.new(for_reactor, num_boilers, num_turbines)
-- update status text
logic.update_status_text(self)
-- check if emergency coolant is needed
if self.plc_cache.rps_status.no_cool then
emer_cool.open()
elseif not self.plc_cache.rps_trip then
-- can't turn off on sufficient coolant level since it might drop again
-- turn off once system is OK again
emer_cool.close()
-- handle redstone I/O
if #self.redstone > 0 then
logic.handle_redstone(self)
end
end
@ -576,6 +583,13 @@ function unit.new(for_reactor, num_boilers, num_turbines)
end
end
-- queue a SCRAM command only if a manual SCRAM has not already occured
function public.cond_scram()
if self.plc_s ~= nil and not self.plc_cache.rps_status.manual then
self.plc_s.in_queue.push_command(PLC_S_CMDS.SCRAM)
end
end
-- acknowledge all alarms (if possible)
function public.ack_all()
for i = 1, #self.db.alarm_states do

View File

@ -3,6 +3,8 @@ local rsio = require("scada-common.rsio")
local types = require("scada-common.types")
local util = require("scada-common.util")
local plc = require("supervisor.session.plc")
local ALARM_STATE = types.ALARM_STATE
local TRI_FAIL = types.TRI_FAIL
@ -10,6 +12,8 @@ local DUMPING_MODE = types.DUMPING_MODE
local IO = rsio.IO
local PLC_S_CMDS = plc.PLC_S_CMDS
local aistate_string = {
"INACTIVE",
"TRIPPING",
@ -620,4 +624,71 @@ function logic.update_status_text(self)
end
end
-- handle unit redstone I/O
---@param self _unit_self unit instance
function logic.handle_redstone(self)
-- reactor controls
if self.plc_s ~= nil then
if (not self.plc_cache.rps_status.manual) and self.io_ctl.digital_read(IO.R_SCRAM) then
-- reactor SCRAM requested but not yet done; perform it
self.plc_s.in_queue.push_command(PLC_S_CMDS.SCRAM)
end
if self.plc_cache.rps_trip and self.io_ctl.digital_read(IO.R_RESET) then
-- reactor RPS reset requested but not yet done; perform it
self.plc_s.in_queue.push_command(PLC_S_CMDS.RPS_RESET)
end
if (not self.db.annunciator.AutoControl) and (not self.plc_cache.active) and
(not self.plc_cache.rps_trip) and self.io_ctl.digital_read(IO.R_ACTIVE) then
-- reactor enable requested and allowable, but not yet done; perform it
self.plc_s.in_queue.push_command(PLC_S_CMDS.ENABLE)
end
end
-- check for request to ack all alarms
if self.io_ctl.digital_read(IO.U_ACK) then
for i = 1, #self.db.alarm_states do
if self.db.alarm_states[i] == ALARM_STATE.TRIPPED then
self.db.alarm_states[i] = ALARM_STATE.ACKED
end
end
end
-- write reactor status outputs
self.io_ctl.digital_write(IO.R_ACTIVE, self.plc_cache.active)
self.io_ctl.digital_write(IO.R_AUTO_CTRL, self.db.annunciator.AutoControl)
self.io_ctl.digital_write(IO.R_SCRAMMED, self.plc_cache.rps_trip)
self.io_ctl.digital_write(IO.R_AUTO_SCRAM, self.plc_cache.rps_status.automatic)
self.io_ctl.digital_write(IO.R_DMG_CRIT, self.plc_cache.rps_status.dmg_crit)
self.io_ctl.digital_write(IO.R_HIGH_TEMP, self.plc_cache.rps_status.high_temp)
self.io_ctl.digital_write(IO.R_NO_COOLANT, self.plc_cache.rps_status.no_cool)
self.io_ctl.digital_write(IO.R_EXCESS_HC, self.plc_cache.rps_status.ex_hcool)
self.io_ctl.digital_write(IO.R_EXCESS_WS, self.plc_cache.rps_status.ex_waste)
self.io_ctl.digital_write(IO.R_INSUFF_FUEL, self.plc_cache.rps_status.no_fuel)
self.io_ctl.digital_write(IO.R_PLC_FAULT, self.plc_cache.rps_status.fault)
self.io_ctl.digital_write(IO.R_PLC_TIMEOUT, self.plc_cache.rps_status.timeout)
-- write unit outputs
local has_alarm = false
for i = 1, #self.db.alarm_states do
if self.db.alarm_states[i] == ALARM_STATE.TRIPPED then
has_alarm = true
break
end
end
self.io_ctl.digital_write(IO.U_ALARM, has_alarm)
-- check if emergency coolant is needed
if self.plc_cache.rps_status.no_cool then
self.valves.emer_cool.open()
elseif not self.plc_cache.rps_trip then
-- can't turn off on sufficient coolant level since it might drop again
-- turn off once system is OK again
self.valves.emer_cool.close()
end
end
return logic