blob: d22223c867a1e5e85e35c3e85109c464cc95cebc [file] [log] [blame]
Neels Hofmeyreb446fe2019-05-07 06:44:12 +02001#!/usr/bin/env python3
2doc = '''Remotely do a clock calibration of a sysmoBTS.
3
4You need is ssh root access to the BTS, and an antenna connected to the NWL.
5
6Remotely 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
15import sys
16import subprocess
17import re
18import shlex
19import argparse
20
21calib_val_re = re.compile(r'clock-calibration +([0-9]+)')
22result_re = re.compile('The calibration value is: ([0-9]*)')
23
24class Globals:
25 orig_calib_val = None
26 calib_val = None
27 bts = 'bts0'
28 band = '900'
29 arfcn = None
30
31def error(*msgs):
32 sys.stderr.write(''.join(str(m) for m in msgs))
33 sys.stderr.write('\n')
34 exit(1)
35
36def log(*msgs):
37 print(''.join(str(m) for m in msgs))
38
39def cmd_to_str(cmd):
40 return ' '.join(shlex.quote(c) for c in cmd)
41
42def 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
51def call(*cmd):
52 o = call_output(*cmd)
53 if o:
54 log(o)
55
56def 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
63def 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
73def 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
85def 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
98def 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
104def 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
110if __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