#130 facility data object, some code cleanup, comms protocol changed from 1.0.1 to 1.1.0

This commit is contained in:
Mikayla Fischler 2022-12-10 13:58:17 -05:00
parent 41913441d5
commit 03f0216d51
15 changed files with 635 additions and 283 deletions

View File

@ -18,7 +18,7 @@ local DEVICE_TYPES = comms.DEVICE_TYPES
local ESTABLISH_ACK = comms.ESTABLISH_ACK local ESTABLISH_ACK = comms.ESTABLISH_ACK
local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES
local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES
local CRDN_COMMANDS = comms.CRDN_COMMANDS local UNIT_COMMANDS = comms.UNIT_COMMANDS
local coordinator = {} local coordinator = {}
@ -309,11 +309,11 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa
end end
-- send a unit command -- send a unit command
---@param cmd CRDN_COMMANDS command ---@param cmd UNIT_COMMANDS command
---@param unit integer unit ID ---@param unit integer unit ID
---@param option any? optional options (like burn rate) ---@param option any? optional options (like burn rate)
function public.send_command(cmd, unit, option) function public.send_command(cmd, unit, option)
_send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.COMMAND_UNIT, { cmd, unit, option }) _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.UNIT_CMD, { cmd, unit, option })
end end
-- parse a packet -- parse a packet
@ -388,20 +388,35 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa
-- handle packet -- handle packet
if protocol == PROTOCOLS.SCADA_CRDN then if protocol == PROTOCOLS.SCADA_CRDN then
if self.sv_linked then if self.sv_linked then
if packet.type == SCADA_CRDN_TYPES.STRUCT_BUILDS then if packet.type == SCADA_CRDN_TYPES.FAC_BUILDS then
-- record builds -- record facility builds
if iocontrol.record_builds(packet.data) then if iocontrol.record_facility_builds(packet.data) then
-- acknowledge receipt of builds -- acknowledge receipt of builds
_send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.STRUCT_BUILDS, {}) _send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.FAC_BUILDS, {})
else else
log.error("received invalid SCADA_CRDN build packet") log.error("received invalid FAC_BUILDS packet")
end
elseif packet.type == SCADA_CRDN_TYPES.FAC_STATUS then
-- update facility status
if not iocontrol.update_facility_status(packet.data) then
log.error("received invalid FAC_STATUS packet")
end
elseif packet.type == SCADA_CRDN_TYPES.FAC_CMD then
-- facility command acknowledgement
elseif packet.type == SCADA_CRDN_TYPES.UNIT_BUILDS then
-- record builds
if iocontrol.record_unit_builds(packet.data) then
-- acknowledge receipt of builds
_send_sv(PROTOCOLS.SCADA_CRDN, SCADA_CRDN_TYPES.UNIT_BUILDS, {})
else
log.error("received invalid UNIT_BUILDS packet")
end end
elseif packet.type == SCADA_CRDN_TYPES.UNIT_STATUSES then elseif packet.type == SCADA_CRDN_TYPES.UNIT_STATUSES then
-- update statuses -- update statuses
if not iocontrol.update_statuses(packet.data) then if not iocontrol.update_unit_statuses(packet.data) then
log.error("received invalid SCADA_CRDN unit statuses packet") log.error("received invalid UNIT_STATUSES packet")
end end
elseif packet.type == SCADA_CRDN_TYPES.COMMAND_UNIT then elseif packet.type == SCADA_CRDN_TYPES.UNIT_CMD then
-- unit command acknowledgement -- unit command acknowledgement
if packet.length == 3 then if packet.length == 3 then
local cmd = packet.data[1] local cmd = packet.data[1]
@ -411,17 +426,17 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa
local unit = iocontrol.get_db().units[unit_id] ---@type ioctl_entry local unit = iocontrol.get_db().units[unit_id] ---@type ioctl_entry
if unit ~= nil then if unit ~= nil then
if cmd == CRDN_COMMANDS.SCRAM then if cmd == UNIT_COMMANDS.SCRAM then
unit.scram_ack(ack) unit.scram_ack(ack)
elseif cmd == CRDN_COMMANDS.START then elseif cmd == UNIT_COMMANDS.START then
unit.start_ack(ack) unit.start_ack(ack)
elseif cmd == CRDN_COMMANDS.RESET_RPS then elseif cmd == UNIT_COMMANDS.RESET_RPS then
unit.reset_rps_ack(ack) unit.reset_rps_ack(ack)
elseif cmd == CRDN_COMMANDS.SET_BURN then elseif cmd == UNIT_COMMANDS.SET_BURN then
unit.set_burn_ack(ack) unit.set_burn_ack(ack)
elseif cmd == CRDN_COMMANDS.SET_WASTE then elseif cmd == UNIT_COMMANDS.SET_WASTE then
unit.set_waste_ack(ack) unit.set_waste_ack(ack)
elseif cmd == CRDN_COMMANDS.ACK_ALL_ALARMS then elseif cmd == UNIT_COMMANDS.ACK_ALL_ALARMS then
unit.ack_alarms_ack(ack) unit.ack_alarms_ack(ack)
else else
log.debug(util.c("received command ack with unknown command ", cmd)) log.debug(util.c("received command ack with unknown command ", cmd))
@ -432,8 +447,6 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa
else else
log.debug("SCADA_CRDN unit command ack packet length mismatch") log.debug("SCADA_CRDN unit command ack packet length mismatch")
end end
elseif packet.type == SCADA_CRDN_TYPES.ALARM then
---@todo alarm/architecture handling
else else
log.warning("received unknown SCADA_CRDN packet type " .. packet.type) log.warning("received unknown SCADA_CRDN packet type " .. packet.type)
end end

View File

@ -6,7 +6,7 @@ local util = require("scada-common.util")
local sounder = require("coordinator.sounder") local sounder = require("coordinator.sounder")
local CRDN_COMMANDS = comms.CRDN_COMMANDS local UNIT_COMMANDS = comms.UNIT_COMMANDS
local ALARM_STATE = types.ALARM_STATE local ALARM_STATE = types.ALARM_STATE
@ -22,19 +22,29 @@ local io = {}
function iocontrol.init(conf, comms) function iocontrol.init(conf, comms)
io.facility = { io.facility = {
scram = false, scram = false,
num_units = conf.num_units, num_units = conf.num_units, ---@type integer
ps = psil.create() ps = psil.create(),
induction_ps_tbl = {},
induction_data_tbl = {}
} }
-- create induction tables (max 1 per unit, preferably 1 total)
for _ = 1, conf.num_units do
local data = {} ---@type imatrix_session_db
table.insert(io.facility.induction_ps_tbl, psil.create())
table.insert(io.facility.induction_data_tbl, data)
end
io.units = {} io.units = {}
for i = 1, conf.num_units do for i = 1, conf.num_units do
local function ack(alarm) local function ack(alarm)
comms.send_command(CRDN_COMMANDS.ACK_ALARM, i, alarm) comms.send_command(UNIT_COMMANDS.ACK_ALARM, i, alarm)
log.debug(util.c("UNIT[", i, "]: ACK ALARM ", alarm)) log.debug(util.c("UNIT[", i, "]: ACK ALARM ", alarm))
end end
local function reset(alarm) local function reset(alarm)
comms.send_command(CRDN_COMMANDS.RESET_ALARM, i, alarm) comms.send_command(UNIT_COMMANDS.RESET_ALARM, i, alarm)
log.debug(util.c("UNIT[", i, "]: RESET ALARM ", alarm)) log.debug(util.c("UNIT[", i, "]: RESET ALARM ", alarm))
end end
@ -107,33 +117,33 @@ function iocontrol.init(conf, comms)
function entry.start() function entry.start()
entry.control_state = true entry.control_state = true
comms.send_command(CRDN_COMMANDS.START, i) comms.send_command(UNIT_COMMANDS.START, i)
log.debug(util.c("UNIT[", i, "]: START")) log.debug(util.c("UNIT[", i, "]: START"))
end end
function entry.scram() function entry.scram()
entry.control_state = false entry.control_state = false
comms.send_command(CRDN_COMMANDS.SCRAM, i) comms.send_command(UNIT_COMMANDS.SCRAM, i)
log.debug(util.c("UNIT[", i, "]: SCRAM")) log.debug(util.c("UNIT[", i, "]: SCRAM"))
end end
function entry.reset_rps() function entry.reset_rps()
comms.send_command(CRDN_COMMANDS.RESET_RPS, i) comms.send_command(UNIT_COMMANDS.RESET_RPS, i)
log.debug(util.c("UNIT[", i, "]: RESET_RPS")) log.debug(util.c("UNIT[", i, "]: RESET_RPS"))
end end
function entry.ack_alarms() function entry.ack_alarms()
comms.send_command(CRDN_COMMANDS.ACK_ALL_ALARMS, i) comms.send_command(UNIT_COMMANDS.ACK_ALL_ALARMS, i)
log.debug(util.c("UNIT[", i, "]: ACK_ALL_ALARMS")) log.debug(util.c("UNIT[", i, "]: ACK_ALL_ALARMS"))
end end
function entry.set_burn(rate) function entry.set_burn(rate)
comms.send_command(CRDN_COMMANDS.SET_BURN, i, rate) comms.send_command(UNIT_COMMANDS.SET_BURN, i, rate)
log.debug(util.c("UNIT[", i, "]: SET_BURN = ", rate)) log.debug(util.c("UNIT[", i, "]: SET_BURN = ", rate))
end end
function entry.set_waste(mode) function entry.set_waste(mode)
comms.send_command(CRDN_COMMANDS.SET_WASTE, i, mode) comms.send_command(UNIT_COMMANDS.SET_WASTE, i, mode)
log.debug(util.c("UNIT[", i, "]: SET_WASTE = ", mode)) log.debug(util.c("UNIT[", i, "]: SET_WASTE = ", mode))
end end
@ -158,58 +168,173 @@ function iocontrol.init(conf, comms)
end end
end end
-- populate structure builds -- populate facility structure builds
---@param build table
---@return boolean valid
function iocontrol.record_facility_builds(build)
if type(build) == "table" then
local fac = io.facility
-- induction matricies
if type(build.induction) == "table" then
for id, matrix in pairs(build.induction) do
if type(fac.induction_data_tbl[id]) == "table" then
fac.induction_data_tbl[id].formed = matrix[1] ---@type boolean
fac.induction_data_tbl[id].build = matrix[2] ---@type table
fac.induction_ps_tbl[id].publish("formed", matrix[1])
for key, val in pairs(fac.induction_data_tbl[id].build) do
fac.induction_ps_tbl[id].publish(key, val)
end
else
log.debug(util.c("iocontrol.record_facility_builds: invalid induction matrix id ", id))
end
end
end
else
log.error("facility builds not a table")
return false
end
return true
end
-- populate unit structure builds
---@param builds table ---@param builds table
---@return boolean valid ---@return boolean valid
function iocontrol.record_builds(builds) function iocontrol.record_unit_builds(builds)
if #builds ~= #io.units then -- note: if not all units and RTUs are connected, some will be nil
log.error("number of provided unit builds does not match expected number of units") for id, build in pairs(builds) do
local unit = io.units[id] ---@type ioctl_entry
if type(build) ~= "table" then
log.error(util.c("corrupted unit builds provided, unit ", id, " not a table"))
return false
elseif type(unit) ~= "table" then
log.error(util.c("corrupted unit builds provided, invalid unit ", id))
return false
end
local log_header = util.c("iocontrol.record_unit_builds[unit ", id, "]: ")
-- reactor build
if type(build.reactor) == "table" then
unit.reactor_data.mek_struct = build.reactor
for key, val in pairs(unit.reactor_data.mek_struct) do
unit.reactor_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.reactor_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
-- 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))
end
end
end
end
return true
end
-- update facility status
---@param status table
---@return boolean valid
function iocontrol.update_facility_status(status)
local log_header = util.c("iocontrol.update_facility_status: ")
if type(status) ~= "table" then
log.debug(log_header .. "status not a table")
return false return false
else else
-- note: if not all units and RTUs are connected, some will be nil local fac = io.facility
for i = 1, #builds do
local unit = io.units[i] ---@type ioctl_entry
local build = builds[i]
-- reactor build -- RTU statuses
if type(build.reactor) == "table" then
unit.reactor_data.mek_struct = build.reactor
for key, val in pairs(unit.reactor_data.mek_struct) do
unit.reactor_ps.publish(key, val)
end
if (type(unit.reactor_data.mek_struct.length) == "number") and (unit.reactor_data.mek_struct.length ~= 0) and local rtu_statuses = status[1]
(type(unit.reactor_data.mek_struct.width) == "number") and (unit.reactor_data.mek_struct.width ~= 0) then
unit.reactor_ps.publish("size", { unit.reactor_data.mek_struct.length, unit.reactor_data.mek_struct.width })
end
end
-- boiler builds if type(rtu_statuses) == "table" then
if type(build.boilers) == "table" then -- induction matricies statuses
for id, boiler in pairs(build.boilers) do if type(rtu_statuses.induction) == "table" then
unit.boiler_data_tbl[id].formed = boiler[1] ---@type boolean for id = 1, #fac.induction_ps_tbl do
unit.boiler_data_tbl[id].build = boiler[2] ---@type table if rtu_statuses.induction[id] == nil then
-- disconnected
unit.boiler_ps_tbl[id].publish("formed", boiler[1]) fac.induction_ps_tbl[id].publish("computed_status", 1)
for key, val in pairs(unit.boiler_data_tbl[id].build) do
unit.boiler_ps_tbl[id].publish(key, val)
end end
end end
end
-- turbine builds for id, matrix in pairs(rtu_statuses.induction) do
if type(build.turbines) == "table" then if type(fac.induction_data_tbl[id]) == "table" then
for id, turbine in pairs(build.turbines) do local rtu_faulted = matrix[1] ---@type boolean
unit.turbine_data_tbl[id].formed = turbine[1] ---@type boolean fac.induction_data_tbl[id].formed = matrix[2] ---@type boolean
unit.turbine_data_tbl[id].build = turbine[2] ---@type table fac.induction_data_tbl[id].state = matrix[3] ---@type table
fac.induction_data_tbl[id].tanks = matrix[4] ---@type table
unit.turbine_ps_tbl[id].publish("formed", turbine[1]) local data = fac.induction_data_tbl[id] ---@type imatrix_session_db
for key, val in pairs(unit.turbine_data_tbl[id].build) do fac.induction_ps_tbl[id].publish("formed", data.formed)
unit.turbine_ps_tbl[id].publish(key, val) fac.induction_ps_tbl[id].publish("faulted", rtu_faulted)
if data.formed then
if rtu_faulted then
fac.induction_ps_tbl[id].publish("computed_status", 3) -- faulted
elseif data.tanks.energy_fill >= 0.99 then
fac.induction_ps_tbl[id].publish("computed_status", 6) -- full
elseif data.tanks.energy_fill <= 0.01 then
fac.induction_ps_tbl[id].publish("computed_status", 5) -- empty
else
fac.induction_ps_tbl[id].publish("computed_status", 4) -- on-line
end
else
fac.induction_ps_tbl[id].publish("computed_status", 2) -- not formed
end
for key, val in pairs(fac.induction_data_tbl[id].state) do
fac.induction_ps_tbl[id].publish(key, val)
end
for key, val in pairs(fac.induction_data_tbl[id].tanks) do
fac.induction_ps_tbl[id].publish(key, val)
end
else
log.debug(util.c(log_header, "invalid induction matrix id ", id))
end end
end end
else
log.debug(log_header .. "induction matrix list not a table")
end end
end end
end end
@ -220,16 +345,17 @@ end
-- update unit statuses -- update unit statuses
---@param statuses table ---@param statuses table
---@return boolean valid ---@return boolean valid
function iocontrol.update_statuses(statuses) function iocontrol.update_unit_statuses(statuses)
if type(statuses) ~= "table" then if type(statuses) ~= "table" then
log.debug("iocontrol.update_statuses: unit statuses not a table") log.debug("iocontrol.update_unit_statuses: unit statuses not a table")
return false return false
elseif #statuses ~= #io.units then elseif #statuses ~= #io.units then
log.debug("iocontrol.update_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 return false
else else
-- get all unit statuses
for i = 1, #statuses do for i = 1, #statuses do
local log_header = util.c("iocontrol.update_statuses[unit ", i, "]: ") local log_header = util.c("iocontrol.update_unit_statuses[unit ", i, "]: ")
local unit = io.units[i] ---@type ioctl_entry local unit = io.units[i] ---@type ioctl_entry
local status = statuses[i] local status = statuses[i]
@ -264,18 +390,18 @@ function iocontrol.update_statuses(statuses)
unit.reactor_data.mek_status = mek_status ---@type mek_status unit.reactor_data.mek_status = mek_status ---@type mek_status
if unit.reactor_data.mek_status.status then if unit.reactor_data.mek_status.status then
unit.reactor_ps.publish("computed_status", 3) -- running unit.reactor_ps.publish("computed_status", 5) -- running
else else
if unit.reactor_data.no_reactor then if unit.reactor_data.no_reactor then
unit.reactor_ps.publish("computed_status", 5) -- faulted unit.reactor_ps.publish("computed_status", 3) -- faulted
elseif not unit.reactor_data.formed then elseif not unit.reactor_data.formed then
unit.reactor_ps.publish("computed_status", 6) -- multiblock not formed unit.reactor_ps.publish("computed_status", 2) -- multiblock not formed
elseif unit.reactor_data.rps_status.force_dis then elseif unit.reactor_data.rps_status.force_dis then
unit.reactor_ps.publish("computed_status", 7) -- reactor force disabled unit.reactor_ps.publish("computed_status", 7) -- reactor force disabled
elseif unit.reactor_data.rps_tripped and unit.reactor_data.rps_trip_cause ~= "manual" then elseif unit.reactor_data.rps_tripped and unit.reactor_data.rps_trip_cause ~= "manual" then
unit.reactor_ps.publish("computed_status", 4) -- SCRAM unit.reactor_ps.publish("computed_status", 6) -- SCRAM
else else
unit.reactor_ps.publish("computed_status", 2) -- disabled unit.reactor_ps.publish("computed_status", 4) -- disabled
end end
end end
@ -307,7 +433,7 @@ function iocontrol.update_statuses(statuses)
if type(rtu_statuses) == "table" then if type(rtu_statuses) == "table" then
-- boiler statuses -- boiler statuses
if type(rtu_statuses.boilers) == "table" then if type(rtu_statuses.boilers) == "table" then
for id = 1, #unit.boiler_data_tbl do for id = 1, #unit.boiler_ps_tbl do
if rtu_statuses.boilers[i] == nil then if rtu_statuses.boilers[i] == nil then
-- disconnected -- disconnected
unit.boiler_ps_tbl[id].publish("computed_status", 1) unit.boiler_ps_tbl[id].publish("computed_status", 1)
@ -315,34 +441,38 @@ function iocontrol.update_statuses(statuses)
end end
for id, boiler in pairs(rtu_statuses.boilers) do for id, boiler in pairs(rtu_statuses.boilers) do
local rtu_faulted = boiler[1] ---@type boolean if type(unit.boiler_data_tbl[id]) == "table" then
unit.boiler_data_tbl[id].formed = boiler[2] ---@type boolean local rtu_faulted = boiler[1] ---@type boolean
unit.boiler_data_tbl[id].state = boiler[3] ---@type table unit.boiler_data_tbl[id].formed = boiler[2] ---@type boolean
unit.boiler_data_tbl[id].tanks = boiler[4] ---@type table 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 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("formed", data.formed)
unit.boiler_ps_tbl[id].publish("faulted", rtu_faulted) unit.boiler_ps_tbl[id].publish("faulted", rtu_faulted)
if data.formed then if data.formed then
if rtu_faulted then if rtu_faulted then
unit.boiler_ps_tbl[id].publish("computed_status", 4) -- faulted unit.boiler_ps_tbl[id].publish("computed_status", 3) -- faulted
elseif data.state.boil_rate > 0 then elseif data.state.boil_rate > 0 then
unit.boiler_ps_tbl[id].publish("computed_status", 3) -- active 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", 2) -- 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", 5) -- not formed log.debug(util.c(log_header, "invalid boiler id ", id))
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
end end
else else
@ -359,36 +489,40 @@ function iocontrol.update_statuses(statuses)
end end
for id, turbine in pairs(rtu_statuses.turbines) do for id, turbine in pairs(rtu_statuses.turbines) do
local rtu_faulted = turbine[1] ---@type boolean if type(unit.turbine_data_tbl[id]) == "table" then
unit.turbine_data_tbl[id].formed = turbine[2] ---@type boolean local rtu_faulted = turbine[1] ---@type boolean
unit.turbine_data_tbl[id].state = turbine[3] ---@type table unit.turbine_data_tbl[id].formed = turbine[2] ---@type boolean
unit.turbine_data_tbl[id].tanks = turbine[4] ---@type table unit.turbine_data_tbl[id].state = turbine[3] ---@type table
unit.turbine_data_tbl[id].tanks = turbine[4] ---@type table
local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db
unit.turbine_ps_tbl[id].publish("formed", data.formed) unit.turbine_ps_tbl[id].publish("formed", data.formed)
unit.turbine_ps_tbl[id].publish("faulted", rtu_faulted) unit.turbine_ps_tbl[id].publish("faulted", rtu_faulted)
if data.formed then if data.formed then
if data.tanks.energy_fill >= 0.99 then if data.tanks.energy_fill >= 0.99 then
unit.turbine_ps_tbl[id].publish("computed_status", 4) -- trip unit.turbine_ps_tbl[id].publish("computed_status", 6) -- trip
elseif rtu_faulted then elseif rtu_faulted then
unit.turbine_ps_tbl[id].publish("computed_status", 5) -- faulted unit.turbine_ps_tbl[id].publish("computed_status", 3) -- faulted
elseif data.state.flow_rate < 100 then elseif data.state.flow_rate < 100 then
unit.turbine_ps_tbl[id].publish("computed_status", 2) -- idle 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", 3) -- 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", 6) -- not formed log.debug(util.c(log_header, "invalid turbine id ", id))
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
end end
else else

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.7.7" local COORDINATOR_VERSION = "beta-v0.8.0"
local print = util.print local print = util.print
local println = util.println local println = util.println
@ -365,4 +365,8 @@ local function main()
log.info("exited") log.info("exited")
end end
if not xpcall(main, crash.handler) then crash.exit() end if not xpcall(main, crash.handler) then
pcall(renderer.close_ui)
pcall(sounder.stop)
crash.exit()
end

View File

@ -39,6 +39,14 @@ style.reactor = {
color = cpair(colors.black, colors.yellow), color = cpair(colors.black, colors.yellow),
text = "PLC OFF-LINE" text = "PLC OFF-LINE"
}, },
{
color = cpair(colors.black, colors.orange),
text = "NOT FORMED"
},
{
color = cpair(colors.black, colors.orange),
text = "PLC FAULT"
},
{ {
color = cpair(colors.white, colors.gray), color = cpair(colors.white, colors.gray),
text = "DISABLED" text = "DISABLED"
@ -51,14 +59,6 @@ style.reactor = {
color = cpair(colors.black, colors.red), color = cpair(colors.black, colors.red),
text = "SCRAMMED" text = "SCRAMMED"
}, },
{
color = cpair(colors.black, colors.orange),
text = "PLC FAULT"
},
{
color = cpair(colors.black, colors.orange),
text = "NOT FORMED"
},
{ {
color = cpair(colors.black, colors.red), color = cpair(colors.black, colors.red),
text = "FORCE DISABLED" text = "FORCE DISABLED"
@ -73,14 +73,6 @@ style.boiler = {
color = cpair(colors.black, colors.yellow), color = cpair(colors.black, colors.yellow),
text = "OFF-LINE" text = "OFF-LINE"
}, },
{
color = cpair(colors.white, colors.gray),
text = "IDLE"
},
{
color = cpair(colors.black, colors.green),
text = "ACTIVE"
},
{ {
color = cpair(colors.black, colors.orange), color = cpair(colors.black, colors.orange),
text = "RTU FAULT" text = "RTU FAULT"
@ -88,6 +80,14 @@ style.boiler = {
{ {
color = cpair(colors.black, colors.orange), color = cpair(colors.black, colors.orange),
text = "NOT FORMED" text = "NOT FORMED"
},
{
color = cpair(colors.white, colors.gray),
text = "IDLE"
},
{
color = cpair(colors.black, colors.green),
text = "ACTIVE"
} }
} }
} }
@ -99,6 +99,14 @@ style.turbine = {
color = cpair(colors.black, colors.yellow), color = cpair(colors.black, colors.yellow),
text = "OFF-LINE" text = "OFF-LINE"
}, },
{
color = cpair(colors.black, colors.orange),
text = "NOT FORMED"
},
{
color = cpair(colors.black, colors.orange),
text = "RTU FAULT"
},
{ {
color = cpair(colors.white, colors.gray), color = cpair(colors.white, colors.gray),
text = "IDLE" text = "IDLE"
@ -110,15 +118,37 @@ style.turbine = {
{ {
color = cpair(colors.black, colors.red), color = cpair(colors.black, colors.red),
text = "TRIP" text = "TRIP"
}
}
}
style.imatrix = {
-- induction matrix states
states = {
{
color = cpair(colors.black, colors.yellow),
text = "OFF-LINE"
},
{
color = cpair(colors.black, colors.orange),
text = "NOT FORMED"
}, },
{ {
color = cpair(colors.black, colors.orange), color = cpair(colors.black, colors.orange),
text = "RTU FAULT" text = "RTU FAULT"
}, },
{ {
color = cpair(colors.black, colors.orange), color = cpair(colors.white, colors.green),
text = "NOT FORMED" text = "ONLINE"
} },
{
color = cpair(colors.black, colors.yellow),
text = "LOW CHARGE"
},
{
color = cpair(colors.black, colors.red),
text = "HIGH CHARGE"
},
} }
} }

View File

@ -14,7 +14,7 @@ local config = require("reactor-plc.config")
local plc = require("reactor-plc.plc") local plc = require("reactor-plc.plc")
local threads = require("reactor-plc.threads") local threads = require("reactor-plc.threads")
local R_PLC_VERSION = "beta-v0.9.9" local R_PLC_VERSION = "beta-v0.9.10"
local print = util.print local print = util.print
local println = util.println local println = util.println

View File

@ -25,7 +25,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 = "beta-v0.9.9" local RTU_VERSION = "beta-v0.9.10"
local rtu_t = types.rtu_t local rtu_t = types.rtu_t

View File

@ -12,7 +12,7 @@ local rtu_t = types.rtu_t
local insert = table.insert local insert = table.insert
comms.version = "1.0.1" comms.version = "1.1.0"
---@alias PROTOCOLS integer ---@alias PROTOCOLS integer
local PROTOCOLS = { local PROTOCOLS = {
@ -38,7 +38,7 @@ local RPLC_TYPES = {
MEK_BURN_RATE = 2, -- set burn rate MEK_BURN_RATE = 2, -- set burn rate
RPS_ENABLE = 3, -- enable reactor RPS_ENABLE = 3, -- enable reactor
RPS_SCRAM = 4, -- SCRAM reactor (manual request) RPS_SCRAM = 4, -- SCRAM reactor (manual request)
RPS_ASCRAM = 5, -- SCRAM reactor (automatic request) RPS_ASCRAM = 5, -- SCRAM reactor (automatic request)
RPS_STATUS = 6, -- RPS status RPS_STATUS = 6, -- RPS status
RPS_ALARM = 7, -- RPS alarm broadcast RPS_ALARM = 7, -- RPS alarm broadcast
RPS_RESET = 8 -- clear RPS trip (if in bad state, will trip immediately) RPS_RESET = 8 -- clear RPS trip (if in bad state, will trip immediately)
@ -62,14 +62,16 @@ local ESTABLISH_ACK = {
---@alias SCADA_CRDN_TYPES integer ---@alias SCADA_CRDN_TYPES integer
local SCADA_CRDN_TYPES = { local SCADA_CRDN_TYPES = {
STRUCT_BUILDS = 0, -- mekanism structure builds FAC_BUILDS = 0, -- facility RTU builds
UNIT_STATUSES = 1, -- state of reactor units FAC_STATUS = 1, -- state of facility and facility devices
COMMAND_UNIT = 2, -- command a reactor unit FAC_CMD = 2, -- faility command
ALARM = 3 -- alarm signaling UNIT_BUILDS = 3, -- build of each reactor unit (reactor + RTUs)
UNIT_STATUSES = 4, -- state of each of the reactor units
UNIT_CMD = 5 -- command a reactor unit
} }
---@alias CRDN_COMMANDS integer ---@alias UNIT_COMMANDS integer
local CRDN_COMMANDS = { local UNIT_COMMANDS = {
SCRAM = 0, -- SCRAM the reactor SCRAM = 0, -- SCRAM the reactor
START = 1, -- start the reactor START = 1, -- start the reactor
RESET_RPS = 2, -- reset the RPS RESET_RPS = 2, -- reset the RPS
@ -102,7 +104,7 @@ comms.RPLC_TYPES = RPLC_TYPES
comms.ESTABLISH_ACK = ESTABLISH_ACK comms.ESTABLISH_ACK = ESTABLISH_ACK
comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES
comms.SCADA_CRDN_TYPES = SCADA_CRDN_TYPES comms.SCADA_CRDN_TYPES = SCADA_CRDN_TYPES
comms.CRDN_COMMANDS = CRDN_COMMANDS comms.UNIT_COMMANDS = UNIT_COMMANDS
comms.CAPI_TYPES = CAPI_TYPES comms.CAPI_TYPES = CAPI_TYPES
comms.RTU_UNIT_TYPES = RTU_UNIT_TYPES comms.RTU_UNIT_TYPES = RTU_UNIT_TYPES
@ -484,10 +486,12 @@ function comms.crdn_packet()
-- check that type is known -- check that type is known
local function _crdn_type_valid() local function _crdn_type_valid()
return self.type == SCADA_CRDN_TYPES.STRUCT_BUILDS or return self.type == SCADA_CRDN_TYPES.FAC_BUILDS or
self.type == SCADA_CRDN_TYPES.FAC_STATUS or
self.type == SCADA_CRDN_TYPES.FAC_CMD or
self.type == SCADA_CRDN_TYPES.UNIT_BUILDS or
self.type == SCADA_CRDN_TYPES.UNIT_STATUSES or self.type == SCADA_CRDN_TYPES.UNIT_STATUSES or
self.type == SCADA_CRDN_TYPES.COMMAND_UNIT or self.type == SCADA_CRDN_TYPES.UNIT_STATUSES
self.type == SCADA_CRDN_TYPES.ALARM
end end
-- make a coordinator packet -- make a coordinator packet

View File

@ -10,7 +10,7 @@ local coordinator = {}
local PROTOCOLS = comms.PROTOCOLS local PROTOCOLS = comms.PROTOCOLS
local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES
local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES local SCADA_CRDN_TYPES = comms.SCADA_CRDN_TYPES
local CRDN_COMMANDS = comms.CRDN_COMMANDS local UNIT_COMMANDS = comms.UNIT_COMMANDS
local SV_Q_CMDS = svqtypes.SV_Q_CMDS local SV_Q_CMDS = svqtypes.SV_Q_CMDS
local SV_Q_DATA = svqtypes.SV_Q_DATA local SV_Q_DATA = svqtypes.SV_Q_DATA
@ -44,15 +44,14 @@ local PERIODICS = {
---@param id integer ---@param id integer
---@param in_queue mqueue ---@param in_queue mqueue
---@param out_queue mqueue ---@param out_queue mqueue
---@param facility_units table ---@param facility facility
function coordinator.new_session(id, in_queue, out_queue, facility_units) ---@param units table
function coordinator.new_session(id, in_queue, out_queue, facility, units)
local log_header = "crdn_session(" .. id .. "): " local log_header = "crdn_session(" .. id .. "): "
local self = { local self = {
id = id,
in_q = in_queue, in_q = in_queue,
out_q = out_queue, out_q = out_queue,
units = facility_units,
-- connection properties -- connection properties
seq_num = 0, seq_num = 0,
r_seq_num = nil, r_seq_num = nil,
@ -67,11 +66,13 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units)
}, },
-- when to next retry one of these messages -- when to next retry one of these messages
retry_times = { retry_times = {
builds_packet = 0 f_builds_packet = 0,
u_builds_packet = 0
}, },
-- message acknowledgements -- message acknowledgements
acks = { acks = {
builds = false fac_builds = false,
unit_builds = false
} }
} }
@ -109,26 +110,41 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units)
self.seq_num = self.seq_num + 1 self.seq_num = self.seq_num + 1
end end
-- send facility builds
local function _send_fac_builds()
self.acks.fac_builds = false
_send(SCADA_CRDN_TYPES.FAC_BUILDS, facility.get_build())
end
-- send unit builds -- send unit builds
local function _send_builds() local function _send_unit_builds()
self.acks.builds = false self.acks.unit_builds = false
local builds = {} local builds = {}
for i = 1, #self.units do for i = 1, #units do
local unit = self.units[i] ---@type reactor_unit local unit = units[i] ---@type reactor_unit
builds[unit.get_id()] = unit.get_build() builds[unit.get_id()] = unit.get_build()
end end
_send(SCADA_CRDN_TYPES.STRUCT_BUILDS, builds) _send(SCADA_CRDN_TYPES.UNIT_BUILDS, builds)
end
-- send facility status
local function _send_fac_status()
local status = {
facility.get_rtu_statuses()
}
_send(SCADA_CRDN_TYPES.FAC_STATUS, status)
end end
-- send unit statuses -- send unit statuses
local function _send_status() local function _send_unit_statuses()
local status = {} local status = {}
for i = 1, #self.units do for i = 1, #units do
local unit = self.units[i] ---@type reactor_unit local unit = units[i] ---@type reactor_unit
status[unit.get_id()] = { status[unit.get_id()] = {
unit.get_reactor_status(), unit.get_reactor_status(),
unit.get_rtu_statuses(), unit.get_rtu_statuses(),
@ -183,10 +199,13 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units)
log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type)
end end
elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_CRDN then elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_CRDN then
if pkt.type == SCADA_CRDN_TYPES.STRUCT_BUILDS then if pkt.type == SCADA_CRDN_TYPES.FAC_BUILDS then
-- acknowledgement to coordinator receiving builds -- acknowledgement to coordinator receiving builds
self.acks.builds = true self.acks.fac_builds = true
elseif pkt.type == SCADA_CRDN_TYPES.COMMAND_UNIT then elseif pkt.type == SCADA_CRDN_TYPES.UNIT_BUILDS then
-- acknowledgement to coordinator receiving builds
self.acks.unit_builds = true
elseif pkt.type == SCADA_CRDN_TYPES.UNIT_CMD then
if pkt.length >= 2 then if pkt.length >= 2 then
-- get command and unit id -- get command and unit id
local cmd = pkt.data[1] local cmd = pkt.data[1]
@ -196,37 +215,37 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units)
local data = { uid, pkt.data[3] } local data = { uid, pkt.data[3] }
-- continue if valid unit id -- continue if valid unit id
if util.is_int(uid) and uid > 0 and uid <= #self.units then if util.is_int(uid) and uid > 0 and uid <= #units then
local unit = self.units[uid] ---@type reactor_unit local unit = units[uid] ---@type reactor_unit
if cmd == CRDN_COMMANDS.START then if cmd == UNIT_COMMANDS.START then
self.out_q.push_data(SV_Q_DATA.START, data) self.out_q.push_data(SV_Q_DATA.START, data)
elseif cmd == CRDN_COMMANDS.SCRAM then elseif cmd == UNIT_COMMANDS.SCRAM then
self.out_q.push_data(SV_Q_DATA.SCRAM, data) self.out_q.push_data(SV_Q_DATA.SCRAM, data)
elseif cmd == CRDN_COMMANDS.RESET_RPS then elseif cmd == UNIT_COMMANDS.RESET_RPS then
self.out_q.push_data(SV_Q_DATA.RESET_RPS, data) self.out_q.push_data(SV_Q_DATA.RESET_RPS, data)
elseif cmd == CRDN_COMMANDS.SET_BURN then elseif cmd == UNIT_COMMANDS.SET_BURN then
if pkt.length == 3 then if pkt.length == 3 then
self.out_q.push_data(SV_Q_DATA.SET_BURN, data) self.out_q.push_data(SV_Q_DATA.SET_BURN, data)
else else
log.debug(log_header .. "CRDN command unit burn rate missing option") log.debug(log_header .. "CRDN command unit burn rate missing option")
end end
elseif cmd == CRDN_COMMANDS.SET_WASTE then elseif cmd == UNIT_COMMANDS.SET_WASTE then
if pkt.length == 3 then if pkt.length == 3 then
unit.set_waste(pkt.data[3]) unit.set_waste(pkt.data[3])
else else
log.debug(log_header .. "CRDN command unit set waste missing option") log.debug(log_header .. "CRDN command unit set waste missing option")
end end
elseif cmd == CRDN_COMMANDS.ACK_ALL_ALARMS then elseif cmd == UNIT_COMMANDS.ACK_ALL_ALARMS then
unit.ack_all() unit.ack_all()
_send(SCADA_CRDN_TYPES.COMMAND_UNIT, { cmd, uid, true }) _send(SCADA_CRDN_TYPES.UNIT_CMD, { cmd, uid, true })
elseif cmd == CRDN_COMMANDS.ACK_ALARM then elseif cmd == UNIT_COMMANDS.ACK_ALARM then
if pkt.length == 3 then if pkt.length == 3 then
unit.ack_alarm(pkt.data[3]) unit.ack_alarm(pkt.data[3])
else else
log.debug(log_header .. "CRDN command unit ack alarm missing id") log.debug(log_header .. "CRDN command unit ack alarm missing id")
end end
elseif cmd == CRDN_COMMANDS.RESET_ALARM then elseif cmd == UNIT_COMMANDS.RESET_ALARM then
if pkt.length == 3 then if pkt.length == 3 then
unit.reset_alarm(pkt.data[3]) unit.reset_alarm(pkt.data[3])
else else
@ -251,7 +270,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units)
local public = {} local public = {}
-- get the session ID -- get the session ID
function public.get_id() return self.id end function public.get_id() return id end
-- check if a timer matches this session's watchdog -- check if a timer matches this session's watchdog
function public.check_wd(timer) function public.check_wd(timer)
@ -262,7 +281,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units)
function public.close() function public.close()
_close() _close()
_send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {})
println("connection to coordinator " .. self.id .. " closed by server") println("connection to coordinator " .. id .. " closed by server")
log.info(log_header .. "session closed by server") log.info(log_header .. "session closed by server")
end end
@ -289,9 +308,9 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units)
local cmd = message.message local cmd = message.message
if cmd == CRD_S_CMDS.RESEND_BUILDS then if cmd == CRD_S_CMDS.RESEND_BUILDS then
-- re-send builds -- re-send builds
self.acks.builds = false
self.retry_times.builds_packet = util.time() + RETRY_PERIOD self.retry_times.builds_packet = util.time() + RETRY_PERIOD
_send_builds() _send_fac_builds()
_send_unit_builds()
end end
elseif message.qtype == mqueue.TYPE.DATA then elseif message.qtype == mqueue.TYPE.DATA then
-- instruction with body -- instruction with body
@ -299,7 +318,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units)
if cmd.key == CRD_S_DATA.CMD_ACK then if cmd.key == CRD_S_DATA.CMD_ACK then
local ack = cmd.val ---@type coord_ack local ack = cmd.val ---@type coord_ack
_send(SCADA_CRDN_TYPES.COMMAND_UNIT, { ack.cmd, ack.unit, ack.ack }) _send(SCADA_CRDN_TYPES.UNIT_CMD, { ack.cmd, ack.unit, ack.ack })
end end
end end
end end
@ -313,7 +332,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units)
-- exit if connection was closed -- exit if connection was closed
if not self.connected then if not self.connected then
println("connection to coordinator " .. self.id .. " closed by remote host") println("connection to coordinator " .. id .. " closed by remote host")
log.info(log_header .. "session closed by remote host") log.info(log_header .. "session closed by remote host")
return self.connected return self.connected
end end
@ -334,11 +353,12 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units)
periodics.keep_alive = 0 periodics.keep_alive = 0
end end
-- unit statuses to coordinator -- statuses to coordinator
periodics.status_packet = periodics.status_packet + elapsed periodics.status_packet = periodics.status_packet + elapsed
if periodics.status_packet >= PERIODICS.STATUS then if periodics.status_packet >= PERIODICS.STATUS then
_send_status() _send_fac_status()
_send_unit_statuses()
periodics.status_packet = 0 periodics.status_packet = 0
end end
@ -350,12 +370,19 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units)
local rtimes = self.retry_times local rtimes = self.retry_times
-- builds packet retry -- builds packet retries
if not self.acks.builds then if not self.acks.fac_builds then
if rtimes.builds_packet - util.time() <= 0 then if rtimes.f_builds_packet - util.time() <= 0 then
_send_builds() _send_fac_builds()
rtimes.builds_packet = util.time() + RETRY_PERIOD rtimes.f_builds_packet = util.time() + RETRY_PERIOD
end
end
if not self.acks.unit_builds then
if rtimes.u_builds_packet - util.time() <= 0 then
_send_unit_builds()
rtimes.u_builds_packet = util.time() + RETRY_PERIOD
end end
end end
end end

View File

@ -0,0 +1,100 @@
local log = require("scada-common.log")
local rsio = require("scada-common.rsio")
local util = require("scada-common.util")
local rsctl = require("supervisor.session.rsctl")
---@class facility_management
local facility = {}
-- create a new facility management object
function facility.new()
local self = {
induction = {},
redstone = {}
}
-- init redstone RTU I/O controller
local rs_rtu_io_ctl = rsctl.new(self.redstone)
-- unlink disconnected units
---@param sessions table
local function _unlink_disconnected_units(sessions)
util.filter_table(sessions, function (u) return u.is_connected() end)
end
-- PUBLIC FUNCTIONS --
---@class facility
local public = {}
-- ADD/LINK DEVICES --
-- link a redstone RTU session
---@param rs_unit unit_session
function public.add_redstone(rs_unit)
table.insert(self.redstone, rs_unit)
end
-- link an imatrix RTU session
---@param imatrix unit_session
function public.add_imatrix(imatrix)
table.insert(self.induction, imatrix)
end
-- purge devices associated with the given RTU session ID
---@param session integer RTU session ID
function public.purge_rtu_devices(session)
util.filter_table(self.redstone, function (s) return s.get_session_id() ~= session end)
util.filter_table(self.induction, function (s) return s.get_session_id() ~= session end)
end
-- UPDATE --
-- update (iterate) the facility management
function public.update()
-- unlink RTU unit sessions if they are closed
_unlink_disconnected_units(self.induction)
_unlink_disconnected_units(self.redstone)
end
-- READ STATES/PROPERTIES --
-- get build properties of all machines
function public.get_build()
local build = {}
build.induction = {}
for i = 1, #self.induction do
local matrix = self.induction[i] ---@type unit_session
build.induction[matrix.get_device_idx()] = { matrix.get_db().formed, matrix.get_db().build }
end
return build
end
-- get RTU statuses
function public.get_rtu_statuses()
local status = {}
-- status of induction matricies (including tanks)
status.induction = {}
for i = 1, #self.induction do
local matrix = self.induction[i] ---@type unit_session
status.induction[matrix.get_device_idx()] = {
matrix.is_faulted(),
matrix.get_db().formed,
matrix.get_db().state,
matrix.get_db().tanks
}
end
---@todo other RTU statuses
return status
end
return public
end
return facility

View File

@ -11,7 +11,7 @@ local PROTOCOLS = comms.PROTOCOLS
local RPLC_TYPES = comms.RPLC_TYPES local RPLC_TYPES = comms.RPLC_TYPES
local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES
local CRDN_COMMANDS = comms.CRDN_COMMANDS local UNIT_COMMANDS = comms.UNIT_COMMANDS
local print = util.print local print = util.print
local println = util.println local println = util.println
@ -352,7 +352,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
-- send acknowledgement to coordinator -- send acknowledgement to coordinator
self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, {
unit = self.for_reactor, unit = self.for_reactor,
cmd = CRDN_COMMANDS.SET_BURN, cmd = UNIT_COMMANDS.SET_BURN,
ack = ack ack = ack
}) })
elseif pkt.type == RPLC_TYPES.RPS_ENABLE then elseif pkt.type == RPLC_TYPES.RPS_ENABLE then
@ -367,7 +367,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
-- send acknowledgement to coordinator -- send acknowledgement to coordinator
self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, {
unit = self.for_reactor, unit = self.for_reactor,
cmd = CRDN_COMMANDS.START, cmd = UNIT_COMMANDS.START,
ack = ack ack = ack
}) })
elseif pkt.type == RPLC_TYPES.RPS_SCRAM then elseif pkt.type == RPLC_TYPES.RPS_SCRAM then
@ -383,7 +383,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
-- send acknowledgement to coordinator -- send acknowledgement to coordinator
self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, {
unit = self.for_reactor, unit = self.for_reactor,
cmd = CRDN_COMMANDS.SCRAM, cmd = UNIT_COMMANDS.SCRAM,
ack = ack ack = ack
}) })
elseif pkt.type == RPLC_TYPES.RPS_ASCRAM then elseif pkt.type == RPLC_TYPES.RPS_ASCRAM then
@ -435,7 +435,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
-- send acknowledgement to coordinator -- send acknowledgement to coordinator
self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, {
unit = self.for_reactor, unit = self.for_reactor,
cmd = CRDN_COMMANDS.RESET_RPS, cmd = UNIT_COMMANDS.RESET_RPS,
ack = ack ack = ack
}) })
else else

View File

@ -0,0 +1,39 @@
--
-- Redstone RTU Session I/O Controller
--
local rsctl = {}
-- create a new redstone RTU I/O controller
---@param redstone_rtus table redstone RTU sessions
function rsctl.new(redstone_rtus)
---@class rs_controller
local public = {}
-- write to a digital redstone port (applies to all RTUs)
---@param port IO_PORT
---@param value boolean
function public.digital_write(port, value)
for i = 1, #redstone_rtus do
local db = redstone_rtus[i].get_db() ---@type redstone_session_db
local io = db.io[port] ---@type rs_db_dig_io|nil
if io ~= nil then io.write(value) end
end
end
-- read a digital redstone port<br>
-- this will read from the first one encountered if there are multiple, because there should not be multiple
---@param port IO_PORT
---@return boolean|nil
function public.digital_read(port)
for i = 1, #redstone_rtus do
local db = redstone_rtus[i].get_db() ---@type redstone_session_db
local io = db.io[port] ---@type rs_db_dig_io|nil
if io ~= nil then return io.read() end
end
end
return public
end
return rsctl

View File

@ -36,16 +36,15 @@ local PERIODICS = {
---@param in_queue mqueue ---@param in_queue mqueue
---@param out_queue mqueue ---@param out_queue mqueue
---@param advertisement table ---@param advertisement table
---@param facility facility
---@param facility_units table ---@param facility_units table
function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) function rtu.new_session(id, in_queue, out_queue, advertisement, facility, facility_units)
local log_header = "rtu_session(" .. id .. "): " local log_header = "rtu_session(" .. id .. "): "
local self = { local self = {
id = id,
in_q = in_queue, in_q = in_queue,
out_q = out_queue, out_q = out_queue,
modbus_q = mqueue.new(), modbus_q = mqueue.new(),
f_units = facility_units,
advert = advertisement, advert = advertisement,
-- connection properties -- connection properties
seq_num = 0, seq_num = 0,
@ -72,9 +71,10 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units)
local function _handle_advertisement() local function _handle_advertisement()
_reset_config() _reset_config()
for i = 1, #self.f_units do for i = 1, #facility_units do
local unit = self.f_units[i] ---@type reactor_unit local unit = facility_units[i] ---@type reactor_unit
unit.purge_rtu_devices(self.id) unit.purge_rtu_devices(id)
facility.purge_rtu_devices(id)
end end
for i = 1, #self.advert do for i = 1, #self.advert do
@ -104,47 +104,61 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units)
if advert_validator.valid() then if advert_validator.valid() then
advert_validator.assert_min(unit_advert.index, 1) advert_validator.assert_min(unit_advert.index, 1)
advert_validator.assert_min(unit_advert.reactor, 1) advert_validator.assert_min(unit_advert.reactor, 0)
advert_validator.assert_max(unit_advert.reactor, #self.f_units) advert_validator.assert_max(unit_advert.reactor, #facility_units)
if not advert_validator.valid() then u_type = false end if not advert_validator.valid() then u_type = false end
else else
u_type = false u_type = false
end end
local type_string = util.strval(u_type)
if type(u_type) == "number" then type_string = util.strval(comms.advert_type_to_rtu_t(u_type)) end
-- create unit by type -- create unit by type
if u_type == false then if u_type == false then
-- validation fail -- validation fail
log.debug(log_header .. "advertisement unit validation failure") log.debug(log_header .. "advertisement unit validation failure")
else else
local target_unit = self.f_units[unit_advert.reactor] ---@type reactor_unit if unit_advert.reactor > 0 then
local target_unit = facility_units[unit_advert.reactor] ---@type reactor_unit
if u_type == RTU_UNIT_TYPES.REDSTONE then if u_type == RTU_UNIT_TYPES.REDSTONE then
-- redstone -- redstone
unit = svrs_redstone.new(self.id, i, unit_advert, self.modbus_q) unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q)
if type(unit) ~= "nil" then target_unit.add_redstone(unit) end if type(unit) ~= "nil" then target_unit.add_redstone(unit) end
elseif u_type == RTU_UNIT_TYPES.BOILER_VALVE then elseif u_type == RTU_UNIT_TYPES.BOILER_VALVE then
-- boiler (Mekanism 10.1+) -- boiler (Mekanism 10.1+)
unit = svrs_boilerv.new(self.id, i, unit_advert, self.modbus_q) unit = svrs_boilerv.new(id, i, unit_advert, self.modbus_q)
if type(unit) ~= "nil" then target_unit.add_boiler(unit) end if type(unit) ~= "nil" then target_unit.add_boiler(unit) end
elseif u_type == RTU_UNIT_TYPES.TURBINE_VALVE then elseif u_type == RTU_UNIT_TYPES.TURBINE_VALVE then
-- turbine (Mekanism 10.1+) -- turbine (Mekanism 10.1+)
unit = svrs_turbinev.new(self.id, i, unit_advert, self.modbus_q) unit = svrs_turbinev.new(id, i, unit_advert, self.modbus_q)
if type(unit) ~= "nil" then target_unit.add_turbine(unit) end if type(unit) ~= "nil" then target_unit.add_turbine(unit) end
elseif u_type == RTU_UNIT_TYPES.IMATRIX then else
-- induction matrix log.error(util.c(log_header, "bad advertisement: encountered unsupported reactor-specific RTU type ", type_string))
unit = svrs_imatrix.new(self.id, i, unit_advert, self.modbus_q) end
elseif u_type == RTU_UNIT_TYPES.SPS then
-- super-critical phase shifter
unit = svrs_sps.new(self.id, i, unit_advert, self.modbus_q)
elseif u_type == RTU_UNIT_TYPES.SNA then
-- solar neutron activator
unit = svrs_sna.new(self.id, i, unit_advert, self.modbus_q)
elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then
-- environment detector
unit = svrs_envd.new(self.id, i, unit_advert, self.modbus_q)
else else
log.error(log_header .. "bad advertisement: encountered unsupported RTU type") if u_type == RTU_UNIT_TYPES.REDSTONE then
-- redstone
unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q)
if type(unit) ~= "nil" then facility.add_redstone(unit) end
elseif u_type == RTU_UNIT_TYPES.IMATRIX then
-- induction matrix
unit = svrs_imatrix.new(id, i, unit_advert, self.modbus_q)
if type(unit) ~= "nil" then facility.add_imatrix(unit) end
elseif u_type == RTU_UNIT_TYPES.SPS then
-- super-critical phase shifter
unit = svrs_sps.new(id, i, unit_advert, self.modbus_q)
elseif u_type == RTU_UNIT_TYPES.SNA then
-- solar neutron activator
unit = svrs_sna.new(id, i, unit_advert, self.modbus_q)
elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then
-- environment detector
unit = svrs_envd.new(id, i, unit_advert, self.modbus_q)
else
log.error(util.c(log_header, "bad advertisement: encountered unsupported reactor-independent RTU type ", type_string))
end
end end
end end
@ -152,10 +166,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units)
table.insert(self.units, unit) table.insert(self.units, unit)
else else
_reset_config() _reset_config()
if type(u_type) == "number" then log.error(util.c(log_header, "bad advertisement: error occured while creating a unit (type is ", type_string, ")"))
local type_string = util.strval(comms.advert_type_to_rtu_t(u_type))
log.error(log_header .. "bad advertisement: error occured while creating a unit (type is " .. type_string .. ")")
end
break break
end end
end end
@ -271,7 +282,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units)
-- PUBLIC FUNCTIONS -- -- PUBLIC FUNCTIONS --
-- get the session ID -- get the session ID
function public.get_id() return self.id end function public.get_id() return id end
-- check if a timer matches this session's watchdog -- check if a timer matches this session's watchdog
---@param timer number ---@param timer number

View File

@ -2,6 +2,7 @@ local log = require("scada-common.log")
local mqueue = require("scada-common.mqueue") local mqueue = require("scada-common.mqueue")
local util = require("scada-common.util") local util = require("scada-common.util")
local facility = require("supervisor.session.facility")
local svqtypes = require("supervisor.session.svqtypes") local svqtypes = require("supervisor.session.svqtypes")
local unit = require("supervisor.session.unit") local unit = require("supervisor.session.unit")
@ -32,7 +33,8 @@ svsessions.SESSION_TYPE = SESSION_TYPE
local self = { local self = {
modem = nil, modem = nil,
num_reactors = 0, num_reactors = 0,
facility_units = {}, facility = facility.new(),
units = {},
rtu_sessions = {}, rtu_sessions = {},
plc_sessions = {}, plc_sessions = {},
coord_sessions = {}, coord_sessions = {},
@ -197,10 +199,10 @@ end
function svsessions.init(modem, num_reactors, cooling_conf) function svsessions.init(modem, num_reactors, cooling_conf)
self.modem = modem self.modem = modem
self.num_reactors = num_reactors self.num_reactors = num_reactors
self.facility_units = {} self.units = {}
for i = 1, self.num_reactors do for i = 1, self.num_reactors do
table.insert(self.facility_units, unit.new(i, cooling_conf[i].BOILERS, cooling_conf[i].TURBINES)) table.insert(self.units, unit.new(i, cooling_conf[i].BOILERS, cooling_conf[i].TURBINES))
end end
end end
@ -297,7 +299,7 @@ function svsessions.establish_plc_session(local_port, remote_port, for_reactor,
plc_s.instance = plc.new_session(self.next_plc_id, for_reactor, plc_s.in_queue, plc_s.out_queue) plc_s.instance = plc.new_session(self.next_plc_id, for_reactor, plc_s.in_queue, plc_s.out_queue)
table.insert(self.plc_sessions, plc_s) table.insert(self.plc_sessions, plc_s)
self.facility_units[for_reactor].link_plc_session(plc_s) self.units[for_reactor].link_plc_session(plc_s)
log.debug("established new PLC session to " .. remote_port .. " with ID " .. self.next_plc_id) log.debug("established new PLC session to " .. remote_port .. " with ID " .. self.next_plc_id)
@ -330,7 +332,7 @@ function svsessions.establish_rtu_session(local_port, remote_port, advertisement
instance = nil ---@type rtu_session instance = nil ---@type rtu_session
} }
rtu_s.instance = rtu.new_session(self.next_rtu_id, rtu_s.in_queue, rtu_s.out_queue, advertisement, self.facility_units) rtu_s.instance = rtu.new_session(self.next_rtu_id, rtu_s.in_queue, rtu_s.out_queue, advertisement, self.facility, self.units)
table.insert(self.rtu_sessions, rtu_s) table.insert(self.rtu_sessions, rtu_s)
log.debug("established new RTU session to " .. remote_port .. " with ID " .. self.next_rtu_id) log.debug("established new RTU session to " .. remote_port .. " with ID " .. self.next_rtu_id)
@ -360,7 +362,7 @@ function svsessions.establish_coord_session(local_port, remote_port, version)
instance = nil ---@type coord_session instance = nil ---@type coord_session
} }
coord_s.instance = coordinator.new_session(self.next_coord_id, coord_s.in_queue, coord_s.out_queue, self.facility_units) coord_s.instance = coordinator.new_session(self.next_coord_id, coord_s.in_queue, coord_s.out_queue, self.facility, self.units)
table.insert(self.coord_sessions, coord_s) table.insert(self.coord_sessions, coord_s)
log.debug("established new coordinator session to " .. remote_port .. " with ID " .. self.next_coord_id) log.debug("established new coordinator session to " .. remote_port .. " with ID " .. self.next_coord_id)
@ -399,9 +401,12 @@ function svsessions.iterate_all()
-- iterate coordinator sessions -- iterate coordinator sessions
_iterate(self.coord_sessions) _iterate(self.coord_sessions)
-- iterate facility
self.facility.update()
-- iterate units -- iterate units
for i = 1, #self.facility_units do for i = 1, #self.units do
local u = self.facility_units[i] ---@type reactor_unit local u = self.units[i] ---@type reactor_unit
u.update() u.update()
end end
end end

View File

@ -1,9 +1,9 @@
local log = require("scada-common.log") local log = require("scada-common.log")
local rsio = require("scada-common.rsio") local rsio = require("scada-common.rsio")
local types = require("scada-common.types") local types = require("scada-common.types")
local util = require("scada-common.util") local util = require("scada-common.util")
local qtypes = require("supervisor.session.rtu.qtypes") local rsctl = require("supervisor.session.rsctl")
---@class reactor_control_unit ---@class reactor_control_unit
local unit = {} local unit = {}
@ -178,6 +178,9 @@ function unit.new(for_reactor, num_boilers, num_turbines)
} }
} }
-- init redstone RTU I/O controller
local rs_rtu_io_ctl = rsctl.new(self.redstone)
-- init boiler table fields -- init boiler table fields
for _ = 1, num_boilers do for _ = 1, num_boilers do
table.insert(self.db.annunciator.BoilerOnline, false) table.insert(self.db.annunciator.BoilerOnline, false)
@ -192,9 +195,6 @@ function unit.new(for_reactor, num_boilers, num_turbines)
table.insert(self.db.annunciator.TurbineTrip, false) table.insert(self.db.annunciator.TurbineTrip, false)
end end
---@class reactor_unit
local public = {}
-- PRIVATE FUNCTIONS -- -- PRIVATE FUNCTIONS --
--#region time derivative utility functions --#region time derivative utility functions
@ -237,26 +237,8 @@ function unit.new(for_reactor, num_boilers, num_turbines)
--#region redstone I/O --#region redstone I/O
-- write to a redstone port local __rs_w = rs_rtu_io_ctl.digital_write
local function __rs_w(port, value) local __rs_r = rs_rtu_io_ctl.digital_read
for i = 1, #self.redstone do
local db = self.redstone[i].get_db() ---@type redstone_session_db
local io = db.io[port] ---@type rs_db_dig_io|nil
if io ~= nil then io.write(value) end
end
end
-- read a redstone port<br>
-- this will read from the first one encountered if there are multiple, because there should not be multiple
---@param port IO_PORT
---@return boolean|nil
local function __rs_r(port)
for i = 1, #self.redstone do
local db = self.redstone[i].get_db() ---@type redstone_session_db
local io = db.io[port] ---@type rs_db_dig_io|nil
if io ~= nil then return io.read() end
end
end
-- waste valves -- waste valves
local waste_pu = { open = function () __rs_w(IO.WASTE_PU, true) end, close = function () __rs_w(IO.WASTE_PU, false) end } local waste_pu = { open = function () __rs_w(IO.WASTE_PU, true) end, close = function () __rs_w(IO.WASTE_PU, false) end }
@ -702,6 +684,9 @@ function unit.new(for_reactor, num_boilers, num_turbines)
-- PUBLIC FUNCTIONS -- -- PUBLIC FUNCTIONS --
---@class reactor_unit
local public = {}
-- ADD/LINK DEVICES -- -- ADD/LINK DEVICES --
-- link the PLC -- link the PLC
@ -722,7 +707,6 @@ function unit.new(for_reactor, num_boilers, num_turbines)
-- link a redstone RTU session -- link a redstone RTU session
---@param rs_unit unit_session ---@param rs_unit unit_session
function public.add_redstone(rs_unit) function public.add_redstone(rs_unit)
-- insert into list
table.insert(self.redstone, rs_unit) table.insert(self.redstone, rs_unit)
end end
@ -781,6 +765,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
-- unlink RTU unit sessions if they are closed -- unlink RTU unit sessions if they are closed
_unlink_disconnected_units(self.boilers) _unlink_disconnected_units(self.boilers)
_unlink_disconnected_units(self.turbines) _unlink_disconnected_units(self.turbines)
_unlink_disconnected_units(self.redstone)
-- update annunciator logic -- update annunciator logic
_update_annunciator() _update_annunciator()

View File

@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions")
local config = require("supervisor.config") local config = require("supervisor.config")
local supervisor = require("supervisor.supervisor") local supervisor = require("supervisor.supervisor")
local SUPERVISOR_VERSION = "beta-v0.8.5" local SUPERVISOR_VERSION = "beta-v0.9.0"
local print = util.print local print = util.print
local println = util.println local println = util.println