Initial import
[uberlogger] / btgpslogger.py
1 #!/usr/bin/env python
2
3 from PyQt4.QtGui import *
4 from PyQt4.QtCore import SIGNAL, SLOT, Qt, QTimer, QThread
5
6 from time import sleep
7 from threading import Thread
8 from datetime import datetime
9
10 import bluetooth
11 import os
12 import socket
13 import sys
14 import dbus
15
16 from nmea import GPSData
17
18 devices = [
19         [ "00:0D:B5:38:9E:16", "BT-335" ],
20         [ "00:0D:B5:38:AF:C7", "BT-821" ],
21 ]
22
23 reconnect_delay = 10
24 logprefix = "/home/user/gps/"
25
26 # lets you get/set powered state of your bluetooth adapter
27 # code from http://tomch.com/wp/?p=132
28 def enable_bluetooth(enabled = None):
29         bus = dbus.SystemBus();
30         root = bus.get_object('org.bluez', '/')
31         manager = dbus.Interface(root, 'org.bluez.Manager')
32         defaultAdapter = manager.DefaultAdapter()
33         obj = bus.get_object('org.bluez', defaultAdapter)
34         adapter = dbus.Interface(obj, 'org.bluez.Adapter')
35         props = adapter.GetProperties()
36
37         if enabled is None:
38                 return adapter.GetProperties()['Powered']
39         elif enabled:
40                 adapter.SetProperty('Powered', True)
41         else:
42                 adapter.SetProperty('Powered', False)
43
44
45 class GPSThread(QThread):
46         def __init__(self, addr, name, parent = None):
47                 QThread.__init__(self, parent)
48
49                 self.addr = addr
50                 self.name = name
51                 self.exiting = False
52                 self.logfile = None
53
54                 self.total_length = 0
55                 self.total_connects = 0
56                 self.total_lines = 0
57
58         def __del__(self):
59                 self.exiting = True
60                 self.wait()
61
62         def stop(self):
63                 self.exiting = True
64
65         def init(self):
66                 logname = os.path.join(logprefix, "%s.%s.nmea" % (datetime.today().strftime("%Y.%m.%d"), self.name))
67
68                 enable_bluetooth(True)
69                 self.logfile = open(logname, 'a')
70
71         def cleanup(self):
72                 if self.logfile is not None:
73                         self.logfile.close()
74                         self.logfile = None
75
76         def main_loop(self):
77                 error = None
78                 buffer = ""
79                 last_length = 0
80                 socket = None
81
82                 gpsdata = GPSData()
83
84                 try:
85                         # connect
86                         while not self.exiting and socket is None:
87                                 self.emit(SIGNAL("status_updated(QString)"), "Connecting...")
88                                 socket = bluetooth.BluetoothSocket()
89                                 socket.connect((self.addr, 1))
90                                 socket.settimeout(10)
91                                 self.total_connects += 1
92
93                                 self.emit(SIGNAL("status_updated(QString)"), "Connected")
94
95                         # read
96                         while not self.exiting and socket is not None:
97                                 chunk = socket.recv(1024)
98
99                                 if len(chunk) == 0:
100                                         raise Exception("Zero read")
101
102                                 buffer += chunk
103                                 self.total_length += len(chunk)
104                                 self.logfile.write(chunk)
105
106                                 # parse lines
107                                 lines = buffer.split('\n')
108                                 buffer = lines.pop()
109
110                                 for line in lines:
111                                         gpsdata.parse_nmea_string(line)
112
113                                 self.total_lines += len(lines)
114
115                                 # update display info every 10k
116                                 if self.total_length - last_length > 512:
117                                         self.emit(SIGNAL("status_updated(QString)"), "Logged %d lines, %d bytes, %d connects" % (self.total_lines, self.total_length, self.total_connects))
118                                         last_length = self.total_length
119
120                                 self.emit(SIGNAL("data_updated(QString)"), gpsdata.dump())
121
122                 except IOError, e:
123                         error = "%s: %s" % ("Cannot connect" if socket is None else "Read error", str(e))
124                 except:
125                         error = "%s: %s" % ("Cannot connect" if socket is None else "Read error", sys.exc_info())
126
127                 if self.exiting or error is None: return
128
129                 # process error: wait some time and retry
130                 global reconnect_delay
131                 count = reconnect_delay
132                 while not self.exiting and count > 0:
133                         self.emit(SIGNAL("status_updated(QString)"), "%s, retry in %d" % (error, count))
134                         sleep(1)
135                         count -= 1
136
137                 socket = None
138
139         def run(self):
140                 try:
141                         self.init()
142
143                         while not self.exiting:
144                                 self.main_loop()
145                 except Exception, e:
146                         self.emit(SIGNAL("status_updated(QString)"), "FATAL: %s" % str(e))
147
148                 try:
149                         self.cleanup()
150                 except:
151                         self.emit(SIGNAL("status_updated(QString)"), "FATAL: cleanup failed")
152
153                 self.emit(SIGNAL("status_updated(QString)"), "stopped")
154
155 class ContainerWidget(QWidget):
156         def __init__(self, addr, name, parent=None):
157                 QWidget.__init__(self, parent)
158
159                 # data
160                 self.addr = addr
161                 self.name = name
162                 self.thread = None
163                 self.status = "stopped"
164
165                 # UI: header
166                 self.startbutton = QPushButton("Start")
167                 self.startbutton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
168                 self.connect(self.startbutton, SIGNAL('clicked()'), self.start_thread)
169
170                 self.stopbutton = QPushButton("Stop")
171                 self.stopbutton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
172                 self.stopbutton.setEnabled(False)
173                 self.connect(self.stopbutton, SIGNAL('clicked()'), self.stop_thread)
174
175                 self.statuswidget = QLabel()
176                 self.statuswidget.setWordWrap(True)
177
178                 self.monitorwidget = QLabel()
179
180                 header = QHBoxLayout()
181                 header.addWidget(self.startbutton)
182                 header.addWidget(self.stopbutton)
183                 header.addWidget(self.statuswidget)
184
185                 self.layout = QVBoxLayout()
186                 self.layout.addLayout(header)
187                 self.layout.addWidget(self.monitorwidget)
188
189                 self.setLayout(self.layout)
190
191                 # done
192                 self.update_status()
193
194         def __del__(self):
195                 self.thread = None
196
197         def start_thread(self):
198                 if self.thread is not None:
199                         return
200
201                 self.startbutton.setEnabled(False)
202                 self.stopbutton.setEnabled(True)
203
204                 self.thread = GPSThread(self.addr, self.name, self)
205                 self.connect(self.thread, SIGNAL("status_updated(QString)"), self.update_status)
206                 self.connect(self.thread, SIGNAL("data_updated(QString)"), self.update_monitor)
207                 self.connect(self.thread, SIGNAL("finished()"), self.gc_thread)
208                 self.thread.start()
209
210         def stop_thread(self):
211                 if self.thread is None:
212                         return
213
214                 self.stopbutton.setEnabled(False)
215                 self.thread.stop()
216
217         def gc_thread(self):
218                 self.thread = None # join
219
220                 self.startbutton.setEnabled(True)
221                 self.stopbutton.setEnabled(False)
222                 self.update_status()
223
224         def update_status(self, status = None):
225                 if status is not None:
226                         self.status = status
227                 self.statuswidget.setText("%s: %s" % (self.name, self.status))
228
229         def update_monitor(self, data = None):
230                 self.monitorwidget.setText(data)
231
232 class MainWindow(QWidget):
233         def __init__(self, parent=None):
234                 QWidget.__init__(self, parent)
235                 self.setWindowTitle("UberLogger")
236
237                 layout = QVBoxLayout()
238
239                 global devices
240                 for addr, name in devices:
241                         layout.addWidget(ContainerWidget(addr, name))
242
243                 self.setLayout(layout)
244
245 def main():
246         app = QApplication(sys.argv)
247
248         window = MainWindow()
249         window.show()
250         sys.exit(app.exec_())
251
252 if __name__ == "__main__":
253         main()