source: thinkgear_emulator/puzzlebox_thinkgear_server.py @ 115

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

thinkgear_emulator/puzzlebox_thinkgear_emulator_configuration.py:

  • FLASH_SOCKET_POLICY_FILE updated

thinkgear_emulator/puzzlebox_thinkgear_server.py:

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