diff --git a/mavsdk/__init__.py b/mavsdk/__init__.py index 8b0c94da..6f33452e 100644 --- a/mavsdk/__init__.py +++ b/mavsdk/__init__.py @@ -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: @@ -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) diff --git a/mavsdk/_base.py b/mavsdk/_base.py index 7e3efd2c..60649ad6 100644 --- a/mavsdk/_base.py +++ b/mavsdk/_base.py @@ -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): """ diff --git a/mavsdk/async_plugin_manager.py b/mavsdk/async_plugin_manager.py index 49978ecf..259bda88 100644 --- a/mavsdk/async_plugin_manager.py +++ b/mavsdk/async_plugin_manager.py @@ -1,80 +1,37 @@ # -*- 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): @@ -82,24 +39,3 @@ 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 diff --git a/mavsdk/drone.py b/mavsdk/drone.py new file mode 100644 index 00000000..08b5922e --- /dev/null +++ b/mavsdk/drone.py @@ -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)