Neels Hofmeyr | eb446fe | 2019-05-07 06:44:12 +0200 | [diff] [blame^] | 1 | #!/usr/bin/env python3 |
| 2 | doc = '''Remotely do a clock calibration of a sysmoBTS. |
| 3 | |
| 4 | You need is ssh root access to the BTS, and an antenna connected to the NWL. |
| 5 | |
| 6 | Remotely goes through the steps to obtain a OCXO calibration value from netlisten. |
| 7 | - Obtain the current calibration value from /etc/osmocom/osmo-bts-sysmo.cfg. |
| 8 | - Stop the osmo-bts-sysmo.service. |
| 9 | - Do a scan to get the strongest received ARFCN. |
| 10 | - Run n passes of sysmobts-calib (default: 7) to obtain an average calibration val. |
| 11 | - Write this calibration value back to /etc/osmocom/osmo-bts-sysmo.cfg. |
| 12 | - Start osmo-bts-sysmo.service. |
| 13 | ''' |
| 14 | |
| 15 | import sys |
| 16 | import subprocess |
| 17 | import re |
| 18 | import shlex |
| 19 | import argparse |
| 20 | |
| 21 | calib_val_re = re.compile(r'clock-calibration +([0-9]+)') |
| 22 | result_re = re.compile('The calibration value is: ([0-9]*)') |
| 23 | |
| 24 | class Globals: |
| 25 | orig_calib_val = None |
| 26 | calib_val = None |
| 27 | bts = 'bts0' |
| 28 | band = '900' |
| 29 | arfcn = None |
| 30 | |
| 31 | def error(*msgs): |
| 32 | sys.stderr.write(''.join(str(m) for m in msgs)) |
| 33 | sys.stderr.write('\n') |
| 34 | exit(1) |
| 35 | |
| 36 | def log(*msgs): |
| 37 | print(''.join(str(m) for m in msgs)) |
| 38 | |
| 39 | def cmd_to_str(cmd): |
| 40 | return ' '.join(shlex.quote(c) for c in cmd) |
| 41 | |
| 42 | def call_output(*cmd): |
| 43 | cmd = ('ssh', Globals.bts,) + cmd |
| 44 | log('+ %s' % cmd_to_str(cmd)) |
| 45 | sys.stdout.flush() |
| 46 | sys.stderr.flush() |
| 47 | p = subprocess.Popen(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) |
| 48 | o,e = p.communicate() |
| 49 | return o.decode('utf-8') |
| 50 | |
| 51 | def call(*cmd): |
| 52 | o = call_output(*cmd) |
| 53 | if o: |
| 54 | log(o) |
| 55 | |
| 56 | def reload_dsp(): |
| 57 | #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'") |
| 58 | # systemd service contains the DSP reload commands in the ExecStopPost. |
| 59 | # So starting and stopping the service is the easy way to reload the DSP. |
| 60 | call('systemctl', 'start', 'osmo-bts-sysmo') |
| 61 | call('systemctl', 'stop', 'osmo-bts-sysmo') |
| 62 | |
| 63 | def get_cfg_calib_val(): |
| 64 | o = call_output('grep', 'clock-calibration', '/etc/osmocom/osmo-bts-sysmo.cfg') |
| 65 | if not o: |
| 66 | return None |
| 67 | o = o.strip() |
| 68 | m = calib_val_re.match(o) |
| 69 | if not m: |
| 70 | return None |
| 71 | return m.group(1) |
| 72 | |
| 73 | def set_cfg_calib_val(calib_val): |
| 74 | if get_cfg_calib_val() is None: |
| 75 | call('sed', '-i', "'s/^ instance 0$/&\\n clock-calibration %s/'" % calib_val, '/etc/osmocom/osmo-bts-sysmo.cfg'); |
| 76 | else: |
| 77 | call('sed', '-i', "'s/clock-calibration.*$/clock-calibration %s/'" % calib_val, '/etc/osmocom/osmo-bts-sysmo.cfg'); |
| 78 | |
| 79 | now = get_cfg_calib_val() |
| 80 | if now != calib_val: |
| 81 | print('Failed to set calibration value, set manually in osmo-bts-sysmo.cfg') |
| 82 | print('phy 0\n instance 0\n clock-calibration %s' % calib_val) |
| 83 | |
| 84 | |
| 85 | def ask(*question, valid_answers=('*',)): |
| 86 | while True: |
| 87 | print('\n' + '\n '.join(question)) |
| 88 | |
| 89 | answer = sys.stdin.readline().strip() |
| 90 | for v in valid_answers: |
| 91 | if v == answer: |
| 92 | return answer |
| 93 | if v == '*': |
| 94 | return answer |
| 95 | if v == '+' and len(answer): |
| 96 | return answer |
| 97 | |
| 98 | def call_sysmobts_calib(mode, *args): |
| 99 | o = call_output('sysmobts-calib', '-c', 'ocxo', '-s', 'netlisten', '-b', Globals.band, '-i', Globals.calib_val, '-m', mode, *args) |
| 100 | log(o) |
| 101 | reload_dsp() |
| 102 | return o |
| 103 | |
| 104 | def int_be_one(string): |
| 105 | val = int(string) |
| 106 | if val < 1: |
| 107 | raise argparse.ArgumentTypeError('value must be at least 1') |
| 108 | return val |
| 109 | |
| 110 | if __name__ == '__main__': |
| 111 | parser = argparse.ArgumentParser(description=doc) |
| 112 | parser.add_argument('-b', '--band', dest='band', default=None, |
| 113 | help='Which GSM band to scan and calibrate to (850, 900, 1800, 1900)') |
| 114 | parser.add_argument('-a', '--arfcn', dest='arfcn', default=None, |
| 115 | help="Don't scan, directly use this ARFCN to calibrate to") |
| 116 | parser.add_argument('-i', '--initial-clock-correction', dest='calib_val', default=None, |
| 117 | help='Clock calibration value to start out with. If omitted, this is obtained from' |
| 118 | ' /etc/osmocom/osmo-bts-sysmo.cfg from the BTS file system.') |
| 119 | parser.add_argument('-I', '--set-clock-correction', dest='set_calib_val', default=None, |
| 120 | help="Don't scan or calibrate, just set the given value in the config file") |
| 121 | parser.add_argument('-G', '--get-clock-correction', dest='get_calib_val', default=False, action='store_true', |
| 122 | help="Don't scan or calibrate, just read the given value in the config file") |
| 123 | parser.add_argument('-n', '--passes', dest='passes', default=7, type=int_be_one, |
| 124 | help="How many times to run sysmobts-calib to obtain a resulting calibration value average") |
| 125 | parser.add_argument('args', nargs=1, help='Hostname (SSH) to reach the BTS at') |
| 126 | |
| 127 | cmdline = parser.parse_args() |
| 128 | |
| 129 | Globals.bts = cmdline.args[0] |
| 130 | if cmdline.band: |
| 131 | Globals.band = cmdline.band |
| 132 | |
| 133 | if cmdline.set_calib_val: |
| 134 | set_cfg_calib_val(cmdline.set_calib_val) |
| 135 | exit(0) |
| 136 | |
| 137 | if cmdline.get_calib_val: |
| 138 | print(get_cfg_calib_val()) |
| 139 | exit(0) |
| 140 | |
| 141 | Globals.orig_calib_val = cmdline.calib_val |
| 142 | if Globals.orig_calib_val is None: |
| 143 | Globals.orig_calib_val = get_cfg_calib_val() or '0' |
| 144 | Globals.calib_val = Globals.orig_calib_val |
| 145 | |
| 146 | print('Starting out with clock calibration value %s' % Globals.calib_val) |
| 147 | |
| 148 | #call('systemctl', 'stop', 'osmo-bts-sysmo') |
| 149 | reload_dsp() |
| 150 | |
| 151 | if cmdline.arfcn: |
| 152 | Globals.arfcn = cmdline.arfcn |
| 153 | else: |
| 154 | arfcns = call_sysmobts_calib('scan') |
| 155 | best_arfcn_line = arfcns.splitlines()[-1] |
| 156 | Globals.arfcn = best_arfcn_line.split(':')[0].split(' ')[-1] |
| 157 | try: |
| 158 | int(Globals.arfcn) |
| 159 | except: |
| 160 | error('Error while scanning bands') |
| 161 | |
| 162 | print('Using ARFCN %r' % Globals.arfcn) |
| 163 | |
| 164 | collected_values = [] |
| 165 | |
| 166 | passes = cmdline.passes |
| 167 | if passes < 1: |
| 168 | passes = 1 |
| 169 | |
| 170 | for i in range(passes): |
| 171 | print('\npass %d of %d' % (i+1, passes)) |
| 172 | o = call_sysmobts_calib('calibrate', '-a', Globals.arfcn) |
| 173 | for m in result_re.finditer(o): |
| 174 | collected_values.append(int(m.group(1))) |
| 175 | |
| 176 | collected_values = list(sorted(collected_values)) |
| 177 | print(collected_values) |
| 178 | if not collected_values: |
| 179 | continue |
| 180 | |
| 181 | best_values = collected_values |
| 182 | if len(best_values) > 3: |
| 183 | best_values = best_values[1:-1] |
| 184 | |
| 185 | avg = sum(best_values) / len(best_values) |
| 186 | Globals.calib_val = str(int(avg)) |
| 187 | print('clock-calibration: started with %s, current=%s' % |
| 188 | (Globals.orig_calib_val, Globals.calib_val)) |
| 189 | |
| 190 | print('RESULT:', Globals.calib_val, ' (was %s)' % Globals.orig_calib_val) |
| 191 | |
| 192 | cfg_calib_val = get_cfg_calib_val() |
| 193 | if Globals.calib_val != cfg_calib_val: |
| 194 | a = ask('osmo-bts-sysmo.cfg currently has %s\nmodify osmo-bts-sysmo.cfg to clock-calibration %s? (ok, no)' |
| 195 | % (cfg_calib_val, Globals.calib_val), |
| 196 | valid_answers=('ok', 'no', '')) |
| 197 | if a == 'ok': |
| 198 | set_cfg_calib_val(Globals.calib_val) |
| 199 | call('systemctl', 'start', 'osmo-bts-sysmo') |
| 200 | # vim: shiftwidth=4 expandtab tabstop=4 |