source: thinkgear_emulator/puzzlebox_thinkgear_serial_protocol.py @ 121

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

thinkgear_emulator/puzzlebox_thinkgear_serial_protocol.py:

  • packet structure properly parsed and checksum'd
  • Property svn:executable set to *
File size: 13.6 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Puzzlebox - Brainstorms - Network - Server - ThinkGear - Serial Protocol
5#
6# Copyright Puzzlebox Productions, LLC (2010)
7#
8# This code is released under the GNU Pulic License (GPL) version 2
9# For more information please refer to http://www.gnu.org/copyleft/gpl.html
10#
11# Last Update: 2010.07.22
12#
13#####################################################################
14# Example:
15#    rfcomm connect rfcomm0 00:13:EF:00:1B:FE 3
16#####################################################################
17
18import sys
19import serial
20
21#from PyQt4 import QtCore, QtNetwork
22
23import puzzlebox_thinkgear_emulator_configuration as configuration
24#import puzzlebox_logger
25
26#####################################################################
27# Globals
28#####################################################################
29
30DEBUG = 2
31
32DEFAULT_SERIAL_PORT_WINDOWS = 'COM2'
33DEFAULT_SERIAL_PORT_LINUX = '/dev/rfcomm0'
34
35if (sys.platform == 'win32'):
36        DEFAULT_SERIAL_PORT = DEFAULT_SERIAL_PORT_WINDOWS
37else:
38        DEFAULT_SERIAL_PORT = DEFAULT_SERIAL_PORT_LINUX
39
40DEFAULT_SERIAL_BAUDRATE = 57600
41
42DEFAULT_MINDSET_ADDRESS = '00:13:EF:00:1B:FE'
43
44#####################################################################
45# Classes
46#####################################################################
47
48class puzzlebox_thinkgear_serial_protocol:
49       
50        def __init__(self, log, \
51                               serial_port=DEFAULT_SERIAL_PORT, \
52                               DEBUG=DEBUG, \
53                               parent=None):
54               
55                self.log = log
56                self.DEBUG = DEBUG
57               
58                self.serial_port = serial_port
59                self.device = None
60                self.buffer = ''
61               
62                self.device = self.initialize_device()
63       
64       
65        ##################################################################
66       
67        def initialize_device(self):
68               
69                baudrate = DEFAULT_SERIAL_BAUDRATE
70                bytesize = 8
71                parity = 'NONE'
72                stopbits = 1
73                software_flow_control = 'f'
74                rts_cts_flow_control = 'f'
75                #timeout = 15
76                timeout = 5
77               
78                # convert bytesize
79                if (bytesize == 5):
80                        init_byte_size = serial.FIVEBITS
81                elif (bytesize == 6):
82                        init_byte_size = serial.SIXBITS
83                elif (bytesize == 7):
84                        init_byte_size = serial.SEVENBITS
85                elif (bytesize == 8):
86                        init_byte_size = serial.EIGHTBITS
87                else:
88                        #self.log.perror("Invalid value for %s modem byte size! Using default (8)" % modem_type)
89                        init_byte_size = serial.EIGHTBITS
90               
91                # convert parity
92                if (parity == 'NONE'):
93                        init_parity = serial.PARITY_NONE
94                elif (parity == 'EVEN'):
95                        init_parity = serial.PARITY_EVEN
96                elif (parity == 'ODD'):
97                        init_parity = serial.PARITY_ODD
98                else:
99                        #self.log.perror("Invalid value for %s modem parity! Using default (NONE)" % modem_type)
100                        init_parity = serial.PARITY_NONE
101               
102                # convert stopbits
103                if (stopbits == 1):
104                        init_stopbits = serial.STOPBITS_ONE
105                elif (stopbits == 2):
106                        init_stopbits = serial.STOPBITS_TWO
107                else:
108                        #self.log.perror("Invalid value for %s modem stopbits! Using default (8)" % modem_type)
109                        init_byte_size = serial.STOPBITS_ONE
110               
111                # convert software flow control
112                if (software_flow_control == 't'):
113                        init_software_flow_control = 1
114                else:
115                        init_software_flow_control = 0
116
117                # convert rts cts flow control
118                if (rts_cts_flow_control == 't'):
119                        init_rts_cts_flow_control = 1
120                else:
121                        init_rts_cts_flow_control = 0
122               
123               
124                # Initialize the modem
125                #self.log.pdebug("Initializing %s modem" % modem_code)
126               
127                device = serial.Serial(port = serial_port, \
128                                            baudrate = baudrate, \
129                                            bytesize = init_byte_size, \
130                                            parity = init_parity, \
131                                            stopbits = init_stopbits, \
132                                            xonxoff = init_software_flow_control, \
133                                            rtscts = init_rts_cts_flow_control, \
134                                            timeout = timeout)
135               
136               
137                return(device)
138       
139       
140        ##################################################################
141       
142        def communicate_with_handsfree_profile(self):
143               
144                #"AT+CKPD=200" - Indicates a Bluetooth button press
145                #"AT+VGM=" - Indicates a microphone volume change
146                #"AT+VGS=" - Indicates a speakerphone volume change
147                #"AT+BRSF=" - The Headset is asking what features are supported
148                #"AT+CIND?" - The Headset is asking about the indicators that are signaled
149                #"AT+CIND=?" - The Headset is asking about the test indicators
150                #"AT+CMER=" - The Headset is asking which indicates are registered for updates
151                #"ATA" - When an incoming call has been answered, usually a Bluetooth button press
152                #"AT+CHUP" - When a call has been hung up, usually a Bluetooth button press
153                #"ATD>" - The Headset is requesting the local device to perform a memory dial
154                #"ATD" - The Headset is requesting to dial the number
155                #"AT+BLDN" - The Headset is requesting to perform last number dialed
156                #"AT+CCWA=" - The Headset has enabled call waiting
157                #"AT+CLIP=" - The Headset has enabled CLI (Calling Line Identification)
158                #"AT+VTS=" - The Headset is asking to send DTMF digits
159                #"AT+CHLD=" - The Headset is asking to put the call on Hold
160                #"AT+BVRA=" - The Headset is requesting voice recognition
161                #"ATH" - Call hang-up
162               
163                #self.device.write('\x29')
164                #self.device.write('AT+BRSF=24\r\n')
165               
166                buffer = ''
167               
168                while True:
169                        reply = self.device.read()
170                       
171                        if (len(reply) != 0):
172                                if DEBUG > 1:
173                                        print reply
174                                buffer += reply
175                       
176                        if buffer == "AT+BRSF=24\r":
177                                print "--> Received:",
178                                print buffer
179                                response = '\r\nOK\r\n'
180                                print "<-- Sending:",
181                                print response.replace('\r\n', '')
182                                self.device.write(response)
183                                buffer = ''
184                       
185                        elif buffer == 'AT+CIND=?\r':
186                                print "--> Received:",
187                                print buffer
188                                # first field indicates that we have cellular service [0-1]
189                                # second field indicates that we're in a call (0 for false) [0-1]
190                                # third field indicates the current call setup (0 for idle) [0-3]
191                                response = '\r\n+CIND: 1,0,0\r\n'
192                                print "<-- Sending:",
193                                print response.replace('\r\n', '')
194                                self.device.write(response)
195                                response = '\r\nOK\r\n'
196                                print "<-- Sending:",
197                                print response.replace('\r\n', '')
198                                self.device.write(response)
199                                buffer = ''
200                       
201                        elif buffer == 'AT+CMER=3, 0, 0, 1\r':
202                                print "--> Received:",
203                                print buffer
204                                response = '\r\nOK\r\n'
205                                print "<-- Sending:",
206                                print response.replace('\r\n', '')
207                                self.device.write(response)
208                                response = '\r\n+CIEV:2,1\r\n'
209                                print "<-- Sending:",
210                                print response.replace('\r\n', '')
211                                self.device.write(response)
212                                response = '\r\n+CIEV:3,0\r\n'
213                                print "<-- Sending:",
214                                print response.replace('\r\n', '')
215                                self.device.write(response)
216                                buffer = ''
217                       
218                        elif buffer == 'AT+VGS=15\r':
219                                print "--> Received:",
220                                print buffer
221                                response = '\r\nOK\r\n'
222                                print "<-- Sending:",
223                                print response.replace('\r\n', '')
224                                self.device.write(response)
225                                buffer = ''
226                       
227                        elif buffer == 'AT+VGM=08\r':
228                                print "--> Received:",
229                                print buffer
230                                response = '\r\nOK\r\n'
231                                print "<-- Sending:",
232                                print response.replace('\r\n', '')
233                                self.device.write(response)
234                                buffer = ''
235                               
236                                self.device.close()
237                                sys.exit()
238       
239       
240        ##################################################################
241       
242        def process_data_payload(self, data_payload):
243               
244                pass
245       
246       
247        ##################################################################
248       
249        def process_packet(self, packet):
250               
251                valid_length = False
252                valid_checksum = False
253               
254                if self.DEBUG > 1:
255                        print packet
256               
257               
258                # SPEC: [PLENGTH] byte indicates the length, in bytes, of the
259                # Packet's Data Payload [PAYLOAD...] section, and may be any value
260                # from 0 up to 169. Any higher value indicates an error
261                # (PLENGTH TOO LARGE). Be sure to note that [PLENGTH] is the length
262                # of the Packet's Data Payload, NOT of the entire Packet.
263                # The Packet's complete length will always be [PLENGTH] + 4.
264
265                packet_length = packet[2]
266                packet_length = packet_length.encode("hex")
267                packet_length = int(packet_length, 16)
268               
269                if ((packet_length <= 169) and \
270                         (packet_length + 4) == (len(packet))):
271                        if self.DEBUG > 1:
272                                print "packet length correct"
273                        valid_length = True
274                else:
275                        if self.DEBUG:
276                                print "ERROR: packet length bad"
277               
278               
279                if valid_length:
280                       
281                        data_payload = packet[3:-1]
282                       
283                        # SPEC: The [CHKSUM] Byte must be used to verify the integrity of the
284                        # Packet's Data Payload. The Payload's Checksum is defined as:
285                        #  1. summing all the bytes of the Packet's Data Payload
286                        #  2. taking the lowest 8 bits of the sum
287                        #  3. performing the bit inverse (one's compliment inverse)
288                        #     on those lowest 8 bits
289                       
290                        packet_checksum = packet[-1]
291                        packet_checksum = packet_checksum.encode("hex")
292                        packet_checksum = int(packet_checksum, 16)
293                       
294                        payload_checksum = 0
295                        for byte in data_payload:
296                                value = byte.encode("hex")
297                                value = int(value, 16)
298                                payload_checksum += value
299                       
300                        # Take the lowest 8 bits of the calculated payload_checksum
301                        # and invert them. Serious C code mojo.
302                        payload_checksum &= 0xff
303                        payload_checksum = ~payload_checksum & 0xff
304                       
305                       
306                        if packet_checksum != payload_checksum:
307                                if self.DEBUG > 1:
308                                        print "ERROR: packet checksum does not match"
309                                        print "       packet_checksum:",
310                                        print packet_checksum
311                                        print "       payload_checksum:",
312                                        print payload_checksum
313                        else:
314                                valid_checksum = True
315                                if self.DEBUG > 1:
316                                        print "packet checksum correct"
317                                       
318                                self.process_data_payload(data_payload)
319               
320               
321                return(valid_length, valid_checksum)
322       
323       
324        ##################################################################
325       
326        def process_byte(self, byte):
327               
328                self.buffer += byte
329                self.byte_count += 1
330               
331                if (len(self.buffer) > 2):
332                        if self.buffer[-2:] == '\xAA\xAA':
333                                # New packet header found
334                               
335                                (valid_length, valid_checksum) = self.process_packet(self.buffer[:-2])
336                               
337                                if ((valid_length) or \
338                                         (len(self.buffer) > 173)):
339                                        # If processing the packet returned valid checks then we
340                                        # restart reading the buffer to examine new packages.
341                                        # However if the current buffer size is larger than 173 bytes
342                                        # (the maximum possible packet length according to the protocol
343                                        # specification document, then we still want to reset the buffer
344                                        # because an error has occured in the stream)
345                                        self.buffer = '\xAA\xAA'
346                                        self.packet_count += 1
347               
348               
349                elif self.buffer == "AT+BRSF=24\r":
350                        # This string is received when connecting to the wrong
351                        # Bluetooth serial device channel of the ThinkGear device
352                        if self.DEBUG:
353                                print "--> Received:",
354                                print self.buffer
355                        response = '\r\nOK\r\n'
356                        if self.DEBUG:
357                                print "<-- Sending:",
358                                print response.replace('\r\n', '')
359                        self.device.write(response)
360                        self.buffer = ''
361                       
362                        if self.DEBUG:
363                                print "ERROR: Serial device connected to wrong channel"
364                                print "(Consider changing from channel 1 to channel 3",
365                                print " or another COM port for example)"
366                       
367                        self.device.close()
368                        sys.exit()
369               
370               
371                if ((self.DEBUG > 1) and \
372                         ((self.byte_count >= 8192) or \
373                          (self.packet_count >= 10))):
374                        if self.DEBUG:
375                                print "max debugging count reached, disconnecting"
376                        self.device.close()
377                        sys.exit()
378       
379       
380        ##################################################################
381       
382        def start(self):
383               
384                self.buffer = ''
385                self.packet_count = 0
386                self.byte_count = 0
387               
388                while True:
389                       
390                        byte = self.device.read()
391                       
392                        if (len(byte) != 0):
393                                if DEBUG > 2:
394                                        print byte
395                                       
396                                self.process_byte(byte)
397
398
399#####################################################################
400#####################################################################
401
402        def read_response(self, modem):
403
404                # This function is passed to the modem each time the software
405                # has asked the modem to perform a particular function.
406                # It looks for an "OK" message from the modem, and stores all
407                # data read back from the modem in a line, one entry per response
408                # line. The function returns that list of responses, as well as
409                # a status flag indicating whether the modem timed out while
410                # waiting for the "OK" response. The timeout gets set during
411                # modem initialization, based on the value stored in the database
412
413                modem_timed_out = 0
414
415                #self.log.pdebug("Waiting for 'OK' from %s modem..." % self.modem_type)
416
417                response = []
418
419                while 'OK\r\n' not in response:
420                        reply = modem.readline()
421                        if (len(reply) == 0):
422                                #self.log.perror("Modem timeout has been exceeded")
423                                modem_timed_out = 1
424                                break # The timeout has been exceeded
425                        else:
426                                if (reply != '\r\n'):
427                                        log_reply = string.replace(reply, '\r', '')
428                                        log_reply = string.replace(log_reply, '\n', '')
429                                        #self.log.pdebug(log_reply)
430                                response.append(reply)
431
432                #self.log.debug(response)
433                if self.DEBUG:
434                        print "DEBUG:",
435                        print response
436
437                return (response, modem_timed_out)
438
439
440        ##################################################################
441
442        def test_modem(self, modem):
443
444                # This function simply sends an "AT" command to the modem
445                # and checks for a reponse. It is useful for verifying
446                # that the modem is attached, configured, and functioning correctly
447
448                modem.write("AT\r")
449                (response, modem_timed_out) = read_response(modem)
450
451
452#####################################################################
453# Main
454#####################################################################
455
456if __name__ == '__main__':
457       
458        # Perform correct KeyboardInterrupt handling
459        #signal.signal(signal.SIGINT, signal.SIG_DFL)
460       
461        #log = puzzlebox_logger.puzzlebox_logger(logfile='server_thinkgear')
462        log = None
463       
464        # Collect default settings and command line parameters
465        serial_port = DEFAULT_SERIAL_PORT
466       
467        for each in sys.argv:
468               
469                if each.startswith("--port="):
470                        serial_port = each[ len("--port="): ]
471       
472       
473        #app = QtCore.QCoreApplication(sys.argv)
474       
475        server = puzzlebox_thinkgear_serial_protocol(log, \
476                                                           serial_port, \
477                                                           DEBUG=DEBUG)
478       
479        #sys.exit(app.exec_())
480       
481        server.start()
482       
483       
Note: See TracBrowser for help on using the repository browser.