source: trunk/brainstorms/Puzzlebox/Brainstorms/Helicopter_Control.py @ 227

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

trunk/brainstorms/Puzzlebox/Brainstorms/Wheelchair_Control.py:

  • intitial checkin

trunk/brainstorms/Puzzlebox/Brainstorms/Helicopter_Control.py:

  • unnecessary imports removed
  • Property svn:executable set to *
File size: 19.8 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Puzzlebox - Brainstorms - Helicopter Control
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__changelog__ = """
12Last Update: 2010.11.22
13
14"""
15
16import sys, time
17import signal
18import serial
19
20try:
21        import PySide as PyQt4
22        from PySide import QtCore
23except:
24        print "Using PyQt4 module"
25        from PyQt4 import QtCore
26else:
27        print "Using PySide module"
28
29#import Configuration as configuration
30
31#import Puzzlebox.Synapse.Protocol as protocol
32#from Puzzlebox.Synapse import Protocol
33
34#####################################################################
35# Globals
36#####################################################################
37
38DEBUG = 2
39
40DEFAULT_COMMAND = 'dump_packets'
41
42SERIAL_DEVICE = '/dev/ttyUSB0'
43#SERIAL_DEVICE = '/dev/ttyACM0'
44#DEFAULT_SERIAL_BAUDRATE = 115200 # This is the closest "standard" baud rate the USB-to-Serial device will support
45#DEFAULT_SERIAL_BAUDRATE = 125000 # This is the speed reported by the forum post
46#DEFAULT_SERIAL_BAUDRATE = 128000 # This is the next closest somewhat commonly-found baud rate (though not supported by device)
47DEFAULT_SERIAL_BAUDRATE = 133333 # This is the speed reported by the logic analyzer
48#DEFAULT_SERIAL_BAUDRATE = 230400 # This is the next highest "standard" baud rate the USB-to-Serial device will support
49DEFAULT_MODE = 'read'
50
51PROTOCOL_SYNC_TIME = 5
52PROTOCOL_SYNC_HEAD1 = '\x00'
53PROTOCOL_SYNC_HEAD2 = '\x00'
54PROTOCOL_ADD_SYNC_TO_HEAD = False
55PROTOCOL_ADD_SYNC_TO_TAIL = False
56PACKET_LENGTH = 14
57PAYLOAD_MINIMUM_LENGTH = 12
58PACKET_READ_SIZE = 14
59ECHO_ON = False
60
61DEVICE_BUFFER_TIMER = 22  # Frame cycle 22ms
62
63COMMAND_PACKET = {
64        'neutral':        '\x00\x00\x00\xfa\x05\xc5\x09\xde\x0e\x0b\x13\x54\x14\xaa',  # default neutral setting to use for all commands
65        'no_thrust':      '\x00\x00\x00\x5a\x05\xc5\x09\xde\x0e\x0b\x13\x54\x14\xaa',  # lowest trim setting for throttle
66        'minimum_thrust': '\x00\x00\x00\xca\x05\xc5\x09\xde\x0e\x0b\x13\x54\x14\xaa',  # lowest trim level at which throttle kicks in
67        'minimum_thrust_minus_one': '\x00\x00\x00\xc6\x05\xc5\x09\xde\x0e\x0b\x13\x54\x14\xaa',  # lowest trim level at which throttle kicks in
68        'maximum_thrust': '\x00\x00\x03\x54\x05\xc5\x09\xde\x0e\x0b\x13\x54\x14\xaa',  # maximum possible throttle and trim
69        'fifty_percent_thrust': '\x00\x00\x01\x7d\x05\xc5\x09\xde\x0e\x0b\x13\x54\x14\xaa', # calculated 50% throttle
70        'test_packet':    '\x00\x00\x03\x54\x06\x15\x09\xca\x0e\x2f\x13\x54\x14\xaa', # test packet from saleae logic screenshot
71        'maximum_forward': '\x00\x00\x00\x5a\x05\xc5\x0b\x54\x0e\x0b\x13\x54\x14\xaa', # maximum possible elevator and trim
72            'fly_forward': '\x00\x00\x01\x7d\x05\xc5\x0a\xde\x0e\x0b\x13\x54\x14\xaa', \
73}
74
75DEFAULT_COMMAND_PACKET = COMMAND_PACKET['neutral']
76
77#####################################################################
78# Classes
79#####################################################################
80
81class puzzlebox_brainstorms_helicopter_control(QtCore.QThread):
82       
83        def __init__(self, \
84                     device_address=SERIAL_DEVICE, \
85                     command=DEFAULT_COMMAND, \
86                     DEBUG=DEBUG, \
87                     parent=None):
88               
89                QtCore.QThread.__init__(self, parent)
90               
91                self.log = None
92                self.DEBUG = DEBUG
93                self.parent = parent
94               
95                self.device_address = device_address
96                self.command = command
97                self.mode = DEFAULT_MODE
98               
99                self.serial_device = None
100                self.protocol = None
101               
102               
103                #self.configureRemote()
104       
105       
106        ##################################################################
107       
108        def configureRemote(self):
109               
110                self.serial_device = \
111                        SerialDevice( \
112                                self.log, \
113                                device_address=self.device_address, \
114                                mode=self.mode, \
115                                DEBUG=self.DEBUG, \
116                                parent=self)
117               
118                self.serial_device.start()
119               
120               
121                self.protocol = \
122                        ProtocolHandler( \
123                                self.log, \
124                                self.serial_device, \
125                                mode=self.mode, \
126                                DEBUG=self.DEBUG, \
127                                parent=self)
128               
129                self.protocol.start()
130       
131       
132        ##################################################################
133       
134        def processPacket(self, packet):
135               
136                if self.DEBUG:
137                        print "data_payload:",
138                        #print packet['data_payload']
139                        print packet['data_payload'].encode("hex")
140                       
141                        #if packet['data_payload'].encode("hex") == '80acdf22cdb08b8d54':
142                                #print True
143                                #import cPickle as pickle
144                                #file = open('packet.data', 'w')
145                                #pickle.dump(packet['data_payload'], file)
146                                #file.close()
147                                #sys.exit(app.exec_())
148               
149                #if (packet != {}):
150                        #self.packet_queue.append(packet)
151                        ###self.sendPacketQueue()
152               
153                if (self.parent != None):
154                        self.parent.processPacket(self.protocol.data_packet)
155       
156       
157        ##################################################################
158       
159        def dump_packets(self):
160               
161                pass
162       
163       
164        ##################################################################
165       
166        def neutral(self):
167               
168                if self.DEBUG:
169                        print "--> Command: neutral"
170               
171                #self.protocol.command_packet = COMMAND_PACKET['neutral']
172                self.protocol.command_packet = COMMAND_PACKET['no_thrust']
173       
174       
175        ##################################################################
176       
177        def test_packet(self):
178               
179                if self.DEBUG:
180                        print "--> Command: test_packet"
181               
182                self.protocol.command_packet = COMMAND_PACKET['test_packet']
183       
184       
185        ##################################################################
186       
187        def test_mode(self):
188               
189                if self.DEBUG:
190                        print "--> Command: test_mode"
191               
192                self.protocol.command_packet = COMMAND_PACKET['no_thrust']
193                QtCore.QThread.msleep(PROTOCOL_SYNC_TIME * 1000) # 4 seconds minimum to sync
194               
195                self.protocol.command_packet = COMMAND_PACKET['minimum_thrust']
196                QtCore.QThread.msleep(2 * 1000) # 1 second
197               
198                self.protocol.command_packet = COMMAND_PACKET['no_thrust']
199       
200       
201        ##################################################################
202       
203        def hover(self, duration=2):
204               
205                if self.DEBUG:
206                        print "--> Command: hover"
207               
208                self.protocol.command_packet = COMMAND_PACKET['no_thrust']
209                QtCore.QThread.msleep(PROTOCOL_SYNC_TIME * 1000) # 4 seconds minimum to sync
210               
211                self.protocol.command_packet = COMMAND_PACKET['fifty_percent_thrust']
212                QtCore.QThread.msleep(duration * 1000)
213               
214                self.protocol.command_packet = COMMAND_PACKET['no_thrust']
215       
216       
217        ##################################################################
218       
219        def fly_forward(self, duration=2):
220               
221                if self.DEBUG:
222                        print "--> Command: fly_forward"
223               
224                self.protocol.command_packet = COMMAND_PACKET['no_thrust']
225                QtCore.QThread.msleep(PROTOCOL_SYNC_TIME * 1000)
226               
227                self.protocol.command_packet = COMMAND_PACKET['fly_forwardsvn']
228                QtCore.QThread.msleep(duration * 1000)
229               
230                self.protocol.command_packet = COMMAND_PACKET['no_thrust']
231       
232       
233        ##################################################################
234       
235        def processCommand(self):
236               
237                if (self.command == 'dump_packets') or (self.command == 'read'):
238                        self.mode = 'read'
239                else:
240                        self.mode = 'write'
241               
242               
243                self.configureRemote()
244               
245               
246                if (self.command == 'dump_packets'):
247                        self.mode = 'read'
248                        self.dump_packets()
249               
250                elif (self.command == 'neutral'):
251                        self.mode = 'write'
252                        self.neutral()
253               
254                elif (self.command == 'test_packet'):
255                        self.mode = 'write'
256                        self.test_packet()
257               
258                elif (self.command == 'test_mode'):
259                        self.mode = 'write'
260                        self.test_mode()
261               
262                elif (self.command == 'hover'):
263                        self.mode = 'write'
264                        self.hover()
265               
266                elif (self.command == 'fly_forward'):
267                        self.mode = 'write'
268                        self.fly_forward(duration=2)
269       
270       
271        ##################################################################
272       
273        def stop(self):
274               
275                #self.connection.close()
276                pass
277       
278       
279        ##################################################################
280       
281        def run(self):
282               
283                if self.DEBUG:
284                        print "<---- [%s] Main thread running" % "Helicopter Remote"
285               
286               
287                self.processCommand()
288               
289                self.exec_()
290       
291       
292        ##################################################################
293       
294        def exitThread(self, callThreadQuit=True):
295               
296                try:
297                        self.emulationTimer.stop()
298                except:
299                        pass
300               
301                if self.serial_device != None:
302                        self.serial_device.exitThread()
303               
304                if self.protocol != None:
305                        self.protocol.exitThread()
306               
307                self.socket.close()
308               
309                if callThreadQuit:
310                        QtCore.QThread.quit(self)
311               
312                if self.parent == None:
313                        sys.exit()
314
315
316#####################################################################
317#####################################################################
318
319class ProtocolHandler(QtCore.QThread):
320       
321        def __init__(self, log, \
322                               serial_device, \
323                               mode=DEFAULT_MODE, \
324                               DEBUG=DEBUG, \
325                               parent=None):
326               
327                QtCore.QThread.__init__(self,parent)
328               
329                self.log = log
330                self.DEBUG = DEBUG
331                self.parent = parent
332               
333                self.device = None
334                self.mode = mode
335               
336                self.device = serial_device
337               
338                self.packet_count = 0
339                self.bad_packets = 0
340               
341                self.keep_running = True
342               
343                self.command_packet = DEFAULT_COMMAND_PACKET
344       
345       
346        ##################################################################
347       
348        def processDataPayload(self, data_payload, payload_timestamp):
349               
350                packet_update = {}
351                packet_update['data_payload'] = data_payload
352                packet_update['payload_timestamp'] = payload_timestamp
353               
354               
355                if (self.parent != None):
356                        self.parent.processPacket(packet_update)
357       
358       
359        ##################################################################
360       
361        def parseStream(self):
362               
363                # Loop forever, parsing one packet per loop
364                packet_count = 0
365               
366                while self.keep_running:
367                       
368                        # Synchronize on [SYNC] bytes
369                        byte = self.device.read()
370                        #print byte.encode("hex")
371                       
372                        #if (byte != PROTOCOL_SYNC):
373                        if (byte != PROTOCOL_SYNC_HEAD1):
374                                continue
375                       
376                        byte = self.device.read()
377                        if (byte != PROTOCOL_SYNC_HEAD2):
378                                continue
379                       
380                       
381                        payload_timestamp = time.time()
382                       
383                        data_payload = self.device.getBuffer()
384                        data_payload = "%s%s%s" % (PROTOCOL_SYNC_HEAD1, PROTOCOL_SYNC_HEAD2, data_payload)
385                       
386                       
387                        if len(data_payload) < PAYLOAD_MINIMUM_LENGTH:
388                        #if len(data_payload) != PACKET_LENGTH:
389                                continue
390                       
391                       
392                        self.processDataPayload(data_payload, payload_timestamp)
393                       
394                       
395                        #if self.DEBUG > 1:
396                                #packet_count += 1
397                                #if packet_count >= DEBUG_PACKET_COUNT:
398                                        #print "max debugging count reached, disconnecting"
399                                        #self.keep_running = False
400                                        #self.device.stop()
401                                        #QtCore.QThread.quit(self)
402                                        ##sys.exit()
403       
404       
405        ##################################################################
406       
407        def writeStream(self):
408               
409                # Loop forever, writing one packet per loop
410                packet_count = 0
411               
412                #import cPickle as pickle
413                #file = open('packet.data', 'r')
414                #packet = pickle.loads(file.read())
415                #file.close()
416               
417                while self.keep_running:
418                       
419                        # Preppend or Append [SYNC] bytes
420                        #if PROTOCOL_ADD_SYNC_TO_HEAD:
421                                #buffer = PROTOCOL_SYNC_HEAD1
422                                #buffer += PROTOCOL_SYNC_HEAD2
423                                #buffer += self.command_packet
424                       
425                        #if PROTOCOL_ADD_SYNC_TO_TAIL:
426                                #buffer = self.command_packet
427                                #buffer += PROTOCOL_SYNC_HEAD1
428                                #buffer += PROTOCOL_SYNC_HEAD2
429                               
430                        buffer = self.command_packet
431                        self.device.buffer = buffer
432                        #self.device.buffer = packet
433                        #print packet.encode("hex")
434                       
435                        # Sleep for 20 ms
436                        # Based on 50 Hz refresh rate of Blade MLP4DSM RC device
437                        # (1/50) * 1000 = 20
438                        QtCore.QThread.msleep(DEVICE_BUFFER_TIMER)
439       
440       
441        ##################################################################
442       
443        def run(self):
444               
445                self.packet_count = 0
446                self.bad_packets = 0
447                self.session_start_timestamp = time.time()
448               
449                if self.mode == 'read':
450                        self.parseStream()
451               
452                elif self.mode == 'write':
453                        self.writeStream()
454       
455       
456        ##################################################################
457       
458        def exitThread(self, callThreadQuit=True):
459               
460                try:
461                        self.device.stop()
462                except:
463                        pass
464               
465                #self.wait()
466                if callThreadQuit:
467                        QtCore.QThread.quit(self)
468
469
470#####################################################################
471#####################################################################
472
473class SerialDevice(QtCore.QThread):
474       
475        def __init__(self, log, \
476                               device_address=SERIAL_DEVICE, \
477                               mode=DEFAULT_MODE, \
478                               DEBUG=DEBUG, \
479                               parent=None):
480               
481                QtCore.QThread.__init__(self, parent)
482               
483                self.log = log
484                self.DEBUG = DEBUG
485               
486                self.device_address = device_address
487                self.mode = mode
488                self.device = None
489                self.buffer = ''
490               
491                if (self.device_address.count(':') == 5):
492                        # Device address is a Bluetooth MAC address
493                        self.device = self.initializeBluetoothDevice()
494                else:
495                        # Device address is a serial port address
496                        self.device = self.initializeSerialDevice()
497               
498                #self.buffer_check_timer = QtCore.QTimer()
499                #QtCore.QObject.connect(self.buffer_check_timer, \
500                                       #QtCore.SIGNAL("timeout()"), \
501                                       #self.checkBuffer)
502                #self.buffer_check_timer.start(DEVICE_BUFFER_TIMER)
503               
504                self.keep_running = True
505       
506       
507        ##################################################################
508       
509        #def initializeBluetoothDevice(self):
510               
511                #socket = bluetooth.BluetoothSocket( bluetooth.RFCOMM )
512               
513                #try:
514                        #socket.connect((self.device_address, THINKGEAR_DEVICE_BLUETOOTH_CHANNEL))
515               
516                #except Exception, e:
517                        #if self.DEBUG:
518                                #print "ERROR:",
519                                #print e
520                                #sys.exit()
521               
522               
523                #return socket
524       
525       
526        ###################################################################
527       
528        def initializeSerialDevice(self):
529               
530                baudrate = DEFAULT_SERIAL_BAUDRATE
531                bytesize = 8
532                parity = 'NONE'
533                stopbits = 1
534                software_flow_control = 'f'
535                rts_cts_flow_control = 'f'
536                #timeout = 15
537                timeout = 5
538               
539                # convert bytesize
540                if (bytesize == 5):
541                        init_byte_size = serial.FIVEBITS
542                elif (bytesize == 6):
543                        init_byte_size = serial.SIXBITS
544                elif (bytesize == 7):
545                        init_byte_size = serial.SEVENBITS
546                elif (bytesize == 8):
547                        init_byte_size = serial.EIGHTBITS
548                else:
549                        #self.log.perror("Invalid value for %s modem byte size! Using default (8)" % modem_type)
550                        init_byte_size = serial.EIGHTBITS
551               
552                # convert parity
553                if (parity == 'NONE'):
554                        init_parity = serial.PARITY_NONE
555                elif (parity == 'EVEN'):
556                        init_parity = serial.PARITY_EVEN
557                elif (parity == 'ODD'):
558                        init_parity = serial.PARITY_ODD
559                else:
560                        #self.log.perror("Invalid value for %s modem parity! Using default (NONE)" % modem_type)
561                        init_parity = serial.PARITY_NONE
562               
563                # convert stopbits
564                if (stopbits == 1):
565                        init_stopbits = serial.STOPBITS_ONE
566                elif (stopbits == 2):
567                        init_stopbits = serial.STOPBITS_TWO
568                else:
569                        #self.log.perror("Invalid value for %s modem stopbits! Using default (8)" % modem_type)
570                        init_byte_size = serial.STOPBITS_ONE
571               
572                # convert software flow control
573                if (software_flow_control == 't'):
574                        init_software_flow_control = 1
575                else:
576                        init_software_flow_control = 0
577               
578                # convert rts cts flow control
579                if (rts_cts_flow_control == 't'):
580                        init_rts_cts_flow_control = 1
581                else:
582                        init_rts_cts_flow_control = 0
583               
584               
585                try:
586                       
587                        device = serialWrapper(port = self.device_address, \
588                                                    baudrate = baudrate, \
589                                                    bytesize = init_byte_size, \
590                                                    parity = init_parity, \
591                                                    stopbits = init_stopbits, \
592                                                    xonxoff = init_software_flow_control, \
593                                                    rtscts = init_rts_cts_flow_control, \
594                                                    timeout = timeout)
595               
596                except Exception, e:
597                        if self.DEBUG:
598                                print "ERROR:",
599                                print e,
600                                print self.device_address
601                                sys.exit()
602               
603               
604                #device.flushInput()
605                ##device.flushOutput()
606               
607               
608                return(device)
609       
610       
611        ###################################################################
612       
613        #def checkBuffer(self):
614               
615                #if self.DEBUG > 1:
616                        #print "INFO: Buffer size check:",
617                        #print len(self.buffer),
618                        #print "(maximum before reset is %i)" % DEVICE_BUFFER_MAX_SIZE
619               
620                #if (DEVICE_BUFFER_MAX_SIZE <= len(self.buffer)):
621                       
622                        #if self.DEBUG:
623                                #print "ERROR: Buffer size has grown too large, resetting"
624                       
625                        #self.reset()
626       
627       
628        ###################################################################
629       
630        def getBuffer(self):
631               
632                data_payload = self.buffer
633               
634                self.resetBuffer()
635               
636               
637                return(data_payload)
638       
639       
640        ###################################################################
641       
642        def resetBuffer(self):
643               
644                self.buffer = ''
645       
646       
647        ###################################################################
648       
649        def read(self, length=1):
650               
651                # Sleep for 20 ms if buffer is empty
652                # Based on 50 Hz refresh rate of Blade MLP4DSM RC device
653                # (1/50) * 1000 = 20
654                while len(self.buffer) < length:
655                        QtCore.QThread.msleep(DEVICE_BUFFER_TIMER)
656                       
657                bytes = self.buffer[:length]
658               
659                self.buffer = self.buffer[length:]
660               
661                return(bytes)
662       
663       
664        ###################################################################
665       
666        def stop(self):
667               
668                #self.buffer_check_timer.stop()
669                self.keep_running = False
670       
671       
672        ###################################################################
673       
674        def exitThread(self, callThreadQuit=True):
675               
676                self.stop()
677                self.close()
678               
679                if callThreadQuit:
680                        QtCore.QThread.quit(self)
681       
682       
683        ###################################################################
684       
685        def close(self):
686               
687                self.device.close()
688       
689       
690        ###################################################################
691       
692        def readBuffer(self):
693       
694                self.buffer = ''
695               
696                while self.keep_running:
697                       
698                       
699                        # High-Speed Echo Mode
700                        if (self.DEBUG > 3) and ECHO_ON:
701                                byte = self.device.recv(PACKET_READ_SIZE)
702                                self.device.write(byte)
703                                continue
704                       
705                       
706                        try:
707                                #byte = self.device.read()
708                                byte = self.device.recv(PACKET_READ_SIZE)
709                               
710                                #if ECHO_ON:
711                                self.device.write(byte)
712                               
713                                if (len(byte) != 0):
714                                        if self.DEBUG > 2:
715                                                print "Device read:",
716                                                print byte,
717                                                if ECHO_ON:
718                                                        print byte.encode("hex"),
719                                                        print "wrote:",
720                                                print byte.encode("hex")
721                                               
722                                        self.buffer += byte
723                       
724                        except:
725                                if self.DEBUG:
726                                        print "ERROR: failed to read from serial device"
727                                break
728               
729               
730                self.exitThread()
731       
732       
733        ###################################################################
734       
735        def writeBuffer(self):
736       
737                self.buffer = ''
738                #beacon_timer = 0
739               
740                while self.keep_running:
741                       
742                        if (len(self.buffer) != 0):
743                                buffer = self.buffer
744                                self.buffer = ''
745                               
746                               
747                                #if beacon_timer >= 750:
748                                        #buffer += '\xaa' + buffer
749                                        #beacon_timer = 0
750                               
751                               
752                                try:
753                                        self.device.write(buffer)
754                                       
755                                        if self.DEBUG > 1:
756                                                print "Device wrote:",
757                                                #print buffer,
758                                                print buffer.encode("hex")
759                               
760                                except:
761                                        if self.DEBUG:
762                                                print "ERROR: failed to write to serial device"
763                                        break
764                       
765                       
766                        # Sleep for 20 ms if buffer is empty
767                        # Based on 50 Hz refresh rate of Blade MLP4DSM RC device
768                        # (1/50) * 1000 = 20
769                        QtCore.QThread.msleep(DEVICE_BUFFER_TIMER)
770                        #beacon_timer += DEVICE_BUFFER_TIMER
771               
772               
773                self.exitThread()
774       
775       
776        ###################################################################
777       
778        def run(self):
779               
780                if self.mode == 'read':
781                        self.readBuffer()
782               
783                elif self.mode == 'write':
784                        self.writeBuffer()
785
786
787#####################################################################
788#####################################################################
789
790class serialWrapper(serial.Serial):
791       
792        #__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)
793       
794        def recv(self, size=1):
795               
796                return(self.read(size))
797
798
799#####################################################################
800# Functions
801#####################################################################
802
803#####################################################################
804# Main
805#####################################################################
806
807if __name__ == '__main__':
808       
809        # Perform correct KeyboardInterrupt handling
810        signal.signal(signal.SIGINT, signal.SIG_DFL)
811       
812        # Collect default settings and command line parameters
813        device = SERIAL_DEVICE
814        command = DEFAULT_COMMAND
815       
816        for each in sys.argv:
817               
818                if each.startswith("--device="):
819                        device = each[ len("--device="): ]
820                elif each.startswith("--command="):
821                        command = each[ len("--command="): ]
822       
823       
824        app = QtCore.QCoreApplication(sys.argv)
825       
826        rc = puzzlebox_brainstorms_helicopter_control(device_address=device, command=command, DEBUG=DEBUG)
827       
828        rc.start()
829       
830        sys.exit(app.exec_())
831
Note: See TracBrowser for help on using the repository browser.