Merge pull request #599 from MikaylaFischler/devel

Pocket Beta Release
This commit is contained in:
Mikayla 2025-01-27 12:52:32 -05:00 committed by GitHub
commit cf9e26ac8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 1043 additions and 269 deletions

View File

@ -495,6 +495,49 @@ end
--#region Statuses --#region Statuses
-- generate the text string for the induction matrix charge/discharge ETA
---@param eta_ms number eta in milliseconds
local function gen_eta_text(eta_ms)
local str, pre = "", util.trinary(eta_ms >= 0, "Full in ", "Empty in ")
local seconds = math.abs(eta_ms) / 1000
local minutes = seconds / 60
local hours = minutes / 60
local days = hours / 24
if math.abs(eta_ms) < 1000 or (eta_ms ~= eta_ms) then
-- really small or NaN
str = "No ETA"
elseif days < 1000 then
days = math.floor(days)
hours = math.floor(hours % 24)
minutes = math.floor(minutes % 60)
seconds = math.floor(seconds % 60)
if days > 0 then
str = days .. "d"
elseif hours > 0 then
str = hours .. "h " .. minutes .. "m"
elseif minutes > 0 then
str = minutes .. "m " .. seconds .. "s"
elseif seconds > 0 then
str = seconds .. "s"
end
str = pre .. str
else
local years = math.floor(days / 365.25)
if years <= 99999999 then
str = pre .. years .. "y"
else
str = pre .. "eras"
end
end
return str
end
-- record and publish multiblock status data -- record and publish multiblock status data
---@param entry any ---@param entry any
---@param data imatrix_session_db|sps_session_db|dynamicv_session_db|turbinev_session_db|boilerv_session_db ---@param data imatrix_session_db|sps_session_db|dynamicv_session_db|turbinev_session_db|boilerv_session_db
@ -616,6 +659,7 @@ function iocontrol.update_facility_status(status)
ps.publish("avg_inflow", in_f) ps.publish("avg_inflow", in_f)
ps.publish("avg_outflow", out_f) ps.publish("avg_outflow", out_f)
ps.publish("eta_ms", eta) ps.publish("eta_ms", eta)
ps.publish("eta_string", gen_eta_text(eta or 0))
ps.publish("is_charging", in_f > out_f) ps.publish("is_charging", in_f > out_f)
ps.publish("is_discharging", out_f > in_f) ps.publish("is_discharging", out_f > in_f)

View File

@ -260,11 +260,52 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
{ fac.auto_ready, fac.auto_active, fac.auto_ramping, fac.auto_saturated }, { fac.auto_ready, fac.auto_active, fac.auto_ramping, fac.auto_saturated },
{ fac.auto_current_waste_product, fac.auto_pu_fallback_active }, { fac.auto_current_waste_product, fac.auto_pu_fallback_active },
util.table_len(fac.tank_data_tbl), util.table_len(fac.tank_data_tbl),
fac.induction_data_tbl[1] ~= nil, fac.induction_data_tbl[1] ~= nil, ---@fixme this means nothing
fac.sps_data_tbl[1] ~= nil, fac.sps_data_tbl[1] ~= nil ---@fixme this means nothing
} }
_send(CRDN_TYPE.API_GET_FAC, data) _send(CRDN_TYPE.API_GET_FAC, data)
elseif pkt.type == CRDN_TYPE.API_GET_FAC_DTL then
local fac = db.facility
local mtx_sps = fac.induction_ps_tbl[1]
local units = {}
local tank_statuses = {}
for i = 1, #db.units do
local u = db.units[i]
units[i] = { u.connected, u.annunciator, u.reactor_data, u.tank_data_tbl }
for t = 1, #u.tank_ps_tbl do table.insert(tank_statuses, u.tank_ps_tbl[t].get("computed_status")) end
end
for i = 1, #fac.tank_ps_tbl do table.insert(tank_statuses, fac.tank_ps_tbl[i].get("computed_status")) end
local matrix_data = {
mtx_sps.get("eta_string"),
mtx_sps.get("avg_charge"),
mtx_sps.get("avg_inflow"),
mtx_sps.get("avg_outflow"),
mtx_sps.get("is_charging"),
mtx_sps.get("is_discharging"),
mtx_sps.get("at_max_io")
}
local data = {
fac.all_sys_ok,
fac.rtu_count,
fac.auto_scram,
fac.ascram_status,
tank_statuses,
fac.tank_data_tbl,
fac.induction_ps_tbl[1].get("computed_status") or types.IMATRIX_STATE.OFFLINE,
fac.induction_data_tbl[1],
matrix_data,
fac.sps_ps_tbl[1].get("computed_status") or types.SPS_STATE.OFFLINE,
fac.sps_data_tbl[1],
units
}
_send(CRDN_TYPE.API_GET_FAC_DTL, data)
elseif pkt.type == CRDN_TYPE.API_GET_UNIT then elseif pkt.type == CRDN_TYPE.API_GET_UNIT then
if pkt.length == 1 and type(pkt.data[1]) == "number" then if pkt.length == 1 and type(pkt.data[1]) == "number" then
local u = db.units[pkt.data[1]] local u = db.units[pkt.data[1]]

View File

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

View File

@ -25,10 +25,9 @@ local ALIGN = core.ALIGN
---@param root Container parent ---@param root Container parent
---@param x integer top left x ---@param x integer top left x
---@param y integer top left y ---@param y integer top left y
---@param data imatrix_session_db matrix data
---@param ps psil ps interface ---@param ps psil ps interface
---@param id number? matrix ID ---@param id number? matrix ID
local function new_view(root, x, y, data, ps, id) local function new_view(root, x, y, ps, id)
local label_fg = style.theme.label_fg local label_fg = style.theme.label_fg
local text_fg = style.theme.text_fg local text_fg = style.theme.text_fg
local lu_col = style.lu_colors local lu_col = style.lu_colors
@ -94,6 +93,7 @@ local function new_view(root, x, y, data, ps, id)
TextBox{parent=rect,text="FILL I/O",x=2,y=20,width=8,fg_bg=label_fg} TextBox{parent=rect,text="FILL I/O",x=2,y=20,width=8,fg_bg=label_fg}
local function calc_saturation(val) local function calc_saturation(val)
local data = db.facility.induction_data_tbl[id or 1]
if (type(data.build) == "table") and (type(data.build.transfer_cap) == "number") and (data.build.transfer_cap > 0) then if (type(data.build) == "table") and (type(data.build.transfer_cap) == "number") and (data.build.transfer_cap > 0) then
return val / data.build.transfer_cap return val / data.build.transfer_cap
else return 0 end else return 0 end
@ -105,46 +105,7 @@ local function new_view(root, x, y, data, ps, id)
local eta = TextBox{parent=rect,x=11,y=20,width=20,text="ETA Unknown",alignment=ALIGN.CENTER,fg_bg=style.theme.field_box} local eta = TextBox{parent=rect,x=11,y=20,width=20,text="ETA Unknown",alignment=ALIGN.CENTER,fg_bg=style.theme.field_box}
eta.register(ps, "eta_ms", function (eta_ms) eta.register(ps, "eta_string", eta.set_value)
local str, pre = "", util.trinary(eta_ms >= 0, "Full in ", "Empty in ")
local seconds = math.abs(eta_ms) / 1000
local minutes = seconds / 60
local hours = minutes / 60
local days = hours / 24
if math.abs(eta_ms) < 1000 or (eta_ms ~= eta_ms) then
-- really small or NaN
str = "No ETA"
elseif days < 1000 then
days = math.floor(days)
hours = math.floor(hours % 24)
minutes = math.floor(minutes % 60)
seconds = math.floor(seconds % 60)
if days > 0 then
str = days .. "d"
elseif hours > 0 then
str = hours .. "h " .. minutes .. "m"
elseif minutes > 0 then
str = minutes .. "m " .. seconds .. "s"
elseif seconds > 0 then
str = seconds .. "s"
end
str = pre .. str
else
local years = math.floor(days / 365.25)
if years <= 99999999 then
str = pre .. years .. "y"
else
str = pre .. "eras"
end
end
eta.set_value(str)
end)
end end
return new_view return new_view

View File

@ -88,7 +88,7 @@ local function init(main)
util.nop() util.nop()
imatrix(main, 131, cnc_bottom_align_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1]) imatrix(main, 131, cnc_bottom_align_start, facility.induction_ps_tbl[1])
end end
return init return init

View File

@ -94,6 +94,7 @@ function iocontrol.init_core(pkt_comms, nav, cfg)
-- API access -- API access
---@class pocket_ioctl_api ---@class pocket_ioctl_api
io.api = { io.api = {
get_fac = function () comms.api__get_facility() end,
get_unit = function (unit) comms.api__get_unit(unit) end, get_unit = function (unit) comms.api__get_unit(unit) end,
get_ctrl = function () comms.api__get_control() end, get_ctrl = function () comms.api__get_control() end,
get_proc = function () comms.api__get_process() end, get_proc = function () comms.api__get_process() end,
@ -192,6 +193,14 @@ function iocontrol.init_fac(conf)
table.insert(io.facility.sps_ps_tbl, psil.create()) table.insert(io.facility.sps_ps_tbl, psil.create())
table.insert(io.facility.sps_data_tbl, {}) table.insert(io.facility.sps_data_tbl, {})
-- create facility tank tables
for i = 1, #io.facility.tank_list do
if io.facility.tank_list[i] == 2 then
table.insert(io.facility.tank_ps_tbl, psil.create())
table.insert(io.facility.tank_data_tbl, {})
end
end
-- create unit data structures -- create unit data structures
io.units = {} ---@type pioctl_unit[] io.units = {} ---@type pioctl_unit[]
for i = 1, conf.num_units do for i = 1, conf.num_units do

View File

@ -12,6 +12,8 @@ local ALARM_STATE = types.ALARM_STATE
local BLR_STATE = types.BOILER_STATE local BLR_STATE = types.BOILER_STATE
local TRB_STATE = types.TURBINE_STATE local TRB_STATE = types.TURBINE_STATE
local TNK_STATE = types.TANK_STATE local TNK_STATE = types.TANK_STATE
local MTX_STATE = types.IMATRIX_STATE
local SPS_STATE = types.SPS_STATE
local io ---@type pocket_ioctl local io ---@type pocket_ioctl
local iorx = {} ---@class iorx local iorx = {} ---@class iorx
@ -55,6 +57,11 @@ local function _record_multiblock_status(faulted, data, ps)
ps.publish("formed", data.formed) ps.publish("formed", data.formed)
ps.publish("faulted", faulted) 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
for key, val in pairs(data.state) do ps.publish(key, val) end for key, val in pairs(data.state) do ps.publish(key, val) end
for key, val in pairs(data.tanks) do ps.publish(key, val) end for key, val in pairs(data.tanks) do ps.publish(key, val) end
end end
@ -647,10 +654,171 @@ function iorx.record_waste_data(data)
fac.ps.publish("po_am_rate", fac.waste_stats[5]) fac.ps.publish("po_am_rate", fac.waste_stats[5])
fac.ps.publish("spent_waste_rate", fac.waste_stats[6]) fac.ps.publish("spent_waste_rate", fac.waste_stats[6])
fac.ps.publish("sps_computed_status", f_data[8]) fac.sps_ps_tbl[1].publish("SPSStateStatus", f_data[8])
fac.ps.publish("sps_process_rate", f_data[9]) fac.ps.publish("sps_process_rate", f_data[9])
end end
-- update facility app with facility and unit data from API_GET_FAC_DTL
---@param data table
function iorx.record_fac_detail_data(data)
local fac = io.facility
local tank_statuses = data[5]
local next_t_stat = 1
-- annunciator
fac.all_sys_ok = data[1]
fac.rtu_count = data[2]
fac.auto_scram = data[3]
fac.ascram_status = data[4]
fac.ps.publish("all_sys_ok", fac.all_sys_ok)
fac.ps.publish("rtu_count", fac.rtu_count)
fac.ps.publish("auto_scram", fac.auto_scram)
fac.ps.publish("as_matrix_fault", fac.ascram_status.matrix_fault)
fac.ps.publish("as_matrix_fill", fac.ascram_status.matrix_fill)
fac.ps.publish("as_crit_alarm", fac.ascram_status.crit_alarm)
fac.ps.publish("as_radiation", fac.ascram_status.radiation)
fac.ps.publish("as_gen_fault", fac.ascram_status.gen_fault)
-- unit data
local units = data[12]
for i = 1, io.facility.num_units do
local unit = io.units[i]
local u_rx = units[i]
unit.connected = u_rx[1]
unit.annunciator = u_rx[2]
unit.reactor_data = u_rx[3]
local control_status = 1
if unit.connected then
if unit.reactor_data.rps_tripped then control_status = 2 end
if unit.reactor_data.mek_status.status then
control_status = util.trinary(unit.annunciator.AutoControl, 4, 3)
end
end
unit.unit_ps.publish("U_ControlStatus", control_status)
unit.tank_data_tbl = u_rx[4]
for id = 1, #unit.tank_data_tbl do
local tank = unit.tank_data_tbl[id]
local ps = unit.tank_ps_tbl[id]
local c_stat = tank_statuses[next_t_stat]
local tank_status = 1
if c_stat ~= TNK_STATE.OFFLINE then
if c_stat == TNK_STATE.FAULT then
tank_status = 3
elseif tank.formed then
tank_status = 4
else
tank_status = 2
end
end
ps.publish("DynamicTankStatus", tank_status)
ps.publish("DynamicTankStateStatus", c_stat)
next_t_stat = next_t_stat + 1
end
end
-- facility dynamic tank data
fac.tank_data_tbl = data[6]
for id = 1, #fac.tank_data_tbl do
local tank = fac.tank_data_tbl[id]
local ps = fac.tank_ps_tbl[id]
local c_stat = tank_statuses[next_t_stat]
local tank_status = 1
if c_stat ~= TNK_STATE.OFFLINE then
if c_stat == TNK_STATE.FAULT then
tank_status = 3
elseif tank.formed then
tank_status = 4
else
tank_status = 2
end
_record_multiblock_status(c_stat == TNK_STATE.FAULT, tank, ps)
end
ps.publish("DynamicTankStatus", tank_status)
ps.publish("DynamicTankStateStatus", c_stat)
next_t_stat = next_t_stat + 1
end
-- induction matrix data
fac.induction_data_tbl[1] = data[8]
local matrix = fac.induction_data_tbl[1]
local m_ps = fac.induction_ps_tbl[1]
local m_stat = data[7]
local mtx_status = 1
if m_stat ~= MTX_STATE.OFFLINE then
if m_stat == MTX_STATE.FAULT then
mtx_status = 3
elseif matrix.formed then
mtx_status = 4
else
mtx_status = 2
end
_record_multiblock_status(m_stat == MTX_STATE.FAULT, matrix, m_ps)
end
m_ps.publish("InductionMatrixStatus", mtx_status)
m_ps.publish("InductionMatrixStateStatus", m_stat)
m_ps.publish("eta_string", data[9][1])
m_ps.publish("avg_charge", data[9][2])
m_ps.publish("avg_inflow", data[9][3])
m_ps.publish("avg_outflow", data[9][4])
m_ps.publish("is_charging", data[9][5])
m_ps.publish("is_discharging", data[9][6])
m_ps.publish("at_max_io", data[9][7])
-- sps data
fac.sps_data_tbl[1] = data[11]
local sps = fac.sps_data_tbl[1]
local s_ps = fac.sps_ps_tbl[1]
local s_stat = data[10]
local sps_status = 1
if s_stat ~= SPS_STATE.OFFLINE then
if s_stat == SPS_STATE.FAULT then
sps_status = 3
elseif sps.formed then
sps_status = 4
else
sps_status = 2
end
_record_multiblock_status(s_stat == SPS_STATE.FAULT, sps, s_ps)
end
s_ps.publish("SPSStatus", sps_status)
s_ps.publish("SPSStateStatus", s_stat)
end
return function (io_obj) return function (io_obj)
io = io_obj io = io_obj
return iorx return iorx

View File

@ -88,16 +88,17 @@ local APP_ID = {
LOADER = 2, LOADER = 2,
-- main app pages -- main app pages
UNITS = 3, UNITS = 3,
CONTROL = 4, FACILITY = 4,
PROCESS = 5, CONTROL = 5,
WASTE = 6, PROCESS = 6,
GUIDE = 7, WASTE = 7,
ABOUT = 8, GUIDE = 8,
ABOUT = 9,
-- diagnostic app pages -- diagnostic app pages
ALARMS = 9, ALARMS = 10,
-- other -- other
DUMMY = 10, DUMMY = 11,
NUM_APPS = 10 NUM_APPS = 11
} }
pocket.APP_ID = APP_ID pocket.APP_ID = APP_ID
@ -553,6 +554,11 @@ 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 if self.sv.linked then _send_sv(MGMT_TYPE.DIAG_ALARM_SET, { id, state }) end
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
end
-- coordinator get unit data -- coordinator get unit data
function public.api__get_unit(unit) function public.api__get_unit(unit)
if self.api.linked then _send_api(CRDN_TYPE.API_GET_UNIT, { unit }) end if self.api.linked then _send_api(CRDN_TYPE.API_GET_UNIT, { unit }) end
@ -729,6 +735,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
if _check_length(packet, 11) then if _check_length(packet, 11) then
iocontrol.rx.record_facility_data(packet.data) iocontrol.rx.record_facility_data(packet.data)
end end
elseif packet.type == CRDN_TYPE.API_GET_FAC_DTL then
if _check_length(packet, 12) then
iocontrol.rx.record_fac_detail_data(packet.data)
end
elseif packet.type == CRDN_TYPE.API_GET_UNIT then elseif packet.type == CRDN_TYPE.API_GET_UNIT then
if _check_length(packet, 12) and type(packet.data[1]) == "number" and iocontrol.get_db().units[packet.data[1]] then if _check_length(packet, 12) and type(packet.data[1]) == "number" and iocontrol.get_db().units[packet.data[1]] then
iocontrol.rx.record_unit_data(packet.data) iocontrol.rx.record_unit_data(packet.data)
@ -903,7 +913,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
local ready = packet.data[1] local ready = packet.data[1]
local states = packet.data[2] local states = packet.data[2]
diag.tone_test.ready_warn.set_value(util.trinary(ready, "", "system not ready")) diag.tone_test.ready_warn.set_value(util.trinary(ready, "", "system not idle"))
for i = 1, #states do for i = 1, #states do
if diag.tone_test.tone_buttons[i] ~= nil then if diag.tone_test.tone_buttons[i] ~= nil then
@ -922,7 +932,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
local ready = packet.data[1] local ready = packet.data[1]
local states = packet.data[2] local states = packet.data[2]
diag.tone_test.ready_warn.set_value(util.trinary(ready, "", "system not ready")) diag.tone_test.ready_warn.set_value(util.trinary(ready, "", "system not idle"))
for i = 1, #states do for i = 1, #states do
if diag.tone_test.alarm_buttons[i] ~= nil then if diag.tone_test.alarm_buttons[i] ~= nil then

View File

@ -20,7 +20,7 @@ local pocket = require("pocket.pocket")
local renderer = require("pocket.renderer") local renderer = require("pocket.renderer")
local threads = require("pocket.threads") local threads = require("pocket.threads")
local POCKET_VERSION = "v0.12.13-alpha" local POCKET_VERSION = "v0.13.0-beta"
local println = util.println local println = util.println
local println_ts = util.println_ts local println_ts = util.println_ts

View File

@ -1,5 +1,5 @@
-- --
-- Unit Control Page -- Facility & Unit Control App
-- --
local types = require("scada-common.types") local types = require("scada-common.types")

258
pocket/ui/apps/facility.lua Normal file
View File

@ -0,0 +1,258 @@
--
-- Facility Overview App
--
local util = require("scada-common.util")
local iocontrol = require("pocket.iocontrol")
local pocket = require("pocket.pocket")
local style = require("pocket.ui.style")
local dyn_tank = require("pocket.ui.pages.dynamic_tank")
local facility_sps = require("pocket.ui.pages.facility_sps")
local induction_mtx = require("pocket.ui.pages.facility_matrix")
local core = require("graphics.core")
local Div = require("graphics.elements.Div")
local MultiPane = require("graphics.elements.MultiPane")
local TextBox = require("graphics.elements.TextBox")
local WaitingAnim = require("graphics.elements.animations.Waiting")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
local ALIGN = core.ALIGN
local cpair = core.cpair
local APP_ID = pocket.APP_ID
local label_fg_bg = style.label
local lu_col = style.label_unit_pair
local basic_states = style.icon_states.basic_states
local mode_states = style.icon_states.mode_states
local red_ind_s = style.icon_states.red_ind_s
local yel_ind_s = style.icon_states.yel_ind_s
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 frame = Div{parent=root,x=1,y=1}
local app = db.nav.register_app(APP_ID.FACILITY, 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.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 tank_page_navs = {}
local page_div = nil ---@type Div|nil
-- load the app (create the elements)
local function load()
local fac = db.facility
local f_ps = fac.ps
page_div = Div{parent=main,y=2,width=main.get_width()}
local panes = {} ---@type Div[]
-- refresh data callback, every 500ms it will re-send the query
local last_update = 0
local function update()
if util.time_ms() - last_update >= 500 then
db.api.get_fac()
last_update = util.time_ms()
end
end
--#region facility overview
local main_pane = Div{parent=page_div}
local f_div = Div{parent=main_pane,x=2,width=main.get_width()-2}
table.insert(panes, main_pane)
local fac_page = app.new_page(nil, #panes)
fac_page.tasks = { update }
TextBox{parent=f_div,y=1,text="Facility",alignment=ALIGN.CENTER}
local mtx_state = IconIndicator{parent=f_div,y=3,label="Matrix Status",states=basic_states}
local sps_state = IconIndicator{parent=f_div,label="SPS Status",states=basic_states}
mtx_state.register(fac.induction_ps_tbl[1], "InductionMatrixStatus", mtx_state.update)
sps_state.register(fac.sps_ps_tbl[1], "SPSStatus", sps_state.update)
TextBox{parent=f_div,y=6,text="RTU Gateways",fg_bg=label_fg_bg}
local rtu_count = DataIndicator{parent=f_div,x=19,y=6,label="",format="%3d",value=0,lu_colors=lu_col,width=3}
rtu_count.register(f_ps, "rtu_count", rtu_count.update)
TextBox{parent=f_div,y=8,text="Induction Matrix",alignment=ALIGN.CENTER}
local eta = TextBox{parent=f_div,x=1,y=10,text="ETA Unknown",alignment=ALIGN.CENTER,fg_bg=cpair(colors.white,colors.gray)}
eta.register(fac.induction_ps_tbl[1], "eta_string", eta.set_value)
TextBox{parent=f_div,y=12,text="Unit Statuses",alignment=ALIGN.CENTER}
f_div.line_break()
for i = 1, fac.num_units do
local ctrl = IconIndicator{parent=f_div,label="U"..i.." Control State",states=mode_states}
ctrl.register(db.units[i].unit_ps, "U_ControlStatus", ctrl.update)
end
--#endregion
--#region facility annunciator
local a_pane = Div{parent=page_div}
local a_div = Div{parent=a_pane,x=2,width=main.get_width()-2}
table.insert(panes, a_pane)
local annunc_page = app.new_page(nil, #panes)
annunc_page.tasks = { update }
TextBox{parent=a_div,y=1,text="Annunciator",alignment=ALIGN.CENTER}
local all_ok = IconIndicator{parent=a_div,y=3,label="Units Online",states=grn_ind_s}
local ind_mat = IconIndicator{parent=a_div,label="Induction Matrix",states=grn_ind_s}
local sps = IconIndicator{parent=a_div,label="SPS Connected",states=grn_ind_s}
all_ok.register(f_ps, "all_sys_ok", all_ok.update)
ind_mat.register(fac.induction_ps_tbl[1], "InductionMatrixStateStatus", function (status) ind_mat.update(status > 1) end)
sps.register(fac.sps_ps_tbl[1], "SPSStateStatus", function (status) sps.update(status > 1) end)
a_div.line_break()
local auto_scram = IconIndicator{parent=a_div,label="Automatic SCRAM",states=red_ind_s}
local matrix_flt = IconIndicator{parent=a_div,label="Ind. Matrix Fault",states=yel_ind_s}
local matrix_fill = IconIndicator{parent=a_div,label="Matrix Charge Hi",states=red_ind_s}
local unit_crit = IconIndicator{parent=a_div,label="Unit Crit. Alarm",states=red_ind_s}
local fac_rad_h = IconIndicator{parent=a_div,label="FAC Radiation Hi",states=red_ind_s}
local gen_fault = IconIndicator{parent=a_div,label="Gen Control Fault",states=yel_ind_s}
auto_scram.register(f_ps, "auto_scram", auto_scram.update)
matrix_flt.register(f_ps, "as_matrix_fault", matrix_flt.update)
matrix_fill.register(f_ps, "as_matrix_fill", matrix_fill.update)
unit_crit.register(f_ps, "as_crit_alarm", unit_crit.update)
fac_rad_h.register(f_ps, "as_radiation", fac_rad_h.update)
gen_fault.register(f_ps, "as_gen_fault", gen_fault.update)
--#endregion
--#region induction matrix
local mtx_page_nav = induction_mtx(app, panes, Div{parent=page_div}, fac.induction_ps_tbl[1], update)
--#endregion
--#region SPS
local sps_page_nav = facility_sps(app, panes, Div{parent=page_div}, fac.sps_ps_tbl[1], update)
--#endregion
--#region facility tank pages
local t_pane = Div{parent=page_div}
local t_div = Div{parent=t_pane,x=2,width=main.get_width()-2}
table.insert(panes, t_pane)
local tank_page = app.new_page(nil, #panes)
tank_page.tasks = { update }
TextBox{parent=t_div,y=1,text="Facility Tanks",alignment=ALIGN.CENTER}
local f_tank_id = 1
for t = 1, #fac.tank_list do
if fac.tank_list[t] == 1 then
t_div.line_break()
local tank = IconIndicator{parent=t_div,x=1,label="Unit Tank "..t.." (U-"..t..")",states=basic_states}
tank.register(db.units[t].tank_ps_tbl[1], "DynamicTankStatus", tank.update)
TextBox{parent=t_div,x=5,text="\x07 Unit "..t,fg_bg=label_fg_bg}
elseif fac.tank_list[t] == 2 then
tank_page_navs[f_tank_id] = dyn_tank(app, nil, panes, Div{parent=page_div}, t, fac.tank_ps_tbl[f_tank_id], update)
t_div.line_break()
local tank = IconIndicator{parent=t_div,x=1,label="Fac. Tank "..f_tank_id.." (F-"..f_tank_id..")",states=basic_states}
tank.register(fac.tank_ps_tbl[f_tank_id], "DynamicTankStatus", tank.update)
local connections = ""
for i = 1, #fac.tank_conns do
if fac.tank_conns[i] == t then
if connections ~= "" then
connections = connections .. "\n\x07 Unit " .. i
else
connections = "\x07 Unit " .. i
end
end
end
TextBox{parent=t_div,x=5,text=connections,fg_bg=label_fg_bg}
f_tank_id = f_tank_id + 1
end
end
--#endregion
-- setup multipane
local f_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
app.set_root_pane(f_pane)
-- setup sidebar
local list = {
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home },
{ label = "FAC", tall = true, color = core.cpair(colors.black, colors.orange), callback = fac_page.nav_to },
{ label = "ANN", color = core.cpair(colors.black, colors.yellow), callback = annunc_page.nav_to },
{ label = "MTX", color = core.cpair(colors.black, colors.white), callback = mtx_page_nav },
{ label = "SPS", color = core.cpair(colors.black, colors.purple), callback = sps_page_nav },
{ label = "TNK", tall = true, color = core.cpair(colors.black, colors.blue), callback = tank_page.nav_to }
}
for i = 1, #fac.tank_data_tbl do
table.insert(list, { label = "F-" .. i, color = core.cpair(colors.black, colors.lightGray), callback = tank_page_navs[i] })
end
app.set_sidebar(list)
-- done, show the app
load_pane.set_value(2)
end
-- delete the elements and switch back to the loading screen
local function unload()
if page_div then
page_div.delete()
page_div = nil
end
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = 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

View File

@ -1,5 +1,5 @@
-- --
-- Process Control Page -- Process Control App
-- --
local types = require("scada-common.types") local types = require("scada-common.types")

View File

@ -1,5 +1,5 @@
-- --
-- Unit Overview Page -- Unit Overview App
-- --
local util = require("scada-common.util") local util = require("scada-common.util")
@ -33,9 +33,8 @@ local cpair = core.cpair
local APP_ID = pocket.APP_ID local APP_ID = pocket.APP_ID
-- local label = style.label
local lu_col = style.label_unit_pair
local text_fg = style.text_fg local text_fg = style.text_fg
local lu_col = style.label_unit_pair
local basic_states = style.icon_states.basic_states local basic_states = style.icon_states.basic_states
local mode_states = style.icon_states.mode_states local mode_states = style.icon_states.mode_states
local red_ind_s = style.icon_states.red_ind_s local red_ind_s = style.icon_states.red_ind_s

View File

@ -1,5 +1,5 @@
-- --
-- Waste Control Page -- Waste Control App
-- --
local util = require("scada-common.util") local util = require("scada-common.util")
@ -33,9 +33,7 @@ local APP_ID = pocket.APP_ID
local label_fg_bg = style.label local label_fg_bg = style.label
local text_fg = style.text_fg local text_fg = style.text_fg
local lu_col = style.label_unit_pair local lu_col = style.label_unit_pair
local yel_ind_s = style.icon_states.yel_ind_s local yel_ind_s = style.icon_states.yel_ind_s
local wht_ind_s = style.icon_states.wht_ind_s local wht_ind_s = style.icon_states.wht_ind_s
@ -249,7 +247,7 @@ local function new_view(root)
local sps_status = StateIndicator{parent=s_div,x=5,y=3,states=style.sps.states,value=1,min_width=12} local sps_status = StateIndicator{parent=s_div,x=5,y=3,states=style.sps.states,value=1,min_width=12}
sps_status.register(f_ps, "sps_computed_status", sps_status.update) sps_status.register(db.facility.sps_ps_tbl[1], "SPSStateStatus", sps_status.update)
TextBox{parent=s_div,y=5,text="Input Rate",width=10,fg_bg=label_fg_bg} TextBox{parent=s_div,y=5,text="Input Rate",width=10,fg_bg=label_fg_bg}
local sps_in = DataIndicator{parent=s_div,label="",format="%16.2f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg} local sps_in = DataIndicator{parent=s_div,label="",format="%16.2f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg}
@ -264,8 +262,8 @@ local function new_view(root)
--#endregion --#endregion
-- setup multipane -- setup multipane
local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes} local w_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
app.set_root_pane(u_pane) app.set_root_pane(w_pane)
-- setup sidebar -- setup sidebar

View File

@ -10,6 +10,7 @@ local pocket = require("pocket.pocket")
local control_app = require("pocket.ui.apps.control") local control_app = require("pocket.ui.apps.control")
local diag_apps = require("pocket.ui.apps.diag_apps") local diag_apps = require("pocket.ui.apps.diag_apps")
local dummy_app = require("pocket.ui.apps.dummy_app") local dummy_app = require("pocket.ui.apps.dummy_app")
local facil_app = require("pocket.ui.apps.facility")
local guide_app = require("pocket.ui.apps.guide") local guide_app = require("pocket.ui.apps.guide")
local loader_app = require("pocket.ui.apps.loader") local loader_app = require("pocket.ui.apps.loader")
local process_app = require("pocket.ui.apps.process") local process_app = require("pocket.ui.apps.process")
@ -45,7 +46,7 @@ local function init(main)
local db = iocontrol.get_db() local db = iocontrol.get_db()
-- window header message and connection status -- window header message and connection status
TextBox{parent=main,y=1,text="EARLY ACCESS ALPHA S C ",fg_bg=style.header} TextBox{parent=main,y=1,text=" S C ",fg_bg=style.header}
local svr_conn = SignalBar{parent=main,y=1,x=22,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)} local svr_conn = SignalBar{parent=main,y=1,x=22,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)}
local crd_conn = SignalBar{parent=main,y=1,x=26,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)} local crd_conn = SignalBar{parent=main,y=1,x=26,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)}
@ -65,6 +66,7 @@ local function init(main)
-- create all the apps & pages -- create all the apps & pages
home_page(page_div) home_page(page_div)
unit_app(page_div) unit_app(page_div)
facil_app(page_div)
control_app(page_div) control_app(page_div)
process_app(page_div) process_app(page_div)
waste_app(page_div) waste_app(page_div)

View File

@ -18,6 +18,7 @@ local StateIndicator = require("graphics.elements.indicators.StateIndicator")
local CONTAINER_MODE = types.CONTAINER_MODE 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 local cpair = core.cpair
local label = style.label local label = style.label
@ -31,7 +32,7 @@ local mode_ind_s = {
-- create a dynamic tank view for the unit or facility app -- create a dynamic tank view for the unit or facility app
---@param app pocket_app ---@param app pocket_app
---@param page nav_tree_page ---@param page nav_tree_page|nil parent page, if applicable
---@param panes Div[] ---@param panes Div[]
---@param tank_pane Div ---@param tank_pane Div
---@param tank_id integer global facility tank ID (as used for tank list, etc) ---@param tank_id integer global facility tank ID (as used for tank list, etc)
@ -46,22 +47,35 @@ return function (app, page, panes, tank_pane, tank_id, ps, update)
local tank_page = app.new_page(page, #panes) local tank_page = app.new_page(page, #panes)
tank_page.tasks = { update } tank_page.tasks = { update }
TextBox{parent=tank_div,y=1,text="Dyn Tank",width=9} local tank_assign = ""
local status = StateIndicator{parent=tank_div,x=10,y=1,states=style.dtank.states,value=1,min_width=12} local f_tank_count = 0
for i = 1, #fac.tank_list do
local is_fac = fac.tank_list[i] == 2
if is_fac then f_tank_count = f_tank_count + 1 end
if i == tank_id then
tank_assign = util.trinary(is_fac, "F-" .. f_tank_count, "U-" .. i)
break
end
end
TextBox{parent=tank_div,y=1,text="Dynamic Tank "..tank_assign,alignment=ALIGN.CENTER}
local status = StateIndicator{parent=tank_div,x=5,y=3,states=style.dtank.states,value=1,min_width=12}
status.register(ps, "DynamicTankStateStatus", status.update) status.register(ps, "DynamicTankStateStatus", status.update)
TextBox{parent=tank_div,y=3,text="Fill",width=10,fg_bg=label} TextBox{parent=tank_div,y=5,text="Fill",width=10,fg_bg=label}
local tank_pcnt = DataIndicator{parent=tank_div,x=14,y=3,label="",format="%5.2f",value=100,unit="%",lu_colors=lu_col,width=8,fg_bg=text_fg} local tank_pcnt = DataIndicator{parent=tank_div,x=14,y=5,label="",format="%5.2f",value=100,unit="%",lu_colors=lu_col,width=8,fg_bg=text_fg}
local tank_amnt = DataIndicator{parent=tank_div,label="",format="%18d",value=0,commas=true,unit="mB",lu_colors=lu_col,width=21,fg_bg=text_fg} local tank_amnt = DataIndicator{parent=tank_div,label="",format="%18d",value=0,commas=true,unit="mB",lu_colors=lu_col,width=21,fg_bg=text_fg}
local is_water = fac.tank_fluid_types[tank_id] == COOLANT_TYPE.WATER local is_water = fac.tank_fluid_types[tank_id] == COOLANT_TYPE.WATER
TextBox{parent=tank_div,y=6,text=util.trinary(is_water,"Water","Sodium").." Level",width=12,fg_bg=label} TextBox{parent=tank_div,y=8,text=util.trinary(is_water,"Water","Sodium").." Level",width=12,fg_bg=label}
local level = HorizontalBar{parent=tank_div,y=7,bar_fg_bg=cpair(util.trinary(is_water,colors.blue,colors.lightBlue),colors.gray),height=1,width=21} local level = HorizontalBar{parent=tank_div,y=9,bar_fg_bg=cpair(util.trinary(is_water,colors.blue,colors.lightBlue),colors.gray),height=1,width=21}
TextBox{parent=tank_div,y=9,text="Tank Fill Mode",width=14,fg_bg=label} TextBox{parent=tank_div,y=11,text="Tank Fill Mode",width=14,fg_bg=label}
local can_fill = IconIndicator{parent=tank_div,y=10,label="Fill",states=mode_ind_s} local can_fill = IconIndicator{parent=tank_div,y=12,label="Fill",states=mode_ind_s}
local can_empty = IconIndicator{parent=tank_div,y=11,label="Empty",states=mode_ind_s} local can_empty = IconIndicator{parent=tank_div,y=13,label="Empty",states=mode_ind_s}
local function _can_fill(mode) local function _can_fill(mode)
can_fill.update((mode == CONTAINER_MODE.BOTH) or (mode == CONTAINER_MODE.FILL)) can_fill.update((mode == CONTAINER_MODE.BOTH) or (mode == CONTAINER_MODE.FILL))

View File

@ -0,0 +1,121 @@
local iocontrol = require("pocket.iocontrol")
local style = require("pocket.ui.style")
local core = require("graphics.core")
local Div = require("graphics.elements.Div")
local TextBox = require("graphics.elements.TextBox")
local PushButton = require("graphics.elements.controls.PushButton")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local HorizontalBar = require("graphics.elements.indicators.HorizontalBar")
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
local PowerIndicator = require("graphics.elements.indicators.PowerIndicator")
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
local ALIGN = core.ALIGN
local cpair = core.cpair
local label = style.label
local lu_col = style.label_unit_pair
local text_fg = style.text_fg
local yel_ind_s = style.icon_states.yel_ind_s
local wht_ind_s = style.icon_states.wht_ind_s
-- create an induction matrix view for the facility app
---@param app pocket_app
---@param panes Div[]
---@param matrix_pane Div
---@param ps psil
---@param update function
return function (app, panes, matrix_pane, ps, update)
local db = iocontrol.get_db()
local fac = db.facility
local mtx_div = Div{parent=matrix_pane,x=2,width=matrix_pane.get_width()-2}
table.insert(panes, mtx_div)
local matrix_page = app.new_page(nil, #panes)
matrix_page.tasks = { update }
TextBox{parent=mtx_div,y=1,text="Induction Matrix",alignment=ALIGN.CENTER}
local status = StateIndicator{parent=mtx_div,x=5,y=3,states=style.imatrix.states,value=1,min_width=12}
status.register(ps, "InductionMatrixStateStatus", status.update)
TextBox{parent=mtx_div,text="Chg",y=5,fg_bg=label}
local chg_bar = HorizontalBar{parent=mtx_div,x=5,y=5,height=1,fg_bg=cpair(colors.green,colors.gray)}
TextBox{parent=mtx_div,text="In",y=7,fg_bg=label}
local in_bar = HorizontalBar{parent=mtx_div,x=5,y=7,height=1,fg_bg=cpair(colors.blue,colors.gray)}
TextBox{parent=mtx_div,text="Out",y=9,fg_bg=label}
local out_bar = HorizontalBar{parent=mtx_div,x=5,y=9,height=1,fg_bg=cpair(colors.red,colors.gray)}
local function calc_saturation(val)
local data = fac.induction_data_tbl[1]
if (type(data.build) == "table") and (type(data.build.transfer_cap) == "number") and (data.build.transfer_cap > 0) then
return val / data.build.transfer_cap
else return 0 end
end
chg_bar.register(ps, "energy_fill", chg_bar.update)
in_bar.register(ps, "last_input", function (val) in_bar.update(calc_saturation(val)) end)
out_bar.register(ps, "last_output", function (val) out_bar.update(calc_saturation(val)) end)
local energy = PowerIndicator{parent=mtx_div,y=11,lu_colors=lu_col,label="Chg: ",unit=db.energy_label,format="%8.2f",value=0,width=21,fg_bg=text_fg}
local avg_chg = PowerIndicator{parent=mtx_div,lu_colors=lu_col,label="\xb7Avg: ",unit=db.energy_label,format="%8.2f",value=0,width=21,fg_bg=text_fg}
local input = PowerIndicator{parent=mtx_div,lu_colors=lu_col,label="In: ",unit=db.energy_label,format="%8.2f",rate=true,value=0,width=21,fg_bg=text_fg}
local avg_in = PowerIndicator{parent=mtx_div,lu_colors=lu_col,label="\xb7Avg: ",unit=db.energy_label,format="%8.2f",rate=true,value=0,width=21,fg_bg=text_fg}
local output = PowerIndicator{parent=mtx_div,lu_colors=lu_col,label="Out: ",unit=db.energy_label,format="%8.2f",rate=true,value=0,width=21,fg_bg=text_fg}
local avg_out = PowerIndicator{parent=mtx_div,lu_colors=lu_col,label="\xb7Avg: ",unit=db.energy_label,format="%8.2f",rate=true,value=0,width=21,fg_bg=text_fg}
energy.register(ps, "energy", function (val) energy.update(db.energy_convert(val)) end)
avg_chg.register(ps, "avg_charge", avg_chg.update)
input.register(ps, "last_input", function (val) input.update(db.energy_convert(val)) end)
avg_in.register(ps, "avg_inflow", avg_in.update)
output.register(ps, "last_output", function (val) output.update(db.energy_convert(val)) end)
avg_out.register(ps, "avg_outflow", avg_out.update)
local mtx_ext_div = Div{parent=matrix_pane,x=2,width=matrix_pane.get_width()-2}
table.insert(panes, mtx_ext_div)
local mtx_ext_page = app.new_page(matrix_page, #panes)
mtx_ext_page.tasks = { update }
PushButton{parent=mtx_div,x=9,y=18,text="MORE",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=mtx_ext_page.nav_to}
PushButton{parent=mtx_ext_div,x=9,y=18,text="BACK",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=matrix_page.nav_to}
TextBox{parent=mtx_ext_div,y=1,text="More Matrix Info",alignment=ALIGN.CENTER}
local chging = IconIndicator{parent=mtx_ext_div,y=3,label="Charging",states=wht_ind_s}
local dischg = IconIndicator{parent=mtx_ext_div,y=4,label="Discharging",states=wht_ind_s}
TextBox{parent=mtx_ext_div,text="Energy Fill",x=1,y=6,width=13,fg_bg=label}
local fill = DataIndicator{parent=mtx_ext_div,x=14,y=6,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
chging.register(ps, "is_charging", chging.update)
dischg.register(ps, "is_discharging", dischg.update)
fill.register(ps, "energy_fill", function (x) fill.update(x * 100) end)
local max_io = IconIndicator{parent=mtx_ext_div,y=8,label="Max I/O Rate",states=yel_ind_s}
TextBox{parent=mtx_ext_div,text="Input Util.",x=1,y=10,width=13,fg_bg=label}
local in_util = DataIndicator{parent=mtx_ext_div,x=14,y=10,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
TextBox{parent=mtx_ext_div,text="Output Util.",x=1,y=11,width=13,fg_bg=label}
local out_util = DataIndicator{parent=mtx_ext_div,x=14,y=11,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
max_io.register(ps, "at_max_io", max_io.update)
in_util.register(ps, "last_input", function (x) in_util.update(calc_saturation(x) * 100) end)
out_util.register(ps, "last_output", function (x) out_util.update(calc_saturation(x) * 100) end)
TextBox{parent=mtx_ext_div,text="Capacity ("..db.energy_label..")",x=1,y=13,fg_bg=label}
local capacity = DataIndicator{parent=mtx_ext_div,y=14,lu_colors=lu_col,label="",unit="",format="%21d",value=0,width=21,fg_bg=text_fg}
TextBox{parent=mtx_ext_div,text="Max In/Out ("..db.energy_label.."/t)",x=1,y=15,fg_bg=label}
local trans_cap = DataIndicator{parent=mtx_ext_div,y=16,lu_colors=lu_col,label="",unit="",format="%21d",rate=true,value=0,width=21,fg_bg=text_fg}
capacity.register(ps, "max_energy", function (val) capacity.update(db.energy_convert(val)) end)
trans_cap.register(ps, "transfer_cap", function (val) trans_cap.update(db.energy_convert(val)) end)
return matrix_page.nav_to
end

View File

@ -0,0 +1,84 @@
local iocontrol = require("pocket.iocontrol")
local style = require("pocket.ui.style")
local core = require("graphics.core")
local Div = require("graphics.elements.Div")
local TextBox = require("graphics.elements.TextBox")
local PushButton = require("graphics.elements.controls.PushButton")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local HorizontalBar = require("graphics.elements.indicators.HorizontalBar")
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
local ALIGN = core.ALIGN
local cpair = core.cpair
local label = style.label
local lu_col = style.label_unit_pair
local text_fg = style.text_fg
-- create an SPS view in the facility app
---@param app pocket_app
---@param panes Div[]
---@param sps_pane Div
---@param ps psil
---@param update function
return function (app, panes, sps_pane, ps, update)
local db = iocontrol.get_db()
local sps_div = Div{parent=sps_pane,x=2,width=sps_pane.get_width()-2}
table.insert(panes, sps_div)
local sps_page = app.new_page(nil, #panes)
sps_page.tasks = { update }
TextBox{parent=sps_div,y=1,text="Facility SPS",alignment=ALIGN.CENTER}
local status = StateIndicator{parent=sps_div,x=5,y=3,states=style.sps.states,value=1,min_width=12}
status.register(ps, "SPSStateStatus", status.update)
TextBox{parent=sps_div,text="Po",y=5,fg_bg=label}
local po_bar = HorizontalBar{parent=sps_div,x=4,y=5,fg_bg=cpair(colors.cyan,colors.gray),height=1}
TextBox{parent=sps_div,text="AM",y=7,fg_bg=label}
local am_bar = HorizontalBar{parent=sps_div,x=4,y=7,fg_bg=cpair(colors.purple,colors.gray),height=1}
po_bar.register(ps, "input_fill", po_bar.update)
am_bar.register(ps, "output_fill", am_bar.update)
TextBox{parent=sps_div,y=9,text="Input Rate",width=10,fg_bg=label}
local input_rate = DataIndicator{parent=sps_div,label="",format="%16.2f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg}
TextBox{parent=sps_div,y=12,text="Production Rate",width=15,fg_bg=label}
local proc_rate = DataIndicator{parent=sps_div,label="",format="%16d",value=0,unit="\xb5B/t",lu_colors=lu_col,width=21,fg_bg=text_fg}
proc_rate.register(ps, "process_rate", function (r) proc_rate.update(r * 1000) end)
input_rate.register(db.facility.ps, "po_am_rate", input_rate.update)
local sps_ext_div = Div{parent=sps_pane,x=2,width=sps_pane.get_width()-2}
table.insert(panes, sps_ext_div)
local sps_ext_page = app.new_page(sps_page, #panes)
sps_ext_page.tasks = { update }
PushButton{parent=sps_div,x=9,y=18,text="MORE",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=sps_ext_page.nav_to}
PushButton{parent=sps_ext_div,x=9,y=18,text="BACK",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=sps_page.nav_to}
TextBox{parent=sps_ext_div,y=1,text="More SPS Info",alignment=ALIGN.CENTER}
TextBox{parent=sps_ext_div,text="Polonium",x=1,y=3,width=13,fg_bg=label}
local input_p = DataIndicator{parent=sps_ext_div,x=14,y=3,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
local input_amnt = DataIndicator{parent=sps_ext_div,x=1,y=4,lu_colors=lu_col,label="",unit="mB",format="%18.0f",value=0,commas=true,width=21,fg_bg=text_fg}
input_p.register(ps, "input_fill", function (x) input_p.update(x * 100) end)
input_amnt.register(ps, "input", function (x) input_amnt.update(x.amount) end)
TextBox{parent=sps_ext_div,text="Antimatter",x=1,y=6,width=15,fg_bg=label}
local output_p = DataIndicator{parent=sps_ext_div,x=14,y=6,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
local output_amnt = DataIndicator{parent=sps_ext_div,x=1,y=7,lu_colors=lu_col,label="",unit="\xb5B",format="%18.3f",value=0,commas=true,width=21,fg_bg=text_fg}
output_p.register(ps, "output_fill", function (x) output_p.update(x * 100) end)
output_amnt.register(ps, "output", function (x) output_amnt.update(x.amount) end)
return sps_page.nav_to
end

View File

@ -46,7 +46,7 @@ local function new_view(root)
local active_fg_bg = cpair(colors.white,colors.gray) local active_fg_bg = cpair(colors.white,colors.gray)
App{parent=apps_1,x=2,y=2,text="U",title="Units",callback=function()open(APP_ID.UNITS)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg} App{parent=apps_1,x=2,y=2,text="U",title="Units",callback=function()open(APP_ID.UNITS)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg}
App{parent=apps_1,x=9,y=2,text="F",title="Facil",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg} App{parent=apps_1,x=9,y=2,text="F",title="Facil",callback=function()open(APP_ID.FACILITY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg}
App{parent=apps_1,x=16,y=2,text="\x15",title="Control",callback=function()open(APP_ID.CONTROL)end,app_fg_bg=cpair(colors.black,colors.green),active_fg_bg=active_fg_bg} App{parent=apps_1,x=16,y=2,text="\x15",title="Control",callback=function()open(APP_ID.CONTROL)end,app_fg_bg=cpair(colors.black,colors.green),active_fg_bg=active_fg_bg}
App{parent=apps_1,x=2,y=7,text="\x17",title="Process",callback=function()open(APP_ID.PROCESS)end,app_fg_bg=cpair(colors.black,colors.purple),active_fg_bg=active_fg_bg} App{parent=apps_1,x=2,y=7,text="\x17",title="Process",callback=function()open(APP_ID.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=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}

View File

@ -481,16 +481,14 @@ function rtu.comms(version, nic, conn_watchdog)
-- check validity then pass off to unit comms thread -- check validity then pass off to unit comms thread
return_code, reply = unit.modbus_io.check_request(packet) return_code, reply = unit.modbus_io.check_request(packet)
if return_code then if return_code then
-- check if there are more than 3 active transactions -- check if there are more than 3 active transactions, which will be treated as busy
-- still queue the packet, but this may indicate a problem
if unit.pkt_queue.length() > 3 then if unit.pkt_queue.length() > 3 then
reply = modbus.reply__srv_device_busy(packet) reply = modbus.reply__srv_device_busy(packet)
log.debug("queueing new request with " .. unit.pkt_queue.length() .. log.warning("device busy, discarding new request" .. unit_dbg_tag)
" transactions already in the queue" .. unit_dbg_tag) else
end -- queue the command if not busy
-- always queue the command even if busy
unit.pkt_queue.push_packet(packet) unit.pkt_queue.push_packet(packet)
end
else else
log.warning("cannot perform requested MODBUS operation" .. unit_dbg_tag) log.warning("cannot perform requested MODBUS operation" .. unit_dbg_tag)
end end

View File

@ -31,7 +31,7 @@ local sna_rtu = require("rtu.dev.sna_rtu")
local sps_rtu = require("rtu.dev.sps_rtu") local sps_rtu = require("rtu.dev.sps_rtu")
local turbinev_rtu = require("rtu.dev.turbinev_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu")
local RTU_VERSION = "v1.10.21" local RTU_VERSION = "v1.11.0"
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
local RTU_HW_STATE = databus.RTU_HW_STATE local RTU_HW_STATE = databus.RTU_HW_STATE

View File

@ -17,8 +17,8 @@ local max_distance = nil
local comms = {} local comms = {}
-- protocol/data versions (protocol/data independent changes tracked by util.lua version) -- protocol/data versions (protocol/data independent changes tracked by util.lua version)
comms.version = "3.0.3" comms.version = "3.0.4"
comms.api_version = "0.0.8" comms.api_version = "0.0.9"
---@enum PROTOCOL ---@enum PROTOCOL
local PROTOCOL = { local PROTOCOL = {
@ -66,11 +66,12 @@ local CRDN_TYPE = {
UNIT_BUILDS = 4, -- build of each reactor unit (reactor + RTUs) UNIT_BUILDS = 4, -- build of each reactor unit (reactor + RTUs)
UNIT_STATUSES = 5, -- state of each of the reactor units UNIT_STATUSES = 5, -- state of each of the reactor units
UNIT_CMD = 6, -- command a reactor unit UNIT_CMD = 6, -- command a reactor unit
API_GET_FAC = 7, -- API: get all the facility data API_GET_FAC = 7, -- API: get the facility general data
API_GET_UNIT = 8, -- API: get reactor unit data API_GET_FAC_DTL = 8, -- API: get (detailed) data for the facility app
API_GET_CTRL = 9, -- API: get data for the control app API_GET_UNIT = 9, -- API: get reactor unit data
API_GET_PROC = 10, -- API: get data for the process app API_GET_CTRL = 10, -- API: get data for the control app
API_GET_WASTE = 11 -- API: get data for the waste 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 ---@enum ESTABLISH_ACK

View File

@ -105,27 +105,39 @@ function boilerv.new(session_id, unit_id, advert, out_queue)
-- PRIVATE FUNCTIONS -- -- PRIVATE FUNCTIONS --
-- query if the multiblock is formed -- query if the multiblock is formed
local function _request_formed() ---@param time_now integer
local function _request_formed(time_now)
-- read discrete input 1 (start = 1, count = 1) -- read discrete input 1 (start = 1, count = 1)
self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) if self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) ~= false then
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
end
end end
-- query the build of the device -- query the build of the device
local function _request_build() ---@param time_now integer
local function _request_build(time_now)
-- read input registers 1 through 12 (start = 1, count = 12) -- read input registers 1 through 12 (start = 1, count = 12)
self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 12 }) if self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 12 }) ~= false then
self.periodics.next_build_req = time_now + PERIODICS.BUILD
end
end end
-- query the state of the device -- query the state of the device
local function _request_state() ---@param time_now integer
local function _request_state(time_now)
-- read input registers 13 through 15 (start = 13, count = 3) -- read input registers 13 through 15 (start = 13, count = 3)
self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 13, 3 }) if self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 13, 3 }) ~= false then
self.periodics.next_state_req = time_now + PERIODICS.STATE
end
end end
-- query the tanks of the device -- query the tanks of the device
local function _request_tanks() ---@param time_now integer
local function _request_tanks(time_now)
-- read input registers 16 through 27 (start = 16, count = 12) -- read input registers 16 through 27 (start = 16, count = 12)
self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 16, 12 }) if self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 16, 12 }) ~= false then
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
end
end end
-- PUBLIC FUNCTIONS -- -- PUBLIC FUNCTIONS --
@ -210,26 +222,12 @@ function boilerv.new(session_id, unit_id, advert, out_queue)
-- update this runner -- update this runner
---@param time_now integer milliseconds ---@param time_now integer milliseconds
function public.update(time_now) function public.update(time_now)
if self.periodics.next_formed_req <= time_now then if self.periodics.next_formed_req <= time_now then _request_formed(time_now) end
_request_formed()
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
end
if self.db.formed then if self.db.formed then
if not self.has_build and self.periodics.next_build_req <= time_now then if not self.has_build and self.periodics.next_build_req <= time_now then _request_build(time_now) end
_request_build() if self.periodics.next_state_req <= time_now then _request_state(time_now) end
self.periodics.next_build_req = time_now + PERIODICS.BUILD if self.periodics.next_tanks_req <= time_now then _request_tanks(time_now) end
end
if self.periodics.next_state_req <= time_now then
_request_state()
self.periodics.next_state_req = time_now + PERIODICS.STATE
end
if self.periodics.next_tanks_req <= time_now then
_request_tanks()
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
end
end end
self.session.post_update() self.session.post_update()

View File

@ -42,6 +42,8 @@ local PERIODICS = {
TANKS = 500 TANKS = 500
} }
local WRITE_BUSY_WAIT = 1000
-- create a new dynamicv rtu session runner -- create a new dynamicv rtu session runner
---@nodiscard ---@nodiscard
---@param session_id integer RTU gateway session ID ---@param session_id integer RTU gateway session ID
@ -63,6 +65,8 @@ function dynamicv.new(session_id, unit_id, advert, out_queue)
local self = { local self = {
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
has_build = false, has_build = false,
mode_cmd = nil, ---@type container_mode|nil
resend_mode = false,
periodics = { periodics = {
next_formed_req = 0, next_formed_req = 0,
next_build_req = 0, next_build_req = 0,
@ -101,45 +105,77 @@ function dynamicv.new(session_id, unit_id, advert, out_queue)
-- increment the container mode -- increment the container mode
local function _inc_cont_mode() local function _inc_cont_mode()
-- set mode command
if self.mode_cmd == "BOTH" then self.mode_cmd = "FILL"
elseif self.mode_cmd == "FILL" then self.mode_cmd = "EMPTY"
elseif self.mode_cmd == "EMPTY" then self.mode_cmd = "BOTH"
end
-- write coil 1 with unused value 0 -- write coil 1 with unused value 0
self.session.send_request(TXN_TYPES.INC_CONT, MODBUS_FCODE.WRITE_SINGLE_COIL, { 1, 0 }) if self.session.send_request(TXN_TYPES.INC_CONT, MODBUS_FCODE.WRITE_SINGLE_COIL, { 1, 0 }, WRITE_BUSY_WAIT) == false then
self.resend_mode = true
end
end end
-- decrement the container mode -- decrement the container mode
local function _dec_cont_mode() local function _dec_cont_mode()
-- set mode command
if self.mode_cmd == "BOTH" then self.mode_cmd = "EMPTY"
elseif self.mode_cmd == "EMPTY" then self.mode_cmd = "FILL"
elseif self.mode_cmd == "FILL" then self.mode_cmd = "BOTH"
end
-- write coil 2 with unused value 0 -- write coil 2 with unused value 0
self.session.send_request(TXN_TYPES.DEC_CONT, MODBUS_FCODE.WRITE_SINGLE_COIL, { 2, 0 }) if self.session.send_request(TXN_TYPES.DEC_CONT, MODBUS_FCODE.WRITE_SINGLE_COIL, { 2, 0 , WRITE_BUSY_WAIT}) == false then
self.resend_mode = false
end
end end
-- set the container mode -- set the container mode
---@param mode container_mode ---@param mode container_mode
local function _set_cont_mode(mode) local function _set_cont_mode(mode)
self.mode_cmd = mode
-- write holding register 1 -- write holding register 1
self.session.send_request(TXN_TYPES.SET_CONT, MODBUS_FCODE.WRITE_SINGLE_HOLD_REG, { 1, mode }) if self.session.send_request(TXN_TYPES.SET_CONT, MODBUS_FCODE.WRITE_SINGLE_HOLD_REG, { 1, mode }, WRITE_BUSY_WAIT) == false then
self.resend_mode = false
end
end end
-- query if the multiblock is formed -- query if the multiblock is formed
local function _request_formed() ---@param time_now integer
local function _request_formed(time_now)
-- read discrete input 1 (start = 1, count = 1) -- read discrete input 1 (start = 1, count = 1)
self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) if self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) ~= false then
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
end
end end
-- query the build of the device -- query the build of the device
local function _request_build() ---@param time_now integer
local function _request_build(time_now)
-- read input registers 1 through 7 (start = 1, count = 7) -- read input registers 1 through 7 (start = 1, count = 7)
self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 7 }) if self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 7 }) ~= false then
self.periodics.next_build_req = time_now + PERIODICS.BUILD
end
end end
-- query the state of the device -- query the state of the device
local function _request_state() ---@param time_now integer
local function _request_state(time_now)
-- read holding register 1 (start = 1, count = 1) -- read holding register 1 (start = 1, count = 1)
self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_MUL_HOLD_REGS, { 1, 1 }) if self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_MUL_HOLD_REGS, { 1, 1 }) ~= false then
self.periodics.next_state_req = time_now + PERIODICS.STATE
end
end end
-- query the tanks of the device -- query the tanks of the device
local function _request_tanks() ---@param time_now integer
local function _request_tanks(time_now)
-- read input registers 8 through 9 (start = 8, count = 2) -- read input registers 8 through 9 (start = 8, count = 2)
self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 8, 2 }) if self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 8, 2 }) ~= false then
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
end
end end
-- PUBLIC FUNCTIONS -- -- PUBLIC FUNCTIONS --
@ -182,6 +218,10 @@ function dynamicv.new(session_id, unit_id, advert, out_queue)
if m_pkt.length == 1 then if m_pkt.length == 1 then
self.db.state.last_update = util.time_ms() self.db.state.last_update = util.time_ms()
self.db.state.container_mode = m_pkt.data[1] self.db.state.container_mode = m_pkt.data[1]
if self.mode_cmd == nil then
self.mode_cmd = self.db.state.container_mode
end
else else
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
end end
@ -247,30 +287,22 @@ function dynamicv.new(session_id, unit_id, advert, out_queue)
end end
end end
-- try to resend mode if needed
if self.resend_mode then
self.resend_mode = false
_set_cont_mode(self.mode_cmd)
end
time_now = util.time() time_now = util.time()
-- handle periodics -- handle periodics
if self.periodics.next_formed_req <= time_now then if self.periodics.next_formed_req <= time_now then _request_formed(time_now) end
_request_formed()
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
end
if self.db.formed then if self.db.formed then
if not self.has_build and self.periodics.next_build_req <= time_now then if not self.has_build and self.periodics.next_build_req <= time_now then _request_build(time_now) end
_request_build() if self.periodics.next_state_req <= time_now then _request_state(time_now) end
self.periodics.next_build_req = time_now + PERIODICS.BUILD if self.periodics.next_tanks_req <= time_now then _request_tanks(time_now) end
end
if self.periodics.next_state_req <= time_now then
_request_state()
self.periodics.next_state_req = time_now + PERIODICS.STATE
end
if self.periodics.next_tanks_req <= time_now then
_request_tanks()
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
end
end end
self.session.post_update() self.session.post_update()

View File

@ -58,9 +58,12 @@ function envd.new(session_id, unit_id, advert, out_queue)
-- PRIVATE FUNCTIONS -- -- PRIVATE FUNCTIONS --
-- query the radiation readings of the device -- query the radiation readings of the device
local function _request_radiation() ---@param time_now integer
local function _request_radiation(time_now)
-- read input registers 1 and 2 (start = 1, count = 2) -- read input registers 1 and 2 (start = 1, count = 2)
self.session.send_request(TXN_TYPES.RAD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 2 }) if self.session.send_request(TXN_TYPES.RAD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 2 }) ~= false then
self.periodics.next_rad_req = time_now + PERIODICS.RAD
end
end end
-- PUBLIC FUNCTIONS -- -- PUBLIC FUNCTIONS --
@ -90,10 +93,7 @@ function envd.new(session_id, unit_id, advert, out_queue)
-- update this runner -- update this runner
---@param time_now integer milliseconds ---@param time_now integer milliseconds
function public.update(time_now) function public.update(time_now)
if self.periodics.next_rad_req <= time_now then if self.periodics.next_rad_req <= time_now then _request_radiation(time_now) end
_request_radiation()
self.periodics.next_rad_req = time_now + PERIODICS.RAD
end
self.session.post_update() self.session.post_update()
end end

View File

@ -89,27 +89,39 @@ function imatrix.new(session_id, unit_id, advert, out_queue)
-- PRIVATE FUNCTIONS -- -- PRIVATE FUNCTIONS --
-- query if the multiblock is formed -- query if the multiblock is formed
local function _request_formed() ---@param time_now integer
local function _request_formed(time_now)
-- read discrete input 1 (start = 1, count = 1) -- read discrete input 1 (start = 1, count = 1)
self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) if self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) ~= false then
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
end
end end
-- query the build of the device -- query the build of the device
local function _request_build() ---@param time_now integer
local function _request_build(time_now)
-- read input registers 1 through 9 (start = 1, count = 9) -- read input registers 1 through 9 (start = 1, count = 9)
self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 9 }) if self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 9 }) ~= false then
self.periodics.next_build_req = time_now + PERIODICS.BUILD
end
end end
-- query the state of the device -- query the state of the device
local function _request_state() ---@param time_now integer
local function _request_state(time_now)
-- read input register 10 through 11 (start = 10, count = 2) -- read input register 10 through 11 (start = 10, count = 2)
self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 10, 2 }) if self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 10, 2 }) ~= false then
self.periodics.next_state_req = time_now + PERIODICS.STATE
end
end end
-- query the tanks of the device -- query the tanks of the device
local function _request_tanks() ---@param time_now integer
local function _request_tanks(time_now)
-- read input registers 12 through 15 (start = 12, count = 3) -- read input registers 12 through 15 (start = 12, count = 3)
self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 12, 3 }) if self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 12, 3 }) ~= false then
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
end
end end
-- PUBLIC FUNCTIONS -- -- PUBLIC FUNCTIONS --
@ -181,26 +193,12 @@ function imatrix.new(session_id, unit_id, advert, out_queue)
-- update this runner -- update this runner
---@param time_now integer milliseconds ---@param time_now integer milliseconds
function public.update(time_now) function public.update(time_now)
if self.periodics.next_formed_req <= time_now then if self.periodics.next_formed_req <= time_now then _request_formed(time_now) end
_request_formed()
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
end
if self.db.formed then if self.db.formed then
if not self.has_build and self.periodics.next_build_req <= time_now then if not self.has_build and self.periodics.next_build_req <= time_now then _request_build(time_now) end
_request_build() if self.periodics.next_state_req <= time_now then _request_state(time_now) end
self.periodics.next_build_req = time_now + PERIODICS.BUILD if self.periodics.next_tanks_req <= time_now then _request_tanks(time_now) end
end
if self.periodics.next_state_req <= time_now then
_request_state()
self.periodics.next_state_req = time_now + PERIODICS.STATE
end
if self.periodics.next_tanks_req <= time_now then
_request_tanks()
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
end
end end
self.session.post_update() self.session.post_update()

View File

@ -80,21 +80,30 @@ function sna.new(session_id, unit_id, advert, out_queue)
-- PRIVATE FUNCTIONS -- -- PRIVATE FUNCTIONS --
-- query the build of the device -- query the build of the device
local function _request_build() ---@param time_now integer
local function _request_build(time_now)
-- read input registers 1 through 2 (start = 1, count = 2) -- read input registers 1 through 2 (start = 1, count = 2)
self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 2 }) if self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 2 }) ~= false then
self.periodics.next_build_req = time_now + PERIODICS.BUILD
end
end end
-- query the state of the device -- query the state of the device
local function _request_state() ---@param time_now integer
local function _request_state(time_now)
-- read input registers 3 through 4 (start = 3, count = 2) -- read input registers 3 through 4 (start = 3, count = 2)
self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 3, 2 }) if self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 3, 2 }) ~= false then
self.periodics.next_state_req = time_now + PERIODICS.STATE
end
end end
-- query the tanks of the device -- query the tanks of the device
local function _request_tanks() ---@param time_now integer
local function _request_tanks(time_now)
-- read input registers 5 through 10 (start = 5, count = 6) -- read input registers 5 through 10 (start = 5, count = 6)
self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 5, 6 }) if self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 5, 6 }) ~= false then
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
end
end end
-- PUBLIC FUNCTIONS -- -- PUBLIC FUNCTIONS --
@ -152,20 +161,9 @@ function sna.new(session_id, unit_id, advert, out_queue)
-- update this runner -- update this runner
---@param time_now integer milliseconds ---@param time_now integer milliseconds
function public.update(time_now) function public.update(time_now)
if not self.has_build and self.periodics.next_build_req <= time_now then if not self.has_build and self.periodics.next_build_req <= time_now then _request_build(time_now) end
_request_build() if self.periodics.next_state_req <= time_now then _request_state(time_now) end
self.periodics.next_build_req = time_now + PERIODICS.BUILD if self.periodics.next_tanks_req <= time_now then _request_tanks(time_now) end
end
if self.periodics.next_state_req <= time_now then
_request_state()
self.periodics.next_state_req = time_now + PERIODICS.STATE
end
if self.periodics.next_tanks_req <= time_now then
_request_tanks()
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
end
self.session.post_update() self.session.post_update()
end end

View File

@ -94,27 +94,39 @@ function sps.new(session_id, unit_id, advert, out_queue)
-- PRIVATE FUNCTIONS -- -- PRIVATE FUNCTIONS --
-- query if the multiblock is formed -- query if the multiblock is formed
local function _request_formed() ---@param time_now integer
local function _request_formed(time_now)
-- read discrete input 1 (start = 1, count = 1) -- read discrete input 1 (start = 1, count = 1)
self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) if self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) ~= false then
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
end
end end
-- query the build of the device -- query the build of the device
local function _request_build() ---@param time_now integer
local function _request_build(time_now)
-- read input registers 1 through 9 (start = 1, count = 9) -- read input registers 1 through 9 (start = 1, count = 9)
self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 9 }) if self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 9 }) ~= false then
self.periodics.next_build_req = time_now + PERIODICS.BUILD
end
end end
-- query the state of the device -- query the state of the device
local function _request_state() ---@param time_now integer
local function _request_state(time_now)
-- read input register 10 (start = 10, count = 1) -- read input register 10 (start = 10, count = 1)
self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 10, 1 }) if self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 10, 1 }) ~= false then
self.periodics.next_state_req = time_now + PERIODICS.STATE
end
end end
-- query the tanks of the device -- query the tanks of the device
local function _request_tanks() ---@param time_now integer
local function _request_tanks(time_now)
-- read input registers 11 through 19 (start = 11, count = 9) -- read input registers 11 through 19 (start = 11, count = 9)
self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 11, 9 }) if self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 11, 9 }) ~= false then
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
end
end end
-- PUBLIC FUNCTIONS -- -- PUBLIC FUNCTIONS --
@ -191,26 +203,12 @@ function sps.new(session_id, unit_id, advert, out_queue)
-- update this runner -- update this runner
---@param time_now integer milliseconds ---@param time_now integer milliseconds
function public.update(time_now) function public.update(time_now)
if self.periodics.next_formed_req <= time_now then if self.periodics.next_formed_req <= time_now then _request_formed(time_now) end
_request_formed()
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
end
if self.db.formed then if self.db.formed then
if not self.has_build and self.periodics.next_build_req <= time_now then if not self.has_build and self.periodics.next_build_req <= time_now then _request_build(time_now) end
_request_build() if self.periodics.next_state_req <= time_now then _request_state(time_now) end
self.periodics.next_build_req = time_now + PERIODICS.BUILD if self.periodics.next_tanks_req <= time_now then _request_tanks(time_now) end
end
if self.periodics.next_state_req <= time_now then
_request_state()
self.periodics.next_state_req = time_now + PERIODICS.STATE
end
if self.periodics.next_tanks_req <= time_now then
_request_tanks()
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
end
end end
self.session.post_update() self.session.post_update()

View File

@ -42,6 +42,8 @@ local PERIODICS = {
TANKS = 1000 TANKS = 1000
} }
local WRITE_BUSY_WAIT = 1000
-- create a new turbinev rtu session runner -- create a new turbinev rtu session runner
---@nodiscard ---@nodiscard
---@param session_id integer RTU gateway session ID ---@param session_id integer RTU gateway session ID
@ -63,6 +65,8 @@ function turbinev.new(session_id, unit_id, advert, out_queue)
local self = { local self = {
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
has_build = false, has_build = false,
mode_cmd = nil, ---@type dumping_mode|nil
resend_mode = false,
periodics = { periodics = {
next_formed_req = 0, next_formed_req = 0,
next_build_req = 0, next_build_req = 0,
@ -116,45 +120,77 @@ function turbinev.new(session_id, unit_id, advert, out_queue)
-- increment the dumping mode -- increment the dumping mode
local function _inc_dump_mode() local function _inc_dump_mode()
-- set mode command
if self.mode_cmd == "IDLE" then self.mode_cmd = "DUMPING_EXCESS"
elseif self.mode_cmd == "DUMPING_EXCESS" then self.mode_cmd = "DUMPING"
elseif self.mode_cmd == "DUMPING" then self.mode_cmd = "IDLE"
end
-- write coil 1 with unused value 0 -- write coil 1 with unused value 0
self.session.send_request(TXN_TYPES.INC_DUMP, MODBUS_FCODE.WRITE_SINGLE_COIL, { 1, 0 }) if self.session.send_request(TXN_TYPES.INC_DUMP, MODBUS_FCODE.WRITE_SINGLE_COIL, { 1, 0 }, WRITE_BUSY_WAIT) == false then
self.resend_mode = true
end
end end
-- decrement the dumping mode -- decrement the dumping mode
local function _dec_dump_mode() local function _dec_dump_mode()
-- set mode command
if self.mode_cmd == "IDLE" then self.mode_cmd = "DUMPING"
elseif self.mode_cmd == "DUMPING_EXCESS" then self.mode_cmd = "IDLE"
elseif self.mode_cmd == "DUMPING" then self.mode_cmd = "DUMPING_EXCESS"
end
-- write coil 2 with unused value 0 -- write coil 2 with unused value 0
self.session.send_request(TXN_TYPES.DEC_DUMP, MODBUS_FCODE.WRITE_SINGLE_COIL, { 2, 0 }) if self.session.send_request(TXN_TYPES.DEC_DUMP, MODBUS_FCODE.WRITE_SINGLE_COIL, { 2, 0 }, WRITE_BUSY_WAIT) == false then
self.resend_mode = true
end
end end
-- set the dumping mode -- set the dumping mode
---@param mode dumping_mode ---@param mode dumping_mode
local function _set_dump_mode(mode) local function _set_dump_mode(mode)
self.mode_cmd = mode
-- write holding register 1 -- write holding register 1
self.session.send_request(TXN_TYPES.SET_DUMP, MODBUS_FCODE.WRITE_SINGLE_HOLD_REG, { 1, mode }) if self.session.send_request(TXN_TYPES.SET_DUMP, MODBUS_FCODE.WRITE_SINGLE_HOLD_REG, { 1, mode }, WRITE_BUSY_WAIT) == false then
self.resend_mode = true
end
end end
-- query if the multiblock is formed -- query if the multiblock is formed
local function _request_formed() ---@param time_now integer
local function _request_formed(time_now)
-- read discrete input 1 (start = 1, count = 1) -- read discrete input 1 (start = 1, count = 1)
self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) if self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) ~= false then
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
end
end end
-- query the build of the device -- query the build of the device
local function _request_build() ---@param time_now integer
local function _request_build(time_now)
-- read input registers 1 through 15 (start = 1, count = 15) -- read input registers 1 through 15 (start = 1, count = 15)
self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 15 }) if self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 15 }) ~= false then
self.periodics.next_build_req = time_now + PERIODICS.BUILD
end
end end
-- query the state of the device -- query the state of the device
local function _request_state() ---@param time_now integer
local function _request_state(time_now)
-- read input registers 16 through 19 (start = 16, count = 4) -- read input registers 16 through 19 (start = 16, count = 4)
self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 16, 4 }) if self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 16, 4 }) ~= false then
self.periodics.next_state_req = time_now + PERIODICS.STATE
end
end end
-- query the tanks of the device -- query the tanks of the device
local function _request_tanks() ---@param time_now integer
local function _request_tanks(time_now)
-- read input registers 20 through 25 (start = 20, count = 6) -- read input registers 20 through 25 (start = 20, count = 6)
self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 20, 6 }) if self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 20, 6 }) ~= false then
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
end
end end
-- PUBLIC FUNCTIONS -- -- PUBLIC FUNCTIONS --
@ -208,6 +244,10 @@ function turbinev.new(session_id, unit_id, advert, out_queue)
self.db.state.prod_rate = m_pkt.data[2] self.db.state.prod_rate = m_pkt.data[2]
self.db.state.steam_input_rate = m_pkt.data[3] self.db.state.steam_input_rate = m_pkt.data[3]
self.db.state.dumping_mode = m_pkt.data[4] self.db.state.dumping_mode = m_pkt.data[4]
if self.mode_cmd == nil then
self.mode_cmd = self.db.state.dumping_mode
end
else else
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")") log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
end end
@ -277,30 +317,22 @@ function turbinev.new(session_id, unit_id, advert, out_queue)
end end
end end
-- try to resend mode if needed
if self.resend_mode then
self.resend_mode = false
_set_dump_mode(self.mode_cmd)
end
time_now = util.time() time_now = util.time()
-- handle periodics -- handle periodics
if self.periodics.next_formed_req <= time_now then if self.periodics.next_formed_req <= time_now then _request_formed(time_now) end
_request_formed()
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
end
if self.db.formed then if self.db.formed then
if not self.has_build and self.periodics.next_build_req <= time_now then if not self.has_build and self.periodics.next_build_req <= time_now then _request_build(time_now) end
_request_build() if self.periodics.next_state_req <= time_now then _request_state(time_now) end
self.periodics.next_build_req = time_now + PERIODICS.BUILD if self.periodics.next_tanks_req <= time_now then _request_tanks(time_now) end
end
if self.periodics.next_state_req <= time_now then
_request_state()
self.periodics.next_state_req = time_now + PERIODICS.STATE
end
if self.periodics.next_tanks_req <= time_now then
_request_tanks()
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
end
end end
self.session.post_update() self.session.post_update()

View File

@ -22,6 +22,8 @@ local RTU_US_DATA = {
unit_session.RTU_US_CMDS = RTU_US_CMDS unit_session.RTU_US_CMDS = RTU_US_CMDS
unit_session.RTU_US_DATA = RTU_US_DATA unit_session.RTU_US_DATA = RTU_US_DATA
local DEFAULT_BUSY_WAIT = 3000
-- create a new unit session runner -- create a new unit session runner
---@nodiscard ---@nodiscard
---@param session_id integer RTU gateway session ID ---@param session_id integer RTU gateway session ID
@ -36,7 +38,8 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t
reactor = advert.reactor, reactor = advert.reactor,
transaction_controller = txnctrl.new(), transaction_controller = txnctrl.new(),
connected = true, connected = true,
device_fail = false device_fail = false,
last_busy = 0
} }
---@class _unit_session ---@class _unit_session
@ -53,14 +56,21 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t
---@param txn_type integer transaction type ---@param txn_type integer transaction type
---@param f_code MODBUS_FCODE function code ---@param f_code MODBUS_FCODE function code
---@param register_param (number|string)[] register range or register and values ---@param register_param (number|string)[] register range or register and values
---@return integer txn_id transaction ID of this transaction ---@param busy_wait integer|nil milliseconds to wait (>0), or uses the default
function protected.send_request(txn_type, f_code, register_param) ---@return integer|false txn_id transaction ID of this transaction or false if not sent due to being busy
function protected.send_request(txn_type, f_code, register_param, busy_wait)
local txn_id = false ---@type integer|false
busy_wait = busy_wait or DEFAULT_BUSY_WAIT
if (util.time_ms() - self.last_busy) >= busy_wait then
local m_pkt = comms.modbus_packet() local m_pkt = comms.modbus_packet()
local txn_id = self.transaction_controller.create(txn_type) txn_id = self.transaction_controller.create(txn_type)
m_pkt.make(txn_id, unit_id, f_code, register_param) m_pkt.make(txn_id, unit_id, f_code, register_param)
out_queue.push_packet(m_pkt) out_queue.push_packet(m_pkt)
end
return txn_id return txn_id
end end
@ -99,9 +109,9 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t
-- will have to wait on reply, renew the transaction -- will have to wait on reply, renew the transaction
self.transaction_controller.renew(m_pkt.txn_id, txn_type) self.transaction_controller.renew(m_pkt.txn_id, txn_type)
elseif ex == MODBUS_EXCODE.SERVER_DEVICE_BUSY then elseif ex == MODBUS_EXCODE.SERVER_DEVICE_BUSY then
-- will have to wait on reply, renew the transaction -- will have to try again later
self.transaction_controller.renew(m_pkt.txn_id, txn_type) self.last_busy = util.time_ms()
log.debug(log_tag .. "MODBUS: device busy" .. txn_tag) log.warning(log_tag .. "MODBUS: device busy" .. txn_tag)
elseif ex == MODBUS_EXCODE.NEG_ACKNOWLEDGE then elseif ex == MODBUS_EXCODE.NEG_ACKNOWLEDGE then
-- general failure -- general failure
log.error(log_tag .. "MODBUS: negative acknowledge (bad request)" .. txn_tag) log.error(log_tag .. "MODBUS: negative acknowledge (bad request)" .. txn_tag)

View File

@ -22,7 +22,7 @@ local supervisor = require("supervisor.supervisor")
local svsessions = require("supervisor.session.svsessions") local svsessions = require("supervisor.session.svsessions")
local SUPERVISOR_VERSION = "v1.6.1" local SUPERVISOR_VERSION = "v1.6.2"
local println = util.println local println = util.println
local println_ts = util.println_ts local println_ts = util.println_ts