#95 added boiler/turbine RTUs to supervisor, tons of RTU/MODBUS related bugfixes, adjusted annunciator conditions

This commit is contained in:
Mikayla Fischler 2022-09-18 22:25:59 -04:00
parent 88c34d8bca
commit d0d20b1299
15 changed files with 114 additions and 49 deletions

View File

@ -60,6 +60,7 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units)
id = id,
in_q = in_queue,
out_q = out_queue,
modbus_q = mqueue.new(),
f_units = facility_units,
advert = advertisement,
-- connection properties
@ -107,6 +108,8 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units)
rsio = self.advert[i][4]
}
local target_unit = self.f_units[unit_advert.reactor] ---@type reactor_unit
local u_type = unit_advert.type
-- validate unit advertisement
@ -133,35 +136,39 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units)
-- validation fail
elseif u_type == RTU_UNIT_TYPES.REDSTONE then
-- redstone
unit, rs_in_q = svrs_redstone.new(self.id, i, unit_advert, self.out_q)
unit, rs_in_q = svrs_redstone.new(self.id, i, unit_advert, self.modbus_q)
elseif u_type == RTU_UNIT_TYPES.BOILER then
-- boiler
unit = svrs_boiler.new(self.id, i, unit_advert, self.out_q)
unit = svrs_boiler.new(self.id, i, unit_advert, self.modbus_q)
target_unit.add_boiler(unit)
elseif u_type == RTU_UNIT_TYPES.BOILER_VALVE then
-- boiler (Mekanism 10.1+)
unit = svrs_boilerv.new(self.id, 1, unit_advert, self.out_q)
unit = svrs_boilerv.new(self.id, i, unit_advert, self.modbus_q)
target_unit.add_boiler(unit)
elseif u_type == RTU_UNIT_TYPES.TURBINE then
-- turbine
unit = svrs_turbine.new(self.id, i, unit_advert, self.out_q)
unit = svrs_turbine.new(self.id, i, unit_advert, self.modbus_q)
target_unit.add_turbine(unit)
elseif u_type == RTU_UNIT_TYPES.TURBINE_VALVE then
-- turbine (Mekanism 10.1+)
unit, tbv_in_q = svrs_turbinev.new(self.id, i, unit_advert, self.out_q)
unit, tbv_in_q = svrs_turbinev.new(self.id, i, unit_advert, self.modbus_q)
target_unit.add_turbine(unit)
self.turbine_cmd_capable = true
elseif u_type == RTU_UNIT_TYPES.EMACHINE then
-- mekanism [energy] machine
unit = svrs_emachine.new(self.id, i, unit_advert, self.out_q)
unit = svrs_emachine.new(self.id, i, unit_advert, self.modbus_q)
elseif u_type == RTU_UNIT_TYPES.IMATRIX then
-- induction matrix
unit = svrs_imatrix.new(self.id, i, unit_advert, self.out_q)
unit = svrs_imatrix.new(self.id, i, unit_advert, self.modbus_q)
elseif u_type == RTU_UNIT_TYPES.SPS then
-- super-critical phase shifter
unit = svrs_sps.new(self.id, i, unit_advert, self.out_q)
unit = svrs_sps.new(self.id, i, unit_advert, self.modbus_q)
elseif u_type == RTU_UNIT_TYPES.SNA then
-- solar neutron activator
unit = svrs_sna.new(self.id, i, unit_advert, self.out_q)
unit = svrs_sna.new(self.id, i, unit_advert, self.modbus_q)
elseif u_type == RTU_UNIT_TYPES.ENV_DETECTOR then
-- environment detector
unit = svrs_envd.new(self.id, i, unit_advert, self.out_q)
unit = svrs_envd.new(self.id, i, unit_advert, self.modbus_q)
else
log.error(log_header .. "bad advertisement: encountered unsupported RTU type")
end
@ -213,6 +220,17 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units)
end
end
-- send a MODBUS packet
---@param m_pkt modbus_packet MODBUS packet
local function _send_modbus(m_pkt)
local s_pkt = comms.scada_packet()
s_pkt.make(self.seq_num, PROTOCOLS.MODBUS_TCP, m_pkt.raw_sendable())
self.out_q.push_packet(s_pkt)
self.seq_num = self.seq_num + 1
end
-- send a SCADA management packet
---@param msg_type SCADA_MGMT_TYPES
---@param msg table
@ -387,11 +405,29 @@ function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units)
end
self.periodics.last_update = util.time()
----------------------------------------------
-- pass MODBUS packets on to main out queue --
----------------------------------------------
for _ = 1, self.modbus_q.length() do
-- get the next message
local msg = self.modbus_q.pop()
if msg ~= nil then
if msg.qtype == mqueue.TYPE.PACKET then
_send_modbus(msg.message)
end
end
end
end
return self.connected
end
-- handle initial advertisement
_handle_advertisement()
return public
end

View File

@ -108,7 +108,7 @@ function boiler.new(session_id, unit_id, advert, out_queue)
-- handle a packet
---@param m_pkt modbus_frame
function public.handle_packet(m_pkt)
local txn_type = self.session.try_resolve(m_pkt.txn_id)
local txn_type = self.session.try_resolve(m_pkt)
if txn_type == false then
-- nothing to do
elseif txn_type == TXN_TYPES.BUILD then

View File

@ -1,6 +1,7 @@
local comms = require("scada-common.comms")
local log = require("scada-common.log")
local types = require("scada-common.types")
local util = require("scada-common.util")
local unit_session = require("supervisor.session.rtu.unit_session")
@ -60,8 +61,8 @@ function boilerv.new(session_id, unit_id, advert, out_queue)
length = 0,
width = 0,
height = 0,
min_pos = 0,
max_pos = 0,
min_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate
max_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate
boil_cap = 0.0,
steam_cap = 0,
water_cap = 0,
@ -76,16 +77,16 @@ function boilerv.new(session_id, unit_id, advert, out_queue)
boil_rate = 0.0
},
tanks = {
steam = {}, ---@type tank_fluid
steam = { type = "mekanism:empty_gas", amount = 0 }, ---@type tank_fluid
steam_need = 0,
steam_fill = 0.0,
water = {}, ---@type tank_fluid
water = { type = "mekanism:empty_gas", amount = 0 }, ---@type tank_fluid
water_need = 0,
water_fill = 0.0,
hcool = {}, ---@type tank_fluid
hcool = { type = "mekanism:empty_gas", amount = 0 }, ---@type tank_fluid
hcool_need = 0,
hcool_fill = 0.0,
ccool = {}, ---@type tank_fluid
ccool = { type = "mekanism:empty_gas", amount = 0 }, ---@type tank_fluid
ccool_need = 0,
ccool_fill = 0.0
}
@ -105,19 +106,19 @@ function boilerv.new(session_id, unit_id, advert, out_queue)
-- query the build of the device
local function _request_build()
-- read input registers 1 through 13 (start = 1, count = 13)
self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 7 })
self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 13 })
end
-- query the state of the device
local function _request_state()
-- read input registers 14 through 16 (start = 14, count = 2)
-- read input registers 14 through 15 (start = 14, count = 2)
self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 14, 2 })
end
-- query the tanks of the device
local function _request_tanks()
-- read input registers 17 through 29 (start = 17, count = 12)
self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 17, 12 })
-- read input registers 16 through 27 (start = 16, count = 12)
self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 16, 12 })
end
-- PUBLIC FUNCTIONS --
@ -125,7 +126,7 @@ function boilerv.new(session_id, unit_id, advert, out_queue)
-- handle a packet
---@param m_pkt modbus_frame
function public.handle_packet(m_pkt)
local txn_type = self.session.try_resolve(m_pkt.txn_id)
local txn_type = self.session.try_resolve(m_pkt)
if txn_type == false then
-- nothing to do
elseif txn_type == TXN_TYPES.FORMED then

View File

@ -79,7 +79,7 @@ function emachine.new(session_id, unit_id, advert, out_queue)
-- handle a packet
---@param m_pkt modbus_frame
function public.handle_packet(m_pkt)
local txn_type = self.session.try_resolve(m_pkt.txn_id)
local txn_type = self.session.try_resolve(m_pkt)
if txn_type == false then
-- nothing to do
elseif txn_type == TXN_TYPES.BUILD then

View File

@ -62,7 +62,7 @@ function envd.new(session_id, unit_id, advert, out_queue)
-- handle a packet
---@param m_pkt modbus_frame
function public.handle_packet(m_pkt)
local txn_type = self.session.try_resolve(m_pkt.txn_id)
local txn_type = self.session.try_resolve(m_pkt)
if txn_type == false then
-- nothing to do
elseif txn_type == TXN_TYPES.RAD then

View File

@ -112,7 +112,7 @@ function imatrix.new(session_id, unit_id, advert, out_queue)
-- handle a packet
---@param m_pkt modbus_frame
function public.handle_packet(m_pkt)
local txn_type = self.session.try_resolve(m_pkt.txn_id)
local txn_type = self.session.try_resolve(m_pkt)
if txn_type == false then
-- nothing to do
elseif txn_type == TXN_TYPES.FORMED then

View File

@ -144,7 +144,7 @@ function redstone.new(session_id, unit_id, advert, out_queue)
-- handle a packet
---@param m_pkt modbus_frame
function public.handle_packet(m_pkt)
local txn_type = self.session.try_resolve(m_pkt.txn_id)
local txn_type = self.session.try_resolve(m_pkt)
if txn_type == false then
-- nothing to do
elseif txn_type == TXN_TYPES.DI_READ then

View File

@ -97,7 +97,7 @@ function sna.new(session_id, unit_id, advert, out_queue)
-- handle a packet
---@param m_pkt modbus_frame
function public.handle_packet(m_pkt)
local txn_type = self.session.try_resolve(m_pkt.txn_id)
local txn_type = self.session.try_resolve(m_pkt)
if txn_type == false then
-- nothing to do
elseif txn_type == TXN_TYPES.BUILD then

View File

@ -117,7 +117,7 @@ function sps.new(session_id, unit_id, advert, out_queue)
-- handle a packet
---@param m_pkt modbus_frame
function public.handle_packet(m_pkt)
local txn_type = self.session.try_resolve(m_pkt.txn_id)
local txn_type = self.session.try_resolve(m_pkt)
if txn_type == false then
-- nothing to do
elseif txn_type == TXN_TYPES.FORMED then

View File

@ -104,7 +104,7 @@ function turbine.new(session_id, unit_id, advert, out_queue)
-- handle a packet
---@param m_pkt modbus_frame
function public.handle_packet(m_pkt)
local txn_type = self.session.try_resolve(m_pkt.txn_id)
local txn_type = self.session.try_resolve(m_pkt)
if txn_type == false then
-- nothing to do
elseif txn_type == TXN_TYPES.BUILD then

View File

@ -70,6 +70,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue)
in_q = mqueue.new(),
has_build = false,
periodics = {
next_formed_req = 0,
next_build_req = 0,
next_state_req = 0,
next_tanks_req = 0
@ -81,8 +82,8 @@ function turbinev.new(session_id, unit_id, advert, out_queue)
length = 0,
width = 0,
height = 0,
min_pos = 0,
max_pos = 0,
min_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate
max_pos = { x = 0, y = 0, z = 0 }, ---@type coordinate
blades = 0,
coils = 0,
vents = 0,
@ -101,7 +102,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue)
dumping_mode = DUMPING_MODE.IDLE ---@type DUMPING_MODE
},
tanks = {
steam = 0,
steam = { type = "mekanism:empty_gas", amount = 0 }, ---@type tank_fluid
steam_need = 0,
steam_fill = 0.0,
energy = 0,
@ -163,9 +164,17 @@ function turbinev.new(session_id, unit_id, advert, out_queue)
-- handle a packet
---@param m_pkt modbus_frame
function public.handle_packet(m_pkt)
local txn_type = self.session.try_resolve(m_pkt.txn_id)
local txn_type = self.session.try_resolve(m_pkt)
if txn_type == false then
-- nothing to do
elseif txn_type == TXN_TYPES.FORMED then
-- formed response
-- load in data if correct length
if m_pkt.length == 1 then
self.db.formed = m_pkt.data[1]
else
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
end
elseif txn_type == TXN_TYPES.BUILD then
-- build response
if m_pkt.length == 15 then

View File

@ -19,6 +19,7 @@ function txnctrl.new()
local public = {}
local insert = table.insert
local remove = table.remove
-- get the length of the transaction list
function public.length()
@ -55,8 +56,9 @@ function txnctrl.new()
for i = 1, public.length() do
if self.list[i].txn_id == txn_id then
txn_type = self.list[i].txn_type
self.list[i] = nil
local entry = remove(self.list, i)
txn_type = entry.txn_type
break
end
end

View File

@ -1,6 +1,7 @@
local comms = require("scada-common.comms")
local log = require("scada-common.log")
local types = require("scada-common.types")
local util = require("scada-common.util")
local txnctrl = require("supervisor.session.rtu.txnctrl")
@ -57,7 +58,7 @@ function unit_session.new(unit_id, advert, out_queue, log_tag, txn_tags)
if m_pkt.scada_frame.protocol() == PROTOCOLS.MODBUS_TCP then
if m_pkt.unit_id == self.unit_id then
local txn_type = self.transaction_controller.resolve(m_pkt.txn_id)
local txn_tag = " (" .. self.txn_tags[txn_type] .. ")"
local txn_tag = " (" .. util.strval(self.txn_tags[txn_type]) .. ")"
if bit.band(m_pkt.func_code, MODBUS_FCODE.ERROR_FLAG) ~= 0 then
-- transaction incomplete or failed

View File

@ -17,7 +17,8 @@ local DT_KEYS = {
BoilerSteam = "BST",
BoilerCCool = "BCC",
BoilerHCool = "BHC",
TurbineSteam = "TST"
TurbineSteam = "TST",
TurbinePower = "TPR"
}
-- create a new reactor unit
@ -135,22 +136,21 @@ function unit.new(for_reactor, num_boilers, num_turbines)
for i = 1, #self.boilers do
local boiler = self.boilers[i] ---@type unit_session
local db = boiler.get_db() ---@type boiler_session_db
local db = boiler.get_db() ---@type boilerv_session_db
---@todo Mekanism 10.1+ will change water/steam to need .amount
_compute_dt(DT_KEYS.BoilerWater .. boiler.get_device_idx(), db.tanks.water)
_compute_dt(DT_KEYS.BoilerSteam .. boiler.get_device_idx(), db.tanks.steam)
_compute_dt(DT_KEYS.BoilerWater .. boiler.get_device_idx(), db.tanks.water.amount)
_compute_dt(DT_KEYS.BoilerSteam .. boiler.get_device_idx(), db.tanks.steam.amount)
_compute_dt(DT_KEYS.BoilerCCool .. boiler.get_device_idx(), db.tanks.ccool.amount)
_compute_dt(DT_KEYS.BoilerHCool .. boiler.get_device_idx(), db.tanks.hcool.amount)
end
for i = 1, #self.turbines do
local turbine = self.turbines[i] ---@type unit_session
local db = turbine.get_db() ---@type turbine_session_db
local db = turbine.get_db() ---@type turbinev_session_db
_compute_dt(DT_KEYS.TurbineSteam .. turbine.get_device_idx(), db.tanks.steam)
---@todo Mekanism 10.1+ needed
-- _compute_dt(DT_KEYS.TurbinePower .. turbine.get_device_idx(), db.?)
_compute_dt(DT_KEYS.TurbineSteam .. turbine.get_device_idx(), db.tanks.steam.amount)
---@todo unused currently?
_compute_dt(DT_KEYS.TurbinePower .. turbine.get_device_idx(), db.tanks.energy)
end
end
@ -242,10 +242,12 @@ function unit.new(for_reactor, num_boilers, num_turbines)
local idx = boiler.get_device_idx()
local db = boiler.get_db() ---@type boiler_session_db
local gaining_hc = _get_dt(DT_KEYS.BoilerHCool .. idx) > 0 or db.tanks.hcool_fill == 1
-- gaining heated coolant
cfmismatch = cfmismatch or _get_dt(DT_KEYS.BoilerHCool .. idx) > 0 or db.tanks.hcool_fill == 1
cfmismatch = cfmismatch or gaining_hc
-- losing cooled coolant
cfmismatch = cfmismatch or _get_dt(DT_KEYS.BoilerCCool .. idx) < 0 or db.tanks.ccool_fill == 0
cfmismatch = cfmismatch or _get_dt(DT_KEYS.BoilerCCool .. idx) < 0 or (gaining_hc and db.tanks.ccool_fill == 0)
end
self.db.annunciator.CoolantFeedMismatch = cfmismatch
@ -264,7 +266,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
-- go through turbines for stats and online
for i = 1, #self.turbines do
local session = self.turbine[i] ---@type unit_session
local session = self.turbines[i] ---@type unit_session
local turbine = session.get_db() ---@type turbine_session_db
total_flow_rate = total_flow_rate + turbine.state.flow_rate
@ -329,6 +331,8 @@ function unit.new(for_reactor, num_boilers, num_turbines)
-- PUBLIC FUNCTIONS --
-- ADD/LINK DEVICES --
-- link the PLC
---@param plc_session plc_session_struct
function public.link_plc_session(plc_session)
@ -388,6 +392,8 @@ function unit.new(for_reactor, num_boilers, num_turbines)
table.insert(self.redstone[field], accessor)
end
-- UPDATE SESSION --
-- update (iterate) this unit
function public.update()
-- unlink PLC if session was closed
@ -403,6 +409,16 @@ function unit.new(for_reactor, num_boilers, num_turbines)
_update_annunciator()
end
-- COMMAND UNIT --
-- SCRAM reactor
function public.scram()
if self.plc_s ~= nil then
end
end
-- READ STATES/PROPERTIES --
-- get build properties of all machines
function public.get_build()
local build = {}

View File

@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions")
local config = require("supervisor.config")
local supervisor = require("supervisor.supervisor")
local SUPERVISOR_VERSION = "beta-v0.5.10"
local SUPERVISOR_VERSION = "beta-v0.5.11"
local print = util.print
local println = util.println