Skip to content

Commit

Permalink
create a drone class to improve interface and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
JonasVautherin committed Sep 17, 2019
1 parent 303e814 commit 7743709
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 152 deletions.
73 changes: 2 additions & 71 deletions mavsdk/__init__.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,11 @@
# -*- coding: utf-8 -*-

import asyncio
import atexit
import os
import platform
import subprocess
import sys
import time

from mavsdk import bin

# List of the core plugins
CORE_PLUGINS = [
"Action",
"Calibration",
"Camera",
"Core",
"Gimbal",
"Info",
"Mission",
"Param",
"Offboard",
"Telemetry"
]
from .drone import Drone
from .generated import * # NOQA

# Check for compatibility
if float(".".join(platform.python_version_tuple()[0:-1])) < 3.6:
Expand All @@ -38,55 +21,3 @@
except ImportError:
# No uvloop installed on the system; the default eventloop works as well!
pass


def get_event_loop():
""" Asyncio eventloop """
return asyncio.get_event_loop()


# Plugins rely on the eventloop
from .async_plugin_manager import AsyncPluginManager # NOQA
from .generated import * # NOQA


def connect(*args, **kwargs):
"""
Generates a dronecore instance with all available Core plugins registered
and ready to use
"""
plugin_manager = AsyncPluginManager(*args, **kwargs)

for plugin in CORE_PLUGINS:
globals()[plugin](plugin_manager)

return plugin_manager


if sys.version_info >= (3, 7):
from importlib.resources import path
else:
from importlib_resources import path


def start_mavlink(connection_url=None):
"""
Starts the gRPC server in a subprocess, listening on localhost:50051
"""
with path(bin, 'mavsdk_server') as backend:
bin_path_and_args = [os.fspath(backend)]
if connection_url:
bin_path_and_args.append(connection_url)
p = subprocess.Popen(bin_path_and_args,
shell=False,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)

# Wait to make sure the gRPC server is started.
# There are better, yet more complex ways to do that.
time.sleep(2)

def cleanup():
p.kill()

atexit.register(cleanup)
8 changes: 3 additions & 5 deletions mavsdk/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@ class AsyncBase:
Base implementation for the async gRPC connection
"""

def __init__(self, async_plugin_manager=None):
self.init_plugin(async_plugin_manager)
def __init__(self, async_plugin_manager):
self._init_plugin(async_plugin_manager)

def init_plugin(self, async_plugin_manager):
def _init_plugin(self, async_plugin_manager):
"""
Sort of "registers" the plugin to the backend
"""
if async_plugin_manager:
self._loop = async_plugin_manager.loop
self._setup_stub(async_plugin_manager.channel)
async_plugin_manager.register_plugin(self)

def _setup_stub(self, channel):
"""
Expand Down
88 changes: 12 additions & 76 deletions mavsdk/async_plugin_manager.py
Original file line number Diff line number Diff line change
@@ -1,105 +1,41 @@
# -*- coding: utf-8 -*-
import aiogrpc
from . import get_event_loop


class AsyncPluginManager:
"""
Connects to a running mavsdk server or starts one and manages plugins
:param host: IP address of host running the backend
:param port: Port number
:param secure: Use an SSL Layer (currently not supported)
:param loop: Event loop MAVSDK is running on
Initialize the plugin manager:
>>> manager = AsyncPluginManager(host="127.0.0.1")
There are two (uniform) ways to register a plugin to the backend:
>>> action = Action(manager)
or
>>> action = Action()
>>> action.init_core(manager)
"""
def __init__(
self,
host=None,
port=50051,
secure=False,
loop=None):
@classmethod
async def create(cls, host, port=50051):

self = AsyncPluginManager()

self.host, self.port, self.secure = host, port, secure
self.host = host
self.port = port
self.plugins = {}

if host:
# Connect to the backend when there is a hostname passed
self._connect_backend()
else:
# Spinup a backend
raise NotImplementedError()
await self._connect_backend()

self._loop = loop or get_event_loop()
return self

def _connect_backend(self):
async def _connect_backend(self):
"""
Initializes the connection to the running backend
"""
if self.secure:
# For now just allow insecure connections
raise NotImplementedError()

#: gRPC channel
self._channel = aiogrpc.insecure_channel(
"{}:{}".format(self.host, self.port)
)

def _spinup_backend(self):
"""
Spinup a backend and connect to it
"""
#: backend is running on localhost
self.host = "127.0.0.1"
# Standart port
self.port = 50051
# Spinup the backend, not implemented yet
raise NotImplementedError()
# connect to the local running backend
self._connect_backend()

@property
def loop(self):
""" Event loop """
return self._loop
print("Waiting for mavsdk_server to be ready...")
await aiogrpc.channel_ready_future(self._channel)
print("Connected to mavsdk_server!")

@property
def channel(self):
"""
gRPC channel to the backend
"""
return self._channel

@property
def available_plugins(self):
"""
List of registered plugins
"""
return list(self.plugins.keys())

def register_plugin(self, plugin):
"""
Registers a plugin and adds it to the available plugin library
"""
if plugin and plugin not in self.available_plugins:
self.plugins[plugin.name.lower()] = plugin

def __getattr__(self, name):
""" convenient way to access plugins """
if name in self.plugins.keys():
return self.plugins[name]
else:
return None
129 changes: 129 additions & 0 deletions mavsdk/drone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# -*- coding: utf-8 -*-

from .async_plugin_manager import AsyncPluginManager # NOQA
from .generated import * # NOQA

from . import bin


class Drone:
_core_plugins = [
"Action",
"Calibration",
"Camera",
"Core",
"Gimbal",
"Info",
"Mission",
"Param",
"Offboard",
"Telemetry"
]

def __init__(self, mavsdk_server_address=None, port=50051):
self._mavsdk_server_address = mavsdk_server_address
self._port = port

self._plugins = {}

async def connect(self, drone_address=None):
if self._mavsdk_server_address is None:
self._mavsdk_server_address = 'localhost'
self._start_mavsdk_server(drone_address)

await self._init_plugins(self._mavsdk_server_address, self._port)

async def _init_plugins(self, host, port):
plugin_manager = await AsyncPluginManager.create(host=host, port=port)

for plugin in self._core_plugins:
self._plugins[plugin.lower()] = globals()[plugin](plugin_manager)

@property
def action(self) -> Action:
if "action" not in self._plugins:
raise RuntimeError("Action plugin has not been initialized! Did you run `Drone.connect()`?")
return self._plugins["action"]

@property
def calibration(self) -> Calibration:
if "calibration" not in self._plugins:
raise RuntimeError("Calibration plugin has not been initialized! Did you run `Drone.connect()`?")
return self._plugins["calibration"]

@property
def camera(self) -> Camera:
if "camera" not in self._plugins:
raise RuntimeError("Camera plugin has not been initialized! Did you run `Drone.connect()`?")
return self._plugins["camera"]

@property
def core(self) -> Core:
if "core" not in self._plugins:
raise RuntimeError("Core plugin has not been initialized! Did you run `Drone.connect()`?")
return self._plugins["core"]

@property
def gimbal(self) -> Gimbal:
if "gimbal" not in self._plugins:
raise RuntimeError("Gimbal plugin has not been initialized! Did you run `Drone.connect()`?")
return self._plugins["gimbal"]

@property
def info(self) -> Info:
if "info" not in self._plugins:
raise RuntimeError("Info plugin has not been initialized! Did you run `Drone.connect()`?")
return self._plugins["info"]

@property
def mission(self) -> Mission:
if "mission" not in self._plugins:
raise RuntimeError("Mission plugin has not been initialized! Did you run `Drone.connect()`?")
return self._plugins["mission"]

@property
def param(self) -> Param:
if "param" not in self._plugins:
raise RuntimeError("Param plugin has not been initialized! Did you run `Drone.connect()`?")
return self._plugins["param"]

@property
def offboard(self) -> Offboard:
if "offboard" not in self._plugins:
raise RuntimeError("Offboard plugin has not been initialized! Did you run `Drone.connect()`?")
return self._plugins["offboard"]

@property
def telemetry(self) -> Telemetry:
if "telemetry" not in self._plugins:
raise RuntimeError("Telemetry plugin has not been initialized! Did you run `Drone.connect()`?")
return self._plugins["telemetry"]

@staticmethod
def _start_mavsdk_server(drone_address=None):
"""
Starts the gRPC server in a subprocess, listening on localhost:50051
"""
import atexit
import os
import subprocess
import sys

if sys.version_info >= (3, 7):
from importlib.resources import path
else:
from importlib_resources import path

with path(bin, 'mavsdk_server') as backend:
bin_path_and_args = [os.fspath(backend)]
if drone_address:
bin_path_and_args.append(drone_address)
p = subprocess.Popen(bin_path_and_args,
shell=False,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)

def cleanup():
p.kill()

atexit.register(cleanup)

0 comments on commit 7743709

Please sign in to comment.