You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
4.2 KiB
160 lines
4.2 KiB
"""Library for controlling multiple DJI Ryze Tello drones.
|
|
"""
|
|
|
|
from threading import Thread, Barrier
|
|
from queue import Queue
|
|
from typing import List, Callable
|
|
|
|
from .tello import Tello, TelloException
|
|
from .enforce_types import enforce_types
|
|
|
|
|
|
@enforce_types
|
|
class TelloSwarm:
|
|
"""Swarm library for controlling multiple Tellos simultaneously
|
|
"""
|
|
|
|
tellos: List[Tello]
|
|
barrier: Barrier
|
|
funcBarier: Barrier
|
|
funcQueues: List[Queue]
|
|
threads: List[Thread]
|
|
|
|
@staticmethod
|
|
def fromFile(path: str):
|
|
"""Create TelloSwarm from file. The file should contain one IP address per line.
|
|
|
|
Arguments:
|
|
path: path to the file
|
|
"""
|
|
with open(path, 'r') as fd:
|
|
ips = fd.readlines()
|
|
|
|
return TelloSwarm.fromIps(ips)
|
|
|
|
@staticmethod
|
|
def fromIps(ips: list):
|
|
"""Create TelloSwarm from a list of IP addresses.
|
|
|
|
Arguments:
|
|
ips: list of IP Addresses
|
|
"""
|
|
if not ips:
|
|
raise TelloException("No ips provided")
|
|
|
|
tellos = []
|
|
for ip in ips:
|
|
tellos.append(Tello(ip.strip()))
|
|
|
|
return TelloSwarm(tellos)
|
|
|
|
def __init__(self, tellos: List[Tello]):
|
|
"""Initialize a TelloSwarm instance
|
|
|
|
Arguments:
|
|
tellos: list of [Tello][tello] instances
|
|
"""
|
|
self.tellos = tellos
|
|
self.barrier = Barrier(len(tellos))
|
|
self.funcBarrier = Barrier(len(tellos) + 1)
|
|
self.funcQueues = [Queue() for tello in tellos]
|
|
|
|
def worker(i):
|
|
queue = self.funcQueues[i]
|
|
tello = self.tellos[i]
|
|
|
|
while True:
|
|
func = queue.get()
|
|
self.funcBarrier.wait()
|
|
func(i, tello)
|
|
self.funcBarrier.wait()
|
|
|
|
self.threads = []
|
|
for i, _ in enumerate(tellos):
|
|
thread = Thread(target=worker, daemon=True, args=(i,))
|
|
thread.start()
|
|
self.threads.append(thread)
|
|
|
|
def sequential(self, func: Callable[[int, Tello], None]):
|
|
"""Call `func` for each tello sequentially. The function retrieves
|
|
two arguments: The index `i` of the current drone and `tello` the
|
|
current [Tello][tello] instance.
|
|
|
|
```python
|
|
swarm.parallel(lambda i, tello: tello.land())
|
|
```
|
|
"""
|
|
|
|
for i, tello in enumerate(self.tellos):
|
|
func(i, tello)
|
|
|
|
def parallel(self, func: Callable[[int, Tello], None]):
|
|
"""Call `func` for each tello in parallel. The function retrieves
|
|
two arguments: The index `i` of the current drone and `tello` the
|
|
current [Tello][tello] instance.
|
|
|
|
You can use `swarm.sync()` for syncing between threads.
|
|
|
|
```python
|
|
swarm.parallel(lambda i, tello: tello.move_up(50 + i * 10))
|
|
```
|
|
"""
|
|
|
|
for queue in self.funcQueues:
|
|
queue.put(func)
|
|
|
|
self.funcBarrier.wait()
|
|
self.funcBarrier.wait()
|
|
|
|
def sync(self, timeout: float = None):
|
|
"""Sync parallel tello threads. The code continues when all threads
|
|
have called `swarm.sync`.
|
|
|
|
```python
|
|
def doStuff(i, tello):
|
|
tello.move_up(50 + i * 10)
|
|
swarm.sync()
|
|
|
|
if i == 2:
|
|
tello.flip_back()
|
|
# make all other drones wait for one to complete its flip
|
|
swarm.sync()
|
|
|
|
swarm.parallel(doStuff)
|
|
```
|
|
"""
|
|
return self.barrier.wait(timeout)
|
|
|
|
def __getattr__(self, attr):
|
|
"""Call a standard tello function in parallel on all tellos.
|
|
|
|
```python
|
|
swarm.command()
|
|
swarm.takeoff()
|
|
swarm.move_up(50)
|
|
```
|
|
"""
|
|
def callAll(*args, **kwargs):
|
|
self.parallel(lambda i, tello: getattr(tello, attr)(*args, **kwargs))
|
|
|
|
return callAll
|
|
|
|
def __iter__(self):
|
|
"""Iterate over all drones in the swarm.
|
|
|
|
```python
|
|
for tello in swarm:
|
|
print(tello.get_battery())
|
|
```
|
|
"""
|
|
return iter(self.tellos)
|
|
|
|
def __len__(self):
|
|
"""Return the amount of tellos in the swarm
|
|
|
|
```python
|
|
print("Tello count: {}".format(len(swarm)))
|
|
```
|
|
"""
|
|
return len(self.tellos)
|