Merge pull request #482 from MikaylaFischler/412-additional-matrix-integrations

412 additional matrix integrations
This commit is contained in:
Mikayla 2024-04-28 13:08:51 -04:00 committed by GitHub
commit e91fd2fcaa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 501 additions and 299 deletions

View File

@ -92,6 +92,7 @@ function iocontrol.init(conf, comms, temp_scale)
---@type WASTE_PRODUCT ---@type WASTE_PRODUCT
auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM, auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM,
auto_pu_fallback_active = false, auto_pu_fallback_active = false,
auto_sps_disabled = false,
radiation = types.new_zero_radiation_reading(), radiation = types.new_zero_radiation_reading(),
@ -593,7 +594,7 @@ function iocontrol.update_facility_status(status)
local ctl_status = status[1] local ctl_status = status[1]
if type(ctl_status) == "table" and #ctl_status == 16 then if type(ctl_status) == "table" and #ctl_status == 17 then
fac.all_sys_ok = ctl_status[1] fac.all_sys_ok = ctl_status[1]
fac.auto_ready = ctl_status[2] fac.auto_ready = ctl_status[2]
@ -644,9 +645,11 @@ function iocontrol.update_facility_status(status)
fac.auto_current_waste_product = ctl_status[15] fac.auto_current_waste_product = ctl_status[15]
fac.auto_pu_fallback_active = ctl_status[16] fac.auto_pu_fallback_active = ctl_status[16]
fac.auto_sps_disabled = ctl_status[17]
fac.ps.publish("current_waste_product", fac.auto_current_waste_product) fac.ps.publish("current_waste_product", fac.auto_current_waste_product)
fac.ps.publish("pu_fallback_active", fac.auto_pu_fallback_active) fac.ps.publish("pu_fallback_active", fac.auto_pu_fallback_active)
fac.ps.publish("sps_disabled_low_power", fac.auto_sps_disabled)
else else
log.debug(log_header .. "control status not a table or length mismatch") log.debug(log_header .. "control status not a table or length mismatch")
valid = false valid = false
@ -663,10 +666,26 @@ function iocontrol.update_facility_status(status)
fac.rtu_count = rtu_statuses.count fac.rtu_count = rtu_statuses.count
-- power statistics -- power statistics
if type(rtu_statuses.power) == "table" then if type(rtu_statuses.power) == "table" and #rtu_statuses.power == 4 then
fac.induction_ps_tbl[1].publish("avg_charge", rtu_statuses.power[1]) local data = fac.induction_data_tbl[1] ---@type imatrix_session_db
fac.induction_ps_tbl[1].publish("avg_inflow", rtu_statuses.power[2]) local ps = fac.induction_ps_tbl[1] ---@type psil
fac.induction_ps_tbl[1].publish("avg_outflow", rtu_statuses.power[3])
local chg = tonumber(rtu_statuses.power[1])
local in_f = tonumber(rtu_statuses.power[2])
local out_f = tonumber(rtu_statuses.power[3])
local eta = tonumber(rtu_statuses.power[4])
ps.publish("avg_charge", chg)
ps.publish("avg_inflow", in_f)
ps.publish("avg_outflow", out_f)
ps.publish("eta_ms", eta)
ps.publish("is_charging", in_f > out_f)
ps.publish("is_discharging", out_f > in_f)
if data and data.build then
ps.publish("at_max_io", in_f >= data.build.transfer_cap or out_f >= data.build.transfer_cap)
end
else else
log.debug(log_header .. "power statistics list not a table") log.debug(log_header .. "power statistics list not a table")
valid = false valid = false

View File

@ -29,7 +29,8 @@ local self = {
gen_target = 0.0, gen_target = 0.0,
limits = {}, limits = {},
waste_product = PRODUCT.PLUTONIUM, waste_product = PRODUCT.PLUTONIUM,
pu_fallback = false pu_fallback = false,
sps_low_power = false
}, },
waste_modes = {}, waste_modes = {},
priority_groups = {} priority_groups = {}
@ -65,6 +66,7 @@ function process.init(iocontrol, coord_comms)
ctl_proc.limits = config.limits ctl_proc.limits = config.limits
ctl_proc.waste_product = config.waste_product ctl_proc.waste_product = config.waste_product
ctl_proc.pu_fallback = config.pu_fallback ctl_proc.pu_fallback = config.pu_fallback
ctl_proc.sps_low_power = config.sps_low_power
self.io.facility.ps.publish("process_mode", ctl_proc.mode) self.io.facility.ps.publish("process_mode", ctl_proc.mode)
self.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target) self.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target)
@ -72,6 +74,7 @@ function process.init(iocontrol, coord_comms)
self.io.facility.ps.publish("process_gen_target", ctl_proc.gen_target) self.io.facility.ps.publish("process_gen_target", ctl_proc.gen_target)
self.io.facility.ps.publish("process_waste_product", ctl_proc.waste_product) self.io.facility.ps.publish("process_waste_product", ctl_proc.waste_product)
self.io.facility.ps.publish("process_pu_fallback", ctl_proc.pu_fallback) self.io.facility.ps.publish("process_pu_fallback", ctl_proc.pu_fallback)
self.io.facility.ps.publish("process_sps_low_power", ctl_proc.sps_low_power)
for id = 1, math.min(#ctl_proc.limits, self.io.facility.num_units) do for id = 1, math.min(#ctl_proc.limits, self.io.facility.num_units) do
local unit = self.io.units[id] ---@type ioctl_unit local unit = self.io.units[id] ---@type ioctl_unit
@ -83,6 +86,7 @@ function process.init(iocontrol, coord_comms)
-- notify supervisor of auto waste config -- notify supervisor of auto waste config
self.comms.send_fac_command(FAC_COMMAND.SET_WASTE_MODE, ctl_proc.waste_product) self.comms.send_fac_command(FAC_COMMAND.SET_WASTE_MODE, ctl_proc.waste_product)
self.comms.send_fac_command(FAC_COMMAND.SET_PU_FB, ctl_proc.pu_fallback) self.comms.send_fac_command(FAC_COMMAND.SET_PU_FB, ctl_proc.pu_fallback)
self.comms.send_fac_command(FAC_COMMAND.SET_SPS_LP, ctl_proc.sps_low_power)
end end
-- unit waste states -- unit waste states
@ -259,6 +263,18 @@ function process.set_pu_fallback(enabled)
_write_auto_config() _write_auto_config()
end end
-- set automatic process control SPS usage at low power
---@param enabled boolean whether to enable SPS usage at low power
function process.set_sps_low_power(enabled)
self.comms.send_fac_command(FAC_COMMAND.SET_SPS_LP, enabled)
log.debug(util.c("PROCESS: SET SPS LOW POWER ", enabled))
-- update config table and save
self.control_states.process.sps_low_power = enabled
_write_auto_config()
end
-- save process control settings -- save process control settings
---@param mode PROCESS control mode ---@param mode PROCESS control mode
---@param burn_target number burn rate target ---@param burn_target number burn rate target

View File

@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer")
local sounder = require("coordinator.sounder") local sounder = require("coordinator.sounder")
local threads = require("coordinator.threads") local threads = require("coordinator.threads")
local COORDINATOR_VERSION = "v1.4.3" local COORDINATOR_VERSION = "v1.4.4"
local CHUNK_LOAD_DELAY_S = 30.0 local CHUNK_LOAD_DELAY_S = 30.0

View File

@ -9,6 +9,7 @@ local Rectangle = require("graphics.elements.rectangle")
local TextBox = require("graphics.elements.textbox") local TextBox = require("graphics.elements.textbox")
local DataIndicator = require("graphics.elements.indicators.data") local DataIndicator = require("graphics.elements.indicators.data")
local IndicatorLight = require("graphics.elements.indicators.light")
local PowerIndicator = require("graphics.elements.indicators.power") local PowerIndicator = require("graphics.elements.indicators.power")
local StateIndicator = require("graphics.elements.indicators.state") local StateIndicator = require("graphics.elements.indicators.state")
local VerticalBar = require("graphics.elements.indicators.vbar") local VerticalBar = require("graphics.elements.indicators.vbar")
@ -26,9 +27,13 @@ local ALIGN = core.ALIGN
---@param ps psil ps interface ---@param ps psil ps interface
---@param id number? matrix ID ---@param id number? matrix ID
local function new_view(root, x, y, data, ps, id) local function new_view(root, x, y, data, ps, id)
local label_fg = style.theme.label_fg
local text_fg = style.theme.text_fg local text_fg = style.theme.text_fg
local lu_col = style.lu_colors local lu_col = style.lu_colors
local ind_yel = style.ind_yel
local ind_wht = style.ind_wht
local title = "INDUCTION MATRIX" local title = "INDUCTION MATRIX"
if type(id) == "number" then title = title .. id end if type(id) == "number" then title = title .. id end
@ -42,45 +47,47 @@ local function new_view(root, x, y, data, ps, id)
local rect = Rectangle{parent=matrix,border=border(1,colors.gray,true),width=33,height=22,x=1,y=3} local rect = Rectangle{parent=matrix,border=border(1,colors.gray,true),width=33,height=22,x=1,y=3}
local status = StateIndicator{parent=rect,x=10,y=1,states=style.imatrix.states,value=1,min_width=14} local status = StateIndicator{parent=rect,x=10,y=1,states=style.imatrix.states,value=1,min_width=14}
local energy = PowerIndicator{parent=rect,x=7,y=3,lu_colors=lu_col,label="Energy: ",format="%8.2f",value=0,width=26,fg_bg=text_fg} local capacity = PowerIndicator{parent=rect,x=7,y=3,lu_colors=lu_col,label="Capacity:",format="%8.2f",value=0,width=26,fg_bg=text_fg}
local capacity = PowerIndicator{parent=rect,x=7,y=4,lu_colors=lu_col,label="Capacity:",format="%8.2f",value=0,width=26,fg_bg=text_fg} local energy = PowerIndicator{parent=rect,x=7,y=4,lu_colors=lu_col,label="Energy: ",format="%8.2f",value=0,width=26,fg_bg=text_fg}
local input = PowerIndicator{parent=rect,x=7,y=5,lu_colors=lu_col,label="Input: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg} local avg_chg = PowerIndicator{parent=rect,x=7,y=5,lu_colors=lu_col,label="\xb7Average:",format="%8.2f",value=0,width=26,fg_bg=text_fg}
local output = PowerIndicator{parent=rect,x=7,y=6,lu_colors=lu_col,label="Output: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg} local input = PowerIndicator{parent=rect,x=7,y=6,lu_colors=lu_col,label="Input: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
local avg_in = PowerIndicator{parent=rect,x=7,y=7,lu_colors=lu_col,label="\xb7Average:",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
local avg_chg = PowerIndicator{parent=rect,x=7,y=8,lu_colors=lu_col,label="Avg. Chg:",format="%8.2f",value=0,width=26,fg_bg=text_fg} local output = PowerIndicator{parent=rect,x=7,y=8,lu_colors=lu_col,label="Output: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
local avg_in = PowerIndicator{parent=rect,x=7,y=9,lu_colors=lu_col,label="Avg. In: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg} local avg_out = PowerIndicator{parent=rect,x=7,y=9,lu_colors=lu_col,label="\xb7Average:",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
local avg_out = PowerIndicator{parent=rect,x=7,y=10,lu_colors=lu_col,label="Avg. Out:",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg} local trans_cap = PowerIndicator{parent=rect,x=7,y=10,lu_colors=lu_col,label="Max I/O: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
status.register(ps, "computed_status", status.update) status.register(ps, "computed_status", status.update)
energy.register(ps, "energy", function (val) energy.update(util.joules_to_fe(val)) end)
capacity.register(ps, "max_energy", function (val) capacity.update(util.joules_to_fe(val)) end) capacity.register(ps, "max_energy", function (val) capacity.update(util.joules_to_fe(val)) end)
input.register(ps, "last_input", function (val) input.update(util.joules_to_fe(val)) end) energy.register(ps, "energy", function (val) energy.update(util.joules_to_fe(val)) end)
output.register(ps, "last_output", function (val) output.update(util.joules_to_fe(val)) end)
avg_chg.register(ps, "avg_charge", avg_chg.update) avg_chg.register(ps, "avg_charge", avg_chg.update)
input.register(ps, "last_input", function (val) input.update(util.joules_to_fe(val)) end)
avg_in.register(ps, "avg_inflow", avg_in.update) avg_in.register(ps, "avg_inflow", avg_in.update)
output.register(ps, "last_output", function (val) output.update(util.joules_to_fe(val)) end)
avg_out.register(ps, "avg_outflow", avg_out.update) avg_out.register(ps, "avg_outflow", avg_out.update)
trans_cap.register(ps, "transfer_cap", function (val) trans_cap.update(util.joules_to_fe(val)) end)
local fill = DataIndicator{parent=rect,x=11,y=12,lu_colors=lu_col,label="Fill:",unit="%",format="%8.2f",value=0,width=18,fg_bg=text_fg} local fill = DataIndicator{parent=rect,x=11,y=12,lu_colors=lu_col,label="Fill: ",format="%7.2f",unit="%",value=0,width=20,fg_bg=text_fg}
local cells = DataIndicator{parent=rect,x=11,y=13,lu_colors=lu_col,label="Cells: ",format="%7d",value=0,width=18,fg_bg=text_fg}
local cells = DataIndicator{parent=rect,x=11,y=14,lu_colors=lu_col,label="Cells: ",format="%7d",value=0,width=18,fg_bg=text_fg} local providers = DataIndicator{parent=rect,x=11,y=14,lu_colors=lu_col,label="Providers:",format="%7d",value=0,width=18,fg_bg=text_fg}
local providers = DataIndicator{parent=rect,x=11,y=15,lu_colors=lu_col,label="Providers:",format="%7d",value=0,width=18,fg_bg=text_fg}
TextBox{parent=rect,text="Transfer Capacity",x=11,y=17,height=1,width=17,fg_bg=style.theme.label_fg}
local trans_cap = PowerIndicator{parent=rect,x=19,y=18,lu_colors=lu_col,label="",format="%5.2f",rate=true,value=0,width=12,fg_bg=text_fg}
fill.register(ps, "energy_fill", function (val) fill.update(val * 100) end)
cells.register(ps, "cells", cells.update) cells.register(ps, "cells", cells.update)
providers.register(ps, "providers", providers.update) providers.register(ps, "providers", providers.update)
fill.register(ps, "energy_fill", function (val) fill.update(val * 100) end)
trans_cap.register(ps, "transfer_cap", function (val) trans_cap.update(util.joules_to_fe(val)) end) local chging = IndicatorLight{parent=rect,x=11,y=16,label="Charging",colors=ind_wht}
local dischg = IndicatorLight{parent=rect,x=11,y=17,label="Discharging",colors=ind_wht}
local max_io = IndicatorLight{parent=rect,x=11,y=18,label="Max I/O Rate",colors=ind_yel}
chging.register(ps, "is_charging", chging.update)
dischg.register(ps, "is_discharging", dischg.update)
max_io.register(ps, "at_max_io", max_io.update)
local charge = VerticalBar{parent=rect,x=2,y=2,fg_bg=cpair(colors.green,colors.gray),height=17,width=4} local charge = VerticalBar{parent=rect,x=2,y=2,fg_bg=cpair(colors.green,colors.gray),height=17,width=4}
local in_cap = VerticalBar{parent=rect,x=7,y=12,fg_bg=cpair(colors.red,colors.gray),height=7,width=1} local in_cap = VerticalBar{parent=rect,x=7,y=12,fg_bg=cpair(colors.red,colors.gray),height=7,width=1}
local out_cap = VerticalBar{parent=rect,x=9,y=12,fg_bg=cpair(colors.blue,colors.gray),height=7,width=1} local out_cap = VerticalBar{parent=rect,x=9,y=12,fg_bg=cpair(colors.blue,colors.gray),height=7,width=1}
TextBox{parent=rect,text="FILL",x=2,y=20,height=1,width=4,fg_bg=text_fg} TextBox{parent=rect,text="FILL I/O",x=2,y=20,height=1,width=8,fg_bg=label_fg}
TextBox{parent=rect,text="I/O",x=7,y=20,height=1,width=3,fg_bg=text_fg}
local function calc_saturation(val) local function calc_saturation(val)
if (type(data.build) == "table") and (type(data.build.transfer_cap) == "number") and (data.build.transfer_cap > 0) then if (type(data.build) == "table") and (type(data.build.transfer_cap) == "number") and (data.build.transfer_cap > 0) then
@ -91,6 +98,49 @@ local function new_view(root, x, y, data, ps, id)
charge.register(ps, "energy_fill", charge.update) charge.register(ps, "energy_fill", charge.update)
in_cap.register(ps, "last_input", function (val) in_cap.update(calc_saturation(val)) end) in_cap.register(ps, "last_input", function (val) in_cap.update(calc_saturation(val)) end)
out_cap.register(ps, "last_output", function (val) out_cap.update(calc_saturation(val)) end) out_cap.register(ps, "last_output", function (val) out_cap.update(calc_saturation(val)) end)
local eta = TextBox{parent=rect,x=11,y=20,width=20,height=1,text="ETA Unknown",alignment=ALIGN.CENTER,fg_bg=style.theme.field_box}
eta.register(ps, "eta_ms", function (eta_ms)
local str, pre = "", util.trinary(eta_ms >= 0, "Full in ", "Empty in ")
local seconds = math.abs(eta_ms) / 1000
local minutes = seconds / 60
local hours = minutes / 60
local days = hours / 24
if math.abs(eta_ms) < 1000 or (eta_ms ~= eta_ms) then
-- really small or NaN
str = "No ETA"
elseif days < 1000 then
days = math.floor(days)
hours = math.floor(hours % 24)
minutes = math.floor(minutes % 60)
seconds = math.floor(seconds % 60)
if days > 0 then
str = days .. "d"
elseif hours > 0 then
str = hours .. "h " .. minutes .. "m"
elseif minutes > 0 then
str = minutes .. "m " .. seconds .. "s"
elseif seconds > 0 then
str = seconds .. "s"
end
str = pre .. str
else
local years = math.floor(days / 365.25)
if years <= 99999999 then
str = pre .. years .. "y"
else
str = pre .. "eras"
end
end
eta.set_value(str)
end)
end end
return new_view return new_view

View File

@ -341,31 +341,25 @@ local function new_view(root, x, y)
status.register(facility.ps, "current_waste_product", status.update) status.register(facility.ps, "current_waste_product", status.update)
local waste_prod = RadioButton{parent=rect,x=2,y=3,options=style.waste.options,callback=process.set_process_waste,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.brown} local waste_prod = RadioButton{parent=rect,x=2,y=3,options=style.waste.options,callback=process.set_process_waste,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.brown}
local pu_fallback = Checkbox{parent=rect,x=2,y=7,label="Pu Fallback",callback=process.set_pu_fallback,box_fg_bg=cpair(colors.green,style.theme.checkbox_bg)}
waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value) waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value)
pu_fallback.register(facility.ps, "process_pu_fallback", pu_fallback.set_value)
local fb_active = IndicatorLight{parent=rect,x=2,y=9,label="Fallback Active",colors=ind_wht} local fb_active = IndicatorLight{parent=rect,x=2,y=7,label="Fallback Active",colors=ind_wht}
local sps_disabled = IndicatorLight{parent=rect,x=2,y=8,label="SPS Disabled LC",colors=ind_yel}
fb_active.register(facility.ps, "pu_fallback_active", fb_active.update) fb_active.register(facility.ps, "pu_fallback_active", fb_active.update)
sps_disabled.register(facility.ps, "sps_disabled_low_power", sps_disabled.update)
TextBox{parent=rect,x=2,y=11,text="Plutonium Rate",height=1,width=17,fg_bg=style.label} local pu_fallback = Checkbox{parent=rect,x=2,y=10,label="Pu Fallback",callback=process.set_pu_fallback,box_fg_bg=cpair(colors.brown,style.theme.checkbox_bg)}
local pu_rate = DataIndicator{parent=rect,x=2,label="",unit="mB/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=s_field,width=17}
TextBox{parent=rect,x=2,y=14,text="Polonium Rate",height=1,width=17,fg_bg=style.label} TextBox{parent=rect,x=2,y=12,height=3,text="Switch to Pu when SNAs cannot keep up with waste.",fg_bg=style.label}
local po_rate = DataIndicator{parent=rect,x=2,label="",unit="mB/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=s_field,width=17}
TextBox{parent=rect,x=2,y=17,text="Antimatter Rate",height=1,width=17,fg_bg=style.label} local lc_sps = Checkbox{parent=rect,x=2,y=16,label="Low Charge SPS",callback=process.set_sps_low_power,box_fg_bg=cpair(colors.brown,style.theme.checkbox_bg)}
local am_rate = DataIndicator{parent=rect,x=2,label="",unit="\xb5B/t",format="%12d",value=0,lu_colors=lu_cpair,fg_bg=s_field,width=17}
pu_rate.register(facility.ps, "pu_rate", pu_rate.update) TextBox{parent=rect,x=2,y=18,height=3,text="Use SPS at low charge, otherwise switches to Po.",fg_bg=style.label}
po_rate.register(facility.ps, "po_rate", po_rate.update)
am_rate.register(facility.ps, "am_rate", am_rate.update)
local sna_count = DataIndicator{parent=rect,x=2,y=20,label="Linked SNAs:",format="%4d",value=0,lu_colors=lu_cpair,width=17} pu_fallback.register(facility.ps, "process_pu_fallback", pu_fallback.set_value)
lc_sps.register(facility.ps, "process_sps_low_power", lc_sps.set_value)
sna_count.register(facility.ps, "sna_count", sna_count.update)
end end
return new_view return new_view

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")
@ -33,45 +34,50 @@ local tri = util.trinary
local cpair = core.cpair local cpair = core.cpair
local IO = rsio.IO local IO = rsio.IO
local IO_LVL = rsio.IO_LVL
local IO_MODE = rsio.IO_MODE
local LEFT = core.ALIGN.LEFT local LEFT = core.ALIGN.LEFT
local CENTER = core.ALIGN.CENTER 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
@ -442,7 +448,7 @@ local function config_view(display)
TextBox{parent=net_c_3,x=1,y=11,height=1,text="Facility Auth Key"} TextBox{parent=net_c_3,x=1,y=11,height=1,text="Facility Auth Key"}
local key, _, censor = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg} local key, _, censor = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
local function censor_key(enable) censor(util.trinary(enable, "*", nil)) end local function censor_key(enable) censor(tri(enable, "*", nil)) end
local hide_key = CheckBox{parent=net_c_3,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key} local hide_key = CheckBox{parent=net_c_3,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
@ -555,7 +561,7 @@ local function config_view(display)
PushButton{parent=clr_c_2,x=44,y=14,min_width=6,text="Done",callback=function()clr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=clr_c_2,x=44,y=14,min_width=6,text="Done",callback=function()clr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
local function back_from_colors() local function back_from_colors()
main_pane.set_value(util.trinary(tool_ctl.jumped_to_color, 1, 4)) main_pane.set_value(tri(tool_ctl.jumped_to_color, 1, 4))
tool_ctl.jumped_to_color = false tool_ctl.jumped_to_color = false
recolor(1) recolor(1)
end end
@ -897,7 +903,7 @@ local function config_view(display)
tool_ctl.p_desc.reposition(1, 8) tool_ctl.p_desc.reposition(1, 8)
tool_ctl.p_desc.set_value("You can connect more than one environment detector for a particular unit or the facility. In that case, the maximum radiation reading from those assigned to that particular unit or the facility will be used for alarms and display.") tool_ctl.p_desc.set_value("You can connect more than one environment detector for a particular unit or the facility. In that case, the maximum radiation reading from those assigned to that particular unit or the facility will be used for alarms and display.")
elseif type == "inductionPort" or type == "spsPort" then elseif type == "inductionPort" or type == "spsPort" then
local dev = util.trinary(type == "inductionPort", "induction matrix", "SPS") local dev = tri(type == "inductionPort", "induction matrix", "SPS")
tool_ctl.p_idx.hide(true) tool_ctl.p_idx.hide(true)
tool_ctl.p_unit.hide(true) tool_ctl.p_unit.hide(true)
tool_ctl.p_prompt.set_value("This is the " .. dev .. " for the facility.") tool_ctl.p_prompt.set_value("This is the " .. dev .. " for the facility.")
@ -923,7 +929,7 @@ local function config_view(display)
tool_ctl.ppm_devs.remove_all() tool_ctl.ppm_devs.remove_all()
for name, entry in pairs(mounts) do for name, entry in pairs(mounts) do
if util.table_contains(RTU_DEV_TYPES, entry.type) then if util.table_contains(RTU_DEV_TYPES, entry.type) then
local bkg = util.trinary(alternate, colors.white, colors.lightGray) local bkg = tri(alternate, colors.white, colors.lightGray)
---@cast entry ppm_entry ---@cast entry ppm_entry
local line = Div{parent=tool_ctl.ppm_devs,height=2,fg_bg=cpair(colors.black,bkg)} local line = Div{parent=tool_ctl.ppm_devs,height=2,fg_bg=cpair(colors.black,bkg)}
@ -1085,8 +1091,9 @@ local function config_view(display)
local rs_c_4 = Div{parent=rs_cfg,x=2,y=4,width=49} local rs_c_4 = Div{parent=rs_cfg,x=2,y=4,width=49}
local rs_c_5 = Div{parent=rs_cfg,x=2,y=4,width=49} local rs_c_5 = Div{parent=rs_cfg,x=2,y=4,width=49}
local rs_c_6 = Div{parent=rs_cfg,x=2,y=4,width=49} local rs_c_6 = Div{parent=rs_cfg,x=2,y=4,width=49}
local rs_c_7 = Div{parent=rs_cfg,x=2,y=4,width=49}
local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6}} local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6,rs_c_7}}
TextBox{parent=rs_cfg,x=1,y=2,height=1,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)} TextBox{parent=rs_cfg,x=1,y=2,height=1,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)}
@ -1143,9 +1150,23 @@ local function config_view(display)
text = "You selected the ALL_WASTE shortcut." text = "You selected the ALL_WASTE shortcut."
else else
tool_ctl.rs_cfg_shortcut.hide(true) tool_ctl.rs_cfg_shortcut.hide(true)
tool_ctl.rs_cfg_side_l.set_value(util.trinary(rsio.get_io_dir(port) == rsio.IO_DIR.IN, "Input Side", "Output Side")) tool_ctl.rs_cfg_side_l.set_value(tri(rsio.get_io_dir(port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
tool_ctl.rs_cfg_color.show() tool_ctl.rs_cfg_color.show()
text = "You selected " .. rsio.to_string(port) .. " (for "
local io_type = "analog input "
local io_mode = rsio.get_io_mode(port)
local inv = tri(rsio.digital_is_active(port, IO_LVL.LOW) == true, "inverted ", "")
if io_mode == IO_MODE.DIGITAL_IN then
io_type = inv .. "digital input "
elseif io_mode == IO_MODE.DIGITAL_OUT then
io_type = inv .. "digital output "
elseif io_mode == IO_MODE.ANALOG_OUT then
io_type = "analog output "
end
text = "You selected the " .. io_type .. rsio.to_string(port) .. " (for "
if PORT_DSGN[port] == 1 then if PORT_DSGN[port] == 1 then
text = text .. "a unit)." text = text .. "a unit)."
tool_ctl.rs_cfg_unit_l.show() tool_ctl.rs_cfg_unit_l.show()
@ -1167,25 +1188,35 @@ 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 = tri(rsio.get_io_dir(p) == rsio.IO_DIR.IN, "[in]", "[out]")
local btn_color = tri(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}
tool_ctl.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=1,text=""} tool_ctl.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=2,text=""}
tool_ctl.rs_cfg_unit_l = TextBox{parent=rs_c_3,x=27,y=3,width=7,height=1,text="Unit ID"} PushButton{parent=rs_c_3,x=36,y=3,text="What's that?",min_width=14,callback=function()rs_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
tool_ctl.rs_cfg_unit = NumberField{parent=rs_c_3,x=27,y=4,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
tool_ctl.rs_cfg_side_l = TextBox{parent=rs_c_3,x=1,y=3,width=11,height=1,text="Output Side"} TextBox{parent=rs_c_7,x=1,y=1,height=4,text="(Normal) Digital Input: On if there is a redstone signal, off otherwise\nInverted Digital Input: On without a redstone signal, off otherwise"}
local side = Radio2D{parent=rs_c_3,x=1,y=4,rows=2,columns=3,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red} TextBox{parent=rs_c_7,x=1,y=6,height=4,text="(Normal) Digital Output: Redstone signal to 'turn it on', none to 'turn it off'\nInverted Digital Output: No redstone signal to 'turn it on', redstone signal to 'turn it off'"}
TextBox{parent=rs_c_7,x=1,y=11,height=2,text="Analog Input: 0-15 redstone power level input\nAnalog Output: 0-15 scaled redstone power level output"}
PushButton{parent=rs_c_7,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
tool_ctl.rs_cfg_side_l = TextBox{parent=rs_c_3,x=1,y=4,width=11,height=1,text="Output Side"}
local side = Radio2D{parent=rs_c_3,x=1,y=5,rows=1,columns=6,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red}
tool_ctl.rs_cfg_unit_l = TextBox{parent=rs_c_3,x=25,y=7,width=7,height=1,text="Unit ID"}
tool_ctl.rs_cfg_unit = NumberField{parent=rs_c_3,x=33,y=7,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
local function set_bundled(bundled) local function set_bundled(bundled)
if bundled then tool_ctl.rs_cfg_color.enable() else tool_ctl.rs_cfg_color.disable() end if bundled then tool_ctl.rs_cfg_color.enable() else tool_ctl.rs_cfg_color.disable() end
@ -1216,10 +1247,10 @@ local function config_view(display)
if port >= 0 then if port >= 0 then
---@type rtu_rs_definition ---@type rtu_rs_definition
local def = { local def = {
unit = util.trinary(PORT_DSGN[port] == 1, u, nil), unit = tri(PORT_DSGN[port] == 1, u, nil),
port = port, port = port,
side = side_options_map[side.get_value()], side = side_options_map[side.get_value()],
color = util.trinary(bundled.get_value(), color_options_map[tool_ctl.rs_cfg_color.get_value()], nil) color = tri(bundled.get_value(), color_options_map[tool_ctl.rs_cfg_color.get_value()], nil)
} }
if tool_ctl.rs_cfg_editing == false then if tool_ctl.rs_cfg_editing == false then
@ -1233,10 +1264,10 @@ local function config_view(display)
local default_colors = { colors.red, colors.orange, colors.yellow, colors.lime } local default_colors = { colors.red, colors.orange, colors.yellow, colors.lime }
for i = 0, 3 do for i = 0, 3 do
table.insert(tmp_cfg.Redstone, { table.insert(tmp_cfg.Redstone, {
unit = util.trinary(PORT_DSGN[IO.WASTE_PU + i] == 1, u, nil), unit = tri(PORT_DSGN[IO.WASTE_PU + i] == 1, u, nil),
port = IO.WASTE_PU + i, port = IO.WASTE_PU + i,
side = util.trinary(bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]), side = tri(bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]),
color = util.trinary(bundled.get_value(), default_colors[i + 1], nil) color = tri(bundled.get_value(), default_colors[i + 1], nil)
}) })
end end
end end
@ -1289,7 +1320,7 @@ local function config_view(display)
peri_import_list.remove_all() peri_import_list.remove_all()
for _, entry in ipairs(config.RTU_DEVICES) do for _, entry in ipairs(config.RTU_DEVICES) do
local for_facility = entry.for_reactor == 0 local for_facility = entry.for_reactor == 0
local ini_unit = util.trinary(for_facility, nil, entry.for_reactor) local ini_unit = tri(for_facility, nil, entry.for_reactor)
local def = { name = entry.name, unit = ini_unit, index = entry.index } local def = { name = entry.name, unit = ini_unit, index = entry.index }
local mount = mounts[def.name] ---@type ppm_entry|nil local mount = mounts[def.name] ---@type ppm_entry|nil
@ -1368,7 +1399,7 @@ local function config_view(display)
table.insert(tmp_cfg.Redstone, def) table.insert(tmp_cfg.Redstone, def)
local name = rsio.to_string(def.port) local name = rsio.to_string(def.port)
local io_dir = util.trinary(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b") local io_dir = tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
local conn = def.side local conn = def.side
local unit = "facility" local unit = "facility"
@ -1431,7 +1462,7 @@ local function config_view(display)
local val = util.strval(raw) local val = util.strval(raw)
if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) if f[1] == "AuthKey" then val = string.rep("*", string.len(val))
elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") elseif f[1] == "LogMode" then val = tri(raw == log.MODE.APPEND, "append", "replace")
elseif f[1] == "FrontPanelTheme" then elseif f[1] == "FrontPanelTheme" then
val = util.strval(themes.fp_theme_name(raw)) val = util.strval(themes.fp_theme_name(raw))
elseif f[1] == "ColorMode" then elseif f[1] == "ColorMode" then
@ -1440,7 +1471,7 @@ local function config_view(display)
if val == "nil" then val = "<not set>" end if val == "nil" then val = "<not set>" end
local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white)) local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
alternate = not alternate alternate = not alternate
if string.len(val) > val_max_w then if string.len(val) > val_max_w then
@ -1554,7 +1585,7 @@ local function config_view(display)
end end
tool_ctl.rs_cfg_selection.set_value(text) tool_ctl.rs_cfg_selection.set_value(text)
tool_ctl.rs_cfg_side_l.set_value(util.trinary(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "Input Side", "Output Side")) tool_ctl.rs_cfg_side_l.set_value(tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
side.set_value(side_to_idx(def.side)) side.set_value(side_to_idx(def.side))
bundled.set_value(def.color ~= nil) bundled.set_value(def.color ~= nil)
tool_ctl.rs_cfg_color.set_value(value) tool_ctl.rs_cfg_color.set_value(value)
@ -1575,7 +1606,7 @@ local function config_view(display)
local def = cfg.Redstone[i] ---@type rtu_rs_definition local def = cfg.Redstone[i] ---@type rtu_rs_definition
local name = rsio.to_string(def.port) local name = rsio.to_string(def.port)
local io_dir = util.trinary(rsio.get_io_mode(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b") local io_dir = tri(rsio.get_io_mode(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
local conn = def.side local conn = def.side
local unit = util.strval(def.unit or "F") local unit = util.strval(def.unit or "F")

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

@ -17,7 +17,7 @@ local max_distance = nil
local comms = {} local comms = {}
-- protocol/data versions (protocol/data independent changes tracked by util.lua version) -- protocol/data versions (protocol/data independent changes tracked by util.lua version)
comms.version = "2.5.0" comms.version = "2.5.1"
comms.api_version = "0.0.1" comms.api_version = "0.0.1"
---@enum PROTOCOL ---@enum PROTOCOL
@ -97,7 +97,8 @@ local FAC_COMMAND = {
START = 2, -- start automatic process control START = 2, -- start automatic process control
ACK_ALL_ALARMS = 3, -- acknowledge all alarms on all units ACK_ALL_ALARMS = 3, -- acknowledge all alarms on all units
SET_WASTE_MODE = 4, -- set automatic waste processing mode SET_WASTE_MODE = 4, -- set automatic waste processing mode
SET_PU_FB = 5 -- set plutonium fallback mode SET_PU_FB = 5, -- set plutonium fallback mode
SET_SPS_LP = 6 -- set SPS at low power mode
} }
---@enum UNIT_COMMAND ---@enum UNIT_COMMAND

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 low
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
@ -181,8 +181,7 @@ function util.round(x) return math.floor(x + 0.5) end
-- get a new moving average object -- get a new moving average object
---@nodiscard ---@nodiscard
---@param length integer history length ---@param length integer history length
---@param default number value to fill history with for first call to compute() function util.mov_avg(length)
function util.mov_avg(length, default)
local data = {} local data = {}
local index = 1 local index = 1
local last_t = 0 ---@type number|nil local last_t = 0 ---@type number|nil
@ -190,11 +189,15 @@ function util.mov_avg(length, default)
---@class moving_average ---@class moving_average
local public = {} local public = {}
-- reset all to a given value -- reset all to a given value, or clear all data if no value is given
---@param x number value ---@param x number? value
function public.reset(x) function public.reset(x)
index = 1
data = {} data = {}
for _ = 1, length do t_insert(data, x) end
if x then
for _ = 1, length do t_insert(data, x) end
end
end end
-- record a new value -- record a new value
@ -214,12 +217,15 @@ function util.mov_avg(length, default)
---@nodiscard ---@nodiscard
---@return number average ---@return number average
function public.compute() function public.compute()
local sum = 0 if #data == 0 then return 0 end
for i = 1, length do sum = sum + data[i] end
return sum / length
end
public.reset(default) local sum = 0
for i = 1, #data do
sum = sum + data[i]
end
return sum / #data
end
return public return public
end end

View File

@ -120,6 +120,8 @@ function facility.new(config, cooling_conf)
waste_product = WASTE.PLUTONIUM, waste_product = WASTE.PLUTONIUM,
current_waste_product = WASTE.PLUTONIUM, current_waste_product = WASTE.PLUTONIUM,
pu_fallback = false, pu_fallback = false,
sps_low_power = false,
disabled_sps = false,
-- alarm tones -- alarm tones
tone_states = {}, tone_states = {},
test_tone_set = false, test_tone_set = false,
@ -128,9 +130,14 @@ function facility.new(config, cooling_conf)
test_alarm_states = {}, test_alarm_states = {},
-- statistics -- statistics
im_stat_init = false, im_stat_init = false,
avg_charge = util.mov_avg(3, 0.0), avg_charge = util.mov_avg(3), -- 3 seconds
avg_inflow = util.mov_avg(6, 0.0), avg_inflow = util.mov_avg(6), -- 3 seconds
avg_outflow = util.mov_avg(6, 0.0) avg_outflow = util.mov_avg(6), -- 3 seconds
-- induction matrix charge delta stats
avg_net = util.mov_avg(60), -- 60 seconds
imtx_last_capacity = 0,
imtx_last_charge = 0,
imtx_last_charge_t = 0
} }
-- create units -- create units
@ -300,22 +307,45 @@ 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
if (charge_update > 0) and (rate_update > 0) then if (charge_update > 0) and (rate_update > 0) then
local energy = util.joules_to_fe(db.tanks.energy)
local input = util.joules_to_fe(db.state.last_input)
local output = util.joules_to_fe(db.state.last_output)
if self.im_stat_init then if self.im_stat_init then
self.avg_charge.record(util.joules_to_fe(db.tanks.energy), charge_update) self.avg_charge.record(energy, charge_update)
self.avg_inflow.record(util.joules_to_fe(db.state.last_input), rate_update) self.avg_inflow.record(input, rate_update)
self.avg_outflow.record(util.joules_to_fe(db.state.last_output), rate_update) self.avg_outflow.record(output, rate_update)
if charge_update ~= self.imtx_last_charge_t then
local delta = (energy - self.imtx_last_charge) / (charge_update - self.imtx_last_charge_t)
self.imtx_last_charge = energy
self.imtx_last_charge_t = charge_update
-- if the capacity changed, toss out existing data
if db.build.max_energy ~= self.imtx_last_capacity then
self.imtx_last_capacity = db.build.max_energy
self.avg_net.reset()
else
self.avg_net.record(delta, charge_update)
end
end
else else
self.im_stat_init = true self.im_stat_init = true
self.avg_charge.reset(util.joules_to_fe(db.tanks.energy))
self.avg_inflow.reset(util.joules_to_fe(db.state.last_input)) self.avg_charge.reset(energy)
self.avg_outflow.reset(util.joules_to_fe(db.state.last_output)) self.avg_inflow.reset(input)
self.avg_outflow.reset(output)
self.imtx_last_charge = energy
self.imtx_last_charge_t = charge_update
end end
end end
else else
@ -620,8 +650,7 @@ function facility.new(config, cooling_conf)
local astatus = self.ascram_status local astatus = self.ascram_status
if self.induction[1] ~= nil then if self.induction[1] ~= nil then
local matrix = self.induction[1] ---@type unit_session local db = self.induction[1].get_db() ---@type imatrix_session_db
local db = matrix.get_db() ---@type imatrix_session_db
-- clear matrix disconnected -- clear matrix disconnected
if astatus.matrix_dc then if astatus.matrix_dc then
@ -774,6 +803,15 @@ 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 db = self.induction[1].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
@ -804,9 +842,25 @@ function facility.new(config, cooling_conf)
end end
-- update waste product -- update waste product
if self.waste_product == WASTE.PLUTONIUM or (self.pu_fallback and insufficent_po_rate) then
self.current_waste_product = self.waste_product
if (not self.sps_low_power) and (self.waste_product == WASTE.ANTI_MATTER) and (self.induction[1] ~= nil) then
local db = self.induction[1].get_db() ---@type imatrix_session_db
if db.tanks.energy_fill >= 0.15 then
self.disabled_sps = false
elseif self.disabled_sps or ((db.tanks.last_update > 0) and (db.tanks.energy_fill < 0.1)) then
self.disabled_sps = true
self.current_waste_product = WASTE.POLONIUM
end
else
self.disabled_sps = false
end
if self.pu_fallback and insufficent_po_rate then
self.current_waste_product = WASTE.PLUTONIUM self.current_waste_product = WASTE.PLUTONIUM
else self.current_waste_product = self.waste_product end end
-- make sure dynamic tanks are allowing outflow if required -- make sure dynamic tanks are allowing outflow if required
-- set all, rather than trying to determine which is for which (simpler & safer) -- set all, rather than trying to determine which is for which (simpler & safer)
@ -1063,6 +1117,14 @@ function facility.new(config, cooling_conf)
return self.pu_fallback return self.pu_fallback
end end
-- enable/disable SPS at low power
---@param enabled boolean requested state
---@return boolean enabled newly set value
function public.set_sps_low_power(enabled)
self.sps_low_power = enabled == true
return self.sps_low_power
end
--#endregion --#endregion
--#region Diagnostic Testing --#region Diagnostic Testing
@ -1167,7 +1229,8 @@ function facility.new(config, cooling_conf)
self.status_text[2], self.status_text[2],
self.group_map, self.group_map,
self.current_waste_product, self.current_waste_product,
(self.current_waste_product == WASTE.PLUTONIUM) and (self.waste_product ~= WASTE.PLUTONIUM) self.pu_fallback and (self.current_waste_product == WASTE.PLUTONIUM) and (self.waste_product ~= WASTE.PLUTONIUM),
self.disabled_sps
} }
end end
@ -1183,15 +1246,21 @@ function facility.new(config, cooling_conf)
status.power = { status.power = {
self.avg_charge.compute(), self.avg_charge.compute(),
self.avg_inflow.compute(), self.avg_inflow.compute(),
self.avg_outflow.compute() self.avg_outflow.compute(),
0
} }
-- status of induction matricies (including tanks) -- status of induction matricies (including tanks)
status.induction = {} status.induction = {}
for i = 1, #self.induction do for i = 1, #self.induction do
local matrix = self.induction[i] ---@type unit_session local matrix = self.induction[i] ---@type unit_session
local db = matrix.get_db() ---@type imatrix_session_db local db = matrix.get_db() ---@type imatrix_session_db
status.induction[i] = { matrix.is_faulted(), db.formed, db.state, db.tanks } status.induction[i] = { matrix.is_faulted(), db.formed, db.state, db.tanks }
local fe_per_ms = self.avg_net.compute()
local remaining = util.joules_to_fe(util.trinary(fe_per_ms >= 0, db.tanks.energy_need, db.tanks.energy))
status.power[4] = remaining / fe_per_ms
end end
-- status of sps -- status of sps

View File

@ -270,6 +270,12 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil
else else
log.debug(log_header .. "CRDN set pu fallback packet length mismatch") log.debug(log_header .. "CRDN set pu fallback packet length mismatch")
end end
elseif cmd == FAC_COMMAND.SET_SPS_LP then
if pkt.length == 2 then
_send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_sps_low_power(pkt.data[2]) })
else
log.debug(log_header .. "CRDN set sps low power packet length mismatch")
end
else else
log.debug(log_header .. "CRDN facility command unknown") log.debug(log_header .. "CRDN facility command unknown")
end end

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

@ -71,8 +71,8 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
---@class _unit_self ---@class _unit_self
local self = { local self = {
r_id = reactor_id, r_id = reactor_id,
plc_s = nil, ---@class plc_session_struct plc_s = nil, ---@type plc_session_struct
plc_i = nil, ---@class plc_session plc_i = nil, ---@type plc_session
num_boilers = num_boilers, num_boilers = num_boilers,
num_turbines = num_turbines, num_turbines = num_turbines,
types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE }, types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE },

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 -- only using the key for a quick lookup, value just can't be 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")