#118 coordinator code cleanup

This commit is contained in:
Mikayla Fischler 2023-02-22 23:09:47 -05:00
parent 79494f0587
commit 4340518ecf
18 changed files with 484 additions and 435 deletions

View File

@ -4,13 +4,17 @@ local apisessions = {}
function apisessions.handle_packet(packet) function apisessions.handle_packet(packet)
end end
function apisessions.check_all_watchdogs() -- attempt to identify which session's watchdog timer fired
end ---@param timer_event number
function apisessions.check_all_watchdogs(timer_event)
function apisessions.close_all()
end end
-- delete all closed sessions
function apisessions.free_all_closed() function apisessions.free_all_closed()
end end
-- close all open connections
function apisessions.close_all()
end
return apisessions return apisessions

View File

@ -25,6 +25,7 @@ local FAC_COMMAND = comms.FAC_COMMAND
local coordinator = {} local coordinator = {}
-- request the user to select a monitor -- request the user to select a monitor
---@nodiscard
---@param names table available monitors ---@param names table available monitors
---@return boolean|string|nil ---@return boolean|string|nil
local function ask_monitor(names) local function ask_monitor(names)
@ -64,9 +65,11 @@ function coordinator.configure_monitors(num_units)
end end
-- we need a certain number of monitors (1 per unit + 1 primary display) -- we need a certain number of monitors (1 per unit + 1 primary display)
if #names < num_units + 1 then local num_displays_needed = num_units + 1
println("not enough monitors connected (need " .. num_units + 1 .. ")") if #names < num_displays_needed then
log.warning("insufficient monitors present (need " .. num_units + 1 .. ")") local message = "not enough monitors connected (need " .. num_displays_needed .. ")"
println(message)
log.warning(message)
return false return false
end end
@ -125,7 +128,6 @@ function coordinator.configure_monitors(num_units)
else else
-- make sure all displays are connected -- make sure all displays are connected
for i = 1, num_units do for i = 1, num_units do
---@diagnostic disable-next-line: need-check-nil
local display = unit_displays[i] local display = unit_displays[i]
if not util.table_contains(names, display) then if not util.table_contains(names, display) then
@ -183,14 +185,19 @@ function coordinator.log_sys(message) log_dmesg(message, "SYSTEM") end
function coordinator.log_boot(message) log_dmesg(message, "BOOT") end function coordinator.log_boot(message) log_dmesg(message, "BOOT") end
function coordinator.log_comms(message) log_dmesg(message, "COMMS") end function coordinator.log_comms(message) log_dmesg(message, "COMMS") end
-- log a message for communications connecting, providing access to progress indication control functions
---@nodiscard
---@param message string ---@param message string
---@return function update, function done ---@return function update, function done
function coordinator.log_comms_connecting(message) function coordinator.log_comms_connecting(message)
---@diagnostic disable-next-line: return-type-mismatch local update, done = log_dmesg(message, "COMMS", true)
return log_dmesg(message, "COMMS", true) ---@cast update function
---@cast done function
return update, done
end end
-- coordinator communications -- coordinator communications
---@nodiscard
---@param version string coordinator version ---@param version string coordinator version
---@param modem table modem device ---@param modem table modem device
---@param sv_port integer port of configured supervisor ---@param sv_port integer port of configured supervisor
@ -203,23 +210,19 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
sv_linked = false, sv_linked = false,
sv_seq_num = 0, sv_seq_num = 0,
sv_r_seq_num = nil, sv_r_seq_num = nil,
modem = modem,
connected = false, connected = false,
last_est_ack = ESTABLISH_ACK.ALLOW last_est_ack = ESTABLISH_ACK.ALLOW
} }
---@class coord_comms
local public = {}
comms.set_trusted_range(range) comms.set_trusted_range(range)
-- PRIVATE FUNCTIONS -- -- PRIVATE FUNCTIONS --
-- configure modem channels -- configure modem channels
local function _conf_channels() local function _conf_channels()
self.modem.closeAll() modem.closeAll()
self.modem.open(sv_listen) modem.open(sv_listen)
self.modem.open(api_listen) modem.open(api_listen)
end end
_conf_channels() _conf_channels()
@ -242,7 +245,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
pkt.make(msg_type, msg) pkt.make(msg_type, msg)
s_pkt.make(self.sv_seq_num, protocol, pkt.raw_sendable()) s_pkt.make(self.sv_seq_num, protocol, pkt.raw_sendable())
self.modem.transmit(sv_port, sv_listen, s_pkt.raw_sendable()) modem.transmit(sv_port, sv_listen, s_pkt.raw_sendable())
self.sv_seq_num = self.sv_seq_num + 1 self.sv_seq_num = self.sv_seq_num + 1
end end
@ -259,11 +262,13 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
-- PUBLIC FUNCTIONS -- -- PUBLIC FUNCTIONS --
---@class coord_comms
local public = {}
-- reconnect a newly connected modem -- reconnect a newly connected modem
---@param modem table ---@param new_modem table
---@diagnostic disable-next-line: redefined-local function public.reconnect_modem(new_modem)
function public.reconnect_modem(modem) modem = new_modem
self.modem = modem
_conf_channels() _conf_channels()
end end
@ -275,6 +280,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
end end
-- attempt to connect to the subervisor -- attempt to connect to the subervisor
---@nodiscard
---@param timeout_s number timeout in seconds ---@param timeout_s number timeout in seconds
---@param tick_dmesg_waiting function callback to tick dmesg waiting ---@param tick_dmesg_waiting function callback to tick dmesg waiting
---@param task_done function callback to show done on dmesg ---@param task_done function callback to show done on dmesg
@ -400,7 +406,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
if l_port == api_listen then if l_port == api_listen then
if protocol == PROTOCOL.COORD_API then if protocol == PROTOCOL.COORD_API then
---@diagnostic disable-next-line: param-type-mismatch ---@cast packet capi_frame
apisessions.handle_packet(packet) apisessions.handle_packet(packet)
else else
log.debug("illegal packet type " .. protocol .. " on api listening channel", true) log.debug("illegal packet type " .. protocol .. " on api listening channel", true)
@ -421,6 +427,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
-- handle packet -- handle packet
if protocol == PROTOCOL.SCADA_CRDN then if protocol == PROTOCOL.SCADA_CRDN then
---@cast packet crdn_frame
if self.sv_linked then if self.sv_linked then
if packet.type == SCADA_CRDN_TYPE.INITIAL_BUILDS then if packet.type == SCADA_CRDN_TYPE.INITIAL_BUILDS then
if packet.length == 2 then if packet.length == 2 then
@ -432,7 +439,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
-- acknowledge receipt of builds -- acknowledge receipt of builds
_send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.INITIAL_BUILDS, {}) _send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.INITIAL_BUILDS, {})
else else
log.error("received invalid INITIAL_BUILDS packet") log.debug("received invalid INITIAL_BUILDS packet")
end end
else else
log.debug("INITIAL_BUILDS packet length mismatch") log.debug("INITIAL_BUILDS packet length mismatch")
@ -444,7 +451,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
-- acknowledge receipt of builds -- acknowledge receipt of builds
_send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.FAC_BUILDS, {}) _send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.FAC_BUILDS, {})
else else
log.error("received invalid FAC_BUILDS packet") log.debug("received invalid FAC_BUILDS packet")
end end
else else
log.debug("FAC_BUILDS packet length mismatch") log.debug("FAC_BUILDS packet length mismatch")
@ -452,7 +459,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
elseif packet.type == SCADA_CRDN_TYPE.FAC_STATUS then elseif packet.type == SCADA_CRDN_TYPE.FAC_STATUS then
-- update facility status -- update facility status
if not iocontrol.update_facility_status(packet.data) then if not iocontrol.update_facility_status(packet.data) then
log.error("received invalid FAC_STATUS packet") log.debug("received invalid FAC_STATUS packet")
end end
elseif packet.type == SCADA_CRDN_TYPE.FAC_CMD then elseif packet.type == SCADA_CRDN_TYPE.FAC_CMD then
-- facility command acknowledgement -- facility command acknowledgement
@ -485,7 +492,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
-- acknowledge receipt of builds -- acknowledge receipt of builds
_send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.UNIT_BUILDS, {}) _send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.UNIT_BUILDS, {})
else else
log.error("received invalid UNIT_BUILDS packet") log.debug("received invalid UNIT_BUILDS packet")
end end
else else
log.debug("UNIT_BUILDS packet length mismatch") log.debug("UNIT_BUILDS packet length mismatch")
@ -518,7 +525,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then
unit.ack_alarms_ack(ack) unit.ack_alarms_ack(ack)
elseif cmd == UNIT_COMMAND.SET_GROUP then elseif cmd == UNIT_COMMAND.SET_GROUP then
---@todo how is this going to be handled? -- UI will be updated to display current group if changed successfully
else else
log.debug(util.c("received unit command ack with unknown command ", cmd)) log.debug(util.c("received unit command ack with unknown command ", cmd))
end end
@ -535,6 +542,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
log.debug("discarding SCADA_CRDN packet before linked") log.debug("discarding SCADA_CRDN packet before linked")
end end
elseif protocol == PROTOCOL.SCADA_MGMT then elseif protocol == PROTOCOL.SCADA_MGMT then
---@cast packet mgmt_frame
if packet.type == SCADA_MGMT_TYPE.ESTABLISH then if packet.type == SCADA_MGMT_TYPE.ESTABLISH then
-- connection with supervisor established -- connection with supervisor established
if packet.length == 2 then if packet.length == 2 then
@ -562,10 +570,10 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
self.sv_linked = true self.sv_linked = true
else else
log.error("invalid supervisor configuration definitions received, establish failed") log.debug("invalid supervisor configuration definitions received, establish failed")
end end
else else
log.error("invalid supervisor configuration table received, establish failed") log.debug("invalid supervisor configuration table received, establish failed")
end end
else else
log.debug("SCADA_MGMT establish packet reply (len = 2) unsupported") log.debug("SCADA_MGMT establish packet reply (len = 2) unsupported")
@ -577,11 +585,11 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
if est_ack == ESTABLISH_ACK.DENY then if est_ack == ESTABLISH_ACK.DENY then
if self.last_est_ack ~= est_ack then if self.last_est_ack ~= est_ack then
log.debug("supervisor connection denied") log.info("supervisor connection denied")
end end
elseif est_ack == ESTABLISH_ACK.COLLISION then elseif est_ack == ESTABLISH_ACK.COLLISION then
if self.last_est_ack ~= est_ack then if self.last_est_ack ~= est_ack then
log.debug("supervisor connection denied due to collision") log.info("supervisor connection denied due to collision")
end end
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
if self.last_est_ack ~= est_ack then if self.last_est_ack ~= est_ack then
@ -619,9 +627,9 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
sv_watchdog.cancel() sv_watchdog.cancel()
self.sv_linked = false self.sv_linked = false
println_ts("server connection closed by remote host") println_ts("server connection closed by remote host")
log.warning("server connection closed by remote host") log.info("server connection closed by remote host")
else else
log.warning("received unknown SCADA_MGMT packet type " .. packet.type) log.debug("received unknown SCADA_MGMT packet type " .. packet.type)
end end
else else
log.debug("discarding non-link SCADA_MGMT packet before linked") log.debug("discarding non-link SCADA_MGMT packet before linked")
@ -636,6 +644,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
end end
-- check if the coordinator is still linked to the supervisor -- check if the coordinator is still linked to the supervisor
---@nodiscard
function public.is_linked() return self.sv_linked end function public.is_linked() return self.sv_linked end
return public return public

View File

@ -1,3 +1,7 @@
--
-- I/O Control for Supervisor/Coordinator Integration
--
local log = require("scada-common.log") local log = require("scada-common.log")
local psil = require("scada-common.psil") local psil = require("scada-common.psil")
local types = require("scada-common.types") local types = require("scada-common.types")
@ -16,7 +20,6 @@ local io = {}
-- initialize the coordinator IO controller -- initialize the coordinator IO controller
---@param conf facility_conf configuration ---@param conf facility_conf configuration
---@param comms coord_comms comms reference ---@param comms coord_comms comms reference
---@diagnostic disable-next-line: redefined-local
function iocontrol.init(conf, comms) function iocontrol.init(conf, comms)
---@class ioctl_facility ---@class ioctl_facility
io.facility = { io.facility = {
@ -41,11 +44,11 @@ function iocontrol.init(conf, comms)
radiation = types.new_zero_radiation_reading(), radiation = types.new_zero_radiation_reading(),
save_cfg_ack = function (success) end, ---@param success boolean save_cfg_ack = function (success) end, ---@param success boolean
start_ack = function (success) end, ---@param success boolean start_ack = function (success) end, ---@param success boolean
stop_ack = function (success) end, ---@param success boolean stop_ack = function (success) end, ---@param success boolean
scram_ack = function (success) end, ---@param success boolean scram_ack = function (success) end, ---@param success boolean
ack_alarms_ack = function (success) end, ---@param success boolean ack_alarms_ack = function (success) end, ---@param success boolean
ps = psil.create(), ps = psil.create(),
@ -56,7 +59,7 @@ function iocontrol.init(conf, comms)
env_d_data = {} env_d_data = {}
} }
-- create induction tables (max 1 per unit, preferably 1 total) -- create induction tables (currently only 1 is supported)
for _ = 1, conf.num_units do for _ = 1, conf.num_units do
local data = {} ---@type imatrix_session_db local data = {} ---@type imatrix_session_db
table.insert(io.facility.induction_ps_tbl, psil.create()) table.insert(io.facility.induction_ps_tbl, psil.create())
@ -170,6 +173,8 @@ end
---@param build table ---@param build table
---@return boolean valid ---@return boolean valid
function iocontrol.record_facility_builds(build) function iocontrol.record_facility_builds(build)
local valid = true
if type(build) == "table" then if type(build) == "table" then
local fac = io.facility local fac = io.facility
@ -187,96 +192,103 @@ function iocontrol.record_facility_builds(build)
end end
else else
log.debug(util.c("iocontrol.record_facility_builds: invalid induction matrix id ", id)) log.debug(util.c("iocontrol.record_facility_builds: invalid induction matrix id ", id))
valid = false
end end
end end
end end
else else
log.error("facility builds not a table") log.debug("facility builds not a table")
return false valid = false
end end
return true return valid
end end
-- populate unit structure builds -- populate unit structure builds
---@param builds table ---@param builds table
---@return boolean valid ---@return boolean valid
function iocontrol.record_unit_builds(builds) function iocontrol.record_unit_builds(builds)
local valid = true
-- note: if not all units and RTUs are connected, some will be nil -- note: if not all units and RTUs are connected, some will be nil
for id, build in pairs(builds) do for id, build in pairs(builds) do
local unit = io.units[id] ---@type ioctl_unit local unit = io.units[id] ---@type ioctl_unit
local log_header = util.c("iocontrol.record_unit_builds[UNIT ", id, "]: ")
if type(build) ~= "table" then if type(build) ~= "table" then
log.error(util.c("corrupted unit builds provided, unit ", id, " not a table")) log.debug(log_header .. "build not a table")
return false valid = false
elseif type(unit) ~= "table" then elseif type(unit) ~= "table" then
log.error(util.c("corrupted unit builds provided, invalid unit ", id)) log.debug(log_header .. "invalid unit id")
return false valid = false
end else
-- reactor build
if type(build.reactor) == "table" then
unit.reactor_data.mek_struct = build.reactor ---@type mek_struct
for key, val in pairs(unit.reactor_data.mek_struct) do
unit.unit_ps.publish(key, val)
end
local log_header = util.c("iocontrol.record_unit_builds[unit ", id, "]: ") if (type(unit.reactor_data.mek_struct.length) == "number") and (unit.reactor_data.mek_struct.length ~= 0) and
(type(unit.reactor_data.mek_struct.width) == "number") and (unit.reactor_data.mek_struct.width ~= 0) then
-- reactor build unit.unit_ps.publish("size", { unit.reactor_data.mek_struct.length, unit.reactor_data.mek_struct.width })
if type(build.reactor) == "table" then
unit.reactor_data.mek_struct = build.reactor ---@type mek_struct
for key, val in pairs(unit.reactor_data.mek_struct) do
unit.unit_ps.publish(key, val)
end
if (type(unit.reactor_data.mek_struct.length) == "number") and (unit.reactor_data.mek_struct.length ~= 0) and
(type(unit.reactor_data.mek_struct.width) == "number") and (unit.reactor_data.mek_struct.width ~= 0) then
unit.unit_ps.publish("size", { unit.reactor_data.mek_struct.length, unit.reactor_data.mek_struct.width })
end
end
-- boiler builds
if type(build.boilers) == "table" then
for b_id, boiler in pairs(build.boilers) do
if type(unit.boiler_data_tbl[b_id]) == "table" then
unit.boiler_data_tbl[b_id].formed = boiler[1] ---@type boolean
unit.boiler_data_tbl[b_id].build = boiler[2] ---@type table
unit.boiler_ps_tbl[b_id].publish("formed", boiler[1])
for key, val in pairs(unit.boiler_data_tbl[b_id].build) do
unit.boiler_ps_tbl[b_id].publish(key, val)
end
else
log.debug(util.c(log_header, "invalid boiler id ", b_id))
end end
end end
end
-- turbine builds -- boiler builds
if type(build.turbines) == "table" then if type(build.boilers) == "table" then
for t_id, turbine in pairs(build.turbines) do for b_id, boiler in pairs(build.boilers) do
if type(unit.turbine_data_tbl[t_id]) == "table" then if type(unit.boiler_data_tbl[b_id]) == "table" then
unit.turbine_data_tbl[t_id].formed = turbine[1] ---@type boolean unit.boiler_data_tbl[b_id].formed = boiler[1] ---@type boolean
unit.turbine_data_tbl[t_id].build = turbine[2] ---@type table unit.boiler_data_tbl[b_id].build = boiler[2] ---@type table
unit.turbine_ps_tbl[t_id].publish("formed", turbine[1]) unit.boiler_ps_tbl[b_id].publish("formed", boiler[1])
for key, val in pairs(unit.turbine_data_tbl[t_id].build) do for key, val in pairs(unit.boiler_data_tbl[b_id].build) do
unit.turbine_ps_tbl[t_id].publish(key, val) unit.boiler_ps_tbl[b_id].publish(key, val)
end
else
log.debug(util.c(log_header, "invalid boiler id ", b_id))
valid = false
end
end
end
-- turbine builds
if type(build.turbines) == "table" then
for t_id, turbine in pairs(build.turbines) do
if type(unit.turbine_data_tbl[t_id]) == "table" then
unit.turbine_data_tbl[t_id].formed = turbine[1] ---@type boolean
unit.turbine_data_tbl[t_id].build = turbine[2] ---@type table
unit.turbine_ps_tbl[t_id].publish("formed", turbine[1])
for key, val in pairs(unit.turbine_data_tbl[t_id].build) do
unit.turbine_ps_tbl[t_id].publish(key, val)
end
else
log.debug(util.c(log_header, "invalid turbine id ", t_id))
valid = false
end end
else
log.debug(util.c(log_header, "invalid turbine id ", t_id))
end end
end end
end end
end end
return true return valid
end end
-- update facility status -- update facility status
---@param status table ---@param status table
---@return boolean valid ---@return boolean valid
function iocontrol.update_facility_status(status) function iocontrol.update_facility_status(status)
local valid = true
local log_header = util.c("iocontrol.update_facility_status: ") local log_header = util.c("iocontrol.update_facility_status: ")
if type(status) ~= "table" then if type(status) ~= "table" then
log.debug(log_header .. "status not a table") log.debug(util.c(log_header, "status not a table"))
return false valid = false
else else
local fac = io.facility local fac = io.facility
@ -284,10 +296,17 @@ function iocontrol.update_facility_status(status)
local ctl_status = status[1] local ctl_status = status[1]
if type(ctl_status) == "table" and (#ctl_status == 14) then if type(ctl_status) == "table" and #ctl_status == 14 then
fac.all_sys_ok = ctl_status[1] fac.all_sys_ok = ctl_status[1]
fac.auto_ready = ctl_status[2] fac.auto_ready = ctl_status[2]
fac.auto_active = ctl_status[3] > 0
if type(ctl_status[3]) == "number" then
fac.auto_active = ctl_status[3] > 1
else
fac.auto_active = false
valid = false
end
fac.auto_ramping = ctl_status[4] fac.auto_ramping = ctl_status[4]
fac.auto_saturated = ctl_status[5] fac.auto_saturated = ctl_status[5]
@ -327,6 +346,7 @@ function iocontrol.update_facility_status(status)
end end
else else
log.debug(log_header .. "control status not a table or length mismatch") log.debug(log_header .. "control status not a table or length mismatch")
valid = false
end end
-- RTU statuses -- RTU statuses
@ -334,10 +354,10 @@ function iocontrol.update_facility_status(status)
local rtu_statuses = status[2] local rtu_statuses = status[2]
fac.rtu_count = 0 fac.rtu_count = 0
if type(rtu_statuses) == "table" then if type(rtu_statuses) == "table" then
-- connected RTU count -- connected RTU count
fac.rtu_count = rtu_statuses.count fac.rtu_count = rtu_statuses.count
fac.ps.publish("rtu_count", fac.rtu_count)
-- power statistics -- power statistics
if type(rtu_statuses.power) == "table" then if type(rtu_statuses.power) == "table" then
@ -346,6 +366,7 @@ function iocontrol.update_facility_status(status)
fac.induction_ps_tbl[1].publish("avg_outflow", rtu_statuses.power[3]) fac.induction_ps_tbl[1].publish("avg_outflow", rtu_statuses.power[3])
else else
log.debug(log_header .. "power statistics list not a table") log.debug(log_header .. "power statistics list not a table")
valid = false
end end
-- induction matricies statuses -- induction matricies statuses
@ -371,16 +392,16 @@ function iocontrol.update_facility_status(status)
if data.formed then if data.formed then
if rtu_faulted then if rtu_faulted then
fac.induction_ps_tbl[id].publish("computed_status", 3) -- faulted fac.induction_ps_tbl[id].publish("computed_status", 3) -- faulted
elseif data.tanks.energy_fill >= 0.99 then elseif data.tanks.energy_fill >= 0.99 then
fac.induction_ps_tbl[id].publish("computed_status", 6) -- full fac.induction_ps_tbl[id].publish("computed_status", 6) -- full
elseif data.tanks.energy_fill <= 0.01 then elseif data.tanks.energy_fill <= 0.01 then
fac.induction_ps_tbl[id].publish("computed_status", 5) -- empty fac.induction_ps_tbl[id].publish("computed_status", 5) -- empty
else else
fac.induction_ps_tbl[id].publish("computed_status", 4) -- on-line fac.induction_ps_tbl[id].publish("computed_status", 4) -- on-line
end end
else else
fac.induction_ps_tbl[id].publish("computed_status", 2) -- not formed fac.induction_ps_tbl[id].publish("computed_status", 2) -- not formed
end end
for key, val in pairs(fac.induction_data_tbl[id].state) do for key, val in pairs(fac.induction_data_tbl[id].state) do
@ -396,6 +417,7 @@ function iocontrol.update_facility_status(status)
end end
else else
log.debug(log_header .. "induction matrix list not a table") log.debug(log_header .. "induction matrix list not a table")
valid = false
end end
-- environment detector status -- environment detector status
@ -413,313 +435,324 @@ function iocontrol.update_facility_status(status)
end end
else else
log.debug(log_header .. "radiation monitor list not a table") log.debug(log_header .. "radiation monitor list not a table")
return false valid = false
end end
else else
log.debug(log_header .. "rtu statuses not a table") log.debug(log_header .. "rtu statuses not a table")
valid = false
end end
fac.ps.publish("rtu_count", fac.rtu_count)
end end
return true return valid
end end
-- update unit statuses -- update unit statuses
---@param statuses table ---@param statuses table
---@return boolean valid ---@return boolean valid
function iocontrol.update_unit_statuses(statuses) function iocontrol.update_unit_statuses(statuses)
local valid = true
if type(statuses) ~= "table" then if type(statuses) ~= "table" then
log.debug("iocontrol.update_unit_statuses: unit statuses not a table") log.debug("iocontrol.update_unit_statuses: unit statuses not a table")
return false valid = false
elseif #statuses ~= #io.units then elseif #statuses ~= #io.units then
log.debug("iocontrol.update_unit_statuses: number of provided unit statuses does not match expected number of units") log.debug("iocontrol.update_unit_statuses: number of provided unit statuses does not match expected number of units")
return false valid = false
else else
local burn_rate_sum = 0.0 local burn_rate_sum = 0.0
-- get all unit statuses -- get all unit statuses
for i = 1, #statuses do for i = 1, #statuses do
local log_header = util.c("iocontrol.update_unit_statuses[unit ", i, "]: ") local log_header = util.c("iocontrol.update_unit_statuses[unit ", i, "]: ")
local unit = io.units[i] ---@type ioctl_unit local unit = io.units[i] ---@type ioctl_unit
local status = statuses[i] local status = statuses[i]
if type(status) ~= "table" or #status ~= 5 then if type(status) ~= "table" or #status ~= 5 then
log.debug(log_header .. "invalid status entry in unit statuses (not a table or invalid length)") log.debug(log_header .. "invalid status entry in unit statuses (not a table or invalid length)")
return false valid = false
end
-- reactor PLC status
local reactor_status = status[1]
if type(reactor_status) ~= "table" then
reactor_status = {}
log.debug(log_header .. "reactor status not a table")
end
if #reactor_status == 0 then
unit.unit_ps.publish("computed_status", 1) -- disconnected
elseif #reactor_status == 3 then
local mek_status = reactor_status[1]
local rps_status = reactor_status[2]
local gen_status = reactor_status[3]
if #gen_status == 6 then
unit.reactor_data.last_status_update = gen_status[1]
unit.reactor_data.control_state = gen_status[2]
unit.reactor_data.rps_tripped = gen_status[3]
unit.reactor_data.rps_trip_cause = gen_status[4]
unit.reactor_data.no_reactor = gen_status[5]
unit.reactor_data.formed = gen_status[6]
else
log.debug(log_header .. "reactor general status length mismatch")
end
unit.reactor_data.rps_status = rps_status ---@type rps_status
unit.reactor_data.mek_status = mek_status ---@type mek_status
-- if status hasn't been received, mek_status = {}
if type(unit.reactor_data.mek_status.act_burn_rate) == "number" then
burn_rate_sum = burn_rate_sum + unit.reactor_data.mek_status.act_burn_rate
end
if unit.reactor_data.mek_status.status then
unit.unit_ps.publish("computed_status", 5) -- running
else
if unit.reactor_data.no_reactor then
unit.unit_ps.publish("computed_status", 3) -- faulted
elseif not unit.reactor_data.formed then
unit.unit_ps.publish("computed_status", 2) -- multiblock not formed
elseif unit.reactor_data.rps_status.force_dis then
unit.unit_ps.publish("computed_status", 7) -- reactor force disabled
elseif unit.reactor_data.rps_tripped and unit.reactor_data.rps_trip_cause ~= "manual" then
unit.unit_ps.publish("computed_status", 6) -- SCRAM
else
unit.unit_ps.publish("computed_status", 4) -- disabled
end
end
for key, val in pairs(unit.reactor_data) do
if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then
unit.unit_ps.publish(key, val)
end
end
if type(unit.reactor_data.rps_status) == "table" then
for key, val in pairs(unit.reactor_data.rps_status) do
unit.unit_ps.publish(key, val)
end
end
if type(unit.reactor_data.mek_status) == "table" then
for key, val in pairs(unit.reactor_data.mek_status) do
unit.unit_ps.publish(key, val)
end
end
else else
log.debug(log_header .. "reactor status length mismatch") -- reactor PLC status
end local reactor_status = status[1]
-- RTU statuses if type(reactor_status) ~= "table" then
reactor_status = {}
log.debug(log_header .. "reactor status not a table")
end
local rtu_statuses = status[2] if #reactor_status == 0 then
unit.unit_ps.publish("computed_status", 1) -- disconnected
elseif #reactor_status == 3 then
local mek_status = reactor_status[1]
local rps_status = reactor_status[2]
local gen_status = reactor_status[3]
if type(rtu_statuses) == "table" then if #gen_status == 6 then
-- boiler statuses unit.reactor_data.last_status_update = gen_status[1]
if type(rtu_statuses.boilers) == "table" then unit.reactor_data.control_state = gen_status[2]
for id = 1, #unit.boiler_ps_tbl do unit.reactor_data.rps_tripped = gen_status[3]
if rtu_statuses.boilers[i] == nil then unit.reactor_data.rps_trip_cause = gen_status[4]
-- disconnected unit.reactor_data.no_reactor = gen_status[5]
unit.boiler_ps_tbl[id].publish("computed_status", 1) unit.reactor_data.formed = gen_status[6]
else
log.debug(log_header .. "reactor general status length mismatch")
end
unit.reactor_data.rps_status = rps_status ---@type rps_status
unit.reactor_data.mek_status = mek_status ---@type mek_status
-- if status hasn't been received, mek_status = {}
if type(unit.reactor_data.mek_status.act_burn_rate) == "number" then
burn_rate_sum = burn_rate_sum + unit.reactor_data.mek_status.act_burn_rate
end
if unit.reactor_data.mek_status.status then
unit.unit_ps.publish("computed_status", 5) -- running
else
if unit.reactor_data.no_reactor then
unit.unit_ps.publish("computed_status", 3) -- faulted
elseif not unit.reactor_data.formed then
unit.unit_ps.publish("computed_status", 2) -- multiblock not formed
elseif unit.reactor_data.rps_status.force_dis then
unit.unit_ps.publish("computed_status", 7) -- reactor force disabled
elseif unit.reactor_data.rps_tripped and unit.reactor_data.rps_trip_cause ~= "manual" then
unit.unit_ps.publish("computed_status", 6) -- SCRAM
else
unit.unit_ps.publish("computed_status", 4) -- disabled
end end
end end
for id, boiler in pairs(rtu_statuses.boilers) do for key, val in pairs(unit.reactor_data) do
if type(unit.boiler_data_tbl[id]) == "table" then if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then
local rtu_faulted = boiler[1] ---@type boolean unit.unit_ps.publish(key, val)
unit.boiler_data_tbl[id].formed = boiler[2] ---@type boolean end
unit.boiler_data_tbl[id].state = boiler[3] ---@type table end
unit.boiler_data_tbl[id].tanks = boiler[4] ---@type table
local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db if type(unit.reactor_data.rps_status) == "table" then
for key, val in pairs(unit.reactor_data.rps_status) do
unit.unit_ps.publish(key, val)
end
end
unit.boiler_ps_tbl[id].publish("formed", data.formed) if type(unit.reactor_data.mek_status) == "table" then
unit.boiler_ps_tbl[id].publish("faulted", rtu_faulted) for key, val in pairs(unit.reactor_data.mek_status) do
unit.unit_ps.publish(key, val)
end
end
else
log.debug(log_header .. "reactor status length mismatch")
valid = false
end
if rtu_faulted then -- RTU statuses
unit.boiler_ps_tbl[id].publish("computed_status", 3) -- faulted local rtu_statuses = status[2]
elseif data.formed then
if data.state.boil_rate > 0 then if type(rtu_statuses) == "table" then
unit.boiler_ps_tbl[id].publish("computed_status", 5) -- active -- boiler statuses
if type(rtu_statuses.boilers) == "table" then
for id = 1, #unit.boiler_ps_tbl do
if rtu_statuses.boilers[i] == nil then
-- disconnected
unit.boiler_ps_tbl[id].publish("computed_status", 1)
end
end
for id, boiler in pairs(rtu_statuses.boilers) do
if type(unit.boiler_data_tbl[id]) == "table" then
local rtu_faulted = boiler[1] ---@type boolean
unit.boiler_data_tbl[id].formed = boiler[2] ---@type boolean
unit.boiler_data_tbl[id].state = boiler[3] ---@type table
unit.boiler_data_tbl[id].tanks = boiler[4] ---@type table
local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db
unit.boiler_ps_tbl[id].publish("formed", data.formed)
unit.boiler_ps_tbl[id].publish("faulted", rtu_faulted)
if rtu_faulted then
unit.boiler_ps_tbl[id].publish("computed_status", 3) -- faulted
elseif data.formed then
if data.state.boil_rate > 0 then
unit.boiler_ps_tbl[id].publish("computed_status", 5) -- active
else
unit.boiler_ps_tbl[id].publish("computed_status", 4) -- idle
end
else else
unit.boiler_ps_tbl[id].publish("computed_status", 4) -- idle unit.boiler_ps_tbl[id].publish("computed_status", 2) -- not formed
end
for key, val in pairs(unit.boiler_data_tbl[id].state) do
unit.boiler_ps_tbl[id].publish(key, val)
end
for key, val in pairs(unit.boiler_data_tbl[id].tanks) do
unit.boiler_ps_tbl[id].publish(key, val)
end end
else else
unit.boiler_ps_tbl[id].publish("computed_status", 2) -- not formed log.debug(util.c(log_header, "invalid boiler id ", id))
valid = false
end end
for key, val in pairs(unit.boiler_data_tbl[id].state) do
unit.boiler_ps_tbl[id].publish(key, val)
end
for key, val in pairs(unit.boiler_data_tbl[id].tanks) do
unit.boiler_ps_tbl[id].publish(key, val)
end
else
log.debug(util.c(log_header, "invalid boiler id ", id))
end
end
else
log.debug(log_header .. "boiler list not a table")
end
-- turbine statuses
if type(rtu_statuses.turbines) == "table" then
for id = 1, #unit.turbine_ps_tbl do
if rtu_statuses.turbines[i] == nil then
-- disconnected
unit.turbine_ps_tbl[id].publish("computed_status", 1)
end end
else
log.debug(log_header .. "boiler list not a table")
valid = false
end end
for id, turbine in pairs(rtu_statuses.turbines) do -- turbine statuses
if type(unit.turbine_data_tbl[id]) == "table" then if type(rtu_statuses.turbines) == "table" then
local rtu_faulted = turbine[1] ---@type boolean for id = 1, #unit.turbine_ps_tbl do
unit.turbine_data_tbl[id].formed = turbine[2] ---@type boolean if rtu_statuses.turbines[i] == nil then
unit.turbine_data_tbl[id].state = turbine[3] ---@type table -- disconnected
unit.turbine_data_tbl[id].tanks = turbine[4] ---@type table unit.turbine_ps_tbl[id].publish("computed_status", 1)
end
end
local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db for id, turbine in pairs(rtu_statuses.turbines) do
if type(unit.turbine_data_tbl[id]) == "table" then
local rtu_faulted = turbine[1] ---@type boolean
unit.turbine_data_tbl[id].formed = turbine[2] ---@type boolean
unit.turbine_data_tbl[id].state = turbine[3] ---@type table
unit.turbine_data_tbl[id].tanks = turbine[4] ---@type table
unit.turbine_ps_tbl[id].publish("formed", data.formed) local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db
unit.turbine_ps_tbl[id].publish("faulted", rtu_faulted)
if rtu_faulted then unit.turbine_ps_tbl[id].publish("formed", data.formed)
unit.turbine_ps_tbl[id].publish("computed_status", 3) -- faulted unit.turbine_ps_tbl[id].publish("faulted", rtu_faulted)
elseif data.formed then
if data.tanks.energy_fill >= 0.99 then if rtu_faulted then
unit.turbine_ps_tbl[id].publish("computed_status", 6) -- trip unit.turbine_ps_tbl[id].publish("computed_status", 3) -- faulted
elseif data.state.flow_rate < 100 then elseif data.formed then
unit.turbine_ps_tbl[id].publish("computed_status", 4) -- idle if data.tanks.energy_fill >= 0.99 then
unit.turbine_ps_tbl[id].publish("computed_status", 6) -- trip
elseif data.state.flow_rate < 100 then
unit.turbine_ps_tbl[id].publish("computed_status", 4) -- idle
else
unit.turbine_ps_tbl[id].publish("computed_status", 5) -- active
end
else else
unit.turbine_ps_tbl[id].publish("computed_status", 5) -- active unit.turbine_ps_tbl[id].publish("computed_status", 2) -- not formed
end
for key, val in pairs(unit.turbine_data_tbl[id].state) do
unit.turbine_ps_tbl[id].publish(key, val)
end
for key, val in pairs(unit.turbine_data_tbl[id].tanks) do
unit.turbine_ps_tbl[id].publish(key, val)
end end
else else
unit.turbine_ps_tbl[id].publish("computed_status", 2) -- not formed log.debug(util.c(log_header, "invalid turbine id ", id))
valid = false
end end
end
else
log.debug(log_header .. "turbine list not a table")
valid = false
end
for key, val in pairs(unit.turbine_data_tbl[id].state) do -- environment detector status
unit.turbine_ps_tbl[id].publish(key, val) if type(rtu_statuses.rad_mon) == "table" then
end if #rtu_statuses.rad_mon > 0 then
local rad_mon = rtu_statuses.rad_mon[1]
local rtu_faulted = rad_mon[1] ---@type boolean
unit.radiation = rad_mon[2] ---@type number
for key, val in pairs(unit.turbine_data_tbl[id].tanks) do unit.unit_ps.publish("radiation", unit.radiation)
unit.turbine_ps_tbl[id].publish(key, val)
end
else else
log.debug(util.c(log_header, "invalid turbine id ", id)) unit.radiation = types.new_zero_radiation_reading()
end
else
log.debug(log_header .. "radiation monitor list not a table")
valid = false
end
else
log.debug(log_header .. "rtu list not a table")
valid = false
end
-- annunciator
unit.annunciator = status[3]
if type(unit.annunciator) ~= "table" then
unit.annunciator = {}
log.debug(log_header .. "annunciator state not a table")
valid = false
end
for key, val in pairs(unit.annunciator) do
if key == "TurbineTrip" then
-- split up turbine trip table for all turbines and a general OR combination
local trips = val
local any = false
for id = 1, #trips do
any = any or trips[id]
unit.turbine_ps_tbl[id].publish(key, trips[id])
end
unit.unit_ps.publish("TurbineTrip", any)
elseif key == "BoilerOnline" or key == "HeatingRateLow" or key == "WaterLevelLow" then
-- split up array for all boilers
for id = 1, #val do
unit.boiler_ps_tbl[id].publish(key, val[id])
end
elseif key == "TurbineOnline" or key == "SteamDumpOpen" or key == "TurbineOverSpeed" then
-- split up array for all turbines
for id = 1, #val do
unit.turbine_ps_tbl[id].publish(key, val[id])
end
elseif type(val) == "table" then
-- we missed one of the tables?
log.debug(log_header .. "unrecognized table found in annunciator list, this is a bug")
valid = false
else
-- non-table fields
unit.unit_ps.publish(key, val)
end
end
-- alarms
local alarm_states = status[4]
if type(alarm_states) == "table" then
for id = 1, #alarm_states do
local state = alarm_states[id]
unit.alarms[id] = state
if state == types.ALARM_STATE.TRIPPED or state == types.ALARM_STATE.ACKED then
unit.unit_ps.publish("Alarm_" .. id, 2)
elseif state == types.ALARM_STATE.RING_BACK then
unit.unit_ps.publish("Alarm_" .. id, 3)
else
unit.unit_ps.publish("Alarm_" .. id, 1)
end end
end end
else else
log.debug(log_header .. "turbine list not a table") log.debug(log_header .. "alarm states not a table")
return false valid = false
end end
-- environment detector status -- unit state fields
if type(rtu_statuses.rad_mon) == "table" then local unit_state = status[5]
if #rtu_statuses.rad_mon > 0 then
local rad_mon = rtu_statuses.rad_mon[1]
local rtu_faulted = rad_mon[1] ---@type boolean
unit.radiation = rad_mon[2] ---@type number
unit.unit_ps.publish("radiation", unit.radiation) if type(unit_state) == "table" then
if #unit_state == 5 then
unit.unit_ps.publish("U_StatusLine1", unit_state[1])
unit.unit_ps.publish("U_StatusLine2", unit_state[2])
unit.unit_ps.publish("U_WasteMode", unit_state[3])
unit.unit_ps.publish("U_AutoReady", unit_state[4])
unit.unit_ps.publish("U_AutoDegraded", unit_state[5])
else else
unit.radiation = types.new_zero_radiation_reading() log.debug(log_header .. "unit state length mismatch")
valid = false
end end
else else
log.debug(log_header .. "radiation monitor list not a table") log.debug(log_header .. "unit state not a table")
return false valid = false
end end
else
log.debug(log_header .. "rtu list not a table")
end
-- annunciator
unit.annunciator = status[3]
if type(unit.annunciator) ~= "table" then
unit.annunciator = {}
log.debug(log_header .. "annunciator state not a table")
end
for key, val in pairs(unit.annunciator) do
if key == "TurbineTrip" then
-- split up turbine trip table for all turbines and a general OR combination
local trips = val
local any = false
for id = 1, #trips do
any = any or trips[id]
unit.turbine_ps_tbl[id].publish(key, trips[id])
end
unit.unit_ps.publish("TurbineTrip", any)
elseif key == "BoilerOnline" or key == "HeatingRateLow" or key == "WaterLevelLow" then
-- split up array for all boilers
for id = 1, #val do
unit.boiler_ps_tbl[id].publish(key, val[id])
end
elseif key == "TurbineOnline" or key == "SteamDumpOpen" or key == "TurbineOverSpeed" then
-- split up array for all turbines
for id = 1, #val do
unit.turbine_ps_tbl[id].publish(key, val[id])
end
elseif type(val) == "table" then
-- we missed one of the tables?
log.error(log_header .. "unrecognized table found in annunciator list, this is a bug", true)
else
-- non-table fields
unit.unit_ps.publish(key, val)
end
end
-- alarms
local alarm_states = status[4]
if type(alarm_states) == "table" then
for id = 1, #alarm_states do
local state = alarm_states[id]
unit.alarms[id] = state
if state == types.ALARM_STATE.TRIPPED or state == types.ALARM_STATE.ACKED then
unit.unit_ps.publish("Alarm_" .. id, 2)
elseif state == types.ALARM_STATE.RING_BACK then
unit.unit_ps.publish("Alarm_" .. id, 3)
else
unit.unit_ps.publish("Alarm_" .. id, 1)
end
end
else
log.debug(log_header .. "alarm states not a table")
end
-- unit state fields
local unit_state = status[5]
if type(unit_state) == "table" then
if #unit_state == 5 then
unit.unit_ps.publish("U_StatusLine1", unit_state[1])
unit.unit_ps.publish("U_StatusLine2", unit_state[2])
unit.unit_ps.publish("U_WasteMode", unit_state[3])
unit.unit_ps.publish("U_AutoReady", unit_state[4])
unit.unit_ps.publish("U_AutoDegraded", unit_state[5])
else
log.debug(log_header .. "unit state length mismatch")
end
else
log.debug(log_header .. "unit state not a table")
end end
end end
@ -729,7 +762,7 @@ function iocontrol.update_unit_statuses(statuses)
sounder.eval(io.units) sounder.eval(io.units)
end end
return true return valid
end end
-- get the IO controller database -- get the IO controller database

View File

@ -1,3 +1,6 @@
--
-- Process Control Management
--
local comms = require("scada-common.comms") local comms = require("scada-common.comms")
local log = require("scada-common.log") local log = require("scada-common.log")
@ -30,11 +33,11 @@ local self = {
-------------------------- --------------------------
-- initialize the process controller -- initialize the process controller
---@param iocontrol ioctl ---@param iocontrol ioctl iocontrl system
---@diagnostic disable-next-line: redefined-local ---@param coord_comms coord_comms coordinator communications
function process.init(iocontrol, comms) function process.init(iocontrol, coord_comms)
self.io = iocontrol self.io = iocontrol
self.comms = comms self.comms = coord_comms
for i = 1, self.io.facility.num_units do for i = 1, self.io.facility.num_units do
self.config.limits[i] = 0.1 self.config.limits[i] = 0.1
@ -91,13 +94,13 @@ end
-- facility SCRAM command -- facility SCRAM command
function process.fac_scram() function process.fac_scram()
self.comms.send_fac_command(FAC_COMMAND.SCRAM_ALL) self.comms.send_fac_command(FAC_COMMAND.SCRAM_ALL)
log.debug("FAC: SCRAM ALL") log.debug("PROCESS: FAC SCRAM ALL")
end end
-- facility alarm acknowledge command -- facility alarm acknowledge command
function process.fac_ack_alarms() function process.fac_ack_alarms()
self.comms.send_fac_command(FAC_COMMAND.ACK_ALL_ALARMS) self.comms.send_fac_command(FAC_COMMAND.ACK_ALL_ALARMS)
log.debug("FAC: ACK ALL ALARMS") log.debug("PROCESS: FAC ACK ALL ALARMS")
end end
-- start reactor -- start reactor
@ -105,7 +108,7 @@ end
function process.start(id) function process.start(id)
self.io.units[id].control_state = true self.io.units[id].control_state = true
self.comms.send_unit_command(UNIT_COMMAND.START, id) self.comms.send_unit_command(UNIT_COMMAND.START, id)
log.debug(util.c("UNIT[", id, "]: START")) log.debug(util.c("PROCESS: UNIT[", id, "] START"))
end end
-- SCRAM reactor -- SCRAM reactor
@ -113,14 +116,14 @@ end
function process.scram(id) function process.scram(id)
self.io.units[id].control_state = false self.io.units[id].control_state = false
self.comms.send_unit_command(UNIT_COMMAND.SCRAM, id) self.comms.send_unit_command(UNIT_COMMAND.SCRAM, id)
log.debug(util.c("UNIT[", id, "]: SCRAM")) log.debug(util.c("PROCESS: UNIT[", id, "] SCRAM"))
end end
-- reset reactor protection system -- reset reactor protection system
---@param id integer unit ID ---@param id integer unit ID
function process.reset_rps(id) function process.reset_rps(id)
self.comms.send_unit_command(UNIT_COMMAND.RESET_RPS, id) self.comms.send_unit_command(UNIT_COMMAND.RESET_RPS, id)
log.debug(util.c("UNIT[", id, "]: RESET RPS")) log.debug(util.c("PROCESS: UNIT[", id, "] RESET RPS"))
end end
-- set burn rate -- set burn rate
@ -128,7 +131,7 @@ end
---@param rate number burn rate ---@param rate number burn rate
function process.set_rate(id, rate) function process.set_rate(id, rate)
self.comms.send_unit_command(UNIT_COMMAND.SET_BURN, id, rate) self.comms.send_unit_command(UNIT_COMMAND.SET_BURN, id, rate)
log.debug(util.c("UNIT[", id, "]: SET BURN = ", rate)) log.debug(util.c("PROCESS: UNIT[", id, "] SET BURN ", rate))
end end
-- set waste mode -- set waste mode
@ -139,13 +142,11 @@ function process.set_waste(id, mode)
self.io.units[id].unit_ps.publish("U_WasteMode", mode) self.io.units[id].unit_ps.publish("U_WasteMode", mode)
self.comms.send_unit_command(UNIT_COMMAND.SET_WASTE, id, mode) self.comms.send_unit_command(UNIT_COMMAND.SET_WASTE, id, mode)
log.debug(util.c("UNIT[", id, "]: SET WASTE = ", mode)) log.debug(util.c("PROCESS: UNIT[", id, "] SET WASTE ", mode))
local waste_mode = settings.get("WASTE_MODES") ---@type table|nil local waste_mode = settings.get("WASTE_MODES") ---@type table|nil
if type(waste_mode) ~= "table" then if type(waste_mode) ~= "table" then waste_mode = {} end
waste_mode = {}
end
waste_mode[id] = mode waste_mode[id] = mode
@ -160,7 +161,7 @@ end
---@param id integer unit ID ---@param id integer unit ID
function process.ack_all_alarms(id) function process.ack_all_alarms(id)
self.comms.send_unit_command(UNIT_COMMAND.ACK_ALL_ALARMS, id) self.comms.send_unit_command(UNIT_COMMAND.ACK_ALL_ALARMS, id)
log.debug(util.c("UNIT[", id, "]: ACK ALL ALARMS")) log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALL ALARMS"))
end end
-- acknowledge an alarm -- acknowledge an alarm
@ -168,7 +169,7 @@ end
---@param alarm integer alarm ID ---@param alarm integer alarm ID
function process.ack_alarm(id, alarm) function process.ack_alarm(id, alarm)
self.comms.send_unit_command(UNIT_COMMAND.ACK_ALARM, id, alarm) self.comms.send_unit_command(UNIT_COMMAND.ACK_ALARM, id, alarm)
log.debug(util.c("UNIT[", id, "]: ACK ALARM ", alarm)) log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALARM ", alarm))
end end
-- reset an alarm -- reset an alarm
@ -176,7 +177,7 @@ end
---@param alarm integer alarm ID ---@param alarm integer alarm ID
function process.reset_alarm(id, alarm) function process.reset_alarm(id, alarm)
self.comms.send_unit_command(UNIT_COMMAND.RESET_ALARM, id, alarm) self.comms.send_unit_command(UNIT_COMMAND.RESET_ALARM, id, alarm)
log.debug(util.c("UNIT[", id, "]: RESET ALARM ", alarm)) log.debug(util.c("PROCESS: UNIT[", id, "] RESET ALARM ", alarm))
end end
-- assign a unit to a group -- assign a unit to a group
@ -184,13 +185,11 @@ end
---@param group_id integer|0 group ID or 0 for independent ---@param group_id integer|0 group ID or 0 for independent
function process.set_group(unit_id, group_id) function process.set_group(unit_id, group_id)
self.comms.send_unit_command(UNIT_COMMAND.SET_GROUP, unit_id, group_id) self.comms.send_unit_command(UNIT_COMMAND.SET_GROUP, unit_id, group_id)
log.debug(util.c("UNIT[", unit_id, "]: SET GROUP ", group_id)) log.debug(util.c("PROCESS: UNIT[", unit_id, "] SET GROUP ", group_id))
local prio_groups = settings.get("PRIORITY_GROUPS") ---@type table|nil local prio_groups = settings.get("PRIORITY_GROUPS") ---@type table|nil
if type(prio_groups) ~= "table" then if type(prio_groups) ~= "table" then prio_groups = {} end
prio_groups = {}
end
prio_groups[unit_id] = group_id prio_groups[unit_id] = group_id
@ -208,13 +207,13 @@ end
-- stop automatic process control -- stop automatic process control
function process.stop_auto() function process.stop_auto()
self.comms.send_fac_command(FAC_COMMAND.STOP) self.comms.send_fac_command(FAC_COMMAND.STOP)
log.debug("FAC: STOP AUTO") log.debug("PROCESS: STOP AUTO CTL")
end end
-- start automatic process control -- start automatic process control
function process.start_auto() function process.start_auto()
self.comms.send_auto_start(self.config) self.comms.send_auto_start(self.config)
log.debug("FAC: START AUTO") log.debug("PROCESS: START AUTO CTL")
end end
-- save process control settings -- save process control settings
@ -246,8 +245,6 @@ function process.save(mode, burn_target, charge_target, gen_target, limits)
log.warning("process.save(): failed to save coordinator settings file") log.warning("process.save(): failed to save coordinator settings file")
end end
log.debug("saved = " .. util.strval(saved))
self.io.facility.save_cfg_ack(saved) self.io.facility.save_cfg_ack(saved)
end end
@ -273,18 +270,4 @@ function process.start_ack_handle(response)
self.io.facility.start_ack(ack) self.io.facility.start_ack(ack)
end end
--------------------------
-- SUPERVISOR RESPONSES --
--------------------------
-- acknowledgement from the supervisor to assign a unit to a group
function process.sv_assign(unit_id, group_id)
self.io.units[unit_id].group = group_id
end
-- acknowledgement from the supervisor to assign a unit a burn rate limit
function process.sv_limit(unit_id, limit)
self.io.units[unit_id].limit = limit
end
return process return process

View File

@ -1,3 +1,7 @@
--
-- Graphics Rendering Control
--
local log = require("scada-common.log") local log = require("scada-common.log")
local util = require("scada-common.util") local util = require("scada-common.util")
@ -56,6 +60,7 @@ function renderer.set_displays(monitors)
end end
-- check if the renderer is configured to use a given monitor peripheral -- check if the renderer is configured to use a given monitor peripheral
---@nodiscard
---@param periph table peripheral ---@param periph table peripheral
---@return boolean is_used ---@return boolean is_used
function renderer.is_monitor_used(periph) function renderer.is_monitor_used(periph)
@ -87,6 +92,7 @@ function renderer.reset(recolor)
end end
-- check main display width -- check main display width
---@nodiscard
---@return boolean width_okay ---@return boolean width_okay
function renderer.validate_main_display_width() function renderer.validate_main_display_width()
local w, _ = engine.monitors.primary.getSize() local w, _ = engine.monitors.primary.getSize()
@ -94,6 +100,7 @@ function renderer.validate_main_display_width()
end end
-- check display sizes -- check display sizes
---@nodiscard
---@return boolean valid all unit display dimensions OK ---@return boolean valid all unit display dimensions OK
function renderer.validate_unit_display_sizes() function renderer.validate_unit_display_sizes()
local valid = true local valid = true
@ -101,7 +108,7 @@ function renderer.validate_unit_display_sizes()
for id, monitor in pairs(engine.monitors.unit_displays) do for id, monitor in pairs(engine.monitors.unit_displays) do
local w, h = monitor.getSize() local w, h = monitor.getSize()
if w ~= 79 or h ~= 52 then if w ~= 79 or h ~= 52 then
log.warning(util.c("unit ", id, " display resolution not 79 wide by 52 tall: ", w, ", ", h)) log.warning(util.c("RENDERER: unit ", id, " display resolution not 79 wide by 52 tall: ", w, ", ", h))
valid = false valid = false
end end
end end
@ -171,6 +178,7 @@ function renderer.close_ui()
end end
-- is the UI ready? -- is the UI ready?
---@nodiscard
---@return boolean ready ---@return boolean ready
function renderer.ui_ready() return engine.ui_ready end function renderer.ui_ready() return engine.ui_ready end

View File

@ -14,7 +14,7 @@ local sounder = {}
local _2_PI = 2 * math.pi -- 2 whole pies, hope you're hungry local _2_PI = 2 * math.pi -- 2 whole pies, hope you're hungry
local _DRATE = 48000 -- 48kHz audio local _DRATE = 48000 -- 48kHz audio
local _MAX_VAL = 127/2 -- max signed integer in this 8-bit audio local _MAX_VAL = 127 / 2 -- max signed integer in this 8-bit audio
local _MAX_SAMPLES = 0x20000 -- 128 * 1024 samples local _MAX_SAMPLES = 0x20000 -- 128 * 1024 samples
local _05s_SAMPLES = 24000 -- half a second worth of samples local _05s_SAMPLES = 24000 -- half a second worth of samples
@ -26,7 +26,8 @@ local alarm_ctl = {
playing = false, playing = false,
num_active = 0, num_active = 0,
next_block = 1, next_block = 1,
quad_buffer = { {}, {}, {}, {} } -- split audio up into 0.5s samples so specific components can be ended quicker -- split audio up into 0.5s samples so specific components can be ended quicker
quad_buffer = { {}, {}, {}, {} }
} }
-- sounds modeled after https://www.e2s.com/references-and-guidelines/listen-and-download-alarm-tones -- sounds modeled after https://www.e2s.com/references-and-guidelines/listen-and-download-alarm-tones
@ -52,6 +53,7 @@ local TONES = {
} }
-- calculate how many samples are in the given number of milliseconds -- calculate how many samples are in the given number of milliseconds
---@nodiscard
---@param ms integer milliseconds ---@param ms integer milliseconds
---@return integer samples ---@return integer samples
local function ms_to_samples(ms) return math.floor(ms * 48) end local function ms_to_samples(ms) return math.floor(ms * 48) end
@ -224,6 +226,7 @@ end
--#endregion --#endregion
-- hard audio limiter -- hard audio limiter
---@nodiscard
---@param output number output level ---@param output number output level
---@return number limited -128.0 to 127.0 ---@return number limited -128.0 to 127.0
local function limit(output) local function limit(output)
@ -454,7 +457,7 @@ function sounder.test_power_scale()
end end
end end
log.debug("power rescale test took " .. (util.time_ms() - start) .. "ms") log.debug("SOUNDER: power rescale test took " .. (util.time_ms() - start) .. "ms")
end end
--#endregion --#endregion

View File

@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol")
local renderer = require("coordinator.renderer") local renderer = require("coordinator.renderer")
local sounder = require("coordinator.sounder") local sounder = require("coordinator.sounder")
local COORDINATOR_VERSION = "beta-v0.10.1" local COORDINATOR_VERSION = "v0.11.0"
local print = util.print local print = util.print
local println = util.println local println = util.println
@ -81,7 +81,7 @@ local function main()
-- setup monitors -- setup monitors
local configured, monitors = coordinator.configure_monitors(config.NUM_UNITS) local configured, monitors = coordinator.configure_monitors(config.NUM_UNITS)
if not configured or monitors == nil then if not configured or monitors == nil then
println("boot> monitor setup failed") println("startup> monitor setup failed")
log.fatal("monitor configuration failed") log.fatal("monitor configuration failed")
return return
end end
@ -91,11 +91,11 @@ local function main()
renderer.reset(config.RECOLOR) renderer.reset(config.RECOLOR)
if not renderer.validate_main_display_width() then if not renderer.validate_main_display_width() then
println("boot> main display must be 8 blocks wide") println("startup> main display must be 8 blocks wide")
log.fatal("main display not wide enough") log.fatal("main display not wide enough")
return return
elseif not renderer.validate_unit_display_sizes() then elseif not renderer.validate_unit_display_sizes() then
println("boot> one or more unit display dimensions incorrect; they must be 4x4 blocks") println("startup> one or more unit display dimensions incorrect; they must be 4x4 blocks")
log.fatal("unit display dimensions incorrect") log.fatal("unit display dimensions incorrect")
return return
end end
@ -116,7 +116,7 @@ local function main()
local speaker = ppm.get_device("speaker") local speaker = ppm.get_device("speaker")
if speaker == nil then if speaker == nil then
log_boot("annunciator alarm speaker not found") log_boot("annunciator alarm speaker not found")
println("boot> speaker not found") println("startup> speaker not found")
log.fatal("no annunciator alarm speaker found") log.fatal("no annunciator alarm speaker found")
return return
else else
@ -135,7 +135,7 @@ local function main()
local modem = ppm.get_wireless_modem() local modem = ppm.get_wireless_modem()
if modem == nil then if modem == nil then
log_comms("wireless modem not found") log_comms("wireless modem not found")
println("boot> wireless modem not found") println("startup> wireless modem not found")
log.fatal("no wireless modem on startup") log.fatal("no wireless modem on startup")
return return
else else
@ -145,12 +145,12 @@ local function main()
-- create connection watchdog -- create connection watchdog
local conn_watchdog = util.new_watchdog(config.COMMS_TIMEOUT) local conn_watchdog = util.new_watchdog(config.COMMS_TIMEOUT)
conn_watchdog.cancel() conn_watchdog.cancel()
log.debug("boot> conn watchdog created") log.debug("startup> conn watchdog created")
-- start comms, open all channels -- start comms, open all channels
local coord_comms = coordinator.comms(COORDINATOR_VERSION, modem, config.SCADA_SV_PORT, config.SCADA_SV_LISTEN, local coord_comms = coordinator.comms(COORDINATOR_VERSION, modem, config.SCADA_SV_PORT, config.SCADA_SV_LISTEN,
config.SCADA_API_LISTEN, config.TRUSTED_RANGE, conn_watchdog) config.SCADA_API_LISTEN, config.TRUSTED_RANGE, conn_watchdog)
log.debug("boot> comms init") log.debug("startup> comms init")
log_comms("comms initialized") log_comms("comms initialized")
-- base loop clock (2Hz, 10 ticks) -- base loop clock (2Hz, 10 ticks)
@ -176,7 +176,7 @@ local function main()
end end
if not init_connect_sv() then if not init_connect_sv() then
println("boot> failed to connect to supervisor") println("startup> failed to connect to supervisor")
log_sys("system shutdown") log_sys("system shutdown")
return return
else else
@ -199,7 +199,7 @@ local function main()
renderer.close_ui() renderer.close_ui()
log_graphics(util.c("UI crashed: ", message)) log_graphics(util.c("UI crashed: ", message))
println_ts("UI crashed") println_ts("UI crashed")
log.fatal(util.c("ui crashed with error ", message)) log.fatal(util.c("GUI crashed with error ", message))
else else
log_graphics("first UI draw took " .. (util.time_ms() - draw_start) .. "ms") log_graphics("first UI draw took " .. (util.time_ms() - draw_start) .. "ms")
@ -223,7 +223,7 @@ local function main()
if ui_ok then if ui_ok then
-- start connection watchdog -- start connection watchdog
conn_watchdog.feed() conn_watchdog.feed()
log.debug("boot> conn watchdog started") log.debug("startup> conn watchdog started")
log_sys("system started successfully") log_sys("system started successfully")
end end
@ -243,7 +243,6 @@ local function main()
no_modem = true no_modem = true
log_sys("comms modem disconnected") log_sys("comms modem disconnected")
println_ts("wireless modem disconnected!") println_ts("wireless modem disconnected!")
log.error("comms modem disconnected!")
-- close out UI -- close out UI
renderer.close_ui() renderer.close_ui()
@ -252,20 +251,21 @@ local function main()
log_sys("awaiting comms modem reconnect...") log_sys("awaiting comms modem reconnect...")
else else
log_sys("non-comms modem disconnected") log_sys("non-comms modem disconnected")
log.warning("non-comms modem disconnected")
end end
elseif type == "monitor" then elseif type == "monitor" then
if renderer.is_monitor_used(device) then if renderer.is_monitor_used(device) then
-- "halt and catch fire" style handling -- "halt and catch fire" style handling
println_ts("lost a configured monitor, system will now exit") local msg = "lost a configured monitor, system will now exit"
log_sys("lost a configured monitor, system will now exit") println_ts(msg)
log_sys(msg)
break break
else else
log_sys("lost unused monitor, ignoring") log_sys("lost unused monitor, ignoring")
end end
elseif type == "speaker" then elseif type == "speaker" then
println_ts("lost alarm sounder speaker") local msg = "lost alarm sounder speaker"
log_sys("lost alarm sounder speaker") println_ts(msg)
log_sys(msg)
end end
end end
elseif event == "peripheral" then elseif event == "peripheral" then
@ -291,8 +291,9 @@ local function main()
elseif type == "monitor" then elseif type == "monitor" then
-- not supported, system will exit on loss of in-use monitors -- not supported, system will exit on loss of in-use monitors
elseif type == "speaker" then elseif type == "speaker" then
println_ts("alarm sounder speaker reconnected") local msg = "alarm sounder speaker reconnected"
log_sys("alarm sounder speaker reconnected") println_ts(msg)
log_sys(msg)
sounder.reconnect(device) sounder.reconnect(device)
end end
end end
@ -301,7 +302,7 @@ local function main()
-- main loop tick -- main loop tick
-- free any closed sessions -- free any closed sessions
--apisessions.free_all_closed() apisessions.free_all_closed()
-- update date and time string for main display -- update date and time string for main display
iocontrol.get_db().facility.ps.publish("date_time", os.date(date_format)) iocontrol.get_db().facility.ps.publish("date_time", os.date(date_format))
@ -326,7 +327,7 @@ local function main()
-- a non-clock/main watchdog timer event -- a non-clock/main watchdog timer event
--check API watchdogs --check API watchdogs
--apisessions.check_all_watchdogs(param1) apisessions.check_all_watchdogs(param1)
-- notify timer callback dispatcher -- notify timer callback dispatcher
tcallbackdsp.handle(param1) tcallbackdsp.handle(param1)

View File

@ -13,6 +13,7 @@ local cpair = core.graphics.cpair
local border = core.graphics.border local border = core.graphics.border
-- new boiler view -- new boiler view
---@nodiscard
---@param root graphics_element parent ---@param root graphics_element 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

View File

@ -19,6 +19,7 @@ local border = core.graphics.border
local TEXT_ALIGN = core.graphics.TEXT_ALIGN local TEXT_ALIGN = core.graphics.TEXT_ALIGN
-- new induction matrix view -- new induction matrix view
---@nodiscard
---@param root graphics_element parent ---@param root graphics_element 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

View File

@ -16,11 +16,8 @@ local DataIndicator = require("graphics.elements.indicators.data")
local IndicatorLight = require("graphics.elements.indicators.light") local IndicatorLight = require("graphics.elements.indicators.light")
local RadIndicator = require("graphics.elements.indicators.rad") local RadIndicator = require("graphics.elements.indicators.rad")
local TriIndicatorLight = require("graphics.elements.indicators.trilight") local TriIndicatorLight = require("graphics.elements.indicators.trilight")
local VerticalBar = require("graphics.elements.indicators.vbar")
local HazardButton = require("graphics.elements.controls.hazard_button") local HazardButton = require("graphics.elements.controls.hazard_button")
local MultiButton = require("graphics.elements.controls.multi_button")
local PushButton = require("graphics.elements.controls.push_button")
local RadioButton = require("graphics.elements.controls.radio_button") local RadioButton = require("graphics.elements.controls.radio_button")
local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric")
@ -32,6 +29,7 @@ local border = core.graphics.border
local period = core.flasher.PERIOD local period = core.flasher.PERIOD
-- new process control view -- new process control view
---@nodiscard
---@param root graphics_element parent ---@param root graphics_element 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

View File

@ -1,3 +1,5 @@
local types = require("scada-common.types")
local style = require("coordinator.ui.style") local style = require("coordinator.ui.style")
local core = require("graphics.core") local core = require("graphics.core")
@ -13,6 +15,7 @@ local cpair = core.graphics.cpair
local border = core.graphics.border local border = core.graphics.border
-- create new reactor view -- create new reactor view
---@nodiscard
---@param root graphics_element parent ---@param root graphics_element 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
@ -47,7 +50,7 @@ local function new_view(root, x, y, data, ps)
local waste = HorizontalBar{parent=reactor_fills,x=8,y=5,show_percent=true,bar_fg_bg=cpair(colors.brown,colors.gray),height=1,width=14} local waste = HorizontalBar{parent=reactor_fills,x=8,y=5,show_percent=true,bar_fg_bg=cpair(colors.brown,colors.gray),height=1,width=14}
ps.subscribe("ccool_type", function (type) ps.subscribe("ccool_type", function (type)
if type == "mekanism:sodium" then if type == types.FLUID.SODIUM then
ccool.recolor(cpair(colors.lightBlue, colors.gray)) ccool.recolor(cpair(colors.lightBlue, colors.gray))
else else
ccool.recolor(cpair(colors.blue, colors.gray)) ccool.recolor(cpair(colors.blue, colors.gray))
@ -55,7 +58,7 @@ local function new_view(root, x, y, data, ps)
end) end)
ps.subscribe("hcool_type", function (type) ps.subscribe("hcool_type", function (type)
if type == "mekanism:superheated_sodium" then if type == types.FLUID.SUPERHEATED_SODIUM then
hcool.recolor(cpair(colors.orange, colors.gray)) hcool.recolor(cpair(colors.orange, colors.gray))
else else
hcool.recolor(cpair(colors.white, colors.gray)) hcool.recolor(cpair(colors.white, colors.gray))

View File

@ -15,6 +15,7 @@ local cpair = core.graphics.cpair
local border = core.graphics.border local border = core.graphics.border
-- new turbine view -- new turbine view
---@nodiscard
---@param root graphics_element parent ---@param root graphics_element 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

View File

@ -57,6 +57,7 @@ local waste_opts = {
} }
-- create a unit view -- create a unit view
---@nodiscard
---@param parent graphics_element parent ---@param parent graphics_element parent
---@param id integer ---@param id integer
local function init(parent, id) local function init(parent, id)
@ -237,13 +238,13 @@ local function init(parent, id)
local rcs_annunc = Div{parent=rcs,width=27,height=22,x=2,y=1} local rcs_annunc = Div{parent=rcs,width=27,height=22,x=2,y=1}
local rcs_tags = Div{parent=rcs,width=2,height=13,x=29,y=9} local rcs_tags = Div{parent=rcs,width=2,height=13,x=29,y=9}
local c_flt = IndicatorLight{parent=rcs_annunc,label="RCS Hardware Fault",colors=cpair(colors.yellow,colors.gray)} local c_flt = IndicatorLight{parent=rcs_annunc,label="RCS Hardware Fault",colors=cpair(colors.yellow,colors.gray)}
local c_emg = TriIndicatorLight{parent=rcs_annunc,label="Emergency Coolant",c1=colors.gray,c2=colors.white,c3=colors.yellow} local c_emg = TriIndicatorLight{parent=rcs_annunc,label="Emergency Coolant",c1=colors.gray,c2=colors.white,c3=colors.yellow}
local c_cfm = IndicatorLight{parent=rcs_annunc,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} local c_cfm = IndicatorLight{parent=rcs_annunc,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)}
local c_brm = IndicatorLight{parent=rcs_annunc,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)} local c_brm = IndicatorLight{parent=rcs_annunc,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)}
local c_sfm = IndicatorLight{parent=rcs_annunc,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} local c_sfm = IndicatorLight{parent=rcs_annunc,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)}
local c_mwrf = IndicatorLight{parent=rcs_annunc,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} local c_mwrf = IndicatorLight{parent=rcs_annunc,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)}
local c_tbnt = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} local c_tbnt = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
u_ps.subscribe("RCSFault", c_flt.update) u_ps.subscribe("RCSFault", c_flt.update)
u_ps.subscribe("EmergencyCoolant", c_emg.update) u_ps.subscribe("EmergencyCoolant", c_emg.update)

View File

@ -19,6 +19,7 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local pipe = core.graphics.pipe local pipe = core.graphics.pipe
-- make a new unit overview window -- make a new unit overview window
---@nodiscard
---@param parent graphics_element parent ---@param parent graphics_element 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
@ -51,7 +52,7 @@ local function make(parent, x, y, unit)
-- REACTOR -- -- REACTOR --
------------- -------------
reactor_view(root, 1, 3, unit.reactor_data, unit.unit_ps) local _ = reactor_view(root, 1, 3, unit.reactor_data, unit.unit_ps)
if num_boilers > 0 then if num_boilers > 0 then
local coolant_pipes = {} local coolant_pipes = {}
@ -101,16 +102,16 @@ local function make(parent, x, y, unit)
local steam_pipes_b = {} local steam_pipes_b = {}
if no_boilers then if no_boilers then
table.insert(steam_pipes_b, pipe(0, 1, 3, 1, colors.white)) -- steam to turbine 1 table.insert(steam_pipes_b, pipe(0, 1, 3, 1, colors.white)) -- steam to turbine 1
table.insert(steam_pipes_b, pipe(0, 2, 3, 2, colors.blue)) -- water to turbine 1 table.insert(steam_pipes_b, pipe(0, 2, 3, 2, colors.blue)) -- water to turbine 1
if num_turbines >= 2 then if num_turbines >= 2 then
table.insert(steam_pipes_b, pipe(1, 2, 3, 9, colors.white)) -- steam to turbine 2 table.insert(steam_pipes_b, pipe(1, 2, 3, 9, colors.white)) -- steam to turbine 2
table.insert(steam_pipes_b, pipe(2, 3, 3, 10, colors.blue)) -- water to turbine 2 table.insert(steam_pipes_b, pipe(2, 3, 3, 10, colors.blue)) -- water to turbine 2
end end
if num_turbines >= 3 then if num_turbines >= 3 then
table.insert(steam_pipes_b, pipe(1, 9, 3, 17, colors.white)) -- steam boiler 1 to turbine 1 junction end table.insert(steam_pipes_b, pipe(1, 9, 3, 17, colors.white)) -- steam boiler 1 to turbine 1 junction end
table.insert(steam_pipes_b, pipe(2, 10, 3, 18, colors.blue)) -- water boiler 1 to turbine 1 junction start table.insert(steam_pipes_b, pipe(2, 10, 3, 18, colors.blue)) -- water boiler 1 to turbine 1 junction start
end end
else else

View File

@ -1,5 +1,5 @@
-- --
-- Reactor Unit SCADA Coordinator GUI -- Reactor Unit Waiting Spinner
-- --
local style = require("coordinator.ui.style") local style = require("coordinator.ui.style")
@ -16,6 +16,7 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local cpair = core.graphics.cpair local cpair = core.graphics.cpair
-- create a unit waiting view -- create a unit waiting view
---@nodiscard
---@param parent graphics_element parent ---@param parent graphics_element parent
---@param y integer y offset ---@param y integer y offset
local function init(parent, y) local function init(parent, y)

View File

@ -3,13 +3,11 @@ local completion = require("cc.completion")
local util = require("scada-common.util") local util = require("scada-common.util")
local print = util.print local print = util.print
local println = util.println
local print_ts = util.print_ts
local println_ts = util.println_ts
local dialog = {} local dialog = {}
-- ask the user yes or no -- ask the user yes or no
---@nodiscard
---@param question string ---@param question string
---@param default boolean ---@param default boolean
---@return boolean|nil ---@return boolean|nil
@ -36,6 +34,7 @@ function dialog.ask_y_n(question, default)
end end
-- ask the user for an input within a set of options -- ask the user for an input within a set of options
---@nodiscard
---@param options table ---@param options table
---@param cancel string ---@param cancel string
---@return boolean|string|nil ---@return boolean|string|nil

View File

@ -30,6 +30,7 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local cpair = core.graphics.cpair local cpair = core.graphics.cpair
-- create new main view -- create new main view
---@nodiscard
---@param monitor table main viewscreen ---@param monitor table main viewscreen
local function init(monitor) local function init(monitor)
local facility = iocontrol.get_db().facility local facility = iocontrol.get_db().facility
@ -77,7 +78,7 @@ local function init(monitor)
end end
end end
-- command & control -- command & control
cnc_y_start = cnc_y_start cnc_y_start = cnc_y_start
@ -90,7 +91,7 @@ local function init(monitor)
cnc_bottom_align_start = cnc_bottom_align_start + 2 cnc_bottom_align_start = cnc_bottom_align_start + 2
local process = process_ctl(main, 2, cnc_bottom_align_start) local _ = process_ctl(main, 2, cnc_bottom_align_start)
-- testing -- testing
---@fixme remove test code ---@fixme remove test code
@ -123,7 +124,7 @@ local function init(monitor)
SwitchButton{parent=audio,x=1,text="RCS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rcs} SwitchButton{parent=audio,x=1,text="RCS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rcs}
SwitchButton{parent=audio,x=1,text="TURBINE TRIP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_turbinet} SwitchButton{parent=audio,x=1,text="TURBINE TRIP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_turbinet}
local imatrix_1 = imatrix(main, 131, cnc_bottom_align_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1]) local _ = imatrix(main, 131, cnc_bottom_align_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1])
return main return main
end end

View File

@ -9,12 +9,13 @@ local unit_detail = require("coordinator.ui.components.unit_detail")
local DisplayBox = require("graphics.elements.displaybox") local DisplayBox = require("graphics.elements.displaybox")
-- create a unit view -- create a unit view
---@nodiscard
---@param monitor table ---@param monitor table
---@param id integer ---@param id integer
local function init(monitor, id) local function init(monitor, id)
local main = DisplayBox{window=monitor,fg_bg=style.root} local main = DisplayBox{window=monitor,fg_bg=style.root}
unit_detail(main, id) local _ = unit_detail(main, id)
return main return main
end end