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

Last change on this file since 313 was 313, checked in by sc, 9 years ago
  • Updates to support plugins in Puzzlebox Jigsaw
  • Property svn:executable set to *
File size: 35.1 KB
RevLine 
[119]1# -*- coding: utf-8 -*-
[163]2
[282]3# Copyright Puzzlebox Productions, LLC (2010-2011)
[119]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
[163]8# Old Classes:
9#       puzzlebox_synapse_protocol_thinkgear = ProtocolHandler
10#       puzzlebox_synapse_protocol_thinkgear_serial_device = SerialDevice
11
12__changelog__ = """\
[304]13Last Update: 2011.12.04
[163]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
[272]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
[163]61
[298]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
[163]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 ###
[192]84import sys, time
[125]85import signal
[181]86import serial
[134]87
[140]88if (sys.platform != 'win32'):
89        import bluetooth
90
[300]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:
[304]99                print "ERROR: Exception importing PySide:",
[300]100                print e
101                configuration.ENABLE_PYSIDE = False
102        else:
[307]103                print "INFO: [Synapse:Protocol] Using PySide module"
[300]104
105if not configuration.ENABLE_PYSIDE:
[307]106        print "INFO: [Synapse:Protocol] Using PyQt4 module"
[130]107        from PyQt4 import QtCore
[146]108
[119]109
[300]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
[163]121### GLOBALS ###
[119]122
[304]123DEBUG = configuration.DEBUG
[119]124
[130]125THINKGEAR_DEVICE_SERIAL_PORT = configuration.THINKGEAR_DEVICE_SERIAL_PORT
[164]126#THINKGEAR_DEVICE_BLUETOOTH_ADDRESS = \
127        #configuration.THINKGEAR_DEVICE_BLUETOOTH_ADDRESS
[120]128
[272]129#DEFAULT_SERIAL_BAUDRATE = 57600
130DEFAULT_SERIAL_BAUDRATE = 115200
[119]131
[139]132#THINKGEAR_DEVICE_BLUETOOTH_ADDRESS = '00:13:EF:00:1B:FE'
133THINKGEAR_DEVICE_BLUETOOTH_CHANNEL = 3
[119]134
[274]135THINKGEAR_DEVICE_AUTOCONNECT_INTERVAL = 4 # seconds between attempting
136                                          # to send auto-connect packets
[272]137THINKGEAR_DEVICE_ID = configuration.THINKGEAR_DEVICE_ID
[273]138#THINKGEAR_DEFAULT_DEVICE_ID = '\x7d\x68'
139#THINKGEAR_DEFAULT_DEVICE_ID = '\xe4\x68'
[272]140
[124]141PROTOCOL_SYNC = '\xAA'
[122]142PROTOCOL_EXCODE = '\x55'
143
[150]144EEG_POWER_BAND_ORDER = configuration.THINKGEAR_EEG_POWER_BAND_ORDER
[129]145
[126]146DEVICE_BUFFER_CHECK_TIMER = 60 * 1000 # Check buffer size once every minute
[274]147DEVICE_READ_BUFFER_CHECK_TIMER = 10 * 1000 # Check buffer size once x seconds
[126]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
[123]160DEBUG_BYTE_COUNT = 819200
[127]161DEBUG_PACKET_COUNT = 1024
[122]162
[163]163### CLASSES ###
[119]164
[163]165class ProtocolHandler(QtCore.QThread):
[119]166       
167        def __init__(self, log, \
[125]168                               serial_device, \
[272]169                               device_id=THINKGEAR_DEVICE_ID, \
[294]170                               device_model=None, \
[119]171                               DEBUG=DEBUG, \
172                               parent=None):
173               
[125]174                QtCore.QThread.__init__(self,parent)
175               
[119]176                self.log = log
177                self.DEBUG = DEBUG
[147]178                self.parent = parent
[119]179               
[272]180                self.device_id = device_id
[294]181                self.device_model = device_model
[272]182               
[119]183                self.device = None
[121]184                self.buffer = ''
[274]185                self.payload_timestamp = time.time()
[119]186               
[125]187                self.device = serial_device
[273]188                self.auto_connect_timestamp = time.time()
[127]189               
[130]190                self.data_packet = {}
191                self.data_packet['eegPower'] = {}
192                self.data_packet['eSense'] = {}
193               
[192]194                self.packet_count = 0
[191]195                self.bad_packets = 0
[192]196                self.session_start_time = None
[191]197               
[127]198                self.keep_running = True
[119]199       
200       
201        ##################################################################
202       
[129]203        def communicateWithHandsfreeProfile(self):
[119]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):
[120]233                                if DEBUG > 1:
234                                        print reply
[119]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                               
[133]297                               
298                                self.exitThread()
299                                #self.keep_running = False
300                                #self.device.stop()
301                                #QtCore.QThread.quit(self)
[127]302                                #sys.exit()
[121]303       
304       
305        ##################################################################
306       
[127]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       
[129]333        def processRawEEGValue(self, data_values):
[127]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       
[129]382        def processAsicEegPower(self, data_values):
[127]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():
[163]414                        eegPower[key] = self.hexStringEndianSwap(eegPower[key])
[127]415                        #eegPower[key] = eegPower[key].encode("hex")
416                        eegPower[key] = int(eegPower[key], 16)
417               
418               
419                return(eegPower)
420       
421       
422        ##################################################################
423       
[198]424        def processDataRow(self, \
425                           extended_code_level, \
426                           code, \
427                           length, \
428                           data_values, \
429                           timestamp):
[121]430               
[123]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])
[294]456                           Any    0xAA        - NEVER USED (reserved for [SYNC])
[123]457               
[294]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               
[129]468                packet_update = {}
[123]469               
[198]470                packet_update['timestamp'] = timestamp
471               
[192]472                self.packet_count += 1
473               
[123]474                if extended_code_level == 0:
475                       
476                        if code == '02':
477                                poor_signal_quality = int(data_values, 16)
[124]478                                if self.DEBUG > 1:
[128]479                                        print # Empty line at the beginning of most packets
[129]480                                        print "poorSignalLevel:",
[124]481                                        print poor_signal_quality
[129]482                               
483                                packet_update['poorSignalLevel'] = poor_signal_quality
[123]484                       
[129]485                       
[123]486                        elif code == '04':
487                                attention = int(data_values, 16)
[124]488                                if self.DEBUG > 1:
[129]489                                        print "attention:",
[124]490                                        print attention
[129]491                               
492                                packet_update['eSense'] = {}
493                                packet_update['eSense']['attention'] = attention
[123]494                       
[129]495                       
[123]496                        elif code == '05':
497                                meditation = int(data_values, 16)
[124]498                                if self.DEBUG > 1:
[129]499                                        print "meditation:",
[124]500                                        print meditation
[129]501                               
502                                packet_update['eSense'] = {}
503                                packet_update['eSense']['meditation'] = meditation
[123]504                       
[129]505                       
[123]506                        elif code == '16':
507                                blink_strength = int(data_values, 16)
[124]508                                if self.DEBUG > 1:
[129]509                                        print "blinkStrength:",
[124]510                                        print blink_strength
[129]511                               
512                                packet_update['blinkStrength'] = blink_strength
[123]513                       
[129]514                       
[123]515                        elif code == '80':
[192]516                                #self.packet_count -= 1 # We don't count raw EEG packets for Interface
[123]517                                raw_wave_value = data_values
[128]518                                if self.DEBUG > 3:
[124]519                                        print "Raw EEG:",
520                                        print raw_wave_value
[129]521                                raw_eeg_value = self.processRawEEGValue(data_values)
[127]522                                if self.DEBUG > 2:
523                                        print "Raw EEG Value:",
524                                        print raw_eeg_value
[129]525                               
526                                packet_update['rawEeg'] = raw_eeg_value
[123]527                       
[129]528                       
[123]529                        elif code == '83':
530                                asic_eeg_power = data_values
[128]531                                if self.DEBUG > 2:
[124]532                                        print "ASIC_EEG_POWER:",
533                                        print asic_eeg_power
[129]534                                eegPower = self.processAsicEegPower(data_values)
[127]535                                if self.DEBUG > 1:
[129]536                                        for key in EEG_POWER_BAND_ORDER:
[127]537                                                print "%s: %i" % (key, eegPower[key])
[129]538                               
539                                packet_update['eegPower'] = {}
540                                for key in eegPower.keys():
541                                        packet_update['eegPower'][key] = eegPower[key]
[127]542                       
[129]543                       
[294]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                       
[272]585                        elif code == 'd4':
[273]586                                current_time = time.time()
587                                if current_time - self.auto_connect_timestamp > \
588                                        THINKGEAR_DEVICE_AUTOCONNECT_INTERVAL:
589                                        if self.DEBUG:
[294]590                                                print "INFO: ThinkGear device in standby/scan mode. Writing auto-connect packet."
[273]591                                        self.auto_connect_timestamp = current_time
592                                        self.device.device.write('\xc2')
593                                        #self.device.device.write('\xc0\xe4\x68')
[272]594                       
595                       
[123]596                        else:
[192]597                                self.bad_packets += 1
[123]598                                if self.DEBUG:
[271]599                                        print "ERROR: data payload row code not matched:",
600                                        print code
[129]601               
602               
603                return(packet_update)
[121]604       
605       
606        ##################################################################
607       
[198]608        def processDataPayload(self, data_payload, timestamp):
[122]609               
610                '''A DataRow consists of bytes in the following format:
611                ([EXCODE]...) [CODE] ([VLENGTH])   [VALUE...]
612                ____________________ ____________ ___________
613                ^^^^(Value Type)^^^^ ^^(length)^^ ^^(value)^^'''
614               
[129]615               
[128]616                if self.DEBUG > 3:
[122]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                       
[128]651                        if self.DEBUG > 3:
[123]652                                print "EXCODE level:",
653                                print extended_code_level,
654                                print " CODE:",
655                                print code,
656                                print " length:",
657                                print length
[127]658                                #print type(code)
[122]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
[275]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
[122]674                                data_values += value.encode("hex")
675                                value_index += 1
676                       
[128]677                        if self.DEBUG > 3:
[123]678                                print "Data Values:",
679                                print data_values
680                                print
[122]681                       
[129]682                        packet_update = self.processDataRow(extended_code_level, \
[130]683                                                            code, \
684                                                            length, \
[198]685                                                            data_values, \
686                                                            timestamp)
[123]687                       
[130]688                        self.updateDataPacket(packet_update)
[129]689                       
690                       
[122]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       
[129]699        def parseStream(self):
[124]700               
[130]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               
[124]707                # Loop forever, parsing one Packet per loop...
708                packet_count = 0
709               
[127]710                while self.keep_running:
[124]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                       
[274]722                        self.payload_timestamp = time.time()
[124]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):
[191]738                                self.bad_packets += 1
[124]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:
[192]778                                self.bad_packets += 1
[124]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
[126]785                                       
[129]786                                        #self.device.checkBuffer()
[124]787                               
788                                continue
789                       
790                       
[119]791                        else:
[124]792                                # Since [CKSUM] is OK, parse the Data Payload
[128]793                                if self.DEBUG > 3:
[124]794                                        print "packet checksum correct"
795                               
796                               
[274]797                                self.processDataPayload(data_payload, self.payload_timestamp)
[127]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()
[125]808       
809       
810        ##################################################################
811       
[130]812        def updateDataPacket(self, packet_update):
[129]813               
[130]814                if 'eSense' in packet_update.keys():
[152]815                        process_packet = {'eSense': {}}
[130]816                        for key in packet_update['eSense'].keys():
817                                self.data_packet['eSense'][key] = packet_update['eSense'][key]
[152]818                                process_packet['eSense'][key] = packet_update['eSense'][key]
[130]819               
820                else:
821                        self.data_packet.update(packet_update)
[152]822                        process_packet = packet_update
[130]823               
[152]824               
[200]825                process_packet['timestamp'] = packet_update['timestamp']
826               
827               
[131]828                if self.DEBUG > 3:
829                        print self.data_packet
[151]830               
831               
[152]832                if (self.parent != None):
[286]833                       
834                        # NOTE: is it possible this call is blocking the Protocol
835                        #       thread from continuing to parse data?
836                       
[152]837                        self.parent.processPacketThinkGear(process_packet)
[129]838       
839       
840        ##################################################################
841       
[313]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       
[125]858        def run(self):
859               
[192]860                self.packet_count = 0
[191]861                self.bad_packets = 0
[192]862                self.session_start_timestamp = time.time()
[191]863               
[298]864                if self.device != None and self.device.device != None:
[294]865                        if self.device_model == 'NeuroSky MindWave':
866                                if self.DEBUG:
[298]867                                        print "INFO: ThinkGear device model MindWave selected. Writing disconnect packet."
868                                self.device.device.write('\xc1')
869                                if self.DEBUG:
[294]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
[291]875                        self.parseStream()
[133]876       
877       
878        ##################################################################
879       
880        def exitThread(self, callThreadQuit=True):
881               
[313]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
[299]892               
[313]893                self.disconnectHardware()
[299]894               
[133]895                try:
896                        self.device.stop()
897                except:
898                        pass
899               
[299]900               
[133]901                if callThreadQuit:
902                        QtCore.QThread.quit(self)
[119]903
904
[191]905#####################################################################
906#####################################################################
[125]907
[163]908class SerialDevice(QtCore.QThread):
[125]909       
910        def __init__(self, log, \
[139]911                               device_address=THINKGEAR_DEVICE_SERIAL_PORT, \
[125]912                               DEBUG=DEBUG, \
913                               parent=None):
914               
[274]915                QtCore.QThread.__init__(self, parent)
[125]916               
917                self.log = log
918                self.DEBUG = DEBUG
[274]919                self.parent = parent
[125]920               
[139]921                self.device_address = device_address
[125]922                self.device = None
923                self.buffer = ''
924               
[139]925                if (self.device_address.count(':') == 5):
926                        # Device address is a Bluetooth MAC address
[271]927                        if self.DEBUG:
928                                print "Initializing Bluetooth Device",
929                                print self.device_address
[139]930                        self.device = self.initializeBluetoothDevice()
931                else:
932                        # Device address is a serial port address
[271]933                        if self.DEBUG:
934                                print "Initializing Serial Device",
935                                print self.device_address
[139]936                        self.device = self.initializeSerialDevice()
[126]937               
938                self.buffer_check_timer = QtCore.QTimer()
939                QtCore.QObject.connect(self.buffer_check_timer, \
940                                       QtCore.SIGNAL("timeout()"), \
[129]941                                       self.checkBuffer)
942                self.buffer_check_timer.start(DEVICE_BUFFER_CHECK_TIMER)
[127]943               
[274]944                self.read_buffer_check_timer = QtCore.QTimer()
945                QtCore.QObject.connect(self.read_buffer_check_timer, \
946                                       QtCore.SIGNAL("timeout()"), \
947                                       self.checkReadBuffer)
[275]948#               self.read_buffer_check_timer.start(DEVICE_READ_BUFFER_CHECK_TIMER)
[274]949               
[127]950                self.keep_running = True
[125]951       
952       
953        ##################################################################
954       
[134]955        def initializeBluetoothDevice(self):
956               
957                socket = bluetooth.BluetoothSocket( bluetooth.RFCOMM )
958               
[139]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               
[134]969                return socket
970       
[139]971       
[134]972        ##################################################################
973       
[139]974        def initializeSerialDevice(self):
[125]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               
[163]1030               
[128]1031                try:
[164]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, \
[128]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)
[125]1049               
[128]1050                except Exception, e:
1051                        if self.DEBUG:
1052                                print "ERROR:",
[164]1053                                print e,
1054                                print self.device_address
[282]1055                                #sys.exit()
1056                                return(None)
[125]1057               
[128]1058               
1059                device.flushInput()
[133]1060                #device.flushOutput()
[128]1061               
1062               
[272]1063                #if self.DEBUG:
1064                        #print "Writing device connect packet"
1065                #device.write('\xc2')
1066               
1067               
[125]1068                return(device)
1069       
1070       
1071        ##################################################################
1072       
[129]1073        def checkBuffer(self):
[126]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       
[274]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       
[126]1111        def reset(self):
1112               
1113                self.buffer = ''
1114       
1115       
1116        ##################################################################
1117       
[125]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:
[286]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
[125]1131                       
1132                bytes = self.buffer[:length]
1133               
1134                self.buffer = self.buffer[length:]
1135               
1136                return(bytes)
1137       
1138       
1139        ##################################################################
1140       
[127]1141        def stop(self):
1142               
1143                self.keep_running = False
[133]1144                self.buffer_check_timer.stop()
[274]1145                self.read_buffer_check_timer.stop()
[286]1146                self.buffer = ''
[125]1147       
[127]1148       
1149        ##################################################################
1150       
[133]1151        def exitThread(self, callThreadQuit=True):
1152               
1153                self.stop()
1154                self.close()
1155               
1156                if callThreadQuit:
1157                        QtCore.QThread.quit(self)
1158       
1159       
1160        ##################################################################
1161       
[127]1162        def close(self):
1163               
[282]1164                if self.device != None:
[293]1165                       
1166                        try:
1167                                self.device.close()
1168                        except Exception, e:
1169                                #if self.DEBUG:
1170                                        #print "ERROR: Protocol failed to close device in close():",
1171                                        #print e
1172                                pass
[125]1173       
1174       
1175        ##################################################################
1176       
1177        def run(self):
1178               
[291]1179                if self.device == None:
1180                        self.keep_running = False
1181               
[125]1182                self.buffer = ''
1183               
[129]1184                while self.keep_running:
[125]1185                       
[127]1186                        try:
[134]1187                                #byte = self.device.read()
1188                                byte = self.device.recv(1)
[133]1189                               
1190                                if (len(byte) != 0):
1191                                        if self.DEBUG > 2:
1192                                                print "Device read:",
1193                                                print byte
1194                                               
1195                                        self.buffer += byte
1196                       
[127]1197                        except:
1198                                if self.DEBUG:
1199                                        print "ERROR: failed to read from serial device"
1200                                break
1201               
1202               
[133]1203                self.exitThread()
[177]1204
1205
1206#####################################################################
1207#####################################################################
1208
1209class serialWrapper(serial.Serial):
[193]1210       
[177]1211        #__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)
[193]1212       
[177]1213        def recv(self, size=1):
[193]1214               
[177]1215                return(self.read(size))
1216
Note: See TracBrowser for help on using the repository browser.