+#!/usr/bin/env python
+
+from PyQt4.QtGui import *
+from PyQt4.QtCore import SIGNAL, SLOT, Qt, QTimer, QThread
+
+from time import sleep
+from threading import Thread
+from datetime import datetime
+
+import bluetooth
+import os
+import socket
+import sys
+import dbus
+
+from nmea import GPSData
+
+devices = [
+ [ "00:0D:B5:38:9E:16", "BT-335" ],
+ [ "00:0D:B5:38:AF:C7", "BT-821" ],
+]
+
+reconnect_delay = 10
+logprefix = "/home/user/gps/"
+
+# lets you get/set powered state of your bluetooth adapter
+# code from http://tomch.com/wp/?p=132
+def enable_bluetooth(enabled = None):
+ bus = dbus.SystemBus();
+ root = bus.get_object('org.bluez', '/')
+ manager = dbus.Interface(root, 'org.bluez.Manager')
+ defaultAdapter = manager.DefaultAdapter()
+ obj = bus.get_object('org.bluez', defaultAdapter)
+ adapter = dbus.Interface(obj, 'org.bluez.Adapter')
+ props = adapter.GetProperties()
+
+ if enabled is None:
+ return adapter.GetProperties()['Powered']
+ elif enabled:
+ adapter.SetProperty('Powered', True)
+ else:
+ adapter.SetProperty('Powered', False)
+
+
+class GPSThread(QThread):
+ def __init__(self, addr, name, parent = None):
+ QThread.__init__(self, parent)
+
+ self.addr = addr
+ self.name = name
+ self.exiting = False
+ self.logfile = None
+
+ self.total_length = 0
+ self.total_connects = 0
+ self.total_lines = 0
+
+ def __del__(self):
+ self.exiting = True
+ self.wait()
+
+ def stop(self):
+ self.exiting = True
+
+ def init(self):
+ logname = os.path.join(logprefix, "%s.%s.nmea" % (datetime.today().strftime("%Y.%m.%d"), self.name))
+
+ enable_bluetooth(True)
+ self.logfile = open(logname, 'a')
+
+ def cleanup(self):
+ if self.logfile is not None:
+ self.logfile.close()
+ self.logfile = None
+
+ def main_loop(self):
+ error = None
+ buffer = ""
+ last_length = 0
+ socket = None
+
+ gpsdata = GPSData()
+
+ try:
+ # connect
+ while not self.exiting and socket is None:
+ self.emit(SIGNAL("status_updated(QString)"), "Connecting...")
+ socket = bluetooth.BluetoothSocket()
+ socket.connect((self.addr, 1))
+ socket.settimeout(10)
+ self.total_connects += 1
+
+ self.emit(SIGNAL("status_updated(QString)"), "Connected")
+
+ # read
+ while not self.exiting and socket is not None:
+ chunk = socket.recv(1024)
+
+ if len(chunk) == 0:
+ raise Exception("Zero read")
+
+ buffer += chunk
+ self.total_length += len(chunk)
+ self.logfile.write(chunk)
+
+ # parse lines
+ lines = buffer.split('\n')
+ buffer = lines.pop()
+
+ for line in lines:
+ gpsdata.parse_nmea_string(line)
+
+ self.total_lines += len(lines)
+
+ # update display info every 10k
+ if self.total_length - last_length > 512:
+ self.emit(SIGNAL("status_updated(QString)"), "Logged %d lines, %d bytes, %d connects" % (self.total_lines, self.total_length, self.total_connects))
+ last_length = self.total_length
+
+ self.emit(SIGNAL("data_updated(QString)"), gpsdata.dump())
+
+ except IOError, e:
+ error = "%s: %s" % ("Cannot connect" if socket is None else "Read error", str(e))
+ except:
+ error = "%s: %s" % ("Cannot connect" if socket is None else "Read error", sys.exc_info())
+
+ if self.exiting or error is None: return
+
+ # process error: wait some time and retry
+ global reconnect_delay
+ count = reconnect_delay
+ while not self.exiting and count > 0:
+ self.emit(SIGNAL("status_updated(QString)"), "%s, retry in %d" % (error, count))
+ sleep(1)
+ count -= 1
+
+ socket = None
+
+ def run(self):
+ try:
+ self.init()
+
+ while not self.exiting:
+ self.main_loop()
+ except Exception, e:
+ self.emit(SIGNAL("status_updated(QString)"), "FATAL: %s" % str(e))
+
+ try:
+ self.cleanup()
+ except:
+ self.emit(SIGNAL("status_updated(QString)"), "FATAL: cleanup failed")
+
+ self.emit(SIGNAL("status_updated(QString)"), "stopped")
+
+class ContainerWidget(QWidget):
+ def __init__(self, addr, name, parent=None):
+ QWidget.__init__(self, parent)
+
+ # data
+ self.addr = addr
+ self.name = name
+ self.thread = None
+ self.status = "stopped"
+
+ # UI: header
+ self.startbutton = QPushButton("Start")
+ self.startbutton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
+ self.connect(self.startbutton, SIGNAL('clicked()'), self.start_thread)
+
+ self.stopbutton = QPushButton("Stop")
+ self.stopbutton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
+ self.stopbutton.setEnabled(False)
+ self.connect(self.stopbutton, SIGNAL('clicked()'), self.stop_thread)
+
+ self.statuswidget = QLabel()
+ self.statuswidget.setWordWrap(True)
+
+ self.monitorwidget = QLabel()
+
+ header = QHBoxLayout()
+ header.addWidget(self.startbutton)
+ header.addWidget(self.stopbutton)
+ header.addWidget(self.statuswidget)
+
+ self.layout = QVBoxLayout()
+ self.layout.addLayout(header)
+ self.layout.addWidget(self.monitorwidget)
+
+ self.setLayout(self.layout)
+
+ # done
+ self.update_status()
+
+ def __del__(self):
+ self.thread = None
+
+ def start_thread(self):
+ if self.thread is not None:
+ return
+
+ self.startbutton.setEnabled(False)
+ self.stopbutton.setEnabled(True)
+
+ self.thread = GPSThread(self.addr, self.name, self)
+ self.connect(self.thread, SIGNAL("status_updated(QString)"), self.update_status)
+ self.connect(self.thread, SIGNAL("data_updated(QString)"), self.update_monitor)
+ self.connect(self.thread, SIGNAL("finished()"), self.gc_thread)
+ self.thread.start()
+
+ def stop_thread(self):
+ if self.thread is None:
+ return
+
+ self.stopbutton.setEnabled(False)
+ self.thread.stop()
+
+ def gc_thread(self):
+ self.thread = None # join
+
+ self.startbutton.setEnabled(True)
+ self.stopbutton.setEnabled(False)
+ self.update_status()
+
+ def update_status(self, status = None):
+ if status is not None:
+ self.status = status
+ self.statuswidget.setText("%s: %s" % (self.name, self.status))
+
+ def update_monitor(self, data = None):
+ self.monitorwidget.setText(data)
+
+class MainWindow(QWidget):
+ def __init__(self, parent=None):
+ QWidget.__init__(self, parent)
+ self.setWindowTitle("UberLogger")
+
+ layout = QVBoxLayout()
+
+ global devices
+ for addr, name in devices:
+ layout.addWidget(ContainerWidget(addr, name))
+
+ self.setLayout(layout)
+
+def main():
+ app = QApplication(sys.argv)
+
+ window = MainWindow()
+ window.show()
+ sys.exit(app.exec_())
+
+if __name__ == "__main__":
+ main()