Merge pull request #546 from MikaylaFischler/pocket-alpha-dev
Start of Pocket Controls
This commit is contained in:
commit
a4452ebbd2
@ -38,7 +38,6 @@ local MGMT_TYPE = comms.MGMT_TYPE
|
|||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@ -1401,7 +1400,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
local textbox
|
local textbox
|
||||||
if height > 1 then
|
if height > 1 then
|
||||||
textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1,alignment=LEFT}
|
textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1}
|
||||||
else
|
else
|
||||||
textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT}
|
textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT}
|
||||||
end
|
end
|
||||||
|
|||||||
@ -387,7 +387,7 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- send the auto process control configuration with a start command
|
-- send the auto process control configuration with a start command
|
||||||
---@param auto_cfg coord_auto_config configuration
|
---@param auto_cfg sys_auto_config configuration
|
||||||
function public.send_auto_start(auto_cfg)
|
function public.send_auto_start(auto_cfg)
|
||||||
_send_sv(PROTOCOL.SCADA_CRDN, CRDN_TYPE.FAC_CMD, {
|
_send_sv(PROTOCOL.SCADA_CRDN, CRDN_TYPE.FAC_CMD, {
|
||||||
FAC_COMMAND.START, auto_cfg.mode, auto_cfg.burn_target, auto_cfg.charge_target, auto_cfg.gen_target, auto_cfg.limits
|
FAC_COMMAND.START, auto_cfg.mode, auto_cfg.burn_target, auto_cfg.charge_target, auto_cfg.gen_target, auto_cfg.limits
|
||||||
@ -576,7 +576,7 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
local ack = packet.data[2] == true
|
local ack = packet.data[2] == true
|
||||||
|
|
||||||
if cmd == FAC_COMMAND.SCRAM_ALL then
|
if cmd == FAC_COMMAND.SCRAM_ALL then
|
||||||
iocontrol.get_db().facility.scram_ack(ack)
|
process.fac_ack(cmd, ack)
|
||||||
elseif cmd == FAC_COMMAND.STOP then
|
elseif cmd == FAC_COMMAND.STOP then
|
||||||
iocontrol.get_db().facility.stop_ack(ack)
|
iocontrol.get_db().facility.stop_ack(ack)
|
||||||
elseif cmd == FAC_COMMAND.START then
|
elseif cmd == FAC_COMMAND.START then
|
||||||
@ -586,11 +586,13 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
log.debug("SCADA_CRDN process start (with configuration) ack echo packet length mismatch")
|
log.debug("SCADA_CRDN process start (with configuration) ack echo packet length mismatch")
|
||||||
end
|
end
|
||||||
elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then
|
elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then
|
||||||
iocontrol.get_db().facility.ack_alarms_ack(ack)
|
process.fac_ack(cmd, ack)
|
||||||
elseif cmd == FAC_COMMAND.SET_WASTE_MODE then
|
elseif cmd == FAC_COMMAND.SET_WASTE_MODE then
|
||||||
process.waste_ack_handle(packet.data[2])
|
process.waste_ack_handle(packet.data[2])
|
||||||
elseif cmd == FAC_COMMAND.SET_PU_FB then
|
elseif cmd == FAC_COMMAND.SET_PU_FB then
|
||||||
process.pu_fb_ack_handle(packet.data[2])
|
process.pu_fb_ack_handle(packet.data[2])
|
||||||
|
elseif cmd == FAC_COMMAND.SET_SPS_LP then
|
||||||
|
process.sps_lp_ack_handle(packet.data[2])
|
||||||
else
|
else
|
||||||
log.debug(util.c("received facility command ack with unknown command ", cmd))
|
log.debug(util.c("received facility command ack with unknown command ", cmd))
|
||||||
end
|
end
|
||||||
@ -625,21 +627,15 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
|
|
||||||
if unit ~= nil then
|
if unit ~= nil then
|
||||||
if cmd == UNIT_COMMAND.SCRAM then
|
if cmd == UNIT_COMMAND.SCRAM then
|
||||||
unit.scram_ack(ack)
|
process.unit_ack(unit_id, cmd, ack)
|
||||||
elseif cmd == UNIT_COMMAND.START then
|
elseif cmd == UNIT_COMMAND.START then
|
||||||
unit.start_ack(ack)
|
process.unit_ack(unit_id, cmd, ack)
|
||||||
elseif cmd == UNIT_COMMAND.RESET_RPS then
|
elseif cmd == UNIT_COMMAND.RESET_RPS then
|
||||||
unit.reset_rps_ack(ack)
|
process.unit_ack(unit_id, cmd, ack)
|
||||||
elseif cmd == UNIT_COMMAND.SET_BURN then
|
|
||||||
unit.set_burn_ack(ack)
|
|
||||||
elseif cmd == UNIT_COMMAND.SET_WASTE then
|
|
||||||
unit.set_waste_ack(ack)
|
|
||||||
elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then
|
elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then
|
||||||
unit.ack_alarms_ack(ack)
|
process.unit_ack(unit_id, cmd, ack)
|
||||||
elseif cmd == UNIT_COMMAND.SET_GROUP then
|
|
||||||
-- UI will be updated to display current group if changed successfully
|
|
||||||
else
|
else
|
||||||
log.debug(util.c("received unit command ack with unknown command ", cmd))
|
log.debug(util.c("received unsupported unit command ack for command ", cmd))
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(util.c("received unit command ack with unknown unit ", unit_id))
|
log.debug(util.c("received unit command ack with unknown unit ", unit_id))
|
||||||
|
|||||||
@ -118,8 +118,6 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
|||||||
save_cfg_ack = __generic_ack,
|
save_cfg_ack = __generic_ack,
|
||||||
start_ack = __generic_ack,
|
start_ack = __generic_ack,
|
||||||
stop_ack = __generic_ack,
|
stop_ack = __generic_ack,
|
||||||
scram_ack = __generic_ack,
|
|
||||||
ack_alarms_ack = __generic_ack,
|
|
||||||
|
|
||||||
alarm_tones = { false, false, false, false, false, false, false, false },
|
alarm_tones = { false, false, false, false, false, false, false, false },
|
||||||
|
|
||||||
@ -184,24 +182,17 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
|||||||
turbine_flow_stable = false,
|
turbine_flow_stable = false,
|
||||||
|
|
||||||
-- auto control group
|
-- auto control group
|
||||||
a_group = 0,
|
a_group = types.AUTO_GROUP.MANUAL,
|
||||||
|
|
||||||
start = function () process.start(i) end,
|
start = function () io.process.start(i) end,
|
||||||
scram = function () process.scram(i) end,
|
scram = function () io.process.scram(i) end,
|
||||||
reset_rps = function () process.reset_rps(i) end,
|
reset_rps = function () io.process.reset_rps(i) end,
|
||||||
ack_alarms = function () process.ack_all_alarms(i) end,
|
ack_alarms = function () io.process.ack_all_alarms(i) end,
|
||||||
set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate
|
set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate
|
||||||
set_waste = function (mode) process.set_unit_waste(i, mode) end, ---@param mode WASTE_MODE waste processing mode
|
set_waste = function (mode) process.set_unit_waste(i, mode) end, ---@param mode WASTE_MODE waste processing mode
|
||||||
|
|
||||||
set_group = function (grp) process.set_group(i, grp) end, ---@param grp integer|0 group ID or 0 for manual
|
set_group = function (grp) process.set_group(i, grp) end, ---@param grp integer|0 group ID or 0 for manual
|
||||||
|
|
||||||
start_ack = __generic_ack,
|
|
||||||
scram_ack = __generic_ack,
|
|
||||||
reset_rps_ack = __generic_ack,
|
|
||||||
ack_alarms_ack = __generic_ack,
|
|
||||||
set_burn_ack = __generic_ack,
|
|
||||||
set_waste_ack = __generic_ack,
|
|
||||||
|
|
||||||
alarm_callbacks = {
|
alarm_callbacks = {
|
||||||
c_breach = { ack = function () ack(1) end, reset = function () reset(1) end },
|
c_breach = { ack = function () ack(1) end, reset = function () reset(1) end },
|
||||||
radiation = { ack = function () ack(2) end, reset = function () reset(2) end },
|
radiation = { ack = function () ack(2) end, reset = function () reset(2) end },
|
||||||
@ -281,6 +272,9 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
|||||||
|
|
||||||
-- pass IO control here since it can't be require'd due to a require loop
|
-- pass IO control here since it can't be require'd due to a require loop
|
||||||
process.init(io, comms)
|
process.init(io, comms)
|
||||||
|
|
||||||
|
-- coordinator's process handle
|
||||||
|
io.process = process.create_handle()
|
||||||
end
|
end
|
||||||
|
|
||||||
--#region Front Panel PSIL
|
--#region Front Panel PSIL
|
||||||
@ -575,11 +569,10 @@ function iocontrol.update_facility_status(status)
|
|||||||
local group_map = ctl_status[14]
|
local group_map = ctl_status[14]
|
||||||
|
|
||||||
if (type(group_map) == "table") and (#group_map == fac.num_units) then
|
if (type(group_map) == "table") and (#group_map == fac.num_units) then
|
||||||
local names = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" }
|
|
||||||
for i = 1, #group_map do
|
for i = 1, #group_map do
|
||||||
io.units[i].a_group = group_map[i]
|
io.units[i].a_group = group_map[i]
|
||||||
io.units[i].unit_ps.publish("auto_group_id", group_map[i])
|
io.units[i].unit_ps.publish("auto_group_id", group_map[i])
|
||||||
io.units[i].unit_ps.publish("auto_group", names[group_map[i] + 1])
|
io.units[i].unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[group_map[i] + 1])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -7,21 +7,23 @@ local log = require("scada-common.log")
|
|||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local FAC_COMMAND = comms.FAC_COMMAND
|
local F_CMD = comms.FAC_COMMAND
|
||||||
local UNIT_COMMAND = comms.UNIT_COMMAND
|
local U_CMD = comms.UNIT_COMMAND
|
||||||
|
|
||||||
local PROCESS = types.PROCESS
|
local PROCESS = types.PROCESS
|
||||||
local PRODUCT = types.WASTE_PRODUCT
|
local PRODUCT = types.WASTE_PRODUCT
|
||||||
|
|
||||||
|
local REQUEST_TIMEOUT_MS = 10000
|
||||||
|
|
||||||
---@class process_controller
|
---@class process_controller
|
||||||
local process = {}
|
local process = {}
|
||||||
|
|
||||||
local self = {
|
local pctl = {
|
||||||
io = nil, ---@type ioctl
|
io = nil, ---@type ioctl
|
||||||
comms = nil, ---@type coord_comms
|
comms = nil, ---@type coord_comms
|
||||||
---@class coord_control_states
|
---@class sys_control_states
|
||||||
control_states = {
|
control_states = {
|
||||||
---@class coord_auto_config
|
---@class sys_auto_config
|
||||||
process = {
|
process = {
|
||||||
mode = PROCESS.INACTIVE,
|
mode = PROCESS.INACTIVE,
|
||||||
burn_target = 0.0,
|
burn_target = 0.0,
|
||||||
@ -34,28 +36,52 @@ local self = {
|
|||||||
},
|
},
|
||||||
waste_modes = {},
|
waste_modes = {},
|
||||||
priority_groups = {}
|
priority_groups = {}
|
||||||
|
},
|
||||||
|
commands = {
|
||||||
|
unit = {}, ---@type process_command_state[][]
|
||||||
|
fac = {} ---@type process_command_state[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
--------------------------
|
---@class process_command_state
|
||||||
-- UNIT COMMAND CONTROL --
|
---@field active boolean if this command is live
|
||||||
--------------------------
|
---@field timeout integer expiration time of this command request
|
||||||
|
---@field requestors table list of callbacks from the requestors
|
||||||
|
|
||||||
|
-- write auto process control to config file
|
||||||
|
local function _write_auto_config()
|
||||||
|
-- save config
|
||||||
|
settings.set("ControlStates", pctl.control_states)
|
||||||
|
local saved = settings.save("/coordinator.settings")
|
||||||
|
if not saved then
|
||||||
|
log.warning("process._write_auto_config(): failed to save coordinator settings file")
|
||||||
|
end
|
||||||
|
|
||||||
|
return saved
|
||||||
|
end
|
||||||
|
|
||||||
-- initialize the process controller
|
-- initialize the process controller
|
||||||
---@param iocontrol ioctl iocontrl system
|
---@param iocontrol ioctl iocontrl system
|
||||||
---@param coord_comms coord_comms coordinator communications
|
---@param coord_comms coord_comms coordinator communications
|
||||||
function process.init(iocontrol, coord_comms)
|
function process.init(iocontrol, coord_comms)
|
||||||
self.io = iocontrol
|
pctl.io = iocontrol
|
||||||
self.comms = coord_comms
|
pctl.comms = coord_comms
|
||||||
|
|
||||||
local ctl_proc = self.control_states.process
|
-- create command handling objects
|
||||||
|
for _, v in pairs(F_CMD) do pctl.commands.fac[v] = { active = false, timeout = 0, requestors = {} } end
|
||||||
|
for i = 1, pctl.io.facility.num_units do
|
||||||
|
pctl.commands.unit[i] = {}
|
||||||
|
for _, v in pairs(U_CMD) do pctl.commands.unit[i][v] = { active = false, timeout = 0, requestors = {} } end
|
||||||
|
end
|
||||||
|
|
||||||
for i = 1, self.io.facility.num_units do
|
local ctl_proc = pctl.control_states.process
|
||||||
|
|
||||||
|
for i = 1, pctl.io.facility.num_units do
|
||||||
ctl_proc.limits[i] = 0.1
|
ctl_proc.limits[i] = 0.1
|
||||||
end
|
end
|
||||||
|
|
||||||
local ctrl_states = settings.get("ControlStates", {})
|
local ctrl_states = settings.get("ControlStates", {})
|
||||||
local config = ctrl_states.process ---@type coord_auto_config
|
local config = ctrl_states.process ---@type sys_auto_config
|
||||||
|
|
||||||
-- facility auto control configuration
|
-- facility auto control configuration
|
||||||
if type(config) == "table" then
|
if type(config) == "table" then
|
||||||
@ -68,33 +94,33 @@ function process.init(iocontrol, coord_comms)
|
|||||||
ctl_proc.pu_fallback = config.pu_fallback
|
ctl_proc.pu_fallback = config.pu_fallback
|
||||||
ctl_proc.sps_low_power = config.sps_low_power
|
ctl_proc.sps_low_power = config.sps_low_power
|
||||||
|
|
||||||
self.io.facility.ps.publish("process_mode", ctl_proc.mode)
|
pctl.io.facility.ps.publish("process_mode", ctl_proc.mode)
|
||||||
self.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target)
|
pctl.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target)
|
||||||
self.io.facility.ps.publish("process_charge_target", self.io.energy_convert_from_fe(ctl_proc.charge_target))
|
pctl.io.facility.ps.publish("process_charge_target", pctl.io.energy_convert_from_fe(ctl_proc.charge_target))
|
||||||
self.io.facility.ps.publish("process_gen_target", self.io.energy_convert_from_fe(ctl_proc.gen_target))
|
pctl.io.facility.ps.publish("process_gen_target", pctl.io.energy_convert_from_fe(ctl_proc.gen_target))
|
||||||
self.io.facility.ps.publish("process_waste_product", ctl_proc.waste_product)
|
pctl.io.facility.ps.publish("process_waste_product", ctl_proc.waste_product)
|
||||||
self.io.facility.ps.publish("process_pu_fallback", ctl_proc.pu_fallback)
|
pctl.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)
|
pctl.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, pctl.io.facility.num_units) do
|
||||||
local unit = self.io.units[id] ---@type ioctl_unit
|
local unit = pctl.io.units[id] ---@type ioctl_unit
|
||||||
unit.unit_ps.publish("burn_limit", ctl_proc.limits[id])
|
unit.unit_ps.publish("burn_limit", ctl_proc.limits[id])
|
||||||
end
|
end
|
||||||
|
|
||||||
log.info("PROCESS: loaded auto control settings")
|
log.info("PROCESS: loaded auto control settings")
|
||||||
|
|
||||||
-- 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)
|
pctl.comms.send_fac_command(F_CMD.SET_WASTE_MODE, ctl_proc.waste_product)
|
||||||
self.comms.send_fac_command(FAC_COMMAND.SET_PU_FB, ctl_proc.pu_fallback)
|
pctl.comms.send_fac_command(F_CMD.SET_PU_FB, ctl_proc.pu_fallback)
|
||||||
self.comms.send_fac_command(FAC_COMMAND.SET_SPS_LP, ctl_proc.sps_low_power)
|
pctl.comms.send_fac_command(F_CMD.SET_SPS_LP, ctl_proc.sps_low_power)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- unit waste states
|
-- unit waste states
|
||||||
local waste_modes = ctrl_states.waste_modes ---@type table|nil
|
local waste_modes = ctrl_states.waste_modes ---@type table|nil
|
||||||
if type(waste_modes) == "table" then
|
if type(waste_modes) == "table" then
|
||||||
for id, mode in pairs(waste_modes) do
|
for id, mode in pairs(waste_modes) do
|
||||||
self.control_states.waste_modes[id] = mode
|
pctl.control_states.waste_modes[id] = mode
|
||||||
self.comms.send_unit_command(UNIT_COMMAND.SET_WASTE, id, mode)
|
pctl.comms.send_unit_command(U_CMD.SET_WASTE, id, mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
log.info("PROCESS: loaded unit waste mode settings")
|
log.info("PROCESS: loaded unit waste mode settings")
|
||||||
@ -104,54 +130,208 @@ function process.init(iocontrol, coord_comms)
|
|||||||
local prio_groups = ctrl_states.priority_groups ---@type table|nil
|
local prio_groups = ctrl_states.priority_groups ---@type table|nil
|
||||||
if type(prio_groups) == "table" then
|
if type(prio_groups) == "table" then
|
||||||
for id, group in pairs(prio_groups) do
|
for id, group in pairs(prio_groups) do
|
||||||
self.control_states.priority_groups[id] = group
|
pctl.control_states.priority_groups[id] = group
|
||||||
self.comms.send_unit_command(UNIT_COMMAND.SET_GROUP, id, group)
|
pctl.comms.send_unit_command(U_CMD.SET_GROUP, id, group)
|
||||||
end
|
end
|
||||||
|
|
||||||
log.info("PROCESS: loaded priority groups settings")
|
log.info("PROCESS: loaded priority groups settings")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- create a handle to process control for usage of commands that get acknowledgements
|
||||||
|
function process.create_handle()
|
||||||
|
---@class process_handle
|
||||||
|
local handle = {}
|
||||||
|
|
||||||
|
-- add this handle to the requestors and activate the command if inactive
|
||||||
|
---@param cmd process_command_state
|
||||||
|
---@param ack function
|
||||||
|
local function request(cmd, ack)
|
||||||
|
local new = not cmd.active
|
||||||
|
|
||||||
|
if new then
|
||||||
|
cmd.active = true
|
||||||
|
cmd.timeout = util.time_ms() + REQUEST_TIMEOUT_MS
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(cmd.requestors, ack)
|
||||||
|
|
||||||
|
return new
|
||||||
|
end
|
||||||
|
|
||||||
|
local function u_request(u_id, cmd_id, ack) return request(pctl.commands.unit[u_id][cmd_id], ack) end
|
||||||
|
local function f_request(cmd_id, ack) return request(pctl.commands.fac[cmd_id], ack) end
|
||||||
|
|
||||||
|
--#region Facility Commands
|
||||||
|
|
||||||
-- facility SCRAM command
|
-- facility SCRAM command
|
||||||
function process.fac_scram()
|
function handle.fac_scram()
|
||||||
self.comms.send_fac_command(FAC_COMMAND.SCRAM_ALL)
|
if f_request(F_CMD.SCRAM_ALL, handle.fac_ack.on_scram) then
|
||||||
|
pctl.comms.send_fac_command(F_CMD.SCRAM_ALL)
|
||||||
log.debug("PROCESS: FAC SCRAM ALL")
|
log.debug("PROCESS: FAC SCRAM ALL")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- facility alarm acknowledge command
|
-- facility alarm acknowledge command
|
||||||
function process.fac_ack_alarms()
|
function handle.fac_ack_alarms()
|
||||||
self.comms.send_fac_command(FAC_COMMAND.ACK_ALL_ALARMS)
|
if f_request(F_CMD.ACK_ALL_ALARMS, handle.fac_ack.on_ack_alarms) then
|
||||||
|
pctl.comms.send_fac_command(F_CMD.ACK_ALL_ALARMS)
|
||||||
log.debug("PROCESS: FAC ACK ALL ALARMS")
|
log.debug("PROCESS: FAC ACK ALL ALARMS")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- start reactor
|
handle.fac_ack = {}
|
||||||
|
|
||||||
|
-- luacheck: no unused args
|
||||||
|
|
||||||
|
-- facility SCRAM ack, override to implement
|
||||||
|
---@param success boolean
|
||||||
|
---@diagnostic disable-next-line: unused-local
|
||||||
|
function handle.fac_ack.on_scram(success) end
|
||||||
|
|
||||||
|
-- facility acknowledge all alarms ack, override to implement
|
||||||
|
---@param success boolean
|
||||||
|
---@diagnostic disable-next-line: unused-local
|
||||||
|
function handle.fac_ack.on_ack_alarms(success) end
|
||||||
|
|
||||||
|
-- luacheck: unused args
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Unit Commands
|
||||||
|
|
||||||
|
-- start a reactor
|
||||||
---@param id integer unit ID
|
---@param id integer unit ID
|
||||||
function process.start(id)
|
function handle.start(id)
|
||||||
self.io.units[id].control_state = true
|
if u_request(id, U_CMD.START, handle.unit_ack[id].on_start) then
|
||||||
self.comms.send_unit_command(UNIT_COMMAND.START, id)
|
pctl.io.units[id].control_state = true
|
||||||
|
pctl.comms.send_unit_command(U_CMD.START, id)
|
||||||
log.debug(util.c("PROCESS: UNIT[", id, "] START"))
|
log.debug(util.c("PROCESS: UNIT[", id, "] START"))
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- SCRAM reactor
|
-- SCRAM reactor
|
||||||
---@param id integer unit ID
|
---@param id integer unit ID
|
||||||
function process.scram(id)
|
function handle.scram(id)
|
||||||
self.io.units[id].control_state = false
|
if u_request(id, U_CMD.SCRAM, handle.unit_ack[id].on_scram) then
|
||||||
self.comms.send_unit_command(UNIT_COMMAND.SCRAM, id)
|
pctl.io.units[id].control_state = false
|
||||||
|
pctl.comms.send_unit_command(U_CMD.SCRAM, id)
|
||||||
log.debug(util.c("PROCESS: UNIT[", id, "] SCRAM"))
|
log.debug(util.c("PROCESS: UNIT[", id, "] SCRAM"))
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- reset reactor protection system
|
-- reset reactor protection system
|
||||||
---@param id integer unit ID
|
---@param id integer unit ID
|
||||||
function process.reset_rps(id)
|
function handle.reset_rps(id)
|
||||||
self.comms.send_unit_command(UNIT_COMMAND.RESET_RPS, id)
|
if u_request(id, U_CMD.RESET_RPS, handle.unit_ack[id].on_rps_reset) then
|
||||||
|
pctl.comms.send_unit_command(U_CMD.RESET_RPS, id)
|
||||||
log.debug(util.c("PROCESS: UNIT[", id, "] RESET RPS"))
|
log.debug(util.c("PROCESS: UNIT[", id, "] RESET RPS"))
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- acknowledge all alarms
|
||||||
|
---@param id integer unit ID
|
||||||
|
function handle.ack_all_alarms(id)
|
||||||
|
if u_request(id, U_CMD.ACK_ALL_ALARMS, handle.unit_ack[id].on_ack_alarms) then
|
||||||
|
pctl.comms.send_unit_command(U_CMD.ACK_ALL_ALARMS, id)
|
||||||
|
log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALL ALARMS"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- unit command acknowledgement callbacks, indexed by unit ID
|
||||||
|
---@type process_unit_ack[]
|
||||||
|
handle.unit_ack = {}
|
||||||
|
|
||||||
|
for u = 1, pctl.io.facility.num_units do
|
||||||
|
handle.unit_ack[u] = {}
|
||||||
|
|
||||||
|
---@class process_unit_ack
|
||||||
|
local u_ack = handle.unit_ack[u]
|
||||||
|
|
||||||
|
-- luacheck: no unused args
|
||||||
|
|
||||||
|
-- unit start ack, override to implement
|
||||||
|
---@param success boolean
|
||||||
|
---@diagnostic disable-next-line: unused-local
|
||||||
|
function u_ack.on_start(success) end
|
||||||
|
|
||||||
|
-- unit SCRAM ack, override to implement
|
||||||
|
---@param success boolean
|
||||||
|
---@diagnostic disable-next-line: unused-local
|
||||||
|
function u_ack.on_scram(success) end
|
||||||
|
|
||||||
|
-- unit RPS reset ack, override to implement
|
||||||
|
---@param success boolean
|
||||||
|
---@diagnostic disable-next-line: unused-local
|
||||||
|
function u_ack.on_rps_reset(success) end
|
||||||
|
|
||||||
|
-- unit acknowledge all alarms ack, override to implement
|
||||||
|
---@param success boolean
|
||||||
|
---@diagnostic disable-next-line: unused-local
|
||||||
|
function u_ack.on_ack_alarms(success) end
|
||||||
|
|
||||||
|
-- luacheck: unused args
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
return handle
|
||||||
|
end
|
||||||
|
|
||||||
|
-- clear outstanding process commands that have timed out
|
||||||
|
function process.clear_timed_out()
|
||||||
|
local now = util.time_ms()
|
||||||
|
local objs = { pctl.commands.fac, table.unpack(pctl.commands.unit) }
|
||||||
|
|
||||||
|
for _, obj in pairs(objs) do
|
||||||
|
-- cancel expired requests
|
||||||
|
for _, cmd in pairs(obj) do
|
||||||
|
if cmd.active and now > cmd.timeout then
|
||||||
|
cmd.active = false
|
||||||
|
cmd.requestors = {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle a command acknowledgement
|
||||||
|
---@param cmd_state process_command_state
|
||||||
|
---@param success boolean if the command was successful
|
||||||
|
local function cmd_ack(cmd_state, success)
|
||||||
|
if cmd_state.active then
|
||||||
|
cmd_state.active = false
|
||||||
|
|
||||||
|
-- call all acknowledge callback functions
|
||||||
|
for i = 1, #cmd_state.requestors do
|
||||||
|
cmd_state.requestors[i](success)
|
||||||
|
end
|
||||||
|
|
||||||
|
cmd_state.requestors = {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle a facility command acknowledgement
|
||||||
|
---@param command FAC_COMMAND command
|
||||||
|
---@param success boolean if the command was successful
|
||||||
|
function process.fac_ack(command, success)
|
||||||
|
cmd_ack(pctl.commands.fac[command], success)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle a unit command acknowledgement
|
||||||
|
---@param unit integer unit ID
|
||||||
|
---@param command UNIT_COMMAND command
|
||||||
|
---@param success boolean if the command was successful
|
||||||
|
function process.unit_ack(unit, command, success)
|
||||||
|
cmd_ack(pctl.commands.unit[unit][command], success)
|
||||||
|
end
|
||||||
|
|
||||||
|
--#region One-Way Commands (no acknowledgements)
|
||||||
|
|
||||||
-- set burn rate
|
-- set burn rate
|
||||||
---@param id integer unit ID
|
---@param id integer unit ID
|
||||||
---@param rate number burn rate
|
---@param rate number burn rate
|
||||||
function process.set_rate(id, rate)
|
function process.set_rate(id, rate)
|
||||||
self.comms.send_unit_command(UNIT_COMMAND.SET_BURN, id, rate)
|
pctl.comms.send_unit_command(U_CMD.SET_BURN, id, rate)
|
||||||
log.debug(util.c("PROCESS: UNIT[", id, "] SET BURN ", rate))
|
log.debug(util.c("PROCESS: UNIT[", id, "] SET BURN ", rate))
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -160,31 +340,24 @@ end
|
|||||||
---@param mode integer waste mode
|
---@param mode integer waste mode
|
||||||
function process.set_unit_waste(id, mode)
|
function process.set_unit_waste(id, mode)
|
||||||
-- publish so that if it fails then it gets reset
|
-- publish so that if it fails then it gets reset
|
||||||
self.io.units[id].unit_ps.publish("U_WasteMode", mode)
|
pctl.io.units[id].unit_ps.publish("U_WasteMode", mode)
|
||||||
|
|
||||||
self.comms.send_unit_command(UNIT_COMMAND.SET_WASTE, id, mode)
|
pctl.comms.send_unit_command(U_CMD.SET_WASTE, id, mode)
|
||||||
log.debug(util.c("PROCESS: UNIT[", id, "] SET WASTE ", mode))
|
log.debug(util.c("PROCESS: UNIT[", id, "] SET WASTE ", mode))
|
||||||
|
|
||||||
self.control_states.waste_modes[id] = mode
|
pctl.control_states.waste_modes[id] = mode
|
||||||
settings.set("ControlStates", self.control_states)
|
settings.set("ControlStates", pctl.control_states)
|
||||||
|
|
||||||
if not settings.save("/coordinator.settings") then
|
if not settings.save("/coordinator.settings") then
|
||||||
log.error("process.set_unit_waste(): failed to save coordinator settings file")
|
log.error("process.set_unit_waste(): failed to save coordinator settings file")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- acknowledge all alarms
|
|
||||||
---@param id integer unit ID
|
|
||||||
function process.ack_all_alarms(id)
|
|
||||||
self.comms.send_unit_command(UNIT_COMMAND.ACK_ALL_ALARMS, id)
|
|
||||||
log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALL ALARMS"))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- acknowledge an alarm
|
-- acknowledge an alarm
|
||||||
---@param id integer unit ID
|
---@param id integer unit ID
|
||||||
---@param alarm integer alarm ID
|
---@param alarm integer alarm ID
|
||||||
function process.ack_alarm(id, alarm)
|
function process.ack_alarm(id, alarm)
|
||||||
self.comms.send_unit_command(UNIT_COMMAND.ACK_ALARM, id, alarm)
|
pctl.comms.send_unit_command(U_CMD.ACK_ALARM, id, alarm)
|
||||||
log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALARM ", alarm))
|
log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALARM ", alarm))
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -192,7 +365,7 @@ end
|
|||||||
---@param id integer unit ID
|
---@param id integer unit ID
|
||||||
---@param alarm integer alarm ID
|
---@param alarm integer alarm ID
|
||||||
function process.reset_alarm(id, alarm)
|
function process.reset_alarm(id, alarm)
|
||||||
self.comms.send_unit_command(UNIT_COMMAND.RESET_ALARM, id, alarm)
|
pctl.comms.send_unit_command(U_CMD.RESET_ALARM, id, alarm)
|
||||||
log.debug(util.c("PROCESS: UNIT[", id, "] RESET ALARM ", alarm))
|
log.debug(util.c("PROCESS: UNIT[", id, "] RESET ALARM ", alarm))
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -200,78 +373,68 @@ end
|
|||||||
---@param unit_id integer unit ID
|
---@param unit_id integer unit ID
|
||||||
---@param group_id integer|0 group ID or 0 for independent
|
---@param group_id integer|0 group ID or 0 for independent
|
||||||
function process.set_group(unit_id, group_id)
|
function process.set_group(unit_id, group_id)
|
||||||
self.comms.send_unit_command(UNIT_COMMAND.SET_GROUP, unit_id, group_id)
|
pctl.comms.send_unit_command(U_CMD.SET_GROUP, unit_id, group_id)
|
||||||
log.debug(util.c("PROCESS: UNIT[", unit_id, "] SET GROUP ", group_id))
|
log.debug(util.c("PROCESS: UNIT[", unit_id, "] SET GROUP ", group_id))
|
||||||
|
|
||||||
self.control_states.priority_groups[unit_id] = group_id
|
pctl.control_states.priority_groups[unit_id] = group_id
|
||||||
settings.set("ControlStates", self.control_states)
|
settings.set("ControlStates", pctl.control_states)
|
||||||
|
|
||||||
if not settings.save("/coordinator.settings") then
|
if not settings.save("/coordinator.settings") then
|
||||||
log.error("process.set_group(): failed to save coordinator settings file")
|
log.error("process.set_group(): failed to save coordinator settings file")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
--------------------------
|
--------------------------
|
||||||
-- AUTO PROCESS CONTROL --
|
-- AUTO PROCESS CONTROL --
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
-- write auto process control to config file
|
-- start automatic process control
|
||||||
local function _write_auto_config()
|
function process.start_auto()
|
||||||
-- save config
|
pctl.comms.send_auto_start(pctl.control_states.process)
|
||||||
settings.set("ControlStates", self.control_states)
|
log.debug("PROCESS: START AUTO CTL")
|
||||||
local saved = settings.save("/coordinator.settings")
|
|
||||||
if not saved then
|
|
||||||
log.warning("process._write_auto_config(): failed to save coordinator settings file")
|
|
||||||
end
|
|
||||||
|
|
||||||
return saved
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- stop automatic process control
|
-- stop automatic process control
|
||||||
function process.stop_auto()
|
function process.stop_auto()
|
||||||
self.comms.send_fac_command(FAC_COMMAND.STOP)
|
pctl.comms.send_fac_command(F_CMD.STOP)
|
||||||
log.debug("PROCESS: STOP AUTO CTL")
|
log.debug("PROCESS: STOP AUTO CTL")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- start automatic process control
|
|
||||||
function process.start_auto()
|
|
||||||
self.comms.send_auto_start(self.control_states.process)
|
|
||||||
log.debug("PROCESS: START AUTO CTL")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- set automatic process control waste mode
|
-- set automatic process control waste mode
|
||||||
---@param product WASTE_PRODUCT waste product for auto control
|
---@param product WASTE_PRODUCT waste product for auto control
|
||||||
function process.set_process_waste(product)
|
function process.set_process_waste(product)
|
||||||
self.comms.send_fac_command(FAC_COMMAND.SET_WASTE_MODE, product)
|
pctl.comms.send_fac_command(F_CMD.SET_WASTE_MODE, product)
|
||||||
|
|
||||||
log.debug(util.c("PROCESS: SET WASTE ", product))
|
log.debug(util.c("PROCESS: SET WASTE ", product))
|
||||||
|
|
||||||
-- update config table and save
|
-- update config table and save
|
||||||
self.control_states.process.waste_product = product
|
pctl.control_states.process.waste_product = product
|
||||||
_write_auto_config()
|
_write_auto_config()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- set automatic process control plutonium fallback
|
-- set automatic process control plutonium fallback
|
||||||
---@param enabled boolean whether to enable plutonium fallback
|
---@param enabled boolean whether to enable plutonium fallback
|
||||||
function process.set_pu_fallback(enabled)
|
function process.set_pu_fallback(enabled)
|
||||||
self.comms.send_fac_command(FAC_COMMAND.SET_PU_FB, enabled)
|
pctl.comms.send_fac_command(F_CMD.SET_PU_FB, enabled)
|
||||||
|
|
||||||
log.debug(util.c("PROCESS: SET PU FALLBACK ", enabled))
|
log.debug(util.c("PROCESS: SET PU FALLBACK ", enabled))
|
||||||
|
|
||||||
-- update config table and save
|
-- update config table and save
|
||||||
self.control_states.process.pu_fallback = enabled
|
pctl.control_states.process.pu_fallback = enabled
|
||||||
_write_auto_config()
|
_write_auto_config()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- set automatic process control SPS usage at low power
|
-- set automatic process control SPS usage at low power
|
||||||
---@param enabled boolean whether to enable SPS usage at low power
|
---@param enabled boolean whether to enable SPS usage at low power
|
||||||
function process.set_sps_low_power(enabled)
|
function process.set_sps_low_power(enabled)
|
||||||
self.comms.send_fac_command(FAC_COMMAND.SET_SPS_LP, enabled)
|
pctl.comms.send_fac_command(F_CMD.SET_SPS_LP, enabled)
|
||||||
|
|
||||||
log.debug(util.c("PROCESS: SET SPS LOW POWER ", enabled))
|
log.debug(util.c("PROCESS: SET SPS LOW POWER ", enabled))
|
||||||
|
|
||||||
-- update config table and save
|
-- update config table and save
|
||||||
self.control_states.process.sps_low_power = enabled
|
pctl.control_states.process.sps_low_power = enabled
|
||||||
_write_auto_config()
|
_write_auto_config()
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -285,7 +448,7 @@ function process.save(mode, burn_target, charge_target, gen_target, limits)
|
|||||||
log.debug("PROCESS: SAVE")
|
log.debug("PROCESS: SAVE")
|
||||||
|
|
||||||
-- update config table
|
-- update config table
|
||||||
local ctl_proc = self.control_states.process
|
local ctl_proc = pctl.control_states.process
|
||||||
ctl_proc.mode = mode
|
ctl_proc.mode = mode
|
||||||
ctl_proc.burn_target = burn_target
|
ctl_proc.burn_target = burn_target
|
||||||
ctl_proc.charge_target = charge_target
|
ctl_proc.charge_target = charge_target
|
||||||
@ -293,7 +456,7 @@ function process.save(mode, burn_target, charge_target, gen_target, limits)
|
|||||||
ctl_proc.limits = limits
|
ctl_proc.limits = limits
|
||||||
|
|
||||||
-- save config
|
-- save config
|
||||||
self.io.facility.save_cfg_ack(_write_auto_config())
|
pctl.io.facility.save_cfg_ack(_write_auto_config())
|
||||||
end
|
end
|
||||||
|
|
||||||
-- handle a start command acknowledgement
|
-- handle a start command acknowledgement
|
||||||
@ -301,39 +464,46 @@ end
|
|||||||
function process.start_ack_handle(response)
|
function process.start_ack_handle(response)
|
||||||
local ack = response[1]
|
local ack = response[1]
|
||||||
|
|
||||||
local ctl_proc = self.control_states.process
|
local ctl_proc = pctl.control_states.process
|
||||||
ctl_proc.mode = response[2]
|
ctl_proc.mode = response[2]
|
||||||
ctl_proc.burn_target = response[3]
|
ctl_proc.burn_target = response[3]
|
||||||
ctl_proc.charge_target = response[4]
|
ctl_proc.charge_target = response[4]
|
||||||
ctl_proc.gen_target = response[5]
|
ctl_proc.gen_target = response[5]
|
||||||
|
|
||||||
for i = 1, math.min(#response[6], self.io.facility.num_units) do
|
for i = 1, math.min(#response[6], pctl.io.facility.num_units) do
|
||||||
ctl_proc.limits[i] = response[6][i]
|
ctl_proc.limits[i] = response[6][i]
|
||||||
|
|
||||||
local unit = self.io.units[i] ---@type ioctl_unit
|
local unit = pctl.io.units[i] ---@type ioctl_unit
|
||||||
unit.unit_ps.publish("burn_limit", ctl_proc.limits[i])
|
unit.unit_ps.publish("burn_limit", ctl_proc.limits[i])
|
||||||
end
|
end
|
||||||
|
|
||||||
self.io.facility.ps.publish("process_mode", ctl_proc.mode)
|
pctl.io.facility.ps.publish("process_mode", ctl_proc.mode)
|
||||||
self.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target)
|
pctl.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target)
|
||||||
self.io.facility.ps.publish("process_charge_target", self.io.energy_convert_from_fe(ctl_proc.charge_target))
|
pctl.io.facility.ps.publish("process_charge_target", pctl.io.energy_convert_from_fe(ctl_proc.charge_target))
|
||||||
self.io.facility.ps.publish("process_gen_target", self.io.energy_convert_from_fe(ctl_proc.gen_target))
|
pctl.io.facility.ps.publish("process_gen_target", pctl.io.energy_convert_from_fe(ctl_proc.gen_target))
|
||||||
|
|
||||||
self.io.facility.start_ack(ack)
|
pctl.io.facility.start_ack(ack)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- record waste product state after attempting to change it
|
-- record waste product settting after attempting to change it
|
||||||
---@param response WASTE_PRODUCT supervisor waste product state
|
---@param response WASTE_PRODUCT supervisor waste product settting
|
||||||
function process.waste_ack_handle(response)
|
function process.waste_ack_handle(response)
|
||||||
self.control_states.process.waste_product = response
|
pctl.control_states.process.waste_product = response
|
||||||
self.io.facility.ps.publish("process_waste_product", response)
|
pctl.io.facility.ps.publish("process_waste_product", response)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- record plutonium fallback state after attempting to change it
|
-- record plutonium fallback settting after attempting to change it
|
||||||
---@param response boolean supervisor plutonium fallback state
|
---@param response boolean supervisor plutonium fallback settting
|
||||||
function process.pu_fb_ack_handle(response)
|
function process.pu_fb_ack_handle(response)
|
||||||
self.control_states.process.pu_fallback = response
|
pctl.control_states.process.pu_fallback = response
|
||||||
self.io.facility.ps.publish("process_pu_fallback", response)
|
pctl.io.facility.ps.publish("process_pu_fallback", response)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- record SPS low power settting after attempting to change it
|
||||||
|
---@param response boolean supervisor SPS low power settting
|
||||||
|
function process.sps_lp_ack_handle(response)
|
||||||
|
pctl.control_states.process.sps_low_power = response
|
||||||
|
pctl.io.facility.ps.publish("process_sps_low_power", response)
|
||||||
end
|
end
|
||||||
|
|
||||||
return process
|
return process
|
||||||
|
|||||||
@ -4,12 +4,15 @@ local mqueue = require("scada-common.mqueue")
|
|||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local iocontrol = require("coordinator.iocontrol")
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
|
local process = require("coordinator.process")
|
||||||
|
|
||||||
local pocket = {}
|
local pocket = {}
|
||||||
|
|
||||||
local PROTOCOL = comms.PROTOCOL
|
local PROTOCOL = comms.PROTOCOL
|
||||||
local CRDN_TYPE = comms.CRDN_TYPE
|
local CRDN_TYPE = comms.CRDN_TYPE
|
||||||
local MGMT_TYPE = comms.MGMT_TYPE
|
local MGMT_TYPE = comms.MGMT_TYPE
|
||||||
|
local FAC_COMMAND = comms.FAC_COMMAND
|
||||||
|
local UNIT_COMMAND = comms.UNIT_COMMAND
|
||||||
|
|
||||||
-- retry time constants in ms
|
-- retry time constants in ms
|
||||||
-- local INITIAL_WAIT = 1500
|
-- local INITIAL_WAIT = 1500
|
||||||
@ -37,7 +40,7 @@ local PERIODICS = {
|
|||||||
---@param out_queue mqueue out message queue
|
---@param out_queue mqueue out message queue
|
||||||
---@param timeout number communications timeout
|
---@param timeout number communications timeout
|
||||||
function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
||||||
local log_header = "pkt_session(" .. id .. "): "
|
local log_tag = "pkt_session(" .. id .. "): "
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
-- connection properties
|
-- connection properties
|
||||||
@ -46,6 +49,8 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
|||||||
connected = true,
|
connected = true,
|
||||||
conn_watchdog = util.new_watchdog(timeout),
|
conn_watchdog = util.new_watchdog(timeout),
|
||||||
last_rtt = 0,
|
last_rtt = 0,
|
||||||
|
-- process accessor handle
|
||||||
|
proc_handle = process.create_handle(),
|
||||||
-- periodic messages
|
-- periodic messages
|
||||||
periodics = {
|
periodics = {
|
||||||
last_update = 0,
|
last_update = 0,
|
||||||
@ -101,12 +106,24 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
|||||||
self.seq_num = self.seq_num + 1
|
self.seq_num = self.seq_num + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- link callback transmissions
|
||||||
|
|
||||||
|
self.proc_handle.fac_ack.on_scram = function (success) _send(CRDN_TYPE.FAC_CMD, { FAC_COMMAND.SCRAM_ALL, success }) end
|
||||||
|
self.proc_handle.fac_ack.on_ack_alarms = function (success) _send(CRDN_TYPE.FAC_CMD, { FAC_COMMAND.ACK_ALL_ALARMS, success }) end
|
||||||
|
|
||||||
|
for u = 1, iocontrol.get_db().facility.num_units do
|
||||||
|
self.proc_handle.unit_ack[u].on_start = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.START, u, success }) end
|
||||||
|
self.proc_handle.unit_ack[u].on_scram = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.SCRAM, u, success }) end
|
||||||
|
self.proc_handle.unit_ack[u].on_rps_reset = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.RESET_RPS, u, success }) end
|
||||||
|
self.proc_handle.unit_ack[u].on_ack_alarms = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.ACK_ALL_ALARMS, u, success }) end
|
||||||
|
end
|
||||||
|
|
||||||
-- handle a packet
|
-- handle a packet
|
||||||
---@param pkt mgmt_frame|crdn_frame
|
---@param pkt mgmt_frame|crdn_frame
|
||||||
local function _handle_packet(pkt)
|
local function _handle_packet(pkt)
|
||||||
-- check sequence number
|
-- check sequence number
|
||||||
if self.r_seq_num ~= pkt.scada_frame.seq_num() then
|
if self.r_seq_num ~= pkt.scada_frame.seq_num() then
|
||||||
log.warning(log_header .. "sequence out-of-order: next = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num())
|
log.warning(log_tag .. "sequence out-of-order: next = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num())
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
self.r_seq_num = pkt.scada_frame.seq_num() + 1
|
self.r_seq_num = pkt.scada_frame.seq_num() + 1
|
||||||
@ -122,7 +139,68 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
|||||||
local db = iocontrol.get_db()
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
-- handle packet by type
|
-- handle packet by type
|
||||||
if pkt.type == CRDN_TYPE.API_GET_FAC then
|
if pkt.type == CRDN_TYPE.FAC_CMD then
|
||||||
|
if pkt.length >= 1 then
|
||||||
|
local cmd = pkt.data[1]
|
||||||
|
|
||||||
|
if cmd == FAC_COMMAND.SCRAM_ALL then
|
||||||
|
log.info(log_tag .. "FAC SCRAM ALL")
|
||||||
|
self.proc_handle.fac_scram()
|
||||||
|
elseif cmd == FAC_COMMAND.STOP then
|
||||||
|
elseif cmd == FAC_COMMAND.START then
|
||||||
|
elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then
|
||||||
|
log.info(log_tag .. "FAC ACK ALL ALARMS")
|
||||||
|
self.proc_handle.fac_ack_alarms()
|
||||||
|
elseif cmd == FAC_COMMAND.SET_WASTE_MODE then
|
||||||
|
elseif cmd == FAC_COMMAND.SET_PU_FB then
|
||||||
|
elseif cmd == FAC_COMMAND.SET_SPS_LP then
|
||||||
|
else
|
||||||
|
log.debug(log_tag .. "CRDN facility command unknown")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug(log_tag .. "CRDN facility command packet length mismatch")
|
||||||
|
end
|
||||||
|
elseif pkt.type == CRDN_TYPE.UNIT_CMD then
|
||||||
|
if pkt.length >= 2 then
|
||||||
|
-- get command and unit id
|
||||||
|
local cmd = pkt.data[1]
|
||||||
|
local uid = pkt.data[2]
|
||||||
|
|
||||||
|
-- continue if valid unit id
|
||||||
|
if util.is_int(uid) and uid > 0 and uid <= #db.units then
|
||||||
|
if cmd == UNIT_COMMAND.SCRAM then
|
||||||
|
log.info(util.c(log_tag, "UNIT[", uid, "] SCRAM"))
|
||||||
|
self.proc_handle.scram(uid)
|
||||||
|
elseif cmd == UNIT_COMMAND.START then
|
||||||
|
log.info(util.c(log_tag, "UNIT[", uid, "] START"))
|
||||||
|
self.proc_handle.start(uid)
|
||||||
|
elseif cmd == UNIT_COMMAND.RESET_RPS then
|
||||||
|
log.info(util.c(log_tag, "UNIT[", uid, "] RESET RPS"))
|
||||||
|
self.proc_handle.reset_rps(uid)
|
||||||
|
elseif cmd == UNIT_COMMAND.SET_BURN then
|
||||||
|
if pkt.length == 3 then
|
||||||
|
log.info(util.c(log_tag, "UNIT[", uid, "] SET BURN ", pkt.data[3]))
|
||||||
|
process.set_rate(uid, pkt.data[3])
|
||||||
|
else
|
||||||
|
log.debug(log_tag .. "CRDN unit command burn rate missing option")
|
||||||
|
end
|
||||||
|
elseif cmd == UNIT_COMMAND.SET_WASTE then
|
||||||
|
elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then
|
||||||
|
log.info(util.c(log_tag, "UNIT[", uid, "] ACK ALL ALARMS"))
|
||||||
|
self.proc_handle.ack_all_alarms(uid)
|
||||||
|
elseif cmd == UNIT_COMMAND.ACK_ALARM then
|
||||||
|
elseif cmd == UNIT_COMMAND.RESET_ALARM then
|
||||||
|
elseif cmd == UNIT_COMMAND.SET_GROUP then
|
||||||
|
else
|
||||||
|
log.debug(log_tag .. "CRDN unit command unknown")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug(log_tag .. "CRDN unit command invalid")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug(log_tag .. "CRDN unit command packet length mismatch")
|
||||||
|
end
|
||||||
|
elseif pkt.type == CRDN_TYPE.API_GET_FAC then
|
||||||
local fac = db.facility
|
local fac = db.facility
|
||||||
|
|
||||||
local data = {
|
local data = {
|
||||||
@ -146,6 +224,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
|||||||
u.unit_id,
|
u.unit_id,
|
||||||
u.connected,
|
u.connected,
|
||||||
u.rtu_hw,
|
u.rtu_hw,
|
||||||
|
u.a_group,
|
||||||
u.alarms,
|
u.alarms,
|
||||||
u.annunciator,
|
u.annunciator,
|
||||||
u.reactor_data,
|
u.reactor_data,
|
||||||
@ -160,7 +239,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "handler received unsupported CRDN packet type " .. pkt.type)
|
log.debug(log_tag .. "handler received unsupported CRDN packet type " .. pkt.type)
|
||||||
end
|
end
|
||||||
elseif pkt.scada_frame.protocol() == PROTOCOL.SCADA_MGMT then
|
elseif pkt.scada_frame.protocol() == PROTOCOL.SCADA_MGMT then
|
||||||
---@cast pkt mgmt_frame
|
---@cast pkt mgmt_frame
|
||||||
@ -173,7 +252,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
|||||||
self.last_rtt = srv_now - srv_start
|
self.last_rtt = srv_now - srv_start
|
||||||
|
|
||||||
if self.last_rtt > 750 then
|
if self.last_rtt > 750 then
|
||||||
log.warning(log_header .. "PKT KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)")
|
log.warning(log_tag .. "PKT KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- log.debug(log_header .. "PKT RTT = " .. self.last_rtt .. "ms")
|
-- log.debug(log_header .. "PKT RTT = " .. self.last_rtt .. "ms")
|
||||||
@ -181,7 +260,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
|||||||
|
|
||||||
iocontrol.fp_pkt_rtt(id, self.last_rtt)
|
iocontrol.fp_pkt_rtt(id, self.last_rtt)
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "SCADA keep alive packet length mismatch")
|
log.debug(log_tag .. "SCADA keep alive packet length mismatch")
|
||||||
end
|
end
|
||||||
elseif pkt.type == MGMT_TYPE.CLOSE then
|
elseif pkt.type == MGMT_TYPE.CLOSE then
|
||||||
-- close the session
|
-- close the session
|
||||||
@ -189,9 +268,9 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
|||||||
elseif pkt.type == MGMT_TYPE.ESTABLISH then
|
elseif pkt.type == MGMT_TYPE.ESTABLISH then
|
||||||
-- something is wrong, kill the session
|
-- something is wrong, kill the session
|
||||||
_close()
|
_close()
|
||||||
log.warning(log_header .. "terminated session due to an unexpected ESTABLISH packet")
|
log.warning(log_tag .. "terminated session due to an unexpected ESTABLISH packet")
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type)
|
log.debug(log_tag .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -216,7 +295,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
|||||||
function public.close()
|
function public.close()
|
||||||
_close()
|
_close()
|
||||||
_send_mgmt(MGMT_TYPE.CLOSE, {})
|
_send_mgmt(MGMT_TYPE.CLOSE, {})
|
||||||
log.info(log_header .. "session closed by server")
|
log.info(log_tag .. "session closed by server")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- iterate the session
|
-- iterate the session
|
||||||
@ -247,14 +326,14 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
|||||||
|
|
||||||
-- max 100ms spent processing queue
|
-- max 100ms spent processing queue
|
||||||
if util.time() - handle_start > 100 then
|
if util.time() - handle_start > 100 then
|
||||||
log.warning(log_header .. "exceeded 100ms queue process limit")
|
log.warning(log_tag .. "exceeded 100ms queue process limit")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- exit if connection was closed
|
-- exit if connection was closed
|
||||||
if not self.connected then
|
if not self.connected then
|
||||||
log.info(log_header .. "session closed by remote host")
|
log.info(log_tag .. "session closed by remote host")
|
||||||
return self.connected
|
return self.connected
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -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.5.7"
|
local COORDINATOR_VERSION = "v1.5.8"
|
||||||
|
|
||||||
local CHUNK_LOAD_DELAY_S = 30.0
|
local CHUNK_LOAD_DELAY_S = 30.0
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ local util = require("scada-common.util")
|
|||||||
|
|
||||||
local coordinator = require("coordinator.coordinator")
|
local coordinator = require("coordinator.coordinator")
|
||||||
local iocontrol = require("coordinator.iocontrol")
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
|
local process = require("coordinator.process")
|
||||||
local renderer = require("coordinator.renderer")
|
local renderer = require("coordinator.renderer")
|
||||||
local sounder = require("coordinator.sounder")
|
local sounder = require("coordinator.sounder")
|
||||||
|
|
||||||
@ -147,6 +148,9 @@ function threads.thread__main(smem)
|
|||||||
apisessions.iterate_all()
|
apisessions.iterate_all()
|
||||||
apisessions.free_all_closed()
|
apisessions.free_all_closed()
|
||||||
|
|
||||||
|
-- clear timed out process commands
|
||||||
|
process.clear_timed_out()
|
||||||
|
|
||||||
if renderer.ui_ready() then
|
if renderer.ui_ready() then
|
||||||
-- update clock used on main and flow monitors
|
-- update clock used on main and flow monitors
|
||||||
iocontrol.get_db().facility.ps.publish("date_time", os.date(smem.date_format))
|
iocontrol.get_db().facility.ps.publish("date_time", os.date(smem.date_format))
|
||||||
|
|||||||
@ -63,11 +63,11 @@ local function new_view(root, x, y)
|
|||||||
|
|
||||||
local main = Div{parent=root,width=128,height=24,x=x,y=y}
|
local main = Div{parent=root,width=128,height=24,x=x,y=y}
|
||||||
|
|
||||||
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}
|
local scram = HazardButton{parent=main,x=1,y=1,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=db.process.fac_scram,fg_bg=hzd_fg_bg}
|
||||||
local ack_a = HazardButton{parent=main,x=16,y=1,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=process.fac_ack_alarms,fg_bg=hzd_fg_bg}
|
local ack_a = HazardButton{parent=main,x=16,y=1,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=db.process.fac_ack_alarms,fg_bg=hzd_fg_bg}
|
||||||
|
|
||||||
facility.scram_ack = scram.on_response
|
db.process.fac_ack.on_scram = scram.on_response
|
||||||
facility.ack_alarms_ack = ack_a.on_response
|
db.process.fac_ack.on_ack_alarms = ack_a.on_response
|
||||||
|
|
||||||
local all_ok = IndicatorLight{parent=main,y=5,label="Unit Systems Online",colors=ind_grn}
|
local all_ok = IndicatorLight{parent=main,y=5,label="Unit Systems Online",colors=ind_grn}
|
||||||
local rad_mon = TriIndicatorLight{parent=main,label="Radiation Monitor",c1=style.ind_bkg,c2=ind_yel.fgd,c3=ind_grn.fgd}
|
local rad_mon = TriIndicatorLight{parent=main,label="Radiation Monitor",c1=style.ind_bkg,c2=ind_yel.fgd,c3=ind_grn.fgd}
|
||||||
|
|||||||
@ -29,6 +29,8 @@ local PushButton = require("graphics.elements.controls.push_button")
|
|||||||
local RadioButton = require("graphics.elements.controls.radio_button")
|
local RadioButton = require("graphics.elements.controls.radio_button")
|
||||||
local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric")
|
local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric")
|
||||||
|
|
||||||
|
local AUTO_GROUP = types.AUTO_GROUP
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
local ALIGN = core.ALIGN
|
||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
@ -373,16 +375,16 @@ local function init(parent, id)
|
|||||||
local scram = HazardButton{parent=main,x=2,y=32,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=hzd_fg_bg}
|
local scram = HazardButton{parent=main,x=2,y=32,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=hzd_fg_bg}
|
||||||
local reset = HazardButton{parent=main,x=22,y=32,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,fg_bg=hzd_fg_bg}
|
local reset = HazardButton{parent=main,x=22,y=32,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,fg_bg=hzd_fg_bg}
|
||||||
|
|
||||||
unit.start_ack = start.on_response
|
db.process.unit_ack[id].on_start = start.on_response
|
||||||
unit.scram_ack = scram.on_response
|
db.process.unit_ack[id].on_scram = scram.on_response
|
||||||
unit.reset_rps_ack = reset.on_response
|
db.process.unit_ack[id].on_rps_reset = reset.on_response
|
||||||
unit.ack_alarms_ack = ack_a.on_response
|
db.process.unit_ack[id].on_ack_alarms = ack_a.on_response
|
||||||
|
|
||||||
local function start_button_en_check()
|
local function start_button_en_check()
|
||||||
if (unit.reactor_data ~= nil) and (unit.reactor_data.mek_status ~= nil) then
|
if (unit.reactor_data ~= nil) and (unit.reactor_data.mek_status ~= nil) then
|
||||||
local can_start = (not unit.reactor_data.mek_status.status) and
|
local can_start = (not unit.reactor_data.mek_status.status) and
|
||||||
(not unit.reactor_data.rps_tripped) and
|
(not unit.reactor_data.rps_tripped) and
|
||||||
(unit.a_group == 0)
|
(unit.a_group == AUTO_GROUP.MANUAL)
|
||||||
if can_start then start.enable() else start.disable() end
|
if can_start then start.enable() else start.disable() end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -486,9 +488,7 @@ local function init(parent, id)
|
|||||||
local auto_ctl = Rectangle{parent=main,border=border(1,colors.purple,true),thin=true,width=13,height=15,x=32,y=37}
|
local auto_ctl = Rectangle{parent=main,border=border(1,colors.purple,true),thin=true,width=13,height=15,x=32,y=37}
|
||||||
local auto_div = Div{parent=auto_ctl,width=13,height=15,x=1,y=1}
|
local auto_div = Div{parent=auto_ctl,width=13,height=15,x=1,y=1}
|
||||||
|
|
||||||
local ctl_opts = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" }
|
local group = RadioButton{parent=auto_div,options=types.AUTO_GROUP_NAMES,callback=function()end,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.purple}
|
||||||
|
|
||||||
local group = RadioButton{parent=auto_div,options=ctl_opts,callback=function()end,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.purple}
|
|
||||||
|
|
||||||
group.register(u_ps, "auto_group_id", function (gid) group.set_value(gid + 1) end)
|
group.register(u_ps, "auto_group_id", function (gid) group.set_value(gid + 1) end)
|
||||||
|
|
||||||
@ -523,10 +523,10 @@ local function init(parent, id)
|
|||||||
|
|
||||||
-- enable/disable controls based on group assignment (start button is separate)
|
-- enable/disable controls based on group assignment (start button is separate)
|
||||||
burn_rate.register(u_ps, "auto_group_id", function (gid)
|
burn_rate.register(u_ps, "auto_group_id", function (gid)
|
||||||
if gid == 0 then burn_rate.enable() else burn_rate.disable() end
|
if gid == AUTO_GROUP.MANUAL then burn_rate.enable() else burn_rate.disable() end
|
||||||
end)
|
end)
|
||||||
set_burn_btn.register(u_ps, "auto_group_id", function (gid)
|
set_burn_btn.register(u_ps, "auto_group_id", function (gid)
|
||||||
if gid == 0 then set_burn_btn.enable() else set_burn_btn.disable() end
|
if gid == AUTO_GROUP.MANUAL then set_burn_btn.enable() else set_burn_btn.disable() end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- can't change group if auto is engaged regardless of if this unit is part of auto control
|
-- can't change group if auto is engaged regardless of if this unit is part of auto control
|
||||||
|
|||||||
@ -7,7 +7,7 @@ local flasher = require("graphics.flasher")
|
|||||||
|
|
||||||
local core = {}
|
local core = {}
|
||||||
|
|
||||||
core.version = "2.3.3"
|
core.version = "2.3.4"
|
||||||
|
|
||||||
core.flasher = flasher
|
core.flasher = flasher
|
||||||
core.events = events
|
core.events = events
|
||||||
@ -123,15 +123,17 @@ end
|
|||||||
|
|
||||||
-- Interactive Field Manager
|
-- Interactive Field Manager
|
||||||
|
|
||||||
---@param e graphics_base
|
---@param e graphics_base element
|
||||||
---@param max_len any
|
---@param max_len any max value length
|
||||||
---@param fg_bg any
|
---@param fg_bg any enabled fg/bg
|
||||||
---@param dis_fg_bg any
|
---@param dis_fg_bg any disabled fg/bg
|
||||||
function core.new_ifield(e, max_len, fg_bg, dis_fg_bg)
|
---@param align_right any true to align content right while unfocused
|
||||||
|
function core.new_ifield(e, max_len, fg_bg, dis_fg_bg, align_right)
|
||||||
local self = {
|
local self = {
|
||||||
frame_start = 1,
|
frame_start = 1,
|
||||||
visible_text = e.value,
|
visible_text = e.value,
|
||||||
cursor_pos = string.len(e.value) + 1,
|
cursor_pos = string.len(e.value) + 1,
|
||||||
|
align_offset = 0,
|
||||||
selected_all = false
|
selected_all = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +188,12 @@ function core.new_ifield(e, max_len, fg_bg, dis_fg_bg)
|
|||||||
e.w_write(string.rep(" ", e.frame.w))
|
e.w_write(string.rep(" ", e.frame.w))
|
||||||
e.w_set_cur(1, 1)
|
e.w_set_cur(1, 1)
|
||||||
|
|
||||||
local function _write()
|
local function _write(align_r)
|
||||||
|
if align_r and string.len(self.visible_text) <=e.frame.w then
|
||||||
|
self.align_offset = (e.frame.w - string.len(self.visible_text))
|
||||||
|
e.w_set_cur((e.frame.w - string.len(self.visible_text)) + 1, 1)
|
||||||
|
end
|
||||||
|
|
||||||
if self.censor then
|
if self.censor then
|
||||||
e.w_write(string.rep(self.censor, string.len(self.visible_text)))
|
e.w_write(string.rep(self.censor, string.len(self.visible_text)))
|
||||||
else
|
else
|
||||||
@ -226,15 +233,27 @@ function core.new_ifield(e, max_len, fg_bg, dis_fg_bg)
|
|||||||
self.selected_all = false
|
self.selected_all = false
|
||||||
|
|
||||||
-- write text without cursor
|
-- write text without cursor
|
||||||
_write()
|
_write(align_right)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- move cursor to x
|
-- get an x value to pass to move_cursor taking into account right alignment offset present when unfocused
|
||||||
---@param x integer
|
---@param x integer
|
||||||
|
function public.get_cursor_align_shift(x)
|
||||||
|
return math.max(0, x - self.align_offset)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- move cursor to x
|
||||||
|
---@param x integer x position or 0 to jump to the end
|
||||||
function public.move_cursor(x)
|
function public.move_cursor(x)
|
||||||
self.selected_all = false
|
self.selected_all = false
|
||||||
|
|
||||||
|
if x <= 0 then
|
||||||
|
self.cursor_pos = string.len(self.visible_text) + 1
|
||||||
|
else
|
||||||
self.cursor_pos = math.min(x, string.len(self.visible_text) + 1)
|
self.cursor_pos = math.min(x, string.len(self.visible_text) + 1)
|
||||||
|
end
|
||||||
|
|
||||||
public.show()
|
public.show()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -574,6 +574,15 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
|
|||||||
---@return graphics_element
|
---@return graphics_element
|
||||||
function public.get_child(id) return protected.children[protected.child_id_map[id]].get() end
|
function public.get_child(id) return protected.children[protected.child_id_map[id]].get() end
|
||||||
|
|
||||||
|
-- get all children
|
||||||
|
---@nodiscard
|
||||||
|
---@return table children table of graphics_element objects
|
||||||
|
function public.get_children()
|
||||||
|
local list = {}
|
||||||
|
for k, v in pairs(protected.children) do list[k] = v.get() end
|
||||||
|
return list
|
||||||
|
end
|
||||||
|
|
||||||
-- remove a child element
|
-- remove a child element
|
||||||
---@param id element_id
|
---@param id element_id
|
||||||
function public.remove(id)
|
function public.remove(id)
|
||||||
|
|||||||
@ -10,6 +10,7 @@ local element = require("graphics.element")
|
|||||||
---@field accent color accent color for hazard border
|
---@field accent color accent color for hazard border
|
||||||
---@field dis_colors? cpair text color and border color when disabled
|
---@field dis_colors? cpair text color and border color when disabled
|
||||||
---@field callback function function to call on touch
|
---@field callback function function to call on touch
|
||||||
|
---@field timeout? integer override for the default 1.5 second timeout, in seconds
|
||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
@ -28,6 +29,8 @@ local function hazard_button(args)
|
|||||||
args.height = 3
|
args.height = 3
|
||||||
args.width = string.len(args.text) + 4
|
args.width = string.len(args.text) + 4
|
||||||
|
|
||||||
|
local timeout = args.timeout or 1.5
|
||||||
|
|
||||||
-- create new graphics element base object
|
-- create new graphics element base object
|
||||||
local e = element.new(args)
|
local e = element.new(args)
|
||||||
|
|
||||||
@ -149,8 +152,8 @@ local function hazard_button(args)
|
|||||||
tcd.abort(on_success)
|
tcd.abort(on_success)
|
||||||
tcd.abort(on_failure)
|
tcd.abort(on_failure)
|
||||||
|
|
||||||
-- 1.5 second timeout
|
-- operation timeout animation
|
||||||
tcd.dispatch(1.5, on_timeout)
|
tcd.dispatch(timeout, on_timeout)
|
||||||
|
|
||||||
args.callback()
|
args.callback()
|
||||||
end
|
end
|
||||||
|
|||||||
@ -17,6 +17,7 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK
|
|||||||
---@field max_frac_digits? integer maximum number of fractional digits, enforced on unfocus
|
---@field max_frac_digits? integer maximum number of fractional digits, enforced on unfocus
|
||||||
---@field allow_decimal? boolean true to allow decimals
|
---@field allow_decimal? boolean true to allow decimals
|
||||||
---@field allow_negative? boolean true to allow negative numbers
|
---@field allow_negative? boolean true to allow negative numbers
|
||||||
|
---@field align_right? boolean true to align right while unfocused
|
||||||
---@field dis_fg_bg? cpair foreground/background colors when disabled
|
---@field dis_fg_bg? cpair foreground/background colors when disabled
|
||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
@ -47,7 +48,7 @@ local function number_field(args)
|
|||||||
e.value = "" .. (args.default or 0)
|
e.value = "" .. (args.default or 0)
|
||||||
|
|
||||||
-- make an interactive field manager
|
-- make an interactive field manager
|
||||||
local ifield = core.new_ifield(e, args.max_chars, args.fg_bg, args.dis_fg_bg)
|
local ifield = core.new_ifield(e, args.max_chars, args.fg_bg, args.dis_fg_bg, args.align_right)
|
||||||
|
|
||||||
-- handle mouse interaction
|
-- handle mouse interaction
|
||||||
---@param event mouse_interaction mouse event
|
---@param event mouse_interaction mouse event
|
||||||
@ -55,10 +56,16 @@ local function number_field(args)
|
|||||||
-- only handle if on an increment or decrement arrow
|
-- only handle if on an increment or decrement arrow
|
||||||
if e.enabled and e.in_frame_bounds(event.current.x, event.current.y) then
|
if e.enabled and e.in_frame_bounds(event.current.x, event.current.y) then
|
||||||
if core.events.was_clicked(event.type) then
|
if core.events.was_clicked(event.type) then
|
||||||
|
local x = event.current.x
|
||||||
|
|
||||||
|
if not e.is_focused() then
|
||||||
|
x = ifield.get_cursor_align_shift(x)
|
||||||
|
end
|
||||||
|
|
||||||
e.take_focus()
|
e.take_focus()
|
||||||
|
|
||||||
if event.type == MOUSE_CLICK.UP then
|
if event.type == MOUSE_CLICK.UP then
|
||||||
ifield.move_cursor(event.current.x)
|
ifield.move_cursor(x)
|
||||||
end
|
end
|
||||||
elseif event.type == MOUSE_CLICK.DOUBLE_CLICK then
|
elseif event.type == MOUSE_CLICK.DOUBLE_CLICK then
|
||||||
ifield.select_all()
|
ifield.select_all()
|
||||||
|
|||||||
@ -27,7 +27,6 @@ local tri = util.trinary
|
|||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@ -536,7 +535,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
local textbox
|
local textbox
|
||||||
if height > 1 then
|
if height > 1 then
|
||||||
textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1,alignment=LEFT}
|
textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1}
|
||||||
else
|
else
|
||||||
textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT}
|
textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT}
|
||||||
end
|
end
|
||||||
|
|||||||
@ -3,11 +3,12 @@
|
|||||||
--
|
--
|
||||||
|
|
||||||
local const = require("scada-common.constants")
|
local const = require("scada-common.constants")
|
||||||
-- local log = require("scada-common.log")
|
|
||||||
local psil = require("scada-common.psil")
|
local psil = require("scada-common.psil")
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local process = require("pocket.process")
|
||||||
|
|
||||||
local ALARM = types.ALARM
|
local ALARM = types.ALARM
|
||||||
local ALARM_STATE = types.ALARM_STATE
|
local ALARM_STATE = types.ALARM_STATE
|
||||||
|
|
||||||
@ -38,13 +39,24 @@ local io = {
|
|||||||
ps = psil.create()
|
ps = psil.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- luacheck: no unused args
|
||||||
|
|
||||||
|
-- placeholder acknowledge function for type hinting
|
||||||
|
---@param success boolean
|
||||||
|
---@diagnostic disable-next-line: unused-local
|
||||||
|
local function __generic_ack(success) end
|
||||||
|
|
||||||
|
-- luacheck: unused args
|
||||||
|
|
||||||
local config = nil ---@type pkt_config
|
local config = nil ---@type pkt_config
|
||||||
|
local comms = nil ---@type pocket_comms
|
||||||
|
|
||||||
-- initialize facility-independent components of pocket iocontrol
|
-- initialize facility-independent components of pocket iocontrol
|
||||||
---@param comms pocket_comms
|
---@param pkt_comms pocket_comms
|
||||||
---@param nav pocket_nav
|
---@param nav pocket_nav
|
||||||
---@param cfg pkt_config
|
---@param cfg pkt_config
|
||||||
function iocontrol.init_core(comms, nav, cfg)
|
function iocontrol.init_core(pkt_comms, nav, cfg)
|
||||||
|
comms = pkt_comms
|
||||||
config = cfg
|
config = cfg
|
||||||
|
|
||||||
io.nav = nav
|
io.nav = nav
|
||||||
@ -154,6 +166,11 @@ function iocontrol.init_fac(conf)
|
|||||||
|
|
||||||
radiation = types.new_zero_radiation_reading(),
|
radiation = types.new_zero_radiation_reading(),
|
||||||
|
|
||||||
|
start_ack = __generic_ack,
|
||||||
|
stop_ack = __generic_ack,
|
||||||
|
scram_ack = __generic_ack,
|
||||||
|
ack_alarms_ack = __generic_ack,
|
||||||
|
|
||||||
ps = psil.create(),
|
ps = psil.create(),
|
||||||
|
|
||||||
induction_ps_tbl = {},
|
induction_ps_tbl = {},
|
||||||
@ -298,7 +315,18 @@ function iocontrol.init_fac(conf)
|
|||||||
turbine_flow_stable = false,
|
turbine_flow_stable = false,
|
||||||
|
|
||||||
-- auto control group
|
-- auto control group
|
||||||
a_group = 0,
|
a_group = types.AUTO_GROUP.MANUAL,
|
||||||
|
|
||||||
|
start = function () process.start(i) end,
|
||||||
|
scram = function () process.scram(i) end,
|
||||||
|
reset_rps = function () process.reset_rps(i) end,
|
||||||
|
ack_alarms = function () process.ack_all_alarms(i) end,
|
||||||
|
set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate
|
||||||
|
|
||||||
|
start_ack = __generic_ack,
|
||||||
|
scram_ack = __generic_ack,
|
||||||
|
reset_rps_ack = __generic_ack,
|
||||||
|
ack_alarms_ack = __generic_ack,
|
||||||
|
|
||||||
---@type alarms
|
---@type alarms
|
||||||
alarms = { ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE },
|
alarms = { ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE },
|
||||||
@ -346,6 +374,9 @@ function iocontrol.init_fac(conf)
|
|||||||
|
|
||||||
table.insert(io.units, entry)
|
table.insert(io.units, entry)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- pass IO control here since it can't be require'd due to a require loop
|
||||||
|
process.init(io, comms)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- set network link state
|
-- set network link state
|
||||||
@ -458,11 +489,15 @@ function iocontrol.record_unit_data(data)
|
|||||||
|
|
||||||
unit.connected = data[2]
|
unit.connected = data[2]
|
||||||
unit.rtu_hw = data[3]
|
unit.rtu_hw = data[3]
|
||||||
unit.alarms = data[4]
|
unit.a_group = data[4]
|
||||||
|
unit.alarms = data[5]
|
||||||
|
|
||||||
|
unit.unit_ps.publish("auto_group_id", unit.a_group)
|
||||||
|
unit.unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[unit.a_group + 1])
|
||||||
|
|
||||||
--#region Annunciator
|
--#region Annunciator
|
||||||
|
|
||||||
unit.annunciator = data[5]
|
unit.annunciator = data[6]
|
||||||
|
|
||||||
local rcs_disconn, rcs_warn, rcs_hazard = false, false, false
|
local rcs_disconn, rcs_warn, rcs_hazard = false, false, false
|
||||||
|
|
||||||
@ -540,7 +575,7 @@ function iocontrol.record_unit_data(data)
|
|||||||
|
|
||||||
--#region Reactor Data
|
--#region Reactor Data
|
||||||
|
|
||||||
unit.reactor_data = data[6]
|
unit.reactor_data = data[7]
|
||||||
|
|
||||||
local control_status = 1
|
local control_status = 1
|
||||||
local reactor_status = 1
|
local reactor_status = 1
|
||||||
@ -612,7 +647,7 @@ function iocontrol.record_unit_data(data)
|
|||||||
|
|
||||||
--#region RTU Devices
|
--#region RTU Devices
|
||||||
|
|
||||||
unit.boiler_data_tbl = data[7]
|
unit.boiler_data_tbl = data[8]
|
||||||
|
|
||||||
for id = 1, #unit.boiler_data_tbl do
|
for id = 1, #unit.boiler_data_tbl do
|
||||||
local boiler = unit.boiler_data_tbl[id] ---@type boilerv_session_db
|
local boiler = unit.boiler_data_tbl[id] ---@type boilerv_session_db
|
||||||
@ -645,7 +680,7 @@ function iocontrol.record_unit_data(data)
|
|||||||
ps.publish("BoilerStateStatus", computed_status)
|
ps.publish("BoilerStateStatus", computed_status)
|
||||||
end
|
end
|
||||||
|
|
||||||
unit.turbine_data_tbl = data[8]
|
unit.turbine_data_tbl = data[9]
|
||||||
|
|
||||||
for id = 1, #unit.turbine_data_tbl do
|
for id = 1, #unit.turbine_data_tbl do
|
||||||
local turbine = unit.turbine_data_tbl[id] ---@type turbinev_session_db
|
local turbine = unit.turbine_data_tbl[id] ---@type turbinev_session_db
|
||||||
@ -680,16 +715,16 @@ function iocontrol.record_unit_data(data)
|
|||||||
ps.publish("TurbineStateStatus", computed_status)
|
ps.publish("TurbineStateStatus", computed_status)
|
||||||
end
|
end
|
||||||
|
|
||||||
unit.tank_data_tbl = data[9]
|
unit.tank_data_tbl = data[10]
|
||||||
|
|
||||||
unit.last_rate_change_ms = data[10]
|
unit.last_rate_change_ms = data[11]
|
||||||
unit.turbine_flow_stable = data[11]
|
unit.turbine_flow_stable = data[12]
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
--#region Status Information Display
|
--#region Status Information Display
|
||||||
|
|
||||||
local ecam = {} -- aviation reference :) back to VATSIM I go...
|
local ecam = {} -- aviation reference :)
|
||||||
|
|
||||||
-- local function red(text) return { text = text, color = colors.red } end
|
-- local function red(text) return { text = text, color = colors.red } end
|
||||||
local function white(text) return { text = text, color = colors.white } end
|
local function white(text) return { text = text, color = colors.white } end
|
||||||
|
|||||||
@ -9,6 +9,8 @@ local DEVICE_TYPE = comms.DEVICE_TYPE
|
|||||||
local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
||||||
local MGMT_TYPE = comms.MGMT_TYPE
|
local MGMT_TYPE = comms.MGMT_TYPE
|
||||||
local CRDN_TYPE = comms.CRDN_TYPE
|
local CRDN_TYPE = comms.CRDN_TYPE
|
||||||
|
local UNIT_COMMAND = comms.UNIT_COMMAND
|
||||||
|
local FAC_COMMAND = comms.FAC_COMMAND
|
||||||
|
|
||||||
local LINK_STATE = iocontrol.LINK_STATE
|
local LINK_STATE = iocontrol.LINK_STATE
|
||||||
|
|
||||||
@ -84,13 +86,14 @@ local APP_ID = {
|
|||||||
LOADER = 2,
|
LOADER = 2,
|
||||||
-- main app pages
|
-- main app pages
|
||||||
UNITS = 3,
|
UNITS = 3,
|
||||||
GUIDE = 4,
|
CONTROL = 4,
|
||||||
ABOUT = 5,
|
GUIDE = 5,
|
||||||
|
ABOUT = 6,
|
||||||
-- diag app page
|
-- diag app page
|
||||||
ALARMS = 6,
|
ALARMS = 7,
|
||||||
-- other
|
-- other
|
||||||
DUMMY = 7,
|
DUMMY = 8,
|
||||||
NUM_APPS = 7
|
NUM_APPS = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
pocket.APP_ID = APP_ID
|
pocket.APP_ID = APP_ID
|
||||||
@ -543,6 +546,21 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
if self.api.linked then _send_api(CRDN_TYPE.API_GET_UNIT, { unit }) end
|
if self.api.linked then _send_api(CRDN_TYPE.API_GET_UNIT, { unit }) end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- send a facility command
|
||||||
|
---@param cmd FAC_COMMAND command
|
||||||
|
---@param option any? optional option options for the optional options (like waste mode)
|
||||||
|
function public.send_fac_command(cmd, option)
|
||||||
|
_send_api(CRDN_TYPE.FAC_CMD, { cmd, option })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- send a unit command
|
||||||
|
---@param cmd UNIT_COMMAND command
|
||||||
|
---@param unit integer unit ID
|
||||||
|
---@param option any? optional option options for the optional options (like burn rate)
|
||||||
|
function public.send_unit_command(cmd, unit, option)
|
||||||
|
_send_api(CRDN_TYPE.UNIT_CMD, { cmd, unit, option })
|
||||||
|
end
|
||||||
|
|
||||||
-- parse a packet
|
-- parse a packet
|
||||||
---@param side string
|
---@param side string
|
||||||
---@param sender integer
|
---@param sender integer
|
||||||
@ -583,7 +601,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
local ok = util.trinary(max == nil, packet.length == length, packet.length >= length and packet.length <= (max or 0))
|
local ok = util.trinary(max == nil, packet.length == length, packet.length >= length and packet.length <= (max or 0))
|
||||||
if not ok then
|
if not ok then
|
||||||
local fmt = "[comms] RX_PACKET{r_chan=%d,proto=%d,type=%d}: packet length mismatch -> expect %d != actual %d"
|
local fmt = "[comms] RX_PACKET{r_chan=%d,proto=%d,type=%d}: packet length mismatch -> expect %d != actual %d"
|
||||||
log.debug(util.sprintf(fmt, packet.scada_frame.remote_channel(), packet.scada_frame.protocol(), packet.type, length, packet.scada_frame.length()))
|
log.debug(util.sprintf(fmt, packet.scada_frame.remote_channel(), packet.scada_frame.protocol(), packet.type, length, packet.length))
|
||||||
end
|
end
|
||||||
return ok
|
return ok
|
||||||
end
|
end
|
||||||
@ -628,12 +646,56 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
if protocol == PROTOCOL.SCADA_CRDN then
|
if protocol == PROTOCOL.SCADA_CRDN then
|
||||||
---@cast packet crdn_frame
|
---@cast packet crdn_frame
|
||||||
if self.api.linked then
|
if self.api.linked then
|
||||||
if packet.type == CRDN_TYPE.API_GET_FAC then
|
if packet.type == CRDN_TYPE.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_COMMAND.SCRAM_ALL then
|
||||||
|
iocontrol.get_db().facility.scram_ack(ack)
|
||||||
|
elseif cmd == FAC_COMMAND.STOP then
|
||||||
|
elseif cmd == FAC_COMMAND.START then
|
||||||
|
elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then
|
||||||
|
iocontrol.get_db().facility.ack_alarms_ack(ack)
|
||||||
|
elseif cmd == FAC_COMMAND.SET_WASTE_MODE then
|
||||||
|
elseif cmd == FAC_COMMAND.SET_PU_FB then
|
||||||
|
elseif cmd == FAC_COMMAND.SET_SPS_LP then
|
||||||
|
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 == CRDN_TYPE.UNIT_CMD then
|
||||||
|
-- unit command acknowledgement
|
||||||
|
if packet.length == 3 then
|
||||||
|
local cmd = packet.data[1]
|
||||||
|
local unit_id = packet.data[2]
|
||||||
|
local ack = packet.data[3] == true
|
||||||
|
|
||||||
|
local unit = iocontrol.get_db().units[unit_id] ---@type pioctl_unit
|
||||||
|
|
||||||
|
if unit ~= nil then
|
||||||
|
if cmd == UNIT_COMMAND.SCRAM then
|
||||||
|
unit.scram_ack(ack)
|
||||||
|
elseif cmd == UNIT_COMMAND.START then
|
||||||
|
unit.start_ack(ack)
|
||||||
|
elseif cmd == UNIT_COMMAND.RESET_RPS then
|
||||||
|
unit.reset_rps_ack(ack)
|
||||||
|
elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then
|
||||||
|
unit.ack_alarms_ack(ack)
|
||||||
|
else
|
||||||
|
log.debug(util.c("received unsupported unit command ack for command ", cmd))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif packet.type == CRDN_TYPE.API_GET_FAC then
|
||||||
if _check_length(packet, 11) then
|
if _check_length(packet, 11) then
|
||||||
iocontrol.record_facility_data(packet.data)
|
iocontrol.record_facility_data(packet.data)
|
||||||
end
|
end
|
||||||
elseif packet.type == CRDN_TYPE.API_GET_UNIT then
|
elseif packet.type == CRDN_TYPE.API_GET_UNIT then
|
||||||
if _check_length(packet, 11) and type(packet.data[1]) == "number" and iocontrol.get_db().units[packet.data[1]] then
|
if _check_length(packet, 12) and type(packet.data[1]) == "number" and iocontrol.get_db().units[packet.data[1]] then
|
||||||
iocontrol.record_unit_data(packet.data)
|
iocontrol.record_unit_data(packet.data)
|
||||||
end
|
end
|
||||||
else _fail_type(packet) end
|
else _fail_type(packet) end
|
||||||
|
|||||||
94
pocket/process.lua
Normal file
94
pocket/process.lua
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
--
|
||||||
|
-- Process Control Management
|
||||||
|
--
|
||||||
|
|
||||||
|
local comms = require("scada-common.comms")
|
||||||
|
local log = require("scada-common.log")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local FAC_COMMAND = comms.FAC_COMMAND
|
||||||
|
local UNIT_COMMAND = comms.UNIT_COMMAND
|
||||||
|
|
||||||
|
---@class pocket_process_controller
|
||||||
|
local process = {}
|
||||||
|
|
||||||
|
local self = {
|
||||||
|
io = nil, ---@type ioctl
|
||||||
|
comms = nil ---@type pocket_comms
|
||||||
|
}
|
||||||
|
|
||||||
|
-- initialize the process controller
|
||||||
|
---@param iocontrol pocket_ioctl iocontrl system
|
||||||
|
---@param pocket_comms pocket_comms pocket communications
|
||||||
|
function process.init(iocontrol, pocket_comms)
|
||||||
|
self.io = iocontrol
|
||||||
|
self.comms = pocket_comms
|
||||||
|
end
|
||||||
|
|
||||||
|
-- facility SCRAM command
|
||||||
|
function process.fac_scram()
|
||||||
|
self.comms.send_fac_command(FAC_COMMAND.SCRAM_ALL)
|
||||||
|
log.debug("PROCESS: FAC SCRAM ALL")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- facility alarm acknowledge command
|
||||||
|
function process.fac_ack_alarms()
|
||||||
|
self.comms.send_fac_command(FAC_COMMAND.ACK_ALL_ALARMS)
|
||||||
|
log.debug("PROCESS: FAC ACK ALL ALARMS")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- start reactor
|
||||||
|
---@param id integer unit ID
|
||||||
|
function process.start(id)
|
||||||
|
self.io.units[id].control_state = true
|
||||||
|
self.comms.send_unit_command(UNIT_COMMAND.START, id)
|
||||||
|
log.debug(util.c("PROCESS: UNIT[", id, "] START"))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- SCRAM reactor
|
||||||
|
---@param id integer unit ID
|
||||||
|
function process.scram(id)
|
||||||
|
self.io.units[id].control_state = false
|
||||||
|
self.comms.send_unit_command(UNIT_COMMAND.SCRAM, id)
|
||||||
|
log.debug(util.c("PROCESS: UNIT[", id, "] SCRAM"))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- reset reactor protection system
|
||||||
|
---@param id integer unit ID
|
||||||
|
function process.reset_rps(id)
|
||||||
|
self.comms.send_unit_command(UNIT_COMMAND.RESET_RPS, id)
|
||||||
|
log.debug(util.c("PROCESS: UNIT[", id, "] RESET RPS"))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- set burn rate
|
||||||
|
---@param id integer unit ID
|
||||||
|
---@param rate number burn rate
|
||||||
|
function process.set_rate(id, rate)
|
||||||
|
self.comms.send_unit_command(UNIT_COMMAND.SET_BURN, id, rate)
|
||||||
|
log.debug(util.c("PROCESS: UNIT[", id, "] SET BURN ", rate))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- acknowledge all alarms
|
||||||
|
---@param id integer unit ID
|
||||||
|
function process.ack_all_alarms(id)
|
||||||
|
self.comms.send_unit_command(UNIT_COMMAND.ACK_ALL_ALARMS, id)
|
||||||
|
log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALL ALARMS"))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- acknowledge an alarm
|
||||||
|
---@param id integer unit ID
|
||||||
|
---@param alarm integer alarm ID
|
||||||
|
function process.ack_alarm(id, alarm)
|
||||||
|
self.comms.send_unit_command(UNIT_COMMAND.ACK_ALARM, id, alarm)
|
||||||
|
log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALARM ", alarm))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- reset an alarm
|
||||||
|
---@param id integer unit ID
|
||||||
|
---@param alarm integer alarm ID
|
||||||
|
function process.reset_alarm(id, alarm)
|
||||||
|
self.comms.send_unit_command(UNIT_COMMAND.RESET_ALARM, id, alarm)
|
||||||
|
log.debug(util.c("PROCESS: UNIT[", id, "] RESET ALARM ", alarm))
|
||||||
|
end
|
||||||
|
|
||||||
|
return process
|
||||||
@ -20,7 +20,7 @@ local pocket = require("pocket.pocket")
|
|||||||
local renderer = require("pocket.renderer")
|
local renderer = require("pocket.renderer")
|
||||||
local threads = require("pocket.threads")
|
local threads = require("pocket.threads")
|
||||||
|
|
||||||
local POCKET_VERSION = "v0.11.9-alpha"
|
local POCKET_VERSION = "v0.12.0-alpha"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
|
|||||||
233
pocket/ui/apps/control.lua
Normal file
233
pocket/ui/apps/control.lua
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
--
|
||||||
|
-- Unit Control Page
|
||||||
|
--
|
||||||
|
|
||||||
|
local types = require("scada-common.types")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
local process = require("pocket.process")
|
||||||
|
|
||||||
|
local style = require("pocket.ui.style")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.div")
|
||||||
|
local MultiPane = require("graphics.elements.multipane")
|
||||||
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
|
local WaitingAnim = require("graphics.elements.animations.waiting")
|
||||||
|
|
||||||
|
local HazardButton = require("graphics.elements.controls.hazard_button")
|
||||||
|
local PushButton = require("graphics.elements.controls.push_button")
|
||||||
|
|
||||||
|
local NumberField = require("graphics.elements.form.number_field")
|
||||||
|
|
||||||
|
local DataIndicator = require("graphics.elements.indicators.data")
|
||||||
|
local IconIndicator = require("graphics.elements.indicators.icon")
|
||||||
|
|
||||||
|
local AUTO_GROUP = types.AUTO_GROUP
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
|
local lu_col = style.label_unit_pair
|
||||||
|
local text_fg = style.text_fg
|
||||||
|
local mode_states = style.icon_states.mode_states
|
||||||
|
|
||||||
|
local hzd_fg_bg = cpair(colors.white, colors.gray)
|
||||||
|
local dis_colors = cpair(colors.white, colors.lightGray)
|
||||||
|
|
||||||
|
-- new unit control page view
|
||||||
|
---@param root graphics_element parent
|
||||||
|
local function new_view(root)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
|
local frame = Div{parent=root,x=1,y=1}
|
||||||
|
|
||||||
|
local app = db.nav.register_app(APP_ID.CONTROL, frame, nil, false, true)
|
||||||
|
|
||||||
|
local load_div = Div{parent=frame,x=1,y=1}
|
||||||
|
local main = Div{parent=frame,x=1,y=1}
|
||||||
|
|
||||||
|
TextBox{parent=load_div,y=12,text="Loading...",alignment=ALIGN.CENTER}
|
||||||
|
WaitingAnim{parent=load_div,x=math.floor(main.get_width()/2)-1,y=8,fg_bg=cpair(colors.green,colors._INHERIT)}
|
||||||
|
|
||||||
|
local load_pane = MultiPane{parent=main,x=1,y=1,panes={load_div,main}}
|
||||||
|
|
||||||
|
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end } })
|
||||||
|
|
||||||
|
local btn_fg_bg = cpair(colors.green, colors.black)
|
||||||
|
local btn_active = cpair(colors.white, colors.black)
|
||||||
|
|
||||||
|
local page_div = nil ---@type nil|graphics_element
|
||||||
|
|
||||||
|
-- set sidebar to display unit-specific fields based on a specified unit
|
||||||
|
local function set_sidebar()
|
||||||
|
local list = {
|
||||||
|
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end },
|
||||||
|
{ label = "FAC", color = core.cpair(colors.black, colors.orange), callback = function () app.switcher(db.facility.num_units + 1) end }
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 1, db.facility.num_units do
|
||||||
|
table.insert(list, { label = "U-" .. i, color = core.cpair(colors.black, colors.lightGray), callback = function () app.switcher(i) end })
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_sidebar(list)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- load the app (create the elements)
|
||||||
|
local function load()
|
||||||
|
page_div = Div{parent=main,y=2,width=main.get_width()}
|
||||||
|
|
||||||
|
local panes = {}
|
||||||
|
|
||||||
|
local active_unit = 1
|
||||||
|
|
||||||
|
-- create all page divs
|
||||||
|
for _ = 1, db.facility.num_units + 1 do
|
||||||
|
local div = Div{parent=page_div}
|
||||||
|
table.insert(panes, div)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- previous unit
|
||||||
|
local function prev(x)
|
||||||
|
active_unit = util.trinary(x == 1, db.facility.num_units, x - 1)
|
||||||
|
app.switcher(active_unit)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- next unit
|
||||||
|
local function next(x)
|
||||||
|
active_unit = util.trinary(x == db.facility.num_units, 1, x + 1)
|
||||||
|
app.switcher(active_unit)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, db.facility.num_units do
|
||||||
|
local u_pane = panes[i]
|
||||||
|
local u_div = Div{parent=u_pane,x=2,width=main.get_width()-2}
|
||||||
|
local unit = db.units[i] ---@type pioctl_unit
|
||||||
|
local u_ps = unit.unit_ps
|
||||||
|
|
||||||
|
-- refresh data callback, every 500ms it will re-send the query
|
||||||
|
local last_update = 0
|
||||||
|
local function update()
|
||||||
|
if util.time_ms() - last_update >= 500 then
|
||||||
|
db.api.get_unit(i)
|
||||||
|
last_update = util.time_ms()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local u_page = app.new_page(nil, i)
|
||||||
|
u_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=u_div,y=1,text="Reactor Unit #"..i,alignment=ALIGN.CENTER}
|
||||||
|
PushButton{parent=u_div,x=1,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()prev(i)end}
|
||||||
|
PushButton{parent=u_div,x=21,y=1,text=">",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()next(i)end}
|
||||||
|
|
||||||
|
local rate = DataIndicator{parent=u_div,y=3,lu_colors=lu_col,label="Burn",unit="mB/t",format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg}
|
||||||
|
local temp = DataIndicator{parent=u_div,lu_colors=lu_col,label="Temp",unit=db.temp_label,format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg}
|
||||||
|
|
||||||
|
local ctrl = IconIndicator{parent=u_div,x=1,y=6,label="Control State",states=mode_states}
|
||||||
|
|
||||||
|
rate.register(u_ps, "act_burn_rate", rate.update)
|
||||||
|
temp.register(u_ps, "temp", function (t) temp.update(db.temp_convert(t)) end)
|
||||||
|
ctrl.register(u_ps, "U_ControlStatus", ctrl.update)
|
||||||
|
|
||||||
|
u_div.line_break()
|
||||||
|
|
||||||
|
TextBox{parent=u_div,y=8,text="CMD",width=4,fg_bg=cpair(colors.lightGray,colors.black)}
|
||||||
|
TextBox{parent=u_div,x=14,y=8,text="mB/t",width=4,fg_bg=cpair(colors.lightGray,colors.black)}
|
||||||
|
local burn_cmd = NumberField{parent=u_div,x=5,y=8,width=8,default=0.01,min=0.01,max_frac_digits=2,max_chars=8,allow_decimal=true,align_right=true,fg_bg=cpair(colors.white,colors.gray),dis_fg_bg=cpair(colors.gray,colors.lightGray)}
|
||||||
|
|
||||||
|
local set_burn = function () unit.set_burn(burn_cmd.get_value()) end
|
||||||
|
local set_burn_btn = PushButton{parent=u_div,x=19,y=8,text="SET",min_width=5,fg_bg=cpair(colors.green,colors.black),active_fg_bg=cpair(colors.white,colors.black),dis_fg_bg=cpair(colors.gray,colors.black),callback=set_burn}
|
||||||
|
|
||||||
|
-- enable/disable controls based on group assignment (start button is separate)
|
||||||
|
burn_cmd.register(u_ps, "auto_group_id", function (gid)
|
||||||
|
if gid == AUTO_GROUP.MANUAL then burn_cmd.enable() else burn_cmd.disable() end
|
||||||
|
end)
|
||||||
|
set_burn_btn.register(u_ps, "auto_group_id", function (gid)
|
||||||
|
if gid == AUTO_GROUP.MANUAL then set_burn_btn.enable() else set_burn_btn.disable() end
|
||||||
|
end)
|
||||||
|
|
||||||
|
burn_cmd.register(u_ps, "burn_rate", burn_cmd.set_value)
|
||||||
|
burn_cmd.register(u_ps, "max_burn", burn_cmd.set_max)
|
||||||
|
|
||||||
|
local start = HazardButton{parent=u_div,x=2,y=11,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,timeout=3,fg_bg=hzd_fg_bg}
|
||||||
|
local ack_a = HazardButton{parent=u_div,x=12,y=11,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=unit.ack_alarms,timeout=3,fg_bg=hzd_fg_bg}
|
||||||
|
local scram = HazardButton{parent=u_div,x=2,y=15,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,timeout=3,fg_bg=hzd_fg_bg}
|
||||||
|
local reset = HazardButton{parent=u_div,x=12,y=15,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,timeout=3,fg_bg=hzd_fg_bg}
|
||||||
|
|
||||||
|
unit.start_ack = start.on_response
|
||||||
|
unit.ack_alarms_ack = ack_a.on_response
|
||||||
|
unit.scram_ack = scram.on_response
|
||||||
|
unit.reset_rps_ack = reset.on_response
|
||||||
|
|
||||||
|
local function start_button_en_check()
|
||||||
|
if (unit.reactor_data ~= nil) and (unit.reactor_data.mek_status ~= nil) then
|
||||||
|
local can_start = (not unit.reactor_data.mek_status.status) and
|
||||||
|
(not unit.reactor_data.rps_tripped) and
|
||||||
|
(unit.a_group == AUTO_GROUP.MANUAL)
|
||||||
|
if can_start then start.enable() else start.disable() end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
start.register(u_ps, "status", start_button_en_check)
|
||||||
|
start.register(u_ps, "rps_tripped", start_button_en_check)
|
||||||
|
start.register(u_ps, "auto_group_id", start_button_en_check)
|
||||||
|
start.register(u_ps, "AutoControl", start_button_en_check)
|
||||||
|
|
||||||
|
reset.register(u_ps, "rps_tripped", function (active) if active then reset.enable() else reset.disable() end end)
|
||||||
|
|
||||||
|
util.nop()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- facility controls
|
||||||
|
|
||||||
|
local f_pane = panes[db.facility.num_units + 1]
|
||||||
|
local f_div = Div{parent=f_pane,x=2,width=main.get_width()-2}
|
||||||
|
|
||||||
|
app.new_page(nil, db.facility.num_units + 1)
|
||||||
|
|
||||||
|
TextBox{parent=f_div,y=1,text="Facility Commands",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local scram = HazardButton{parent=f_div,x=5,y=6,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=process.fac_scram,timeout=3,fg_bg=hzd_fg_bg}
|
||||||
|
local ack_a = HazardButton{parent=f_div,x=7,y=11,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=process.fac_ack_alarms,timeout=3,fg_bg=hzd_fg_bg}
|
||||||
|
|
||||||
|
db.facility.scram_ack = scram.on_response
|
||||||
|
db.facility.ack_alarms_ack = ack_a.on_response
|
||||||
|
|
||||||
|
-- setup multipane
|
||||||
|
local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||||
|
app.set_root_pane(u_pane)
|
||||||
|
|
||||||
|
set_sidebar()
|
||||||
|
|
||||||
|
-- done, show the app
|
||||||
|
load_pane.set_value(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- delete the elements and switch back to the loading screen
|
||||||
|
local function unload()
|
||||||
|
if page_div then
|
||||||
|
page_div.delete()
|
||||||
|
page_div = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end } })
|
||||||
|
app.delete_pages()
|
||||||
|
|
||||||
|
-- show loading screen
|
||||||
|
load_pane.set_value(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_load(load)
|
||||||
|
app.set_unload(unload)
|
||||||
|
|
||||||
|
return main
|
||||||
|
end
|
||||||
|
|
||||||
|
return new_view
|
||||||
@ -3,6 +3,7 @@
|
|||||||
--
|
--
|
||||||
|
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
local log = require("scada-common.log")
|
||||||
|
|
||||||
local iocontrol = require("pocket.iocontrol")
|
local iocontrol = require("pocket.iocontrol")
|
||||||
local pocket = require("pocket.pocket")
|
local pocket = require("pocket.pocket")
|
||||||
@ -78,6 +79,7 @@ local function new_view(root)
|
|||||||
local uis_page = app.new_page(main_page, 4)
|
local uis_page = app.new_page(main_page, 4)
|
||||||
local fps_page = app.new_page(main_page, 5)
|
local fps_page = app.new_page(main_page, 5)
|
||||||
local gls_page = app.new_page(main_page, 6)
|
local gls_page = app.new_page(main_page, 6)
|
||||||
|
local lnk_page = app.new_page(main_page, 7)
|
||||||
|
|
||||||
local home = Div{parent=page_div,x=2}
|
local home = Div{parent=page_div,x=2}
|
||||||
local search = Div{parent=page_div,x=2}
|
local search = Div{parent=page_div,x=2}
|
||||||
@ -85,7 +87,8 @@ local function new_view(root)
|
|||||||
local uis = Div{parent=page_div,x=2,width=p_width}
|
local uis = Div{parent=page_div,x=2,width=p_width}
|
||||||
local fps = Div{parent=page_div,x=2,width=p_width}
|
local fps = Div{parent=page_div,x=2,width=p_width}
|
||||||
local gls = Div{parent=page_div,x=2,width=p_width}
|
local gls = Div{parent=page_div,x=2,width=p_width}
|
||||||
local panes = { home, search, use, uis, fps, gls }
|
local lnk = Div{parent=page_div,x=2,width=p_width}
|
||||||
|
local panes = { home, search, use, uis, fps, gls, lnk }
|
||||||
|
|
||||||
local doc_map = {}
|
local doc_map = {}
|
||||||
local search_db = {}
|
local search_db = {}
|
||||||
@ -100,6 +103,7 @@ local function new_view(root)
|
|||||||
PushButton{parent=home,text="Operator UIs >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=uis_page.nav_to}
|
PushButton{parent=home,text="Operator UIs >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=uis_page.nav_to}
|
||||||
PushButton{parent=home,text="Front Panels >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fps_page.nav_to}
|
PushButton{parent=home,text="Front Panels >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fps_page.nav_to}
|
||||||
PushButton{parent=home,text="Glossary >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_page.nav_to}
|
PushButton{parent=home,text="Glossary >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_page.nav_to}
|
||||||
|
PushButton{parent=home,y=10,text="Wiki and Discord >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=lnk_page.nav_to}
|
||||||
|
|
||||||
TextBox{parent=search,y=1,text="Search",alignment=ALIGN.CENTER}
|
TextBox{parent=search,y=1,text="Search",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
@ -113,34 +117,41 @@ local function new_view(root)
|
|||||||
|
|
||||||
function func_ref.run_search()
|
function func_ref.run_search()
|
||||||
local query = string.lower(query_field.get_value())
|
local query = string.lower(query_field.get_value())
|
||||||
local s_results = { {}, {}, {} }
|
local s_results = { {}, {}, {}, {} }
|
||||||
|
|
||||||
search_results.remove_all()
|
search_results.remove_all()
|
||||||
|
|
||||||
if string.len(query) < 3 then
|
if string.len(query) < 2 then
|
||||||
TextBox{parent=search_results,text="Search requires at least 3 characters."}
|
TextBox{parent=search_results,text="Search requires at least 2 characters."}
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local start = util.time_ms()
|
||||||
|
|
||||||
for _, entry in ipairs(search_db) do
|
for _, entry in ipairs(search_db) do
|
||||||
local s_start, _ = string.find(entry[1], query, 1, true)
|
local s_start, s_end = string.find(entry[1], query, 1, true)
|
||||||
|
|
||||||
if s_start == nil then
|
if s_start == nil then
|
||||||
elseif s_start == 1 then
|
elseif s_start == 1 then
|
||||||
-- best match, start of key
|
if s_end == string.len(entry[1]) then
|
||||||
|
-- best match: full match
|
||||||
table.insert(s_results[1], entry)
|
table.insert(s_results[1], entry)
|
||||||
|
else
|
||||||
|
-- very good match, start of key
|
||||||
|
table.insert(s_results[2], entry)
|
||||||
|
end
|
||||||
elseif string.sub(query, s_start - 1, s_start) == " " then
|
elseif string.sub(query, s_start - 1, s_start) == " " then
|
||||||
-- start of word, good match
|
-- start of word, good match
|
||||||
table.insert(s_results[2], entry)
|
table.insert(s_results[3], entry)
|
||||||
else
|
else
|
||||||
-- basic match in content
|
-- basic match in content
|
||||||
table.insert(s_results[3], entry)
|
table.insert(s_results[4], entry)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local empty = true
|
local empty = true
|
||||||
|
|
||||||
for tier = 1, 3 do
|
for tier = 1, 4 do
|
||||||
for idx = 1, #s_results[tier] do
|
for idx = 1, #s_results[tier] do
|
||||||
local entry = s_results[tier][idx]
|
local entry = s_results[tier][idx]
|
||||||
TextBox{parent=search_results,text=entry[3].." >",fg_bg=cpair(colors.gray,colors.black)}
|
TextBox{parent=search_results,text=entry[3].." >",fg_bg=cpair(colors.gray,colors.black)}
|
||||||
@ -150,6 +161,8 @@ local function new_view(root)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
log.debug("App.Guide: search for \"" .. query .. "\" completed in " .. (util.time_ms() - start) .. "ms")
|
||||||
|
|
||||||
if empty then
|
if empty then
|
||||||
TextBox{parent=search_results,text="No results found."}
|
TextBox{parent=search_results,text="No results found."}
|
||||||
end
|
end
|
||||||
@ -188,7 +201,8 @@ local function new_view(root)
|
|||||||
local unit_gen_page = guide_section(sect_construct_data, annunc_page, "Unit General", docs.annunc.unit.main_section, 170)
|
local unit_gen_page = guide_section(sect_construct_data, annunc_page, "Unit General", docs.annunc.unit.main_section, 170)
|
||||||
local unit_rps_page = guide_section(sect_construct_data, annunc_page, "Unit RPS", docs.annunc.unit.rps_section, 100)
|
local unit_rps_page = guide_section(sect_construct_data, annunc_page, "Unit RPS", docs.annunc.unit.rps_section, 100)
|
||||||
local unit_rcs_page = guide_section(sect_construct_data, annunc_page, "Unit RCS", docs.annunc.unit.rcs_section, 170)
|
local unit_rcs_page = guide_section(sect_construct_data, annunc_page, "Unit RCS", docs.annunc.unit.rcs_section, 170)
|
||||||
local fac_annunc_page = guide_section(sect_construct_data, annunc_page, "Facility", docs.annunc.unit.fac_section, 100)
|
|
||||||
|
local fac_annunc_page = guide_section(sect_construct_data, annunc_page, "Facility", docs.annunc.facility.main_section, 110)
|
||||||
|
|
||||||
PushButton{parent=annunc_div,y=3,text="Unit General >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_gen_page.nav_to}
|
PushButton{parent=annunc_div,y=3,text="Unit General >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_gen_page.nav_to}
|
||||||
PushButton{parent=annunc_div,text="Unit RPS >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_rps_page.nav_to}
|
PushButton{parent=annunc_div,text="Unit RPS >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_rps_page.nav_to}
|
||||||
@ -199,21 +213,39 @@ local function new_view(root)
|
|||||||
TextBox{parent=fps,y=1,text="Front Panels",alignment=ALIGN.CENTER}
|
TextBox{parent=fps,y=1,text="Front Panels",alignment=ALIGN.CENTER}
|
||||||
PushButton{parent=fps,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
PushButton{parent=fps,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
||||||
|
|
||||||
PushButton{parent=fps,y=3,text="Common Items >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
local fp_common_page = guide_section(sect_construct_data, fps_page, "Common Items", docs.fp.common, 100)
|
||||||
PushButton{parent=fps,text="Reactor PLC >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
local fp_rplc_page = guide_section(sect_construct_data, fps_page, "Reactor PLC", docs.fp.r_plc, 180)
|
||||||
PushButton{parent=fps,text="RTU Gateway >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
local fp_rtu_page = guide_section(sect_construct_data, fps_page, "RTU Gateway", docs.fp.rtu_gw, 100)
|
||||||
PushButton{parent=fps,text="Supervisor >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
local fp_supervisor_page = guide_section(sect_construct_data, fps_page, "Supervisor", docs.fp.supervisor, 160)
|
||||||
|
|
||||||
|
PushButton{parent=fps,y=3,text="Common Items >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_common_page.nav_to}
|
||||||
|
PushButton{parent=fps,text="Reactor PLC >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_rplc_page.nav_to}
|
||||||
|
PushButton{parent=fps,text="RTU Gateway >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_rtu_page.nav_to}
|
||||||
|
PushButton{parent=fps,text="Supervisor >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_supervisor_page.nav_to}
|
||||||
PushButton{parent=fps,text="Coordinator >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
PushButton{parent=fps,text="Coordinator >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||||
|
|
||||||
TextBox{parent=gls,y=1,text="Glossary",alignment=ALIGN.CENTER}
|
TextBox{parent=gls,y=1,text="Glossary",alignment=ALIGN.CENTER}
|
||||||
PushButton{parent=gls,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
PushButton{parent=gls,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
||||||
|
|
||||||
local gls_abbv_page = guide_section(sect_construct_data, gls_page, "Abbreviations", docs.glossary.abbvs, 130)
|
local gls_abbv_page = guide_section(sect_construct_data, gls_page, "Abbreviations", docs.glossary.abbvs, 140)
|
||||||
local gls_term_page = guide_section(sect_construct_data, gls_page, "Terminology", docs.glossary.terms, 100)
|
local gls_term_page = guide_section(sect_construct_data, gls_page, "Terminology", docs.glossary.terms, 100)
|
||||||
|
|
||||||
PushButton{parent=gls,y=3,text="Abbreviations >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_abbv_page.nav_to}
|
PushButton{parent=gls,y=3,text="Abbreviations >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_abbv_page.nav_to}
|
||||||
PushButton{parent=gls,text="Terminology >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_term_page.nav_to}
|
PushButton{parent=gls,text="Terminology >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_term_page.nav_to}
|
||||||
|
|
||||||
|
TextBox{parent=lnk,y=1,text="Wiki and Discord",alignment=ALIGN.CENTER}
|
||||||
|
PushButton{parent=lnk,x=1,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
||||||
|
|
||||||
|
lnk.line_break()
|
||||||
|
TextBox{parent=lnk,text="GitHub",fg_bg=cpair(colors.lightGray,colors.black)}
|
||||||
|
TextBox{parent=lnk,text="https://github.com/MikaylaFischler/cc-mek-scada"}
|
||||||
|
lnk.line_break()
|
||||||
|
TextBox{parent=lnk,text="Wiki",fg_bg=cpair(colors.lightGray,colors.black)}
|
||||||
|
TextBox{parent=lnk,text="https://github.com/MikaylaFischler/cc-mek-scada/wiki"}
|
||||||
|
lnk.line_break()
|
||||||
|
TextBox{parent=lnk,text="Discord",fg_bg=cpair(colors.lightGray,colors.black)}
|
||||||
|
TextBox{parent=lnk,text="discord.gg/R9NSCkhcwt"}
|
||||||
|
|
||||||
-- setup multipane
|
-- setup multipane
|
||||||
local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||||
app.set_root_pane(u_pane)
|
app.set_root_pane(u_pane)
|
||||||
|
|||||||
@ -1,13 +1,65 @@
|
|||||||
|
local const = require("scada-common.constants")
|
||||||
|
|
||||||
local docs = {}
|
local docs = {}
|
||||||
|
|
||||||
|
---@enum DOC_ITEM_TYPE
|
||||||
|
local DOC_ITEM_TYPE = {
|
||||||
|
SECTION = 1,
|
||||||
|
SUBSECTION = 2,
|
||||||
|
TEXT = 3,
|
||||||
|
LIST = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
---@enum DOC_LIST_TYPE
|
||||||
|
local DOC_LIST_TYPE = {
|
||||||
|
BULLET = 1,
|
||||||
|
NUMBERED = 2,
|
||||||
|
INDICATOR = 3,
|
||||||
|
LED = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
docs.DOC_ITEM_TYPE = DOC_ITEM_TYPE
|
||||||
|
docs.DOC_LIST_TYPE = DOC_LIST_TYPE
|
||||||
|
|
||||||
local target
|
local target
|
||||||
|
|
||||||
local function doc(key, name, desc)
|
local function sect(name)
|
||||||
---@class pocket_doc_item
|
---@class pocket_doc_sect
|
||||||
local item = { key = key, name = name, desc = desc }
|
local item = { type = DOC_ITEM_TYPE.SECTION, name = name }
|
||||||
table.insert(target, item)
|
table.insert(target, item)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param key string item identifier for linking
|
||||||
|
---@param name string item name for display
|
||||||
|
---@param text_a string text body, or the subtitle/note if text_b is specified
|
||||||
|
---@param text_b? string text body if subtitle/note was specified
|
||||||
|
local function doc(key, name, text_a, text_b)
|
||||||
|
if text_b == nil then
|
||||||
|
text_b = text_a
|
||||||
|
---@diagnostic disable-next-line: cast-local-type
|
||||||
|
text_a = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
---@class pocket_doc_subsect
|
||||||
|
local item = { type = DOC_ITEM_TYPE.SUBSECTION, key = key, name = name, subtitle = text_a, body = text_b }
|
||||||
|
table.insert(target, item)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function text(body)
|
||||||
|
---@class pocket_doc_text
|
||||||
|
local item = { type = DOC_ITEM_TYPE.TEXT, text = body }
|
||||||
|
table.insert(target, item)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param type DOC_LIST_TYPE
|
||||||
|
---@param items table
|
||||||
|
---@param colors table|nil colors for indicators or nil for normal lists
|
||||||
|
local function list(type, items, colors)
|
||||||
|
---@class pocket_doc_list
|
||||||
|
local list_def = { type = DOC_ITEM_TYPE.LIST, list_type = type, items = items, colors = colors }
|
||||||
|
table.insert(target, list_def)
|
||||||
|
end
|
||||||
|
|
||||||
-- important to note in the future: The PLC should always be in a chunk with the reactor to ensure it can protect it on chunk load if you do not keep it all chunk loaded
|
-- important to note in the future: The PLC should always be in a chunk with the reactor to ensure it can protect it on chunk load if you do not keep it all chunk loaded
|
||||||
|
|
||||||
docs.alarms = {}
|
docs.alarms = {}
|
||||||
@ -28,15 +80,20 @@ doc("TurbineTripAlarm", "Turbine Trip", "A turbine stopped rotating, likely due
|
|||||||
|
|
||||||
docs.annunc = {
|
docs.annunc = {
|
||||||
unit = {
|
unit = {
|
||||||
main_section = {}, rps_section = {}, rcs_section = {}, fac_section = {}
|
main_section = {}, rps_section = {}, rcs_section = {}
|
||||||
|
},
|
||||||
|
facility = {
|
||||||
|
main_section = {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
target = docs.annunc.unit.main_section
|
target = docs.annunc.unit.main_section
|
||||||
|
sect("Unit Status")
|
||||||
doc("PLCOnline", "PLC Online", "Indicates if the fission reactor PLC is connected. If it isn't, check that your PLC is on and configured properly.")
|
doc("PLCOnline", "PLC Online", "Indicates if the fission reactor PLC is connected. If it isn't, check that your PLC is on and configured properly.")
|
||||||
doc("PLCHeartbeat", "PLC Heartbeat", "An indicator of status data being live. As status messages are received from the PLC, this light will turn on and off. If it gets stuck, the supervisor has stopped receiving data or a screen has frozen.")
|
doc("PLCHeartbeat", "PLC Heartbeat", "An indicator of status data being live. As status messages are received from the PLC, this light will turn on and off. If it gets stuck, the supervisor has stopped receiving data or a screen has frozen.")
|
||||||
doc("RadiationMonitor", "Radiation Monitor", "On if at least one environment detector is connected and assigned to this unit.")
|
doc("RadiationMonitor", "Radiation Monitor", "On if at least one environment detector is connected and assigned to this unit.")
|
||||||
doc("AutoControl", "Automatic Control", "On if the reactor is under the control of one of the automatic control modes.")
|
doc("AutoControl", "Automatic Control", "On if the reactor is under the control of one of the automatic control modes.")
|
||||||
|
sect("Safety Status")
|
||||||
doc("ReactorSCRAM", "Reactor SCRAM", "On if the reactor protection system is holding the reactor SCRAM'd.")
|
doc("ReactorSCRAM", "Reactor SCRAM", "On if the reactor protection system is holding the reactor SCRAM'd.")
|
||||||
doc("ManualReactorSCRAM", "Manual Reactor SCRAM", "On if the operator (you) initiated a SCRAM.")
|
doc("ManualReactorSCRAM", "Manual Reactor SCRAM", "On if the operator (you) initiated a SCRAM.")
|
||||||
doc("AutoReactorSCRAM", "Auto Reactor SCRAM", "On if the automatic control system initiated a SCRAM. The main view screen annunciator will have an indication as to why.")
|
doc("AutoReactorSCRAM", "Auto Reactor SCRAM", "On if the automatic control system initiated a SCRAM. The main view screen annunciator will have an indication as to why.")
|
||||||
@ -78,21 +135,112 @@ doc("TurbineOverSpeed", "Turbine Over Speed", "The turbine is at steam capacity,
|
|||||||
doc("GeneratorTrip", "Generator Trip", "The turbine is no longer outputting power due to it having nowhere to go. Likely due to full power storage. This will lead to a Turbine Trip if not addressed.")
|
doc("GeneratorTrip", "Generator Trip", "The turbine is no longer outputting power due to it having nowhere to go. Likely due to full power storage. This will lead to a Turbine Trip if not addressed.")
|
||||||
doc("TurbineTrip", "Turbine Trip", "The turbine has reached its maximum power charge and has stopped rotating, and as a result stopped cooling steam to water. Ensure the turbine has somewhere to output power, as this is the most common cause of reactor meltdowns. However, the likelihood of a meltdown with this system in place is much lower, especially with emergency coolant helping during turbine trips.")
|
doc("TurbineTrip", "Turbine Trip", "The turbine has reached its maximum power charge and has stopped rotating, and as a result stopped cooling steam to water. Ensure the turbine has somewhere to output power, as this is the most common cause of reactor meltdowns. However, the likelihood of a meltdown with this system in place is much lower, especially with emergency coolant helping during turbine trips.")
|
||||||
|
|
||||||
target = docs.annunc.unit.fac_section
|
target = docs.annunc.facility.main_section
|
||||||
doc("?", "Unit Systems Online", "All unit systems (reactors, boilers, and turbines) are connected.")
|
sect("Connectivity")
|
||||||
doc("?", "Radiation Monitor", "At least one facility radiation monitor is connected")
|
doc("all_sys_ok", "Unit Systems Online", "All unit systems (reactors, boilers, and turbines) are connected.")
|
||||||
doc("?", "Induction Matrix", "The induction matrix is connected.")
|
doc("rad_computed_status", "Radiation Monitor", "At least one facility radiation monitor is connected")
|
||||||
doc("?", "SPS Connected", "Indicates if the super-critical phase shifter is connected.")
|
doc("im_computed_status", "Induction Matrix", "The induction matrix is connected.")
|
||||||
doc("?", "Configured Units Ready", "All units assigned to automatic control are ready to run automatic control.")
|
doc("sps_computed_status", "SPS Connected", "Indicates if the super-critical phase shifter is connected.")
|
||||||
doc("?", "Process Active", "Automatic process control is active.")
|
sect("Automatic Control")
|
||||||
doc("?", "Process Ramping", "Automatic process control is performing an initial ramp-up of the reactors for later PID control (generation and charge mode).")
|
doc("auto_ready", "Configured Units Ready", "All units assigned to automatic control are ready to run automatic control.")
|
||||||
doc("?", "Min/Max Burn Rate", "Auto control has either commanded 0 mB/t or the maximum total burn rate available (from assigned units).")
|
doc("auto_active", "Process Active", "Automatic process control is active.")
|
||||||
doc("?", "Automatic SCRAM", "Automatic control system SCRAM'ed the assigned reactors due to a safety hazard, shown by the below indicators.")
|
doc("auto_ramping", "Process Ramping", "Automatic process control is performing an initial ramp-up of the reactors for later PID control (generation and charge mode).")
|
||||||
doc("?", "Matrix Disconnected", "Automatic SCRAM occurred due to loss of induction matrix connection.")
|
doc("auto_saturated", "Min/Max Burn Rate", "Auto control has either commanded 0 mB/t or the maximum total burn rate available (from assigned units).")
|
||||||
doc("?", "Matrix Charge High", "Automatic SCRAM occurred due to induction matrix charge exceeding acceptable limit.")
|
sect("Automatic SCRAM")
|
||||||
doc("?", "Unit Critical Alarm", "Automatic SCRAM occurred due to critical level unit alarm(s).")
|
doc("auto_scram", "Automatic SCRAM", "Automatic control system SCRAM'ed the assigned reactors due to a safety hazard, shown by the below indicators.")
|
||||||
doc("?", "Facility Radiation High", "Automatic SCRAM occurred due to high facility radiation levels.")
|
doc("as_matrix_dc", "Matrix Disconnected", "Automatic SCRAM occurred due to loss of induction matrix connection.")
|
||||||
doc("?", "Gen. Control Fault", "Automatic SCRAM occurred due to assigned units being degraded/no longer ready during generation mode. The system will automatically resume (starting with initial ramp) once the problem is resolved.")
|
doc("as_matrix_fill", "Matrix Charge High", "Automatic SCRAM occurred due to induction matrix charge exceeding acceptable limit.")
|
||||||
|
doc("as_crit_alarm", "Unit Critical Alarm", "Automatic SCRAM occurred due to critical level unit alarm(s).")
|
||||||
|
doc("as_radiation", "Facility Radiation High", "Automatic SCRAM occurred due to high facility radiation levels.")
|
||||||
|
doc("as_gen_fault", "Gen. Control Fault", "Automatic SCRAM occurred due to assigned units being degraded/no longer ready during generation mode. The system will automatically resume (starting with initial ramp) once the problem is resolved.")
|
||||||
|
|
||||||
|
docs.fp = {
|
||||||
|
common = {}, r_plc = {}, rtu_gw = {}, supervisor = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
--comp id "This must never be the identical between devices, and that can only happen if you duplicate a computer (such as middle-click on it and place it elsewhere in creative mode)."
|
||||||
|
|
||||||
|
target = docs.fp.common
|
||||||
|
sect("Core Status")
|
||||||
|
doc("fp_status", "STATUS", "This is always lit, except on the Reactor PLC (see Reactor PLC section).")
|
||||||
|
doc("fp_heartbeat", "HEARTBEAT", "This alternates between lit and unlit as the main loop on the device runs. If this freezes, something is wrong and the logs will indicate why.")
|
||||||
|
sect("Hardware & Network")
|
||||||
|
doc("fp_modem", "MODEM", "This lights up if the wireless/ender modem is connected. In parentheses is the unique computer ID of this device, which will show up in places such as the supervisor's connection lists.")
|
||||||
|
doc("fp_modem", "NETWORK", "This is present when in standard color modes and indicates the network status using multiple colors.")
|
||||||
|
list(DOC_LIST_TYPE.LED, { "not linked", "linked", "link denied", "bad comms version", "duplicate PLC" }, { colors.gray, colors.green, colors.red, colors.orange, colors.yellow })
|
||||||
|
text("You can fix \"bad comms version\" by ensuring all devices are up-to-date, as this indicates a communications protocol version mismatch. Note that yellow is Reactor PLC-specific, indicating duplicate unit IDs in use.")
|
||||||
|
doc("fp_nt_linked", "NT LINKED", "(color accessibility modes only)", "This indicates the device is linked to the supervisor.")
|
||||||
|
doc("fp_nt_version", "NT VERSION", "(color accessibility modes only)", "This indicates the communications versions of the supervisor and this device do not match. Make sure everything is up-to-date.")
|
||||||
|
sect("Versions")
|
||||||
|
doc("fp_fw", "FW", "Firmware application version of this device.")
|
||||||
|
doc("fp_nt", "NT", "Network (comms) version this device has. These must match between devices in order for them to connect.")
|
||||||
|
|
||||||
|
target = docs.fp.r_plc
|
||||||
|
sect("Core Status")
|
||||||
|
doc("fp_status", "STATUS", "This is green once the PLC is initialized and OK (has all its peripherals) and red if something is wrong, in which case you should refer to the other indicator lights (REACTOR & MODEM).")
|
||||||
|
sect("Hardware & Network")
|
||||||
|
doc("fp_rplc_reactor", "REACTOR", "This indicates the status of the connected reactor peripheral.")
|
||||||
|
list(DOC_LIST_TYPE.LED, { "disconnected", "unformed", "ok" }, { colors.red, colors.yellow, colors.green })
|
||||||
|
doc("fp_nt_collision", "NT COLLISION", "(color accessibility modes only)", "This indicates the Reactor PLC unit ID is a duplicate of another already connected Reactor PLC.")
|
||||||
|
sect("Co-Routine States")
|
||||||
|
doc("fp_rplc_rt_main", "RT MAIN", "This lights up as long as the device's main loop co-routine is running, which it should be as long as STATUS is green.")
|
||||||
|
doc("fp_rplc_rt_rps", "RT RPS", "This should always be lit up if a reactor is connected as it indicates the RPS co-routine is running, otherwise safety checks will not be running.")
|
||||||
|
doc("fp_rplc_rt_ctx", "RT COMMS TX", "This should always be lit if the Reactor PLC is not running in standalone mode, as it indicates the communications transmission co-routine is running.")
|
||||||
|
doc("fp_rplc_rt_crx", "RT COMMS RX", "This should always be lit if the Reactor PLC is not running in standalone mode, as it indicates the communications receiver/handler co-routine is running.")
|
||||||
|
doc("fp_rplc_rt_spctl", "RT SPCTL", "This should always be lit if the Reactor PLC is not running in standalone mode, as it indicates the process setpoint controller co-routine is running.")
|
||||||
|
sect("Status")
|
||||||
|
doc("fp_rct_active", "RCT ACTIVE", "The reactor is active (running).")
|
||||||
|
doc("fp_emer_cool", "EMER COOLANT", "This is only present if PLC-controlled emergency coolant is configured on that device. When lit, it indicates that it has been activated.")
|
||||||
|
doc("fp_rps_trip", "RPS TRIP", "Flashes when the RPS has SCRAM'd the reactor due to a safety trip.")
|
||||||
|
sect("RPS Conditions")
|
||||||
|
doc("fp_rps_man", "MANUAL", "The RPS was tripped manually (SCRAM by user, not via the Mekanism Reactor UI).")
|
||||||
|
doc("fp_rps_auto", "AUTOMATIC", "The RPS was tripped by the supervisor automatically.")
|
||||||
|
doc("fp_rps_to", "TIMEOUT", "The RPS tripped due to losing the supervisor connection.")
|
||||||
|
doc("fp_rps_pflt", "PLC FAULT", "The RPS tripped due to a peripheral error.")
|
||||||
|
doc("fp_rps_rflt", "RCT FAULT", "The RPS tripped due to the reactor not being formed.")
|
||||||
|
doc("fp_rps_temp", "HI DAMAGE", "The RPS tripped due to being >=" .. const.RPS_LIMITS.MAX_DAMAGE_PERCENT .. "% damaged.")
|
||||||
|
doc("fp_rps_temp", "HI TEMP", "The RPS tripped due to high reactor temperature (>=" .. const.RPS_LIMITS.MAX_DAMAGE_TEMPERATURE .. "K).")
|
||||||
|
doc("fp_rps_fuel", "LO FUEL", "The RPS tripped due to having no fuel.")
|
||||||
|
doc("fp_rps_waste", "HI WASTE", "The RPS tripped due to having high levels of waste (>" .. (const.RPS_LIMITS.MAX_WASTE_FILL * 100) .. "%).")
|
||||||
|
doc("fp_rps_ccool", "LO CCOOLANT", "The RPS tripped due to having low levels of cooled coolant (<" .. (const.RPS_LIMITS.MIN_COOLANT_FILL * 100) .. "%).")
|
||||||
|
doc("fp_rps_ccool", "HI HCOOLANT", "The RPS tripped due to having high levels of heated coolant (>" .. (const.RPS_LIMITS.MAX_HEATED_COLLANT_FILL * 100) .. "%).")
|
||||||
|
|
||||||
|
target = docs.fp.rtu_gw
|
||||||
|
sect("Co-Routine States")
|
||||||
|
doc("fp_rtu_rt_main", "RT MAIN", "This indicates if the device's main loop co-routine is running.")
|
||||||
|
doc("fp_rtu_rt_comms", "RT COMMS", "This indicates if the communications handler co-routine is running.")
|
||||||
|
sect("Device List")
|
||||||
|
doc("fp_rtu_rt", "RT", "In each RTU entry row, an RT light indicates if the co-routine for that RTU unit is running. This is never lit for redstone units.")
|
||||||
|
doc("fp_rtu_rt", "Device Status", "In each RTU entry row, the light to the left of the device name indicates its peripheral status.")
|
||||||
|
list(DOC_LIST_TYPE.LED, { "disconnected", "faulted", "unformed", "ok" }, { colors.red, colors.orange, colors.yellow, colors.green })
|
||||||
|
text("Note that disconnected devices lack detailed information and will not be modifiable in configuration until re-connected.")
|
||||||
|
doc("fp_rtu_rt", "Device Assignment", "In each RTU entry row, the device identification is to the right of the status light. This begins with the device type and its index followed by its assignment after the \x1a, which is a unit or the facility (FACIL). Unit 1's 3rd turbine would show up as 'TURBINE 3 \x1a UNIT 1'.")
|
||||||
|
|
||||||
|
target = docs.fp.supervisor
|
||||||
|
sect("Round Trip Times")
|
||||||
|
doc("fp_sv_fw", "RTT", "Each connection has a round trip time, or RTT. Since the supervisor updates at a rate of 150ms, RTTs from ~150ms to ~300ms are typical. Higher RTTs indicate lag, and if they end up in the thousands there will be performance problems.")
|
||||||
|
list(DOC_LIST_TYPE.BULLET, { "green: <=300ms", "yellow: <=500ms ", "red: >500ms" })
|
||||||
|
sect("SVR Tab")
|
||||||
|
text("This tab includes information about the supervisor, covered by 'Common Items'.")
|
||||||
|
sect("PLC Tab")
|
||||||
|
text("This tab lists the expected PLC connections based on the number of configured units. Status information about each connection is shown when linked.")
|
||||||
|
doc("fp_sv_link", "LINK", "This indicates if the reactor PLC is linked.")
|
||||||
|
doc("fp_sv_p_cmpid", "PLC Computer ID", "This shows the computer ID of the reactor PLC, or --- if disconnected.")
|
||||||
|
doc("fp_sv_p_fw", "PLC FW", "This shows the firmware version of the reactor PLC.")
|
||||||
|
sect("RTU Tab")
|
||||||
|
text("As RTU gateways connect to the supervisor, they will show up here along with some information.")
|
||||||
|
doc("fp_sv_r_cmpid", "RTU Computer ID", "At the start of the entry is an @ sign followed by the computer ID of the RTU gateway.")
|
||||||
|
doc("fp_sv_r_units", "UNITS", "This is a count of the number of RTUs configured on the RTU gateway (each line on the RTU gateway's front panel).")
|
||||||
|
doc("fp_sv_r_fw", "RTU FW", "This shows the firmware version of the RTU gateway.")
|
||||||
|
sect("PKT Tab")
|
||||||
|
text("As pocket computers connect to the supervisor, they will show up here along with some information. The properties listed are the same as with RTU gateways (except for UNITS), so they will not be further described here.")
|
||||||
|
sect("DEV Tab")
|
||||||
|
text("If nothing is connected, this will list all the expected RTU devices that aren't found. This page should be blank if everything is connected and configured correctly. If not, it will list certain types of detectable problems.")
|
||||||
|
doc("fp_sv_d_miss", "MISSING", "These items list missing devices, with the details that should be used in the RTU's configuration.")
|
||||||
|
doc("fp_sv_d_oor", "BAD INDEX", "If you have a configuration entry that has an index outside of the maximum number of devices configured on the supervisor, this will show up indicating what entry is incorrect. For example, if you specified a unit has 2 turbines and a #3 connected, it would show up here as out of range.")
|
||||||
|
doc("fp_sv_d_dupe", "DUPLICATE", "If a device tries to connect that is configured the same as another, it will be rejected and show up here. If you try to connect two #1 turbines for a unit, that would fail and one would appear here.")
|
||||||
|
sect("INF Tab")
|
||||||
|
text("This tab gives information about the other tabs, along with extra details on the DEV tab.")
|
||||||
|
|
||||||
docs.glossary = {
|
docs.glossary = {
|
||||||
abbvs = {}, terms = {}
|
abbvs = {}, terms = {}
|
||||||
@ -113,6 +261,7 @@ doc("G_PPM", "PPM", "Protected Peripheral Manager. This is an abstraction layer
|
|||||||
doc("G_RCP", "RCP", "Reactor Coolant Pump. This is from real-world terminology with water-cooled (boiling water and pressurized water) reactors, but in this system it just reflects to the functioning of reactor coolant flow. See the annunciator page on it for more information.")
|
doc("G_RCP", "RCP", "Reactor Coolant Pump. This is from real-world terminology with water-cooled (boiling water and pressurized water) reactors, but in this system it just reflects to the functioning of reactor coolant flow. See the annunciator page on it for more information.")
|
||||||
doc("G_RCS", "RCS", "Reactor Cooling System. The combination of all machines used to cool the reactor (turbines, boilers, dynamic tanks).")
|
doc("G_RCS", "RCS", "Reactor Cooling System. The combination of all machines used to cool the reactor (turbines, boilers, dynamic tanks).")
|
||||||
doc("G_RPS", "RPS", "Reactor Protection System. A component of the reactor PLC responsible for keeping the reactor safe.")
|
doc("G_RPS", "RPS", "Reactor Protection System. A component of the reactor PLC responsible for keeping the reactor safe.")
|
||||||
|
doc("G_RTU", "RT", "co-RouTine. This is used to identify the status of core Lua co-routines on front panels.")
|
||||||
doc("G_RTU", "RTU", "Remote Terminal Unit. Provides monitoring to and basic output from a SCADA system, interfacing with various types of devices/interfaces.")
|
doc("G_RTU", "RTU", "Remote Terminal Unit. Provides monitoring to and basic output from a SCADA system, interfacing with various types of devices/interfaces.")
|
||||||
doc("G_SCADA", "SCADA", "Supervisory Control and Data Acquisition. A control systems architecture used in a wide variety process control applications.")
|
doc("G_SCADA", "SCADA", "Supervisory Control and Data Acquisition. A control systems architecture used in a wide variety process control applications.")
|
||||||
doc("G_SVR", "SVR", "Supervisor. Abbreviation for the supervisory computer.")
|
doc("G_SVR", "SVR", "Supervisor. Abbreviation for the supervisory computer.")
|
||||||
|
|||||||
@ -7,6 +7,7 @@ local util = require("scada-common.util")
|
|||||||
local iocontrol = require("pocket.iocontrol")
|
local iocontrol = require("pocket.iocontrol")
|
||||||
local pocket = require("pocket.pocket")
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
|
local control_app = require("pocket.ui.apps.control")
|
||||||
local diag_apps = require("pocket.ui.apps.diag_apps")
|
local diag_apps = require("pocket.ui.apps.diag_apps")
|
||||||
local dummy_app = require("pocket.ui.apps.dummy_app")
|
local dummy_app = require("pocket.ui.apps.dummy_app")
|
||||||
local guide_app = require("pocket.ui.apps.guide")
|
local guide_app = require("pocket.ui.apps.guide")
|
||||||
@ -62,6 +63,7 @@ local function init(main)
|
|||||||
-- create all the apps & pages
|
-- create all the apps & pages
|
||||||
home_page(page_div)
|
home_page(page_div)
|
||||||
unit_app(page_div)
|
unit_app(page_div)
|
||||||
|
control_app(page_div)
|
||||||
guide_app(page_div)
|
guide_app(page_div)
|
||||||
loader_app(page_div)
|
loader_app(page_div)
|
||||||
sys_apps(page_div)
|
sys_apps(page_div)
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local docs = require("pocket.ui.docs")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
|
|
||||||
local Div = require("graphics.elements.div")
|
local Div = require("graphics.elements.div")
|
||||||
@ -9,9 +11,15 @@ local TextBox = require("graphics.elements.textbox")
|
|||||||
|
|
||||||
local PushButton = require("graphics.elements.controls.push_button")
|
local PushButton = require("graphics.elements.controls.push_button")
|
||||||
|
|
||||||
|
local IndicatorLight = require("graphics.elements.indicators.light")
|
||||||
|
local LED = require("graphics.elements.indicators.led")
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
local ALIGN = core.ALIGN
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local DOC_TYPE = docs.DOC_ITEM_TYPE
|
||||||
|
local LIST_TYPE = docs.DOC_LIST_TYPE
|
||||||
|
|
||||||
-- new guide documentation section
|
-- new guide documentation section
|
||||||
---@param data _guide_section_constructor_data
|
---@param data _guide_section_constructor_data
|
||||||
---@param base_page nav_tree_page
|
---@param base_page nav_tree_page
|
||||||
@ -34,20 +42,57 @@ return function (data, base_page, title, items, scroll_height)
|
|||||||
TextBox{parent=section_view_div,y=1,text=title,alignment=ALIGN.CENTER}
|
TextBox{parent=section_view_div,y=1,text=title,alignment=ALIGN.CENTER}
|
||||||
PushButton{parent=section_view_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=section_page.nav_to}
|
PushButton{parent=section_view_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=section_page.nav_to}
|
||||||
|
|
||||||
local name_list = ListBox{parent=section_div,x=1,y=3,scroll_height=30,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
local name_list = ListBox{parent=section_div,x=1,y=3,scroll_height=60,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||||
local def_list = ListBox{parent=section_view_div,x=1,y=3,scroll_height=scroll_height,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
local def_list = ListBox{parent=section_view_div,x=1,y=3,scroll_height=scroll_height,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
local _end
|
local sect_id = 1
|
||||||
|
local page_end
|
||||||
|
|
||||||
for i = 1, #items do
|
for i = 1, #items do
|
||||||
local item = items[i] ---@type pocket_doc_item
|
local item = items[i] ---@type pocket_doc_sect|pocket_doc_subsect|pocket_doc_text|pocket_doc_list
|
||||||
|
|
||||||
local anchor = TextBox{parent=def_list,text=item.name,anchor=true,fg_bg=cpair(colors.blue,colors.black)}
|
if item.type == DOC_TYPE.SECTION then
|
||||||
TextBox{parent=def_list,text=item.desc}
|
---@cast item pocket_doc_sect
|
||||||
_end = Div{parent=def_list,height=1,can_focus=true}
|
|
||||||
|
local title_text = sect_id.."."
|
||||||
|
local title_offs = string.len(title_text) + 2
|
||||||
|
|
||||||
|
local sect_title = Div{parent=def_list,height=1}
|
||||||
|
TextBox{parent=sect_title,x=1,text=title_text,fg_bg=cpair(colors.lightGray,colors.black)}
|
||||||
|
local anchor = TextBox{parent=sect_title,x=title_offs,y=1,text=item.name,anchor=true,fg_bg=cpair(colors.green,colors.black)}
|
||||||
|
|
||||||
|
page_end = Div{parent=def_list,height=1,can_focus=true}
|
||||||
|
|
||||||
local function view()
|
local function view()
|
||||||
_end.focus()
|
page_end.focus()
|
||||||
|
view_page.nav_to()
|
||||||
|
anchor.focus()
|
||||||
|
end
|
||||||
|
|
||||||
|
if #name_list.get_children() > 0 then
|
||||||
|
local _ = Div{parent=name_list,height=1}
|
||||||
|
end
|
||||||
|
|
||||||
|
local name_title = Div{parent=name_list,height=1}
|
||||||
|
TextBox{parent=name_title,x=1,text=title_text,fg_bg=cpair(colors.lightGray,colors.black)}
|
||||||
|
PushButton{parent=name_title,x=title_offs,y=1,text=item.name,alignment=ALIGN.LEFT,fg_bg=cpair(colors.green,colors.black),active_fg_bg=btn_active,callback=view}
|
||||||
|
|
||||||
|
sect_id = sect_id + 1
|
||||||
|
elseif item.type == DOC_TYPE.SUBSECTION then
|
||||||
|
---@cast item pocket_doc_subsect
|
||||||
|
|
||||||
|
local anchor = TextBox{parent=def_list,text=item.name,anchor=true,fg_bg=cpair(colors.blue,colors.black)}
|
||||||
|
|
||||||
|
if item.subtitle then
|
||||||
|
TextBox{parent=def_list,text=item.subtitle,fg_bg=cpair(colors.gray,colors.black)}
|
||||||
|
end
|
||||||
|
|
||||||
|
TextBox{parent=def_list,text=item.body}
|
||||||
|
|
||||||
|
page_end = Div{parent=def_list,height=1,can_focus=true}
|
||||||
|
|
||||||
|
local function view()
|
||||||
|
page_end.focus()
|
||||||
view_page.nav_to()
|
view_page.nav_to()
|
||||||
anchor.focus()
|
anchor.focus()
|
||||||
end
|
end
|
||||||
@ -55,12 +100,46 @@ return function (data, base_page, title, items, scroll_height)
|
|||||||
doc_map[item.key] = view
|
doc_map[item.key] = view
|
||||||
table.insert(search_db, { string.lower(item.name), item.name, title, view })
|
table.insert(search_db, { string.lower(item.name), item.name, title, view })
|
||||||
|
|
||||||
PushButton{parent=name_list,text=item.name,fg_bg=cpair(colors.blue,colors.black),active_fg_bg=btn_active,callback=view}
|
local name_entry = Div{parent=name_list,height=#util.strwrap(item.name,name_list.get_width()-3)}
|
||||||
|
TextBox{parent=name_entry,x=1,text="\x10",fg_bg=cpair(colors.gray,colors.black)}
|
||||||
|
PushButton{parent=name_entry,x=3,y=1,text=item.name,alignment=ALIGN.LEFT,fg_bg=cpair(colors.blue,colors.black),active_fg_bg=btn_active,callback=view}
|
||||||
|
elseif item.type == DOC_TYPE.TEXT then
|
||||||
|
---@cast item pocket_doc_text
|
||||||
|
|
||||||
|
TextBox{parent=def_list,text=item.text}
|
||||||
|
|
||||||
|
page_end = Div{parent=def_list,height=1,can_focus=true}
|
||||||
|
elseif item.type == DOC_TYPE.LIST then
|
||||||
|
---@cast item pocket_doc_list
|
||||||
|
|
||||||
|
local container = Div{parent=def_list,height=#item.items}
|
||||||
|
|
||||||
|
if item.list_type == LIST_TYPE.BULLET then
|
||||||
|
for _, li in ipairs(item.items) do
|
||||||
|
TextBox{parent=container,x=2,text="\x07 "..li}
|
||||||
|
end
|
||||||
|
elseif item.list_type == LIST_TYPE.NUMBERED then
|
||||||
|
local width = string.len("" .. #item.items)
|
||||||
|
for idx, li in ipairs(item.items) do
|
||||||
|
TextBox{parent=container,x=2,text=util.sprintf("%" .. width .. "d. %s", idx, li)}
|
||||||
|
end
|
||||||
|
elseif item.list_type == LIST_TYPE.INDICATOR then
|
||||||
|
for idx, li in ipairs(item.items) do
|
||||||
|
local _ = IndicatorLight{parent=container,x=2,label=li,colors=cpair(colors.black,item.colors[idx])}
|
||||||
|
end
|
||||||
|
elseif item.list_type == LIST_TYPE.LED then
|
||||||
|
for idx, li in ipairs(item.items) do
|
||||||
|
local _ = LED{parent=container,x=2,label=li,colors=cpair(colors.black,item.colors[idx])}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
page_end = Div{parent=def_list,height=1,can_focus=true}
|
||||||
|
end
|
||||||
|
|
||||||
if i % 12 == 0 then util.nop() end
|
if i % 12 == 0 then util.nop() end
|
||||||
end
|
end
|
||||||
|
|
||||||
log.debug("guide section " .. title .. " generated with final height ".. _end.get_y())
|
log.debug("guide section " .. title .. " generated with final height ".. page_end.get_y())
|
||||||
|
|
||||||
util.nop()
|
util.nop()
|
||||||
|
|
||||||
|
|||||||
@ -47,7 +47,7 @@ local function new_view(root)
|
|||||||
|
|
||||||
App{parent=apps_1,x=2,y=2,text="U",title="Units",callback=function()open(APP_ID.UNITS)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=2,y=2,text="U",title="Units",callback=function()open(APP_ID.UNITS)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_1,x=9,y=2,text="F",title="Facil",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=9,y=2,text="F",title="Facil",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_1,x=16,y=2,text="\x15",title="Control",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.green),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=16,y=2,text="\x15",title="Control",callback=function()open(APP_ID.CONTROL)end,app_fg_bg=cpair(colors.black,colors.green),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_1,x=2,y=7,text="\x17",title="Process",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.purple),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=2,y=7,text="\x17",title="Process",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.purple),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_1,x=9,y=7,text="\x7f",title="Waste",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.brown),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=9,y=7,text="\x7f",title="Waste",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.brown),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_1,x=16,y=7,text="\x08",title="Devices",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=16,y=7,text="\x08",title="Devices",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=active_fg_bg}
|
||||||
|
|||||||
@ -22,7 +22,6 @@ local IndLight = require("graphics.elements.indicators.light")
|
|||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
local LEFT = core.ALIGN.LEFT
|
|
||||||
local RIGHT = core.ALIGN.RIGHT
|
local RIGHT = core.ALIGN.RIGHT
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
@ -606,7 +605,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
|
|
||||||
local textbox
|
local textbox
|
||||||
if height > 1 then
|
if height > 1 then
|
||||||
textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1,alignment=LEFT}
|
textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1}
|
||||||
else
|
else
|
||||||
textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT}
|
textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT}
|
||||||
end
|
end
|
||||||
|
|||||||
@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc")
|
|||||||
local renderer = require("reactor-plc.renderer")
|
local renderer = require("reactor-plc.renderer")
|
||||||
local threads = require("reactor-plc.threads")
|
local threads = require("reactor-plc.threads")
|
||||||
|
|
||||||
local R_PLC_VERSION = "v1.8.7"
|
local R_PLC_VERSION = "v1.8.8"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
|
|||||||
@ -1457,7 +1457,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
local textbox
|
local textbox
|
||||||
if height > 1 then
|
if height > 1 then
|
||||||
textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1,alignment=LEFT}
|
textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1}
|
||||||
else
|
else
|
||||||
textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT}
|
textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT}
|
||||||
end
|
end
|
||||||
|
|||||||
@ -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.10.7"
|
local RTU_VERSION = "v1.10.8"
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@ -209,6 +209,23 @@ types.PROCESS_NAMES = {
|
|||||||
"GEN_RATE_FAULT_IDLE"
|
"GEN_RATE_FAULT_IDLE"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---@enum AUTO_GROUP
|
||||||
|
types.AUTO_GROUP = {
|
||||||
|
MANUAL = 0,
|
||||||
|
PRIMARY = 1,
|
||||||
|
SECONDARY = 2,
|
||||||
|
TERTIARY = 3,
|
||||||
|
BACKUP = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
types.AUTO_GROUP_NAMES = {
|
||||||
|
"Manual",
|
||||||
|
"Primary",
|
||||||
|
"Secondary",
|
||||||
|
"Tertiary",
|
||||||
|
"Backup"
|
||||||
|
}
|
||||||
|
|
||||||
---@enum WASTE_MODE
|
---@enum WASTE_MODE
|
||||||
types.WASTE_MODE = {
|
types.WASTE_MODE = {
|
||||||
AUTO = 1,
|
AUTO = 1,
|
||||||
|
|||||||
@ -30,7 +30,6 @@ local tri = util.trinary
|
|||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@ -1174,7 +1173,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
local textbox
|
local textbox
|
||||||
if height > 1 then
|
if height > 1 then
|
||||||
textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1,alignment=LEFT}
|
textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1}
|
||||||
else
|
else
|
||||||
textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT}
|
textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT}
|
||||||
end
|
end
|
||||||
|
|||||||
@ -8,6 +8,7 @@ local fac_update = require("supervisor.facility_update")
|
|||||||
local rsctl = require("supervisor.session.rsctl")
|
local rsctl = require("supervisor.session.rsctl")
|
||||||
local svsessions = require("supervisor.session.svsessions")
|
local svsessions = require("supervisor.session.svsessions")
|
||||||
|
|
||||||
|
local AUTO_GROUP = types.AUTO_GROUP
|
||||||
local PROCESS = types.PROCESS
|
local PROCESS = types.PROCESS
|
||||||
local RTU_ID_FAIL = types.RTU_ID_FAIL
|
local RTU_ID_FAIL = types.RTU_ID_FAIL
|
||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
@ -73,8 +74,8 @@ function facility.new(config)
|
|||||||
burn_target = 0.1, -- burn rate target for aggregate burn mode
|
burn_target = 0.1, -- burn rate target for aggregate burn mode
|
||||||
charge_setpoint = 0, -- FE charge target setpoint
|
charge_setpoint = 0, -- FE charge target setpoint
|
||||||
gen_rate_setpoint = 0, -- FE/t charge rate target setpoint
|
gen_rate_setpoint = 0, -- FE/t charge rate target setpoint
|
||||||
group_map = {}, -- units -> group IDs
|
group_map = {}, ---@type AUTO_GROUP[] units -> group IDs
|
||||||
prio_defs = { {}, {}, {}, {} }, -- priority definitions (each level is a table of units)
|
prio_defs = { {}, {}, {}, {} }, ---@type reactor_unit[][] priority definitions (each level is a table of units)
|
||||||
at_max_burn = false,
|
at_max_burn = false,
|
||||||
ascram = false,
|
ascram = false,
|
||||||
ascram_reason = AUTO_SCRAM.NONE,
|
ascram_reason = AUTO_SCRAM.NONE,
|
||||||
@ -130,7 +131,7 @@ function facility.new(config)
|
|||||||
for i = 1, config.UnitCount do
|
for i = 1, config.UnitCount do
|
||||||
table.insert(self.units,
|
table.insert(self.units,
|
||||||
unit.new(i, self.cooling_conf.r_cool[i].BoilerCount, self.cooling_conf.r_cool[i].TurbineCount, config.ExtChargeIdling))
|
unit.new(i, self.cooling_conf.r_cool[i].BoilerCount, self.cooling_conf.r_cool[i].TurbineCount, config.ExtChargeIdling))
|
||||||
table.insert(self.group_map, 0)
|
table.insert(self.group_map, AUTO_GROUP.MANUAL)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- list for RTU session management
|
-- list for RTU session management
|
||||||
@ -375,11 +376,14 @@ function facility.new(config)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- check automatic control mode
|
||||||
|
function public.auto_is_active() return self.mode ~= PROCESS.INACTIVE end
|
||||||
|
|
||||||
-- stop auto control
|
-- stop auto control
|
||||||
function public.auto_stop() self.mode = PROCESS.INACTIVE end
|
function public.auto_stop() self.mode = PROCESS.INACTIVE end
|
||||||
|
|
||||||
-- set automatic control configuration and start the process
|
-- set automatic control configuration and start the process
|
||||||
---@param auto_cfg coord_auto_config configuration
|
---@param auto_cfg sys_auto_config configuration
|
||||||
---@return table response ready state (successfully started) and current configuration (after updating)
|
---@return table response ready state (successfully started) and current configuration (after updating)
|
||||||
function public.auto_start(auto_cfg)
|
function public.auto_start(auto_cfg)
|
||||||
local charge_scaler = 1000000 -- convert MFE to FE
|
local charge_scaler = 1000000 -- convert MFE to FE
|
||||||
@ -451,24 +455,29 @@ function facility.new(config)
|
|||||||
|
|
||||||
-- set the automatic control group of a unit
|
-- set the automatic control group of a unit
|
||||||
---@param unit_id integer unit ID
|
---@param unit_id integer unit ID
|
||||||
---@param group integer group ID or 0 for independent
|
---@param group AUTO_GROUP group ID or 0 for independent
|
||||||
function public.set_group(unit_id, group)
|
function public.set_group(unit_id, group)
|
||||||
if (group >= 0 and group <= 4) and (unit_id > 0 and unit_id <= config.UnitCount) and self.mode == PROCESS.INACTIVE then
|
if (group >= AUTO_GROUP.MANUAL and group <= AUTO_GROUP.BACKUP) and (unit_id > 0 and unit_id <= config.UnitCount) and self.mode == PROCESS.INACTIVE then
|
||||||
-- remove from old group if previously assigned
|
-- remove from old group if previously assigned
|
||||||
local old_group = self.group_map[unit_id]
|
local old_group = self.group_map[unit_id]
|
||||||
if old_group ~= 0 then
|
if old_group ~= AUTO_GROUP.MANUAL then
|
||||||
util.filter_table(self.prio_defs[old_group], function (u) return u.get_id() ~= unit_id end)
|
util.filter_table(self.prio_defs[old_group], function (u) return u.get_id() ~= unit_id end)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.group_map[unit_id] = group
|
self.group_map[unit_id] = group
|
||||||
|
|
||||||
-- add to group if not independent
|
-- add to group if not independent
|
||||||
if group > 0 then
|
if group > AUTO_GROUP.MANUAL then
|
||||||
table.insert(self.prio_defs[group], self.units[unit_id])
|
table.insert(self.prio_defs[group], self.units[unit_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- get the automatic control group of a unit
|
||||||
|
---@param unit_id integer unit ID
|
||||||
|
---@nodiscard
|
||||||
|
function public.get_group(unit_id) return self.group_map[unit_id] end
|
||||||
|
|
||||||
-- set waste production
|
-- set waste production
|
||||||
---@param product WASTE_PRODUCT target product
|
---@param product WASTE_PRODUCT target product
|
||||||
---@return WASTE_PRODUCT product newly set value, if valid
|
---@return WASTE_PRODUCT product newly set value, if valid
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
local comms = require("scada-common.comms")
|
local comms = require("scada-common.comms")
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local mqueue = require("scada-common.mqueue")
|
local mqueue = require("scada-common.mqueue")
|
||||||
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local databus = require("supervisor.databus")
|
local databus = require("supervisor.databus")
|
||||||
@ -15,6 +16,9 @@ local CRDN_TYPE = comms.CRDN_TYPE
|
|||||||
local UNIT_COMMAND = comms.UNIT_COMMAND
|
local UNIT_COMMAND = comms.UNIT_COMMAND
|
||||||
local FAC_COMMAND = comms.FAC_COMMAND
|
local FAC_COMMAND = comms.FAC_COMMAND
|
||||||
|
|
||||||
|
local AUTO_GROUP = types.AUTO_GROUP
|
||||||
|
local WASTE_MODE = types.WASTE_MODE
|
||||||
|
|
||||||
local SV_Q_DATA = svqtypes.SV_Q_DATA
|
local SV_Q_DATA = svqtypes.SV_Q_DATA
|
||||||
|
|
||||||
-- retry time constants in ms
|
-- retry time constants in ms
|
||||||
@ -241,11 +245,16 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
facility.scram_all()
|
facility.scram_all()
|
||||||
_send(CRDN_TYPE.FAC_CMD, { cmd, true })
|
_send(CRDN_TYPE.FAC_CMD, { cmd, true })
|
||||||
elseif cmd == FAC_COMMAND.STOP then
|
elseif cmd == FAC_COMMAND.STOP then
|
||||||
|
local was_active = facility.auto_is_active()
|
||||||
|
|
||||||
|
if was_active then
|
||||||
facility.auto_stop()
|
facility.auto_stop()
|
||||||
_send(CRDN_TYPE.FAC_CMD, { cmd, true })
|
end
|
||||||
|
|
||||||
|
_send(CRDN_TYPE.FAC_CMD, { cmd, was_active })
|
||||||
elseif cmd == FAC_COMMAND.START then
|
elseif cmd == FAC_COMMAND.START then
|
||||||
if pkt.length == 6 then
|
if pkt.length == 6 then
|
||||||
---@type coord_auto_config
|
---@type sys_auto_config
|
||||||
local config = {
|
local config = {
|
||||||
mode = pkt.data[2],
|
mode = pkt.data[2],
|
||||||
burn_target = pkt.data[3],
|
burn_target = pkt.data[3],
|
||||||
@ -300,21 +309,30 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
-- continue if valid unit id
|
-- continue if valid unit id
|
||||||
if util.is_int(uid) and uid > 0 and uid <= #self.units then
|
if util.is_int(uid) and uid > 0 and uid <= #self.units then
|
||||||
local unit = self.units[uid] ---@type reactor_unit
|
local unit = self.units[uid] ---@type reactor_unit
|
||||||
|
local manual = facility.get_group(uid) == AUTO_GROUP.MANUAL
|
||||||
|
|
||||||
if cmd == UNIT_COMMAND.START then
|
if cmd == UNIT_COMMAND.SCRAM then
|
||||||
out_queue.push_data(SV_Q_DATA.START, data)
|
|
||||||
elseif cmd == UNIT_COMMAND.SCRAM then
|
|
||||||
out_queue.push_data(SV_Q_DATA.SCRAM, data)
|
out_queue.push_data(SV_Q_DATA.SCRAM, data)
|
||||||
|
elseif cmd == UNIT_COMMAND.START then
|
||||||
|
if manual then
|
||||||
|
out_queue.push_data(SV_Q_DATA.START, data)
|
||||||
|
else
|
||||||
|
-- denied
|
||||||
|
_send(CRDN_TYPE.UNIT_CMD, { cmd, uid, false })
|
||||||
|
end
|
||||||
elseif cmd == UNIT_COMMAND.RESET_RPS then
|
elseif cmd == UNIT_COMMAND.RESET_RPS then
|
||||||
out_queue.push_data(SV_Q_DATA.RESET_RPS, data)
|
out_queue.push_data(SV_Q_DATA.RESET_RPS, data)
|
||||||
elseif cmd == UNIT_COMMAND.SET_BURN then
|
elseif cmd == UNIT_COMMAND.SET_BURN then
|
||||||
if pkt.length == 3 then
|
if pkt.length == 3 then
|
||||||
|
if manual then
|
||||||
out_queue.push_data(SV_Q_DATA.SET_BURN, data)
|
out_queue.push_data(SV_Q_DATA.SET_BURN, data)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "CRDN unit command burn rate missing option")
|
log.debug(log_tag .. "CRDN unit command burn rate missing option")
|
||||||
end
|
end
|
||||||
elseif cmd == UNIT_COMMAND.SET_WASTE then
|
elseif cmd == UNIT_COMMAND.SET_WASTE then
|
||||||
if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] > 0) and (pkt.data[3] <= 4) then
|
if (pkt.length == 3) and (type(pkt.data[3]) == "number") and
|
||||||
|
(pkt.data[3] >= WASTE_MODE.AUTO) and (pkt.data[3] <= WASTE_MODE.MANUAL_ANTI_MATTER) then
|
||||||
unit.set_waste_mode(pkt.data[3])
|
unit.set_waste_mode(pkt.data[3])
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "CRDN unit command set waste missing/invalid option")
|
log.debug(log_tag .. "CRDN unit command set waste missing/invalid option")
|
||||||
@ -335,9 +353,9 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
log.debug(log_tag .. "CRDN unit command reset alarm missing alarm id")
|
log.debug(log_tag .. "CRDN unit command reset alarm missing alarm id")
|
||||||
end
|
end
|
||||||
elseif cmd == UNIT_COMMAND.SET_GROUP then
|
elseif cmd == UNIT_COMMAND.SET_GROUP then
|
||||||
if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] >= 0) and (pkt.data[3] <= 4) then
|
if (pkt.length == 3) and (type(pkt.data[3]) == "number") and
|
||||||
|
(pkt.data[3] >= AUTO_GROUP.MANUAL) and (pkt.data[3] <= AUTO_GROUP.BACKUP) then
|
||||||
facility.set_group(unit.get_id(), pkt.data[3])
|
facility.set_group(unit.get_id(), pkt.data[3])
|
||||||
_send(CRDN_TYPE.UNIT_CMD, { cmd, uid, pkt.data[3] })
|
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "CRDN unit command set group missing group id")
|
log.debug(log_tag .. "CRDN unit command set group missing group id")
|
||||||
end
|
end
|
||||||
|
|||||||
@ -395,13 +395,6 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
elseif ack == false then
|
elseif ack == false then
|
||||||
log.debug(log_tag .. "burn rate update failed!")
|
log.debug(log_tag .. "burn rate update failed!")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- send acknowledgement to coordinator
|
|
||||||
out_queue.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, {
|
|
||||||
unit = reactor_id,
|
|
||||||
cmd = UNIT_COMMAND.SET_BURN,
|
|
||||||
ack = ack
|
|
||||||
})
|
|
||||||
elseif pkt.type == RPLC_TYPE.RPS_ENABLE then
|
elseif pkt.type == RPLC_TYPE.RPS_ENABLE then
|
||||||
-- enable acknowledgement
|
-- enable acknowledgement
|
||||||
local ack = _get_ack(pkt)
|
local ack = _get_ack(pkt)
|
||||||
|
|||||||
@ -22,7 +22,7 @@ local supervisor = require("supervisor.supervisor")
|
|||||||
|
|
||||||
local svsessions = require("supervisor.session.svsessions")
|
local svsessions = require("supervisor.session.svsessions")
|
||||||
|
|
||||||
local SUPERVISOR_VERSION = "v1.5.2"
|
local SUPERVISOR_VERSION = "v1.5.3"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user