source: remote_control/puzzlebox_brainstorms_client_interface.py @ 109

Last change on this file since 109 was 109, checked in by sc, 11 years ago

remote_control/puzzlebox_brainstorms_client_interface_design.py:

  • concentration/relaxation/speed enable buttons working

remote_control/puzzlebox_brainstorms_network_client.py:

  • LEGO Mindstorms NXT status label bugfix

remote_control/puzzlebox_brainstorms_client_interface.py:

  • concentration/relaxation/speed enable buttons working
  • exit dialog added
  • driving motors stopped when client disconnected

remote_control/puzzlebox_brainstorms_client_interface_network.py:

  • client instantiation bugfix

remote_control/puzzlebox_brainstorms_client_interface_local.py:

  • connection to local server instance added

remote_control/puzzlebox_brainstorms_network_server.py:

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