source: remote_control/puzzlebox_brainstorms_server_thinkgear.py @ 57

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

client_thinkgear:

  • ThinkGear? authorization added
  • response processing added

configuration:

  • comment/whitespace cleanup

server_thinkgear:

File size: 10.1 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.21
12#
13#####################################################################
14# To do:
15#  - Server starts session sending data stream per command received
16#       instead of once per connection
17#####################################################################
18
19import os, signal, sys, time
20import simplejson as json
21
22from twisted.internet import reactor, protocol, defer, task
23from twisted.protocols import basic
24
25import puzzlebox_brainstorms_configuration as configuration
26#import puzzlebox_logger
27
28#####################################################################
29# Globals
30#####################################################################
31
32DEBUG = 1
33
34SERVER_INTERFACE = configuration.SERVER_INTERFACE
35SERVER_PORT = configuration.THINKGEAR_SERVER_PORT
36
37FLASH_POLICY_FILE_REQUEST = configuration.FLASH_POLICY_FILE_REQUEST
38FLASH_SOCKET_POLICY_FILE = configuration.FLASH_SOCKET_POLICY_FILE
39
40THINKGEAR_DELIMITER = '\r'
41
42MESSAGE_FREQUENCY_TIMER = 1 # 1 Hz
43BLINK_FREQUENCY_TIMER = 10 # 10 seconds
44
45DEFAULT_AUTHORIZATION_MESSAGE = \
46        {"isAuthorized": True}
47                # Tells the client whether the server has authorized
48                # access to the user's headset data. The value is
49                # either true or false.
50
51DEFAULT_SIGNAL_LEVEL_MESSAGE = \
52        {"poorSignalLevel": 0}
53                # A quantifier of the quality of the brainwave signal.
54                # This is an integer value that is generally in the
55                # range of 0 to 200, with 0 indicating a
56                # good signal and 200 indicating an off-head state.
57
58DEFAULT_EEG_POWER_MESSAGE = \
59        {"eegPower": { \
60                'lowGamma': 0, \
61                'highGamma': 0, \
62                'highAlpha': 0, \
63                'delta': 0, \
64                'highBeta': 0, \
65                'lowAlpha': 0, \
66                'lowBeta': 0, \
67                'theta': 0, \
68                }, \
69         } # A container for the EEG powers. These may
70           # be either integer or floating-point values.
71
72DEFAULT_ESENSE_MESSAGE = \
73        {"eSense": { \
74                'meditation': 0, \
75                'attention': 0, \
76                }, \
77        } # A container for the eSense™ attributes.
78          # These are integer values between 0 and 100,
79          # where 0 is perceived as a lack of that attribute
80          # and 100 is an excess of that attribute.
81               
82DEFAULT_BLINK_MESSAGE = {"blinkStrength": 255}
83        # The strength of a detected blink. This is
84        # an integer in the range of 0-255.
85
86DEFAULT_RAWEEG_MESSAGE = {"rawEeg": 255}
87        # The raw data reading off the forehead sensor.
88        # This may be either an integer or a floating-point value.
89
90DEFAULT_PACKET = {}
91DEFAULT_PACKET.update(DEFAULT_EEG_POWER_MESSAGE)
92DEFAULT_PACKET.update(DEFAULT_SIGNAL_LEVEL_MESSAGE)
93DEFAULT_PACKET.update(DEFAULT_ESENSE_MESSAGE)
94
95DEFAULT_RESPONSE_MESSAGE = DEFAULT_SIGNAL_LEVEL_MESSAGE
96
97#####################################################################
98# Classes
99#####################################################################
100
101class puzzlebox_brainstorms_server_thinkgear(protocol.ServerFactory):
102       
103        def __init__(self, log, DEBUG=DEBUG):
104               
105                self.log = log
106                self.DEBUG = DEBUG
107               
108                self.protocol = puzzlebox_brainstorms_server_protocol
109
110                self.status_packet = DEFAULT_PACKET
111                self.client_connected = False
112
113
114        ##################################################################
115
116        def validate_checksum(self, checksum):
117
118                '''The key used by the client application to identify
119itself. This must be 40 hexadecimal characters, ideally generated
120using an SHA-1 digest. The appKey is an identifier that is unique
121to each application, rather than each instance of an application.
122It is used by the server to bypass the authorization process if a
123user had previously authorized the requesting client. To reduce
124the chance of overlap with the appKey of other applications,
125the appKey should be generated using an SHA-1 digest.'''
126
127                is_valid = True
128
129                hexadecimal_characters = '0123456789abcdef'
130
131                if len(checksum) != 40:
132                        is_valid = False
133                else:
134                        for character in checksum:
135                                if character not in hexadecimal_characters:
136                                        is_valid = False
137
138                return(is_valid)
139       
140       
141        ##################################################################
142
143        def authorize_client(self, data):
144
145                '''The client must initiate an authorization request
146and the server must authorize the client before the
147server will start transmitting any headset data.'''
148
149                is_authorized = self.validate_checksum(data['appKey'])
150
151                # A human-readable name identifying the client
152                # application. This can be a maximum of 255 characters.
153               
154                if len(data['appName']) > 255:
155                        is_authorized = False
156
157
158                return(is_authorized)
159                       
160
161        ##################################################################
162       
163        def process_data(self, data):
164               
165                d = defer.Deferred()
166
167                # Special socket handling for Flash applications
168                if (data == FLASH_POLICY_FILE_REQUEST):
169                        response = FLASH_SOCKET_POLICY_FILE
170
171                elif (type(data) == type({}) and \
172                      data.has_key('appName') and \
173                      data.has_key('appKey')):
174                        authorized = self.authorize_client(data)
175
176                        response = DEFAULT_AUTHORIZATION_MESSAGE
177
178                        response = {}
179                        response['isAuthorized'] = authorized
180
181
182                else:
183                        response = DEFAULT_RESPONSE_MESSAGE
184
185               
186                if self.DEBUG:
187                        print "<-- [ThinkGear Emulator] Received:",
188                        print data
189               
190                if response:
191                        d.callback(response)
192
193               
194                return d
195       
196       
197        ##################################################################
198       
199        def process_connection_lost(self):
200
201                if self.DEBUG:
202                        print "--> [ThinkGear Emulator] Connection lost"
203
204                self.client_connected = False
205
206                self.looping_timer.stop()
207
208
209        ##################################################################
210
211        def update_status(self):
212
213                if self.DEBUG > 1:
214                        print "status:",
215                        print self.status_packet
216               
217
218        ##################################################################
219
220        def start_updating(self):
221
222                self.client_connected = True
223                self.looping_timer = task.LoopingCall(self.update_status)
224                self.looping_timer.start(MESSAGE_FREQUENCY_TIMER)
225
226
227#####################################################################
228# Protocol
229#####################################################################
230
231class puzzlebox_brainstorms_server_protocol(basic.LineReceiver):
232
233        delimiter='\r'
234       
235        def __init__(self):
236               
237                self.DEBUG = DEBUG
238                self.data_chunk = ""
239
240       
241        ##################################################################
242       
243        def connectionMade(self):
244               
245                if self.DEBUG:
246                        print "<-- [ThinkGear Emulator] Client connected"
247                       
248                self.factory.start_updating()
249
250
251        ##################################################################
252       
253        def noReply(self):
254               
255                try:
256                        self.factory.replyDefer.callback('NO_REPLY')
257                except:
258                        if self.DEBUG:
259                                print "noReply failed to call callback"
260               
261                self.transport.loseConnection()
262       
263       
264        ##################################################################
265
266        def lineReceived(self, line):
267
268                # Ignore blank lines
269                if not line:
270                        return
271               
272##                data = json.loads(line)
273##
274##                if self.DEBUG:
275##                      print "line received:",
276##                      print data
277##
278##              d = self.factory.process_data("%s" % data)
279##              d.addCallback(self.send_response)
280
281
282                self.dataReceived(line)
283
284       
285        ##################################################################
286       
287        def dataReceived(self, data_received):
288
289                data_to_process = None
290               
291                self.data_chunk += data_received
292               
293                try:
294                        data_to_process = json.loads(self.data_chunk)
295               
296                except Exception, e:
297
298                        # Special socket handling for Flash applications
299                        if (data_received == FLASH_POLICY_FILE_REQUEST):
300                               
301                                if self.DEBUG:
302                                        print "Flash policy file requested"
303                                       
304                                data_to_process = data_received
305                               
306                               
307                        else:
308                                if self.DEBUG:
309                                        print "Partial data received (or error:",
310                                        print e
311                                        print ")."
312
313                                        print "data_chunk:",
314                                        print self.data_chunk
315
316
317                if (data_to_process != None):
318
319                        self.data_chunk = ""
320
321                        d = self.factory.process_data(data_to_process)
322                        d.addCallback(self.send_response)
323       
324       
325        ##################################################################
326       
327        def send_response(self, response):
328
329                # Special socket handling for Flash applications
330                if (response == FLASH_SOCKET_POLICY_FILE):
331##                      self.transport.write(response)
332                        self.sendLine(response)
333
334                else:
335                        response = json.dumps(response)
336                        self.sendLine(response)
337
338                if self.factory.client_connected:
339                        if self.DEBUG:
340                                print "----> [ThinkGear Emulator] Sending:",
341                                print response
342
343                reactor.callLater(MESSAGE_FREQUENCY_TIMER, \
344                                  self.send_response, \
345                                  self.factory.status_packet)
346
347       
348        ##################################################################
349       
350        def connectionLost(self, reason):
351               
352                self.factory.process_connection_lost()
353
354
355#####################################################################
356# Main
357#####################################################################
358
359if __name__ == '__main__':
360       
361        #log = puzzlebox_logger.puzzlebox_logger(logfile='master_control')
362        log = None
363       
364        # Collect default settings and command line parameters
365        server_interface = SERVER_INTERFACE
366        server_port = SERVER_PORT
367       
368        for each in sys.argv:
369               
370                if each.startswith("--interface="):
371                        server_interface = each[ len("--interface="): ]
372                if each.startswith("--port="):
373                        server_port = each[ len("--port="): ]
374       
375       
376        thinkgear_server = puzzlebox_brainstorms_server_thinkgear(log, DEBUG=DEBUG)
377       
378        if DEBUG:
379                print "--> [ThinkGear Emulator] Initializing server on %s:%i" % \
380                        (server_interface, server_port)
381       
382        reactor.listenTCP(interface=server_interface, \
383                          port=server_port, \
384                          factory=thinkgear_server)
385        reactor.run()
386
Note: See TracBrowser for help on using the repository browser.