mavlink/MAVSDK-Python

telemetry.position() not working.

Closed this issue ยท 19 comments

mavsdk Version: 0.6.1-1-g409a253
mavsdk_server Version: arm64 v0.24.0

I'm using jetson nano and pixhawk 4.

Steps to reproduce:
1.change example/telemetry.py to use serial port
2.run python3 telemetry.py

Expected Behavior:
show all telemetry in example

Actual Behavior:
only show gps info, in air and battery, position not show. However I can see position info in QGroundControl.

Can you takeoff from QGC? And can you takeoff from MAVSDK?

I assume you are using HITL? Could you try with udp and SITL?

I need to look into this at some point. It has now come up several times.

Always from python?

Always from python?

Maybe, not sure.

I had the same problem earlier, maybe that can help, though I used actual board and GPS device, not simulation.
MAVSDK-Python telemetry only had position data if is_home_position was True in health telemetry. So even if is_global_position is True and it sees 10 satellites, it needs the home position data to send the position telemetry. To achieve that I needed to take the gps device out under the clear sky, (is_global_position_ok was True inside the house also) and wait a few seconds.

it needs the home position data to send the position telemetry

Doesn't that mean that it needs a GPS fix? Which makes sense to me: before the drone knows its GPS position, it cannot send it. Or am I missing something?

I had the same problem earlier, maybe that can help, though I used actual board and GPS device, not simulation.

MAVSDK-Python telemetry only had position data if is_home_position was True in health telemetry. So even if is_global_position is True and it sees 10 satellites, it needs the home position data to send the position telemetry. To achieve that I needed to take the gps device out under the clear sky, (is_global_position_ok was True inside the house also) and wait a few seconds.

Thanks, this may solve my problem. I did use a GPS device and I put it outside the window before, QGC show fixed gps data, SDK show fixtype change but does not have position data. I will take it out and test later.

Screenshot inside house
Screenshot inside house 1
Screenshot out

Confirmed.

@JonasVautherin that is not the case I think. I just tested it again inside, getting these results:

GpsInfo: [num_satellites: 9 | fix_type: FIX_3D] | Health: [is_gyrometer_calibration_ok: True | is_accelerometer_calibration_ok: True | is_magnetometer_calibration_ok: True | is_level_calibration_ok: True | is_local_position_ok: True | is_global_position_ok: True | is_home_position_ok: False]

and the code is stuck at getting data from drone.telemetry.position, not providing anything, as in
async for pos in drone.telemetry.position():
drone.telemetry.position() would be 'empty'.

When I tested outside earlier, the only difference was that in health also the last check (home position) was True (and it was also indicated by the gps device with the corresponding beeping), and after that position data were streamed correctly.

@skymaze I'm happy it is solved!

Ok I think I'll need @julianoes insights regarding this "home" thing ๐Ÿค”

I looked into this bug. I think something is wrong with the version at pypi.org

I've run into a similar bug when using telemetry.position() . In my program, I have a line that's nearly identical to:

async for position in drone.telemetry.position():
    print(position)

In my case, the values returned from telemetry.position() are stuck near the first place sensed by the drone. I see the altitude changing by a small amount (less than 0.5 meters). And I wasn't able to determine if the latitude and longitude values are changing. But if they are changing, then it's also by a small amount. Furthermore, the values don't match reality. I can tell a simulated drone to take off (and watch it fly up), but the positions returned only change by that small amount.

I run into the bug when I install version 0.7.0 at https://pypi.org/project/mavsdk/. Specifically, when I use the version provided by pip3 install mavsdk.

To avoid the bug, I can tell pip to install git+https://github.com/mavlink/MAVSDK-Python.git@0.7.0.

I'm using Ubuntu 18.04, and here is how I set up my build environment:

git clone https://github.com/PX4/Firmware.git --recursive
cd Firmware
bash ./Tools/setup/ubuntu.sh

sudo apt install --yes software-properties-common
sudo add-apt-repository --yes ppa:deadsnakes/ppa
sudo apt update
sudo apt install --yes python3.8 python3.8-venv python3.8-doc python3.8-dev binutils

From there I create a python 3.8 venv and I use pip to install git+https://github.com/mavlink/MAVSDK-Python.git@0.7.0.

@murphym18: thanks for the report! So I just tried to pip install mavsdk, print the position and takeoff. I see the altitude moving from 0 to 2.5 meters. I have multiple questions for you:

  • Did you try against SITL over UDP? The issue mentions serial, and nobody above mentioned trying with SITL. It would be helpful to know.
  • You mention using 0.7.0 from github, which may mean that it's a clean install. Would you mind trying in a venv? Something like this:
mkdir /tmp/test-mavsdk
cd /tmp/test-mavsdk
python3 -m venv venv
source ./venv/bin/activate
pip install mavsdk

Then still from this venv, run:

async for position in drone.telemetry.position():
    print(position)

And make the drone takeoff (in SITL that can be just commander takeoff from the psh prompt).

Did you try against SITL over UDP? The issue mentions serial, and nobody above mentioned trying with SITL. It would be helpful to know.

Yup I used UDP. Here is the system address: udp://:14540

You mention using 0.7.0 from github, which may mean that it's a clean install. Would you mind trying in a venv?

I've been using virtual environments exclusively (created using python3.8 -m venv .venv).
To install mavsdk I changed my requirements.txt file to look like this:

boltons
coverage
geographiclib
git+https://github.com/mavlink/MAVSDK-Python.git@0.7.0
numpy
nvector
pylint
pytest
pyyaml
radon
ratelimiter
wheel

But at your suggestion, I made a new venv, with pip install mavsdk. I also created a script with:

async for position in drone.telemetry.position():
    print(position)

When I entered commander takeoff in jmavsim, I could see the drone's altitude rising to about 1.75.

Given that behavior, I rechecked my program (the one that uses git+https://github.com/mavlink/MAVSDK-Python.git@0.7.0). And it turns out the bug is still there, but only when I connect to more than one drone.

It might be helpful to see how I launch more than one drone. So the remainder of this post covers that.

For each drone, I start a new mavsdk_server process. Here is the module:

"""A mavsdk_server launcher

The module starts a mavsdk_server for the given system address. It also stop the server's process at exit.

Example:

>>> from _mavsdk_backend import start_mavsdk_server
>>> from mavsdk import System
>>> sys_addr = "udp://:14540"
>>> backend_host, backend_port = start_mavsdk_server(system_address=sys_addr)
>>> drone = System(mavsdk_server_address=backend_host, port=backend_port)
>>> # in an async function
>>> await drone.connect(system_address=sys_addr) 


"""
import atexit
import os
import subprocess
import sys
import threading
from importlib.resources import path

from mavsdk import bin


_next_port = 50051
_servers = []
_lock = threading.Lock()

def start_mavsdk_server(system_address):
    with _lock:
        if len(_servers) < 1:
            atexit.register(_cleanup)
        
        mavsdk_port = _get_port()
        _mavsdk(system_address, mavsdk_port)

        return 'localhost', mavsdk_port

def _get_port():
    global _next_port
    port = _next_port
    _next_port = _next_port + 1
    return port
        
def _mavsdk(system_address, port):
    with path(bin, 'mavsdk_server') as backend:
        exec_cmd = [os.fspath(backend), "-p", str(port), system_address]
        
        p = subprocess.Popen(exec_cmd,
                                shell=False,
                                stdout=subprocess.DEVNULL,
                                stderr=subprocess.DEVNULL)
        _servers.append(p)
        

def _cleanup():
    with _lock:
        for mavsdk_process in _servers:
            mavsdk_process.kill()

In another module I use the start_mavsdk_server function to create an object, of type Px4Drone. The details of that object aren't relevant. Px4Drone owns the drone instance:

async def _get_instance(system_address, mavsdk_server_host, mavsdk_server_port, uav_id, message_callback):
    drone = System(mavsdk_server_host, mavsdk_server_port)
    await drone.connect(system_address)
    async for state in drone.core.connection_state():
        if state.is_connected:
            log.info(f"Drone discovered with UUID: {state.uuid}")
            if uav_id is None:
                uav_id = state.uuid
            break
    px4 = Px4Drone(drone, uav_id, message_callback)

    return px4

def get_instance(system_address="udp://:14540", uav_id=None, message_callback=noop):
    mavsdk_server_address, mavsdk_server_port = start_mavsdk_server(system_address)
    fut = asyncio.run_coroutine_threadsafe(
        _get_instance(system_address, mavsdk_server_address, mavsdk_server_port, uav_id, message_callback), loop)
    return fut.result()

And Px4Drone grabs the location with this method:

    async def _position(self):
        async for position in self.drone.telemetry.position():
            self.log.debug(f"position: {position}")
            self.position = position

After takeoff, when I look at the logs, I can see the position value as 0.007000000216066837. This is the case even though jmavsim shows the drone in the air: Here is a screenshot:

Screenshot from 2020-05-30 04-31-22

Very nice, @murphym18 ๐Ÿ‘ ๐Ÿ˜Š.

This raises 2 questions on my side:

  1. When you use git+https://github.com/mavlink/MAVSDK-Python.git@0.7.0, where does the embedded mavsdk_server executable come from? It is not in the MAVSDK-Python repo, so it has to come from somewhere.
  2. I see def get_instance(system_address="udp://:14540". I assume that you create multiple SITL instances, and each of them is on another port. So system_address is not always "udp://:14540", but rather "udp://:14540" for the first drone, "udp://:14541" for the next one, etc. Is that correct?

Because it works with one drone, but not with multiple drones, it suggests that maybe they conflict with each other in some way. Having a separate MAVLink port and a separate mavsdk_server instance for each should work (I used it in the past, though not with 0.7.0 which I will try), but that's not super straightforward with the current API. The way you do it in the code above is nice ๐Ÿ‘.

where does the embedded mavsdk_server executable come from?

As far as I know, mavsdk_server gets built on my machine. pip fails if I haven't run:

git clone https://github.com/PX4/Firmware.git --recursive
cd Firmware
bash ./Tools/setup/ubuntu.sh

And there is a compilation error saying something like the compiler isn't found.

Here is a gist with all the output

In fact, it takes a long time to compile and I was wondering if there was a way to make the compiler use more than one thread. ๐Ÿ˜†

I see def get_instance(system_address="udp://:14540". I assume that you create multiple SITL instances, and each of them is on another port. So system_address is not always "udp://:14540", but rather "udp://:14540" for the first drone, "udp://:14541" for the next one, etc. Is that correct?

Yes, that is correct. In the scenario screenshotted above, I call the function with "udp://:14550", "udp://:14541", "udp://:14542"

But I just noticed that the first port was 14550. So I ran another test with ports 14540, 14541 and 14542. And I get the same position bug.

Thank you by the way. When I get more time, I was hoping to make a pull request to add a multi-drone example in response to #102

As far as I know, mavsdk_server gets built on my machine. pip fails if I haven't run:

That would surprise me. Nothing in MAVSDK-Python says how mavsdk-server should be built. Howerver, setup.py does download mavsdk_server from the releases page. Which in the end would result in the same binary as the one embedded in the wheel you get with pip install mavsdk ๐Ÿค”.

I don't really get what pip install git+https://github.com/mavlink/MAVSDK-Python.git@0.7.0 does. Does it try to build the dependencies as well? I ran it and it seemed to take a very long time. Is it trying to build gRPC from sources?

One thing that's missing in MAVSDK-Python is that we just discard the output of mavsdk_server. Would you mind trying to not start mavsdk_server from your python code, but to start it manually in a shell? I can't find an error in your code, but I just want to make sure that we don't end up connecting all the drone objects to the same mavsdk_server instance, or something like that...

Is there an upper limit to the number of drone.telemetry subscriptions we can access at the same time?

I got telemetry.position() to work with more than one drone in a couple of versions of mavsdk-python and in mavsdk-C++. When it works, I notice that I'm subscribed to position and not much else. Usually, for each drone, I subscribe to many streams. Here is an excerpt:

        for sensor_function, sensor_name in  [
            (self.drone.core.connection_state, 'connection_state'),
            (self.drone.telemetry.armed, 'armed_state'),
            (self.drone.telemetry.attitude_euler, 'attitude'),
            (self.drone.telemetry.battery, 'battery'),
            (self.drone.telemetry.flight_mode, 'flight_mode'),
            (self.drone.telemetry.gps_info, 'gps_info'),
            (self.drone.telemetry.health, 'health'),
            (self.drone.telemetry.in_air, 'in_air'),
            (self.drone.telemetry.position, 'position'),
            (self.drone.telemetry.status_text, 'status_text'),
            (self.drone.telemetry.velocity_ned, 'velocity'),
        ]:
            self.log.debug(f"{sensor_name} sensor starting")
            asyncio.create_task(self._read_sensor(sensor_function, sensor_name))

I wonder if that's too many. With one drone, it seems to work fine. But if I try to control two or more drones within the same process (using the same event loop), I find the problem where the position does not change. I can separate each drone into its own process as a workaround.

Here are a few more details.

  • When the position doesn't work, I can still control the drones. drone.action calls work well.
  • If I launch mavsdk_server separately, then I can connect mavsdk-python to a flying drone. In that case, the first position I receive is correct, but it doesn't seem to change much after that.

It almost looks like I'm receiving the positions very slowly. But I can't be sure. I might try running a simulation for a few hours to see if the position catches up.

Is there an upper limit to the number of drone.telemetry subscriptions we can access at the same time?

One limitation is that for one mavsdk_server instance, you can subscribe to a stream only once. So for one drone object, if you call drone.telemetry.position() twice, the first one will stop receiving events. That's documented here: #60.

Could it be what you are experiencing?

Closing as there was no more answer. Please comment again or answer if the issue persists.