summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPau Espin Pedrol <pespin@sysmocom.de>2019-09-20 13:44:08 +0200
committerPau Espin Pedrol <pespin@sysmocom.de>2019-09-23 20:41:19 +0200
commitd7cb2da6db78f4c280781212ad887f0491b01608 (patch)
tree1baaba8bc6f9066c86c270b71a0371920ed2f797
parent2f77cfd21ee4ca17db89b9e668090cc4bd487fab (diff)
-rw-r--r--example/defaults.conf6
-rw-r--r--example/resources.conf.rnd10
-rw-r--r--src/osmo_gsm_tester/hnb_nano3g.py164
-rw-r--r--src/osmo_gsm_tester/osmo_hnbgw.py136
-rw-r--r--src/osmo_gsm_tester/resource.py15
-rw-r--r--src/osmo_gsm_tester/schema.py2
-rw-r--r--src/osmo_gsm_tester/suite.py25
-rw-r--r--src/osmo_gsm_tester/templates/osmo-hnbgw.cfg.tmpl32
-rwxr-xr-xsuites/3g_debug/interactive.py153
-rw-r--r--suites/3g_debug/suite.conf7
10 files changed, 546 insertions, 4 deletions
diff --git a/example/defaults.conf b/example/defaults.conf
index 353cf9b..5a6baf1 100644
--- a/example/defaults.conf
+++ b/example/defaults.conf
@@ -92,3 +92,9 @@ osmo_bts_oc2g:
max_trx: 1
trx_list:
- nominal_power: 25
+
+hnb:
+ net:
+ mcc: 901
+ mnc: 70
+ uarfcn: 9800
diff --git a/example/resources.conf.rnd b/example/resources.conf.rnd
index 0fad50f..88d0613 100644
--- a/example/resources.conf.rnd
+++ b/example/resources.conf.rnd
@@ -46,6 +46,16 @@ bts:
device: '01:01:55:2e:b6'
port: '1'
+hnb:
+- label: nano3GS8
+ type: nano3g
+ addr: 10.42.42.121
+ band: PCS-1900
+ power_supply:
+ type: 'sispm'
+ device: '01:01:55:2e:b6'
+ port: '3'
+
arfcn:
- arfcn: 512
band: GSM-1800
diff --git a/src/osmo_gsm_tester/hnb_nano3g.py b/src/osmo_gsm_tester/hnb_nano3g.py
new file mode 100644
index 0000000..63abf10
--- /dev/null
+++ b/src/osmo_gsm_tester/hnb_nano3g.py
@@ -0,0 +1,164 @@
+# osmo_gsm_tester: specifics for running an ip.access nanoBTS
+#
+# Copyright (C) 2018 by sysmocom - s.f.m.c. GmbH
+#
+# Author: Pau Espin Pedrol <pespin@sysmocom.de>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+import os
+import pprint
+import re
+from . import log, config, util, process, pcap_recorder, abisipfind
+from . import powersupply
+from .event_loop import MainLoop
+
+class HnbNano3g(log.Origin):
+
+ SSH_USER='root'
+ IPA_DMI_PATH='/opt/ipaccess/DMI/ipa-dmi'
+##############
+# PROTECTED
+##############
+ def __init__(self, suite_run, conf):
+ super().__init__(log.C_RUN, 'nano3g_%s' % conf.get('label', 'nolabel'), 'nanobts')
+ self.suite_run = suite_run
+ self.conf = conf
+ self.pwsup = None
+ self.hnbgw = None
+ self.lac = None
+ self.rac = None
+ self.cellid = None
+
+ def _configure(self):
+ if self.hnbgw is None:
+ raise log.Error('HNodeB needs to be added to a HNBGW before it can be configured')
+
+ values = dict(hnb=config.get_defaults('hnb'))
+ config.overlay(values, self.suite_run.config())
+ config.overlay(values, { 'hnb': self.conf })
+ self.gen_conf = values
+ self.dbg('OSMO-BTS-TRX CONFIG:\n' + pprint.pformat(values))
+
+ pwsup_opt = self.conf.get('power_supply', {})
+ if not pwsup_opt:
+ raise log.Error('No power_supply attribute provided in conf!')
+ pwsup_type = pwsup_opt.get('type')
+ if not pwsup_type:
+ raise log.Error('No type attribute provided in power_supply conf!' % trx_i)
+ self.pwsup = powersupply.get_instance_by_type(pwsup_type, pwsup_opt)
+
+ def wait_ready(self, local_bind_ip, hnb_ip):
+ self.log('Finding nano3g %s, binding on %s...' % (hnb_ip, local_bind_ip))
+ ipfind = abisipfind.AbisIpFind(self.suite_run, self.run_dir, local_bind_ip, 'preconf')
+ ipfind.start()
+ ipfind.wait_bts_ready(hnb_ip)
+ running_unitid, running_trx = ipfind.get_unitid_by_ip(hnb_ip)
+ self.log('Found nano3g %s with unit_id %d trx %d' % (hnb_ip, running_unitid, running_trx))
+ ipfind.stop()
+
+ def run_ssh_cmd(self, name, popen_args):
+ # On OpenSSH >= 7.0, only-supported algos by nano3g are disabled by default, they need to be enabled manually:
+ # https://projects.osmocom.org/projects/cellular-infrastructure/wiki/Configuring_the_ipaccess_nano3G#SSH-Access
+ ssh_args=['-o', 'KexAlgorithms=+diffie-hellman-group1-sha1', '-c', 'aes128-cbc']
+ process.run_remote_sync(self.run_dir, HnbNano3g.SSH_USER, self.addr(), name, popen_args, remote_cwd=None, ssh_args=ssh_args)
+
+ def run_dmi_cmd(self, name, cmd_str):
+ self.run_ssh_cmd('dmi-' + name, (HnbNano3g.IPA_DMI_PATH, '-c', '\"%s\"' % cmd_str))
+
+ def run_reboot(self):
+ self.run_ssh_cmd('reboot', reboot)
+
+ def set_permanent_settings(self):
+ # These settings are set permantently and require a reboot after set:
+ self.run_dmi_cmd('set_mcc', 'set mcc=%s' % self.gen_conf['hnb']['net']['mcc'])
+ self.run_dmi_cmd('set_mnc', 'set mnc=%s' % self.gen_conf['hnb']['net']['mnc'])
+ # [uarfcnDownlink, 1900 MHz band], [scramblingCode], [dummyCellId]
+ self.run_dmi_cmd('set_rfparams', 'set rfParamsCandidateList=({%s, 401, %d})' % (self.gen_conf['hnb']['net']['uarfcn'], self.cellid))
+ # [lac], [rac]
+ self.run_dmi_cmd('set_lac', 'set lacRacCandidateList=({%d, (%d)})' % (self.lac, self.rac))
+ self.run_dmi_cmd('set_hnbcid', 'set hnbCId=%d', self.cellid)
+ self.run_dmi_cmd('set_rncid', 'set rncIdentity=0')
+
+ def set_volatile_settings(self):
+ self.run_dmi_cmd('set_hnbgwaddr', 'set hnbGwAddress=%s' % self.addr())
+ self.run_dmi_cmd('action_2061', 'action 2061')
+ self.run_dmi_cmd('action_1216', 'action 1216')
+ self.run_dmi_cmd('action_establish_conn', 'action establishPermanentHnbGwConnection')
+ self.run_dmi_cmd('set_csgaccessmode', 'set csgAccessMode=CSG_ACCESS_MODE_OPEN_ACCESS')
+
+########################
+# PUBLIC - INTERNAL API
+#######################
+
+ def set_lac(self, lac):
+ self.lac = lac
+
+ def set_rac(self, rac):
+ self.rac = rac
+
+ def set_cellid(self, cellid):
+ self.cellid = cellid
+
+ def cleanup(self):
+ if self.pwsup:
+ self.dbg('Powering off')
+ self.pwsup.power_set(False)
+ self.pwsup = None
+
+###################
+# PUBLIC (test API included)
+###################
+
+ def start(self):
+ self.run_dir = util.Dir(self.suite_run.get_test_run_dir().new_dir(self.name()))
+ self._configure()
+
+ hnb_ip = self.addr()
+
+ # Make sure nano3g is powered and in a clean state:
+ self.dbg('Powering cycling nano3g')
+ self.pwsup.power_cycle(1.0)
+
+ pcap_recorder.PcapRecorder(self.suite_run, self.run_dir.new_dir('pcap'), None,
+ 'host %s and port not 22' % hnb_ip)
+
+ # This fine for now, however concurrent tests using nano3g may run into "address already in use" since dst is broadcast.
+ # Once concurrency is needed, a new config attr should be added to have an extra static IP assigned on the main-unit to each Nano3g resource.
+ local_bind_ip = util.dst_ip_get_local_bind(hnb_ip)
+
+ # Wait until nano3g is ready (announcing through abisip-find) and then set permanent settings
+ self.wait_ready(local_bind_ip, hnb_ip)
+ self.set_permanent_settings()
+
+ # Configs applied above require a reboot of the nano3g to be applied:
+ self.run_reboot()
+ #sleep a few seconds (to avoid getting abisipind results previous to reboot):
+ MainLoop.sleep(self, 5.0)
+
+ # Wait until nano3g is ready again and apply volatile settings:
+ self.wait_ready(local_bind_ip, hnb_ip)
+ self.set_volatile_settings()
+
+ # After applying volatile settings, nano3g should end up connected to our HNBGW:
+ MainLoop.wait(self, self.hnbgw.hnb_is_connected, self, timeout=600)
+ self.log('nano3g connected to HNBGW')
+
+ def addr(self):
+ return self.conf.get('addr')
+
+ def set_hnbgw(self, hnbgw):
+ self.hnbgw = hnbgw
+
+# vim: expandtab tabstop=4 shiftwidth=4
diff --git a/src/osmo_gsm_tester/osmo_hnbgw.py b/src/osmo_gsm_tester/osmo_hnbgw.py
new file mode 100644
index 0000000..24e6373
--- /dev/null
+++ b/src/osmo_gsm_tester/osmo_hnbgw.py
@@ -0,0 +1,136 @@
+# osmo_gsm_tester: specifics for running an osmo-hnbgw
+#
+# Copyright (C) 2019 by sysmocom - s.f.m.c. GmbH
+#
+# Author: Pau Espin Pedrol <pespin@sysmocom.de>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+import os
+import re
+import pprint
+
+from . import log, util, config, template, process, osmo_ctrl, pcap_recorder
+
+class OsmoHnbgw(log.Origin):
+
+ def __init__(self, suite_run, stp, ip_address):
+ super().__init__(log.C_RUN, 'osmo-hnbgw_%s' % ip_address.get('addr'))
+ self.run_dir = None
+ self.config_file = None
+ self.process = None
+ self.suite_run = suite_run
+ self.ip_address = ip_address
+ self.hnb_li = []
+ self.stp = stp
+
+ def start(self):
+ self.log('Starting osmo-hnbgw')
+ self.run_dir = util.Dir(self.suite_run.get_test_run_dir().new_dir(self.name()))
+ self.configure()
+
+ inst = util.Dir(os.path.abspath(self.suite_run.trial.get_inst('osmo-iuh')))
+
+ binary = inst.child('bin', 'osmo-hnbgw')
+ if not os.path.isfile(binary):
+ raise RuntimeError('Binary missing: %r' % binary)
+ lib = inst.child('lib')
+ if not os.path.isdir(lib):
+ raise RuntimeError('No lib/ in %r' % inst)
+
+
+ filter = 'host %s and port not 22' % self.addr()
+ pcap_recorder.PcapRecorder(self.suite_run, self.run_dir.new_dir('pcap'), None, filter)
+
+ env = { 'LD_LIBRARY_PATH': util.prepend_library_path(lib) }
+
+ self.dbg(run_dir=self.run_dir, binary=binary, env=env)
+ self.process = process.Process(self.name(), self.run_dir,
+ (binary, '-c',
+ os.path.abspath(self.config_file)),
+ env=env)
+ self.suite_run.remember_to_stop(self.process)
+ self.process.launch()
+
+ def configure(self):
+ self.config_file = self.run_dir.new_file('osmo-hnbgw.cfg')
+ self.dbg(config_file=self.config_file)
+
+ values = dict(hnbgw=config.get_defaults('hnbgw'))
+ config.overlay(values, self.suite_run.config())
+ config.overlay(values, dict(hnbgw=dict(ip_address=self.ip_address)))
+ config.overlay(values, self.stp.conf_for_client())
+
+ #hnb_list = []
+ #for hnb in self.hnb_li:
+ # hnb_list.append(hnb.conf_for_hnbgw())
+ #config.overlay(values, dict(hnbgw=dict(hnb_list=hnb_list)))
+
+ self.dbg('HNBGW CONFIG:\n' + pprint.pformat(values))
+
+ with open(self.config_file, 'w') as f:
+ r = template.render('osmo-hnbgw.cfg', values)
+ self.dbg(r)
+ f.write(r)
+
+ def addr(self):
+ return self.ip_address.get('addr')
+
+ def hnb_add(self, hnb):
+ self.hnb_li.append(hnb)
+ hnb.set_hnbgw(self)
+
+ def hnb_num(self, hnb):
+ 'Provide number id used by OsmoHNBGW to identify configured HNB'
+ # We take advantage from the fact that VTY code assigns VTY in ascending
+ # order through the HNB nodes found. As we populate the config iterating
+ # over this list, we have a 1:1 match in indexes.
+ return self.hnb_li.index(hnb)
+
+ def hnb_is_connected(self, hnb):
+ return OsmoHnbgwCtrl(self).hnb_is_connected(self.hnb_num(hnb))
+
+ def running(self):
+ return not self.process.terminated()
+
+
+class OsmoHnbgwCtrl(log.Origin):
+ PORT = 4261
+ HNB_INFO_VAR = "hnb.%d.info"
+ HNB_INFO_RE = re.compile("GET_REPLY (\d+) hnb.\d+.info (?P<info>\w+)")
+
+ def __init__(self, hnbgw):
+ self.hnbgw = hnbgw
+ super().__init__(log.C_BUS, 'CTRL(%s:%d)' % (self.hnbgw.addr(), OsmoHnbgwCtrl.PORT))
+
+ def ctrl(self):
+ return osmo_ctrl.OsmoCtrl(self.hnbgw.addr(), OsmoHnbgwCtrl.PORT)
+
+ def hnb_is_connected(self, hnb_num):
+ with self.ctrl() as ctrl:
+ ctrl.do_get(OsmoHnbgwCtrl.HNB_INFO_VAR % hnb_num)
+ data = ctrl.receive()
+ while (len(data) > 0):
+ (answer, data) = ctrl.remove_ipa_ctrl_header(data)
+ answer_str = answer.decode('utf-8')
+ answer_str = answer_str.replace('\n', ' ')
+ res = OsmoHnbgwCtrl.HNB_INFO_RE.match(answer_str)
+ if res:
+ info = str(res.group('info'))
+ self.log('got info: "%s"' % info)
+ if info == 'connected':
+ return True
+ return False
+
+# vim: expandtab tabstop=4 shiftwidth=4
diff --git a/src/osmo_gsm_tester/resource.py b/src/osmo_gsm_tester/resource.py
index 1b18076..55ad648 100644
--- a/src/osmo_gsm_tester/resource.py
+++ b/src/osmo_gsm_tester/resource.py
@@ -27,6 +27,7 @@ from . import config
from . import util
from . import schema
from . import bts_sysmo, bts_osmotrx, bts_osmovirtual, bts_octphy, bts_nanobts, bts_oc2g
+from . import hnb_nano3g
from . import modem
from . import ms_osmo_mobile
@@ -41,10 +42,11 @@ RESERVED_RESOURCES_FILE = 'reserved_resources.state'
R_IP_ADDRESS = 'ip_address'
R_BTS = 'bts'
+R_HNB = 'hnb'
R_ARFCN = 'arfcn'
R_MODEM = 'modem'
R_OSMOCON = 'osmocon_phone'
-R_ALL = (R_IP_ADDRESS, R_BTS, R_ARFCN, R_MODEM, R_OSMOCON)
+R_ALL = (R_IP_ADDRESS, R_BTS, R_HNB, R_ARFCN, R_MODEM, R_OSMOCON)
RESOURCES_SCHEMA = {
'ip_address[].addr': schema.IPV4,
@@ -76,6 +78,13 @@ RESOURCES_SCHEMA = {
'bts[].osmo_trx.dev_args': schema.STR,
'bts[].osmo_trx.multi_arfcn': schema.BOOL_STR,
'bts[].osmo_trx.max_trxd_version': schema.UINT,
+ 'hnb[].label': schema.STR,
+ 'hnb[].type': schema.STR,
+ 'hnb[].addr': schema.IPV4,
+ 'hnb[].band': schema.BAND,
+ 'hnb[].power_supply.type': schema.STR,
+ 'hnb[].power_supply.device': schema.STR,
+ 'hnb[].power_supply.port': schema.STR,
'arfcn[].arfcn': schema.INT,
'arfcn[].band': schema.BAND,
'modem[].type': schema.STR,
@@ -108,6 +117,10 @@ KNOWN_BTS_TYPES = {
'nanobts': bts_nanobts.NanoBts,
}
+KNOWN_HNB_TYPES = {
+ 'nano3g': hnb_nano3g.HnbNano3g,
+ }
+
KNOWN_MS_TYPES = {
# Map None to ofono for forward compability
diff --git a/src/osmo_gsm_tester/schema.py b/src/osmo_gsm_tester/schema.py
index 14fe640..91d45cc 100644
--- a/src/osmo_gsm_tester/schema.py
+++ b/src/osmo_gsm_tester/schema.py
@@ -39,7 +39,7 @@ def match_re(name, regex, val):
raise ValueError('Invalid %s: %r' % (name, val))
def band(val):
- if val in ('GSM-900', 'GSM-1800', 'GSM-1900'):
+ if val in ('GSM-900', 'GSM-1800', 'GSM-1900', 'PCS-1900'):
return
raise ValueError('Unknown GSM band: %r' % val)
diff --git a/src/osmo_gsm_tester/suite.py b/src/osmo_gsm_tester/suite.py
index 0738e96..3b2d87d 100644
--- a/src/osmo_gsm_tester/suite.py
+++ b/src/osmo_gsm_tester/suite.py
@@ -21,9 +21,9 @@ import os
import sys
import time
import pprint
-from . import config, log, util, resource, test
+from . import config, log, util, resource, test, process
from .event_loop import MainLoop
-from . import osmo_nitb, osmo_hlr, osmo_mgcpgw, osmo_mgw, osmo_msc, osmo_bsc, osmo_stp, osmo_ggsn, osmo_sgsn, esme, osmocon, ms_driver, iperf3, process
+from . import osmo_nitb, osmo_hlr, osmo_mgcpgw, osmo_mgw, osmo_msc, osmo_bsc, osmo_stp, osmo_ggsn, osmo_sgsn, esme, osmocon, ms_driver, iperf3, osmo_hnbgw
class Timeout(Exception):
pass
@@ -327,6 +327,19 @@ class SuiteRun(log.Origin):
self.register_for_cleanup(bts)
return bts
+ def hnbgw(self, stp, ip_address=None):
+ if ip_address is None:
+ ip_address = self.ip_address()
+ return osmo_hnbgw.OsmoHnbgw(self, stp, ip_address)
+
+ def hnb(self, specifics=None):
+ hnb = hnb_obj(self, self.reserved_resources.get(resource.R_HNB, specifics=specifics))
+ hnb.set_lac(self.lac())
+ hnb.set_rac(self.rac())
+ hnb.set_cellid(self.cellid())
+ self.register_for_cleanup(hnb)
+ return hnb
+
def modem(self, specifics=None):
conf = self.reserved_resources.get(resource.R_MODEM, specifics=specifics)
ms_type = conf.get('type')
@@ -477,4 +490,12 @@ def bts_obj(suite_run, conf):
raise RuntimeError('No such BTS type is defined: %r' % bts_type)
return bts_class(suite_run, conf)
+def hnb_obj(suite_run, conf):
+ hnb_type = conf.get('type')
+ log.dbg('create HNB object', type=hnb_type)
+ hnb_class = resource.KNOWN_HNB_TYPES.get(hnb_type)
+ if hnb_class is None:
+ raise RuntimeError('No such HNB type is defined: %r' % hnb_type)
+ return hnb_class(suite_run, conf)
+
# vim: expandtab tabstop=4 shiftwidth=4
diff --git a/src/osmo_gsm_tester/templates/osmo-hnbgw.cfg.tmpl b/src/osmo_gsm_tester/templates/osmo-hnbgw.cfg.tmpl
new file mode 100644
index 0000000..244873e
--- /dev/null
+++ b/src/osmo_gsm_tester/templates/osmo-hnbgw.cfg.tmpl
@@ -0,0 +1,32 @@
+!
+! OsmoHNBGW (0) configuration saved from vty
+!!
+!
+log stderr
+ logging filter all 1
+ logging color 1
+ logging print category 1
+ logging timestamp 1
+ logging print extended-timestamp 1
+ logging level all debug
+ logging level lglobal notice
+ logging level llapd notice
+ logging level linp notice
+ logging level lmux notice
+ logging level lmi notice
+ logging level lmib notice
+ logging level lsms notice
+ logging level lctrl notice
+ logging level lgtp notice
+ logging level lstats notice
+ logging level set-all debug
+line vty
+ no login
+ bind ${hnbgw.ip_address.addr}
+ctrl
+ bind ${hnbgw.ip_address.addr}
+
+hnbgw
+ iuh
+ local-ip ${hnbgw.ip_address.addr}
+ hnbap-allow-tmsi 1
diff --git a/suites/3g_debug/interactive.py b/suites/3g_debug/interactive.py
new file mode 100755
index 0000000..0de7c9f
--- /dev/null
+++ b/suites/3g_debug/interactive.py
@@ -0,0 +1,153 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+hlr = suite.hlr()
+hnb = suite.hnb()
+mgw_msc = suite.mgw()
+stp = suite.stp()
+ggsn = suite.ggsn()
+sgsn = suite.sgsn(hlr, ggsn)
+msc = suite.msc(hlr, mgw_msc, stp)
+hnbgw = suite.hnbgw(stp)
+
+modems = suite.modems(int(prompt('How many modems?')))
+
+hnbgw.hnb_add(hnb)
+
+hlr.start()
+stp.start()
+ggsn.start()
+sgsn.start()
+msc.start()
+mgw_msc.start()
+hnbgw.start()
+
+hnb.start()
+print('Waiting for hnb to connect to hnbgw...')
+wait(hnbgw.hnb_is_connected, hnb)
+
+for m in modems:
+ hlr.subscriber_add(m)
+ m.connect(msc.mcc_mnc())
+
+while True:
+ cmd = prompt('Enter command: (q)uit (s)ms (g)et-registered (w)ait-registered, call-list [<ms_msisdn>], call-dial <src_msisdn> <dst_msisdn>, call-wait-incoming <src_msisdn> <dst_msisdn>, call-answer <mt_msisdn> <call_id>, call-hangup <ms_msisdn> <call_id>, ussd <command>, data-attach, data-wait, data-detach, data-activate')
+ cmd = cmd.strip().lower()
+
+ if not cmd:
+ continue
+
+ params = cmd.split()
+
+ if 'quit'.startswith(cmd):
+ break
+
+ elif 'wait-registered'.startswith(cmd):
+ try:
+ for m in modems:
+ wait(m.is_connected, msc.mcc_mnc())
+ wait(msc.subscriber_attached, *modems)
+ except Timeout:
+ print('Timeout while waiting for registration.')
+
+ elif 'get-registered'.startswith(cmd):
+ print(msc.imsi_list_attached())
+ print('RESULT: %s' %
+ ('All modems are registered.' if msc.subscriber_attached(*modems)
+ else 'Some modem(s) not registered yet.'))
+
+ elif 'sms'.startswith(cmd):
+ for mo in modems:
+ for mt in modems:
+ mo.sms_send(mt.msisdn, 'to ' + mt.name())
+
+ elif cmd.startswith('call-list'):
+ if len(params) != 1 and len(params) != 2:
+ print('wrong format')
+ continue
+ for ms in modems:
+ if len(params) == 1 or str(ms.msisdn) == params[1]:
+ print('call-list: %r %r' % (ms.name(), ms.call_id_list()))
+
+ elif cmd.startswith('call-dial'):
+ if len(params) != 3:
+ print('wrong format')
+ continue
+ src_msisdn, dst_msisdn = params[1:]
+ for mo in modems:
+ if str(mo.msisdn) == src_msisdn:
+ print('dialing %s->%s' % (src_msisdn, dst_msisdn))
+ call_id = mo.call_dial(dst_msisdn)
+ print('dial success: call_id=%r' % call_id)
+
+ elif cmd.startswith('call-wait-incoming'):
+ if len(params) != 3:
+ print('wrong format')
+ continue
+ src_msisdn, dst_msisdn = params[1:]
+ for mt in modems:
+ if str(mt.msisdn) == dst_msisdn:
+ print('waiting for incoming %s->%s' % (src_msisdn, dst_msisdn))
+ call_id = mt.call_wait_incoming(src_msisdn)
+ print('incoming call success: call_id=%r' % call_id)
+
+ elif cmd.startswith('call-answer'):
+ if len(params) != 3:
+ print('wrong format')
+ continue
+ mt_msisdn, call_id = params[1:]
+ for mt in modems:
+ if str(mt.msisdn) == mt_msisdn:
+ print('answering %s %r' % (mt.name(), call_id))
+ mt.call_answer(call_id)
+
+ elif cmd.startswith('call-hangup'):
+ if len(params) != 3:
+ print('wrong format')
+ continue
+ ms_msisdn, call_id = params[1:]
+ for ms in modems:
+ if str(ms.msisdn) == ms_msisdn:
+ print('hanging up %s %r' % (ms.name(), call_id))
+ ms.call_hangup(call_id)
+
+ elif cmd.startswith('ussd'):
+ if len(params) != 2:
+ print('wrong format')
+ continue
+ ussd_cmd = params[1]
+ for ms in modems:
+ print('modem %s: ussd %s' % (ms.name(), ussd_cmd))
+ response = ms.ussd_send(ussd_cmd)
+ print('modem %s: response=%r' % (ms.name(), response))
+
+ elif cmd.startswith('data-attach'):
+ if len(params) != 1:
+ print('wrong format')
+ continue
+ for ms in modems:
+ print('modem %s: attach' % ms.name())
+ ms.attach()
+ wait(ms.is_attached)
+ print('modem %s: attached' % ms.name())
+
+ elif cmd.startswith('data-detach'):
+ if len(params) != 1:
+ print('wrong format')
+ continue
+ for ms in modems:
+ print('modem %s: detach' % ms.name())
+ ms.attach()
+ wait(lambda: not ms.is_attached())
+ print('modem %s: detached' % ms.name())
+
+ elif cmd.startswith('data-activate'):
+ if len(params) != 1:
+ print('wrong format')
+ continue
+ for ms in modems:
+ print('modem %s: activate' % ms.name())
+ response = ms.activate_context()
+ print('modem %s: response=%r' % (ms.name(), response))
+
+ else:
+ print('Unknown command: %s' % cmd)
diff --git a/suites/3g_debug/suite.conf b/suites/3g_debug/suite.conf
new file mode 100644
index 0000000..400fa28
--- /dev/null
+++ b/suites/3g_debug/suite.conf
@@ -0,0 +1,7 @@
+resources:
+ ip_address:
+ - times: 8
+ hnb:
+ - times: 1
+ modem:
+ - times: 2