source: trunk/synapse/puzzlebox_synapse_server_thinkgear.py @ 161

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

synapse/setup.py:

  • renamed from synapse/puzzlebox_synapse_setup.py
  • preparation for 0.3.0 release

synapse/puzzlebox_synapse_interface.py:

  • preparation for 0.3.0 release
  • general code and comment cleanup
  • UPDATE_INTERFACE_VIA_TIMER switch added for selecting thread-safe interface update methods

synapse/puzzlebox_synapse_server_thinkgear.py:

  • general code and comment cleanup
  • Property svn:executable set to *
File size: 16.8 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Puzzlebox - Synapse - Server
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.08.09
12#
13#####################################################################
14
15import os, sys, time
16import signal
17import math
18
19import simplejson as json
20
21try:
22        import PySide as PyQt4
23        from PySide import QtCore# , QtNetwork
24except:
25        print "Using PyQt4 module"
26        from PyQt4 import QtCore#, QtNetwork
27else:
28        print "Using PySide module"
29
30from PyQt4 import QtNetwork
31
32import puzzlebox_synapse_configuration as configuration
33import puzzlebox_synapse_protocol_thinkgear as serial_interface
34#import puzzlebox_logger
35
36#####################################################################
37# Globals
38#####################################################################
39
40DEBUG = 1
41
42SERVER_INTERFACE = configuration.THINKGEAR_SERVER_INTERFACE
43SERVER_PORT = configuration.THINKGEAR_SERVER_PORT
44THINKGEAR_DEVICE_SERIAL_PORT = configuration.THINKGEAR_DEVICE_SERIAL_PORT
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#####################################################################
125# Classes
126#####################################################################
127
128class puzzlebox_synapse_server_thinkgear(QtCore.QThread):
129       
130        def __init__(self, log, \
131                          server_interface=SERVER_INTERFACE, \
132                          server_port=SERVER_PORT, \
133                          device_address=THINKGEAR_DEVICE_SERIAL_PORT, \
134                          emulate_headset_data=ENABLE_SIMULATE_HEADSET_DATA, \
135                          DEBUG=DEBUG, \
136                          parent=None):
137               
138                QtCore.QThread.__init__(self,parent)
139               
140                self.log = log
141                self.DEBUG = DEBUG
142                self.parent = parent
143               
144                self.server_interface = server_interface
145                self.server_port = server_port
146                self.device_address = device_address
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.puzzlebox_synapse_protocol_thinkgear_serial_device( \
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.puzzlebox_synapse_protocol_thinkgear( \
192                                        self.log, \
193                                        self.serial_device, \
194                                        DEBUG=self.DEBUG, \
195                                        parent=self)
196                       
197                        self.protocol.start()
198       
199       
200        ##################################################################
201       
202        def emulationEvent(self):
203               
204                self.updateStatus()
205                self.sendPacketQueue()
206       
207       
208        ##################################################################
209       
210        def configureNetwork(self):
211       
212                #self.blockSize = 0
213                self.socket = QtNetwork.QTcpServer()
214                self.socket.name = 'ThinkGear Server'
215               
216                if self.DEBUG:
217                        print "<---- [%s] Initializing server on %s:%i" % \
218                           (self.socket.name, self.server_interface, self.server_port)
219               
220               
221                if ((self.server_interface == '') or \
222                         (self.server_interface == '*')):
223                        address=QtNetwork.QHostAddress.Any
224                else:
225                        #address=self.server_interface
226                        address=QtNetwork.QHostAddress(self.server_interface)
227               
228               
229                result = self.socket.listen(address, self.server_port)
230               
231               
232                if not result:
233                        if self.DEBUG:
234                                print "ERROR [%s] Unable to start the server:" % self.socket.name,
235                                print self.socket.errorString()
236                               
237                        self.socket.close()
238                        return
239               
240               
241                self.socket.newConnection.connect(self.processConnection)
242                #self.socket.error.connect(self.displayError)
243       
244       
245        ##################################################################
246       
247        def deleteDisconnected(self):
248               
249                connection_index = 0
250               
251                for connection in self.connections:
252                       
253                        if ((connection.state() != QtNetwork.QAbstractSocket.ConnectingState) and \
254                                 (connection.state() != QtNetwork.QAbstractSocket.ConnectedState)):
255                               
256                                if self.DEBUG:
257                                        print "- - [%s] Deleting disconnected socket" % self.socket.name
258                               
259                                connection.deleteLater()
260                                # Delete references to disconnected sockets
261                                del (self.connections[connection_index])
262                       
263                       
264                        connection_index += 1
265       
266       
267        ##################################################################
268       
269        def processConnection(self):
270               
271                clientConnection = self.socket.nextPendingConnection()
272                clientConnection.disconnected.connect(self.deleteDisconnected)
273               
274                self.connections.append(clientConnection)
275               
276                self.clientConnection = clientConnection
277               
278                # the next connected client to enter the readyRead state
279                # will be processed first
280                clientConnection.readyRead.connect(self.processClientConnection)
281       
282       
283        ##################################################################
284       
285        def processClientConnection(self):
286               
287                clientConnection = self.clientConnection
288               
289                socket_buffer = clientConnection.readAll()
290               
291                for packet in socket_buffer.split(DELIMITER):
292                       
293                        data_to_process = None
294                       
295                        if packet != '':
296                               
297                                try:
298                                        data_to_process = json.loads(packet.data())
299                               
300                                except Exception, e:
301                                       
302                                        # Special socket handling for Flash applications
303                                        if (packet == FLASH_POLICY_FILE_REQUEST):
304                                               
305                                                if self.DEBUG:
306                                                        print "--> [%s] Flash policy file requested" % self.socket.name
307                                               
308                                                data_to_process = packet.data()
309                                       
310                                       
311                                        else:
312                                               
313                                                if self.DEBUG:
314                                                        print "--> [ThinkGear Emulator] Partial data received (or error:",
315                                                        print e
316                                                        print ")."
317                                                       
318                                                        print "packet data:",
319                                                        print packet.data()
320                               
321                               
322                                else:
323                                       
324                                        if self.DEBUG:
325                                                print "--> [%s] Received:" % self.socket.name,
326                                                print data_to_process
327                               
328                               
329                                if (data_to_process != None):
330                                       
331                                        response = self.processData(data_to_process)
332                                       
333                                        if (response != None):
334                                               
335                                                self.sendResponse(clientConnection, response)
336       
337       
338        ##################################################################
339       
340        def sendResponse(self, connection, response, disconnect_after_sending=False):
341               
342                # Special socket handling for Flash applications
343                if (response == FLASH_SOCKET_POLICY_FILE):
344                        data = response
345                else:
346                        data = json.dumps(response)
347                        data = data + DELIMITER
348               
349                if connection.waitForConnected(CLIENT_NO_REPLY_WAIT):
350                       
351                        if self.DEBUG > 1:
352                                print "<-- [%s] Sending:" % self.socket.name,
353                                print data
354                       
355                        connection.write(data)
356                       
357                        connection.waitForBytesWritten(CLIENT_NO_REPLY_WAIT)
358                       
359                        if disconnect_after_sending:
360                                connection.disconnectFromHost()
361       
362       
363        ##################################################################
364       
365        def sendPacketQueue(self):
366               
367                if self.connections != []:
368                       
369                        while (len(self.packet_queue) > 0):
370                               
371                                packet = self.packet_queue[0]
372                                del self.packet_queue[0]
373                               
374                                for connection in self.connections:
375                                       
376                                        if connection.state() == QtNetwork.QAbstractSocket.ConnectedState:
377                                               
378                                                self.sendResponse(connection, packet)
379       
380       
381        ##################################################################
382       
383        def processData(self, data):
384               
385                response = None
386               
387                # Special socket handling for Flash applications
388                if (data == FLASH_POLICY_FILE_REQUEST):
389                       
390                        response = FLASH_SOCKET_POLICY_FILE
391                       
392                        #self.packet_queue.insert(0, FLASH_SOCKET_POLICY_FILE)
393               
394               
395                elif (type(data) == type({}) and \
396                      data.has_key('appName') and \
397                      data.has_key('appKey')):
398                        authorized = self.authorizeClient(data)
399                       
400                        response = {}
401                        response['isAuthorized'] = authorized
402                       
403                        #self.packet_queue.insert(0, response)
404               
405               
406                return(response)
407       
408       
409        ##################################################################
410       
411        def validateChecksum(self, checksum):
412               
413                '''The key used by the client application to identify
414itself. This must be 40 hexadecimal characters, ideally generated
415using an SHA-1 digest. The appKey is an identifier that is unique
416to each application, rather than each instance of an application.
417It is used by the server to bypass the authorization process if a
418user had previously authorized the requesting client. To reduce
419the chance of overlap with the appKey of other applications,
420the appKey should be generated using an SHA-1 digest.'''
421               
422                is_valid = True
423               
424                hexadecimal_characters = '0123456789abcdef'
425               
426                if len(checksum) != 40:
427                        is_valid = False
428                else:
429                        for character in checksum:
430                                if character not in hexadecimal_characters:
431                                        is_valid = False
432               
433                return(is_valid)
434       
435       
436        ##################################################################
437       
438        def authorizeClient(self, data):
439       
440                '''The client must initiate an authorization request
441and the server must authorize the client before the
442server will start transmitting any headset data.'''
443               
444                is_authorized = self.validateChecksum(data['appKey'])
445               
446                # A human-readable name identifying the client
447                # application. This can be a maximum of 255 characters.
448               
449                if len(data['appName']) > 255:
450                        is_authorized = False
451               
452               
453                return(is_authorized)
454       
455       
456        ##################################################################
457       
458        def calculateWavePoint(self, x, max_height=100, wave_length=10):
459               
460                # start at 0, increase to max value at half of one
461                # wavelength, decrease to 0 by end of wavelength
462                y = ( (max_height/2) * \
463                      math.sin ((x-1) * ( math.pi / (wave_length / 2)))) + \
464                      (max_height/2)
465               
466                # start at max value, decrease to 0 at half of one
467                # wavelegnth, increase to max by end of wavelength
468                #y = ( (max_height/2) * \
469                      #math.cos (x * ( math.pi / (wave_length / 2)))) + \
470                      #(max_height/2)
471               
472               
473                return(y)
474       
475       
476        ##################################################################
477       
478        def simulateHeadsetData(self):
479               
480                response = DEFAULT_PACKET
481               
482                time_value = self.connection_timestamp - time.time()
483               
484                for key in response.keys():
485                       
486                        if key == 'poorSignalLevel':
487                                pass
488                       
489                        elif key == 'eSense':
490                                plot = self.calculateWavePoint( \
491                                        time_value, \
492                                        max_height=100, \
493                                        wave_length=DEFAULT_SAMPLE_WAVELENGTH)
494                               
495                                for each in response[key].keys():
496                                       
497                                        if ((each == 'attention') and \
498                                                 (THINKGEAR_ATTENTION_MULTIPLIER != None)):
499                                                value = plot * \
500                                                   THINKGEAR_ATTENTION_MULTIPLIER
501                                       
502                                        elif ((each == 'meditation') and \
503                                                   (THINKGEAR_MEDITATION_MULTIPLIER != None)):
504                                                value = plot * \
505                                                   THINKGEAR_MEDITATION_MULTIPLIER
506                                       
507                                        if value < 0:
508                                                value = 0
509                                        elif value > 100:
510                                                value = 100
511                                       
512                                        response[key][each] = value
513                       
514                       
515                        elif key == 'eegPower':
516                                plot = self.calculateWavePoint( \
517                                        time_value, \
518                                        max_height=65535, \
519                                        wave_length=DEFAULT_SAMPLE_WAVELENGTH)
520                               
521                                for each in response[key].keys():
522                                        if ((THINKGEAR_EEG_POWER_MULTIPLIERS != None) and \
523                                                 (each in THINKGEAR_EEG_POWER_MULTIPLIERS.keys())):
524                                                value = THINKGEAR_EEG_POWER_MULTIPLIERS[each] * plot
525                                        else:
526                                                value = plot
527                                        response[key][each] = value
528               
529               
530                return(response)
531       
532       
533        ##################################################################
534       
535        def processPacketThinkGear(self, packet):
536               
537                if self.DEBUG > 2:
538                        print packet
539               
540                if (packet != {}):
541                        self.packet_queue.append(packet)
542                        self.sendPacketQueue()
543               
544                #if (self.parent != None):
545                        #self.parent.processPacketThinkGear(self.protocol.data_packet)
546       
547       
548        ##################################################################
549       
550        def updateStatus(self):
551               
552                # Craft a simulated data packet
553                packet = self.simulateHeadsetData()
554               
555                self.packet_queue.append(packet)
556               
557                if (self.parent != None):
558                        self.parent.processPacketThinkGear(packet)
559               
560                # Include simulated blinks at desired frequency
561                if ((self.blink_frequency_timer != None) and \
562                                (self.blink_frequency_timer > 0) and \
563                                (time.time() - self.blink_timestamp > \
564                                self.blink_frequency_timer)):
565                       
566                        self.blink_timestamp = time.time()
567                       
568                        packet = DEFAULT_BLINK_MESSAGE
569                        self.packet_queue.append(packet)
570       
571       
572        ##################################################################
573       
574        def run(self):
575               
576                if self.DEBUG:
577                        print "<---- [%s] Main thread running" % self.socket.name
578               
579                self.exec_()
580       
581       
582        ##################################################################
583       
584        def exitThread(self, callThreadQuit=True):
585               
586                try:
587                        self.emulationTimer.stop()
588                except:
589                        pass
590               
591                if self.serial_device != None:
592                        self.serial_device.exitThread()
593               
594                if self.protocol != None:
595                        self.protocol.exitThread()
596               
597                self.socket.close()
598               
599                if callThreadQuit:
600                        QtCore.QThread.quit(self)
601               
602                if self.parent == None:
603                        sys.exit()
604
605
606#####################################################################
607# Functions
608#####################################################################
609
610def exitHandler(signal, frame):
611       
612        if DEBUG > 1:
613                print "exitHandler: Interrupt signal caught"
614       
615        server.exitThread(callThreadQuit=False)
616        sys.exit()
617
618
619#####################################################################
620# Main
621#####################################################################
622
623if __name__ == '__main__':
624       
625        # Perform correct KeyboardInterrupt handling
626        signal.signal(signal.SIGINT, signal.SIG_DFL)
627        #signal.signal(signal.SIGINT, exitHandler)
628       
629        #log = puzzlebox_logger.puzzlebox_logger(logfile='server_thinkgear')
630        log = None
631       
632        # Collect default settings and command line parameters
633        server_interface = SERVER_INTERFACE
634        server_port = SERVER_PORT
635        device_address = THINKGEAR_DEVICE_SERIAL_PORT
636       
637       
638        for each in sys.argv:
639               
640                if each.startswith("--interface="):
641                        server_interface = each[ len("--interface="): ]
642                if each.startswith("--port="):
643                        server_port = each[ len("--port="): ]
644                if each.startswith("--device="):
645                        device_address = each[ len("--device="): ]
646       
647       
648        app = QtCore.QCoreApplication(sys.argv)
649       
650        server = puzzlebox_synapse_server_thinkgear( \
651                         log, \
652                         server_interface, \
653                         server_port, \
654                         device_address, \
655                         emulate_headset_data = \
656                            ENABLE_SIMULATE_HEADSET_DATA, \
657                         DEBUG=DEBUG)
658       
659        server.start()
660       
661        sys.exit(app.exec_())
662
Note: See TracBrowser for help on using the repository browser.