source: remote_control/puzzlebox_brainstorms_client_interface.py @ 111

Last change on this file since 111 was 111, checked in by sc, 11 years ago
  • PySide? import attempted before PyQt4 where available
  • problem specifying port when calling socket.listen on QTcpServer
File size: 19.6 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Puzzlebox - Brainstorms - Client Interface - Qt
5#
6# Copyright Puzzlebox Productions, LLC (2010)
7#
8# This code is released under the GNU Pulic License (GPL) version 2
9# For more information please refer to http://www.gnu.org/copyleft/gpl.html
10#
11# Last Update: 2010.07.10
12#
13#####################################################################
14# To Do:
15# - server may not correctly handle multiple clients connected
16#      to an embedded Brainstorms server
17# - disable autorepeating on shortcut keys
18# - update configuration.ini file with settings entered into interface
19#####################################################################
20
21import os, sys
22
23try:
24        import PySide as PyQt4
25        from PySide import QtCore, QtGui, QtNetwork
26except:
27        print "Using PyQt4 module"
28        from PyQt4 import QtCore, QtGui, QtNetwork
29else:
30        print "Using PySide module"
31
32#from PyQt4 import QtCore, QtGui, QtNetwork
33#from PySide import QtCore, QtGui, QtNetwork
34
35from puzzlebox_brainstorms_client_interface_design import Ui_Form
36
37import simplejson as json
38
39import puzzlebox_brainstorms_configuration as configuration
40import puzzlebox_brainstorms_network_client as brainstorms_client
41import puzzlebox_brainstorms_network_client_thinkgear as thinkgear_client
42#import puzzlebox_logger
43
44#####################################################################
45# Globals
46#####################################################################
47
48DEBUG = 1
49
50THINKGEAR_POWER_THRESHOLDS = configuration.THINKGEAR_POWER_THRESHOLDS
51
52BLUETOOTH_DEVICE = configuration.NXT_BLUETOOTH_DEVICE
53
54NXT_BLUETOOTH_DEVICE = configuration.NXT_BLUETOOTH_DEVICE
55
56DEFAULT_NXT_POWER_LEVEL = configuration.DEFAULT_NXT_POWER_LEVEL
57
58THINKGEAR_SERVER_HOST = configuration.THINKGEAR_SERVER_HOST
59THINKGEAR_SERVER_PORT = configuration.THINKGEAR_SERVER_PORT
60
61#####################################################################
62# Classes
63#####################################################################
64
65class puzzlebox_brainstorms_client_interface(QtGui.QWidget, Ui_Form):
66       
67        def __init__(self, log, server=None, DEBUG=DEBUG, parent = None):
68               
69                self.log = log
70                self.DEBUG = DEBUG
71               
72                QtGui.QWidget.__init__(self, parent)
73                self.setupUi(self)
74               
75                self.configureSettings()
76                self.connectWidgets()
77               
78                self.name = "Brainstorms Interface"
79               
80                self.brainstormsServer = server
81                self.brainstormsClient = None
82               
83                self.drive_state = 'stop_motors'
84                self.current_speed = 0
85       
86       
87        ##################################################################
88       
89        def configureSettings(self):
90               
91                # Brainstorms Interface
92               
93                icon = QtGui.QIcon()
94                icon.addPixmap(QtGui.QPixmap("images/puzzlebox.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
95                self.setWindowIcon(icon)
96               
97                self.pushButtonTurnLeft.setEnabled(False)
98                self.pushButtonForward.setEnabled(False)
99                self.pushButtonTurnRight.setEnabled(False)
100                self.pushButtonTurnLeftReverse.setEnabled(False)
101                self.pushButtonReverse.setEnabled(False)
102                self.pushButtonTurnRightReverse.setEnabled(False)
103               
104                self.pushButtonConcentrationEnable.setDown(True)
105                self.pushButtonRelaxationEnable.setDown(True)
106                self.pushButtonSpeedEnable.setDown(True)
107               
108               
109                # LEGO Mindstorms
110               
111                self.textLabelNXTStatus.setText("Status: Disconnected")
112               
113                # Display communication port for LEGO Mindstorms NXT device
114                self.lineEditNXTPort.setText(NXT_BLUETOOTH_DEVICE)
115                self.lineEditNXTPort.setEnabled(True)
116               
117               
118                # EEG Headset
119               
120                # Display Host for ThinkGear Connect Socket Server
121                self.lineEditThinkGearHost.setText(THINKGEAR_SERVER_HOST)
122                #self.lineEditThinkGearHost.setEnabled(False)
123               
124                # Display Port for ThinkGear Connect Socket Server
125                self.lineEditThinkGearPort.setText('%i' % THINKGEAR_SERVER_PORT)
126                #self.lineEditThinkGearPort.setEnabled(False)
127       
128       
129        ##################################################################
130       
131        def getMinimumThreshold(self, threshold):
132               
133                '''Return the minimum detection level which results
134                in a non-zero power setting'''
135               
136                minimum = 100
137               
138                threshold_keys = threshold.keys()
139                threshold_keys.sort()
140                threshold_keys.reverse()
141               
142                for key in threshold_keys:
143                       
144                        if ((threshold[key] < minimum) and \
145                                 (threshold[key] > 0)):
146                                minimum = key
147               
148               
149                return(minimum)
150       
151       
152        ##################################################################
153       
154        def configureNetworkBrainstorms(self):
155               
156                bluetooth_device = self.lineEditNXTPort.text()
157               
158                self.brainstormsClient = \
159                   brainstorms_client.puzzlebox_brainstorms_network_client( \
160                           self.log, \
161                           bluetooth_device=bluetooth_device, \
162                           parent=self)
163               
164                self.brainstormsClient.sendCommand('connect', \
165                                                   bluetooth_device=bluetooth_device)
166       
167       
168        ##################################################################
169       
170        def connectToBrainstormsServer(self):
171               
172                if self.DEBUG:
173                        print "<---- [%s] Connecting to Brainstorms Server" % self.name
174               
175                self.configureNetworkBrainstorms()
176               
177                #if (self.brainstormsClient.socket.state() != QtNetwork.QAbstractSocket.ConnectedState):
178                        #QtGui.QMessageBox.information(self, \
179                                                                #self.brainstormsClient.socket.name, \
180                                           #"Failed to connect to Brainstorms socket server")
181               
182                #else:
183                self.disconnect(self.pushButtonNXTConnect, \
184                                                        QtCore.SIGNAL("clicked()"), \
185                                                        self.connectToBrainstormsServer)
186               
187                self.connect(self.pushButtonNXTConnect, \
188                                                        QtCore.SIGNAL("clicked()"), \
189                                                        self.disconnectFromBrainstormsServer)
190               
191                self.textLabelNXTStatus.setText("Status: Connected")
192                self.pushButtonNXTConnect.setText('Disconnect')
193               
194                self.lineEditNXTPort.setEnabled(False)
195               
196                self.pushButtonTurnLeft.setEnabled(True)
197                self.pushButtonForward.setEnabled(True)
198                self.pushButtonTurnRight.setEnabled(True)
199                self.pushButtonTurnLeftReverse.setEnabled(True)
200                self.pushButtonReverse.setEnabled(True)
201                self.pushButtonTurnRightReverse.setEnabled(True)
202       
203       
204        ##################################################################
205       
206        def disconnectFromBrainstormsServer(self):
207               
208                if self.DEBUG:
209                        print "- - [%s] Disconnecting from Brainstorms Server" % self.name
210               
211                self.stopMotors()
212               
213                # Ensure the stopMotors command has been received by the server
214                # so the NXT robot will stop before the client disconnects
215                self.brainstormsClient.socket.flush()
216               
217                self.brainstormsClient.socket.disconnectFromHost()
218               
219                self.disconnect(self.pushButtonNXTConnect, \
220                                  QtCore.SIGNAL("clicked()"), \
221                                  self.disconnectFromBrainstormsServer)
222               
223                self.connect(self.pushButtonNXTConnect, \
224                                  QtCore.SIGNAL("clicked()"), \
225                                  self.connectToBrainstormsServer)
226               
227                self.textLabelNXTStatus.setText("Status: Disconnected")
228                self.pushButtonNXTConnect.setText('Connect')
229               
230                self.lineEditNXTPort.setEnabled(True)
231               
232                self.pushButtonTurnLeft.setEnabled(False)
233                self.pushButtonForward.setEnabled(False)
234                self.pushButtonTurnRight.setEnabled(False)
235                self.pushButtonTurnLeftReverse.setEnabled(False)
236                self.pushButtonReverse.setEnabled(False)
237                self.pushButtonTurnRightReverse.setEnabled(False)
238               
239                self.brainstormsClient = None
240       
241       
242        ##################################################################
243       
244        def connectToThinkGearHost(self):
245               
246                if self.DEBUG:
247                        print "Connecting to ThinkGear Host"
248               
249                server_host = str(self.lineEditThinkGearHost.text())
250                server_port = int(self.lineEditThinkGearPort.text())
251               
252                self.thinkgearClient = \
253                   thinkgear_client.puzzlebox_brainstorms_network_client_thinkgear( \
254                           self.log, \
255                           server_host=server_host, \
256                           server_port=server_port, \
257                           DEBUG=0, \
258                           parent=self)
259               
260                if (self.thinkgearClient.socket.state() != QtNetwork.QAbstractSocket.ConnectedState):
261                        QtGui.QMessageBox.information(self, \
262                                                                self.thinkgearClient.socket.name, \
263                                           "Failed to connect to ThinkGear socket server")
264               
265                else:
266                        self.disconnect(self.pushButtonThinkGearConnect, \
267                                                         QtCore.SIGNAL("clicked()"), \
268                                                         self.connectToThinkGearHost)
269                       
270                        self.connect(self.pushButtonThinkGearConnect, \
271                                                         QtCore.SIGNAL("clicked()"), \
272                                                         self.disconnectFromThinkGearHost)
273                       
274                        self.pushButtonThinkGearConnect.setText('Disconnect')
275                       
276                        self.lineEditThinkGearHost.setEnabled(False)
277                        self.lineEditThinkGearPort.setEnabled(False)
278       
279       
280        ##################################################################
281       
282        def disconnectFromThinkGearHost(self):
283               
284                if self.DEBUG:
285                        print "Disconnecting from ThinkGear Host"
286               
287                self.thinkgearClient.disconnectFromHost()
288               
289                self.disconnect(self.pushButtonThinkGearConnect, \
290                                  QtCore.SIGNAL("clicked()"), \
291                                  self.disconnectFromThinkGearHost)
292               
293                self.connect(self.pushButtonThinkGearConnect, \
294                                  QtCore.SIGNAL("clicked()"), \
295                                  self.connectToThinkGearHost)
296               
297                self.pushButtonForward.emit(QtCore.SIGNAL("released()"))
298               
299                self.pushButtonThinkGearConnect.setText('Connect')
300               
301                self.lineEditThinkGearHost.setEnabled(True)
302                self.lineEditThinkGearPort.setEnabled(True)
303               
304                self.progressBarConcentration.setValue(0)
305                self.progressBarRelaxation.setValue(0)
306                self.progressBarSpeed.setValue(0)
307       
308       
309        ##################################################################
310       
311        def updateConcentrationButton(self):
312               
313                if self.pushButtonConcentrationEnable.isChecked():
314                       
315                        self.pushButtonConcentrationEnable.setText('Enabled')
316               
317                else:
318                       
319                        self.pushButtonConcentrationEnable.setText('Disabled')
320                        self.progressBarConcentration.setValue(0)
321               
322               
323                self.updateSpeed()
324       
325       
326        ##################################################################
327       
328        def updateRelaxationButton(self):
329               
330                if self.pushButtonRelaxationEnable.isChecked():
331               
332                        self.pushButtonRelaxationEnable.setText('Enabled')
333               
334                else:
335                       
336                        self.pushButtonRelaxationEnable.setText('Disabled')
337                        self.progressBarRelaxation.setValue(0)
338               
339               
340                self.updateSpeed()
341       
342       
343        ##################################################################
344       
345        def updateSpeedButton(self):
346               
347                if self.pushButtonSpeedEnable.isChecked():
348               
349                        self.pushButtonSpeedEnable.setText('Enabled')
350                        self.updateSpeed()
351               
352                else:
353                       
354                        self.pushButtonSpeedEnable.setText('Disabled')
355                        self.progressBarSpeed.setValue(0)
356                        self.stopMotors()
357       
358       
359        ##################################################################
360       
361        def connectWidgets(self):
362               
363                self.connect(self.pushButtonTurnLeft, QtCore.SIGNAL("pressed()"), \
364                             self.turnLeft)
365                self.connect(self.pushButtonTurnLeft, QtCore.SIGNAL("released()"), \
366                             self.stopMotors)
367               
368                self.connect(self.pushButtonForward, QtCore.SIGNAL("pressed()"), \
369                             self.driveForward)
370                self.connect(self.pushButtonForward, QtCore.SIGNAL("released()"), \
371                             self.stopMotors)
372               
373                self.connect(self.pushButtonTurnRight, QtCore.SIGNAL("pressed()"), \
374                             self.turnRight)
375                self.connect(self.pushButtonTurnRight, QtCore.SIGNAL("released()"), \
376                             self.stopMotors)
377               
378                self.connect(self.pushButtonTurnLeftReverse, QtCore.SIGNAL("pressed()"), \
379                             self.turnLeftInReverse)
380                self.connect(self.pushButtonTurnLeftReverse, QtCore.SIGNAL("released()"), \
381                             self.stopMotors)
382               
383                self.connect(self.pushButtonReverse, QtCore.SIGNAL("pressed()"), \
384                             self.driveReverse)
385                self.connect(self.pushButtonReverse, QtCore.SIGNAL("released()"), \
386                             self.stopMotors)
387               
388                self.connect(self.pushButtonTurnRightReverse, QtCore.SIGNAL("pressed()"), \
389                             self.turnRightInReverse)
390                self.connect(self.pushButtonTurnRightReverse, QtCore.SIGNAL("released()"), \
391                             self.stopMotors)
392               
393               
394                self.connect(self.pushButtonNXTConnect, \
395                                  QtCore.SIGNAL("clicked()"), \
396                                  self.connectToBrainstormsServer)
397               
398                self.connect(self.pushButtonThinkGearConnect, \
399                                  QtCore.SIGNAL("clicked()"), \
400                                  self.connectToThinkGearHost)
401               
402               
403                self.connect(self.pushButtonConcentrationEnable, \
404                                  QtCore.SIGNAL("clicked()"), \
405                                  self.updateConcentrationButton)
406               
407                self.connect(self.pushButtonRelaxationEnable, \
408                                  QtCore.SIGNAL("clicked()"), \
409                                  self.updateRelaxationButton)
410               
411                self.connect(self.pushButtonSpeedEnable, \
412                                  QtCore.SIGNAL("clicked()"), \
413                                  self.updateSpeedButton)
414               
415               
416                #shortcut = QtGui.QShortcut(self)
417                #shortcut.setKey(tr("Down"))
418                #self.connect(shortcut, QtCore.SIGNAL("pressed()"), self.driveReverse)
419               
420               
421                action = QtGui.QAction(self)
422                action.setShortcut(QtGui.QKeySequence("W"))
423                self.connect(action, QtCore.SIGNAL("activated()"), self.pushButtonForward, QtCore.SLOT("animateClick()"))
424                self.addAction(action)
425               
426                action = QtGui.QAction(self)
427                action.setShortcut(QtGui.QKeySequence("Up"))
428                self.connect(action, QtCore.SIGNAL("activated()"), self.pushButtonForward, QtCore.SLOT("animateClick()"))
429                self.addAction(action)
430               
431               
432                action = QtGui.QAction(self)
433                action.setShortcut(QtGui.QKeySequence("Left"))
434                self.connect(action, QtCore.SIGNAL("activated()"), self.pushButtonTurnLeft, QtCore.SLOT("animateClick()"))
435                self.addAction(action)
436               
437                action = QtGui.QAction(self)
438                action.setShortcut(QtGui.QKeySequence("A"))
439                self.connect(action, QtCore.SIGNAL("activated()"), self.pushButtonTurnLeft, QtCore.SLOT("animateClick()"))
440                self.addAction(action)
441               
442               
443                action = QtGui.QAction(self)
444                action.setShortcut(QtGui.QKeySequence("S"))
445                self.connect(action, QtCore.SIGNAL("activated()"), self.pushButtonReverse, QtCore.SLOT("animateClick()"))
446                self.addAction(action)
447               
448                action = QtGui.QAction(self)
449                action.setShortcut(QtGui.QKeySequence("Down"))
450                self.connect(action, QtCore.SIGNAL("activated()"), self.pushButtonReverse, QtCore.SLOT("animateClick()"))
451                self.addAction(action)
452               
453               
454                action = QtGui.QAction(self)
455                action.setShortcut(QtGui.QKeySequence("D"))
456                self.connect(action, QtCore.SIGNAL("activated()"), self.pushButtonTurnRight, QtCore.SLOT("animateClick()"))
457                self.addAction(action)
458               
459                action = QtGui.QAction(self)
460                action.setShortcut(QtGui.QKeySequence("Right"))
461                self.connect(action, QtCore.SIGNAL("activated()"), self.pushButtonTurnRight, QtCore.SLOT("animateClick()"))
462                self.addAction(action)
463               
464               
465                action = QtGui.QAction(self)
466                action.setShortcut(QtGui.QKeySequence("Z"))
467                self.connect(action, QtCore.SIGNAL("activated()"), self.pushButtonTurnLeftReverse, QtCore.SLOT("animateClick()"))
468                self.addAction(action)
469               
470               
471                action = QtGui.QAction(self)
472                action.setShortcut(QtGui.QKeySequence("C"))
473                self.connect(action, QtCore.SIGNAL("activated()"), self.pushButtonTurnRightReverse, QtCore.SLOT("animateClick()"))
474                self.addAction(action)
475               
476               
477                #self.pushButtonForward.setAutoRepeat(False)
478                #self.pushButtonForward.setAutoRepeatDelay(0)
479                #self.pushButtonForward.setAutoRepeatInterval(0)
480       
481       
482        ##################################################################
483       
484        def turnLeft(self):
485                self.brainstormsClient.sendCommand('turn_left')
486                self.drive_state = 'turn_left'
487       
488        def driveForward(self):
489                #if self.DEBUG:
490                        #print "driveForward"
491                self.pushButtonForward.setDown(True)
492                if (self.drive_state != 'drive_forward'):
493                        self.updateSpeed(new_speed=DEFAULT_NXT_POWER_LEVEL)
494                self.brainstormsClient.sendCommand('drive_forward', power=self.current_speed)
495                self.drive_state = 'drive_forward'
496       
497        def turnRight(self):
498                self.brainstormsClient.sendCommand('turn_right')
499                self.drive_state = 'turn_right'
500       
501        def turnLeftInReverse(self):
502                self.brainstormsClient.sendCommand('turn_left_in_reverse')
503                self.drive_state = 'turn_left_in_reverse'
504       
505        def driveReverse(self):
506                self.brainstormsClient.sendCommand('drive_reverse')
507                self.drive_state = 'drive_reverse'
508       
509        def turnRightInReverse(self):
510                self.brainstormsClient.sendCommand('turn_right_in_reverse')
511                self.drive_state = 'turn_right_in_reverse'
512       
513        def stopMotors(self):
514                self.pushButtonForward.setDown(False)
515                if (self.current_speed != 0):
516                        self.updateSpeed(new_speed=0)
517                if self.brainstormsClient != None:
518                        self.brainstormsClient.sendCommand('stop_motors')
519                self.drive_state = 'stop_motors'
520       
521       
522        ##################################################################
523       
524        def updateSpeed(self, new_speed=None):
525               
526                if new_speed == None:
527               
528                        concentration=self.progressBarConcentration.value()
529                        relaxation=self.progressBarRelaxation.value()
530                       
531                        new_speed = self.calculateSpeed(concentration, relaxation)
532               
533               
534                # Update GUI
535                if self.pushButtonSpeedEnable.isChecked():
536                        self.progressBarSpeed.setValue(new_speed)
537               
538               
539                # If there is a change between the new and current speeds
540                # and either the robot is currently driving forward
541                # or the "speed control" button is enabled,
542                # then send the updated speed to the robot
543                if ((self.current_speed != new_speed) and \
544                         ((self.drive_state == 'drive_forward') or \
545                          (self.pushButtonSpeedEnable.isChecked()))):
546                       
547                        if (new_speed == 0):
548                                self.current_speed = new_speed
549                                self.stopMotors()
550                        else:
551                                if ((self.brainstormsClient != None) and \
552                                    (self.pushButtonSpeedEnable.isChecked())):
553                                        self.pushButtonForward.setDown(True)
554                                        self.brainstormsClient.sendCommand('drive_forward', power=new_speed)
555               
556               
557                self.current_speed = new_speed
558       
559       
560        ##################################################################
561       
562        def calculateSpeed(self, concentration, relaxation):
563               
564                speed = 0
565               
566                thresholds = THINKGEAR_POWER_THRESHOLDS
567               
568                match = int(concentration)
569               
570                while ((match not in thresholds['concentration'].keys()) and \
571                            (match >= 0)):
572                        match -= 1
573               
574               
575                if match in thresholds['concentration'].keys():
576                        speed = thresholds['concentration'][match]
577               
578               
579                match = int(relaxation)
580               
581                while ((match not in thresholds['relaxation'].keys()) and \
582                            (match >= 0)):
583                        match -= 1
584               
585                if match in thresholds['relaxation'].keys():
586                        speed = speed + thresholds['relaxation'][match]
587               
588               
589                # LEGO Mindstorms power settings cannot exceed 100
590                # and don't drive well with levels less than 50
591                if (speed > 100):
592                        speed = 100
593                elif (speed < 50):
594                        speed = 0
595               
596               
597                return(speed)
598       
599       
600        ##################################################################
601       
602        def processPacketThinkGear(self, packet):
603               
604                if ('eSense' in packet.keys()):
605                       
606                        if ('attention' in packet['eSense'].keys()):
607                                if self.pushButtonConcentrationEnable.isChecked():
608                                        self.progressBarConcentration.setValue(packet['eSense']['attention'])
609                       
610                        if ('meditation' in packet['eSense'].keys()):
611                                if self.pushButtonRelaxationEnable.isChecked():
612                                        self.progressBarRelaxation.setValue(packet['eSense']['meditation'])
613               
614               
615                self.updateSpeed()
616       
617       
618        ##################################################################
619       
620        def closeEvent(self, event):
621               
622                quit_message = "Are you sure you want to exit the program?"
623               
624                reply = QtGui.QMessageBox.question( \
625                           self, \
626                          'Message', \
627                           quit_message, \
628                           QtGui.QMessageBox.Yes, \
629                           QtGui.QMessageBox.No)
630               
631                if reply == QtGui.QMessageBox.Yes:
632                       
633                        if self.brainstormsClient != None:
634                                self.stopMotors()
635                                self.brainstormsClient.socket.flush()
636                               
637                                if self.brainstormsServer != None:
638                                       
639                                        if self.brainstormsServer.rc == None:
640                                               
641                                                bluetooth_device = str(self.lineEditNXTPort.text())
642                                                self.brainstormsServer.executeCommand( \
643                                                        'stop_motors', \
644                                                        bluetooth_device=bluetooth_device)
645                                               
646                                        else:
647                                                self.brainstormsServer.rc.run('stop_motors')
648                       
649                       
650                        event.accept()
651               
652                else:
653                        event.ignore()
654
655
656#####################################################################
657# Functions
658#####################################################################
659
660#####################################################################
661# Main
662#####################################################################
663
664if __name__ == '__main__':
665       
666        #log = puzzlebox_logger.puzzlebox_logger(logfile='client_interface')
667        log = None
668       
669        app = QtGui.QApplication(sys.argv)
670       
671        window = puzzlebox_brainstorms_client_interface(log, DEBUG)
672        window.show()
673       
674        sys.exit(app.exec_())
675
Note: See TracBrowser for help on using the repository browser.