Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

telemetry.position() not working. #176

Closed
skymaze opened this issue Apr 28, 2020 · 19 comments
Closed

telemetry.position() not working. #176

skymaze opened this issue Apr 28, 2020 · 19 comments
Labels

Comments

@skymaze
Copy link

skymaze commented Apr 28, 2020

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.

@JonasVautherin
Copy link
Collaborator

JonasVautherin commented Apr 28, 2020

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

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

@julianoes julianoes added the bug label Apr 29, 2020
@julianoes
Copy link
Collaborator

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

@JonasVautherin
Copy link
Collaborator

Always from python?

@julianoes
Copy link
Collaborator

Always from python?

Maybe, not sure.

@benedek97
Copy link

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.

@JonasVautherin
Copy link
Collaborator

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?

@skymaze
Copy link
Author

skymaze commented May 7, 2020

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.

@skymaze
Copy link
Author

skymaze commented May 8, 2020

Screenshot inside house
Screenshot inside house 1
Screenshot out

Confirmed.

@benedek97
Copy link

@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!

@JonasVautherin
Copy link
Collaborator

Ok I think I'll need @julianoes insights regarding this "home" thing 🤔

@murphym18
Copy link

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.

@JonasVautherin
Copy link
Collaborator

@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).

@murphym18
Copy link

murphym18 commented May 30, 2020

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

@JonasVautherin
Copy link
Collaborator

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 👍.

@murphym18
Copy link

murphym18 commented May 30, 2020

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

@JonasVautherin
Copy link
Collaborator

JonasVautherin commented May 31, 2020

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...

@murphym18
Copy link

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.

@JonasVautherin
Copy link
Collaborator

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?

@julianoes
Copy link
Collaborator

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants