source: trunk/synapse/Puzzlebox/Synapse/Interface.py @ 165

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

trunk/synapse/scripts/build-interface.sh:

  • renamed from trunk/synapse/interface/build.sh

trunk/synapse/interface/puzzlebox_synapse_interface_design.ui:

  • added tab for matplotlib

trunk/synapse/Puzzlebox/Synapse/Interface.py:

  • added initial support for matplotlib graphing
  • Property svn:executable set to *
File size: 18.8 KB
Line 
1# -*- coding: utf-8 -*-
2
3# Copyright Puzzlebox Productions, LLC (2010)
4#
5# This code is released under the GNU Pulic License (GPL) version 2
6# For more information please refer to http://www.gnu.org/copyleft/gpl.html
7
8# Old Class Names:
9#       puzzlebox_synapse_interface = QtUI
10
11__changelog__ = """\
12Last Update: 2010.08.19
13
14"""
15
16__todo__ = """
17- update configuration.ini file with settings entered into interface
18
19"""
20
21### IMPORTS ###
22import os, sys
23import simplejson as json
24
25import random
26from numpy import arange, sin, pi
27from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
28from matplotlib.figure import Figure
29
30if (sys.platform != 'win32'):
31        import bluetooth
32        DEFAULT_IMAGE_PATH = '/usr/share/puzzlebox_synapse/images'
33else:
34        import serial
35        DEFAULT_IMAGE_PATH = 'images'
36
37try:
38        import PySide as PyQt4
39        from PySide import QtCore, QtGui
40except:
41        print "Using PyQt4 module"
42        from PyQt4 import QtCore, QtGui
43else:
44        print "Using PySide module"
45
46# from puzzlebox_synapse_interface_design import Ui_Form
47from Interface_Design import Ui_Form as Design
48
49import Configuration as configuration
50import Server as synapse_server
51import Client as thinkgear_client
52#import puzzlebox_logger
53
54### GLOBALS ###
55
56DEBUG = 1
57
58THINKGEAR_SERVER_HOST = configuration.THINKGEAR_SERVER_HOST
59THINKGEAR_SERVER_PORT = configuration.THINKGEAR_SERVER_PORT
60
61THINKGEAR_EEG_POWER_BAND_ORDER = configuration.THINKGEAR_EEG_POWER_BAND_ORDER
62
63THINKGEAR_EMULATION_MAX_ESENSE_VALUE = \
64        configuration.THINKGEAR_EMULATION_MAX_ESENSE_VALUE
65THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE = \
66        configuration.THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE
67
68PATH_TO_HCITOOL = '/usr/bin/hcitool'
69
70UPDATE_INTERFACE_VIA_TIMER = True # Alternative is to establish a
71                                  # ThinkGear Connect client which
72                                  # updates the interface on demand
73                                  # as packets are received
74
75INTERFACE_UPDATE_FREQUENCY = 1000 # ms
76
77### CLASSES ###
78
79class QtUI(QtGui.QWidget, Design):
80       
81        def __init__(self, log, server=None, DEBUG=DEBUG, parent = None):
82               
83                self.log = log
84                self.DEBUG = DEBUG
85               
86                QtGui.QWidget.__init__(self, parent)
87                self.setupUi(self)
88               
89                self.configureSettings()
90                self.connectWidgets()
91               
92                self.name = "Synapse Interface"
93               
94                self.thinkGearConnectServer = None
95                self.thinkgearConnectClient = None
96               
97                self.maxEEGPower = THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE
98               
99                self.debug_console_buffer = ''
100               
101                if UPDATE_INTERFACE_VIA_TIMER:
102                        self.updateInterfaceTimer = QtCore.QTimer()
103                        QtCore.QObject.connect(self.updateInterfaceTimer, \
104                                                    QtCore.SIGNAL("timeout()"), \
105                                                    self.updateInterface)
106       
107       
108        ##################################################################
109       
110        def configureSettings(self):
111               
112                # Synapse Interface
113                image_path = "puzzlebox.ico"
114                if not os.path.exists(image_path):
115                        image_path = os.path.join(DEFAULT_IMAGE_PATH, image_path)
116               
117                if os.path.exists(image_path):
118                        icon = QtGui.QIcon()
119                        icon.addPixmap(QtGui.QPixmap(image_path), \
120                                            QtGui.QIcon.Normal, \
121                                            QtGui.QIcon.Off)
122                        self.setWindowIcon(icon)
123               
124                image_path = "puzzlebox_logo.png"
125                if not os.path.exists(image_path):
126                        image_path = os.path.join(DEFAULT_IMAGE_PATH, image_path)
127                if os.path.exists(image_path):
128                        self.labelPuzzleboxIcon.setPixmap(QtGui.QPixmap(image_path))
129               
130               
131                # ThinkGear Device
132                self.searchForThinkGearDevices()
133               
134               
135                # ThinkGear Connect Server
136                self.textLabelBluetoothStatus.setText("Status: Disconnected")
137               
138                # Display Host for ThinkGear Connect Socket Server
139                self.lineEditThinkGearHost.setText(THINKGEAR_SERVER_HOST)
140               
141                # Display Port for ThinkGear Connect Socket Server
142                self.lineEditThinkGearPort.setText('%i' % THINKGEAR_SERVER_PORT)
143               
144               
145                # ThinkgGear Progress Bars
146                self.progressBarEEGDelta.setMaximum(THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE)
147                self.progressBarEEGTheta.setMaximum(THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE)
148                self.progressBarEEGLowAlpha.setMaximum(THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE)
149                self.progressBarEEGHighAlpha.setMaximum(THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE)
150                self.progressBarEEGLowBeta.setMaximum(THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE)
151                self.progressBarEEGHighBeta.setMaximum(THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE)
152                self.progressBarEEGLowGamma.setMaximum(THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE)
153                self.progressBarEEGMidGamma.setMaximum(THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE)
154               
155                self.progressBarAttention.setMaximum(THINKGEAR_EMULATION_MAX_ESENSE_VALUE)
156                self.progressBarMeditation.setMaximum(THINKGEAR_EMULATION_MAX_ESENSE_VALUE)
157               
158                self.progressBarSignalContactQuality.setMaximum(200)
159               
160               
161                #self.matplot = MyDynamicMplCanvas(QtGui.QWidget(self), width=5, height=4, dpi=100)
162               
163                print dir(self.tabEEGSignals)
164               
165                self.matplot = MyDynamicMplCanvas(self.tabEEGSignals, width=5, height=4, dpi=100)
166                #l.addWidget(matplot)
167       
168       
169        ##################################################################
170       
171        def connectWidgets(self):
172               
173                self.connect(self.pushButtonBluetoothSearch, \
174                                  QtCore.SIGNAL("clicked()"), \
175                                  self.searchForThinkGearDevices)
176               
177                self.connect(self.pushButtonBluetoothConnect, \
178                                  QtCore.SIGNAL("clicked()"), \
179                                  self.connectToThinkGearDevice)
180               
181                self.connect(self.pushButtonThinkGearConnect, \
182                                  QtCore.SIGNAL("clicked()"), \
183                                  self.startThinkGearConnectServer)
184       
185       
186        ##################################################################
187       
188        def connectToThinkGearDevice(self):
189               
190                device_selection = self.comboBoxDeviceSelect.currentText()
191               
192                self.disconnect(self.pushButtonBluetoothConnect, \
193                                     QtCore.SIGNAL("clicked()"), \
194                                     self.connectToThinkGearDevice)
195               
196                self.connect(self.pushButtonBluetoothConnect, \
197                                  QtCore.SIGNAL("clicked()"), \
198                                  self.disconnectFromThinkGearDevice)
199               
200                self.textLabelBluetoothStatus.setText("Status: Connected")
201               
202                self.pushButtonBluetoothSearch.setEnabled(False)
203               
204                self.pushButtonBluetoothConnect.setText('Disconnect')
205                self.pushButtonBluetoothConnect.setChecked(True)
206               
207                self.comboBoxDeviceSelect.setEnabled(False)
208       
209       
210        ##################################################################
211       
212        def disconnectFromThinkGearDevice(self):
213               
214                self.disconnect(self.pushButtonBluetoothConnect, \
215                                     QtCore.SIGNAL("clicked()"), \
216                                     self.disconnectFromThinkGearDevice)
217               
218                self.connect(self.pushButtonBluetoothConnect, \
219                                  QtCore.SIGNAL("clicked()"), \
220                                  self.connectToThinkGearDevice)
221               
222                self.textLabelBluetoothStatus.setText("Status: Disconnected")
223               
224                self.pushButtonBluetoothSearch.setEnabled(True)
225               
226                self.pushButtonBluetoothConnect.setText('Connect')
227                self.pushButtonBluetoothConnect.setChecked(False)
228               
229                self.comboBoxDeviceSelect.setEnabled(True)
230               
231               
232                self.progressBarEEGDelta.setValue(0)
233                self.progressBarEEGTheta.setValue(0)
234                self.progressBarEEGLowAlpha.setValue(0)
235                self.progressBarEEGHighAlpha.setValue(0)
236                self.progressBarEEGLowBeta.setValue(0)
237                self.progressBarEEGHighBeta.setValue(0)
238                self.progressBarEEGLowGamma.setValue(0)
239                self.progressBarEEGMidGamma.setValue(0)
240               
241                self.progressBarAttention.setValue(0)
242                self.progressBarMeditation.setValue(0)
243               
244                self.progressBarSignalContactQuality.setValue(0)
245               
246                self.maxEEGPower = THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE
247               
248                # In case the user connects to a MindSet, then disconnects
249                # and re-connects to a MindSet Emulator,
250                # we need to reset the max power values
251                self.progressBarEEGDelta.setMaximum(self.maxEEGPower)
252                self.progressBarEEGTheta.setMaximum(self.maxEEGPower)
253                self.progressBarEEGLowAlpha.setMaximum(self.maxEEGPower)
254                self.progressBarEEGHighAlpha.setMaximum(self.maxEEGPower)
255                self.progressBarEEGLowBeta.setMaximum(self.maxEEGPower)
256                self.progressBarEEGHighBeta.setMaximum(self.maxEEGPower)
257                self.progressBarEEGLowGamma.setMaximum(self.maxEEGPower)
258                self.progressBarEEGMidGamma.setMaximum(self.maxEEGPower)
259       
260       
261        ##################################################################
262       
263        def startThinkGearConnectServer(self):
264               
265                # Ensure EEG device is connected first
266               
267                if not self.pushButtonBluetoothConnect.isChecked():
268                        self.connectToThinkGearDevice()
269               
270               
271                self.pushButtonBluetoothSearch.setEnabled(False)
272                self.pushButtonBluetoothConnect.setEnabled(False)
273               
274                server_interface = str(self.lineEditThinkGearHost.text())
275                server_port = int(self.lineEditThinkGearPort.text())
276                device_address = str(self.comboBoxDeviceSelect.currentText())
277                emulate_headset_data = (device_address == 'MindSet Emulator')
278               
279               
280                self.thinkGearConnectServer = \
281                        synapse_server.ThinkgearServer( \
282                                self.log, \
283                                server_interface=server_interface, \
284                                server_port=server_port, \
285                                device_address=device_address, \
286                                emulate_headset_data=emulate_headset_data, \
287                                DEBUG=DEBUG, \
288                                parent=self)
289               
290                self.thinkGearConnectServer.start()
291               
292               
293                if UPDATE_INTERFACE_VIA_TIMER:
294                        self.updateInterfaceTimer.start(INTERFACE_UPDATE_FREQUENCY)
295               
296                else:
297                        self.thinkgearConnectClient = \
298                                thinkgear_client.QtClient( \
299                                        self.log, \
300                                        server_host=server_interface, \
301                                        server_port=server_port, \
302                                        DEBUG=0, \
303                                        parent=self)
304                       
305                        self.thinkgearConnectClient.start()
306               
307               
308                self.disconnect(self.pushButtonThinkGearConnect, \
309                                     QtCore.SIGNAL("clicked()"), \
310                                     self.startThinkGearConnectServer)
311               
312                self.connect(self.pushButtonThinkGearConnect, \
313                                  QtCore.SIGNAL("clicked()"), \
314                                  self.stopThinkGearConnectServer)
315               
316                self.lineEditThinkGearHost.setEnabled(False)
317                self.lineEditThinkGearPort.setEnabled(False)
318               
319                self.pushButtonThinkGearConnect.setText('Stop')
320       
321       
322        ##################################################################
323       
324        def stopThinkGearConnectServer(self):
325               
326                if UPDATE_INTERFACE_VIA_TIMER:
327                        self.updateInterfaceTimer.stop()
328                else:
329                        self.thinkgearConnectClient.disconnectFromHost()
330               
331                self.thinkGearConnectServer.exitThread()
332               
333                self.disconnect(self.pushButtonThinkGearConnect, \
334                                     QtCore.SIGNAL("clicked()"), \
335                                     self.stopThinkGearConnectServer)
336               
337                self.connect(self.pushButtonThinkGearConnect, \
338                                  QtCore.SIGNAL("clicked()"), \
339                                  self.startThinkGearConnectServer)
340               
341                self.lineEditThinkGearHost.setEnabled(True)
342                self.lineEditThinkGearPort.setEnabled(True)
343               
344                self.pushButtonThinkGearConnect.setText('Start')
345               
346                #self.pushButtonBluetoothSearch.setEnabled(True)
347                self.pushButtonBluetoothConnect.setEnabled(True)
348       
349       
350        ##################################################################
351       
352        def updateInterface(self):
353               
354                if not self.thinkGearConnectServer.emulate_headset_data:
355                        self.processPacketThinkGear( \
356                                self.thinkGearConnectServer.protocol.data_packet)
357       
358       
359        ##################################################################
360       
361        def processPacketThinkGear(self, packet):
362               
363                if self.DEBUG > 2:
364                        print packet
365               
366               
367                if ('poorSignalLevel' in packet.keys()):
368                        value = 200 - packet['poorSignalLevel']
369                        self.progressBarSignalContactQuality.setValue(value)
370                        self.textEditDebugConsole.append("")
371                        self.textEditDebugConsole.append("poorSignalLevel: %i" % \
372                                                              packet['poorSignalLevel'])
373               
374               
375                if ('eSense' in packet.keys()):
376                       
377                        if ('attention' in packet['eSense'].keys()):
378                                value = packet['eSense']['attention']
379                                self.progressBarAttention.setValue(value)
380                                self.textEditDebugConsole.append("eSense attention: %i" % value)
381                       
382                        if ('meditation' in packet['eSense'].keys()):
383                                value = packet['eSense']['meditation']
384                                self.progressBarMeditation.setValue(value)
385                                self.textEditDebugConsole.append("eSense meditation: %i" % value)
386               
387               
388                if ('eegPower' in packet.keys()):
389                       
390                        # If we are not emulating packets we'll set the maximum EEG Power value
391                        # threshold to the default (or maximum value found within this packet)
392                        if not self.thinkGearConnectServer.emulate_headset_data:
393                                self.maxEEGPower = THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE
394                       
395                        for value in packet['eegPower'].keys():
396                                if packet['eegPower'][value] > self.maxEEGPower:
397                                        self.maxEEGPower = packet['eegPower'][value]
398                       
399                       
400                        if ('delta' in packet['eegPower'].keys()):
401                                value = packet['eegPower']['delta']
402                                self.progressBarEEGDelta.setMaximum(self.maxEEGPower)
403                                self.progressBarEEGDelta.setValue(value)
404                                self.textEditDebugConsole.append("delta: %i" % value)
405                       
406                        if ('theta' in packet['eegPower'].keys()):
407                                value = packet['eegPower']['theta']
408                                self.progressBarEEGTheta.setMaximum(self.maxEEGPower)
409                                self.progressBarEEGTheta.setValue(value)
410                                self.textEditDebugConsole.append("theta: %i" % value)
411                       
412                        if ('lowAlpha' in packet['eegPower'].keys()):
413                                value = packet['eegPower']['lowAlpha']
414                                self.progressBarEEGLowAlpha.setMaximum(self.maxEEGPower)
415                                self.progressBarEEGLowAlpha.setValue(value)
416                                self.textEditDebugConsole.append("lowAlpha: %i" % value)
417                       
418                        if ('highAlpha' in packet['eegPower'].keys()):
419                                value = packet['eegPower']['highAlpha']
420                                self.progressBarEEGHighAlpha.setMaximum(self.maxEEGPower)
421                                self.progressBarEEGHighAlpha.setValue(value)
422                                self.textEditDebugConsole.append("highAlpha: %i" % value)
423                       
424                        if ('lowBeta' in packet['eegPower'].keys()):
425                                value = packet['eegPower']['lowBeta']
426                                self.progressBarEEGLowBeta.setMaximum(self.maxEEGPower)
427                                self.progressBarEEGLowBeta.setValue(value)
428                                self.textEditDebugConsole.append("lowBeta: %i" % value)
429                       
430                        if ('highBeta' in packet['eegPower'].keys()):
431                                value = packet['eegPower']['highBeta']
432                                self.progressBarEEGHighBeta.setMaximum(self.maxEEGPower)
433                                self.progressBarEEGHighBeta.setValue(value)
434                                self.textEditDebugConsole.append("highBeta: %i" % value)
435                       
436                        if ('lowGamma' in packet['eegPower'].keys()):
437                                value = packet['eegPower']['lowGamma']
438                                self.progressBarEEGLowGamma.setMaximum(self.maxEEGPower)
439                                self.progressBarEEGLowGamma.setValue(value)
440                                self.textEditDebugConsole.append("lowGamma: %i" % value)
441                       
442                        if ('highGamma' in packet['eegPower'].keys()):
443                                value = packet['eegPower']['highGamma']
444                                self.progressBarEEGMidGamma.setMaximum(self.maxEEGPower)
445                                self.progressBarEEGMidGamma.setValue(value)
446                                self.textEditDebugConsole.append("highGamma: %i" % value)
447       
448       
449        ##################################################################
450       
451        def enumerateSerialPorts(self):
452               
453                """ Uses the Win32 registry to return an
454                iterator of serial (COM) ports
455                existing on this computer.
456               
457                from http://eli.thegreenplace.net/2009/07/31/listing-all-serial-ports-on-windows-with-python/
458                """
459         
460                path = 'HARDWARE\\DEVICEMAP\\SERIALCOMM'
461                try:
462                        key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path)
463                except WindowsError:
464                        raise IterationError
465               
466                for i in itertools.count():
467                        try:
468                                val = winreg.EnumValue(key, i)
469                                yield str(val[1])
470                        except EnvironmentError:
471                                break
472       
473       
474        ##################################################################
475       
476        def fullPortName(self, portname):
477               
478                """ Given a port-name (of the form COM7,
479                COM12, CNCA0, etc.) returns a full
480                name suitable for opening with the
481                Serial class.
482                """
483               
484                m = re.match('^COM(\d+)$', portname)
485                if m and int(m.group(1)) < 10:
486                        return portname
487               
488                return '\\\\.\\' + portname
489       
490       
491        ##################################################################
492       
493        def searchForThinkGearDevices(self):
494               
495                #self.pushButtonBluetoothSearch.setText('Searching')
496               
497                mindset_devices = []
498               
499                if (sys.platform == 'win32'):
500                       
501                        for portname in self.enumerateSerialPorts():
502                               
503                                #portname = self.fullPortName(portname)
504                                mindset_devices.append(portname)
505               
506               
507                else:
508                       
509                        # Bluetooth module doesn't compile properly under Windows
510                       
511                        bluetooth_devices = []
512                       
513                        #bluetooth_devices = bluetooth.discover_devices( \
514                                                    #duration=5, \
515                                                    #flush_cache=True, \
516                                                    #lookup_names=True)
517                       
518                        command = '%s con' % PATH_TO_HCITOOL
519                       
520                        output = os.popen(command, 'r')
521                       
522                        for line in output.readlines():
523                                try:
524                                        address = line.split(' ')[2]
525                                except:
526                                        pass
527                                else:
528                                        bluetooth_devices.append(address)
529                       
530                        for address in bluetooth_devices:
531                                device_name = bluetooth.lookup_name(address)
532                                if device_name == 'MindSet':
533                                        mindset_devices.append(address)
534                       
535                       
536                        if self.DEBUG:
537                                print "Bluetooth MindSet devices found:",
538                                print mindset_devices
539                       
540                       
541                        self.comboBoxDeviceSelect.clear()
542                       
543                        self.comboBoxDeviceSelect.addItem('MindSet Emulator')
544               
545               
546                for mindset_device in mindset_devices:
547                        self.comboBoxDeviceSelect.addItem(mindset_device)
548               
549               
550                #self.pushButtonBluetoothSearch.setText('Search')
551       
552       
553        ##################################################################
554       
555        def closeEvent(self, event):
556               
557                quit_message = "Are you sure you want to exit the program?"
558               
559                reply = QtGui.QMessageBox.question( \
560                           self, \
561                          'Message', \
562                           quit_message, \
563                           QtGui.QMessageBox.Yes, \
564                           QtGui.QMessageBox.No)
565               
566                if reply == QtGui.QMessageBox.Yes:
567                       
568                        if UPDATE_INTERFACE_VIA_TIMER:
569                                self.updateInterfaceTimer.stop()
570                        else:
571                                if self.thinkgearConnectClient != None:
572                                        self.thinkgearConnectClient.disconnectFromHost()
573                       
574                        if self.thinkGearConnectServer != None:
575                                self.thinkGearConnectServer.exitThread()
576                       
577                        event.accept()
578               
579                else:
580                        event.ignore()
581
582
583#####################################################################
584#####################################################################
585
586class MyMplCanvas(FigureCanvas):
587       
588        """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
589       
590        def __init__(self, parent=None, width=5, height=4, dpi=100):
591               
592                fig = Figure(figsize=(width, height), dpi=dpi)
593                self.axes = fig.add_subplot(111)
594                # We want the axes cleared every time plot() is called
595                self.axes.hold(False)
596               
597                self.compute_initial_figure()
598               
599                #
600                FigureCanvas.__init__(self, fig)
601                self.setParent(parent)
602               
603                FigureCanvas.setSizePolicy(self,
604                                                                                        QtGui.QSizePolicy.Expanding,
605                                                                                        QtGui.QSizePolicy.Expanding)
606                FigureCanvas.updateGeometry(self)
607       
608       
609        ##################################################################
610       
611        def compute_initial_figure(self):
612                pass
613
614
615#####################################################################
616#####################################################################
617
618class MyDynamicMplCanvas(MyMplCanvas):
619       
620        """A canvas that updates itself every second with a new plot."""
621       
622        def __init__(self, *args, **kwargs):
623               
624                MyMplCanvas.__init__(self, *args, **kwargs)
625               
626                timer = QtCore.QTimer(self)
627                QtCore.QObject.connect(timer, QtCore.SIGNAL("timeout()"), self.update_figure)
628                timer.start(1000)
629       
630       
631        ##################################################################
632       
633        def compute_initial_figure(self):
634                self.axes.plot([0, 1, 2, 3], [1, 2, 0, 4], 'r')
635       
636       
637        ##################################################################
638       
639        def update_figure(self):
640                # Build a list of 4 random integers between 0 and 10 (both inclusive)
641                l = [ random.randint(0, 10) for i in xrange(4) ]
642               
643                self.axes.plot([0, 1, 2, 3], l, 'r')
644                self.draw()
645
Note: See TracBrowser for help on using the repository browser.