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>
14 import sys
, os
, serial
, time
, dbus
, csv
22 from dbus
.mainloop
.glib
import DBusGMainLoop
23 DBusGMainLoop(set_as_default
=True)
25 def arfcn_to_freq(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
34 class Terminal( object ):
36 # try to get portname from MUXer
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"
44 self
.serial
= serial
.Serial( str(port
), 115200, rtscts
=False, xonxoff
=False, timeout
=2)
48 assert self
.serial
.isOpen(), "can't open serial port"
54 self
.serial
.write(cmd
+ '\r\n')
60 chr = self
.serial
.read()
63 if chr != '\n': s
+= chr
64 while len(s
) > 0 and s
[-1] in ('\r', '\n'):
68 def exec_cmd(self
, cmd
):
70 if debug
: print 'Exec', cmd
75 while len(result
) == 0 or (result
[-1] != 'OK' and result
[-1] != 'ERROR'):
77 if debug
: print 'DEBUG', line
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:]
93 def get_neighbour_cell_info(self
):
94 result
= self
.exec_cmd('AT%EM=2,3')
95 count
= int(result
[0].split(' ')[1])
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
):
100 for i
in range(len(hdrs
)):
101 ## Cannot be sure, that the "result" will have the right format, better catch the exceptions
103 d
[hdrs
[i
]] = int(result
[1+i
].split(',')[cell
])
106 if debug
: print "DEBUG:","ResponseSplitError:","splitting:",result
[1+i
]
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')
115 for i
in range(len(hdrs
)):
116 d
[hdrs
[i
]] = int(data
[i
])
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')
124 for i
in range(len(hdrs
)):
125 d
[hdrs
[i
]] = int(data
[i
])
126 d
['freq'] = arfcn_to_freq(d
['arfcn'])
132 self
.bus
= dbus
.SystemBus()
135 self
.usage_obj
= None
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'
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' )
149 print 'ERROR: GPS failed to initialize'
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
)
161 self
.timestamp
= None
168 self
.usage_iface
.ReleaseResource("GPS")
170 def cbAccuracyChanged( self
, fields
, pdop
, hdop
, vdop
):
171 #print 'Accuracy changed', fields, pdop, hdop, vdop
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
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
):
187 return self
.bus
.get_object( busname
, objname
)
188 except DBusException
, e
:
189 logger
.warning( "could not create proxy for %s:%s" % ( busname
, objname
) )
192 """Log GSM Cell location data, only logs when data is changed."""
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
210 ## Zero the number of logged points
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
225 row
= (lat
, lon
, hdop
, loc
, cell
, neighs
)
226 if row
== self
.lastrow
:
227 # Nothing that is worth writing about
234 res
.append(d
.get(key
, -1))
236 common
= [ts
, lon
, lat
, hdop
, loc
['mcc'], loc
['mnc']]
237 self
.f
.writerow([1] + common
+ fields(cell
, self
.ext_keys
))
239 self
.f
.writerow([2] + common
+ fields(neigh
, self
.ext_keys
))
240 ## Increment number of logged points
245 self
.cancel_log
= False
246 self
.cb
= lambda x
,y
,z
: None
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'
264 self
.cancel_log
= True
266 self
.cell_log
.close()
274 def callback_set(self
, func
):
277 def cell_logger(self
):
279 print 'Logging stopped'
280 self
.cancel_log
= False
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()
289 print 'Nearby Cells:'
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']
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']
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
)
314 class Namespace
: pass
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 """
330 hbox
= gtk
.HBox(True,0)
331 for index
,ctl
in enumerate(controls
):
333 label
= gtk
.Label(label_text
[index
])
334 label
.modify_font(pango
.FontDescription("Sherif 3 Bold"))
335 hbox
.pack_start(label
,True,True,0)
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)
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
354 self
.header
= add_displine(boxbig
,['','C','N1','N2','N3','N4','N5','N6'],['L','L','L','L','L','L','L','L'])
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','',''])
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']))
404 for ncell
in neighbours
:
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
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('-')
424 if self
.data
.gps
.lat
!= None :
425 self
.gpsline
[0].set_label(str(round(self
.data
.gps
.lat
,3)))
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)))
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
))
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
))
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')
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):
463 if __name__
== "__main__":
464 mainwin
= MainWindow()