source: remote_control/puzzlebox_brainstorms_network_server_thinkgear.py @ 78

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

thinkgear_emulator/puzzlebox_thinkgear_client.py:

  • whitespace cleanup

thinkgear_emulator/puzzlebox_thinkgear_server.py:

  • whitespace cleanup
  • added minor exception handling

remote_control/puzzlebox_brainstorms_client_interface.py

  • connect/disconnect from ThinkGear? socket server support added

remote_control/interface/qt4_form.py:

  • minor tweaks to layout

configuration:

  • THINKGEAR_SERVER_INTERFACE variable added

remote_control/puzzlebox_brainstorms_network_server_thinkgear.py

  • initial checkin
  • conversion from Python Twisted to QtNetwork? model

remote_control/puzzlebox_brainstorms_network_server.py:

  • QtCore?.QCoreApplication support added
  • Property svn:executable set to *
File size: 12.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.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.DEBUG:
149                        print "timerEvent called"
150               
151                if (self.connections != []):
152                       
153                        self.updateStatus()
154                        self.sendPacketQueue()
155       
156       
157        ##################################################################
158       
159        def configureNetwork(self):
160       
161                #self.blockSize = 0
162                self.socket = QtNetwork.QTcpServer()
163                self.socket.name = 'ThinkGear Server'
164               
165                if self.DEBUG:
166                        print "----> [%s] Initializing server on %s:%i" % \
167                           (self.socket.name, self.server_interface, self.server_port)
168                       
169                if (self.server_interface == ''):
170                        result = self.socket.listen(port=self.server_port)
171                else:
172                        result = self.socket.listen(address=self.server_interface, \
173                                           port=self.server_port)
174               
175                if not result:
176                        if self.DEBUG:
177                                print "ERROR [%s] Unable to start the server:", self.socket.name,
178                                print self.socket.errorString()
179                               
180                        self.socket.close()
181                        return
182               
183               
184                self.socket.newConnection.connect(self.processConnection)
185                #self.socket.error.connect(self.displayError)
186       
187       
188        ##################################################################
189       
190        def processConnection(self):
191               
192                clientConnection = self.socket.nextPendingConnection()
193                clientConnection.disconnected.connect(clientConnection.deleteLater)
194               
195                # Wait until client sends authorization request or configuration packet
196                while not clientConnection.waitForReadyRead(CLIENT_NO_REPLY_WAIT):
197                        pass
198               
199                socket_buffer = clientConnection.readAll()
200               
201                for packet in socket_buffer.split(DELIMITER):
202                       
203                        data_to_process = None
204                       
205                        if packet != '':
206                               
207                                try:
208                                        data_to_process = json.loads(packet.data())
209                               
210                                except Exception, e:
211                                       
212                                        # Special socket handling for Flash applications
213                                        if (packet == FLASH_POLICY_FILE_REQUEST):
214                                               
215                                                if self.DEBUG:
216                                                        print "<-- [ThinkGear Emulator] Flash policy file requested"
217                                               
218                                                data_to_process = packet.data()
219                                       
220                                       
221                                        else:
222                                               
223                                                if self.DEBUG:
224                                                        print "<-- [ThinkGear Emulator] Partial data received (or error:",
225                                                        print e
226                                                        print ")."
227                                                       
228                                                        print "packet data:",
229                                                        print packet.data()
230                               
231                               
232                                else:
233                                       
234                                        if self.DEBUG:
235                                                print "<-- [%s] Received:" % self.socket.name,
236                                                print data_to_process
237                               
238                               
239                                if (data_to_process != None):
240                                       
241                                        response = self.processData(data_to_process)
242                                       
243                                        if (response != None):
244                                               
245                                                self.sendResponse(clientConnection, response)
246               
247               
248                self.connections.append[clientConnection]
249       
250       
251        ##################################################################
252       
253        def sendResponse(self, connection, response, disconnect_after_sending=False):
254               
255                data = json.dumps(response)
256               
257                if connection.waitForConnected(CLIENT_NO_REPLY_WAIT):
258                       
259                        connection.write(data)
260                       
261                        connection.waitForBytesWritten(CLIENT_NO_REPLY_WAIT)
262                       
263                        if disconnect_after_sending:
264                                connection.disconnectFromHost()
265       
266       
267        ##################################################################
268       
269        def sendPacketQueue(self):
270               
271                while (len(self.packet_queue) > 0):
272                       
273                        packet = self.packet_queue[0]
274                        del self.packet_queue[0]
275                       
276                        for connection in self.connections:
277                               
278                                self.sendResponse(connection, packet)
279       
280       
281        ##################################################################
282       
283        def processData(self, data):
284               
285                response = None
286               
287                # Special socket handling for Flash applications
288                if (data == FLASH_POLICY_FILE_REQUEST):
289                       
290                        response = FLASH_SOCKET_POLICY_FILE
291                       
292                        self.packet_queue.insert(0, FLASH_SOCKET_POLICY_FILE)
293               
294               
295                elif (type(data) == type({}) and \
296                      data.has_key('appName') and \
297                      data.has_key('appKey')):
298                        authorized = self.authorizeClient(data)
299                       
300                        response = {}
301                        response['isAuthorized'] = authorized
302                       
303                        self.packet_queue.insert(0, response)
304               
305               
306                return(response)
307       
308       
309        ##################################################################
310       
311        def validateChecksum(self, checksum):
312               
313                '''The key used by the client application to identify
314itself. This must be 40 hexadecimal characters, ideally generated
315using an SHA-1 digest. The appKey is an identifier that is unique
316to each application, rather than each instance of an application.
317It is used by the server to bypass the authorization process if a
318user had previously authorized the requesting client. To reduce
319the chance of overlap with the appKey of other applications,
320the appKey should be generated using an SHA-1 digest.'''
321               
322                is_valid = True
323               
324                hexadecimal_characters = '0123456789abcdef'
325               
326                if len(checksum) != 40:
327                        is_valid = False
328                else:
329                        for character in checksum:
330                                if character not in hexadecimal_characters:
331                                        is_valid = False
332               
333                return(is_valid)
334       
335       
336        ##################################################################
337       
338        def authorizeClient(self, data):
339       
340                '''The client must initiate an authorization request
341and the server must authorize the client before the
342server will start transmitting any headset data.'''
343               
344                is_authorized = self.validateChecksum(data['appKey'])
345               
346                # A human-readable name identifying the client
347                # application. This can be a maximum of 255 characters.
348               
349                if len(data['appName']) > 255:
350                        is_authorized = False
351               
352               
353                return(is_authorized)
354       
355       
356        ##################################################################
357       
358        def calculateWavePoint(self, x, max_height=100, wave_length=10):
359               
360                # start at 0, increase to max value at half of one
361                # wavelength, decrease to 0 by end of wavelength
362                y = ( (max_height/2) * \
363                      math.sin ((x-1) * ( math.pi / (wave_length / 2)))) + \
364                      (max_height/2)
365               
366                # start at max value, decrease to 0 at half of one
367                # wavelegnth, increase to max by end of wavelength
368                #y = ( (max_height/2) * \
369                      #math.cos (x * ( math.pi / (wave_length / 2)))) + \
370                      #(max_height/2)
371               
372               
373                return(y)
374       
375       
376        ##################################################################
377       
378        def simulateHeadsetData(self):
379               
380                response = DEFAULT_PACKET
381               
382                time_value = self.connection_timestamp - time.time()
383               
384                for key in response.keys():
385                       
386                        if key == 'poorSignalLevel':
387                                pass
388                       
389                        elif key == 'eSense':
390                                plot = self.calculateWavePoint( \
391                                        time_value, \
392                                        max_height=100, \
393                                        wave_length=DEFAULT_SAMPLE_WAVELENGTH)
394                               
395                                for each in response[key].keys():
396                                        response[key][each] = plot
397                       
398                        elif key == 'eegPower':
399                                plot = self.calculateWavePoint( \
400                                        time_value, \
401                                        max_height=65535, \
402                                        wave_length=DEFAULT_SAMPLE_WAVELENGTH)
403                               
404                                for each in response[key].keys():
405                                        response[key][each] = plot
406               
407               
408                return(response)
409       
410       
411        ##################################################################
412       
413        def updateStatus(self):
414               
415                if ENABLE_SIMULATE_HEADSET_DATA:
416                       
417                        # Craft a simulated data packet
418                        self.packet_queue.append( \
419                                self.simulateHeadsetData() )
420                       
421                        # Include simulated blinks at desired frequency
422                        if ((self.blink_frequency_timer != None) and \
423                            (time.time() - self.blink_timestamp > \
424                             self.blink_frequency_timer)):
425                               
426                                self.blink_timestamp = time.time()
427                               
428                                packet = DEFAULT_BLINK_MESSAGE
429                                self.packet_queue.append(packet)
430       
431       
432        ##################################################################
433       
434        #def start_updating(self):
435       
436                #self.client_connected = True
437                #self.connection_timestamp = time.time()
438                #self.looping_timer = task.LoopingCall(self.update_status)
439                #self.looping_timer.start(self.message_frequency_timer)
440       
441       
442#####################################################################
443# Main
444#####################################################################
445
446if __name__ == '__main__':
447       
448        # Perform correct KeyboardInterrupt handling
449        signal.signal(signal.SIGINT, signal.SIG_DFL)
450       
451        #log = puzzlebox_logger.puzzlebox_logger(logfile='thinkgear_server')
452        log = None
453       
454        # Collect default settings and command line parameters
455        server_interface = SERVER_INTERFACE
456        server_port = SERVER_PORT
457       
458        for each in sys.argv:
459               
460                if each.startswith("--interface="):
461                        server_interface = each[ len("--interface="): ]
462                if each.startswith("--port="):
463                        server_port = each[ len("--port="): ]
464       
465       
466        app = QtCore.QCoreApplication(sys.argv)
467       
468        server = puzzlebox_brainstorms_network_server_thinkgear(log, \
469                                                                server_interface, \
470                                                                server_port, \
471                                                                DEBUG=DEBUG)
472       
473        #while server.socket.waitForNewConnection(-1):
474                #pass
475       
476       
477        sys.exit(app.exec_())
478
Note: See TracBrowser for help on using the repository browser.