-
Notifications
You must be signed in to change notification settings - Fork 1
/
gui.py
428 lines (357 loc) · 14.7 KB
/
gui.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
"""Graphical User Interface (GUI)
This file designs a graphical-user-interface which runs all Pi-Pi management
via one event loop thread, two worker threads, and a classless method.
Notes
-------------------------------------------------------------------------------
Created by Austin Gilbert, Aashima Mehta, and Cameron Ufland for the University
of Washington, Bothell in affiliation with PACCAR Inc.
"""
import sys, time
import socket
import pi
import broadcast
import subprocess
import logging
from PyQt5.uic import loadUi
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QDialog, QApplication, QLabel, QWidget
logging.basicConfig(format="%(message)s", level=logging.INFO)
class MainWorker(QThread):
"""The main worker thread used to run all Pi-Pi management code and update
status, progress, and version number on the GUI.
The Pi-Pi management code broadcasts the Pi's version number to other
Pis and sets up a server or client depending on the version number
received from other Pis.
Attributes
----------
status : pyqtSignal -> str
the signal to update logging status and display on GUI
progress : pyqtSignal -> int
the signal to update the progress bar and display on GUI
version : pyqtSignal -> int
the signal to update the version number and display on GUI
finished : pyqtSignal
the signal to indicate finishing the thread
Methods
-------
run()
runs the Pi-Pi management program and emits all signals
stop()
stops the thread by setting boolean "running" to false
"""
status = pyqtSignal(str)
progress = pyqtSignal(int)
version = pyqtSignal(str)
finished = pyqtSignal()
def __init__(self):
super(MainWorker, self).__init__()
self.running = True
def run(self):
"""Runs the Pi-Pi management program and emits all signals. The Pi-Pi
management program broadcasts the Pi's version number to other Pi's and
sets up a server or client depending on the version number received from
other Pis.
"""
self.status.emit('Starting...')
# Reads version number of this Pi
with open('update.txt', 'r') as file:
version = int(file.readline())
self.version.emit(f'{version}')
this_Pi = pi.Pi(version, getSystemIP())
time.sleep(1)
# Loops until Pi is found
while(self.running):
# TODO: if 'Cancel' button pressed, exit while loop
bdct = broadcast.Broadcast(this_Pi.getVersion())
bdct.txBroadcast() # broadcasts version number to network
oldTime = time.time()
# Retries each time interval
while (time.time()-oldTime) < 1.0:
self.status.emit('Searching...')
ver, addr = bdct.rxBroadcast() # Receives broadcasts on the
# network
ver = int(ver)
# If program receives its own broadcast...
if addr[0] == this_Pi.getIP()or addr[0] == "": continue
else:
# If no broadcast found...
if ver == int(this_Pi.getVersion()) or ver == -1: continue
else:
self.status.emit('Truck Found!')
time.sleep(1)
# If this Pi has outdated version...
if ver > int(this_Pi.getVersion()):
self.status.emit('Preparing for Update...')
self.progress.emit(10)
time.sleep(1)
# If server runs on this Pi successfully...
if (self.runServer(this_Pi)):
this_Pi.setVersion(ver) # Updates this Pi's
# version number
# If this Pi has an up-to-date version...
elif ver < this_Pi.getVersion():
self.status.emit('Preparing for Transfer...')
self.progress.emit(10)
time.sleep(1)
# Runs client on this Pi
self.runClient(pi.Pi(ver, addr[0]))
break
def runServer(self, localPi):
"""Runs a TCP server on this Pi, thereby recieving an update file.
Parameters
----------
localPi : Pi
the Pi object to designate this Pi
Returns
-------
bool
a boolean representing the success of the function
"""
# Tries running a TCP server...
try:
serverAddr = (localPi.getIP(), 1750) # Creates server address with
# local IP and Port 1750
self.status.emit('Establishing Connection...')
time.sleep(1)
# Starts server
with socket.socket() as server:
server.bind(serverAddr)
server.listen(1) # Listens for remote client
server.settimeout(10) # Starts 10 second server timeout timer
conn, connaddr = server.accept() # Connects to remote client
self.status.emit('Connection Successful!')
self.progress.emit(20)
time.sleep(1)
# With successful connection...
with conn:
self.status.emit('Downloading...')
self.progress.emit(40)
time.sleep(1)
chunk = conn.recv(4096) # Downloads first chunk (4 kB)
data = chunk # Stores the first chunk
# While more chunks exist...
while chunk:
chunk = conn.recv(4096) # Downloads chunks
data = data + chunk # Stores chunks
self.progress.emit(90)
time.sleep(1)
# Writes stored data to local update file
with open('update.txt', 'wb') as update:
update.write(data)
self.status.emit("Update Downloaded Successfully!")
self.progress.emit(100)
time.sleep(1)
return True
# Error in running TCP server...
except:
self.status.emit("Error: Timeout in server.")
time.sleep(1)
return False
def runClient(self, remotePi):
"""Runs a TCP Client on this Pi, thereby sending an update file.
Parameters
----------
remotePi : Pi
the Pi object to designate the remote Pi
Returns
-------
bool
a boolean representing the success of the function
"""
# Tries running a TCP client...
try:
clientAddr = (remotePi.getIP(), 1750) # Creates client address with
# local IP and Port 1750
self.status.emit('Establishing Connection...')
time.sleep(1)
# Starts client
with socket.socket() as client:
client.settimeout(10) # Starts 10 second client timeout timer
client.connect(clientAddr) # Connects to remote server
self.status.emit('Connection Successful!')
self.progress.emit(20)
time.sleep(1)
# Reads and sends stored data to server
self.status.emit('Sending...')
self.progress.emit(40)
time.sleep(1)
with open('update.txt', 'rb') as update:
client.sendfile(update, 0) # Sends file
self.progress.emit(90)
time.sleep(1)
self.status.emit("Update Sent Successfully!")
self.progress.emit(100)
time.sleep(1)
client.close()
return True
# Error in running TCP server...
except:
self.status.emit("Error: Timeout in client.")
time.sleep(1)
return False
def stop(self, restart):
"""Stops the thread by setting boolean "running" to false"""
self.status.emit('Ending Broadcast...')
time.sleep(1)
if restart:
self.status.emit('Restarting...')
time.sleep(1)
self.running = False
self.finished.emit()
class SignalWorker(QThread):
"""The secondary worker thread used to run subprocess terminal commands and
update the signal strength and signal quality on the GUI.
The subprocess code uses the "ifconfig" bash command to get information
on signal strength and quality in the form of a dBm and percentage
value respectively.
Attributes
----------
signalStrength : pyqtSignal -> str
the internal signal to update the signal strength and display on GUI
signalQuality : pyqtSignal -> str
the internal signal to update the signal quality and display on GUI
Methods
-------
run()
runs the subprocess program and emits all signals
Notes
-----
This method will run until the window exits on a click/tap of the "X".
"""
signalStrength = pyqtSignal(str)
signalQuality = pyqtSignal(str)
def __init__(self):
super(SignalWorker, self).__init__()
def run(self):
"""Runs the subprocess program and emits all signals."""
while(True):
batcmd = 'sudo iwlist wlan0 scan | egrep -C 2 "my-mesh-network"'
getSignal = str(subprocess.check_output(batcmd, shell = True))
try:
self.signalStrength.emit('{} dBm'.format(int(getSignal[50:53])))
except ValueError:
self.signalStrength.emit('{} dBm'.format(int(getSignal[50:52])))
except:
self.signalStrength.emit('Error')
try:
self.signalQuality.emit('{:.0%}'.format(float(getSignal[30:32])
/70.0))
except ValueError:
self.signalQuality.emit('{:.0%}'.format(float(getSignal[30:31])
/70.0))
except:
self.signalQuality.emit('Error')
class PiPiTransfer(QDialog):
"""The class used to design a window with signals and slots for the Pi-Pi
management program.
Methods
-------
runProgram()
manages main worker thread and its signals
collectSignal()
manages secondary worker thread and its signals
technicianMode()
stops the main worker thread and switches screens to Pi-Phone screen
cancelled()
restarts all threads; called when cancel button is pressed
writeStatus(label)
writes the status to the GUI
writeProgress(value)
writes the progress bar value to the GUI
writeSignalStrength(label)
writes the signal strength to the GUI
writeSignalQuality(label)
writes the signal quality to the GUI
writeVersionNum(label)
writes the version number to the GUI
"""
def __init__(self):
super(PiPiTransfer, self).__init__()
loadUi("pipitransfer.ui",self) #editor must only open "Pi GUI Folder"
self.cancelButton.clicked.connect(self.cancelled)
self.runProgram()
self.collectSignal()
def runProgram(self):
"""Manages main worker thread and its signals."""
self.worker = MainWorker()
self.finished.connect(self.worker.deleteLater)
self.worker.status.connect(self.writeStatus)
self.worker.version.connect(self.writeVersionNum)
self.worker.start()
def collectSignal(self):
"""Manages secondary worker thread and its signals."""
self.worker2 = SignalWorker()
self.finished.connect(self.worker2.deleteLater)
self.worker2.signalStrength.connect(self.writeSignalStrength)
self.worker2.signalQuality.connect(self.writeSignalQuality)
self.worker2.start()
def cancelled(self):
"""Restarts all Threads.
Is called when the cancel button is pressed.
"""
self.worker.stop(restart = True)
self.runProgram()
def writeStatus(self, label):
"""Writes the status to the GUI.
Parameters
----------
label : str
text label to update on the GUI
"""
self.status.setText(label)
logging.info(label)
def writeProgress(self, value):
"""Writes the progress bar value to the GUI.
Parameters
----------
value : int
int label to update on the GUI
"""
self.progressBar.setValue(value)
def writeSignalStrength(self, label):
"""Writes the signal strength to the GUI.
Parameters
----------
label : str
text label to update on the GUI
"""
self.signalStrength.setText(label)
def writeSignalQuality(self, label):
"""Writes the signal quality to the GUI.
Parameters
----------
label : str
text label to update on the GUI
"""
self.signalQuality.setText(label)
def writeVersionNum(self, label):
"""Writes the version number to the GUI.
Parameters
----------
label : str
text label to update on the GUI
"""
self.versionNum.setText(label)
def getSystemIP():
"""Uses a subprocess to get the IP of the local Pi.
Returns
-------
str
a string representing the IP of the local Pi
"""
batcmd = 'hostname -I'
getIP = subprocess.check_output(batcmd, shell = True).strip()
return getIP.decode("utf-8")
"""The following must be run in both gui.py and main.py:"""
# Creates application screen
app = QApplication(sys.argv)
userScreen = PiPiTransfer()
# Creates a stackable widget with multiple screens
widget = QtWidgets.QStackedWidget()
widget.addWidget(userScreen)
widget.setFixedHeight(720)
widget.setFixedWidth(480)
widget.show()
# Runs main event loop thread and exits when "X" is clicked/tapped
sys.exit(app.exec_())