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

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

Brainstorms/Helicopter_Control.py:

  • sync_to_helicopter control added

Brainstorms/Interface?.py:

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