Compare commits
No commits in common. "main" and "v1.9.0-beta" have entirely different histories.
main
...
v1.9.0-bet
@ -1,57 +0,0 @@
|
||||
# Contribution Guide
|
||||
|
||||
>[!NOTE]
|
||||
Until the system is out of beta, contributions will be limited as I wrap up the specific release feature set.
|
||||
|
||||
This project is highly complex for a ComputerCraft Lua application. Contributions need to follow style guides and meet the code quality I've kept this project up to for years. Contributions must be tested appropriately with test results included.
|
||||
|
||||
I have extensively tested software components for stability required for safety, with tiers of software robustness.
|
||||
1. **Critical: High-Impact** -
|
||||
The Reactor-PLC is "uncrashable" and must remain so. I've extensively reviewed every line and behavior, so any code contributions must be at this high standard. Simple is stable, so the less code the better. Always check for parameter validity and extensively test any changes to critical thread functions.
|
||||
2. **Important: Moderate-Impact** -
|
||||
The Supervisor and RTU Gateway should rarely, if ever, crash. Certain places may not be held to as strict of a level as above, but should be written understanding all the possible inputs to and impacts of a section of code.
|
||||
3. **Useful: Low-Impact** -
|
||||
The Coordinator and Pocket are nice UI apps, and things can break. There's a lot of data going to and from them, so checking every single incoming value would have negative performance impacts and increase program size. If they break, the user can restart them. Don't introduce careless bugs, but making assumptions about the integrity of incoming data is acceptable.
|
||||
|
||||
## Valuable Contributions
|
||||
|
||||
Pull requests should not consist of purely whitespace changes, comment changes, or other trivial changes. They should target specific features, bug fixes, or functional improvements. I reserve the right to decline PRs that don't follow this in good faith.
|
||||
|
||||
## Project Management Guidelines
|
||||
|
||||
Any contributions should be linked to an open GitHub issue. These are used to track progress, discuss changes, etc. Surprise changes to this project might conflict with existing plans, so I prefer we coordinate changes ahead of time.
|
||||
|
||||
## Software Guidelines
|
||||
|
||||
These guidelines are subject to change. The general rule is make the code look like the rest of the code around it and elsewhere in the project.
|
||||
|
||||
### Style Guide
|
||||
|
||||
PRs will only be accepted if they match the style of this project and pass manual and automated code analysis. Listing out the whole style guide would take a while, so as stated above, please review code adjacent to your modifications.
|
||||
|
||||
1. **No Block Comments.**
|
||||
These interfere with the minification used for the bundled installation files due to the complexity of parsing Lua block comments. The minification code is meant to be simple to have 0 risk of breaking anything, so I'm staying far away from those.
|
||||
2. **Comment Your Code.**
|
||||
This includes type hints as used elsewhere throughout the project. Your comments should be associated with parts of code that are more complex or unclear, or otherwise to split up sections of tasks. You'll see `--#region` used in various places.
|
||||
- Type hints are intended to be utilized by the `sumneko.lua` vscode extension. You should use this while developing, as it provides extremely valuable functionality.
|
||||
3. **Whitespace Usage.**
|
||||
Whitespace should be used to separate function parameters and operators. The one exception is the unique styling of graphics elements, which you should compare against if modifying them.
|
||||
- 4 spaces are used for all indentation.
|
||||
- Try to align assignment operator lines as is done elsewhere (adding space before `=`).
|
||||
- Use empty new lines to separate steps or distinct groups of operations.
|
||||
- Generally add new lines for each step in loops and for statements. For some single-line ones, they may be compressed into a single line. This saves on space utilization, especially on deeply indented lines.
|
||||
4. **Variables and Classes.**
|
||||
- Variables, functions, and class-like tables follow the snake_case convention.
|
||||
- Graphics objects and configuration settings follow PascalCase.
|
||||
- Constants follow all-caps SNAKE_CASE and local ones should be declared at the top of files after `require` statements and external ones (like `local ALARM = types.ALARM`).
|
||||
5. **No `goto`.**
|
||||
These are generally frowned upon due to reducing code readability.
|
||||
6. **Multiple `return`s.**
|
||||
These are allowed to minimize code size, but if it is simple to avoid multiple, do so.
|
||||
7. **Classes and Objects.**
|
||||
Review the existing code for examples on how objects are implemented in this project. They do not use Lua's `:` operator and `self` functionality. A manual object-like table definition is used. Some global single-instance classes don't use a `new()` function, such as the [PPM](https://github.com/MikaylaFischler/cc-mek-scada/blob/main/scada-common/ppm.lua). Multi-instance ones do, such as the Supervisor's [unit](https://github.com/MikaylaFischler/cc-mek-scada/blob/main/supervisor/unit.lua) class.
|
||||
|
||||
### No AI
|
||||
|
||||
Your code should follow the style guide, be succinct, make sense, and you should be able to explain what it does. Random changes done in multiple places will be deemed suspicious along with poor comments or nonsensical code.
|
||||
Use your contributions as programming practice or to hone your skills; don't automate away thinking.
|
||||
@ -19,7 +19,7 @@ local CCMSI_VERSION = "v1.21"
|
||||
|
||||
local install_dir = "/.install-cache"
|
||||
local manifest_path = "https://mikaylafischler.github.io/cc-mek-scada/manifests/"
|
||||
local repo_path = "http://git.befatorinc.de/TheHomecraft/cc-mek-scada/raw/"
|
||||
local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/"
|
||||
|
||||
---@diagnostic disable-next-line: undefined-global
|
||||
local _is_pkt_env = pocket -- luacheck: ignore pocket
|
||||
|
||||
@ -234,24 +234,19 @@ function hmi.create(tool_ctl, main_pane, cfg_sys, divs, style)
|
||||
|
||||
TextBox{parent=crd_cfg,x=1,y=2,text=" Coordinator UI Configuration",fg_bg=cpair(colors.black,colors.lime)}
|
||||
|
||||
TextBox{parent=crd_c_1,x=1,y=1,height=2,text="You can customize the UI with the interface options below."}
|
||||
TextBox{parent=crd_c_1,x=1,y=1,height=3,text="Configure the UI interface options below if you wish to customize formats."}
|
||||
|
||||
TextBox{parent=crd_c_1,x=1,y=4,text="Clock Time Format"}
|
||||
tool_ctl.clock_fmt = RadioButton{parent=crd_c_1,x=1,y=5,default=util.trinary(ini_cfg.Time24Hour,1,2),options={"24-Hour","12-Hour"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||
|
||||
TextBox{parent=crd_c_1,x=20,y=4,text="Po/Pu Pellet Color"}
|
||||
TextBox{parent=crd_c_1,x=39,y=4,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||
tool_ctl.pellet_color = RadioButton{parent=crd_c_1,x=20,y=5,default=util.trinary(ini_cfg.GreenPuPellet,1,2),options={"Green Pu/Cyan Po","Cyan Pu/Green Po (Mek 10.4+)"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||
|
||||
TextBox{parent=crd_c_1,x=1,y=8,text="Temperature Scale"}
|
||||
tool_ctl.temp_scale = RadioButton{parent=crd_c_1,x=1,y=9,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||
|
||||
TextBox{parent=crd_c_1,x=20,y=8,text="Energy Scale"}
|
||||
tool_ctl.energy_scale = RadioButton{parent=crd_c_1,x=20,y=9,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||
TextBox{parent=crd_c_1,x=24,y=8,text="Energy Scale"}
|
||||
tool_ctl.energy_scale = RadioButton{parent=crd_c_1,x=24,y=9,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||
|
||||
local function submit_ui_opts()
|
||||
tmp_cfg.Time24Hour = tool_ctl.clock_fmt.get_value() == 1
|
||||
tmp_cfg.GreenPuPellet = tool_ctl.pellet_color.get_value() == 1
|
||||
tmp_cfg.TempScale = tool_ctl.temp_scale.get_value()
|
||||
tmp_cfg.EnergyScale = tool_ctl.energy_scale.get_value()
|
||||
main_pane.set_value(7)
|
||||
|
||||
@ -380,7 +380,6 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
||||
try_set(tool_ctl.num_units, ini_cfg.UnitCount)
|
||||
try_set(tool_ctl.dis_flow_view, ini_cfg.DisableFlowView)
|
||||
try_set(tool_ctl.s_vol, ini_cfg.SpeakerVolume)
|
||||
try_set(tool_ctl.pellet_color, ini_cfg.GreenPuPellet)
|
||||
try_set(tool_ctl.clock_fmt, tri(ini_cfg.Time24Hour, 1, 2))
|
||||
try_set(tool_ctl.temp_scale, ini_cfg.TempScale)
|
||||
try_set(tool_ctl.energy_scale, ini_cfg.EnergyScale)
|
||||
@ -529,8 +528,6 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
||||
|
||||
if f[1] == "AuthKey" then val = string.rep("*", string.len(val))
|
||||
elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace")
|
||||
elseif f[1] == "GreenPuPellet" then
|
||||
val = tri(raw, "Green Pu/Cyan Po", "Cyan Pu/Green Po")
|
||||
elseif f[1] == "TempScale" then
|
||||
val = util.strval(types.TEMP_SCALE_NAMES[raw])
|
||||
elseif f[1] == "EnergyScale" then
|
||||
@ -553,7 +550,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
||||
local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
||||
alternate = not alternate
|
||||
|
||||
if (string.len(val) > val_max_w) or string.find(val, "\n") then
|
||||
if string.len(val) > val_max_w then
|
||||
local lines = util.strwrap(val, inner_width)
|
||||
height = #lines + 1
|
||||
end
|
||||
|
||||
@ -35,8 +35,7 @@ local changes = {
|
||||
{ "v1.2.4", { "Added temperature scale options" } },
|
||||
{ "v1.2.12", { "Added main UI theme", "Added front panel UI theme", "Added color accessibility modes" } },
|
||||
{ "v1.3.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
||||
{ "v1.5.1", { "Added energy scale options" } },
|
||||
{ "v1.6.13", { "Added option for Po/Pu pellet green/cyan pairing" } }
|
||||
{ "v1.5.1", { "Added energy scale options" } }
|
||||
}
|
||||
|
||||
---@class crd_configurator
|
||||
@ -78,7 +77,6 @@ local tool_ctl = {
|
||||
-- settings elements from hmi
|
||||
dis_flow_view = nil, ---@type Checkbox
|
||||
s_vol = nil, ---@type NumberField
|
||||
pellet_color = nil, ---@type RadioButton
|
||||
clock_fmt = nil, ---@type RadioButton
|
||||
temp_scale = nil, ---@type RadioButton
|
||||
energy_scale = nil, ---@type RadioButton
|
||||
@ -97,7 +95,6 @@ local tmp_cfg = {
|
||||
UnitCount = 1,
|
||||
SpeakerVolume = 1.0,
|
||||
Time24Hour = true,
|
||||
GreenPuPellet = false,
|
||||
TempScale = 1, ---@type TEMP_SCALE
|
||||
EnergyScale = 1, ---@type ENERGY_SCALE
|
||||
DisableFlowView = false,
|
||||
@ -132,7 +129,6 @@ local fields = {
|
||||
{ "UnitDisplays", "Unit Monitors", {} },
|
||||
{ "SpeakerVolume", "Speaker Volume", 1.0 },
|
||||
{ "Time24Hour", "Use 24-hour Time Format", true },
|
||||
{ "GreenPuPellet", "Pellet Colors", false },
|
||||
{ "TempScale", "Temperature Scale", types.TEMP_SCALE.KELVIN },
|
||||
{ "EnergyScale", "Energy Scale", types.ENERGY_SCALE.FE },
|
||||
{ "DisableFlowView", "Disable Flow Monitor (legacy, discouraged)", false },
|
||||
|
||||
@ -38,7 +38,6 @@ function coordinator.load_config()
|
||||
config.UnitCount = settings.get("UnitCount")
|
||||
config.SpeakerVolume = settings.get("SpeakerVolume")
|
||||
config.Time24Hour = settings.get("Time24Hour")
|
||||
config.GreenPuPellet = settings.get("GreenPuPellet")
|
||||
config.TempScale = settings.get("TempScale")
|
||||
config.EnergyScale = settings.get("EnergyScale")
|
||||
|
||||
@ -68,7 +67,6 @@ function coordinator.load_config()
|
||||
cfv.assert_type_int(config.UnitCount)
|
||||
cfv.assert_range(config.UnitCount, 1, 4)
|
||||
cfv.assert_type_bool(config.Time24Hour)
|
||||
cfv.assert_type_bool(config.GreenPuPellet)
|
||||
cfv.assert_type_int(config.TempScale)
|
||||
cfv.assert_range(config.TempScale, 1, 4)
|
||||
cfv.assert_type_int(config.EnergyScale)
|
||||
@ -382,18 +380,6 @@ function coordinator.comms(version, nic, sv_watchdog)
|
||||
_send_sv(PROTOCOL.SCADA_MGMT, MGMT_TYPE.CLOSE, {})
|
||||
end
|
||||
|
||||
-- send the resume ready state to the supervisor
|
||||
---@param mode PROCESS process control mode
|
||||
---@param burn_target number burn rate target
|
||||
---@param charge_target number charge level target
|
||||
---@param gen_target number generation rate target
|
||||
---@param limits number[] unit burn rate limits
|
||||
function public.send_ready(mode, burn_target, charge_target, gen_target, limits)
|
||||
_send_sv(PROTOCOL.SCADA_CRDN, CRDN_TYPE.PROCESS_READY, {
|
||||
mode, burn_target, charge_target, gen_target, limits
|
||||
})
|
||||
end
|
||||
|
||||
-- send a facility command
|
||||
---@param cmd FAC_COMMAND command
|
||||
---@param option any? optional option options for the optional options (like waste mode)
|
||||
|
||||
@ -132,9 +132,7 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
||||
sps_data_tbl = {}, ---@type sps_session_db[]
|
||||
|
||||
tank_ps_tbl = {}, ---@type psil[]
|
||||
tank_data_tbl = {}, ---@type dynamicv_session_db[]
|
||||
|
||||
rad_monitors = {} ---@type { radiation: radiation_reading, raw: number }[]
|
||||
tank_data_tbl = {} ---@type dynamicv_session_db[]
|
||||
}
|
||||
|
||||
-- create induction and SPS tables (currently only 1 of each is supported)
|
||||
@ -166,7 +164,6 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
||||
num_turbines = 0,
|
||||
num_snas = 0,
|
||||
has_tank = conf.cooling.r_cool[i].TankConnection,
|
||||
aux_coolant = conf.cooling.aux_coolant[i],
|
||||
|
||||
status_lines = { "", "" },
|
||||
|
||||
@ -244,9 +241,7 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
||||
turbine_data_tbl = {}, ---@type turbinev_session_db[]
|
||||
|
||||
tank_ps_tbl = {}, ---@type psil[]
|
||||
tank_data_tbl = {}, ---@type dynamicv_session_db[]
|
||||
|
||||
rad_monitors = {} ---@type { radiation: radiation_reading, raw: number }[]
|
||||
tank_data_tbl = {} ---@type dynamicv_session_db[]
|
||||
}
|
||||
|
||||
-- on other facility modes, overwrite unit TANK option with facility tank defs
|
||||
@ -801,9 +796,7 @@ function iocontrol.update_facility_status(status)
|
||||
if type(rtu_statuses.envds) == "table" then
|
||||
local max_rad, max_reading, any_conn, any_faulted = 0, types.new_zero_radiation_reading(), false, false
|
||||
|
||||
fac.rad_monitors = {}
|
||||
|
||||
for id, envd in pairs(rtu_statuses.envds) do
|
||||
for _, envd in pairs(rtu_statuses.envds) do
|
||||
local rtu_faulted = envd[1] ---@type boolean
|
||||
local radiation = envd[2] ---@type radiation_reading
|
||||
local rad_raw = envd[3] ---@type number
|
||||
@ -815,10 +808,6 @@ function iocontrol.update_facility_status(status)
|
||||
max_rad = rad_raw
|
||||
max_reading = radiation
|
||||
end
|
||||
|
||||
if not rtu_faulted then
|
||||
fac.rad_monitors[id] = { radiation = radiation, raw = rad_raw }
|
||||
end
|
||||
end
|
||||
|
||||
if any_conn then
|
||||
@ -1109,12 +1098,9 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
if type(rtu_statuses.envds) == "table" then
|
||||
local max_rad, max_reading, any_conn = 0, types.new_zero_radiation_reading(), false
|
||||
|
||||
unit.rad_monitors = {}
|
||||
|
||||
for id, envd in pairs(rtu_statuses.envds) do
|
||||
local rtu_faulted = envd[1] ---@type boolean
|
||||
local radiation = envd[2] ---@type radiation_reading
|
||||
local rad_raw = envd[3] ---@type number
|
||||
for _, envd in pairs(rtu_statuses.envds) do
|
||||
local radiation = envd[2] ---@type radiation_reading
|
||||
local rad_raw = envd[3] ---@type number
|
||||
|
||||
any_conn = true
|
||||
|
||||
@ -1122,10 +1108,6 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
max_rad = rad_raw
|
||||
max_reading = radiation
|
||||
end
|
||||
|
||||
if not rtu_faulted then
|
||||
unit.rad_monitors[id] = { radiation = radiation, raw = rad_raw }
|
||||
end
|
||||
end
|
||||
|
||||
if any_conn then
|
||||
@ -1232,7 +1214,7 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
local valve_states = status[6]
|
||||
|
||||
if type(valve_states) == "table" then
|
||||
if #valve_states == 6 then
|
||||
if #valve_states == 5 then
|
||||
unit.unit_ps.publish("V_pu_conn", valve_states[1] > 0)
|
||||
unit.unit_ps.publish("V_pu_state", valve_states[1] == 2)
|
||||
unit.unit_ps.publish("V_po_conn", valve_states[2] > 0)
|
||||
@ -1243,8 +1225,6 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
unit.unit_ps.publish("V_am_state", valve_states[4] == 2)
|
||||
unit.unit_ps.publish("V_emc_conn", valve_states[5] > 0)
|
||||
unit.unit_ps.publish("V_emc_state", valve_states[5] == 2)
|
||||
unit.unit_ps.publish("V_aux_conn", valve_states[6] > 0)
|
||||
unit.unit_ps.publish("V_aux_state", valve_states[6] == 2)
|
||||
else
|
||||
log.debug(log_header .. "valve states length mismatch")
|
||||
valid = false
|
||||
|
||||
@ -139,11 +139,6 @@ function process.init(iocontrol, coord_comms)
|
||||
|
||||
log.info("PROCESS: loaded priority groups settings")
|
||||
end
|
||||
|
||||
-- report to the supervisor all initial configuration data has been sent
|
||||
-- startup resume can occur if needed
|
||||
local p = ctl_proc
|
||||
pctl.comms.send_ready(p.mode, p.burn_target, p.charge_target, p.gen_target, p.limits)
|
||||
end
|
||||
|
||||
-- create a handle to process control for usage of commands that get acknowledgements
|
||||
|
||||
@ -137,7 +137,7 @@ function renderer.try_start_fp()
|
||||
if not engine.fp_ready then
|
||||
-- show front panel view on terminal
|
||||
status, msg = pcall(function ()
|
||||
engine.ui.front_panel = DisplayBox{window=term.current(),fg_bg=style.fp.root}
|
||||
engine.ui.front_panel = DisplayBox{window=term.native(),fg_bg=style.fp.root}
|
||||
panel_view(engine.ui.front_panel, #engine.monitors.unit_displays)
|
||||
end)
|
||||
|
||||
|
||||
@ -427,13 +427,6 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
||||
}
|
||||
|
||||
_send(CRDN_TYPE.API_GET_WASTE, data)
|
||||
elseif pkt.type == CRDN_TYPE.API_GET_RAD then
|
||||
local data = {}
|
||||
|
||||
for i = 1, #db.units do data[i] = db.units[i].rad_monitors end
|
||||
data[#db.units + 1] = db.facility.rad_monitors
|
||||
|
||||
_send(CRDN_TYPE.API_GET_RAD, data)
|
||||
else
|
||||
log.debug(log_tag .. "handler received unsupported CRDN packet type " .. pkt.type)
|
||||
end
|
||||
|
||||
@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer")
|
||||
local sounder = require("coordinator.sounder")
|
||||
local threads = require("coordinator.threads")
|
||||
|
||||
local COORDINATOR_VERSION = "v1.6.16"
|
||||
local COORDINATOR_VERSION = "v1.6.4"
|
||||
|
||||
local CHUNK_LOAD_DELAY_S = 30.0
|
||||
|
||||
|
||||
@ -28,8 +28,6 @@ local function init(parent, id)
|
||||
|
||||
local ps = iocontrol.get_db().fp.ps
|
||||
|
||||
local term_w, _ = term.getSize()
|
||||
|
||||
-- root div
|
||||
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2}
|
||||
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=s_hi_bright}
|
||||
@ -45,9 +43,9 @@ local function init(parent, id)
|
||||
local pkt_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,fg_bg=label_fg}
|
||||
pkt_fw_v.register(ps, ps_prefix .. "fw", pkt_fw_v.set_value)
|
||||
|
||||
TextBox{parent=entry,x=term_w-16,y=2,text="RTT:",width=4}
|
||||
local pkt_rtt = DataIndicator{parent=entry,x=term_w-11,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
||||
TextBox{parent=entry,x=term_w-5,y=2,text="ms",width=4,fg_bg=label_fg}
|
||||
TextBox{parent=entry,x=35,y=2,text="RTT:",width=4}
|
||||
local pkt_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
||||
TextBox{parent=entry,x=46,y=2,text="ms",width=4,fg_bg=label_fg}
|
||||
pkt_rtt.register(ps, ps_prefix .. "rtt", pkt_rtt.update)
|
||||
pkt_rtt.register(ps, ps_prefix .. "rtt_color", pkt_rtt.recolor)
|
||||
|
||||
|
||||
@ -325,7 +325,7 @@ local function new_view(root, x, y)
|
||||
|
||||
TextBox{parent=waste_status,y=i,text="U"..i.." Waste",width=8}
|
||||
local a_waste = IndicatorLight{parent=waste_status,x=10,y=i,label="Auto",colors=ind_wht}
|
||||
local waste_m = StateIndicator{parent=waste_status,x=17,y=i,states=style.get_waste().states_abbrv,value=1,min_width=6}
|
||||
local waste_m = StateIndicator{parent=waste_status,x=17,y=i,states=style.waste.states_abbrv,value=1,min_width=6}
|
||||
|
||||
a_waste.register(unit.unit_ps, "U_AutoWaste", a_waste.update)
|
||||
waste_m.register(unit.unit_ps, "U_WasteProduct", waste_m.update)
|
||||
@ -339,11 +339,11 @@ local function new_view(root, x, y)
|
||||
TextBox{parent=waste_sel,text="WASTE PRODUCTION",alignment=ALIGN.CENTER,width=21,x=1,y=2,fg_bg=cutout_fg_bg}
|
||||
|
||||
local rect = Rectangle{parent=waste_sel,border=border(1,colors.brown,true),width=21,height=22,x=1,y=3}
|
||||
local status = StateIndicator{parent=rect,x=2,y=1,states=style.get_waste().states,value=1,min_width=17}
|
||||
local status = StateIndicator{parent=rect,x=2,y=1,states=style.waste.states,value=1,min_width=17}
|
||||
|
||||
status.register(facility.ps, "current_waste_product", status.update)
|
||||
|
||||
local waste_prod = RadioButton{parent=rect,x=2,y=3,options=style.get_waste().options,callback=process.set_process_waste,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.brown}
|
||||
local waste_prod = RadioButton{parent=rect,x=2,y=3,options=style.waste.options,callback=process.set_process_waste,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.brown}
|
||||
|
||||
waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value)
|
||||
|
||||
|
||||
@ -398,7 +398,7 @@ local function init(parent, id)
|
||||
local waste_proc = Rectangle{parent=main,border=border(1,colors.brown,true),thin=true,width=33,height=3,x=46,y=49}
|
||||
local waste_div = Div{parent=waste_proc,x=2,y=1,width=31,height=1}
|
||||
|
||||
local waste_mode = MultiButton{parent=waste_div,x=1,y=1,options=style.get_waste().unit_opts,callback=unit.set_waste,min_width=6}
|
||||
local waste_mode = MultiButton{parent=waste_div,x=1,y=1,options=style.waste.unit_opts,callback=unit.set_waste,min_width=6}
|
||||
|
||||
waste_mode.register(u_ps, "U_WasteMode", waste_mode.set_value)
|
||||
|
||||
|
||||
@ -59,7 +59,7 @@ local function make(parent, x, y, wide, unit_id)
|
||||
local tank_conns = facility.tank_conns
|
||||
local tank_types = facility.tank_fluid_types
|
||||
|
||||
local v_start = 1 + ((unit.unit_id - 1) * 6)
|
||||
local v_start = 1 + ((unit.unit_id - 1) * 5)
|
||||
local prv_start = 1 + ((unit.unit_id - 1) * 3)
|
||||
local v_fields = { "pu", "po", "pl", "am" }
|
||||
local v_names = {
|
||||
@ -94,21 +94,11 @@ local function make(parent, x, y, wide, unit_id)
|
||||
if unit.num_boilers > 0 then
|
||||
table.insert(rc_pipes, pipe(0, 1, _wide(28, 19), 1, colors.lightBlue, true))
|
||||
table.insert(rc_pipes, pipe(0, 3, _wide(28, 19), 3, colors.orange, true))
|
||||
table.insert(rc_pipes, pipe(_wide(46, 39), 1, _wide(72, 58), 1, colors.blue, true))
|
||||
table.insert(rc_pipes, pipe(_wide(46, 39), 3, _wide(72, 58), 3, colors.white, true))
|
||||
|
||||
if unit.aux_coolant then
|
||||
local em_water = facility.tank_fluid_types[facility.tank_conns[unit_id]] == COOLANT_TYPE.WATER
|
||||
local offset = util.trinary(unit.has_tank and em_water, 3, 0)
|
||||
table.insert(rc_pipes, pipe(_wide(51, 41) + offset, 0, _wide(51, 41) + offset, 0, colors.blue, true))
|
||||
end
|
||||
table.insert(rc_pipes, pipe(_wide(46 ,39), 1, _wide(72,58), 1, colors.blue, true))
|
||||
table.insert(rc_pipes, pipe(_wide(46,39), 3, _wide(72,58), 3, colors.white, true))
|
||||
else
|
||||
table.insert(rc_pipes, pipe(0, 1, _wide(72, 58), 1, colors.blue, true))
|
||||
table.insert(rc_pipes, pipe(0, 3, _wide(72, 58), 3, colors.white, true))
|
||||
|
||||
if unit.aux_coolant then
|
||||
table.insert(rc_pipes, pipe(8, 0, 8, 0, colors.blue, true))
|
||||
end
|
||||
table.insert(rc_pipes, pipe(0, 1, _wide(72,58), 1, colors.blue, true))
|
||||
table.insert(rc_pipes, pipe(0, 3, _wide(72,58), 3, colors.white, true))
|
||||
end
|
||||
|
||||
if unit.has_tank then
|
||||
@ -179,12 +169,12 @@ local function make(parent, x, y, wide, unit_id)
|
||||
pipe(_wide(22, 19), 1, _wide(49, 45), 1, colors.brown, true),
|
||||
pipe(_wide(22, 19), 5, _wide(28, 24), 5, colors.brown, true),
|
||||
|
||||
pipe(_wide(64, 53), 1, _wide(95, 81), 1, colors.cyan, true),
|
||||
pipe(_wide(64, 53), 1, _wide(95, 81), 1, colors.green, true),
|
||||
|
||||
pipe(_wide(48, 43), 4, _wide(71, 61), 4, colors.green, true),
|
||||
pipe(_wide(66, 57), 4, _wide(71, 61), 8, colors.green, true),
|
||||
pipe(_wide(74, 63), 4, _wide(95, 81), 4, colors.green, true),
|
||||
pipe(_wide(74, 63), 8, _wide(133, 111), 8, colors.green, true),
|
||||
pipe(_wide(48, 43), 4, _wide(71, 61), 4, colors.cyan, true),
|
||||
pipe(_wide(66, 57), 4, _wide(71, 61), 8, colors.cyan, true),
|
||||
pipe(_wide(74, 63), 4, _wide(95, 81), 4, colors.cyan, true),
|
||||
pipe(_wide(74, 63), 8, _wide(133, 111), 8, colors.cyan, true),
|
||||
|
||||
pipe(_wide(108, 94), 1, _wide(132, 110), 6, waste_c, true, true),
|
||||
pipe(_wide(108, 94), 4, _wide(111, 95), 1, waste_c, true, true),
|
||||
@ -232,21 +222,17 @@ local function make(parent, x, y, wide, unit_id)
|
||||
_machine(_wide(116, 94), 6, "SPENT WASTE \x1b")
|
||||
|
||||
TextBox{parent=waste,x=_wide(30,25),y=3,text="SNAs [Po]",alignment=ALIGN.CENTER,width=19,fg_bg=wh_gray}
|
||||
local sna_po = Rectangle{parent=waste,x=_wide(30,25),y=4,border=border(1,colors.gray,true),width=19,height=8,thin=true,fg_bg=style.theme.highlight_box_bright}
|
||||
local sna_po = Rectangle{parent=waste,x=_wide(30,25),y=4,border=border(1,colors.gray,true),width=19,height=7,thin=true,fg_bg=style.theme.highlight_box_bright}
|
||||
local sna_act = IndicatorLight{parent=sna_po,label="ACTIVE",colors=ind_grn}
|
||||
local sna_cnt = DataIndicator{parent=sna_po,x=12,y=1,lu_colors=lu_c_d,label="CNT",unit="",format="%2d",value=0,width=7}
|
||||
TextBox{parent=sna_po,y=3,text="PEAK\x1a",width=5,fg_bg=cpair(style.theme.label_dark,colors._INHERIT)}
|
||||
TextBox{parent=sna_po,text="MAX \x1a",width=5,fg_bg=cpair(style.theme.label_dark,colors._INHERIT)}
|
||||
local sna_pk = DataIndicator{parent=sna_po,x=6,y=3,lu_colors=lu_c_d,label="",unit="mB/t",format="%7.2f",value=0,width=17}
|
||||
local sna_max_o = DataIndicator{parent=sna_po,x=6,lu_colors=lu_c_d,label="",unit="mB/t",format="%7.2f",value=0,width=17}
|
||||
local sna_max_i = DataIndicator{parent=sna_po,lu_colors=lu_c_d,label="\x1aMAX",unit="mB/t",format="%7.2f",value=0,width=17}
|
||||
local sna_in = DataIndicator{parent=sna_po,lu_colors=lu_c_d,label="\x1aIN",unit="mB/t",format="%8.2f",value=0,width=17}
|
||||
local sna_pk = DataIndicator{parent=sna_po,y=3,lu_colors=lu_c_d,label="PEAK",unit="mB/t",format="%7.2f",value=0,width=17}
|
||||
local sna_max = DataIndicator{parent=sna_po,lu_colors=lu_c_d,label="MAX",unit="mB/t",format="%8.2f",value=0,width=17}
|
||||
local sna_in = DataIndicator{parent=sna_po,lu_colors=lu_c_d,label="IN",unit="mB/t",format="%9.2f",value=0,width=17}
|
||||
|
||||
sna_act.register(unit.unit_ps, "po_rate", function (r) sna_act.update(r > 0) end)
|
||||
sna_cnt.register(unit.unit_ps, "sna_count", sna_cnt.update)
|
||||
sna_pk.register(unit.unit_ps, "sna_peak_rate", sna_pk.update)
|
||||
sna_max_o.register(unit.unit_ps, "sna_max_rate", sna_max_o.update)
|
||||
sna_max_i.register(unit.unit_ps, "sna_max_rate", function (r) sna_max_i.update(r * 10) end)
|
||||
sna_max.register(unit.unit_ps, "sna_max_rate", sna_max.update)
|
||||
sna_in.register(unit.unit_ps, "sna_in", sna_in.update)
|
||||
|
||||
return root
|
||||
|
||||
@ -268,7 +268,7 @@ local function init(main)
|
||||
for i = 1, facility.num_units do
|
||||
local y_offset = y_ofs(i)
|
||||
unit_flow(main, flow_x, 5 + y_offset, #emcool_pipes == 0, i)
|
||||
table.insert(po_pipes, pipe(0, 3 + y_offset, 4, 0, colors.green, true, true))
|
||||
table.insert(po_pipes, pipe(0, 3 + y_offset, 4, 0, colors.cyan, true, true))
|
||||
util.nop()
|
||||
end
|
||||
|
||||
@ -286,7 +286,7 @@ local function init(main)
|
||||
|
||||
TextBox{parent=main,x=12,y=vy,text="\x10\x11",fg_bg=text_col,width=2}
|
||||
|
||||
local conn = IndicatorLight{parent=main,x=9,y=vy+1,label=util.sprintf("PV%02d-EMC", (i * 6) - 1),colors=style.ind_grn}
|
||||
local conn = IndicatorLight{parent=main,x=9,y=vy+1,label=util.sprintf("PV%02d-EMC", i * 5),colors=style.ind_grn}
|
||||
local open = IndicatorLight{parent=main,x=9,y=vy+2,label="OPEN",colors=style.ind_wht}
|
||||
|
||||
conn.register(units[i].unit_ps, "V_emc_conn", conn.update)
|
||||
@ -294,35 +294,6 @@ local function init(main)
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------
|
||||
-- auxiliary coolant valves --
|
||||
------------------------------
|
||||
|
||||
for i = 1, facility.num_units do
|
||||
if units[i].aux_coolant then
|
||||
local vx
|
||||
local vy = 3 + y_ofs(i)
|
||||
|
||||
if #emcool_pipes == 0 then
|
||||
vx = util.trinary(units[i].num_boilers == 0, 36, 79)
|
||||
else
|
||||
local em_water = tank_types[tank_conns[i]] == COOLANT_TYPE.WATER
|
||||
vx = util.trinary(units[i].num_boilers == 0, 58, util.trinary(units[i].has_tank and em_water, 94, 91))
|
||||
end
|
||||
|
||||
PipeNetwork{parent=main,x=vx-6,y=vy,pipes={pipe(0,1,9,0,colors.blue,true)},bg=style.theme.bg}
|
||||
|
||||
TextBox{parent=main,x=vx,y=vy,text="\x10\x11",fg_bg=text_col,width=2}
|
||||
TextBox{parent=main,x=vx+5,y=vy,text="\x1b",fg_bg=cpair(colors.blue,text_col.bkg),width=1}
|
||||
|
||||
local conn = IndicatorLight{parent=main,x=vx-3,y=vy+1,label=util.sprintf("PV%02d-AUX", i * 6),colors=style.ind_grn}
|
||||
local open = IndicatorLight{parent=main,x=vx-3,y=vy+2,label="OPEN",colors=style.ind_wht}
|
||||
|
||||
conn.register(units[i].unit_ps, "V_aux_conn", conn.update)
|
||||
open.register(units[i].unit_ps, "V_aux_state", open.update)
|
||||
end
|
||||
end
|
||||
|
||||
-------------------
|
||||
-- dynamic tanks --
|
||||
-------------------
|
||||
|
||||
@ -39,8 +39,6 @@ local led_grn = style.led_grn
|
||||
local function init(panel, num_units)
|
||||
local ps = iocontrol.get_db().fp.ps
|
||||
|
||||
local term_w, term_h = term.getSize()
|
||||
|
||||
TextBox{parent=panel,y=1,text="SCADA COORDINATOR",alignment=ALIGN.CENTER,fg_bg=style.fp_theme.header}
|
||||
|
||||
local page_div = Div{parent=panel,x=1,y=3}
|
||||
@ -63,7 +61,7 @@ local function init(panel, num_units)
|
||||
local modem = LED{parent=system,label="MODEM",colors=led_grn}
|
||||
|
||||
if not style.colorblind then
|
||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.fp_ind_bkg}}
|
||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,style.fp_ind_bkg}}
|
||||
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
network.register(ps, "link_state", network.update)
|
||||
else
|
||||
@ -133,9 +131,9 @@ local function init(panel, num_units)
|
||||
-- about footer
|
||||
--
|
||||
|
||||
local about = Div{parent=main_page,width=15,height=2,y=term_h-3,fg_bg=style.fp.disabled_fg}
|
||||
local fw_v = TextBox{parent=about,text="FW: v00.00.00"}
|
||||
local comms_v = TextBox{parent=about,text="NT: v00.00.00"}
|
||||
local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=style.fp.disabled_fg}
|
||||
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00"}
|
||||
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00"}
|
||||
|
||||
fw_v.register(ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
||||
comms_v.register(ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
||||
@ -147,7 +145,7 @@ local function init(panel, num_units)
|
||||
-- API page
|
||||
|
||||
local api_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||
local api_list = ListBox{parent=api_page,y=1,height=term_h-2,width=term_w,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||
local api_list = ListBox{parent=api_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||
local _ = Div{parent=api_list,height=1} -- padding
|
||||
|
||||
-- assemble page panes
|
||||
|
||||
@ -2,20 +2,16 @@
|
||||
-- Graphics Style Options
|
||||
--
|
||||
|
||||
local util = require("scada-common.util")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local themes = require("graphics.themes")
|
||||
|
||||
local coordinator = require("coordinator.coordinator")
|
||||
local core = require("graphics.core")
|
||||
local themes = require("graphics.themes")
|
||||
|
||||
---@class crd_style
|
||||
local style = {}
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
local config = coordinator.config
|
||||
|
||||
-- front panel styling
|
||||
|
||||
style.fp_theme = themes.sandstone
|
||||
@ -227,34 +223,27 @@ style.sps = {
|
||||
}
|
||||
}
|
||||
|
||||
-- get waste styling, which depends on the configuration
|
||||
---@return { states: { color: color, text: string }, states_abbrv: { color: color, text: string }, options: string[], unit_opts: { text: string, fg_bg: cpair, active_fg_bg:cpair } }
|
||||
function style.get_waste()
|
||||
local pu_color = util.trinary(config.GreenPuPellet, colors.green, colors.cyan)
|
||||
local po_color = util.trinary(config.GreenPuPellet, colors.cyan, colors.green)
|
||||
|
||||
return {
|
||||
-- auto waste processing states
|
||||
states = {
|
||||
{ color = cpair(colors.black, pu_color), text = "PLUTONIUM" },
|
||||
{ color = cpair(colors.black, po_color), text = "POLONIUM" },
|
||||
{ color = cpair(colors.black, colors.purple), text = "ANTI MATTER" }
|
||||
},
|
||||
states_abbrv = {
|
||||
{ color = cpair(colors.black, pu_color), text = "Pu" },
|
||||
{ color = cpair(colors.black, po_color), text = "Po" },
|
||||
{ color = cpair(colors.black, colors.purple), text = "AM" }
|
||||
},
|
||||
-- process radio button options
|
||||
options = { "Plutonium", "Polonium", "Antimatter" },
|
||||
-- unit waste selection
|
||||
unit_opts = {
|
||||
{ text = "Auto", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.white, colors.gray) },
|
||||
{ text = "Pu", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, pu_color) },
|
||||
{ text = "Po", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, po_color) },
|
||||
{ text = "AM", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, colors.purple) }
|
||||
}
|
||||
style.waste = {
|
||||
-- auto waste processing states
|
||||
states = {
|
||||
{ color = cpair(colors.black, colors.green), text = "PLUTONIUM" },
|
||||
{ color = cpair(colors.black, colors.cyan), text = "POLONIUM" },
|
||||
{ color = cpair(colors.black, colors.purple), text = "ANTI MATTER" }
|
||||
},
|
||||
states_abbrv = {
|
||||
{ color = cpair(colors.black, colors.green), text = "Pu" },
|
||||
{ color = cpair(colors.black, colors.cyan), text = "Po" },
|
||||
{ color = cpair(colors.black, colors.purple), text = "AM" }
|
||||
},
|
||||
-- process radio button options
|
||||
options = { "Plutonium", "Polonium", "Antimatter" },
|
||||
-- unit waste selection
|
||||
unit_opts = {
|
||||
{ text = "Auto", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.white, colors.gray) },
|
||||
{ text = "Pu", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, colors.green) },
|
||||
{ text = "Po", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, colors.cyan) },
|
||||
{ text = "AM", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, colors.purple) }
|
||||
}
|
||||
end
|
||||
}
|
||||
|
||||
return style
|
||||
|
||||
@ -7,7 +7,7 @@ local flasher = require("graphics.flasher")
|
||||
|
||||
local core = {}
|
||||
|
||||
core.version = "2.4.8"
|
||||
core.version = "2.4.7"
|
||||
|
||||
core.flasher = flasher
|
||||
core.events = events
|
||||
|
||||
@ -86,13 +86,6 @@ return function (args)
|
||||
e.redraw()
|
||||
end
|
||||
|
||||
-- change the foreground color of the text
|
||||
---@param c color
|
||||
function e.recolor(c)
|
||||
e.w_set_fgd(c)
|
||||
e.redraw()
|
||||
end
|
||||
|
||||
---@class TextBox:graphics_element
|
||||
local TextBox, id = e.complete(true)
|
||||
|
||||
|
||||
@ -53,44 +53,25 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
||||
--#region Pocket UI
|
||||
|
||||
local ui_c_1 = Div{parent=ui_cfg,x=2,y=4,width=24}
|
||||
local ui_c_2 = Div{parent=ui_cfg,x=2,y=4,width=24}
|
||||
|
||||
local ui_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={ui_c_1,ui_c_2}}
|
||||
|
||||
TextBox{parent=ui_cfg,x=1,y=2,text=" Pocket UI",fg_bg=cpair(colors.black,colors.lime)}
|
||||
|
||||
TextBox{parent=ui_c_1,x=1,y=1,height=3,text="You may customize UI options below."}
|
||||
TextBox{parent=ui_c_1,x=1,y=1,height=3,text="You may customize units below."}
|
||||
|
||||
TextBox{parent=ui_c_1,y=4,text="Po/Pu Pellet Color"}
|
||||
TextBox{parent=ui_c_1,x=20,y=4,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||
local pellet_color = RadioButton{parent=ui_c_1,y=5,default=util.trinary(ini_cfg.GreenPuPellet,1,2),options={"Green Pu/Cyan Po","Cyan Pu/Green Po"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||
TextBox{parent=ui_c_1,x=1,y=4,text="Temperature Scale"}
|
||||
local temp_scale = RadioButton{parent=ui_c_1,x=1,y=5,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||
|
||||
TextBox{parent=ui_c_1,y=8,height=4,text="In Mekanism 10.4 and later, pellet colors now match gas colors (Cyan Pu/Green Po).",fg_bg=g_lg_fg_bg}
|
||||
TextBox{parent=ui_c_1,x=1,y=10,text="Energy Scale"}
|
||||
local energy_scale = RadioButton{parent=ui_c_1,x=1,y=11,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||
|
||||
local function submit_ui_opts()
|
||||
tmp_cfg.GreenPuPellet = pellet_color.get_value() == 1
|
||||
ui_pane.set_value(2)
|
||||
end
|
||||
|
||||
PushButton{parent=ui_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=ui_c_1,x=19,y=15,text="Next \x1a",callback=submit_ui_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=ui_c_2,x=1,y=1,height=3,text="You may customize units below."}
|
||||
|
||||
TextBox{parent=ui_c_2,x=1,y=4,text="Temperature Scale"}
|
||||
local temp_scale = RadioButton{parent=ui_c_2,x=1,y=5,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||
|
||||
TextBox{parent=ui_c_2,x=1,y=10,text="Energy Scale"}
|
||||
local energy_scale = RadioButton{parent=ui_c_2,x=1,y=11,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||
|
||||
local function submit_ui_units()
|
||||
tmp_cfg.TempScale = temp_scale.get_value()
|
||||
tmp_cfg.EnergyScale = energy_scale.get_value()
|
||||
main_pane.set_value(3)
|
||||
end
|
||||
|
||||
PushButton{parent=ui_c_2,x=1,y=15,text="\x1b Back",callback=function()ui_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=ui_c_2,x=19,y=15,text="Next \x1a",callback=submit_ui_units,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=ui_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=ui_c_1,x=19,y=15,text="Next \x1a",callback=submit_ui_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
|
||||
@ -285,7 +266,6 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
||||
load_settings(settings_cfg, true)
|
||||
load_settings(ini_cfg)
|
||||
|
||||
try_set(pellet_color, ini_cfg.GreenPuPellet)
|
||||
try_set(temp_scale, ini_cfg.TempScale)
|
||||
try_set(energy_scale, ini_cfg.EnergyScale)
|
||||
try_set(svr_chan, ini_cfg.SVR_Channel)
|
||||
@ -394,8 +374,6 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
||||
val = string.rep("*", string.len(val))
|
||||
elseif f[1] == "LogMode" then
|
||||
val = tri(raw == log.MODE.APPEND, "append", "replace")
|
||||
elseif f[1] == "GreenPuPellet" then
|
||||
val = tri(raw, "Green Pu/Cyan Po", "Cyan Pu/Green Po")
|
||||
elseif f[1] == "TempScale" then
|
||||
val = util.strval(types.TEMP_SCALE_NAMES[raw])
|
||||
elseif f[1] == "EnergyScale" then
|
||||
@ -407,7 +385,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
||||
local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
||||
alternate = not alternate
|
||||
|
||||
if (string.len(val) > val_max_w) or string.find(val, "\n") then
|
||||
if string.len(val) > val_max_w then
|
||||
local lines = util.strwrap(val, inner_width)
|
||||
height = #lines + 1
|
||||
end
|
||||
|
||||
@ -29,8 +29,7 @@ local CENTER = core.ALIGN.CENTER
|
||||
-- changes to the config data/format to let the user know
|
||||
local changes = {
|
||||
{ "v0.9.2", { "Added temperature scale options" } },
|
||||
{ "v0.11.3", { "Added energy scale options" } },
|
||||
{ "v0.13.2", { "Added option for Po/Pu pellet green/cyan pairing" } }
|
||||
{ "v0.11.3", { "Added energy scale options" } }
|
||||
}
|
||||
|
||||
---@class pkt_configurator
|
||||
@ -65,7 +64,6 @@ local tool_ctl = {
|
||||
|
||||
---@class pkt_config
|
||||
local tmp_cfg = {
|
||||
GreenPuPellet = false,
|
||||
TempScale = 1, ---@type TEMP_SCALE
|
||||
EnergyScale = 1, ---@type ENERGY_SCALE
|
||||
SVR_Channel = nil, ---@type integer
|
||||
@ -86,7 +84,6 @@ local settings_cfg = {}
|
||||
|
||||
-- all settings fields, their nice names, and their default values
|
||||
local fields = {
|
||||
{ "GreenPuPellet", "Pellet Colors", false },
|
||||
{ "TempScale", "Temperature Scale", types.TEMP_SCALE.KELVIN },
|
||||
{ "EnergyScale", "Energy Scale", types.ENERGY_SCALE.FE },
|
||||
{ "SVR_Channel", "SVR Channel", 16240 },
|
||||
|
||||
@ -17,6 +17,7 @@ local ENERGY_UNITS = types.ENERGY_SCALE_UNITS
|
||||
local TEMP_SCALE = types.TEMP_SCALE
|
||||
local TEMP_UNITS = types.TEMP_SCALE_UNITS
|
||||
|
||||
---@todo nominal trip time is ping (0ms to 10ms usually)
|
||||
local WARN_TT = 40
|
||||
local HIGH_TT = 80
|
||||
|
||||
@ -34,9 +35,8 @@ iocontrol.LINK_STATE = LINK_STATE
|
||||
|
||||
---@class pocket_ioctl
|
||||
local io = {
|
||||
version = "unknown", -- pocket version
|
||||
ps = psil.create(), -- pocket PSIL
|
||||
loader_require = { sv = false, api = false }
|
||||
version = "unknown",
|
||||
ps = psil.create()
|
||||
}
|
||||
|
||||
local config = nil ---@type pkt_config
|
||||
@ -85,13 +85,12 @@ function iocontrol.init_core(pkt_comms, nav, cfg)
|
||||
|
||||
get_tone_states = function () comms.diag__get_alarm_tones() end,
|
||||
|
||||
tone_buttons = {}, ---@type SwitchButton[]
|
||||
alarm_buttons = {} ---@type Checkbox[]
|
||||
ready_warn = nil, ---@type TextBox
|
||||
tone_buttons = {}, ---@type SwitchButton[]
|
||||
alarm_buttons = {}, ---@type Checkbox[]
|
||||
tone_indicators = {} ---@type IndicatorLight[] indicators to update from supervisor tone states
|
||||
}
|
||||
|
||||
-- computer list
|
||||
io.diag.get_comps = function () comms.diag__get_computers() end
|
||||
|
||||
-- API access
|
||||
---@class pocket_ioctl_api
|
||||
io.api = {
|
||||
@ -99,8 +98,7 @@ function iocontrol.init_core(pkt_comms, nav, cfg)
|
||||
get_unit = function (unit) comms.api__get_unit(unit) end,
|
||||
get_ctrl = function () comms.api__get_control() end,
|
||||
get_proc = function () comms.api__get_process() end,
|
||||
get_waste = function () comms.api__get_waste() end,
|
||||
get_rad = function () comms.api__get_rad() end
|
||||
get_waste = function () comms.api__get_waste() end
|
||||
}
|
||||
end
|
||||
|
||||
@ -186,9 +184,7 @@ function iocontrol.init_fac(conf)
|
||||
sps_data_tbl = {}, ---@type sps_session_db[]
|
||||
|
||||
tank_ps_tbl = {}, ---@type psil[]
|
||||
tank_data_tbl = {}, ---@type dynamicv_session_db[]
|
||||
|
||||
rad_monitors = {} ---@type { radiation: radiation_reading, raw: number }[]
|
||||
tank_data_tbl = {} ---@type dynamicv_session_db[]
|
||||
}
|
||||
|
||||
-- create induction and SPS tables (currently only 1 of each is supported)
|
||||
@ -268,9 +264,7 @@ function iocontrol.init_fac(conf)
|
||||
turbine_data_tbl = {}, ---@type turbinev_session_db[]
|
||||
|
||||
tank_ps_tbl = {}, ---@type psil[]
|
||||
tank_data_tbl = {}, ---@type dynamicv_session_db[]
|
||||
|
||||
rad_monitors = {} ---@type { radiation: radiation_reading, raw: number }[]
|
||||
tank_data_tbl = {} ---@type dynamicv_session_db[]
|
||||
}
|
||||
|
||||
-- on other facility modes, overwrite unit TANK option with facility tank defs
|
||||
|
||||
136
pocket/iorx.lua
136
pocket/iorx.lua
@ -2,13 +2,10 @@
|
||||
-- I/O Control's Data Receive (Rx) Handlers
|
||||
--
|
||||
|
||||
local comms = require("scada-common.comms")
|
||||
local const = require("scada-common.constants")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local DEV_TYPE = comms.DEVICE_TYPE
|
||||
|
||||
local ALARM = types.ALARM
|
||||
local ALARM_STATE = types.ALARM_STATE
|
||||
|
||||
@ -60,6 +57,7 @@ local function _record_multiblock_status(faulted, data, ps)
|
||||
ps.publish("formed", data.formed)
|
||||
ps.publish("faulted", faulted)
|
||||
|
||||
---@todo revisit this
|
||||
if data.build then
|
||||
for key, val in pairs(data.build) do ps.publish(key, val) end
|
||||
end
|
||||
@ -316,7 +314,7 @@ function iorx.record_unit_data(data)
|
||||
local function blue(text) return { text = text, color = colors.blue } end
|
||||
|
||||
-- if unit.reactor_data.rps_status then
|
||||
-- for k, _ in pairs(unit.alarms) do
|
||||
-- for k, v in pairs(unit.alarms) do
|
||||
-- unit.alarms[k] = ALARM_STATE.TRIPPED
|
||||
-- end
|
||||
-- end
|
||||
@ -660,6 +658,7 @@ function iorx.record_waste_data(data)
|
||||
fac.ps.publish("sps_process_rate", f_data[9])
|
||||
end
|
||||
|
||||
|
||||
-- update facility app with facility and unit data from API_GET_FAC_DTL
|
||||
---@param data table
|
||||
function iorx.record_fac_detail_data(data)
|
||||
@ -820,135 +819,6 @@ function iorx.record_fac_detail_data(data)
|
||||
s_ps.publish("SPSStateStatus", s_stat)
|
||||
end
|
||||
|
||||
-- update the radiation monitor app with radiation monitor data from API_GET_RAD
|
||||
---@param data table
|
||||
function iorx.record_radiation_data(data)
|
||||
-- unit radiation monitors
|
||||
|
||||
for u_id = 1, #io.units do
|
||||
local unit = io.units[u_id]
|
||||
local max_rad = 0
|
||||
local connected = {}
|
||||
|
||||
unit.radiation = types.new_zero_radiation_reading()
|
||||
unit.rad_monitors = data[u_id]
|
||||
|
||||
for id, mon in pairs(unit.rad_monitors) do
|
||||
table.insert(connected, id)
|
||||
|
||||
unit.unit_ps.publish("radiation@" .. id, mon.radiation)
|
||||
|
||||
if mon.raw > max_rad then
|
||||
max_rad = mon.raw
|
||||
unit.radiation = mon.radiation
|
||||
end
|
||||
end
|
||||
|
||||
unit.unit_ps.publish("radiation", unit.radiation)
|
||||
unit.unit_ps.publish("radiation_monitors", textutils.serialize(connected))
|
||||
end
|
||||
|
||||
-- facility radiation monitors
|
||||
|
||||
local fac = io.facility
|
||||
|
||||
fac.radiation = types.new_zero_radiation_reading()
|
||||
fac.rad_monitors = data[#io.units + 1]
|
||||
|
||||
local max_rad = 0
|
||||
local connected = {}
|
||||
|
||||
for id, mon in pairs(fac.rad_monitors) do
|
||||
table.insert(connected, id)
|
||||
|
||||
fac.ps.publish("radiation@" .. id, mon.radiation)
|
||||
|
||||
if mon.raw > max_rad then
|
||||
max_rad = mon.raw
|
||||
fac.radiation = mon.radiation
|
||||
end
|
||||
end
|
||||
|
||||
fac.ps.publish("radiation", fac.radiation)
|
||||
fac.ps.publish("radiation_monitors", textutils.serialize(connected))
|
||||
end
|
||||
|
||||
local comp_record = {}
|
||||
|
||||
-- update the computers app with the network data from INFO_LIST_CMP
|
||||
---@param data table
|
||||
function iorx.record_network_data(data)
|
||||
local ps = io.ps
|
||||
local connected = {}
|
||||
local crd_online = false
|
||||
|
||||
ps.publish("comp_online", #data)
|
||||
|
||||
-- add/update connected computers
|
||||
for i = 1, #data do
|
||||
local entry = data[i]
|
||||
local type = entry[1]
|
||||
local id = entry[2]
|
||||
local pfx = "comp_" .. id
|
||||
|
||||
connected[id] = true
|
||||
|
||||
if type == DEV_TYPE.SVR then
|
||||
ps.publish("comp_svr_addr", id)
|
||||
ps.publish("comp_svr_fw", entry[3])
|
||||
elseif type == DEV_TYPE.CRD then
|
||||
crd_online = true
|
||||
ps.publish("comp_crd_addr", id)
|
||||
ps.publish("comp_crd_fw", entry[3])
|
||||
ps.publish("comp_crd_rtt", entry[4])
|
||||
else
|
||||
ps.publish(pfx .. "_type", entry[1])
|
||||
ps.publish(pfx .. "_addr", id)
|
||||
ps.publish(pfx .. "_fw", entry[3])
|
||||
ps.publish(pfx .. "_rtt", entry[4])
|
||||
|
||||
if type == DEV_TYPE.PLC then
|
||||
ps.publish(pfx .. "_unit", entry[5])
|
||||
end
|
||||
|
||||
if not comp_record[id] then
|
||||
comp_record[id] = true
|
||||
|
||||
-- trigger the app to create the new element
|
||||
ps.publish("comp_connect", id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- handle the coordinator being online or not
|
||||
-- no need to worry about the supervisor since this data is from the supervisor, so it has to be 'online' if received
|
||||
ps.publish("comp_crd_online", crd_online)
|
||||
if not crd_online then
|
||||
ps.publish("comp_crd_addr", "---")
|
||||
ps.publish("comp_crd_fw", "---")
|
||||
ps.publish("comp_crd_rtt", "---")
|
||||
end
|
||||
|
||||
-- reset the published value
|
||||
ps.publish("comp_connect", false)
|
||||
|
||||
-- remove disconnected computers
|
||||
for id, state in pairs(comp_record) do
|
||||
if state and not connected[id] then
|
||||
comp_record[id] = false
|
||||
|
||||
-- trigger the app to delete the element
|
||||
ps.publish("comp_disconnect", id)
|
||||
end
|
||||
end
|
||||
|
||||
-- reset the published value
|
||||
ps.publish("comp_disconnect", false)
|
||||
end
|
||||
|
||||
-- clear the tracked connected computer record
|
||||
function iorx.clear_comp_record() comp_record = {} end
|
||||
|
||||
return function (io_obj)
|
||||
io = io_obj
|
||||
return iorx
|
||||
|
||||
@ -16,10 +16,16 @@ local LINK_STATE = iocontrol.LINK_STATE
|
||||
|
||||
local pocket = {}
|
||||
|
||||
local MQ__RENDER_CMD = {
|
||||
UNLOAD_SV_APPS = 1,
|
||||
UNLOAD_API_APPS = 2
|
||||
}
|
||||
|
||||
local MQ__RENDER_DATA = {
|
||||
LOAD_APP = 1
|
||||
}
|
||||
|
||||
pocket.MQ__RENDER_CMD = MQ__RENDER_CMD
|
||||
pocket.MQ__RENDER_DATA = MQ__RENDER_DATA
|
||||
|
||||
---@type pkt_config
|
||||
@ -32,7 +38,6 @@ pocket.config = config
|
||||
function pocket.load_config()
|
||||
if not settings.load("/pocket.settings") then return false end
|
||||
|
||||
config.GreenPuPellet = settings.get("GreenPuPellet")
|
||||
config.TempScale = settings.get("TempScale")
|
||||
config.EnergyScale = settings.get("EnergyScale")
|
||||
|
||||
@ -49,7 +54,6 @@ function pocket.load_config()
|
||||
|
||||
local cfv = util.new_validator()
|
||||
|
||||
cfv.assert_type_bool(config.GreenPuPellet)
|
||||
cfv.assert_type_int(config.TempScale)
|
||||
cfv.assert_range(config.TempScale, 1, 4)
|
||||
cfv.assert_type_int(config.EnergyScale)
|
||||
@ -82,7 +86,7 @@ local APP_ID = {
|
||||
-- core UI
|
||||
ROOT = 1,
|
||||
LOADER = 2,
|
||||
-- main apps
|
||||
-- main app pages
|
||||
UNITS = 3,
|
||||
FACILITY = 4,
|
||||
CONTROL = 5,
|
||||
@ -90,12 +94,11 @@ local APP_ID = {
|
||||
WASTE = 7,
|
||||
GUIDE = 8,
|
||||
ABOUT = 9,
|
||||
RADMON = 10,
|
||||
-- diagnostic apps
|
||||
ALARMS = 11,
|
||||
COMPS = 12,
|
||||
-- count
|
||||
NUM_APPS = 12
|
||||
-- diagnostic app pages
|
||||
ALARMS = 10,
|
||||
-- other
|
||||
DUMMY = 11,
|
||||
NUM_APPS = 11
|
||||
}
|
||||
|
||||
pocket.APP_ID = APP_ID
|
||||
@ -144,7 +147,7 @@ function pocket.init_nav(smem)
|
||||
---@class pocket_app
|
||||
local app = {
|
||||
loaded = false,
|
||||
cur_page = nil, ---@type nav_tree_page|nil
|
||||
cur_page = nil, ---@type nav_tree_page
|
||||
pane = pane,
|
||||
paned_pages = {}, ---@type nav_tree_page[]
|
||||
sidebar_items = {} ---@type sidebar_entry[]
|
||||
@ -264,28 +267,21 @@ function pocket.init_nav(smem)
|
||||
|
||||
-- open an app
|
||||
---@param app_id POCKET_APP_ID
|
||||
---@param on_ready? function
|
||||
function nav.open_app(app_id, on_ready)
|
||||
---@param on_loaded? function
|
||||
function nav.open_app(app_id, on_loaded)
|
||||
-- reset help return on navigating out of an app
|
||||
if app_id == APP_ID.ROOT then self.help_return = nil end
|
||||
|
||||
local app = self.apps[app_id]
|
||||
if app then
|
||||
local p_comms = smem.pkt_sys.pocket_comms
|
||||
local req_sv, req_api = app.check_requires()
|
||||
|
||||
if (req_sv and not p_comms.is_sv_linked()) or (req_api and not p_comms.is_api_linked()) then
|
||||
-- report required connction(s)
|
||||
iocontrol.get_db().loader_require = { sv = req_sv, api = req_api }
|
||||
iocontrol.get_db().ps.toggle("loader_reqs")
|
||||
|
||||
if app.requires_conn() and not smem.pkt_sys.pocket_comms.is_linked() then
|
||||
-- bring up the app loader
|
||||
self.loader_return = app_id
|
||||
app_id = APP_ID.LOADER
|
||||
app = self.apps[app_id]
|
||||
else self.loader_return = nil end
|
||||
|
||||
if not app.loaded then smem.q.mq_render.push_data(MQ__RENDER_DATA.LOAD_APP, { app_id, on_ready }) end
|
||||
if not app.loaded then smem.q.mq_render.push_data(MQ__RENDER_DATA.LOAD_APP, { app_id, on_loaded }) end
|
||||
|
||||
self.cur_app = app_id
|
||||
self.pane.set_value(app_id)
|
||||
@ -293,8 +289,6 @@ function pocket.init_nav(smem)
|
||||
if #app.sidebar_items > 0 then
|
||||
self.sidebar.update(app.sidebar_items)
|
||||
end
|
||||
|
||||
if app.loaded and on_ready then on_ready() end
|
||||
else
|
||||
log.debug("tried to open unknown app")
|
||||
end
|
||||
@ -342,7 +336,7 @@ function pocket.init_nav(smem)
|
||||
function nav.get_containers() return self.containers end
|
||||
|
||||
-- get the currently active page
|
||||
---@return nav_tree_page|nil
|
||||
---@return nav_tree_page
|
||||
function nav.get_current_page()
|
||||
return self.apps[self.cur_app].get_current_page()
|
||||
end
|
||||
@ -371,7 +365,8 @@ function pocket.init_nav(smem)
|
||||
self.help_return = self.cur_app
|
||||
|
||||
nav.open_app(APP_ID.GUIDE, function ()
|
||||
if self.help_map[key] then self.help_map[key]() end
|
||||
local show = self.help_map[key]
|
||||
if show then show() end
|
||||
end)
|
||||
end
|
||||
|
||||
@ -559,11 +554,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
||||
if self.sv.linked then _send_sv(MGMT_TYPE.DIAG_ALARM_SET, { id, state }) end
|
||||
end
|
||||
|
||||
-- supervisor get connected computers
|
||||
function public.diag__get_computers()
|
||||
if self.sv.linked then _send_sv(MGMT_TYPE.INFO_LIST_CMP, {}) end
|
||||
end
|
||||
|
||||
-- coordinator get facility app data
|
||||
function public.api__get_facility()
|
||||
if self.api.linked then _send_api(CRDN_TYPE.API_GET_FAC_DTL, {}) end
|
||||
@ -589,11 +579,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
||||
if self.api.linked then _send_api(CRDN_TYPE.API_GET_WASTE, {}) end
|
||||
end
|
||||
|
||||
-- coordinator get radiation app data
|
||||
function public.api__get_rad()
|
||||
if self.api.linked then _send_api(CRDN_TYPE.API_GET_RAD, {}) end
|
||||
end
|
||||
|
||||
-- send a facility command
|
||||
---@param cmd FAC_COMMAND command
|
||||
---@param option any? optional option options for the optional options (like waste mode)
|
||||
@ -670,7 +655,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
||||
---@param packet mgmt_frame|crdn_frame|nil
|
||||
function public.handle_packet(packet)
|
||||
local diag = iocontrol.get_db().diag
|
||||
local ps = iocontrol.get_db().ps
|
||||
|
||||
if packet ~= nil then
|
||||
local l_chan = packet.scada_frame.local_channel()
|
||||
@ -771,10 +755,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
||||
if _check_length(packet, #iocontrol.get_db().units + 1) then
|
||||
iocontrol.rx.record_waste_data(packet.data)
|
||||
end
|
||||
elseif packet.type == CRDN_TYPE.API_GET_RAD then
|
||||
if _check_length(packet, #iocontrol.get_db().units + 1) then
|
||||
iocontrol.rx.record_radiation_data(packet.data)
|
||||
end
|
||||
else _fail_type(packet) end
|
||||
else
|
||||
log.debug("discarding coordinator SCADA_CRDN packet before linked")
|
||||
@ -922,23 +902,23 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
||||
elseif packet.type == MGMT_TYPE.DIAG_TONE_GET then
|
||||
if _check_length(packet, 8) then
|
||||
for i = 1, #packet.data do
|
||||
ps.publish("alarm_tone_" .. i, packet.data[i] == true)
|
||||
diag.tone_test.tone_indicators[i].update(packet.data[i] == true)
|
||||
end
|
||||
end
|
||||
elseif packet.type == MGMT_TYPE.DIAG_TONE_SET then
|
||||
if packet.length == 1 and packet.data[1] == false then
|
||||
ps.publish("alarm_ready_warn", "testing denied")
|
||||
diag.tone_test.ready_warn.set_value("testing denied")
|
||||
log.debug("supervisor SCADA diag tone set failed")
|
||||
elseif packet.length == 2 and type(packet.data[2]) == "table" then
|
||||
local ready = packet.data[1]
|
||||
local states = packet.data[2]
|
||||
|
||||
ps.publish("alarm_ready_warn", util.trinary(ready, "", "system not idle"))
|
||||
diag.tone_test.ready_warn.set_value(util.trinary(ready, "", "system not idle"))
|
||||
|
||||
for i = 1, #states do
|
||||
if diag.tone_test.tone_buttons[i] ~= nil then
|
||||
diag.tone_test.tone_buttons[i].set_value(states[i] == true)
|
||||
ps.publish("alarm_tone_" .. i, states[i] == true)
|
||||
diag.tone_test.tone_indicators[i].update(states[i] == true)
|
||||
end
|
||||
end
|
||||
else
|
||||
@ -946,13 +926,13 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
||||
end
|
||||
elseif packet.type == MGMT_TYPE.DIAG_ALARM_SET then
|
||||
if packet.length == 1 and packet.data[1] == false then
|
||||
ps.publish("alarm_ready_warn", "testing denied")
|
||||
diag.tone_test.ready_warn.set_value("testing denied")
|
||||
log.debug("supervisor SCADA diag alarm set failed")
|
||||
elseif packet.length == 2 and type(packet.data[2]) == "table" then
|
||||
local ready = packet.data[1]
|
||||
local states = packet.data[2]
|
||||
|
||||
ps.publish("alarm_ready_warn", util.trinary(ready, "", "system not idle"))
|
||||
diag.tone_test.ready_warn.set_value(util.trinary(ready, "", "system not idle"))
|
||||
|
||||
for i = 1, #states do
|
||||
if diag.tone_test.alarm_buttons[i] ~= nil then
|
||||
@ -962,8 +942,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
||||
else
|
||||
log.debug("supervisor SCADA diag alarm set packet length/type mismatch")
|
||||
end
|
||||
elseif packet.type == MGMT_TYPE.INFO_LIST_CMP then
|
||||
iocontrol.rx.record_network_data(packet.data)
|
||||
else _fail_type(packet) end
|
||||
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
||||
-- connection with supervisor established
|
||||
|
||||
@ -2,10 +2,8 @@
|
||||
-- SCADA System Access on a Pocket Computer
|
||||
--
|
||||
|
||||
---@diagnostic disable-next-line: lowercase-global
|
||||
pocket = pocket or periphemu -- luacheck: ignore pocket
|
||||
|
||||
local _is_pocket_env = pocket -- luacheck: ignore pocket
|
||||
---@diagnostic disable-next-line: undefined-global
|
||||
local _is_pocket_env = pocket or periphemu -- luacheck: ignore pocket
|
||||
|
||||
require("/initenv").init_env()
|
||||
|
||||
@ -22,7 +20,7 @@ local pocket = require("pocket.pocket")
|
||||
local renderer = require("pocket.renderer")
|
||||
local threads = require("pocket.threads")
|
||||
|
||||
local POCKET_VERSION = "v1.0.3"
|
||||
local POCKET_VERSION = "v0.13.0-beta"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
|
||||
@ -14,6 +14,7 @@ local threads = {}
|
||||
local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks)
|
||||
local RENDER_SLEEP = 100 -- (100ms, 2 ticks)
|
||||
|
||||
local MQ__RENDER_CMD = pocket.MQ__RENDER_CMD
|
||||
local MQ__RENDER_DATA = pocket.MQ__RENDER_DATA
|
||||
|
||||
-- main thread
|
||||
@ -57,10 +58,8 @@ function threads.thread__main(smem)
|
||||
pocket_comms.link_update()
|
||||
|
||||
-- update any tasks for the active page
|
||||
if nav.get_current_page() then
|
||||
local page_tasks = nav.get_current_page().tasks
|
||||
for i = 1, #page_tasks do page_tasks[i]() end
|
||||
end
|
||||
local page_tasks = nav.get_current_page().tasks
|
||||
for i = 1, #page_tasks do page_tasks[i]() end
|
||||
|
||||
loop_clock.start()
|
||||
elseif sv_wd.is_timer(param1) then
|
||||
@ -158,6 +157,9 @@ function threads.thread__render(smem)
|
||||
if msg ~= nil then
|
||||
if msg.qtype == mqueue.TYPE.COMMAND then
|
||||
-- received a command
|
||||
if msg.message == MQ__RENDER_CMD.UNLOAD_SV_APPS then
|
||||
elseif msg.message == MQ__RENDER_CMD.UNLOAD_API_APPS then
|
||||
end
|
||||
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||
-- received data
|
||||
local cmd = msg.message ---@type queue_data
|
||||
|
||||
@ -1,184 +0,0 @@
|
||||
--
|
||||
-- Alarm Test App
|
||||
--
|
||||
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
local pocket = require("pocket.pocket")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.Div")
|
||||
local MultiPane = require("graphics.elements.MultiPane")
|
||||
local TextBox = require("graphics.elements.TextBox")
|
||||
|
||||
local IndicatorLight = require("graphics.elements.indicators.IndicatorLight")
|
||||
|
||||
local Checkbox = require("graphics.elements.controls.Checkbox")
|
||||
local PushButton = require("graphics.elements.controls.PushButton")
|
||||
local SwitchButton = require("graphics.elements.controls.SwitchButton")
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
local cpair = core.cpair
|
||||
|
||||
local APP_ID = pocket.APP_ID
|
||||
|
||||
local c_wht_gray = cpair(colors.white, colors.gray)
|
||||
local c_red_gray = cpair(colors.red, colors.gray)
|
||||
local c_yel_gray = cpair(colors.yellow, colors.gray)
|
||||
local c_blue_gray = cpair(colors.blue, colors.gray)
|
||||
|
||||
-- create alarm test page view
|
||||
---@param root Container parent
|
||||
local function new_view(root)
|
||||
local db = iocontrol.get_db()
|
||||
local ps = db.ps
|
||||
local ttest = db.diag.tone_test
|
||||
|
||||
local frame = Div{parent=root,x=1,y=1}
|
||||
|
||||
local app = db.nav.register_app(APP_ID.ALARMS, frame, nil, true)
|
||||
|
||||
local main = Div{parent=frame,x=1,y=1}
|
||||
local page_div = Div{parent=main,y=2,width=main.get_width()}
|
||||
|
||||
--#region alarm testing
|
||||
|
||||
local alarm_page = app.new_page(nil, 1)
|
||||
alarm_page.tasks = { db.diag.tone_test.get_tone_states }
|
||||
|
||||
local alarms_div = Div{parent=page_div}
|
||||
|
||||
TextBox{parent=alarms_div,text="Alarm Sounder Tests",alignment=ALIGN.CENTER}
|
||||
|
||||
local alarm_ready_warn = TextBox{parent=alarms_div,y=2,text="",alignment=ALIGN.CENTER,fg_bg=cpair(colors.yellow,colors.black)}
|
||||
alarm_ready_warn.register(ps, "alarm_ready_warn", alarm_ready_warn.set_value)
|
||||
|
||||
local alarm_page_states = Div{parent=alarms_div,x=2,y=3,height=5,width=8}
|
||||
|
||||
TextBox{parent=alarm_page_states,text="States",alignment=ALIGN.CENTER}
|
||||
local ta_1 = IndicatorLight{parent=alarm_page_states,label="1",colors=c_blue_gray}
|
||||
local ta_2 = IndicatorLight{parent=alarm_page_states,label="2",colors=c_blue_gray}
|
||||
local ta_3 = IndicatorLight{parent=alarm_page_states,label="3",colors=c_blue_gray}
|
||||
local ta_4 = IndicatorLight{parent=alarm_page_states,label="4",colors=c_blue_gray}
|
||||
local ta_5 = IndicatorLight{parent=alarm_page_states,x=6,y=2,label="5",colors=c_blue_gray}
|
||||
local ta_6 = IndicatorLight{parent=alarm_page_states,x=6,label="6",colors=c_blue_gray}
|
||||
local ta_7 = IndicatorLight{parent=alarm_page_states,x=6,label="7",colors=c_blue_gray}
|
||||
local ta_8 = IndicatorLight{parent=alarm_page_states,x=6,label="8",colors=c_blue_gray}
|
||||
|
||||
local ta = { ta_1, ta_2, ta_3, ta_4, ta_5, ta_6, ta_7, ta_8 }
|
||||
|
||||
for i = 1, #ta do
|
||||
ta[i].register(ps, "alarm_tone_" .. i, ta[i].update)
|
||||
end
|
||||
|
||||
local alarms = Div{parent=alarms_div,x=11,y=3,height=15,fg_bg=cpair(colors.lightGray,colors.black)}
|
||||
|
||||
TextBox{parent=alarms,text="Alarms (\x13)",alignment=ALIGN.CENTER,fg_bg=alarms_div.get_fg_bg()}
|
||||
|
||||
local alarm_btns = {}
|
||||
alarm_btns[1] = Checkbox{parent=alarms,label="BREACH",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_breach}
|
||||
alarm_btns[2] = Checkbox{parent=alarms,label="RADIATION",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_rad}
|
||||
alarm_btns[3] = Checkbox{parent=alarms,label="RCT LOST",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_lost}
|
||||
alarm_btns[4] = Checkbox{parent=alarms,label="CRIT DAMAGE",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_crit}
|
||||
alarm_btns[5] = Checkbox{parent=alarms,label="DAMAGE",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_dmg}
|
||||
alarm_btns[6] = Checkbox{parent=alarms,label="OVER TEMP",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_overtemp}
|
||||
alarm_btns[7] = Checkbox{parent=alarms,label="HIGH TEMP",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_hightemp}
|
||||
alarm_btns[8] = Checkbox{parent=alarms,label="WASTE LEAK",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_wasteleak}
|
||||
alarm_btns[9] = Checkbox{parent=alarms,label="WASTE HIGH",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_highwaste}
|
||||
alarm_btns[10] = Checkbox{parent=alarms,label="RPS TRANS",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_rps}
|
||||
alarm_btns[11] = Checkbox{parent=alarms,label="RCS TRANS",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_rcs}
|
||||
alarm_btns[12] = Checkbox{parent=alarms,label="TURBINE TRP",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_turbinet}
|
||||
|
||||
ttest.alarm_buttons = alarm_btns
|
||||
|
||||
local function stop_all_alarms()
|
||||
for i = 1, #alarm_btns do alarm_btns[i].set_value(false) end
|
||||
ttest.stop_alarms()
|
||||
end
|
||||
|
||||
PushButton{parent=alarms,x=3,y=15,text="STOP \x13",min_width=8,fg_bg=cpair(colors.black,colors.red),active_fg_bg=c_wht_gray,callback=stop_all_alarms}
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region direct tone testing
|
||||
|
||||
local tones_page = app.new_page(nil, 2)
|
||||
tones_page.tasks = { db.diag.tone_test.get_tone_states }
|
||||
|
||||
local tones_div = Div{parent=page_div}
|
||||
|
||||
TextBox{parent=tones_div,text="Alarm Sounder Tests",alignment=ALIGN.CENTER}
|
||||
|
||||
local tone_ready_warn = TextBox{parent=tones_div,y=2,text="",alignment=ALIGN.CENTER,fg_bg=cpair(colors.yellow,colors.black)}
|
||||
tone_ready_warn.register(ps, "alarm_ready_warn", tone_ready_warn.set_value)
|
||||
|
||||
local tone_page_states = Div{parent=tones_div,x=3,y=3,height=5,width=8}
|
||||
|
||||
TextBox{parent=tone_page_states,text="States",alignment=ALIGN.CENTER}
|
||||
local tt_1 = IndicatorLight{parent=tone_page_states,label="1",colors=c_blue_gray}
|
||||
local tt_2 = IndicatorLight{parent=tone_page_states,label="2",colors=c_blue_gray}
|
||||
local tt_3 = IndicatorLight{parent=tone_page_states,label="3",colors=c_blue_gray}
|
||||
local tt_4 = IndicatorLight{parent=tone_page_states,label="4",colors=c_blue_gray}
|
||||
local tt_5 = IndicatorLight{parent=tone_page_states,x=6,y=2,label="5",colors=c_blue_gray}
|
||||
local tt_6 = IndicatorLight{parent=tone_page_states,x=6,label="6",colors=c_blue_gray}
|
||||
local tt_7 = IndicatorLight{parent=tone_page_states,x=6,label="7",colors=c_blue_gray}
|
||||
local tt_8 = IndicatorLight{parent=tone_page_states,x=6,label="8",colors=c_blue_gray}
|
||||
|
||||
local tt = { tt_1, tt_2, tt_3, tt_4, tt_5, tt_6, tt_7, tt_8 }
|
||||
|
||||
for i = 1, #tt do
|
||||
tt[i].register(ps, "alarm_tone_" .. i, tt[i].update)
|
||||
end
|
||||
|
||||
local tones = Div{parent=tones_div,x=14,y=3,height=10,width=8,fg_bg=cpair(colors.black,colors.yellow)}
|
||||
|
||||
TextBox{parent=tones,text="Tones",alignment=ALIGN.CENTER,fg_bg=tones_div.get_fg_bg()}
|
||||
|
||||
local test_btns = {}
|
||||
test_btns[1] = SwitchButton{parent=tones,text="TEST 1",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_1}
|
||||
test_btns[2] = SwitchButton{parent=tones,text="TEST 2",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_2}
|
||||
test_btns[3] = SwitchButton{parent=tones,text="TEST 3",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_3}
|
||||
test_btns[4] = SwitchButton{parent=tones,text="TEST 4",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_4}
|
||||
test_btns[5] = SwitchButton{parent=tones,text="TEST 5",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_5}
|
||||
test_btns[6] = SwitchButton{parent=tones,text="TEST 6",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_6}
|
||||
test_btns[7] = SwitchButton{parent=tones,text="TEST 7",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_7}
|
||||
test_btns[8] = SwitchButton{parent=tones,text="TEST 8",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_8}
|
||||
|
||||
ttest.tone_buttons = test_btns
|
||||
|
||||
local function stop_all_tones()
|
||||
for i = 1, #test_btns do test_btns[i].set_value(false) end
|
||||
ttest.stop_tones()
|
||||
end
|
||||
|
||||
PushButton{parent=tones,text="STOP",min_width=8,active_fg_bg=c_wht_gray,fg_bg=cpair(colors.black,colors.red),callback=stop_all_tones}
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region info page
|
||||
|
||||
app.new_page(nil, 3)
|
||||
|
||||
local info_div = Div{parent=page_div}
|
||||
|
||||
TextBox{parent=info_div,x=2,y=1,text="This app provides tools to test alarm sounds by alarm and by tone (1-8)."}
|
||||
TextBox{parent=info_div,x=2,y=6,text="The system must be idle (all units stopped with no alarms active) for testing to run."}
|
||||
TextBox{parent=info_div,x=2,y=12,text="Currently, testing will be denied unless you have a Facility Authentication Key set (this will change in the future)."}
|
||||
|
||||
--#endregion
|
||||
|
||||
-- setup multipane
|
||||
local u_pane = MultiPane{parent=page_div,x=1,y=1,panes={alarms_div,tones_div,info_div}}
|
||||
app.set_root_pane(u_pane)
|
||||
|
||||
local list = {
|
||||
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home },
|
||||
{ label = " \x13 ", color = core.cpair(colors.black, colors.red), callback = function () app.switcher(1) end },
|
||||
{ label = " \x0f ", color = core.cpair(colors.black, colors.yellow), callback = function () app.switcher(2) end },
|
||||
{ label = " ? ", color = core.cpair(colors.black, colors.blue), callback = function () app.switcher(3) end }
|
||||
}
|
||||
|
||||
app.set_sidebar(list)
|
||||
end
|
||||
|
||||
return new_view
|
||||
@ -1,297 +0,0 @@
|
||||
--
|
||||
-- Computer List App
|
||||
--
|
||||
|
||||
local comms = require("scada-common.comms")
|
||||
local const = require("scada-common.constants")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
local pocket = require("pocket.pocket")
|
||||
|
||||
local style = require("pocket.ui.style")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.Div")
|
||||
local ListBox = require("graphics.elements.ListBox")
|
||||
local MultiPane = require("graphics.elements.MultiPane")
|
||||
local Rectangle = require("graphics.elements.Rectangle")
|
||||
local TextBox = require("graphics.elements.TextBox")
|
||||
|
||||
local WaitingAnim = require("graphics.elements.animations.Waiting")
|
||||
|
||||
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
||||
|
||||
local DEV_TYPE = comms.DEVICE_TYPE
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
local cpair = core.cpair
|
||||
local border = core.border
|
||||
|
||||
local APP_ID = pocket.APP_ID
|
||||
|
||||
local lu_col = style.label_unit_pair
|
||||
local box_label = cpair(colors.lightGray, colors.gray)
|
||||
|
||||
-- new computer list page view
|
||||
---@param root Container 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.COMPS, frame, nil, true, false)
|
||||
|
||||
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.orange,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 = db.nav.go_home } })
|
||||
|
||||
local page_div = nil ---@type Div|nil
|
||||
|
||||
-- load the app (create the elements)
|
||||
local function load()
|
||||
local ps = db.ps
|
||||
|
||||
page_div = Div{parent=main,y=2,width=main.get_width()}
|
||||
|
||||
local panes = {} ---@type Div[]
|
||||
|
||||
-- create all page divs
|
||||
for _ = 1, 4 do
|
||||
local div = Div{parent=page_div}
|
||||
table.insert(panes, div)
|
||||
end
|
||||
|
||||
local last_update = 0
|
||||
-- refresh data callback, every 1s it will re-send the query
|
||||
local function update()
|
||||
if util.time_ms() - last_update >= 1000 then
|
||||
db.diag.get_comps()
|
||||
last_update = util.time_ms()
|
||||
end
|
||||
end
|
||||
|
||||
-- create indicators for the ID, firmware, and RTT
|
||||
---@param pfx string
|
||||
---@param rect Rectangle
|
||||
local function create_common_indicators(pfx, rect)
|
||||
local first = TextBox{parent=rect,text="Computer",fg_bg=box_label}
|
||||
TextBox{parent=rect,text="Firmware",fg_bg=box_label}
|
||||
TextBox{parent=rect,text="RTT (ms)",fg_bg=box_label}
|
||||
|
||||
local y = first.get_y()
|
||||
local addr = TextBox{parent=rect,x=10,y=y,text="---"}
|
||||
local fw = TextBox{parent=rect,x=10,y=y+1,text="---"}
|
||||
local rtt = TextBox{parent=rect,x=10,y=y+2,text="---"}
|
||||
|
||||
addr.register(ps, pfx .. "_addr", function (v) addr.set_value(util.strval(v)) end)
|
||||
fw.register(ps, pfx .. "_fw", function (v) fw.set_value(util.strval(v)) end)
|
||||
|
||||
rtt.register(ps, pfx .. "_rtt", function (value)
|
||||
rtt.set_value(util.strval(value))
|
||||
|
||||
if value == "---" then
|
||||
rtt.recolor(colors.white)
|
||||
elseif value > const.HIGH_RTT then
|
||||
rtt.recolor(colors.red)
|
||||
elseif value > const.WARN_RTT then
|
||||
rtt.recolor(colors.yellow)
|
||||
else
|
||||
rtt.recolor(colors.green)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--#region main computer page
|
||||
|
||||
local m_div = Div{parent=panes[1],x=2,width=main.get_width()-2}
|
||||
|
||||
local main_page = app.new_page(nil, 1)
|
||||
main_page.tasks = { update }
|
||||
|
||||
TextBox{parent=m_div,y=1,text="Connected Computers",alignment=ALIGN.CENTER}
|
||||
|
||||
local conns = DataIndicator{parent=m_div,y=3,lu_colors=lu_col,label="Total Online",unit="",format="%8d",value=0,commas=true,width=21}
|
||||
conns.register(ps, "comp_online", conns.update)
|
||||
|
||||
local svr_div = Div{parent=m_div,y=4,height=6}
|
||||
local svr_rect = Rectangle{parent=svr_div,height=6,width=22,border=border(1,colors.white,true),thin=true,fg_bg=cpair(colors.white,colors.gray)}
|
||||
|
||||
TextBox{parent=svr_rect,text="Supervisor"}
|
||||
TextBox{parent=svr_rect,text="Status",fg_bg=box_label}
|
||||
TextBox{parent=svr_rect,x=10,y=2,text="Online",fg_bg=cpair(colors.green,colors._INHERIT)}
|
||||
TextBox{parent=svr_rect,text="Computer",fg_bg=box_label}
|
||||
TextBox{parent=svr_rect,text="Firmware",fg_bg=box_label}
|
||||
local svr_addr = TextBox{parent=svr_rect,x=10,y=3,text="?"}
|
||||
local svr_fw = TextBox{parent=svr_rect,x=10,y=4,text="?"}
|
||||
|
||||
svr_addr.register(ps, "comp_svr_addr", function (v) svr_addr.set_value(util.strval(v)) end)
|
||||
svr_fw.register(ps, "comp_svr_fw", function (v) svr_fw.set_value(util.strval(v)) end)
|
||||
|
||||
local crd_div = Div{parent=m_div,y=11,height=7}
|
||||
local crd_rect = Rectangle{parent=crd_div,height=7,width=21,border=border(1,colors.white,true),thin=true,fg_bg=cpair(colors.white,colors.gray)}
|
||||
|
||||
TextBox{parent=crd_rect,text="Coordinator"}
|
||||
TextBox{parent=crd_rect,text="Status",fg_bg=box_label}
|
||||
local crd_online = TextBox{parent=crd_rect,x=10,y=2,width=8,text="Off-line",fg_bg=cpair(colors.red,colors._INHERIT)}
|
||||
|
||||
create_common_indicators("comp_crd", crd_rect)
|
||||
|
||||
crd_online.register(ps, "comp_crd_online", function (online)
|
||||
if online then
|
||||
crd_online.recolor(colors.green)
|
||||
crd_online.set_value("Online")
|
||||
else
|
||||
crd_online.recolor(colors.red)
|
||||
crd_online.set_value("Off-line")
|
||||
end
|
||||
end)
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region PLC page
|
||||
|
||||
local p_div = Div{parent=panes[2],width=main.get_width()}
|
||||
|
||||
local plc_page = app.new_page(nil, 2)
|
||||
plc_page.tasks = { update }
|
||||
|
||||
TextBox{parent=p_div,y=1,text="PLC Devices",alignment=ALIGN.CENTER}
|
||||
|
||||
local plc_list = ListBox{parent=p_div,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||
local plc_elems = {} ---@type graphics_element[]
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region RTU gateway page
|
||||
|
||||
local r_div = Div{parent=panes[3],width=main.get_width()}
|
||||
|
||||
local rtu_page = app.new_page(nil, 3)
|
||||
rtu_page.tasks = { update }
|
||||
|
||||
TextBox{parent=r_div,y=1,text="RTU Gateway Devices",alignment=ALIGN.CENTER}
|
||||
|
||||
local rtu_list = ListBox{parent=r_div,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||
local rtu_elems = {} ---@type graphics_element[]
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region pocket computer page
|
||||
|
||||
local pk_div = Div{parent=panes[4],width=main.get_width()}
|
||||
|
||||
local pkt_page = app.new_page(nil, 4)
|
||||
pkt_page.tasks = { update }
|
||||
|
||||
TextBox{parent=pk_div,y=1,text="Pocket Devices",alignment=ALIGN.CENTER}
|
||||
|
||||
local pkt_list = ListBox{parent=pk_div,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||
local pkt_elems = {} ---@type graphics_element[]
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region connect/disconnect management
|
||||
|
||||
ps.subscribe("comp_connect", function (id)
|
||||
if id == false then return end
|
||||
|
||||
local pfx = "comp_" .. id
|
||||
local type = ps.get(pfx .. "_type")
|
||||
|
||||
if type == DEV_TYPE.PLC then
|
||||
plc_elems[id] = Div{parent=plc_list,height=7}
|
||||
local rect = Rectangle{parent=plc_elems[id],height=6,x=2,width=20,border=border(1,colors.white,true),thin=true,fg_bg=cpair(colors.white,colors.gray)}
|
||||
|
||||
local title = TextBox{parent=rect,text="PLC (Unit ?)"}
|
||||
title.register(ps, pfx .. "_unit", function (unit) title.set_value("PLC (Unit " .. unit .. ")") end)
|
||||
|
||||
create_common_indicators(pfx, rect)
|
||||
elseif type == DEV_TYPE.RTU then
|
||||
rtu_elems[id] = Div{parent=rtu_list,height=7}
|
||||
local rect = Rectangle{parent=rtu_elems[id],height=6,x=2,width=20,border=border(1,colors.white,true),thin=true,fg_bg=cpair(colors.white,colors.gray)}
|
||||
|
||||
TextBox{parent=rect,text="RTU Gateway"}
|
||||
|
||||
create_common_indicators(pfx, rect)
|
||||
elseif type == DEV_TYPE.PKT then
|
||||
pkt_elems[id] = Div{parent=pkt_list,height=7}
|
||||
local rect = Rectangle{parent=pkt_elems[id],height=6,x=2,width=20,border=border(1,colors.white,true),thin=true,fg_bg=cpair(colors.white,colors.gray)}
|
||||
|
||||
TextBox{parent=rect,text="Pocket Computer"}
|
||||
|
||||
create_common_indicators(pfx, rect)
|
||||
end
|
||||
end)
|
||||
|
||||
ps.subscribe("comp_disconnect", function (id)
|
||||
if id == false then return end
|
||||
|
||||
local type = ps.get("comp_" ..id .. "_type")
|
||||
|
||||
if type == DEV_TYPE.PLC then
|
||||
if plc_elems[id] then plc_elems[id].delete() end
|
||||
plc_elems[id] = nil
|
||||
elseif type == DEV_TYPE.RTU then
|
||||
if rtu_elems[id] then rtu_elems[id].delete() end
|
||||
rtu_elems[id] = nil
|
||||
elseif type == DEV_TYPE.PKT then
|
||||
if pkt_elems[id] then pkt_elems[id].delete() end
|
||||
pkt_elems[id] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
--#endregion
|
||||
|
||||
-- setup multipane
|
||||
local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||
app.set_root_pane(u_pane)
|
||||
|
||||
-- setup sidebar
|
||||
|
||||
local list = {
|
||||
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home },
|
||||
{ label = " @ ", color = core.cpair(colors.black, colors.blue), callback = main_page.nav_to },
|
||||
{ label = "PLC", color = core.cpair(colors.black, colors.red), callback = plc_page.nav_to },
|
||||
{ label = "RTU", color = core.cpair(colors.black, colors.orange), callback = rtu_page.nav_to },
|
||||
{ label = "PKT", color = core.cpair(colors.black, colors.lightGray), callback = pkt_page.nav_to }
|
||||
}
|
||||
|
||||
app.set_sidebar(list)
|
||||
|
||||
-- done, show the app
|
||||
main_page.nav_to()
|
||||
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 = db.nav.go_home } })
|
||||
app.delete_pages()
|
||||
|
||||
-- show loading screen
|
||||
load_pane.set_value(1)
|
||||
|
||||
-- clear the list of connected computers so that connections re-appear on reload of this app
|
||||
iocontrol.rx.clear_comp_record()
|
||||
end
|
||||
|
||||
app.set_load(load)
|
||||
app.set_unload(unload)
|
||||
|
||||
return main
|
||||
end
|
||||
|
||||
return new_view
|
||||
118
pocket/ui/apps/diag_apps.lua
Normal file
118
pocket/ui/apps/diag_apps.lua
Normal file
@ -0,0 +1,118 @@
|
||||
--
|
||||
-- Diagnostic Apps
|
||||
--
|
||||
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
local pocket = require("pocket.pocket")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.Div")
|
||||
local TextBox = require("graphics.elements.TextBox")
|
||||
|
||||
local IndicatorLight = require("graphics.elements.indicators.IndicatorLight")
|
||||
|
||||
local Checkbox = require("graphics.elements.controls.Checkbox")
|
||||
local PushButton = require("graphics.elements.controls.PushButton")
|
||||
local SwitchButton = require("graphics.elements.controls.SwitchButton")
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
local cpair = core.cpair
|
||||
|
||||
local APP_ID = pocket.APP_ID
|
||||
|
||||
-- create diagnostic app pages
|
||||
---@param root Container parent
|
||||
local function create_pages(root)
|
||||
local db = iocontrol.get_db()
|
||||
|
||||
------------------------
|
||||
-- Alarm Testing Page --
|
||||
------------------------
|
||||
|
||||
local alarm_test = Div{parent=root,x=1,y=1}
|
||||
|
||||
local alarm_app = db.nav.register_app(APP_ID.ALARMS, alarm_test, nil, true)
|
||||
|
||||
local page = alarm_app.new_page(nil, function () end)
|
||||
page.tasks = { db.diag.tone_test.get_tone_states }
|
||||
|
||||
local ttest = db.diag.tone_test
|
||||
|
||||
local c_wht_gray = cpair(colors.white, colors.gray)
|
||||
local c_red_gray = cpair(colors.red, colors.gray)
|
||||
local c_yel_gray = cpair(colors.yellow, colors.gray)
|
||||
local c_blue_gray = cpair(colors.blue, colors.gray)
|
||||
|
||||
local audio = Div{parent=alarm_test,x=1,y=1}
|
||||
|
||||
TextBox{parent=audio,y=1,text="Alarm Sounder Tests",alignment=ALIGN.CENTER}
|
||||
|
||||
ttest.ready_warn = TextBox{parent=audio,y=2,text="",alignment=ALIGN.CENTER,fg_bg=cpair(colors.yellow,colors.black)}
|
||||
|
||||
local tones = Div{parent=audio,x=2,y=3,height=10,width=8,fg_bg=cpair(colors.black,colors.yellow)}
|
||||
|
||||
TextBox{parent=tones,text="Tones",alignment=ALIGN.CENTER,fg_bg=audio.get_fg_bg()}
|
||||
|
||||
local test_btns = {}
|
||||
test_btns[1] = SwitchButton{parent=tones,text="TEST 1",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_1}
|
||||
test_btns[2] = SwitchButton{parent=tones,text="TEST 2",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_2}
|
||||
test_btns[3] = SwitchButton{parent=tones,text="TEST 3",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_3}
|
||||
test_btns[4] = SwitchButton{parent=tones,text="TEST 4",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_4}
|
||||
test_btns[5] = SwitchButton{parent=tones,text="TEST 5",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_5}
|
||||
test_btns[6] = SwitchButton{parent=tones,text="TEST 6",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_6}
|
||||
test_btns[7] = SwitchButton{parent=tones,text="TEST 7",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_7}
|
||||
test_btns[8] = SwitchButton{parent=tones,text="TEST 8",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_8}
|
||||
|
||||
ttest.tone_buttons = test_btns
|
||||
|
||||
local function stop_all_tones()
|
||||
for i = 1, #test_btns do test_btns[i].set_value(false) end
|
||||
ttest.stop_tones()
|
||||
end
|
||||
|
||||
PushButton{parent=tones,text="STOP",min_width=8,active_fg_bg=c_wht_gray,fg_bg=cpair(colors.black,colors.red),callback=stop_all_tones}
|
||||
|
||||
local alarms = Div{parent=audio,x=11,y=3,height=15,fg_bg=cpair(colors.lightGray,colors.black)}
|
||||
|
||||
TextBox{parent=alarms,text="Alarms (\x13)",alignment=ALIGN.CENTER,fg_bg=audio.get_fg_bg()}
|
||||
|
||||
local alarm_btns = {}
|
||||
alarm_btns[1] = Checkbox{parent=alarms,label="BREACH",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_breach}
|
||||
alarm_btns[2] = Checkbox{parent=alarms,label="RADIATION",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_rad}
|
||||
alarm_btns[3] = Checkbox{parent=alarms,label="RCT LOST",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_lost}
|
||||
alarm_btns[4] = Checkbox{parent=alarms,label="CRIT DAMAGE",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_crit}
|
||||
alarm_btns[5] = Checkbox{parent=alarms,label="DAMAGE",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_dmg}
|
||||
alarm_btns[6] = Checkbox{parent=alarms,label="OVER TEMP",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_overtemp}
|
||||
alarm_btns[7] = Checkbox{parent=alarms,label="HIGH TEMP",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_hightemp}
|
||||
alarm_btns[8] = Checkbox{parent=alarms,label="WASTE LEAK",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_wasteleak}
|
||||
alarm_btns[9] = Checkbox{parent=alarms,label="WASTE HIGH",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_highwaste}
|
||||
alarm_btns[10] = Checkbox{parent=alarms,label="RPS TRANS",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_rps}
|
||||
alarm_btns[11] = Checkbox{parent=alarms,label="RCS TRANS",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_rcs}
|
||||
alarm_btns[12] = Checkbox{parent=alarms,label="TURBINE TRP",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_turbinet}
|
||||
|
||||
ttest.alarm_buttons = alarm_btns
|
||||
|
||||
local function stop_all_alarms()
|
||||
for i = 1, #alarm_btns do alarm_btns[i].set_value(false) end
|
||||
ttest.stop_alarms()
|
||||
end
|
||||
|
||||
PushButton{parent=alarms,x=3,y=15,text="STOP \x13",min_width=8,fg_bg=cpair(colors.black,colors.red),active_fg_bg=c_wht_gray,callback=stop_all_alarms}
|
||||
|
||||
local states = Div{parent=audio,x=2,y=14,height=5,width=8}
|
||||
|
||||
TextBox{parent=states,text="States",alignment=ALIGN.CENTER}
|
||||
local t_1 = IndicatorLight{parent=states,label="1",colors=c_blue_gray}
|
||||
local t_2 = IndicatorLight{parent=states,label="2",colors=c_blue_gray}
|
||||
local t_3 = IndicatorLight{parent=states,label="3",colors=c_blue_gray}
|
||||
local t_4 = IndicatorLight{parent=states,label="4",colors=c_blue_gray}
|
||||
local t_5 = IndicatorLight{parent=states,x=6,y=2,label="5",colors=c_blue_gray}
|
||||
local t_6 = IndicatorLight{parent=states,x=6,label="6",colors=c_blue_gray}
|
||||
local t_7 = IndicatorLight{parent=states,x=6,label="7",colors=c_blue_gray}
|
||||
local t_8 = IndicatorLight{parent=states,x=6,label="8",colors=c_blue_gray}
|
||||
|
||||
ttest.tone_indicators = { t_1, t_2, t_3, t_4, t_5, t_6, t_7, t_8 }
|
||||
end
|
||||
|
||||
return create_pages
|
||||
29
pocket/ui/apps/dummy_app.lua
Normal file
29
pocket/ui/apps/dummy_app.lua
Normal file
@ -0,0 +1,29 @@
|
||||
--
|
||||
-- Placeholder App
|
||||
--
|
||||
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
local pocket = require("pocket.pocket")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.Div")
|
||||
local TextBox = require("graphics.elements.TextBox")
|
||||
|
||||
local APP_ID = pocket.APP_ID
|
||||
|
||||
-- create placeholder app page
|
||||
---@param root Container parent
|
||||
local function create_pages(root)
|
||||
local db = iocontrol.get_db()
|
||||
|
||||
local main = Div{parent=root,x=1,y=1}
|
||||
|
||||
db.nav.register_app(APP_ID.DUMMY, main).new_page(nil, function () end)
|
||||
|
||||
TextBox{parent=main,text="This app is not implemented yet.",x=1,y=2,alignment=core.ALIGN.CENTER}
|
||||
|
||||
TextBox{parent=main,text=" pretend something cool is here \x03",x=1,y=10,alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.gray,colors.black)}
|
||||
end
|
||||
|
||||
return create_pages
|
||||
@ -41,7 +41,7 @@ local grn_ind_s = style.icon_states.grn_ind_s
|
||||
-- new unit page view
|
||||
---@param root Container parent
|
||||
local function new_view(root)
|
||||
local db = iocontrol.get_db()
|
||||
local db = iocontrol.get_db()
|
||||
|
||||
local frame = Div{parent=root,x=1,y=1}
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ local iocontrol = require("pocket.iocontrol")
|
||||
local pocket = require("pocket.pocket")
|
||||
|
||||
local docs = require("pocket.ui.docs")
|
||||
-- local style = require("pocket.ui.style")
|
||||
|
||||
local guide_section = require("pocket.ui.pages.guide_section")
|
||||
|
||||
@ -30,6 +31,10 @@ local cpair = core.cpair
|
||||
|
||||
local APP_ID = pocket.APP_ID
|
||||
|
||||
-- local label = style.label
|
||||
-- local lu_col = style.label_unit_pair
|
||||
-- local text_fg = style.text_fg
|
||||
|
||||
-- new system guide view
|
||||
---@param root Container parent
|
||||
local function new_view(root)
|
||||
@ -42,21 +47,14 @@ local function new_view(root)
|
||||
local load_div = Div{parent=frame,x=1,y=1}
|
||||
local main = Div{parent=frame,x=1,y=1}
|
||||
|
||||
WaitingAnim{parent=load_div,x=math.floor(main.get_width()/2)-1,y=8,fg_bg=cpair(colors.cyan,colors._INHERIT)}
|
||||
TextBox{parent=load_div,y=12,text="Loading...",alignment=ALIGN.CENTER}
|
||||
local load_text_1 = TextBox{parent=load_div,y=14,text="",alignment=ALIGN.CENTER,fg_bg=cpair(colors.lightGray,colors._INHERIT)}
|
||||
local load_text_2 = TextBox{parent=load_div,y=15,text="",alignment=ALIGN.CENTER,fg_bg=cpair(colors.lightGray,colors._INHERIT)}
|
||||
|
||||
-- give more detailed information so the user doesn't give up
|
||||
local function load_text(a, b)
|
||||
if a then load_text_1.set_value(a) end
|
||||
load_text_2.set_value(b or "")
|
||||
end
|
||||
WaitingAnim{parent=load_div,x=math.floor(main.get_width()/2)-1,y=8,fg_bg=cpair(colors.cyan,colors._INHERIT)}
|
||||
|
||||
local load_pane = MultiPane{parent=main,x=1,y=1,panes={load_div,main}}
|
||||
|
||||
local btn_fg_bg = cpair(colors.cyan, colors.black)
|
||||
local btn_active = cpair(colors.white, colors.black)
|
||||
local btn_disable = cpair(colors.gray, colors.black)
|
||||
|
||||
app.set_sidebar({{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home }})
|
||||
|
||||
@ -73,7 +71,7 @@ local function new_view(root)
|
||||
app.set_sidebar(list)
|
||||
|
||||
page_div = Div{parent=main,y=2}
|
||||
local p_width = page_div.get_width() - 1
|
||||
local p_width = page_div.get_width() - 2
|
||||
|
||||
local main_page = app.new_page(nil, 1)
|
||||
local search_page = app.new_page(main_page, 2)
|
||||
@ -106,8 +104,6 @@ local function new_view(root)
|
||||
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}
|
||||
|
||||
load_text("Search")
|
||||
|
||||
TextBox{parent=search,y=1,text="Search",alignment=ALIGN.CENTER}
|
||||
|
||||
local query_field = TextField{parent=search,x=1,y=3,width=18,fg_bg=cpair(colors.white,colors.gray)}
|
||||
@ -175,29 +171,14 @@ local function new_view(root)
|
||||
|
||||
util.nop()
|
||||
|
||||
load_text("System Usage")
|
||||
|
||||
TextBox{parent=use,y=1,text="System Usage",alignment=ALIGN.CENTER}
|
||||
PushButton{parent=use,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
||||
|
||||
load_text(false, "Connecting Devices")
|
||||
local conn_dev_page = guide_section(sect_construct_data, use_page, "Connecting Devs", docs.usage.conn, 110)
|
||||
load_text(false, "Configuring Devices")
|
||||
local config_dev_page = guide_section(sect_construct_data, use_page, "Configuring Devs", docs.usage.config, 350)
|
||||
load_text(false, "Manual Control")
|
||||
local man_ctrl_page = guide_section(sect_construct_data, use_page, "Manual Control", docs.usage.manual, 100)
|
||||
load_text(false, "Auto Control")
|
||||
local auto_ctrl_page = guide_section(sect_construct_data, use_page, "Auto Control", docs.usage.auto, 200)
|
||||
load_text(false, "Waste Control")
|
||||
local waste_ctrl_page = guide_section(sect_construct_data, use_page, "Waste Control", docs.usage.waste, 120)
|
||||
|
||||
PushButton{parent=use,y=3,text="Connecting Devices >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=conn_dev_page.nav_to}
|
||||
PushButton{parent=use,text="Configuring Devices >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=config_dev_page.nav_to}
|
||||
PushButton{parent=use,text="Manual Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=man_ctrl_page.nav_to}
|
||||
PushButton{parent=use,text="Automatic Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=auto_ctrl_page.nav_to}
|
||||
PushButton{parent=use,text="Waste Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=waste_ctrl_page.nav_to}
|
||||
|
||||
load_text("Operator UIs")
|
||||
PushButton{parent=use,y=3,text="Configuring Devices >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||
PushButton{parent=use,text="Connecting Devices >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||
PushButton{parent=use,text="Manual Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||
PushButton{parent=use,text="Automatic Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||
PushButton{parent=use,text="Waste Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||
|
||||
TextBox{parent=uis,y=1,text="Operator UIs",alignment=ALIGN.CENTER}
|
||||
PushButton{parent=uis,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
||||
@ -206,84 +187,51 @@ local function new_view(root)
|
||||
local annunc_div = Div{parent=page_div,x=2}
|
||||
table.insert(panes, annunc_div)
|
||||
|
||||
local coord_page = app.new_page(uis_page, #panes + 1)
|
||||
local coord_div = Div{parent=page_div,x=2}
|
||||
table.insert(panes, coord_div)
|
||||
|
||||
load_text(false, "Alarms")
|
||||
|
||||
local alarms_page = guide_section(sect_construct_data, uis_page, "Alarms", docs.alarms, 100)
|
||||
|
||||
PushButton{parent=uis,y=3,text="Alarms >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=alarms_page.nav_to}
|
||||
PushButton{parent=uis,text="Annunciators >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=annunc_page.nav_to}
|
||||
PushButton{parent=uis,text="Coordinator UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=coord_page.nav_to}
|
||||
|
||||
load_text(false, "Annunciators")
|
||||
PushButton{parent=uis,text="Pocket UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||
PushButton{parent=uis,text="Coordinator UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||
|
||||
TextBox{parent=annunc_div,y=1,text="Annunciators",alignment=ALIGN.CENTER}
|
||||
PushButton{parent=annunc_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=uis_page.nav_to}
|
||||
|
||||
local fac_annunc_page = guide_section(sect_construct_data, annunc_page, "Facility", docs.annunc.facility.main_section, 110)
|
||||
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_rcs_page = guide_section(sect_construct_data, annunc_page, "Unit RCS", docs.annunc.unit.rcs_section, 170)
|
||||
|
||||
PushButton{parent=annunc_div,y=3,text="Facility General >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fac_annunc_page.nav_to}
|
||||
PushButton{parent=annunc_div,text="Unit General >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_gen_page.nav_to}
|
||||
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,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 RCS >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_rcs_page.nav_to}
|
||||
|
||||
load_text(false, "Coordinator UI")
|
||||
|
||||
TextBox{parent=coord_div,y=1,text="Coordinator UI",alignment=ALIGN.CENTER}
|
||||
PushButton{parent=coord_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=uis_page.nav_to}
|
||||
|
||||
load_text(false, "Main Display")
|
||||
local main_disp_page = guide_section(sect_construct_data, coord_page, "Main Display", docs.c_ui.main, 300)
|
||||
load_text(false, "Flow Display")
|
||||
local flow_disp_page = guide_section(sect_construct_data, coord_page, "Flow Display", docs.c_ui.flow, 210)
|
||||
load_text(false, "Unit Displays")
|
||||
local unit_disp_page = guide_section(sect_construct_data, coord_page, "Unit Displays", docs.c_ui.unit, 150)
|
||||
|
||||
PushButton{parent=coord_div,y=3,text="Main Display >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_disp_page.nav_to}
|
||||
PushButton{parent=coord_div,text="Flow Display >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=flow_disp_page.nav_to}
|
||||
PushButton{parent=coord_div,text="Unit Displays >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_disp_page.nav_to}
|
||||
|
||||
load_text("Front Panels")
|
||||
PushButton{parent=annunc_div,text="Facility General >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fac_annunc_page.nav_to}
|
||||
PushButton{parent=annunc_div,text="Waste & Valves >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||
|
||||
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}
|
||||
|
||||
load_text(false, "Common Items")
|
||||
local fp_common_page = guide_section(sect_construct_data, fps_page, "Common Items", docs.fp.common, 100)
|
||||
load_text(false, "Reactor PLC")
|
||||
local fp_rplc_page = guide_section(sect_construct_data, fps_page, "Reactor PLC", docs.fp.r_plc, 190)
|
||||
load_text(false, "RTU Gateway")
|
||||
local fp_rplc_page = guide_section(sect_construct_data, fps_page, "Reactor PLC", docs.fp.r_plc, 180)
|
||||
local fp_rtu_page = guide_section(sect_construct_data, fps_page, "RTU Gateway", docs.fp.rtu_gw, 100)
|
||||
load_text(false, "Supervisor")
|
||||
local fp_supervisor_page = guide_section(sect_construct_data, fps_page, "Supervisor", docs.fp.supervisor, 160)
|
||||
load_text(false, "Coordinator")
|
||||
local fp_coordinator_page = guide_section(sect_construct_data, fps_page, "Coordinator", docs.fp.coordinator, 80)
|
||||
|
||||
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,callback=fp_coordinator_page.nav_to}
|
||||
|
||||
load_text("Glossary")
|
||||
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}
|
||||
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, 140)
|
||||
local gls_term_page = guide_section(sect_construct_data, gls_page, "Terminology", docs.glossary.terms, 120)
|
||||
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,text="Terminology >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_term_page.nav_to}
|
||||
|
||||
load_text("Links")
|
||||
|
||||
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}
|
||||
|
||||
|
||||
@ -32,29 +32,16 @@ local function create_pages(root)
|
||||
|
||||
local root_pane = MultiPane{parent=main,x=1,y=1,panes={conn_sv_wait,conn_api_wait,main_pane}}
|
||||
|
||||
local function update()
|
||||
local state = db.ps.get("link_state")
|
||||
|
||||
if state == LINK_STATE.UNLINKED then
|
||||
root_pane.register(db.ps, "link_state", function (state)
|
||||
if state == LINK_STATE.UNLINKED or state == LINK_STATE.API_LINK_ONLY then
|
||||
root_pane.set_value(1)
|
||||
elseif state == LINK_STATE.API_LINK_ONLY then
|
||||
if not db.loader_require.sv then
|
||||
root_pane.set_value(3)
|
||||
db.nav.on_loader_connected()
|
||||
else root_pane.set_value(1) end
|
||||
elseif state == LINK_STATE.SV_LINK_ONLY then
|
||||
if not db.loader_require.api then
|
||||
root_pane.set_value(3)
|
||||
db.nav.on_loader_connected()
|
||||
else root_pane.set_value(2) end
|
||||
root_pane.set_value(2)
|
||||
else
|
||||
root_pane.set_value(3)
|
||||
db.nav.on_loader_connected()
|
||||
end
|
||||
end
|
||||
|
||||
root_pane.register(db.ps, "link_state", update)
|
||||
root_pane.register(db.ps, "loader_reqs", update)
|
||||
end)
|
||||
|
||||
TextBox{parent=main_pane,text="Connected!",x=1,y=6,alignment=core.ALIGN.CENTER}
|
||||
end
|
||||
|
||||
@ -194,7 +194,7 @@ local function new_view(root)
|
||||
|
||||
TextBox{parent=c_div,y=1,text="Process Control",alignment=ALIGN.CENTER}
|
||||
|
||||
local u_stat = Rectangle{parent=c_div,border=border(1,colors.gray,true),thin=true,width=21,height=5,x=1,y=3,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
local u_stat = Rectangle{parent=c_div,border=border(1,colors.gray,true),thin=true,width=21,height=5,x=1,y=3,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",alignment=ALIGN.CENTER}
|
||||
local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data...",height=2,alignment=ALIGN.CENTER,trim_whitespace=true,fg_bg=cpair(colors.gray,colors.lightGray)}
|
||||
|
||||
@ -210,7 +210,7 @@ local function new_view(root)
|
||||
end
|
||||
|
||||
local start = HazardButton{parent=c_div,x=2,y=9,text="START",accent=colors.lightBlue,callback=_start_auto,timeout=3,fg_bg=hzd_fg_bg,dis_colors=dis_colors}
|
||||
local stop = HazardButton{parent=c_div,x=13,y=9,text="STOP",accent=colors.red,callback=process.process_stop,timeout=3,fg_bg=hzd_fg_bg,dis_colors=dis_colors}
|
||||
local stop = HazardButton{parent=c_div,x=13,y=9,text="STOP",accent=colors.red,callback=process.process_stop,timeout=3,fg_bg=hzd_fg_bg,dis_colors=dis_colors}
|
||||
|
||||
db.facility.start_ack = start.on_response
|
||||
db.facility.stop_ack = stop.on_response
|
||||
|
||||
@ -1,219 +0,0 @@
|
||||
--
|
||||
-- Radiation Monitor App
|
||||
--
|
||||
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
local pocket = require("pocket.pocket")
|
||||
|
||||
local style = require("pocket.ui.style")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.Div")
|
||||
local ListBox = require("graphics.elements.ListBox")
|
||||
local MultiPane = require("graphics.elements.MultiPane")
|
||||
local Rectangle = require("graphics.elements.Rectangle")
|
||||
local TextBox = require("graphics.elements.TextBox")
|
||||
|
||||
local WaitingAnim = require("graphics.elements.animations.Waiting")
|
||||
|
||||
local RadIndicator = require("graphics.elements.indicators.RadIndicator")
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
local cpair = core.cpair
|
||||
local border = core.border
|
||||
|
||||
local APP_ID = pocket.APP_ID
|
||||
|
||||
local label_fg_bg = style.label
|
||||
local lu_col = style.label_unit_pair
|
||||
|
||||
-- new radiation monitor page view
|
||||
---@param root Container 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.RADMON, 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.yellow,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 = db.nav.go_home } })
|
||||
|
||||
local page_div = nil ---@type Div|nil
|
||||
|
||||
-- load the app (create the elements)
|
||||
local function load()
|
||||
local f_ps = db.facility.ps
|
||||
|
||||
page_div = Div{parent=main,y=2,width=main.get_width()}
|
||||
|
||||
local panes = {} ---@type Div[]
|
||||
|
||||
-- create all page divs
|
||||
for _ = 1, db.facility.num_units + 2 do
|
||||
local div = Div{parent=page_div}
|
||||
table.insert(panes, div)
|
||||
end
|
||||
|
||||
local last_update = 0
|
||||
-- refresh data callback, every 500ms it will re-send the query
|
||||
local function update()
|
||||
if util.time_ms() - last_update >= 500 then
|
||||
db.api.get_rad()
|
||||
last_update = util.time_ms()
|
||||
end
|
||||
end
|
||||
|
||||
-- create a new radiation monitor list
|
||||
---@param parent Container
|
||||
---@param ps psil
|
||||
local function new_mon_list(parent, ps)
|
||||
local mon_list = ListBox{parent=parent,y=6,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||
|
||||
local elem_list = {} ---@type graphics_element[]
|
||||
|
||||
mon_list.register(ps, "radiation_monitors", function (data)
|
||||
local ids = textutils.unserialize(data)
|
||||
|
||||
-- delete any disconnected monitors
|
||||
for id, elem in pairs(elem_list) do
|
||||
if not util.table_contains(ids, id) then
|
||||
elem.delete()
|
||||
elem_list[id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- add newly connected monitors
|
||||
for _, id in pairs(ids) do
|
||||
if not elem_list[id] then
|
||||
elem_list[id] = Div{parent=mon_list,height=5}
|
||||
local mon_rect = Rectangle{parent=elem_list[id],height=4,x=2,width=20,border=border(1,colors.gray,true),thin=true,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
|
||||
TextBox{parent=mon_rect,text="Env. Detector "..id}
|
||||
local mon_rad = RadIndicator{parent=mon_rect,x=2,label="",format="%13.3f",lu_colors=cpair(colors.gray,colors.gray),width=18}
|
||||
mon_rad.register(ps, "radiation@" .. id, mon_rad.update)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--#region unit radiation monitors
|
||||
|
||||
for i = 1, db.facility.num_units do
|
||||
local u_pane = panes[i]
|
||||
local u_div = Div{parent=u_pane}
|
||||
local unit = db.units[i]
|
||||
local u_ps = unit.unit_ps
|
||||
|
||||
local u_page = app.new_page(nil, i)
|
||||
u_page.tasks = { update }
|
||||
|
||||
TextBox{parent=u_div,y=1,text="Unit #"..i.." Monitors",alignment=ALIGN.CENTER}
|
||||
|
||||
TextBox{parent=u_div,x=2,y=3,text="Max Radiation",fg_bg=label_fg_bg}
|
||||
local radiation = RadIndicator{parent=u_div,x=2,label="",format="%17.3f",lu_colors=lu_col,width=21}
|
||||
radiation.register(u_ps, "radiation", radiation.update)
|
||||
|
||||
new_mon_list(u_div, u_ps)
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region overview page
|
||||
|
||||
local s_pane = panes[db.facility.num_units + 1]
|
||||
local s_div = Div{parent=s_pane,x=2,width=main.get_width()-2}
|
||||
|
||||
local stat_page = app.new_page(nil, db.facility.num_units + 1)
|
||||
stat_page.tasks = { update }
|
||||
|
||||
TextBox{parent=s_div,y=1,text=" Radiation Monitoring",alignment=ALIGN.CENTER}
|
||||
|
||||
TextBox{parent=s_div,y=3,text="Max Facility Rad.",fg_bg=label_fg_bg}
|
||||
local s_f_rad = RadIndicator{parent=s_div,label="",format="%17.3f",lu_colors=lu_col,width=21}
|
||||
s_f_rad.register(f_ps, "radiation", s_f_rad.update)
|
||||
|
||||
for i = 1, db.facility.num_units do
|
||||
local unit = db.units[i]
|
||||
local u_ps = unit.unit_ps
|
||||
|
||||
s_div.line_break()
|
||||
TextBox{parent=s_div,text="Max Unit "..i.." Radiation",fg_bg=label_fg_bg}
|
||||
local s_u_rad = RadIndicator{parent=s_div,label="",format="%17.3f",lu_colors=lu_col,width=21}
|
||||
s_u_rad.register(u_ps, "radiation", s_u_rad.update)
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region overview page
|
||||
|
||||
local f_pane = panes[db.facility.num_units + 2]
|
||||
local f_div = Div{parent=f_pane,width=main.get_width()}
|
||||
|
||||
local fac_page = app.new_page(nil, db.facility.num_units + 2)
|
||||
fac_page.tasks = { update }
|
||||
|
||||
TextBox{parent=f_div,y=1,text="Facility Monitors",alignment=ALIGN.CENTER}
|
||||
|
||||
TextBox{parent=f_div,x=2,y=3,text="Max Radiation",fg_bg=label_fg_bg}
|
||||
local f_rad = RadIndicator{parent=f_div,x=2,label="",format="%17.3f",lu_colors=lu_col,width=21}
|
||||
f_rad.register(f_ps, "radiation", f_rad.update)
|
||||
|
||||
new_mon_list(f_div, f_ps)
|
||||
|
||||
--#endregion
|
||||
|
||||
-- setup multipane
|
||||
local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||
app.set_root_pane(u_pane)
|
||||
|
||||
-- setup sidebar
|
||||
|
||||
local list = {
|
||||
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home },
|
||||
{ label = " \x1e ", color = core.cpair(colors.black, colors.blue), callback = stat_page.nav_to },
|
||||
{ label = "FAC", color = core.cpair(colors.black, colors.yellow), callback = fac_page.nav_to }
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
-- done, show the app
|
||||
stat_page.nav_to()
|
||||
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 = db.nav.go_home } })
|
||||
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
|
||||
@ -1,5 +1,5 @@
|
||||
--
|
||||
-- About Page
|
||||
-- System Apps
|
||||
--
|
||||
|
||||
local comms = require("scada-common.comms")
|
||||
@ -24,21 +24,25 @@ local cpair = core.cpair
|
||||
|
||||
local APP_ID = pocket.APP_ID
|
||||
|
||||
-- create about page view
|
||||
-- create system app pages
|
||||
---@param root Container parent
|
||||
local function create_pages(root)
|
||||
local db = iocontrol.get_db()
|
||||
|
||||
local frame = Div{parent=root,x=1,y=1}
|
||||
----------------
|
||||
-- About Page --
|
||||
----------------
|
||||
|
||||
local app = db.nav.register_app(APP_ID.ABOUT, frame)
|
||||
local about_root = Div{parent=root,x=1,y=1}
|
||||
|
||||
local about_page = app.new_page(nil, 1)
|
||||
local nt_page = app.new_page(about_page, 2)
|
||||
local fw_page = app.new_page(about_page, 3)
|
||||
local hw_page = app.new_page(about_page, 4)
|
||||
local about_app = db.nav.register_app(APP_ID.ABOUT, about_root)
|
||||
|
||||
local about = Div{parent=frame,x=1,y=2}
|
||||
local about_page = about_app.new_page(nil, 1)
|
||||
local nt_page = about_app.new_page(about_page, 2)
|
||||
local fw_page = about_app.new_page(about_page, 3)
|
||||
local hw_page = about_app.new_page(about_page, 4)
|
||||
|
||||
local about = Div{parent=about_root,x=1,y=2}
|
||||
|
||||
TextBox{parent=about,y=1,text="System Information",alignment=ALIGN.CENTER}
|
||||
|
||||
@ -54,7 +58,7 @@ local function create_pages(root)
|
||||
|
||||
local config = pocket.config
|
||||
|
||||
local nt_div = Div{parent=frame,x=1,y=2}
|
||||
local nt_div = Div{parent=about_root,x=1,y=2}
|
||||
TextBox{parent=nt_div,y=1,text="Network Details",alignment=ALIGN.CENTER}
|
||||
|
||||
PushButton{parent=nt_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
||||
@ -83,7 +87,7 @@ local function create_pages(root)
|
||||
|
||||
--#region Firmware Versions
|
||||
|
||||
local fw_div = Div{parent=frame,x=1,y=2}
|
||||
local fw_div = Div{parent=about_root,x=1,y=2}
|
||||
TextBox{parent=fw_div,y=1,text="Firmware Versions",alignment=ALIGN.CENTER}
|
||||
|
||||
PushButton{parent=fw_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
||||
@ -119,7 +123,7 @@ local function create_pages(root)
|
||||
|
||||
--#region Host Versions
|
||||
|
||||
local hw_div = Div{parent=frame,x=1,y=2}
|
||||
local hw_div = Div{parent=about_root,x=1,y=2}
|
||||
TextBox{parent=hw_div,y=1,text="Host Versions",alignment=ALIGN.CENTER}
|
||||
|
||||
PushButton{parent=hw_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
||||
@ -134,9 +138,9 @@ local function create_pages(root)
|
||||
|
||||
--#endregion
|
||||
|
||||
local root_pane = MultiPane{parent=frame,x=1,y=1,panes={about,nt_div,fw_div,hw_div}}
|
||||
local root_pane = MultiPane{parent=about_root,x=1,y=1,panes={about,nt_div,fw_div,hw_div}}
|
||||
|
||||
app.set_root_pane(root_pane)
|
||||
about_app.set_root_pane(root_pane)
|
||||
end
|
||||
|
||||
return create_pages
|
||||
@ -312,6 +312,8 @@ local function new_view(root)
|
||||
c_emg.register(u_ps, "EmergencyCoolant", c_emg.update)
|
||||
c_mwrf.register(u_ps, "MaxWaterReturnFeed", c_mwrf.update)
|
||||
|
||||
-- rcs_div.line_break()
|
||||
-- TextBox{parent=rcs_div,text="Mismatches",alignment=ALIGN.CENTER,fg_bg=label}
|
||||
local c_cfm = IconIndicator{parent=rcs_div,label="Coolant Feed",states=yel_ind_s}
|
||||
local c_brm = IconIndicator{parent=rcs_div,label="Boil Rate",states=yel_ind_s}
|
||||
local c_sfm = IconIndicator{parent=rcs_div,label="Steam Feed",states=yel_ind_s}
|
||||
@ -321,6 +323,7 @@ local function new_view(root)
|
||||
c_sfm.register(u_ps, "SteamFeedMismatch", c_sfm.update)
|
||||
|
||||
rcs_div.line_break()
|
||||
-- TextBox{parent=rcs_div,text="Aggregate Checks",alignment=ALIGN.CENTER,fg_bg=label}
|
||||
|
||||
if unit.num_boilers > 0 then
|
||||
local wll = IconIndicator{parent=rcs_div,label="Boiler Water Lo",states=red_ind_s}
|
||||
|
||||
@ -95,8 +95,8 @@ local function new_view(root)
|
||||
|
||||
local function set_waste(mode) process.set_unit_waste(i, mode) end
|
||||
|
||||
local waste_prod = StateIndicator{parent=u_div,x=16,y=3,states=style.get_waste().states_abbrv,value=1,min_width=6}
|
||||
local waste_mode = RadioButton{parent=u_div,y=3,options=style.get_waste().unit_opts,callback=set_waste,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.white}
|
||||
local waste_prod = StateIndicator{parent=u_div,x=16,y=3,states=style.waste.states_abbrv,value=1,min_width=6}
|
||||
local waste_mode = RadioButton{parent=u_div,y=3,options=style.waste.unit_opts,callback=set_waste,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.white}
|
||||
|
||||
waste_prod.register(u_ps, "U_WasteProduct", waste_prod.update)
|
||||
waste_mode.register(u_ps, "U_WasteMode", waste_mode.set_value)
|
||||
@ -159,8 +159,8 @@ local function new_view(root)
|
||||
|
||||
TextBox{parent=c_div,y=1,text="Waste Control",alignment=ALIGN.CENTER}
|
||||
|
||||
local status = StateIndicator{parent=c_div,x=3,y=3,states=style.get_waste().states,value=1,min_width=17}
|
||||
local waste_prod = RadioButton{parent=c_div,y=5,options=style.get_waste().options,callback=process.set_process_waste,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.white}
|
||||
local status = StateIndicator{parent=c_div,x=3,y=3,states=style.waste.states,value=1,min_width=17}
|
||||
local waste_prod = RadioButton{parent=c_div,y=5,options=style.waste.options,callback=process.set_process_waste,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.white}
|
||||
|
||||
status.register(f_ps, "current_waste_product", status.update)
|
||||
waste_prod.register(f_ps, "process_waste_product", waste_prod.set_value)
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
--
|
||||
-- All the text documentation used in the Guide app is defined in this file.
|
||||
--
|
||||
|
||||
local const = require("scada-common.constants")
|
||||
|
||||
local docs = {}
|
||||
@ -11,9 +7,7 @@ local DOC_ITEM_TYPE = {
|
||||
SECTION = 1,
|
||||
SUBSECTION = 2,
|
||||
TEXT = 3,
|
||||
NOTE = 4,
|
||||
TIP = 5,
|
||||
LIST = 6
|
||||
LIST = 4
|
||||
}
|
||||
|
||||
---@enum DOC_LIST_TYPE
|
||||
@ -57,18 +51,6 @@ local function text(body)
|
||||
table.insert(target, item)
|
||||
end
|
||||
|
||||
local function note(body)
|
||||
---@class pocket_doc_note
|
||||
local item = { type = DOC_ITEM_TYPE.NOTE, text = body }
|
||||
table.insert(target, item)
|
||||
end
|
||||
|
||||
local function tip(body)
|
||||
---@class pocket_doc_tip
|
||||
local item = { type = DOC_ITEM_TYPE.TIP, 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
|
||||
@ -78,140 +60,14 @@ local function list(type, items, colors)
|
||||
table.insert(target, list_def)
|
||||
end
|
||||
|
||||
--#region System Usage
|
||||
|
||||
docs.usage = {
|
||||
conn = {}, config = {}, manual = {}, auto = {}, waste = {}
|
||||
}
|
||||
|
||||
target = docs.usage.conn
|
||||
sect("Overview")
|
||||
tip("For the best setup experience, see the Wiki on GitHub or the YouTube channel! This app does not contain all information.")
|
||||
text("Mekanism devices are connected to ComputerCraft computers that form the SCADA control system.")
|
||||
sect("Mekanism Conns")
|
||||
text("Multiblocks and single block devices are both connected directly to a computer by touching it or via wired modems.")
|
||||
doc("usage_conn_mb", "Multiblocks", "For multiblocks, a logic adapter is used if it exists for that multiblock, otherwise a valve or port block is used.")
|
||||
text("A wired modem is only connected to the block when you right click it and it gets a red border and you see a message in the chat with the peripheral name.")
|
||||
tip("Do not connect all peripherals in the system on the same network cable, since Reactor PLCs will grab the first reactor they find and you may accidentally duplicate RTUs.")
|
||||
sect("Computer Conns")
|
||||
tip("It helps to be familiar with how ComputerCraft manages peripherals before using this system, though it is not necessary.")
|
||||
doc("usage_conn_network", "Network", "All computers in the system communicate with each other via wireless or ender modems. Ender modems are preferred due to the unlimited range.")
|
||||
text("Five different network channels are used and must have the same value for each name across all devices.")
|
||||
text("For example, the supervisor channel SVR_CHANNEL must be set to the same channel for all devices in your system. Two different named channels should not share the same value (such as SVR_CHANNEL vs CRD_CHANNEL).")
|
||||
doc("usage_conn_peri", "Peripherals", "ComputerCraft peripherals like monitors and speakers need to touch the computer or be connected via wired modems.")
|
||||
|
||||
target = docs.usage.config
|
||||
sect("Overview")
|
||||
tip("For the best setup experience, see the Wiki on GitHub or the YouTube channel! This app does not contain all information.")
|
||||
text("All devices have a configurator program you can launch by running the 'configure' command.")
|
||||
sect("Networking")
|
||||
doc("usage_cfg_id", "Computer ID", "A computer ID must NEVER be the identical between devices, which can only happen if you duplicate a computer (such as if you middle-click on it and place it again in creative mode).")
|
||||
doc("usage_cfg_chan", "Channels", "Channels are used for the computer to computer communication, described in the connection guide section. Channels with the same name must have the same value across all devices in your system and channels with different names cannot overlap.")
|
||||
doc("usage_cfg_to", "Conn Timeout", "After this period of time the device will close the connection assuming the other device is unresponsive.")
|
||||
doc("usage_cfg_tr", "Trusted Range", "Devices further than this block distance away will have any network traffic rejected by this device.")
|
||||
doc("usage_cfg_auth", "Authentication", "To provide a level of security, you can enable facility-wide authentication by setting keys, which must be the same (and set) on all your devices. This adds computation time to each network transmission so you should only do this if you need it on multiplayer.")
|
||||
sect("Logging")
|
||||
text("Logs are automatically saved to a log.txt file in the root of the computer. You can change the path to it, if it contains verbose debug messages, and if it is appended to or overwritten each time the program runs.")
|
||||
text("If you intend to be able to share logs, you should leave it to append.")
|
||||
doc("usage_cfg_log_upload", "Sharing Logs", "To share logs, you would run 'pastebin put log.txt' where your log file is then share the code.")
|
||||
sect("Reactor PLC")
|
||||
text("The Reactor PLC must be connected to a single fission reactor that it will manage. Use the configurator to choose if you would like it to operate as networked or not.")
|
||||
tip("The Reactor PLC should always be in a chunk with the reactor to ensure it can protect it on server start and/or chunk load.")
|
||||
doc("usage_cfg_plc_nonet", "Non-Networked", "This lets you use this device as an advanced standalone safety system rather than a basic redstone breaker for easier safety protection.")
|
||||
doc("usage_cfg_plc_net", "Networked", "This is the most commonly used mode. The Reactor PLC will require a connection to the Supervisor to operate and will allow usage through that for more advanced functionality.")
|
||||
doc("usage_cfg_plc_unit", "Unit ID", "When networked, you can set any unit ID ranging from 1 to 4. Multiple Reactor PLCs cannot share the same unit ID.")
|
||||
sect("RTU Gateway")
|
||||
text("The RTU Gateway allows connecting multiple RTU interfaces to the SCADA system. These interfaces may be external peripherals or redstone.")
|
||||
text("All devices except for fission reactors must be connected via an RTU Gateway.")
|
||||
sect("Supervisor")
|
||||
text("The Supervisor configuration is core to the entire system. If you change things about the system, such as the cooling devices or reactor count, it must be updated here.")
|
||||
text("This configuration contains many settings that are detailed better in the configurator so they will not be covered here.")
|
||||
doc("usage_cfg_sv_tanks", "Dynamic Tanks", "Dynamic tanks can be used to provide emergency coolant (and/or auxiliary coolant) to the system. Many layouts are supported by using a mix of facility tanks (connect to 1+ units) and unit tanks (connect to only one unit).")
|
||||
doc("usage_cfg_sv_aux", "Auxiliary Coolant", "This coolant is enabled at the start of reactors to prevent water levels from dropping in the reactor or boiler while the turbine ramps up. This can be connected to a dynamic tank, a sink, or any other water supply.")
|
||||
sect("Coordinator")
|
||||
text("The Coordinator configuration is mainly focused around setting up your displays. This is best to do last after everything else. See the wiki on the GitHub for details on monitor sizing.")
|
||||
tip("When changing the unit count on the Supervisor, you must also update it on the Coordinator.")
|
||||
doc("usage_cfg_crd_main", "Main Monitor", "The main monitor contains the main interface and overview. It is always 8 block wide with varying height depending on how many units you have.")
|
||||
doc("usage_cfg_crd_flow", "Flow Monitor", "The flow monitor contains the waste and coolant flow diagram. It is always 8 block wide with varying height depending on how many units you have.")
|
||||
doc("usage_cfg_crd_unit", "Unit Monitor", "You need one unit monitor per reactor, and it is always a 4x4 monitor.")
|
||||
text("Monitors can be connected by direct contact or via wired modems.")
|
||||
text("Various unit and color options are available to customize the display to your liking. Using energy scales other than RF can impact the precision of your power-related auto control setpoints as RF is always used internally.")
|
||||
sect("Pocket")
|
||||
text("You're already here, so not much to mention!")
|
||||
sect("Self-Check")
|
||||
text("Most application configurators provide a self-check function that will check the validity of your configuration and the network connection. You should run this if you are having issues with that device.")
|
||||
sect("Config Changes")
|
||||
text("When an update adds or removes or otherwise modifies configuration requirements, you will be warned that you need to re-configure. You will not lose any prior data as updates will preserve configurations, you just need to step through the instructions again to add or change any new data.")
|
||||
|
||||
target = docs.usage.manual
|
||||
sect("Overview")
|
||||
text("Manual reactor control still includes safety checks and monitoring, but the burn rate is not automatically controlled.")
|
||||
text("A unit is under manual control when the AUTO CTRL option Manual is selected on the unit display.")
|
||||
note("Specific UIs will not be discussed here. If you need help with the UI, refer to Operator UIs > Coordinator UI > Unit Displays.")
|
||||
sect("Manual Control")
|
||||
text("The unit display on the Coordinator is used to run manual control. You may also start/stop and set the burn rate via the Mekanism UI on the Fission Reactor.")
|
||||
tip("If some controls are grayed out on the unit display, that operation isn't currently available, such as due to the reactor being already started or being under auto control.")
|
||||
text("Manual control is started by the START button and runs at the commanded burn rate next to it, which can be modified before starting or after having started by selecting a value then pressing SET.")
|
||||
text("The reactor can be stopped via SCRAM, then the RPS needs to be reset via RESET.")
|
||||
|
||||
target = docs.usage.auto
|
||||
sect("Overview")
|
||||
text("A main feature of this system is automatic reactor control that supports various managed control modes.")
|
||||
tip("You should first review the Main Display and Unit Display documentation under Operator UIs > Coordinator before proceeding if you are not familiar with the interfaces.")
|
||||
sect("Configuration")
|
||||
note("Configurations cannot be modified while auto control is active.")
|
||||
doc("usage_auto_assign", "Unit Assignments", "Auto control only applies to units set to a mode other than Manual. To prefer certain units or only use the minimum number necessary, priority groups are used to split up the required burn rate.")
|
||||
text("Primary units will be used first, followed by secondary, etc. If multiple are assigned to a group, burn rate will be assigned evenly between them.")
|
||||
text("The next priority group will only be used once the previous one cannot keep up with the total required burn rate for auto control at that moment.")
|
||||
doc("usage_auto_setpoints", "Setpoints", "Three setpoint spinner inputs are available for the three setpoint-based auto control modes. The system will do its best to meet the requested value, with the current value listed below the input.")
|
||||
doc("usage_auto_limits", "Unit Limits", "Each unit can be limited to a maximum auto control burn rate to prevent exceeding any safe levels that you know of.")
|
||||
doc("usage_auto_states", "Unit States", "Any assigned units must be shown as Ready and not Degraded to use auto control. See Operator UIs > Coordinator > Main Display for more.")
|
||||
sect("Operation Modes")
|
||||
text("Four auto control modes are available that function based on configurations set on the main display. All modes except Monitored Max Burn will try to only use the primary group until it can't keep up, then the secondary, etc.")
|
||||
note("No units will be set to a burn rate higher than their limit.")
|
||||
doc("usage_op_mon_max", "Monitored Max Burn", "This mode runs all units assigned to auto control at their unit limit burn rate regardless of priority group.")
|
||||
doc("usage_op_com_rate", "Combined Burn Rate", "Assigned units will be commanded to meet the Burn Target setpoint.")
|
||||
doc("usage_op_chg_level", "Charge Level", "Assigned units will be commanded to bring the induction matrix up to the requested Charge Target.")
|
||||
doc("usage_op_gen_rate", "Generation Rate", "Assigned units will be commanded to maintain the requested Generation Target.")
|
||||
note("The rate used is the input rate into the induction matrix, so using other power generation sources may disrupt this control mode.")
|
||||
sect("Start and Stop")
|
||||
text("A text box is used to indicate the system status. It will also provide information of why the system has paused control or failed to start.")
|
||||
text("You cannot start auto control until all assigned units have all their devices connected and functional and the reactor's RPS is not tripped.")
|
||||
doc("usage_op_save", "SAVE", "SAVE will save the configuration without starting control.")
|
||||
doc("usage_op_start", "START", "START will attempt to start auto control, which includes first saving the configuration.")
|
||||
doc("usage_op_stop", "STOP", "STOP will stop all reactors assigned to automatic control.")
|
||||
|
||||
target = docs.usage.waste
|
||||
sect("Overview")
|
||||
text("When 'valves' are connected for routing waste, this system can manage which waste product(s) are made. The flow monitor shows the diagram of how valves are meant to be connected.")
|
||||
text("There are three waste products, listed below with the colors generally associated with them.")
|
||||
list(DOC_LIST_TYPE.LED, { "Pu - Plutonium", "Po - Polonium", "AM - Antimatter" }, { colors.cyan, colors.green, colors.purple })
|
||||
note("The Po and Pu colors are swapped in older versions of Mekanism.")
|
||||
sect("Unit Waste")
|
||||
text("Units can be set to specific waste products via buttons at the bottom right of a unit display.")
|
||||
note("Refer to Operator UIs > Coordinator UI > Unit Displays for details.")
|
||||
text("If 'Auto' is selected instead of a waste product, that unit's waste will be processed per the facility waste control.")
|
||||
sect("Facility Waste")
|
||||
text("Facility waste control adds additional functionality to waste processing through automatic control.")
|
||||
text("The waste control interface on the main display lets you set a target waste type along with options that can change that based on circumstances.")
|
||||
note("Refer to Operator UIs > Coordinator UI > Main Display for information on the display and control interface.")
|
||||
doc("usage_waste_fallback", "Pu Fallback", "This option switches facility waste control to plutonium when the SNAs cannot keep up, such as at night.")
|
||||
doc("usage_waste_sps_lc", "Low Charge SPS", "This option prevents the facility waste control from stopping antimatter production at low induction matrix charge (< 10%, resumes after reaching 15%).")
|
||||
text("With that option enabled, antimatter production will continue. With it disabled, it will switch to polonium if set to antimatter while charge is low.")
|
||||
note("Pu Fallback takes priority and will switch to plutonium when appropriate regardless of the Low Charge SPS setting.")
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Operator UIs
|
||||
|
||||
--#region Alarms
|
||||
-- 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 = {}
|
||||
|
||||
target = docs.alarms
|
||||
doc("ContainmentBreach", "Containment Breach", "Reactor disconnected or indicated unformed while being at or above 100% damage; explosion assumed.")
|
||||
doc("ContainmentRadiation", "Containment Radiation", "Environment detector(s) assigned to the unit have observed high levels of radiation.")
|
||||
doc("ReactorLost", "Reactor Lost", "Reactor PLC has stopped communicating with the Supervisor.")
|
||||
doc("ReactorLost", "Reactor Lost", "Reactor PLC has stopped communicating with the supervisor.")
|
||||
doc("CriticalDamage", "Damage Critical", "Reactor damage has reached or exceeded 100%, so it will explode at any moment.")
|
||||
doc("ReactorDamage", "Reactor Damage", "Reactor temperature causing increasing damage to the reactor casing.")
|
||||
doc("ReactorOverTemp", "Reactor Over Temp", "Reactor temperature is at or above maximum safe temperature, so it is now taking damage.")
|
||||
@ -222,10 +78,6 @@ doc("RPSTransient", "RPS Transient", "Reactor protection system was activated.")
|
||||
doc("RCSTransient", "RCS Transient", "Something is wrong with the reactor coolant system, check RCS indicators for details.")
|
||||
doc("TurbineTripAlarm", "Turbine Trip", "A turbine stopped rotating, likely due to having full energy storage. This will prevent cooling, so it needs to be resolved before using that unit.")
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Annunciators
|
||||
|
||||
docs.annunc = {
|
||||
unit = {
|
||||
main_section = {}, rps_section = {}, rcs_section = {}
|
||||
@ -237,8 +89,8 @@ docs.annunc = {
|
||||
|
||||
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("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("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("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.")
|
||||
sect("Safety Status")
|
||||
@ -246,7 +98,7 @@ doc("ReactorSCRAM", "Reactor SCRAM", "On if the reactor protection system is hol
|
||||
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("RadiationWarning", "Radiation Warning", "On if radiation levels are above normal. There is likely a leak somewhere, so that should be identified and fixed. Hazmat suit recommended.")
|
||||
doc("RCPTrip", "RCP Trip", "Reactor coolant pump tripped. This is a technical concept not directly mapping to Mekanism. Here, it indicates if there is either high heated coolant or low cooled coolant that caused an RPS trip. Check the coolant system if this occurs.")
|
||||
doc("RCPTrip", "RCP Trip", "Reactor coolant pump tripped. This is a technical concept not directly mapping to Mekansim. Here, it indicates if there is either high heated coolant or low cooled coolant that caused an RPS trip. Check the coolant system if this occurs.")
|
||||
doc("RCSFlowLow", "RCS Flow Low", "Indicates if the reactor coolant system flow is low. This is observed when the cooled coolant level in the reactor is dropping. This can occur while a turbine spins up, but if it persists, check that the cooling system is operating properly. This can occur with smaller boilers or when using pipes and not having enough.")
|
||||
doc("CoolantLevelLow", "Coolant Level Low", "On if the reactor coolant level is lower than it should be. Check the coolant system.")
|
||||
doc("ReactorTempHigh", "Reactor Temp. High", "On if the reactor temperature is above expected maximum operating temperature. This is not yet damaging, but should be attended to. Check coolant system.")
|
||||
@ -266,7 +118,7 @@ doc("high_temp", "Temperature High", "Indicates if the RPS tripped due to reachi
|
||||
doc("low_cool", "Coolant Level Low Low", "Indicates if the RPS tripped due to very low coolant levels that result in the temperature uncontrollably rising. Ensure that the cooling system can provide sufficient cooled coolant flow.")
|
||||
doc("no_fuel", "No Fuel", "Indicates if the RPS tripped due to no fuel being available. Check fuel input.")
|
||||
doc("fault", "PPM Fault", "Indicates if the RPS tripped due to a peripheral access fault. Something went wrong interfacing with the reactor, try restarting the PLC.")
|
||||
doc("timeout", "Connection Timeout", "Indicates if the RPS tripped due to losing connection with the supervisory computer. Check that your PLC and Supervisor remain chunk loaded.")
|
||||
doc("timeout", "Connection Timeout", "Indicates if the RPS tripped due to losing connection with the supervisory computer. Check that your PLC and supervisor remain chunk loaded.")
|
||||
doc("sys_fail", "System Failure", "Indicates if the RPS tripped due to the reactor not being formed. Ensure that the multi-block is formed.")
|
||||
|
||||
target = docs.annunc.unit.rcs_section
|
||||
@ -278,7 +130,7 @@ doc("SteamFeedMismatch", "Steam Feed Mismatch", "There is an above tolerance dif
|
||||
doc("MaxWaterReturnFeed", "Max Water Return Feed", "The turbines are condensing the max rate of water that they can per the structure build. If water return is insufficient, add more saturating condensers to your turbine(s).")
|
||||
doc("WaterLevelLow", "Water Level Low", "The water level in the boiler is low. A larger boiler water tank may help, or you can feed additional water into the boiler from elsewhere.")
|
||||
doc("HeatingRateLow", "Heating Rate Low", "The boiler is not hot enough to boil water, but it is receiving heated coolant. This is almost never a safety concern.")
|
||||
doc("SteamDumpOpen", "Steam Relief Valve Open", "This turns yellow if the turbine is set to dumping excess and red if it is set to dumping [all]. 'Relief Valve' in this case is that setting allowing the venting of steam. You should never have this set to dumping [all]. Emergency coolant activation from the Supervisor will automatically set it to dumping excess to ensure there is no backup of steam as water is added.")
|
||||
doc("SteamDumpOpen", "Steam Relief Valve Open", "This turns yellow if the turbine is set to dumping excess and red if it is set to dumping [all]. 'Relief Valve' in this case is that setting allowing the venting of steam. You should never have this set to dumping [all]. Emergency coolant activation from the supervisor will automatically set it to dumping excess to ensure there is no backup of steam as water is added.")
|
||||
doc("TurbineOverSpeed", "Turbine Over Speed", "The turbine is at steam capacity, but not tripped. You may need more turbines if they can't keep up.")
|
||||
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.")
|
||||
@ -302,144 +154,28 @@ doc("as_crit_alarm", "Unit Critical Alarm", "Automatic SCRAM occurred due to cri
|
||||
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.")
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Coordinator UI
|
||||
|
||||
docs.c_ui = {
|
||||
main = {}, flow = {}, unit = {}
|
||||
}
|
||||
|
||||
target = docs.c_ui.main
|
||||
sect("Facility Diagram")
|
||||
text("The facility overview diagram is made up of unit diagrams showing the reactor, boiler(s) if present, and turbine(s). This includes values of various key statistics such as temperatures along with bars showing the fill percentage of the tanks in each multiblock.")
|
||||
text("Boilers are shown under the reactor, listed in order of index (#1 then #2 below). Turbines are shown to the right, also listed in order of index (indexes are per unit and set in the RTU Gateway configuration).")
|
||||
text("Pipe connections are visualized with color-coded lines, which are primarily to indicate connections, as not all facilities may use pipes.")
|
||||
note("If a component you have is not showing up, ensure the Supervisor is configured for your actual cooling configuration.")
|
||||
sect("Facility Status")
|
||||
note("The annunciator here is described in Operator UIs > Annunciators.")
|
||||
doc("ui_fac_scram", "FAC SCRAM", "This SCRAMs all units in the facility.")
|
||||
doc("ui_fac_ack", "ACK \x13", "This acknowledges (mutes) all alarms for all units in the facility.")
|
||||
doc("ui_fac_rad", "Radiation", "The facility radiation, which is the current maximum of all connected facility radiation monitors (excludes unit monitors).")
|
||||
doc("ui_fac_linked", "Linked RTUs", "The number of RTU Gateways connected.")
|
||||
sect("Automatic Control")
|
||||
text("This interface is used for managing automatic facility control, which only applies to units set via the unit display to be under auto control. This includes setpoints, status, configuration, and control.")
|
||||
doc("ui_fac_auto_bt", "Burn Target", "When set to Combined Burn Rate mode, assigned units will ramp up to meet this combined target.")
|
||||
doc("ui_fac_auto_ct", "Charge Target", "When set to Charge Level mode, assigned units will run to reach and maintain this induction matrix charge level.")
|
||||
doc("ui_fac_auto_gt", "Gen. Target", "When set to Generation Rate mode, assigned units will run to reach and maintain this continuous power output, using the induction matrix input rate.")
|
||||
doc("ui_fac_save", "SAVE", "This saves your configuration without starting control.")
|
||||
doc("ui_fac_start", "START", "This starts the configured automatic control.")
|
||||
tip("START also includes the SAVE operation.")
|
||||
doc("ui_fac_stop", "STOP", "This terminates automatic control, stopping assigned units.")
|
||||
text("There are four automatic control modes, detailed further in System Usage > Automatic Control")
|
||||
doc("ui_fac_auto_mmb", "Monitored Max Burn", "This runs all assigned units at the maximum configured rate.")
|
||||
doc("ui_fac_auto_cbr", "Combined Burn Rate", "This runs assigned units to meet the target combined rate.")
|
||||
doc("ui_fac_auto_cl", "Charge Level", "This runs assigned units to maintain an induction matrix charge level.")
|
||||
doc("ui_fac_auto_gr", "Generation Rate", "This runs assigned units to meet a target induction matrix power input rate.")
|
||||
doc("ui_fac_auto_lim", "Unit Limit", "Each unit can have a limit set that auto control will never exceed.")
|
||||
doc("ui_fac_unit_ready", "Unit Status Ready", "A unit is only ready for auto control if all multiblocks are formed, online with data received, and there is no RPS trip.")
|
||||
doc("ui_fac_unit_degraded", "Unit Status Degraded", "A unit is degraded if the reactor, boiler(s), and/or turbine(s) are faulted or not connected.")
|
||||
sect("Waste Control")
|
||||
text("Above unit statuses are the unit waste statuses, showing which are set to the auto waste mode and the actual current waste production of that unit.")
|
||||
text("The facility automatic waste control interface is surrounded by a brown border and lets you configure that system, starting with the requested waste product.")
|
||||
doc("ui_fac_waste_pu_fall_act", "Fallback Active", "When the system is falling back to plutonium production while SNAs cannot keep up.")
|
||||
doc("ui_fac_waste_sps_lc_act", "SPS Disabled LC", "When the system is falling back to polonium production to prevent draining all power with the SPS while the induction matrix charge has dropped below 10% and not yet reached 15%.")
|
||||
doc("ui_fac_waste_pu_fall", "Pu Fallback", "Switch from Po or Antimatter when the SNAs can't keep up (like at night).")
|
||||
doc("ui_fac_waste_sps_lc", "Low Charge SPS", "Continue running antimatter production even at low induction matrix charge levels (<10%).")
|
||||
sect("Induction Matrix")
|
||||
text("The induction matrix statistics are shown at the bottom right, including fill bars for the FILL, I (input rate), and O (output rate).")
|
||||
text("Averages are computed by the system while other data is directly from the device.")
|
||||
doc("ui_fac_im_charge", "Charging", "Charge is increasing (more input than output).")
|
||||
doc("ui_fac_im_charge", "Discharging", "Charge is draining (more output than input).")
|
||||
doc("ui_fac_im_charge", "Max I/O Rate", "The induction providers are at their maximum rate.")
|
||||
doc("ui_fac_eta", "ETA", "The ETA is based off a longer average so it may take a minute to stabilize, but will give a rough estimate of time to charge/discharge.")
|
||||
|
||||
target = docs.c_ui.flow
|
||||
sect("Flow Diagram")
|
||||
text("The coolant and waste flow monitor is one large P&ID (process and instrumentation diagram) showing an overview of those flows.")
|
||||
text("Color-coded pipes are used to show the connections, and valve symbols \x10\x11 are used to show valves (redstone controlled pipes).")
|
||||
doc("ui_flow_rates", "Flow Rates", "Flow rates are always shown below their respective pipes and sourced from devices when possible. The waste flow is based on the reactor burn rate, then everything downstream of the SNAs are based on the SNA production rate.")
|
||||
doc("ui_flow_valves", "Standard Valves", "Valve naming (PV00-XX) is based on P&ID naming conventions. These count up across the whole facility, and use tags at the end to add clarity.")
|
||||
note("The indicator next to the label turns on when the associated redstone RTU is connected.")
|
||||
list(DOC_LIST_TYPE.BULLET, { "PU: Plutonium", "PO: Polonium", "PL: Po Pellets", "AM: Antimatter", "EMC: Emer. Coolant", "AUX: Aux. Coolant" })
|
||||
doc("ui_flow_valve_open", "OPEN", "This indicates if the respective valve is commanded open.")
|
||||
doc("ui_flow_prv", "PRVs", "Pressure Relief Valves (PRVs) are used to show the turbine steam dumping states of each turbine.")
|
||||
list(DOC_LIST_TYPE.LED, { "Not Dumping", "Dumping Excess", "Dumping" }, { colors.gray, colors.yellow, colors.red })
|
||||
sect("SNAs")
|
||||
text("Solar Neutron Activators are shown on the flow diagram as a combined block due to the large variable count supported.")
|
||||
tip("SNAs consume 10x the waste as they produce in antimatter, so take that into account before connecting too many SNAs.")
|
||||
doc("ui_flow_sna_act", "ACTIVE", "The SNAs have a non-zero total flow.")
|
||||
doc("ui_flow_sna_cnt", "CNT", "The count of SNAs assigned to the unit.")
|
||||
doc("ui_flow_sna_peak_o", "PEAK\x1a", "The combined theoretical peak output the SNAs can achieve under full sunlight.")
|
||||
doc("ui_flow_sna_max_o", "MAX \x1a", "The current combined maximum output rate of the SNAs (based on current sunlight).")
|
||||
doc("ui_flow_sna_max_i", "\x1aMAX", "The computed combined maximum input rate (10x the output rate).")
|
||||
doc("ui_flow_sna_in", "\x1aIN", "The current input rate into the SNAs.")
|
||||
sect("Dynamic Tanks")
|
||||
text("Dynamic tanks configured for the system are listed to the left. The title may start with U for unit tanks or F for facility tanks.")
|
||||
text("The fill information and water level are shown below the status label.")
|
||||
doc("ui_flow_dyn_fill", "FILL", "If filling is enabled by the tank mode (via Mekanism UI).")
|
||||
doc("ui_flow_dyn_empty", "EMPTY", "If emptying is enabled by the tank mode (via Mekanism UI).")
|
||||
sect("SPS")
|
||||
doc("ui_flow_sps_in", "Input Rate", "The rate of polonium into the SPS.")
|
||||
doc("ui_flow_sps_prod", "Production Rate", "The rate of antimatter produced by the SPS.")
|
||||
sect("Statistics")
|
||||
text("The sum of all unit's waste rate statistics are shown under the SPS block. These are combined current rates, not long-term sums.")
|
||||
doc("ui_flow_stat_raw", "RAW WASTE", "The combined rate of raw waste generated by the reactors before processing.")
|
||||
doc("ui_flow_stat_proc", "PROC. WASTE", "The combined rates of different waste product production. Pu is plutonium, Po is polonium, and PoPl is polonium pellets. Antimatter is shown in the SPS block.")
|
||||
doc("ui_flow_stat_spent", "SPENT WASTE", "The combined rate of spent waste generated after processing.")
|
||||
sect("Other Blocks")
|
||||
text("Other blocks, such as CENTRIFUGE, correspond to devices that are not intended to be connected and/or serve as labels.")
|
||||
|
||||
target = docs.c_ui.unit
|
||||
sect("Data Display")
|
||||
text("The unit monitor contains extensive data information, including annunciator and alarm displays described in the associated sections in the Operator UIs section.")
|
||||
doc("ui_unit_core", "Core Map", "A core map diagram is shown at the top right, colored by core temperature. The layout is based off of the multiblock dimensions.")
|
||||
list(DOC_LIST_TYPE.BULLET, { "Gray <= 300\xb0C", "Blue <= 350\xb0C", "Green < 600\xb0C", "Yellow < 100\xb0C", "Orange < 1200\xb0C", "Red < 1300\xb0C", "Pink >= 1300\xb0C" })
|
||||
text("Internal tanks (fuel, cooled coolant, heated coolant, and waste) are displayed below the core map, labeled F, C, H, and W, respectively.")
|
||||
doc("ui_unit_rad", "Radiation", "The unit radiation, which is the current maximum of all connected radiation monitors assigned to this unit.")
|
||||
text("Multiple other data values are shown but should be self-explanatory.")
|
||||
sect("Controls")
|
||||
text("A set of buttons and the burn rate input are used for manual reactor control. When in auto mode, unavailable controls are disabled. The burn rate is only applied after SET is pressed.")
|
||||
doc("ui_unit_start", "START", "This starts the reactor at the requested burn rate.")
|
||||
doc("ui_unit_scram", "SCRAM", "This SCRAMs the reactor.")
|
||||
doc("ui_unit_ack", "ACK \x13", "This acknowledges alarms on this unit.")
|
||||
doc("ui_unit_reset", "RESET", "This resets the RPS for this unit.")
|
||||
sect("Auto Control")
|
||||
text("To put this unit under auto control, select an option other than Manual. You must press SET to apply this, but cannot change this while auto control is active. The priorities available are described in System Usage > Automatic Control.")
|
||||
doc("ui_unit_prio", "Prio. Group", "This displays the unit's auto control priority group.")
|
||||
doc("ui_unit_ready", "READY", "This indicates if the unit is ready for auto control. A unit is only ready for auto control if all multiblocks are formed, online with data received, and there is no RPS trip.")
|
||||
doc("ui_unit_standby", "STANDBY", "This indicates if the unit is set to auto control and that is active, but the auto control does not currently need this reactor to run at the moment, so it is idle.")
|
||||
sect("Waste Processing")
|
||||
text("The unit's waste output configuration can be set via these buttons. Auto will put this unit under control of the facility waste control, otherwise the system will always command the requested option for this unit.")
|
||||
|
||||
--#endregion
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Front Panels
|
||||
|
||||
docs.fp = {
|
||||
common = {}, r_plc = {}, rtu_gw = {}, supervisor = {}, coordinator = {}
|
||||
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", "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.")
|
||||
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("Overview")
|
||||
text("Documentation for Reactor PLC-specific front panel items are below. Refer to 'Common Items' for the items not covered in this section.")
|
||||
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")
|
||||
@ -458,8 +194,8 @@ doc("fp_emer_cool", "EMER COOLANT", "This is only present if PLC-controlled emer
|
||||
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_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.")
|
||||
@ -470,9 +206,6 @@ doc("fp_rps_ccool", "LO CCOOLANT", "The RPS tripped due to having low levels of
|
||||
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("Overview")
|
||||
text("Documentation for RTU Gateway-specific front panel items are below. Refer to 'Common Items' for the items not covered in this section.")
|
||||
doc("fp_rtu_spkr", "SPEAKERS", "This is the count of speaker peripherals connected to this RTU Gateway.")
|
||||
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.")
|
||||
@ -485,49 +218,30 @@ doc("fp_rtu_rt", "Device Assignment", "In each RTU entry row, the device identif
|
||||
|
||||
target = docs.fp.supervisor
|
||||
sect("Round Trip Times")
|
||||
doc("fp_sv_rtt", "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.")
|
||||
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'.")
|
||||
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.")
|
||||
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.")
|
||||
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.")
|
||||
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_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.")
|
||||
|
||||
target = docs.fp.coordinator
|
||||
sect("Round Trip Times")
|
||||
doc("fp_crd_rtt", "RTT", "Each connection has a round trip time, or RTT. Since the Coordinator updates at a rate of 500ms, RTTs ~500ms - ~1000ms are typical. Higher RTTs indicate lag, which results in performance problems.")
|
||||
list(DOC_LIST_TYPE.BULLET, { "green: <=1000ms", "yellow: <=1500ms ", "red: >1500ms" })
|
||||
sect("CRD Tab")
|
||||
text("This tab includes information about the Coordinator, partially covered by 'Common Items'.")
|
||||
doc("fp_crd_spkr", "SPEAKER", "This indicates if the speaker is connected.")
|
||||
doc("fp_crd_rt_main", "RT MAIN", "This indicates that the device's main loop co-routine is running.")
|
||||
doc("fp_crd_rt_render", "RT RENDER", "This indicates that the Coordinator graphics renderer co-routine is running.")
|
||||
doc("fp_crd_mon_main", "MAIN MONITOR", "The connection status of the main display monitor.")
|
||||
doc("fp_crd_mon_flow", "FLOW MONITOR", "The connection status of the coolant and waste flow display monitor.")
|
||||
doc("fp_crd_mon_unit", "UNIT X MONITOR", "The connection status of the monitor associated with a given unit.")
|
||||
sect("API Tab")
|
||||
text("This tab lists connected pocket computers. Refer to the Supervisor PKT tab documentation for details on fields.")
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Glossary
|
||||
|
||||
docs.glossary = {
|
||||
abbvs = {}, terms = {}
|
||||
}
|
||||
@ -535,7 +249,8 @@ docs.glossary = {
|
||||
target = docs.glossary.abbvs
|
||||
doc("G_ACK", "ACK", "Alarm ACKnowledge. Pressing this acknowledges that you understand an alarm occurred and would like to stop the audio tone(s).")
|
||||
doc("G_Auto", "Auto", "Automatic.")
|
||||
doc("G_CRD", "CRD", "Coordinator. Abbreviation for the Coordinator computer.")
|
||||
doc("G_CRD", "CRD", "Coordinator. Abbreviation for the coordinator computer.")
|
||||
doc("G_DBG", "DBG", "Debug. Abbreviation for the debugging sessions from pocket computers found on the supervisor's front panel.")
|
||||
doc("G_FP", "FP", "Front Panel. See Terminology.")
|
||||
doc("G_Hi", "Hi", "High.")
|
||||
doc("G_Lo", "Lo", "Low.")
|
||||
@ -545,7 +260,7 @@ doc("G_PLC", "PLC", "Programmable Logic Controller. A device that not only repor
|
||||
doc("G_PPM", "PPM", "Protected Peripheral Manager. This is an abstraction layer created for this project that prevents peripheral calls from crashing applications.")
|
||||
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_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_SCADA", "SCADA", "Supervisory Control and Data Acquisition. A control systems architecture used in a wide variety process control applications.")
|
||||
@ -554,8 +269,6 @@ doc("G_UI", "UI", "User Interface.")
|
||||
|
||||
target = docs.glossary.terms
|
||||
doc("G_AssignedUnit", "Assigned Unit", "A unit that is assigned to an automatic control group (not assigned to Manual).")
|
||||
doc("G_AuxCoolant", "Auxiliary Coolant", "A separate water input to the reactor or boiler to supplement return water from a turbine during initial ramp-up.")
|
||||
doc("G_EmerCoolant", "Emergency Coolant", "A dynamic tank or other water supply used when a reactor or boiler does not have enough water to stop a runaway reactor overheat.")
|
||||
doc("G_Fault", "Fault", "Something has gone wrong and/or failed to function.")
|
||||
doc("G_FrontPanel", "Front Panel", "A basic interface on the front of a device for viewing and sometimes modifying its state. This is what you see when looking at a computer running one of the SCADA applications.")
|
||||
doc("G_HighHigh", "High High", "Very High.")
|
||||
@ -569,6 +282,4 @@ doc("G_Tripped", "Tripped", "An alarm condition has been met, and is still met."
|
||||
doc("G_Tripping", "Tripping", "Alarm condition(s) is/are met, but has/have not reached the minimum time before the condition(s) is/are deemed a problem.")
|
||||
doc("G_TurbineTrip", "Turbine Trip", "The turbine stopped, which prevents heated coolant from being cooled. In Mekanism, this would occur when a turbine cannot generate any more energy due to filling its buffer and having no output with any remaining energy capacity.")
|
||||
|
||||
--#endregion
|
||||
|
||||
return docs
|
||||
|
||||
@ -7,15 +7,14 @@ local util = require("scada-common.util")
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
local pocket = require("pocket.pocket")
|
||||
|
||||
local about_app = require("pocket.ui.apps.about")
|
||||
local alarm_app = require("pocket.ui.apps.alarm")
|
||||
local comps_app = require("pocket.ui.apps.comps")
|
||||
local control_app = require("pocket.ui.apps.control")
|
||||
local diag_apps = require("pocket.ui.apps.diag_apps")
|
||||
local dummy_app = require("pocket.ui.apps.dummy_app")
|
||||
local facil_app = require("pocket.ui.apps.facility")
|
||||
local guide_app = require("pocket.ui.apps.guide")
|
||||
local loader_app = require("pocket.ui.apps.loader")
|
||||
local process_app = require("pocket.ui.apps.process")
|
||||
local rad_app = require("pocket.ui.apps.radiation")
|
||||
local sys_apps = require("pocket.ui.apps.sys_apps")
|
||||
local unit_app = require("pocket.ui.apps.unit")
|
||||
local waste_app = require("pocket.ui.apps.waste")
|
||||
|
||||
@ -72,11 +71,10 @@ local function init(main)
|
||||
process_app(page_div)
|
||||
waste_app(page_div)
|
||||
guide_app(page_div)
|
||||
rad_app(page_div)
|
||||
loader_app(page_div)
|
||||
about_app(page_div)
|
||||
alarm_app(page_div)
|
||||
comps_app(page_div)
|
||||
sys_apps(page_div)
|
||||
diag_apps(page_div)
|
||||
dummy_app(page_div)
|
||||
|
||||
-- verify all apps were created
|
||||
assert(util.table_len(db.nav.get_containers()) == APP_ID.NUM_APPS, "app IDs were not sequential or some apps weren't registered")
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
--
|
||||
-- Dynamic Tank View
|
||||
--
|
||||
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
@ -20,7 +16,7 @@ local IconIndicator = require("graphics.elements.indicators.IconIndicator")
|
||||
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
|
||||
|
||||
local CONTAINER_MODE = types.CONTAINER_MODE
|
||||
local COOLANT_TYPE = types.COOLANT_TYPE
|
||||
local COOLANT_TYPE = types.COOLANT_TYPE
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
local cpair = core.cpair
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
--
|
||||
-- Induction Matrix View
|
||||
--
|
||||
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
|
||||
local style = require("pocket.ui.style")
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
--
|
||||
-- SPS View
|
||||
--
|
||||
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
|
||||
local style = require("pocket.ui.style")
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
--
|
||||
-- A Guide App Subsection
|
||||
--
|
||||
|
||||
local log = require("scada-common.log")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
@ -21,7 +17,7 @@ local LED = require("graphics.elements.indicators.LED")
|
||||
local ALIGN = core.ALIGN
|
||||
local cpair = core.cpair
|
||||
|
||||
local DOC_TYPE = docs.DOC_ITEM_TYPE
|
||||
local DOC_TYPE = docs.DOC_ITEM_TYPE
|
||||
local LIST_TYPE = docs.DOC_LIST_TYPE
|
||||
|
||||
-- new guide documentation section
|
||||
@ -38,13 +34,13 @@ return function (data, base_page, title, items, scroll_height)
|
||||
local section_div = Div{parent=page_div,x=2}
|
||||
table.insert(panes, section_div)
|
||||
TextBox{parent=section_div,y=1,text=title,alignment=ALIGN.CENTER}
|
||||
PushButton{parent=section_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=base_page.nav_to}
|
||||
PushButton{parent=section_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=base_page.nav_to}
|
||||
|
||||
local view_page = app.new_page(section_page, #panes + 1)
|
||||
local section_view_div = Div{parent=page_div,x=2}
|
||||
table.insert(panes, section_view_div)
|
||||
TextBox{parent=section_view_div,y=1,text=title,alignment=ALIGN.CENTER}
|
||||
PushButton{parent=section_view_div,x=2,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=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)}
|
||||
@ -53,7 +49,7 @@ return function (data, base_page, title, items, scroll_height)
|
||||
local page_end
|
||||
|
||||
for i = 1, #items do
|
||||
local item = items[i] ---@type pocket_doc_sect|pocket_doc_subsect|pocket_doc_text|pocket_doc_note|pocket_doc_tip|pocket_doc_list
|
||||
local item = items[i] ---@type pocket_doc_sect|pocket_doc_subsect|pocket_doc_text|pocket_doc_list
|
||||
|
||||
if item.type == DOC_TYPE.SECTION then
|
||||
---@cast item pocket_doc_sect
|
||||
@ -77,8 +73,6 @@ return function (data, base_page, title, items, scroll_height)
|
||||
local _ = Div{parent=name_list,height=1}
|
||||
end
|
||||
|
||||
table.insert(search_db, { string.lower(item.name), item.name, title, view })
|
||||
|
||||
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}
|
||||
@ -114,19 +108,6 @@ return function (data, base_page, title, items, scroll_height)
|
||||
|
||||
TextBox{parent=def_list,text=item.text}
|
||||
|
||||
page_end = Div{parent=def_list,height=1,can_focus=true}
|
||||
elseif item.type == DOC_TYPE.NOTE then
|
||||
---@cast item pocket_doc_note
|
||||
|
||||
TextBox{parent=def_list,text=item.text,fg_bg=cpair(colors.gray,colors._INHERIT)}
|
||||
|
||||
page_end = Div{parent=def_list,height=1,can_focus=true}
|
||||
elseif item.type == DOC_TYPE.TIP then
|
||||
---@cast item pocket_doc_tip
|
||||
|
||||
TextBox{parent=def_list,text="TIP!",fg_bg=cpair(colors.orange,colors._INHERIT)}
|
||||
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
|
||||
|
||||
@ -9,9 +9,11 @@ local core = require("graphics.core")
|
||||
|
||||
local AppMultiPane = require("graphics.elements.AppMultiPane")
|
||||
local Div = require("graphics.elements.Div")
|
||||
local TextBox = require("graphics.elements.TextBox")
|
||||
|
||||
local App = require("graphics.elements.controls.App")
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
local cpair = core.cpair
|
||||
|
||||
local APP_ID = pocket.APP_ID
|
||||
@ -48,12 +50,15 @@ local function new_view(root)
|
||||
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.PROCESS)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.WASTE)end,app_fg_bg=cpair(colors.black,colors.brown),active_fg_bg=active_fg_bg}
|
||||
App{parent=apps_1,x=16,y=7,text="\xb6",title="Guide",callback=function()open(APP_ID.GUIDE)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg}
|
||||
App{parent=apps_1,x=2,y=12,text="?",title="About",callback=function()open(APP_ID.ABOUT)end,app_fg_bg=cpair(colors.black,colors.white),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=2,y=12,text="\xb6",title="Guide",callback=function()open(APP_ID.GUIDE)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg}
|
||||
App{parent=apps_1,x=9,y=12,text="?",title="About",callback=function()open(APP_ID.ABOUT)end,app_fg_bg=cpair(colors.black,colors.white),active_fg_bg=active_fg_bg}
|
||||
|
||||
App{parent=apps_2,x=2,y=2,text="\x0f",title="Alarm",callback=function()open(APP_ID.ALARMS)end,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=active_fg_bg}
|
||||
App{parent=apps_2,x=9,y=2,text="@",title="Comps",callback=function()open(APP_ID.COMPS)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg}
|
||||
App{parent=apps_2,x=16,y=2,text="\x1e",title="Rad",callback=function()open(APP_ID.RADMON)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg}
|
||||
TextBox{parent=apps_2,text="Diagnostic Apps",x=1,y=2,alignment=ALIGN.CENTER}
|
||||
|
||||
App{parent=apps_2,x=2,y=4,text="\x0f",title="Alarm",callback=function()open(APP_ID.ALARMS)end,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=active_fg_bg}
|
||||
App{parent=apps_2,x=9,y=4,text="\x1e",title="LoopT",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg}
|
||||
App{parent=apps_2,x=16,y=4,text="@",title="Comps",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg}
|
||||
|
||||
return main
|
||||
end
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
--
|
||||
-- Unit Boiler View
|
||||
--
|
||||
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
@ -17,8 +13,8 @@ local TextBox = require("graphics.elements.TextBox")
|
||||
local PushButton = require("graphics.elements.controls.PushButton")
|
||||
|
||||
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
||||
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
|
||||
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
|
||||
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
|
||||
local VerticalBar = require("graphics.elements.indicators.VerticalBar")
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
--
|
||||
-- Unit Reactor View
|
||||
--
|
||||
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
@ -17,8 +13,8 @@ local TextBox = require("graphics.elements.TextBox")
|
||||
local PushButton = require("graphics.elements.controls.PushButton")
|
||||
|
||||
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
||||
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
|
||||
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
|
||||
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
|
||||
local VerticalBar = require("graphics.elements.indicators.VerticalBar")
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
--
|
||||
-- Unit Turbine View
|
||||
--
|
||||
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
|
||||
@ -2,18 +2,12 @@
|
||||
-- Graphics Style Options
|
||||
--
|
||||
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local pocket = require("pocket.pocket")
|
||||
local core = require("graphics.core")
|
||||
|
||||
local style = {}
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
local config = pocket.config
|
||||
|
||||
-- GLOBAL --
|
||||
|
||||
style.root = cpair(colors.white, colors.black)
|
||||
@ -177,29 +171,22 @@ style.sps = {
|
||||
}
|
||||
}
|
||||
|
||||
-- get waste styling, which depends on the configuration
|
||||
---@return { states: { color: color, text: string }, states_abbrv: { color: color, text: string }, options: string[], unit_opts: string[] }
|
||||
function style.get_waste()
|
||||
local pu_color = util.trinary(config.GreenPuPellet, colors.green, colors.cyan)
|
||||
local po_color = util.trinary(config.GreenPuPellet, colors.cyan, colors.green)
|
||||
|
||||
return {
|
||||
-- auto waste processing states
|
||||
states = {
|
||||
{ color = cpair(colors.black, pu_color), text = "PLUTONIUM" },
|
||||
{ color = cpair(colors.black, po_color), text = "POLONIUM" },
|
||||
{ color = cpair(colors.black, colors.purple), text = "ANTI MATTER" }
|
||||
},
|
||||
states_abbrv = {
|
||||
{ color = cpair(colors.black, pu_color), text = "Pu" },
|
||||
{ color = cpair(colors.black, po_color), text = "Po" },
|
||||
{ color = cpair(colors.black, colors.purple), text = "AM" }
|
||||
},
|
||||
-- process radio button options
|
||||
options = { "Plutonium", "Polonium", "Antimatter" },
|
||||
-- unit waste selection
|
||||
unit_opts = { "Auto", "Plutonium", "Polonium", "Antimatter" }
|
||||
}
|
||||
end
|
||||
style.waste = {
|
||||
-- auto waste processing states
|
||||
states = {
|
||||
{ color = cpair(colors.black, colors.green), text = "PLUTONIUM" },
|
||||
{ color = cpair(colors.black, colors.cyan), text = "POLONIUM" },
|
||||
{ color = cpair(colors.black, colors.purple), text = "ANTI MATTER" }
|
||||
},
|
||||
states_abbrv = {
|
||||
{ color = cpair(colors.black, colors.green), text = "Pu" },
|
||||
{ color = cpair(colors.black, colors.cyan), text = "Po" },
|
||||
{ color = cpair(colors.black, colors.purple), text = "AM" }
|
||||
},
|
||||
-- process radio button options
|
||||
options = { "Plutonium", "Polonium", "Antimatter" },
|
||||
-- unit waste selection
|
||||
unit_opts = { "Auto", "Plutonium", "Polonium", "Antimatter" }
|
||||
}
|
||||
|
||||
return style
|
||||
|
||||
@ -84,7 +84,7 @@ local function handle_packet(packet)
|
||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||
error_msg = "another reactor PLC is connected with this reactor unit ID"
|
||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
error_msg = "reactor PLC comms version does not match supervisor comms version, make sure both devices are up-to-date (ccmsi update)"
|
||||
error_msg = "reactor PLC comms version does not match supervisor comms version, make sure both devices are up-to-date (ccmsi update ...)"
|
||||
else
|
||||
error_msg = "error: invalid reply from supervisor"
|
||||
end
|
||||
@ -120,15 +120,11 @@ local function self_check()
|
||||
|
||||
self.self_check_pass = true
|
||||
|
||||
local cfg = self.settings
|
||||
local modem = ppm.get_wireless_modem()
|
||||
local reactor = ppm.get_fission_reactor()
|
||||
local valid_cfg = plc.validate_config(cfg)
|
||||
|
||||
if cfg.Networked then
|
||||
self.self_check_msg("> check wireless/ender modem connected...", modem ~= nil, "you must connect an ender or wireless modem to the reactor PLC")
|
||||
end
|
||||
local valid_cfg = plc.validate_config(self.settings)
|
||||
|
||||
self.self_check_msg("> check wireless/ender modem connected...", modem ~= nil, "you must connect an ender or wireless modem to the reactor PLC")
|
||||
self.self_check_msg("> check fission reactor connected...", reactor ~= nil, "please connect the reactor PLC to the reactor's fission reactor logic adapter")
|
||||
self.self_check_msg("> check fission reactor formed...")
|
||||
-- this consumes events, but that is fine here
|
||||
@ -136,12 +132,12 @@ local function self_check()
|
||||
|
||||
self.self_check_msg("> check configuration...", valid_cfg, "go through Configure System and apply settings to set any missing settings and repair any corrupted ones")
|
||||
|
||||
if cfg.Networked and valid_cfg and modem then
|
||||
if valid_cfg and modem then
|
||||
self.self_check_msg("> check supervisor connection...")
|
||||
|
||||
-- init mac as needed
|
||||
if cfg.AuthKey and string.len(cfg.AuthKey) >= 8 then
|
||||
network.init_mac(cfg.AuthKey)
|
||||
if self.settings.AuthKey and string.len(self.settings.AuthKey) >= 8 then
|
||||
network.init_mac(self.settings.AuthKey)
|
||||
else
|
||||
network.deinit_mac()
|
||||
end
|
||||
@ -149,12 +145,12 @@ local function self_check()
|
||||
self.nic = network.nic(modem)
|
||||
|
||||
self.nic.closeAll()
|
||||
self.nic.open(cfg.PLC_Channel)
|
||||
self.nic.open(self.settings.PLC_Channel)
|
||||
|
||||
self.sv_addr = comms.BROADCAST
|
||||
self.net_listen = true
|
||||
|
||||
send_sv(MGMT_TYPE.ESTABLISH, { comms.version, "0.0.0", DEVICE_TYPE.PLC, cfg.UnitID })
|
||||
send_sv(MGMT_TYPE.ESTABLISH, { comms.version, "0.0.0", DEVICE_TYPE.PLC, self.settings.UnitID })
|
||||
|
||||
tcd.dispatch_unique(8, handle_timeout)
|
||||
else
|
||||
|
||||
@ -82,9 +82,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
||||
local plc_c_2 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
||||
local plc_c_3 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
||||
local plc_c_4 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
||||
local plc_c_5 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
||||
|
||||
local plc_pane = MultiPane{parent=plc_cfg,x=1,y=4,panes={plc_c_1,plc_c_2,plc_c_3,plc_c_4,plc_c_5}}
|
||||
local plc_pane = MultiPane{parent=plc_cfg,x=1,y=4,panes={plc_c_1,plc_c_2,plc_c_3,plc_c_4}}
|
||||
|
||||
TextBox{parent=plc_cfg,x=1,y=2,text=" PLC Configuration",fg_bg=cpair(colors.black,colors.orange)}
|
||||
|
||||
@ -153,21 +152,13 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
||||
|
||||
function self.bundled_emcool(en) if en then color.enable() else color.disable() end end
|
||||
|
||||
TextBox{parent=plc_c_5,x=1,y=1,height=5,text="Advanced Options"}
|
||||
local invert = Checkbox{parent=plc_c_5,x=1,y=3,label="Invert",default=ini_cfg.EmerCoolInvert,box_fg_bg=cpair(colors.orange,colors.black),callback=function()end}
|
||||
TextBox{parent=plc_c_5,x=10,y=3,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||
TextBox{parent=plc_c_5,x=3,y=4,height=4,text="Digital I/O is already inverted (or not) based on intended use. If you have a non-standard setup, you can use this option to avoid needing a redstone inverter.",fg_bg=cpair(colors.gray,colors.lightGray)}
|
||||
PushButton{parent=plc_c_5,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
local function submit_emcool()
|
||||
tmp_cfg.EmerCoolSide = side_options_map[side.get_value()]
|
||||
tmp_cfg.EmerCoolColor = util.trinary(bundled.get_value(), color_options_map[color.get_value()], nil)
|
||||
tmp_cfg.EmerCoolInvert = invert.get_value()
|
||||
next_from_plc()
|
||||
end
|
||||
|
||||
PushButton{parent=plc_c_4,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=plc_c_4,x=33,y=14,min_width=10,text="Advanced",callback=function()plc_pane.set_value(5)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||
PushButton{parent=plc_c_4,x=44,y=14,text="Next \x1a",callback=submit_emcool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
@ -470,7 +461,6 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
||||
try_set(side, side_to_idx(ini_cfg.EmerCoolSide))
|
||||
try_set(bundled, ini_cfg.EmerCoolColor ~= nil)
|
||||
if ini_cfg.EmerCoolColor ~= nil then try_set(color, color_to_idx(ini_cfg.EmerCoolColor)) end
|
||||
try_set(invert, ini_cfg.EmerCoolInvert)
|
||||
try_set(svr_chan, ini_cfg.SVR_Channel)
|
||||
try_set(plc_chan, ini_cfg.PLC_Channel)
|
||||
try_set(timeout, ini_cfg.ConnTimeout)
|
||||
@ -543,11 +533,9 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
||||
if tmp_cfg.EmerCoolEnable then
|
||||
tmp_cfg.EmerCoolSide = config.EMERGENCY_COOL.side
|
||||
tmp_cfg.EmerCoolColor = config.EMERGENCY_COOL.color
|
||||
tmp_cfg.EmerCoolInvert = false
|
||||
else
|
||||
tmp_cfg.EmerCoolSide = nil
|
||||
tmp_cfg.EmerCoolColor = nil
|
||||
tmp_cfg.EmerCoolInvert = false
|
||||
end
|
||||
|
||||
tmp_cfg.SVR_Channel = config.SVR_CHANNEL
|
||||
@ -604,7 +592,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
||||
local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
||||
alternate = not alternate
|
||||
|
||||
if (string.len(val) > val_max_w) or string.find(val, "\n") then
|
||||
if string.len(val) > val_max_w then
|
||||
local lines = util.strwrap(val, inner_width)
|
||||
height = #lines + 1
|
||||
end
|
||||
|
||||
@ -32,8 +32,7 @@ local changes = {
|
||||
{ "v1.6.2", { "AuthKey minimum length is now 8 (if set)" } },
|
||||
{ "v1.6.8", { "ConnTimeout can now have a fractional part" } },
|
||||
{ "v1.6.15", { "Added front panel UI theme", "Added color accessibility modes" } },
|
||||
{ "v1.7.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
||||
{ "v1.8.21", { "Added option to invert emergency coolant redstone control" } }
|
||||
{ "v1.7.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } }
|
||||
}
|
||||
|
||||
---@class plc_configurator
|
||||
@ -77,7 +76,6 @@ local tmp_cfg = {
|
||||
EmerCoolEnable = false,
|
||||
EmerCoolSide = nil, ---@type string|nil
|
||||
EmerCoolColor = nil, ---@type color|nil
|
||||
EmerCoolInvert = false, ---@type boolean
|
||||
SVR_Channel = nil, ---@type integer
|
||||
PLC_Channel = nil, ---@type integer
|
||||
ConnTimeout = nil, ---@type number
|
||||
@ -102,7 +100,6 @@ local fields = {
|
||||
{ "EmerCoolEnable", "Emergency Coolant", false },
|
||||
{ "EmerCoolSide", "Emergency Coolant Side", nil },
|
||||
{ "EmerCoolColor", "Emergency Coolant Color", nil },
|
||||
{ "EmerCoolInvert", "Emergency Coolant Invert", false },
|
||||
{ "SVR_Channel", "SVR Channel", 16240 },
|
||||
{ "PLC_Channel", "PLC Channel", 16241 },
|
||||
{ "ConnTimeout", "Connection Timeout", 5 },
|
||||
|
||||
@ -40,8 +40,6 @@ local function init(panel)
|
||||
|
||||
local disabled_fg = style.fp.disabled_fg
|
||||
|
||||
local term_w, term_h = term.getSize()
|
||||
|
||||
local header = TextBox{parent=panel,y=1,text="FISSION REACTOR PLC - UNIT ?",alignment=ALIGN.CENTER,fg_bg=style.theme.header}
|
||||
header.register(databus.ps, "unit_id", function (id) header.set_value(util.c("FISSION REACTOR PLC - UNIT ", id)) end)
|
||||
|
||||
@ -62,7 +60,7 @@ local function init(panel)
|
||||
local modem = LED{parent=system,label="MODEM",colors=ind_grn}
|
||||
|
||||
if not style.colorblind then
|
||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.ind_bkg}}
|
||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,style.ind_bkg}}
|
||||
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
network.register(databus.ps, "link_state", network.update)
|
||||
else
|
||||
@ -123,7 +121,7 @@ local function init(panel)
|
||||
-- status & controls
|
||||
--
|
||||
|
||||
local status = Div{parent=panel,width=term_w-32,height=18,x=17,y=3}
|
||||
local status = Div{parent=panel,width=19,height=18,x=17,y=3}
|
||||
|
||||
local active = LED{parent=status,x=2,width=12,label="RCT ACTIVE",colors=ind_grn}
|
||||
|
||||
@ -133,15 +131,14 @@ local function init(panel)
|
||||
emer_cool.register(databus.ps, "emer_cool", emer_cool.update)
|
||||
end
|
||||
|
||||
local status_trip_rct = Rectangle{parent=status,height=3,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true}
|
||||
local status_trip = Div{parent=status_trip_rct,height=1,fg_bg=s_hi_box}
|
||||
local status_trip_rct = Rectangle{parent=status,width=20,height=3,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true}
|
||||
local status_trip = Div{parent=status_trip_rct,width=18,height=1,fg_bg=s_hi_box}
|
||||
local scram = LED{parent=status_trip,width=10,label="RPS TRIP",colors=ind_red,flash=true,period=flasher.PERIOD.BLINK_250_MS}
|
||||
|
||||
local controls_rct = Rectangle{parent=status,width=status.get_width()-2,height=3,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true}
|
||||
local controls = Div{parent=controls_rct,width=controls_rct.get_width()-2,height=1,fg_bg=s_hi_box}
|
||||
local button_padding = math.floor((controls.get_width() - 14) / 3)
|
||||
PushButton{parent=controls,x=button_padding+1,y=1,min_width=7,text="SCRAM",callback=databus.rps_scram,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.black,colors.red_off)}
|
||||
PushButton{parent=controls,x=(2*button_padding)+9,y=1,min_width=7,text="RESET",callback=databus.rps_reset,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.black,colors.yellow_off)}
|
||||
local controls_rct = Rectangle{parent=status,width=17,height=3,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true}
|
||||
local controls = Div{parent=controls_rct,width=15,height=1,fg_bg=s_hi_box}
|
||||
PushButton{parent=controls,x=1,y=1,min_width=7,text="SCRAM",callback=databus.rps_scram,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.black,colors.red_off)}
|
||||
PushButton{parent=controls,x=9,y=1,min_width=7,text="RESET",callback=databus.rps_reset,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.black,colors.yellow_off)}
|
||||
|
||||
active.register(databus.ps, "reactor_active", active.update)
|
||||
scram.register(databus.ps, "rps_scram", scram.update)
|
||||
@ -150,9 +147,9 @@ local function init(panel)
|
||||
-- about footer
|
||||
--
|
||||
|
||||
local about = Div{parent=panel,width=15,height=2,y=term_h-1,fg_bg=disabled_fg}
|
||||
local fw_v = TextBox{parent=about,text="FW: v00.00.00"}
|
||||
local comms_v = TextBox{parent=about,text="NT: v00.00.00"}
|
||||
local about = Div{parent=panel,width=15,height=3,x=1,y=18,fg_bg=disabled_fg}
|
||||
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00"}
|
||||
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00"}
|
||||
|
||||
fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
||||
comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
||||
@ -161,7 +158,7 @@ local function init(panel)
|
||||
-- rps list
|
||||
--
|
||||
|
||||
local rps = Rectangle{parent=panel,width=16,height=16,x=term_w-15,y=3,border=border(1,s_hi_box.bkg),thin=true,fg_bg=s_hi_box}
|
||||
local rps = Rectangle{parent=panel,width=16,height=16,x=36,y=3,border=border(1,s_hi_box.bkg),thin=true,fg_bg=s_hi_box}
|
||||
local rps_man = LED{parent=rps,label="MANUAL",colors=ind_red}
|
||||
local rps_auto = LED{parent=rps,label="AUTOMATIC",colors=ind_red}
|
||||
local rps_tmo = LED{parent=rps,label="TIMEOUT",colors=ind_red}
|
||||
|
||||
@ -23,7 +23,8 @@ local AUTO_ACK = comms.PLC_AUTO_ACK
|
||||
|
||||
local RPS_LIMITS = const.RPS_LIMITS
|
||||
|
||||
-- specific errors thrown when scram/start is used that still count as success
|
||||
-- I sure hope the devs don't change this error message, not that it would have safety implications
|
||||
-- I wish they didn't change it to be like this
|
||||
local PCALL_SCRAM_MSG = "Scram requires the reactor to be active."
|
||||
local PCALL_START_MSG = "Reactor is already active."
|
||||
|
||||
@ -43,7 +44,6 @@ function plc.load_config()
|
||||
config.EmerCoolEnable = settings.get("EmerCoolEnable")
|
||||
config.EmerCoolSide = settings.get("EmerCoolSide")
|
||||
config.EmerCoolColor = settings.get("EmerCoolColor")
|
||||
config.EmerCoolInvert = settings.get("EmerCoolInvert")
|
||||
|
||||
config.SVR_Channel = settings.get("SVR_Channel")
|
||||
config.PLC_Channel = settings.get("PLC_Channel")
|
||||
@ -99,7 +99,6 @@ function plc.validate_config(cfg)
|
||||
if cfg.EmerCoolEnable then
|
||||
cfv.assert_eq(rsio.is_valid_side(cfg.EmerCoolSide), true)
|
||||
cfv.assert_eq(cfg.EmerCoolColor == nil or rsio.is_color(cfg.EmerCoolColor), true)
|
||||
cfv.assert_type_bool(cfg.EmerCoolInvert)
|
||||
end
|
||||
|
||||
return cfv.valid()
|
||||
@ -168,8 +167,7 @@ function plc.rps_init(reactor, is_formed)
|
||||
local function _set_emer_cool(state)
|
||||
-- check if this was configured: if it's a table, fields have already been validated.
|
||||
if config.EmerCoolEnable then
|
||||
-- use ~= as XOR for simple inversion
|
||||
local level = rsio.digital_write_active(rsio.IO.U_EMER_COOL, config.EmerCoolInvert ~= state)
|
||||
local level = rsio.digital_write_active(rsio.IO.U_EMER_COOL, state)
|
||||
|
||||
if level ~= false then
|
||||
if rsio.is_color(config.EmerCoolColor) then
|
||||
|
||||
@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc")
|
||||
local renderer = require("reactor-plc.renderer")
|
||||
local threads = require("reactor-plc.threads")
|
||||
|
||||
local R_PLC_VERSION = "v1.8.22"
|
||||
local R_PLC_VERSION = "v1.8.14"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@ -169,12 +169,12 @@ local function main()
|
||||
-- PLC init<br>
|
||||
--- EVENT_CONSUMER: this function consumes events
|
||||
local function init()
|
||||
-- scram on boot if networked, otherwise leave the reactor be
|
||||
if __shared_memory.networked and (not plc_state.no_reactor) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then
|
||||
-- just booting up, no fission allowed (neutrons stay put thanks)
|
||||
if (not plc_state.no_reactor) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then
|
||||
smem_dev.reactor.scram()
|
||||
end
|
||||
|
||||
-- setup front panel
|
||||
-- front panel time!
|
||||
if not renderer.ui_ready() then
|
||||
local message
|
||||
plc_state.fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode)
|
||||
|
||||
@ -1,318 +0,0 @@
|
||||
local comms = require("scada-common.comms")
|
||||
local network = require("scada-common.network")
|
||||
local ppm = require("scada-common.ppm")
|
||||
local rsio = require("scada-common.rsio")
|
||||
local tcd = require("scada-common.tcd")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local rtu = require("rtu.rtu")
|
||||
|
||||
local redstone = require("rtu.config.redstone")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.Div")
|
||||
local ListBox = require("graphics.elements.ListBox")
|
||||
local TextBox = require("graphics.elements.TextBox")
|
||||
|
||||
local PushButton = require("graphics.elements.controls.PushButton")
|
||||
|
||||
local tri = util.trinary
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
local PROTOCOL = comms.PROTOCOL
|
||||
local DEVICE_TYPE = comms.DEVICE_TYPE
|
||||
local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
||||
local MGMT_TYPE = comms.MGMT_TYPE
|
||||
|
||||
local self = {
|
||||
nic = nil, ---@type nic
|
||||
net_listen = false,
|
||||
sv_addr = comms.BROADCAST,
|
||||
sv_seq_num = util.time_ms() * 10,
|
||||
|
||||
self_check_pass = true,
|
||||
|
||||
settings = nil, ---@type rtu_config
|
||||
|
||||
run_test_btn = nil, ---@type PushButton
|
||||
sc_log = nil, ---@type ListBox
|
||||
self_check_msg = nil ---@type function
|
||||
}
|
||||
|
||||
-- report successful completion of the check
|
||||
local function check_complete()
|
||||
TextBox{parent=self.sc_log,text="> all tests passed!",fg_bg=cpair(colors.blue,colors._INHERIT)}
|
||||
TextBox{parent=self.sc_log,text=""}
|
||||
local more = Div{parent=self.sc_log,height=3,fg_bg=cpair(colors.gray,colors._INHERIT)}
|
||||
TextBox{parent=more,text="if you still have a problem:"}
|
||||
TextBox{parent=more,text="- check the wiki on GitHub"}
|
||||
TextBox{parent=more,text="- ask for help on GitHub discussions or Discord"}
|
||||
end
|
||||
|
||||
-- send a management packet to the supervisor
|
||||
---@param msg_type MGMT_TYPE
|
||||
---@param msg table
|
||||
local function send_sv(msg_type, msg)
|
||||
local s_pkt = comms.scada_packet()
|
||||
local pkt = comms.mgmt_packet()
|
||||
|
||||
pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.sv_addr, self.sv_seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
||||
|
||||
self.nic.transmit(self.settings.SVR_Channel, self.settings.RTU_Channel, s_pkt)
|
||||
self.sv_seq_num = self.sv_seq_num + 1
|
||||
end
|
||||
|
||||
-- handle an establish message from the supervisor
|
||||
---@param packet mgmt_frame
|
||||
local function handle_packet(packet)
|
||||
local error_msg = nil
|
||||
|
||||
if packet.scada_frame.local_channel() ~= self.settings.RTU_Channel then
|
||||
error_msg = "error: unknown receive channel"
|
||||
elseif packet.scada_frame.remote_channel() == self.settings.SVR_Channel and packet.scada_frame.protocol() == PROTOCOL.SCADA_MGMT then
|
||||
if packet.type == MGMT_TYPE.ESTABLISH then
|
||||
if packet.length == 1 then
|
||||
local est_ack = packet.data[1]
|
||||
|
||||
if est_ack== ESTABLISH_ACK.ALLOW then
|
||||
self.self_check_msg(nil, true, "")
|
||||
self.sv_addr = packet.scada_frame.src_addr()
|
||||
send_sv(MGMT_TYPE.CLOSE, {})
|
||||
if self.self_check_pass then check_complete() end
|
||||
elseif est_ack == ESTABLISH_ACK.DENY then
|
||||
error_msg = "error: supervisor connection denied"
|
||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
error_msg = "RTU gateway comms version does not match supervisor comms version, make sure both devices are up-to-date (ccmsi update)"
|
||||
else
|
||||
error_msg = "error: invalid reply from supervisor"
|
||||
end
|
||||
else
|
||||
error_msg = "error: invalid reply length from supervisor"
|
||||
end
|
||||
else
|
||||
error_msg = "error: didn't get an establish reply from supervisor"
|
||||
end
|
||||
end
|
||||
|
||||
self.net_listen = false
|
||||
self.run_test_btn.enable()
|
||||
|
||||
if error_msg then
|
||||
self.self_check_msg(nil, false, error_msg)
|
||||
end
|
||||
end
|
||||
|
||||
-- handle supervisor connection failure
|
||||
local function handle_timeout()
|
||||
self.net_listen = false
|
||||
self.run_test_btn.enable()
|
||||
self.self_check_msg(nil, false, "make sure your supervisor is running, your channels are correct, trusted ranges are set properly (if enabled), facility keys match (if set), and if you are using wireless modems rather than ender modems, that your devices are close together in the same dimension")
|
||||
end
|
||||
|
||||
|
||||
-- check if a value is an integer within a range (inclusive)
|
||||
---@param x any
|
||||
---@param min integer
|
||||
---@param max integer
|
||||
local function is_int_min_max(x, min, max) return util.is_int(x) and x >= min and x <= max end
|
||||
|
||||
-- execute the self-check
|
||||
local function self_check()
|
||||
self.run_test_btn.disable()
|
||||
|
||||
self.sc_log.remove_all()
|
||||
ppm.mount_all()
|
||||
|
||||
self.self_check_pass = true
|
||||
|
||||
local cfg = self.settings
|
||||
local modem = ppm.get_wireless_modem()
|
||||
local valid_cfg = rtu.validate_config(cfg)
|
||||
|
||||
self.self_check_msg("> check wireless/ender modem connected...", modem ~= nil, "you must connect an ender or wireless modem to the RTU gateway")
|
||||
self.self_check_msg("> check gateway configuration...", valid_cfg, "go through Configure Gateway and apply settings to set any missing settings and repair any corrupted ones")
|
||||
|
||||
-- check redstone configurations
|
||||
|
||||
local phys = {} ---@type rtu_rs_definition[][]
|
||||
local inputs = { [0] = {}, {}, {}, {}, {} }
|
||||
|
||||
for i = 1, #cfg.Redstone do
|
||||
local entry = cfg.Redstone[i]
|
||||
local name = entry.relay or "local"
|
||||
|
||||
if phys[name] == nil then phys[name] = {} end
|
||||
table.insert(phys[entry.relay or "local"], entry)
|
||||
end
|
||||
|
||||
for name, entries in pairs(phys) do
|
||||
TextBox{parent=self.sc_log,text="> checking redstone @ "..name.."...",fg_bg=cpair(colors.blue,colors.white)}
|
||||
|
||||
local ifaces = {}
|
||||
local bundled_sides = {}
|
||||
|
||||
for i = 1, #entries do
|
||||
local entry = entries[i]
|
||||
local ident = entry.side .. tri(entry.color, ":" .. rsio.color_name(entry.color), "")
|
||||
|
||||
local sc_dupe = util.table_contains(ifaces, ident)
|
||||
local mixed = (bundled_sides[entry.side] and (entry.color == nil)) or (bundled_sides[entry.side] == false and (entry.color ~= nil))
|
||||
|
||||
local mixed_msg = util.trinary(bundled_sides[entry.side], "bundled entry(s) but this entry is not", "non-bundled entry(s) but this entry is")
|
||||
|
||||
self.self_check_msg("> check redstone " .. ident .. " unique...", not sc_dupe, "only one port should be set to a side/color combination")
|
||||
self.self_check_msg("> check redstone " .. ident .. " bundle...", not mixed, "this side has " .. mixed_msg .. " bundled, which will not work")
|
||||
self.self_check_msg("> check redstone " .. ident .. " valid...", redstone.validate(entry), "configuration invalid, please re-configure redstone entry")
|
||||
|
||||
if rsio.get_io_dir(entry.port) == rsio.IO_DIR.IN then
|
||||
local in_dupe = util.table_contains(inputs[entry.unit or 0], entry.port)
|
||||
self.self_check_msg("> check redstone " .. ident .. " input...", not in_dupe, "you cannot have multiple of the same input for a given unit or the facility ("..rsio.to_string(entry.port)..")")
|
||||
end
|
||||
|
||||
bundled_sides[entry.side] = bundled_sides[entry.side] or entry.color ~= nil
|
||||
table.insert(ifaces, ident)
|
||||
end
|
||||
end
|
||||
|
||||
-- check peripheral configurations
|
||||
for i = 1, #cfg.Peripherals do
|
||||
local entry = cfg.Peripherals[i]
|
||||
local valid = false
|
||||
|
||||
if type(entry.name) == "string" then
|
||||
self.self_check_msg("> check " .. entry.name .. " connected...", ppm.get_periph(entry.name), "please connect this device via a wired modem or direct contact and ensure the configuration matches what it connects as")
|
||||
|
||||
local p_type = ppm.get_type(entry.name)
|
||||
|
||||
if p_type == "boilerValve" then
|
||||
valid = is_int_min_max(entry.index, 1, 2) and is_int_min_max(entry.unit, 1, 4)
|
||||
elseif p_type == "turbineValve" then
|
||||
valid = is_int_min_max(entry.index, 1, 3) and is_int_min_max(entry.unit, 1, 4)
|
||||
elseif p_type == "solarNeutronActivator" then
|
||||
valid = is_int_min_max(entry.unit, 1, 4)
|
||||
elseif p_type == "dynamicValve" then
|
||||
valid = (entry.unit == nil and is_int_min_max(entry.index, 1, 4)) or is_int_min_max(entry.unit, 1, 4)
|
||||
elseif p_type == "environmentDetector" or p_type == "environment_detector" then
|
||||
valid = (entry.unit == nil or is_int_min_max(entry.unit, 1, 4)) and util.is_int(entry.index)
|
||||
else
|
||||
valid = true
|
||||
|
||||
if p_type ~= nil and not (p_type == "inductionPort" or p_type == "reinforcedInductionPort" or p_type == "spsPort") then
|
||||
self.self_check_msg("> check " .. entry.name .. " valid...", false, "unrecognized device type")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not valid then
|
||||
self.self_check_msg("> check " .. entry.name .. " valid...", false, "configuration invalid, please re-configure peripheral entry")
|
||||
end
|
||||
end
|
||||
|
||||
if valid_cfg and modem then
|
||||
self.self_check_msg("> check supervisor connection...")
|
||||
|
||||
-- init mac as needed
|
||||
if cfg.AuthKey and string.len(cfg.AuthKey) >= 8 then
|
||||
network.init_mac(cfg.AuthKey)
|
||||
else
|
||||
network.deinit_mac()
|
||||
end
|
||||
|
||||
self.nic = network.nic(modem)
|
||||
|
||||
self.nic.closeAll()
|
||||
self.nic.open(cfg.RTU_Channel)
|
||||
|
||||
self.sv_addr = comms.BROADCAST
|
||||
self.net_listen = true
|
||||
|
||||
send_sv(MGMT_TYPE.ESTABLISH, { comms.version, "0.0.0", DEVICE_TYPE.RTU, {} })
|
||||
|
||||
tcd.dispatch_unique(8, handle_timeout)
|
||||
else
|
||||
if self.self_check_pass then check_complete() end
|
||||
self.run_test_btn.enable()
|
||||
end
|
||||
end
|
||||
|
||||
-- exit self check back home
|
||||
---@param main_pane MultiPane
|
||||
local function exit_self_check(main_pane)
|
||||
tcd.abort(handle_timeout)
|
||||
self.net_listen = false
|
||||
self.run_test_btn.enable()
|
||||
self.sc_log.remove_all()
|
||||
main_pane.set_value(1)
|
||||
end
|
||||
|
||||
local check = {}
|
||||
|
||||
-- create the self-check view
|
||||
---@param main_pane MultiPane
|
||||
---@param settings_cfg rtu_config
|
||||
---@param check_sys Div
|
||||
---@param style { [string]: cpair }
|
||||
function check.create(main_pane, settings_cfg, check_sys, style)
|
||||
local bw_fg_bg = style.bw_fg_bg
|
||||
local g_lg_fg_bg = style.g_lg_fg_bg
|
||||
local nav_fg_bg = style.nav_fg_bg
|
||||
local btn_act_fg_bg = style.btn_act_fg_bg
|
||||
local btn_dis_fg_bg = style.btn_dis_fg_bg
|
||||
|
||||
self.settings = settings_cfg
|
||||
|
||||
local sc = Div{parent=check_sys,x=2,y=4,width=49}
|
||||
|
||||
TextBox{parent=check_sys,x=1,y=2,text=" RTU Gateway Self-Check",fg_bg=bw_fg_bg}
|
||||
|
||||
self.sc_log = ListBox{parent=sc,x=1,y=1,height=12,width=49,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||
|
||||
local last_check = { nil, nil }
|
||||
|
||||
function self.self_check_msg(msg, success, fail_msg)
|
||||
if type(msg) == "string" then
|
||||
last_check[1] = Div{parent=self.sc_log,height=1}
|
||||
local e = TextBox{parent=last_check[1],text=msg,fg_bg=bw_fg_bg}
|
||||
last_check[2] = e.get_x()+string.len(msg)
|
||||
end
|
||||
|
||||
if type(fail_msg) == "string" then
|
||||
TextBox{parent=last_check[1],x=last_check[2],y=1,text=tri(success,"PASS","FAIL"),fg_bg=tri(success,cpair(colors.green,colors._INHERIT),cpair(colors.red,colors._INHERIT))}
|
||||
|
||||
if not success then
|
||||
local fail = Div{parent=self.sc_log,height=#util.strwrap(fail_msg, 46)}
|
||||
TextBox{parent=fail,x=3,text=fail_msg,fg_bg=cpair(colors.gray,colors.white)}
|
||||
end
|
||||
|
||||
self.self_check_pass = self.self_check_pass and success
|
||||
end
|
||||
end
|
||||
|
||||
PushButton{parent=sc,x=1,y=14,text="\x1b Back",callback=function()exit_self_check(main_pane)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
self.run_test_btn = PushButton{parent=sc,x=40,y=14,min_width=10,text="Run Test",callback=function()self_check()end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||
end
|
||||
|
||||
-- handle incoming modem messages
|
||||
---@param side string
|
||||
---@param sender integer
|
||||
---@param reply_to integer
|
||||
---@param message any
|
||||
---@param distance integer
|
||||
function check.receive_sv(side, sender, reply_to, message, distance)
|
||||
if self.nic ~= nil and self.net_listen then
|
||||
local s_pkt = self.nic.receive(side, sender, reply_to, message, distance)
|
||||
|
||||
if s_pkt and s_pkt.protocol() == PROTOCOL.SCADA_MGMT then
|
||||
local mgmt_pkt = comms.mgmt_packet()
|
||||
if mgmt_pkt.decode(s_pkt) then
|
||||
tcd.abort(handle_timeout)
|
||||
handle_packet(mgmt_pkt.get())
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return check
|
||||
@ -43,8 +43,8 @@ local self = {
|
||||
|
||||
local peripherals = {}
|
||||
|
||||
local RTU_DEV_TYPES = { "boilerValve", "turbineValve", "dynamicValve", "inductionPort", "reinforcedInductionPort", "spsPort", "solarNeutronActivator", "environmentDetector", "environment_detector" }
|
||||
local NEEDS_UNIT = { "boilerValve", "turbineValve", "dynamicValve", "solarNeutronActivator", "environmentDetector", "environment_detector" }
|
||||
local RTU_DEV_TYPES = { "boilerValve", "turbineValve", "dynamicValve", "inductionPort", "spsPort", "solarNeutronActivator", "environmentDetector" }
|
||||
local NEEDS_UNIT = { "boilerValve", "turbineValve", "dynamicValve", "solarNeutronActivator", "environmentDetector" }
|
||||
|
||||
-- create the peripherals configuration view
|
||||
---@param tool_ctl _rtu_cfg_tool_ctl
|
||||
@ -149,7 +149,7 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style)
|
||||
reposition("This SNA is for reactor unit # .", 46, 1, 31, 4, 7)
|
||||
self.p_idx.hide()
|
||||
self.p_assign_btn.hide(true)
|
||||
self.p_desc_ext.set_value("Warning: too many devices on one RTU Gateway can cause lag. Note that 10x the \"PEAK\x1a\" rate on the flow monitor gives you the mB/t of waste that the SNA(s) can process. Enough SNAs to provide 2x to 3x of that unit's max burn rate should be a good margin to catch up after night or cloudy weather.")
|
||||
self.p_desc_ext.set_value("Before adding lots of SNAs: multiply the \"PEAK\" rate on the flow monitor (after connecting at least 1 SNA) by 10 to get the mB/t of waste that they can process. Enough SNAs to provide 2x to 3x of your max burn rate should be a good margin to catch up after night or cloudy weather. Too many devices (such as SNAs) on one RTU can cause lag.")
|
||||
elseif type == "dynamicValve" then
|
||||
reposition("This is the below system's # dynamic tank.", 29, 4, 17, 6, 8)
|
||||
self.p_assign_btn.show()
|
||||
@ -165,14 +165,14 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style)
|
||||
end
|
||||
|
||||
self.p_desc.set_value("Each reactor unit can have at most 1 tank and the facility can have at most 4. Each facility tank must have a unique # 1 through 4, regardless of where it is connected. Only a total of 4 tanks can be displayed on the flow monitor.")
|
||||
elseif type == "environmentDetector" or type == "environment_detector" then
|
||||
elseif type == "environmentDetector" then
|
||||
reposition("This is the below system's # env. detector.", 29, 99, 17, 6, 8)
|
||||
self.p_assign_btn.show()
|
||||
self.p_assign_btn.redraw()
|
||||
if self.p_assign_btn.get_value() == 1 then self.p_unit.disable() else self.p_unit.enable() end
|
||||
self.p_desc.set_value("You can connect more than one environment detector for a particular unit or the facility. In that case, the maximum radiation reading from those assigned to that particular unit or the facility will be used for alarms and display.")
|
||||
elseif type == "inductionPort" or type == "reinforcedInductionPort" or type == "spsPort" then
|
||||
local dev = tri(type == "inductionPort" or type == "reinforcedInductionPort", "induction matrix", "SPS")
|
||||
elseif type == "inductionPort" or type == "spsPort" then
|
||||
local dev = tri(type == "inductionPort", "induction matrix", "SPS")
|
||||
self.p_idx.hide(true)
|
||||
self.p_unit.hide(true)
|
||||
self.p_prompt.set_value("This is the " .. dev .. " for the facility.")
|
||||
@ -212,10 +212,10 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style)
|
||||
|
||||
tool_ctl.update_peri_list()
|
||||
|
||||
TextBox{parent=peri_c_3,x=1,y=1,height=4,text="This feature is intended for advanced users. If you just can't see your device, click 'I don't see my device!' instead."}
|
||||
TextBox{parent=peri_c_3,x=1,y=5,height=4,text="Peripheral Name"}
|
||||
local p_name = TextField{parent=peri_c_3,x=1,y=6,width=49,height=1,max_len=128,fg_bg=bw_fg_bg}
|
||||
local p_type = Radio2D{parent=peri_c_3,x=1,y=8,rows=5,columns=2,default=1,options=RTU_DEV_TYPES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.purple}
|
||||
TextBox{parent=peri_c_3,x=1,y=1,height=4,text="This feature is intended for advanced users. If you are clicking this just because your device is not shown, follow the connection instructions in 'I don't see my device!'."}
|
||||
TextBox{parent=peri_c_3,x=1,y=6,height=4,text="Peripheral Name"}
|
||||
local p_name = TextField{parent=peri_c_3,x=1,y=7,width=49,height=1,max_len=128,fg_bg=bw_fg_bg}
|
||||
local p_type = Radio2D{parent=peri_c_3,x=1,y=9,rows=4,columns=2,default=1,options=RTU_DEV_TYPES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.purple}
|
||||
local man_p_err = TextBox{parent=peri_c_3,x=8,y=14,width=35,text="Please enter a peripheral name.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||
man_p_err.hide(true)
|
||||
|
||||
@ -281,7 +281,7 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style)
|
||||
local idx = tonumber(self.p_idx.get_value())
|
||||
|
||||
if util.table_contains(NEEDS_UNIT, peri_type) then
|
||||
if (peri_type == "dynamicValve" or peri_type == "environmentDetector" or peri_type == "environment_detector") and for_facility then
|
||||
if (peri_type == "dynamicValve" or peri_type == "environmentDetector") and for_facility then
|
||||
-- skip
|
||||
elseif not (util.is_int(u) and u > 0 and u < 5) then
|
||||
self.p_err.set_value("Unit ID must be within 1 to 4.")
|
||||
@ -310,7 +310,7 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style)
|
||||
else index = idx end
|
||||
elseif peri_type == "dynamicValve" then
|
||||
index = 1
|
||||
elseif peri_type == "environmentDetector" or peri_type == "environment_detector" then
|
||||
elseif peri_type == "environmentDetector" then
|
||||
if not (util.is_int(idx) and idx > 0) then
|
||||
self.p_err.set_value("Index must be greater than 0.")
|
||||
self.p_err.show()
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
local constants = require("scada-common.constants")
|
||||
local ppm = require("scada-common.ppm")
|
||||
local rsio = require("scada-common.rsio")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
@ -19,10 +18,8 @@ local NumberField = require("graphics.elements.form.NumberField")
|
||||
---@class rtu_rs_definition
|
||||
---@field unit integer|nil
|
||||
---@field port IO_PORT
|
||||
---@field relay string|nil
|
||||
---@field side side
|
||||
---@field color color|nil
|
||||
---@field invert true|nil
|
||||
|
||||
local tri = util.trinary
|
||||
|
||||
@ -35,7 +32,6 @@ local IO_MODE = rsio.IO_MODE
|
||||
local LEFT = core.ALIGN.LEFT
|
||||
|
||||
local self = {
|
||||
rs_cfg_phy = false, ---@type string|nil|false
|
||||
rs_cfg_port = 1, ---@type IO_PORT
|
||||
rs_cfg_editing = false, ---@type integer|false
|
||||
|
||||
@ -45,9 +41,7 @@ local self = {
|
||||
rs_cfg_side_l = nil, ---@type TextBox
|
||||
rs_cfg_bundled = nil, ---@type Checkbox
|
||||
rs_cfg_color = nil, ---@type Radio2D
|
||||
rs_cfg_inverted = nil, ---@type Checkbox
|
||||
rs_cfg_shortcut = nil, ---@type TextBox
|
||||
rs_cfg_advanced = nil ---@type PushButton
|
||||
rs_cfg_shortcut = nil ---@type TextBox
|
||||
}
|
||||
|
||||
-- rsio port descriptions
|
||||
@ -80,12 +74,11 @@ local PORT_DESC_MAP = {
|
||||
{ IO.R_PLC_FAULT, "RPS PLC Fault" },
|
||||
{ IO.R_PLC_TIMEOUT, "RPS Supervisor Timeout" },
|
||||
{ IO.U_ALARM, "Unit Alarm" },
|
||||
{ IO.U_EMER_COOL, "Unit Emergency Cool. Valve" },
|
||||
{ IO.U_AUX_COOL, "Unit Auxiliary Cool. Valve" }
|
||||
{ IO.U_EMER_COOL, "Unit Emergency Cool. Valve" }
|
||||
}
|
||||
|
||||
-- designation (0 = facility, 1 = unit)
|
||||
local PORT_DSGN = { [-1] = 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1 }
|
||||
local PORT_DSGN = { [-1] = 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }
|
||||
|
||||
assert(#PORT_DESC_MAP == rsio.NUM_PORTS)
|
||||
assert(#PORT_DSGN == rsio.NUM_PORTS)
|
||||
@ -111,34 +104,8 @@ local function color_to_idx(color)
|
||||
end
|
||||
end
|
||||
|
||||
-- select the subset of redstone entries assigned to the given phy
|
||||
---@param cfg rtu_rs_definition[] the full redstone entry list
|
||||
---@param phy string|nil which phy to get redstone entries for
|
||||
---@param invert boolean? true to get all except this phy
|
||||
---@return rtu_rs_definition[]
|
||||
local function redstone_subset(cfg, phy, invert)
|
||||
local subset = {}
|
||||
|
||||
for i = 1, #cfg do
|
||||
if ((not invert) and cfg[i].relay == phy) or (invert and cfg[i].relay ~= phy) then
|
||||
table.insert(subset, cfg[i])
|
||||
end
|
||||
end
|
||||
|
||||
return subset
|
||||
end
|
||||
|
||||
local redstone = {}
|
||||
|
||||
-- validate a redstone entry
|
||||
---@param def rtu_rs_definition
|
||||
function redstone.validate(def)
|
||||
return tri(PORT_DSGN[def.port] == 1, util.is_int(def.unit) and def.unit > 0 and def.unit <= 4, def.unit == nil) and
|
||||
rsio.is_valid_port(def.port) and
|
||||
rsio.is_valid_side(def.side) and
|
||||
(def.color == nil or (rsio.is_digital(def.port) and rsio.is_color(def.color)))
|
||||
end
|
||||
|
||||
-- create the redstone configuration view
|
||||
---@param tool_ctl _rtu_cfg_tool_ctl
|
||||
---@param main_pane MultiPane
|
||||
@ -157,89 +124,20 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
||||
|
||||
--#region Redstone
|
||||
|
||||
local rs_c_1 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_2 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_3 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_4 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_5 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_6 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_7 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_8 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_9 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_10 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_1 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_2 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_3 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_4 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_5 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_6 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_7 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
|
||||
local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6,rs_c_7,rs_c_8,rs_c_9,rs_c_10}}
|
||||
local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6,rs_c_7}}
|
||||
|
||||
local header = TextBox{parent=rs_cfg,x=1,y=2,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)}
|
||||
TextBox{parent=rs_cfg,x=1,y=2,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)}
|
||||
|
||||
--#region Interface Selection
|
||||
|
||||
TextBox{parent=rs_c_1,x=1,y=1,text="Configure this computer or a redstone relay."}
|
||||
local iface_list = ListBox{parent=rs_c_1,x=1,y=3,height=10,width=49,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||
|
||||
-- update relay interface list
|
||||
function tool_ctl.update_relay_list()
|
||||
local mounts = ppm.list_mounts()
|
||||
|
||||
iface_list.remove_all()
|
||||
|
||||
-- assemble list of configured relays
|
||||
local relays = {}
|
||||
for i = 1, #tmp_cfg.Redstone do
|
||||
local def = tmp_cfg.Redstone[i]
|
||||
if def.relay and not util.table_contains(relays, def.relay) then
|
||||
table.insert(relays, def.relay)
|
||||
end
|
||||
end
|
||||
|
||||
-- add unconfigured connected relays
|
||||
for name, entry in pairs(mounts) do
|
||||
if entry.type == "redstone_relay" and not util.table_contains(relays, name) then
|
||||
table.insert(relays, name)
|
||||
end
|
||||
end
|
||||
|
||||
local function config_rs(name)
|
||||
header.set_value(" Redstone Connections (" .. name .. ")")
|
||||
|
||||
self.rs_cfg_phy = tri(name == "local", nil, name)
|
||||
|
||||
tool_ctl.gen_rs_summary()
|
||||
rs_pane.set_value(2)
|
||||
end
|
||||
|
||||
local line = Div{parent=iface_list,height=2,fg_bg=cpair(colors.black,colors.white)}
|
||||
TextBox{parent=line,x=1,y=1,text="@ local",fg_bg=cpair(colors.black,colors.white)}
|
||||
TextBox{parent=line,x=3,y=2,text="This Computer",fg_bg=cpair(colors.gray,colors.white)}
|
||||
local count = #redstone_subset(ini_cfg.Redstone, nil)
|
||||
TextBox{parent=line,x=33,y=2,width=16,alignment=core.ALIGN.RIGHT,text=count.." connections",fg_bg=cpair(colors.gray,colors.white)}
|
||||
|
||||
PushButton{parent=line,x=41,y=1,min_width=8,height=1,text="CONFIG",callback=function()config_rs("local")end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||
|
||||
for i = 1, #relays do
|
||||
local name = relays[i]
|
||||
|
||||
line = Div{parent=iface_list,height=2,fg_bg=cpair(colors.black,colors.white)}
|
||||
TextBox{parent=line,x=1,y=1,text="@ "..name,fg_bg=cpair(colors.black,colors.white)}
|
||||
TextBox{parent=line,x=3,y=2,text="Redstone Relay",fg_bg=cpair(colors.gray,colors.white)}
|
||||
TextBox{parent=line,x=18,y=2,text=tri(mounts[name],"ONLINE","OFFLINE"),fg_bg=cpair(tri(mounts[name],colors.green,colors.red),colors.white)}
|
||||
count = #redstone_subset(ini_cfg.Redstone, name)
|
||||
TextBox{parent=line,x=33,y=2,width=16,alignment=core.ALIGN.RIGHT,text=count.." connections",fg_bg=cpair(colors.gray,colors.white)}
|
||||
|
||||
PushButton{parent=line,x=41,y=1,min_width=8,height=1,text="CONFIG",callback=function()config_rs(name)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||
end
|
||||
end
|
||||
|
||||
tool_ctl.update_relay_list()
|
||||
|
||||
PushButton{parent=rs_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_1,x=27,y=14,min_width=23,text="I don't see my relay!",callback=function()rs_pane.set_value(10)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
--#region Configuration List
|
||||
|
||||
TextBox{parent=rs_c_2,x=1,y=1,text=" port side/color unit/facility",fg_bg=g_lg_fg_bg}
|
||||
local rs_list = ListBox{parent=rs_c_2,x=1,y=2,height=11,width=49,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||
TextBox{parent=rs_c_1,x=1,y=1,text=" port side/color unit/facility",fg_bg=g_lg_fg_bg}
|
||||
local rs_list = ListBox{parent=rs_c_1,x=1,y=2,height=11,width=49,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||
|
||||
local function rs_revert()
|
||||
tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone)
|
||||
@ -247,47 +145,43 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
||||
end
|
||||
|
||||
local function rs_apply()
|
||||
-- add the changed data to the existing saved data
|
||||
local new_data = redstone_subset(tmp_cfg.Redstone, self.rs_cfg_phy)
|
||||
local new_save = redstone_subset(ini_cfg.Redstone, self.rs_cfg_phy, true)
|
||||
for i = 1, #new_data do table.insert(new_save, new_data[i]) end
|
||||
|
||||
settings.set("Redstone", new_save)
|
||||
settings.set("Redstone", tmp_cfg.Redstone)
|
||||
|
||||
if settings.save("/rtu.settings") then
|
||||
load_settings(settings_cfg, true)
|
||||
load_settings(ini_cfg)
|
||||
rs_pane.set_value(5)
|
||||
rs_pane.set_value(4)
|
||||
|
||||
-- for return to list from saved screen
|
||||
-- this will delete unsaved changes for other phy's, which is acceptable
|
||||
tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone)
|
||||
tool_ctl.gen_rs_summary()
|
||||
tool_ctl.update_relay_list()
|
||||
else
|
||||
rs_pane.set_value(6)
|
||||
rs_pane.set_value(5)
|
||||
end
|
||||
end
|
||||
|
||||
local function rs_back()
|
||||
self.rs_cfg_phy = false
|
||||
rs_pane.set_value(1)
|
||||
header.set_value(" Redstone Connections")
|
||||
end
|
||||
PushButton{parent=rs_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
local rs_revert_btn = PushButton{parent=rs_c_1,x=8,y=14,min_width=16,text="Revert Changes",callback=rs_revert,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||
PushButton{parent=rs_c_1,x=35,y=14,min_width=7,text="New +",callback=function()rs_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||
local rs_apply_btn = PushButton{parent=rs_c_1,x=43,y=14,min_width=7,text="Apply",callback=rs_apply,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||
|
||||
PushButton{parent=rs_c_2,x=1,y=14,text="\x1b Back",callback=rs_back,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
local rs_revert_btn = PushButton{parent=rs_c_2,x=8,y=14,min_width=16,text="Revert Changes",callback=rs_revert,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||
PushButton{parent=rs_c_2,x=35,y=14,min_width=7,text="New +",callback=function()rs_pane.set_value(3)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||
local rs_apply_btn = PushButton{parent=rs_c_2,x=43,y=14,min_width=7,text="Apply",callback=rs_apply,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||
TextBox{parent=rs_c_6,x=1,y=1,height=5,text="You already configured this input. There can only be one entry for each input.\n\nPlease select a different port."}
|
||||
PushButton{parent=rs_c_6,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
--#region Port Selection
|
||||
TextBox{parent=rs_c_2,x=1,y=1,text="Select one of the below ports to use."}
|
||||
|
||||
TextBox{parent=rs_c_3,x=1,y=1,text="Select one of the below ports to use."}
|
||||
|
||||
local rs_ports = ListBox{parent=rs_c_3,x=1,y=3,height=10,width=49,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||
local rs_ports = ListBox{parent=rs_c_2,x=1,y=3,height=10,width=49,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||
|
||||
local function new_rs(port)
|
||||
if (rsio.get_io_dir(port) == rsio.IO_DIR.IN) then
|
||||
for i = 1, #tmp_cfg.Redstone do
|
||||
if tmp_cfg.Redstone[i].port == port then
|
||||
rs_pane.set_value(6)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.rs_cfg_editing = false
|
||||
|
||||
local text
|
||||
@ -296,8 +190,6 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
||||
self.rs_cfg_color.hide(true)
|
||||
self.rs_cfg_shortcut.show()
|
||||
self.rs_cfg_side_l.set_value("Output Side")
|
||||
self.rs_cfg_bundled.enable()
|
||||
self.rs_cfg_advanced.disable()
|
||||
text = "You selected the ALL_WASTE shortcut."
|
||||
else
|
||||
self.rs_cfg_shortcut.hide(true)
|
||||
@ -312,13 +204,9 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
||||
self.rs_cfg_bundled.set_value(false)
|
||||
self.rs_cfg_bundled.disable()
|
||||
self.rs_cfg_color.disable()
|
||||
self.rs_cfg_inverted.set_value(false)
|
||||
self.rs_cfg_advanced.disable()
|
||||
else
|
||||
self.rs_cfg_bundled.enable()
|
||||
if self.rs_cfg_bundled.get_value() then self.rs_cfg_color.enable() else self.rs_cfg_color.disable() end
|
||||
self.rs_cfg_inverted.set_value(false)
|
||||
self.rs_cfg_advanced.enable()
|
||||
end
|
||||
|
||||
if io_mode == IO_MODE.DIGITAL_IN then
|
||||
@ -344,7 +232,7 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
||||
|
||||
self.rs_cfg_selection.set_value(text)
|
||||
self.rs_cfg_port = port
|
||||
rs_pane.set_value(4)
|
||||
rs_pane.set_value(3)
|
||||
end
|
||||
|
||||
-- add entries to redstone option list
|
||||
@ -365,43 +253,43 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
||||
TextBox{parent=entry,x=22,y=1,text=PORT_DESC_MAP[i][2],fg_bg=cpair(colors.gray,colors.white)}
|
||||
end
|
||||
|
||||
PushButton{parent=rs_c_3,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_2,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
--#region Port Configuration
|
||||
self.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=2,text=""}
|
||||
|
||||
self.rs_cfg_selection = TextBox{parent=rs_c_4,x=1,y=1,height=2,text=""}
|
||||
PushButton{parent=rs_c_3,x=36,y=3,text="What's that?",min_width=14,callback=function()rs_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
PushButton{parent=rs_c_4,x=36,y=3,text="What's that?",min_width=14,callback=function()rs_pane.set_value(8)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
TextBox{parent=rs_c_7,x=1,y=1,height=4,text="(Normal) Digital Input: On if there is a redstone signal, off otherwise\nInverted Digital Input: On without a redstone signal, off otherwise"}
|
||||
TextBox{parent=rs_c_7,x=1,y=6,height=4,text="(Normal) Digital Output: Redstone signal to 'turn it on', none to 'turn it off'\nInverted Digital Output: No redstone signal to 'turn it on', redstone signal to 'turn it off'"}
|
||||
TextBox{parent=rs_c_7,x=1,y=11,height=2,text="Analog Input: 0-15 redstone power level input\nAnalog Output: 0-15 scaled redstone power level output"}
|
||||
PushButton{parent=rs_c_7,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
self.rs_cfg_side_l = TextBox{parent=rs_c_4,x=1,y=4,width=11,text="Output Side"}
|
||||
local side = Radio2D{parent=rs_c_4,x=1,y=5,rows=1,columns=6,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red}
|
||||
self.rs_cfg_side_l = TextBox{parent=rs_c_3,x=1,y=4,width=11,text="Output Side"}
|
||||
local side = Radio2D{parent=rs_c_3,x=1,y=5,rows=1,columns=6,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red}
|
||||
|
||||
self.rs_cfg_unit_l = TextBox{parent=rs_c_4,x=25,y=7,width=7,text="Unit ID"}
|
||||
self.rs_cfg_unit = NumberField{parent=rs_c_4,x=33,y=7,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
|
||||
self.rs_cfg_unit_l = TextBox{parent=rs_c_3,x=25,y=7,width=7,text="Unit ID"}
|
||||
self.rs_cfg_unit = NumberField{parent=rs_c_3,x=33,y=7,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
|
||||
|
||||
local function set_bundled(bundled)
|
||||
if bundled then self.rs_cfg_color.enable() else self.rs_cfg_color.disable() end
|
||||
end
|
||||
|
||||
self.rs_cfg_shortcut = TextBox{parent=rs_c_4,x=1,y=9,height=4,text="This shortcut will add entries for each of the 4 waste outputs. If you select bundled, 4 colors will be assigned to the selected side. Otherwise, 4 default sides will be used."}
|
||||
self.rs_cfg_shortcut = TextBox{parent=rs_c_3,x=1,y=9,height=4,text="This shortcut will add entries for each of the 4 waste outputs. If you select bundled, 4 colors will be assigned to the selected side. Otherwise, 4 default sides will be used."}
|
||||
self.rs_cfg_shortcut.hide(true)
|
||||
|
||||
self.rs_cfg_bundled = Checkbox{parent=rs_c_4,x=1,y=7,label="Is Bundled?",default=false,box_fg_bg=cpair(colors.red,colors.black),callback=set_bundled,disable_fg_bg=g_lg_fg_bg}
|
||||
self.rs_cfg_color = Radio2D{parent=rs_c_4,x=1,y=9,rows=4,columns=4,default=1,options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
||||
self.rs_cfg_bundled = Checkbox{parent=rs_c_3,x=1,y=7,label="Is Bundled?",default=false,box_fg_bg=cpair(colors.red,colors.black),callback=set_bundled,disable_fg_bg=g_lg_fg_bg}
|
||||
self.rs_cfg_color = Radio2D{parent=rs_c_3,x=1,y=9,rows=4,columns=4,default=1,options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
||||
self.rs_cfg_color.disable()
|
||||
|
||||
local rs_err = TextBox{parent=rs_c_4,x=8,y=14,width=30,text="Unit ID invalid.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||
local rs_err = TextBox{parent=rs_c_3,x=8,y=14,width=30,text="Unit ID must be within 1 to 4.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||
rs_err.hide(true)
|
||||
|
||||
local function back_from_rs_opts()
|
||||
rs_err.hide(true)
|
||||
if self.rs_cfg_editing ~= false then rs_pane.set_value(2) else rs_pane.set_value(3) end
|
||||
if self.rs_cfg_editing ~= false then rs_pane.set_value(1) else rs_pane.set_value(2) end
|
||||
end
|
||||
|
||||
local function save_rs_entry()
|
||||
assert(self.rs_cfg_phy ~= false, "tried to save a redstone entry without a phy")
|
||||
|
||||
local port = self.rs_cfg_port
|
||||
local u = tonumber(self.rs_cfg_unit.get_value())
|
||||
|
||||
@ -413,23 +301,11 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
||||
local def = {
|
||||
unit = tri(PORT_DSGN[port] == 1, u, nil),
|
||||
port = port,
|
||||
relay = self.rs_cfg_phy,
|
||||
side = side_options_map[side.get_value()],
|
||||
color = tri(self.rs_cfg_bundled.get_value() and rsio.is_digital(port), color_options_map[self.rs_cfg_color.get_value()], nil),
|
||||
invert = self.rs_cfg_inverted.get_value() or nil
|
||||
color = tri(self.rs_cfg_bundled.get_value() and rsio.is_digital(port), color_options_map[self.rs_cfg_color.get_value()], nil)
|
||||
}
|
||||
|
||||
if self.rs_cfg_editing == false then
|
||||
-- check for duplicate inputs for this unit/facility
|
||||
if (rsio.get_io_dir(port) == rsio.IO_DIR.IN) then
|
||||
for i = 1, #tmp_cfg.Redstone do
|
||||
if tmp_cfg.Redstone[i].port == port and tmp_cfg.Redstone[i].unit == def.unit then
|
||||
rs_pane.set_value(7)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(tmp_cfg.Redstone, def)
|
||||
else
|
||||
def.port = tmp_cfg.Redstone[self.rs_cfg_editing].port
|
||||
@ -442,55 +318,33 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
||||
table.insert(tmp_cfg.Redstone, {
|
||||
unit = tri(PORT_DSGN[IO.WASTE_PU + i] == 1, u, nil),
|
||||
port = IO.WASTE_PU + i,
|
||||
relay = self.rs_cfg_phy,
|
||||
side = tri(self.rs_cfg_bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]),
|
||||
color = tri(self.rs_cfg_bundled.get_value(), default_colors[i + 1], nil)
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
rs_pane.set_value(2)
|
||||
rs_pane.set_value(1)
|
||||
tool_ctl.gen_rs_summary()
|
||||
|
||||
side.set_value(1)
|
||||
self.rs_cfg_bundled.set_value(false)
|
||||
self.rs_cfg_color.set_value(1)
|
||||
self.rs_cfg_color.disable()
|
||||
self.rs_cfg_inverted.set_value(false)
|
||||
self.rs_cfg_advanced.disable()
|
||||
else rs_err.show() end
|
||||
end
|
||||
|
||||
PushButton{parent=rs_c_4,x=1,y=14,text="\x1b Back",callback=back_from_rs_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
self.rs_cfg_advanced = PushButton{parent=rs_c_4,x=30,y=14,min_width=10,text="Advanced",callback=function()rs_pane.set_value(9)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||
PushButton{parent=rs_c_4,x=41,y=14,min_width=9,text="Confirm",callback=save_rs_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_3,x=1,y=14,text="\x1b Back",callback=back_from_rs_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_3,x=41,y=14,min_width=9,text="Confirm",callback=save_rs_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
TextBox{parent=rs_c_4,x=1,y=1,text="Settings saved!"}
|
||||
PushButton{parent=rs_c_4,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_4,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=rs_c_5,x=1,y=1,text="Settings saved!"}
|
||||
PushButton{parent=rs_c_5,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
TextBox{parent=rs_c_5,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
||||
PushButton{parent=rs_c_5,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_5,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=rs_c_6,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
||||
PushButton{parent=rs_c_6,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_6,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=rs_c_7,x=1,y=1,height=6,text="You already configured this input for this facility/unit assignment. There can only be one entry for each input per each unit or the facility (for facility inputs).\n\nPlease select a different port."}
|
||||
PushButton{parent=rs_c_7,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=rs_c_8,x=1,y=1,height=4,text="(Normal) Digital Input: On if there is a redstone signal, off otherwise\nInverted Digital Input: On without a redstone signal, off otherwise"}
|
||||
TextBox{parent=rs_c_8,x=1,y=6,height=4,text="(Normal) Digital Output: Redstone signal to 'turn it on', none to 'turn it off'\nInverted Digital Output: No redstone signal to 'turn it on', redstone signal to 'turn it off'"}
|
||||
TextBox{parent=rs_c_8,x=1,y=11,height=2,text="Analog Input: 0-15 redstone power level input\nAnalog Output: 0-15 scaled redstone power level output"}
|
||||
PushButton{parent=rs_c_8,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=rs_c_9,x=1,y=1,height=5,text="Advanced Options"}
|
||||
self.rs_cfg_inverted = Checkbox{parent=rs_c_9,x=1,y=3,label="Invert",default=false,box_fg_bg=cpair(colors.red,colors.black),callback=function()end,disable_fg_bg=g_lg_fg_bg}
|
||||
TextBox{parent=rs_c_9,x=3,y=4,height=4,text="Digital I/O is already inverted (or not) based on intended use. If you have a non-standard setup, you can use this option to avoid needing a redstone inverter.",fg_bg=cpair(colors.gray,colors.lightGray)}
|
||||
PushButton{parent=rs_c_9,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=rs_c_10,x=1,y=1,height=10,text="Make sure your relay is either touching the RTU gateway or connected via wired modems. There should be a wired modem on a side of the RTU gateway then one on the device, connected by a cable. The modem on the device needs to be right clicked to connect it (which will turn its border red), at which point the peripheral name will be shown in the chat."}
|
||||
PushButton{parent=rs_c_10,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Tool Functions
|
||||
@ -519,11 +373,9 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
||||
if rsio.is_analog(def.port) then
|
||||
self.rs_cfg_bundled.set_value(false)
|
||||
self.rs_cfg_bundled.disable()
|
||||
self.rs_cfg_advanced.disable()
|
||||
else
|
||||
self.rs_cfg_bundled.enable()
|
||||
self.rs_cfg_bundled.set_value(def.color ~= nil)
|
||||
self.rs_cfg_advanced.enable()
|
||||
end
|
||||
|
||||
local value = 1
|
||||
@ -538,8 +390,7 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
||||
self.rs_cfg_side_l.set_value(tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
|
||||
side.set_value(side_to_idx(def.side))
|
||||
self.rs_cfg_color.set_value(value)
|
||||
self.rs_cfg_inverted.set_value(def.invert or false)
|
||||
rs_pane.set_value(4)
|
||||
rs_pane.set_value(3)
|
||||
end
|
||||
|
||||
local function delete_rs_entry(idx)
|
||||
@ -549,41 +400,33 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
||||
|
||||
-- generate the redstone summary list
|
||||
function tool_ctl.gen_rs_summary()
|
||||
assert(self.rs_cfg_phy ~= false, "tried to generate a summary without a phy set")
|
||||
|
||||
rs_list.remove_all()
|
||||
|
||||
local ini = redstone_subset(ini_cfg.Redstone, self.rs_cfg_phy)
|
||||
local tmp = redstone_subset(tmp_cfg.Redstone, self.rs_cfg_phy)
|
||||
|
||||
local modified = #ini ~= #tmp
|
||||
local modified = #ini_cfg.Redstone ~= #tmp_cfg.Redstone
|
||||
|
||||
for i = 1, #tmp_cfg.Redstone do
|
||||
local def = tmp_cfg.Redstone[i]
|
||||
|
||||
if def.relay == self.rs_cfg_phy then
|
||||
local name = rsio.to_string(def.port)
|
||||
local io_dir = tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
||||
local io_c = tri(rsio.is_digital(def.port), colors.blue, colors.purple)
|
||||
local conn = def.side
|
||||
local unit = util.strval(def.unit or "F")
|
||||
local name = rsio.to_string(def.port)
|
||||
local io_dir = tri(rsio.get_io_mode(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
||||
local conn = def.side
|
||||
local unit = util.strval(def.unit or "F")
|
||||
|
||||
if def.color ~= nil then conn = def.side .. "/" .. rsio.color_name(def.color) end
|
||||
if def.color ~= nil then conn = def.side .. "/" .. rsio.color_name(def.color) end
|
||||
|
||||
local entry = Div{parent=rs_list,height=1}
|
||||
TextBox{parent=entry,x=1,y=1,width=1,text=io_dir,fg_bg=cpair(tri(def.invert,colors.orange,io_c),colors.white)}
|
||||
TextBox{parent=entry,x=2,y=1,width=14,text=name}
|
||||
TextBox{parent=entry,x=16,y=1,width=string.len(conn),text=conn,fg_bg=cpair(colors.gray,colors.white)}
|
||||
TextBox{parent=entry,x=33,y=1,width=1,text=unit,fg_bg=cpair(colors.gray,colors.white)}
|
||||
PushButton{parent=entry,x=35,y=1,min_width=6,height=1,text="EDIT",callback=function()edit_rs_entry(i)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=entry,x=41,y=1,min_width=8,height=1,text="DELETE",callback=function()delete_rs_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
||||
local entry = Div{parent=rs_list,height=1}
|
||||
TextBox{parent=entry,x=1,y=1,width=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
TextBox{parent=entry,x=2,y=1,width=14,text=name}
|
||||
TextBox{parent=entry,x=16,y=1,width=string.len(conn),text=conn,fg_bg=cpair(colors.gray,colors.white)}
|
||||
TextBox{parent=entry,x=33,y=1,width=1,text=unit,fg_bg=cpair(colors.gray,colors.white)}
|
||||
PushButton{parent=entry,x=35,y=1,min_width=6,height=1,text="EDIT",callback=function()edit_rs_entry(i)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=entry,x=41,y=1,min_width=8,height=1,text="DELETE",callback=function()delete_rs_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
if not modified then
|
||||
local a = ini_cfg.Redstone[i]
|
||||
local b = tmp_cfg.Redstone[i]
|
||||
if not modified then
|
||||
local a = ini_cfg.Redstone[i]
|
||||
local b = tmp_cfg.Redstone[i]
|
||||
|
||||
modified = (a.unit ~= b.unit) or (a.port ~= b.port) or (a.relay ~= b.relay) or (a.side ~= b.side) or (a.color ~= b.color) or (a.invert ~= b.invert)
|
||||
end
|
||||
modified = (a.unit ~= b.unit) or (a.port ~= b.port) or (a.side ~= b.side) or (a.color ~= b.color)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -506,7 +506,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
||||
local u, idx = def.unit, def.index
|
||||
|
||||
if util.table_contains(NEEDS_UNIT, mount.type) then
|
||||
if (mount.type == "dynamicValve" or mount.type == "environmentDetector" or mount.type == "environment_detector") and for_facility then
|
||||
if (mount.type == "dynamicValve" or mount.type == "environmentDetector") and for_facility then
|
||||
-- skip
|
||||
elseif not (util.is_int(u) and u > 0 and u < 5) then
|
||||
err = true
|
||||
@ -527,7 +527,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
||||
else index = idx end
|
||||
elseif mount.type == "dynamicValve" then
|
||||
index = 1
|
||||
elseif mount.type == "environmentDetector" or mount.type == "environment_detector" then
|
||||
elseif mount.type == "environmentDetector" then
|
||||
if not (util.is_int(idx) and idx > 0) then
|
||||
err = true
|
||||
else index = idx end
|
||||
@ -646,7 +646,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
||||
local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
||||
alternate = not alternate
|
||||
|
||||
if (string.len(val) > val_max_w) or string.find(val, "\n") then
|
||||
if string.len(val) > val_max_w then
|
||||
local lines = util.strwrap(val, inner_width)
|
||||
height = #lines + 1
|
||||
end
|
||||
|
||||
@ -7,7 +7,6 @@ local ppm = require("scada-common.ppm")
|
||||
local tcd = require("scada-common.tcd")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local check = require("rtu.config.check")
|
||||
local peripherals = require("rtu.config.peripherals")
|
||||
local redstone = require("rtu.config.redstone")
|
||||
local system = require("rtu.config.system")
|
||||
@ -35,9 +34,7 @@ local changes = {
|
||||
{ "v1.7.9", { "ConnTimeout can now have a fractional part" } },
|
||||
{ "v1.7.15", { "Added front panel UI theme", "Added color accessibility modes" } },
|
||||
{ "v1.9.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
||||
{ "v1.10.2", { "Re-organized peripheral configuration UI, resulting in some input fields being re-ordered" } },
|
||||
{ "v1.11.8", { "Added advanced option to invert digital redstone signals" } },
|
||||
{ "v1.12.0", { "Added support for redstone relays" } }
|
||||
{ "v1.10.2", { "Re-organized peripheral configuration UI, resulting in some input fields being re-ordered" } }
|
||||
}
|
||||
|
||||
---@class rtu_configurator
|
||||
@ -77,7 +74,6 @@ local tool_ctl = {
|
||||
gen_summary = nil, ---@type function
|
||||
load_legacy = nil, ---@type function
|
||||
update_peri_list = nil, ---@type function
|
||||
update_relay_list = nil, ---@type function
|
||||
gen_peri_summary = nil, ---@type function
|
||||
gen_rs_summary = nil, ---@type function
|
||||
}
|
||||
@ -119,7 +115,6 @@ local fields = {
|
||||
}
|
||||
|
||||
-- deep copy peripherals defs
|
||||
---@param data rtu_peri_definition[]
|
||||
function tool_ctl.deep_copy_peri(data)
|
||||
local array = {}
|
||||
for _, d in ipairs(data) do table.insert(array, { unit = d.unit, index = d.index, name = d.name }) end
|
||||
@ -127,10 +122,9 @@ function tool_ctl.deep_copy_peri(data)
|
||||
end
|
||||
|
||||
-- deep copy redstone defs
|
||||
---@param data rtu_rs_definition[]
|
||||
function tool_ctl.deep_copy_rs(data)
|
||||
local array = {}
|
||||
for _, d in ipairs(data) do table.insert(array, { unit = d.unit, port = d.port, relay = d.relay, side = d.side, color = d.color, invert = d.invert }) end
|
||||
for _, d in ipairs(data) do table.insert(array, { unit = d.unit, port = d.port, side = d.side, color = d.color }) end
|
||||
return array
|
||||
end
|
||||
|
||||
@ -175,9 +169,8 @@ local function config_view(display)
|
||||
local changelog = Div{parent=root_pane_div,x=1,y=1}
|
||||
local peri_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||
local rs_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||
local check_sys = Div{parent=root_pane_div,x=1,y=1}
|
||||
|
||||
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,spkr_cfg,net_cfg,log_cfg,clr_cfg,summary,changelog,peri_cfg,rs_cfg,check_sys}}
|
||||
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,spkr_cfg,net_cfg,log_cfg,clr_cfg,summary,changelog,peri_cfg,rs_cfg}}
|
||||
|
||||
--#region Main Page
|
||||
|
||||
@ -210,6 +203,7 @@ local function config_view(display)
|
||||
end
|
||||
|
||||
local function show_rs_conns()
|
||||
tool_ctl.gen_rs_summary()
|
||||
main_pane.set_value(9)
|
||||
end
|
||||
|
||||
@ -232,9 +226,8 @@ local function config_view(display)
|
||||
|
||||
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
||||
local start_btn = PushButton{parent=main_page,x=42,y=17,min_width=9,text="Startup",callback=startup,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||
PushButton{parent=main_page,x=39,y=y_start,min_width=12,text="Self-Check",callback=function()main_pane.set_value(10)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||
tool_ctl.color_cfg = PushButton{parent=main_page,x=36,y=y_start+2,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||
PushButton{parent=main_page,x=39,y=y_start+4,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
tool_ctl.color_cfg = PushButton{parent=main_page,x=36,y=y_start,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||
PushButton{parent=main_page,x=39,y=y_start+2,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
if tool_ctl.ask_config then start_btn.disable() end
|
||||
|
||||
@ -290,12 +283,6 @@ local function config_view(display)
|
||||
PushButton{parent=cl,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Self-Check
|
||||
|
||||
check.create(main_pane, settings_cfg, check_sys, style)
|
||||
|
||||
--#endregion
|
||||
end
|
||||
|
||||
-- reset terminal screen
|
||||
@ -330,7 +317,7 @@ function configurator.configure(ask_config)
|
||||
config_view(display)
|
||||
|
||||
while true do
|
||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||
local event, param1, param2, param3 = util.pull_event()
|
||||
|
||||
-- handle event
|
||||
if event == "timer" then
|
||||
@ -343,18 +330,14 @@ function configurator.configure(ask_config)
|
||||
if k_e then display.handle_key(k_e) end
|
||||
elseif event == "paste" then
|
||||
display.handle_paste(param1)
|
||||
elseif event == "modem_message" then
|
||||
check.receive_sv(param1, param2, param3, param4, param5)
|
||||
elseif event == "peripheral_detach" then
|
||||
---@diagnostic disable-next-line: discard-returns
|
||||
ppm.handle_unmount(param1)
|
||||
tool_ctl.update_peri_list()
|
||||
tool_ctl.update_relay_list()
|
||||
elseif event == "peripheral" then
|
||||
---@diagnostic disable-next-line: discard-returns
|
||||
ppm.mount(param1)
|
||||
tool_ctl.update_peri_list()
|
||||
tool_ctl.update_relay_list()
|
||||
end
|
||||
|
||||
if event == "terminate" then return end
|
||||
|
||||
@ -11,14 +11,10 @@ local digital_write = rsio.digital_write
|
||||
|
||||
-- create new redstone device
|
||||
---@nodiscard
|
||||
---@param relay? table optional redstone relay to use instead of the computer's redstone interface
|
||||
---@return rtu_rs_device interface, boolean faulted
|
||||
function redstone_rtu.new(relay)
|
||||
function redstone_rtu.new()
|
||||
local unit = rtu.init_unit()
|
||||
|
||||
-- physical interface to use
|
||||
local phy = relay or rs
|
||||
|
||||
-- get RTU interface
|
||||
local interface = unit.interface()
|
||||
|
||||
@ -34,114 +30,85 @@ function redstone_rtu.new(relay)
|
||||
write_holding_reg = interface.write_holding_reg
|
||||
}
|
||||
|
||||
-- change the phy in use (a relay or rs)
|
||||
---@param new_phy table
|
||||
function public.remount_phy(new_phy) phy = new_phy end
|
||||
|
||||
-- NOTE: for runtime speed, inversion logic results in extra code here but less code when functions are called
|
||||
|
||||
-- link digital input
|
||||
---@param side string
|
||||
---@param color integer
|
||||
---@param invert boolean|nil
|
||||
---@return integer count count of digital inputs
|
||||
function public.link_di(side, color, invert)
|
||||
local f_read ---@type function
|
||||
function public.link_di(side, color)
|
||||
local f_read ---@type function
|
||||
|
||||
if color then
|
||||
if invert then
|
||||
f_read = function () return digital_read(not phy.testBundledInput(side, color)) end
|
||||
else
|
||||
f_read = function () return digital_read(phy.testBundledInput(side, color)) end
|
||||
f_read = function ()
|
||||
return digital_read(rs.testBundledInput(side, color))
|
||||
end
|
||||
else
|
||||
if invert then
|
||||
f_read = function () return digital_read(not phy.getInput(side)) end
|
||||
else
|
||||
f_read = function () return digital_read(phy.getInput(side)) end
|
||||
f_read = function ()
|
||||
return digital_read(rs.getInput(side))
|
||||
end
|
||||
end
|
||||
|
||||
return unit.connect_di(f_read)
|
||||
unit.connect_di(f_read)
|
||||
end
|
||||
|
||||
-- link digital output
|
||||
---@param side string
|
||||
---@param color integer
|
||||
---@param invert boolean|nil
|
||||
---@return integer count count of digital outputs
|
||||
function public.link_do(side, color, invert)
|
||||
local f_read ---@type function
|
||||
local f_write ---@type function
|
||||
function public.link_do(side, color)
|
||||
local f_read ---@type function
|
||||
local f_write ---@type function
|
||||
|
||||
if color then
|
||||
if invert then
|
||||
f_read = function () return digital_read(not colors.test(phy.getBundledOutput(side), color)) end
|
||||
f_read = function ()
|
||||
return digital_read(colors.test(rs.getBundledOutput(side), color))
|
||||
end
|
||||
|
||||
f_write = function (level)
|
||||
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||
local output = phy.getBundledOutput(side)
|
||||
f_write = function (level)
|
||||
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||
local output = rs.getBundledOutput(side)
|
||||
|
||||
-- inverted conditions
|
||||
if digital_write(level) then
|
||||
output = colors.subtract(output, color)
|
||||
else output = colors.combine(output, color) end
|
||||
|
||||
phy.setBundledOutput(side, output)
|
||||
if digital_write(level) then
|
||||
output = colors.combine(output, color)
|
||||
else
|
||||
output = colors.subtract(output, color)
|
||||
end
|
||||
end
|
||||
else
|
||||
f_read = function () return digital_read(colors.test(phy.getBundledOutput(side), color)) end
|
||||
|
||||
f_write = function (level)
|
||||
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||
local output = phy.getBundledOutput(side)
|
||||
|
||||
if digital_write(level) then
|
||||
output = colors.combine(output, color)
|
||||
else output = colors.subtract(output, color) end
|
||||
|
||||
phy.setBundledOutput(side, output)
|
||||
end
|
||||
rs.setBundledOutput(side, output)
|
||||
end
|
||||
end
|
||||
else
|
||||
if invert then
|
||||
f_read = function () return digital_read(not phy.getOutput(side)) end
|
||||
f_read = function ()
|
||||
return digital_read(rs.getOutput(side))
|
||||
end
|
||||
|
||||
f_write = function (level)
|
||||
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||
phy.setOutput(side, not digital_write(level))
|
||||
end
|
||||
end
|
||||
else
|
||||
f_read = function () return digital_read(phy.getOutput(side)) end
|
||||
|
||||
f_write = function (level)
|
||||
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||
phy.setOutput(side, digital_write(level))
|
||||
end
|
||||
f_write = function (level)
|
||||
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||
rs.setOutput(side, digital_write(level))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return unit.connect_coil(f_read, f_write)
|
||||
unit.connect_coil(f_read, f_write)
|
||||
end
|
||||
|
||||
-- link analog input
|
||||
---@param side string
|
||||
---@return integer count count of analog inputs
|
||||
function public.link_ai(side)
|
||||
return unit.connect_input_reg(function () return phy.getAnalogInput(side) end)
|
||||
unit.connect_input_reg(
|
||||
function ()
|
||||
return rs.getAnalogInput(side)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
-- link analog output
|
||||
---@param side string
|
||||
---@return integer count count of analog outputs
|
||||
function public.link_ao(side)
|
||||
return unit.connect_holding_reg(
|
||||
function () return phy.getAnalogOutput(side) end,
|
||||
function (value) phy.setAnalogOutput(side, value) end
|
||||
unit.connect_holding_reg(
|
||||
function ()
|
||||
return rs.getAnalogOutput(side)
|
||||
end,
|
||||
function (value)
|
||||
rs.setAnalogOutput(side, value)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@ -399,41 +399,43 @@ function modbus.new(rtu_dev, use_parallel_read)
|
||||
return public
|
||||
end
|
||||
|
||||
-- create an error reply
|
||||
---@nodiscard
|
||||
---@param packet modbus_frame MODBUS packet frame
|
||||
---@param code MODBUS_EXCODE exception code
|
||||
---@return modbus_packet reply
|
||||
local function excode_reply(packet, code)
|
||||
-- reply back with error flag and exception code
|
||||
local reply = comms.modbus_packet()
|
||||
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
||||
reply.make(packet.txn_id, packet.unit_id, fcode, { code })
|
||||
return reply
|
||||
end
|
||||
|
||||
-- return a SERVER_DEVICE_FAIL error reply
|
||||
---@nodiscard
|
||||
---@param packet modbus_frame MODBUS packet frame
|
||||
---@return modbus_packet reply
|
||||
function modbus.reply__srv_device_fail(packet) return excode_reply(packet, MODBUS_EXCODE.SERVER_DEVICE_FAIL) end
|
||||
|
||||
-- return a SERVER_DEVICE_BUSY error reply
|
||||
---@nodiscard
|
||||
---@param packet modbus_frame MODBUS packet frame
|
||||
---@return modbus_packet reply
|
||||
function modbus.reply__srv_device_busy(packet) return excode_reply(packet, MODBUS_EXCODE.SERVER_DEVICE_BUSY) end
|
||||
function modbus.reply__srv_device_busy(packet)
|
||||
-- reply back with error flag and exception code
|
||||
local reply = comms.modbus_packet()
|
||||
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
||||
local data = { MODBUS_EXCODE.SERVER_DEVICE_BUSY }
|
||||
reply.make(packet.txn_id, packet.unit_id, fcode, data)
|
||||
return reply
|
||||
end
|
||||
|
||||
-- return a NEG_ACKNOWLEDGE error reply
|
||||
---@nodiscard
|
||||
---@param packet modbus_frame MODBUS packet frame
|
||||
---@return modbus_packet reply
|
||||
function modbus.reply__neg_ack(packet) return excode_reply(packet, MODBUS_EXCODE.NEG_ACKNOWLEDGE) end
|
||||
function modbus.reply__neg_ack(packet)
|
||||
-- reply back with error flag and exception code
|
||||
local reply = comms.modbus_packet()
|
||||
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
||||
local data = { MODBUS_EXCODE.NEG_ACKNOWLEDGE }
|
||||
reply.make(packet.txn_id, packet.unit_id, fcode, data)
|
||||
return reply
|
||||
end
|
||||
|
||||
-- return a GATEWAY_PATH_UNAVAILABLE error reply
|
||||
---@nodiscard
|
||||
---@param packet modbus_frame MODBUS packet frame
|
||||
---@return modbus_packet reply
|
||||
function modbus.reply__gw_unavailable(packet) return excode_reply(packet, MODBUS_EXCODE.GATEWAY_PATH_UNAVAILABLE) end
|
||||
function modbus.reply__gw_unavailable(packet)
|
||||
-- reply back with error flag and exception code
|
||||
local reply = comms.modbus_packet()
|
||||
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
||||
local data = { MODBUS_EXCODE.GATEWAY_PATH_UNAVAILABLE }
|
||||
reply.make(packet.txn_id, packet.unit_id, fcode, data)
|
||||
return reply
|
||||
end
|
||||
|
||||
return modbus
|
||||
|
||||
@ -19,8 +19,7 @@ local LED = require("graphics.elements.indicators.LED")
|
||||
local LEDPair = require("graphics.elements.indicators.LEDPair")
|
||||
local RGBLED = require("graphics.elements.indicators.RGBLED")
|
||||
|
||||
local LINK_STATE = types.PANEL_LINK_STATE
|
||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
local LINK_STATE = types.PANEL_LINK_STATE
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
|
||||
@ -36,15 +35,13 @@ local UNIT_TYPE_LABELS = { "UNKNOWN", "REDSTONE", "BOILER", "TURBINE", "DYNAMIC
|
||||
local function init(panel, units)
|
||||
local disabled_fg = style.fp.disabled_fg
|
||||
|
||||
local term_w, term_h = term.getSize()
|
||||
|
||||
TextBox{parent=panel,y=1,text="RTU GATEWAY",alignment=ALIGN.CENTER,fg_bg=style.theme.header}
|
||||
|
||||
--
|
||||
-- system indicators
|
||||
--
|
||||
|
||||
local system = Div{parent=panel,width=14,height=term_h-5,x=2,y=3}
|
||||
local system = Div{parent=panel,width=14,height=18,x=2,y=3}
|
||||
|
||||
local on = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)}
|
||||
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=ind_grn}
|
||||
@ -56,7 +53,7 @@ local function init(panel, units)
|
||||
local modem = LED{parent=system,label="MODEM",colors=ind_grn}
|
||||
|
||||
if not style.colorblind then
|
||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.ind_bkg}}
|
||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,style.ind_bkg}}
|
||||
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
network.register(databus.ps, "link_state", network.update)
|
||||
else
|
||||
@ -103,17 +100,17 @@ local function init(panel, units)
|
||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
||||
TextBox{parent=system,x=9,y=4,width=6,text=comp_id,fg_bg=disabled_fg}
|
||||
|
||||
TextBox{parent=system,y=term_h-5,text="SPEAKERS",width=8,fg_bg=style.fp.text_fg}
|
||||
local speaker_count = DataIndicator{parent=system,x=10,y=term_h-5,label="",format="%3d",value=0,width=3,fg_bg=style.theme.field_box}
|
||||
TextBox{parent=system,x=1,y=14,text="SPEAKERS",width=8,fg_bg=style.fp.text_fg}
|
||||
local speaker_count = DataIndicator{parent=system,x=10,y=14,label="",format="%3d",value=0,width=3,fg_bg=style.theme.field_box}
|
||||
speaker_count.register(databus.ps, "speaker_count", speaker_count.update)
|
||||
|
||||
--
|
||||
-- about label
|
||||
--
|
||||
|
||||
local about = Div{parent=panel,width=15,height=2,y=term_h-1,fg_bg=disabled_fg}
|
||||
local fw_v = TextBox{parent=about,text="FW: v00.00.00"}
|
||||
local comms_v = TextBox{parent=about,text="NT: v00.00.00"}
|
||||
local about = Div{parent=panel,width=15,height=3,x=1,y=18,fg_bg=disabled_fg}
|
||||
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00"}
|
||||
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00"}
|
||||
|
||||
fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
||||
comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
||||
@ -122,53 +119,38 @@ local function init(panel, units)
|
||||
-- unit status list
|
||||
--
|
||||
|
||||
local threads = Div{parent=panel,width=8,height=term_h-3,x=17,y=3}
|
||||
local threads = Div{parent=panel,width=8,height=18,x=17,y=3}
|
||||
|
||||
-- display as many units as we can with 1 line of padding above and below
|
||||
local list_length = math.min(#units, term_h - 3)
|
||||
-- display up to 16 units
|
||||
local list_length = math.min(#units, 16)
|
||||
|
||||
-- show routine statuses
|
||||
for i = 1, list_length do
|
||||
TextBox{parent=threads,x=1,y=i,text=util.sprintf("%02d",i)}
|
||||
local rt_unit = LED{parent=threads,x=4,y=i,label="RT",colors=util.trinary(units[i].type~=RTU_UNIT_TYPE.REDSTONE,ind_grn,cpair(style.ind_bkg,style.ind_bkg))}
|
||||
local rt_unit = LED{parent=threads,x=4,y=i,label="RT",colors=ind_grn}
|
||||
rt_unit.register(databus.ps, "routine__unit_" .. i, rt_unit.update)
|
||||
end
|
||||
|
||||
local unit_hw_statuses = Div{parent=panel,height=term_h-3,x=25,y=3}
|
||||
|
||||
local relay_counter = 0
|
||||
local unit_hw_statuses = Div{parent=panel,height=18,x=25,y=3}
|
||||
|
||||
-- show hardware statuses
|
||||
for i = 1, list_length do
|
||||
local unit = units[i]
|
||||
|
||||
local is_rs = unit.type == RTU_UNIT_TYPE.REDSTONE
|
||||
|
||||
-- hardware status
|
||||
local unit_hw = RGBLED{parent=unit_hw_statuses,y=i,label="",colors={colors.red,colors.orange,colors.yellow,colors.green}}
|
||||
|
||||
unit_hw.register(databus.ps, "unit_hw_" .. i, unit_hw.update)
|
||||
|
||||
-- unit name identifier (type + index)
|
||||
local function get_name()
|
||||
if is_rs then
|
||||
local is_local = unit.name == "redstone_local"
|
||||
relay_counter = relay_counter + util.trinary(is_local, 0, 1)
|
||||
return util.c("REDSTONE", util.trinary(is_local, "", " RELAY " .. relay_counter))
|
||||
else
|
||||
return util.c(UNIT_TYPE_LABELS[unit.type + 1], " ", util.trinary(util.is_int(unit.index), unit.index, ""))
|
||||
end
|
||||
end
|
||||
local function get_name(t) return util.c(UNIT_TYPE_LABELS[t + 1], " ", util.trinary(util.is_int(unit.index), unit.index, "")) end
|
||||
local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=get_name(unit.type),width=15}
|
||||
|
||||
local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=get_name(),width=util.trinary(is_rs,24,15)}
|
||||
|
||||
name_box.register(databus.ps, "unit_type_" .. i, function () name_box.set_value(get_name()) end)
|
||||
name_box.register(databus.ps, "unit_type_" .. i, function (t) name_box.set_value(get_name(t)) end)
|
||||
|
||||
-- assignment (unit # or facility)
|
||||
if unit.reactor then
|
||||
local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor)
|
||||
TextBox{parent=unit_hw_statuses,y=i,x=term_w-32,text=for_unit,fg_bg=disabled_fg}
|
||||
end
|
||||
local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor)
|
||||
TextBox{parent=unit_hw_statuses,y=i,x=19,text=for_unit,fg_bg=disabled_fg}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
61
rtu/rtu.lua
61
rtu/rtu.lua
@ -46,42 +46,36 @@ function rtu.load_config()
|
||||
config.FrontPanelTheme = settings.get("FrontPanelTheme")
|
||||
config.ColorMode = settings.get("ColorMode")
|
||||
|
||||
return rtu.validate_config(config)
|
||||
end
|
||||
|
||||
-- validate an RTU gateway configuration
|
||||
---@param cfg rtu_config
|
||||
function rtu.validate_config(cfg)
|
||||
local cfv = util.new_validator()
|
||||
|
||||
cfv.assert_type_num(cfg.SpeakerVolume)
|
||||
cfv.assert_range(cfg.SpeakerVolume, 0, 3)
|
||||
cfv.assert_type_num(config.SpeakerVolume)
|
||||
cfv.assert_range(config.SpeakerVolume, 0, 3)
|
||||
|
||||
cfv.assert_channel(cfg.SVR_Channel)
|
||||
cfv.assert_channel(cfg.RTU_Channel)
|
||||
cfv.assert_type_num(cfg.ConnTimeout)
|
||||
cfv.assert_min(cfg.ConnTimeout, 2)
|
||||
cfv.assert_type_num(cfg.TrustedRange)
|
||||
cfv.assert_min(cfg.TrustedRange, 0)
|
||||
cfv.assert_type_str(cfg.AuthKey)
|
||||
cfv.assert_channel(config.SVR_Channel)
|
||||
cfv.assert_channel(config.RTU_Channel)
|
||||
cfv.assert_type_num(config.ConnTimeout)
|
||||
cfv.assert_min(config.ConnTimeout, 2)
|
||||
cfv.assert_type_num(config.TrustedRange)
|
||||
cfv.assert_min(config.TrustedRange, 0)
|
||||
cfv.assert_type_str(config.AuthKey)
|
||||
|
||||
if type(cfg.AuthKey) == "string" then
|
||||
local len = string.len(cfg.AuthKey)
|
||||
if type(config.AuthKey) == "string" then
|
||||
local len = string.len(config.AuthKey)
|
||||
cfv.assert(len == 0 or len >= 8)
|
||||
end
|
||||
|
||||
cfv.assert_type_int(cfg.LogMode)
|
||||
cfv.assert_range(cfg.LogMode, 0, 1)
|
||||
cfv.assert_type_str(cfg.LogPath)
|
||||
cfv.assert_type_bool(cfg.LogDebug)
|
||||
cfv.assert_type_int(config.LogMode)
|
||||
cfv.assert_range(config.LogMode, 0, 1)
|
||||
cfv.assert_type_str(config.LogPath)
|
||||
cfv.assert_type_bool(config.LogDebug)
|
||||
|
||||
cfv.assert_type_int(cfg.FrontPanelTheme)
|
||||
cfv.assert_range(cfg.FrontPanelTheme, 1, 2)
|
||||
cfv.assert_type_int(cfg.ColorMode)
|
||||
cfv.assert_range(cfg.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
|
||||
cfv.assert_type_int(config.FrontPanelTheme)
|
||||
cfv.assert_range(config.FrontPanelTheme, 1, 2)
|
||||
cfv.assert_type_int(config.ColorMode)
|
||||
cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
|
||||
|
||||
cfv.assert_type_table(cfg.Peripherals)
|
||||
cfv.assert_type_table(cfg.Redstone)
|
||||
cfv.assert_type_table(config.Peripherals)
|
||||
cfv.assert_type_table(config.Redstone)
|
||||
|
||||
return cfv.valid()
|
||||
end
|
||||
@ -338,7 +332,13 @@ function rtu.comms(version, nic, conn_watchdog)
|
||||
local unit = units[i]
|
||||
|
||||
if unit.type ~= nil then
|
||||
insert(advertisement, { unit.type, unit.index, unit.reactor or -1, unit.rs_conns })
|
||||
local advert = { unit.type, unit.index, unit.reactor }
|
||||
|
||||
if unit.type == RTU_UNIT_TYPE.REDSTONE then
|
||||
insert(advert, unit.device)
|
||||
end
|
||||
|
||||
insert(advertisement, advert)
|
||||
end
|
||||
end
|
||||
|
||||
@ -471,10 +471,9 @@ function rtu.comms(version, nic, conn_watchdog)
|
||||
local unit = units[packet.unit_id]
|
||||
local unit_dbg_tag = " (unit " .. packet.unit_id .. ")"
|
||||
|
||||
if unit.type == RTU_UNIT_TYPE.REDSTONE then
|
||||
if unit.name == "redstone_io" then
|
||||
-- immediately execute redstone RTU requests
|
||||
return_code, reply = unit.modbus_io.handle_packet(packet)
|
||||
|
||||
if not return_code then
|
||||
log.warning("requested MODBUS operation failed" .. unit_dbg_tag)
|
||||
end
|
||||
@ -491,7 +490,7 @@ function rtu.comms(version, nic, conn_watchdog)
|
||||
unit.pkt_queue.push_packet(packet)
|
||||
end
|
||||
else
|
||||
log.warning("requested MODBUS operation failed" .. unit_dbg_tag)
|
||||
log.warning("cannot perform requested MODBUS operation" .. unit_dbg_tag)
|
||||
end
|
||||
end
|
||||
else
|
||||
|
||||
219
rtu/startup.lua
219
rtu/startup.lua
@ -31,7 +31,7 @@ local sna_rtu = require("rtu.dev.sna_rtu")
|
||||
local sps_rtu = require("rtu.dev.sps_rtu")
|
||||
local turbinev_rtu = require("rtu.dev.turbinev_rtu")
|
||||
|
||||
local RTU_VERSION = "v1.12.3"
|
||||
local RTU_VERSION = "v1.11.0"
|
||||
|
||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
local RTU_HW_STATE = databus.RTU_HW_STATE
|
||||
@ -140,36 +140,32 @@ local function main()
|
||||
local rtu_redstone = config.Redstone
|
||||
local rtu_devices = config.Peripherals
|
||||
|
||||
-- get a string representation of a port interface
|
||||
---@param entry rtu_rs_definition
|
||||
---@return string
|
||||
local function entry_iface_name(entry)
|
||||
return util.trinary(entry.color ~= nil, util.c(entry.side, "/", rsio.color_name(entry.color)), entry.side)
|
||||
end
|
||||
|
||||
-- configure RTU gateway based on settings file definitions
|
||||
local function sys_config()
|
||||
--#region Redstone Interfaces
|
||||
|
||||
local rs_rtus = {} ---@type { name: string, hw_state: RTU_HW_STATE, rtu: rtu_rs_device, phy: table, banks: rtu_rs_definition[][] }[]
|
||||
local all_conns = { [0] = {}, {}, {}, {}, {} }
|
||||
-- redstone interfaces
|
||||
local rs_rtus = {} ---@type { rtu: rtu_rs_device, capabilities: IO_PORT[] }[]
|
||||
|
||||
-- go through redstone definitions list
|
||||
for entry_idx = 1, #rtu_redstone do
|
||||
local entry = rtu_redstone[entry_idx]
|
||||
|
||||
local assignment
|
||||
local for_reactor = entry.unit
|
||||
local phy = entry.relay or 0
|
||||
local phy_name = entry.relay or "local"
|
||||
local iface_name = entry_iface_name(entry)
|
||||
local iface_name = util.trinary(entry.color ~= nil, util.c(entry.side, "/", rsio.color_name(entry.color)), entry.side)
|
||||
|
||||
if util.is_int(entry.unit) and entry.unit > 0 and entry.unit < 5 then
|
||||
---@cast for_reactor integer
|
||||
assignment = "reactor unit " .. entry.unit
|
||||
if rs_rtus[for_reactor] == nil then
|
||||
log.debug(util.c("sys_config> allocated redstone RTU for reactor unit ", entry.unit))
|
||||
rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} }
|
||||
end
|
||||
elseif entry.unit == nil then
|
||||
assignment = "facility"
|
||||
for_reactor = 0
|
||||
if rs_rtus[for_reactor] == nil then
|
||||
log.debug(util.c("sys_config> allocated redstone RTU for the facility"))
|
||||
rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} }
|
||||
end
|
||||
else
|
||||
local message = util.c("sys_config> invalid unit assignment at block index #", entry_idx)
|
||||
println(message)
|
||||
@ -177,44 +173,14 @@ local function main()
|
||||
return false
|
||||
end
|
||||
|
||||
-- create the appropriate RTU if it doesn't exist and check relay name validity
|
||||
if entry.relay then
|
||||
if type(entry.relay) ~= "string" then
|
||||
local message = util.c("sys_config> invalid redstone relay '", entry.relay, '"')
|
||||
println(message)
|
||||
log.fatal(message)
|
||||
return false
|
||||
elseif not rs_rtus[entry.relay] then
|
||||
log.debug(util.c("sys_config> allocated relay redstone RTU on interface ", entry.relay))
|
||||
|
||||
local hw_state = RTU_HW_STATE.OK
|
||||
local relay = ppm.get_periph(entry.relay)
|
||||
|
||||
if not relay then
|
||||
hw_state = RTU_HW_STATE.OFFLINE
|
||||
log.warning(util.c("sys_config> redstone relay ", entry.relay, " is not connected"))
|
||||
local _, v_device = ppm.mount_virtual()
|
||||
relay = v_device
|
||||
elseif ppm.get_type(entry.relay) ~= "redstone_relay" then
|
||||
hw_state = RTU_HW_STATE.FAULTED
|
||||
log.warning(util.c("sys_config> redstone relay ", entry.relay, " is not a redstone relay"))
|
||||
end
|
||||
|
||||
rs_rtus[entry.relay] = { name = entry.relay, hw_state = hw_state, rtu = redstone_rtu.new(relay), phy = relay, banks = { [0] = {}, {}, {}, {}, {} } }
|
||||
end
|
||||
elseif rs_rtus[0] == nil then
|
||||
log.debug(util.c("sys_config> allocated local redstone RTU"))
|
||||
rs_rtus[0] = { name = "redstone_local", hw_state = RTU_HW_STATE.OK, rtu = redstone_rtu.new(), phy = rs, banks = { [0] = {}, {}, {}, {}, {} } }
|
||||
end
|
||||
|
||||
-- verify configuration
|
||||
local valid = false
|
||||
if rsio.is_valid_port(entry.port) and rsio.is_valid_side(entry.side) then
|
||||
valid = util.trinary(entry.color == nil, true, rsio.is_color(entry.color))
|
||||
end
|
||||
|
||||
local bank = rs_rtus[phy].banks[for_reactor]
|
||||
local conns = all_conns[for_reactor]
|
||||
local rs_rtu = rs_rtus[for_reactor].rtu
|
||||
local capabilities = rs_rtus[for_reactor].capabilities
|
||||
|
||||
if not valid then
|
||||
local message = util.c("sys_config> invalid redstone definition at block index #", entry_idx)
|
||||
@ -226,105 +192,73 @@ local function main()
|
||||
local mode = rsio.get_io_mode(entry.port)
|
||||
if mode == rsio.IO_MODE.DIGITAL_IN then
|
||||
-- can't have duplicate inputs
|
||||
if util.table_contains(conns, entry.port) then
|
||||
local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name, " @ ", phy_name)
|
||||
if util.table_contains(capabilities, entry.port) then
|
||||
local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name)
|
||||
println(message)
|
||||
log.warning(message)
|
||||
else
|
||||
table.insert(bank, entry)
|
||||
rs_rtu.link_di(entry.side, entry.color)
|
||||
end
|
||||
elseif mode == rsio.IO_MODE.DIGITAL_OUT then
|
||||
rs_rtu.link_do(entry.side, entry.color)
|
||||
elseif mode == rsio.IO_MODE.ANALOG_IN then
|
||||
-- can't have duplicate inputs
|
||||
if util.table_contains(conns, entry.port) then
|
||||
local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name, " @ ", phy_name)
|
||||
if util.table_contains(capabilities, entry.port) then
|
||||
local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name)
|
||||
println(message)
|
||||
log.warning(message)
|
||||
else
|
||||
table.insert(bank, entry)
|
||||
rs_rtu.link_ai(entry.side)
|
||||
end
|
||||
elseif (mode == rsio.IO_MODE.DIGITAL_OUT) or (mode == rsio.IO_MODE.ANALOG_OUT) then
|
||||
table.insert(bank, entry)
|
||||
elseif mode == rsio.IO_MODE.ANALOG_OUT then
|
||||
rs_rtu.link_ao(entry.side)
|
||||
else
|
||||
-- should be unreachable code, we already validated ports
|
||||
log.fatal("sys_config> failed to identify IO mode at block index #" .. entry_idx)
|
||||
log.error("sys_config> fell through if chain attempting to identify IO mode at block index #" .. entry_idx, true)
|
||||
println("sys_config> encountered a software error, check logs")
|
||||
return false
|
||||
end
|
||||
|
||||
table.insert(conns, entry.port)
|
||||
table.insert(capabilities, entry.port)
|
||||
|
||||
log.debug(util.c("sys_config> banked redstone ", #conns, ": ", rsio.to_string(entry.port), " (", iface_name, " @ ", phy_name, ") for ", assignment))
|
||||
log.debug(util.c("sys_config> linked redstone ", #capabilities, ": ", rsio.to_string(entry.port), " (", iface_name, ") for ", assignment))
|
||||
end
|
||||
end
|
||||
|
||||
-- create unit entries for redstone RTUs
|
||||
for _, def in pairs(rs_rtus) do
|
||||
local rtu_conns = { [0] = {}, {}, {}, {}, {} }
|
||||
|
||||
-- connect the IO banks
|
||||
for for_reactor = 0, #def.banks do
|
||||
local bank = def.banks[for_reactor]
|
||||
local conns = rtu_conns[for_reactor]
|
||||
local assign = util.trinary(for_reactor > 0, "reactor unit " .. for_reactor, "the facility")
|
||||
|
||||
-- link redstone to the RTU
|
||||
for i = 1, #bank do
|
||||
local conn = bank[i]
|
||||
local phy_name = conn.relay or "local"
|
||||
|
||||
local mode = rsio.get_io_mode(conn.port)
|
||||
if mode == rsio.IO_MODE.DIGITAL_IN then
|
||||
def.rtu.link_di(conn.side, conn.color, conn.invert)
|
||||
elseif mode == rsio.IO_MODE.DIGITAL_OUT then
|
||||
def.rtu.link_do(conn.side, conn.color, conn.invert)
|
||||
elseif mode == rsio.IO_MODE.ANALOG_IN then
|
||||
def.rtu.link_ai(conn.side)
|
||||
elseif mode == rsio.IO_MODE.ANALOG_OUT then
|
||||
def.rtu.link_ao(conn.side)
|
||||
else
|
||||
log.fatal(util.c("sys_config> failed to identify IO mode of ", rsio.to_string(conn.port), " (", entry_iface_name(conn), " @ ", phy_name, ") for ", assign))
|
||||
println("sys_config> encountered a software error, check logs")
|
||||
return false
|
||||
end
|
||||
|
||||
table.insert(conns, conn.port)
|
||||
|
||||
log.debug(util.c("sys_config> linked redstone ", for_reactor, ".", #conns, ": ", rsio.to_string(conn.port), " (", entry_iface_name(conn), ")", " @ ", phy_name, ") for ", assign))
|
||||
end
|
||||
end
|
||||
|
||||
---@type rtu_registry_entry
|
||||
for for_reactor, def in pairs(rs_rtus) do
|
||||
---@class rtu_registry_entry
|
||||
local unit = {
|
||||
uid = 0,
|
||||
name = def.name,
|
||||
type = RTU_UNIT_TYPE.REDSTONE,
|
||||
index = false,
|
||||
reactor = nil,
|
||||
device = def.phy,
|
||||
rs_conns = rtu_conns,
|
||||
is_multiblock = false,
|
||||
formed = nil,
|
||||
hw_state = def.hw_state,
|
||||
rtu = def.rtu,
|
||||
uid = 0, ---@type integer
|
||||
name = "redstone_io", ---@type string
|
||||
type = RTU_UNIT_TYPE.REDSTONE, ---@type RTU_UNIT_TYPE
|
||||
index = false, ---@type integer|false
|
||||
reactor = for_reactor, ---@type integer
|
||||
device = def.capabilities, ---@type IO_PORT[] use device field for redstone ports
|
||||
is_multiblock = false, ---@type boolean
|
||||
formed = nil, ---@type boolean|nil
|
||||
hw_state = RTU_HW_STATE.OK, ---@type RTU_HW_STATE
|
||||
rtu = def.rtu, ---@type rtu_device|rtu_rs_device
|
||||
modbus_io = modbus.new(def.rtu, false),
|
||||
pkt_queue = nil,
|
||||
thread = nil
|
||||
pkt_queue = nil, ---@type mqueue|nil
|
||||
thread = nil ---@type parallel_thread|nil
|
||||
}
|
||||
|
||||
table.insert(units, unit)
|
||||
|
||||
local type = util.trinary(def.phy == rs, "redstone", "redstone_relay")
|
||||
local for_message = "facility"
|
||||
if util.is_int(for_reactor) then
|
||||
for_message = util.c("reactor unit ", for_reactor)
|
||||
end
|
||||
|
||||
log.info(util.c("sys_config> initialized RTU unit #", #units, ": ", unit.name, " (", type, ")"))
|
||||
log.info(util.c("sys_config> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for ", for_message))
|
||||
|
||||
unit.uid = #units
|
||||
|
||||
databus.tx_unit_hw_status(unit.uid, unit.hw_state)
|
||||
end
|
||||
|
||||
--#endregion
|
||||
--#region Mounted Peripherals
|
||||
|
||||
-- mounted peripherals
|
||||
for i = 1, #rtu_devices do
|
||||
local entry = rtu_devices[i] ---@type rtu_peri_definition
|
||||
local name = entry.name
|
||||
@ -440,8 +374,8 @@ local function main()
|
||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||
log.warning(util.c("sys_config> failed to check if '", name, "' is a formed dynamic tank multiblock"))
|
||||
end
|
||||
elseif type == "inductionPort" or type == "reinforcedInductionPort" then
|
||||
-- induction matrix multiblock (normal or reinforced)
|
||||
elseif type == "inductionPort" then
|
||||
-- induction matrix multiblock
|
||||
if not validate_assign(true) then return false end
|
||||
|
||||
rtu_type = RTU_UNIT_TYPE.IMATRIX
|
||||
@ -472,7 +406,7 @@ local function main()
|
||||
|
||||
rtu_type = RTU_UNIT_TYPE.SNA
|
||||
rtu_iface, faulted = sna_rtu.new(device)
|
||||
elseif type == "environmentDetector" or type == "environment_detector" then
|
||||
elseif type == "environmentDetector" then
|
||||
-- advanced peripherals environment detector
|
||||
if not validate_index(1) then return false end
|
||||
if not validate_assign(entry.unit == nil) then return false end
|
||||
@ -505,20 +439,19 @@ local function main()
|
||||
|
||||
---@class rtu_registry_entry
|
||||
local rtu_unit = {
|
||||
uid = 0, ---@type integer RTU unit ID
|
||||
name = name, ---@type string unit name
|
||||
type = rtu_type, ---@type RTU_UNIT_TYPE unit type
|
||||
index = index or false, ---@type integer|false device index
|
||||
reactor = for_reactor, ---@type integer|nil unit/facility assignment
|
||||
device = device, ---@type table peripheral reference
|
||||
rs_conns = nil, ---@type IO_PORT[][]|nil available redstone connections
|
||||
is_multiblock = is_multiblock, ---@type boolean if this is for a multiblock peripheral
|
||||
formed = formed, ---@type boolean|nil if this peripheral is currently formed
|
||||
hw_state = RTU_HW_STATE.OFFLINE, ---@type RTU_HW_STATE hardware device status
|
||||
rtu = rtu_iface, ---@type rtu_device|rtu_rs_device RTU hardware interface
|
||||
modbus_io = modbus.new(rtu_iface, true), ---@type modbus MODBUS interface
|
||||
pkt_queue = mqueue.new(), ---@type mqueue|nil packet queue
|
||||
thread = nil ---@type parallel_thread|nil associated RTU thread
|
||||
uid = 0, ---@type integer
|
||||
name = name, ---@type string
|
||||
type = rtu_type, ---@type RTU_UNIT_TYPE
|
||||
index = index or false, ---@type integer|false
|
||||
reactor = for_reactor, ---@type integer
|
||||
device = device, ---@type table peripheral reference
|
||||
is_multiblock = is_multiblock, ---@type boolean
|
||||
formed = formed, ---@type boolean|nil
|
||||
hw_state = RTU_HW_STATE.OFFLINE, ---@type RTU_HW_STATE
|
||||
rtu = rtu_iface, ---@type rtu_device|rtu_rs_device
|
||||
modbus_io = modbus.new(rtu_iface, true),
|
||||
pkt_queue = mqueue.new(), ---@type mqueue|nil
|
||||
thread = nil ---@type parallel_thread|nil
|
||||
}
|
||||
|
||||
rtu_unit.thread = threads.thread__unit_comms(__shared_memory, rtu_unit)
|
||||
@ -552,8 +485,6 @@ local function main()
|
||||
databus.tx_unit_hw_status(rtu_unit.uid, rtu_unit.hw_state)
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
@ -564,6 +495,17 @@ local function main()
|
||||
log.debug("boot> running sys_config()")
|
||||
|
||||
if sys_config() then
|
||||
-- start UI
|
||||
local message
|
||||
rtu_state.fp_ok, message = renderer.try_start_ui(units, config.FrontPanelTheme, config.ColorMode)
|
||||
|
||||
if not rtu_state.fp_ok then
|
||||
println_ts(util.c("UI error: ", message))
|
||||
println("startup> running without front panel")
|
||||
log.error(util.c("front panel GUI render failed with error ", message))
|
||||
log.info("startup> running in headless mode without front panel")
|
||||
end
|
||||
|
||||
-- check modem
|
||||
if smem_dev.modem == nil then
|
||||
println("startup> wireless modem not found")
|
||||
@ -585,17 +527,6 @@ local function main()
|
||||
|
||||
databus.tx_hw_spkr_count(#smem_dev.sounders)
|
||||
|
||||
-- start UI
|
||||
local message
|
||||
rtu_state.fp_ok, message = renderer.try_start_ui(units, config.FrontPanelTheme, config.ColorMode)
|
||||
|
||||
if not rtu_state.fp_ok then
|
||||
println_ts(util.c("UI error: ", message))
|
||||
println("startup> running without front panel")
|
||||
log.error(util.c("front panel GUI render failed with error ", message))
|
||||
log.info("startup> running in headless mode without front panel")
|
||||
end
|
||||
|
||||
-- start connection watchdog
|
||||
smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout)
|
||||
log.debug("startup> conn watchdog started")
|
||||
@ -622,7 +553,7 @@ local function main()
|
||||
-- run threads
|
||||
parallel.waitForAll(table.unpack(_threads))
|
||||
else
|
||||
println("system initialization failed, exiting...")
|
||||
println("configuration failed, exiting...")
|
||||
end
|
||||
|
||||
renderer.close_ui()
|
||||
|
||||
@ -74,7 +74,7 @@ local function handle_unit_mount(smem, println_ts, iface, type, device, unit)
|
||||
end
|
||||
|
||||
unit.type = RTU_UNIT_TYPE.DYNAMIC_VALVE
|
||||
elseif type == "inductionPort" or type == "reinforcedInductionPort" then
|
||||
elseif type == "inductionPort" then
|
||||
-- induction matrix multiblock
|
||||
if unit.reactor ~= 0 then fail(util.c("induction matrix '", unit.name, "' cannot init, not assigned to facility")) end
|
||||
|
||||
@ -89,7 +89,7 @@ local function handle_unit_mount(smem, println_ts, iface, type, device, unit)
|
||||
if unit.reactor < 1 or unit.reactor > 4 then fail(util.c("SNA '", unit.name, "' cannot init, not assigned to a valid unit")) end
|
||||
|
||||
unit.type = RTU_UNIT_TYPE.SNA
|
||||
elseif type == "environmentDetector" or type == "environment_detector" then
|
||||
elseif type == "environmentDetector" then
|
||||
-- advanced peripherals environment detector
|
||||
if unit.reactor < 0 or unit.reactor > 4 then fail(util.c("environment detector '", unit.name, "' cannot init, no valid assignment provided")) end
|
||||
if (unit.index == false) or unit.index < 1 then fail(util.c("environment detector '", unit.name, "' cannot init, invalid index provided")) end
|
||||
@ -132,8 +132,6 @@ local function handle_unit_mount(smem, println_ts, iface, type, device, unit)
|
||||
unit.rtu, faulted = sna_rtu.new(device)
|
||||
elseif unit.type == RTU_UNIT_TYPE.ENV_DETECTOR then
|
||||
unit.rtu, faulted = envd_rtu.new(device)
|
||||
elseif unit.type == RTU_UNIT_TYPE.REDSTONE then
|
||||
unit.rtu.remount_phy(device)
|
||||
else
|
||||
unknown = true
|
||||
log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true)
|
||||
|
||||
@ -17,8 +17,8 @@ local max_distance = nil
|
||||
local comms = {}
|
||||
|
||||
-- protocol/data versions (protocol/data independent changes tracked by util.lua version)
|
||||
comms.version = "3.0.8"
|
||||
comms.api_version = "0.0.10"
|
||||
comms.version = "3.0.4"
|
||||
comms.api_version = "0.0.9"
|
||||
|
||||
---@enum PROTOCOL
|
||||
local PROTOCOL = {
|
||||
@ -52,29 +52,26 @@ local MGMT_TYPE = {
|
||||
RTU_ADVERT = 3, -- RTU capability advertisement
|
||||
RTU_DEV_REMOUNT = 4, -- RTU multiblock possbily changed (formed, unformed) due to PPM remount
|
||||
RTU_TONE_ALARM = 5, -- instruct RTUs to play specified alarm tones
|
||||
DIAG_TONE_GET = 6, -- (API) diagnostic: get alarm tones
|
||||
DIAG_TONE_SET = 7, -- (API) diagnostic: set alarm tones
|
||||
DIAG_ALARM_SET = 8, -- (API) diagnostic: set alarm to simulate audio for
|
||||
INFO_LIST_CMP = 9 -- (API) info: list all computers on the network
|
||||
DIAG_TONE_GET = 6, -- diagnostic: get alarm tones
|
||||
DIAG_TONE_SET = 7, -- diagnostic: set alarm tones
|
||||
DIAG_ALARM_SET = 8 -- diagnostic: set alarm to simulate audio for
|
||||
}
|
||||
|
||||
---@enum CRDN_TYPE
|
||||
local CRDN_TYPE = {
|
||||
INITIAL_BUILDS = 0, -- initial, complete builds packet to the coordinator
|
||||
PROCESS_READY = 1, -- process init is complete + last set of info for supervisor startup recovery
|
||||
FAC_BUILDS = 2, -- facility RTU builds
|
||||
FAC_STATUS = 3, -- state of facility and facility devices
|
||||
FAC_CMD = 4, -- faility command
|
||||
UNIT_BUILDS = 5, -- build of each reactor unit (reactor + RTUs)
|
||||
UNIT_STATUSES = 6, -- state of each of the reactor units
|
||||
UNIT_CMD = 7, -- command a reactor unit
|
||||
API_GET_FAC = 8, -- API: get the facility general data
|
||||
API_GET_FAC_DTL = 9, -- API: get (detailed) data for the facility app
|
||||
API_GET_UNIT = 10, -- API: get reactor unit data
|
||||
API_GET_CTRL = 11, -- API: get data for the control app
|
||||
API_GET_PROC = 12, -- API: get data for the process app
|
||||
API_GET_WASTE = 13, -- API: get data for the waste app
|
||||
API_GET_RAD = 14 -- API: get data for the radiation monitor app
|
||||
FAC_BUILDS = 1, -- facility RTU builds
|
||||
FAC_STATUS = 2, -- state of facility and facility devices
|
||||
FAC_CMD = 3, -- faility command
|
||||
UNIT_BUILDS = 4, -- build of each reactor unit (reactor + RTUs)
|
||||
UNIT_STATUSES = 5, -- state of each of the reactor units
|
||||
UNIT_CMD = 6, -- command a reactor unit
|
||||
API_GET_FAC = 7, -- API: get the facility general data
|
||||
API_GET_FAC_DTL = 8, -- API: get (detailed) data for the facility app
|
||||
API_GET_UNIT = 9, -- API: get reactor unit data
|
||||
API_GET_CTRL = 10, -- API: get data for the control app
|
||||
API_GET_PROC = 11, -- API: get data for the process app
|
||||
API_GET_WASTE = 12 -- API: get data for the waste app
|
||||
}
|
||||
|
||||
---@enum ESTABLISH_ACK
|
||||
|
||||
@ -72,8 +72,6 @@ local rs = {}
|
||||
|
||||
rs.IMATRIX_CHARGE_LOW = 0.05 -- activation threshold (less than) for F_MATRIX_LOW
|
||||
rs.IMATRIX_CHARGE_HIGH = 0.95 -- activation threshold (greater than) for F_MATRIX_HIGH
|
||||
rs.AUX_COOL_ENABLE = 0.60 -- actiation threshold (less than or equal) for U_AUX_COOL
|
||||
rs.AUX_COOL_DISABLE = 1.00 -- deactivation threshold (greater than or equal) for U_AUX_COOL
|
||||
|
||||
constants.RS_THRESHOLDS = rs
|
||||
|
||||
@ -88,7 +86,6 @@ constants.FLOW_STABILITY_DELAY_MS = 10000
|
||||
-- - background radiation 0.0000001 Sv/h (99.99 nSv/h)
|
||||
-- - "green tint" radiation 0.00001 Sv/h (10 uSv/h)
|
||||
-- - damaging radiation 0.00006 Sv/h (60 uSv/h)
|
||||
|
||||
constants.LOW_RADIATION = 0.00001
|
||||
constants.HAZARD_RADIATION = 0.00006
|
||||
constants.HIGH_RADIATION = 0.001
|
||||
@ -96,11 +93,6 @@ constants.VERY_HIGH_RADIATION = 0.1
|
||||
constants.SEVERE_RADIATION = 8.0
|
||||
constants.EXTREME_RADIATION = 100.0
|
||||
|
||||
-- nominal RTT is ping (0ms to 10ms usually) + 150ms for SV main loop tick
|
||||
|
||||
constants.WARN_RTT = 300 -- 2x as long as expected w/ 0 ping
|
||||
constants.HIGH_RTT = 500 -- 3.33x as long as expected w/ 0 ping
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Mekanism Configuration Constants
|
||||
|
||||
@ -2,9 +2,6 @@
|
||||
-- Crash Handler
|
||||
--
|
||||
|
||||
---@diagnostic disable-next-line: undefined-global
|
||||
local _is_pocket_env = pocket -- luacheck: ignore pocket
|
||||
|
||||
local comms = require("scada-common.comms")
|
||||
local log = require("scada-common.log")
|
||||
local util = require("scada-common.util")
|
||||
@ -39,74 +36,6 @@ local function log_versions(log_msg)
|
||||
if has_lockbox then log_msg(util.c("LOCKBOX VERSION: ", lockbox.version)) end
|
||||
end
|
||||
|
||||
-- render the standard computer crash screen
|
||||
---@param exit function callback on exit button press
|
||||
---@return DisplayBox display
|
||||
local function draw_computer_crash(exit)
|
||||
local DisplayBox = require("graphics.elements.DisplayBox")
|
||||
local Div = require("graphics.elements.Div")
|
||||
local Rectangle = require("graphics.elements.Rectangle")
|
||||
local TextBox = require("graphics.elements.TextBox")
|
||||
local PushButton = require("graphics.elements.controls.PushButton")
|
||||
|
||||
local display = DisplayBox{window=term.current(),fg_bg=core.cpair(colors.white,colors.lightGray)}
|
||||
|
||||
local warning = Div{parent=display,x=2,y=2}
|
||||
TextBox{parent=warning,x=7,text="\x90\n \x90\n \x90\n \x90\n \x90",fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||
TextBox{parent=warning,x=5,y=1,text="\x9f ",width=2,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||
TextBox{parent=warning,x=4,text="\x9f ",width=4,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||
TextBox{parent=warning,x=3,text="\x9f ",width=6,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||
TextBox{parent=warning,x=2,text="\x9f ",width=8,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||
TextBox{parent=warning,text="\x9f ",width=10,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||
TextBox{parent=warning,text="\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f",width=11,fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||
TextBox{parent=warning,x=6,y=3,text=" \n \x83",width=1,fg_bg=core.cpair(colors.yellow,colors.white)}
|
||||
|
||||
TextBox{parent=display,x=13,y=2,text="Critical Software Fault Encountered",alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.yellow,colors._INHERIT)}
|
||||
TextBox{parent=display,x=15,y=4,text="Please consider reporting this on the cc-mek-scada Discord or GitHub.",width=36,alignment=core.ALIGN.CENTER}
|
||||
TextBox{parent=display,x=14,y=7,text="refer to the log file for more info",alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.gray,colors._INHERIT)}
|
||||
|
||||
local box = Rectangle{parent=display,x=2,y=9,width=display.get_width()-2,height=8,border=core.border(1,colors.gray,true),thin=true,fg_bg=core.cpair(colors.black,colors.white)}
|
||||
TextBox{parent=box,text=err}
|
||||
|
||||
PushButton{parent=display,x=23,y=18,text=" Exit ",callback=exit,active_fg_bg=core.cpair(colors.white,colors.gray),fg_bg=core.cpair(colors.black,colors.red)}
|
||||
|
||||
return display
|
||||
end
|
||||
|
||||
-- render the pocket crash screen
|
||||
---@param exit function callback on exit button press
|
||||
---@return DisplayBox display
|
||||
local function draw_pocket_crash(exit)
|
||||
local DisplayBox = require("graphics.elements.DisplayBox")
|
||||
local Div = require("graphics.elements.Div")
|
||||
local Rectangle = require("graphics.elements.Rectangle")
|
||||
local TextBox = require("graphics.elements.TextBox")
|
||||
local PushButton = require("graphics.elements.controls.PushButton")
|
||||
|
||||
local display = DisplayBox{window=term.current(),fg_bg=core.cpair(colors.white,colors.lightGray)}
|
||||
|
||||
local warning = Div{parent=display,x=2,y=1}
|
||||
TextBox{parent=warning,x=4,y=1,text="\x90",width=1,fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||
TextBox{parent=warning,x=3,text="\x81 ",width=2,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||
TextBox{parent=warning,x=5,y=2,text="\x94",width=1,fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||
TextBox{parent=warning,x=2,text="\x81 ",width=4,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||
TextBox{parent=warning,x=6,y=3,text="\x94",width=1,fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||
TextBox{parent=warning,text="\x8e\x8f\x8f\x8e\x8f\x8f\x84",width=7,fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||
TextBox{parent=warning,x=4,y=2,text="\x90",width=1,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||
TextBox{parent=warning,x=4,y=3,text="\x85",width=1,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||
|
||||
TextBox{parent=display,x=10,y=2,text=" Critical Software Fault",width=16,alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.yellow,colors._INHERIT)}
|
||||
TextBox{parent=display,x=2,y=5,text="Consider reporting this on the cc-mek-scada Discord or GitHub.",width=36,alignment=core.ALIGN.CENTER}
|
||||
|
||||
local box = Rectangle{parent=display,y=9,width=display.get_width(),height=8,fg_bg=core.cpair(colors.black,colors.white)}
|
||||
TextBox{parent=box,text=err}
|
||||
|
||||
PushButton{parent=display,x=11,y=18,text=" Exit ",callback=exit,active_fg_bg=core.cpair(colors.white,colors.gray),fg_bg=core.cpair(colors.black,colors.red)}
|
||||
TextBox{parent=display,x=2,y=20,text="see logs for details",width=24,alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.gray,colors._INHERIT)}
|
||||
|
||||
return display
|
||||
end
|
||||
|
||||
-- when running with debug logs, log the useful information that the crash handler knows
|
||||
function crash.dbg_log_env() log_versions(log.debug) end
|
||||
|
||||
@ -125,41 +54,9 @@ end
|
||||
|
||||
-- final error print on failed xpcall, app exits here
|
||||
function crash.exit()
|
||||
local handled, run = false, true
|
||||
local display ---@type DisplayBox
|
||||
|
||||
-- special graphical crash screen
|
||||
if has_graphics then
|
||||
handled, display = pcall(util.trinary(_is_pocket_env, draw_pocket_crash, draw_computer_crash), function () run = false end)
|
||||
|
||||
-- event loop
|
||||
while display and run do
|
||||
local event, param1, param2, param3 = util.pull_event()
|
||||
|
||||
-- handle event
|
||||
if event == "mouse_click" or event == "mouse_up" or event == "double_click" then
|
||||
local mouse = core.events.new_mouse_event(event, param1, param2, param3)
|
||||
if mouse then display.handle_mouse(mouse) end
|
||||
elseif event == "terminate" then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
display.delete()
|
||||
|
||||
term.setCursorPos(1, 1)
|
||||
term.setTextColor(colors.white)
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.clear()
|
||||
end
|
||||
|
||||
log.close()
|
||||
|
||||
-- default text failure message
|
||||
if not handled then
|
||||
util.println("fatal error occured in main application:")
|
||||
error(err, 0)
|
||||
end
|
||||
util.println("fatal error occured in main application:")
|
||||
error(err, 0)
|
||||
end
|
||||
|
||||
return crash
|
||||
|
||||
@ -453,7 +453,7 @@ function ppm.get_fission_reactor() return ppm.get_device("fissionReactorLogicAda
|
||||
---@return Modem|nil modem function table
|
||||
function ppm.get_wireless_modem()
|
||||
local w_modem = nil
|
||||
local emulated_env = true
|
||||
local emulated_env = periphemu ~= nil
|
||||
|
||||
for _, device in pairs(ppm_sys.mounts) do
|
||||
if device.type == "modem" and (emulated_env or device.dev.isWireless()) then
|
||||
|
||||
@ -53,12 +53,12 @@ function psil.create()
|
||||
if ic[key] == nil then alloc(key) end
|
||||
|
||||
if ic[key].value ~= value then
|
||||
ic[key].value = value
|
||||
|
||||
for i = 1, #ic[key].subscribers do
|
||||
ic[key].subscribers[i].notify(value)
|
||||
end
|
||||
end
|
||||
|
||||
ic[key].value = value
|
||||
end
|
||||
|
||||
-- publish a toggled boolean value to a given key, passing it to all subscribers if it has changed<br>
|
||||
|
||||
@ -78,7 +78,6 @@ local IO_PORT = {
|
||||
-- unit outputs
|
||||
U_ALARM = 25, -- active high, unit alarm
|
||||
U_EMER_COOL = 26, -- active low, emergency coolant control
|
||||
U_AUX_COOL = 30, -- active low, auxiliary coolant control
|
||||
|
||||
-- analog outputs --
|
||||
|
||||
@ -91,8 +90,8 @@ rsio.IO_DIR = IO_DIR
|
||||
rsio.IO_MODE = IO_MODE
|
||||
rsio.IO = IO_PORT
|
||||
|
||||
rsio.NUM_PORTS = 30
|
||||
rsio.NUM_DIG_PORTS = 29
|
||||
rsio.NUM_PORTS = 29
|
||||
rsio.NUM_DIG_PORTS = 28
|
||||
rsio.NUM_ANA_PORTS = 1
|
||||
|
||||
-- self checks
|
||||
@ -150,7 +149,6 @@ local MODES = {
|
||||
[IO.R_PLC_TIMEOUT] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.U_ALARM] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.U_EMER_COOL] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.U_AUX_COOL] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.F_MATRIX_CHG] = IO_MODE.ANALOG_OUT
|
||||
}
|
||||
|
||||
@ -210,11 +208,10 @@ local RS_DIO_MAP = {
|
||||
[IO.R_PLC_TIMEOUT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
|
||||
[IO.U_ALARM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.U_EMER_COOL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
||||
[IO.U_AUX_COOL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }
|
||||
[IO.U_EMER_COOL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }
|
||||
}
|
||||
|
||||
assert(rsio.NUM_DIG_PORTS == util.table_len(RS_DIO_MAP), "RS_DIO_MAP length incorrect")
|
||||
assert(rsio.NUM_DIG_PORTS == #RS_DIO_MAP, "RS_DIO_MAP length incorrect")
|
||||
|
||||
-- get the I/O direction of a port
|
||||
---@nodiscard
|
||||
|
||||
@ -125,7 +125,7 @@ function types.new_zero_coordinate() return { x = 0, y = 0, z = 0 } end
|
||||
---@field type RTU_UNIT_TYPE
|
||||
---@field index integer|false
|
||||
---@field reactor integer
|
||||
---@field rs_conns IO_PORT[][]|nil
|
||||
---@field rsio IO_PORT[]|nil
|
||||
|
||||
-- create a new reactor database
|
||||
---@nodiscard
|
||||
@ -465,8 +465,7 @@ types.ALARM = {
|
||||
ReactorHighWaste = 9,
|
||||
RPSTransient = 10,
|
||||
RCSTransient = 11,
|
||||
TurbineTrip = 12,
|
||||
FacilityRadiation = 13
|
||||
TurbineTrip = 12
|
||||
}
|
||||
|
||||
types.ALARM_NAMES = {
|
||||
@ -481,8 +480,7 @@ types.ALARM_NAMES = {
|
||||
"ReactorHighWaste",
|
||||
"RPSTransient",
|
||||
"RCSTransient",
|
||||
"TurbineTrip",
|
||||
"FacilityRadiation"
|
||||
"TurbineTrip"
|
||||
}
|
||||
|
||||
---@enum ALARM_PRIORITY
|
||||
|
||||
@ -24,7 +24,7 @@ local t_pack = table.pack
|
||||
local util = {}
|
||||
|
||||
-- scada-common version
|
||||
util.version = "1.5.4"
|
||||
util.version = "1.4.10"
|
||||
|
||||
util.TICK_TIME_S = 0.05
|
||||
util.TICK_TIME_MS = 50
|
||||
|
||||
@ -1,137 +0,0 @@
|
||||
local log = require("scada-common.log")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local ALARM_STATE = types.ALARM_STATE
|
||||
|
||||
---@class alarm_def
|
||||
---@field state ALARM_INT_STATE internal alarm state
|
||||
---@field trip_time integer time (ms) when first tripped
|
||||
---@field hold_time integer time (s) to hold before tripping
|
||||
---@field id ALARM alarm ID
|
||||
---@field tier integer alarm urgency tier (0 = highest)
|
||||
|
||||
local AISTATE_NAMES = {
|
||||
"INACTIVE",
|
||||
"TRIPPING",
|
||||
"TRIPPED",
|
||||
"ACKED",
|
||||
"RING_BACK",
|
||||
"RING_BACK_TRIPPING"
|
||||
}
|
||||
|
||||
---@enum ALARM_INT_STATE
|
||||
local AISTATE = {
|
||||
INACTIVE = 1,
|
||||
TRIPPING = 2,
|
||||
TRIPPED = 3,
|
||||
ACKED = 4,
|
||||
RING_BACK = 5,
|
||||
RING_BACK_TRIPPING = 6
|
||||
}
|
||||
|
||||
local alarm_ctl = {}
|
||||
|
||||
alarm_ctl.AISTATE = AISTATE
|
||||
alarm_ctl.AISTATE_NAMES = AISTATE_NAMES
|
||||
|
||||
-- update an alarm state based on its current status and if it is tripped
|
||||
---@param caller_tag string tag to use in log messages
|
||||
---@param alarm_states { [ALARM]: ALARM_STATE } unit instance
|
||||
---@param tripped boolean if the alarm condition is sti ll active
|
||||
---@param alarm alarm_def alarm table
|
||||
---@param no_ring_back boolean? true to skip the ring back state, returning to inactive instead
|
||||
---@return boolean new_trip if the alarm just changed to being tripped
|
||||
function alarm_ctl.update_alarm_state(caller_tag, alarm_states, tripped, alarm, no_ring_back)
|
||||
local int_state = alarm.state
|
||||
local ext_state = alarm_states[alarm.id]
|
||||
|
||||
-- alarm inactive
|
||||
if int_state == AISTATE.INACTIVE then
|
||||
if tripped then
|
||||
alarm.trip_time = util.time_ms()
|
||||
if alarm.hold_time > 0 then
|
||||
alarm.state = AISTATE.TRIPPING
|
||||
alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||
else
|
||||
alarm.state = AISTATE.TRIPPED
|
||||
alarm_states[alarm.id] = ALARM_STATE.TRIPPED
|
||||
log.info(util.c(caller_tag, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): TRIPPED [PRIORITY ",
|
||||
types.ALARM_PRIORITY_NAMES[alarm.tier],"]"))
|
||||
end
|
||||
else
|
||||
alarm.trip_time = util.time_ms()
|
||||
alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||
end
|
||||
-- alarm condition met, but not yet for required hold time
|
||||
elseif (int_state == AISTATE.TRIPPING) or (int_state == AISTATE.RING_BACK_TRIPPING) then
|
||||
if tripped then
|
||||
local elapsed = util.time_ms() - alarm.trip_time
|
||||
if elapsed > (alarm.hold_time * 1000) then
|
||||
alarm.state = AISTATE.TRIPPED
|
||||
alarm_states[alarm.id] = ALARM_STATE.TRIPPED
|
||||
log.info(util.c(caller_tag, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): TRIPPED [PRIORITY ",
|
||||
types.ALARM_PRIORITY_NAMES[alarm.tier],"]"))
|
||||
end
|
||||
elseif int_state == AISTATE.RING_BACK_TRIPPING then
|
||||
alarm.trip_time = 0
|
||||
alarm.state = AISTATE.RING_BACK
|
||||
alarm_states[alarm.id] = ALARM_STATE.RING_BACK
|
||||
else
|
||||
alarm.trip_time = 0
|
||||
alarm.state = AISTATE.INACTIVE
|
||||
alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||
end
|
||||
-- alarm tripped and alarming
|
||||
elseif int_state == AISTATE.TRIPPED then
|
||||
if tripped then
|
||||
if ext_state == ALARM_STATE.ACKED then
|
||||
-- was acked by coordinator
|
||||
alarm.state = AISTATE.ACKED
|
||||
end
|
||||
elseif no_ring_back then
|
||||
alarm.state = AISTATE.INACTIVE
|
||||
alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||
else
|
||||
alarm.state = AISTATE.RING_BACK
|
||||
alarm_states[alarm.id] = ALARM_STATE.RING_BACK
|
||||
end
|
||||
-- alarm acknowledged but still tripped
|
||||
elseif int_state == AISTATE.ACKED then
|
||||
if not tripped then
|
||||
if no_ring_back then
|
||||
alarm.state = AISTATE.INACTIVE
|
||||
alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||
else
|
||||
alarm.state = AISTATE.RING_BACK
|
||||
alarm_states[alarm.id] = ALARM_STATE.RING_BACK
|
||||
end
|
||||
end
|
||||
-- alarm no longer tripped, operator must reset to clear
|
||||
elseif int_state == AISTATE.RING_BACK then
|
||||
if tripped then
|
||||
alarm.trip_time = util.time_ms()
|
||||
if alarm.hold_time > 0 then
|
||||
alarm.state = AISTATE.RING_BACK_TRIPPING
|
||||
else
|
||||
alarm.state = AISTATE.TRIPPED
|
||||
alarm_states[alarm.id] = ALARM_STATE.TRIPPED
|
||||
end
|
||||
elseif ext_state == ALARM_STATE.INACTIVE then
|
||||
-- was reset by coordinator
|
||||
alarm.state = AISTATE.INACTIVE
|
||||
alarm.trip_time = 0
|
||||
end
|
||||
else
|
||||
log.error(util.c(caller_tag, " invalid alarm state for alarm ", alarm.id), true)
|
||||
end
|
||||
|
||||
-- check for state change
|
||||
if alarm.state ~= int_state then
|
||||
local change_str = util.c(AISTATE_NAMES[int_state], " -> ", AISTATE_NAMES[alarm.state])
|
||||
log.debug(util.c(caller_tag, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): ", change_str))
|
||||
return alarm.state == AISTATE.TRIPPED
|
||||
else return false end
|
||||
end
|
||||
|
||||
return alarm_ctl
|
||||
@ -185,9 +185,8 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
||||
local fac_c_6 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
||||
local fac_c_7 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
||||
local fac_c_8 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
||||
local fac_c_9 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
||||
|
||||
local fac_pane = MultiPane{parent=fac_cfg,x=1,y=4,panes={fac_c_1,fac_c_2,fac_c_3,fac_c_4,fac_c_5,fac_c_6,fac_c_7,fac_c_8,fac_c_9}}
|
||||
local fac_pane = MultiPane{parent=fac_cfg,x=1,y=4,panes={fac_c_1,fac_c_2,fac_c_3,fac_c_4,fac_c_5,fac_c_6,fac_c_7, fac_c_8}}
|
||||
|
||||
TextBox{parent=fac_cfg,x=1,y=2,text=" Facility Configuration",fg_bg=cpair(colors.black,colors.yellow)}
|
||||
|
||||
@ -206,18 +205,10 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
||||
nu_error.hide(true)
|
||||
tmp_cfg.UnitCount = count
|
||||
|
||||
local c_confs = tool_ctl.cooling_elems
|
||||
local a_confs = tool_ctl.aux_cool_elems
|
||||
|
||||
for i = 2, 4 do
|
||||
if count >= i then
|
||||
c_confs[i].line.show()
|
||||
a_confs[i].line.show()
|
||||
else
|
||||
c_confs[i].line.hide(true)
|
||||
a_confs[i].line.hide(true)
|
||||
end
|
||||
end
|
||||
local confs = tool_ctl.cooling_elems
|
||||
if count >= 2 then confs[2].line.show() else confs[2].line.hide(true) end
|
||||
if count >= 3 then confs[3].line.show() else confs[3].line.hide(true) end
|
||||
if count == 4 then confs[4].line.show() else confs[4].line.hide(true) end
|
||||
|
||||
fac_pane.set_value(2)
|
||||
else nu_error.show() end
|
||||
@ -294,14 +285,6 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
||||
else elem.div.hide(true) end
|
||||
end
|
||||
|
||||
if not any_has_tank then
|
||||
tmp_cfg.FacilityTankMode = 0
|
||||
tmp_cfg.FacilityTankDefs = {}
|
||||
tmp_cfg.FacilityTankList = {}
|
||||
tmp_cfg.FacilityTankConns = {}
|
||||
tmp_cfg.TankFluidTypes = {}
|
||||
end
|
||||
|
||||
if any_has_tank then fac_pane.set_value(3) else main_pane.set_value(3) end
|
||||
end
|
||||
end
|
||||
@ -689,48 +672,25 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
||||
PushButton{parent=fac_c_7,x=1,y=14,text="\x1b Back",callback=back_from_fluids,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=fac_c_7,x=44,y=14,text="Next \x1a",callback=submit_tank_fluids,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
--#region Auxiliary Coolant
|
||||
|
||||
TextBox{parent=fac_c_8,height=5,text="Auxiliary water coolant can be enabled for units to provide extra water during turbine ramp-up. For water cooled reactors, this goes to the reactor. For sodium cooled reactors, water goes to the boiler."}
|
||||
|
||||
for i = 1, 4 do
|
||||
local line = Div{parent=fac_c_8,x=1,y=7+i,height=1}
|
||||
|
||||
TextBox{parent=line,text="Unit "..i.." -",width=8}
|
||||
local aux_cool = Checkbox{parent=line,x=10,y=1,label="Has Auxiliary Coolant",default=ini_cfg.AuxiliaryCoolant[i],box_fg_bg=cpair(colors.yellow,colors.black)}
|
||||
|
||||
tool_ctl.aux_cool_elems[i] = { line = line, enable = aux_cool }
|
||||
end
|
||||
|
||||
local function submit_aux_cool()
|
||||
tmp_cfg.AuxiliaryCoolant = {}
|
||||
|
||||
for i = 1, tmp_cfg.UnitCount do
|
||||
tmp_cfg.AuxiliaryCoolant[i] = tool_ctl.aux_cool_elems[i].enable.get_value()
|
||||
end
|
||||
|
||||
fac_pane.set_value(9)
|
||||
end
|
||||
|
||||
PushButton{parent=fac_c_8,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=fac_c_8,x=44,y=14,text="Next \x1a",callback=submit_aux_cool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
--#region Extended Idling
|
||||
|
||||
TextBox{parent=fac_c_9,height=6,text="Charge control provides automatic control to maintain an induction matrix charge level. In order to have smoother control, reactors that were activated will be held on at 0.01 mB/t for a short period before allowing them to turn off. This minimizes overshooting the charge target."}
|
||||
TextBox{parent=fac_c_9,y=8,height=3,text="You can extend this to a full minute to minimize reactors flickering on/off, but there may be more overshoot of the target."}
|
||||
TextBox{parent=fac_c_8,height=6,text="Charge control provides automatic control to maintain an induction matrix charge level. In order to have smoother control, reactors that were activated will be held on at 0.01 mB/t for a short period before allowing them to turn off. This minimizes overshooting the charge target."}
|
||||
TextBox{parent=fac_c_8,y=8,height=3,text="You can extend this to a full minute to minimize reactors flickering on/off, but there may be more overshoot of the target."}
|
||||
|
||||
local ext_idling = Checkbox{parent=fac_c_9,x=1,y=12,label="Enable Extended Idling",default=ini_cfg.ExtChargeIdling,box_fg_bg=cpair(colors.yellow,colors.black)}
|
||||
local ext_idling = Checkbox{parent=fac_c_8,x=1,y=12,label="Enable Extended Idling",default=ini_cfg.ExtChargeIdling,box_fg_bg=cpair(colors.yellow,colors.black)}
|
||||
|
||||
local function back_from_idling()
|
||||
fac_pane.set_value(tri(tmp_cfg.FacilityTankMode == 0, 3, 7))
|
||||
end
|
||||
|
||||
local function submit_idling()
|
||||
tmp_cfg.ExtChargeIdling = ext_idling.get_value()
|
||||
main_pane.set_value(3)
|
||||
end
|
||||
|
||||
PushButton{parent=fac_c_9,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(8)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=fac_c_9,x=44,y=14,text="Next \x1a",callback=submit_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=fac_c_8,x=1,y=14,text="\x1b Back",callback=back_from_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=fac_c_8,x=44,y=14,text="Next \x1a",callback=submit_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
|
||||
|
||||
@ -402,10 +402,6 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
||||
try_set(tool_ctl.tank_elems[i].tank_opt, ini_cfg.FacilityTankDefs[i])
|
||||
end
|
||||
|
||||
for i = 1, #ini_cfg.AuxiliaryCoolant do
|
||||
try_set(tool_ctl.aux_cool_elems[i].enable, ini_cfg.AuxiliaryCoolant[i])
|
||||
end
|
||||
|
||||
tool_ctl.en_fac_tanks.set_value(ini_cfg.FacilityTankMode > 0)
|
||||
|
||||
tool_ctl.view_cfg.enable()
|
||||
@ -592,7 +588,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
||||
end
|
||||
|
||||
if val == "" then val = "no facility tanks" end
|
||||
elseif f[1] == "FacilityTankMode" and raw == 0 then val = "no facility tanks"
|
||||
elseif f[1] == "FacilityTankMode" and raw == 0 then val = "0 (n/a, unit mode)"
|
||||
elseif f[1] == "FacilityTankDefs" and type(cfg.FacilityTankDefs) == "table" then
|
||||
local tank_name_list = { table.unpack(cfg.FacilityTankList) } ---@type (string|integer)[]
|
||||
local next_f = 1
|
||||
@ -629,13 +625,6 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
||||
|
||||
val = ""
|
||||
|
||||
local count = 0
|
||||
for idx = 1, #tank_list do
|
||||
if tank_list[idx] > 0 then count = count + 1 end
|
||||
end
|
||||
|
||||
local bullet = tri(count < 2, "", " \x07 ")
|
||||
|
||||
for idx = 1, #tank_list do
|
||||
local prefix = "?"
|
||||
local fluid = "water"
|
||||
@ -653,28 +642,11 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
||||
fluid = "sodium"
|
||||
end
|
||||
|
||||
val = val .. tri(val == "", "", "\n") .. util.sprintf(bullet .. "tank %s - %s", prefix, fluid)
|
||||
val = val .. tri(val == "", "", "\n") .. util.sprintf(" \x07 tank %s - %s", prefix, fluid)
|
||||
end
|
||||
end
|
||||
|
||||
if val == "" then val = "no emergency coolant tanks" end
|
||||
elseif f[1] == "AuxiliaryCoolant" then
|
||||
val = ""
|
||||
|
||||
local count = 0
|
||||
for idx = 1, #cfg.AuxiliaryCoolant do
|
||||
if cfg.AuxiliaryCoolant[idx] then count = count + 1 end
|
||||
end
|
||||
|
||||
local bullet = tri(count < 2, "", " \x07 ")
|
||||
|
||||
for idx = 1, #cfg.AuxiliaryCoolant do
|
||||
if cfg.AuxiliaryCoolant[idx] then
|
||||
val = val .. tri(val == "", "", "\n") .. util.sprintf(bullet .. "unit %d", idx)
|
||||
end
|
||||
end
|
||||
|
||||
if val == "" then val = "no auxiliary coolant" end
|
||||
end
|
||||
|
||||
if not skip then
|
||||
@ -683,7 +655,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
||||
local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
||||
alternate = not alternate
|
||||
|
||||
if (string.len(val) > val_max_w) or string.find(val, "\n") then
|
||||
if string.len(val) > val_max_w then
|
||||
local lines = util.strwrap(val, inner_width)
|
||||
height = #lines + 1
|
||||
end
|
||||
|
||||
@ -72,8 +72,7 @@ local tool_ctl = {
|
||||
load_legacy = nil, ---@type function
|
||||
|
||||
cooling_elems = {}, ---@type { line: Div, turbines: NumberField, boilers: NumberField, tank: Checkbox }[]
|
||||
tank_elems = {}, ---@type { div: Div, tank_opt: Radio2D, no_tank: TextBox }[]
|
||||
aux_cool_elems = {} ---@type { line: Div, enable: Checkbox }[]
|
||||
tank_elems = {} ---@type { div: Div, tank_opt: Radio2D, no_tank: TextBox }[]
|
||||
}
|
||||
|
||||
---@class svr_config
|
||||
@ -85,7 +84,6 @@ local tmp_cfg = {
|
||||
FacilityTankList = {}, ---@type integer[] list of tanks by slot (0 = none or covered by an above tank, 1 = unit tank, 2 = facility tank)
|
||||
FacilityTankConns = {}, ---@type integer[] map of unit tank connections (indicies are units, values are tank indicies in the tank list)
|
||||
TankFluidTypes = {}, ---@type integer[] which type of fluid each tank in the tank list should be containing
|
||||
AuxiliaryCoolant = {}, ---@type boolean[] if a unit has auxiliary coolant
|
||||
ExtChargeIdling = false,
|
||||
SVR_Channel = nil, ---@type integer
|
||||
PLC_Channel = nil, ---@type integer
|
||||
@ -119,7 +117,6 @@ local fields = {
|
||||
{ "FacilityTankList", "Facility Tank List", {} }, -- hidden
|
||||
{ "FacilityTankConns", "Facility Tank Connections", {} }, -- hidden
|
||||
{ "TankFluidTypes", "Tank Fluid Types", {} },
|
||||
{ "AuxiliaryCoolant", "Auxiliary Water Coolant", {} },
|
||||
{ "ExtChargeIdling", "Extended Charge Idling", false },
|
||||
{ "SVR_Channel", "SVR Channel", 16240 },
|
||||
{ "PLC_Channel", "PLC Channel", 16241 },
|
||||
|
||||
@ -2,12 +2,15 @@
|
||||
-- Data Bus - Central Communication Linking for Supervisor Front Panel
|
||||
--
|
||||
|
||||
local const = require("scada-common.constants")
|
||||
local psil = require("scada-common.psil")
|
||||
local util = require("scada-common.util")
|
||||
local psil = require("scada-common.psil")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local pgi = require("supervisor.panel.pgi")
|
||||
|
||||
-- nominal RTT is ping (0ms to 10ms usually) + 150ms for SV main loop tick
|
||||
local WARN_RTT = 300 -- 2x as long as expected w/ 0 ping
|
||||
local HIGH_RTT = 500 -- 3.33x as long as expected w/ 0 ping
|
||||
|
||||
local databus = {}
|
||||
|
||||
-- databus PSIL
|
||||
@ -56,9 +59,9 @@ end
|
||||
function databus.tx_plc_rtt(reactor_id, rtt)
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt", rtt)
|
||||
|
||||
if rtt > const.HIGH_RTT then
|
||||
if rtt > HIGH_RTT then
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.red)
|
||||
elseif rtt > const.WARN_RTT then
|
||||
elseif rtt > WARN_RTT then
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.green_hc)
|
||||
@ -87,9 +90,9 @@ end
|
||||
function databus.tx_rtu_rtt(session_id, rtt)
|
||||
databus.ps.publish("rtu_" .. session_id .. "_rtt", rtt)
|
||||
|
||||
if rtt > const.HIGH_RTT then
|
||||
if rtt > HIGH_RTT then
|
||||
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.red)
|
||||
elseif rtt > const.WARN_RTT then
|
||||
elseif rtt > WARN_RTT then
|
||||
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.green_hc)
|
||||
@ -126,9 +129,9 @@ end
|
||||
function databus.tx_crd_rtt(rtt)
|
||||
databus.ps.publish("crd_rtt", rtt)
|
||||
|
||||
if rtt > const.HIGH_RTT then
|
||||
if rtt > HIGH_RTT then
|
||||
databus.ps.publish("crd_rtt_color", colors.red)
|
||||
elseif rtt > const.WARN_RTT then
|
||||
elseif rtt > WARN_RTT then
|
||||
databus.ps.publish("crd_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
databus.ps.publish("crd_rtt_color", colors.green_hc)
|
||||
@ -157,9 +160,9 @@ end
|
||||
function databus.tx_pdg_rtt(session_id, rtt)
|
||||
databus.ps.publish("pdg_" .. session_id .. "_rtt", rtt)
|
||||
|
||||
if rtt > const.HIGH_RTT then
|
||||
if rtt > HIGH_RTT then
|
||||
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.red)
|
||||
elseif rtt > const.WARN_RTT then
|
||||
elseif rtt > WARN_RTT then
|
||||
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.green_hc)
|
||||
|
||||
@ -2,19 +2,13 @@ local log = require("scada-common.log")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local alarm_ctl = require("supervisor.alarm_ctl")
|
||||
local unit = require("supervisor.unit")
|
||||
local fac_update = require("supervisor.facility_update")
|
||||
|
||||
local rsctl = require("supervisor.session.rsctl")
|
||||
local svsessions = require("supervisor.session.svsessions")
|
||||
|
||||
local AISTATE = alarm_ctl.AISTATE
|
||||
|
||||
local ALARM = types.ALARM
|
||||
local ALARM_STATE = types.ALARM_STATE
|
||||
local AUTO_GROUP = types.AUTO_GROUP
|
||||
local PRIO = types.ALARM_PRIORITY
|
||||
local PROCESS = types.PROCESS
|
||||
local RTU_ID_FAIL = types.RTU_ID_FAIL
|
||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
@ -37,17 +31,6 @@ local START_STATUS = {
|
||||
BLADE_MISMATCH = 2
|
||||
}
|
||||
|
||||
---@enum RECOVERY_STATE
|
||||
local RCV_STATE = {
|
||||
INACTIVE = 0,
|
||||
PRIMED = 1,
|
||||
RUNNING = 2,
|
||||
STOPPED = 3
|
||||
}
|
||||
|
||||
local CHARGE_SCALER = 1000000 -- convert MFE to FE
|
||||
local GEN_SCALER = 1000 -- convert kFE to FE
|
||||
|
||||
---@class facility_management
|
||||
local facility = {}
|
||||
|
||||
@ -58,7 +41,7 @@ function facility.new(config)
|
||||
---@class _facility_self
|
||||
local self = {
|
||||
units = {}, ---@type reactor_unit[]
|
||||
types = { AUTO_SCRAM = AUTO_SCRAM, START_STATUS = START_STATUS, RCV_STATE = RCV_STATE },
|
||||
types = { AUTO_SCRAM = AUTO_SCRAM, START_STATUS = START_STATUS },
|
||||
status_text = { "START UP", "initializing..." },
|
||||
all_sys_ok = false,
|
||||
allow_testing = false,
|
||||
@ -70,8 +53,7 @@ function facility.new(config)
|
||||
fac_tank_defs = config.FacilityTankDefs,
|
||||
fac_tank_list = config.FacilityTankList,
|
||||
fac_tank_conns = config.FacilityTankConns,
|
||||
tank_fluid_types = config.TankFluidTypes,
|
||||
aux_coolant = config.AuxiliaryCoolant
|
||||
tank_fluid_types = config.TankFluidTypes
|
||||
},
|
||||
-- rtus
|
||||
rtu_gw_conn_count = 0,
|
||||
@ -84,15 +66,12 @@ function facility.new(config)
|
||||
-- redstone I/O control
|
||||
io_ctl = nil, ---@type rs_controller
|
||||
-- process control
|
||||
recovery = RCV_STATE.INACTIVE, ---@type RECOVERY_STATE
|
||||
recovery_boot_state = nil, ---@type sv_boot_state|nil
|
||||
last_unit_states = {}, ---@type boolean[]
|
||||
units_ready = false,
|
||||
mode = PROCESS.INACTIVE, ---@type PROCESS
|
||||
last_mode = PROCESS.INACTIVE, ---@type PROCESS
|
||||
return_mode = PROCESS.INACTIVE, ---@type PROCESS
|
||||
mode_set = PROCESS.MAX_BURN, ---@type PROCESS
|
||||
start_fail = START_STATUS.OK, ---@type START_STATUS
|
||||
mode = PROCESS.INACTIVE,
|
||||
last_mode = PROCESS.INACTIVE,
|
||||
return_mode = PROCESS.INACTIVE,
|
||||
mode_set = PROCESS.MAX_BURN,
|
||||
start_fail = START_STATUS.OK,
|
||||
max_burn_combined = 0.0, -- maximum burn rate to clamp at
|
||||
burn_target = 0.1, -- burn rate target for aggregate burn mode
|
||||
charge_setpoint = 0, -- FE charge target setpoint
|
||||
@ -122,8 +101,8 @@ function facility.new(config)
|
||||
last_error = 0.0,
|
||||
last_time = 0.0,
|
||||
-- waste processing
|
||||
waste_product = WASTE.PLUTONIUM, ---@type WASTE_PRODUCT
|
||||
current_waste_product = WASTE.PLUTONIUM, ---@type WASTE_PRODUCT
|
||||
waste_product = WASTE.PLUTONIUM,
|
||||
current_waste_product = WASTE.PLUTONIUM,
|
||||
pu_fallback = false,
|
||||
sps_low_power = false,
|
||||
disabled_sps = false,
|
||||
@ -144,36 +123,24 @@ function facility.new(config)
|
||||
imtx_last_charge = 0,
|
||||
imtx_last_charge_t = 0,
|
||||
-- track faulted induction matrix update times to reject
|
||||
imtx_faulted_times = { 0, 0, 0 },
|
||||
-- facility alarms
|
||||
---@type { [string]: alarm_def }
|
||||
alarms = {
|
||||
-- radiation monitor alarm for the facility
|
||||
FacilityRadiation = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.FacilityRadiation, tier = PRIO.CRITICAL },
|
||||
},
|
||||
---@type { [ALARM]: ALARM_STATE }
|
||||
alarm_states = {
|
||||
[ALARM.FacilityRadiation] = ALARM_STATE.INACTIVE
|
||||
}
|
||||
imtx_faulted_times = { 0, 0, 0 }
|
||||
}
|
||||
|
||||
--#region SETUP
|
||||
|
||||
-- provide self to facility update functions
|
||||
local f_update = fac_update(self)
|
||||
|
||||
-- create units
|
||||
for i = 1, config.UnitCount do
|
||||
table.insert(self.units, unit.new(i, self.cooling_conf.r_cool[i].BoilerCount, self.cooling_conf.r_cool[i].TurbineCount, config.ExtChargeIdling, self.cooling_conf.aux_coolant[i]))
|
||||
table.insert(self.units,
|
||||
unit.new(i, self.cooling_conf.r_cool[i].BoilerCount, self.cooling_conf.r_cool[i].TurbineCount, config.ExtChargeIdling))
|
||||
table.insert(self.group_map, AUTO_GROUP.MANUAL)
|
||||
table.insert(self.last_unit_states, false)
|
||||
end
|
||||
|
||||
-- list for RTU session management
|
||||
self.rtu_list = { self.redstone, self.induction, self.sps, self.tanks, self.envd }
|
||||
|
||||
-- init redstone RTU I/O controller
|
||||
self.io_ctl = rsctl.new(self.redstone, 0)
|
||||
self.io_ctl = rsctl.new(self.redstone)
|
||||
|
||||
-- fill blank alarm/tone states
|
||||
for _ = 1, 12 do table.insert(self.test_alarm_states, false) end
|
||||
@ -182,70 +149,6 @@ function facility.new(config)
|
||||
table.insert(self.test_tone_states, false)
|
||||
end
|
||||
|
||||
-- init next boot state
|
||||
settings.set("LastProcessState", PROCESS.INACTIVE)
|
||||
settings.set("LastUnitStates", self.last_unit_states)
|
||||
if not settings.save("/supervisor.settings") then
|
||||
log.warning("FAC: failed to save initial control state into supervisor settings file")
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
-- PRIVATE FUNCTIONS --
|
||||
|
||||
-- check an auto process control configuration and save it if its valid (does not start the process)
|
||||
---@param auto_cfg start_auto_config configuration
|
||||
---@return boolean ready, number[] unit_limits
|
||||
local function _auto_check_and_save(auto_cfg)
|
||||
local ready = false
|
||||
|
||||
-- load up current limits
|
||||
local limits = {}
|
||||
for i = 1, config.UnitCount do
|
||||
limits[i] = self.units[i].get_control_inf().lim_br100 * 100
|
||||
end
|
||||
|
||||
-- only allow changes if not running
|
||||
if self.mode == PROCESS.INACTIVE then
|
||||
if (type(auto_cfg.mode) == "number") and (auto_cfg.mode > PROCESS.INACTIVE) and (auto_cfg.mode <= PROCESS.GEN_RATE) then
|
||||
self.mode_set = auto_cfg.mode
|
||||
end
|
||||
|
||||
if (type(auto_cfg.burn_target) == "number") and auto_cfg.burn_target >= 0.1 then
|
||||
self.burn_target = auto_cfg.burn_target
|
||||
end
|
||||
|
||||
if (type(auto_cfg.charge_target) == "number") and auto_cfg.charge_target >= 0 then
|
||||
self.charge_setpoint = auto_cfg.charge_target * CHARGE_SCALER
|
||||
end
|
||||
|
||||
if (type(auto_cfg.gen_target) == "number") and auto_cfg.gen_target >= 0 then
|
||||
self.gen_rate_setpoint = auto_cfg.gen_target * GEN_SCALER
|
||||
end
|
||||
|
||||
if (type(auto_cfg.limits) == "table") and (#auto_cfg.limits == config.UnitCount) then
|
||||
for i = 1, config.UnitCount do
|
||||
local limit = auto_cfg.limits[i]
|
||||
|
||||
if (type(limit) == "number") and (limit >= 0.1) then
|
||||
limits[i] = limit
|
||||
self.units[i].set_burn_limit(limit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ready = self.mode_set > 0
|
||||
|
||||
if ((self.mode_set == PROCESS.CHARGE) and (self.charge_setpoint <= 0)) or
|
||||
((self.mode_set == PROCESS.GEN_RATE) and (self.gen_rate_setpoint <= 0)) or
|
||||
((self.mode_set == PROCESS.BURN_RATE) and (self.burn_target < 0.1)) then
|
||||
ready = false
|
||||
end
|
||||
end
|
||||
|
||||
return ready, limits
|
||||
end
|
||||
|
||||
-- PUBLIC FUNCTIONS --
|
||||
|
||||
---@class facility
|
||||
@ -336,9 +239,6 @@ function facility.new(config)
|
||||
|
||||
-- update (iterate) the facility management
|
||||
function public.update()
|
||||
-- run reboot recovery routine if needed
|
||||
f_update.boot_recovery()
|
||||
|
||||
-- run process control and evaluate automatic SCRAM
|
||||
f_update.pre_auto()
|
||||
f_update.auto_control(config.ExtChargeIdling)
|
||||
@ -351,9 +251,6 @@ function facility.new(config)
|
||||
-- unit tasks
|
||||
f_update.unit_mgmt()
|
||||
|
||||
-- update alarm states right before updating the audio
|
||||
f_update.update_alarms()
|
||||
|
||||
-- update alarm tones
|
||||
f_update.alarm_audio()
|
||||
end
|
||||
@ -370,50 +267,6 @@ function facility.new(config)
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Startup Recovery
|
||||
|
||||
-- on exit, use this to clear the boot state so we don't resume when exiting cleanly
|
||||
function public.clear_boot_state()
|
||||
settings.unset("LastProcessState")
|
||||
settings.unset("LastUnitStates")
|
||||
|
||||
if not settings.save("/supervisor.settings") then
|
||||
log.warning("facility.clear_boot_state(): failed to save supervisor settings file")
|
||||
else
|
||||
log.debug("FAC: cleared boot state on exit")
|
||||
end
|
||||
end
|
||||
|
||||
-- initialize facility resume boot recovery
|
||||
---@param state sv_boot_state|nil
|
||||
function public.boot_recovery_init(state)
|
||||
if self.recovery == RCV_STATE.INACTIVE and state then
|
||||
self.recovery_boot_state = state
|
||||
self.recovery = RCV_STATE.PRIMED
|
||||
log.info("FAC: startup resume ready")
|
||||
end
|
||||
end
|
||||
|
||||
-- attempt facility resume boot recovery
|
||||
---@param auto_cfg start_auto_config configuration
|
||||
function public.boot_recovery_start(auto_cfg)
|
||||
if self.recovery == RCV_STATE.PRIMED then
|
||||
self.recovery = util.trinary(_auto_check_and_save(auto_cfg), RCV_STATE.RUNNING, RCV_STATE.STOPPED)
|
||||
log.info(util.c("FAC: startup resume ", util.trinary(self.recovery == RCV_STATE.RUNNING, "started", "failed")))
|
||||
else self.recovery = RCV_STATE.STOPPED end
|
||||
end
|
||||
|
||||
-- used on certain coordinator commands to end reboot recovery (remain in current operational state)
|
||||
function public.cancel_recovery()
|
||||
if self.recovery == RCV_STATE.RUNNING then
|
||||
self.recovery = RCV_STATE.STOPPED
|
||||
self.recovery_boot_state = nil
|
||||
log.info("FAC: process startup resume cancelled by user operation")
|
||||
end
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Commands
|
||||
|
||||
-- SCRAM all reactor units
|
||||
@ -423,14 +276,10 @@ function facility.new(config)
|
||||
end
|
||||
end
|
||||
|
||||
-- ack all alarms on all reactor units and the facility
|
||||
-- ack all alarms on all reactor units
|
||||
function public.ack_all()
|
||||
-- unit alarms
|
||||
for i = 1, #self.units do self.units[i].ack_all() end
|
||||
|
||||
-- facility alarms
|
||||
for id, state in pairs(self.alarm_states) do
|
||||
if state == ALARM_STATE.TRIPPED then self.alarm_states[id] = ALARM_STATE.ACKED end
|
||||
for i = 1, #self.units do
|
||||
self.units[i].ack_all()
|
||||
end
|
||||
end
|
||||
|
||||
@ -441,13 +290,59 @@ function facility.new(config)
|
||||
function public.auto_stop() self.mode = PROCESS.INACTIVE end
|
||||
|
||||
-- set automatic control configuration and start the process
|
||||
---@param auto_cfg start_auto_config configuration
|
||||
---@param auto_cfg sys_auto_config configuration
|
||||
---@return table response ready state (successfully started) and current configuration (after updating)
|
||||
function public.auto_start(auto_cfg)
|
||||
local ready, limits = _auto_check_and_save(auto_cfg)
|
||||
local charge_scaler = 1000000 -- convert MFE to FE
|
||||
local gen_scaler = 1000 -- convert kFE to FE
|
||||
local ready = false
|
||||
|
||||
if ready and self.units_ready then
|
||||
self.mode = self.mode_set
|
||||
-- load up current limits
|
||||
local limits = {}
|
||||
for i = 1, config.UnitCount do
|
||||
limits[i] = self.units[i].get_control_inf().lim_br100 * 100
|
||||
end
|
||||
|
||||
-- only allow changes if not running
|
||||
if self.mode == PROCESS.INACTIVE then
|
||||
if (type(auto_cfg.mode) == "number") and (auto_cfg.mode > PROCESS.INACTIVE) and (auto_cfg.mode <= PROCESS.GEN_RATE) then
|
||||
self.mode_set = auto_cfg.mode
|
||||
end
|
||||
|
||||
if (type(auto_cfg.burn_target) == "number") and auto_cfg.burn_target >= 0.1 then
|
||||
self.burn_target = auto_cfg.burn_target
|
||||
end
|
||||
|
||||
if (type(auto_cfg.charge_target) == "number") and auto_cfg.charge_target >= 0 then
|
||||
self.charge_setpoint = auto_cfg.charge_target * charge_scaler
|
||||
end
|
||||
|
||||
if (type(auto_cfg.gen_target) == "number") and auto_cfg.gen_target >= 0 then
|
||||
self.gen_rate_setpoint = auto_cfg.gen_target * gen_scaler
|
||||
end
|
||||
|
||||
if (type(auto_cfg.limits) == "table") and (#auto_cfg.limits == config.UnitCount) then
|
||||
for i = 1, config.UnitCount do
|
||||
local limit = auto_cfg.limits[i]
|
||||
|
||||
if (type(limit) == "number") and (limit >= 0.1) then
|
||||
limits[i] = limit
|
||||
self.units[i].set_burn_limit(limit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ready = self.mode_set > 0
|
||||
|
||||
if ((self.mode_set == PROCESS.CHARGE) and (self.charge_setpoint <= 0)) or
|
||||
((self.mode_set == PROCESS.GEN_RATE) and (self.gen_rate_setpoint <= 0)) or
|
||||
((self.mode_set == PROCESS.BURN_RATE) and (self.burn_target < 0.1)) then
|
||||
ready = false
|
||||
end
|
||||
|
||||
ready = ready and self.units_ready
|
||||
|
||||
if ready then self.mode = self.mode_set end
|
||||
end
|
||||
|
||||
log.debug(util.c("FAC: process start ", util.trinary(ready, "accepted", "rejected")))
|
||||
@ -456,8 +351,8 @@ function facility.new(config)
|
||||
ready,
|
||||
self.mode_set,
|
||||
self.burn_target,
|
||||
self.charge_setpoint / CHARGE_SCALER,
|
||||
self.gen_rate_setpoint / GEN_SCALER,
|
||||
self.charge_setpoint / charge_scaler,
|
||||
self.gen_rate_setpoint / gen_scaler,
|
||||
limits
|
||||
}
|
||||
end
|
||||
|
||||
@ -1,23 +1,17 @@
|
||||
local audio = require("scada-common.audio")
|
||||
local const = require("scada-common.constants")
|
||||
local log = require("scada-common.log")
|
||||
local rsio = require("scada-common.rsio")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
local audio = require("scada-common.audio")
|
||||
local const = require("scada-common.constants")
|
||||
local log = require("scada-common.log")
|
||||
local rsio = require("scada-common.rsio")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local alarm_ctl = require("supervisor.alarm_ctl")
|
||||
|
||||
local plc = require("supervisor.session.plc")
|
||||
local svsessions = require("supervisor.session.svsessions")
|
||||
|
||||
local qtypes = require("supervisor.session.rtu.qtypes")
|
||||
local qtypes = require("supervisor.session.rtu.qtypes")
|
||||
|
||||
local TONE = audio.TONE
|
||||
|
||||
local ALARM = types.ALARM
|
||||
local PRIO = types.ALARM_PRIORITY
|
||||
local ALARM_STATE = types.ALARM_STATE
|
||||
local AUTO_GROUP = types.AUTO_GROUP
|
||||
local CONTAINER_MODE = types.CONTAINER_MODE
|
||||
local PROCESS = types.PROCESS
|
||||
local PROCESS_NAMES = types.PROCESS_NAMES
|
||||
@ -137,54 +131,6 @@ end
|
||||
|
||||
--#region PUBLIC FUNCTIONS
|
||||
|
||||
-- run reboot recovery routine if needed
|
||||
function update.boot_recovery()
|
||||
local RCV_STATE = self.types.RCV_STATE
|
||||
|
||||
-- attempt reboot recovery if in progress
|
||||
if self.recovery == RCV_STATE.RUNNING then
|
||||
local was_inactive = self.recovery_boot_state.mode == PROCESS.INACTIVE or self.recovery_boot_state.mode == PROCESS.SYSTEM_ALARM_IDLE
|
||||
|
||||
-- try to start auto control
|
||||
if self.recovery_boot_state.mode ~= nil and self.units_ready then
|
||||
if not was_inactive then
|
||||
self.mode = self.mode_set
|
||||
log.info("FAC: process startup resume initiated")
|
||||
end
|
||||
|
||||
self.recovery_boot_state.mode = nil
|
||||
end
|
||||
|
||||
local recovered = self.recovery_boot_state.mode == nil or was_inactive
|
||||
|
||||
-- restore manual control reactors
|
||||
for i = 1, #self.units do
|
||||
local u = self.units[i]
|
||||
|
||||
if self.recovery_boot_state.unit_states[i] and self.group_map[i] == AUTO_GROUP.MANUAL then
|
||||
recovered = false
|
||||
|
||||
if u.get_control_inf().ready then
|
||||
local plc_s = svsessions.get_reactor_session(i)
|
||||
if plc_s ~= nil then
|
||||
plc_s.in_queue.push_command(plc.PLC_S_CMDS.ENABLE)
|
||||
log.info("FAC: startup resume enabling manually controlled reactor unit #" .. i)
|
||||
|
||||
-- only execute once
|
||||
self.recovery_boot_state.unit_states[i] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if recovered then
|
||||
self.recovery = RCV_STATE.STOPPED
|
||||
self.recovery_boot_state = nil
|
||||
log.info("FAC: startup resume sequence completed")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- automatic control pre-update logic
|
||||
function update.pre_auto()
|
||||
-- unlink RTU sessions if they are closed
|
||||
@ -297,11 +243,6 @@ function update.auto_control(ExtChargeIdling)
|
||||
|
||||
log.debug(util.c("FAC: state changed from ", PROCESS_NAMES[self.last_mode + 1], " to ", PROCESS_NAMES[self.mode + 1]))
|
||||
|
||||
settings.set("LastProcessState", self.mode)
|
||||
if not settings.save("/supervisor.settings") then
|
||||
log.warning("facility_update.auto_control(): failed to save supervisor settings file")
|
||||
end
|
||||
|
||||
if (self.last_mode == PROCESS.INACTIVE) or (self.last_mode == PROCESS.GEN_RATE_FAULT_IDLE) then
|
||||
self.start_fail = START_STATUS.OK
|
||||
|
||||
@ -645,7 +586,7 @@ function update.auto_safety()
|
||||
end
|
||||
|
||||
if (self.mode ~= PROCESS.INACTIVE) and (self.mode ~= PROCESS.SYSTEM_ALARM_IDLE) then
|
||||
local scram = astatus.matrix_fault or astatus.matrix_fill or astatus.crit_alarm or astatus.radiation or astatus.gen_fault
|
||||
local scram = astatus.matrix_fault or astatus.matrix_fill or astatus.crit_alarm or astatus.gen_fault
|
||||
|
||||
if scram and not self.ascram then
|
||||
-- SCRAM all units
|
||||
@ -701,32 +642,25 @@ function update.auto_safety()
|
||||
self.ascram_reason = AUTO_SCRAM.NONE
|
||||
|
||||
-- reset PLC RPS trips if we should
|
||||
for i = 1, #self.prio_defs do
|
||||
for _, u in pairs(self.prio_defs[i]) do
|
||||
u.auto_cond_rps_reset()
|
||||
end
|
||||
for i = 1, #self.units do
|
||||
local u = self.units[i]
|
||||
u.auto_cond_rps_reset()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- update last mode, set next mode, and update saved state as needed
|
||||
-- update last mode and set next mode
|
||||
function update.post_auto()
|
||||
self.last_mode = self.mode
|
||||
self.mode = next_mode
|
||||
end
|
||||
|
||||
-- update facility alarm states
|
||||
function update.update_alarms()
|
||||
-- Facility Radiation
|
||||
alarm_ctl.update_alarm_state("FAC", self.alarm_states, self.ascram_status.radiation, self.alarms.FacilityRadiation, true)
|
||||
end
|
||||
|
||||
-- update alarm audio control
|
||||
function update.alarm_audio()
|
||||
local allow_test = self.allow_testing and self.test_tone_set
|
||||
|
||||
local alarms = { false, false, false, false, false, false, false, false, false, false, false, false, false }
|
||||
local alarms = { false, false, false, false, false, false, false, false, false, false, false, false }
|
||||
|
||||
-- reset tone states before re-evaluting
|
||||
for i = 1, #self.tone_states do self.tone_states[i] = false end
|
||||
@ -742,11 +676,8 @@ function update.alarm_audio()
|
||||
end
|
||||
end
|
||||
|
||||
-- record facility alarms
|
||||
alarms[ALARM.FacilityRadiation] = self.alarm_states[ALARM.FacilityRadiation] == ALARM_STATE.TRIPPED
|
||||
|
||||
-- clear testing alarms if we aren't using them
|
||||
if not self.test_tone_reset then
|
||||
-- clear testing alarms if we aren't using them
|
||||
for i = 1, #self.test_alarm_states do self.test_alarm_states[i] = false end
|
||||
end
|
||||
end
|
||||
@ -785,7 +716,7 @@ function update.alarm_audio()
|
||||
end
|
||||
|
||||
-- radiation is a big concern, always play this CRITICAL level alarm if active
|
||||
if alarms[ALARM.ContainmentRadiation] or alarms[ALARM.FacilityRadiation] then
|
||||
if alarms[ALARM.ContainmentRadiation] then
|
||||
self.tone_states[TONE.T_800Hz_1000Hz_Alt] = true
|
||||
-- we are going to disable the RPS trip alarm audio due to conflict, and if it was enabled
|
||||
-- then we can re-enable the reactor lost alarm audio since it doesn't painfully combine with this one
|
||||
@ -861,7 +792,6 @@ end
|
||||
function update.unit_mgmt()
|
||||
local insufficent_po_rate = false
|
||||
local need_emcool = false
|
||||
local write_state = false
|
||||
|
||||
for i = 1, #self.units do
|
||||
local u = self.units[i]
|
||||
@ -877,21 +807,6 @@ function update.unit_mgmt()
|
||||
if (self.cooling_conf.fac_tank_mode > 0) and u.is_emer_cool_tripped() and (self.cooling_conf.fac_tank_defs[i] == 2) then
|
||||
need_emcool = true
|
||||
end
|
||||
|
||||
-- check for enabled state changes to save
|
||||
if self.last_unit_states[i] ~= u.is_reactor_enabled() then
|
||||
self.last_unit_states[i] = u.is_reactor_enabled()
|
||||
write_state = true
|
||||
end
|
||||
end
|
||||
|
||||
-- record unit control states
|
||||
|
||||
if write_state then
|
||||
settings.set("LastUnitStates", self.last_unit_states)
|
||||
if not settings.save("/supervisor.settings") then
|
||||
log.warning("facility_update.unit_mgmt(): failed to save supervisor settings file")
|
||||
end
|
||||
end
|
||||
|
||||
-- update waste product
|
||||
|
||||
@ -25,8 +25,6 @@ local function init(parent, id)
|
||||
|
||||
local label_fg = style.fp.label_fg
|
||||
|
||||
local term_w, _ = term.getSize()
|
||||
|
||||
-- root div
|
||||
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2}
|
||||
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=style.theme.highlight_box_bright}
|
||||
@ -42,9 +40,9 @@ local function init(parent, id)
|
||||
local pdg_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,fg_bg=label_fg}
|
||||
pdg_fw_v.register(databus.ps, ps_prefix .. "fw", pdg_fw_v.set_value)
|
||||
|
||||
TextBox{parent=entry,x=term_w-16,y=2,text="RTT:",width=4}
|
||||
local pdg_rtt = DataIndicator{parent=entry,x=term_w-11,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
||||
TextBox{parent=entry,x=term_w-5,y=2,text="ms",width=4,fg_bg=label_fg}
|
||||
TextBox{parent=entry,x=35,y=2,text="RTT:",width=4}
|
||||
local pdg_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
||||
TextBox{parent=entry,x=46,y=2,text="ms",width=4,fg_bg=label_fg}
|
||||
pdg_rtt.register(databus.ps, ps_prefix .. "rtt", pdg_rtt.update)
|
||||
pdg_rtt.register(databus.ps, ps_prefix .. "rtt_color", pdg_rtt.recolor)
|
||||
|
||||
|
||||
@ -25,8 +25,6 @@ local function init(parent, id)
|
||||
|
||||
local label_fg = style.fp.label_fg
|
||||
|
||||
local term_w, _ = term.getSize()
|
||||
|
||||
-- root div
|
||||
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2}
|
||||
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=style.theme.highlight_box_bright}
|
||||
@ -42,13 +40,13 @@ local function init(parent, id)
|
||||
local unit_count = DataIndicator{parent=entry,x=17,y=2,label="",unit="",format="%2d",value=0,width=2,fg_bg=style.fp.label_d_fg}
|
||||
unit_count.register(databus.ps, ps_prefix .. "units", unit_count.set_value)
|
||||
|
||||
TextBox{parent=entry,x=term_w-30,y=2,text="FW:",width=3}
|
||||
local rtu_fw_v = TextBox{parent=entry,x=term_w-26,y=2,text=" ------- ",width=9,fg_bg=label_fg}
|
||||
TextBox{parent=entry,x=21,y=2,text="FW:",width=3}
|
||||
local rtu_fw_v = TextBox{parent=entry,x=25,y=2,text=" ------- ",width=9,fg_bg=label_fg}
|
||||
rtu_fw_v.register(databus.ps, ps_prefix .. "fw", rtu_fw_v.set_value)
|
||||
|
||||
TextBox{parent=entry,x=term_w-15,y=2,text="RTT:",width=4}
|
||||
local rtu_rtt = DataIndicator{parent=entry,x=term_w-11,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
||||
TextBox{parent=entry,x=term_w-5,y=2,text="ms",width=4,fg_bg=label_fg}
|
||||
TextBox{parent=entry,x=36,y=2,text="RTT:",width=4}
|
||||
local rtu_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
||||
TextBox{parent=entry,x=46,y=2,text="ms",width=4,fg_bg=label_fg}
|
||||
rtu_rtt.register(databus.ps, ps_prefix .. "rtt", rtu_rtt.update)
|
||||
rtu_rtt.register(databus.ps, ps_prefix .. "rtt_color", rtu_rtt.recolor)
|
||||
|
||||
|
||||
@ -41,8 +41,6 @@ local function init(panel)
|
||||
local label_fg = style.fp.label_fg
|
||||
local label_d_fg = style.fp.label_d_fg
|
||||
|
||||
local term_w, term_h = term.getSize()
|
||||
|
||||
TextBox{parent=panel,y=1,text="SCADA SUPERVISOR",alignment=ALIGN.CENTER,fg_bg=style.theme.header}
|
||||
|
||||
local page_div = Div{parent=panel,x=1,y=3}
|
||||
@ -75,9 +73,9 @@ local function init(panel)
|
||||
-- about footer
|
||||
--
|
||||
|
||||
local about = Div{parent=main_page,width=15,height=2,y=term_h-3,fg_bg=style.fp.disabled_fg}
|
||||
local fw_v = TextBox{parent=about,text="FW: v00.00.00"}
|
||||
local comms_v = TextBox{parent=about,text="NT: v00.00.00"}
|
||||
local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=style.fp.disabled_fg}
|
||||
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00"}
|
||||
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00"}
|
||||
|
||||
fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
||||
comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
||||
@ -89,7 +87,7 @@ local function init(panel)
|
||||
-- plc sessions page
|
||||
|
||||
local plc_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||
local plc_list = Div{parent=plc_page,x=2,y=2,width=term_w-2}
|
||||
local plc_list = Div{parent=plc_page,x=2,y=2,width=49}
|
||||
|
||||
for i = 1, supervisor.config.UnitCount do
|
||||
local ps_prefix = "plc_" .. i .. "_"
|
||||
@ -105,13 +103,13 @@ local function init(panel)
|
||||
local plc_addr = TextBox{parent=plc_entry,x=17,y=2,text=" --- ",width=5,fg_bg=label_d_fg}
|
||||
plc_addr.register(databus.ps, ps_prefix .. "addr", plc_addr.set_value)
|
||||
|
||||
TextBox{parent=plc_entry,x=term_w-28,y=2,text="FW:",width=3}
|
||||
local plc_fw_v = TextBox{parent=plc_entry,x=term_w-24,y=2,text=" ------- ",width=9,fg_bg=label_fg}
|
||||
TextBox{parent=plc_entry,x=23,y=2,text="FW:",width=3}
|
||||
local plc_fw_v = TextBox{parent=plc_entry,x=27,y=2,text=" ------- ",width=9,fg_bg=label_fg}
|
||||
plc_fw_v.register(databus.ps, ps_prefix .. "fw", plc_fw_v.set_value)
|
||||
|
||||
TextBox{parent=plc_entry,x=term_w-14,y=2,text="RTT:",width=4}
|
||||
local plc_rtt = DataIndicator{parent=plc_entry,x=term_w-9,y=2,label="",unit="",format="%4d",value=0,width=4,fg_bg=label_fg}
|
||||
TextBox{parent=plc_entry,x=term_w-4,y=2,text="ms",width=4,fg_bg=label_fg}
|
||||
TextBox{parent=plc_entry,x=37,y=2,text="RTT:",width=4}
|
||||
local plc_rtt = DataIndicator{parent=plc_entry,x=42,y=2,label="",unit="",format="%4d",value=0,width=4,fg_bg=label_fg}
|
||||
TextBox{parent=plc_entry,x=47,y=2,text="ms",width=4,fg_bg=label_fg}
|
||||
plc_rtt.register(databus.ps, ps_prefix .. "rtt", plc_rtt.update)
|
||||
plc_rtt.register(databus.ps, ps_prefix .. "rtt_color", plc_rtt.recolor)
|
||||
|
||||
@ -121,13 +119,13 @@ local function init(panel)
|
||||
-- rtu sessions page
|
||||
|
||||
local rtu_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||
local rtu_list = ListBox{parent=rtu_page,y=1,height=term_h-2,width=term_w,scroll_height=1000,fg_bg=cpair(colors.black,colors.ivory),nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||
local rtu_list = ListBox{parent=rtu_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=cpair(colors.black,colors.ivory),nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||
local _ = Div{parent=rtu_list,height=1} -- padding
|
||||
|
||||
-- coordinator session page
|
||||
|
||||
local crd_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||
local crd_box = Div{parent=crd_page,x=2,y=2,width=term_w-2,height=4,fg_bg=s_hi_bright}
|
||||
local crd_box = Div{parent=crd_page,x=2,y=2,width=49,height=4,fg_bg=s_hi_bright}
|
||||
|
||||
local crd_conn = LED{parent=crd_box,x=2,y=2,label="CONNECTION",colors=cpair(colors.green_hc,colors.green_off)}
|
||||
crd_conn.register(databus.ps, "crd_conn", crd_conn.update)
|
||||
@ -140,27 +138,27 @@ local function init(panel)
|
||||
local crd_fw_v = TextBox{parent=crd_box,x=26,y=2,text=" ------- ",width=9,fg_bg=label_fg}
|
||||
crd_fw_v.register(databus.ps, "crd_fw", crd_fw_v.set_value)
|
||||
|
||||
TextBox{parent=crd_box,x=term_w-15,y=2,text="RTT:",width=4}
|
||||
local crd_rtt = DataIndicator{parent=crd_box,x=term_w-10,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
||||
TextBox{parent=crd_box,x=term_w-4,y=2,text="ms",width=4,fg_bg=label_fg}
|
||||
TextBox{parent=crd_box,x=36,y=2,text="RTT:",width=4}
|
||||
local crd_rtt = DataIndicator{parent=crd_box,x=41,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
||||
TextBox{parent=crd_box,x=47,y=2,text="ms",width=4,fg_bg=label_fg}
|
||||
crd_rtt.register(databus.ps, "crd_rtt", crd_rtt.update)
|
||||
crd_rtt.register(databus.ps, "crd_rtt_color", crd_rtt.recolor)
|
||||
|
||||
-- pocket sessions page
|
||||
|
||||
local pkt_page = Div{parent=page_div,y=1,hidden=true}
|
||||
local pdg_list = ListBox{parent=pkt_page,y=1,height=term_h-2,width=term_w,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||
local pkt_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||
local pdg_list = ListBox{parent=pkt_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||
local _ = Div{parent=pdg_list,height=1} -- padding
|
||||
|
||||
-- RTU device ID check/diagnostics page
|
||||
|
||||
local chk_page = Div{parent=page_div,y=1,hidden=true}
|
||||
local chk_list = ListBox{parent=chk_page,y=1,height=term_h-2,width=term_w,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||
local chk_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||
local chk_list = ListBox{parent=chk_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||
local _ = Div{parent=chk_list,height=1} -- padding
|
||||
|
||||
-- info page
|
||||
|
||||
local info_page = Div{parent=page_div,y=1,hidden=true}
|
||||
local info_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||
local info = Div{parent=info_page,height=6,x=2,y=2}
|
||||
|
||||
TextBox{parent=info,text="SVR \x1a Supervisor Status"}
|
||||
@ -170,7 +168,7 @@ local function init(panel)
|
||||
TextBox{parent=info,text="PKT \x1a Pocket Connections"}
|
||||
TextBox{parent=info,text="DEV \x1a RTU Device/Configuration Alerts"}
|
||||
|
||||
local notes = Div{parent=info_page,width=term_w-2,height=8,x=2,y=9,fg_bg=style.fp.disabled_fg}
|
||||
local notes = Div{parent=info_page,width=49,height=8,x=2,y=9,fg_bg=style.fp.disabled_fg}
|
||||
|
||||
TextBox{parent=notes,text="The DEV tab will show missing devices and devices that connected with incorrect information. Missing entries will indicate how the configuration should be, duplicate entries will indicate what is a duplicate, and out-of-range entries will indicate the invalid entry. An out-of-range example is a #2 turbine when you should only have 1 turbine for that unit."}
|
||||
|
||||
|
||||
@ -234,23 +234,6 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
||||
if pkt.type == CRDN_TYPE.INITIAL_BUILDS then
|
||||
-- acknowledgement to coordinator receiving builds
|
||||
self.acks.builds = true
|
||||
elseif pkt.type == CRDN_TYPE.PROCESS_READY then
|
||||
if pkt.length == 5 then
|
||||
-- coordinator has sent all initial process data, power-on recovery is now possible
|
||||
|
||||
---@type start_auto_config
|
||||
local config = {
|
||||
mode = pkt.data[1],
|
||||
burn_target = pkt.data[2],
|
||||
charge_target = pkt.data[3],
|
||||
gen_target = pkt.data[4],
|
||||
limits = pkt.data[5]
|
||||
}
|
||||
|
||||
facility.boot_recovery_start(config)
|
||||
else
|
||||
log.debug(log_tag .. "CRDN process ready packet length mismatch")
|
||||
end
|
||||
elseif pkt.type == CRDN_TYPE.FAC_BUILDS then
|
||||
-- acknowledgement to coordinator receiving builds
|
||||
self.acks.fac_builds = true
|
||||
@ -260,11 +243,8 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
||||
|
||||
if cmd == FAC_COMMAND.SCRAM_ALL then
|
||||
facility.scram_all()
|
||||
facility.cancel_recovery()
|
||||
_send(CRDN_TYPE.FAC_CMD, { cmd, true })
|
||||
elseif cmd == FAC_COMMAND.STOP then
|
||||
facility.cancel_recovery()
|
||||
|
||||
local was_active = facility.auto_is_active()
|
||||
|
||||
if was_active then
|
||||
@ -273,16 +253,15 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
||||
|
||||
_send(CRDN_TYPE.FAC_CMD, { cmd, was_active })
|
||||
elseif cmd == FAC_COMMAND.START then
|
||||
facility.cancel_recovery()
|
||||
|
||||
if pkt.length == 6 then
|
||||
---@class start_auto_config
|
||||
---@type sys_auto_config
|
||||
---@diagnostic disable-next-line: missing-fields
|
||||
local config = {
|
||||
mode = pkt.data[2], ---@type PROCESS
|
||||
burn_target = pkt.data[3], ---@type number
|
||||
charge_target = pkt.data[4], ---@type number
|
||||
gen_target = pkt.data[5], ---@type number
|
||||
limits = pkt.data[6] ---@type number[]
|
||||
mode = pkt.data[2],
|
||||
burn_target = pkt.data[3],
|
||||
charge_target = pkt.data[4],
|
||||
gen_target = pkt.data[5],
|
||||
limits = pkt.data[6]
|
||||
}
|
||||
|
||||
_send(CRDN_TYPE.FAC_CMD, { cmd, table.unpack(facility.auto_start(config)) })
|
||||
@ -334,11 +313,8 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
||||
local manual = facility.get_group(uid) == AUTO_GROUP.MANUAL
|
||||
|
||||
if cmd == UNIT_COMMAND.SCRAM then
|
||||
facility.cancel_recovery()
|
||||
out_queue.push_data(SV_Q_DATA.SCRAM, data)
|
||||
elseif cmd == UNIT_COMMAND.START then
|
||||
facility.cancel_recovery()
|
||||
|
||||
if manual then
|
||||
out_queue.push_data(SV_Q_DATA.START, data)
|
||||
else
|
||||
@ -348,8 +324,6 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
||||
elseif cmd == UNIT_COMMAND.RESET_RPS then
|
||||
out_queue.push_data(SV_Q_DATA.RESET_RPS, data)
|
||||
elseif cmd == UNIT_COMMAND.SET_BURN then
|
||||
facility.cancel_recovery()
|
||||
|
||||
if pkt.length == 3 then
|
||||
if manual then
|
||||
out_queue.push_data(SV_Q_DATA.SET_BURN, data)
|
||||
@ -380,8 +354,6 @@ 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")
|
||||
end
|
||||
elseif cmd == UNIT_COMMAND.SET_GROUP then
|
||||
facility.cancel_recovery()
|
||||
|
||||
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])
|
||||
|
||||
@ -53,15 +53,15 @@ local PERIODICS = {
|
||||
---@param in_queue mqueue in message queue
|
||||
---@param out_queue mqueue out message queue
|
||||
---@param timeout number communications timeout
|
||||
---@param initial_reset boolean[] initial PLC reset on timeout flags, indexed by reactor_id
|
||||
---@param fp_ok boolean if the front panel UI is running
|
||||
function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue, timeout, initial_reset, fp_ok)
|
||||
function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue, timeout, fp_ok)
|
||||
-- print a log message to the terminal as long as the UI isn't running
|
||||
local function println(message) if not fp_ok then util.println_ts(message) end end
|
||||
|
||||
local log_tag = "plc_session(" .. id .. "): "
|
||||
|
||||
local self = {
|
||||
commanded_state = false,
|
||||
commanded_burn_rate = 0.0,
|
||||
auto_cmd_token = 0,
|
||||
ramping_rate = false,
|
||||
@ -72,7 +72,6 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
||||
connected = true,
|
||||
received_struct = false,
|
||||
received_status_cache = false,
|
||||
received_rps_status = false,
|
||||
conn_watchdog = util.new_watchdog(timeout),
|
||||
last_rtt = 0,
|
||||
-- periodic messages
|
||||
@ -382,16 +381,6 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
||||
local status = pcall(_copy_rps_status, pkt.data)
|
||||
if status then
|
||||
-- copied in RPS status data OK
|
||||
self.received_rps_status = true
|
||||
|
||||
-- try initial reset if needed
|
||||
if initial_reset[reactor_id] then
|
||||
initial_reset[reactor_id] = false
|
||||
if self.sDB.rps_trip_cause == "timeout" then
|
||||
_send(RPLC_TYPE.RPS_AUTO_RESET, {})
|
||||
log.debug(log_tag .. "initial RPS reset on timeout status sent")
|
||||
end
|
||||
end
|
||||
else
|
||||
-- error copying RPS status data
|
||||
log.error(log_tag .. "failed to parse RPS status packet data")
|
||||
@ -405,16 +394,6 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
||||
local status = pcall(_copy_rps_status, { true, table.unpack(pkt.data) })
|
||||
if status then
|
||||
-- copied in RPS status data OK
|
||||
self.received_rps_status = true
|
||||
|
||||
-- try initial reset if needed
|
||||
if initial_reset[reactor_id] then
|
||||
initial_reset[reactor_id] = false
|
||||
if self.sDB.rps_trip_cause == "timeout" then
|
||||
_send(RPLC_TYPE.RPS_AUTO_RESET, {})
|
||||
log.debug(log_tag .. "initial RPS reset on timeout alarm sent")
|
||||
end
|
||||
end
|
||||
else
|
||||
-- error copying RPS status data
|
||||
log.error(log_tag .. "failed to parse RPS alarm status data")
|
||||
@ -508,10 +487,6 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
||||
---@nodiscard
|
||||
function public.get_db() return self.sDB end
|
||||
|
||||
-- check if the reactor structure, status, and RPS status have been received
|
||||
---@nodiscard
|
||||
function public.check_received_all_data() return self.received_struct and self.received_status_cache and self.received_rps_status end
|
||||
|
||||
-- check if ramping is completed by first verifying auto command token ack
|
||||
---@nodiscard
|
||||
function public.is_ramp_complete()
|
||||
|
||||
@ -2,12 +2,10 @@ local comms = require("scada-common.comms")
|
||||
local log = require("scada-common.log")
|
||||
local mqueue = require("scada-common.mqueue")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local databus = require("supervisor.databus")
|
||||
|
||||
local pocket = {}
|
||||
|
||||
local DEV_TYPE = comms.DEVICE_TYPE
|
||||
local PROTOCOL = comms.PROTOCOL
|
||||
local MGMT_TYPE = comms.MGMT_TYPE
|
||||
|
||||
@ -36,10 +34,9 @@ local PERIODICS = {
|
||||
---@param in_queue mqueue in message queue
|
||||
---@param out_queue mqueue out message queue
|
||||
---@param timeout number communications timeout
|
||||
---@param sessions svsessions_list list of computer sessions, read-only
|
||||
---@param facility facility facility data table
|
||||
---@param fp_ok boolean if the front panel UI is running
|
||||
function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, sessions, facility, fp_ok)
|
||||
function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, facility, fp_ok)
|
||||
-- print a log message to the terminal as long as the UI isn't running
|
||||
local function println(message) if not fp_ok then util.println_ts(message) end end
|
||||
|
||||
@ -185,47 +182,6 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout,
|
||||
end
|
||||
|
||||
if not valid then _send_mgmt(MGMT_TYPE.DIAG_ALARM_SET, { false }) end
|
||||
elseif pkt.type == MGMT_TYPE.INFO_LIST_CMP then
|
||||
local get = databus.ps.get
|
||||
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
local devices = { { DEV_TYPE.SVR, os.getComputerID(), get("version"), 0 } }
|
||||
|
||||
-- add the coordinator if connected
|
||||
if get("crd_conn") then
|
||||
table.insert(devices, { DEV_TYPE.CRD, get("crd_addr"), get("crd_fw"), get("crd_rtt") })
|
||||
end
|
||||
|
||||
-- add the PLCs if connected
|
||||
for i = 1, #facility.get_units() do
|
||||
local tag = "plc_" .. i
|
||||
|
||||
local addr = -1
|
||||
for _, s in ipairs(sessions.plc) do
|
||||
if s.reactor == i then
|
||||
addr = s.s_addr
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if get(tag .. "_conn") then
|
||||
table.insert(devices, { DEV_TYPE.PLC, addr, get(tag .. "_fw"), get(tag .. "_rtt"), i })
|
||||
end
|
||||
end
|
||||
|
||||
-- add connected RTUs
|
||||
for i = 1, #sessions.rtu do
|
||||
local s = sessions.rtu[i]
|
||||
table.insert(devices, { DEV_TYPE.RTU, s.s_addr, s.version, get("rtu_" .. s.instance.get_id() .. "_rtt") })
|
||||
end
|
||||
|
||||
-- add connected pocket computers
|
||||
for i = 1, #sessions.pdg do
|
||||
local s = sessions.pdg[i]
|
||||
table.insert(devices, { DEV_TYPE.PKT, s.s_addr, s.version, get("pdg_" .. s.instance.get_id() .. "_rtt") })
|
||||
end
|
||||
|
||||
_send_mgmt(MGMT_TYPE.INFO_LIST_CMP, devices)
|
||||
else
|
||||
log.debug(log_tag .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type)
|
||||
end
|
||||
|
||||
@ -9,8 +9,7 @@ local rsctl = {}
|
||||
-- create a new redstone RTU I/O controller
|
||||
---@nodiscard
|
||||
---@param redstone_rtus redstone_session[] redstone RTU sessions
|
||||
---@param bank integer I/O bank (unit/facility assignment) to interface with
|
||||
function rsctl.new(redstone_rtus, bank)
|
||||
function rsctl.new(redstone_rtus)
|
||||
---@class rs_controller
|
||||
local public = {}
|
||||
|
||||
@ -19,7 +18,7 @@ function rsctl.new(redstone_rtus, bank)
|
||||
---@return boolean
|
||||
function public.is_connected(port)
|
||||
for i = 1, #redstone_rtus do
|
||||
if redstone_rtus[i].get_db().io[bank][port] ~= nil then return true end
|
||||
if redstone_rtus[i].get_db().io[port] ~= nil then return true end
|
||||
end
|
||||
|
||||
return false
|
||||
@ -30,7 +29,7 @@ function rsctl.new(redstone_rtus, bank)
|
||||
---@param value boolean
|
||||
function public.digital_write(port, value)
|
||||
for i = 1, #redstone_rtus do
|
||||
local io = redstone_rtus[i].get_db().io[bank][port]
|
||||
local io = redstone_rtus[i].get_db().io[port]
|
||||
if io ~= nil then io.write(value) end
|
||||
end
|
||||
end
|
||||
@ -41,7 +40,7 @@ function rsctl.new(redstone_rtus, bank)
|
||||
---@return boolean|nil
|
||||
function public.digital_read(port)
|
||||
for i = 1, #redstone_rtus do
|
||||
local io = redstone_rtus[i].get_db().io[bank][port]
|
||||
local io = redstone_rtus[i].get_db().io[port]
|
||||
if io ~= nil then return io.read() --[[@as boolean|nil]] end
|
||||
end
|
||||
end
|
||||
@ -53,7 +52,7 @@ function rsctl.new(redstone_rtus, bank)
|
||||
---@param max number maximum value for scaling 0 to 15
|
||||
function public.analog_write(port, value, min, max)
|
||||
for i = 1, #redstone_rtus do
|
||||
local io = redstone_rtus[i].get_db().io[bank][port]
|
||||
local io = redstone_rtus[i].get_db().io[port]
|
||||
if io ~= nil then io.write(rsio.analog_write(value, min, max)) end
|
||||
end
|
||||
end
|
||||
|
||||
@ -93,7 +93,7 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
||||
type = self.advert[i][1],
|
||||
index = self.advert[i][2],
|
||||
reactor = self.advert[i][3],
|
||||
rs_conns = self.advert[i][4]
|
||||
rsio = self.advert[i][4]
|
||||
}
|
||||
|
||||
local u_type = unit_advert.type ---@type RTU_UNIT_TYPE|boolean
|
||||
@ -104,17 +104,14 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
||||
advert_validator.assert(util.is_int(unit_advert.index) or (unit_advert.index == false))
|
||||
advert_validator.assert_type_int(unit_advert.reactor)
|
||||
|
||||
if u_type == RTU_UNIT_TYPE.REDSTONE then
|
||||
advert_validator.assert_type_table(unit_advert.rsio)
|
||||
end
|
||||
|
||||
if advert_validator.valid() then
|
||||
if util.is_int(unit_advert.index) then advert_validator.assert_min(unit_advert.index, 1) end
|
||||
|
||||
if (unit_advert.reactor == -1) or (u_type == RTU_UNIT_TYPE.REDSTONE) then
|
||||
advert_validator.assert((unit_advert.reactor == -1) and (u_type == RTU_UNIT_TYPE.REDSTONE))
|
||||
advert_validator.assert_type_table(unit_advert.rs_conns)
|
||||
else
|
||||
advert_validator.assert_min(unit_advert.reactor, 0)
|
||||
advert_validator.assert_max(unit_advert.reactor, #self.fac_units)
|
||||
end
|
||||
|
||||
advert_validator.assert_min(unit_advert.reactor, 0)
|
||||
advert_validator.assert_max(unit_advert.reactor, #self.fac_units)
|
||||
if not advert_validator.valid() then u_type = false end
|
||||
else
|
||||
u_type = false
|
||||
@ -129,34 +126,15 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
||||
-- validation fail
|
||||
log.debug(log_tag .. "_handle_advertisement(): advertisement unit validation failure")
|
||||
else
|
||||
if unit_advert.reactor == -1 then
|
||||
-- redstone RTUs can be used in multiple different assignments
|
||||
if u_type == RTU_UNIT_TYPE.REDSTONE then
|
||||
-- redstone
|
||||
unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q)
|
||||
|
||||
-- link this to any subsystems this RTU provides connections for
|
||||
if type(unit) ~= "nil" then
|
||||
for assignment, conns in pairs(unit_advert.rs_conns) do
|
||||
if #conns > 0 then
|
||||
if assignment == 0 then
|
||||
facility.add_redstone(unit)
|
||||
elseif assignment > 0 and assignment <= #self.fac_units then
|
||||
self.fac_units[assignment].add_redstone(unit)
|
||||
else
|
||||
log.warning(util.c(log_tag, "_handle_advertisement(): invalid redstone RTU assignment ", assignment))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
log.warning(util.c(log_tag, "_handle_advertisement(): encountered unsupported multi-assignment RTU type ", type_string))
|
||||
end
|
||||
elseif unit_advert.reactor > 0 then
|
||||
if unit_advert.reactor > 0 then
|
||||
local target_unit = self.fac_units[unit_advert.reactor]
|
||||
|
||||
-- unit RTUs
|
||||
if u_type == RTU_UNIT_TYPE.BOILER_VALVE then
|
||||
if u_type == RTU_UNIT_TYPE.REDSTONE then
|
||||
-- redstone
|
||||
unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q)
|
||||
if type(unit) ~= "nil" then target_unit.add_redstone(unit) end
|
||||
elseif u_type == RTU_UNIT_TYPE.BOILER_VALVE then
|
||||
-- boiler
|
||||
unit = svrs_boilerv.new(id, i, unit_advert, self.modbus_q)
|
||||
if type(unit) ~= "nil" then target_unit.add_boiler(unit) end
|
||||
|
||||
@ -10,6 +10,7 @@ local redstone = {}
|
||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
local MODBUS_FCODE = types.MODBUS_FCODE
|
||||
|
||||
local IO_PORT = rsio.IO
|
||||
local IO_LVL = rsio.IO_LVL
|
||||
local IO_MODE = rsio.IO_MODE
|
||||
|
||||
@ -38,9 +39,6 @@ local PERIODICS = {
|
||||
OUTPUT_SYNC = 200
|
||||
}
|
||||
|
||||
-- create a new block of IO banks (facility, then each unit)
|
||||
local function new_io_block() return { [0] = {}, {}, {}, {}, {} } end
|
||||
|
||||
---@class dig_phy_entry
|
||||
---@field phy IO_LVL actual value
|
||||
---@field req IO_LVL commanded value
|
||||
@ -76,27 +74,27 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
next_ir_req = 0,
|
||||
next_hr_sync = 0
|
||||
},
|
||||
---@class rs_io_map
|
||||
io_map = {
|
||||
digital_in = {}, ---@type { bank: integer, port: IO_PORT }[] discrete inputs
|
||||
digital_out = {}, ---@type { bank: integer, port: IO_PORT }[] coils
|
||||
analog_in = {}, ---@type { bank: integer, port: IO_PORT }[] input registers
|
||||
analog_out = {} ---@type { bank: integer, port: IO_PORT }[] holding registers
|
||||
---@class rs_io_list
|
||||
io_list = {
|
||||
digital_in = {}, ---@type IO_PORT[] discrete inputs
|
||||
digital_out = {}, ---@type IO_PORT[] coils
|
||||
analog_in = {}, ---@type IO_PORT[] input registers
|
||||
analog_out = {} ---@type IO_PORT[] holding registers
|
||||
},
|
||||
phy_trans = { coils = -1, hold_regs = -1 },
|
||||
-- last set/read ports (reflecting the current state of the RTU)
|
||||
---@class rs_io_states
|
||||
phy_io = {
|
||||
digital_in = new_io_block(), ---@type dig_phy_entry[][] discrete inputs
|
||||
digital_out = new_io_block(), ---@type dig_phy_entry[][] coils
|
||||
analog_in = new_io_block(), ---@type ana_phy_entry[][] input registers
|
||||
analog_out = new_io_block() ---@type ana_phy_entry[][] holding registers
|
||||
digital_in = {}, ---@type dig_phy_entry[] discrete inputs
|
||||
digital_out = {}, ---@type dig_phy_entry[] coils
|
||||
analog_in = {}, ---@type ana_phy_entry[] input registers
|
||||
analog_out = {} ---@type ana_phy_entry[] holding registers
|
||||
},
|
||||
---@class redstone_session_db
|
||||
db = {
|
||||
-- read/write functions for connected I/O
|
||||
---@type (rs_db_dig_io|rs_db_ana_io)[][]
|
||||
io = new_io_block()
|
||||
---@type (rs_db_dig_io|rs_db_ana_io)[]
|
||||
io = {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,91 +103,93 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
|
||||
-- INITIALIZE --
|
||||
|
||||
-- create all ports as disconnected
|
||||
for _ = 1, #IO_PORT do
|
||||
table.insert(self.db, IO_LVL.DISCONNECT)
|
||||
end
|
||||
|
||||
-- setup I/O
|
||||
for bank = 0, 4 do
|
||||
for i = 1, #advert.rs_conns[bank] do
|
||||
local port = advert.rs_conns[bank][i]
|
||||
for i = 1, #advert.rsio do
|
||||
local port = advert.rsio[i]
|
||||
|
||||
if rsio.is_valid_port(port) then
|
||||
local mode = rsio.get_io_mode(port)
|
||||
local io_entry = { bank = bank, port = port }
|
||||
if rsio.is_valid_port(port) then
|
||||
local mode = rsio.get_io_mode(port)
|
||||
|
||||
if mode == IO_MODE.DIGITAL_IN then
|
||||
self.has_di = true
|
||||
table.insert(self.io_map.digital_in, io_entry)
|
||||
if mode == IO_MODE.DIGITAL_IN then
|
||||
self.has_di = true
|
||||
table.insert(self.io_list.digital_in, port)
|
||||
|
||||
self.phy_io.digital_in[bank][port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING }
|
||||
self.phy_io.digital_in[port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING }
|
||||
|
||||
---@class rs_db_dig_io
|
||||
local io_f = {
|
||||
---@nodiscard
|
||||
read = function () return rsio.digital_is_active(port, self.phy_io.digital_in[bank][port].phy) end,
|
||||
write = function () end
|
||||
}
|
||||
---@class rs_db_dig_io
|
||||
local io_f = {
|
||||
---@nodiscard
|
||||
read = function () return rsio.digital_is_active(port, self.phy_io.digital_in[port].phy) end,
|
||||
write = function () end
|
||||
}
|
||||
|
||||
self.db.io[bank][port] = io_f
|
||||
elseif mode == IO_MODE.DIGITAL_OUT then
|
||||
self.has_do = true
|
||||
table.insert(self.io_map.digital_out, io_entry)
|
||||
self.db.io[port] = io_f
|
||||
elseif mode == IO_MODE.DIGITAL_OUT then
|
||||
self.has_do = true
|
||||
table.insert(self.io_list.digital_out, port)
|
||||
|
||||
self.phy_io.digital_out[bank][port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING }
|
||||
self.phy_io.digital_out[port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING }
|
||||
|
||||
---@class rs_db_dig_io
|
||||
local io_f = {
|
||||
---@nodiscard
|
||||
read = function () return rsio.digital_is_active(port, self.phy_io.digital_out[bank][port].phy) end,
|
||||
---@param active boolean
|
||||
write = function (active)
|
||||
local level = rsio.digital_write_active(port, active)
|
||||
if level ~= nil then self.phy_io.digital_out[bank][port].req = level end
|
||||
---@class rs_db_dig_io
|
||||
local io_f = {
|
||||
---@nodiscard
|
||||
read = function () return rsio.digital_is_active(port, self.phy_io.digital_out[port].phy) end,
|
||||
---@param active boolean
|
||||
write = function (active)
|
||||
local level = rsio.digital_write_active(port, active)
|
||||
if level ~= nil then self.phy_io.digital_out[port].req = level end
|
||||
end
|
||||
}
|
||||
|
||||
self.db.io[port] = io_f
|
||||
elseif mode == IO_MODE.ANALOG_IN then
|
||||
self.has_ai = true
|
||||
table.insert(self.io_list.analog_in, port)
|
||||
|
||||
self.phy_io.analog_in[port] = { phy = 0, req = 0 }
|
||||
|
||||
---@class rs_db_ana_io
|
||||
local io_f = {
|
||||
---@nodiscard
|
||||
---@return integer
|
||||
read = function () return self.phy_io.analog_in[port].phy end,
|
||||
write = function () end
|
||||
}
|
||||
|
||||
self.db.io[port] = io_f
|
||||
elseif mode == IO_MODE.ANALOG_OUT then
|
||||
self.has_ao = true
|
||||
table.insert(self.io_list.analog_out, port)
|
||||
|
||||
self.phy_io.analog_out[port] = { phy = 0, req = 0 }
|
||||
|
||||
---@class rs_db_ana_io
|
||||
local io_f = {
|
||||
---@nodiscard
|
||||
---@return integer
|
||||
read = function () return self.phy_io.analog_out[port].phy end,
|
||||
---@param value integer
|
||||
write = function (value)
|
||||
if value >= 0 and value <= 15 then
|
||||
self.phy_io.analog_out[port].req = value
|
||||
end
|
||||
}
|
||||
end
|
||||
}
|
||||
|
||||
self.db.io[bank][port] = io_f
|
||||
elseif mode == IO_MODE.ANALOG_IN then
|
||||
self.has_ai = true
|
||||
table.insert(self.io_map.analog_in, io_entry)
|
||||
|
||||
self.phy_io.analog_in[bank][port] = { phy = 0, req = 0 }
|
||||
|
||||
---@class rs_db_ana_io
|
||||
local io_f = {
|
||||
---@nodiscard
|
||||
---@return integer
|
||||
read = function () return self.phy_io.analog_in[bank][port].phy end,
|
||||
write = function () end
|
||||
}
|
||||
|
||||
self.db.io[bank][port] = io_f
|
||||
elseif mode == IO_MODE.ANALOG_OUT then
|
||||
self.has_ao = true
|
||||
table.insert(self.io_map.analog_out, io_entry)
|
||||
|
||||
self.phy_io.analog_out[bank][port] = { phy = 0, req = 0 }
|
||||
|
||||
---@class rs_db_ana_io
|
||||
local io_f = {
|
||||
---@nodiscard
|
||||
---@return integer
|
||||
read = function () return self.phy_io.analog_out[bank][port].phy end,
|
||||
---@param value integer
|
||||
write = function (value)
|
||||
if value >= 0 and value <= 15 then
|
||||
self.phy_io.analog_out[bank][port].req = value
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
self.db.io[bank][port] = io_f
|
||||
else
|
||||
-- should be unreachable code, we already validated ports
|
||||
log.error(util.c(log_tag, "failed to identify advertisement port IO mode (", bank, ":", port, ")"), true)
|
||||
return nil
|
||||
end
|
||||
self.db.io[port] = io_f
|
||||
else
|
||||
log.error(util.c(log_tag, "invalid advertisement port (", bank, ":", port, ")"), true)
|
||||
-- should be unreachable code, we already validated ports
|
||||
log.error(util.c(log_tag, "failed to identify advertisement port IO mode (", port, ")"), true)
|
||||
return nil
|
||||
end
|
||||
else
|
||||
log.error(util.c(log_tag, "invalid advertisement port (", port, ")"), true)
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
@ -197,12 +197,12 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
|
||||
-- query discrete inputs
|
||||
local function _request_discrete_inputs()
|
||||
self.session.send_request(TXN_TYPES.DI_READ, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, #self.io_map.digital_in })
|
||||
self.session.send_request(TXN_TYPES.DI_READ, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, #self.io_list.digital_in })
|
||||
end
|
||||
|
||||
-- query input registers
|
||||
local function _request_input_registers()
|
||||
self.session.send_request(TXN_TYPES.INPUT_REG_READ, MODBUS_FCODE.READ_INPUT_REGS, { 1, #self.io_map.analog_in })
|
||||
self.session.send_request(TXN_TYPES.INPUT_REG_READ, MODBUS_FCODE.READ_INPUT_REGS, { 1, #self.io_list.analog_in })
|
||||
end
|
||||
|
||||
-- write all coil outputs
|
||||
@ -210,9 +210,9 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
local params = { 1 }
|
||||
|
||||
local outputs = self.phy_io.digital_out
|
||||
for i = 1, #self.io_map.digital_out do
|
||||
local entry = self.io_map.digital_out[i]
|
||||
table.insert(params, outputs[entry.bank][entry.port].req)
|
||||
for i = 1, #self.io_list.digital_out do
|
||||
local port = self.io_list.digital_out[i]
|
||||
table.insert(params, outputs[port].req)
|
||||
end
|
||||
|
||||
self.phy_trans.coils = self.session.send_request(TXN_TYPES.COIL_WRITE, MODBUS_FCODE.WRITE_MUL_COILS, params)
|
||||
@ -220,7 +220,7 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
|
||||
-- read all coil outputs
|
||||
local function _read_coils()
|
||||
self.session.send_request(TXN_TYPES.COIL_READ, MODBUS_FCODE.READ_COILS, { 1, #self.io_map.digital_out })
|
||||
self.session.send_request(TXN_TYPES.COIL_READ, MODBUS_FCODE.READ_COILS, { 1, #self.io_list.digital_out })
|
||||
end
|
||||
|
||||
-- write all holding register outputs
|
||||
@ -228,9 +228,9 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
local params = { 1 }
|
||||
|
||||
local outputs = self.phy_io.analog_out
|
||||
for i = 1, #self.io_map.analog_out do
|
||||
local entry = self.io_map.analog_out[i]
|
||||
table.insert(params, outputs[entry.bank][entry.port].req)
|
||||
for i = 1, #self.io_list.analog_out do
|
||||
local port = self.io_list.analog_out[i]
|
||||
table.insert(params, outputs[port].req)
|
||||
end
|
||||
|
||||
self.phy_trans.hold_regs = self.session.send_request(TXN_TYPES.HOLD_REG_WRITE, MODBUS_FCODE.WRITE_MUL_HOLD_REGS, params)
|
||||
@ -238,7 +238,7 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
|
||||
-- read all holding register outputs
|
||||
local function _read_holding_registers()
|
||||
self.session.send_request(TXN_TYPES.HOLD_REG_READ, MODBUS_FCODE.READ_MUL_HOLD_REGS, { 1, #self.io_map.analog_out })
|
||||
self.session.send_request(TXN_TYPES.HOLD_REG_READ, MODBUS_FCODE.READ_MUL_HOLD_REGS, { 1, #self.io_list.analog_out })
|
||||
end
|
||||
|
||||
-- PUBLIC FUNCTIONS --
|
||||
@ -259,24 +259,24 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
end
|
||||
elseif txn_type == TXN_TYPES.DI_READ then
|
||||
-- discrete input read response
|
||||
if m_pkt.length == #self.io_map.digital_in then
|
||||
if m_pkt.length == #self.io_list.digital_in then
|
||||
for i = 1, m_pkt.length do
|
||||
local entry = self.io_map.digital_in[i]
|
||||
local port = self.io_list.digital_in[i]
|
||||
local value = m_pkt.data[i]
|
||||
|
||||
self.phy_io.digital_in[entry.bank][entry.port].phy = value
|
||||
self.phy_io.digital_in[port].phy = value
|
||||
end
|
||||
else
|
||||
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||
end
|
||||
elseif txn_type == TXN_TYPES.INPUT_REG_READ then
|
||||
-- input register read response
|
||||
if m_pkt.length == #self.io_map.analog_in then
|
||||
if m_pkt.length == #self.io_list.analog_in then
|
||||
for i = 1, m_pkt.length do
|
||||
local entry = self.io_map.analog_in[i]
|
||||
local port = self.io_list.analog_in[i]
|
||||
local value = m_pkt.data[i]
|
||||
|
||||
self.phy_io.analog_in[entry.bank][entry.port].phy = value
|
||||
self.phy_io.analog_in[port].phy = value
|
||||
end
|
||||
else
|
||||
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||
@ -288,14 +288,15 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
-- update phy I/O table
|
||||
-- if there are multiple outputs for the same port, they will overwrite eachother (but *should* be identical)
|
||||
-- given these are redstone outputs, if one worked they all should have, so no additional verification will be done
|
||||
if m_pkt.length == #self.io_map.digital_out then
|
||||
if m_pkt.length == #self.io_list.digital_out then
|
||||
for i = 1, m_pkt.length do
|
||||
local entry = self.io_map.digital_out[i]
|
||||
local state = self.phy_io.digital_out[entry.bank][entry.port]
|
||||
local port = self.io_list.digital_out[i]
|
||||
local value = m_pkt.data[i]
|
||||
|
||||
state.phy = value
|
||||
if state.req == IO_LVL.FLOATING then state.req = value end
|
||||
self.phy_io.digital_out[port].phy = value
|
||||
if self.phy_io.digital_out[port].req == IO_LVL.FLOATING then
|
||||
self.phy_io.digital_out[port].req = value
|
||||
end
|
||||
end
|
||||
|
||||
self.phy_trans.coils = TXN_READY
|
||||
@ -309,12 +310,12 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
-- update phy I/O table
|
||||
-- if there are multiple outputs for the same port, they will overwrite eachother (but *should* be identical)
|
||||
-- given these are redstone outputs, if one worked they all should have, so no additional verification will be done
|
||||
if m_pkt.length == #self.io_map.analog_out then
|
||||
if m_pkt.length == #self.io_list.analog_out then
|
||||
for i = 1, m_pkt.length do
|
||||
local entry = self.io_map.analog_out[i]
|
||||
local port = self.io_list.analog_out[i]
|
||||
local value = m_pkt.data[i]
|
||||
|
||||
self.phy_io.analog_out[entry.bank][entry.port].phy = value
|
||||
self.phy_io.analog_out[port].phy = value
|
||||
end
|
||||
else
|
||||
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||
@ -342,17 +343,8 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
-- sync digital outputs
|
||||
if self.has_do then
|
||||
if (self.periodics.next_cl_sync <= time_now) and (self.phy_trans.coils == TXN_READY) then
|
||||
for bank = 0, 4 do
|
||||
local changed = false
|
||||
|
||||
for _, entry in pairs(self.phy_io.digital_out[bank]) do
|
||||
if entry.phy ~= entry.req then
|
||||
changed = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if changed then
|
||||
for _, entry in pairs(self.phy_io.digital_out) do
|
||||
if entry.phy ~= entry.req then
|
||||
_write_coils()
|
||||
break
|
||||
end
|
||||
@ -373,17 +365,8 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
-- sync analog outputs
|
||||
if self.has_ao then
|
||||
if (self.periodics.next_hr_sync <= time_now) and (self.phy_trans.hold_regs == TXN_READY) then
|
||||
for bank = 0, 4 do
|
||||
local changed = false
|
||||
|
||||
for _, entry in pairs(self.phy_io.analog_out[bank]) do
|
||||
if entry.phy ~= entry.req then
|
||||
changed = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if changed then
|
||||
for _, entry in pairs(self.phy_io.analog_out) do
|
||||
if entry.phy ~= entry.req then
|
||||
_write_holding_registers()
|
||||
break
|
||||
end
|
||||
@ -396,10 +379,9 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
self.session.post_update()
|
||||
end
|
||||
|
||||
-- force a re-read of cached outputs
|
||||
-- invalidate build cache
|
||||
function public.invalidate_cache()
|
||||
_read_coils()
|
||||
_read_holding_registers()
|
||||
-- no build cache for this device
|
||||
end
|
||||
|
||||
-- get the unit session database
|
||||
|
||||
@ -45,15 +45,13 @@ local self = {
|
||||
fp_ok = false,
|
||||
config = nil, ---@type svr_config
|
||||
facility = nil, ---@type facility|nil
|
||||
plc_ini_reset = {},
|
||||
-- lists of connected sessions
|
||||
---@class svsessions_list
|
||||
---@diagnostic disable: missing-fields
|
||||
sessions = {
|
||||
rtu = {}, ---@type rtu_session_struct[]
|
||||
plc = {}, ---@type plc_session_struct[]
|
||||
crd = {}, ---@type crd_session_struct[]
|
||||
pdg = {} ---@type pdg_session_struct[]
|
||||
rtu = {}, ---@type rtu_session_struct
|
||||
plc = {}, ---@type plc_session_struct
|
||||
crd = {}, ---@type crd_session_struct
|
||||
pdg = {} ---@type pdg_session_struct
|
||||
},
|
||||
---@diagnostic enable: missing-fields
|
||||
-- next session IDs
|
||||
@ -393,7 +391,6 @@ function svsessions.init(nic, fp_ok, config, facility)
|
||||
conns.tanks[1] = true
|
||||
end
|
||||
|
||||
self.plc_ini_reset[i] = true
|
||||
self.dev_dbg.connected.units[i] = conns
|
||||
end
|
||||
end
|
||||
@ -489,7 +486,7 @@ function svsessions.establish_plc_session(source_addr, i_seq_num, for_reactor, v
|
||||
|
||||
local id = self.next_ids.plc
|
||||
|
||||
plc_s.instance = plc.new_session(id, source_addr, i_seq_num, for_reactor, plc_s.in_queue, plc_s.out_queue, self.config.PLC_Timeout, self.plc_ini_reset, self.fp_ok)
|
||||
plc_s.instance = plc.new_session(id, source_addr, i_seq_num, for_reactor, plc_s.in_queue, plc_s.out_queue, self.config.PLC_Timeout, self.fp_ok)
|
||||
table.insert(self.sessions.plc, plc_s)
|
||||
|
||||
local units = self.facility.get_units()
|
||||
@ -622,7 +619,7 @@ function svsessions.establish_pdg_session(source_addr, i_seq_num, version)
|
||||
|
||||
local id = self.next_ids.pdg
|
||||
|
||||
pdg_s.instance = pocket.new_session(id, source_addr, i_seq_num, pdg_s.in_queue, pdg_s.out_queue, self.config.PKT_Timeout, self.sessions, self.facility, self.fp_ok)
|
||||
pdg_s.instance = pocket.new_session(id, source_addr, i_seq_num, pdg_s.in_queue, pdg_s.out_queue, self.config.PKT_Timeout, self.facility, self.fp_ok)
|
||||
table.insert(self.sessions.pdg, pdg_s)
|
||||
|
||||
local mt = {
|
||||
|
||||
@ -10,7 +10,6 @@ local log = require("scada-common.log")
|
||||
local network = require("scada-common.network")
|
||||
local ppm = require("scada-common.ppm")
|
||||
local tcd = require("scada-common.tcd")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local core = require("graphics.core")
|
||||
@ -23,7 +22,7 @@ local supervisor = require("supervisor.supervisor")
|
||||
|
||||
local svsessions = require("supervisor.session.svsessions")
|
||||
|
||||
local SUPERVISOR_VERSION = "v1.7.1"
|
||||
local SUPERVISOR_VERSION = "v1.6.2"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@ -73,21 +72,6 @@ if config.FacilityTankMode > 0 then
|
||||
cfv.assert_type_int(def)
|
||||
cfv.assert_range(def, 0, 2)
|
||||
assert(cfv.valid(), "startup> invalid facility tank definition for reactor unit " .. i)
|
||||
|
||||
local entry = config.FacilityTankList[i]
|
||||
cfv.assert_type_int(entry)
|
||||
cfv.assert_range(entry, 0, 2)
|
||||
assert(cfv.valid(), "startup> invalid facility tank list entry for tank " .. i)
|
||||
|
||||
local conn = config.FacilityTankConns[i]
|
||||
cfv.assert_type_int(conn)
|
||||
cfv.assert_range(conn, 0, #config.FacilityTankDefs)
|
||||
assert(cfv.valid(), "startup> invalid facility tank connection for reactor unit " .. i)
|
||||
|
||||
local type = config.TankFluidTypes[i]
|
||||
cfv.assert_type_int(type)
|
||||
cfv.assert_range(type, 0, types.COOLANT_TYPE.SODIUM)
|
||||
assert(cfv.valid(), "startup> invalid tank fluid type for tank " .. i)
|
||||
end
|
||||
end
|
||||
|
||||
@ -163,9 +147,6 @@ local function main()
|
||||
-- halve the rate heartbeat LED flash
|
||||
local heartbeat_toggle = true
|
||||
|
||||
-- init startup recovery
|
||||
sv_facility.boot_recovery_init(supervisor.boot_state)
|
||||
|
||||
-- event loop
|
||||
while true do
|
||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||
@ -256,8 +237,6 @@ local function main()
|
||||
end
|
||||
end
|
||||
|
||||
sv_facility.clear_boot_state()
|
||||
|
||||
renderer.close_ui()
|
||||
|
||||
util.println_ts("exited")
|
||||
|
||||
@ -19,24 +19,10 @@ local config = {}
|
||||
|
||||
supervisor.config = config
|
||||
|
||||
-- control state from last unexpected shutdown
|
||||
supervisor.boot_state = nil ---@type sv_boot_state|nil
|
||||
|
||||
-- load the supervisor configuration and startup state
|
||||
-- load the supervisor configuration
|
||||
function supervisor.load_config()
|
||||
if not settings.load("/supervisor.settings") then return false end
|
||||
|
||||
---@class sv_boot_state
|
||||
local boot_state = {
|
||||
mode = settings.get("LastProcessState"), ---@type PROCESS
|
||||
unit_states = settings.get("LastUnitStates") ---@type boolean[]
|
||||
}
|
||||
|
||||
-- only record boot state if likely valid
|
||||
if type(boot_state.mode) == "number" and type(boot_state.unit_states) == "table" then
|
||||
supervisor.boot_state = boot_state
|
||||
end
|
||||
|
||||
config.UnitCount = settings.get("UnitCount")
|
||||
config.CoolingConfig = settings.get("CoolingConfig")
|
||||
config.FacilityTankMode = settings.get("FacilityTankMode")
|
||||
@ -44,7 +30,6 @@ function supervisor.load_config()
|
||||
config.FacilityTankList = settings.get("FacilityTankList")
|
||||
config.FacilityTankConns = settings.get("FacilityTankConns")
|
||||
config.TankFluidTypes = settings.get("TankFluidTypes")
|
||||
config.AuxiliaryCoolant = settings.get("AuxiliaryCoolant")
|
||||
config.ExtChargeIdling = settings.get("ExtChargeIdling")
|
||||
|
||||
config.SVR_Channel = settings.get("SVR_Channel")
|
||||
@ -79,7 +64,6 @@ function supervisor.load_config()
|
||||
cfv.assert_type_table(config.FacilityTankList)
|
||||
cfv.assert_type_table(config.FacilityTankConns)
|
||||
cfv.assert_type_table(config.TankFluidTypes)
|
||||
cfv.assert_type_table(config.AuxiliaryCoolant)
|
||||
cfv.assert_range(config.FacilityTankMode, 0, 8)
|
||||
|
||||
cfv.assert_type_bool(config.ExtChargeIdling)
|
||||
@ -262,32 +246,20 @@ function supervisor.comms(_version, nic, fp_ok, facility)
|
||||
-- PLC linking request
|
||||
if packet.length == 4 and type(packet.data[4]) == "number" then
|
||||
local reactor_id = packet.data[4]
|
||||
local plc_id = svsessions.establish_plc_session(src_addr, i_seq_num, reactor_id, firmware_v)
|
||||
|
||||
-- check ID validity
|
||||
if reactor_id < 1 or reactor_id > config.UnitCount then
|
||||
-- reactor index out of range
|
||||
if last_ack ~= ESTABLISH_ACK.DENY then
|
||||
log.warning(util.c("PLC_ESTABLISH: denied assignment ", reactor_id, " outside of configured unit count ", config.UnitCount))
|
||||
if plc_id == false then
|
||||
-- reactor already has a PLC assigned
|
||||
if last_ack ~= ESTABLISH_ACK.COLLISION then
|
||||
log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id))
|
||||
end
|
||||
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION)
|
||||
else
|
||||
-- try to establish the session
|
||||
local plc_id = svsessions.establish_plc_session(src_addr, i_seq_num, reactor_id, firmware_v)
|
||||
|
||||
if plc_id == false then
|
||||
-- reactor already has a PLC assigned
|
||||
if last_ack ~= ESTABLISH_ACK.COLLISION then
|
||||
log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id))
|
||||
end
|
||||
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION)
|
||||
else
|
||||
-- got an ID; assigned to a reactor successfully
|
||||
println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected"))
|
||||
log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id))
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
||||
end
|
||||
-- got an ID; assigned to a reactor successfully
|
||||
println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected"))
|
||||
log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id))
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
||||
end
|
||||
else
|
||||
log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type")
|
||||
|
||||
@ -3,23 +3,20 @@ local rsio = require("scada-common.rsio")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local alarm_ctl = require("supervisor.alarm_ctl")
|
||||
local unit_logic = require("supervisor.unit_logic")
|
||||
local logic = require("supervisor.unitlogic")
|
||||
|
||||
local plc = require("supervisor.session.plc")
|
||||
local rsctl = require("supervisor.session.rsctl")
|
||||
local svsessions = require("supervisor.session.svsessions")
|
||||
|
||||
local AISTATE = alarm_ctl.AISTATE
|
||||
|
||||
local ALARM = types.ALARM
|
||||
local ALARM_STATE = types.ALARM_STATE
|
||||
local PRIO = types.ALARM_PRIORITY
|
||||
local RTU_ID_FAIL = types.RTU_ID_FAIL
|
||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
local TRI_FAIL = types.TRI_FAIL
|
||||
local WASTE_MODE = types.WASTE_MODE
|
||||
local WASTE = types.WASTE_PRODUCT
|
||||
local ALARM = types.ALARM
|
||||
local PRIO = types.ALARM_PRIORITY
|
||||
local ALARM_STATE = types.ALARM_STATE
|
||||
local TRI_FAIL = types.TRI_FAIL
|
||||
local RTU_ID_FAIL = types.RTU_ID_FAIL
|
||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
|
||||
local PLC_S_CMDS = plc.PLC_S_CMDS
|
||||
|
||||
@ -40,6 +37,23 @@ local DT_KEYS = {
|
||||
TurbinePower = "TPR"
|
||||
}
|
||||
|
||||
---@enum ALARM_INT_STATE
|
||||
local AISTATE = {
|
||||
INACTIVE = 1,
|
||||
TRIPPING = 2,
|
||||
TRIPPED = 3,
|
||||
ACKED = 4,
|
||||
RING_BACK = 5,
|
||||
RING_BACK_TRIPPING = 6
|
||||
}
|
||||
|
||||
---@class alarm_def
|
||||
---@field state ALARM_INT_STATE internal alarm state
|
||||
---@field trip_time integer time (ms) when first tripped
|
||||
---@field hold_time integer time (s) to hold before tripping
|
||||
---@field id ALARM alarm ID
|
||||
---@field tier integer alarm urgency tier (0 = highest)
|
||||
|
||||
-- burn rate to idle at
|
||||
local IDLE_RATE = 0.01
|
||||
|
||||
@ -52,8 +66,7 @@ local unit = {}
|
||||
---@param num_boilers integer number of boilers expected
|
||||
---@param num_turbines integer number of turbines expected
|
||||
---@param ext_idle boolean extended idling mode
|
||||
---@param aux_coolant boolean if this unit has auxiliary coolant
|
||||
function unit.new(reactor_id, num_boilers, num_turbines, ext_idle, aux_coolant)
|
||||
function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
||||
-- time (ms) to idle for auto idling
|
||||
local IDLE_TIME = util.trinary(ext_idle, 60000, 10000)
|
||||
|
||||
@ -66,8 +79,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle, aux_coolant)
|
||||
plc_i = nil, ---@type plc_session
|
||||
num_boilers = num_boilers,
|
||||
num_turbines = num_turbines,
|
||||
aux_coolant = aux_coolant,
|
||||
types = { DT_KEYS = DT_KEYS },
|
||||
types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE },
|
||||
-- rtus
|
||||
rtu_list = {}, ---@type unit_session[][]
|
||||
redstone = {}, ---@type redstone_session[]
|
||||
@ -80,8 +92,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle, aux_coolant)
|
||||
io_ctl = nil, ---@type rs_controller
|
||||
---@diagnostic disable-next-line: missing-fields
|
||||
valves = {}, ---@type unit_valves
|
||||
em_cool_opened = false,
|
||||
aux_cool_opened = false,
|
||||
emcool_opened = false,
|
||||
-- auto control
|
||||
auto_engaged = false,
|
||||
auto_idle = false,
|
||||
@ -100,7 +111,6 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle, aux_coolant)
|
||||
damage_est_last = 0,
|
||||
waste_product = WASTE.PLUTONIUM, ---@type WASTE_PRODUCT
|
||||
status_text = { "UNKNOWN", "awaiting connection..." },
|
||||
enable_aux_cool = false,
|
||||
-- logic for alarms
|
||||
had_reactor = false,
|
||||
turbine_flow_stable = false,
|
||||
@ -244,7 +254,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle, aux_coolant)
|
||||
self.rtu_list = { self.redstone, self.boilers, self.turbines, self.tanks, self.snas, self.envd }
|
||||
|
||||
-- init redstone RTU I/O controller
|
||||
self.io_ctl = rsctl.new(self.redstone, reactor_id)
|
||||
self.io_ctl = rsctl.new(self.redstone)
|
||||
|
||||
-- init boiler table fields
|
||||
for _ = 1, num_boilers do
|
||||
@ -363,7 +373,6 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle, aux_coolant)
|
||||
local waste_po = _make_valve_iface(IO.WASTE_POPL)
|
||||
local waste_sps = _make_valve_iface(IO.WASTE_AM)
|
||||
local emer_cool = _make_valve_iface(IO.U_EMER_COOL)
|
||||
local aux_cool = _make_valve_iface(IO.U_AUX_COOL)
|
||||
|
||||
---@class unit_valves
|
||||
self.valves = {
|
||||
@ -371,8 +380,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle, aux_coolant)
|
||||
waste_sna = waste_sna,
|
||||
waste_po = waste_po,
|
||||
waste_sps = waste_sps,
|
||||
emer_cool = emer_cool,
|
||||
aux_cool = aux_cool
|
||||
emer_cool = emer_cool
|
||||
}
|
||||
|
||||
-- route reactor waste for a given waste product
|
||||
@ -583,22 +591,22 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle, aux_coolant)
|
||||
_dt__compute_all()
|
||||
|
||||
-- update annunciator logic
|
||||
unit_logic.update_annunciator(self)
|
||||
logic.update_annunciator(self)
|
||||
|
||||
-- update alarm status
|
||||
unit_logic.update_alarms(self)
|
||||
logic.update_alarms(self)
|
||||
|
||||
-- if in auto mode, SCRAM on certain alarms
|
||||
unit_logic.update_auto_safety(self, public)
|
||||
logic.update_auto_safety(public, self)
|
||||
|
||||
-- update status text
|
||||
unit_logic.update_status_text(self)
|
||||
logic.update_status_text(self)
|
||||
|
||||
-- handle redstone I/O
|
||||
if #self.redstone > 0 then
|
||||
unit_logic.handle_redstone(self)
|
||||
logic.handle_redstone(self)
|
||||
elseif not self.plc_cache.rps_trip then
|
||||
self.em_cool_opened = false
|
||||
self.emcool_opened = false
|
||||
end
|
||||
end
|
||||
|
||||
@ -716,7 +724,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle, aux_coolant)
|
||||
|
||||
-- queue a command to clear timeout/auto-scram if set
|
||||
function public.auto_cond_rps_reset()
|
||||
if self.plc_s ~= nil and self.plc_i ~= nil and (not self.auto_was_alarmed) and (not self.em_cool_opened) then
|
||||
if self.plc_s ~= nil and self.plc_i ~= nil and (not self.auto_was_alarmed) and (not self.emcool_opened) then
|
||||
local rps = self.plc_i.get_rps()
|
||||
if rps.timeout or rps.automatic then
|
||||
self.plc_i.auto_lock(true) -- if it timed out/restarted, auto lock was lost, so re-lock it
|
||||
@ -761,8 +769,10 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle, aux_coolant)
|
||||
|
||||
-- acknowledge all alarms (if possible)
|
||||
function public.ack_all()
|
||||
for id, state in pairs(self.db.alarm_states) do
|
||||
if state == ALARM_STATE.TRIPPED then self.db.alarm_states[id] = ALARM_STATE.ACKED end
|
||||
for i = 1, #self.db.alarm_states do
|
||||
if self.db.alarm_states[i] == ALARM_STATE.TRIPPED then
|
||||
self.db.alarm_states[i] = ALARM_STATE.ACKED
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -830,12 +840,6 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle, aux_coolant)
|
||||
return false
|
||||
end
|
||||
|
||||
-- check the active state of the reactor (if connected)
|
||||
---@nodiscard
|
||||
function public.is_reactor_enabled()
|
||||
if self.plc_i ~= nil then return self.plc_i.get_status().status else return false end
|
||||
end
|
||||
|
||||
-- check if the reactor is connected, is stopped, the RPS is not tripped, and no alarms are active
|
||||
---@nodiscard
|
||||
function public.is_safe_idle()
|
||||
@ -855,7 +859,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle, aux_coolant)
|
||||
|
||||
-- check if emergency coolant activation has been tripped
|
||||
---@nodiscard
|
||||
function public.is_emer_cool_tripped() return self.em_cool_opened end
|
||||
function public.is_emer_cool_tripped() return self.emcool_opened end
|
||||
|
||||
-- get build properties of machines
|
||||
--
|
||||
@ -1049,8 +1053,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle, aux_coolant)
|
||||
v.waste_sna.check(),
|
||||
v.waste_po.check(),
|
||||
v.waste_sps.check(),
|
||||
v.emer_cool.check(),
|
||||
v.aux_cool.check()
|
||||
v.emer_cool.check()
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@ -1,16 +1,12 @@
|
||||
local const = require("scada-common.constants")
|
||||
local log = require("scada-common.log")
|
||||
local rsio = require("scada-common.rsio")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
local const = require("scada-common.constants")
|
||||
local log = require("scada-common.log")
|
||||
local rsio = require("scada-common.rsio")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local alarm_ctl = require("supervisor.alarm_ctl")
|
||||
local plc = require("supervisor.session.plc")
|
||||
|
||||
local plc = require("supervisor.session.plc")
|
||||
|
||||
local qtypes = require("supervisor.session.rtu.qtypes")
|
||||
|
||||
local AISTATE = alarm_ctl.AISTATE
|
||||
local qtypes = require("supervisor.session.rtu.qtypes")
|
||||
|
||||
local RPS_TRIP_CAUSE = types.RPS_TRIP_CAUSE
|
||||
local TRI_FAIL = types.TRI_FAIL
|
||||
@ -26,10 +22,19 @@ local IO = rsio.IO
|
||||
|
||||
local PLC_S_CMDS = plc.PLC_S_CMDS
|
||||
|
||||
local ANNUNC_LIMS = const.ANNUNCIATOR_LIMITS
|
||||
local ALARM_LIMS = const.ALARM_LIMITS
|
||||
local AISTATE_NAMES = {
|
||||
"INACTIVE",
|
||||
"TRIPPING",
|
||||
"TRIPPED",
|
||||
"ACKED",
|
||||
"RING_BACK",
|
||||
"RING_BACK_TRIPPING"
|
||||
}
|
||||
|
||||
local FLOW_STABILITY_DELAY_MS = const.FLOW_STABILITY_DELAY_MS
|
||||
local RS_THRESH = const.RS_THRESHOLDS
|
||||
|
||||
local ANNUNC_LIMS = const.ANNUNCIATOR_LIMITS
|
||||
local ALARM_LIMS = const.ALARM_LIMITS
|
||||
|
||||
---@class unit_logic_extension
|
||||
local logic = {}
|
||||
@ -49,10 +54,6 @@ function logic.update_annunciator(self)
|
||||
-- variables for boiler, or reactor if no boilers used
|
||||
local total_boil_rate = 0.0
|
||||
|
||||
-- auxiliary coolant control
|
||||
local need_aux_cool = false
|
||||
local dis_aux_cool = true
|
||||
|
||||
--#region Reactor
|
||||
|
||||
annunc.AutoControl = self.auto_engaged
|
||||
@ -66,10 +67,11 @@ function logic.update_annunciator(self)
|
||||
local plc_db = self.plc_i.get_db()
|
||||
|
||||
-- update ready state
|
||||
-- - must be connected to a formed reactor
|
||||
-- - can't have a tripped RPS
|
||||
-- - must have received status, struct, and RPS status at least once
|
||||
plc_ready = plc_db.formed and (not plc_db.no_reactor) and (not plc_db.rps_tripped) and self.plc_i.check_received_all_data()
|
||||
-- - can't be tripped
|
||||
-- - must have received status at least once
|
||||
-- - must have received struct at least once
|
||||
plc_ready = plc_db.formed and (not plc_db.no_reactor) and (not plc_db.rps_tripped) and
|
||||
(next(self.plc_i.get_status()) ~= nil) and (next(self.plc_i.get_struct()) ~= nil)
|
||||
|
||||
-- update auto control limit
|
||||
if (plc_db.mek_struct.max_burn > 0) and ((self.db.control.lim_br100 / 100) > plc_db.mek_struct.max_burn) then
|
||||
@ -147,9 +149,6 @@ function logic.update_annunciator(self)
|
||||
-- if no boilers, use reactor heating rate to check for boil rate mismatch
|
||||
if num_boilers == 0 then
|
||||
total_boil_rate = plc_db.mek_status.heating_rate
|
||||
|
||||
need_aux_cool = plc_db.mek_status.ccool_fill <= RS_THRESH.AUX_COOL_ENABLE
|
||||
dis_aux_cool = plc_db.mek_status.ccool_fill >= RS_THRESH.AUX_COOL_DISABLE
|
||||
end
|
||||
else
|
||||
self.plc_cache.ok = false
|
||||
@ -173,8 +172,12 @@ function logic.update_annunciator(self)
|
||||
|
||||
annunc.EmergencyCoolant = 1
|
||||
|
||||
if self.io_ctl.is_connected(IO.U_EMER_COOL) then
|
||||
annunc.EmergencyCoolant = util.trinary(self.io_ctl.digital_read(IO.U_EMER_COOL), 3, 2)
|
||||
for i = 1, #self.redstone do
|
||||
local io = self.redstone[i].get_db().io[IO.U_EMER_COOL]
|
||||
if io ~= nil then
|
||||
annunc.EmergencyCoolant = util.trinary(io.read(), 3, 2)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
--#endregion
|
||||
@ -213,9 +216,6 @@ function logic.update_annunciator(self)
|
||||
|
||||
annunc.BoilerOnline[idx] = true
|
||||
annunc.WaterLevelLow[idx] = boiler.tanks.water_fill < ANNUNC_LIMS.WaterLevelLow
|
||||
|
||||
need_aux_cool = need_aux_cool or (boiler.tanks.water_fill <= RS_THRESH.AUX_COOL_ENABLE)
|
||||
dis_aux_cool = dis_aux_cool and (boiler.tanks.water_fill >= RS_THRESH.AUX_COOL_DISABLE)
|
||||
end
|
||||
|
||||
-- check heating rate low
|
||||
@ -342,11 +342,11 @@ function logic.update_annunciator(self)
|
||||
end
|
||||
|
||||
if rotation_stable then
|
||||
log.debug(util.c("UNIT ", self.r_id, " turbine ", idx, " reached rotational stability (", rotation, ")"))
|
||||
log.debug(util.c("UNIT ", self.r_id, ": turbine ", idx, " reached rotational stability (", rotation, ")"))
|
||||
end
|
||||
|
||||
if flow_stable then
|
||||
log.debug(util.c("UNIT ", self.r_id, " turbine ", idx, " reached flow stability (", turbine.state.flow_rate, " mB/t)"))
|
||||
log.debug(util.c("UNIT ", self.r_id, ": turbine ", idx, " reached flow stability (", turbine.state.flow_rate, " mB/t)"))
|
||||
end
|
||||
|
||||
turbines_stable = turbines_stable and (rotation_stable or flow_stable)
|
||||
@ -358,7 +358,7 @@ function logic.update_annunciator(self)
|
||||
|
||||
turbines_stable = false
|
||||
|
||||
log.debug(util.c("UNIT ", self.r_id, " turbine ", idx, " reset stability (new rate ", turbine.state.steam_input_rate, " != ", last.input_rate," mB/t)"))
|
||||
log.debug(util.c("UNIT ", self.r_id, ": turbine ", idx, " reset stability (new rate ", turbine.state.steam_input_rate, " != ", last.input_rate," mB/t)"))
|
||||
end
|
||||
|
||||
last.input_rate = turbine.state.steam_input_rate
|
||||
@ -407,25 +407,100 @@ function logic.update_annunciator(self)
|
||||
|
||||
-- update auto control ready state for this unit
|
||||
self.db.control.ready = plc_ready and boilers_ready and turbines_ready
|
||||
|
||||
-- update auxiliary coolant command
|
||||
if plc_ready then
|
||||
self.enable_aux_cool = self.plc_i.get_db().mek_status.status and
|
||||
(self.enable_aux_cool or need_aux_cool) and not (dis_aux_cool and self.turbine_flow_stable)
|
||||
else self.enable_aux_cool = false end
|
||||
end
|
||||
|
||||
-- update an alarm state given conditions
|
||||
---@param self _unit_self
|
||||
---@param self _unit_self unit instance
|
||||
---@param tripped boolean if the alarm condition is still active
|
||||
---@param alarm alarm_def alarm table
|
||||
---@return boolean new_trip if the alarm just changed to being tripped
|
||||
local function _update_alarm_state(self, tripped, alarm)
|
||||
return alarm_ctl.update_alarm_state("UNIT " .. self.r_id, self.db.alarm_states, tripped, alarm)
|
||||
local AISTATE = self.types.AISTATE
|
||||
local int_state = alarm.state
|
||||
local ext_state = self.db.alarm_states[alarm.id]
|
||||
|
||||
-- alarm inactive
|
||||
if int_state == AISTATE.INACTIVE then
|
||||
if tripped then
|
||||
alarm.trip_time = util.time_ms()
|
||||
if alarm.hold_time > 0 then
|
||||
alarm.state = AISTATE.TRIPPING
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||
else
|
||||
alarm.state = AISTATE.TRIPPED
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED
|
||||
log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): TRIPPED [PRIORITY ",
|
||||
types.ALARM_PRIORITY_NAMES[alarm.tier],"]"))
|
||||
end
|
||||
else
|
||||
alarm.trip_time = util.time_ms()
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||
end
|
||||
-- alarm condition met, but not yet for required hold time
|
||||
elseif (int_state == AISTATE.TRIPPING) or (int_state == AISTATE.RING_BACK_TRIPPING) then
|
||||
if tripped then
|
||||
local elapsed = util.time_ms() - alarm.trip_time
|
||||
if elapsed > (alarm.hold_time * 1000) then
|
||||
alarm.state = AISTATE.TRIPPED
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED
|
||||
log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): TRIPPED [PRIORITY ",
|
||||
types.ALARM_PRIORITY_NAMES[alarm.tier],"]"))
|
||||
end
|
||||
elseif int_state == AISTATE.RING_BACK_TRIPPING then
|
||||
alarm.trip_time = 0
|
||||
alarm.state = AISTATE.RING_BACK
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.RING_BACK
|
||||
else
|
||||
alarm.trip_time = 0
|
||||
alarm.state = AISTATE.INACTIVE
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||
end
|
||||
-- alarm tripped and alarming
|
||||
elseif int_state == AISTATE.TRIPPED then
|
||||
if tripped then
|
||||
if ext_state == ALARM_STATE.ACKED then
|
||||
-- was acked by coordinator
|
||||
alarm.state = AISTATE.ACKED
|
||||
end
|
||||
else
|
||||
alarm.state = AISTATE.RING_BACK
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.RING_BACK
|
||||
end
|
||||
-- alarm acknowledged but still tripped
|
||||
elseif int_state == AISTATE.ACKED then
|
||||
if not tripped then
|
||||
alarm.state = AISTATE.RING_BACK
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.RING_BACK
|
||||
end
|
||||
-- alarm no longer tripped, operator must reset to clear
|
||||
elseif int_state == AISTATE.RING_BACK then
|
||||
if tripped then
|
||||
alarm.trip_time = util.time_ms()
|
||||
if alarm.hold_time > 0 then
|
||||
alarm.state = AISTATE.RING_BACK_TRIPPING
|
||||
else
|
||||
alarm.state = AISTATE.TRIPPED
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED
|
||||
end
|
||||
elseif ext_state == ALARM_STATE.INACTIVE then
|
||||
-- was reset by coordinator
|
||||
alarm.state = AISTATE.INACTIVE
|
||||
alarm.trip_time = 0
|
||||
end
|
||||
else
|
||||
log.error(util.c("invalid alarm state for unit ", self.r_id, " alarm ", alarm.id), true)
|
||||
end
|
||||
|
||||
-- check for state change
|
||||
if alarm.state ~= int_state then
|
||||
local change_str = util.c(AISTATE_NAMES[int_state], " -> ", AISTATE_NAMES[alarm.state])
|
||||
log.debug(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): ", change_str))
|
||||
return alarm.state == AISTATE.TRIPPED
|
||||
else return false end
|
||||
end
|
||||
|
||||
-- evaluate alarm conditions
|
||||
---@param self _unit_self
|
||||
---@param self _unit_self unit instance
|
||||
function logic.update_alarms(self)
|
||||
local annunc = self.db.annunciator
|
||||
local plc_cache = self.plc_cache
|
||||
@ -538,9 +613,11 @@ function logic.update_alarms(self)
|
||||
end
|
||||
|
||||
-- update the internal automatic safety control performed while in auto control mode
|
||||
---@param self _unit_self
|
||||
---@param public reactor_unit reactor unit public functions
|
||||
function logic.update_auto_safety(self, public)
|
||||
---@param self _unit_self unit instance
|
||||
function logic.update_auto_safety(public, self)
|
||||
local AISTATE = self.types.AISTATE
|
||||
|
||||
if self.auto_engaged then
|
||||
local alarmed = false
|
||||
|
||||
@ -567,8 +644,9 @@ function logic.update_auto_safety(self, public)
|
||||
end
|
||||
|
||||
-- update the two unit status text messages
|
||||
---@param self _unit_self
|
||||
---@param self _unit_self unit instance
|
||||
function logic.update_status_text(self)
|
||||
local AISTATE = self.types.AISTATE
|
||||
local annunc = self.db.annunciator
|
||||
|
||||
-- check if an alarm is active (tripped or ack'd)
|
||||
@ -651,7 +729,7 @@ function logic.update_status_text(self)
|
||||
self.status_text = { "RCS TRANSIENT", "check coolant system" }
|
||||
-- elseif is_active(self.alarms.RPSTransient) then
|
||||
-- RPS status handled when checking reactor status
|
||||
elseif self.em_cool_opened then
|
||||
elseif self.emcool_opened then
|
||||
self.status_text = { "EMERGENCY COOLANT OPENED", "reset RPS to close valve" }
|
||||
-- connection dependent states
|
||||
elseif self.plc_i ~= nil then
|
||||
@ -730,8 +808,9 @@ function logic.update_status_text(self)
|
||||
end
|
||||
|
||||
-- handle unit redstone I/O
|
||||
---@param self _unit_self
|
||||
---@param self _unit_self unit instance
|
||||
function logic.handle_redstone(self)
|
||||
local AISTATE = self.types.AISTATE
|
||||
local annunc = self.db.annunciator
|
||||
local cache = self.plc_cache
|
||||
local rps = cache.rps_status
|
||||
@ -808,10 +887,10 @@ function logic.handle_redstone(self)
|
||||
(annunc.CoolantLevelLow or (boiler_water_low and rps.ex_hcool)) and
|
||||
is_active(self.alarms.ReactorOverTemp))
|
||||
|
||||
if enable_emer_cool and not self.em_cool_opened then
|
||||
if enable_emer_cool and not self.emcool_opened then
|
||||
log.debug(util.c(">> Emergency Coolant Enable Detail Report (Unit ", self.r_id, ") <<"))
|
||||
log.debug(util.c("| CoolantLevelLow[", annunc.CoolantLevelLow, "] CoolantLevelLowLow[", rps.low_cool, "] ExcessHeatedCoolant[", rps.ex_hcool, "]"))
|
||||
log.debug(util.c("| ReactorOverTemp[", alarm_ctl.AISTATE_NAMES[self.alarms.ReactorOverTemp.state], "]"))
|
||||
log.debug(util.c("| ReactorOverTemp[", AISTATE_NAMES[self.alarms.ReactorOverTemp.state], "]"))
|
||||
|
||||
for i = 1, #annunc.WaterLevelLow do
|
||||
log.debug(util.c("| WaterLevelLow(", i, ")[", annunc.WaterLevelLow[i], "]"))
|
||||
@ -832,13 +911,13 @@ function logic.handle_redstone(self)
|
||||
end
|
||||
end
|
||||
|
||||
if annunc.EmergencyCoolant > 1 and self.em_cool_opened then
|
||||
if annunc.EmergencyCoolant > 1 and self.emcool_opened then
|
||||
log.info(util.c("UNIT ", self.r_id, " emergency coolant valve closed"))
|
||||
log.info(util.c("UNIT ", self.r_id, " turbines set to not dump steam"))
|
||||
end
|
||||
|
||||
self.em_cool_opened = false
|
||||
elseif enable_emer_cool or self.em_cool_opened then
|
||||
self.emcool_opened = false
|
||||
elseif enable_emer_cool or self.emcool_opened then
|
||||
-- set turbines to dump excess steam
|
||||
for i = 1, #self.turbines do
|
||||
local session = self.turbines[i]
|
||||
@ -859,33 +938,16 @@ function logic.handle_redstone(self)
|
||||
end
|
||||
end
|
||||
|
||||
if annunc.EmergencyCoolant > 1 and not self.em_cool_opened then
|
||||
if annunc.EmergencyCoolant > 1 and not self.emcool_opened then
|
||||
log.info(util.c("UNIT ", self.r_id, " emergency coolant valve opened"))
|
||||
log.info(util.c("UNIT ", self.r_id, " turbines set to dump excess steam"))
|
||||
end
|
||||
|
||||
self.em_cool_opened = true
|
||||
self.emcool_opened = true
|
||||
end
|
||||
|
||||
-- set valve state always
|
||||
if self.em_cool_opened then self.valves.emer_cool.open() else self.valves.emer_cool.close() end
|
||||
|
||||
-----------------------
|
||||
-- Auxiliary Coolant --
|
||||
-----------------------
|
||||
|
||||
if self.aux_coolant then
|
||||
if self.enable_aux_cool and (not self.aux_cool_opened) then
|
||||
log.info(util.c("UNIT ", self.r_id, " auxiliary coolant valve opened"))
|
||||
self.aux_cool_opened = true
|
||||
elseif (not self.enable_aux_cool) and self.aux_cool_opened then
|
||||
log.info(util.c("UNIT ", self.r_id, " auxiliary coolant valve closed"))
|
||||
self.aux_cool_opened = false
|
||||
end
|
||||
|
||||
-- set valve state always
|
||||
if self.aux_cool_opened then self.valves.aux_cool.open() else self.valves.aux_cool.close() end
|
||||
end
|
||||
if self.emcool_opened then self.valves.emer_cool.open() else self.valves.emer_cool.close() end
|
||||
end
|
||||
|
||||
return logic
|
||||
Loading…
x
Reference in New Issue
Block a user