source: remote_control/puzzlebox_brainstorms_server_thinkgear.py @ 61

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

client_thinkgear:

  • authorization not sent by default

server_thinkgear:

  • authorization and flash socket policy responses receive priority in the packet queue
File size: 11.8 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Puzzlebox - Brainstorms - Server - ThinkGear Emulator
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.22
12#
13#####################################################################
14
15import math, os, signal, sys, time
16import simplejson as json
17
18from twisted.internet import reactor, protocol, defer, task
19from twisted.protocols import basic
20
21import puzzlebox_brainstorms_configuration as configuration
22#import puzzlebox_logger
23
24#####################################################################
25# Globals
26#####################################################################
27
28DEBUG = 1
29
30SERVER_INTERFACE = configuration.SERVER_INTERFACE
31SERVER_PORT = configuration.THINKGEAR_SERVER_PORT
32
33FLASH_POLICY_FILE_REQUEST = configuration.FLASH_POLICY_FILE_REQUEST
34FLASH_SOCKET_POLICY_FILE = configuration.FLASH_SOCKET_POLICY_FILE
35
36THINKGEAR_DELIMITER = '\r'
37
38MESSAGE_FREQUENCY_TIMER = 1 # 1 Hz
39
40ENABLE_SIMULATE_HEADSET_DATA = True
41
42BLINK_FREQUENCY_TIMER = 6 # blink every 6 seconds
43                          # (6 seconds is listed by Wikipedia
44                          # as being the average number of times
45                          # an adult blinks in a laboratory setting)
46
47DEFAULT_SAMPLE_WAVELENGTH = 10 # number of seconds from 0 to max to 0 for
48                               # any given detection value below
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_server_thinkgear(protocol.ServerFactory):
108       
109        def __init__(self, log, DEBUG=DEBUG):
110               
111                self.log = log
112                self.DEBUG = DEBUG
113               
114                self.protocol = puzzlebox_brainstorms_server_protocol
115
116                self.message_frequency_timer = MESSAGE_FREQUENCY_TIMER
117                self.blink_frequency_timer = BLINK_FREQUENCY_TIMER
118
119                self.client_connected = False
120                self.connection_timestamp = time.time()
121                self.blink_timestamp = time.time()
122
123                self.packet_queue = []
124
125
126        ##################################################################
127
128        def validate_checksum(self, checksum):
129
130                '''The key used by the client application to identify
131itself. This must be 40 hexadecimal characters, ideally generated
132using an SHA-1 digest. The appKey is an identifier that is unique
133to each application, rather than each instance of an application.
134It is used by the server to bypass the authorization process if a
135user had previously authorized the requesting client. To reduce
136the chance of overlap with the appKey of other applications,
137the appKey should be generated using an SHA-1 digest.'''
138
139                is_valid = True
140
141                hexadecimal_characters = '0123456789abcdef'
142
143                if len(checksum) != 40:
144                        is_valid = False
145                else:
146                        for character in checksum:
147                                if character not in hexadecimal_characters:
148                                        is_valid = False
149
150                return(is_valid)
151       
152       
153        ##################################################################
154
155        def authorize_client(self, data):
156
157                '''The client must initiate an authorization request
158and the server must authorize the client before the
159server will start transmitting any headset data.'''
160
161                is_authorized = self.validate_checksum(data['appKey'])
162
163                # A human-readable name identifying the client
164                # application. This can be a maximum of 255 characters.
165               
166                if len(data['appName']) > 255:
167                        is_authorized = False
168
169
170                return(is_authorized)
171                       
172
173        ##################################################################
174
175        def calculate_wave_point(self, x, max_height=100, wave_length=10):
176
177                # start at 0, increase to max value at half of one
178                # wavelength, decrease to 0 by end of wavelength
179                y = ( (max_height/2) * \
180                      math.sin ((x-1) * ( math.pi / (wave_length / 2)))) + \
181                      (max_height/2)
182
183                # start at max value, decrease to 0 at half of one
184                # wavelegnth, increase to max by end of wavelength
185##              y = ( (max_height/2) * \
186##                    math.cos (x * ( math.pi / (wave_length / 2)))) + \
187##                    (max_height/2)
188
189
190                return(y)
191
192
193        ##################################################################
194
195        def simulate_headset_data(self):
196
197                response = DEFAULT_PACKET
198
199                time_value = self.connection_timestamp - time.time()
200
201                for key in response.keys():
202
203                        if key == 'poorSignalLevel':
204                                pass
205
206                        elif key == 'eSense':
207                                plot = self.calculate_wave_point( \
208                                        time_value, \
209                                        max_height=100, \
210                                        wave_length=DEFAULT_SAMPLE_WAVELENGTH)
211
212                                for each in response[key].keys():
213                                        response[key][each] = plot
214
215                        elif key == 'eegPower':
216                                plot = self.calculate_wave_point( \
217                                        time_value, \
218                                        max_height=65535, \
219                                        wave_length=DEFAULT_SAMPLE_WAVELENGTH)
220
221                                for each in response[key].keys():
222                                        response[key][each] = plot
223
224
225                return(response)
226
227
228        ##################################################################
229
230        def process_data(self, data):
231
232                d = defer.Deferred()
233
234                # Special socket handling for Flash applications
235                if (data == FLASH_POLICY_FILE_REQUEST):
236                        self.packet_queue.insert(0, FLASH_SOCKET_POLICY_FILE)
237                       
238                elif (type(data) == type({}) and \
239                      data.has_key('appName') and \
240                      data.has_key('appKey')):
241                        authorized = self.authorize_client(data)
242
243                        response = {}
244                        response['isAuthorized'] = authorized
245
246                        self.packet_queue.insert(0, response)
247
248                else:
249                        # No specific information needs to be returned
250                        pass
251
252               
253                if self.DEBUG:
254                        print "<-- [ThinkGear Emulator] Received:",
255                        print data
256
257               
258                d.callback(self.message_frequency_timer)
259
260               
261                return d
262       
263       
264        ##################################################################
265       
266        def process_connection_lost(self):
267
268                if self.DEBUG:
269                        print "--> [ThinkGear Emulator] Connection lost"
270
271                self.client_connected = False
272
273                self.looping_timer.stop()
274
275
276        ##################################################################
277
278##        def get_next_packet(self)
279##
280##                for packet in self.packet_queue:
281##
282##                        try:
283##                               
284##                                if packet.has_key('isAuthorized'):
285
286        ##################################################################
287
288        def update_status(self):
289
290                if ENABLE_SIMULATE_HEADSET_DATA:
291
292                        # Craft a simulated data packet
293                        self.packet_queue.append( \
294                                self.simulate_headset_data() )
295
296                        # Include simulated blinks at desired frequency
297                        if ((self.blink_frequency_timer != None) and \
298                            (time.time() - self.blink_timestamp > \
299                             self.blink_frequency_timer)):
300
301                                self.blink_timestamp = time.time()
302
303                                packet = DEFAULT_BLINK_MESSAGE
304                                self.packet_queue.append(packet)
305
306               
307        ##################################################################
308
309        def start_updating(self):
310
311                self.client_connected = True
312                self.connection_timestamp = time.time()
313                self.looping_timer = task.LoopingCall(self.update_status)
314                self.looping_timer.start(self.message_frequency_timer)
315
316
317#####################################################################
318# Protocol
319#####################################################################
320
321class puzzlebox_brainstorms_server_protocol(basic.LineReceiver):
322
323        delimiter='\r'
324       
325        def __init__(self):
326               
327                self.DEBUG = DEBUG
328                self.data_chunk = ""
329
330       
331        ##################################################################
332       
333        def connectionMade(self):
334               
335                if self.DEBUG:
336                        print "<---- [ThinkGear Emulator] Client connected"
337                       
338                self.factory.start_updating()
339
340
341        ##################################################################
342       
343        def noReply(self):
344               
345                try:
346                        self.factory.replyDefer.callback('NO_REPLY')
347                except:
348                        if self.DEBUG:
349                                print "noReply failed to call callback"
350               
351                self.transport.loseConnection()
352
353       
354        ##################################################################
355       
356        def dataReceived(self, data_received):
357
358                data_to_process = None
359               
360                self.data_chunk += data_received
361               
362                try:
363                        data_to_process = json.loads(self.data_chunk)
364               
365                except Exception, e:
366
367                        # Special socket handling for Flash applications
368                        if (data_received == FLASH_POLICY_FILE_REQUEST):
369                               
370                                if self.DEBUG:
371                                        print "<-- [ThinkGear Emulator] Flash policy file requested"
372                                       
373                                data_to_process = data_received
374                               
375                               
376                        else:
377                                if self.DEBUG:
378                                        print "<-- [ThinkGear Emulator] Partial data received (or error:",
379                                        print e
380                                        print ")."
381
382                                        print "data_chunk:",
383                                        print self.data_chunk
384
385
386                if (data_to_process != None):
387
388                        self.data_chunk = ""
389
390                        d = self.factory.process_data(data_to_process)
391                        d.addCallback(self.send_response)
392
393
394        ##################################################################
395       
396        def send_response(self, delay_timer):
397
398                while (len(self.factory.packet_queue) > 0):
399
400                        packet = self.factory.packet_queue[0]
401                        del self.factory.packet_queue[0]
402##                        packet = self.factory.packet_queue.pop()
403
404                        # Special socket handling for Flash applications
405                        if (packet != FLASH_SOCKET_POLICY_FILE):
406                                packet = json.dumps(packet)
407
408                        if self.DEBUG:
409                                print "--> [ThinkGear Emulator] Sending:",
410                                print packet
411
412                        self.sendLine(packet)
413
414
415                if self.factory.client_connected:
416
417                        reactor.callLater(delay_timer, \
418                                          self.send_response, delay_timer)
419
420                               
421        ##################################################################
422       
423        def connectionLost(self, reason):
424               
425                self.factory.process_connection_lost()
426
427
428#####################################################################
429# Main
430#####################################################################
431
432if __name__ == '__main__':
433       
434        #log = puzzlebox_logger.puzzlebox_logger(logfile='server_thinkgear')
435        log = None
436       
437        # Collect default settings and command line parameters
438        server_interface = SERVER_INTERFACE
439        server_port = SERVER_PORT
440       
441        for each in sys.argv:
442               
443                if each.startswith("--interface="):
444                        server_interface = each[ len("--interface="): ]
445                if each.startswith("--port="):
446                        server_port = each[ len("--port="): ]
447       
448       
449        thinkgear_server = puzzlebox_brainstorms_server_thinkgear(log, DEBUG=DEBUG)
450       
451        if DEBUG:
452                print "----> [ThinkGear Emulator] Initializing server on %s:%i" % \
453                        (server_interface, server_port)
454       
455        reactor.listenTCP(interface=server_interface, \
456                          port=server_port, \
457                          factory=thinkgear_server)
458        reactor.run()
459
Note: See TracBrowser for help on using the repository browser.