Monthly Archives: October 2023

Drone Programming – Swarm Contol, Video Streaming & Mission Pad

One of our main purposes for drone programming is achieving swarm control, that’s why we bought three Tello EDUs and today we are so excited that we can make this happen. Once again, thanks to DJITELLOPY, it saves us a lot of effort to develop our API and make this program done in a few lines. Once we understood the structure of TelloSwarm, it was not that hard to make the swarm control with video streaming and mission pad flying. We started with three drones, we want to control 10 or more for a Drones show.

Roadblock – Damaged Motor

But, there was a huge roadblock for us. It was one of the drones with a motor damaged, it smashed onto the wall when we tested the program. Since we didn’t get any warranty because we brought it from HK to the UK (even just 1.5 months..), we needed to order the spare part and did the motor replacement. The repair we did is quite similar as shown in this video – Simple Motor Replace. In case you need to replace the motor, it is the simplest way. We don’t want to damage the main board because of the poor soldering skills.

Drone Swarm

See below for the full codes. This time, we created two codes. We created the first one which is a very straightforward coding – manage the drone one by one with the parallel process, i.e. a beginner work but also easy for beginner to read . After we studied DJITELLOPY SWARM functions, we modified this. We put them together for a head to head comparison.

Light color – DJITELLPY SWARM version

from djitellopy import Tello, TelloSwarm
import cv2
import threading
import time, logging

flip = True
fly = True
video = False

telloswarm = TelloSwarm.fromIps(['192.168.3.81','192.168.3.82','192.168.3.83'])

for i, tello in zip(range(3), telloswarm):
    tello.LOGGER.setLevel(logging.ERROR) 
    tello.connect()
    print(f'Tello Battery {i+1}: {tello.get_battery()}')
    tello.change_vs_udp(8881+i)
    tello.set_video_resolution(Tello.RESOLUTION_480P)
    tello.set_video_bitrate(Tello.BITRATE_1MBPS)

landed = False   
def tello_video(tello, drone_number):
    while not landed:
        
        frame = tello.get_frame_read().frame
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) 
        cv2.imshow(f'Tello {drone_number}' , frame)
        cv2.moveWindow(f'Tello {drone_number}', (drone_number - 1)*900, 50)
        if cv2.waitKey(40) & 0xFF == ord('q'):
            break
    
if video:    
    telloswarm.parallel(lambda drone, tello: tello.streamon())
    time.sleep(3)
    
    tello1_video = threading.Thread(target=tello_video, args=(telloswarm.tellos[0], 1), daemon=True)
    tello2_video = threading.Thread(target=tello_video, args=(telloswarm.tellos[1], 2), daemon=True)
    tello3_video = threading.Thread(target=tello_video, args=(telloswarm.tellos[2], 3), daemon=True)
    tello1_video.start()
    tello2_video.start()
    tello3_video.start()
    
if flip or fly:
    telloswarm.send_rc_control(0,0,0,0)
    telloswarm.takeoff()
    telloswarm.set_speed(10) 
    
if flip:
    fpath_1 = ['l', 'b', 'r']
    fpath_2 = ['r', 'f', 'l']
    telloswarm.parallel(lambda drone, tello: tello.flip(fpath_1[drone]))
    telloswarm.parallel(lambda drone, tello: tello.flip(fpath_2[drone]))
    
if fly:
    telloswarm.move_up(50)
    path_1 = [(-60, -60, 50, 20, 1), (60, -60, 100, 30, 2), (0, 120, 150, 40, 3)]
    path_2 = [(60, -60, 100, 30, 2), (0, 120, 150, 40, 3), (-60, -60, 50, 20, 1)]
    path_3 = [(0, 120, 150, 40, 3), (-60, -60, 50, 20, 1), (60, -60, 100, 30, 2)]
    path_4 = [(0, 0, 50, 20, 1), (0, 0, 50, 20, 2), (0, 0, 50, 20, 3)]
    telloswarm.parallel(lambda drone, tello: tello.enable_mission_pads)
    telloswarm.parallel(lambda drone, tello: tello.go_xyz_speed_mid(path_1[drone][0], path_1[drone][1], path_1[drone][2], path_1[drone][3], path_1[drone][4]))
    telloswarm.parallel(lambda drone, tello: tello.go_xyz_speed_mid(path_2[drone][0], path_2[drone][1], path_2[drone][2], path_2[drone][3], path_2[drone][4]))
    telloswarm.parallel(lambda drone, tello: tello.go_xyz_speed_mid(path_3[drone][0], path_3[drone][1], path_3[drone][2], path_3[drone][3], path_3[drone][4]))
    telloswarm.parallel(lambda drone, tello: tello.go_xyz_speed_mid(path_4[drone][0], path_4[drone][1], path_4[drone][2], path_4[drone][3], path_4[drone][4]))
    
if flip or fly:    
    telloswarm.land()
    landed = True
    
if video:
    tello1_video.join()
    tello2_video.join()
    tello3_video.join()
     
    telloswarm.parallel(lambda drone, tello: tello.streamoff())
    
telloswarm.end()
Python

Dark color – ‘Straight forward’ very beginner version

from djitellopy import Tello, TelloSwarm
import cv2
import threading
import time, logging

flip = True
fly = True
video = True

telloswarm = TelloSwarm.fromIps(['192.168.3.81','192.168.3.82','192.168.3.83'])
tello1 = telloswarm.tellos[0]
tello2 = telloswarm.tellos[1]
tello3 = telloswarm.tellos[2]

tello1.LOGGER.setLevel(logging.ERROR) 
tello2.LOGGER.setLevel(logging.ERROR) 
tello3.LOGGER.setLevel(logging.ERROR) 

tello1.connect()
tello2.connect()
tello3.connect()

print(f'Tello1 Battery : {tello1.get_battery()}')
print(f'Tello2 Battery : {tello2.get_battery()}')
print(f'Tello3 Battery : {tello3.get_battery()}')

tello1.change_vs_udp(8881)
tello1.set_video_resolution(Tello.RESOLUTION_480P)
tello1.set_video_bitrate(Tello.BITRATE_1MBPS)

tello2.change_vs_udp(8882)
tello2.set_video_resolution(Tello.RESOLUTION_480P)
tello2.set_video_bitrate(Tello.BITRATE_1MBPS)

tello3.change_vs_udp(8883)
tello3.set_video_resolution(Tello.RESOLUTION_480P)
tello3.set_video_bitrate(Tello.BITRATE_1MBPS)

landed = False   
def tello_video(tello, drone_number):
    while not landed:
        
        frame = tello.get_frame_read().frame
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) 
        cv2.imshow(f'Tello {drone_number}' , frame)
        cv2.moveWindow(f'Tello {drone_number}', (drone_number - 1)*900, 50)
        if cv2.waitKey(40) & 0xFF == ord('q'):
            cv2.destroyWindow(f'Tello {drone_number}')
            break

def tello_flip(tello, direction):
    tello.flip(direction)
    
def tello_mpad(tello, x, y, z, speed, mpad):
    tello.enable_mission_pads
    tello.go_xyz_speed_mid(x, y, z, speed, mpad)
        
if video:
    
    tello1.streamon()
    tello1_video = threading.Thread(target=tello_video, args=(tello1, 1), daemon=True)
    tello1_video.start()

    tello2.streamon()
    tello2_video = threading.Thread(target=tello_video, args=(tello2, 2), daemon=True)
    tello2_video.start()
    
    tello3.streamon()
    tello3_video = threading.Thread(target=tello_video, args=(tello3, 3), daemon=True)
    tello3_video.start()

    time.sleep(3)

if flip or fly:
    telloswarm.send_rc_control(0,0,0,0)
    telloswarm.takeoff()
    telloswarm.set_speed(10) 
    
if flip:
    tello1_fpath = ['l','r']
    tello2_fpath = ['b','f']
    tello3_fpath = ['r','l']
    
    for i in range(2):
        flip1 = tello1_fpath[i]
        flip2 = tello2_fpath[i]
        flip3 = tello3_fpath[i]    
        tello1_flip = threading.Thread(target=tello_flip, args=(tello1, flip1), daemon=True)
        tello2_flip = threading.Thread(target=tello_flip, args=(tello2, flip2), daemon=True)
        tello3_flip = threading.Thread(target=tello_flip, args=(tello3, flip3), daemon=True)
        tello1_flip.start()
        tello2_flip.start()
        tello3_flip.start()
        tello1_flip.join()
        tello2_flip.join()
        tello3_flip.join()
    
if fly:
    telloswarm.move_up(50)
    tello1_path = [(-60, -60, 50, 20, 1), (60, -60, 100, 30, 2), (0, 120, 150, 40, 3), (0, 0, 50, 20, 1)]
    tello2_path = [(60, -60, 100, 30, 2), (0, 120, 150, 40, 3), (-60, -60, 50, 20, 1), (0, 0, 50, 20, 2)]
    tello3_path = [(0, 120, 150, 40, 3), (-60, -60, 50, 20, 1), (60, -60, 100, 30, 2), (0, 0, 50, 20, 3)]
    
    for i in range(4):
        x1, y1, z1, s1, mpad1 = tello1_path[i]
        x2, y2, z2, s2, mpad2 = tello2_path[i]
        x3, y3, z3, s3, mpad3 = tello3_path[i]
        
        tello1_mpad = threading.Thread(target=tello_mpad, args=(tello1, x1, y1, z1, s1, mpad1), daemon=True)
        tello2_mpad = threading.Thread(target=tello_mpad, args=(tello2, x2, y2, z2, s2, mpad2), daemon=True)
        tello3_mpad = threading.Thread(target=tello_mpad, args=(tello3, x3, y3, z3, s3, mpad3), daemon=True)
        tello1_mpad.start()
        tello2_mpad.start()
        tello3_mpad.start()
        tello1_mpad.join()
        tello2_mpad.join()
        tello3_mpad.join()
    
if flip or fly:    
    telloswarm.land()
    landed = True
    
if video:    
    tello1_video.join()
    tello2_video.join()
    tello3_video.join()

    tello1.streamoff()
    tello2.streamoff()
    tello3.streamoff()

telloswarm.end()
Python

Tello regular vs Tello EDU

Before we got our Tello EDU, we had two regular Tellos for years. When we popped up the idea of programming a drone, drone swarm control was our target. However, we just found that doing swarm with regular Tello is quite impossible because of the network connection constraint. It connect as access point instead of station mode, i.e. your PC or mobile get a fixed IP from Tello instead of Tello connecting to the network to get it’s IP. You will still get the same IP even connect to two regular Tellos with two wifi devices in your PC, someone worked this out with Linux, you may want to have a look – Adventures with DJI Ryze Tello: Controlling a Tello Swarm.

For us, we took the easier way, bought some Tello EDU and started our drone programming journey. The main different between Tello EDU and regular Tello is the network connection, supporting mission pad and SDK 3.0.

Setup your Tello EDU

Before you can do the swarm or multiple drone control, you need to change the Tello EDU network setup to the access point, you can do this from the Tello EDU APP, we recommend this because you can also check any firmware update. Otherwise, you may need the following codes, run it when your PC connects the Tello in station mode. Keep in mind that the SSID cannot include ‘.’, spaces, or other special characters, and you can always reset this by pressing the power button for 5 seconds.

from djitellopy import Tello

tello = Tello()
tello.connect()
tello.set_wifi_credentials(SSID, Password)
Python

Connect the drones

telloswarm = TelloSwarm.fromIps(['192.168.3.81','192.168.3.82','192.168.3.83'])
Python

Told TelloSwarm the drones’ IP, and we assigned fixed IP to our drones so that we don’t need to check it every time. Once TelloSwarm connects with the drones’ IP, it will put all drones under tellowarm.tellos.

Setup the drones

for i, tello in zip(range(3), telloswarm):
    tello.LOGGER.setLevel(logging.ERROR) 
    tello.connect()
    print(f'Tello Battery {i+1}: {tello.get_battery()}')
    tello.change_vs_udp(8881+i)
    tello.set_video_resolution(Tello.RESOLUTION_480P)
    tello.set_video_bitrate(Tello.BITRATE_1MBPS)
Python
tello1 = telloswarm.tellos[0]
tello2 = telloswarm.tellos[1]
tello3 = telloswarm.tellos[2]

tello1.LOGGER.setLevel(logging.ERROR) 
tello2.LOGGER.setLevel(logging.ERROR) 
tello3.LOGGER.setLevel(logging.ERROR) 

tello1.connect()
tello2.connect()
tello3.connect()

print(f'Tello1 Battery : {tello1.get_battery()}')
print(f'Tello2 Battery : {tello2.get_battery()}')
print(f'Tello3 Battery : {tello3.get_battery()}')

tello1.change_vs_udp(8881)
tello1.set_video_resolution(Tello.RESOLUTION_480P)
tello1.set_video_bitrate(Tello.BITRATE_1MBPS)

tello2.change_vs_udp(8882)
tello2.set_video_resolution(Tello.RESOLUTION_480P)
tello2.set_video_bitrate(Tello.BITRATE_1MBPS)

tello3.change_vs_udp(8883)
tello3.set_video_resolution(Tello.RESOLUTION_480P)
tello3.set_video_bitrate(Tello.BITRATE_1MBPS)
Python

Used a loop to setup each drones,

  1. Change the logging level to ERROR only, ignore all INFO feedback from DJITELLOPY.
  2. Connect the drone.
  3. Get and display the battery information.
  4. Change the video stream port to 888x, so that they will not be conflicting with each other, the original port 11111.
  5. Lower the resolution and bitrate, just want to ensure that we can get the video display. It get problems some times even with my AX6000 level router.

Swarm Action

if flip:
    fpath_1 = ['l', 'b', 'r']
    fpath_2 = ['r', 'f', 'l']
    telloswarm.parallel(lambda drone, tello: tello.flip(fpath_1[drone]))
    telloswarm.parallel(lambda drone, tello: tello.flip(fpath_2[drone]))
    
if fly:
    telloswarm.move_up(50)
    path_1 = [(-60, -60, 50, 20, 1), (60, -60, 100, 30, 2), (0, 120, 150, 40, 3)]
    path_2 = [(60, -60, 100, 30, 2), (0, 120, 150, 40, 3), (-60, -60, 50, 20, 1)]
    path_3 = [(0, 120, 150, 40, 3), (-60, -60, 50, 20, 1), (60, -60, 100, 30, 2)]
    path_4 = [(0, 0, 50, 20, 1), (0, 0, 50, 20, 2), (0, 0, 50, 20, 3)]
    telloswarm.parallel(lambda drone, tello: tello.enable_mission_pads)
    telloswarm.parallel(lambda drone, tello: tello.go_xyz_speed_mid(path_1[drone][0], path_1[drone][1], path_1[drone][2], path_1[drone][3], path_1[drone][4]))
    telloswarm.parallel(lambda drone, tello: tello.go_xyz_speed_mid(path_2[drone][0], path_2[drone][1], path_2[drone][2], path_2[drone][3], path_2[drone][4]))
    telloswarm.parallel(lambda drone, tello: tello.go_xyz_speed_mid(path_3[drone][0], path_3[drone][1], path_3[drone][2], path_3[drone][3], path_3[drone][4]))
    telloswarm.parallel(lambda drone, tello: tello.go_xyz_speed_mid(path_4[drone][0], path_4[drone][1], path_4[drone][2], path_4[drone][3], path_4[drone][4]))
Python
if flip:
    tello1_fpath = ['l','r']
    tello2_fpath = ['b','f']
    tello3_fpath = ['r','l']
    
    for i in range(2):
        flip1 = tello1_fpath[i]
        flip2 = tello2_fpath[i]
        flip3 = tello3_fpath[i]    
        tello1_flip = threading.Thread(target=tello_flip, args=(tello1, flip1), daemon=True)
        tello2_flip = threading.Thread(target=tello_flip, args=(tello2, flip2), daemon=True)
        tello3_flip = threading.Thread(target=tello_flip, args=(tello3, flip3), daemon=True)
        tello1_flip.start()
        tello2_flip.start()
        tello3_flip.start()
        tello1_flip.join()
        tello2_flip.join()
        tello3_flip.join()
    
if fly:
    telloswarm.move_up(50)
    tello1_path = [(-60, -60, 50, 20, 1), (60, -60, 100, 30, 2), (0, 120, 150, 40, 3), (0, 0, 50, 20, 1)]
    tello2_path = [(60, -60, 100, 30, 2), (0, 120, 150, 40, 3), (-60, -60, 50, 20, 1), (0, 0, 50, 20, 2)]
    tello3_path = [(0, 120, 150, 40, 3), (-60, -60, 50, 20, 1), (60, -60, 100, 30, 2), (0, 0, 50, 20, 3)]
    
    for i in range(4):
        x1, y1, z1, s1, mpad1 = tello1_path[i]
        x2, y2, z2, s2, mpad2 = tello2_path[i]
        x3, y3, z3, s3, mpad3 = tello3_path[i]
        
        tello1_mpad = threading.Thread(target=tello_mpad, args=(tello1, x1, y1, z1, s1, mpad1), daemon=True)
        tello2_mpad = threading.Thread(target=tello_mpad, args=(tello2, x2, y2, z2, s2, mpad2), daemon=True)
        tello3_mpad = threading.Thread(target=tello_mpad, args=(tello3, x3, y3, z3, s3, mpad3), daemon=True)
        tello1_mpad.start()
        tello2_mpad.start()
        tello3_mpad.start()
        tello1_mpad.join()
        tello2_mpad.join()
        tello3_mpad.join()
Python

We used telloswarm.parallel(lambda drone, tello: XXXXX) to command all drones at the same time, you can see from above. XXXXX can be any tello command or even a function.

  1. Line #48 & 50 : Flip drone #1 to left, drone #2 to backward and drone #3 to right
  2. Line #49 & 51 : Flip drone #1 to right, drone #2 to forward and drone #3 to left
  3. Line #55 & 59 : Fly drone #1 to -60, -60, 50 (x, y, z) away from mission pad 1 at speed of 20, drone #2 to 60, -60, 100 away from mission pad 2 at speed of 30 and drone #3 to 0, 120, 150 away from mission pad 3 at speed of 40
  4. Line #56 & 60, 57 & 61, 58 & 62, 59 & 63, fly to difficult positions according to the nearby mission pad to achieve drone rotations.

For telloswarm.parallel, it will wait for all actions to be done before moving to the next step.

Video streaming from drones

if video:    
    telloswarm.parallel(lambda drone, tello: tello.streamon())
    time.sleep(3)
    
    tello1_video = threading.Thread(target=tello_video, args=(telloswarm.tellos[0], 1), daemon=True)
    tello2_video = threading.Thread(target=tello_video, args=(telloswarm.tellos[1], 2), daemon=True)
    tello3_video = threading.Thread(target=tello_video, args=(telloswarm.tellos[2], 3), daemon=True)
    tello1_video.start()
    tello2_video.start()
    tello3_video.start()
Python
if video:
    
    tello1.streamon()
    tello1_video = threading.Thread(target=tello_video, args=(tello1, 1), daemon=True)
    tello1_video.start()

    tello2.streamon()
    tello2_video = threading.Thread(target=tello_video, args=(tello2, 2), daemon=True)
    tello2_video.start()
    
    tello3.streamon()
    tello3_video = threading.Thread(target=tello_video, args=(tello3, 3), daemon=True)
    tello3_video.start()

    time.sleep(3)
Python

Originally, we were using the telloswarm.parallel to execute this and the result is very good. However, as we mentioned before that the telloswarm.parallel wait until the action complete, it cannot serve our purpose, i.e. keep the video capture when flying. So, we used threading, just like what we did in the other project but running three drones by the same time.

It is quite easy to achieve drone swarm control and video streaming by using DJITELLOPY. However, I found it is a bit slow or long processing when using DJITELLOPY. Next project, we may want to communciate to the Tello SDK directly to see the result.