So, where to begin?
I tried to use this ROS2 tello_driver. Back then it seemed promising, but after some messing around, I've learned that this ROS2 tello_driver is intended for tello EDU, which uses SKD 2.0, while the regular tello uses SDK 1.3.
DJI tend to do so in their so-called open-API products, like Tello and RobomasterS1 (we have one of these in work, for fun and hacking purposes).
tl;dr
In the previous post, I realized that the ROS2 tello_driver's flight_data message was missing the POS data, which is relevant for making an Autonomous drone. Hence I hacked the sh**t out of the good old tello_driver (I worked with it before), and of this ROS2 tello_driver as well, and trying to figure out what makes them tick.
As part of learning and improving ROS2 & C++ skills, in this post, I will share the journey I went through, and what I've learned on the way.
Let's dive in!
The next step was figuring out which communication protocol does the ROS2 tello_driver uses, and what are the differences between this protocol and the one that TelloPy's driver uses. According to their explanation, the ROS2 tello_driver is implemented around the DJI's SDK. DJI released two types of SDK's: 1.3, and 2.0, therefore I needed to get to know them both, here is what I learned:
SDK 2.0
According to DJI sdk 2 documentation:
The user can send many HIGH-LEVEL commands through port: 8889, such as:
- takeoff
- land
- flip
- go x y z speed mid <--- 'mid' == Mission ID
- Many more high-level commands..
The Tello sends back its state (through port: 8990):
- mid
- x
- y
- z
- pith
- Many more parameters..
- Well, that's lovely.
If the drone knows it's position (Only god knows how it is being inferred - for me it is a BlackBox), then the goal of building an autonomous drone is more feasible then before!
In the previous post, showed that installing ROS2 tello_driver is easy as plug and play, also I posted a short video showing the /flight_data
topic ( a ROS convention for message):
One can see the empty x y z fields of the message.
After some digging in the ROS2 tello_driver code, I found that it has a state_socket thread, which performs minimal parsing from the raw data:
Let's break it down:
It seems the SDK is being inferred from the message:
Accordingly, x y z fields are being filled:
According to the SDK 2.0; drone state
is being published to port 8890.
Using Wireshark, I saw that the packet sent to port 8890 contains parsed data. That fits the sdk 2.0 description:
Packet's data
section contains drone state in one line:
pitch:0;roll:0;yaw:-5;vgx:0;vgy:0;vgz:0;templ:60;temph:62;tof:10;h:0;bat:63;baro:149.54;time:0;agx:-15.00;agy:1.00;agz:-998.00;
This oneliner can be easily parsed using this parser:
And with this parser, data is being inserted from the message into fields
:
The final ROS2 message look likes that:
(using the following command, in a new terminal: ros2 topic echo /flight_data
)
Using the good-old TelloPy driver
Surprisingly, after running the TelloPy's 'simpletakeoff.py' script and subscribing to 'drone.EVENTLOG_DATA', data was successfully received from the drone, including POS!!!
This is the simple_takeoff.py
script I ran (with minor modifications):
And this is how the drone.EVENT_LOG_DATA
looks like:
Can you see the POS: 0.02 0.01 -0.01
?!
What is happening under the hood?
Well, it seems that hanyazou wrapped a go_lang project that hacked the Tello packets protocol.
Looking again at the simple_takeoff.py
:
Tello.py
module looks like that:
Wait what?! what is port 9000????
It appears that there's an undocumented port, through which all the drone's data is 'spilled' into.
After sniffing that port with Wireshark:
There's no human-readable
data here.
And yet, the driver extrapolated data successfully.
So I looked even deeper...
This is what the __recv_thread looks like:
Side Note
*KEEP ALIVE
- The Tello drone turns itself OFF if no command is received within 30 sec. Hence for every data the Tello drone sends to the TelloPy client, self.__send_stick_command()
is called to simulate stick commands back to the Tello drone.
Before diving into self.__process_packet(data)
, Some preperation is needed.
Inside protocol.py
, hanyazou mapped all possible commands to their HEX value:
Also, I've learned from the go_lang project the structure of the Tello drone packets:
Position | Usage |
---|---|
0 | 0xcc indicates the beggining of a packet |
1-2 | Packet size, 13 bit encoded ([2] << 8) | ([1] >> 3 ) |
3 | CRC8 of bytes 0-2 |
4 | Packet type ID |
5-6 | Command ID, little endian |
7-8 | Sequence number of the packet, little endian |
9-(n-2) | Data (if any) |
(n-1)-n | CRC16 of bytes 0 to n-2 |
So for example: 0xcc 0x58 0x00 0x7c 0x68 0x54 0x00 0xe4 0x01 0xc2 0x16
Value | Usage |
---|---|
0xcc | the beggining of a packet |
0x58 0x00 | Packet size of 11 |
0x7c | CRC8 of bytes 0-2 |
0x68 | Packet type ID |
0x54 0x00 | "Takeoff" command ID, little endian |
0xe4 0x01 | Sequence number of the packet, little-endian |
0xc2 0x16 | CRC16 of bytes 0 to 8 |
Knowing the packet structure, and using the messages mapping described above, I can now understand the sniffed package from port 9000:
0xcc 0xa0 0x11 0x50 0x88 0x51 0x10 0x14 0x02 0x00 0x55 0x43 0x00 0x15 .....
Value | Usage |
---|---|
0xcc | the beggining of a packet |
0xa0 0x11 | Packet size of 564.0 |
0x50 | CRC8 of bytes 0-2 |
0x88 | Packet type ID |
0x51 0x10 | "LOG_DATA_MSG" command ID, little endian, hence: 0x1051 |
0x14 0x02 | Sequence number of the packet, little-endian |
0x00 0x55 0x43 ..... | Data |
As for processing a packet:
While Breaking down this packet processor. Although it seemed scary with all that long code, it all went down to this line::
Earlier, in simplte_takeoff.py
, I subscribed to drone.subscribe(drone.EVENT_LOG_DATA, handler)
. This is the type of packet I looked for in this packet_processor:
This line is the actual data extraction. This is what I looked for:
Bare with me, Its almost over :)
Peeking inside log_data.update()
:
Jackpot!
This line:
And this one:
Now that I've found a way to extract POS from the packet that comes from port 9000, I need to implement a similar packet_processor in CPP, and add it to the ROS2 tello_driver.
So, what is the difference?
- SUPPORTs ROS2 (discussed in the previous post).
- Uses a documented SDK, supplied by DJI.
- Listens to port
8890
, which according to the SDK, suppliesdrone state
:human-readable
data, that can be easily parsed and published.
It has known serialized structure:pitch:0;roll:0;yaw:-5;vgx:0;vgy:0;vgz:0...
- CPP
- Fast.
- Relevant for me.
TelloPy driver:
- NO ROS2 SUPPORT
- Listens to port
9000
- An undocumented open port at the drone, this port appears to sendRAW_LOG_DATA
that contains EVERYTHING! It has been discovered by the community - with reverse engineering (and probably lots of hours using Wireshark) - PYTHON only
- Easy to understand & maintain & hack.
Final thoughts
In this post, I've focused on the journey I took.
It seems that I'm ought to implement a CPP packet_processor, for the ROS2 tello_driver. Thus adding support for RAW_LOG_DATA
logging capabilities.
This is an excellent opportunity to support the development of ROS2. This mini-series is all about supporting the ROS2 community. I do have thoughts about POS
data accuracy/availability though, will it be enough for a position estimator? is it solid enough to be considered in the VSLAM
algorithm?
While the questions arise, the quest must go on.
Cheers, Gal.