#!/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