mirror of
https://github.com/MikaylaFischler/cc-mek-scada.git
synced 2024-08-30 18:22:34 +00:00
#143 #103 #101 #102 work in progress auto control, added coordinator controls, save/auto load configuration, auto enable/disable on reactor PLC for auto control (untested)
This commit is contained in:
parent
e808ee2be0
commit
e9562a140c
@ -2,10 +2,10 @@ local comms = require("scada-common.comms")
|
||||
local log = require("scada-common.log")
|
||||
local ppm = require("scada-common.ppm")
|
||||
local util = require("scada-common.util")
|
||||
local process = require("coordinator.process")
|
||||
|
||||
local apisessions = require("coordinator.apisessions")
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
local process = require("coordinator.process")
|
||||
|
||||
local dialog = require("coordinator.ui.dialog")
|
||||
|
||||
@ -20,6 +20,7 @@ local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
||||
local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES
|
||||
local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES
|
||||
local UNIT_COMMANDS = comms.UNIT_COMMANDS
|
||||
local FAC_COMMANDS = comms.FAC_COMMANDS
|
||||
|
||||
local coordinator = {}
|
||||
|
||||
@ -313,11 +314,25 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa
|
||||
return self.sv_linked
|
||||
end
|
||||
|
||||
-- send a facility command
|
||||
---@param cmd FAC_COMMANDS command
|
||||
function public.send_fac_command(cmd)
|
||||
_send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.FAC_CMD, { cmd })
|
||||
end
|
||||
|
||||
-- send the auto process control configuration with a start command
|
||||
---@param config coord_auto_config configuration
|
||||
function public.send_auto_start(config)
|
||||
_send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.FAC_CMD, {
|
||||
FAC_COMMANDS.START, config.mode, config.burn_target, config.charge_target, config.gen_target, config.limits
|
||||
})
|
||||
end
|
||||
|
||||
-- send a unit command
|
||||
---@param cmd UNIT_COMMANDS command
|
||||
---@param unit integer unit ID
|
||||
---@param option any? optional options (like burn rate)
|
||||
function public.send_command(cmd, unit, option)
|
||||
---@param option any? optional option options for the optional options (like burn rate) (does option still look like a word?)
|
||||
function public.send_unit_command(cmd, unit, option)
|
||||
_send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.UNIT_CMD, { cmd, unit, option })
|
||||
end
|
||||
|
||||
@ -412,6 +427,26 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa
|
||||
end
|
||||
elseif packet.type == SCADA_CRDN_TYPES.FAC_CMD then
|
||||
-- facility command acknowledgement
|
||||
if packet.length >= 2 then
|
||||
local cmd = packet.data[1]
|
||||
local ack = packet.data[2] == true
|
||||
|
||||
if cmd == FAC_COMMANDS.SCRAM_ALL then
|
||||
iocontrol.get_db().facility.scram_ack(ack)
|
||||
elseif cmd == FAC_COMMANDS.STOP then
|
||||
iocontrol.get_db().facility.stop_ack(ack)
|
||||
elseif cmd == FAC_COMMANDS.START then
|
||||
if packet.length == 7 then
|
||||
process.start_ack_handle({ table.unpack(packet.data, 2) })
|
||||
else
|
||||
log.debug("SCADA_CRDN process start (with configuration) ack echo packet length mismatch")
|
||||
end
|
||||
else
|
||||
log.debug(util.c("received facility command ack with unknown command ", cmd))
|
||||
end
|
||||
else
|
||||
log.debug("SCADA_CRDN facility command ack packet length mismatch")
|
||||
end
|
||||
elseif packet.type == SCADA_CRDN_TYPES.UNIT_BUILDS then
|
||||
-- record builds
|
||||
if iocontrol.record_unit_builds(packet.data) then
|
||||
|
@ -24,9 +24,17 @@ function iocontrol.init(conf, comms)
|
||||
---@class ioctl_facility
|
||||
io.facility = {
|
||||
auto_active = false,
|
||||
scram = false,
|
||||
auto_ramping = false,
|
||||
auto_scram = false,
|
||||
auto_scram_cause = "ok", ---@type auto_scram_cause
|
||||
|
||||
num_units = conf.num_units, ---@type integer
|
||||
|
||||
save_cfg_ack = function (success) end, ---@param success boolean
|
||||
start_ack = function (success) end, ---@param success boolean
|
||||
stop_ack = function (success) end, ---@param success boolean
|
||||
scram_ack = function (success) end, ---@param success boolean
|
||||
|
||||
num_units = conf.num_units, ---@type integer
|
||||
ps = psil.create(),
|
||||
|
||||
induction_ps_tbl = {},
|
||||
@ -69,7 +77,6 @@ function iocontrol.init(conf, comms)
|
||||
set_waste = function (mode) process.set_waste(i, mode) end, ---@param mode integer waste processing mode
|
||||
|
||||
set_group = function (grp) process.set_group(i, grp) end, ---@param grp integer|0 group ID or 0
|
||||
set_limit = function (lim) process.set_limit(i, lim) end, ---@param lim number burn rate limit
|
||||
|
||||
start_ack = function (success) end, ---@param success boolean
|
||||
scram_ack = function (success) end, ---@param success boolean
|
||||
@ -195,7 +202,7 @@ function iocontrol.record_unit_builds(builds)
|
||||
|
||||
-- reactor build
|
||||
if type(build.reactor) == "table" then
|
||||
unit.reactor_data.mek_struct = build.reactor
|
||||
unit.reactor_data.mek_struct = build.reactor ---@type mek_struct
|
||||
for key, val in pairs(unit.reactor_data.mek_struct) do
|
||||
unit.reactor_ps.publish(key, val)
|
||||
end
|
||||
@ -257,11 +264,38 @@ function iocontrol.update_facility_status(status)
|
||||
else
|
||||
local fac = io.facility
|
||||
|
||||
-- auto control status information
|
||||
|
||||
local ctl_status = status[1]
|
||||
|
||||
if type(ctl_status) == "table" then
|
||||
fac.auto_active = ctl_status[1] > 0
|
||||
fac.auto_ramping = ctl_status[2]
|
||||
fac.auto_scram = ctl_status[3]
|
||||
fac.auto_scram_cause = ctl_status[4]
|
||||
|
||||
fac.ps.publish("auto_active", fac.auto_active)
|
||||
fac.ps.publish("auto_ramping", fac.auto_ramping)
|
||||
fac.ps.publish("auto_scram", fac.auto_scram)
|
||||
fac.ps.publish("auto_scram_cause", fac.auto_scram_cause)
|
||||
else
|
||||
log.debug(log_header .. "control status not a table")
|
||||
end
|
||||
|
||||
-- RTU statuses
|
||||
|
||||
local rtu_statuses = status[1]
|
||||
local rtu_statuses = status[2]
|
||||
|
||||
if type(rtu_statuses) == "table" then
|
||||
-- power statistics
|
||||
if type(rtu_statuses.power) == "table" then
|
||||
fac.ps.publish("avg_charge", rtu_statuses.power[1])
|
||||
fac.ps.publish("avg_inflow", rtu_statuses.power[2])
|
||||
fac.ps.publish("avg_outflow", rtu_statuses.power[3])
|
||||
else
|
||||
log.debug(log_header .. "power statistics list not a table")
|
||||
end
|
||||
|
||||
-- induction matricies statuses
|
||||
if type(rtu_statuses.induction) == "table" then
|
||||
for id = 1, #fac.induction_ps_tbl do
|
||||
@ -328,6 +362,8 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
log.debug("iocontrol.update_unit_statuses: number of provided unit statuses does not match expected number of units")
|
||||
return false
|
||||
else
|
||||
local burn_rate_sum = 0.0
|
||||
|
||||
-- get all unit statuses
|
||||
for i = 1, #statuses do
|
||||
local log_header = util.c("iocontrol.update_unit_statuses[unit ", i, "]: ")
|
||||
@ -369,6 +405,11 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
unit.reactor_data.rps_status = rps_status ---@type rps_status
|
||||
unit.reactor_data.mek_status = mek_status ---@type mek_status
|
||||
|
||||
-- if status hasn't been received, mek_status = {}
|
||||
if type(unit.reactor_data.mek_status.act_burn_rate) == "number" then
|
||||
burn_rate_sum = burn_rate_sum + unit.reactor_data.mek_status.act_burn_rate
|
||||
end
|
||||
|
||||
if unit.reactor_data.mek_status.status then
|
||||
unit.reactor_ps.publish("computed_status", 5) -- running
|
||||
else
|
||||
@ -596,8 +637,8 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
local auto_ctl_state = status[6]
|
||||
|
||||
if type(auto_ctl_state) == "table" then
|
||||
if #auto_ctl_state == 1 then
|
||||
unit.reactor_ps.publish("burn_limit", auto_ctl_state[1])
|
||||
if #auto_ctl_state == 0 then
|
||||
---@todo
|
||||
else
|
||||
log.debug(log_header .. "auto control state length mismatch")
|
||||
end
|
||||
@ -606,6 +647,8 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
end
|
||||
end
|
||||
|
||||
io.facility.ps.publish("burn_sum", burn_rate_sum)
|
||||
|
||||
-- update alarm sounder
|
||||
sounder.eval(io.units)
|
||||
end
|
||||
|
@ -1,16 +1,28 @@
|
||||
|
||||
local comms = require("scada-common.comms")
|
||||
local log = require("scada-common.log")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local FAC_COMMANDS = comms.FAC_COMMANDS
|
||||
local UNIT_COMMANDS = comms.UNIT_COMMANDS
|
||||
|
||||
local PROCESS = types.PROCESS
|
||||
|
||||
---@class process_controller
|
||||
local process = {}
|
||||
|
||||
local self = {
|
||||
io = nil, ---@type ioctl
|
||||
comms = nil ---@type coord_comms
|
||||
comms = nil, ---@type coord_comms
|
||||
---@class coord_auto_config
|
||||
config = {
|
||||
mode = 0, ---@type PROCESS
|
||||
burn_target = 0.0,
|
||||
charge_target = 0.0,
|
||||
gen_target = 0.0,
|
||||
limits = {} ---@type table
|
||||
}
|
||||
}
|
||||
|
||||
--------------------------
|
||||
@ -24,11 +36,37 @@ function process.init(iocontrol, comms)
|
||||
self.io = iocontrol
|
||||
self.comms = comms
|
||||
|
||||
for i = 1, self.io.facility.num_units do
|
||||
self.config.limits[i] = 0.1
|
||||
end
|
||||
|
||||
-- load settings
|
||||
if not settings.load("/coord.settings") then
|
||||
log.error("process.init(): failed to load coordinator settings file")
|
||||
end
|
||||
|
||||
local config = settings.get("PROCESS") ---@type coord_auto_config|nil
|
||||
|
||||
if type(config) == "table" then
|
||||
self.config.mode = config.mode
|
||||
self.config.burn_target = config.burn_target
|
||||
self.config.charge_target = config.charge_target
|
||||
self.config.gen_target = config.gen_target
|
||||
self.config.limits = config.limits
|
||||
|
||||
self.io.facility.ps.publish("process_mode", self.config.mode)
|
||||
self.io.facility.ps.publish("process_burn_target", self.config.burn_target)
|
||||
self.io.facility.ps.publish("process_charge_target", self.config.charge_target)
|
||||
self.io.facility.ps.publish("process_gen_target", self.config.gen_target)
|
||||
|
||||
for id = 1, math.min(#self.config.limits, self.io.facility.num_units) do
|
||||
local unit = self.io.units[id] ---@type ioctl_unit
|
||||
unit.reactor_ps.publish("burn_limit", self.config.limits[id])
|
||||
end
|
||||
|
||||
log.info("PROCESS: loaded auto control settings from coord.settings")
|
||||
end
|
||||
|
||||
local waste_mode = settings.get("WASTE_MODES") ---@type table|nil
|
||||
|
||||
if type(waste_mode) == "table" then
|
||||
@ -44,7 +82,7 @@ end
|
||||
---@param id integer unit ID
|
||||
function process.start(id)
|
||||
self.io.units[id].control_state = true
|
||||
self.comms.send_command(UNIT_COMMANDS.START, id)
|
||||
self.comms.send_unit_command(UNIT_COMMANDS.START, id)
|
||||
log.debug(util.c("UNIT[", id, "]: START"))
|
||||
end
|
||||
|
||||
@ -52,14 +90,14 @@ end
|
||||
---@param id integer unit ID
|
||||
function process.scram(id)
|
||||
self.io.units[id].control_state = false
|
||||
self.comms.send_command(UNIT_COMMANDS.SCRAM, id)
|
||||
self.comms.send_unit_command(UNIT_COMMANDS.SCRAM, id)
|
||||
log.debug(util.c("UNIT[", id, "]: SCRAM"))
|
||||
end
|
||||
|
||||
-- reset reactor protection system
|
||||
---@param id integer unit ID
|
||||
function process.reset_rps(id)
|
||||
self.comms.send_command(UNIT_COMMANDS.RESET_RPS, id)
|
||||
self.comms.send_unit_command(UNIT_COMMANDS.RESET_RPS, id)
|
||||
log.debug(util.c("UNIT[", id, "]: RESET RPS"))
|
||||
end
|
||||
|
||||
@ -67,7 +105,7 @@ end
|
||||
---@param id integer unit ID
|
||||
---@param rate number burn rate
|
||||
function process.set_rate(id, rate)
|
||||
self.comms.send_command(UNIT_COMMANDS.SET_BURN, id, rate)
|
||||
self.comms.send_unit_command(UNIT_COMMANDS.SET_BURN, id, rate)
|
||||
log.debug(util.c("UNIT[", id, "]: SET BURN = ", rate))
|
||||
end
|
||||
|
||||
@ -75,7 +113,7 @@ end
|
||||
---@param id integer unit ID
|
||||
---@param mode integer waste mode
|
||||
function process.set_waste(id, mode)
|
||||
self.comms.send_command(UNIT_COMMANDS.SET_WASTE, id, mode)
|
||||
self.comms.send_unit_command(UNIT_COMMANDS.SET_WASTE, id, mode)
|
||||
log.debug(util.c("UNIT[", id, "]: SET WASTE = ", mode))
|
||||
|
||||
local waste_mode = settings.get("WASTE_MODES") ---@type table|nil
|
||||
@ -96,7 +134,7 @@ end
|
||||
-- acknowledge all alarms
|
||||
---@param id integer unit ID
|
||||
function process.ack_all_alarms(id)
|
||||
self.comms.send_command(UNIT_COMMANDS.ACK_ALL_ALARMS, id)
|
||||
self.comms.send_unit_command(UNIT_COMMANDS.ACK_ALL_ALARMS, id)
|
||||
log.debug(util.c("UNIT[", id, "]: ACK ALL ALARMS"))
|
||||
end
|
||||
|
||||
@ -104,7 +142,7 @@ end
|
||||
---@param id integer unit ID
|
||||
---@param alarm integer alarm ID
|
||||
function process.ack_alarm(id, alarm)
|
||||
self.comms.send_command(UNIT_COMMANDS.ACK_ALARM, id, alarm)
|
||||
self.comms.send_unit_command(UNIT_COMMANDS.ACK_ALARM, id, alarm)
|
||||
log.debug(util.c("UNIT[", id, "]: ACK ALARM ", alarm))
|
||||
end
|
||||
|
||||
@ -112,7 +150,7 @@ end
|
||||
---@param id integer unit ID
|
||||
---@param alarm integer alarm ID
|
||||
function process.reset_alarm(id, alarm)
|
||||
self.comms.send_command(UNIT_COMMANDS.RESET_ALARM, id, alarm)
|
||||
self.comms.send_unit_command(UNIT_COMMANDS.RESET_ALARM, id, alarm)
|
||||
log.debug(util.c("UNIT[", id, "]: RESET ALARM ", alarm))
|
||||
end
|
||||
|
||||
@ -120,16 +158,86 @@ end
|
||||
---@param unit_id integer unit ID
|
||||
---@param group_id integer|0 group ID or 0 for independent
|
||||
function process.set_group(unit_id, group_id)
|
||||
self.comms.send_command(UNIT_COMMANDS.SET_GROUP, unit_id, group_id)
|
||||
self.comms.send_unit_command(UNIT_COMMANDS.SET_GROUP, unit_id, group_id)
|
||||
log.debug(util.c("UNIT[", unit_id, "]: SET GROUP ", group_id))
|
||||
end
|
||||
|
||||
-- set the burn rate limit
|
||||
---@param id integer unit ID
|
||||
---@param limit number burn rate limit
|
||||
function process.set_limit(id, limit)
|
||||
self.comms.send_command(UNIT_COMMANDS.SET_LIMIT, id, limit)
|
||||
log.debug(util.c("UNIT[", id, "]: SET LIMIT = ", limit))
|
||||
--------------------------
|
||||
-- AUTO PROCESS CONTROL --
|
||||
--------------------------
|
||||
|
||||
-- facility SCRAM command
|
||||
function process.fac_scram()
|
||||
self.comms.send_fac_command(FAC_COMMANDS.SCRAM_ALL)
|
||||
log.debug("FAC: SCRAM ALL")
|
||||
end
|
||||
|
||||
-- stop automatic process control
|
||||
function process.stop_auto()
|
||||
self.comms.send_fac_command(FAC_COMMANDS.STOP)
|
||||
log.debug("FAC: STOP AUTO")
|
||||
end
|
||||
|
||||
-- start automatic process control
|
||||
function process.start_auto()
|
||||
self.comms.send_auto_start(self.config)
|
||||
log.debug("FAC: START AUTO")
|
||||
end
|
||||
|
||||
-- save process control settings
|
||||
---@param mode PROCESS control mode
|
||||
---@param burn_target number burn rate target
|
||||
---@param charge_target number charge target
|
||||
---@param gen_target number generation rate target
|
||||
---@param limits table unit burn rate limits
|
||||
function process.save(mode, burn_target, charge_target, gen_target, limits)
|
||||
-- attempt to load settings
|
||||
if not settings.load("/coord.settings") then
|
||||
log.warning("process.save(): failed to load coordinator settings file")
|
||||
end
|
||||
|
||||
-- config table
|
||||
self.config = {
|
||||
mode = mode,
|
||||
burn_target = burn_target,
|
||||
charge_target = charge_target,
|
||||
gen_target = gen_target,
|
||||
limits = limits
|
||||
}
|
||||
|
||||
-- save config
|
||||
settings.set("PROCESS", self.config)
|
||||
local saved = settings.save("/coord.settings")
|
||||
|
||||
if not saved then
|
||||
log.warning("process.save(): failed to save coordinator settings file")
|
||||
end
|
||||
|
||||
log.debug("saved = " .. util.strval(saved))
|
||||
|
||||
self.io.facility.save_cfg_ack(saved)
|
||||
end
|
||||
|
||||
-- handle a start command acknowledgement
|
||||
---@param response table ack and configuration reply
|
||||
function process.start_ack_handle(response)
|
||||
local ack = response[1]
|
||||
|
||||
self.config.mode = response[2]
|
||||
self.config.burn_target = response[3]
|
||||
self.config.charge_target = response[4]
|
||||
self.config.gen_target = response[5]
|
||||
|
||||
for i = 1, #response[6] do
|
||||
self.config.limits[i] = response[6][i]
|
||||
end
|
||||
|
||||
self.io.facility.ps.publish("auto_mode", self.config.mode)
|
||||
self.io.facility.ps.publish("burn_target", self.config.burn_target)
|
||||
self.io.facility.ps.publish("charge_target", self.config.charge_target)
|
||||
self.io.facility.ps.publish("gen_target", self.config.gen_target)
|
||||
|
||||
self.io.facility.start_ack(ack)
|
||||
end
|
||||
|
||||
--------------------------
|
||||
|
@ -1,6 +1,8 @@
|
||||
local tcd = require("scada-common.tcallbackdsp")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
local process = require("coordinator.process")
|
||||
|
||||
local style = require("coordinator.ui.style")
|
||||
|
||||
@ -36,29 +38,127 @@ local function new_view(root, x, y)
|
||||
local facility = iocontrol.get_db().facility
|
||||
local units = iocontrol.get_db().units
|
||||
|
||||
local bw_fg_bg = cpair(colors.black, colors.white)
|
||||
local bw_fg_bg = cpair(colors.black, colors.white)
|
||||
local hzd_fg_bg = cpair(colors.white, colors.gray)
|
||||
local dis_colors = cpair(colors.white, colors.lightGray)
|
||||
|
||||
local proc = Div{parent=root,width=60,height=24,x=x,y=y}
|
||||
local main = Div{parent=root,width=80,height=24,x=x,y=y}
|
||||
|
||||
local limits = Div{parent=proc,width=40,height=24,x=30,y=1}
|
||||
local scram = HazardButton{parent=main,x=1,y=1,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=process.fac_scram,fg_bg=hzd_fg_bg}
|
||||
|
||||
facility.scram_ack = scram.on_response
|
||||
|
||||
---------------------
|
||||
-- process control --
|
||||
---------------------
|
||||
|
||||
local proc = Div{parent=main,width=54,height=24,x=27,y=1}
|
||||
|
||||
-----------------------------
|
||||
-- process control targets --
|
||||
-----------------------------
|
||||
|
||||
local targets = Div{parent=proc,width=31,height=24,x=1,y=1}
|
||||
|
||||
local burn_tag = Div{parent=targets,x=1,y=1,width=8,height=4,fg_bg=cpair(colors.black,colors.purple)}
|
||||
TextBox{parent=burn_tag,x=2,y=2,text="Burn Target",width=7,height=2}
|
||||
|
||||
local burn_target = Div{parent=targets,x=9,y=1,width=23,height=3,fg_bg=cpair(colors.gray,colors.white)}
|
||||
local b_target = SpinboxNumeric{parent=burn_target,x=11,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg}
|
||||
TextBox{parent=burn_target,x=18,y=2,text="mB/t"}
|
||||
local burn_sum = DataIndicator{parent=targets,x=9,y=4,label="",format="%18.1f",value=0,unit="mB/t",commas=true,lu_colors=cpair(colors.black,colors.black),width=23,fg_bg=cpair(colors.black,colors.brown)}
|
||||
|
||||
facility.ps.subscribe("process_burn_target", b_target.set_value)
|
||||
facility.ps.subscribe("burn_sum", burn_sum.update)
|
||||
|
||||
local chg_tag = Div{parent=targets,x=1,y=6,width=8,height=4,fg_bg=cpair(colors.black,colors.purple)}
|
||||
TextBox{parent=chg_tag,x=2,y=2,text="Charge Target",width=7,height=2}
|
||||
|
||||
local chg_target = Div{parent=targets,x=9,y=6,width=23,height=3,fg_bg=cpair(colors.gray,colors.white)}
|
||||
local c_target = SpinboxNumeric{parent=chg_target,x=2,y=1,whole_num_precision=15,fractional_precision=0,min=0,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg}
|
||||
TextBox{parent=chg_target,x=18,y=2,text="kFE"}
|
||||
local cur_charge = DataIndicator{parent=targets,x=9,y=9,label="",format="%19d",value=0,unit="kFE",commas=true,lu_colors=cpair(colors.black,colors.black),width=23,fg_bg=cpair(colors.black,colors.brown)}
|
||||
|
||||
facility.ps.subscribe("process_charge_target", c_target.set_value)
|
||||
facility.induction_ps_tbl[1].subscribe("energy", function (j) cur_charge.update(util.joules_to_fe(j) / 1000) end)
|
||||
|
||||
local gen_tag = Div{parent=targets,x=1,y=11,width=8,height=4,fg_bg=cpair(colors.black,colors.purple)}
|
||||
TextBox{parent=gen_tag,x=2,y=2,text="Gen. Target",width=7,height=2}
|
||||
|
||||
local gen_target = Div{parent=targets,x=9,y=11,width=23,height=3,fg_bg=cpair(colors.gray,colors.white)}
|
||||
local g_target = SpinboxNumeric{parent=gen_target,x=8,y=1,whole_num_precision=9,fractional_precision=0,min=0,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg}
|
||||
TextBox{parent=gen_target,x=18,y=2,text="kFE/t"}
|
||||
local cur_gen = DataIndicator{parent=targets,x=9,y=14,label="",format="%17d",value=0,unit="kFE/t",commas=true,lu_colors=cpair(colors.black,colors.black),width=23,fg_bg=cpair(colors.black,colors.brown)}
|
||||
|
||||
facility.ps.subscribe("process_gen_target", g_target.set_value)
|
||||
facility.induction_ps_tbl[1].subscribe("last_input", function (j) cur_gen.update(util.joules_to_fe(j) / 1000) end)
|
||||
|
||||
-----------------
|
||||
-- unit limits --
|
||||
-----------------
|
||||
|
||||
local limit_div = Div{parent=proc,width=40,height=19,x=34,y=6}
|
||||
|
||||
local rate_limits = {}
|
||||
|
||||
for i = 1, facility.num_units do
|
||||
local unit = units[i] ---@type ioctl_entry
|
||||
local unit = units[i] ---@type ioctl_unit
|
||||
|
||||
local _y = ((i - 1) * 4) + 1
|
||||
local _y = ((i - 1) * 5) + 1
|
||||
|
||||
TextBox{parent=limits,x=1,y=_y+1,text="Unit "..i}
|
||||
|
||||
local lim_ctl = Div{parent=limits,x=8,y=_y,width=20,height=3,fg_bg=cpair(colors.gray,colors.white)}
|
||||
local burn_rate = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,max=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg}
|
||||
|
||||
unit.reactor_ps.subscribe("max_burn", burn_rate.set_max)
|
||||
unit.reactor_ps.subscribe("burn_limit", burn_rate.set_value)
|
||||
local unit_tag = Div{parent=limit_div,x=1,y=_y,width=8,height=4,fg_bg=cpair(colors.black,colors.lightBlue)}
|
||||
TextBox{parent=unit_tag,x=2,y=2,text="Unit "..i.." Limit",width=7,height=2}
|
||||
|
||||
local lim_ctl = Div{parent=limit_div,x=9,y=_y,width=14,height=3,fg_bg=cpair(colors.gray,colors.white)}
|
||||
rate_limits[i] = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg}
|
||||
TextBox{parent=lim_ctl,x=9,y=2,text="mB/t"}
|
||||
|
||||
local set_burn = function () unit.set_limit(burn_rate.get_value()) end
|
||||
PushButton{parent=lim_ctl,x=14,y=2,text="SAVE",min_width=6,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn}
|
||||
unit.reactor_ps.subscribe("max_burn", rate_limits[i].set_max)
|
||||
unit.reactor_ps.subscribe("burn_limit", rate_limits[i].set_value)
|
||||
|
||||
local cur_burn = DataIndicator{parent=limit_div,x=9,y=_y+3,label="",format="%7.1f",value=0,unit="mB/t",commas=false,lu_colors=cpair(colors.black,colors.black),width=14,fg_bg=cpair(colors.black,colors.brown)}
|
||||
|
||||
unit.reactor_ps.subscribe("act_burn_rate", cur_burn.update)
|
||||
end
|
||||
|
||||
-------------------------
|
||||
-- controls and status --
|
||||
-------------------------
|
||||
|
||||
local ctl_opts = { "Regulated", "Burn Rate", "Charge Level", "Generation Rate" }
|
||||
local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.purple,colors.black),radio_bg=colors.gray}
|
||||
|
||||
facility.ps.subscribe("process_mode", mode.set_value)
|
||||
|
||||
local u_stat = Rectangle{parent=proc,border=border(1,colors.gray,true),thin=true,width=31,height=4,x=1,y=16,fg_bg=bw_fg_bg}
|
||||
local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=31,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=bw_fg_bg}
|
||||
local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data",width=31,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)}
|
||||
|
||||
local auto_controls = Div{parent=proc,x=1,y=20,width=31,height=5,fg_bg=cpair(colors.gray,colors.white)}
|
||||
|
||||
-- save the automatic process control configuration without starting
|
||||
local function _save_cfg()
|
||||
local limits = {}
|
||||
for i = 1, #rate_limits do limits[i] = rate_limits[i].get_value() end
|
||||
|
||||
process.save(mode.get_value(), b_target.get_value(), c_target.get_value(), g_target.get_value(), limits)
|
||||
end
|
||||
|
||||
-- start automatic control after saving process control settings
|
||||
local function _start_auto()
|
||||
_save_cfg()
|
||||
process.start_auto()
|
||||
end
|
||||
|
||||
local save = HazardButton{parent=auto_controls,x=2,y=2,text="SAVE",accent=colors.purple,dis_colors=dis_colors,callback=_save_cfg,fg_bg=hzd_fg_bg}
|
||||
local start = HazardButton{parent=auto_controls,x=13,y=2,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=_start_auto,fg_bg=hzd_fg_bg}
|
||||
local stop = HazardButton{parent=auto_controls,x=23,y=2,text="STOP",accent=colors.orange,dis_colors=dis_colors,callback=process.stop_auto,fg_bg=hzd_fg_bg}
|
||||
|
||||
facility.start_ack = start.on_response
|
||||
facility.stop_ack = stop.on_response
|
||||
|
||||
function facility.save_cfg_ack(ack)
|
||||
tcd.dispatch(0.2, function () save.on_response(ack) end)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -83,7 +83,7 @@ local function init(monitor)
|
||||
-- testing
|
||||
---@fixme remove test code
|
||||
|
||||
ColorMap{parent=main,x=2,y=(main.height()-1)}
|
||||
ColorMap{parent=main,x=132,y=(main.height()-1)}
|
||||
|
||||
local audio = Div{parent=main,width=34,height=15,x=95,y=cnc_y_start}
|
||||
|
||||
|
@ -145,9 +145,6 @@ local function hazard_button(args)
|
||||
---@diagnostic disable-next-line: unused-local
|
||||
function e.handle_touch(event)
|
||||
if e.enabled then
|
||||
-- call the touch callback
|
||||
args.callback()
|
||||
|
||||
-- change text color to indicate clicked
|
||||
e.window.setTextColor(args.accent)
|
||||
e.window.setCursorPos(3, 2)
|
||||
@ -160,6 +157,9 @@ local function hazard_button(args)
|
||||
|
||||
-- 1.5 second timeout
|
||||
tcd.dispatch(1.5, on_timeout)
|
||||
|
||||
-- call the touch callback
|
||||
args.callback()
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -13,6 +13,7 @@ local DEVICE_TYPES = comms.DEVICE_TYPES
|
||||
local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
||||
local RPLC_TYPES = comms.RPLC_TYPES
|
||||
local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES
|
||||
local AUTO_ACK = comms.PLC_AUTO_ACK
|
||||
|
||||
local print = util.print
|
||||
local println = util.println
|
||||
@ -24,6 +25,8 @@ local println_ts = util.println_ts
|
||||
local PCALL_SCRAM_MSG = "pcall: Scram requires the reactor to be active."
|
||||
local PCALL_START_MSG = "pcall: Reactor is already active."
|
||||
|
||||
local AUTO_TOGGLE_DELAY_MS = 5000
|
||||
|
||||
-- RPS SAFETY CONSTANTS
|
||||
|
||||
local MAX_DAMAGE_PERCENT = 90
|
||||
@ -61,6 +64,7 @@ function plc.rps_init(reactor, is_formed)
|
||||
reactor = reactor,
|
||||
state = { false, false, false, false, false, false, false, false, false, false, false, false },
|
||||
reactor_enabled = false,
|
||||
enabled_at = 0,
|
||||
formed = is_formed,
|
||||
force_disabled = false,
|
||||
tripped = false,
|
||||
@ -215,7 +219,7 @@ function plc.rps_init(reactor, is_formed)
|
||||
self.state[state_keys.sys_fail] = true
|
||||
end
|
||||
|
||||
-- SCRAM the reactor now
|
||||
-- SCRAM the reactor now (blocks waiting for server tick)
|
||||
---@return boolean success
|
||||
function public.scram()
|
||||
log.info("RPS: reactor SCRAM")
|
||||
@ -226,11 +230,12 @@ function plc.rps_init(reactor, is_formed)
|
||||
return false
|
||||
else
|
||||
self.reactor_enabled = false
|
||||
self.last_runtime = util.time_ms() - self.enabled_at
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- start the reactor
|
||||
-- start the reactor now (blocks waiting for server tick)
|
||||
---@return boolean success
|
||||
function public.activate()
|
||||
if not self.tripped then
|
||||
@ -241,6 +246,7 @@ function plc.rps_init(reactor, is_formed)
|
||||
log.error("RPS: failed reactor start")
|
||||
else
|
||||
self.reactor_enabled = true
|
||||
self.enabled_at = util.time_ms()
|
||||
return true
|
||||
end
|
||||
else
|
||||
@ -250,6 +256,21 @@ function plc.rps_init(reactor, is_formed)
|
||||
return false
|
||||
end
|
||||
|
||||
-- automatic control activate/re-activate
|
||||
---@return boolean success
|
||||
function public.auto_activate()
|
||||
-- clear automatic SCRAM if it was the cause
|
||||
if self.tripped and self.trip_cause == "automatic" then
|
||||
self.state[state_keys.automatic] = true
|
||||
self.trip_cause = rps_status_t.ok
|
||||
self.tripped = false
|
||||
|
||||
log.debug("RPS: cleared automatic SCRAM for re-activation")
|
||||
end
|
||||
|
||||
return public.activate()
|
||||
end
|
||||
|
||||
-- check all safety conditions
|
||||
---@return boolean tripped, rps_status_t trip_status, boolean first_trip
|
||||
function public.check()
|
||||
@ -324,7 +345,8 @@ function plc.rps_init(reactor, is_formed)
|
||||
self.tripped = true
|
||||
self.trip_cause = status
|
||||
|
||||
-- in the case that the reactor is detected to be active, it will be scrammed shortly after this in the main RPS loop if we don't here
|
||||
-- in the case that the reactor is detected to be active,
|
||||
-- it will be scrammed shortly after this in the main RPS loop if we don't here
|
||||
if self.formed then
|
||||
if not self.force_disabled then
|
||||
public.scram()
|
||||
@ -348,6 +370,10 @@ function plc.rps_init(reactor, is_formed)
|
||||
function public.is_formed() return self.formed end
|
||||
function public.is_force_disabled() return self.force_disabled end
|
||||
|
||||
-- get the runtime of the reactor if active, or the last runtime if disabled
|
||||
---@return integer runtime time since last enable
|
||||
function public.get_runtime() return util.trinary(self.reactor_enabled, util.time_ms() - self.enabled_at, self.last_runtime) end
|
||||
|
||||
-- reset the RPS
|
||||
---@param quiet? boolean true to suppress the info log message
|
||||
function public.reset(quiet)
|
||||
@ -383,8 +409,8 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co
|
||||
reactor = reactor,
|
||||
scrammed = false,
|
||||
linked = false,
|
||||
status_cache = nil,
|
||||
resend_build = false,
|
||||
status_cache = nil,
|
||||
max_burn_rate = nil
|
||||
}
|
||||
|
||||
@ -532,9 +558,9 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co
|
||||
|
||||
-- general ack
|
||||
---@param msg_type RPLC_TYPES
|
||||
---@param succeeded boolean
|
||||
local function _send_ack(msg_type, succeeded)
|
||||
_send(msg_type, { succeeded })
|
||||
---@param status boolean|integer
|
||||
local function _send_ack(msg_type, status)
|
||||
_send(msg_type, { status })
|
||||
end
|
||||
|
||||
-- send structure properties (these should not change, server will cache these)
|
||||
@ -587,6 +613,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co
|
||||
self.reactor = reactor
|
||||
self.status_cache = nil
|
||||
self.resend_build = true
|
||||
self.max_burn_rate = nil
|
||||
end
|
||||
|
||||
-- unlink from the server
|
||||
@ -731,7 +758,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co
|
||||
log.debug("sent out structure again, did supervisor miss it?")
|
||||
elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then
|
||||
-- set the burn rate
|
||||
if packet.length == 2 then
|
||||
if (packet.length == 2) and (type(packet.data[1]) == "number") then
|
||||
local success = false
|
||||
local burn_rate = math.floor(packet.data[1] * 10) / 10
|
||||
local ramp = packet.data[2]
|
||||
@ -759,7 +786,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co
|
||||
|
||||
_send_ack(packet.type, success)
|
||||
else
|
||||
log.debug("RPLC set burn rate packet length mismatch")
|
||||
log.debug("RPLC set burn rate packet length mismatch or non-numeric burn rate")
|
||||
end
|
||||
elseif packet.type == RPLC_TYPES.RPS_ENABLE then
|
||||
-- enable the reactor
|
||||
@ -779,6 +806,63 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co
|
||||
-- reset the RPS status
|
||||
rps.reset()
|
||||
_send_ack(packet.type, true)
|
||||
elseif packet.type == RPLC_TYPES.AUTO_BURN_RATE then
|
||||
-- automatic control requested a new burn rate
|
||||
if (packet.length == 2) and (type(packet.data[1]) == "number") then
|
||||
local ack = AUTO_ACK.FAIL
|
||||
local burn_rate = math.floor(packet.data[1] * 10) / 10
|
||||
local ramp = packet.data[2]
|
||||
|
||||
-- if no known max burn rate, check again
|
||||
if self.max_burn_rate == nil then
|
||||
self.max_burn_rate = self.reactor.getMaxBurnRate()
|
||||
end
|
||||
|
||||
-- if we know our max burn rate, update current burn rate setpoint if in range
|
||||
if self.max_burn_rate ~= ppm.ACCESS_FAULT then
|
||||
if burn_rate < 0.1 then
|
||||
if rps.is_active() then
|
||||
if rps.get_runtime() > AUTO_TOGGLE_DELAY_MS then
|
||||
-- auto scram to disable
|
||||
if rps.scram() then
|
||||
ack = AUTO_ACK.ZERO_DIS_OK
|
||||
self.auto_last_disable = util.time_ms()
|
||||
end
|
||||
else
|
||||
-- too soon to disable
|
||||
ack = AUTO_ACK.ZERO_DIS_WAIT
|
||||
end
|
||||
else
|
||||
ack = AUTO_ACK.ZERO_DIS_OK
|
||||
end
|
||||
elseif burn_rate <= self.max_burn_rate then
|
||||
if not rps.is_active() then
|
||||
-- activate the reactor
|
||||
if not rps.auto_activate() then
|
||||
log.debug("automatic reactor activation failed")
|
||||
end
|
||||
end
|
||||
|
||||
-- if active, set/ramp burn rate
|
||||
if rps.is_active() then
|
||||
if ramp then
|
||||
setpoints.burn_rate_en = true
|
||||
setpoints.burn_rate = burn_rate
|
||||
ack = AUTO_ACK.RAMP_SET_OK
|
||||
else
|
||||
self.reactor.setBurnRate(burn_rate)
|
||||
ack = util.trinary(self.reactor.__p_is_faulted(), AUTO_ACK.FAIL, AUTO_ACK.DIRECT_SET_OK)
|
||||
end
|
||||
end
|
||||
else
|
||||
log.debug(burn_rate .. " rate outside of 0 < x <= " .. self.max_burn_rate)
|
||||
end
|
||||
end
|
||||
|
||||
_send_ack(packet.type, ack)
|
||||
else
|
||||
log.debug("RPLC set automatic burn rate packet length mismatch or non-numeric burn rate")
|
||||
end
|
||||
else
|
||||
log.warning("received unknown RPLC packet type " .. packet.type)
|
||||
end
|
||||
|
@ -14,7 +14,7 @@ local config = require("reactor-plc.config")
|
||||
local plc = require("reactor-plc.plc")
|
||||
local threads = require("reactor-plc.threads")
|
||||
|
||||
local R_PLC_VERSION = "beta-v0.9.10"
|
||||
local R_PLC_VERSION = "beta-v0.10.0"
|
||||
|
||||
local print = util.print
|
||||
local println = util.println
|
||||
|
@ -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.9.10"
|
||||
local RTU_VERSION = "beta-v0.9.11"
|
||||
|
||||
local rtu_t = types.rtu_t
|
||||
|
||||
|
@ -12,7 +12,7 @@ local rtu_t = types.rtu_t
|
||||
|
||||
local insert = table.insert
|
||||
|
||||
comms.version = "1.1.1"
|
||||
comms.version = "1.1.2"
|
||||
|
||||
---@alias PROTOCOLS integer
|
||||
local PROTOCOLS = {
|
||||
@ -23,14 +23,6 @@ local PROTOCOLS = {
|
||||
COORD_API = 4 -- data/control packets for pocket computers to/from coordinators
|
||||
}
|
||||
|
||||
---@alias DEVICE_TYPES integer
|
||||
local DEVICE_TYPES = {
|
||||
PLC = 0, -- PLC device type for establish
|
||||
RTU = 1, -- RTU device type for establish
|
||||
SV = 2, -- supervisor device type for establish
|
||||
CRDN = 3 -- coordinator device type for establish
|
||||
}
|
||||
|
||||
---@alias RPLC_TYPES integer
|
||||
local RPLC_TYPES = {
|
||||
STATUS = 0, -- reactor/system status
|
||||
@ -41,7 +33,8 @@ local RPLC_TYPES = {
|
||||
RPS_ASCRAM = 5, -- SCRAM reactor (automatic request)
|
||||
RPS_STATUS = 6, -- RPS status
|
||||
RPS_ALARM = 7, -- RPS alarm broadcast
|
||||
RPS_RESET = 8 -- clear RPS trip (if in bad state, will trip immediately)
|
||||
RPS_RESET = 8, -- clear RPS trip (if in bad state, will trip immediately)
|
||||
AUTO_BURN_RATE = 9 -- set an automatic burn rate, PLC will respond with status, enable toggle speed limited
|
||||
}
|
||||
|
||||
---@alias SCADA_MGMT_TYPES integer
|
||||
@ -53,13 +46,6 @@ local SCADA_MGMT_TYPES = {
|
||||
RTU_DEV_REMOUNT = 4 -- RTU multiblock possbily changed (formed, unformed) due to PPM remount
|
||||
}
|
||||
|
||||
---@alias ESTABLISH_ACK integer
|
||||
local ESTABLISH_ACK = {
|
||||
ALLOW = 0, -- link approved
|
||||
DENY = 1, -- link denied
|
||||
COLLISION = 2 -- link denied due to existing active link
|
||||
}
|
||||
|
||||
---@alias SCADA_CRDN_TYPES integer
|
||||
local SCADA_CRDN_TYPES = {
|
||||
FAC_BUILDS = 0, -- facility RTU builds
|
||||
@ -70,25 +56,26 @@ local SCADA_CRDN_TYPES = {
|
||||
UNIT_CMD = 5 -- command a reactor unit
|
||||
}
|
||||
|
||||
---@alias UNIT_COMMANDS integer
|
||||
local UNIT_COMMANDS = {
|
||||
SCRAM = 0, -- SCRAM the reactor
|
||||
START = 1, -- start the reactor
|
||||
RESET_RPS = 2, -- reset the RPS
|
||||
SET_BURN = 3, -- set the burn rate
|
||||
SET_WASTE = 4, -- set the waste processing mode
|
||||
ACK_ALL_ALARMS = 5, -- ack all active alarms
|
||||
ACK_ALARM = 6, -- ack a particular alarm
|
||||
RESET_ALARM = 7, -- reset a particular alarm
|
||||
SET_GROUP = 8, -- assign this unit to a group
|
||||
SET_LIMIT = 9 -- set this unit maximum auto burn rate
|
||||
}
|
||||
|
||||
---@alias CAPI_TYPES integer
|
||||
local CAPI_TYPES = {
|
||||
ESTABLISH = 0 -- initial greeting
|
||||
}
|
||||
|
||||
---@alias ESTABLISH_ACK integer
|
||||
local ESTABLISH_ACK = {
|
||||
ALLOW = 0, -- link approved
|
||||
DENY = 1, -- link denied
|
||||
COLLISION = 2 -- link denied due to existing active link
|
||||
}
|
||||
|
||||
---@alias DEVICE_TYPES integer
|
||||
local DEVICE_TYPES = {
|
||||
PLC = 0, -- PLC device type for establish
|
||||
RTU = 1, -- RTU device type for establish
|
||||
SV = 2, -- supervisor device type for establish
|
||||
CRDN = 3 -- coordinator device type for establish
|
||||
}
|
||||
|
||||
---@alias RTU_UNIT_TYPES integer
|
||||
local RTU_UNIT_TYPES = {
|
||||
REDSTONE = 0, -- redstone I/O
|
||||
@ -100,16 +87,51 @@ local RTU_UNIT_TYPES = {
|
||||
ENV_DETECTOR = 6 -- environment detector
|
||||
}
|
||||
|
||||
---@alias PLC_AUTO_ACK integer
|
||||
local PLC_AUTO_ACK = {
|
||||
FAIL = 0, -- failed to set burn rate/burn rate invalid
|
||||
DIRECT_SET_OK = 1, -- successfully set burn rate
|
||||
RAMP_SET_OK = 2, -- successfully started burn rate ramping
|
||||
ZERO_DIS_OK = 3, -- successfully disabled reactor with < 0.1 burn rate
|
||||
ZERO_DIS_WAIT = 4 -- too soon to disable reactor with < 0.1 burn rate
|
||||
}
|
||||
|
||||
---@alias FAC_COMMANDS integer
|
||||
local FAC_COMMANDS = {
|
||||
SCRAM_ALL = 0, -- SCRAM all reactors
|
||||
STOP = 1, -- stop automatic control
|
||||
START = 2 -- start automatic control
|
||||
}
|
||||
|
||||
---@alias UNIT_COMMANDS integer
|
||||
local UNIT_COMMANDS = {
|
||||
SCRAM = 0, -- SCRAM the reactor
|
||||
START = 1, -- start the reactor
|
||||
RESET_RPS = 2, -- reset the RPS
|
||||
SET_BURN = 3, -- set the burn rate
|
||||
SET_WASTE = 4, -- set the waste processing mode
|
||||
ACK_ALL_ALARMS = 5, -- ack all active alarms
|
||||
ACK_ALARM = 6, -- ack a particular alarm
|
||||
RESET_ALARM = 7, -- reset a particular alarm
|
||||
SET_GROUP = 8 -- assign this unit to a group
|
||||
}
|
||||
|
||||
comms.PROTOCOLS = PROTOCOLS
|
||||
comms.DEVICE_TYPES = DEVICE_TYPES
|
||||
|
||||
comms.RPLC_TYPES = RPLC_TYPES
|
||||
comms.ESTABLISH_ACK = ESTABLISH_ACK
|
||||
comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES
|
||||
comms.SCADA_CRDN_TYPES = SCADA_CRDN_TYPES
|
||||
comms.UNIT_COMMANDS = UNIT_COMMANDS
|
||||
comms.CAPI_TYPES = CAPI_TYPES
|
||||
|
||||
comms.ESTABLISH_ACK = ESTABLISH_ACK
|
||||
comms.DEVICE_TYPES = DEVICE_TYPES
|
||||
comms.RTU_UNIT_TYPES = RTU_UNIT_TYPES
|
||||
|
||||
comms.PLC_AUTO_ACK = PLC_AUTO_ACK
|
||||
|
||||
comms.UNIT_COMMANDS = UNIT_COMMANDS
|
||||
comms.FAC_COMMANDS = FAC_COMMANDS
|
||||
|
||||
---@alias packet scada_packet|modbus_packet|rplc_packet|mgmt_packet|crdn_packet|capi_packet
|
||||
---@alias frame modbus_frame|rplc_frame|mgmt_frame|crdn_frame|capi_frame
|
||||
|
||||
@ -308,9 +330,10 @@ function comms.rplc_packet()
|
||||
self.type == RPLC_TYPES.RPS_ENABLE or
|
||||
self.type == RPLC_TYPES.RPS_SCRAM or
|
||||
self.type == RPLC_TYPES.RPS_ASCRAM or
|
||||
self.type == RPLC_TYPES.RPS_ALARM or
|
||||
self.type == RPLC_TYPES.RPS_STATUS or
|
||||
self.type == RPLC_TYPES.RPS_RESET
|
||||
self.type == RPLC_TYPES.RPS_ALARM or
|
||||
self.type == RPLC_TYPES.RPS_RESET or
|
||||
self.type == RPLC_TYPES.AUTO_BURN_RATE
|
||||
end
|
||||
|
||||
-- make an RPLC packet
|
||||
|
@ -35,6 +35,15 @@ types.TRI_FAIL = {
|
||||
FULL = 2
|
||||
}
|
||||
|
||||
---@alias PROCESS integer
|
||||
types.PROCESS = {
|
||||
INACTIVE = 0,
|
||||
SIMPLE = 1,
|
||||
BURN_RATE = 2,
|
||||
CHARGE = 3,
|
||||
GEN_RATE = 4
|
||||
}
|
||||
|
||||
---@alias WASTE_MODE integer
|
||||
types.WASTE_MODE = {
|
||||
AUTO = 1,
|
||||
@ -164,6 +173,9 @@ types.ALARM_STATE = {
|
||||
---| "sys_fail"
|
||||
---| "force_disabled"
|
||||
|
||||
---@alias auto_scram_cause
|
||||
---| "ok"
|
||||
|
||||
---@alias rtu_t string
|
||||
types.rtu_t = {
|
||||
redstone = "redstone",
|
||||
|
@ -202,6 +202,13 @@ function util.is_int(x)
|
||||
return type(x) == "number" and x == math.floor(x)
|
||||
end
|
||||
|
||||
-- get the sign of a number
|
||||
---@param x number value
|
||||
---@return integer sign (-1 for < 0, 1 otherwise)
|
||||
function util.sign(x)
|
||||
return util.trinary(x < 0, -1, 1)
|
||||
end
|
||||
|
||||
-- round a number to an integer
|
||||
---@return integer rounded
|
||||
function util.round(x)
|
||||
|
@ -11,6 +11,7 @@ local PROTOCOLS = comms.PROTOCOLS
|
||||
local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES
|
||||
local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES
|
||||
local UNIT_COMMANDS = comms.UNIT_COMMANDS
|
||||
local FAC_COMMANDS = comms.FAC_COMMANDS
|
||||
|
||||
local SV_Q_CMDS = svqtypes.SV_Q_CMDS
|
||||
local SV_Q_DATA = svqtypes.SV_Q_DATA
|
||||
@ -133,6 +134,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility)
|
||||
-- send facility status
|
||||
local function _send_fac_status()
|
||||
local status = {
|
||||
facility.get_control_status(),
|
||||
facility.get_rtu_statuses()
|
||||
}
|
||||
|
||||
@ -146,9 +148,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility)
|
||||
for i = 1, #self.units do
|
||||
local unit = self.units[i] ---@type reactor_unit
|
||||
|
||||
local auto_ctl = {
|
||||
unit.get_control_inf().lim_br10 / 10
|
||||
}
|
||||
local auto_ctl = {}
|
||||
|
||||
status[unit.get_id()] = {
|
||||
unit.get_reactor_status(),
|
||||
@ -208,6 +208,37 @@ function coordinator.new_session(id, in_queue, out_queue, facility)
|
||||
if pkt.type == SCADA_CRDN_TYPES.FAC_BUILDS then
|
||||
-- acknowledgement to coordinator receiving builds
|
||||
self.acks.fac_builds = true
|
||||
elseif pkt.type == SCADA_CRDN_TYPES.FAC_CMD then
|
||||
if pkt.length >= 1 then
|
||||
local cmd = pkt.data[1]
|
||||
|
||||
if cmd == FAC_COMMANDS.SCRAM_ALL then
|
||||
facility.scram_all()
|
||||
_send(SCADA_CRDN_TYPES.FAC_CMD, { cmd, true })
|
||||
elseif cmd == FAC_COMMANDS.STOP then
|
||||
facility.auto_stop()
|
||||
_send(SCADA_CRDN_TYPES.FAC_CMD, { cmd, true })
|
||||
elseif cmd == FAC_COMMANDS.START then
|
||||
if pkt.length == 6 then
|
||||
---@type coord_auto_config
|
||||
local config = {
|
||||
mode = pkt.data[2],
|
||||
burn_target = pkt.data[3],
|
||||
charge_target = pkt.data[4],
|
||||
gen_target = pkt.data[5],
|
||||
limits = pkt.data[6]
|
||||
}
|
||||
|
||||
_send(SCADA_CRDN_TYPES.FAC_CMD, { cmd, table.unpack(facility.auto_start(config)) })
|
||||
else
|
||||
log.debug(log_header .. "CRDN auto start (with configuration) packet length mismatch")
|
||||
end
|
||||
else
|
||||
log.debug(log_header .. "CRDN facility command unknown")
|
||||
end
|
||||
else
|
||||
log.debug(log_header .. "CRDN facility command packet length mismatch")
|
||||
end
|
||||
elseif pkt.type == SCADA_CRDN_TYPES.UNIT_BUILDS then
|
||||
-- acknowledgement to coordinator receiving builds
|
||||
self.acks.unit_builds = true
|
||||
@ -234,13 +265,13 @@ function coordinator.new_session(id, in_queue, out_queue, facility)
|
||||
if pkt.length == 3 then
|
||||
self.out_q.push_data(SV_Q_DATA.SET_BURN, data)
|
||||
else
|
||||
log.debug(log_header .. "CRDN command unit burn rate missing option")
|
||||
log.debug(log_header .. "CRDN unit command burn rate missing option")
|
||||
end
|
||||
elseif cmd == UNIT_COMMANDS.SET_WASTE then
|
||||
if pkt.length == 3 then
|
||||
unit.set_waste(pkt.data[3])
|
||||
else
|
||||
log.debug(log_header .. "CRDN command unit set waste missing option")
|
||||
log.debug(log_header .. "CRDN unit command set waste missing option")
|
||||
end
|
||||
elseif cmd == UNIT_COMMANDS.ACK_ALL_ALARMS then
|
||||
unit.ack_all()
|
||||
@ -249,36 +280,29 @@ function coordinator.new_session(id, in_queue, out_queue, facility)
|
||||
if pkt.length == 3 then
|
||||
unit.ack_alarm(pkt.data[3])
|
||||
else
|
||||
log.debug(log_header .. "CRDN command unit ack alarm missing alarm id")
|
||||
log.debug(log_header .. "CRDN unit command ack alarm missing alarm id")
|
||||
end
|
||||
elseif cmd == UNIT_COMMANDS.RESET_ALARM then
|
||||
if pkt.length == 3 then
|
||||
unit.reset_alarm(pkt.data[3])
|
||||
else
|
||||
log.debug(log_header .. "CRDN command unit reset alarm missing alarm id")
|
||||
log.debug(log_header .. "CRDN unit command reset alarm missing alarm id")
|
||||
end
|
||||
elseif cmd == UNIT_COMMANDS.SET_GROUP then
|
||||
if pkt.length == 3 then
|
||||
facility.set_group(unit.get_id(), pkt.data[3])
|
||||
_send(SCADA_CRDN_TYPES.UNIT_CMD, { cmd, uid, pkt.data[3] })
|
||||
else
|
||||
log.debug(log_header .. "CRDN command unit set group missing group id")
|
||||
end
|
||||
elseif cmd == UNIT_COMMANDS.SET_LIMIT then
|
||||
if pkt.length == 3 then
|
||||
unit.set_burn_limit(pkt.data[3])
|
||||
_send(SCADA_CRDN_TYPES.UNIT_CMD, { cmd, uid, pkt.data[3] })
|
||||
else
|
||||
log.debug(log_header .. "CRDN command unit set limit missing group id")
|
||||
log.debug(log_header .. "CRDN unit command set group missing group id")
|
||||
end
|
||||
else
|
||||
log.debug(log_header .. "CRDN command unknown")
|
||||
log.debug(log_header .. "CRDN unit command unknown")
|
||||
end
|
||||
else
|
||||
log.debug(log_header .. "CRDN command unit invalid")
|
||||
log.debug(log_header .. "CRDN unit command invalid")
|
||||
end
|
||||
else
|
||||
log.debug(log_header .. "CRDN command unit packet length mismatch")
|
||||
log.debug(log_header .. "CRDN unit command packet length mismatch")
|
||||
end
|
||||
else
|
||||
log.debug(log_header .. "handler received unexpected SCADA_CRDN packet type " .. pkt.type)
|
||||
@ -331,6 +355,8 @@ function coordinator.new_session(id, in_queue, out_queue, facility)
|
||||
self.retry_times.builds_packet = util.time() + RETRY_PERIOD
|
||||
_send_fac_builds()
|
||||
_send_unit_builds()
|
||||
else
|
||||
log.warning(log_header .. "unsupported command received in in_queue (this is a bug)")
|
||||
end
|
||||
elseif message.qtype == mqueue.TYPE.DATA then
|
||||
-- instruction with body
|
||||
@ -339,6 +365,8 @@ function coordinator.new_session(id, in_queue, out_queue, facility)
|
||||
if cmd.key == CRD_S_DATA.CMD_ACK then
|
||||
local ack = cmd.val ---@type coord_ack
|
||||
_send(SCADA_CRDN_TYPES.UNIT_CMD, { ack.cmd, ack.unit, ack.ack })
|
||||
else
|
||||
log.warning(log_header .. "unsupported data command received in in_queue (this is a bug)")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,12 +1,12 @@
|
||||
local log = require("scada-common.log")
|
||||
local rsio = require("scada-common.rsio")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local rsctl = require("supervisor.session.rsctl")
|
||||
local unit = require("supervisor.session.unit")
|
||||
|
||||
local HEATING_WATER = 20000
|
||||
local HEATING_SODIUM = 200000
|
||||
local PROCESS = types.PROCESS
|
||||
|
||||
-- 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)
|
||||
@ -15,15 +15,6 @@ local POWER_PER_BLADE = util.joules_to_fe(7140)
|
||||
local MAX_CHARGE = 0.99
|
||||
local RE_ENABLE_CHARGE = 0.95
|
||||
|
||||
---@alias PROCESS integer
|
||||
local PROCESS = {
|
||||
INACTIVE = 1,
|
||||
SIMPLE = 2,
|
||||
CHARGE = 3,
|
||||
GEN_RATE = 4,
|
||||
BURN_RATE = 5
|
||||
}
|
||||
|
||||
local AUTO_SCRAM = {
|
||||
NONE = 0,
|
||||
MATRIX_DC = 1,
|
||||
@ -31,7 +22,7 @@ local AUTO_SCRAM = {
|
||||
}
|
||||
|
||||
local charge_Kp = 1.0
|
||||
local charge_Ki = 0.0
|
||||
local charge_Ki = 0.00001
|
||||
local charge_Kd = 0.0
|
||||
|
||||
local rate_Kp = 1.0
|
||||
@ -41,8 +32,6 @@ local rate_Kd = 0.0
|
||||
---@class facility_management
|
||||
local facility = {}
|
||||
|
||||
facility.PROCESS_MODES = PROCESS
|
||||
|
||||
-- create a new facility management object
|
||||
---@param num_reactors integer number of reactor units
|
||||
---@param cooling_conf table cooling configurations of reactor units
|
||||
@ -54,9 +43,11 @@ function facility.new(num_reactors, cooling_conf)
|
||||
-- process control
|
||||
mode = PROCESS.INACTIVE,
|
||||
last_mode = PROCESS.INACTIVE,
|
||||
burn_target = 0.0, -- burn rate target for aggregate burn mode
|
||||
mode_set = PROCESS.SIMPLE,
|
||||
max_burn_combined = 0.0, -- maximum burn rate to clamp at
|
||||
burn_target = 0.1, -- burn rate target for aggregate burn mode
|
||||
charge_target = 0, -- FE charge target
|
||||
charge_rate = 0, -- FE/t charge rate target
|
||||
gen_rate_target = 0, -- FE/t charge rate target
|
||||
group_map = { 0, 0, 0, 0 }, -- units -> group IDs
|
||||
prio_defs = { {}, {}, {}, {} }, -- priority definitions (each level is a table of units)
|
||||
ascram = false,
|
||||
@ -67,6 +58,7 @@ function facility.new(num_reactors, cooling_conf)
|
||||
initial_ramp = true,
|
||||
waiting_on_ramp = false,
|
||||
accumulator = 0.0,
|
||||
saturated = false,
|
||||
last_error = 0.0,
|
||||
last_time = 0.0,
|
||||
-- statistics
|
||||
@ -214,6 +206,8 @@ function facility.new(num_reactors, cooling_conf)
|
||||
if state_changed then
|
||||
if self.last_mode == PROCESS.INACTIVE then
|
||||
local blade_count = 0
|
||||
self.max_burn_combined = 0.0
|
||||
|
||||
for i = 1, #self.prio_defs do
|
||||
table.sort(self.prio_defs[i],
|
||||
---@param a reactor_unit
|
||||
@ -224,6 +218,7 @@ function facility.new(num_reactors, cooling_conf)
|
||||
for _, u in pairs(self.prio_defs[i]) do
|
||||
blade_count = blade_count + u.get_db().blade_count
|
||||
u.a_engage()
|
||||
self.max_burn_combined = self.max_burn_combined + (u.get_control_inf().lim_br10 / 10.0)
|
||||
end
|
||||
end
|
||||
|
||||
@ -247,6 +242,18 @@ function facility.new(num_reactors, cooling_conf)
|
||||
if state_changed then
|
||||
self.time_start = now
|
||||
end
|
||||
elseif self.mode == PROCESS.BURN_RATE then
|
||||
-- a total aggregate burn rate
|
||||
if state_changed then
|
||||
-- nothing special to do
|
||||
elseif self.waiting_on_ramp and _all_units_ramped() then
|
||||
self.waiting_on_ramp = false
|
||||
self.time_start = now
|
||||
end
|
||||
|
||||
if not self.waiting_on_ramp then
|
||||
_allocate_burn_rate(self.burn_target, self.initial_ramp)
|
||||
end
|
||||
elseif self.mode == PROCESS.CHARGE then
|
||||
-- target a level of charge
|
||||
local error = (self.charge_target - avg_charge) / self.charge_conversion
|
||||
@ -261,23 +268,32 @@ function facility.new(num_reactors, cooling_conf)
|
||||
end
|
||||
|
||||
if not self.waiting_on_ramp then
|
||||
self.accumulator = self.accumulator + (avg_charge / self.charge_conversion)
|
||||
if not self.saturated then
|
||||
self.accumulator = self.accumulator + ((avg_charge / self.charge_conversion) * (now - self.last_time))
|
||||
end
|
||||
|
||||
local runtime = now - self.time_start
|
||||
local integral = self.accumulator / runtime
|
||||
local derivative = (error - self.last_error) / (now - self.last_time)
|
||||
local integral = self.accumulator
|
||||
-- local derivative = (error - self.last_error) / (now - self.last_time)
|
||||
|
||||
local P = (charge_Kp * error)
|
||||
local I = (charge_Ki * integral)
|
||||
local D = (charge_Kd * derivative)
|
||||
local D = 0 -- (charge_Kd * derivative)
|
||||
|
||||
local setpoint = P + I + D
|
||||
|
||||
-- round setpoint -> setpoint rounded (sp_r)
|
||||
local sp_r = util.round(setpoint * 10.0) / 10.0
|
||||
|
||||
log.debug(util.sprintf("PROC_CHRG[%f] { CHRG[%f] ERR[%f] INT[%f] => SP[%f] SP_R[%f] <= P[%f] I[%f] D[%d] }",
|
||||
runtime, avg_charge, error, integral, setpoint, sp_r, P, I, D))
|
||||
-- clamp at range -> setpoint clamped (sp_c)
|
||||
local sp_c = math.max(0, math.min(sp_r, self.max_burn_combined))
|
||||
|
||||
_allocate_burn_rate(sp_r, self.initial_ramp)
|
||||
self.saturated = sp_r ~= sp_c
|
||||
|
||||
log.debug(util.sprintf("PROC_CHRG[%f] { CHRG[%f] ERR[%f] INT[%f] => SP[%f] SP_C[%f] <= P[%f] I[%f] D[%d] }",
|
||||
runtime, avg_charge, error, integral, setpoint, sp_c, P, I, D))
|
||||
|
||||
_allocate_burn_rate(sp_c, self.initial_ramp)
|
||||
|
||||
if self.initial_ramp then
|
||||
self.waiting_on_ramp = true
|
||||
@ -285,7 +301,7 @@ function facility.new(num_reactors, cooling_conf)
|
||||
end
|
||||
elseif self.mode == PROCESS.GEN_RATE then
|
||||
-- target a rate of generation
|
||||
local error = (self.charge_rate - avg_inflow) / self.charge_conversion
|
||||
local error = (self.gen_rate_target - avg_inflow) / self.charge_conversion
|
||||
local setpoint = 0.0
|
||||
|
||||
if state_changed then
|
||||
@ -303,36 +319,32 @@ function facility.new(num_reactors, cooling_conf)
|
||||
end
|
||||
|
||||
if not self.waiting_on_ramp then
|
||||
self.accumulator = self.accumulator + (avg_inflow / self.charge_conversion)
|
||||
if not self.saturated then
|
||||
self.accumulator = self.accumulator + ((avg_inflow / self.charge_conversion) * (now - self.last_time))
|
||||
end
|
||||
|
||||
local runtime = util.time_s() - self.time_start
|
||||
local integral = self.accumulator / runtime
|
||||
local derivative = (error - self.last_error) / (now - self.last_time)
|
||||
local integral = self.accumulator
|
||||
-- local derivative = (error - self.last_error) / (now - self.last_time)
|
||||
|
||||
local P = (rate_Kp * error)
|
||||
local I = (rate_Ki * integral)
|
||||
local D = (rate_Kd * derivative)
|
||||
local D = 0 -- (rate_Kd * derivative)
|
||||
|
||||
setpoint = P + I + D
|
||||
|
||||
-- round setpoint -> setpoint rounded (sp_r)
|
||||
local sp_r = util.round(setpoint * 10.0) / 10.0
|
||||
|
||||
log.debug(util.sprintf("PROC_RATE[%f] { RATE[%f] ERR[%f] INT[%f] => SP[%f] SP_R[%f] <= P[%f] I[%f] D[%f] }",
|
||||
runtime, avg_inflow, error, integral, setpoint, sp_r, P, I, D))
|
||||
-- clamp at range -> setpoint clamped (sp_c)
|
||||
local sp_c = math.max(0, math.min(sp_r, self.max_burn_combined))
|
||||
|
||||
_allocate_burn_rate(sp_r, false)
|
||||
end
|
||||
elseif self.mode == PROCESS.BURN_RATE then
|
||||
-- a total aggregate burn rate
|
||||
if state_changed then
|
||||
-- nothing special to do
|
||||
elseif self.waiting_on_ramp and _all_units_ramped() then
|
||||
self.waiting_on_ramp = false
|
||||
self.time_start = now
|
||||
end
|
||||
self.saturated = sp_r ~= sp_c
|
||||
|
||||
if not self.waiting_on_ramp then
|
||||
_allocate_burn_rate(self.burn_target, self.initial_ramp)
|
||||
log.debug(util.sprintf("PROC_RATE[%f] { RATE[%f] ERR[%f] INT[%f] => SP[%f] SP_C[%f] <= P[%f] I[%f] D[%f] }",
|
||||
runtime, avg_inflow, error, integral, setpoint, sp_c, P, I, D))
|
||||
|
||||
_allocate_burn_rate(sp_c, false)
|
||||
end
|
||||
end
|
||||
|
||||
@ -387,6 +399,83 @@ function facility.new(num_reactors, cooling_conf)
|
||||
end
|
||||
end
|
||||
|
||||
-- COMMANDS --
|
||||
|
||||
-- SCRAM all reactor units
|
||||
function public.scram_all()
|
||||
for i = 1, #self.units do
|
||||
local u = self.units[i] ---@type reactor_unit
|
||||
u.scram()
|
||||
end
|
||||
end
|
||||
|
||||
-- stop auto control
|
||||
function public.auto_stop()
|
||||
self.mode = PROCESS.INACTIVE
|
||||
end
|
||||
|
||||
-- set automatic control configuration and start the process
|
||||
---@param config coord_auto_config configuration
|
||||
---@return table response ready state (successfully started) and current configuration (after updating)
|
||||
function public.auto_start(config)
|
||||
local ready = false
|
||||
|
||||
-- load up current limits
|
||||
local limits = {}
|
||||
for i = 1, num_reactors do
|
||||
local u = self.units[i] ---@type reactor_unit
|
||||
limits[i] = u.get_control_inf().lim_br10 * 10
|
||||
end
|
||||
|
||||
-- only allow changes if not running
|
||||
if self.mode == PROCESS.INACTIVE then
|
||||
if (type(config.mode) == "number") and (config.mode > PROCESS.INACTIVE) and (config.mode <= PROCESS.SIMPLE) then
|
||||
self.mode_set = config.mode
|
||||
end
|
||||
|
||||
if (type(config.burn_target) == "number") and config.burn_target >= 0.1 then
|
||||
self.burn_target = config.burn_target
|
||||
log.debug("SET BURN TARGET " .. config.burn_target)
|
||||
end
|
||||
|
||||
if (type(config.charge_target) == "number") and config.charge_target >= 0 then
|
||||
self.charge_target = config.charge_target
|
||||
log.debug("SET CHARGE TARGET " .. config.charge_target)
|
||||
end
|
||||
|
||||
if (type(config.gen_target) == "number") and config.gen_target >= 0 then
|
||||
self.gen_rate_target = config.gen_target
|
||||
log.debug("SET RATE TARGET " .. config.gen_target)
|
||||
end
|
||||
|
||||
if (type(config.limits) == "table") and (#config.limits == num_reactors) then
|
||||
for i = 1, num_reactors do
|
||||
local limit = config.limits[i]
|
||||
|
||||
if (type(limit) == "number") and (limit >= 0.1) then
|
||||
limits[i] = limit
|
||||
self.units[i].set_burn_limit(limit)
|
||||
log.debug("SET UNIT " .. i .. " LIMIT " .. limit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ready = self.mode_set > 0
|
||||
|
||||
if (self.mode_set == PROCESS.CHARGE) and (self.charge_target <= 0) then
|
||||
ready = false
|
||||
elseif (self.mode_set == PROCESS.GEN_RATE) and (self.gen_rate_target <= 0) then
|
||||
ready = false
|
||||
elseif (self.mode_set == PROCESS.BURN_RATE) and (self.burn_target <= 0.1) then
|
||||
ready = false
|
||||
end
|
||||
|
||||
if ready then self.mode = self.mode_set end
|
||||
end
|
||||
|
||||
return { ready, self.mode_set, self.burn_target, self.charge_target, self.gen_rate_target, limits }
|
||||
end
|
||||
|
||||
-- SETTINGS --
|
||||
|
||||
-- set the automatic control group of a unit
|
||||
@ -424,10 +513,27 @@ function facility.new(num_reactors, cooling_conf)
|
||||
return build
|
||||
end
|
||||
|
||||
-- get automatic process control status
|
||||
function public.get_control_status()
|
||||
return {
|
||||
self.mode,
|
||||
self.waiting_on_ramp,
|
||||
self.ascram,
|
||||
self.ascram_reason
|
||||
}
|
||||
end
|
||||
|
||||
-- get RTU statuses
|
||||
function public.get_rtu_statuses()
|
||||
local status = {}
|
||||
|
||||
-- power averages from induction matricies
|
||||
status.power = {
|
||||
self.avg_charge,
|
||||
self.avg_inflow,
|
||||
self.avg_outflow
|
||||
}
|
||||
|
||||
-- status of induction matricies (including tanks)
|
||||
status.induction = {}
|
||||
for i = 1, #self.induction do
|
||||
|
@ -10,6 +10,7 @@ local plc = {}
|
||||
local PROTOCOLS = comms.PROTOCOLS
|
||||
local RPLC_TYPES = comms.RPLC_TYPES
|
||||
local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES
|
||||
local PLC_AUTO_ACK = comms.PLC_AUTO_ACK
|
||||
|
||||
local UNIT_COMMANDS = comms.UNIT_COMMANDS
|
||||
|
||||
@ -19,8 +20,9 @@ local print_ts = util.print_ts
|
||||
local println_ts = util.println_ts
|
||||
|
||||
-- retry time constants in ms
|
||||
local INITIAL_WAIT = 1500
|
||||
local RETRY_PERIOD = 1000
|
||||
local INITIAL_WAIT = 1500
|
||||
local INITIAL_AUTO_WAIT = 1000
|
||||
local RETRY_PERIOD = 1000
|
||||
|
||||
local PLC_S_CMDS = {
|
||||
SCRAM = 1,
|
||||
@ -440,6 +442,21 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
|
||||
cmd = UNIT_COMMANDS.RESET_RPS,
|
||||
ack = ack
|
||||
})
|
||||
elseif pkt.type == RPLC_TYPES.AUTO_BURN_RATE then
|
||||
if pkt.length == 1 then
|
||||
local ack = pkt.data[1]
|
||||
|
||||
self.acks.burn_rate = ack ~= PLC_AUTO_ACK.FAIL
|
||||
|
||||
if ack == PLC_AUTO_ACK.FAIL then
|
||||
elseif ack == PLC_AUTO_ACK.DIRECT_SET_OK then
|
||||
elseif ack == PLC_AUTO_ACK.RAMP_SET_OK then
|
||||
elseif ack == PLC_AUTO_ACK.ZERO_DIS_OK then
|
||||
elseif ack == PLC_AUTO_ACK.ZERO_DIS_WAIT then
|
||||
end
|
||||
else
|
||||
log.debug(log_header .. "RPLC automatic burn rate ack packet length mismatch")
|
||||
end
|
||||
else
|
||||
log.debug(log_header .. "handler received unsupported RPLC packet type " .. pkt.type)
|
||||
end
|
||||
@ -517,6 +534,11 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
|
||||
---@param engage boolean true to engage the lockout
|
||||
function public.auto_lock(engage)
|
||||
self.auto_lock = engage
|
||||
|
||||
-- stop retrying a burn rate command
|
||||
if engage then
|
||||
self.acks.burn_rate = true
|
||||
end
|
||||
end
|
||||
|
||||
-- set the burn rate on behalf of automatic control
|
||||
@ -583,6 +605,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
|
||||
self.acks.rps_reset = false
|
||||
self.retry_times.rps_reset_req = util.time() + INITIAL_WAIT
|
||||
_send(RPLC_TYPES.RPS_RESET, {})
|
||||
else
|
||||
log.warning(log_header .. "unsupported command received in in_queue (this is a bug)")
|
||||
end
|
||||
elseif message.qtype == mqueue.TYPE.DATA then
|
||||
-- instruction with body
|
||||
@ -613,13 +637,20 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
|
||||
end
|
||||
elseif cmd.key == PLC_S_DATA.AUTO_BURN_RATE then
|
||||
-- set automatic burn rate
|
||||
cmd.val = math.floor(cmd.val * 10) / 10 -- round to 10ths place
|
||||
if cmd.val > 0 and cmd.val <= self.sDB.mek_struct.max_burn then
|
||||
self.commanded_burn_rate = cmd.val
|
||||
self.acks.burn_rate = false
|
||||
self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT
|
||||
_send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate })
|
||||
if self.auto_lock then
|
||||
cmd.val = math.floor(cmd.val * 10) / 10 -- round to 10ths place
|
||||
if cmd.val > 0 and cmd.val <= self.sDB.mek_struct.max_burn then
|
||||
self.commanded_burn_rate = cmd.val
|
||||
|
||||
-- this is only for manual control, only retry auto ramps
|
||||
self.acks.burn_rate = not self.ramping_rate
|
||||
self.retry_times.burn_rate_req = util.time() + INITIAL_AUTO_WAIT
|
||||
|
||||
_send(RPLC_TYPES.AUTO_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate })
|
||||
end
|
||||
end
|
||||
else
|
||||
log.warning(log_header .. "unsupported data command received in in_queue (this is a bug)")
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -685,7 +716,12 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
|
||||
|
||||
if not self.acks.burn_rate then
|
||||
if rtimes.burn_rate_req - util.time() <= 0 then
|
||||
_send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate })
|
||||
if self.auto_lock then
|
||||
_send(RPLC_TYPES.AUTO_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate })
|
||||
else
|
||||
_send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate })
|
||||
end
|
||||
|
||||
rtimes.burn_rate_req = util.time() + RETRY_PERIOD
|
||||
end
|
||||
end
|
||||
|
@ -450,6 +450,13 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
|
||||
-- OPERATIONS --
|
||||
|
||||
-- queue a command to SCRAM the reactor
|
||||
function public.scram()
|
||||
if self.plc_s ~= nil 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
|
||||
|
@ -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.9.6"
|
||||
local SUPERVISOR_VERSION = "beta-v0.9.7"
|
||||
|
||||
local print = util.print
|
||||
local println = util.println
|
||||
|
Loading…
Reference in New Issue
Block a user