<p>pespin <strong>submitted</strong> this change.</p><p><a href="https://gerrit.osmocom.org/c/osmo-gsm-tester/+/20521">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  pespin: Looks good to me, approved
  Jenkins Builder: Verified

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">4g: Introduce ZMQ GnuRadio stream broker<br><br>srsENB currently creates 1 zmq stream (1 tx, 1 rx) for each cell (2 if<br>MIMO is enabled). Each cell transceives on a given EARFCN (and several<br>cells can transmit on same EARFCN).<br><br>However, for handover test purposes, we want to join all cells operating<br>on the same EARFCN to transceive on the same ZMQ conn, so that an srsUE<br>can interact with them at the same time (same as if the medium was shared).<br>Furthermore, we want to set different gains on each of those paths<br>before merging them in order to emulate RF conditions like handover.<br><br>In order to do so, a new element called the Broker is introduced, which<br>is placed in between ENBs and UEs ZMQ conenctions, multiplexing the<br>connections on the ENB side towards the UE side.<br><br>A separate process for the broker is run remotely (ENB run host) which<br>listens on a ctrl socket for commands. An internal Broker class is used<br>in osmo-gsm-tester to interact with the remote script, for instance to<br>configure the ports, start and stop the remote process, send commands to<br>it, etc.<br>On each ENB, when the rfemu "gnuradio_zmq" rfemu implementation is selected<br>in configuration, it will configure its zmq connections and the UE ones to<br>go over the Broker.<br><br>As a result, that means the UE zmq port configuration is expected to be<br>different than when no broker is in used, since there's the multiplexing<br>per EARFCN in between.<br><br>In this commit, only 1 ENB is supported, but multi-enb support is<br>planned in the future.<br><br>The handover test passes in the docker setup with this config:<br>"""<br>OSMO_GSM_TESTER_OPTS="-T -l dbg -s 4g:srsue-rftype@zmq+srsenb-rftype@zmq+" \<br>    "mod-enb-nprb@6+mod-enb-ncells@2+mod-enb-cells-2ca+suite-4g@10,2+" \<br>        "mod-enb-meas-enable -t =handover.py"<br>"""<br><br>and in resources.conf (or scenario), added:<br>"""<br>enb:<br>  ...<br>  cell_list:<br>    - dl_rfemu:<br>       type: gnuradio_zmq<br>    - dl_rfemu:<br>        type: gnuradio_zmq<br>"""<br><br>Note that since the broker is used, there's not need for mod-srsue-ncarriers@2<br>since the broker is joining the 2 enb cells into 1 stream on the UE side.<br><br>Change-Id: I6282cda400558dcb356276786d91e6388524c5b1<br>---<br>M src/osmo_gsm_tester/obj/enb.py<br>M src/osmo_gsm_tester/obj/enb_amarisoft.py<br>M src/osmo_gsm_tester/obj/enb_srs.py<br>M src/osmo_gsm_tester/obj/ms_srs.py<br>M src/osmo_gsm_tester/obj/rfemu.py<br>A src/osmo_gsm_tester/obj/rfemu_gnuradio_zmq.py<br>M sysmocom/scenarios/mod-enb-cells-2ca.conf<br>A utils/bin/osmo-gsm-tester_zmq_broker.py<br>8 files changed, 515 insertions(+), 31 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/src/osmo_gsm_tester/obj/enb.py b/src/osmo_gsm_tester/obj/enb.py</span><br><span>index 38d9a25..f15bbe3 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/enb.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/enb.py</span><br><span>@@ -21,6 +21,7 @@</span><br><span> from ..core import log, config</span><br><span> from ..core import schema</span><br><span> from . import run_node</span><br><span style="color: hsl(120, 100%, 40%);">+from .rfemu_gnuradio_zmq import GrBroker</span><br><span> </span><br><span> def on_register_schemas():</span><br><span>     resource_schema = {</span><br><span>@@ -85,7 +86,53 @@</span><br><span>         self._num_prb = 0</span><br><span>         self._num_cells = None</span><br><span>         self._epc = None</span><br><span style="color: hsl(0, 100%, 40%);">-        self._zmq_base_bind_port = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.gen_conf = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.gr_broker = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def using_grbroker(self, cfg_values):</span><br><span style="color: hsl(120, 100%, 40%);">+        # whether we are to use Grbroker in between ENB and UE.</span><br><span style="color: hsl(120, 100%, 40%);">+        # Initial checks:</span><br><span style="color: hsl(120, 100%, 40%);">+        if cfg_values['enb'].get('rf_dev_type') != 'zmq':</span><br><span style="color: hsl(120, 100%, 40%);">+            return False</span><br><span style="color: hsl(120, 100%, 40%);">+        cell_list = cfg_values['enb']['cell_list']</span><br><span style="color: hsl(120, 100%, 40%);">+        use_match = False</span><br><span style="color: hsl(120, 100%, 40%);">+        notuse_match = False</span><br><span style="color: hsl(120, 100%, 40%);">+        for cell in cell_list:</span><br><span style="color: hsl(120, 100%, 40%);">+            if cell.get('dl_rfemu', False) and cell['dl_rfemu'].get('type', None) == 'gnuradio_zmq':</span><br><span style="color: hsl(120, 100%, 40%);">+                use_match = True</span><br><span style="color: hsl(120, 100%, 40%);">+            else:</span><br><span style="color: hsl(120, 100%, 40%);">+                notuse_match = True</span><br><span style="color: hsl(120, 100%, 40%);">+        if use_match and notuse_match:</span><br><span style="color: hsl(120, 100%, 40%);">+            raise log.Error('Some Cells are configured to use gnuradio_zmq and some are not, unsupported')</span><br><span style="color: hsl(120, 100%, 40%);">+        return use_match</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def calc_required_zmq_ports(self, cfg_values):</span><br><span style="color: hsl(120, 100%, 40%);">+        cell_list = cfg_values['enb']['cell_list']</span><br><span style="color: hsl(120, 100%, 40%);">+        return len(cell_list) * self.num_ports() # *2 if MIMO</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def calc_required_zmq_ports_joined_earfcn(self, cfg_values):</span><br><span style="color: hsl(120, 100%, 40%);">+        #gr_broker will join the earfcns, so we need to count uniqe earfcns:</span><br><span style="color: hsl(120, 100%, 40%);">+        cell_list = cfg_values['enb']['cell_list']</span><br><span style="color: hsl(120, 100%, 40%);">+        earfcn_li = []</span><br><span style="color: hsl(120, 100%, 40%);">+        [earfcn_li.append(int(cell['dl_earfcn'])) for cell in cell_list if int(cell['dl_earfcn']) not in earfcn_li]</span><br><span style="color: hsl(120, 100%, 40%);">+        return len(earfcn_li) * self.num_ports() # *2 if MIMO</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def assign_enb_zmq_ports(self, cfg_values, port_name, base_port):</span><br><span style="color: hsl(120, 100%, 40%);">+        port_offset = 0</span><br><span style="color: hsl(120, 100%, 40%);">+        cell_list = cfg_values['enb']['cell_list']</span><br><span style="color: hsl(120, 100%, 40%);">+        for cell in cell_list:</span><br><span style="color: hsl(120, 100%, 40%);">+            cell[port_name] = base_port + port_offset</span><br><span style="color: hsl(120, 100%, 40%);">+            port_offset += self.num_ports()</span><br><span style="color: hsl(120, 100%, 40%);">+        # TODO: do we need to assign cell_list back?</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def assign_enb_zmq_ports_joined_earfcn(self, cfg_values, port_name, base_port):</span><br><span style="color: hsl(120, 100%, 40%);">+        # TODO: Set in cell one bind port per unique earfcn, this is where UE will connect to when we use grbroker.</span><br><span style="color: hsl(120, 100%, 40%);">+        cell_list = cfg_values['enb']['cell_list']</span><br><span style="color: hsl(120, 100%, 40%);">+        earfcn_li = []</span><br><span style="color: hsl(120, 100%, 40%);">+        [earfcn_li.append(int(cell['dl_earfcn'])) for cell in cell_list if int(cell['dl_earfcn']) not in earfcn_li]</span><br><span style="color: hsl(120, 100%, 40%);">+        for cell in cell_list:</span><br><span style="color: hsl(120, 100%, 40%);">+            cell[port_name] = base_port + earfcn_li.index(int(cell['dl_earfcn'])) * self.num_ports()</span><br><span> </span><br><span>     def configure(self, config_specifics_li):</span><br><span>         values = dict(enb=config.get_defaults('enb'))</span><br><span>@@ -127,6 +174,30 @@</span><br><span>                     scell_list_new.append(scell_id)</span><br><span>             values['enb']['cell_list'][i]['scell_list'] = scell_list_new</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+        # Assign ZMQ ports to each Cell/EARFCN.</span><br><span style="color: hsl(120, 100%, 40%);">+        if values['enb'].get('rf_dev_type') == 'zmq':</span><br><span style="color: hsl(120, 100%, 40%);">+            resourcep = self.testenv.suite().resource_pool()</span><br><span style="color: hsl(120, 100%, 40%);">+            num_ports = self.calc_required_zmq_ports(values)</span><br><span style="color: hsl(120, 100%, 40%);">+            num_ports_joined_earfcn = self.calc_required_zmq_ports_joined_earfcn(values)</span><br><span style="color: hsl(120, 100%, 40%);">+            ue_bind_port = self.ue.zmq_base_bind_port()</span><br><span style="color: hsl(120, 100%, 40%);">+            enb_bind_port = resourcep.next_zmq_port_range(self, num_ports)</span><br><span style="color: hsl(120, 100%, 40%);">+            self.assign_enb_zmq_ports(values, 'zmq_enb_bind_port', enb_bind_port)</span><br><span style="color: hsl(120, 100%, 40%);">+            # If we are to use a GrBroker, then initialize here to have remote zmq ports available:</span><br><span style="color: hsl(120, 100%, 40%);">+            if self.using_grbroker(values):</span><br><span style="color: hsl(120, 100%, 40%);">+                zmq_enb_peer_port = resourcep.next_zmq_port_range(self, num_ports)</span><br><span style="color: hsl(120, 100%, 40%);">+                self.assign_enb_zmq_ports(values, 'zmq_enb_peer_port', zmq_enb_peer_port) # These are actually bound to GrBroker</span><br><span style="color: hsl(120, 100%, 40%);">+                self.assign_enb_zmq_ports_joined_earfcn(values, 'zmq_ue_bind_port', ue_bind_port) # This is were GrBroker binds on the UE side</span><br><span style="color: hsl(120, 100%, 40%);">+                zmq_ue_peer_port = resourcep.next_zmq_port_range(self, num_ports_joined_earfcn)</span><br><span style="color: hsl(120, 100%, 40%);">+                self.assign_enb_zmq_ports_joined_earfcn(values, 'zmq_ue_peer_port', zmq_ue_peer_port) # This is were GrBroker binds on the UE side</span><br><span style="color: hsl(120, 100%, 40%);">+                # Already set gen_conf here in advance since gr_broker needs the cell list</span><br><span style="color: hsl(120, 100%, 40%);">+                self.gen_conf = values</span><br><span style="color: hsl(120, 100%, 40%);">+                self.gr_broker = GrBroker.ref()</span><br><span style="color: hsl(120, 100%, 40%);">+                self.gr_broker.handle_enb(self)</span><br><span style="color: hsl(120, 100%, 40%);">+            else:</span><br><span style="color: hsl(120, 100%, 40%);">+                self.assign_enb_zmq_ports(values, 'zmq_enb_peer_port', ue_bind_port)</span><br><span style="color: hsl(120, 100%, 40%);">+                self.assign_enb_zmq_ports(values, 'zmq_ue_bind_port', ue_bind_port) #If no broker we need to match amount of ports</span><br><span style="color: hsl(120, 100%, 40%);">+                self.assign_enb_zmq_ports(values, 'zmq_ue_peer_port', enb_bind_port)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>         return values</span><br><span> </span><br><span>     def id(self):</span><br><span>@@ -145,14 +216,13 @@</span><br><span> ########################</span><br><span>     def cleanup(self):</span><br><span>         'Nothing to do by default. Subclass can override if required.'</span><br><span style="color: hsl(0, 100%, 40%);">-        pass</span><br><span style="color: hsl(120, 100%, 40%);">+        if self.gr_broker:</span><br><span style="color: hsl(120, 100%, 40%);">+            GrBroker.unref()</span><br><span style="color: hsl(120, 100%, 40%);">+            self.gr_broker = None</span><br><span> </span><br><span>     def num_prb(self):</span><br><span>         return self._num_prb</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-    def zmq_base_bind_port(self):</span><br><span style="color: hsl(0, 100%, 40%);">-        return self._zmq_base_bind_port</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span>     #reference: srsLTE.git srslte_symbol_sz()</span><br><span>     def num_prb2symbol_sz(self, num_prb):</span><br><span>         if num_prb == 6:</span><br><span>@@ -168,24 +238,50 @@</span><br><span>     def num_prb2base_srate(self, num_prb):</span><br><span>         return self.num_prb2symbol_sz(num_prb) * 15 * 1000</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-    def get_zmq_rf_dev_args(self):</span><br><span style="color: hsl(120, 100%, 40%);">+    def get_zmq_rf_dev_args(self, cfg_values):</span><br><span>         base_srate = self.num_prb2base_srate(self.num_prb())</span><br><span style="color: hsl(0, 100%, 40%);">-        if self._zmq_base_bind_port is None:</span><br><span style="color: hsl(0, 100%, 40%);">-            self._zmq_base_bind_port = self.testenv.suite().resource_pool().next_zmq_port_range(self, 4)</span><br><span style="color: hsl(0, 100%, 40%);">-        ue_base_port = self.ue.zmq_base_bind_port()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if self.gr_broker:</span><br><span style="color: hsl(120, 100%, 40%);">+            ul_rem_addr = self.addr()</span><br><span style="color: hsl(120, 100%, 40%);">+        else:</span><br><span style="color: hsl(120, 100%, 40%);">+            ul_rem_addr = self.ue.addr()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        rf_dev_args = 'fail_on_disconnect=true'</span><br><span style="color: hsl(120, 100%, 40%);">+        idx = 0</span><br><span style="color: hsl(120, 100%, 40%);">+        cell_list = cfg_values['enb']['cell_list']</span><br><span>         # Define all 8 possible RF ports (2x CA with 2x2 MIMO)</span><br><span style="color: hsl(0, 100%, 40%);">-        rf_dev_args = 'fail_on_disconnect=true' \</span><br><span style="color: hsl(0, 100%, 40%);">-                    + ',tx_port0=tcp://' + self.addr() + ':' + str(self._zmq_base_bind_port + 0) \</span><br><span style="color: hsl(0, 100%, 40%);">-                    + ',tx_port1=tcp://' + self.addr() + ':' + str(self._zmq_base_bind_port + 1) \</span><br><span style="color: hsl(0, 100%, 40%);">-                    + ',tx_port2=tcp://' + self.addr() + ':' + str(self._zmq_base_bind_port + 2) \</span><br><span style="color: hsl(0, 100%, 40%);">-                    + ',tx_port3=tcp://' + self.addr() + ':' + str(self._zmq_base_bind_port + 3) \</span><br><span style="color: hsl(0, 100%, 40%);">-                    + ',rx_port0=tcp://' + self.ue.addr() + ':' + str(ue_base_port + 0) \</span><br><span style="color: hsl(0, 100%, 40%);">-                    + ',rx_port1=tcp://' + self.ue.addr() + ':' + str(ue_base_port + 1) \</span><br><span style="color: hsl(0, 100%, 40%);">-                    + ',rx_port2=tcp://' + self.ue.addr() + ':' + str(ue_base_port + 2) \</span><br><span style="color: hsl(0, 100%, 40%);">-                    + ',rx_port3=tcp://' + self.ue.addr() + ':' + str(ue_base_port + 3)</span><br><span style="color: hsl(120, 100%, 40%);">+        for cell in cell_list:</span><br><span style="color: hsl(120, 100%, 40%);">+            rf_dev_args += ',tx_port%u=tcp://%s:%u' %(idx, self.addr(), cell['zmq_enb_bind_port'] + 0)</span><br><span style="color: hsl(120, 100%, 40%);">+            if self.num_ports() > 1:</span><br><span style="color: hsl(120, 100%, 40%);">+                rf_dev_args += ',tx_port%u=tcp://%s:%u' %(idx + 1, self.addr(), cell['zmq_enb_bind_port'] + 1)</span><br><span style="color: hsl(120, 100%, 40%);">+            rf_dev_args += ',rx_port%u=tcp://%s:%u' %(idx, ul_rem_addr, cell['zmq_enb_peer_port'] + 0)</span><br><span style="color: hsl(120, 100%, 40%);">+            if self.num_ports() > 1:</span><br><span style="color: hsl(120, 100%, 40%);">+                rf_dev_args += ',rx_port%u=tcp://%s:%u' %(idx + 1, ul_rem_addr, cell['zmq_enb_peer_port'] + 1)</span><br><span style="color: hsl(120, 100%, 40%);">+            idx += self.num_ports()</span><br><span> </span><br><span>         rf_dev_args += ',id=enb,base_srate=' + str(base_srate)</span><br><span style="color: hsl(120, 100%, 40%);">+        return rf_dev_args</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+    def get_zmq_rf_dev_args_for_ue(self, ue):</span><br><span style="color: hsl(120, 100%, 40%);">+        cell_list = self.gen_conf['enb']['cell_list']</span><br><span style="color: hsl(120, 100%, 40%);">+        rf_dev_args = ''</span><br><span style="color: hsl(120, 100%, 40%);">+        idx = 0</span><br><span style="color: hsl(120, 100%, 40%);">+        earfcns_done = []</span><br><span style="color: hsl(120, 100%, 40%);">+        for cell in cell_list:</span><br><span style="color: hsl(120, 100%, 40%);">+            if self.gr_broker:</span><br><span style="color: hsl(120, 100%, 40%);">+                if cell['dl_earfcn'] in earfcns_done:</span><br><span style="color: hsl(120, 100%, 40%);">+                    continue</span><br><span style="color: hsl(120, 100%, 40%);">+                earfcns_done.append(cell['dl_earfcn'])</span><br><span style="color: hsl(120, 100%, 40%);">+            rf_dev_args += ',tx_port%u=tcp://%s:%u' %(idx, ue.addr(), cell['zmq_ue_bind_port'] + 0)</span><br><span style="color: hsl(120, 100%, 40%);">+            if self.num_ports() > 1:</span><br><span style="color: hsl(120, 100%, 40%);">+                rf_dev_args += ',tx_port%u=tcp://%s:%u' %(idx + 1, ue.addr(), cell['zmq_ue_bind_port'] + 1)</span><br><span style="color: hsl(120, 100%, 40%);">+            rf_dev_args += ',rx_port%u=tcp://%s:%u' %(idx, self.addr(), cell['zmq_ue_peer_port'] + 0)</span><br><span style="color: hsl(120, 100%, 40%);">+            if self.num_ports() > 1:</span><br><span style="color: hsl(120, 100%, 40%);">+                rf_dev_args += ',rx_port%u=tcp://%s:%u' %(idx + 1, self.addr(), cell['zmq_ue_peer_port'] + 1)</span><br><span style="color: hsl(120, 100%, 40%);">+            idx += self.num_ports()</span><br><span style="color: hsl(120, 100%, 40%);">+        # remove trailing comma:</span><br><span style="color: hsl(120, 100%, 40%);">+        if rf_dev_args[0] == ',':</span><br><span style="color: hsl(120, 100%, 40%);">+            return rf_dev_args[1:]</span><br><span>         return rf_dev_args</span><br><span> </span><br><span>     def get_instance_by_type(testenv, conf):</span><br><span>diff --git a/src/osmo_gsm_tester/obj/enb_amarisoft.py b/src/osmo_gsm_tester/obj/enb_amarisoft.py</span><br><span>index 1fc4485..9bed63e 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/enb_amarisoft.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/enb_amarisoft.py</span><br><span>@@ -100,6 +100,8 @@</span><br><span>             self.rem_host.scpfrom('scp-back-phy-signal-log', self.remote_phy_signal_file, self.phy_signal_file)</span><br><span>         except Exception as e:</span><br><span>             self.log(repr(e))</span><br><span style="color: hsl(120, 100%, 40%);">+        # Clean up for parent class:</span><br><span style="color: hsl(120, 100%, 40%);">+        super().cleanup()</span><br><span> </span><br><span>     def start(self, epc):</span><br><span>         self.log('Starting AmarisoftENB')</span><br><span>@@ -173,7 +175,7 @@</span><br><span>         # We need to set some specific variables programatically here to match IP addresses:</span><br><span>         if self._conf.get('rf_dev_type') == 'zmq':</span><br><span>             base_srate = self.num_prb2base_srate(self.num_prb())</span><br><span style="color: hsl(0, 100%, 40%);">-            rf_dev_args = self.get_zmq_rf_dev_args()</span><br><span style="color: hsl(120, 100%, 40%);">+            rf_dev_args = self.get_zmq_rf_dev_args(values)</span><br><span>             config.overlay(values, dict(enb=dict(sample_rate = base_srate / (1000*1000),</span><br><span>                                                  rf_dev_args = rf_dev_args)))</span><br><span> </span><br><span>diff --git a/src/osmo_gsm_tester/obj/enb_srs.py b/src/osmo_gsm_tester/obj/enb_srs.py</span><br><span>index 77f196f..493fef2 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/enb_srs.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/enb_srs.py</span><br><span>@@ -100,6 +100,8 @@</span><br><span> </span><br><span>         # Collect KPIs for each TC</span><br><span>         self.testenv.test().set_kpis(self.get_kpis())</span><br><span style="color: hsl(120, 100%, 40%);">+        # Clean up for parent class:</span><br><span style="color: hsl(120, 100%, 40%);">+        super().cleanup()</span><br><span> </span><br><span>     def start(self, epc):</span><br><span>         self.log('Starting srsENB')</span><br><span>@@ -198,7 +200,7 @@</span><br><span> </span><br><span>         # We need to set some specific variables programatically here to match IP addresses:</span><br><span>         if self._conf.get('rf_dev_type') == 'zmq':</span><br><span style="color: hsl(0, 100%, 40%);">-            rf_dev_args = self.get_zmq_rf_dev_args()</span><br><span style="color: hsl(120, 100%, 40%);">+            rf_dev_args = self.get_zmq_rf_dev_args(values)</span><br><span>             config.overlay(values, dict(enb=dict(rf_dev_args=rf_dev_args)))</span><br><span> </span><br><span>         # Set UHD frame size as a function of the cell bandwidth on B2XX</span><br><span>@@ -258,7 +260,7 @@</span><br><span>         rfemu_cfg = cell_list[cell].get('dl_rfemu', None)</span><br><span>         if rfemu_cfg is None:</span><br><span>             raise log.Error('rfemu attribute not found in cell_list item!')</span><br><span style="color: hsl(0, 100%, 40%);">-        if rfemu_cfg['type'] == 'srsenb_stdin':</span><br><span style="color: hsl(120, 100%, 40%);">+        if rfemu_cfg['type'] == 'srsenb_stdin' or rfemu_cfg['type'] == 'gnuradio_zmq':</span><br><span>             # These fields are required so the rfemu class can interact with us:</span><br><span>              config.overlay(rfemu_cfg, dict(enb=self,</span><br><span>                                             cell_id=cell_list[cell]['cell_id']))</span><br><span>diff --git a/src/osmo_gsm_tester/obj/ms_srs.py b/src/osmo_gsm_tester/obj/ms_srs.py</span><br><span>index 76d26c0..0a7624a 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/ms_srs.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/ms_srs.py</span><br><span>@@ -270,16 +270,9 @@</span><br><span>         # We need to set some specific variables programatically here to match IP addresses:</span><br><span>         if self._conf.get('rf_dev_type') == 'zmq':</span><br><span>             base_srate = num_prb2base_srate(self.enb.num_prb())</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>             # Define all 8 possible RF ports (2x CA with 2x2 MIMO)</span><br><span style="color: hsl(0, 100%, 40%);">-            enb_base_port = self.enb.zmq_base_bind_port()</span><br><span style="color: hsl(0, 100%, 40%);">-            rf_dev_args = 'tx_port0=tcp://' + self.addr() + ':' + str(self._zmq_base_bind_port + 0) \</span><br><span style="color: hsl(0, 100%, 40%);">-                        + ',tx_port1=tcp://' + self.addr() + ':' + str(self._zmq_base_bind_port + 1) \</span><br><span style="color: hsl(0, 100%, 40%);">-                        + ',tx_port2=tcp://' + self.addr() + ':' + str(self._zmq_base_bind_port + 2) \</span><br><span style="color: hsl(0, 100%, 40%);">-                        + ',tx_port3=tcp://' + self.addr() + ':' + str(self._zmq_base_bind_port + 3) \</span><br><span style="color: hsl(0, 100%, 40%);">-                        + ',rx_port0=tcp://' + self.enb.addr() + ':' + str(enb_base_port + 0) \</span><br><span style="color: hsl(0, 100%, 40%);">-                        + ',rx_port1=tcp://' + self.enb.addr() + ':' + str(enb_base_port + 1) \</span><br><span style="color: hsl(0, 100%, 40%);">-                        + ',rx_port2=tcp://' + self.enb.addr() + ':' + str(enb_base_port + 2) \</span><br><span style="color: hsl(0, 100%, 40%);">-                        + ',rx_port3=tcp://' + self.enb.addr() + ':' + str(enb_base_port + 3)</span><br><span style="color: hsl(120, 100%, 40%);">+            rf_dev_args = self.enb.get_zmq_rf_dev_args_for_ue(self)</span><br><span> </span><br><span>             if self.num_carriers == 1:</span><br><span>                 # Single carrier</span><br><span>diff --git a/src/osmo_gsm_tester/obj/rfemu.py b/src/osmo_gsm_tester/obj/rfemu.py</span><br><span>index ec8ed68..2c50c09 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/rfemu.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/rfemu.py</span><br><span>@@ -55,6 +55,9 @@</span><br><span>     elif rfemu_type == 'srsenb_stdin':</span><br><span>         from .rfemu_srsenb_stdin import RFemulationSrsStdin</span><br><span>         obj = RFemulationSrsStdin</span><br><span style="color: hsl(120, 100%, 40%);">+    elif rfemu_type == 'gnuradio_zmq':</span><br><span style="color: hsl(120, 100%, 40%);">+        from .rfemu_gnuradio_zmq import RFemulationGnuradioZmq</span><br><span style="color: hsl(120, 100%, 40%);">+        obj = RFemulationGnuradioZmq</span><br><span>     else:</span><br><span>         raise log.Error('RFemulation type not supported:', rfemu_type)</span><br><span> </span><br><span>diff --git a/src/osmo_gsm_tester/obj/rfemu_gnuradio_zmq.py b/src/osmo_gsm_tester/obj/rfemu_gnuradio_zmq.py</span><br><span>new file mode 100644</span><br><span>index 0000000..c5398a9</span><br><span>--- /dev/null</span><br><span>+++ b/src/osmo_gsm_tester/obj/rfemu_gnuradio_zmq.py</span><br><span>@@ -0,0 +1,198 @@</span><br><span style="color: hsl(120, 100%, 40%);">+# osmo_gsm_tester: class defining a RF emulation object implemented using SRS ENB stdin interface</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# Copyright (C) 2020 by sysmocom - s.f.m.c. GmbH</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# Author: Pau Espin Pedrol <pespin@sysmocom.de></span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is free software: you can redistribute it and/or modify</span><br><span style="color: hsl(120, 100%, 40%);">+# it under the terms of the GNU General Public License as</span><br><span style="color: hsl(120, 100%, 40%);">+# published by the Free Software Foundation, either version 3 of the</span><br><span style="color: hsl(120, 100%, 40%);">+# License, or (at your option) any later version.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is distributed in the hope that it will be useful,</span><br><span style="color: hsl(120, 100%, 40%);">+# but WITHOUT ANY WARRANTY; without even the implied warranty of</span><br><span style="color: hsl(120, 100%, 40%);">+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the</span><br><span style="color: hsl(120, 100%, 40%);">+# GNU General Public License for more details.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# You should have received a copy of the GNU General Public License</span><br><span style="color: hsl(120, 100%, 40%);">+# along with this program.  If not, see <http://www.gnu.org/licenses/>.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+import json</span><br><span style="color: hsl(120, 100%, 40%);">+import socket</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import log</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import util</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import process</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import remote</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core.event_loop import MainLoop</span><br><span style="color: hsl(120, 100%, 40%);">+from .rfemu import RFemulation</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class GrBroker(log.Origin):</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    # static fields:</span><br><span style="color: hsl(120, 100%, 40%);">+    refcount = 0</span><br><span style="color: hsl(120, 100%, 40%);">+    instance = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        super().__init__(log.C_RUN, 'zmq_gr_broker')</span><br><span style="color: hsl(120, 100%, 40%);">+        self.process = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.ctrl_port = 5005</span><br><span style="color: hsl(120, 100%, 40%);">+        self.run_dir = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.rem_host = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.cfg = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.enb = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.addr = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.ctrl_sk = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    @staticmethod</span><br><span style="color: hsl(120, 100%, 40%);">+    def ref():</span><br><span style="color: hsl(120, 100%, 40%);">+        if GrBroker.refcount == 0:</span><br><span style="color: hsl(120, 100%, 40%);">+            GrBroker.instance = GrBroker()</span><br><span style="color: hsl(120, 100%, 40%);">+        GrBroker.refcount = GrBroker.refcount + 1</span><br><span style="color: hsl(120, 100%, 40%);">+        return GrBroker.instance</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    @staticmethod</span><br><span style="color: hsl(120, 100%, 40%);">+    def unref():</span><br><span style="color: hsl(120, 100%, 40%);">+        GrBroker.refcount = GrBroker.refcount - 1</span><br><span style="color: hsl(120, 100%, 40%);">+        if GrBroker.refcount == 0:</span><br><span style="color: hsl(120, 100%, 40%);">+            GrBroker.instance.cleanup()</span><br><span style="color: hsl(120, 100%, 40%);">+            GrBroker.instance = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def cleanup(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        if self.ctrl_sk is not None:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.cmd_exit()</span><br><span style="color: hsl(120, 100%, 40%);">+            self.ctrl_sk.close()</span><br><span style="color: hsl(120, 100%, 40%);">+            self.ctrl_sk = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.enb = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def handle_enb(self, enb):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.enb = enb</span><br><span style="color: hsl(120, 100%, 40%);">+        self.addr = self.enb.addr()</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv = self.enb.testenv</span><br><span style="color: hsl(120, 100%, 40%);">+        self.cfg = self.gen_json(enb)</span><br><span style="color: hsl(120, 100%, 40%);">+        # FIXME: we may need to delay this somehow if we want to support several ENBs</span><br><span style="color: hsl(120, 100%, 40%);">+        self.start()</span><br><span style="color: hsl(120, 100%, 40%);">+        self.setup()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def gen_json_enb(self, enb):</span><br><span style="color: hsl(120, 100%, 40%);">+        res = []</span><br><span style="color: hsl(120, 100%, 40%);">+        cell_list = enb.gen_conf['enb']['cell_list']</span><br><span style="color: hsl(120, 100%, 40%);">+        for cell in cell_list:</span><br><span style="color: hsl(120, 100%, 40%);">+            # TODO: probably add enb_id, cell_id to support several ENB</span><br><span style="color: hsl(120, 100%, 40%);">+            data = {'earfcn': int(cell['dl_earfcn']),</span><br><span style="color: hsl(120, 100%, 40%);">+                    'bind_port': int(cell['zmq_enb_peer_port']),</span><br><span style="color: hsl(120, 100%, 40%);">+                    'peer_addr': enb.addr(),</span><br><span style="color: hsl(120, 100%, 40%);">+                    'peer_port': int(cell['zmq_enb_bind_port']),</span><br><span style="color: hsl(120, 100%, 40%);">+                    'use_mimo': True if enb.num_ports() > 1 else False</span><br><span style="color: hsl(120, 100%, 40%);">+                    }</span><br><span style="color: hsl(120, 100%, 40%);">+            res.append(data)</span><br><span style="color: hsl(120, 100%, 40%);">+        return res</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def gen_json_ue(self, enb):</span><br><span style="color: hsl(120, 100%, 40%);">+        res = {}</span><br><span style="color: hsl(120, 100%, 40%);">+        res = []</span><br><span style="color: hsl(120, 100%, 40%);">+        earfcns_done = []</span><br><span style="color: hsl(120, 100%, 40%);">+        cell_list = enb.gen_conf['enb']['cell_list']</span><br><span style="color: hsl(120, 100%, 40%);">+        for cell in cell_list:</span><br><span style="color: hsl(120, 100%, 40%);">+            data = {}</span><br><span style="color: hsl(120, 100%, 40%);">+            if int(cell['dl_earfcn']) in earfcns_done:</span><br><span style="color: hsl(120, 100%, 40%);">+                continue</span><br><span style="color: hsl(120, 100%, 40%);">+            earfcns_done.append(int(cell['dl_earfcn']))</span><br><span style="color: hsl(120, 100%, 40%);">+            data = {'earfcn': int(cell['dl_earfcn']),</span><br><span style="color: hsl(120, 100%, 40%);">+                    'bind_port': int(cell['zmq_ue_peer_port']),</span><br><span style="color: hsl(120, 100%, 40%);">+                    'peer_addr': enb.ue.addr(),</span><br><span style="color: hsl(120, 100%, 40%);">+                    'peer_port': int(cell['zmq_ue_bind_port']),</span><br><span style="color: hsl(120, 100%, 40%);">+                    'use_mimo': True if enb.num_ports() > 1 else False</span><br><span style="color: hsl(120, 100%, 40%);">+                    }</span><br><span style="color: hsl(120, 100%, 40%);">+            res.append(data)</span><br><span style="color: hsl(120, 100%, 40%);">+        return res</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def gen_json(self, enb):</span><br><span style="color: hsl(120, 100%, 40%);">+        res = {'enb': [self.gen_json_enb(enb)],</span><br><span style="color: hsl(120, 100%, 40%);">+               'ue': [self.gen_json_ue(enb)]}</span><br><span style="color: hsl(120, 100%, 40%);">+        return res</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def start(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.run_dir = util.Dir(self.testenv.test().get_run_dir().new_dir(self.name()))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        args = ('osmo-gsm-tester_zmq_broker.py',</span><br><span style="color: hsl(120, 100%, 40%);">+                '-c', str(self.ctrl_port),</span><br><span style="color: hsl(120, 100%, 40%);">+                '-b', self.enb.addr())</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if self.enb._run_node.is_local():</span><br><span style="color: hsl(120, 100%, 40%);">+            self.process = process.Process(self.name(), self.run_dir, args)</span><br><span style="color: hsl(120, 100%, 40%);">+        else:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.rem_host = remote.RemoteHost(self.run_dir, self.enb._run_node.ssh_user(), self.enb._run_node.ssh_addr())</span><br><span style="color: hsl(120, 100%, 40%);">+            self.process = self.rem_host.RemoteProcessSafeExit('zmq_gr_broker', util.Dir('/tmp/ogt_%s' % self.name()), args, wait_time_sec=7)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv.remember_to_stop(self.process)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.process.launch()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def setup(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.dbg('waiting for gr script to be available...')</span><br><span style="color: hsl(120, 100%, 40%);">+        MainLoop.sleep(5)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.ctrl_sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)</span><br><span style="color: hsl(120, 100%, 40%);">+        buf = json.dumps(self.cfg)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.send_cmd(buf)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def send_cmd(self, str_buf):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.dbg('sending cmd: "%s"' % str_buf)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.ctrl_sk.sendto(str_buf.encode('utf-8'), (self.addr, self.ctrl_port))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def cmd_set_relative_gain_on_local_port(self, port, rel_gain):</span><br><span style="color: hsl(120, 100%, 40%);">+        d = { 'action': 'set_relative_gain',</span><br><span style="color: hsl(120, 100%, 40%);">+              'port': port,</span><br><span style="color: hsl(120, 100%, 40%);">+              'rel_gain': rel_gain</span><br><span style="color: hsl(120, 100%, 40%);">+            }</span><br><span style="color: hsl(120, 100%, 40%);">+        buf = json.dumps(d)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.send_cmd(buf)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def cmd_exit(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        d = { 'action': 'exit' }</span><br><span style="color: hsl(120, 100%, 40%);">+        buf = json.dumps(d)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.send_cmd(buf)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class RFemulationGnuradioZmq(RFemulation):</span><br><span style="color: hsl(120, 100%, 40%);">+##############</span><br><span style="color: hsl(120, 100%, 40%);">+# PROTECTED</span><br><span style="color: hsl(120, 100%, 40%);">+##############</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, conf):</span><br><span style="color: hsl(120, 100%, 40%);">+        super().__init__(conf, 'gnuradio_zmq')</span><br><span style="color: hsl(120, 100%, 40%);">+        self.broker = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.ctrl_port = 5005</span><br><span style="color: hsl(120, 100%, 40%);">+        self.cell_id = int(conf.get('cell_id'))</span><br><span style="color: hsl(120, 100%, 40%);">+        if self.cell_id is None:</span><br><span style="color: hsl(120, 100%, 40%);">+            raise log.Error('No "cell_id" attribute provided in rfemu conf!')</span><br><span style="color: hsl(120, 100%, 40%);">+        self.enb = conf.get('enb')</span><br><span style="color: hsl(120, 100%, 40%);">+        if self.enb is None:</span><br><span style="color: hsl(120, 100%, 40%);">+            raise log.Error('No "srsenb" attribute provided in rfemu conf!')</span><br><span style="color: hsl(120, 100%, 40%);">+        self.set_name('%s_%s_%d' % (self.name(), self.enb.name(), self.cell_id))</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv = self.enb.testenv</span><br><span style="color: hsl(120, 100%, 40%);">+        self.configure()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def __del__(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        if self.broker:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.broker.unref()</span><br><span style="color: hsl(120, 100%, 40%);">+            self.broker = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.enb = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def configure(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.broker = GrBroker.ref()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+#############################</span><br><span style="color: hsl(120, 100%, 40%);">+# PUBLIC (test API included)</span><br><span style="color: hsl(120, 100%, 40%);">+#############################</span><br><span style="color: hsl(120, 100%, 40%);">+    def set_attenuation(self, db):</span><br><span style="color: hsl(120, 100%, 40%);">+        for cell in self.enb.gen_conf['enb']['cell_list']:</span><br><span style="color: hsl(120, 100%, 40%);">+            if int(cell['cell_id']) == self.cell_id:</span><br><span style="color: hsl(120, 100%, 40%);">+                max_att_db = self.get_max_attenuation()</span><br><span style="color: hsl(120, 100%, 40%);">+                self.broker.cmd_set_relative_gain_on_local_port(cell['zmq_enb_peer_port'], (max_att_db - db)/max_att_db)</span><br><span style="color: hsl(120, 100%, 40%);">+                break</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def get_max_attenuation(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        return 12 # maximum cell_gain value in srs. Is this correct value?</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+# vim: expandtab tabstop=4 shiftwidth=4</span><br><span>diff --git a/sysmocom/scenarios/mod-enb-cells-2ca.conf b/sysmocom/scenarios/mod-enb-cells-2ca.conf</span><br><span>index 1dbea73..bdc458b 100644</span><br><span>--- a/sysmocom/scenarios/mod-enb-cells-2ca.conf</span><br><span>+++ b/sysmocom/scenarios/mod-enb-cells-2ca.conf</span><br><span>@@ -10,7 +10,7 @@</span><br><span>       ncell_list: [0x01]</span><br><span>     - cell_id: 0x01</span><br><span>       pci: 0x02</span><br><span style="color: hsl(0, 100%, 40%);">-      dl_earfcn: 3050</span><br><span style="color: hsl(120, 100%, 40%);">+      dl_earfcn: 2850</span><br><span>       rf_port: 1</span><br><span>       scell_list: [0x00]</span><br><span>       ncell_list: [0x00]</span><br><span>diff --git a/utils/bin/osmo-gsm-tester_zmq_broker.py b/utils/bin/osmo-gsm-tester_zmq_broker.py</span><br><span>new file mode 100755</span><br><span>index 0000000..3681e7e</span><br><span>--- /dev/null</span><br><span>+++ b/utils/bin/osmo-gsm-tester_zmq_broker.py</span><br><span>@@ -0,0 +1,190 @@</span><br><span style="color: hsl(120, 100%, 40%);">+#!/usr/bin/env python2</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+from distutils.version import StrictVersion</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+from gnuradio.fft import window</span><br><span style="color: hsl(120, 100%, 40%);">+from gnuradio import blocks</span><br><span style="color: hsl(120, 100%, 40%);">+from gnuradio import gr</span><br><span style="color: hsl(120, 100%, 40%);">+from gnuradio.filter import firdes</span><br><span style="color: hsl(120, 100%, 40%);">+import sys</span><br><span style="color: hsl(120, 100%, 40%);">+import json</span><br><span style="color: hsl(120, 100%, 40%);">+from argparse import ArgumentParser</span><br><span style="color: hsl(120, 100%, 40%);">+from gnuradio.eng_arg import eng_float, intx</span><br><span style="color: hsl(120, 100%, 40%);">+from gnuradio import eng_notation</span><br><span style="color: hsl(120, 100%, 40%);">+from gnuradio import zeromq</span><br><span style="color: hsl(120, 100%, 40%);">+import socket</span><br><span style="color: hsl(120, 100%, 40%);">+import argparse</span><br><span style="color: hsl(120, 100%, 40%);">+from signal import *</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class GrBroker(gr.top_block):</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, args, cfg):</span><br><span style="color: hsl(120, 100%, 40%);">+        gr.top_block.__init__(self, "Intra Handover Flowgraph")</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        ##################################################</span><br><span style="color: hsl(120, 100%, 40%);">+        # Variables</span><br><span style="color: hsl(120, 100%, 40%);">+        ##################################################</span><br><span style="color: hsl(120, 100%, 40%);">+        self.args = args</span><br><span style="color: hsl(120, 100%, 40%);">+        self.cfg = cfg</span><br><span style="color: hsl(120, 100%, 40%);">+        self.samp_rate = samp_rate = 23040000</span><br><span style="color: hsl(120, 100%, 40%);">+        self.relative_gain = relative_gain = 1.0</span><br><span style="color: hsl(120, 100%, 40%);">+        self.blocks_add = {}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        ##################################################</span><br><span style="color: hsl(120, 100%, 40%);">+        # Blocks</span><br><span style="color: hsl(120, 100%, 40%);">+        ##################################################</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # Build ENB side + connect to per stream multilier:</span><br><span style="color: hsl(120, 100%, 40%);">+        for enb in self.cfg['enb']:</span><br><span style="color: hsl(120, 100%, 40%);">+            for it in enb:</span><br><span style="color: hsl(120, 100%, 40%);">+                source_addr = 'tcp://%s:%u' % (it['peer_addr'].encode('utf-8'), it['peer_port'])</span><br><span style="color: hsl(120, 100%, 40%);">+                sink_addr = 'tcp://%s:%u' % (args.bind_addr, it['bind_port'])</span><br><span style="color: hsl(120, 100%, 40%);">+                print('enb: earfcn=%u source=%r sink=%r' % (it['earfcn'], source_addr, sink_addr))</span><br><span style="color: hsl(120, 100%, 40%);">+                it['gr_block_zmq_source'] = zeromq.req_source(gr.sizeof_gr_complex, 1, source_addr, 100, False, -1)</span><br><span style="color: hsl(120, 100%, 40%);">+                it['gr_block_zmq_sink'] = zeromq.rep_sink(gr.sizeof_gr_complex, 1, sink_addr, 100, False, -1)</span><br><span style="color: hsl(120, 100%, 40%);">+                it['gr_block_multiply'] = blocks.multiply_const_cc(relative_gain)</span><br><span style="color: hsl(120, 100%, 40%);">+                it['gr_block_multiply'].set_block_alias('relative_gain %s' % source_addr)</span><br><span style="color: hsl(120, 100%, 40%);">+                self.connect((it['gr_block_zmq_source'], 0), (it['gr_block_multiply'], 0))</span><br><span style="color: hsl(120, 100%, 40%);">+                if it['use_mimo']:</span><br><span style="color: hsl(120, 100%, 40%);">+                    source_addr = 'tcp://%s:%u' % (it['peer_addr'].encode('utf-8'), it['peer_port'] + 1)</span><br><span style="color: hsl(120, 100%, 40%);">+                    sink_addr = 'tcp://%s:%u' % (args.bind_addr, it['bind_port'] + 1)</span><br><span style="color: hsl(120, 100%, 40%);">+                    print('enb: earfcn=%u source=%r sink=%r (MIMO)' % (it['earfcn'], source_addr, sink_addr))</span><br><span style="color: hsl(120, 100%, 40%);">+                    it['gr_block_zmq_source2'] = zeromq.req_source(gr.sizeof_gr_complex, 1, source_addr, 100, False, -1)</span><br><span style="color: hsl(120, 100%, 40%);">+                    it['gr_block_zmq_sink2'] = zeromq.rep_sink(gr.sizeof_gr_complex, 1, sink_addr, 100, False, -1)</span><br><span style="color: hsl(120, 100%, 40%);">+                    it['gr_block_multiply2'] = blocks.multiply_const_cc(relative_gain)</span><br><span style="color: hsl(120, 100%, 40%);">+                    it['gr_block_multiply2'].set_block_alias('relative_gain %s' % source_addr)</span><br><span style="color: hsl(120, 100%, 40%);">+                    self.connect((it['gr_block_zmq_source2'], 0), (it['gr_block_multiply2'], 0))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # Build UE side:</span><br><span style="color: hsl(120, 100%, 40%);">+        for ue in self.cfg['ue']:</span><br><span style="color: hsl(120, 100%, 40%);">+            for it in ue:</span><br><span style="color: hsl(120, 100%, 40%);">+                source_addr = 'tcp://%s:%u' % (it['peer_addr'].encode('utf-8'), it['peer_port'])</span><br><span style="color: hsl(120, 100%, 40%);">+                sink_addr = 'tcp://%s:%u' % (args.bind_addr, it['bind_port'])</span><br><span style="color: hsl(120, 100%, 40%);">+                print('ue: earfcn=%u source=%r sink=%r' % (it['earfcn'], source_addr, sink_addr))</span><br><span style="color: hsl(120, 100%, 40%);">+                it['gr_block_zmq_source'] = zeromq.req_source(gr.sizeof_gr_complex, 1, source_addr, 100, False, -1)</span><br><span style="color: hsl(120, 100%, 40%);">+                it['gr_block_zmq_sink'] = zeromq.rep_sink(gr.sizeof_gr_complex, 1, sink_addr, 100, False, -1)</span><br><span style="color: hsl(120, 100%, 40%);">+                if it['use_mimo']:</span><br><span style="color: hsl(120, 100%, 40%);">+                    source_addr = 'tcp://%s:%u' % (it['peer_addr'].encode('utf-8'), it['peer_port'] + 1)</span><br><span style="color: hsl(120, 100%, 40%);">+                    sink_addr = 'tcp://%s:%u' % (args.bind_addr, it['bind_port'] + 1)</span><br><span style="color: hsl(120, 100%, 40%);">+                    print('ue: earfcn=%u source=%r sink=%r (MIMO)' % (it['earfcn'], source_addr, sink_addr))</span><br><span style="color: hsl(120, 100%, 40%);">+                    it['gr_block_zmq_source2'] = zeromq.req_source(gr.sizeof_gr_complex, 1, source_addr, 100, False, -1)</span><br><span style="color: hsl(120, 100%, 40%);">+                    it['gr_block_zmq_sink2'] = zeromq.rep_sink(gr.sizeof_gr_complex, 1, sink_addr, 100, False, -1)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # Create per EARFCN adder (only 2->1 supported so far)</span><br><span style="color: hsl(120, 100%, 40%);">+        earfcn_li = self.calc_earfcn_list()</span><br><span style="color: hsl(120, 100%, 40%);">+        blocks_add_next_avail_port = {}</span><br><span style="color: hsl(120, 100%, 40%);">+        for earfcn in earfcn_li:</span><br><span style="color: hsl(120, 100%, 40%);">+                self.blocks_add[earfcn] = blocks.add_vcc(1)</span><br><span style="color: hsl(120, 100%, 40%);">+                blocks_add_next_avail_port[earfcn] = 0</span><br><span style="color: hsl(120, 100%, 40%);">+        # Connect the ENB-side multipliers to the Adder input ports:</span><br><span style="color: hsl(120, 100%, 40%);">+        idx = 0</span><br><span style="color: hsl(120, 100%, 40%);">+        for enb in self.cfg['enb']:</span><br><span style="color: hsl(120, 100%, 40%);">+            for it in enb:</span><br><span style="color: hsl(120, 100%, 40%);">+                print('Connecting ENB port %u to Adder[%u] for earfcn %u' % (it['bind_port'], blocks_add_next_avail_port[earfcn], it['earfcn']))</span><br><span style="color: hsl(120, 100%, 40%);">+                self.connect((it['gr_block_multiply'], 0), (self.blocks_add[it['earfcn']], blocks_add_next_avail_port[earfcn]))</span><br><span style="color: hsl(120, 100%, 40%);">+                # TODO: if it['use_mimo'], connect it['gr_block_multiply2'] to some adder...</span><br><span style="color: hsl(120, 100%, 40%);">+                blocks_add_next_avail_port[earfcn] += 1</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # Connect the Adder to the UE-side (Dl):</span><br><span style="color: hsl(120, 100%, 40%);">+        for earfcn, bl_add in self.blocks_add.items():</span><br><span style="color: hsl(120, 100%, 40%);">+            for ue in self.cfg['ue']:</span><br><span style="color: hsl(120, 100%, 40%);">+                for it in ue:</span><br><span style="color: hsl(120, 100%, 40%);">+                    if it['earfcn'] != earfcn:</span><br><span style="color: hsl(120, 100%, 40%);">+                        continue</span><br><span style="color: hsl(120, 100%, 40%);">+                    print('Connecting Adder for earfcn %u to UE port %u' % (earfcn, it['bind_port']))</span><br><span style="color: hsl(120, 100%, 40%);">+                    self.connect((bl_add, 0), (it['gr_block_zmq_sink'], 0))</span><br><span style="color: hsl(120, 100%, 40%);">+                    # TODO: if it['use_mimo'], connect some adder to it['gr_block_zmq_sink2']...</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # UL: Connect 1 UE port splitting it into N ENB ports:</span><br><span style="color: hsl(120, 100%, 40%);">+        for ue in self.cfg['ue']:</span><br><span style="color: hsl(120, 100%, 40%);">+            for it_ue in ue:</span><br><span style="color: hsl(120, 100%, 40%);">+                for enb in self.cfg['enb']:</span><br><span style="color: hsl(120, 100%, 40%);">+                    for it_enb in enb:</span><br><span style="color: hsl(120, 100%, 40%);">+                        if it_ue['earfcn'] != it_enb['earfcn']:</span><br><span style="color: hsl(120, 100%, 40%);">+                            continue</span><br><span style="color: hsl(120, 100%, 40%);">+                        print('connecting UE port %u to ENB port %u, earfcn=%u' % (it_ue['bind_port'], it_enb['bind_port'], it_enb['earfcn']))</span><br><span style="color: hsl(120, 100%, 40%);">+                        self.connect((it_ue['gr_block_zmq_source'], 0), (it_enb['gr_block_zmq_sink'], 0))</span><br><span style="color: hsl(120, 100%, 40%);">+                        if it_ue['use_mimo'] and it_enb['use_mimo']:</span><br><span style="color: hsl(120, 100%, 40%);">+                            self.connect((it_ue['gr_block_zmq_source2'], 0), (it_enb['gr_block_zmq_sink2'], 0))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def calc_earfcn_list(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        earfcn_li = []</span><br><span style="color: hsl(120, 100%, 40%);">+        for enb in self.cfg['enb']:</span><br><span style="color: hsl(120, 100%, 40%);">+            for it in enb:</span><br><span style="color: hsl(120, 100%, 40%);">+                if it['earfcn'] not in earfcn_li:</span><br><span style="color: hsl(120, 100%, 40%);">+                    earfcn_li.append(it['earfcn'])</span><br><span style="color: hsl(120, 100%, 40%);">+        return earfcn_li</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def set_relative_gain(self, port, relative_gain):</span><br><span style="color: hsl(120, 100%, 40%);">+        for enb in self.cfg['enb']:</span><br><span style="color: hsl(120, 100%, 40%);">+            for it in enb:</span><br><span style="color: hsl(120, 100%, 40%);">+                if it['bind_port'] == port:</span><br><span style="color: hsl(120, 100%, 40%);">+                    print('setting port %u rel_gain to %f' % (port, relative_gain))</span><br><span style="color: hsl(120, 100%, 40%);">+                    it['gr_block_multiply'].set_k(relative_gain)</span><br><span style="color: hsl(120, 100%, 40%);">+                    return</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def mainloop(sock, broker):</span><br><span style="color: hsl(120, 100%, 40%);">+    while True:</span><br><span style="color: hsl(120, 100%, 40%);">+        chunk = sock.recv(4096)</span><br><span style="color: hsl(120, 100%, 40%);">+        stringdata = chunk.decode('utf-8')</span><br><span style="color: hsl(120, 100%, 40%);">+        msg = json.loads(stringdata)</span><br><span style="color: hsl(120, 100%, 40%);">+        print('Received msg: %s' % msg)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if msg['action'] == 'exit':</span><br><span style="color: hsl(120, 100%, 40%);">+            print('Received exit command. Stopping radio...')</span><br><span style="color: hsl(120, 100%, 40%);">+            return</span><br><span style="color: hsl(120, 100%, 40%);">+        elif msg['action'] == 'set_relative_gain':</span><br><span style="color: hsl(120, 100%, 40%);">+            broker.set_relative_gain(msg['port'], msg['rel_gain'])</span><br><span style="color: hsl(120, 100%, 40%);">+        else:</span><br><span style="color: hsl(120, 100%, 40%);">+            print('Unknwon action for message: %s' % msg)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def sig_handler_cleanup(signum, frame):</span><br><span style="color: hsl(120, 100%, 40%);">+    print("killed by signal %d" % signum)</span><br><span style="color: hsl(120, 100%, 40%);">+    # This sys.exit() will raise a SystemExit base exception at the current</span><br><span style="color: hsl(120, 100%, 40%);">+    # point of execution. Code must be prepared to clean system-wide resources</span><br><span style="color: hsl(120, 100%, 40%);">+    # by using the "finally" section. This allows at the end 'atexit' hooks to</span><br><span style="color: hsl(120, 100%, 40%);">+    # be called before exiting.</span><br><span style="color: hsl(120, 100%, 40%);">+    sys.exit(1)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def main():</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    for sig in (SIGINT, SIGTERM, SIGQUIT, SIGPIPE, SIGHUP):</span><br><span style="color: hsl(120, 100%, 40%);">+        signal(sig, sig_handler_cleanup)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    parser = argparse.ArgumentParser()</span><br><span style="color: hsl(120, 100%, 40%);">+    parser.add_argument('-b', '--bind-addr', dest='bind_addr', help="Address where local sockets are bound to")</span><br><span style="color: hsl(120, 100%, 40%);">+    parser.add_argument('-c', '--ctrl-port', dest='ctrl_port', type=int, default=5005, help="Port where CTRL interface is bound to")</span><br><span style="color: hsl(120, 100%, 40%);">+    args = parser.parse_args()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    print('bind_addr:', repr(args.bind_addr))</span><br><span style="color: hsl(120, 100%, 40%);">+    print('ctrl_port:', repr(args.ctrl_port))</span><br><span style="color: hsl(120, 100%, 40%);">+    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)</span><br><span style="color: hsl(120, 100%, 40%);">+    sock.bind((args.bind_addr, args.ctrl_port))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    broker = None</span><br><span style="color: hsl(120, 100%, 40%);">+    try:</span><br><span style="color: hsl(120, 100%, 40%);">+        print('waiting for configuration on UDP socket...')</span><br><span style="color: hsl(120, 100%, 40%);">+        chunk = sock.recv(4096)</span><br><span style="color: hsl(120, 100%, 40%);">+        print('Received udp packet')</span><br><span style="color: hsl(120, 100%, 40%);">+        stringdata = chunk.decode('utf-8')</span><br><span style="color: hsl(120, 100%, 40%);">+        cfg = json.loads(stringdata)</span><br><span style="color: hsl(120, 100%, 40%);">+        print('Got config:', stringdata)</span><br><span style="color: hsl(120, 100%, 40%);">+        broker = GrBroker(args, cfg)</span><br><span style="color: hsl(120, 100%, 40%);">+        print('Starting...')</span><br><span style="color: hsl(120, 100%, 40%);">+        broker.start()</span><br><span style="color: hsl(120, 100%, 40%);">+        print('in mainloop')</span><br><span style="color: hsl(120, 100%, 40%);">+        mainloop(sock, broker)</span><br><span style="color: hsl(120, 100%, 40%);">+    except KeyboardInterrupt:</span><br><span style="color: hsl(120, 100%, 40%);">+        pass</span><br><span style="color: hsl(120, 100%, 40%);">+    print('main loop ended, exiting...')</span><br><span style="color: hsl(120, 100%, 40%);">+    # closing flowgraph and socket</span><br><span style="color: hsl(120, 100%, 40%);">+    sock.close()</span><br><span style="color: hsl(120, 100%, 40%);">+    if broker:</span><br><span style="color: hsl(120, 100%, 40%);">+        broker.stop()</span><br><span style="color: hsl(120, 100%, 40%);">+        broker.wait()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+if __name__ == '__main__':</span><br><span style="color: hsl(120, 100%, 40%);">+    main()</span><br><span style="color: hsl(120, 100%, 40%);">+    print("exit")</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/c/osmo-gsm-tester/+/20521">change 20521</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.osmocom.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.osmocom.org/c/osmo-gsm-tester/+/20521"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: osmo-gsm-tester </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-Change-Id: I6282cda400558dcb356276786d91e6388524c5b1 </div>
<div style="display:none"> Gerrit-Change-Number: 20521 </div>
<div style="display:none"> Gerrit-PatchSet: 5 </div>
<div style="display:none"> Gerrit-Owner: pespin <pespin@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins Builder </div>
<div style="display:none"> Gerrit-Reviewer: pespin <pespin@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: srs_andre <andre@softwareradiosystems.com> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>