<p>laforge <strong>submitted</strong> this change.</p><p><a href="https://gerrit.osmocom.org/c/mncc-python/+/17403">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  laforge: Looks good to me, approved; Verified

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">mncc_mt_loadgen.py: Program for MT call load testing with rtpsource<br><br>This program (derived from mncc_test.py) is used to start MT calls<br>via the MNCC interface of OsmoMSC.  Every calls RTP is connected<br>to the new 'rtpsource' process, which generates a realistic RTP<br>flow in terms of number of packets (20ms interval) and payload size.<br><br>Change-Id: I9e5d799aaeeff5188dc97061f0d6e1873d9bf653<br>---<br>A ctrl.py<br>A mncc_mt_loadgen.py<br>2 files changed, 258 insertions(+), 0 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/ctrl.py b/ctrl.py</span><br><span>new file mode 100644</span><br><span>index 0000000..6f51684</span><br><span>--- /dev/null</span><br><span>+++ b/ctrl.py</span><br><span>@@ -0,0 +1,70 @@</span><br><span style="color: hsl(120, 100%, 40%);">+# Simple class for synchronous/blocking interface with a remote</span><br><span style="color: hsl(120, 100%, 40%);">+# Osmocom program via the CTRL interface</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+# (C) 2020 by Harald Welte <laforge@gnumonks.org></span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# Licensed under GNU General Public License, Version 2 or at your</span><br><span style="color: hsl(120, 100%, 40%);">+# option, any later version.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+from osmopy.osmo_ipa import Ctrl</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%);">+class OsmoCtrlSimple:</span><br><span style="color: hsl(120, 100%, 40%);">+    '''Simple class for synchronous/blocking interface with a remote</span><br><span style="color: hsl(120, 100%, 40%);">+       Osmocom program via the CTRL interface'''</span><br><span style="color: hsl(120, 100%, 40%);">+    remote_host = "127.0.0.1"</span><br><span style="color: hsl(120, 100%, 40%);">+    remote_port = 0</span><br><span style="color: hsl(120, 100%, 40%);">+    sock = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, host=None, port=None):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.remote_host = host</span><br><span style="color: hsl(120, 100%, 40%);">+        self.remote_port = port</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def connect(self, host=None, port=None):</span><br><span style="color: hsl(120, 100%, 40%);">+        if host:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.remote_host = host</span><br><span style="color: hsl(120, 100%, 40%);">+        if port:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.remote_port = port</span><br><span style="color: hsl(120, 100%, 40%);">+        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.sock.setblocking(1)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.sock.connect((self.remote_host, self.remote_port))</span><br><span style="color: hsl(120, 100%, 40%);">+        return self.sock</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def disconnect(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.sock.close()</span><br><span style="color: hsl(120, 100%, 40%);">+        self.sock = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def _leftovers(self, fl):</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        Read outstanding data if any according to flags</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        try:</span><br><span style="color: hsl(120, 100%, 40%);">+                data = self.sock.recv(1024, fl)</span><br><span style="color: hsl(120, 100%, 40%);">+        except socket.error as _:</span><br><span style="color: hsl(120, 100%, 40%);">+                return False</span><br><span style="color: hsl(120, 100%, 40%);">+        if len(data) != 0:</span><br><span style="color: hsl(120, 100%, 40%);">+                tail = data</span><br><span style="color: hsl(120, 100%, 40%);">+                while True:</span><br><span style="color: hsl(120, 100%, 40%);">+                        (head, tail) = Ctrl().split_combined(tail)</span><br><span style="color: hsl(120, 100%, 40%);">+                        print("Got message:", Ctrl().rem_header(head))</span><br><span style="color: hsl(120, 100%, 40%);">+                        if len(tail) == 0:</span><br><span style="color: hsl(120, 100%, 40%);">+                                break</span><br><span style="color: hsl(120, 100%, 40%);">+                return True</span><br><span style="color: hsl(120, 100%, 40%);">+        return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def do_set_get(self, var, value = None):</span><br><span style="color: hsl(120, 100%, 40%);">+        self._leftovers(socket.MSG_DONTWAIT)</span><br><span style="color: hsl(120, 100%, 40%);">+        (r, c) = Ctrl().cmd(var, value)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.sock.send(c)</span><br><span style="color: hsl(120, 100%, 40%);">+        while True:</span><br><span style="color: hsl(120, 100%, 40%);">+                ret = self.sock.recv(4096)</span><br><span style="color: hsl(120, 100%, 40%);">+                # handle multiple messages, ignore TRAPs</span><br><span style="color: hsl(120, 100%, 40%);">+                ret = Ctrl().skip_traps(ret)</span><br><span style="color: hsl(120, 100%, 40%);">+                if ret != None:</span><br><span style="color: hsl(120, 100%, 40%);">+                    (i, k, v) = Ctrl().parse(ret)</span><br><span style="color: hsl(120, 100%, 40%);">+                    break;</span><br><span style="color: hsl(120, 100%, 40%);">+        return (Ctrl().rem_header(ret),) + Ctrl().verify(ret, r, var, value)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def set_var(self, var, val):</span><br><span style="color: hsl(120, 100%, 40%);">+        (a, _, _) = self.do_set_get(var, val)</span><br><span style="color: hsl(120, 100%, 40%);">+        return a</span><br><span>diff --git a/mncc_mt_loadgen.py b/mncc_mt_loadgen.py</span><br><span>new file mode 100755</span><br><span>index 0000000..a7daabf</span><br><span>--- /dev/null</span><br><span>+++ b/mncc_mt_loadgen.py</span><br><span>@@ -0,0 +1,188 @@</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%);">+# Python testing tool for establishing mobile-terminated calls using</span><br><span style="color: hsl(120, 100%, 40%);">+# the OsmoMSC MNCC interface.  It wants to communicate via CTRL with a</span><br><span style="color: hsl(120, 100%, 40%);">+# 'rtpsoucre' program which will take care of generating RtP flows</span><br><span style="color: hsl(120, 100%, 40%);">+# for the MT calls that are established.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# (C) 2015, 2020 by Harald Welte <laforge@gnumonks.org></span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# Licensed under GNU General Public License, Version 2 or at your</span><br><span style="color: hsl(120, 100%, 40%);">+# option, any later version.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+RTPSOURCE_CTRL_IP = "127.0.0.1"</span><br><span style="color: hsl(120, 100%, 40%);">+RTPSOURCE_CTRL_PORT = 11111</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%);">+from gsm_call_fsm import GsmCallFsm, GSM48</span><br><span style="color: hsl(120, 100%, 40%);">+from mncc_sock import MnccSocket</span><br><span style="color: hsl(120, 100%, 40%);">+from thread import start_new_thread</span><br><span style="color: hsl(120, 100%, 40%);">+from ctrl import OsmoCtrlSimple</span><br><span style="color: hsl(120, 100%, 40%);">+import pykka</span><br><span style="color: hsl(120, 100%, 40%);">+import logging as log</span><br><span style="color: hsl(120, 100%, 40%);">+import signal, sys, time</span><br><span style="color: hsl(120, 100%, 40%);">+import readline, code</span><br><span style="color: hsl(120, 100%, 40%);">+import socket</span><br><span style="color: hsl(120, 100%, 40%);">+import struct</span><br><span style="color: hsl(120, 100%, 40%);">+import mncc</span><br><span style="color: hsl(120, 100%, 40%);">+from mncc_sock import mncc_rtp_msg</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def int2ipstr(addr):</span><br><span style="color: hsl(120, 100%, 40%);">+    '''Convert from an integer to a strinf-formatted IPv4 address'''</span><br><span style="color: hsl(120, 100%, 40%);">+    return socket.inet_ntoa(struct.pack("!I", addr))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def ipstr2int(addr):</span><br><span style="color: hsl(120, 100%, 40%);">+    '''Convert from a string-formatted IPv4 address to integer'''</span><br><span style="color: hsl(120, 100%, 40%);">+    return struct.unpack("!I", socket.inet_aton(addr))[0]</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 MnccActor(pykka.ThreadingActor):</span><br><span style="color: hsl(120, 100%, 40%);">+    '''MnccActor provides an interface for GsmCallFsm to send MNCC messages'''</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, mncc_sock):</span><br><span style="color: hsl(120, 100%, 40%);">+        super(MnccActor, self).__init__()</span><br><span style="color: hsl(120, 100%, 40%);">+        self.mncc_sock = mncc_sock</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def on_receive(self, message):</span><br><span style="color: hsl(120, 100%, 40%);">+        if message['type'] == 'send':</span><br><span style="color: hsl(120, 100%, 40%);">+            msg = message['msg']</span><br><span style="color: hsl(120, 100%, 40%);">+            log.debug('MnccActor TxMNCC %s' % msg)</span><br><span style="color: hsl(120, 100%, 40%);">+            mncc_sock.send(msg)</span><br><span style="color: hsl(120, 100%, 40%);">+        else:</span><br><span style="color: hsl(120, 100%, 40%);">+            raise Exception('mncc', 'MnccActor Received unhandled %s' % message)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def mncc_rx_thread(mncc_sock):</span><br><span style="color: hsl(120, 100%, 40%);">+    '''MNCC receive thread, broadcasting received MNCC packets to GsmCallFsm'''</span><br><span style="color: hsl(120, 100%, 40%);">+    while 1:</span><br><span style="color: hsl(120, 100%, 40%);">+        msg = mncc_sock.recv()</span><br><span style="color: hsl(120, 100%, 40%);">+        if msg == None:</span><br><span style="color: hsl(120, 100%, 40%);">+            raise Exception('mncc', 'MnccSocket disconnected')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if msg.is_frame():</span><br><span style="color: hsl(120, 100%, 40%);">+            log.warning("Dropping traffic frame: %s" % msg)</span><br><span style="color: hsl(120, 100%, 40%);">+            continue</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        log.debug("MnccActor RxMNCC %s, broadcasting to Call FSMs" % msg)</span><br><span style="color: hsl(120, 100%, 40%);">+        # we simply broadcast to all calls</span><br><span style="color: hsl(120, 100%, 40%);">+        pykka.ActorRegistry.broadcast({'type': 'mncc', 'msg': msg}, GsmCallFsm)</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 RtpSourceCtrlActor(pykka.ThreadingActor):</span><br><span style="color: hsl(120, 100%, 40%);">+    '''Actor for talking with the rtpsource program via an osmocom CTRL interface'''</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, ctrl_host, ctrl_port=11111):</span><br><span style="color: hsl(120, 100%, 40%);">+        super(RtpSourceCtrlActor, self).__init__()</span><br><span style="color: hsl(120, 100%, 40%);">+        self.ctrl = OsmoCtrlSimple(ctrl_host, ctrl_port)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.ctrl.connect()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def _set_var(self, var, val):</span><br><span style="color: hsl(120, 100%, 40%);">+        (res, var, val) = self.ctrl.do_set_get(var, val)</span><br><span style="color: hsl(120, 100%, 40%);">+        log.debug('RtpSourceCtrlActor result=%s var=%s val=%s' % (res, var, val))</span><br><span style="color: hsl(120, 100%, 40%);">+        return (res, var, val)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def on_receive(self, message):</span><br><span style="color: hsl(120, 100%, 40%);">+        if message['type'] == 'rtp_create':</span><br><span style="color: hsl(120, 100%, 40%);">+            (res, var, val) = self._set_var('rtp_create', message['cname'])</span><br><span style="color: hsl(120, 100%, 40%);">+            v = val.split(',') # input looks like '1,127.23.23.23,37723'</span><br><span style="color: hsl(120, 100%, 40%);">+            return {'cname': v[0], 'remote_host': v[1], 'remote_port': int(v[2])}</span><br><span style="color: hsl(120, 100%, 40%);">+        elif message['type'] == 'rtp_connect':</span><br><span style="color: hsl(120, 100%, 40%);">+            val = '%s,%s,%u,%u' % (message['cname'], message['remote_host'],</span><br><span style="color: hsl(120, 100%, 40%);">+                                   message['remote_port'], message['payload_type'])</span><br><span style="color: hsl(120, 100%, 40%);">+            (res, var, val) = self._set_var('rtp_connect', val)</span><br><span style="color: hsl(120, 100%, 40%);">+            return res</span><br><span style="color: hsl(120, 100%, 40%);">+        elif message['type'] == 'rtp_delete':</span><br><span style="color: hsl(120, 100%, 40%);">+            (res, var, val) = self._set_var('rtp_delete', message['cname'])</span><br><span style="color: hsl(120, 100%, 40%);">+            return res</span><br><span style="color: hsl(120, 100%, 40%);">+        else:</span><br><span style="color: hsl(120, 100%, 40%);">+            raise Exception('ctrl', 'RtpSourceCtrlActor Received unhandled %s' % message)</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 MTCallRtpsource(pykka.ThreadingActor):</span><br><span style="color: hsl(120, 100%, 40%);">+    '''Actor to start a network-initiated MT (mobile terminated) call to a given MSISDN,</span><br><span style="color: hsl(120, 100%, 40%);">+       connecting the RTP flow to an extenal rtpsource program'''</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, mncc_act, ctrl_act, codecs_permitted = GSM48.AllCodecs):</span><br><span style="color: hsl(120, 100%, 40%);">+        super(MTCallRtpsource, self).__init__()</span><br><span style="color: hsl(120, 100%, 40%);">+        self.mncc_act = mncc_act</span><br><span style="color: hsl(120, 100%, 40%);">+        self.ctrl_act = ctrl_act</span><br><span style="color: hsl(120, 100%, 40%);">+        self.codecs_permitted = codecs_permitted</span><br><span style="color: hsl(120, 100%, 40%);">+        self.call = GsmCallFsm.start(self.mncc_act, self.actor_ref, True, self.codecs_permitted)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.callref = self.call.ask({'type':'get_callref'})</span><br><span style="color: hsl(120, 100%, 40%);">+        self.state = 'NULL'</span><br><span style="color: hsl(120, 100%, 40%);">+        self.rtp_msc = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def start_call(self, msisdn_called, msisdn_calling):</span><br><span style="color: hsl(120, 100%, 40%);">+        '''Start a MT call from given [external] calling party to given mobile party MSISDN'''</span><br><span style="color: hsl(120, 100%, 40%);">+        self.msisdn_called = msisdn_called</span><br><span style="color: hsl(120, 100%, 40%);">+        self.msisdn_calling = msisdn_calling</span><br><span style="color: hsl(120, 100%, 40%);">+        # allocate a RTP connection @ rtpsource</span><br><span style="color: hsl(120, 100%, 40%);">+        r = self.ctrl_act.ask({'type':'rtp_create', 'cname':self.callref})</span><br><span style="color: hsl(120, 100%, 40%);">+        self.ext_rtp_host = r['remote_host']</span><br><span style="color: hsl(120, 100%, 40%);">+        self.ext_rtp_port = r['remote_port']</span><br><span style="color: hsl(120, 100%, 40%);">+        # start the MNCC call FSM</span><br><span style="color: hsl(120, 100%, 40%);">+        self.call.tell({'type':'start_mt_call',</span><br><span style="color: hsl(120, 100%, 40%);">+                        'calling':self.msisdn_calling, 'called':self.msisdn_called})</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def on_stop(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        # Attempt to do a graceful shutdown by deleting the RTP connection from rtpsource</span><br><span style="color: hsl(120, 100%, 40%);">+        self.ctrl_act.ask({'type':'rtp_delete', 'cname':self.callref})</span><br><span style="color: hsl(120, 100%, 40%);">+        self.call.stop()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def on_failure(self, exception_type, exception_value, traceback):</span><br><span style="color: hsl(120, 100%, 40%);">+        # on_stop() is not called in case of failure; fix that</span><br><span style="color: hsl(120, 100%, 40%);">+        self.on_stop()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def on_receive(self, message):</span><br><span style="color: hsl(120, 100%, 40%);">+        if message['type'] == 'rtp_create_ind':</span><br><span style="color: hsl(120, 100%, 40%);">+            # MSC+MGW informs us of the PLMN side IP/port</span><br><span style="color: hsl(120, 100%, 40%);">+            mncc_rtp_ind = message['rtp']</span><br><span style="color: hsl(120, 100%, 40%);">+            # tell external rtpsourc to connect to this address</span><br><span style="color: hsl(120, 100%, 40%);">+            r = self.ctrl_act.ask({'type':'rtp_connect', 'cname':self.callref,</span><br><span style="color: hsl(120, 100%, 40%);">+                                   'remote_host': int2ipstr(mncc_rtp_ind.ip),</span><br><span style="color: hsl(120, 100%, 40%);">+                                   'remote_port': mncc_rtp_ind.port,</span><br><span style="color: hsl(120, 100%, 40%);">+                                   'payload_type':3})</span><br><span style="color: hsl(120, 100%, 40%);">+            # tell MSC to connect to ip/port @ rtpsource</span><br><span style="color: hsl(120, 100%, 40%);">+            mncc_rtp_conn = mncc_rtp_msg(msg_type=mncc.MNCC_RTP_CONNECT, callref=self.callref,</span><br><span style="color: hsl(120, 100%, 40%);">+                                         ip=ipstr2int(self.ext_rtp_host), port=self.ext_rtp_port)</span><br><span style="color: hsl(120, 100%, 40%);">+            self.mncc_act.tell({'type': 'send', 'msg': mncc_rtp_conn})</span><br><span style="color: hsl(120, 100%, 40%);">+        elif message['type'] == 'call_state_change':</span><br><span style="color: hsl(120, 100%, 40%);">+            if message['new_state'] == 'NULL':</span><br><span style="color: hsl(120, 100%, 40%);">+                # Call FSM has reached the NULL state again (call terminated) </span><br><span style="color: hsl(120, 100%, 40%);">+                # on_stop() will clean up the RTP connection at rtpsource</span><br><span style="color: hsl(120, 100%, 40%);">+                self.stop()</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%);">+# capture SIGINT (Ctrl+C) and stop all actors</span><br><span style="color: hsl(120, 100%, 40%);">+def sigint_handler(signum, frame):</span><br><span style="color: hsl(120, 100%, 40%);">+    pykka.ActorRegistry.stop_all()</span><br><span style="color: hsl(120, 100%, 40%);">+    sys.exit(0)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+log.basicConfig(level = log.DEBUG,</span><br><span style="color: hsl(120, 100%, 40%);">+    format = "%(levelname)s %(filename)s:%(lineno)d %(message)s")</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+signal.signal(signal.SIGINT, sigint_handler)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+# start the MnccSocket and associated pykka actor + rx thread</span><br><span style="color: hsl(120, 100%, 40%);">+mncc_sock = MnccSocket()</span><br><span style="color: hsl(120, 100%, 40%);">+mncc_act = MnccActor.start(mncc_sock)</span><br><span style="color: hsl(120, 100%, 40%);">+start_new_thread(mncc_rx_thread, (mncc_sock,))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+# connect via CTRL to rtpsource</span><br><span style="color: hsl(120, 100%, 40%);">+rtpctrl_act = RtpSourceCtrlActor.start(RTPSOURCE_CTRL_IP, RTPSOURCE_CTRL_PORT)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+# convenience wrapper</span><br><span style="color: hsl(120, 100%, 40%);">+def mt_call(msisdn_called, msisdn_calling = '123456789', codecs = GSM48.AllCodecs):</span><br><span style="color: hsl(120, 100%, 40%);">+    call_conn = MTCallRtpsource.start(mncc_act, rtpctrl_act, codecs).proxy()</span><br><span style="color: hsl(120, 100%, 40%);">+    call_conn.start_call(msisdn_called, msisdn_calling)</span><br><span style="color: hsl(120, 100%, 40%);">+    return call_conn</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+# start one call automatically</span><br><span style="color: hsl(120, 100%, 40%);">+mt_call('90001')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+# start a shell to enable the user to add more calls as needed</span><br><span style="color: hsl(120, 100%, 40%);">+vars = globals().copy()</span><br><span style="color: hsl(120, 100%, 40%);">+vars.update(locals())</span><br><span style="color: hsl(120, 100%, 40%);">+shell = code.InteractiveConsole(vars)</span><br><span style="color: hsl(120, 100%, 40%);">+try:</span><br><span style="color: hsl(120, 100%, 40%);">+    shell.interact()</span><br><span style="color: hsl(120, 100%, 40%);">+except SystemExit:</span><br><span style="color: hsl(120, 100%, 40%);">+    pass</span><br><span style="color: hsl(120, 100%, 40%);">+# clan up after user leaves interactive shell</span><br><span style="color: hsl(120, 100%, 40%);">+sigint_handler(signal.SIGINT, None)</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/c/mncc-python/+/17403">change 17403</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/mncc-python/+/17403"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: mncc-python </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-Change-Id: I9e5d799aaeeff5188dc97061f0d6e1873d9bf653 </div>
<div style="display:none"> Gerrit-Change-Number: 17403 </div>
<div style="display:none"> Gerrit-PatchSet: 3 </div>
<div style="display:none"> Gerrit-Owner: laforge <laforge@osmocom.org> </div>
<div style="display:none"> Gerrit-Reviewer: laforge <laforge@osmocom.org> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>