source: trunk/Puzzlebox/Synapse/Protocol.py @ 318

Last change on this file since 318 was 318, checked in by sc, 9 years ago

Protocol:

  • Improved exception handling

Configuration:

  • PySide? re-selected as default backend
  • Property svn:executable set to *
File size: 35.6 KB
Line 
1# -*- coding: utf-8 -*-
2
3# Copyright Puzzlebox Productions, LLC (2010-2011)
4#
5# This code is released under the GNU Pulic License (GPL) version 2
6# For more information please refer to http://www.gnu.org/copyleft/gpl.html
7
8# Old Classes:
9#       puzzlebox_synapse_protocol_thinkgear = ProtocolHandler
10#       puzzlebox_synapse_protocol_thinkgear_serial_device = SerialDevice
11
12__changelog__ = """\
13Last Update: 2011.12.20
14"""
15
16__doc__ = """\
17Puzzlebox.Synapse.Protocol
18
19usage:
20  from Puzzlebox.Synapse import Protocol
21
22Modules:
23  Protocol.ProtocolHandler()
24  Protocol.SerialDevice()
25
26SPEC:
27
28 CODE Definitions Table
29 Single-Byte CODEs
30 Extended             (Byte)
31 Code Level   [CODE] [LENGTH] Data Value Meaning
32 ----------   ------ -------- ------------------
33           0    0x02        - POOR_SIGNAL Quality (0-255)
34           0    0x04        - ATTENTION eSense (0 to 100)
35           0    0x05        - MEDITATION eSense (0 to 100)
36           0    0x16        - Blink Strength. (0-255) Sent only
37                              when Blink event occurs.
38 Multi-Byte CODEs
39 Extended             (Byte)
40 Code Level   [CODE] [LENGTH] Data Value Meaning
41 ----------   ------ -------- ------------------
42           0    0x80        2 RAW Wave Value: a single big-endian
43                                16-bit two's-compliment signed value
44                                (high-order byte followed by
45                                low-order byte) (-32768 to 32767)
46           0    0x83       24 ASIC_EEG_POWER: eight big-endian
47                                3-byte unsigned integer values
48                                representing delta, theta, low-alpha
49                                high-alpha, low-beta, high-beta,
50                                low-gamma, and mid-gamma EEG band
51                                power values
52         Any    0x55        - NEVER USED (reserved for [EXCODE])
53         Any    0xAA        - NEVER USED (reserved for [SYNC])
54
55MindWave Connection CODEs
56[CODE] [DATA]    Data Value Meaning
57------ --------- ------------
58 0xC0  0xAB 0xCD Connect to headset with ID "ABCD"
59 0xC1          - Disconnect
60 0xC2          - Connect to first available headset
61
62MindWave Response CODEs
63Extended             (Byte)
64Code Level   [CODE] [LENGTH] Data Value Meaning
65----------   ------ -------- ------------------
66         0    0xD0        3 Headset Connect Success
67         0    0xD1        2 Headset Not Found
68         0    0xD2        3 Headset Disconnected
69         0    0xD3        0 Request Denied
70         0    0xD4        1 Standby/Scan Mode
71
72Linux Bluetooth serial protocol profile example:
73    rfcomm connect rfcomm0 00:13:EF:00:1B:FE 3
74
75TODO:
76 - needs to handle:
77   serial.serialutil.SerialException:
78   could not open port /dev/rfcomm0:
79   [Errno 16] Device or resource busy: '/dev/rfcomm0'
80
81"""
82
83### IMPORTS ###
84import sys, time
85import signal
86import serial
87
88if (sys.platform != 'win32'):
89        import bluetooth
90
91
92import Configuration as configuration
93
94if configuration.ENABLE_PYSIDE:
95        try:
96                import PySide as PyQt4
97                from PySide import QtCore
98        except Exception, e:
99                print "ERROR: Exception importing PySide:",
100                print e
101                configuration.ENABLE_PYSIDE = False
102        else:
103                print "INFO: [Synapse:Protocol] Using PySide module"
104
105if not configuration.ENABLE_PYSIDE:
106        print "INFO: [Synapse:Protocol] Using PyQt4 module"
107        from PyQt4 import QtCore
108
109
110#try:
111        #import PySide as PyQt4
112        #from PySide import QtCore
113#except:
114        #print "Using PyQt4 module"
115        #from PyQt4 import QtCore
116#else:
117        #print "Using PySide module"
118
119#import Configuration as configuration
120
121### GLOBALS ###
122
123DEBUG = configuration.DEBUG
124
125THINKGEAR_DEVICE_SERIAL_PORT = configuration.THINKGEAR_DEVICE_SERIAL_PORT
126#THINKGEAR_DEVICE_BLUETOOTH_ADDRESS = \
127        #configuration.THINKGEAR_DEVICE_BLUETOOTH_ADDRESS
128
129#DEFAULT_SERIAL_BAUDRATE = 57600
130DEFAULT_SERIAL_BAUDRATE = 115200
131
132#THINKGEAR_DEVICE_BLUETOOTH_ADDRESS = '00:13:EF:00:1B:FE'
133THINKGEAR_DEVICE_BLUETOOTH_CHANNEL = 3
134
135THINKGEAR_DEVICE_AUTOCONNECT_INTERVAL = 4 # seconds between attempting
136                                          # to send auto-connect packets
137THINKGEAR_DEVICE_ID = configuration.THINKGEAR_DEVICE_ID
138#THINKGEAR_DEFAULT_DEVICE_ID = '\x7d\x68'
139#THINKGEAR_DEFAULT_DEVICE_ID = '\xe4\x68'
140
141PROTOCOL_SYNC = '\xAA'
142PROTOCOL_EXCODE = '\x55'
143
144EEG_POWER_BAND_ORDER = configuration.THINKGEAR_EEG_POWER_BAND_ORDER
145
146DEVICE_BUFFER_CHECK_TIMER = 60 * 1000 # Check buffer size once every minute
147DEVICE_READ_BUFFER_CHECK_TIMER = 10 * 1000 # Check buffer size once x seconds
148DEVICE_BUFFER_MAX_SIZE = 180 # Reset buffer if it grow this large
149                             # as this would indicate the processing
150                             # algorithm is not keeping up with the device
151                             # According to protocol specification,
152                             # "...a complete, valid Packet is ... a maximum
153                             # of 173 bytes long (possible if the Data Payload
154                             # is the maximum 169 bytes long)."
155                             # Therefore we reset if our buffer has grown longer
156                             # than the maximum packet length as this means
157                             # the processing algorthim is at least one full
158                             # packet behind.
159
160DEBUG_BYTE_COUNT = 819200
161DEBUG_PACKET_COUNT = 1024
162
163### CLASSES ###
164
165class ProtocolHandler(QtCore.QThread):
166       
167        def __init__(self, log, \
168                               serial_device, \
169                               device_id=THINKGEAR_DEVICE_ID, \
170                               device_model=None, \
171                               DEBUG=DEBUG, \
172                               parent=None):
173               
174                QtCore.QThread.__init__(self,parent)
175               
176                self.log = log
177                self.DEBUG = DEBUG
178                self.parent = parent
179               
180                self.device_id = device_id
181                self.device_model = device_model
182               
183                self.device = None
184                self.buffer = ''
185                self.payload_timestamp = time.time()
186               
187                self.device = serial_device
188                self.auto_connect_timestamp = time.time()
189               
190                self.data_packet = {}
191                self.data_packet['eegPower'] = {}
192                self.data_packet['eSense'] = {}
193               
194                self.packet_count = 0
195                self.bad_packets = 0
196                self.session_start_time = None
197               
198                self.keep_running = True
199       
200       
201        ##################################################################
202       
203        def communicateWithHandsfreeProfile(self):
204               
205                #"AT+CKPD=200" - Indicates a Bluetooth button press
206                #"AT+VGM=" - Indicates a microphone volume change
207                #"AT+VGS=" - Indicates a speakerphone volume change
208                #"AT+BRSF=" - The Headset is asking what features are supported
209                #"AT+CIND?" - The Headset is asking about the indicators that are signaled
210                #"AT+CIND=?" - The Headset is asking about the test indicators
211                #"AT+CMER=" - The Headset is asking which indicates are registered for updates
212                #"ATA" - When an incoming call has been answered, usually a Bluetooth button press
213                #"AT+CHUP" - When a call has been hung up, usually a Bluetooth button press
214                #"ATD>" - The Headset is requesting the local device to perform a memory dial
215                #"ATD" - The Headset is requesting to dial the number
216                #"AT+BLDN" - The Headset is requesting to perform last number dialed
217                #"AT+CCWA=" - The Headset has enabled call waiting
218                #"AT+CLIP=" - The Headset has enabled CLI (Calling Line Identification)
219                #"AT+VTS=" - The Headset is asking to send DTMF digits
220                #"AT+CHLD=" - The Headset is asking to put the call on Hold
221                #"AT+BVRA=" - The Headset is requesting voice recognition
222                #"ATH" - Call hang-up
223               
224                #self.device.write('\x29')
225                #self.device.write('AT+BRSF=24\r\n')
226               
227                buffer = ''
228               
229                while True:
230                        reply = self.device.read()
231                       
232                        if (len(reply) != 0):
233                                if DEBUG > 1:
234                                        print reply
235                                buffer += reply
236                       
237                        if buffer == "AT+BRSF=24\r":
238                                print "--> Received:",
239                                print buffer
240                                response = '\r\nOK\r\n'
241                                print "<-- Sending:",
242                                print response.replace('\r\n', '')
243                                self.device.write(response)
244                                buffer = ''
245                       
246                        elif buffer == 'AT+CIND=?\r':
247                                print "--> Received:",
248                                print buffer
249                                # first field indicates that we have cellular service [0-1]
250                                # second field indicates that we're in a call (0 for false) [0-1]
251                                # third field indicates the current call setup (0 for idle) [0-3]
252                                response = '\r\n+CIND: 1,0,0\r\n'
253                                print "<-- Sending:",
254                                print response.replace('\r\n', '')
255                                self.device.write(response)
256                                response = '\r\nOK\r\n'
257                                print "<-- Sending:",
258                                print response.replace('\r\n', '')
259                                self.device.write(response)
260                                buffer = ''
261                       
262                        elif buffer == 'AT+CMER=3, 0, 0, 1\r':
263                                print "--> Received:",
264                                print buffer
265                                response = '\r\nOK\r\n'
266                                print "<-- Sending:",
267                                print response.replace('\r\n', '')
268                                self.device.write(response)
269                                response = '\r\n+CIEV:2,1\r\n'
270                                print "<-- Sending:",
271                                print response.replace('\r\n', '')
272                                self.device.write(response)
273                                response = '\r\n+CIEV:3,0\r\n'
274                                print "<-- Sending:",
275                                print response.replace('\r\n', '')
276                                self.device.write(response)
277                                buffer = ''
278                       
279                        elif buffer == 'AT+VGS=15\r':
280                                print "--> Received:",
281                                print buffer
282                                response = '\r\nOK\r\n'
283                                print "<-- Sending:",
284                                print response.replace('\r\n', '')
285                                self.device.write(response)
286                                buffer = ''
287                       
288                        elif buffer == 'AT+VGM=08\r':
289                                print "--> Received:",
290                                print buffer
291                                response = '\r\nOK\r\n'
292                                print "<-- Sending:",
293                                print response.replace('\r\n', '')
294                                self.device.write(response)
295                                buffer = ''
296                               
297                               
298                                self.exitThread()
299                                #self.keep_running = False
300                                #self.device.stop()
301                                #QtCore.QThread.quit(self)
302                                #sys.exit()
303       
304       
305        ##################################################################
306       
307        def hexStringEndianSwap(self, theString):
308                """Rearranges character-couples in a little endian hex string to
309                convert it into a big endian hex string and vice-versa. i.e. 'A3F2'
310                is converted to 'F2A3'
311               
312                @param theString: The string to swap character-couples in
313                @return: A hex string with swapped character-couples. -1 on error.
314               
315                Taken from http://bytes.com/topic/python/answers/652429-convert-little-endian-hex-string-number#post2588668"""
316               
317                # We can't swap character couples in a string that has an odd number
318                # of characters.
319                if len(theString)%2 != 0:
320                        return -1
321               
322                # Swap the couples
323                swapList = []
324                for i in range(0, len(theString), 2):
325                        swapList.insert(0, theString[i:i+2])
326               
327                # Combine everything into one string. Don't use a delimeter.
328                return ''.join(swapList)
329       
330       
331        ##################################################################
332       
333        def processRawEEGValue(self, data_values):
334               
335                '''SPEC: This Data Value consists of two bytes, and represents a
336                single raw wave sample. Its value is a signed 16-bit integer that
337                ranges from -32768 to 32767. The first byte of the Value represents
338                the high-order bits of the twos-compliment value, while the second
339                byte represents the low-order bits. To reconstruct the full raw
340                wave value, simply shift the rst byte left by 8 bits, and
341                bitwise-or with the second byte:
342               
343                short raw = (Value[0]<<8) | Value[1];
344               
345                where Value[0] is the high-order byte, and Value[1] is the
346                low-order byte. In systems or languages where bit operations are
347                inconvenient, the following arithmetic operations may be
348                substituted instead:
349               
350                raw = Value[0]*256 + Value[1];
351                if( raw >= 32768 ) raw = raw - 65536;
352               
353                where raw is of any signed number type in the language that can
354                represent all the numbers from -32768 to 32767.
355               
356                Each ThinkGear model reports its raw wave information in only
357                certain areas of the full -32768 to 32767 range. For example,
358                MindSet reports raw waves that fall between approximately -2048 to
359                2047. By default, output of this Data Value is enabled, and is
360                outputed 512 times a second, or approximately once every 2ms.'''
361               
362                high_order = data_values[0:2]
363                low_order = data_values[2:4]
364               
365                #high_order = high_order.encode("hex")
366                high_order = int(high_order, 16)
367               
368                #low_order = low_order.encode("hex")
369                low_order = int(low_order, 16)
370
371                raw = high_order * 256 + low_order
372               
373                if (raw >= 32768):
374                        raw = raw - 65536
375               
376               
377                return (raw)
378       
379       
380        ##################################################################
381       
382        def processAsicEegPower(self, data_values):
383               
384                '''SPEC: This Data Value represents the current magnitude of 8
385                commonly-recognized types of EEG (brain-waves). This Data Value
386                is output as a series of eight 3-byte unsigned integers in
387                little-endian format.
388                The eight EEG powers are output in the following order:
389                delta (0.5 - 2.75Hz),
390                theta (3.5 - 6.75Hz),
391                low-alpha (7.5 - 9.25Hz),
392                high-alpha (10 - 11.75Hz),
393                low-beta (13 - 16.75Hz),
394                high-beta (18 - 29.75Hz),
395                low-gamma (31 - 39.75Hz), and
396                mid-gamma (41 - 49.75Hz).
397                These values have no units and therefore are only meaningful compared
398                to each other and to themselves, to consider relative quantity and
399                temporal uctuations. By default, output of this Data Value is enabled,
400                and is typically output once a second.'''
401               
402                eegPower = {}
403               
404                eegPower['delta'] = data_values[0:6]
405                eegPower['theta'] = data_values[6:12]
406                eegPower['lowAlpha'] = data_values[12:18]
407                eegPower['highAlpha'] = data_values[18:24]
408                eegPower['lowBeta'] = data_values[24:30]
409                eegPower['highBeta'] = data_values[30:36]
410                eegPower['lowGamma'] = data_values[36:42]
411                eegPower['highGamma'] = data_values[42:48]
412               
413                for key in eegPower.keys():
414                        eegPower[key] = self.hexStringEndianSwap(eegPower[key])
415                        #eegPower[key] = eegPower[key].encode("hex")
416                        eegPower[key] = int(eegPower[key], 16)
417               
418               
419                return(eegPower)
420       
421       
422        ##################################################################
423       
424        def processDataRow(self, \
425                           extended_code_level, \
426                           code, \
427                           length, \
428                           data_values, \
429                           timestamp):
430               
431                '''CODE Definitions Table
432                   Single-Byte CODEs
433                   Extended             (Byte)
434                   Code Level   [CODE] [LENGTH] Data Value Meaning
435                   ----------   ------ -------- ------------------
436                             0    0x02        - POOR_SIGNAL Quality (0-255)
437                             0    0x04        - ATTENTION eSense (0 to 100)
438                             0    0x05        - MEDITATION eSense (0 to 100)
439                             0    0x16        - Blink Strength. (0-255) Sent only
440                                                when Blink event occurs.
441                   Multi-Byte CODEs
442                   Extended             (Byte)
443                   Code Level   [CODE] [LENGTH] Data Value Meaning
444                   ----------   ------ -------- ------------------
445                             0    0x80        2 RAW Wave Value: a single big-endian
446                                                  16-bit two's-compliment signed value
447                                                  (high-order byte followed by
448                                                  low-order byte) (-32768 to 32767)
449                             0    0x83       24 ASIC_EEG_POWER: eight big-endian
450                                                  3-byte unsigned integer values
451                                                  representing delta, theta, low-alpha
452                                                  high-alpha, low-beta, high-beta,
453                                                  low-gamma, and mid-gamma EEG band
454                                                  power values
455                           Any    0x55        - NEVER USED (reserved for [EXCODE])
456                           Any    0xAA        - NEVER USED (reserved for [SYNC])
457               
458                   MindWave CODEs
459                   Extended             (Byte)
460                   Code Level   [CODE] [LENGTH] Data Value Meaning
461                   ----------   ------ -------- ------------------
462                             0    0xD0        3 Headset Connect Success
463                             0    0xD1        2 Headset Not Found
464                             0    0xD2        3 Headset Disconnected
465                             0    0xD3        0 Request Denied
466                             0    0xD4        1 Standby/Scan Mode'''
467               
468                packet_update = {}
469               
470                packet_update['timestamp'] = timestamp
471               
472                self.packet_count += 1
473               
474                if extended_code_level == 0:
475                       
476                        if code == '02':
477                                poor_signal_quality = int(data_values, 16)
478                                if self.DEBUG > 1:
479                                        print # Empty line at the beginning of most packets
480                                        print "poorSignalLevel:",
481                                        print poor_signal_quality
482                               
483                                packet_update['poorSignalLevel'] = poor_signal_quality
484                       
485                       
486                        elif code == '04':
487                                attention = int(data_values, 16)
488                                if self.DEBUG > 1:
489                                        print "attention:",
490                                        print attention
491                               
492                                packet_update['eSense'] = {}
493                                packet_update['eSense']['attention'] = attention
494                       
495                       
496                        elif code == '05':
497                                meditation = int(data_values, 16)
498                                if self.DEBUG > 1:
499                                        print "meditation:",
500                                        print meditation
501                               
502                                packet_update['eSense'] = {}
503                                packet_update['eSense']['meditation'] = meditation
504                       
505                       
506                        elif code == '16':
507                                blink_strength = int(data_values, 16)
508                                if self.DEBUG > 1:
509                                        print "blinkStrength:",
510                                        print blink_strength
511                               
512                                packet_update['blinkStrength'] = blink_strength
513                       
514                       
515                        elif code == '80':
516                                #self.packet_count -= 1 # We don't count raw EEG packets for Interface
517                                raw_wave_value = data_values
518                                if self.DEBUG > 3:
519                                        print "Raw EEG:",
520                                        print raw_wave_value
521                                raw_eeg_value = self.processRawEEGValue(data_values)
522                                if self.DEBUG > 2:
523                                        print "Raw EEG Value:",
524                                        print raw_eeg_value
525                               
526                                packet_update['rawEeg'] = raw_eeg_value
527                       
528                       
529                        elif code == '83':
530                                asic_eeg_power = data_values
531                                if self.DEBUG > 2:
532                                        print "ASIC_EEG_POWER:",
533                                        print asic_eeg_power
534                                eegPower = self.processAsicEegPower(data_values)
535                                if self.DEBUG > 1:
536                                        for key in EEG_POWER_BAND_ORDER:
537                                                print "%s: %i" % (key, eegPower[key])
538                               
539                                packet_update['eegPower'] = {}
540                                for key in eegPower.keys():
541                                        packet_update['eegPower'][key] = eegPower[key]
542                       
543                       
544                        elif code == 'd0':
545                                if self.DEBUG:
546                                        print "INFO: ThinkGear Headset Connect Success"
547                                self.session_start_timestamp = time.time()
548                                self.packet_count = 0
549                                self.bad_packets = 0
550                       
551                       
552                        elif code == 'd1':
553                                current_time = time.time()
554                                if current_time - self.auto_connect_timestamp > \
555                                        THINKGEAR_DEVICE_AUTOCONNECT_INTERVAL:
556                                        if self.DEBUG:
557                                                print "INFO: ThinkGear device not found. Writing auto-connect packet."
558                                        self.auto_connect_timestamp = current_time
559                                        self.device.device.write('\xc2')
560                                        #self.device.device.write('\xc0\xe4\x68')
561                       
562                       
563                        elif code == 'd2':
564                                current_time = time.time()
565                                if current_time - self.auto_connect_timestamp > \
566                                        THINKGEAR_DEVICE_AUTOCONNECT_INTERVAL:
567                                        if self.DEBUG:
568                                                print "INFO: ThinkGear device disconnected. Writing auto-connect packet."
569                                        self.auto_connect_timestamp = current_time
570                                        self.device.device.write('\xc2')
571                                        #self.device.device.write('\xc0\xe4\x68')
572                       
573                       
574                        elif code == 'd3':
575                                current_time = time.time()
576                                if current_time - self.auto_connect_timestamp > \
577                                        THINKGEAR_DEVICE_AUTOCONNECT_INTERVAL:
578                                        if self.DEBUG:
579                                                print "INFO: ThinkGear device request denied. Writing auto-connect packet."
580                                        self.auto_connect_timestamp = current_time
581                                        self.device.device.write('\xc2')
582                                        #self.device.device.write('\xc0\xe4\x68')
583                       
584                       
585                        elif code == 'd4':
586                                current_time = time.time()
587                                if current_time - self.auto_connect_timestamp > \
588                                        THINKGEAR_DEVICE_AUTOCONNECT_INTERVAL:
589                                        if self.DEBUG:
590                                                print "INFO: ThinkGear device in standby/scan mode. Writing auto-connect packet."
591                                        self.auto_connect_timestamp = current_time
592                                        self.device.device.write('\xc2')
593                                        #self.device.device.write('\xc0\xe4\x68')
594                       
595                       
596                        else:
597                                self.bad_packets += 1
598                                if self.DEBUG:
599                                        print "ERROR: data payload row code not matched:",
600                                        print code
601               
602               
603                return(packet_update)
604       
605       
606        ##################################################################
607       
608        def processDataPayload(self, data_payload, timestamp):
609               
610                '''A DataRow consists of bytes in the following format:
611                ([EXCODE]...) [CODE] ([VLENGTH])   [VALUE...]
612                ____________________ ____________ ___________
613                ^^^^(Value Type)^^^^ ^^(length)^^ ^^(value)^^'''
614               
615               
616                if self.DEBUG > 3:
617                        print "data payload:",
618                        for byte in data_payload:
619                                print byte.encode("hex"),
620                        print
621               
622                byte_index = 0
623               
624                # Parse the extended_code_level, code, and length
625                while (byte_index < len(data_payload)):
626                        extended_code_level = 0
627                       
628                        # 1. Parse and count the number of [EXCODE] (0x55)
629                        #    bytes that may be at the beginning of the
630                        #    current DataRow.
631                        while (data_payload[byte_index] == PROTOCOL_EXCODE):
632                                extended_code_level += 1
633                                byte_index += 1
634                       
635                        # 2. Parse the [CODE] byte for the current DataRow.
636                        code = data_payload[byte_index]
637                        byte_index += 1
638                        code = code.encode("hex")
639                       
640                        # 3. If [CODE] >= 0x80, parse the next byte as the
641                        #    [VLENGTH] byte for the current DataRow.
642                        if (code > '\x7f'.encode("hex")):
643                                length = data_payload[byte_index]
644                                byte_index += 1
645                                length = length.encode("hex")
646                                length = int(length, 16)
647                        else:
648                                length = 1
649                       
650                       
651                        if self.DEBUG > 3:
652                                print "EXCODE level:",
653                                print extended_code_level,
654                                print " CODE:",
655                                print code,
656                                print " length:",
657                                print length
658                                #print type(code)
659                       
660                        data_values = ''
661                        value_index = 0
662                       
663                        # 4. Parse and handle the [VALUE...] byte(s) of the current
664                        #    DataRow, based on the DataRow's [EXCODE] level, [CODE],
665                        #    and [VLENGTH] (refer to the Code De nitions Table).
666                        while value_index < length:
667                                # Uh-oh more C mojo
668                                try:
669                                        value = data_payload[(byte_index + value_index)] # & 0xFF
670                                except:
671                                        if self.DEBUG:
672                                                print "ERROR: failed to parse and handle the [VALUE...] bytes of the current DataRow"
673                                        break
674                                data_values += value.encode("hex")
675                                value_index += 1
676                       
677                        if self.DEBUG > 3:
678                                print "Data Values:",
679                                print data_values
680                                print
681                       
682                        packet_update = self.processDataRow(extended_code_level, \
683                                                            code, \
684                                                            length, \
685                                                            data_values, \
686                                                            timestamp)
687                       
688                        self.updateDataPacket(packet_update)
689                       
690                       
691                        byte_index += length
692                       
693                        # 5. If not all bytes have been parsed from the payload[] array,
694                        # return to step 1. to continue parsing the next DataRow.
695       
696       
697        ##################################################################
698       
699        def parseStream(self):
700               
701                '''Each Packet begins with its Header, followed by its Data Payload,
702                and ends with the Payload's Check-sum Byte, as follows:
703                [SYNC] [SYNC] [PLENGTH]      [PAYLOAD...]         [CHKSUM]
704                _______________________      _____________     ____________
705                ^^^^^^^^(Header)^^^^^^^      ^^(Payload)^^     ^(Checksum)^'''
706               
707                # Loop forever, parsing one Packet per loop...
708                packet_count = 0
709               
710                while self.keep_running:
711                       
712                        # Synchronize on [SYNC] bytes
713                        # Read from stream until two consecutive [SYNC] bytes are found
714                        byte = self.device.read()
715                        if (byte != PROTOCOL_SYNC):
716                                continue
717                       
718                        byte = self.device.read()
719                        if (byte != PROTOCOL_SYNC):
720                                continue
721                       
722                        self.payload_timestamp = time.time()
723                       
724                        # Parse [PLENGTH] byte
725                       
726                        # SPEC: [PLENGTH] byte indicates the length, in bytes, of the
727                        # Packet's Data Payload [PAYLOAD...] section, and may be any value
728                        # from 0 up to 169. Any higher value indicates an error
729                        # (PLENGTH TOO LARGE). Be sure to note that [PLENGTH] is the length
730                        # of the Packet's Data Payload, NOT of the entire Packet.
731                        # The Packet's complete length will always be [PLENGTH] + 4.
732                       
733                        byte = self.device.read()
734                        packet_length = byte.encode("hex")
735                        packet_length = int(packet_length, 16)
736                       
737                        if (packet_length > 170):
738                                self.bad_packets += 1
739                                if self.DEBUG:
740                                        print "ERROR: packet length bad"
741                                        continue
742                       
743                       
744                        # Collect [PAYLOAD...] bytes
745                        data_payload = self.device.read(packet_length)
746                       
747                       
748                        # Calculate [PAYLOAD...] checksum
749                       
750                        # SPEC: The [CHKSUM] Byte must be used to verify the integrity of the
751                        # Packet's Data Payload. The Payload's Checksum is defined as:
752                        #  1. summing all the bytes of the Packet's Data Payload
753                        #  2. taking the lowest 8 bits of the sum
754                        #  3. performing the bit inverse (one's compliment inverse)
755                        #     on those lowest 8 bits
756                       
757                        payload_checksum = 0
758                        for byte in data_payload:
759                                value = byte.encode("hex")
760                                value = int(value, 16)
761                                payload_checksum += value
762                       
763                       
764                        # Take the lowest 8 bits of the calculated payload_checksum
765                        # and invert them. Serious C code mojo follows.
766                        payload_checksum &= 0xff
767                        payload_checksum = ~payload_checksum & 0xff
768                       
769                       
770                        # Parse [CKSUM] byte
771                        packet_checksum = self.device.read()
772                        packet_checksum = packet_checksum.encode("hex")
773                        packet_checksum = int(packet_checksum, 16)
774                       
775                       
776                        # Verify [CKSUM] byte against calculated [PAYLOAD...] checksum
777                        if packet_checksum != payload_checksum:
778                                self.bad_packets += 1
779                                if self.DEBUG > 1:
780                                        print "ERROR: packet checksum does not match"
781                                        print "       packet_checksum:",
782                                        print packet_checksum
783                                        print "       payload_checksum:",
784                                        print payload_checksum
785                                       
786                                        #self.device.checkBuffer()
787                               
788                                continue
789                       
790                       
791                        else:
792                                # Since [CKSUM] is OK, parse the Data Payload
793                                if self.DEBUG > 3:
794                                        print "packet checksum correct"
795                               
796                               
797                                self.processDataPayload(data_payload, self.payload_timestamp)
798                               
799                               
800                                #if self.DEBUG > 1:
801                                        #packet_count += 1
802                                        #if packet_count >= DEBUG_PACKET_COUNT:
803                                                #print "max debugging count reached, disconnecting"
804                                                #self.keep_running = False
805                                                #self.device.stop()
806                                                #QtCore.QThread.quit(self)
807                                                ##sys.exit()
808       
809       
810        ##################################################################
811       
812        def updateDataPacket(self, packet_update):
813               
814                if 'eSense' in packet_update.keys():
815                        process_packet = {'eSense': {}}
816                        for key in packet_update['eSense'].keys():
817                                self.data_packet['eSense'][key] = packet_update['eSense'][key]
818                                process_packet['eSense'][key] = packet_update['eSense'][key]
819               
820                else:
821                        self.data_packet.update(packet_update)
822                        process_packet = packet_update
823               
824               
825                process_packet['timestamp'] = packet_update['timestamp']
826               
827               
828                if self.DEBUG > 3:
829                        print self.data_packet
830               
831               
832                if (self.parent != None):
833                       
834                        # NOTE: is it possible this call is blocking the Protocol
835                        #       thread from continuing to parse data?
836                       
837                        self.parent.processPacketThinkGear(process_packet)
838       
839       
840        ##################################################################
841       
842        def disconnectHardware(self):
843               
844                if self.device != None and self.device.device != None:
845                        if self.device_model == 'NeuroSky MindWave':
846                                if self.DEBUG:
847                                        print "INFO: ThinkGear device model MindWave selected. Writing disconnect packet."
848                                try:
849                                        self.device.device.write('\xc1')
850                                except Exception, e:
851                                        if self.DEBUG:
852                                                print "ERROR: failed to write disconnect packet: ",
853                                                print e
854       
855       
856        ##################################################################
857       
858        def run(self):
859               
860                self.packet_count = 0
861                self.bad_packets = 0
862                self.session_start_timestamp = time.time()
863               
864                if self.device != None and self.device.device != None:
865                        if self.device_model == 'NeuroSky MindWave':
866                                if self.DEBUG:
867                                        print "INFO: ThinkGear device model MindWave selected. Writing disconnect packet."
868                                self.device.device.write('\xc1')
869                                if self.DEBUG:
870                                        print "INFO: ThinkGear device model MindWave selected. Writing auto-connect packet."
871                                self.device.device.write('\xc2')
872                        else:
873                                if self.device_model != None and self.DEBUG:
874                                        print "INFO: %s device model selected" % self.device_model
875                        self.parseStream()
876       
877       
878        ##################################################################
879       
880        def exitThread(self, callThreadQuit=True):
881               
882                #if self.device != None and self.device.device != None:
883                        #if self.device_model == 'NeuroSky MindWave':
884                                #if self.DEBUG:
885                                        #print "INFO: ThinkGear device model MindWave selected. Writing disconnect packet."
886                                #try:
887                                        #self.device.device.write('\xc1')
888                                #except Exception, e:
889                                        #if self.DEBUG:
890                                                #print "ERROR: failed to write disconnect packet: ",
891                                                #print e
892               
893                self.disconnectHardware()
894               
895                try:
896                        self.device.stop()
897                except:
898                        pass
899               
900               
901                if callThreadQuit:
902                        QtCore.QThread.quit(self)
903
904
905#####################################################################
906#####################################################################
907
908class SerialDevice(QtCore.QThread):
909       
910        def __init__(self, log, \
911                               device_address=THINKGEAR_DEVICE_SERIAL_PORT, \
912                               DEBUG=DEBUG, \
913                               parent=None):
914               
915                QtCore.QThread.__init__(self, parent)
916               
917                self.log = log
918                self.DEBUG = DEBUG
919                self.parent = parent
920               
921                self.device_address = device_address
922                self.device = None
923                self.buffer = ''
924               
925                if (self.device_address.count(':') == 5):
926                        # Device address is a Bluetooth MAC address
927                        if self.DEBUG:
928                                print "Initializing Bluetooth Device",
929                                print self.device_address
930                        self.device = self.initializeBluetoothDevice()
931                else:
932                        # Device address is a serial port address
933                        if self.DEBUG:
934                                print "Initializing Serial Device",
935                                print self.device_address
936                        self.device = self.initializeSerialDevice()
937               
938                self.buffer_check_timer = QtCore.QTimer()
939                QtCore.QObject.connect(self.buffer_check_timer, \
940                                       QtCore.SIGNAL("timeout()"), \
941                                       self.checkBuffer)
942                self.buffer_check_timer.start(DEVICE_BUFFER_CHECK_TIMER)
943               
944                self.read_buffer_check_timer = QtCore.QTimer()
945                QtCore.QObject.connect(self.read_buffer_check_timer, \
946                                       QtCore.SIGNAL("timeout()"), \
947                                       self.checkReadBuffer)
948#               self.read_buffer_check_timer.start(DEVICE_READ_BUFFER_CHECK_TIMER)
949               
950                self.keep_running = True
951       
952       
953        ##################################################################
954       
955        def initializeBluetoothDevice(self):
956               
957                socket = bluetooth.BluetoothSocket( bluetooth.RFCOMM )
958               
959                try:
960                        socket.connect((self.device_address, THINKGEAR_DEVICE_BLUETOOTH_CHANNEL))
961               
962                except Exception, e:
963                        if self.DEBUG:
964                                print "ERROR:",
965                                print e
966                                sys.exit()
967               
968               
969                return socket
970       
971       
972        ##################################################################
973       
974        def initializeSerialDevice(self):
975               
976                baudrate = DEFAULT_SERIAL_BAUDRATE
977                bytesize = 8
978                parity = 'NONE'
979                stopbits = 1
980                software_flow_control = 'f'
981                rts_cts_flow_control = 'f'
982                #timeout = 15
983                timeout = 5
984               
985                # convert bytesize
986                if (bytesize == 5):
987                        init_byte_size = serial.FIVEBITS
988                elif (bytesize == 6):
989                        init_byte_size = serial.SIXBITS
990                elif (bytesize == 7):
991                        init_byte_size = serial.SEVENBITS
992                elif (bytesize == 8):
993                        init_byte_size = serial.EIGHTBITS
994                else:
995                        #self.log.perror("Invalid value for %s modem byte size! Using default (8)" % modem_type)
996                        init_byte_size = serial.EIGHTBITS
997               
998                # convert parity
999                if (parity == 'NONE'):
1000                        init_parity = serial.PARITY_NONE
1001                elif (parity == 'EVEN'):
1002                        init_parity = serial.PARITY_EVEN
1003                elif (parity == 'ODD'):
1004                        init_parity = serial.PARITY_ODD
1005                else:
1006                        #self.log.perror("Invalid value for %s modem parity! Using default (NONE)" % modem_type)
1007                        init_parity = serial.PARITY_NONE
1008               
1009                # convert stopbits
1010                if (stopbits == 1):
1011                        init_stopbits = serial.STOPBITS_ONE
1012                elif (stopbits == 2):
1013                        init_stopbits = serial.STOPBITS_TWO
1014                else:
1015                        #self.log.perror("Invalid value for %s modem stopbits! Using default (8)" % modem_type)
1016                        init_byte_size = serial.STOPBITS_ONE
1017               
1018                # convert software flow control
1019                if (software_flow_control == 't'):
1020                        init_software_flow_control = 1
1021                else:
1022                        init_software_flow_control = 0
1023               
1024                # convert rts cts flow control
1025                if (rts_cts_flow_control == 't'):
1026                        init_rts_cts_flow_control = 1
1027                else:
1028                        init_rts_cts_flow_control = 0
1029               
1030               
1031                try:
1032##                      device = serial.Serial(port = self.device_address, \
1033##                                                  baudrate = baudrate, \
1034##                                                  bytesize = init_byte_size, \
1035##                                                  parity = init_parity, \
1036##                                                  stopbits = init_stopbits, \
1037##                                                  xonxoff = init_software_flow_control, \
1038##                                                  rtscts = init_rts_cts_flow_control, \
1039##                                                  timeout = timeout)
1040                       
1041                        device = serialWrapper(port = self.device_address, \
1042                                                    baudrate = baudrate, \
1043                                                    bytesize = init_byte_size, \
1044                                                    parity = init_parity, \
1045                                                    stopbits = init_stopbits, \
1046                                                    xonxoff = init_software_flow_control, \
1047                                                    rtscts = init_rts_cts_flow_control, \
1048                                                    timeout = timeout)
1049               
1050                except Exception, e:
1051                        if self.DEBUG:
1052                                print "ERROR:",
1053                                print e,
1054                                print self.device_address
1055                                #sys.exit()
1056                                return(None)
1057               
1058               
1059                device.flushInput()
1060                #device.flushOutput()
1061               
1062               
1063                #if self.DEBUG:
1064                        #print "Writing device connect packet"
1065                #device.write('\xc2')
1066               
1067               
1068                return(device)
1069       
1070       
1071        ##################################################################
1072       
1073        def checkBuffer(self):
1074               
1075                if self.DEBUG > 1:
1076                        print "INFO: Buffer size check:",
1077                        print len(self.buffer),
1078                        print "(maximum before reset is %i)" % DEVICE_BUFFER_MAX_SIZE
1079               
1080                if (DEVICE_BUFFER_MAX_SIZE <= len(self.buffer)):
1081                       
1082                        if self.DEBUG:
1083                                print "ERROR: Buffer size has grown too large, resetting"
1084                       
1085                        self.reset()
1086       
1087       
1088        ##################################################################
1089       
1090        def checkReadBuffer(self):
1091               
1092                if self.DEBUG > 1:
1093                        print "INFO: Read buffer timer check"
1094               
1095                current_time = time.time()
1096               
1097                if ((self.parent != None) and \
1098                    (self.parent.protocol != None)):
1099                       
1100                        if (current_time - self.parent.protocol.payload_timestamp > \
1101                                 DEVICE_BUFFER_CHECK_TIMER):
1102                               
1103                                if self.DEBUG:
1104                                        print "ERROR: Read buffer timer has expired, resetting connection"
1105                               
1106                        self.parent.resetDevice()
1107       
1108       
1109        ##################################################################
1110       
1111        def reset(self):
1112               
1113                self.buffer = ''
1114       
1115       
1116        ##################################################################
1117       
1118        def read(self, length=1):
1119               
1120                # Sleep for 2 ms if buffer is empty
1121                # Based on 512 Hz refresh rate of NeuroSky MindSet device
1122                # (1/512) * 1000 = 1.9531250
1123                while len(self.buffer) < length:
1124                        try:
1125                                QtCore.QThread.msleep(2)
1126                        except Exception, e:
1127                                #if self.DEBUG:
1128                                        #print "ERROR: Protocol failed to call QtCore.QThread.msleep(2) in read():",
1129                                        #print e
1130                                pass
1131                       
1132                bytes = self.buffer[:length]
1133               
1134                self.buffer = self.buffer[length:]
1135               
1136                return(bytes)
1137       
1138       
1139        ##################################################################
1140       
1141        def stop(self):
1142               
1143                self.keep_running = False
1144                try:
1145                        self.buffer_check_timer.stop()
1146                except Exception, e:
1147                        if self.DEBUG:
1148                                print "ERROR: Protocol failed to call self.buffer_check_timer.stop() in stop():",
1149                                print e
1150               
1151                try:
1152                        self.read_buffer_check_timer.stop()
1153                except Exception, e:
1154                        if self.DEBUG:
1155                                print "ERROR: Protocol failed to call self.read_buffer_check_timer.stop() in stop():",
1156                                print e
1157               
1158                self.buffer = ''
1159       
1160       
1161        ##################################################################
1162       
1163        def exitThread(self, callThreadQuit=True):
1164               
1165                self.stop()
1166                self.close()
1167               
1168                if callThreadQuit:
1169                        try:
1170                                QtCore.QThread.quit(self)
1171                        except Exception, e:
1172                                if self.DEBUG:
1173                                        print "ERROR: Protocol failed to call QtCore.QThread.quit(self) in exitThread():",
1174                                        print e
1175       
1176       
1177        ##################################################################
1178       
1179        def close(self):
1180               
1181                if self.device != None:
1182                       
1183                        try:
1184                                self.device.close()
1185                        except Exception, e:
1186                                #if self.DEBUG:
1187                                        #print "ERROR: Protocol failed to close device in close():",
1188                                        #print e
1189                                pass
1190       
1191       
1192        ##################################################################
1193       
1194        def run(self):
1195               
1196                if self.device == None:
1197                        self.keep_running = False
1198               
1199                self.buffer = ''
1200               
1201                while self.keep_running:
1202                       
1203                        try:
1204                                #byte = self.device.read()
1205                                byte = self.device.recv(1)
1206                               
1207                                if (len(byte) != 0):
1208                                        if self.DEBUG > 2:
1209                                                print "Device read:",
1210                                                print byte
1211                                               
1212                                        self.buffer += byte
1213                       
1214                        except:
1215                                if self.DEBUG:
1216                                        print "ERROR: failed to read from serial device"
1217                                break
1218               
1219               
1220                self.exitThread()
1221
1222
1223#####################################################################
1224#####################################################################
1225
1226class serialWrapper(serial.Serial):
1227       
1228        #__init__(port=None, baudrate=9600, bytesize=EIGHTBITS, parity=PARITY_NONE, stopbits=STOPBITS_ONE, timeout=None, xonxoff=False, rtscts=False, writeTimeout=None, dsrdtr=False, interCharTimeout=None)
1229       
1230        def recv(self, size=1):
1231               
1232                return(self.read(size))
1233
Note: See TracBrowser for help on using the repository browser.