source: trunk/Puzzlebox/Synapse/Server.py @ 284

Last change on this file since 284 was 284, checked in by sc, 10 years ago

Interface:

  • custom data headers now handled when exporting to CSV

Server:

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