source: thinkgear_emulator/puzzlebox_thinkgear_server.py @ 117

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

thinkgear_emulator/puzzlebox_thinkgear_server.py:

  • DELIMITER proper usage fixed
  • Property svn:executable set to *
File size: 13.5 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Puzzlebox - Brainstorms - Network - Server - ThinkGear
5#
6# Copyright Puzzlebox Productions, LLC (2010)
7#
8# This code is released under the GNU Pulic License (GPL) version 2
9# For more information please refer to http://www.gnu.org/copyleft/gpl.html
10#
11# Last Update: 2010.07.20
12#
13#####################################################################
14
15import os, sys, time
16import signal
17import math
18
19import simplejson as json
20
21from PyQt4 import QtCore, QtNetwork
22
23import puzzlebox_thinkgear_emulator_configuration as configuration
24#import puzzlebox_logger
25
26#####################################################################
27# Globals
28#####################################################################
29
30DEBUG = 1
31
32SERVER_INTERFACE = configuration.THINKGEAR_SERVER_INTERFACE
33SERVER_PORT = configuration.THINKGEAR_SERVER_PORT
34
35CLIENT_NO_REPLY_WAIT = configuration.CLIENT_NO_REPLY_WAIT * 1000
36
37FLASH_POLICY_FILE_REQUEST = configuration.FLASH_POLICY_FILE_REQUEST
38FLASH_SOCKET_POLICY_FILE = configuration.FLASH_SOCKET_POLICY_FILE
39
40DELIMITER = configuration.THINKGEAR_DELIMITER
41
42MESSAGE_FREQUENCY_TIMER = 1 * 1000 # 1 Hz (1000 ms)
43
44ENABLE_SIMULATE_HEADSET_DATA = configuration.THINKGEAR_ENABLE_SIMULATE_HEADSET_DATA
45
46BLINK_FREQUENCY_TIMER = configuration.THINKGEAR_BLINK_FREQUENCY_TIMER
47
48DEFAULT_SAMPLE_WAVELENGTH = configuration.THINKGEAR_DEFAULT_SAMPLE_WAVELENGTH
49
50THINKGEAR_ATTENTION_MULTIPLIER = configuration.THINKGEAR_ATTENTION_MULTIPLIER
51
52THINKGEAR_MEDITATION_MULTIPLIER = configuration.THINKGEAR_MEDITATION_MULTIPLIER
53
54DEFAULT_AUTHORIZATION_MESSAGE = \
55        {"isAuthorized": True}
56                # Tells the client whether the server has authorized
57                # access to the user's headset data. The value is
58                # either true or false.
59
60DEFAULT_SIGNAL_LEVEL_MESSAGE = \
61        {"poorSignalLevel": 0}
62                # A quantifier of the quality of the brainwave signal.
63                # This is an integer value that is generally in the
64                # range of 0 to 200, with 0 indicating a
65                # good signal and 200 indicating an off-head state.
66
67DEFAULT_EEG_POWER_MESSAGE = \
68        {"eegPower": { \
69                'delta': 0, \
70                'theta': 0, \
71                'lowAlpha': 0, \
72                'highAlpha': 0, \
73                'lowBeta': 0, \
74                'highBeta': 0, \
75                'lowGamma': 0, \
76                'highGamma': 0, \
77                }, \
78        } # A container for the EEG powers. These may
79          # be either integer or floating-point values.
80          # Maximum values are undocumented but assumed to be 65535
81
82DEFAULT_ESENSE_MESSAGE = \
83        {"eSense": { \
84                'attention': 0, \
85                'meditation': 0, \
86                }, \
87        } # A container for the eSense™ attributes.
88          # These are integer values between 0 and 100,
89          # where 0 is perceived as a lack of that attribute
90          # and 100 is an excess of that attribute.
91
92DEFAULT_BLINK_MESSAGE = {"blinkStrength": 255}
93        # The strength of a detected blink. This is
94        # an integer in the range of 0-255.
95
96DEFAULT_RAWEEG_MESSAGE = {"rawEeg": 255}
97        # The raw data reading off the forehead sensor.
98        # This may be either an integer or a floating-point value.
99
100DEFAULT_PACKET = {}
101DEFAULT_PACKET.update(DEFAULT_EEG_POWER_MESSAGE)
102DEFAULT_PACKET.update(DEFAULT_SIGNAL_LEVEL_MESSAGE)
103DEFAULT_PACKET.update(DEFAULT_ESENSE_MESSAGE)
104
105DEFAULT_RESPONSE_MESSAGE = DEFAULT_SIGNAL_LEVEL_MESSAGE
106
107#####################################################################
108# Classes
109#####################################################################
110
111class puzzlebox_brainstorms_network_server_thinkgear:
112       
113        def __init__(self, log, \
114                          server_interface=SERVER_INTERFACE, \
115                          server_port=SERVER_PORT, \
116                          DEBUG=DEBUG, \
117                          parent=None):
118               
119                self.log = log
120                self.DEBUG = DEBUG
121               
122                self.server_interface = server_interface
123                self.server_port = server_port
124               
125                self.message_frequency_timer = MESSAGE_FREQUENCY_TIMER
126                self.blink_frequency_timer = BLINK_FREQUENCY_TIMER
127               
128                self.connection_timestamp = time.time()
129                self.blink_timestamp = time.time()
130               
131                self.connections = []
132                self.packet_queue = []
133               
134                self.configureNetwork()
135               
136                self.updateTimer = QtCore.QTimer()
137                QtCore.QObject.connect(self.updateTimer, QtCore.SIGNAL("timeout()"), self.timerEvent)
138                self.updateTimer.start(MESSAGE_FREQUENCY_TIMER)
139       
140       
141        ##################################################################
142       
143        def timerEvent(self):
144               
145                if (self.connections != []):
146                       
147                        self.updateStatus()
148                        self.sendPacketQueue()
149       
150       
151        ##################################################################
152       
153        def configureNetwork(self):
154       
155                #self.blockSize = 0
156                self.socket = QtNetwork.QTcpServer()
157                self.socket.name = 'ThinkGear Server'
158               
159                if self.DEBUG:
160                        print "<---- [%s] Initializing server on %s:%i" % \
161                           (self.socket.name, self.server_interface, self.server_port)
162                       
163                if (self.server_interface == ''):
164                        result = self.socket.listen(port=self.server_port)
165                else:
166                        result = self.socket.listen(address=self.server_interface, \
167                                           port=self.server_port)
168               
169                if not result:
170                        if self.DEBUG:
171                                print "ERROR [%s] Unable to start the server:" % self.socket.name,
172                                print self.socket.errorString()
173                               
174                        self.socket.close()
175                        return
176               
177               
178                self.socket.newConnection.connect(self.processConnection)
179                #self.socket.error.connect(self.displayError)
180       
181       
182        ##################################################################
183       
184        def deleteDisconnected(self):
185               
186                connection_index = 0
187               
188                for connection in self.connections:
189                       
190                        if ((connection.state() != QtNetwork.QAbstractSocket.ConnectingState) and \
191                                 (connection.state() != QtNetwork.QAbstractSocket.ConnectedState)):
192                               
193                                if self.DEBUG:
194                                        print "- - [%s] Deleting disconnected socket" % self.socket.name
195                               
196                                connection.deleteLater()
197                                # Delete references to disconnected sockets
198                                del (self.connections[connection_index])
199                       
200                       
201                        connection_index += 1
202       
203       
204        ##################################################################
205       
206        def processConnection(self):
207               
208                clientConnection = self.socket.nextPendingConnection()
209                clientConnection.disconnected.connect(self.deleteDisconnected)
210               
211                self.connections.append(clientConnection)
212               
213                self.clientConnection = clientConnection
214               
215                # the next connected client to enter the readyRead state
216                # will be processed first
217                clientConnection.readyRead.connect(self.processClientConnection)
218       
219       
220        ##################################################################
221       
222        def processClientConnection(self):
223               
224                clientConnection = self.clientConnection
225               
226                socket_buffer = clientConnection.readAll()
227               
228                for packet in socket_buffer.split(DELIMITER):
229                       
230                        data_to_process = None
231                       
232                        if packet != '':
233                               
234                                try:
235                                        data_to_process = json.loads(packet.data())
236                               
237                                except Exception, e:
238                                       
239                                        # Special socket handling for Flash applications
240                                        if (packet == FLASH_POLICY_FILE_REQUEST):
241                                               
242                                                if self.DEBUG:
243                                                        print "--> [%s] Flash policy file requested" % self.socket.name
244                                               
245                                                data_to_process = packet.data()
246                                       
247                                       
248                                        else:
249                                               
250                                                if self.DEBUG:
251                                                        print "--> [ThinkGear Emulator] Partial data received (or error:",
252                                                        print e
253                                                        print ")."
254                                                       
255                                                        print "packet data:",
256                                                        print packet.data()
257                               
258                               
259                                else:
260                                       
261                                        if self.DEBUG:
262                                                print "--> [%s] Received:" % self.socket.name,
263                                                print data_to_process
264                               
265                               
266                                if (data_to_process != None):
267                                       
268                                        response = self.processData(data_to_process)
269                                       
270                                        if (response != None):
271                                               
272                                                self.sendResponse(clientConnection, response)
273       
274       
275        ##################################################################
276       
277        def sendResponse(self, connection, response, disconnect_after_sending=False):
278
279                # Special socket handling for Flash applications
280                if (response == FLASH_SOCKET_POLICY_FILE):
281                        data = response
282                else:
283                        data = json.dumps(response)
284                        data = data + DELIMITER
285               
286                if connection.waitForConnected(CLIENT_NO_REPLY_WAIT):
287                       
288                        if self.DEBUG:
289                                print "<-- [%s] Sending:" % self.socket.name,
290                                print data
291                       
292                        connection.write(data)
293                       
294                        connection.waitForBytesWritten(CLIENT_NO_REPLY_WAIT)
295                       
296                        if disconnect_after_sending:
297                                connection.disconnectFromHost()
298       
299       
300        ##################################################################
301       
302        def sendPacketQueue(self):
303               
304                while (len(self.packet_queue) > 0):
305                       
306                        packet = self.packet_queue[0]
307                        del self.packet_queue[0]
308                       
309                        for connection in self.connections:
310                               
311                                if connection.state() == QtNetwork.QAbstractSocket.ConnectedState:
312                                       
313                                        self.sendResponse(connection, packet)
314       
315       
316        ##################################################################
317       
318        def processData(self, data):
319               
320                response = None
321               
322                # Special socket handling for Flash applications
323                if (data == FLASH_POLICY_FILE_REQUEST):
324                       
325                        response = FLASH_SOCKET_POLICY_FILE
326                       
327                        #self.packet_queue.insert(0, FLASH_SOCKET_POLICY_FILE)
328               
329               
330                elif (type(data) == type({}) and \
331                      data.has_key('appName') and \
332                      data.has_key('appKey')):
333                        authorized = self.authorizeClient(data)
334                       
335                        response = {}
336                        response['isAuthorized'] = authorized
337                       
338                        #self.packet_queue.insert(0, response)
339               
340               
341                return(response)
342       
343       
344        ##################################################################
345       
346        def validateChecksum(self, checksum):
347               
348                '''The key used by the client application to identify
349itself. This must be 40 hexadecimal characters, ideally generated
350using an SHA-1 digest. The appKey is an identifier that is unique
351to each application, rather than each instance of an application.
352It is used by the server to bypass the authorization process if a
353user had previously authorized the requesting client. To reduce
354the chance of overlap with the appKey of other applications,
355the appKey should be generated using an SHA-1 digest.'''
356               
357                is_valid = True
358               
359                hexadecimal_characters = '0123456789abcdef'
360               
361                if len(checksum) != 40:
362                        is_valid = False
363                else:
364                        for character in checksum:
365                                if character not in hexadecimal_characters:
366                                        is_valid = False
367               
368                return(is_valid)
369       
370       
371        ##################################################################
372       
373        def authorizeClient(self, data):
374       
375                '''The client must initiate an authorization request
376and the server must authorize the client before the
377server will start transmitting any headset data.'''
378               
379                is_authorized = self.validateChecksum(data['appKey'])
380               
381                # A human-readable name identifying the client
382                # application. This can be a maximum of 255 characters.
383               
384                if len(data['appName']) > 255:
385                        is_authorized = False
386               
387               
388                return(is_authorized)
389       
390       
391        ##################################################################
392       
393        def calculateWavePoint(self, x, max_height=100, wave_length=10):
394               
395                # start at 0, increase to max value at half of one
396                # wavelength, decrease to 0 by end of wavelength
397                y = ( (max_height/2) * \
398                      math.sin ((x-1) * ( math.pi / (wave_length / 2)))) + \
399                      (max_height/2)
400               
401                # start at max value, decrease to 0 at half of one
402                # wavelegnth, increase to max by end of wavelength
403                #y = ( (max_height/2) * \
404                      #math.cos (x * ( math.pi / (wave_length / 2)))) + \
405                      #(max_height/2)
406               
407               
408                return(y)
409       
410       
411        ##################################################################
412       
413        def simulateHeadsetData(self):
414               
415                response = DEFAULT_PACKET
416               
417                time_value = self.connection_timestamp - time.time()
418               
419                for key in response.keys():
420                       
421                        if key == 'poorSignalLevel':
422                                pass
423                       
424                        elif key == 'eSense':
425                                plot = self.calculateWavePoint( \
426                                        time_value, \
427                                        max_height=100, \
428                                        wave_length=DEFAULT_SAMPLE_WAVELENGTH)
429                               
430                                for each in response[key].keys():
431                                       
432                                        if ((each == 'attention') and \
433                                                 (THINKGEAR_ATTENTION_MULTIPLIER != None)):
434                                                value = plot * \
435                                                   THINKGEAR_ATTENTION_MULTIPLIER
436                                       
437                                        elif ((each == 'meditation') and \
438                                                   (THINKGEAR_MEDITATION_MULTIPLIER != None)):
439                                                value = plot * \
440                                                   THINKGEAR_MEDITATION_MULTIPLIER
441                                       
442                                        if value < 0:
443                                                value = 0
444                                        elif value > 100:
445                                                value = 100
446                                       
447                                        response[key][each] = value
448                       
449                       
450                        elif key == 'eegPower':
451                                plot = self.calculateWavePoint( \
452                                        time_value, \
453                                        max_height=65535, \
454                                        wave_length=DEFAULT_SAMPLE_WAVELENGTH)
455                               
456                                for each in response[key].keys():
457                                        response[key][each] = plot
458               
459               
460                return(response)
461       
462       
463        ##################################################################
464       
465        def updateStatus(self):
466               
467                if ENABLE_SIMULATE_HEADSET_DATA:
468                       
469                        # Craft a simulated data packet
470                        self.packet_queue.append( \
471                                self.simulateHeadsetData() )
472                       
473                        # Include simulated blinks at desired frequency
474                        if ((self.blink_frequency_timer != None) and \
475                            (self.blink_frequency_timer > 0) and \
476                            (time.time() - self.blink_timestamp > \
477                             self.blink_frequency_timer)):
478                               
479                                self.blink_timestamp = time.time()
480                               
481                                packet = DEFAULT_BLINK_MESSAGE
482                                self.packet_queue.append(packet)
483
484
485#####################################################################
486# Main
487#####################################################################
488
489if __name__ == '__main__':
490       
491        # Perform correct KeyboardInterrupt handling
492        signal.signal(signal.SIGINT, signal.SIG_DFL)
493       
494        #log = puzzlebox_logger.puzzlebox_logger(logfile='server_thinkgear')
495        log = None
496       
497        # Collect default settings and command line parameters
498        server_interface = SERVER_INTERFACE
499        server_port = SERVER_PORT
500       
501        for each in sys.argv:
502               
503                if each.startswith("--interface="):
504                        server_interface = each[ len("--interface="): ]
505                if each.startswith("--port="):
506                        server_port = each[ len("--port="): ]
507       
508       
509        app = QtCore.QCoreApplication(sys.argv)
510       
511        server = puzzlebox_brainstorms_network_server_thinkgear(log, \
512                                                                server_interface, \
513                                                                server_port, \
514                                                                DEBUG=DEBUG)
515       
516        sys.exit(app.exec_())
517
Note: See TracBrowser for help on using the repository browser.