source: trunk/Puzzlebox/Synapse/ThinkGear/Server.py @ 394

Last change on this file since 394 was 394, checked in by sc, 9 years ago

session handling cleanup

File size: 16.8 KB
Line 
1# -*- coding: utf-8 -*-
2
3# Copyright Puzzlebox Productions, LLC (2010-2012)
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__changelog__ = """\
9Last Update: 2012.05.11
10"""
11
12### IMPORTS ###
13
14import os, sys, time
15import math
16
17import simplejson as json
18
19import Puzzlebox.Synapse.Configuration as configuration
20
21if configuration.ENABLE_PYSIDE:
22        try:
23                import PySide as PyQt4
24                from PySide import QtCore, QtGui, QtNetwork
25        except Exception, e:
26                print "ERROR: Exception importing PySide:",
27                print e
28                configuration.ENABLE_PYSIDE = False
29        else:
30                print "INFO: [Synapse:ThinkGear:Server] Using PySide module"
31
32if not configuration.ENABLE_PYSIDE:
33        print "INFO: [Synapse:ThinkGear:Server] Using PyQt4 module"
34        from PyQt4 import QtCore, QtGui, QtNetwork
35
36import Puzzlebox.Synapse.Server as synapse_server
37import Puzzlebox.Synapse.ThinkGear.Protocol as thinkgear_protocol
38
39#####################################################################
40# Globals
41#####################################################################
42
43DEBUG = configuration.DEBUG
44
45COMMUNICATION_MODE = 'Emit Signal'
46#COMMUNICATION_MODE = 'Call Parent'
47
48SERVER_INTERFACE = configuration.THINKGEAR_SERVER_INTERFACE
49SERVER_PORT = configuration.THINKGEAR_SERVER_PORT
50DEFAULT_DEVICE_MODEL = 'NeuroSky MindWave'
51THINKGEAR_DEVICE_SERIAL_PORT = configuration.THINKGEAR_DEVICE_SERIAL_PORT
52
53CLIENT_NO_REPLY_WAIT = configuration.CLIENT_NO_REPLY_WAIT * 1000
54
55FLASH_POLICY_FILE_REQUEST = configuration.FLASH_POLICY_FILE_REQUEST
56FLASH_SOCKET_POLICY_FILE = configuration.FLASH_SOCKET_POLICY_FILE
57
58DELIMITER = configuration.THINKGEAR_DELIMITER
59
60MESSAGE_FREQUENCY_TIMER = 1 * 1000 # 1 Hz (1000 ms)
61
62ENABLE_SIMULATE_HEADSET_DATA = configuration.THINKGEAR_ENABLE_SIMULATE_HEADSET_DATA
63
64BLINK_FREQUENCY_TIMER = configuration.THINKGEAR_BLINK_FREQUENCY_TIMER
65
66DEFAULT_SAMPLE_WAVELENGTH = configuration.THINKGEAR_DEFAULT_SAMPLE_WAVELENGTH
67
68THINKGEAR_EMULATION_MAX_ESENSE_VALUE = \
69        configuration.THINKGEAR_EMULATION_MAX_ESENSE_VALUE
70THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE = \
71        configuration.THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE
72
73THINKGEAR_ATTENTION_MULTIPLIER = configuration.THINKGEAR_ATTENTION_MULTIPLIER
74THINKGEAR_MEDITATION_MULTIPLIER = configuration.THINKGEAR_MEDITATION_MULTIPLIER
75THINKGEAR_MEDITATION_PLOT_OFFSET = configuration.THINKGEAR_MEDITATION_PLOT_OFFSET
76
77THINKGEAR_EEG_POWER_MULTIPLIERS = configuration.THINKGEAR_EEG_POWER_MULTIPLIERS
78
79DEFAULT_AUTHORIZATION_MESSAGE = \
80        {"isAuthorized": True}
81                # Tells the client whether the server has authorized
82                # access to the user's headset data. The value is
83                # either true or false.
84
85DEFAULT_SIGNAL_LEVEL_MESSAGE = \
86        {"poorSignalLevel": 0}
87                # A quantifier of the quality of the brainwave signal.
88                # This is an integer value that is generally in the
89                # range of 0 to 200, with 0 indicating a
90                # good signal and 200 indicating an off-head state.
91
92DEFAULT_EEG_POWER_MESSAGE = \
93        {"eegPower": { \
94                'delta': 0, \
95                'theta': 0, \
96                'lowAlpha': 0, \
97                'highAlpha': 0, \
98                'lowBeta': 0, \
99                'highBeta': 0, \
100                'lowGamma': 0, \
101                'highGamma': 0, \
102                }, \
103        } # A container for the EEG powers. These may
104          # be either integer or floating-point values.
105          # Maximum values are undocumented but assumed to be 65535
106
107DEFAULT_ESENSE_MESSAGE = \
108        {"eSense": { \
109                'attention': 0, \
110                'meditation': 0, \
111                }, \
112        } # A container for the eSense™ attributes.
113          # These are integer values between 0 and 100,
114          # where 0 is perceived as a lack of that attribute
115          # and 100 is an excess of that attribute.
116
117DEFAULT_BLINK_MESSAGE = {"blinkStrength": 255}
118        # The strength of a detected blink. This is
119        # an integer in the range of 0-255.
120
121DEFAULT_RAWEEG_MESSAGE = {"rawEeg": 255}
122        # The raw data reading off the forehead sensor.
123        # This may be either an integer or a floating-point value.
124
125DEFAULT_PACKET = {}
126DEFAULT_PACKET.update(DEFAULT_EEG_POWER_MESSAGE)
127DEFAULT_PACKET.update(DEFAULT_SIGNAL_LEVEL_MESSAGE)
128DEFAULT_PACKET.update(DEFAULT_ESENSE_MESSAGE)
129
130DEFAULT_RESPONSE_MESSAGE = DEFAULT_SIGNAL_LEVEL_MESSAGE
131
132#PACKET_MINIMUM_TIME_DIFFERENCE_THRESHOLD = 0.75
133
134#####################################################################
135# Classes
136#####################################################################
137
138class puzzlebox_synapse_server_thinkgear(synapse_server.puzzlebox_synapse_server):
139       
140        def __init__(self, log, \
141                          server_interface=SERVER_INTERFACE, \
142                          server_port=SERVER_PORT, \
143                          device_model=None, \
144                          device_address=THINKGEAR_DEVICE_SERIAL_PORT, \
145                          emulate_headset_data=ENABLE_SIMULATE_HEADSET_DATA, \
146                          DEBUG=DEBUG, \
147                          parent=None):
148               
149                QtCore.QThread.__init__(self,parent)
150               
151                self.log = log
152                self.DEBUG = DEBUG
153                self.parent = parent
154               
155                self.server_interface = server_interface
156                self.server_port = server_port
157                self.device_address = device_address
158                self.device_model = device_model
159                self.emulate_headset_data = emulate_headset_data
160               
161                self.name = 'Synapse:ThinkGear:Server'
162               
163                self.connection_timestamp = time.time()
164                self.session_start_timestamp = time.time()
165               
166                self.connections = []
167                self.packet_queue = []
168               
169                self.serial_device = None
170                self.protocol = None
171               
172                self.connect(self, \
173                             QtCore.SIGNAL("sendPacket()"), \
174                             self.sendPacketQueue)
175               
176                self.message_frequency_timer = MESSAGE_FREQUENCY_TIMER
177                self.blink_frequency_timer = BLINK_FREQUENCY_TIMER
178                self.blink_timestamp = time.time()
179               
180               
181                #self.customDataHeaders = 'Attention,Meditation,Signal Level,Delta,Theta,Low Alpha,High Alpha,Low Beta,High Beta,Low Gamma,Mid Gamma'
182                self.customDataHeaders = ['Attention', \
183                                          'Meditation', \
184                                          'Signal Level', \
185                                          'Delta', \
186                                          'Theta', \
187                                          'Low Alpha', \
188                                          'High Alpha', \
189                                          'Low Beta', \
190                                          'High Beta', \
191                                          'Low Gamma', \
192                                          'Mid Gamma', \
193                                         ]
194               
195               
196                self.configureEEG()
197               
198                self.configureNetwork()
199               
200               
201                if (self.emulate_headset_data):
202                        self.emulationTimer = QtCore.QTimer()
203                        QtCore.QObject.connect(self.emulationTimer, \
204                                                    QtCore.SIGNAL("timeout()"), \
205                                                    self.emulationEvent)
206                        self.emulationTimer.start(MESSAGE_FREQUENCY_TIMER)
207       
208       
209        ##################################################################
210       
211        def configureEEG(self):
212               
213                if not self.emulate_headset_data:
214                       
215                        self.serial_device = \
216                                thinkgear_protocol.SerialDevice( \
217                                        self.log, \
218                                        device_address=self.device_address, \
219                                        DEBUG=self.DEBUG, \
220                                        parent=self)
221                       
222                        self.serial_device.start()
223               
224                else:
225                        self.serial_device = None
226               
227               
228                self.protocol = \
229                        thinkgear_protocol.puzzlebox_synapse_protocol_thinkgear( \
230                                self.log, \
231                                self.serial_device, \
232                                device_model=self.device_model, \
233                                DEBUG=self.DEBUG, \
234                                parent=self)
235               
236                #self.plugin_session = self.parent.plugin_session # for Jigsaw compatability
237               
238                self.protocol.start()
239       
240       
241        ##################################################################
242       
243        def emulationEvent(self):
244               
245                self.updateStatus()
246               
247                if COMMUNICATION_MODE == 'Emit Signal':
248                        self.emitSendPacketSignal()
249                else:
250                        self.sendPacketQueue()
251       
252       
253        ##################################################################
254       
255        def processData(self, data):
256               
257                response = None
258               
259                # Special socket handling for Flash applications
260                if (data == FLASH_POLICY_FILE_REQUEST):
261                       
262                        response = FLASH_SOCKET_POLICY_FILE
263                       
264                        #self.packet_queue.insert(0, FLASH_SOCKET_POLICY_FILE)
265               
266               
267                elif (type(data) == type({}) and \
268                      data.has_key('appName') and \
269                      data.has_key('appKey')):
270                       
271                        authorized = self.authorizeClient(data)
272                       
273                        response = {}
274                        response['isAuthorized'] = authorized
275                       
276                        #self.packet_queue.insert(0, response)
277               
278               
279                return(response)
280       
281       
282        ##################################################################
283       
284        def validateChecksum(self, checksum):
285               
286                '''The key used by the client application to identify
287itself. This must be 40 hexadecimal characters, ideally generated
288using an SHA-1 digest. The appKey is an identifier that is unique
289to each application, rather than each instance of an application.
290It is used by the server to bypass the authorization process if a
291user had previously authorized the requesting client. To reduce
292the chance of overlap with the appKey of other applications,
293the appKey should be generated using an SHA-1 digest.'''
294               
295                is_valid = True
296               
297                hexadecimal_characters = '0123456789abcdef'
298               
299                if len(checksum) != 40:
300                        is_valid = False
301                else:
302                        for character in checksum:
303                                if character not in hexadecimal_characters:
304                                        is_valid = False
305               
306                return(is_valid)
307       
308       
309        ##################################################################
310       
311        def authorizeClient(self, data):
312       
313                '''The client must initiate an authorization request
314and the server must authorize the client before the
315server will start transmitting any headset data.'''
316               
317                is_authorized = self.validateChecksum(data['appKey'])
318               
319                # A human-readable name identifying the client
320                # application. This can be a maximum of 255 characters.
321               
322                if len(data['appName']) > 255:
323                        is_authorized = False
324               
325               
326                return(is_authorized)
327       
328       
329        ##################################################################
330       
331        def calculateWavePoint(self, x, max_height=100, wave_length=10):
332               
333                # start at 0, increase to max value at half of one
334                # wavelength, decrease to 0 by end of wavelength
335                y = ( (max_height/2) * \
336                      math.sin ((x-1) * ( math.pi / (wave_length / 2)))) + \
337                      (max_height/2)
338               
339                # start at max value, decrease to 0 at half of one
340                # wavelegnth, increase to max by end of wavelength
341                #y = ( (max_height/2) * \
342                      #math.cos (x * ( math.pi / (wave_length / 2)))) + \
343                      #(max_height/2)
344               
345               
346                return(y)
347       
348       
349        ##################################################################
350       
351        def simulateHeadsetData(self):
352               
353                response = DEFAULT_PACKET
354               
355                response['timestamp'] = time.time()
356               
357                time_value = self.connection_timestamp - time.time()
358               
359                for key in response.keys():
360                       
361                        if key == 'poorSignalLevel':
362                                pass
363                       
364                        elif key == 'eSense':
365                               
366                                plot_attention = self.calculateWavePoint( \
367                                        time_value, \
368                                        max_height=100, \
369                                        wave_length=DEFAULT_SAMPLE_WAVELENGTH)
370                               
371                                plot_meditation = self.calculateWavePoint( \
372                                        time_value + THINKGEAR_MEDITATION_PLOT_OFFSET, \
373                                        max_height=100, \
374                                        wave_length=DEFAULT_SAMPLE_WAVELENGTH)
375                               
376                                for each in response[key].keys():
377                                       
378                                        if ((each == 'attention') and \
379                                                 (THINKGEAR_ATTENTION_MULTIPLIER != None)):
380                                                value = plot_attention * \
381                                                   THINKGEAR_ATTENTION_MULTIPLIER
382                                       
383                                        elif ((each == 'meditation') and \
384                                                   (THINKGEAR_MEDITATION_MULTIPLIER != None)):
385                                                value = plot_meditation * \
386                                                   THINKGEAR_MEDITATION_MULTIPLIER
387                                       
388                                       
389                                        value = int(value)
390                                       
391                                       
392                                        if value < 0:
393                                                value = 0
394                                        elif value > 100:
395                                                value = 100
396                                       
397                                        response[key][each] = value
398                       
399                       
400                        elif key == 'eegPower':
401                                plot = self.calculateWavePoint( \
402                                        time_value, \
403                                        max_height=65535, \
404                                        wave_length=DEFAULT_SAMPLE_WAVELENGTH)
405                               
406                                for each in response[key].keys():
407                                        if ((THINKGEAR_EEG_POWER_MULTIPLIERS != None) and \
408                                                 (each in THINKGEAR_EEG_POWER_MULTIPLIERS.keys())):
409                                                value = int(THINKGEAR_EEG_POWER_MULTIPLIERS[each] * plot)
410                                        else:
411                                                value = plot
412                                        response[key][each] = value
413               
414               
415                return(response)
416       
417       
418        ##################################################################
419       
420        def processPacketThinkGear(self, packet):
421               
422                if self.DEBUG > 2:
423                        print packet
424               
425                if (packet != {}):
426                        self.packet_queue.append(packet)
427                       
428                        if COMMUNICATION_MODE == 'Emit Signal':
429                                self.emitSendPacketSignal()
430                       
431                        else:
432                                self.sendPacketQueue()
433                               
434                                if (self.parent != None):
435                                        #self.parent.processPacketThinkGear(packet)
436                                        self.parent.processPacketEEG(packet)
437       
438       
439        ##################################################################
440       
441        def updateStatus(self):
442               
443                # Craft a simulated data packet
444                packet = self.simulateHeadsetData()
445               
446                self.packet_queue.append(packet)
447               
448                # Include simulated blinks at desired frequency
449                if ((self.blink_frequency_timer != None) and \
450                                (self.blink_frequency_timer > 0) and \
451                                (time.time() - self.blink_timestamp > \
452                                self.blink_frequency_timer)):
453                       
454                        self.blink_timestamp = time.time()
455                       
456                        packet = DEFAULT_BLINK_MESSAGE
457                       
458                        packet['timestamp'] = self.blink_timestamp
459                       
460                        self.packet_queue.append(packet)
461       
462       
463        ##################################################################
464       
465        #def updateSessionStartTime(self, session_start_timestamp):
466               
467                #if self.parent != None:
468                        #self.parent.updateSessionStartTime(session_start_timestamp)
469
470       
471        ##################################################################
472       
473        def resetDevice(self):
474               
475                if self.serial_device != None:
476                        self.serial_device.exitThread()
477               
478                if self.protocol != None:
479                        self.protocol.exitThread()
480               
481                self.configureEEG()
482       
483       
484        ##################################################################
485       
486        def processPacketForExport(self, packet={}, output={}):
487               
488                if 'blinkStrength' in packet.keys():
489                        # Skip any blink packets from log
490                        #continue
491                        return(output)
492               
493                #output['Attention'] = ''
494                #output['Meditation'] = ''
495                #output['Signal Level'] = ''
496                #output['Delta'] = ''
497                #output['Theta'] = ''
498                #output['Low Alpha'] = ''
499                #output['High Alpha'] = ''
500                #output['Low Beta'] = ''
501                #output['High Beta'] = ''
502                #output['Low Gamma'] = ''
503                #output['Mid Gamma'] = ''
504               
505                for header in self.customDataHeaders:
506                        output[header] = ''
507               
508                if 'eSense' in packet.keys():
509                        if 'attention' in packet['eSense'].keys():
510                                output['Attention'] = packet['eSense']['attention']
511                        if 'meditation' in packet['eSense'].keys():
512                                output['Meditation'] = packet['eSense']['meditation']
513               
514                if 'poorSignalLevel' in packet.keys():
515                        output['Signal Level'] = packet['poorSignalLevel']
516               
517                if 'eegPower' in packet.keys():
518                        if 'delta' in packet['eegPower'].keys():
519                                output['Delta'] = packet['eegPower']['delta']
520                        if 'theta' in packet['eegPower'].keys():
521                                output['Theta'] = packet['eegPower']['theta']
522                        if 'lowAlpha' in packet['eegPower'].keys():
523                                output['Low Alpha'] = packet['eegPower']['lowAlpha']
524                        if 'highAlpha' in packet['eegPower'].keys():
525                                output['High Alpha'] = packet['eegPower']['highAlpha']
526                        if 'lowBeta' in packet['eegPower'].keys():
527                                output['Low Beta'] = packet['eegPower']['lowBeta']
528                        if 'highBeta' in packet['eegPower'].keys():
529                                output['High Beta'] = packet['eegPower']['highBeta']
530                        if 'lowGamma' in packet['eegPower'].keys():
531                                output['Low Gamma'] = packet['eegPower']['lowGamma']
532                        if 'highGamma' in packet['eegPower'].keys():
533                                output['Mid Gamma'] = packet['eegPower']['highGamma']
534               
535               
536                return(output)
537       
538       
539        ##################################################################
540       
541        def setPacketCount(self, value):
542               
543                self.parent.setPacketCount(value)
544       
545       
546        ##################################################################
547       
548        def setBadPackets(self, value):
549               
550                self.parent.setBadPackets(value)
551       
552       
553        ##################################################################
554       
555        def incrementPacketCount(self):
556               
557                self.parent.incrementPacketCount()
558       
559       
560        ##################################################################
561       
562        def incrementBadPackets(self):
563               
564                self.parent.incrementBadPackets()
565       
566       
567        ##################################################################
568       
569        def resetSessionStartTime(self):
570               
571                self.parent.resetSessionStartTime()
572       
573       
574        ##################################################################
575       
576        #def run(self):
577               
578                #if self.DEBUG:
579                        #print "<---- [%s] Main thread running" % self.name
580               
581                #self.exec_()
582       
583       
584        ##################################################################
585       
586        def exitThread(self, callThreadQuit=True):
587               
588                if (self.emulate_headset_data):
589                        try:
590                                self.emulationTimer.stop()
591                        except Exception, e:
592                                if self.DEBUG:
593                                        print "ERROR: Exception when stopping emulation timer:",
594                                        print e
595               
596                # Calling exitThread() on protocol first seems to occassionally
597                # create the following error:
598                # RuntimeError: Internal C++ object (PySide.QtNetwork.QTcpSocket) already deleted.
599                # Segmentation fault
600                # ...when program is closed without pressing "Stop" button for EEG
601                #if self.protocol != None:
602                        #self.protocol.exitThread()
603               
604                # Call disconnect block in protocol first due to above error
605                self.protocol.disconnectHardware()
606               
607                if self.serial_device != None:
608                        self.serial_device.exitThread()
609               
610                if self.protocol != None:
611                        self.protocol.exitThread()
612               
613                self.socket.close()
614               
615                if callThreadQuit:
616                        QtCore.QThread.quit(self)
617               
618                if self.parent == None:
619                        sys.exit()
620       
621       
622        ##################################################################
623       
624        #def stop(self):
625               
626                #self.exitThread()
627
Note: See TracBrowser for help on using the repository browser.