From eb446fe27f0b5531f757389cd6dc9dae48385a94 Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Tue, 7 May 2019 06:44:12 +0200 Subject: [PATCH] add sysmobts-calib.py Change-Id: I0cb372bddd115246ad1822dc06d318815387e8a4 --- sysmobts-calib.py | 200 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100755 sysmobts-calib.py diff --git a/sysmobts-calib.py b/sysmobts-calib.py new file mode 100755 index 0000000..d22223c --- /dev/null +++ b/sysmobts-calib.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +doc = '''Remotely do a clock calibration of a sysmoBTS. + +You need is ssh root access to the BTS, and an antenna connected to the NWL. + +Remotely goes through the steps to obtain a OCXO calibration value from netlisten. +- Obtain the current calibration value from /etc/osmocom/osmo-bts-sysmo.cfg. +- Stop the osmo-bts-sysmo.service. +- Do a scan to get the strongest received ARFCN. +- Run n passes of sysmobts-calib (default: 7) to obtain an average calibration val. +- Write this calibration value back to /etc/osmocom/osmo-bts-sysmo.cfg. +- Start osmo-bts-sysmo.service. +''' + +import sys +import subprocess +import re +import shlex +import argparse + +calib_val_re = re.compile(r'clock-calibration +([0-9]+)') +result_re = re.compile('The calibration value is: ([0-9]*)') + +class Globals: + orig_calib_val = None + calib_val = None + bts = 'bts0' + band = '900' + arfcn = None + +def error(*msgs): + sys.stderr.write(''.join(str(m) for m in msgs)) + sys.stderr.write('\n') + exit(1) + +def log(*msgs): + print(''.join(str(m) for m in msgs)) + +def cmd_to_str(cmd): + return ' '.join(shlex.quote(c) for c in cmd) + +def call_output(*cmd): + cmd = ('ssh', Globals.bts,) + cmd + log('+ %s' % cmd_to_str(cmd)) + sys.stdout.flush() + sys.stderr.flush() + p = subprocess.Popen(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + o,e = p.communicate() + return o.decode('utf-8') + +def call(*cmd): + o = call_output(*cmd) + if o: + log(o) + +def reload_dsp(): + #call('/bin/sh', '-c', r"'cat /lib/firmware/sysmobts-v?.bit > /dev/fpgadl_par0 ; sleep 3s; cat /lib/firmware/sysmobts-v?.out > /dev/dspdl_dm644x_0; sleep 1s'") + # systemd service contains the DSP reload commands in the ExecStopPost. + # So starting and stopping the service is the easy way to reload the DSP. + call('systemctl', 'start', 'osmo-bts-sysmo') + call('systemctl', 'stop', 'osmo-bts-sysmo') + +def get_cfg_calib_val(): + o = call_output('grep', 'clock-calibration', '/etc/osmocom/osmo-bts-sysmo.cfg') + if not o: + return None + o = o.strip() + m = calib_val_re.match(o) + if not m: + return None + return m.group(1) + +def set_cfg_calib_val(calib_val): + if get_cfg_calib_val() is None: + call('sed', '-i', "'s/^ instance 0$/&\\n clock-calibration %s/'" % calib_val, '/etc/osmocom/osmo-bts-sysmo.cfg'); + else: + call('sed', '-i', "'s/clock-calibration.*$/clock-calibration %s/'" % calib_val, '/etc/osmocom/osmo-bts-sysmo.cfg'); + + now = get_cfg_calib_val() + if now != calib_val: + print('Failed to set calibration value, set manually in osmo-bts-sysmo.cfg') + print('phy 0\n instance 0\n clock-calibration %s' % calib_val) + + +def ask(*question, valid_answers=('*',)): + while True: + print('\n' + '\n '.join(question)) + + answer = sys.stdin.readline().strip() + for v in valid_answers: + if v == answer: + return answer + if v == '*': + return answer + if v == '+' and len(answer): + return answer + +def call_sysmobts_calib(mode, *args): + o = call_output('sysmobts-calib', '-c', 'ocxo', '-s', 'netlisten', '-b', Globals.band, '-i', Globals.calib_val, '-m', mode, *args) + log(o) + reload_dsp() + return o + +def int_be_one(string): + val = int(string) + if val < 1: + raise argparse.ArgumentTypeError('value must be at least 1') + return val + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=doc) + parser.add_argument('-b', '--band', dest='band', default=None, + help='Which GSM band to scan and calibrate to (850, 900, 1800, 1900)') + parser.add_argument('-a', '--arfcn', dest='arfcn', default=None, + help="Don't scan, directly use this ARFCN to calibrate to") + parser.add_argument('-i', '--initial-clock-correction', dest='calib_val', default=None, + help='Clock calibration value to start out with. If omitted, this is obtained from' + ' /etc/osmocom/osmo-bts-sysmo.cfg from the BTS file system.') + parser.add_argument('-I', '--set-clock-correction', dest='set_calib_val', default=None, + help="Don't scan or calibrate, just set the given value in the config file") + parser.add_argument('-G', '--get-clock-correction', dest='get_calib_val', default=False, action='store_true', + help="Don't scan or calibrate, just read the given value in the config file") + parser.add_argument('-n', '--passes', dest='passes', default=7, type=int_be_one, + help="How many times to run sysmobts-calib to obtain a resulting calibration value average") + parser.add_argument('args', nargs=1, help='Hostname (SSH) to reach the BTS at') + + cmdline = parser.parse_args() + + Globals.bts = cmdline.args[0] + if cmdline.band: + Globals.band = cmdline.band + + if cmdline.set_calib_val: + set_cfg_calib_val(cmdline.set_calib_val) + exit(0) + + if cmdline.get_calib_val: + print(get_cfg_calib_val()) + exit(0) + + Globals.orig_calib_val = cmdline.calib_val + if Globals.orig_calib_val is None: + Globals.orig_calib_val = get_cfg_calib_val() or '0' + Globals.calib_val = Globals.orig_calib_val + + print('Starting out with clock calibration value %s' % Globals.calib_val) + + #call('systemctl', 'stop', 'osmo-bts-sysmo') + reload_dsp() + + if cmdline.arfcn: + Globals.arfcn = cmdline.arfcn + else: + arfcns = call_sysmobts_calib('scan') + best_arfcn_line = arfcns.splitlines()[-1] + Globals.arfcn = best_arfcn_line.split(':')[0].split(' ')[-1] + try: + int(Globals.arfcn) + except: + error('Error while scanning bands') + + print('Using ARFCN %r' % Globals.arfcn) + + collected_values = [] + + passes = cmdline.passes + if passes < 1: + passes = 1 + + for i in range(passes): + print('\npass %d of %d' % (i+1, passes)) + o = call_sysmobts_calib('calibrate', '-a', Globals.arfcn) + for m in result_re.finditer(o): + collected_values.append(int(m.group(1))) + + collected_values = list(sorted(collected_values)) + print(collected_values) + if not collected_values: + continue + + best_values = collected_values + if len(best_values) > 3: + best_values = best_values[1:-1] + + avg = sum(best_values) / len(best_values) + Globals.calib_val = str(int(avg)) + print('clock-calibration: started with %s, current=%s' % + (Globals.orig_calib_val, Globals.calib_val)) + + print('RESULT:', Globals.calib_val, ' (was %s)' % Globals.orig_calib_val) + + cfg_calib_val = get_cfg_calib_val() + if Globals.calib_val != cfg_calib_val: + a = ask('osmo-bts-sysmo.cfg currently has %s\nmodify osmo-bts-sysmo.cfg to clock-calibration %s? (ok, no)' + % (cfg_calib_val, Globals.calib_val), + valid_answers=('ok', 'no', '')) + if a == 'ok': + set_cfg_calib_val(Globals.calib_val) + call('systemctl', 'start', 'osmo-bts-sysmo') +# vim: shiftwidth=4 expandtab tabstop=4