From eadf5c488a7124c9ac5c8543374a6e6f33111720 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 28 Aug 2022 12:12:30 -0400 Subject: [PATCH] #86 improvements to supervisor units, code cleanup --- supervisor/session/coordinator.lua | 10 ++- supervisor/session/rtu.lua | 19 ++++-- supervisor/session/rtu/boiler.lua | 6 +- supervisor/session/rtu/boilerv.lua | 6 +- supervisor/session/rtu/emachine.lua | 6 +- supervisor/session/rtu/envd.lua | 6 +- supervisor/session/rtu/imatrix.lua | 6 +- supervisor/session/rtu/redstone.lua | 12 ++-- supervisor/session/rtu/sna.lua | 6 +- supervisor/session/rtu/sps.lua | 6 +- supervisor/session/rtu/turbine.lua | 6 +- supervisor/session/rtu/turbinev.lua | 10 +-- supervisor/session/rtu/unit_session.lua | 6 +- supervisor/session/svsessions.lua | 39 +++++++++-- supervisor/{ => session}/unit.lua | 90 ++++++++++++------------- supervisor/startup.lua | 8 +-- supervisor/supervisor.lua | 10 +-- 17 files changed, 144 insertions(+), 108 deletions(-) rename supervisor/{ => session}/unit.lua (85%) diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 3ffa89f..95507d2 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -22,13 +22,15 @@ local PERIODICS = { ---@param id integer ---@param in_queue mqueue ---@param out_queue mqueue -function coordinator.new_session(id, in_queue, out_queue) +---@param facility_units table +function coordinator.new_session(id, in_queue, out_queue, facility_units) local log_header = "crdn_session(" .. id .. "): " local self = { id = id, in_q = in_queue, out_q = out_queue, + units = facility_units, -- connection properties seq_num = 0, r_seq_num = nil, @@ -118,7 +120,9 @@ function coordinator.new_session(id, in_queue, out_queue) log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) end elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_CRDN then - if pkt.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + if pkt.type == SCADA_CRDN_TYPES.QUERY_UNIT then + -- return unit statuses + else end end @@ -139,7 +143,7 @@ function coordinator.new_session(id, in_queue, out_queue) function public.close() _close() _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) - println("connection to coordinator #" .. self.id .. " closed by server") + println("connection to coordinator " .. self.id .. " closed by server") log.info(log_header .. "session closed by server") end diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 51f7e21..30e9432 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -1,8 +1,8 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local rsio = require("scada-common.rsio") -local util = require("scada-common.util") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local rsio = require("scada-common.rsio") +local util = require("scada-common.util") -- supervisor rtu sessions (svrs) local svrs_boiler = require("supervisor.session.rtu.boiler") @@ -52,13 +52,15 @@ local PERIODICS = { ---@param in_queue mqueue ---@param out_queue mqueue ---@param advertisement table -function rtu.new_session(id, in_queue, out_queue, advertisement) +---@param facility_units table +function rtu.new_session(id, in_queue, out_queue, advertisement, facility_units) local log_header = "rtu_session(" .. id .. "): " local self = { id = id, in_q = in_queue, out_q = out_queue, + f_units = facility_units, advert = advertisement, -- connection properties seq_num = 0, @@ -66,6 +68,11 @@ function rtu.new_session(id, in_queue, out_queue, advertisement) connected = true, rtu_conn_watchdog = util.new_watchdog(3), last_rtt = 0, + -- periodic messages + periodics = { + last_update = 0, + keep_alive = 0 + }, rs_io_q = {}, turbine_cmd_q = {}, turbine_cmd_capable = false, diff --git a/supervisor/session/rtu/boiler.lua b/supervisor/session/rtu/boiler.lua index 4aecb28..5daef5e 100644 --- a/supervisor/session/rtu/boiler.lua +++ b/supervisor/session/rtu/boiler.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/boilerv.lua b/supervisor/session/rtu/boilerv.lua index 1845668..16125f0 100644 --- a/supervisor/session/rtu/boilerv.lua +++ b/supervisor/session/rtu/boilerv.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/emachine.lua b/supervisor/session/rtu/emachine.lua index d6ec2c7..db8a17b 100644 --- a/supervisor/session/rtu/emachine.lua +++ b/supervisor/session/rtu/emachine.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/envd.lua b/supervisor/session/rtu/envd.lua index ca4a1af..f502e17 100644 --- a/supervisor/session/rtu/envd.lua +++ b/supervisor/session/rtu/envd.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua index 72fdde6..965b57b 100644 --- a/supervisor/session/rtu/imatrix.lua +++ b/supervisor/session/rtu/imatrix.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index 2447094..ba01400 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -1,9 +1,9 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local rsio = require("scada-common.rsio") -local types = require("scada-common.types") -local util = require("scada-common.util") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local rsio = require("scada-common.rsio") +local types = require("scada-common.types") +local util = require("scada-common.util") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/sna.lua b/supervisor/session/rtu/sna.lua index 539b98e..ccfac69 100644 --- a/supervisor/session/rtu/sna.lua +++ b/supervisor/session/rtu/sna.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua index 72c3f13..c2180d9 100644 --- a/supervisor/session/rtu/sps.lua +++ b/supervisor/session/rtu/sps.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/turbine.lua b/supervisor/session/rtu/turbine.lua index c2dd282..ab63fed 100644 --- a/supervisor/session/rtu/turbine.lua +++ b/supervisor/session/rtu/turbine.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua index f8bf9da..b31cb7c 100644 --- a/supervisor/session/rtu/turbinev.lua +++ b/supervisor/session/rtu/turbinev.lua @@ -1,8 +1,8 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local types = require("scada-common.types") -local util = require("scada-common.util") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local types = require("scada-common.types") +local util = require("scada-common.util") local unit_session = require("supervisor.session.rtu.unit_session") diff --git a/supervisor/session/rtu/unit_session.lua b/supervisor/session/rtu/unit_session.lua index ba2e988..ed2f027 100644 --- a/supervisor/session/rtu/unit_session.lua +++ b/supervisor/session/rtu/unit_session.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local types = require("scada-common.types") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") local txnctrl = require("supervisor.session.rtu.txnctrl") diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 3a1ac4b..f01a2f8 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -1,6 +1,8 @@ -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local util = require("scada-common.util") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local util = require("scada-common.util") + +local unit = require("supervisor.session.unit") local coordinator = require("supervisor.session.coordinator") local plc = require("supervisor.session.plc") @@ -21,6 +23,7 @@ svsessions.SESSION_TYPE = SESSION_TYPE local self = { modem = nil, num_reactors = 0, + facility_units = {}, rtu_sessions = {}, plc_sessions = {}, coord_sessions = {}, @@ -119,9 +122,23 @@ end -- PUBLIC FUNCTIONS -- --- link the modem +-- initialize svsessions ---@param modem table -function svsessions.link_modem(modem) +---@param num_reactors integer +---@param cooling_conf table +function svsessions.init(modem, num_reactors, cooling_conf) + self.modem = modem + self.num_reactors = num_reactors + self.facility_units = {} + + for i = 1, self.num_reactors do + table.insert(self.facility_units, unit.new(i, cooling_conf[i].BOILERS, cooling_conf[i].TURBINES)) + end +end + +-- re-link the modem +---@param modem table +function svsessions.relink_modem(modem) self.modem = modem end @@ -200,6 +217,8 @@ 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) table.insert(self.plc_sessions, plc_s) + self.facility_units[for_reactor].link_plc_session(plc_s) + log.debug("established new PLC session to " .. remote_port .. " with ID " .. self.next_plc_id) self.next_plc_id = self.next_plc_id + 1 @@ -232,7 +251,7 @@ function svsessions.establish_rtu_session(local_port, remote_port, advertisement instance = nil } - rtu_s.instance = rtu.new_session(self.next_rtu_id, rtu_s.in_queue, rtu_s.out_queue, advertisement) + rtu_s.instance = rtu.new_session(self.next_rtu_id, rtu_s.in_queue, rtu_s.out_queue, advertisement, self.facility_units) table.insert(self.rtu_sessions, rtu_s) log.debug("established new RTU session to " .. remote_port .. " with ID " .. self.next_rtu_id) @@ -260,7 +279,7 @@ function svsessions.establish_coord_session(local_port, remote_port, version) instance = nil } - coord_s.instance = coordinator.new_session(self.next_coord_id, coord_s.in_queue, coord_s.out_queue) + coord_s.instance = coordinator.new_session(self.next_coord_id, coord_s.in_queue, coord_s.out_queue, self.facility_units) table.insert(self.coord_sessions, coord_s) log.debug("established new coordinator session to " .. remote_port .. " with ID " .. self.next_coord_id) @@ -294,6 +313,12 @@ function svsessions.iterate_all() -- iterate coordinator sessions _iterate(self.coord_sessions) + + -- iterate units + for i = 1, #self.facility_units do + local u = self.facility_units[i] ---@type reactor_unit + u.update() + end end -- delete all closed sessions diff --git a/supervisor/unit.lua b/supervisor/session/unit.lua similarity index 85% rename from supervisor/unit.lua rename to supervisor/session/unit.lua index 33f5853..0ca2637 100644 --- a/supervisor/unit.lua +++ b/supervisor/session/unit.lua @@ -7,15 +7,15 @@ local TRI_FAIL = types.TRI_FAIL local DUMPING_MODE = types.DUMPING_MODE local DT_KEYS = { - ReactorTemp = "RTP", - ReactorFuel = "RFL", + ReactorTemp = "RTP", + ReactorFuel = "RFL", ReactorWaste = "RWS", ReactorCCool = "RCC", ReactorHCool = "RHC", - BoilerWater = "BWR", - BoilerSteam = "BST", - BoilerCCool = "BCC", - BoilerHCool = "BHC", + BoilerWater = "BWR", + BoilerSteam = "BST", + BoilerCCool = "BCC", + BoilerHCool = "BHC", TurbineSteam = "TST" } @@ -37,6 +37,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) annunciator = { -- reactor PLCOnline = false, + PLCHeartbeat = false, -- alternate true/false to blink, each time there is a keep_alive ReactorTrip = false, ManualReactorTrip = false, RCPTrip = false, @@ -47,12 +48,12 @@ function unit.new(for_reactor, num_boilers, num_turbines) WasteLineOcclusion = false, HighStartupRate = false, -- boiler - BoilerOnline = TRI_FAIL.OK, + BoilerOnline = {}, HeatingRateLow = {}, BoilRateMismatch = false, CoolantFeedMismatch = false, -- turbine - TurbineOnline = TRI_FAIL.OK, + TurbineOnline = {}, SteamFeedMismatch = false, MaxWaterReturnFeed = false, SteamDumpOpen = {}, @@ -63,12 +64,14 @@ function unit.new(for_reactor, num_boilers, num_turbines) } -- init boiler table fields - for _ = 1, self.num_boilers do + for _ = 1, num_boilers do + table.insert(self.db.annunciator.BoilerOnline, false) table.insert(self.db.annunciator.HeatingRateLow, false) end -- init turbine table fields - for _ = 1, self.num_turbines do + for _ = 1, num_turbines do + table.insert(self.db.annunciator.TurbineOnline, false) table.insert(self.db.annunciator.SteamDumpOpen, TRI_FAIL.OK) table.insert(self.db.annunciator.TurbineOverSpeed, false) table.insert(self.db.annunciator.TurbineTrip, false) @@ -173,6 +176,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > 1000 self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > 100 self.db.annunciator.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < 0.0 or plc_db.mek_status.fuel_fill <= 0.01 + -- @todo this is catagorized as not urgent, but the >= 0.99 is extremely urgent, revist this (RPS will kick in though) self.db.annunciator.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 0.0 or plc_db.mek_status.waste_fill >= 0.99 -- @todo this is dependent on setup, i.e. how much coolant is buffered and the turbine setup self.db.annunciator.HighStartupRate = not plc_db.control_state and plc_db.mek_status.burn_rate > 40 @@ -182,25 +186,24 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- BOILERS -- ------------- - -- check boiler online status - local connected_boilers = #self.boilers - if connected_boilers == 0 and self.num_boilers > 0 then - self.db.annunciator.BoilerOnline = TRI_FAIL.FULL - elseif connected_boilers > 0 and connected_boilers ~= self.num_boilers then - self.db.annunciator.BoilerOnline = TRI_FAIL.PARTIAL - else - self.db.annunciator.BoilerOnline = TRI_FAIL.OK - end + -- clear boiler online flags + for i = 1, self.counts.boilers do self.db.annunciator.BoilerOnline[i] = false end - -- compute aggregated statistics + -- aggregated statistics local total_boil_rate = 0.0 local boiler_steam_dt_sum = 0.0 local boiler_water_dt_sum = 0.0 + + -- go through boilers for stats and online for i = 1, #self.boilers do - local boiler = self.boilers[i].get_db() ---@type boiler_session_db + local session = self.boilers[i] ---@type unit_session + local boiler = session.get_db() ---@type boiler_session_db + total_boil_rate = total_boil_rate + boiler.state.boil_rate boiler_steam_dt_sum = _get_dt(DT_KEYS.BoilerSteam .. self.boilers[i].get_device_idx()) boiler_water_dt_sum = _get_dt(DT_KEYS.BoilerWater .. self.boilers[i].get_device_idx()) + + self.db.annunciator.BoilerOnline[session.get_device_idx()] = true end -- check heating rate low @@ -242,25 +245,24 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- TURBINES -- -------------- - -- check turbine online status - local connected_turbines = #self.turbines - if connected_turbines == 0 and self.num_turbines > 0 then - self.db.annunciator.TurbineOnline = TRI_FAIL.FULL - elseif connected_turbines > 0 and connected_turbines ~= self.num_turbines then - self.db.annunciator.TurbineOnline = TRI_FAIL.PARTIAL - else - self.db.annunciator.TurbineOnline = TRI_FAIL.OK - end + -- clear turbine online flags + for i = 1, self.counts.turbines do self.db.annunciator.TurbineOnline[i] = false end - -- compute aggregated statistics + -- aggregated statistics local total_flow_rate = 0 local total_input_rate = 0 local max_water_return_rate = 0 + + -- go through turbines for stats and online for i = 1, #self.turbines do - local turbine = self.turbines[i].get_db() ---@type turbine_session_db + local session = self.turbine[i] ---@type unit_session + local turbine = session.get_db() ---@type turbine_session_db + total_flow_rate = total_flow_rate + turbine.state.flow_rate total_input_rate = total_input_rate + turbine.state.steam_input_rate max_water_return_rate = max_water_return_rate + turbine.build.max_water_output + + self.db.annunciator.TurbineOnline[session.get_device_idx()] = true end -- check for steam feed mismatch and max return rate @@ -334,7 +336,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- link a turbine RTU session ---@param turbine unit_session function public.add_turbine(turbine) - if #self.turbines < self.num_turbines and turbine.get_device_idx() <= self.num_turbines then + if #self.turbines < num_turbines and turbine.get_device_idx() <= num_turbines then table.insert(self.turbines, turbine) -- reset deltas @@ -350,7 +352,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- link a boiler RTU session ---@param boiler unit_session function public.add_boiler(boiler) - if #self.boilers < self.num_boilers and boiler.get_device_idx() <= self.num_boilers then + if #self.boilers < num_boilers and boiler.get_device_idx() <= num_boilers then table.insert(self.boilers, boiler) -- reset deltas @@ -379,7 +381,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- update (iterate) this unit function public.update() -- unlink PLC if session was closed - if not self.plc_s.open then + if self.plc_s ~= nil and not self.plc_s.open then self.plc_s = nil end @@ -401,12 +403,14 @@ function unit.new(for_reactor, num_boilers, num_turbines) build.boilers = {} for i = 1, #self.boilers do - table.insert(build.boilers, self.boilers[i].get_db().build) + local boiler = self.boilers[i] ---@type unit_session + build.boilers[boiler.get_device_idx()] = { formed = boiler.get_db().formed, build = boiler.get_db().build } end build.turbines = {} for i = 1, #self.turbines do - table.insert(build.turbines, self.turbines[i].get_db().build) + local turbine = self.turbines[i] ---@type unit_session + build.turbines[turbine.get_device_idx()] = { formed = turbine.get_db().formed, build = turbine.get_db().build } end return build @@ -433,19 +437,15 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- status of boilers (including tanks) status.boilers = {} for i = 1, #self.boilers do - table.insert(status.boilers, { - state = self.boilers[i].get_db().state, - tanks = self.boilers[i].get_db().tanks, - }) + local boiler = self.boilers[i] ---@type unit_session + status.boilers[boiler.get_device_idx()] = { state = boiler.get_db().state, tanks = boiler.get_db().tanks } end -- status of turbines (including tanks) status.turbines = {} for i = 1, #self.turbines do - table.insert(status.turbines, { - state = self.turbines[i].get_db().state, - tanks = self.turbines[i].get_db().tanks, - }) + local turbine = self.turbines[i] ---@type unit_session + status.turbines[turbine.get_device_idx()] = { state = turbine.get_db().state, tanks = turbine.get_db().tanks } end return status diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 49348ed..acc80bb 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -4,16 +4,16 @@ require("/initenv").init_env() -local log = require("scada-common.log") -local ppm = require("scada-common.ppm") -local util = require("scada-common.util") +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.5.4" +local SUPERVISOR_VERSION = "beta-v0.5.5" local print = util.print local println = util.println diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 617e996..3aa9f9c 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -1,6 +1,6 @@ -local comms = require("scada-common.comms") -local log = require("scada-common.log") -local util = require("scada-common.util") +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local util = require("scada-common.util") local svsessions = require("supervisor.session.svsessions") @@ -57,7 +57,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen _open_channels() -- link modem to svsessions - svsessions.link_modem(self.modem) + svsessions.init(self.modem, num_reactors, cooling_conf) -- send PLC link request response ---@param dest integer @@ -112,7 +112,7 @@ function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen ---@diagnostic disable-next-line: redefined-local function public.reconnect_modem(modem) self.modem = modem - svsessions.link_modem(self.modem) + svsessions.relink_modem(self.modem) _open_channels() end