#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,18 +168,55 @@ 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
log.error("number of provided unit builds does not match expected number of units")
return false
else
-- 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 i = 1, #builds do for id, build in pairs(builds) do
local unit = io.units[i] ---@type ioctl_entry local unit = io.units[id] ---@type ioctl_entry
local build = builds[i]
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 -- reactor build
if type(build.reactor) == "table" then if type(build.reactor) == "table" then
@ -186,32 +233,110 @@ function iocontrol.record_builds(builds)
-- boiler builds -- boiler builds
if type(build.boilers) == "table" then if type(build.boilers) == "table" then
for id, boiler in pairs(build.boilers) do for b_id, boiler in pairs(build.boilers) do
unit.boiler_data_tbl[id].formed = boiler[1] ---@type boolean if type(unit.boiler_data_tbl[b_id]) == "table" then
unit.boiler_data_tbl[id].build = boiler[2] ---@type table 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[id].publish("formed", boiler[1]) unit.boiler_ps_tbl[b_id].publish("formed", boiler[1])
for key, val in pairs(unit.boiler_data_tbl[id].build) do for key, val in pairs(unit.boiler_data_tbl[b_id].build) do
unit.boiler_ps_tbl[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))
end end
end end
end end
-- turbine builds -- turbine builds
if type(build.turbines) == "table" then if type(build.turbines) == "table" then
for id, turbine in pairs(build.turbines) do for t_id, turbine in pairs(build.turbines) do
unit.turbine_data_tbl[id].formed = turbine[1] ---@type boolean if type(unit.turbine_data_tbl[t_id]) == "table" then
unit.turbine_data_tbl[id].build = turbine[2] ---@type table 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[id].publish("formed", turbine[1]) unit.turbine_ps_tbl[t_id].publish("formed", turbine[1])
for key, val in pairs(unit.turbine_data_tbl[id].build) do for key, val in pairs(unit.turbine_data_tbl[t_id].build) do
unit.turbine_ps_tbl[id].publish(key, val) 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
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
else
local fac = io.facility
-- RTU statuses
local rtu_statuses = status[1]
if type(rtu_statuses) == "table" then
-- induction matricies statuses
if type(rtu_statuses.induction) == "table" then
for id = 1, #fac.induction_ps_tbl do
if rtu_statuses.induction[id] == nil then
-- disconnected
fac.induction_ps_tbl[id].publish("computed_status", 1)
end
end
for id, matrix in pairs(rtu_statuses.induction) do
if type(fac.induction_data_tbl[id]) == "table" then
local rtu_faulted = matrix[1] ---@type boolean
fac.induction_data_tbl[id].formed = matrix[2] ---@type boolean
fac.induction_data_tbl[id].state = matrix[3] ---@type table
fac.induction_data_tbl[id].tanks = matrix[4] ---@type table
local data = fac.induction_data_tbl[id] ---@type imatrix_session_db
fac.induction_ps_tbl[id].publish("formed", data.formed)
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
else
log.debug(log_header .. "induction matrix list not a table")
end
end
end end
return true return true
@ -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,6 +441,7 @@ 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
if type(unit.boiler_data_tbl[id]) == "table" then
local rtu_faulted = boiler[1] ---@type boolean local rtu_faulted = boiler[1] ---@type boolean
unit.boiler_data_tbl[id].formed = boiler[2] ---@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].state = boiler[3] ---@type table
@ -327,14 +454,14 @@ function iocontrol.update_statuses(statuses)
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 else
unit.boiler_ps_tbl[id].publish("computed_status", 2) -- idle unit.boiler_ps_tbl[id].publish("computed_status", 4) -- idle
end end
else else
unit.boiler_ps_tbl[id].publish("computed_status", 5) -- not formed unit.boiler_ps_tbl[id].publish("computed_status", 2) -- not formed
end end
for key, val in pairs(unit.boiler_data_tbl[id].state) do for key, val in pairs(unit.boiler_data_tbl[id].state) do
@ -344,6 +471,9 @@ function iocontrol.update_statuses(statuses)
for key, val in pairs(unit.boiler_data_tbl[id].tanks) do for key, val in pairs(unit.boiler_data_tbl[id].tanks) do
unit.boiler_ps_tbl[id].publish(key, val) unit.boiler_ps_tbl[id].publish(key, val)
end end
else
log.debug(util.c(log_header, "invalid boiler id ", id))
end
end end
else else
log.debug(log_header .. "boiler list not a table") log.debug(log_header .. "boiler list not a table")
@ -359,6 +489,7 @@ 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
if type(unit.turbine_data_tbl[id]) == "table" then
local rtu_faulted = turbine[1] ---@type boolean local rtu_faulted = turbine[1] ---@type boolean
unit.turbine_data_tbl[id].formed = turbine[2] ---@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].state = turbine[3] ---@type table
@ -371,16 +502,16 @@ function iocontrol.update_statuses(statuses)
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 else
unit.turbine_ps_tbl[id].publish("computed_status", 3) -- active unit.turbine_ps_tbl[id].publish("computed_status", 5) -- active
end end
else else
unit.turbine_ps_tbl[id].publish("computed_status", 6) -- not formed unit.turbine_ps_tbl[id].publish("computed_status", 2) -- not formed
end end
for key, val in pairs(unit.turbine_data_tbl[id].state) do for key, val in pairs(unit.turbine_data_tbl[id].state) do
@ -390,6 +521,9 @@ function iocontrol.update_statuses(statuses)
for key, val in pairs(unit.turbine_data_tbl[id].tanks) do for key, val in pairs(unit.turbine_data_tbl[id].tanks) do
unit.turbine_ps_tbl[id].publish(key, val) unit.turbine_ps_tbl[id].publish(key, val)
end end
else
log.debug(util.c(log_header, "invalid turbine id ", id))
end
end end
else else
log.debug(log_header .. "turbine list not a table") log.debug(log_header .. "turbine list not a table")

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 = {
@ -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
else
log.error(util.c(log_header, "bad advertisement: encountered unsupported reactor-specific RTU type ", type_string))
end
else
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 elseif u_type == RTU_UNIT_TYPES.IMATRIX then
-- induction matrix -- induction matrix
unit = svrs_imatrix.new(self.id, i, unit_advert, self.modbus_q) 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 elseif u_type == RTU_UNIT_TYPES.SPS then
-- super-critical phase shifter -- super-critical phase shifter
unit = svrs_sps.new(self.id, i, unit_advert, self.modbus_q) unit = svrs_sps.new(id, i, unit_advert, self.modbus_q)
elseif u_type == RTU_UNIT_TYPES.SNA then elseif u_type == RTU_UNIT_TYPES.SNA then
-- solar neutron activator -- solar neutron activator
unit = svrs_sna.new(self.id, i, unit_advert, self.modbus_q) unit = svrs_sna.new(id, i, unit_advert, self.modbus_q)
elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then
-- environment detector -- environment detector
unit = svrs_envd.new(self.id, i, unit_advert, self.modbus_q) unit = svrs_envd.new(id, i, unit_advert, self.modbus_q)
else else
log.error(log_header .. "bad advertisement: encountered unsupported RTU type") 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

@ -3,7 +3,7 @@ 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