From: Dmitry Marakasov Date: Wed, 28 Apr 2010 09:21:55 +0000 (+0400) Subject: Initial import X-Git-Url: https://vcs.maemo.org/git/?p=uberlogger;a=commitdiff_plain;h=2c1cebb5f11b486b6b7d8e5777a1a306a58c870f Initial import --- diff --git a/TODO b/TODO new file mode 100644 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 index 0000000..40c83ce --- /dev/null +++ b/btgpslogger.py @@ -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 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 index e69de29..0000000