-
Notifications
You must be signed in to change notification settings - Fork 1
/
ps_drone.py
2105 lines (1920 loc) · 96 KB
/
ps_drone.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#########
# ps_drone.py
# (w)+(c) J. Philipp de Graaff, www.playsheep.de, drone@playsheep.de, 2012-2015
# Project homepage: www.playsheep.de/drone and https://sourceforge.net/projects/ps-drone/
# Dependencies: a POSIX OS, openCV2 for video-support.
# Base-program of the PS-Drone API: "An open and enhanced API for universal control of the Parrot AR.Drone 2.0 quadcopter."
##########
# Modified and advanced version, based on a part of the master of computer science degree dissertation "Universelle
# Kontrolle und Ueberwachung einer Parrot AR.Drone 2.0 auf Basis eines offenen und erweiterten Toolkits"
# by J. Philipp de Graaff, faculty of computer science, Prof. Dr. Hedrich, at the University of Frankfurt / Germany
# Linked at http://www.em.cs.uni-frankfurt.de/index.php?id=43&L=1
# For further details, information, documentation or tutorials visit: www.playsheep.de/drone
##########
# LICENCE:
# Artistic License 2.0 as seen on http://opensource.org/licenses/artistic-license-2.0 (retrieved December 2014)
# If the terms of this license do not permit the full use that you propose to make of PS-Drone, please contact me for a
# different licensing arrangement.
# Visit www.playsheep.de/drone or see the PS-Drone-API-documentation for an abstract from the Artistic License 2.0.
##########
# Dedicated to my beloved wife.
###########
import threading, select, socket, time, tempfile, multiprocessing, struct, os, sys
import thread, signal, subprocess
if os.name == 'posix': import termios, fcntl # for getKey(), ToDo: Reprogram for Windows
commitsuicideV, showVid, vCruns, lockV, debugV = False, False, False, threading.Lock(), False # Global variables for video-decoding
offsetND, suicideND, commitsuicideND = 0, False, False # Global variables for NavDava-decoding
#Neu:
# changeIP
# Video Detection
class Drone(object):
######################################=-
### Start and stop using the drone ###=-
######################################=-
###### Bootup and base configuration
def __init__(self):
self.__Version = "2.1.4"
self.__lock = threading.Lock() # To prevent semaphores
self.__startTime = time.time()
self.__speed = 0.2 # Default drone moving speed in percent.
self.showCommands = False # Shows all sent commands (but not the keepalives)
self.debug = False # Shows some additional debug information
self.valueCorrection = False
self.selfRotation = 0.0185 # use this value, if not checked by getSelfRotation()
self.stopOnComLoss = False # when there is a communication-problem, drone will land or not
# Drone communication variables
self.DroneIP = "192.168.1.1"
self.NavDataPort = 5554
self.VideoPort = 5555
self.CmdPort = 5556
self.CTLPort = 5559
# NavData variables
self.__NavData = ""
self.__State = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
self.__NavDataCount = 0
self.__NavDataTimeStamp = 0.0
self.__NavDataDecodingTime = 0.0
self.__NoNavData = False
# Video variables
self.__VideoImage = None
self.__VideoImageCount = 0
self.__VideoDecodeTimeStamp = 0
self.__VideoDecodeTime = 0
self.__VideoReady = False
self.__vKey = ""
self.__SaveVideo = False
# Config variables
self.__ConfigData = []
self.__ConfigDataCount = 0
self.__ConfigDataTimeStamp = 0
self.__ConfigSending = True
self.__ConfigSessionID = "03016321"
self.__ConfigUserID = "0a100407"
self.__ConfigApplicationID = "03016321"
self.sendConfigSaveMode = False
# Internal variables
self.__NavDataProcess = ""
self.__VideoProcess = ""
self.__vDecodeProcess = ""
self.__ConfigQueue = []
self.__networksuicide = False
self.__receiveDataRunning = False
self.__sendConfigRunning = False
self.__shutdown = False
self.__pDefaultStr = "\033[0m"
self.__pRedStr = "\033[91m"
self.__pGreenStr = "\033[92m"
self.__pYellowStr = "\033[93m"
self.__pBlueStr = "\033[94m"
self.__pPurpleStr = "\033[95m"
self.__pLineUpStr = "\033[1A"
###### Connect to the drone and start all procedures
def startup(self):
# Check for drone in the network and wake it up
try:
socket.socket().connect((self.DroneIP, 21))
socket.socket().close()
except:
self.printRed()
print "Drone is not online"
self.printDefault()
sys.exit(9)
# Internal variables
self.__CmdCounter = 3 # as there are two raw commands, send next steps
self.__calltime = 0 # to get some time-values to debug
#send the first four initial-commands to the drone
self.__sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Open network connection
self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Allow to reuse the address
self.__sock.setblocking(0) # Network should not block
self.__sendrawmsg("\r") # Wakes up command port
time.sleep(0.01)
self.__sendrawmsg("AT*PMODE=1,2\rAT*MISC=2,2,20,2000,3000\r") # Initialising drone as sniffed from datastream demo-tool to AR.Drone
##### Initialising timed thread(s) for drone communication
# Opening NavData- and Video- Processes
self.__VidPipePath = tempfile.gettempdir()+"/dronevid-"+str(threading.enumerate()[0])[-12:-2]+"-"+str(time.time())[-7:].replace(".","")+".h264"
self.__net_pipes = []
self.__NavData_pipe, navdataChild_pipe = multiprocessing.Pipe()
self.__Video_pipe, videoChild_pipe = multiprocessing.Pipe()
self.__vdecode_pipe, self.__vdecodeChild_pipe = multiprocessing.Pipe()
self.__NavDataProcess = multiprocessing.Process( target=mainloopND, args=(self.DroneIP,self.NavDataPort,navdataChild_pipe,os.getpid()))
self.__NavDataProcess.start()
self.__VideoProcess = multiprocessing.Process( target=mainloopV, args=(self.DroneIP,self.VideoPort,self.__VidPipePath,videoChild_pipe,os.getpid()))
self.__VideoProcess.start()
self.__vDecodeProcess = multiprocessing.Process( target=vDecode, args=(self.__VidPipePath,self.__vdecodeChild_pipe,os.getpid()))
# There is a third process called "self.__vDecodeProcess" for decoding video, initiated and started around line 880
# Final settings
self.useDemoMode(True) # This entry is necessary for the drone's firmware, otherwise the NavData contains just header and footer
self.setConfig("custom:session_id","-all")
self.getNDpackage(["demo"])
time.sleep(1)
#setup Network-thread
while not self.__receiveDataRunning or not self.__sendConfigRunning or len(self.__ConfigQueue): # sometimes they would not start why ever, so TK has to double-check
if not self.__receiveDataRunning:
self.__threadReceiveData=threading.Thread(target=self.__receiveData)
self.__threadReceiveData.start()
time.sleep(0.05)
if not self.__sendConfigRunning:
self.__threadSendConfig=threading.Thread(target=self.__sendConfig)
self.__threadSendConfig.start()
time.sleep(0.05)
time.sleep(0.01)
###### Clean Shutdown
def shutdown(self):
if self.__shutdown: sys.exit()
self.__shutdown = True
if self.debug: print "Shutdown..."
self.land()
self.thrust(0,0,0,0)
try: self.__NavData_pipe.send("die!")
except: pass
self.__Video_pipe.send("uninit")
t=time.time()
while self.__VideoReady and (time.time()-t)<5: time.sleep(0.1)
try: self.__Video_pipe.send("die!")
except: pass
time.sleep(0.5)
try: self.__VideoProcess.terminate()
except: pass
try: self.__vDecodeProcess.terminate()
except: pass
try: self.__NavDataProcess.terminate()
except: pass
self.__stopnetwork()
try: self.__threadSendConfig.join()
except: pass
try: self.__threadReceiveData.join()
except: pass
self.__keepalive.cancel()
sys.exit()
##############################################################=-
### Make internal variables to external read-only variables ###=-
##############################################################=-
@property
def Version(self): return self.__Version
@property
def startTime(self): return self.__startTime
@property
def speed(self): return self.__speed
@property
def NavData(self): return self.__NavData
@property
def State(self): return self.__State
@property
def NavDataCount(self): return self.__NavDataCount
@property
def NavDataTimeStamp(self): return self.__NavDataTimeStamp
@property
def NavDataDecodingTime(self): return self.__NavDataDecodingTime
@property
def NoNavData(self): return self.__NoNavData
@property
def VideoImage(self): return self.__VideoImage
@property
def VideoImageCount(self): return self.__VideoImageCount
@property
def VideoDecodeTimeStamp(self): return self.__VideoDecodeTimeStamp
@property
def VideoDecodeTime(self): return self.__VideoDecodeTime
@property
def VideoReady(self): return self.__VideoReady
@property
def SaveVideo(self): return self.__SaveVideo
@property
def ConfigData(self): return self.__ConfigData
@property
def ConfigDataCount(self): return self.__ConfigDataCount
@property
def ConfigDataTimeStamp(self): return self.__ConfigDataTimeStamp
@property
def ConfigSending(self): return self.__ConfigSending
@property
def ConfigSessionID(self): return self.__ConfigSessionID
@property
def ConfigUserID(self): return self.__ConfigUserID
@property
def ConfigApplicationID(self): return self.__ConfigApplicationID
######################=-
### Drone commands ###=-
######################=-
###### Commands for configuration
# change some value
def setConfig(self, name, value): # e.g. drone.setConfig(control:altitude_max","5000")
self.__ConfigQueue.append([str(name), str(value), False]) # Note: changes are not immediately and could take some time
# change some value and send the configuration Identifier (sendConfigIDs) ahead
def setMConfig(self, name, value): # Usage like setConfig
self.__ConfigQueue.append([str(name), str(value), True]) # Note: changes are not immediately and could take some time
# get actual configuration
def getConfig(self): # Stored in "ConfigData"
self.at("CTRL", [5,0]) # Wow, that is new, was not necessary before
self.at("CTRL", [4,0]) # Note: Actual configuration data will be received after setting...
if self.showCommands: self.__calltime = time.time() # ... automatically. An update will take up to 0.015 sec)
# setting IDs to store Konfigurations for later
def setConfigSessionID(self, *args):
try:
value = float(*args[0])
self.__ConfigSessionID = normalLen8(value)
self.setConfig("custom:session_id", self.__ConfigSessionID)
except: return (self.__ConfigSessionID)
def setConfigUserID(self, *args):
try:
value = float(*args[0])
self.__ConfigUserID = normalLen8(value)
self.setConfig("custom:profile_id", self.__ConfigUserID)
except: return (self.__ConfigUserID)
def setConfigApplicationID(self, *args):
try:
value = float(*args[0])
self.__ConfigApplicationID = normalLen8(value)
self.setConfig("custom:application_id", self.__ConfigApplicationID)
except: return (self.__ConfigApplicationID)
def setConfigAllID(self):
self.setConfig("custom:session_id", self.__ConfigSessionID)
self.setConfig("custom:profile_id", self.__ConfigUserID)
self.setConfig("custom:application_id", self.__ConfigApplicationID)
# Reminds the drone which IDs it has to use (important for e.g. switch cameras)
def sendConfigIDs(self):
self.at("CONFIG_IDS", [self.__ConfigSessionID,self.__ConfigUserID,self.__ConfigApplicationID])
###### Calibration
def trim(self):
self.at("FTRIM", [])
def mtrim(self):
self.at("CALIB", [0])
def mantrim(self, thetaAngle, phiAngle, yawAngle): # manual Trim
if self.valueCorrection:
try: thetaAngle = float(thetaAngle)
except: thetaAngle = 0.0
try: phiAngle = float(phiAngle)
except: phiAngle = 0.0
try: yawAngle = float(yawAngle)
except: yawAngle = 0.0
self.at("MTRIM", [thetaAngle,phiAngle,yawAngle]) # floats
def getSelfRotation(self, wait):
if self.valueCorrection:
try: wait = float(wait)
except: wait = 1.0
reftime = time.time()
oangle = self.__NavData["demo"][2][2] # detects the self-rotation-speed of the yaw-sensor
time.sleep(wait)
self.selfRotation = (self.__NavData["demo"][2][2]-oangle)/(time.time()-reftime)
return self.selfRotation
###### Movement
# Default speed of movement
def setSpeed(self, *speed):
try: self.__speed = self.__checkSpeedValue(*speed)
except: pass
return self.__speed
# Absolute movement in x, y and z-direction and rotation
def move(self, leftright, backwardforward, downup, turnleftright): # Absolute movement in x, y and z-direction and rotation
if self.valueCorrection:
try: leftright = float(leftright)
except: leftright = 0.0
try: backwardforward = float(backwardforward)
except: backwardforward = 0.0
try: downup = float(downup)
except: downup = 0.0
try: turnleftright = float(turnleftright)
except: turnleftright = 0.0
if leftright > 1.0: leftright = 1.0
if leftright < -1.0: leftright = -1.0
if backwardforward > 1.0: backwardforward = 1.0
if backwardforward < -1.0: backwardforward = -1.0
if downup > 1.0: downup = 1.0
if downup < -1.0: downup = -1.0
if turnleftright > 1.0: turnleftright = 1.0
if turnleftright < -1.0: turnleftright = -1.0
self.at("PCMD", [3 ,leftright, -backwardforward, downup, turnleftright])
# Relative movement to controller in x, y and z-direction and rotation
def relMove(self, leftright, backwardforward, downup, turnleftright, eastwest, northturnawayaccuracy):
if self.valueCorrection:
try: leftright = float(leftright)
except: leftright = 0.0
try: backwardforward = float(backwardforward)
except: backwardforward = 0.0
try: downup = float(downup)
except: downup = 0.0
try: turnleftright = float(turnleftright)
except: turnleftright = 0.0
if leftright > 1.0: leftright = 1.0
if leftright < -1.0: leftright = -1.0
if backwardforward > 1.0: backwardforward = 1.0
if backwardforward < -1.0: backwardforward = -1.0
if downup > 1.0: downup = 1.0
if downup < -1.0: downup = -1.0
if turnleftright > 1.0: turnleftright = 1.0
if turnleftright < -1.0: turnleftright = -1.0
self.at("PCMD_MAG", [1 ,leftright, -backwardforward, downup, turnleftright, eastwest, northturnawayaccuracy])
# Stop moving
def hover(self):
self.at("PCMD", [0,0.0,0.0,0.0,0.0])
def stop(self): # Hammertime !
self.hover()
# Basic movements
def moveLeft(self,*args):
try: speed=args[0]
except: speed=self.__speed
self.move(-self.__checkSpeedValue(speed),0.0,0.0,0.0)
def moveRight(self,*args):
try: speed=args[0]
except: speed=self.__speed
self.move( self.__checkSpeedValue(speed),0.0,0.0,0.0)
def moveForward(self,*args):
try: speed=args[0]
except: speed=self.__speed
self.move(0.0, self.__checkSpeedValue(speed),0.0,0.0)
def moveBackward(self,*args):
try: speed=args[0]
except: speed=self.__speed
self.move(0.0,-self.__checkSpeedValue(speed),0.0,0.0)
def moveUp(self,*args):
try: speed=args[0]
except: speed=self.__speed
self.move(0.0,0.0, self.__checkSpeedValue(speed),0.0)
def moveDown(self,*args):
try: speed=args[0]
except: speed=self.__speed
self.move(0.0,0.0,-self.__checkSpeedValue(speed),0.0)
def turnLeft(self,*args):
try: speed=args[0]
except: speed=self.__speed
self.move(0.0,0.0,0.0,-self.__checkSpeedValue(speed))
def turnRight(self,*args):
try: speed=args[0]
except: speed=self.__speed
self.move(0.0,0.0,0.0, self.__checkSpeedValue(speed))
# Lets the drone rotate defined angle
# BUG: does not work with 180deg. turns
# ToDo: Should be able to stop in case of failures
def turnAngle(self,ndir,speed,*args):
opos = self.__NavData["demo"][2][2] # get the source/current (original) angle
npos = opos+ndir # calculate the destination (new) angle
minaxis = opos # to make sure, that the jump from -180 to 180 will...
maxaxis = opos # ...be correctly handled
speed = self.__checkSpeedValue(speed)
ospeed = speed # stores the given speed-value
reftime = time.time()
accurateness = 0
try: accurateness = args[0]
except: pass
if accurateness<=0:
accurateness = 0.005 # Destination angle can differ +/- this value (not demo-mode)
if self.__State[10]: accurateness = 0.1 # Destination angle can differ +/- this value in demo-mode
stop = False
counter = 0
direction = 0 # -1 = left | 1 = right To prevent endless loops (happends sometimes, whyever)
while not stop and counter<=5:
ndc = self.__NavDataCount # wait for the next NavData-package
while ndc == self.__NavDataCount: time.sleep(0.001)
kalib = (time.time()-reftime)*self.selfRotation # trys to recalibrate, causing moving sensor-values around 0.0185 deg/sec
cpos = self.__NavData["demo"][2][2] # get the current angle
if minaxis > cpos: minaxis = cpos # set the minimal seen angle
if maxaxis < cpos: maxaxis = cpos # set the maximal seen angle
if cpos-minaxis >= 180: cpos = cpos-360 # correct the angle-value if necessary...
elif maxaxis-cpos >= 180: cpos = cpos+360 # ...for an easier calculation
speed = abs(cpos-npos+kalib) / 10.0 # the closer to the destination the slower the drone turns
if speed > ospeed: speed = ospeed # do not turn faster than recommended
if speed < 0.05: speed = 0.05 # too slow turns causes complications with calibration
self.__speed = speed
if cpos > (npos+kalib): # turn left, if destination angle is lower
self.turnLeft()
if direction == 0: direction = -1
elif direction == 1: direction, counter = -1, counter+1
else: # turn right if destination angle is higher
self.turnRight()
if direction == 0: direction = 1
elif direction == -1: direction, counter = 1, counter+1
if cpos < (npos+kalib+accurateness) and cpos > (npos+kalib-accurateness):# if angle is reached...
self.stop() # ...stop turning
time.sleep(0.01)
stop = True
return(True)
def takeoff(self):
self.at("REF", [290718208]) #290718208=10001010101000000001000000000
def land(self):
self.at("REF", [290717696]) #290717696=10001010101000000000000000000
###### NavData commands
# Switches to Demo- or Full-NavData-mode
def useDemoMode(self,value):
if value: self.setConfig("general:navdata_demo", "TRUE")
else: self.setConfig("general:navdata_demo", "FALSE")
def useMDemoMode(self,value):
if value: self.setMConfig("general:navdata_demo", "TRUE")
else: self.setMConfig("general:navdata_demo", "FALSE")
def getNDpackage(self,packets):
self.__NavData_pipe.send(("send",packets))
def addNDpackage(self,packets):
self.__NavData_pipe.send(("add",packets))
def delNDpackage(self,packets):
self.__NavData_pipe.send(("block",packets))
def reconnectNavData(self):
self.__NavData_pipe.send("reconnect")
###### Video & Marker commands
# This makes the drone fly around and follow 2D tags which the camera is able to detect.
def aflight(self, flag):
self.at("AFLIGHT", [flag]) #Integer: 1: start flight, 0: stop flight
def slowVideo(self, *args):
try: do = args[0]
except: do = True
if do: self.__Video_pipe.send("slowVideo")
else: self.__Video_pipe.send("fastVideo")
def midVideo(self, *args):
try: do = args[0]
except: do = True
if do: self.__Video_pipe.send("midVideo")
else: self.__Video_pipe.send("fastVideo")
def fastVideo(self, *args):
try: do = args[0]
except: do = True
if do: self.__Video_pipe.send("fastVideo")
else: self.__Video_pipe.send("slowVideo")
def saveVideo(self, *args):
try: do = args[0]
except: do = True
if do: self.__Video_pipe.send("saveVideo")
else: self.__Video_pipe.send("unsaveVideo")
def startVideo(self, *args):
try: do = args[0]
except: do = True
if do: self.__Video_pipe.send("init")
else: self.stopVideo()
def stopVideo(self, *args):
try: do = args[0]
except: do = True
if do: self.__Video_pipe.send("uninit")
else: self.startVideo()
def showVideo(self, *args):
try: do = args[0]
except: do = True
if do:
self.__Video_pipe.send("init")
self.__Video_pipe.send("show")
else: self.hideVideo()
def hideVideo(self, *args):
try: do = args[0]
except: do = True
if do:
self.__Video_pipe.send("init")
self.__Video_pipe.send("hide")
else: self.showVideo()
# Selects which video stream to send on the video UDP port.
def hdVideo(self, *args):
try: do = args[0]
except: do = True
if do: self.setMConfig("video:video_codec","131")
else: self.setMConfig("video:video_codec","129")
def sdVideo(self, *args):
try: do = args[0]
except: do = True
if do: self.setMConfig("video:video_codec","129")
else: self.setMConfig("video:video_codec","131")
def mp4Video(self, *args):
try: do = args[0]
except: do = True
if do: self.setMConfig("video:video_codec","128")
else: self.setMConfig("video:video_codec","129")
# Selects which video-framerate (in frames per second) to send on the video UDP port.
def videoFPS(self, fps):
try:
int(fps)
if fps>60: fps = 60
elif fps<1: fps = 1
self.setMConfig("video:codec_fps",fps)
except: pass
# Selects which video-bitrate (in kilobit per second) to send on the video UDP port.
def videoBitrate(self, bitrate):
try:
int(bitrate)
if bitrate > 20000: bitrate = 20000
if bitrate < 250: bitrate = 250
self.setMConfig("video:bitrate",bitrate)
except: pass
# Selects which video stream to send on the video UDP port.
def frontCam(self, *args):
try: do = args[0]
except: do = True
if do: self.setMConfig("video:video_channel","0")
else: self.setMConfig("video:video_channel","1")
def groundCam(self, *args):
try: do = args[0]
except: do = True
if do: self.setMConfig("video:video_channel","1")
else: self.setMConfig("video:video_channel","0")
### Misc commands
def reset(self):
if self.NavDataCount>0 and self.State[31]==1:
self.at("REF", [290717952]) #290717952=10001010101000000000100000000
def thrust(self, fl, fr, rl, rr): # Controls engines directly, overriding control loops.
fl *= 2
if fl > 64000: fl = 64000
elif fl < 0: fl = 0
fr *= 2
if fr > 64000: fr = 64000
elif fr < 0: fr = 0
rl *= 2
if rl > 64000: rl = 64000
elif rl < 0: rl = 0
rr *= 2
if rr > 64000: rr = 64000
elif rr < 0: rr = 0
self.at("PWM", [int(fl), int(fr), int(rr), int(rl)])
# Seems that integer-values could be between 0 (stop) to 511 (full); more than 511 seem to have no effect.
# Beware: if using too high values (e.g. floats (>64k ?)), there will be side-effects like restarting other motors, etc.
# Drone will shut down, if its flight-angle is more than set.
# Control the drone's LED.
def led(self, animation, frequency, duration):
if animation < 21 and frequency > 0 and duration >= 0:
self.at("LED", [animation, float(frequency), duration])
# Makes the drone execute a predefined movement (animation).
def anim(self, animation, duration):
if animation < 20 and duration >= 0:
self.at("ANIM", [animation, duration])
def changeIP(self, IP):
try:
import telnetlib
tnet = telnetlib.Telnet(self.DroneIP)
tnet.write("ifconfig ath0 down;ifconfig ath0 "+IP+" netmask 255.255.255.0;ifconfig ath0 up\n")
tnet.close()
self.DroneIP=IP
OK = True
except:
OK = False
return OK
#########################=-
### Low-level Commands ###=-
#########################=-
# Upgrading the basic drone commands to low-level drone commands:vid
# Adding command-number, checking the values, convert 32-bit float to 32-bit integer and put it in quotes
def at(self, command, params):
self.__lock.acquire()
paramLn = ""
if params:
for p in params:
if type(p) == int: paramLn += ","+str(p)
elif type(p) == float: paramLn += ","+str(struct.unpack("i", struct.pack("f", p))[0])
elif type(p) == str: paramLn += ",\""+p+"\""
msg = "AT*"+command+"="+str(self.__CmdCounter)+paramLn+"\r"
self.__CmdCounter += 1
self.__sendrawmsg(msg)
self.__lock.release()
# Sending the low-level drone-readable commands to the drone...better do not use
def __sendrawmsg(self, msg):
try: self.__keepalive.cancel()
except: pass
if self.showCommands:
if msg.count("COMWDG") < 1: print msg
self.__sock.sendto(msg, (self.DroneIP, self.CmdPort))
self.__keepalive = threading.Timer(0.1, self.__heartbeat)
self.__keepalive.start()
#############################=-
### Convenient Commands ###=-
#############################=-
# Just add water
# Checks the battery-status
def getBattery(self):
batStatus = "OK"
batValue = 0
if self.__State[15] == 1: batStatus = "empty"
try: batValue = self.__NavData['demo'][1]
except: batValue = -1
return (batValue,batStatus) # Percent & status ("OK", "empty")
# Calculates the minor difference between two angles as the drone gives values from -180 to 180...
# ...so e.g. 170 and -160 are +30 difference and drone will turn to the correct direction
def angleDiff(self, base, value):
adiff = ((base+180)-(value+180)) %360
if adiff>180: adiff-=360
return adiff
# Grabs the pressed key (not yet for Windows)
# ToDo: Reprogram for Windows
def getKey(self):
key = ""
fd = sys.stdin.fileno()
if os.name == 'posix':
oldterm = termios.tcgetattr(fd)
newattr = termios.tcgetattr(fd)
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, newattr)
oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
try:
try: key = sys.stdin.read(1)
except IOError: pass
finally:
termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
if os.name == 'nt':
if msvcrt.kbhit(): key = msvcrt.getch()
key += self.__vKey
self.__vKey = ""
return key
# Drone hops like an excited dog
def doggyHop(self):
ospeed = self.__speed
self.__speed = 1
for i in range (0,4,1):
self.moveUp()
time.sleep(0.20)
self.moveDown()
time.sleep(0.20)
self.hover()
self.__speed = ospeed
# Drone wags like a happy dog
def doggyWag(self):
ospeed = self.__speed
self.__speed = 1
for i in range (0,4,1):
self.moveLeft()
time.sleep(0.25)
self.moveRight()
time.sleep(0.25)
self.hover()
self.__speed = ospeed
# Drone nods
def doggyNod(self):
ospeed = self.__speed
self.__speed = 1
for i in range (0,4,1):
self.moveForward()
time.sleep(0.25)
self.moveBackward()
time.sleep(0.25)
self.hover()
self.__speed = ospeed
def printDefault(self, *args):
if os.name == 'posix':
print self.__pDefaultStr,
try:
if len(*args) > 0:
for i in args: print i,
print self.__pDefaultStr
except: pass
def printRed(self, *args):
if os.name == 'posix':
print self.__pRedStr,
try:
if len(*args) > 0:
for i in args: print i,
print self.__pDefaultStr
except: pass
def printGreen(self, *args):
if os.name == 'posix':
print self.__pGreenStr,
try:
if len(*args) > 0:
for i in args: print i,
print self.__pDefaultStr
except: pass
def printYellow(self, *args):
if os.name == 'posix':
print self.__pYellowStr,
try:
if len(*args) > 0:
for i in args: print i,
print self.__pDefaultStr
except: pass
def printBlue(self, *args):
if os.name == 'posix':
print self.__pBlueStr,
try:
if len(*args) > 0:
for i in args: print i,
print self.__pDefaultStr
except: pass
def printPurple(self, *args):
if os.name == 'posix':
print self.__pPurpleStr,
try:
if len(*args) > 0:
for i in args: print i,
print self.__pDefaultStr
except: pass
def printLineUp(self):
if os.name == 'posix': print self.__pLineUpStr,
##################################=-
### Threads & Thread-Sidekicks ###=-
##################################=-
# Idea: the network thread listens to the given network-stream and communication-pipes of other processes, such as for video or navdata-decoding.
# In case the connection to the drone is cut off for more than 2 seconds (so no keep-alive-command has been sent) the network
# needs to reconnect. In order to do so the (private) function "__netrecon" starts after 0.1 seconds of no incoming navdata-datapacket to
# reconnect all given network-sockets.
def __heartbeat(self):
# If the drone does not get a command, it will mutter after 50ms (CTRL watchdog / state[28] will set to 1)
# and panic after 2 seconds and abort data-communication on port 5554 (then you have to initialize the network again).
# Heartbeat will reset the watchdog and, by the way, the ACK_BIT (state[6], to accept any other AT*CONFIG command)
# If mainthread isn't alive anymore (because program crashed or whatever), heartbeat will initiate the shutdown.
if str(threading.enumerate()).count("MainThread, stopped") or str(threading.enumerate()).count("MainThread")==0: self.shutdown()
else: self.at("COMWDG",[])
# CheckAndReact is periodically called by the receiveData-Thread to check for mainly for critical status-error(s) and
# changed debug-modes.
def __checkAndReact(self, debug, showCommands):
# Automatic process-commands, used for syncing debugging-bits to child-processes
if debug != self.debug:
debug = self.debug
if debug:
self.__NavData_pipe.send("debug")
self.__Video_pipe.send("debug")
else:
self.__NavData_pipe.send("undebug")
self.__Video_pipe.send("undebug")
if showCommands != self.showCommands:
showCommands = self.showCommands
if showCommands:
self.__NavData_pipe.send("showCommands")
self.__Video_pipe.send("showCommands")
else:
self.__NavData_pipe.send("hideCommands")
self.__Video_pipe.send("hideCommands")
# Communication problem, shutting down
if self.stopOnComLoss and self.__State[30]:
self.shutdown()
sys.exit()
return (debug,showCommands)
# Thread for sending the configuration. It is asynchronous but save.
# The configuration-requests are in a queue, the first entry is sent. NavData will contain a "Control command ACK" status-bit,...
# ...that configuration is ready to be set. This will be confirmed and the procedure waits until this bit is 0 again; then the next entry will be processed.
# In savemode, there is a check whether the configuration has been changed correctly by requesting the current/latest configuration and double-checking this value.
def __sendConfig(self):
sleeptime, getconfigtag, self.__sendConfigRunning = 0.001, False, True
while not self.__networksuicide:
if len(self.__ConfigQueue): # If there is something in the queue...
if self.__ConfigQueue[0][-1]: self.sendConfigIDs() # ...check for multiuserconfig-request (and send it)
self.__ConfigSending = True # Set tag, to show sending is in process
qlen = len(self.__ConfigQueue)
if qlen > 1: # Testing for double entries, preventing a ping-pong in save-mode
i = 1
while True:
if i >= qlen: break
if self.__ConfigQueue[0][0].lower() == self.__ConfigQueue[i][0].lower():
self.__ConfigQueue.remove(self.__ConfigQueue[0])# Delete double entries
qlen = len(self.__ConfigQueue)
else: i+=1
self.at("CONFIG",self.__ConfigQueue[0][:-1]) # Send the first entry in queue
getconfigtag, configconfirmed, configreconfirmed = False, False, False
while not configconfirmed and not self.__networksuicide: # Wait for confirmation-bit from drone...
if self.__State[6] and not configreconfirmed and not self.__networksuicide:
self.at("CTRL",[5,0]) # ...and send reset the confirmation-bit
configreconfirmed = True
if not self.__State[6] and configreconfirmed and not self.__networksuicide:
configconfirmed = True # Wait for the reset of the confirmation-bit
time.sleep(sleeptime)
# It seems that the drone stores configurations not always correctly; therfore, here is a save-mode:
if self.sendConfigSaveMode and not self.__networksuicide:
lastConfigDataCount = self.__ConfigDataCount # Wait for the next configuration-list
self.getConfig()
while lastConfigDataCount == self.__ConfigDataCount and not self.__networksuicide: time.sleep(sleeptime)
# New & Optimized
for i in range (0,len(self.__ConfigData),1):
if self.__ConfigData[i][0].find(self.__ConfigQueue[0][0]) > -1:
if self.__ConfigData[i][1] != self.__ConfigQueue[0][1]:
if self.debug:
print " Configuration missmatched, resending !"
print " "+self.__ConfigData[i][0]+" should be \""+self.__ConfigQueue[0][1]+"\" is \""+self.__ConfigData[i][1]+"\""
self.__ConfigQueue.append(self.__ConfigQueue[0]) # If value is not correctly set, requeue !
self.__ConfigQueue.remove(self.__ConfigQueue[0]) # Configuration has been (correctly) set, delete request from queue and go on
if self.__networksuicide: self.__ConfigQueue=[]
if not len(self.__ConfigQueue):
if not getconfigtag:
self.getConfig()
getconfigtag = True
self.__ConfigSending = False
else: time.sleep(sleeptime)
if self.debug: print "sendConfig-Tread : committed suicide"
def __receiveData(self):
self.__net_pipes=[]
self.__net_pipes.append(self.__NavData_pipe)
self.__net_pipes.append(self.__Video_pipe)
self.__Config_pipe = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #TCP
self.__Config_pipe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Allow to reuse the address
self.__Config_pipe.setblocking(0)
self.__Config_pipe.connect_ex((self.DroneIP, self.CTLPort))
self.__net_pipes.append(self.__Config_pipe)
VideoIsDead, configdata, cfgdata, cmd = False, [], "", ""
self.__vDecodeRunning, debug, showCommands, self.__receiveDataRunning = False, False, False, True
while not self.__networksuicide:
in_pipe, dummy1, dummy2 = select.select(self.__net_pipes, [], [], 0.1) # When something is in a pipe...
for ip in in_pipe: # ...go and get it
if ip == self.__NavData_pipe: ### Receiving sensor-values from NavData-process
try: self.__NavData, self.__State, self.__NavDataCount, self.__NavDataTimeStamp, self.__NavDataDecodingTime, self.__NoNavData = self.__NavData_pipe.recv()
except: pass
if ip == self.__vdecode_pipe: ### Receiving imagedata and feedback from videodecode-process
try:
cmd, VideoImageCount, VideoImage, VideoDecodeTime = self.__vdecode_pipe.recv() # Imagedata
if self.showCommands and cmd!="Image" : print "** vDec -> Com :",cmd
if cmd == "suicided": self.__Video_pipe.send("vd died") # videodecode-process died
if cmd == "foundCodec": self.__Video_pipe.send("foundCodec") # the codec of the videostream has been found, do not flood anymore
if cmd == "VideoUp": self.__VideoReady = True # Imagedata is available
if cmd == "keypressed": self.__vKey = VideoImage # Pressed key on window
if cmd == "reset": self.__Video_pipe.send(cmd) # proxy to videodecode-process
if cmd == "Image": # Imagedata !
self.__VideoImageCount = VideoImageCount
self.__VideoImage = VideoImage
self.__VideoDecodeTime = VideoDecodeTime
self.__VideoDecodeTimeStamp = time.time()-self.__startTime
except: pass
if ip == self.__Video_pipe: ### Receiving feedback from videostream-process
try:
cmd = self.__Video_pipe.recv()
if self.showCommands and cmd != "": print "** Vid -> Com : ",cmd
if cmd == "vDecProc": # videodecode-process should start
if not self.__vDecodeRunning:
self.__vDecodeProcess = multiprocessing.Process( target=vDecode, args=(self.__VidPipePath,self.__vdecodeChild_pipe,os.getpid()))
self.__vDecodeProcess.start()
self.__net_pipes.append(self.__vdecode_pipe)
self.__vDecodeRunning = True
self.__Video_pipe.send("vDecProcON")
# else: self.__vdecode_pipe.send(cmd) # If / elif / else is somehow not working here...whyever
if cmd == "VideoDown": self.__VideoReady=False # videodecode-process stopped
if cmd == "saveVideo": self.__SaveVideo=True # no preprocessing of the video
if cmd == "unsaveVideo": self.__SaveVideo=False # preprocessing activated again
if cmd == "debug": self.__vdecode_pipe.send(cmd) # proxy to videodecode-process
if cmd == "showCommands": self.__vdecode_pipe.send(cmd) # proxy to videodecode-process
if cmd == "hideCommands": self.__vdecode_pipe.send(cmd) # proxy to videodecode-process
if cmd == "show": self.__vdecode_pipe.send(cmd) # proxy to videodecode-process
if cmd == "hide": self.__vdecode_pipe.send(cmd) # proxy to videodecode-process
if cmd == "vDecProcKill":
self.__vdecode_pipe.send("die!") # videodecode-process should switch off
vDecodeRunning = False
except: pass
if ip==self.__Config_pipe and not self.__networksuicide: ### Receiving drone-configuration
try:
if self.__networksuicide: break # Does not stop sometimes, so the loop will be forced to stop
cfgdata = cfgdata+self.__Config_pipe.recv(65535) # Data comes in two or three packages
if cfgdata.count("\x00"): # Last byte of sent config-file, everything was received
if self.__networksuicide: break
configdata = (cfgdata.split("\n")) # Split the huge package into a configuration-list
for i in range(0, len(configdata), 1):
configdata[i] = configdata[i].split(" = ") # Split the single configuration-lines into configuration and value
self.__ConfigData = configdata[:-1] # Last value is "\x00"
self.__ConfigDataTimeStamp = time.time()-self.__startTime # Set a timestamp for a better coordination
self.__ConfigDataCount+=1 # Alters the count of received Configdata for a better coordination
configdata, cfgdata = [], ""
if self.showCommands: print "Got "+str(len(self.__ConfigData))+" Configdata "+str(time.time()-self.__calltime)
self.__calltime=0
except IOError: pass
debug, showCommands = self.__checkAndReact(debug, showCommands) # Check for errors and things to change
if self.debug: print "receiveData-Thread : committed suicide"
# TestMe
try: self.__Config_pipe.close()
except: pass
def __stopnetwork(self):
self.__networksuicide = True
#############################=-
### Compatibility Commands ###=-
#############################=-
# While programming this API I changed some command-names
# This section converts the old commands into the new ones
def pwm(self, fl, fr, rl, rr): # Controls engines directly, overriding control loops.
if fl > 64000: fl = 64000
if fr > 64000: fr = 64000
if rl > 64000: rl = 64000
if rr > 64000: rr = 64000
self.at("PWM", [int(fl), int(fr), int(rr), int(rl)])
def groundVideo(self, *args): self.groundCam(*args)
def frontVideo(self, *args): self.frontCam(*args)