source: remote_control/puzzlebox_brainstorms_network_server_thinkgear.py @ 80

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

network_server_thinkgear:

  • deletion of disconnected sockets fixed
  • Property svn:executable set to *
File size: 14.4 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.06.30
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_brainstorms_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 = True
45
46BLINK_FREQUENCY_TIMER = 6 # blink every 6 seconds
47                          # (6 seconds is listed by Wikipedia
48                          # as being the average number of times
49                          # an adult blinks in a laboratory setting)
50
51DEFAULT_SAMPLE_WAVELENGTH = 10 # number of seconds from 0 to max to 0 for
52                               # any given detection value below
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.client_connected = False
129                self.connection_timestamp = time.time()
130                self.blink_timestamp = time.time()
131               
132                self.connections = []
133                self.packet_queue = []
134               
135                self.configureNetwork()
136               
137                self.timer = QtCore.QTimer()
138                QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.timerEvent)
139                self.timer.start(MESSAGE_FREQUENCY_TIMER)
140               
141                #self.timer = QtCore.QTimer.singleShot(MESSAGE_FREQUENCY_TIMER, self.timerEvent)
142       
143       
144        ##################################################################
145       
146        def timerEvent(self):
147               
148                if (self.connections != []):
149                       
150                        self.updateStatus()
151                        self.sendPacketQueue()
152       
153       
154        ##################################################################
155       
156        def configureNetwork(self):
157       
158                #self.blockSize = 0
159                self.socket = QtNetwork.QTcpServer()
160                self.socket.name = 'ThinkGear Server'
161               
162                if self.DEBUG:
163                        print "----> [%s] Initializing server on %s:%i" % \
164                           (self.socket.name, self.server_interface, self.server_port)
165                       
166                if (self.server_interface == ''):
167                        result = self.socket.listen(port=self.server_port)
168                else:
169                        result = self.socket.listen(address=self.server_interface, \
170                                           port=self.server_port)
171               
172                if not result:
173                        if self.DEBUG:
174                                print "ERROR [%s] Unable to start the server:", self.socket.name,
175                                print self.socket.errorString()
176                               
177                        self.socket.close()
178                        return
179               
180               
181                self.socket.newConnection.connect(self.processConnection)
182                #self.socket.error.connect(self.displayError)
183       
184       
185        ##################################################################
186       
187        #def getNextConnectionIndex(self):
188               
189                #index = len(self.connections.keys())
190               
191                #while index in self.connection.keys():
192                        #index += 1
193               
194               
195                #return(index)
196       
197       
198        ##################################################################
199       
200        def deleteDisconnected(self):
201               
202                connection_index = 0
203               
204                for connection in self.connections:
205                       
206                        if self.DEBUG:
207                                print "Connection state:",
208                                print connection.state()
209                       
210                        if ((connection.state() != QtNetwork.QAbstractSocket.ConnectingState) and \
211                                 (connection.state() != QtNetwork.QAbstractSocket.ConnectedState)):
212                               
213                                if self.DEBUG:
214                                        print "x-- [%s] Deleting disconnected socket:" % self.socket.name,
215                               
216                                connection.deleteLater()
217                                # Delete references to disconnected sockets
218                                del (self.connections[connection_index])
219                       
220                       
221                        connection_index += 1
222       
223       
224        ##################################################################
225       
226        def processConnection(self):
227               
228                clientConnection = self.socket.nextPendingConnection()
229                #index = self.getNextConnectionIndex()
230                #clientConnection.index = index
231                #clientConnection.disconnected.connect(clientConnection.deleteLater)
232                clientConnection.disconnected.connect(self.deleteDisconnected)
233                #self.connections[index] = clientConnection
234               
235                # Wait until client sends authorization request or configuration packet
236                while not clientConnection.waitForReadyRead(CLIENT_NO_REPLY_WAIT):
237                        pass
238               
239                socket_buffer = clientConnection.readAll()
240               
241                for packet in socket_buffer.split(DELIMITER):
242                       
243                        data_to_process = None
244                       
245                        if packet != '':
246                               
247                                try:
248                                        data_to_process = json.loads(packet.data())
249                               
250                                except Exception, e:
251                                       
252                                        # Special socket handling for Flash applications
253                                        if (packet == FLASH_POLICY_FILE_REQUEST):
254                                               
255                                                if self.DEBUG:
256                                                        print "<-- [%s] Flash policy file requested" % self.socket.name
257                                               
258                                                data_to_process = packet.data()
259                                       
260                                       
261                                        else:
262                                               
263                                                if self.DEBUG:
264                                                        print "<-- [ThinkGear Emulator] Partial data received (or error:",
265                                                        print e
266                                                        print ")."
267                                                       
268                                                        print "packet data:",
269                                                        print packet.data()
270                               
271                               
272                                else:
273                                       
274                                        if self.DEBUG:
275                                                print "<-- [%s] Received:" % self.socket.name,
276                                                print data_to_process
277                               
278                               
279                                if (data_to_process != None):
280                                       
281                                        response = self.processData(data_to_process)
282                                       
283                                        if (response != None):
284                                               
285                                                self.sendResponse(clientConnection, response)
286               
287               
288                self.connections.append(clientConnection)
289       
290       
291        ##################################################################
292       
293        def sendResponse(self, connection, response, disconnect_after_sending=False):
294               
295                data = json.dumps(response)
296               
297                if connection.waitForConnected(CLIENT_NO_REPLY_WAIT):
298                       
299                        if self.DEBUG:
300                                print "<-- [%s] Sending:" % self.socket.name,
301                                print data
302                       
303                        connection.write(data)
304                       
305                        connection.waitForBytesWritten(CLIENT_NO_REPLY_WAIT)
306                       
307                        if disconnect_after_sending:
308                                connection.disconnectFromHost()
309       
310       
311        ##################################################################
312       
313        def sendPacketQueue(self):
314               
315                while (len(self.packet_queue) > 0):
316                       
317                        packet = self.packet_queue[0]
318                        del self.packet_queue[0]
319                       
320                        connection_index = 0
321                       
322                        for connection in self.connections:
323                               
324                                if connection.state() == QtNetwork.QAbstractSocket.ConnectedState:
325                                       
326                                        self.sendResponse(connection, packet)
327                               
328                               
329                                connection_index += 1
330       
331       
332        ##################################################################
333       
334        def processData(self, data):
335               
336                response = None
337               
338                # Special socket handling for Flash applications
339                if (data == FLASH_POLICY_FILE_REQUEST):
340                       
341                        response = FLASH_SOCKET_POLICY_FILE
342                       
343                        self.packet_queue.insert(0, FLASH_SOCKET_POLICY_FILE)
344               
345               
346                elif (type(data) == type({}) and \
347                      data.has_key('appName') and \
348                      data.has_key('appKey')):
349                        authorized = self.authorizeClient(data)
350                       
351                        response = {}
352                        response['isAuthorized'] = authorized
353                       
354                        self.packet_queue.insert(0, response)
355               
356               
357                return(response)
358       
359       
360        ##################################################################
361       
362        def validateChecksum(self, checksum):
363               
364                '''The key used by the client application to identify
365itself. This must be 40 hexadecimal characters, ideally generated
366using an SHA-1 digest. The appKey is an identifier that is unique
367to each application, rather than each instance of an application.
368It is used by the server to bypass the authorization process if a
369user had previously authorized the requesting client. To reduce
370the chance of overlap with the appKey of other applications,
371the appKey should be generated using an SHA-1 digest.'''
372               
373                is_valid = True
374               
375                hexadecimal_characters = '0123456789abcdef'
376               
377                if len(checksum) != 40:
378                        is_valid = False
379                else:
380                        for character in checksum:
381                                if character not in hexadecimal_characters:
382                                        is_valid = False
383               
384                return(is_valid)
385       
386       
387        ##################################################################
388       
389        def authorizeClient(self, data):
390       
391                '''The client must initiate an authorization request
392and the server must authorize the client before the
393server will start transmitting any headset data.'''
394               
395                is_authorized = self.validateChecksum(data['appKey'])
396               
397                # A human-readable name identifying the client
398                # application. This can be a maximum of 255 characters.
399               
400                if len(data['appName']) > 255:
401                        is_authorized = False
402               
403               
404                return(is_authorized)
405       
406       
407        ##################################################################
408       
409        def calculateWavePoint(self, x, max_height=100, wave_length=10):
410               
411                # start at 0, increase to max value at half of one
412                # wavelength, decrease to 0 by end of wavelength
413                y = ( (max_height/2) * \
414                      math.sin ((x-1) * ( math.pi / (wave_length / 2)))) + \
415                      (max_height/2)
416               
417                # start at max value, decrease to 0 at half of one
418                # wavelegnth, increase to max by end of wavelength
419                #y = ( (max_height/2) * \
420                      #math.cos (x * ( math.pi / (wave_length / 2)))) + \
421                      #(max_height/2)
422               
423               
424                return(y)
425       
426       
427        ##################################################################
428       
429        def simulateHeadsetData(self):
430               
431                response = DEFAULT_PACKET
432               
433                time_value = self.connection_timestamp - time.time()
434               
435                for key in response.keys():
436                       
437                        if key == 'poorSignalLevel':
438                                pass
439                       
440                        elif key == 'eSense':
441                                plot = self.calculateWavePoint( \
442                                        time_value, \
443                                        max_height=100, \
444                                        wave_length=DEFAULT_SAMPLE_WAVELENGTH)
445                               
446                                for each in response[key].keys():
447                                        response[key][each] = plot
448                       
449                        elif key == 'eegPower':
450                                plot = self.calculateWavePoint( \
451                                        time_value, \
452                                        max_height=65535, \
453                                        wave_length=DEFAULT_SAMPLE_WAVELENGTH)
454                               
455                                for each in response[key].keys():
456                                        response[key][each] = plot
457               
458               
459                return(response)
460       
461       
462        ##################################################################
463       
464        def updateStatus(self):
465               
466                if ENABLE_SIMULATE_HEADSET_DATA:
467                       
468                        # Craft a simulated data packet
469                        self.packet_queue.append( \
470                                self.simulateHeadsetData() )
471                       
472                        # Include simulated blinks at desired frequency
473                        if ((self.blink_frequency_timer != None) and \
474                            (time.time() - self.blink_timestamp > \
475                             self.blink_frequency_timer)):
476                               
477                                self.blink_timestamp = time.time()
478                               
479                                packet = DEFAULT_BLINK_MESSAGE
480                                self.packet_queue.append(packet)
481       
482       
483#####################################################################
484#####################################################################
485
486#class networkServerThinkGearThread(QtCore.QThread):
487       
488        #error = QtCore.pyqtSignal(QtNetwork.QTcpSocket.SocketError)
489
490        #def __init__(self, socketDescriptor, parent):
491                ##super(networkServerThinkGearThread, self).__init__(parent)
492               
493                #self.socketDescriptor = socketDescriptor
494       
495       
496        ###################################################################
497       
498        #def run(self):
499               
500                #tcpSocket = QtNetwork.QTcpSocket()
501               
502                #if not tcpSocket.setSocketDescriptor(self.socketDescriptor):
503                        #self.error.emit(tcpSocket.error())
504                       
505                        #return
506
507                ##block = QtCore.QByteArray()
508                ##outstr = QtCore.QDataStream(block, QtCore.QIODevice.WriteOnly)
509                ##outstr.setVersion(QtCore.QDataStream.Qt_4_0)
510                ##outstr.writeUInt16(0)
511                ##outstr.writeString(self.text)
512                ##outstr.device().seek(0)
513                ##outstr.writeUInt16(block.count() - 2)
514
515                ##tcpSocket.write(block)
516                ##tcpSocket.disconnectFromHost()
517                ##tcpSocket.waitForDisconnected()
518
519
520#####################################################################
521# Main
522#####################################################################
523
524if __name__ == '__main__':
525       
526        # Perform correct KeyboardInterrupt handling
527        signal.signal(signal.SIGINT, signal.SIG_DFL)
528       
529        #log = puzzlebox_logger.puzzlebox_logger(logfile='server_thinkgear')
530        log = None
531       
532        # Collect default settings and command line parameters
533        server_interface = SERVER_INTERFACE
534        server_port = SERVER_PORT
535       
536        for each in sys.argv:
537               
538                if each.startswith("--interface="):
539                        server_interface = each[ len("--interface="): ]
540                if each.startswith("--port="):
541                        server_port = each[ len("--port="): ]
542       
543       
544        app = QtCore.QCoreApplication(sys.argv)
545       
546        server = puzzlebox_brainstorms_network_server_thinkgear(log, \
547                                                                server_interface, \
548                                                                server_port, \
549                                                                DEBUG=DEBUG)
550       
551        sys.exit(app.exec_())
552
Note: See TracBrowser for help on using the repository browser.