mirror of
https://github.com/MikaylaFischler/cc-mek-scada.git
synced 2024-08-30 18:22:34 +00:00
#118 supervisor cleanup
This commit is contained in:
parent
38ac552613
commit
b7895080cb
@ -53,6 +53,7 @@ local rate_Kd = -1.0
|
||||
local facility = {}
|
||||
|
||||
-- create a new facility management object
|
||||
---@nodiscard
|
||||
---@param num_reactors integer number of reactor units
|
||||
---@param cooling_conf table cooling configurations of reactor units
|
||||
function facility.new(num_reactors, cooling_conf)
|
||||
@ -124,6 +125,7 @@ function facility.new(num_reactors, cooling_conf)
|
||||
end
|
||||
|
||||
-- check if all auto-controlled units completed ramping
|
||||
---@nodiscard
|
||||
local function _all_units_ramped()
|
||||
local all_ramped = true
|
||||
|
||||
@ -185,10 +187,7 @@ function facility.new(num_reactors, cooling_conf)
|
||||
|
||||
unallocated = math.max(0, unallocated - ctl.br100)
|
||||
|
||||
if last ~= ctl.br100 then
|
||||
log.debug("unit " .. u.get_id() .. ": set to " .. ctl.br100 .. " (was " .. last .. ")")
|
||||
u.a_commit_br100(ramp)
|
||||
end
|
||||
if last ~= ctl.br100 then u.a_commit_br100(ramp) end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -426,7 +425,7 @@ function facility.new(num_reactors, cooling_conf)
|
||||
self.accumulator = self.accumulator + (error * (now - self.last_time))
|
||||
end
|
||||
|
||||
local runtime = now - self.time_start
|
||||
-- local runtime = now - self.time_start
|
||||
local integral = self.accumulator
|
||||
local derivative = (error - self.last_error) / (now - self.last_time)
|
||||
|
||||
@ -441,8 +440,8 @@ function facility.new(num_reactors, cooling_conf)
|
||||
|
||||
self.saturated = output ~= out_c
|
||||
|
||||
log.debug(util.sprintf("CHARGE[%f] { CHRG[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%d] }",
|
||||
runtime, avg_charge, error, integral, output, out_c, P, I, D))
|
||||
-- log.debug(util.sprintf("CHARGE[%f] { CHRG[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%d] }",
|
||||
-- runtime, avg_charge, error, integral, output, out_c, P, I, D))
|
||||
|
||||
_allocate_burn_rate(out_c, true)
|
||||
|
||||
@ -495,7 +494,7 @@ function facility.new(num_reactors, cooling_conf)
|
||||
self.accumulator = self.accumulator + (error * (now - self.last_time))
|
||||
end
|
||||
|
||||
local runtime = now - self.time_start
|
||||
-- local runtime = now - self.time_start
|
||||
local integral = self.accumulator
|
||||
local derivative = (error - self.last_error) / (now - self.last_time)
|
||||
|
||||
@ -513,8 +512,8 @@ function facility.new(num_reactors, cooling_conf)
|
||||
|
||||
self.saturated = output ~= out_c
|
||||
|
||||
log.debug(util.sprintf("GEN_RATE[%f] { RATE[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%f] }",
|
||||
runtime, avg_inflow, error, integral, output, out_c, P, I, D))
|
||||
-- log.debug(util.sprintf("GEN_RATE[%f] { RATE[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%f] }",
|
||||
-- runtime, avg_inflow, error, integral, output, out_c, P, I, D))
|
||||
|
||||
_allocate_burn_rate(out_c, false)
|
||||
|
||||
@ -814,6 +813,7 @@ function facility.new(num_reactors, cooling_conf)
|
||||
-- READ STATES/PROPERTIES --
|
||||
|
||||
-- get build properties of all machines
|
||||
---@nodiscard
|
||||
---@param inc_imatrix boolean? true/nil to include induction matrix build, false to exclude
|
||||
function public.get_build(inc_imatrix)
|
||||
local build = {}
|
||||
@ -830,6 +830,7 @@ function facility.new(num_reactors, cooling_conf)
|
||||
end
|
||||
|
||||
-- get automatic process control status
|
||||
---@nodiscard
|
||||
function public.get_control_status()
|
||||
local astat = self.ascram_status
|
||||
return {
|
||||
@ -851,6 +852,7 @@ function facility.new(num_reactors, cooling_conf)
|
||||
end
|
||||
|
||||
-- get RTU statuses
|
||||
---@nodiscard
|
||||
function public.get_rtu_statuses()
|
||||
local status = {}
|
||||
|
||||
@ -889,9 +891,9 @@ function facility.new(num_reactors, cooling_conf)
|
||||
return status
|
||||
end
|
||||
|
||||
function public.get_units()
|
||||
return self.units
|
||||
end
|
||||
-- get the units in this facility
|
||||
---@nodiscard
|
||||
function public.get_units() return self.units end
|
||||
|
||||
return public
|
||||
end
|
||||
|
@ -13,6 +13,7 @@ local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
|
||||
local SCADA_CRDN_TYPE = comms.SCADA_CRDN_TYPE
|
||||
local UNIT_COMMAND = comms.UNIT_COMMAND
|
||||
local FAC_COMMAND = comms.FAC_COMMAND
|
||||
|
||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
|
||||
local SV_Q_CMDS = svqtypes.SV_Q_CMDS
|
||||
@ -46,6 +47,7 @@ local PERIODICS = {
|
||||
}
|
||||
|
||||
-- coordinator supervisor session
|
||||
---@nodiscard
|
||||
---@param id integer session ID
|
||||
---@param in_queue mqueue in message queue
|
||||
---@param out_queue mqueue out message queue
|
||||
@ -55,8 +57,6 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility)
|
||||
local log_header = "crdn_session(" .. id .. "): "
|
||||
|
||||
local self = {
|
||||
in_q = in_queue,
|
||||
out_q = out_queue,
|
||||
units = facility.get_units(),
|
||||
-- connection properties
|
||||
seq_num = 0,
|
||||
@ -100,7 +100,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility)
|
||||
c_pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.SCADA_CRDN, c_pkt.raw_sendable())
|
||||
|
||||
self.out_q.push_packet(s_pkt)
|
||||
out_queue.push_packet(s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
end
|
||||
|
||||
@ -114,7 +114,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility)
|
||||
m_pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
self.out_q.push_packet(s_pkt)
|
||||
out_queue.push_packet(s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
end
|
||||
|
||||
@ -275,14 +275,14 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility)
|
||||
local unit = self.units[uid] ---@type reactor_unit
|
||||
|
||||
if cmd == UNIT_COMMAND.START then
|
||||
self.out_q.push_data(SV_Q_DATA.START, data)
|
||||
out_queue.push_data(SV_Q_DATA.START, data)
|
||||
elseif cmd == UNIT_COMMAND.SCRAM then
|
||||
self.out_q.push_data(SV_Q_DATA.SCRAM, data)
|
||||
out_queue.push_data(SV_Q_DATA.SCRAM, data)
|
||||
elseif cmd == UNIT_COMMAND.RESET_RPS then
|
||||
self.out_q.push_data(SV_Q_DATA.RESET_RPS, data)
|
||||
out_queue.push_data(SV_Q_DATA.RESET_RPS, data)
|
||||
elseif cmd == UNIT_COMMAND.SET_BURN then
|
||||
if pkt.length == 3 then
|
||||
self.out_q.push_data(SV_Q_DATA.SET_BURN, data)
|
||||
out_queue.push_data(SV_Q_DATA.SET_BURN, data)
|
||||
else
|
||||
log.debug(log_header .. "CRDN unit command burn rate missing option")
|
||||
end
|
||||
@ -333,9 +333,11 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility)
|
||||
local public = {}
|
||||
|
||||
-- get the session ID
|
||||
---@nodiscard
|
||||
function public.get_id() return id end
|
||||
|
||||
-- check if a timer matches this session's watchdog
|
||||
---@nodiscard
|
||||
function public.check_wd(timer)
|
||||
return self.conn_watchdog.is_timer(timer) and self.connected
|
||||
end
|
||||
@ -349,6 +351,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility)
|
||||
end
|
||||
|
||||
-- iterate the session
|
||||
---@nodiscard
|
||||
---@return boolean connected
|
||||
function public.iterate()
|
||||
if self.connected then
|
||||
@ -358,9 +361,9 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility)
|
||||
|
||||
local handle_start = util.time()
|
||||
|
||||
while self.in_q.ready() and self.connected do
|
||||
while in_queue.ready() and self.connected do
|
||||
-- get a new message to process
|
||||
local message = self.in_q.pop()
|
||||
local message = in_queue.pop()
|
||||
|
||||
if message ~= nil then
|
||||
if message.qtype == mqueue.TYPE.PACKET then
|
||||
|
@ -12,7 +12,6 @@ local PROTOCOL = comms.PROTOCOL
|
||||
local RPLC_TYPE = comms.RPLC_TYPE
|
||||
local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
|
||||
local PLC_AUTO_ACK = comms.PLC_AUTO_ACK
|
||||
|
||||
local UNIT_COMMAND = comms.UNIT_COMMAND
|
||||
|
||||
local print = util.print
|
||||
@ -47,18 +46,16 @@ local PERIODICS = {
|
||||
}
|
||||
|
||||
-- PLC supervisor session
|
||||
---@nodiscard
|
||||
---@param id integer session ID
|
||||
---@param for_reactor integer reactor ID
|
||||
---@param reactor_id integer reactor ID
|
||||
---@param in_queue mqueue in message queue
|
||||
---@param out_queue mqueue out message queue
|
||||
---@param timeout number communications timeout
|
||||
function plc.new_session(id, for_reactor, in_queue, out_queue, timeout)
|
||||
function plc.new_session(id, reactor_id, in_queue, out_queue, timeout)
|
||||
local log_header = "plc_session(" .. id .. "): "
|
||||
|
||||
local self = {
|
||||
for_reactor = for_reactor,
|
||||
in_q = in_queue,
|
||||
out_q = out_queue,
|
||||
commanded_state = false,
|
||||
commanded_burn_rate = 0.0,
|
||||
auto_cmd_token = 0,
|
||||
@ -250,10 +247,10 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout)
|
||||
local s_pkt = comms.scada_packet()
|
||||
local r_pkt = comms.rplc_packet()
|
||||
|
||||
r_pkt.make(for_reactor, msg_type, msg)
|
||||
r_pkt.make(reactor_id, msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.RPLC, r_pkt.raw_sendable())
|
||||
|
||||
self.out_q.push_packet(s_pkt)
|
||||
out_queue.push_packet(s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
end
|
||||
|
||||
@ -267,11 +264,12 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout)
|
||||
m_pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
self.out_q.push_packet(s_pkt)
|
||||
out_queue.push_packet(s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
end
|
||||
|
||||
-- get an ACK status
|
||||
---@nodiscard
|
||||
---@param pkt rplc_frame
|
||||
---@return boolean|nil ack
|
||||
local function _get_ack(pkt)
|
||||
@ -299,8 +297,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout)
|
||||
-- process packet
|
||||
if pkt.scada_frame.protocol() == PROTOCOL.RPLC then
|
||||
-- check reactor ID
|
||||
if pkt.id ~= for_reactor then
|
||||
log.warning(log_header .. "RPLC packet with ID not matching reactor ID: reactor " .. self.for_reactor .. " != " .. pkt.id)
|
||||
if pkt.id ~= reactor_id then
|
||||
log.warning(log_header .. "RPLC packet with ID not matching reactor ID: reactor " .. reactor_id .. " != " .. pkt.id)
|
||||
return
|
||||
end
|
||||
|
||||
@ -342,7 +340,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout)
|
||||
if status then
|
||||
-- copied in structure data OK
|
||||
self.received_struct = true
|
||||
self.out_q.push_data(svqtypes.SV_Q_DATA.PLC_BUILD_CHANGED, for_reactor)
|
||||
out_queue.push_data(svqtypes.SV_Q_DATA.PLC_BUILD_CHANGED, reactor_id)
|
||||
else
|
||||
-- error copying structure data
|
||||
log.error(log_header .. "failed to parse struct packet data")
|
||||
@ -360,8 +358,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout)
|
||||
end
|
||||
|
||||
-- send acknowledgement to coordinator
|
||||
self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, {
|
||||
unit = self.for_reactor,
|
||||
out_queue.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, {
|
||||
unit = reactor_id,
|
||||
cmd = UNIT_COMMAND.SET_BURN,
|
||||
ack = ack
|
||||
})
|
||||
@ -375,8 +373,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout)
|
||||
end
|
||||
|
||||
-- send acknowledgement to coordinator
|
||||
self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, {
|
||||
unit = self.for_reactor,
|
||||
out_queue.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, {
|
||||
unit = reactor_id,
|
||||
cmd = UNIT_COMMAND.START,
|
||||
ack = ack
|
||||
})
|
||||
@ -391,8 +389,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout)
|
||||
end
|
||||
|
||||
-- send acknowledgement to coordinator
|
||||
self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, {
|
||||
unit = self.for_reactor,
|
||||
out_queue.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, {
|
||||
unit = reactor_id,
|
||||
cmd = UNIT_COMMAND.SCRAM,
|
||||
ack = ack
|
||||
})
|
||||
@ -443,8 +441,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout)
|
||||
end
|
||||
|
||||
-- send acknowledgement to coordinator
|
||||
self.out_q.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, {
|
||||
unit = self.for_reactor,
|
||||
out_queue.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, {
|
||||
unit = reactor_id,
|
||||
cmd = UNIT_COMMAND.RESET_RPS,
|
||||
ack = ack
|
||||
})
|
||||
@ -503,17 +501,22 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout)
|
||||
-- PUBLIC FUNCTIONS --
|
||||
|
||||
-- get the session ID
|
||||
---@nodiscard
|
||||
function public.get_id() return id end
|
||||
|
||||
-- get the session database
|
||||
---@nodiscard
|
||||
function public.get_db() return self.sDB end
|
||||
|
||||
-- check if ramping is completed by first verifying auto command token ack
|
||||
---@nodiscard
|
||||
function public.is_ramp_complete()
|
||||
return (self.sDB.auto_ack_token == self.auto_cmd_token) and (self.commanded_burn_rate == self.sDB.mek_status.act_burn_rate)
|
||||
end
|
||||
|
||||
-- get the reactor structure
|
||||
---@nodiscard
|
||||
---@return mek_struct|table struct struct or empty table
|
||||
function public.get_struct()
|
||||
if self.received_struct then
|
||||
return self.sDB.mek_struct
|
||||
@ -523,6 +526,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout)
|
||||
end
|
||||
|
||||
-- get the reactor status
|
||||
---@nodiscard
|
||||
---@return mek_status|table struct status or empty table
|
||||
function public.get_status()
|
||||
if self.received_status_cache then
|
||||
return self.sDB.mek_status
|
||||
@ -532,11 +537,13 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout)
|
||||
end
|
||||
|
||||
-- get the reactor RPS status
|
||||
---@nodiscard
|
||||
function public.get_rps()
|
||||
return self.sDB.rps_status
|
||||
end
|
||||
|
||||
-- get the general status information
|
||||
---@nodiscard
|
||||
function public.get_general_status()
|
||||
return {
|
||||
self.sDB.last_status_update,
|
||||
@ -564,10 +571,11 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout)
|
||||
---@param ramp boolean true to ramp, false to not
|
||||
function public.auto_set_burn(rate, ramp)
|
||||
self.ramping_rate = ramp
|
||||
self.in_q.push_data(PLC_S_DATA.AUTO_BURN_RATE, rate)
|
||||
in_queue.push_data(PLC_S_DATA.AUTO_BURN_RATE, rate)
|
||||
end
|
||||
|
||||
-- check if a timer matches this session's watchdog
|
||||
---@nodiscard
|
||||
function public.check_wd(timer)
|
||||
return self.plc_conn_watchdog.is_timer(timer) and self.connected
|
||||
end
|
||||
@ -576,11 +584,12 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout)
|
||||
function public.close()
|
||||
_close()
|
||||
_send_mgmt(SCADA_MGMT_TYPE.CLOSE, {})
|
||||
println("connection to reactor " .. self.for_reactor .. " PLC closed by server")
|
||||
println("connection to reactor " .. reactor_id .. " PLC closed by server")
|
||||
log.info(log_header .. "session closed by server")
|
||||
end
|
||||
|
||||
-- iterate the session
|
||||
---@nodiscard
|
||||
---@return boolean connected
|
||||
function public.iterate()
|
||||
if self.connected then
|
||||
@ -590,9 +599,9 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout)
|
||||
|
||||
local handle_start = util.time()
|
||||
|
||||
while self.in_q.ready() and self.connected do
|
||||
while in_queue.ready() and self.connected do
|
||||
-- get a new message to process
|
||||
local message = self.in_q.pop()
|
||||
local message = in_queue.pop()
|
||||
|
||||
if message ~= nil then
|
||||
if message.qtype == mqueue.TYPE.PACKET then
|
||||
@ -688,7 +697,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue, timeout)
|
||||
|
||||
-- exit if connection was closed
|
||||
if not self.connected then
|
||||
println("connection to reactor " .. self.for_reactor .. " PLC closed by remote host")
|
||||
println("connection to reactor " .. reactor_id .. " PLC closed by remote host")
|
||||
log.info(log_header .. "session closed by remote host")
|
||||
return self.connected
|
||||
end
|
||||
|
@ -5,6 +5,7 @@
|
||||
local rsctl = {}
|
||||
|
||||
-- create a new redstone RTU I/O controller
|
||||
---@nodiscard
|
||||
---@param redstone_rtus table redstone RTU sessions
|
||||
function rsctl.new(redstone_rtus)
|
||||
---@class rs_controller
|
||||
|
@ -32,6 +32,7 @@ local PERIODICS = {
|
||||
}
|
||||
|
||||
-- create a new RTU session
|
||||
---@nodiscard
|
||||
---@param id integer session ID
|
||||
---@param in_queue mqueue in message queue
|
||||
---@param out_queue mqueue out message queue
|
||||
@ -42,8 +43,6 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
|
||||
local log_header = "rtu_session(" .. id .. "): "
|
||||
|
||||
local self = {
|
||||
in_q = in_queue,
|
||||
out_q = out_queue,
|
||||
modbus_q = mqueue.new(),
|
||||
advert = advertisement,
|
||||
fac_units = facility.get_units(),
|
||||
@ -196,7 +195,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
|
||||
|
||||
s_pkt.make(self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable())
|
||||
|
||||
self.out_q.push_packet(s_pkt)
|
||||
out_queue.push_packet(s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
end
|
||||
|
||||
@ -210,7 +209,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
|
||||
m_pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
self.out_q.push_packet(s_pkt)
|
||||
out_queue.push_packet(s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
end
|
||||
|
||||
@ -262,10 +261,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
|
||||
elseif pkt.type == SCADA_MGMT_TYPE.RTU_ADVERT then
|
||||
-- RTU unit advertisement
|
||||
log.debug(log_header .. "received updated advertisement")
|
||||
|
||||
-- copy advertisement and remove version tag
|
||||
self.advert = pkt.data
|
||||
table.remove(self.advert, 1)
|
||||
|
||||
-- handle advertisement; this will re-create all unit sub-sessions
|
||||
_handle_advertisement()
|
||||
@ -291,6 +287,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
|
||||
function public.get_id() return id end
|
||||
|
||||
-- check if a timer matches this session's watchdog
|
||||
---@nodiscard
|
||||
---@param timer number
|
||||
function public.check_wd(timer)
|
||||
return self.rtu_conn_watchdog.is_timer(timer) and self.connected
|
||||
@ -305,6 +302,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
|
||||
end
|
||||
|
||||
-- iterate the session
|
||||
---@nodiscard
|
||||
---@return boolean connected
|
||||
function public.iterate()
|
||||
if self.connected then
|
||||
@ -314,9 +312,9 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
|
||||
|
||||
local handle_start = util.time()
|
||||
|
||||
while self.in_q.ready() and self.connected do
|
||||
while in_queue.ready() and self.connected do
|
||||
-- get a new message to process
|
||||
local msg = self.in_q.pop()
|
||||
local msg = in_queue.pop()
|
||||
|
||||
if msg ~= nil then
|
||||
if msg.qtype == mqueue.TYPE.PACKET then
|
||||
@ -389,7 +387,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
|
||||
-- instruction with body
|
||||
local cmd = msg.message ---@type queue_data
|
||||
if cmd.key == unit_session.RTU_US_DATA.BUILD_CHANGED then
|
||||
self.out_q.push_data(svqtypes.SV_Q_DATA.RTU_BUILD_CHANGED, cmd.val)
|
||||
out_queue.push_data(svqtypes.SV_Q_DATA.RTU_BUILD_CHANGED, cmd.val)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -31,6 +31,7 @@ local PERIODICS = {
|
||||
}
|
||||
|
||||
-- create a new boilerv rtu session runner
|
||||
---@nodiscard
|
||||
---@param session_id integer RTU session ID
|
||||
---@param unit_id integer RTU unit ID
|
||||
---@param advert rtu_advertisement RTU advertisement table
|
||||
@ -238,6 +239,7 @@ function boilerv.new(session_id, unit_id, advert, out_queue)
|
||||
end
|
||||
|
||||
-- get the unit session database
|
||||
---@nodiscard
|
||||
function public.get_db() return self.db end
|
||||
|
||||
return public
|
||||
|
@ -22,6 +22,7 @@ local PERIODICS = {
|
||||
}
|
||||
|
||||
-- create a new environment detector rtu session runner
|
||||
---@nodiscard
|
||||
---@param session_id integer
|
||||
---@param unit_id integer
|
||||
---@param advert rtu_advertisement
|
||||
@ -99,6 +100,7 @@ function envd.new(session_id, unit_id, advert, out_queue)
|
||||
end
|
||||
|
||||
-- get the unit session database
|
||||
---@nodiscard
|
||||
function public.get_db() return self.db end
|
||||
|
||||
return public
|
||||
|
@ -31,6 +31,7 @@ local PERIODICS = {
|
||||
}
|
||||
|
||||
-- create a new imatrix rtu session runner
|
||||
---@nodiscard
|
||||
---@param session_id integer RTU session ID
|
||||
---@param unit_id integer RTU unit ID
|
||||
---@param advert rtu_advertisement RTU advertisement table
|
||||
@ -212,6 +213,7 @@ function imatrix.new(session_id, unit_id, advert, out_queue)
|
||||
end
|
||||
|
||||
-- get the unit session database
|
||||
---@nodiscard
|
||||
function public.get_db() return self.db end
|
||||
|
||||
return public
|
||||
|
@ -45,6 +45,7 @@ local PERIODICS = {
|
||||
---@field req IO_LVL
|
||||
|
||||
-- create a new redstone rtu session runner
|
||||
---@nodiscard
|
||||
---@param session_id integer
|
||||
---@param unit_id integer
|
||||
---@param advert rtu_advertisement
|
||||
@ -118,6 +119,7 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
|
||||
---@class rs_db_dig_io
|
||||
local io_f = {
|
||||
---@nodiscard
|
||||
read = function () return rsio.digital_is_active(port, self.phy_io.digital_in[port].phy) end,
|
||||
---@param active boolean
|
||||
write = function (active) end
|
||||
@ -132,6 +134,7 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
|
||||
---@class rs_db_dig_io
|
||||
local io_f = {
|
||||
---@nodiscard
|
||||
read = function () return rsio.digital_is_active(port, self.phy_io.digital_out[port].phy) end,
|
||||
---@param active boolean
|
||||
write = function (active)
|
||||
@ -149,6 +152,7 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
|
||||
---@class rs_db_ana_io
|
||||
local io_f = {
|
||||
---@nodiscard
|
||||
---@return integer
|
||||
read = function () return self.phy_io.analog_in[port].phy end,
|
||||
---@param value integer
|
||||
@ -164,6 +168,7 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
|
||||
---@class rs_db_ana_io
|
||||
local io_f = {
|
||||
---@nodiscard
|
||||
---@return integer
|
||||
read = function () return self.phy_io.analog_out[port].phy end,
|
||||
---@param value integer
|
||||
@ -378,6 +383,7 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
||||
end
|
||||
|
||||
-- get the unit session database
|
||||
---@nodiscard
|
||||
function public.get_db() return self.db end
|
||||
|
||||
return public
|
||||
|
@ -28,6 +28,7 @@ local PERIODICS = {
|
||||
}
|
||||
|
||||
-- create a new sna rtu session runner
|
||||
---@nodiscard
|
||||
---@param session_id integer RTU session ID
|
||||
---@param unit_id integer RTU unit ID
|
||||
---@param advert rtu_advertisement RTU advertisement table
|
||||
@ -175,6 +176,7 @@ function sna.new(session_id, unit_id, advert, out_queue)
|
||||
end
|
||||
|
||||
-- get the unit session database
|
||||
---@nodiscard
|
||||
function public.get_db() return self.db end
|
||||
|
||||
return public
|
||||
|
@ -31,6 +31,7 @@ local PERIODICS = {
|
||||
}
|
||||
|
||||
-- create a new sps rtu session runner
|
||||
---@nodiscard
|
||||
---@param session_id integer RTU session ID
|
||||
---@param unit_id integer RTU unit ID
|
||||
---@param advert rtu_advertisement RTU advertisement table
|
||||
@ -112,7 +113,7 @@ function sps.new(session_id, unit_id, advert, out_queue)
|
||||
-- query the tanks of the device
|
||||
local function _request_tanks()
|
||||
-- read input registers 11 through 19 (start = 11, count = 9)
|
||||
self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 10, 12 })
|
||||
self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 11, 9 })
|
||||
end
|
||||
|
||||
-- PUBLIC FUNCTIONS --
|
||||
@ -222,6 +223,7 @@ function sps.new(session_id, unit_id, advert, out_queue)
|
||||
end
|
||||
|
||||
-- get the unit session database
|
||||
---@nodiscard
|
||||
function public.get_db() return self.db end
|
||||
|
||||
return public
|
||||
|
@ -43,6 +43,7 @@ local PERIODICS = {
|
||||
}
|
||||
|
||||
-- create a new turbinev rtu session runner
|
||||
---@nodiscard
|
||||
---@param session_id integer RTU session ID
|
||||
---@param unit_id integer RTU unit ID
|
||||
---@param advert rtu_advertisement RTU advertisement table
|
||||
@ -309,6 +310,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue)
|
||||
end
|
||||
|
||||
-- get the unit session database
|
||||
---@nodiscard
|
||||
function public.get_db() return self.db end
|
||||
|
||||
return public
|
||||
|
@ -6,9 +6,10 @@ local util = require("scada-common.util")
|
||||
|
||||
local txnctrl = {}
|
||||
|
||||
local TIMEOUT = 2000 -- 2000ms max wait
|
||||
local TIMEOUT = 2000 -- 2000ms max wait
|
||||
|
||||
-- create a new transaction controller
|
||||
---@nodiscard
|
||||
function txnctrl.new()
|
||||
local self = {
|
||||
list = {},
|
||||
@ -22,16 +23,19 @@ function txnctrl.new()
|
||||
local remove = table.remove
|
||||
|
||||
-- get the length of the transaction list
|
||||
---@nodiscard
|
||||
function public.length()
|
||||
return #self.list
|
||||
end
|
||||
|
||||
-- check if there are no active transactions
|
||||
---@nodiscard
|
||||
function public.empty()
|
||||
return #self.list == 0
|
||||
end
|
||||
|
||||
-- create a new transaction of the given type
|
||||
---@nodiscard
|
||||
---@param txn_type integer
|
||||
---@return integer txn_id
|
||||
function public.create(txn_type)
|
||||
@ -49,6 +53,7 @@ function txnctrl.new()
|
||||
end
|
||||
|
||||
-- mark a transaction as resolved to get its transaction type
|
||||
---@nodiscard
|
||||
---@param txn_id integer
|
||||
---@return integer txn_type
|
||||
function public.resolve(txn_id)
|
||||
|
@ -23,6 +23,7 @@ unit_session.RTU_US_CMDS = RTU_US_CMDS
|
||||
unit_session.RTU_US_DATA = RTU_US_DATA
|
||||
|
||||
-- create a new unit session runner
|
||||
---@nodiscard
|
||||
---@param session_id integer RTU session ID
|
||||
---@param unit_id integer MODBUS unit ID
|
||||
---@param advert rtu_advertisement RTU advertisement for this unit
|
||||
@ -31,12 +32,8 @@ unit_session.RTU_US_DATA = RTU_US_DATA
|
||||
---@param txn_tags table transaction log tags
|
||||
function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_tags)
|
||||
local self = {
|
||||
log_tag = log_tag,
|
||||
txn_tags = txn_tags,
|
||||
unit_id = unit_id,
|
||||
device_index = advert.index,
|
||||
reactor = advert.reactor,
|
||||
out_q = out_queue,
|
||||
transaction_controller = txnctrl.new(),
|
||||
connected = true,
|
||||
device_fail = false
|
||||
@ -61,21 +58,22 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t
|
||||
local m_pkt = comms.modbus_packet()
|
||||
local txn_id = self.transaction_controller.create(txn_type)
|
||||
|
||||
m_pkt.make(txn_id, self.unit_id, f_code, register_param)
|
||||
m_pkt.make(txn_id, unit_id, f_code, register_param)
|
||||
|
||||
self.out_q.push_packet(m_pkt)
|
||||
out_queue.push_packet(m_pkt)
|
||||
|
||||
return txn_id
|
||||
end
|
||||
|
||||
-- try to resolve a MODBUS transaction
|
||||
---@nodiscard
|
||||
---@param m_pkt modbus_frame MODBUS packet
|
||||
---@return integer|false txn_type, integer txn_id transaction type or false on error/busy, transaction ID
|
||||
function protected.try_resolve(m_pkt)
|
||||
if m_pkt.scada_frame.protocol() == PROTOCOL.MODBUS_TCP then
|
||||
if m_pkt.unit_id == self.unit_id then
|
||||
if m_pkt.unit_id == unit_id then
|
||||
local txn_type = self.transaction_controller.resolve(m_pkt.txn_id)
|
||||
local txn_tag = " (" .. util.strval(self.txn_tags[txn_type]) .. ")"
|
||||
local txn_tag = " (" .. util.strval(txn_tags[txn_type]) .. ")"
|
||||
|
||||
if bit.band(m_pkt.func_code, MODBUS_FCODE.ERROR_FLAG) ~= 0 then
|
||||
-- transaction incomplete or failed
|
||||
@ -135,26 +133,35 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t
|
||||
end
|
||||
|
||||
-- get the public interface
|
||||
---@nodiscard
|
||||
function protected.get() return public end
|
||||
|
||||
-- PUBLIC FUNCTIONS --
|
||||
|
||||
-- get the unit ID
|
||||
---@nodiscard
|
||||
function public.get_session_id() return session_id end
|
||||
-- get the unit ID
|
||||
function public.get_unit_id() return self.unit_id end
|
||||
---@nodiscard
|
||||
function public.get_unit_id() return unit_id end
|
||||
-- get the device index
|
||||
---@nodiscard
|
||||
function public.get_device_idx() return self.device_index end
|
||||
-- get the reactor ID
|
||||
---@nodiscard
|
||||
function public.get_reactor() return self.reactor end
|
||||
-- get the command queue
|
||||
---@nodiscard
|
||||
function public.get_cmd_queue() return protected.in_q end
|
||||
|
||||
-- close this unit
|
||||
---@nodiscard
|
||||
function public.close() self.connected = false end
|
||||
-- check if this unit is connected
|
||||
---@nodiscard
|
||||
function public.is_connected() return self.connected end
|
||||
-- check if this unit is faulted
|
||||
---@nodiscard
|
||||
function public.is_faulted() return self.device_fail end
|
||||
|
||||
-- PUBLIC TEMPLATE FUNCTIONS --
|
||||
@ -179,6 +186,7 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t
|
||||
end
|
||||
|
||||
-- get the unit session database
|
||||
---@nodiscard
|
||||
function public.get_db()
|
||||
log.debug("template unit_session.get_db() called", true)
|
||||
return {}
|
||||
|
@ -183,9 +183,10 @@ local function _free_closed(sessions)
|
||||
end
|
||||
|
||||
-- find a session by remote port
|
||||
---@nodiscard
|
||||
---@param list table
|
||||
---@param port integer
|
||||
---@return plc_session_struct|rtu_session_struct|nil
|
||||
---@return plc_session_struct|rtu_session_struct|coord_session_struct|nil
|
||||
local function _find_session(list, port)
|
||||
for i = 1, #list do
|
||||
if list[i].r_port == port then return list[i] end
|
||||
@ -212,54 +213,63 @@ function svsessions.relink_modem(modem)
|
||||
end
|
||||
|
||||
-- find an RTU session by the remote port
|
||||
---@nodiscard
|
||||
---@param remote_port integer
|
||||
---@return rtu_session_struct|nil
|
||||
function svsessions.find_rtu_session(remote_port)
|
||||
-- check RTU sessions
|
||||
---@diagnostic disable-next-line: return-type-mismatch
|
||||
return _find_session(self.rtu_sessions, remote_port)
|
||||
local session = _find_session(self.rtu_sessions, remote_port)
|
||||
---@cast session rtu_session_struct
|
||||
return session
|
||||
end
|
||||
|
||||
-- find a PLC session by the remote port
|
||||
---@nodiscard
|
||||
---@param remote_port integer
|
||||
---@return plc_session_struct|nil
|
||||
function svsessions.find_plc_session(remote_port)
|
||||
-- check PLC sessions
|
||||
---@diagnostic disable-next-line: return-type-mismatch
|
||||
return _find_session(self.plc_sessions, remote_port)
|
||||
local session = _find_session(self.plc_sessions, remote_port)
|
||||
---@cast session plc_session_struct
|
||||
return session
|
||||
end
|
||||
|
||||
-- find a PLC/RTU session by the remote port
|
||||
---@nodiscard
|
||||
---@param remote_port integer
|
||||
---@return plc_session_struct|rtu_session_struct|nil
|
||||
function svsessions.find_device_session(remote_port)
|
||||
-- check RTU sessions
|
||||
local s = _find_session(self.rtu_sessions, remote_port)
|
||||
local session = _find_session(self.rtu_sessions, remote_port)
|
||||
|
||||
-- check PLC sessions
|
||||
if s == nil then s = _find_session(self.plc_sessions, remote_port) end
|
||||
if session == nil then session = _find_session(self.plc_sessions, remote_port) end
|
||||
---@cast session plc_session_struct|rtu_session_struct|nil
|
||||
|
||||
return s
|
||||
return session
|
||||
end
|
||||
|
||||
-- find a coordinator session by the remote port
|
||||
--
|
||||
-- find a coordinator session by the remote port<br>
|
||||
-- only one coordinator is allowed, but this is kept to be consistent with all other session tables
|
||||
---@nodiscard
|
||||
---@param remote_port integer
|
||||
---@return nil
|
||||
---@return coord_session_struct|nil
|
||||
function svsessions.find_coord_session(remote_port)
|
||||
-- check coordinator sessions
|
||||
---@diagnostic disable-next-line: return-type-mismatch
|
||||
return _find_session(self.coord_sessions, remote_port)
|
||||
local session = _find_session(self.coord_sessions, remote_port)
|
||||
---@cast session coord_session_struct
|
||||
return session
|
||||
end
|
||||
|
||||
-- get the a coordinator session if exists
|
||||
---@nodiscard
|
||||
---@return coord_session_struct|nil
|
||||
function svsessions.get_coord_session()
|
||||
return self.coord_sessions[1]
|
||||
end
|
||||
|
||||
-- get a session by reactor ID
|
||||
---@nodiscard
|
||||
---@param reactor integer
|
||||
---@return plc_session_struct|nil session
|
||||
function svsessions.get_reactor_session(reactor)
|
||||
@ -275,6 +285,7 @@ function svsessions.get_reactor_session(reactor)
|
||||
end
|
||||
|
||||
-- establish a new PLC session
|
||||
---@nodiscard
|
||||
---@param local_port integer
|
||||
---@param remote_port integer
|
||||
---@param for_reactor integer
|
||||
@ -314,6 +325,7 @@ function svsessions.establish_plc_session(local_port, remote_port, for_reactor,
|
||||
end
|
||||
|
||||
-- establish a new RTU session
|
||||
---@nodiscard
|
||||
---@param local_port integer
|
||||
---@param remote_port integer
|
||||
---@param advertisement table
|
||||
@ -344,6 +356,7 @@ function svsessions.establish_rtu_session(local_port, remote_port, advertisement
|
||||
end
|
||||
|
||||
-- establish a new coordinator session
|
||||
---@nodiscard
|
||||
---@param local_port integer
|
||||
---@param remote_port integer
|
||||
---@param version string
|
||||
|
@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions")
|
||||
local config = require("supervisor.config")
|
||||
local supervisor = require("supervisor.supervisor")
|
||||
|
||||
local SUPERVISOR_VERSION = "beta-v0.12.2"
|
||||
local SUPERVISOR_VERSION = "v0.13.0"
|
||||
|
||||
local print = util.print
|
||||
local println = util.println
|
||||
@ -81,7 +81,7 @@ local function main()
|
||||
|
||||
local modem = ppm.get_wireless_modem()
|
||||
if modem == nil then
|
||||
println("boot> wireless modem not found")
|
||||
println("startup> wireless modem not found")
|
||||
log.fatal("no wireless modem on startup")
|
||||
return
|
||||
end
|
||||
@ -110,7 +110,7 @@ local function main()
|
||||
-- we only care if this is our wireless modem
|
||||
if device == modem then
|
||||
println_ts("wireless modem disconnected!")
|
||||
log.error("comms modem disconnected!")
|
||||
log.warning("comms modem disconnected")
|
||||
else
|
||||
log.warning("non-comms modem disconnected")
|
||||
end
|
||||
@ -127,9 +127,9 @@ local function main()
|
||||
superv_comms.reconnect_modem(modem)
|
||||
|
||||
println_ts("wireless modem reconnected.")
|
||||
log.info("comms modem reconnected.")
|
||||
log.info("comms modem reconnected")
|
||||
else
|
||||
log.info("wired modem reconnected.")
|
||||
log.info("wired modem reconnected")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -17,6 +17,7 @@ local print_ts = util.print_ts
|
||||
local println_ts = util.println_ts
|
||||
|
||||
-- supervisory controller communications
|
||||
---@nodiscard
|
||||
---@param version string supervisor version
|
||||
---@param num_reactors integer number of reactors
|
||||
---@param cooling_conf table cooling configuration table
|
||||
@ -26,32 +27,24 @@ local println_ts = util.println_ts
|
||||
---@param range integer trusted device connection range
|
||||
function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen, coord_listen, range)
|
||||
local self = {
|
||||
version = version,
|
||||
num_reactors = num_reactors,
|
||||
modem = modem,
|
||||
dev_listen = dev_listen,
|
||||
coord_listen = coord_listen,
|
||||
reactor_struct_cache = nil
|
||||
last_est_acks = {}
|
||||
}
|
||||
|
||||
---@class superv_comms
|
||||
local public = {}
|
||||
|
||||
comms.set_trusted_range(range)
|
||||
|
||||
-- PRIVATE FUNCTIONS --
|
||||
|
||||
-- configure modem channels
|
||||
local function _conf_channels()
|
||||
self.modem.closeAll()
|
||||
self.modem.open(self.dev_listen)
|
||||
self.modem.open(self.coord_listen)
|
||||
modem.closeAll()
|
||||
modem.open(dev_listen)
|
||||
modem.open(coord_listen)
|
||||
end
|
||||
|
||||
_conf_channels()
|
||||
|
||||
-- link modem to svsessions
|
||||
svsessions.init(self.modem, num_reactors, cooling_conf)
|
||||
svsessions.init(modem, num_reactors, cooling_conf)
|
||||
|
||||
-- send an establish request response to a PLC/RTU
|
||||
---@param dest integer
|
||||
@ -63,7 +56,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen
|
||||
m_pkt.make(SCADA_MGMT_TYPE.ESTABLISH, msg)
|
||||
s_pkt.make(seq_id, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
self.modem.transmit(dest, self.dev_listen, s_pkt.raw_sendable())
|
||||
modem.transmit(dest, dev_listen, s_pkt.raw_sendable())
|
||||
end
|
||||
|
||||
-- send coordinator connection establish response
|
||||
@ -77,21 +70,24 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen
|
||||
c_pkt.make(SCADA_MGMT_TYPE.ESTABLISH, msg)
|
||||
s_pkt.make(seq_id, PROTOCOL.SCADA_MGMT, c_pkt.raw_sendable())
|
||||
|
||||
self.modem.transmit(dest, self.coord_listen, s_pkt.raw_sendable())
|
||||
modem.transmit(dest, coord_listen, s_pkt.raw_sendable())
|
||||
end
|
||||
|
||||
-- PUBLIC FUNCTIONS --
|
||||
|
||||
---@class superv_comms
|
||||
local public = {}
|
||||
|
||||
-- reconnect a newly connected modem
|
||||
---@param modem table
|
||||
---@diagnostic disable-next-line: redefined-local
|
||||
function public.reconnect_modem(modem)
|
||||
self.modem = modem
|
||||
svsessions.relink_modem(self.modem)
|
||||
---@param new_modem table
|
||||
function public.reconnect_modem(new_modem)
|
||||
modem = new_modem
|
||||
svsessions.relink_modem(new_modem)
|
||||
_conf_channels()
|
||||
end
|
||||
|
||||
-- parse a packet
|
||||
---@nodiscard
|
||||
---@param side string
|
||||
---@param sender integer
|
||||
---@param reply_to integer
|
||||
@ -147,8 +143,9 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen
|
||||
local protocol = packet.scada_frame.protocol()
|
||||
|
||||
-- device (RTU/PLC) listening channel
|
||||
if l_port == self.dev_listen then
|
||||
if l_port == dev_listen then
|
||||
if protocol == PROTOCOL.MODBUS_TCP then
|
||||
---@cast packet modbus_frame
|
||||
-- look for an associated session
|
||||
local session = svsessions.find_rtu_session(r_port)
|
||||
|
||||
@ -161,6 +158,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen
|
||||
log.debug("discarding MODBUS_TCP packet without a known session")
|
||||
end
|
||||
elseif protocol == PROTOCOL.RPLC then
|
||||
---@cast packet rplc_frame
|
||||
-- look for an associated session
|
||||
local session = svsessions.find_plc_session(r_port)
|
||||
|
||||
@ -174,6 +172,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen
|
||||
_send_dev_establish(packet.scada_frame.seq_num() + 1, r_port, { ESTABLISH_ACK.DENY })
|
||||
end
|
||||
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||
---@cast packet mgmt_frame
|
||||
-- look for an associated session
|
||||
local session = svsessions.find_device_session(r_port)
|
||||
|
||||
@ -192,13 +191,13 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen
|
||||
local dev_type = packet.data[3]
|
||||
|
||||
if comms_v ~= comms.version then
|
||||
log.debug(util.c("dropping establish packet with incorrect comms version v", comms_v,
|
||||
" (expected v", comms.version, ")"))
|
||||
_send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.BAD_VERSION })
|
||||
return
|
||||
end
|
||||
if self.last_est_acks[r_port] ~= ESTABLISH_ACK.BAD_VERSION then
|
||||
log.info(util.c("dropping device establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
|
||||
self.last_est_acks[r_port] = ESTABLISH_ACK.BAD_VERSION
|
||||
end
|
||||
|
||||
if dev_type == DEVICE_TYPE.PLC then
|
||||
_send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.BAD_VERSION })
|
||||
elseif dev_type == DEVICE_TYPE.PLC then
|
||||
-- PLC linking request
|
||||
if packet.length == 4 and type(packet.data[4]) == "number" then
|
||||
local reactor_id = packet.data[4]
|
||||
@ -206,13 +205,19 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen
|
||||
|
||||
if plc_id == false then
|
||||
-- reactor already has a PLC assigned
|
||||
log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id))
|
||||
if self.last_est_acks[r_port] ~= ESTABLISH_ACK.COLLISION then
|
||||
log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id))
|
||||
self.last_est_acks[r_port] = ESTABLISH_ACK.COLLISION
|
||||
end
|
||||
|
||||
_send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.COLLISION })
|
||||
else
|
||||
-- got an ID; assigned to a reactor successfully
|
||||
println(util.c("PLC (", firmware_v, ") [:", r_port, "] \xbb reactor ", reactor_id, " connected"))
|
||||
log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [:", r_port, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id))
|
||||
|
||||
_send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW })
|
||||
self.last_est_acks[r_port] = ESTABLISH_ACK.ALLOW
|
||||
end
|
||||
else
|
||||
log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type")
|
||||
@ -226,6 +231,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen
|
||||
|
||||
println(util.c("RTU (", firmware_v, ") [:", r_port, "] \xbb connected"))
|
||||
log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [:", r_port, "] connected with session ID ", s_id))
|
||||
|
||||
_send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW })
|
||||
else
|
||||
log.debug("RTU_ESTABLISH: packet length mismatch")
|
||||
@ -247,11 +253,12 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen
|
||||
log.debug("illegal packet type " .. protocol .. " on device listening channel")
|
||||
end
|
||||
-- coordinator listening channel
|
||||
elseif l_port == self.coord_listen then
|
||||
elseif l_port == coord_listen then
|
||||
-- look for an associated session
|
||||
local session = svsessions.find_coord_session(r_port)
|
||||
|
||||
if protocol == PROTOCOL.SCADA_MGMT then
|
||||
---@cast packet mgmt_frame
|
||||
-- SCADA management packet
|
||||
if session ~= nil then
|
||||
-- pass the packet onto the session handler
|
||||
@ -267,32 +274,39 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen
|
||||
local dev_type = packet.data[3]
|
||||
|
||||
if comms_v ~= comms.version then
|
||||
log.debug(util.c("dropping establish packet with incorrect comms version v", comms_v,
|
||||
" (expected v", comms.version, ")"))
|
||||
if self.last_est_acks[r_port] ~= ESTABLISH_ACK.BAD_VERSION then
|
||||
log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
|
||||
self.last_est_acks[r_port] = ESTABLISH_ACK.BAD_VERSION
|
||||
end
|
||||
|
||||
_send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.BAD_VERSION })
|
||||
return
|
||||
elseif dev_type ~= DEVICE_TYPE.CRDN then
|
||||
log.debug(util.c("illegal establish packet for device ", dev_type, " on CRDN listening channel"))
|
||||
_send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY })
|
||||
return
|
||||
end
|
||||
|
||||
-- this is an attempt to establish a new session
|
||||
local s_id = svsessions.establish_coord_session(l_port, r_port, firmware_v)
|
||||
|
||||
if s_id ~= false then
|
||||
local config = { self.num_reactors }
|
||||
for i = 1, #cooling_conf do
|
||||
table.insert(config, cooling_conf[i].BOILERS)
|
||||
table.insert(config, cooling_conf[i].TURBINES)
|
||||
end
|
||||
|
||||
println(util.c("CRD (",firmware_v, ") [:", r_port, "] \xbb connected"))
|
||||
log.info(util.c("CRDN_ESTABLISH: coordinator (",firmware_v, ") [:", r_port, "] connected with session ID ", s_id))
|
||||
_send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW, config })
|
||||
else
|
||||
log.debug("CRDN_ESTABLISH: denied new coordinator due to already being connected to another coordinator")
|
||||
_send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.COLLISION })
|
||||
-- this is an attempt to establish a new session
|
||||
local s_id = svsessions.establish_coord_session(l_port, r_port, firmware_v)
|
||||
|
||||
if s_id ~= false then
|
||||
local config = { num_reactors }
|
||||
for i = 1, #cooling_conf do
|
||||
table.insert(config, cooling_conf[i].BOILERS)
|
||||
table.insert(config, cooling_conf[i].TURBINES)
|
||||
end
|
||||
|
||||
println(util.c("CRD (",firmware_v, ") [:", r_port, "] \xbb connected"))
|
||||
log.info(util.c("CRDN_ESTABLISH: coordinator (",firmware_v, ") [:", r_port, "] connected with session ID ", s_id))
|
||||
|
||||
_send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW, config })
|
||||
self.last_est_acks[r_port] = ESTABLISH_ACK.ALLOW
|
||||
else
|
||||
if self.last_est_acks[r_port] ~= ESTABLISH_ACK.COLLISION then
|
||||
log.info("CRDN_ESTABLISH: denied new coordinator due to already being connected to another coordinator")
|
||||
self.last_est_acks[r_port] = ESTABLISH_ACK.COLLISION
|
||||
end
|
||||
|
||||
_send_crdn_establish(next_seq_id, r_port, { ESTABLISH_ACK.COLLISION })
|
||||
end
|
||||
end
|
||||
else
|
||||
log.debug("CRDN_ESTABLISH: establish packet length mismatch")
|
||||
@ -303,6 +317,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen
|
||||
log.debug(r_port .. "->" .. l_port .. ": discarding SCADA_MGMT packet without a known session")
|
||||
end
|
||||
elseif protocol == PROTOCOL.SCADA_CRDN then
|
||||
---@cast packet crdn_frame
|
||||
-- coordinator packet
|
||||
if session ~= nil then
|
||||
-- pass the packet onto the session handler
|
||||
|
@ -11,15 +11,14 @@ local rsctl = require("supervisor.session.rsctl")
|
||||
---@class reactor_control_unit
|
||||
local unit = {}
|
||||
|
||||
local WASTE_MODE = types.WASTE_MODE
|
||||
|
||||
local ALARM = types.ALARM
|
||||
local PRIO = types.ALARM_PRIORITY
|
||||
local ALARM_STATE = types.ALARM_STATE
|
||||
|
||||
local TRI_FAIL = types.TRI_FAIL
|
||||
local WASTE_MODE = types.WASTE_MODE
|
||||
local DUMPING_MODE = types.DUMPING_MODE
|
||||
|
||||
local ALARM = types.ALARM
|
||||
local PRIO = types.ALARM_PRIORITY
|
||||
local ALARM_STATE = types.ALARM_STATE
|
||||
local TRI_FAIL = types.TRI_FAIL
|
||||
|
||||
local PLC_S_CMDS = plc.PLC_S_CMDS
|
||||
|
||||
local IO = rsio.IO
|
||||
@ -61,13 +60,14 @@ unit.FLOW_STABILITY_DELAY_MS = FLOW_STABILITY_DELAY_MS
|
||||
---@field tier integer alarm urgency tier (0 = highest)
|
||||
|
||||
-- create a new reactor unit
|
||||
---@param for_reactor integer reactor unit number
|
||||
---@nodiscard
|
||||
---@param reactor_id integer reactor unit number
|
||||
---@param num_boilers integer number of boilers expected
|
||||
---@param num_turbines integer number of turbines expected
|
||||
function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
function unit.new(reactor_id, num_boilers, num_turbines)
|
||||
---@class _unit_self
|
||||
local self = {
|
||||
r_id = for_reactor,
|
||||
r_id = reactor_id,
|
||||
plc_s = nil, ---@class plc_session_struct
|
||||
plc_i = nil, ---@class plc_session
|
||||
num_boilers = num_boilers,
|
||||
@ -278,6 +278,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
local function _reset_dt(key) self.deltas[key] = nil end
|
||||
|
||||
-- get the delta t of a value
|
||||
---@nodiscard
|
||||
---@param key string value key
|
||||
---@return number value value or 0 if not known
|
||||
function self._get_dt(key) if self.deltas[key] then return self.deltas[key].dt else return 0.0 end end
|
||||
@ -326,7 +327,6 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
--#region redstone I/O
|
||||
|
||||
local __rs_w = self.io_ctl.digital_write
|
||||
local __rs_r = self.io_ctl.digital_read
|
||||
|
||||
-- valves
|
||||
local waste_pu = { open = function () __rs_w(IO.WASTE_PU, true) end, close = function () __rs_w(IO.WASTE_PU, false) end }
|
||||
@ -525,9 +525,9 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
end
|
||||
end
|
||||
|
||||
-- get the actual limit of this unit
|
||||
--
|
||||
-- get the actual limit of this unit<br>
|
||||
-- if it is degraded or not ready, the limit will be 0
|
||||
---@nodiscard
|
||||
---@return integer lim_br100
|
||||
function public.a_get_effective_limit()
|
||||
if not self.db.control.ready or self.db.control.degraded or self.plc_cache.rps_trip then
|
||||
@ -551,6 +551,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
end
|
||||
|
||||
-- check if ramping is complete (burn rate is same as target)
|
||||
---@nodiscard
|
||||
---@return boolean complete
|
||||
function public.a_ramp_complete()
|
||||
if self.plc_i ~= nil then
|
||||
@ -610,7 +611,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
-- acknowledge an alarm (if possible)
|
||||
---@param id ALARM alarm ID
|
||||
function public.ack_alarm(id)
|
||||
if (type(id) == "number") and (self.db.alarm_states[id] == ALARM_STATE.TRIPPED) then
|
||||
if type(id) == "number" and self.db.alarm_states[id] == ALARM_STATE.TRIPPED then
|
||||
self.db.alarm_states[id] = ALARM_STATE.ACKED
|
||||
end
|
||||
end
|
||||
@ -618,7 +619,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
-- reset an alarm (if possible)
|
||||
---@param id ALARM alarm ID
|
||||
function public.reset_alarm(id)
|
||||
if (type(id) == "number") and (self.db.alarm_states[id] == ALARM_STATE.RING_BACK) then
|
||||
if type(id) == "number" and self.db.alarm_states[id] == ALARM_STATE.RING_BACK then
|
||||
self.db.alarm_states[id] = ALARM_STATE.INACTIVE
|
||||
end
|
||||
end
|
||||
@ -675,6 +676,8 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
--#region
|
||||
|
||||
-- check if a critical alarm is tripped
|
||||
---@nodiscard
|
||||
---@return boolean tripped
|
||||
function public.has_critical_alarm()
|
||||
for _, alarm in pairs(self.alarms) do
|
||||
if alarm.tier == PRIO.CRITICAL and (alarm.state == AISTATE.TRIPPED or alarm.state == AISTATE.ACKED) then
|
||||
@ -686,6 +689,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
end
|
||||
|
||||
-- get build properties of all machines
|
||||
---@nodiscard
|
||||
---@param inc_plc boolean? true/nil to include PLC build, false to exclude
|
||||
---@param inc_boilers boolean? true/nil to include boiler builds, false to exclude
|
||||
---@param inc_turbines boolean? true/nil to include turbine builds, false to exclude
|
||||
@ -718,6 +722,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
end
|
||||
|
||||
-- get reactor status
|
||||
---@nodiscard
|
||||
function public.get_reactor_status()
|
||||
local status = {}
|
||||
if self.plc_i ~= nil then
|
||||
@ -728,6 +733,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
end
|
||||
|
||||
-- get RTU statuses
|
||||
---@nodiscard
|
||||
function public.get_rtu_statuses()
|
||||
local status = {}
|
||||
|
||||
@ -769,20 +775,25 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
end
|
||||
|
||||
-- get the annunciator status
|
||||
---@nodiscard
|
||||
function public.get_annunciator() return self.db.annunciator end
|
||||
|
||||
-- get the alarm states
|
||||
---@nodiscard
|
||||
function public.get_alarms() return self.db.alarm_states end
|
||||
|
||||
-- get information required for automatic reactor control
|
||||
---@nodiscard
|
||||
function public.get_control_inf() return self.db.control end
|
||||
|
||||
-- get unit state
|
||||
---@nodiscard
|
||||
function public.get_state()
|
||||
return { self.status_text[1], self.status_text[2], self.waste_mode, self.db.control.ready, self.db.control.degraded }
|
||||
end
|
||||
|
||||
-- get the reactor ID
|
||||
---@nodiscard
|
||||
function public.get_id() return self.r_id end
|
||||
|
||||
--#endregion
|
||||
|
@ -5,12 +5,12 @@ local util = require("scada-common.util")
|
||||
|
||||
local plc = require("supervisor.session.plc")
|
||||
|
||||
local PRIO = types.ALARM_PRIORITY
|
||||
local ALARM_STATE = types.ALARM_STATE
|
||||
|
||||
local TRI_FAIL = types.TRI_FAIL
|
||||
local DUMPING_MODE = types.DUMPING_MODE
|
||||
|
||||
local PRIO = types.ALARM_PRIORITY
|
||||
local ALARM_STATE = types.ALARM_STATE
|
||||
|
||||
local IO = rsio.IO
|
||||
|
||||
local PLC_S_CMDS = plc.PLC_S_CMDS
|
||||
@ -555,6 +555,7 @@ function logic.update_status_text(self)
|
||||
local AISTATE = self.types.AISTATE
|
||||
|
||||
-- check if an alarm is active (tripped or ack'd)
|
||||
---@nodiscard
|
||||
---@param alarm table alarm entry
|
||||
---@return boolean active
|
||||
local function is_active(alarm)
|
||||
@ -670,7 +671,7 @@ function logic.update_status_text(self)
|
||||
end
|
||||
end
|
||||
else
|
||||
self.status_text = { "Reactor Off-line", "awaiting connection..." }
|
||||
self.status_text = { "REACTOR OFF-LINE", "awaiting connection..." }
|
||||
end
|
||||
end
|
||||
|
||||
@ -680,6 +681,7 @@ function logic.handle_redstone(self)
|
||||
local AISTATE = self.types.AISTATE
|
||||
|
||||
-- check if an alarm is active (tripped or ack'd)
|
||||
---@nodiscard
|
||||
---@param alarm table alarm entry
|
||||
---@return boolean active
|
||||
local function is_active(alarm)
|
||||
|
Loading…
Reference in New Issue
Block a user