Initial import
authorDmitry Marakasov <amdmi3@amdmi3.ru>
Wed, 28 Apr 2010 09:21:55 +0000 (13:21 +0400)
committerDmitry Marakasov <amdmi3@amdmi3.ru>
Wed, 28 Apr 2010 15:58:59 +0000 (19:58 +0400)
TODO [new file with mode: 0644]
btgpslogger.py [new file with mode: 0755]
nmea.py [new file with mode: 0644]
welcome [deleted file]

diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..7143475
--- /dev/null
+++ b/TODO
@@ -0,0 +1,6 @@
+- Single start/stop button
+- Table for GPS widgets
+- Clean exit
+- Enable bluetooth on start
+- Options changeable in runtime
+- Status and widget updates every second
diff --git a/btgpslogger.py b/btgpslogger.py
new file mode 100755 (executable)
index 0000000..40c83ce
--- /dev/null
@@ -0,0 +1,253 @@
+#!/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()
diff --git a/nmea.py b/nmea.py
new file mode 100644 (file)
index 0000000..4d544a8
--- /dev/null
+++ b/nmea.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+
+import re
+
+class GPSData:
+       def __init__(self):
+               self.reset()
+
+       def reset(self):
+               self.time = None
+               self.lat = None
+               self.lon = None
+               self.quality = None
+               self.nsat = None
+               self.hdop = None
+               self.ele = None
+
+       def parse_nmea_string(self, string):
+               try:
+                       string = string.lstrip('$')
+                       (data, dummy, csum) = string.rpartition('*')
+
+                       mycsum = 0
+                       for byte in data:
+                               mycsum ^= ord(byte)
+
+                       if mycsum != int(csum, 16): # can throw with invalid csum
+                               return
+
+                       data = data.split(',')
+
+                       if data[0] == 'GPGGA':
+                               self.reset()
+
+                               # time
+                               (temp, n) = re.subn('^(\d{2})(\d{2})(\d{2}\.\d+)$', '\\1:\\2:\\3', data[1])
+                               if n == 1:
+                                       self.time = temp
+
+                               # latitude
+                               (temp, n) = re.subn('^(\d{2})(\d{2})\.(.*)$', '\\1.\\2\\3', data[2])
+                               if n == 1:
+                                       self.lat = temp + data[3]
+
+                               # longitude
+                               (temp, n) = re.subn('^0?(\d{2,3}?)(\d{2})\.(.*)$', '\\1.\\2\\3', data[4])
+                               if n == 1:
+                                       self.lon = temp + data[5]
+
+                               # quality
+                               if int(data[6]) == 0:
+                                       self.quality = "none"
+                               elif int(data[6]) == 1:
+                                       self.quality = "normal"
+                               elif int(data[6]) == 2:
+                                       self.quality = "diff"
+                               elif int(data[6]) == 3:
+                                       self.quality = "precision"
+                               else:
+                                       self.quality = None
+
+                               # other params
+                               self.nsat = int(data[7])
+                               self.hdop = data[8]
+                               self.ele = data[9]
+               except:
+                       return
+
+       def dump(self):
+               return "Time: %s\tQuality: %s\nLat: %s\tSat: %s\nLon: %s\tHDOP: %s\nEle: %s" % (self.time, self.quality, self.lat, self.nsat, self.lon, self.hdop, self.ele)
diff --git a/welcome b/welcome
deleted file mode 100644 (file)
index e69de29..0000000