Coder Social home page Coder Social logo

robagar / tello-asyncio Goto Github PK

View Code? Open in Web Editor NEW
24.0 3.0 5.0 100 KB

A library for controlling and interacting with the Tello EDU drone using modern asynchronous Python.

License: GNU Lesser General Public License v2.1

Python 100.00%
python drone tello asyncio

tello-asyncio's Introduction

tello-asyncio

A library for controlling and interacting with the Tello EDU drone using modern asynchronous Python. All operations are implemented as awaitable coroutines, completed when the drone sends acknowledgment of the command message.

Package tello-asyncio on PyPi.

(If Rust is more your thing, there is also an equivalent asynchronous Rust library tello-edu)

$ pip3 install tello-asyncio
import asyncio
from tello_asyncio import Tello

async def main():
    drone = Tello()
    try:
        await drone.connect()
        await drone.takeoff()
        await drone.turn_clockwise(360)
        await drone.land()
    finally:
        await drone.disconnect()

asyncio.run(main())

See the examples directory for more usage example scripts.

Requires Python 3.6+. Developed and tested with Python 3.9.4 in Mac OS and 3.6.9 in Ubuntu 18.04 on a Jetson Nano. The tello_asyncio package has no other dependencies (and never will have any), but some examples need other things to be installed to work.

Full documentation is available on Read the docs

Tello SDK Support

  • Tello SDK 2.0 (Tello EDU) - complete support
  • Tello SDK 3.0 (RoboMaster TT) - complete support, but EXT commands for controlling LEDs etc must be formatted by the user

A Note on Awaiting

The Tello SDK command/response model is a natural fit for the asynchronous python awaitable idea, but the drone will get confused if commands are sent before it's had a chance to respond. Each command should be awaited before sending the next.

It works best sequentially like this...

await drone.takeoff()
await drone.land()

...but not concurrently (which will not work as expected)

await asyncio.gather(
    drone.takeoff(), 
    drone.land()
)

Version History

1.0.0

Basic drone control

  • UDP connection for sending commands and receiving responses (default AP mode only - you must join the drone's own WiFi network)
  • take off and land
  • rotate clockwise and counter-clockwise
  • move up, down, left, right, forward and back

1.1.0

Drone state

  • listens for and parses UDP state messages (not yet including the mission pad related values)
  • access via the read only state object attribute, or via shortcuts like height, temperature etc
  • constructor takes an optional on_state callback argument for notification of new state
  • or use the asynchronous generator state_stream for an infinite stream of updates

1.2.0

Advanced drone control

  • flips
  • go/curve to relative position
  • emergency stop

Video

  • start/stop video stream
  • video url

Error handling

  • handles error command responses from drone

1.3.0

Complete SDK

  • mission pads
  • wifi
  • remote control

Video

  • raw video frame data via callback or async generator

Error handling

  • detects command/response mismatch

1.3.1

  • Documentation

1.3.2

1.4.0

  • Video frame data reassembled properly from UDP packet chunks
  • Working video frame decoding example

1.4.1

  • Video in OpenCV example

1.5.0

  • Python 3.6 support

1.6.0

  • Drone instance passed to state and video callbacks
  • Wait for WiFi network (Linux only)

2.0.0

  • Tello SDK 3.0 support

2.1.0

  • Wait for Wifi network implemented for macOS as well as Linux
  • Mission pad fixes & example improvement (thanks @jsolderitsch!)

2.1.1

  • Examples ask the user for the WiFi name prefix

2.1.2

  • Don't wait for a response from remote control (rc x x x x) commands

2.1.3

  • Various SDK 3.0 fixes (thanks @jdelfino!)
  • Handle "forced stop" message which arrives out of sequence

tello-asyncio's People

Contributors

jdelfino avatar jsolderitsch avatar robagar avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

tello-asyncio's Issues

wifi detection logic not working on Mac with Tello TT network -- version 2.1.0

Sorry to report this after your recent work.

I upgraded to the latest release.

I am using the Tello TT with the LED add-on attached. My network SSID does not begin with TELLO.

I see:

jjsretina:examples jjs$ python battery.py 
waiting for WiFi (TELLO)...
Current Wi-Fi Network: RMTT-9B02B6

Current Wi-Fi Network: RMTT-9B02B6

This repeats indefinitely.

I will try with the LED attachment removed and comment here.

Opencv example does not work for linux or Mac OS: address already in use

Maybe I am doing something wrong but I am following the docs for the asyncio package. I see:

pi@raspberry:~/tello/tello-asyncio/examples $ python3 video_opencv.py 
waiting for WiFi (TELLO)...
...wait for WiFi failed, error: Cannot add child handler, the child watcher does not have a loop attached
assuming WiFi network is connected and continuing
CONNECT 192.168.10.1
SEND command
RECEIVED ok
SEND battery?
RECEIVED 83

battery: 83%
SEND streamoff
RECEIVED ok
DISCONNECT 192.168.10.1
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.7/threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "video_opencv.py", line 27, in fly
    asyncio.run(main())
  File "/usr/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.7/asyncio/base_events.py", line 584, in run_until_complete
    return future.result()
  File "video_opencv.py", line 19, in main
    await drone.start_video()
  File "/home/pi/.local/lib/python3.7/site-packages/tello_asyncio/tello.py", line 611, in start_video
    await self.connect_video(on_frame)
  File "/home/pi/.local/lib/python3.7/site-packages/tello_asyncio/tello.py", line 634, in connect_video
    await self._video.connect(self._loop, self._on_video_frame_chunk, self._on_video_frame)
  File "/home/pi/.local/lib/python3.7/site-packages/tello_asyncio/video.py", line 38, in connect
    local_addr=("0.0.0.0", VIDEO_UDP_PORT)
  File "/usr/lib/python3.7/asyncio/base_events.py", line 1245, in create_datagram_endpoint
    raise exceptions[0]
  File "/usr/lib/python3.7/asyncio/base_events.py", line 1230, in create_datagram_endpoint
    sock.bind(local_address)
OSError: [Errno 98] Address already in use

This is in a VM emulating a Raspberry Pi. Looks like the wifi check fails because of the separate threads? I don't know what address is already in use. The video UDP connection should not have been established before since this is the first time I am running the example.

On the Mac it's a very similar result:

jjsretina:examples jjs$ python video_opencv.py 
waiting for WiFi (RMTT)...
...wait for WiFi failed, error: Cannot add child handler, the child watcher does not have a loop attached
assuming WiFi network is connected and continuing
CONNECT 192.168.10.1
SEND command
RECEIVED ok
SEND battery?
RECEIVED 93

battery: 93%
SEND streamoff
RECEIVED ok
DISCONNECT 192.168.10.1
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/local/Cellar/[email protected]/3.7.10_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/threading.py", line 926, in _bootstrap_inner
    self.run()
  File "/usr/local/Cellar/[email protected]/3.7.10_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "video_opencv.py", line 27, in fly
    asyncio.run(main())
  File "/usr/local/Cellar/[email protected]/3.7.10_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/usr/local/Cellar/[email protected]/3.7.10_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 587, in run_until_complete
    return future.result()
  File "video_opencv.py", line 19, in main
    await drone.start_video()
  File "/usr/local/lib/python3.7/site-packages/tello_asyncio/tello.py", line 611, in start_video
    await self.connect_video(on_frame)
  File "/usr/local/lib/python3.7/site-packages/tello_asyncio/tello.py", line 634, in connect_video
    await self._video.connect(self._loop, self._on_video_frame_chunk, self._on_video_frame)
  File "/usr/local/lib/python3.7/site-packages/tello_asyncio/video.py", line 38, in connect
    local_addr=("0.0.0.0", VIDEO_UDP_PORT)
  File "/usr/local/Cellar/[email protected]/3.7.10_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 1256, in create_datagram_endpoint
    raise exceptions[0]
  File "/usr/local/Cellar/[email protected]/3.7.10_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 1240, in create_datagram_endpoint
    sock.bind(local_address)
OSError: [Errno 48] Address already in use

Could I need to update the version of Python? -- in both cases, a version of Python 3.7 is in use.

opencv weirdness still after code change

I see that the previous issue was closed so I am opening a new one.

I fetched the example code change and tried it.

I see:

jjsretina:examples jjs$ python video_opencv.py 
Please enter the WiFi network name prefix to look for, eg "RMTT". Defaults to "TELLO"> 
waiting for WiFi (TELLO)...
CONNECT 192.168.10.1
SEND command
RECEIVED ok
SEND battery?
RECEIVED 82

battery: 82%
SEND streamon
RECEIVED ok
SEND takeoff
RECEIVED ok
SEND cw 360
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] non-existing PPS 0 referenced
[h264 @ 0x7fbca0009200] decode_slice_header error
[h264 @ 0x7fbca0009200] no frame!
RECEIVED ok
SEND land
RECEIVED ok
SEND streamoff
RECEIVED ok
DISCONNECT 192.168.10.1
^CTraceback (most recent call last):
  File "/Users/jjs/Documents/Villanova/CSC9010-Spr2021/Future/TelloTT/asyncio-fork/tello-asyncio/examples/video_opencv.py", line 45, in <module>
    if cv2.waitKey(1) != -1:
KeyboardInterrupt

The code hangs at the end and I used Ctrl-C to interrupt. The opencv window does popup and shows the view despite the h264 errors. Maybe they are normal?

I updated my Mac to Python 3.9.5 last night and just tried the example today. Interestingly I still got the address in use error with the original code and my new 3.9.5 install and that disappeared when I updated the example.

Running on a Pi with Thonny

Hi
Having some issues - using Thonny with Python 3.9.2 - installed the tello-asyncio via the Tools/Manage packages... - seems to install, however will not find the module. I can see the folder and files here "/home/pi/.local/lib/python3.9/site-packages"

Anybody else had this issue ?
or any ideas ?

So I then switched to python 3.7, again installed tello-asyncio, seems ok - according to the Pi wifi I'm associated with Tello but Thonny refuses to connect, get timeout after entering the wifi network name

ph1lj

Drone wait for wifi network broken for Mac OS

This command:

        await drone.wifi_wait_for_network()

leads to an error on Mac OS (catalina at least):

python clockwise_360.py 
waiting for WiFi...
...wait for WiFi failed, error: /bin/sh: iwgetid: command not found

assuming WiFi network is connected and continuing

Anything that I can do other than removing this await command?

on 'stop' function call drone aborts instead of interrupting movement and hover

Hi

I want to interrupt a drone move using the 'stop' command ( await tello.stop() ) but it raises an error instead of "Stop moving and hover immediately." as the Tello SDK suggests.

I tried a quick fix modifying line 73 of main/tello_asyncio/tello.py from:

                if message == 'ok':

to:

                if message == 'ok' or message == 'forced stop':

It is not fully working and I am not sure it complies with the rest of the code

ROS integration

How does one implement rospy subscriber callbacks with asyncio?

I've tried both create_task and run_coroutine_threadsafe suggested here
https://answers.ros.org/question/362598/asyncawait-in-subscriber-callback/ without success.

run_coroutine_threadsafe looks like the solution but I can't make it work.. Have you had experience with your library of inserting a coroutine into the main loop from another thread?

Example:

self.rl = asyncio.get_running_loop() 

takeOffSub = rospy.Subscriber('takeoff', Empty, self.takeoff_callback)

async def myTakeOff(self):
        await self.drone.takeoff()

def takeoff_callback(self,msg):
        print("taking off")
        future = asyncio.run_coroutine_threadsafe(self.myTakeOff(), self.rl)
        result = future.result()
        print(result)

ROS works on a publisher-subscriber topic model. In this case , when a message (Empty) is published (by another program running simultaneously) to the topic 'takeoff', takeoff-callback() is called. This cannot be a coroutine, so cannot be async; hence, the use of run_coroutine_threadsafe. This call never returns and myTakeOff() is not called. Any ideas what is wrong?
Note: Execution does pass to the callback, "taking off" is printed. myTakeOff() runs successfully when called directly from the the main loop with await.

remote_control() function times out because rc has no response by design

The function remote_control(...) runs into a timeout because rc (accoding to the SDK doc) does not generate a response.

After that, the framwork seems to be in an erratic state because every other command sent afterwards picks up and shows the response but some seconds later you still get a timeout error. Python program must be completely exited and started from scratch in order to comeback to a normal communication.

You can easily test this with the example "interactive_command.py".

go_to_mission_pads.py not working as expected

I changed the code slightly to try to go from pad 2 to pad 1 and then to pad 3.

Here is what I see:

pi@raspberry:~/tello/tello-asyncio/examples $ python3 go_to_mission_pads.py 
waiting for WiFi...
CONNECT 192.168.10.1
SEND command
RECEIVED ok
SEND takeoff
RECEIVED ok
SEND mon
RECEIVED ok
mission pad: 2, position: Vector(x=-4.0, y=-3.0, z=74.0)
SEND go 0 0 100 25 m2
RECEIVED ok
mission pad: 2, position: Vector(x=-4.0, y=-3.0, z=74.0)
SEND go 0 0 100 25 m1
RECEIVED error No valid marker
[go 0 0 100 25 m1] ERROR error No valid marker
SEND land
RECEIVED ok
DISCONNECT 192.168.10.1
Traceback (most recent call last):
  File "go_to_mission_pads.py", line 31, in <module>
    loop.run_until_complete(main())
  File "/usr/lib/python3.7/asyncio/base_events.py", line 584, in run_until_complete
    return future.result()
  File "go_to_mission_pads.py", line 20, in main
    await drone.go_to(relative_position=Vector(0, 0, 100), speed=25, mission_pad=1)
  File "/home/pi/.local/lib/python3.7/site-packages/tello_asyncio/tello.py", line 369, in go_to
    return await self.send(command, timeout=LONG_RESPONSE_TIMEOUT)
  File "/home/pi/.local/lib/python3.7/site-packages/tello_asyncio/tello.py", line 502, in send
    response_message, result = await asyncio.wait_for(response, timeout=timeout)
  File "/usr/lib/python3.7/asyncio/tasks.py", line 416, in wait_for
    return fut.result()
tello_asyncio.tello.Error: error No valid marker

The pads are close by to each other, maybe 50 cm's apart. They are arranged in a straight line.

Maybe my expectations are wrong about what should happen.

Wifi handling needs fix for Tello TT and Open Source module

In types.py there are lines:

class ControllerHardware(Enum):
    TELLO = 'TELLO'
    OPEN_SOURCE = 'RMTT'

In fact if you connect the new Open Source Module to the Tello, the default wifi prefix starts with RMTT.

But the wifi startup logic breaks because there is a hardcoded string in tello.py:

    _wifi_ssid_prefix = 'TELLO'

There needs to be some way to allow the ssid prefix begin with either TELLO or RMTT.

Not sure what the best way to achieve this might be.

Since the Mac platform does not implement the wait for function, then the code examples works even with the new Wifi access point name pattern.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.