Add in the output lines for the GSMPOS and for a simple data overlay for OSM data...
[CellLocator.git] / cell_locator.py
blobaae5f30e82c6ad4e4dee4ceafd1655f6b469a9b1
1 #!/usr/bin/env python
2 """
3 GSM Cell logger based on mickeyterm.
5 (C) 2002-2006 Chris Liechti <cliecht@gmx.net>
6 (C) 2008 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
7 (C) 2008 Baruch Even <baruch@ev-en.org>
9 GPLv3 or later
10 """
12 __version__ = "0.2.4"
14 import sys, os, serial, time, dbus, csv
15 import pygtk
16 pygtk.require('2.0')
17 import gtk, gobject
18 import pango
20 debug = True
22 from dbus.mainloop.glib import DBusGMainLoop
23 DBusGMainLoop(set_as_default=True)
25 def arfcn_to_freq(data):
26 arfcn = int(data)
27 if 172 < arfcn and arfcn < 252: return 869 + (arfcn-127)*0.2
28 if 511 < arfcn and arfcn < 811: return 1930 + (arfcn - 511) * 0.2
29 if 1 < arfcn and arfcn < 124: return 935 + (arfcn * 0.2)
30 if 974 < arfcn and arfcn < 1023: return 925 + (arfcn - 974) * 0.2
31 if 511 < arfcn and arfcn < 886: return 1805 + (arfcn - 511) * 0.2
32 return -1
34 class Terminal( object ):
35 def __init__( self ):
36 # try to get portname from MUXer
37 import dbus
38 bus = dbus.SystemBus()
39 oMuxer = bus.get_object( "org.pyneo.muxer", "/org/pyneo/Muxer" )
40 iMuxer = dbus.Interface( oMuxer, "org.freesmartphone.GSM.MUX" )
41 port = iMuxer.AllocChannel( "celllocator.%d" % os.getpid() )
42 assert port, "could not get path from muxer. need to supply explicit portname"
43 print port
44 self.serial = serial.Serial( str(port), 115200, rtscts=False, xonxoff=False, timeout=2)
46 def open(self):
47 self.serial.open()
48 assert self.serial.isOpen(), "can't open serial port"
50 def close(self):
51 self.serial.close()
53 def write(self, cmd):
54 self.serial.write(cmd + '\r\n')
56 def read( self ):
57 chr = None
58 s = ''
59 while chr != '\n':
60 chr = self.serial.read()
61 if chr == '':
62 return None
63 if chr != '\n': s += chr
64 while len(s) > 0 and s[-1] in ('\r', '\n'):
65 s = s[0:-1]
66 return s
68 def exec_cmd(self, cmd):
69 while 1:
70 if debug: print 'Exec', cmd
71 sys.stdout.flush()
72 self.write(cmd)
73 result = []
74 restart = False
75 while len(result) == 0 or (result[-1] != 'OK' and result[-1] != 'ERROR'):
76 line = self.read()
77 if debug: print 'DEBUG', line
78 if line is None:
79 restart = True
80 break
81 sys.stdout.flush()
82 result.append(line)
83 if restart:
84 continue
85 start = 0
86 for start in range(len(result)):
87 if debug: print 'DEBUG exec', result[start], result[start] == cmd
88 if result[start] == cmd:
89 result = result[start+1:]
90 break
91 return result
93 def get_neighbour_cell_info(self):
94 result = self.exec_cmd('AT%EM=2,3')
95 count = int(result[0].split(' ')[1])
96 cells = []
97 hdrs = ('arfcn', 'c1', 'c2', 'rxlev', 'bsic', 'cell_id', 'lac', 'frame_offset', 'time_alignment', 'cba', 'cbq', 'cell_type_ind', 'rac', 'cell_resel_offset', 'temp_offset', 'rxlev_acc_min')
98 for cell in range(count):
99 d = {}
100 for i in range(len(hdrs)):
101 ## Cannot be sure, that the "result" will have the right format, better catch the exceptions
102 try:
103 d[hdrs[i]] = int(result[1+i].split(',')[cell])
104 except:
105 d[hdrs[i]] = None
106 if debug: print "DEBUG:","ResponseSplitError:","splitting:",result[1+i]
107 cells.append(d)
108 return cells
110 def get_location_and_paging_info(self):
111 result = self.exec_cmd('AT%EM=2,4')
112 data = result[0].split(' ')[1].split(',')
113 hdrs = ('bs_pa_mfrms', 't3212', 'mcc', 'mnc', 'tmsi')
114 d = {}
115 for i in range(len(hdrs)):
116 d[hdrs[i]] = int(data[i])
117 return d
119 def get_service_cell_info(self):
120 result = self.exec_cmd('AT%EM=2,1')
121 data = result[0].split(' ')[1].split(',')
122 hdrs = ('arfcn', 'c1', 'c2', 'rxlev', 'bsic', 'cell_id', 'dsc', 'txlev', 'tn', 'rlt', 'tav', 'rxlev_f', 'rxlev_s', 'rxqual_f', 'rxqual_s', 'lac', 'cba', 'cbq', 'cell_type_ind', 'vocoder')
123 d = {}
124 for i in range(len(hdrs)):
125 d[hdrs[i]] = int(data[i])
126 d['freq'] = arfcn_to_freq(d['arfcn'])
127 return d
130 class GPS:
131 def __init__(self):
132 self.bus = dbus.SystemBus()
133 self.objects = {}
134 self.gps_obj = None
135 self.usage_obj = None
136 self.clear()
138 def init(self):
139 self.usage_obj = self.tryGetProxy( 'org.freesmartphone.ousaged', '/org/freesmartphone/Usage' )
140 if not self.usage_obj:
141 print 'ERROR: Failed getting the usage object'
142 return False
144 self.usage_iface = dbus.Interface(self.usage_obj, 'org.freesmartphone.Usage')
145 self.usage_iface.RequestResource("GPS")
147 self.gps_obj = self.tryGetProxy( 'org.freesmartphone.ogpsd', '/org/freedesktop/Gypsy' )
148 if not self.gps_obj:
149 print 'ERROR: GPS failed to initialize'
150 return False
152 self.gps_accuracy_iface = dbus.Interface(self.gps_obj, 'org.freedesktop.Gypsy.Accuracy')
153 self.gps_accuracy_iface.connect_to_signal( "AccuracyChanged", self.cbAccuracyChanged )
154 self.gps_position_iface = dbus.Interface(self.gps_obj, 'org.freedesktop.Gypsy.Position')
155 self.gps_position_iface.connect_to_signal( "PositionChanged", self.cbPositionChanged )
157 def clear(self):
158 self.pdop = None
159 self.hdop = None
160 self.vdop = None
161 self.timestamp = None
162 self.lat = None
163 self.lon = None
164 self.alt = None
166 def uninit(self):
167 if self.usage_iface:
168 self.usage_iface.ReleaseResource("GPS")
170 def cbAccuracyChanged( self, fields, pdop, hdop, vdop ):
171 #print 'Accuracy changed', fields, pdop, hdop, vdop
172 self.fields = fields
173 self.pdop = float(pdop)
174 self.hdop = float(hdop)
175 self.vdop = float(vdop)
177 def cbPositionChanged( self, fields, timestamp, lat, lon, alt ):
178 #print 'Position changed', fields, timestamp, lat, lon, alt
179 self.fields = fields
180 self.timestamp = int(timestamp)
181 self.lat = float(lat)
182 self.lon = float(lon)
183 self.alt = float(alt)
185 def tryGetProxy( self, busname, objname ):
186 try:
187 return self.bus.get_object( busname, objname )
188 except DBusException, e:
189 logger.warning( "could not create proxy for %s:%s" % ( busname, objname ) )
191 class GsmCellLogger:
192 """Log GSM Cell location data, only logs when data is changed."""
193 def __init__(self):
194 self._f = None
195 self.f = None
196 self.logpoints = 0
198 ext_keys = ('lac', 'cell_id', 'arfcn', 'bsic', 'rxlev', 'tav', 'time_alignment')
200 def open(self, filename):
201 self._f = file(filename, 'a')
202 self.f = csv.writer(self._f)
204 # Create and write a header for the CSV file
205 row = ('record_type', 'timestamp', 'lon', 'lat', 'hdop', 'mcc', 'mnc') + self.ext_keys
206 self.f.writerow(row)
208 self.lastrow = None
210 ## Zero the number of logged points
211 self.logpoints = 0
214 def close(self):
215 self._f.close()
216 self._f = None
217 self.f = None
218 self.lastrow = None
220 def log(self, ts, lat, lon, hdop, loc, cell, neighs):
221 if hdop is None or float(hdop) > 3.0:
222 # Skip logging if the gps data doesn't exist or is not accurate enough
223 return
225 row = (lat, lon, hdop, loc, cell, neighs)
226 if row == self.lastrow:
227 # Nothing that is worth writing about
228 return
229 self.lastrow = row
231 def fields(d, keys):
232 res = []
233 for key in keys:
234 res.append(d.get(key, -1))
235 return res
236 common = [ts, lon, lat, hdop, loc['mcc'], loc['mnc']]
237 self.f.writerow([1] + common + fields(cell, self.ext_keys))
238 for neigh in neighs:
239 self.f.writerow([2] + common + fields(neigh, self.ext_keys))
240 ## Increment number of logged points
241 self.logpoints += 1
243 class DataCollector:
244 def __init__(self):
245 self.cancel_log = False
246 self.cb = lambda x,y,z: None
248 def init(self):
249 self.gps = GPS()
250 self.gps.init()
252 self.t = Terminal()
253 self.t.open()
255 import datetime
256 dtstr = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M')
258 self.cell_log = GsmCellLogger()
259 self.cell_log.open('log_cells-%s.csv' % dtstr)
260 gobject.timeout_add(5000, self.cell_logger)
261 print 'Logging starts'
263 def uninit(self):
264 self.cancel_log = True
266 self.cell_log.close()
267 self.t.close()
268 self.gps.uninit()
270 self.cell_log = None
271 self.t = None
272 self.gps = None
274 def callback_set(self, func):
275 self.cb = func
277 def cell_logger(self):
278 if self.cancel_log:
279 print 'Logging stopped'
280 self.cancel_log = False
281 return False
283 print
284 print self.gps.timestamp, self.gps.lat, self.gps.lon, self.gps.alt, self.gps.hdop, self.gps.vdop, self.gps.pdop
285 neigh = self.t.get_neighbour_cell_info()
286 loc = self.t.get_location_and_paging_info()
287 cell = self.t.get_service_cell_info()
288 print
289 print 'Nearby Cells:'
290 for ncell in neigh:
291 print 'cell_id', ncell['cell_id']
292 print 'bsic', ncell['bsic']
293 print 'rxlev', ncell['rxlev']
294 print 'lac', ncell['lac']
295 print 'time_alignment', ncell['time_alignment']
296 print
297 print
298 print 'This cell:'
299 print 'cell_id', cell['cell_id']
300 print 'lac', cell['lac']
301 print 'tav', cell['tav']
302 print 'bsic', cell['bsic']
303 print 'rxlev', cell['rxlev']
304 print 'mcc', loc['mcc']
305 print 'mnc', loc['mnc']
306 print
307 self.cell_log.log(self.gps.timestamp, self.gps.lat, self.gps.lon, self.gps.hdop, loc, cell, neigh)
308 self.cb(loc, cell, neigh)
309 self.gps.clear()
310 print 'done'
311 sys.stdout.flush()
312 return True
314 class Namespace: pass
316 class MainWindow:
317 def __init__(self):
318 self.data = DataCollector()
320 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
321 self.window.connect("delete_event", self.delete_event)
322 self.window.connect("destroy", self.destroy)
324 ## Vertical box to align result table lines and buttons
325 boxbig = gtk.VBox(False,0)
327 def add_displine( box, label_text, controls):
328 """ Add full line in table: first header, then 7 columns for cell+neighbours """
329 widgets = []
330 hbox = gtk.HBox(True,0)
331 for index,ctl in enumerate(controls):
332 if ctl == 'L':
333 label = gtk.Label(label_text[index])
334 label.modify_font(pango.FontDescription("Sherif 3 Bold"))
335 hbox.pack_start(label,True,True,0)
336 else:
337 data_label = gtk.Label(label_text[index])
338 data_label.modify_font(pango.FontDescription("Sherif 4"))
339 widgets.append(data_label)
340 hbox.pack_start(data_label,True,True,0)
341 box.pack_start(hbox,True,True,0)
342 return widgets
344 #### Button line on top: config, ..., ?
345 box = gtk.HBox(True,0)
346 ## Config button - for future use
347 self.configbutton = gtk.Button('Configuration')
348 #self.configbutton.connect("clicked", self.toggle_log, None)
349 box.pack_start(self.configbutton,True,True,0)
350 boxbig.pack_start(box,True,True,0)
352 ######## Display fields
353 ## Column header
354 self.header = add_displine(boxbig,['','C','N1','N2','N3','N4','N5','N6'],['L','L','L','L','L','L','L','L'])
356 ## Displayed values
357 self.lac = add_displine(boxbig, ['LAC','-','-','-','-','-','-','-'], ['L','','','','','','',''])
358 self.cellid = add_displine(boxbig, ['Cell','-','-','-','-','-','-','-'],['L','','','','','','',''])
359 self.bcch = add_displine(boxbig, ['BCCH','-','-','-','-','-','-','-'],['L','','','','','','',''])
360 self.bsic = add_displine(boxbig, ['BSIC','-','-','-','-','-','-','-'],['L','','','','','','',''])
361 self.rxlev = add_displine(boxbig, ['RxL','-','-','-','-','-','-','-'], ['L','','','','','','',''])
362 self.time_alignment = add_displine(boxbig, ['T/A','','-','-','-','-','-','-'],['L','','','','','','',''])
364 ## Multiparameter line: params either only for current cell, or same for every cells
365 self.multipar = add_displine(boxbig, ['TxL','-','TAV','-','MCC','-','MNC','-'],['L','','L','','L','','L','',])
367 ## Displaying GPS info + number of logged points as the last field
368 self.gpsline = add_displine(boxbig,['GPS','Lat','-','Lon','-','hdop','-','0'],['L','L','','L','','L','',''])
369 ##########
371 ## Start log button - Toggle
372 buttonbox = gtk.HBox(True,0)
373 self.logbutton = gtk.ToggleButton('Log')
374 self.logbutton.connect("clicked", self.toggle_log, None)
375 buttonbox.pack_start(self.logbutton,True,True,0)
377 ## Exit button - only works when not logging!
378 self.exitbutton = gtk.Button('Exit')
379 self.exitbutton.connect("clicked", self.destroy, None)
380 buttonbox.pack_start(self.exitbutton,True,True,0)
381 boxbig.pack_start(buttonbox,True,True,0)
383 self.data.callback_set(self.data_cb)
385 self.window.add(boxbig)
386 self.window.set_title('Cell Locator')
387 self.window.show_all()
390 def data_cb(self, loc, cell, neighbours):
391 """ Display results in table: cell + neighbours"""
392 self.lac[0].set_label(str(cell['lac']))
393 self.cellid[0].set_label(str(cell['cell_id']))
394 self.rxlev[0].set_label(str(cell['rxlev']))
395 self.bcch[0].set_label(str(cell['arfcn']))
396 self.bsic[0].set_label(str(cell['bsic']))
397 self.multipar[0].set_label(str(cell['txlev']))
398 self.multipar[1].set_label(str(cell['tav']))
399 self.multipar[2].set_label(str(loc['mcc']))
400 self.multipar[3].set_label(str(loc['mnc']))
403 index = 0
404 for ncell in neighbours:
405 index += 1
406 self.lac[index].set_label(str(ncell['lac']))
407 self.cellid[index].set_label(str(ncell['cell_id']))
408 self.rxlev[index].set_label(str(ncell['rxlev']))
409 self.bsic[index].set_label(str(ncell['bsic']))
410 self.bcch[index].set_label(str(cell['arfcn']))
411 self.time_alignment[index].set_label(str(ncell['time_alignment']))
413 ## Erase labels if there are less than 6 neighbours
414 if index < 6:
415 for i in range(index+1,7):
416 self.lac[i].set_label('-')
417 self.cellid[i].set_label('-')
418 self.rxlev[i].set_label('-')
419 self.bsic[i].set_label('-')
420 self.bcch[i].set_label('-')
421 self.time_alignment[i].set_label('-')
423 ## Display GPS data
424 if self.data.gps.lat != None :
425 self.gpsline[0].set_label(str(round(self.data.gps.lat,3)))
426 else:
427 self.gpsline[0].set_label('N/A')
428 if self.data.gps.lon != None :
429 self.gpsline[1].set_label(str(round(self.data.gps.lon,3)))
430 else:
431 self.gpsline[1].set_label('N/A')
432 if self.data.gps.hdop != None :
433 self.gpsline[2].set_label(str(self.data.gps.hdop))
434 else:
435 self.gpsline[2].set_label('N/A')
437 ## Display number of logged points
438 self.gpsline[3].set_label(str(self.data.cell_log.logpoints))
440 def main(self):
441 gtk.main()
443 def toggle_log(self, widget, data=None):
444 """ Toggle logging - and Exit/Config button availability """
445 if widget.get_active():
446 self.exitbutton.set_sensitive(False)
447 self.configbutton.set_sensitive(False)
448 widget.set_label('Stop Log')
449 self.data.init()
450 else:
451 self.data.uninit()
452 widget.set_label('Log')
453 self.exitbutton.set_sensitive(True)
454 self.configbutton.set_sensitive(True)
456 def delete_event(self, widget, event, data=None):
457 return False # Allow the window to close
459 def destroy(self, widget, data=None):
460 gtk.main_quit()
463 if __name__ == "__main__":
464 mainwin = MainWindow()
465 mainwin.main()