diff --git a/src/osmo_gsm_tester/obj/enb.py b/src/osmo_gsm_tester/obj/enb.py index 340ea3a6..59b5bfe6 100644 --- a/src/osmo_gsm_tester/obj/enb.py +++ b/src/osmo_gsm_tester/obj/enb.py @@ -113,6 +113,11 @@ class eNodeB(log.Origin, metaclass=ABCMeta): def ue_max_rate(self, downlink=True): pass + @abstractmethod + def get_rfemu(self, cell=0, dl=True): + 'Get rfemu.RFemulation subclass implementation object for given cell index and direction.' + pass + def addr(self): return self._addr diff --git a/src/osmo_gsm_tester/obj/enb_amarisoft.py b/src/osmo_gsm_tester/obj/enb_amarisoft.py index 996af19a..0b748e0d 100644 --- a/src/osmo_gsm_tester/obj/enb_amarisoft.py +++ b/src/osmo_gsm_tester/obj/enb_amarisoft.py @@ -22,6 +22,7 @@ import pprint from ..core import log, util, config, template, process, remote from . import enb +from . import rfemu def rf_type_valid(rf_type_str): return rf_type_str in ('uhd', 'zmq') @@ -62,6 +63,7 @@ class AmarisoftENB(enb.eNodeB): self.run_dir = None self.inst = None self._bin_prefix = None + self.gen_conf = None self.config_file = None self.config_sib1_file = None self.config_sib23_file = None @@ -209,6 +211,8 @@ class AmarisoftENB(enb.eNodeB): config.overlay(values, dict(trx=dict(rf_dev_type=values['enb'].get('rf_dev_type', None), rf_dev_args=values['enb'].get('rf_dev_args', None)))) + self.gen_conf = values + self.gen_conf_file(self.config_file, AmarisoftENB.CFGFILE, values) self.gen_conf_file(self.config_sib1_file, AmarisoftENB.CFGFILE_SIB1, values) self.gen_conf_file(self.config_sib23_file, AmarisoftENB.CFGFILE_SIB23, values) @@ -233,4 +237,19 @@ class AmarisoftENB(enb.eNodeB): def running(self): return not self.process.terminated() + def get_rfemu(self, cell=0, dl=True): + cell_list = self.gen_conf['enb'].get('cell_list', None) + if cell_list is None or len(cell_list) < cell + 1: + raise log.Error('cell_list attribute or subitem not found!') + rfemu_cfg = cell_list[cell].get('dl_rfemu', None) + if rfemu_cfg is None: # craft amarisfot by default: + rfemu_cfg = {'type': 'amarisoftctl', + 'addr': self.addr(), + 'ports': [9001] + } + if rfemu_cfg['type'] == 'amarisoftctl': # this one requires extra config: + config.overlay(rfemu_cfg, dict(cell_id=cell_list[cell]['cell_id'])) + rfemu_obj = rfemu.get_instance_by_type(rfemu_cfg['type'], rfemu_cfg) + return rfemu_obj + # vim: expandtab tabstop=4 shiftwidth=4 diff --git a/src/osmo_gsm_tester/obj/enb_srs.py b/src/osmo_gsm_tester/obj/enb_srs.py index c9721d14..e657b046 100644 --- a/src/osmo_gsm_tester/obj/enb_srs.py +++ b/src/osmo_gsm_tester/obj/enb_srs.py @@ -22,6 +22,7 @@ import pprint from ..core import log, util, config, template, process, remote from . import enb +from . import rfemu def rf_type_valid(rf_type_str): return rf_type_str in ('zmq', 'uhd', 'soapy', 'bladerf') @@ -60,6 +61,7 @@ class srsENB(enb.eNodeB): super().__init__(suite_run, conf, srsENB.BINFILE) self.ue = None self.run_dir = None + self.gen_conf = None self.config_file = None self.config_sib_file = None self.config_rr_file = None @@ -221,6 +223,8 @@ class srsENB(enb.eNodeB): config.overlay(values, dict(enb=dict(rf_dev_args=rf_dev_args))) + self.gen_conf = values + self.gen_conf_file(self.config_file, srsENB.CFGFILE, values) self.gen_conf_file(self.config_sib_file, srsENB.CFGFILE_SIB, values) self.gen_conf_file(self.config_rr_file, srsENB.CFGFILE_RR, values) @@ -243,4 +247,14 @@ class srsENB(enb.eNodeB): def running(self): return not self.process.terminated() + def get_rfemu(self, cell=0, dl=True): + cell_list = self.gen_conf['enb'].get('cell_list', None) + if cell_list is None or len(cell_list) < cell + 1: + raise log.Error('cell_list attribute or subitem not found!') + rfemu_cfg = cell_list[cell].get('dl_rfemu', None) + if rfemu_cfg is None: # craft amarisfot by default: + raise log.Error('rfemu attribute not found in cell_list item!') + rfemu_obj = rfemu.get_instance_by_type(rfemu_cfg['type'], rfemu_cfg) + return rfemu_obj + # vim: expandtab tabstop=4 shiftwidth=4 diff --git a/src/osmo_gsm_tester/obj/rfemu.py b/src/osmo_gsm_tester/obj/rfemu.py new file mode 100644 index 00000000..b2add6be --- /dev/null +++ b/src/osmo_gsm_tester/obj/rfemu.py @@ -0,0 +1,57 @@ +# osmo_gsm_tester: class defining a RF emulation object +# +# Copyright (C) 2020 by sysmocom - s.f.m.c. GmbH +# +# Author: Pau Espin Pedrol +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from abc import ABCMeta, abstractmethod +from ..core import log +from ..core.event_loop import MainLoop + +class RFemulation(log.Origin, metaclass=ABCMeta): + +############## +# PROTECTED +############## + def __init__(self, conf, name): + """Base constructor. Must be called by subclass.""" + super().__init__(log.C_RUN, name) + self.conf = conf + +############################# +# PUBLIC (test API included) +############################# + @abstractmethod + def set_attenuation(self, db): + """Set attenuation in dB on the configured channel""" + pass + +def get_instance_by_type(rfemu_type, rfemu_opt): + """Allocate a RFemulation child class based on type. Opts are passed to the newly created object.""" + if rfemu_type == 'amarisoftctl': + from .rfemu_amarisoftctrl import RFemulationAmarisoftCtrl + obj = RFemulationAmarisoftCtrl + elif rfemu_type == 'minicircuits': + from .rfemu_minicircuits import RFemulationMinicircuitsHTTP + obj = RFemulationMinicircuitsHTTP + else: + raise log.Error('RFemulation type not supported:', rfemu_type) + + return obj(rfemu_opt) + + + +# vim: expandtab tabstop=4 shiftwidth=4 diff --git a/src/osmo_gsm_tester/obj/rfemu_amarisoftctrl.py b/src/osmo_gsm_tester/obj/rfemu_amarisoftctrl.py new file mode 100644 index 00000000..87e4cbdd --- /dev/null +++ b/src/osmo_gsm_tester/obj/rfemu_amarisoftctrl.py @@ -0,0 +1,62 @@ +# osmo_gsm_tester: class defining a RF emulation object implemented using Amarisoft Ctl interface +# +# Copyright (C) 2020 by sysmocom - s.f.m.c. GmbH +# +# Author: Pau Espin Pedrol +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import json +from websocket import create_connection + +from ..core import log +from .rfemu import RFemulation + +class RFemulationAmarisoftCtrl(RFemulation): +############## +# PROTECTED +############## + def __init__(self, conf): + super().__init__(conf, 'amarisoftctl') + self.addr = conf.get('addr') + self.port = conf.get('ports') + if self.addr is None: + raise log.Error('No "addr" attribute provided in supply conf!') + if self.port is None or len(self.port) != 1: + raise log.Error('No "port" attribute provided in supply conf!') + self.port = self.port[0] + self.set_name('amarisoftctl(%s:%d)' % (self.addr, self.port)) + self.cell_id = conf.get('cell_id') + if self.cell_id is None: + raise log.Error('No "cell_id" attribute provided in supply conf!') + self.ws = create_connection("ws://%s:%s" % (self.addr, self.port)) + + def __del__(self): + self.dbg('closing CTRL websocket') + self.ws.close() + +############################# +# PUBLIC (test API included) +############################# + def set_attenuation(self, db): + msg = { "message": "cell_gain", "cell_id": int(self.cell_id), "gain": -db } + msg_str = json.dumps(msg) + self.dbg('sending CTRL msg: "%s"' % msg_str) + self.ws.send(msg_str) + self.dbg('waiting CTRL recv...') + result = self.ws.recv() + self.dbg('Received CTRL msg: "%s"' % result) + + +# vim: expandtab tabstop=4 shiftwidth=4 diff --git a/src/osmo_gsm_tester/obj/rfemu_minicircuits.py b/src/osmo_gsm_tester/obj/rfemu_minicircuits.py new file mode 100644 index 00000000..eea3a0ee --- /dev/null +++ b/src/osmo_gsm_tester/obj/rfemu_minicircuits.py @@ -0,0 +1,67 @@ +# osmo_gsm_tester: class defining a RF emulation object implemented using a Minicircuits RC4DAT-6G-60 device +# +# Copyright (C) 2020 by sysmocom - s.f.m.c. GmbH +# +# Author: Pau Espin Pedrol +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import urllib.request +import xml.etree.ElementTree as ET + +from ..core import log +from .rfemu import RFemulation + +# https://www.minicircuits.com/softwaredownload/Prog_Manual-6-Programmable_Attenuator.pdf +class RFemulationMinicircuitsHTTP(RFemulation): + + # HTTP request timeout, in seconds + HTTP_TIMEOUT = 5 + +############## +# PROTECTED +############## + def __init__(self, conf): + super().__init__(conf, 'minicircuits') + self.addr = conf.get('addr') + self.ports = conf.get('ports') + if self.addr is None: + raise log.Error('No "addr" attribute provided in supply conf!') + if self.ports is None or len(self.ports) == 0: + raise log.Error('No "port" attribute provided in supply conf!') + self.set_name('minicircuits(%s:%r)' % (self.addr, self.ports)) + + def _url_prefix(self): + #http://10.12.1.216/:SetAttPerChan:1:0_2:0_3:0_4:0 + return 'http://' + self.addr + + def _utl_set_attenauation(self, db): + ports_str = "" + for port in self.ports: + ports_str = ports_str + str(port) + ":" + + return self._url_prefix() + '/:CHAN:' + ports_str + 'SETATT:' + str(db) + +############################# +# PUBLIC (test API included) +############################# + def set_attenuation(self, db): + url = self._utl_set_attenauation(db) + self.dbg('sending HTTP req: "%s"' % url) + data = urllib.request.urlopen(url, timeout = self.HTTP_TIMEOUT).read() + data_str = str(data, 'utf-8') + self.dbg('Received response: "%s"' % data_str) + if data_str != '1': + raise log.Error('Mini-circuits attenuation device returned failure! %s' & data_str) +# vim: expandtab tabstop=4 shiftwidth=4 diff --git a/src/osmo_gsm_tester/resource.py b/src/osmo_gsm_tester/resource.py index efb7d5a1..d4757f3a 100644 --- a/src/osmo_gsm_tester/resource.py +++ b/src/osmo_gsm_tester/resource.py @@ -111,6 +111,9 @@ RESOURCES_SCHEMA = { 'enb[].cell_list[].cell_id': schema.UINT, 'enb[].cell_list[].scell_list[]': schema.UINT, 'enb[].cell_list[].dl_earfcn': schema.UINT, + 'enb[].cell_list[].dl_rfemu.type': schema.STR, + 'enb[].cell_list[].dl_rfemu.addr': schema.IPV4, + 'enb[].cell_list[].dl_rfemu.ports[]': schema.UINT, 'arfcn[].arfcn': schema.INT, 'arfcn[].band': schema.BAND, 'modem[].type': schema.STR,