<commit_before>"""
This is the API Reference for the DroneKit-Python API.

The main API is the :py:class:`Vehicle <dronekit.lib.Vehicle>` class.
The code snippet below shows how to use :py:func:`connect` to obtain an instance a connected vehicle:

.. code:: python

    from dronekit import connect

    # Connect to the Vehicle using "connection string" (in this case an address on network)
    vehicle = connect('127.0.0.1:14550', wait_ready=True)

:py:class:`Vehicle <dronekit.lib.Vehicle>` provides access to vehicle *state* through python attributes
(e.g. :py:attr:`Vehicle.mode <dronekit.lib.Vehicle.mode>`)
and to settings/parameters though the :py:attr:`Vehicle.parameters <dronekit.lib.Vehicle.parameters>` attribute.
Asynchronous notification on vehicle attribute changes is available by registering observers.

:py:class:`Vehicle <dronekit.lib.Vehicle>` provides two main ways to control vehicle movement and other operations:

* Direct control of movement outside of missions is also supported. To set a target position you can use
  :py:func:`CommandSequence.goto <dronekit.lib.CommandSequence.goto>`.
  Control over speed, direction, altitude, camera trigger and any other aspect of the vehicle is supported using custom MAVLink messages
  (:py:func:`Vehicle.send_mavlink <dronekit.lib.Vehicle.send_mavlink>`, :py:func:`Vehicle.message_factory <dronekit.lib.Vehicle.message_factory>`).
* Missions are downloaded and uploaded through the :py:attr:`Vehicle.commands <dronekit.lib.Vehicle.commands>` attribute
  (see :py:class:`CommandSequence <dronekit.lib.CommandSequence>` for more information).

A number of other useful classes and methods are listed below.

----

.. todo:: Update this when have confirmed how to register for parameter notifications.

.. py:function:: connect(ip, wait_ready=False, status_printer=errprinter, vehicle_class=Vehicle, rate=4, baud=115200)

    Returns a :py:class:`Vehicle` object connected to the address specified by string parameter ``ip``. 
    Connection string parameters for different targets are listed in the :ref:`getting started guide <get_started_connecting>`.

    :param String ip: Connection string for target address - e.g. 127.0.0.1:14550.
    :param Bool wait_ready: Wait until all :py:func:`Vehicle.parameters` have downloaded before the method returns (default is false)
    :param status_printer: NA    
    :param Vehicle vehicle_class: NA     
    :param int rate: NA
    :param int baud: The baud rate for the connection. The default is 115200.
    
    :returns: A connected :py:class:`Vehicle` object.

----

    .. todo:: 
    
        Confirm what status_printer, vehicle_class and rate "mean" (https://github.com/dronekit/dronekit-python/issues/395#issuecomment-153527657)
        Can we hide in API. Can we get method defined in this file or connect method file exported
        
"""

# DroneAPI module

import threading, time, math, copy
import CloudClient
from pymavlink.dialects.v10 import ardupilotmega
from pymavlink import mavutil, mavwp
from dronekit.util import errprinter

local_path = ''

class APIException(Exception):
    """
    Base class for DroneKit related exceptions.

    :param String msg: Message string describing the exception
    """

    def __init__(self, msg):
        self.msg = msg

class Attitude(object):
    """
    Attitude information.

    .. figure:: http://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Yaw_Axis_Corrected.svg/500px-Yaw_Axis_Corrected.svg.png
        :width: 400px
        :alt: Diagram showing Pitch, Roll, Yaw
        :target: http://commons.wikimedia.org/wiki/File:Yaw_Axis_Corrected.svg

        Diagram showing Pitch, Roll, Yaw (`Creative Commons <http://commons.wikimedia.org/wiki/File:Yaw_Axis_Corrected.svg>`_)

    :param pitch: Pitch in radians
    :param yaw: Yaw in radians
    :param roll: Roll in radians
    """
    def __init__(self, pitch, yaw, roll):
        self.pitch = pitch
        self.yaw = yaw
        self.roll = roll

    def __str__(self):
        return "Attitude:pitch=%s,yaw=%s,roll=%s" % (self.pitch, self.yaw, self.roll)

class LocationGlobal(object):
    """
    A global location object.

    The latitude and longitude are relative to the `WGS84 coordinate system <http://en.wikipedia.org/wiki/World_Geodetic_System>`_.
    The altitude is relative to either the *home position* or "mean sea-level", depending on the value of the ``is_relative``.

    For example, a global location object might be defined as:

    .. code:: python

       LocationGlobal(-34.364114, 149.166022, 30, is_relative=True)

    .. todo:: FIXME: Location class - possibly add a vector3 representation.

    :param lat: Latitude.
    :param lon: Longitude.
    :param alt: Altitude in meters (either relative or absolute).
    :param is_relative: ``True`` if the specified altitude is relative to a 'home' location (this is usually desirable). ``False`` to set altitude relative to "mean sea-level".
    """
    def __init__(self, lat, lon, alt=None, is_relative=True):
        self.lat = lat
        self.lon = lon
        self.alt = alt
        self.is_relative = is_relative

        # This is for backward compatibility.
        self.local_frame = None
        self.global_fame = None

    def __str__(self):
        return "LocationGlobal:lat=%s,lon=%s,alt=%s,is_relative=%s" % (self.lat, self.lon, self.alt, self.is_relative)

# Back-compatibility for earlier clients.
Location = LocationGlobal

class LocationLocal(object):
    """
    A local location object.

    The north, east and down are relative to the EKF origin.  This is most likely the location where the vehicle was turned on.  

    :param north: Position north of the EKF origin in meters.
    :param east: Position east of the EKF origin in meters.
    :param down: Position down from the EKF origin in meters. (i.e. negative altitude in meters)
    """
    def __init__(self, north, east, down):
        self.north = north
        self.east = east
        self.down = down

    def __str__(self):
        return "LocationLocal:north=%s,east=%s,down=%s" % (self.north, self.east, self.down)

class GPSInfo(object):
    """
    Standard information about GPS.

    If there is no GPS lock the parameters are set to ``None``.

    :param IntType eph: GPS horizontal dilution of position (HDOP).
    :param IntType epv: GPS horizontal dilution of position (VDOP).
    :param IntType fix_type: 0-1: no fix, 2: 2D fix, 3: 3D fix
    :param IntType satellites_visible: Number of satellites visible.

    .. todo:: FIXME: GPSInfo class - possibly normalize eph/epv?  report fix type as string?
    """
    def __init__(self, eph, epv, fix_type, satellites_visible):
        self.eph = eph
        self.epv = epv
        self.fix_type = fix_type
        self.satellites_visible = satellites_visible

    def __str__(self):
        return "GPSInfo:fix=%s,num_sat=%s" % (self.fix_type, self.satellites_visible)

class Battery(object):
    """
    System battery information.

    :param voltage: Battery voltage in millivolts.
    :param current: Battery current, in 10 * milliamperes. ``None`` if the autopilot does not support current measurement.
    :param level: Remaining battery energy. ``None`` if the autopilot cannot estimate the remaining battery.
    """
    def __init__(self, voltage, current, level):
        self.voltage = voltage / 1000.0
        if current == -1:
            self.current = None
        else:
            self.current = current / 100.0
        if level == -1:
            self.level = None
        else:
            self.level = level

    def __str__(self):
        return "Battery:voltage={},current={},level={}".format(self.voltage, self.current, self.level)

class Rangefinder(object):
    """
    Rangefinder readings.

    :param distance: Distance (metres). ``None`` if the vehicle doesn't have a rangefinder.
    :param voltage: Voltage (volts). ``None`` if the vehicle doesn't have a rangefinder.
    """
    def __init__(self, distance, voltage):
        self.distance = distance
        self.voltage = voltage

    def __str__(self):
        return "Rangefinder: distance={}, voltage={}".format(self.distance, self.voltage)

class VehicleMode(object):
    """
    This object is used to get and set the current "flight mode".

    The flight mode determines the behaviour of the vehicle and what commands it can obey.
    The recommended flight modes for *DroneKit-Python* apps depend on the vehicle type:

    * Copter apps should use ``AUTO`` mode for "normal" waypoint missions and ``GUIDED`` mode otherwise.
    * Plane and Rover apps should use the ``AUTO`` mode in all cases, re-writing the mission commands if "dynamic"
      behaviour is required (they support only a limited subset of commands in ``GUIDED`` mode).
    * Some modes like ``RETURN_TO_LAUNCH`` can be used on all platforms. Care should be taken
      when using manual modes as these may require remote control input from the user.

    The available set of supported flight modes is vehicle-specific (see
    `Copter <http://copter.ardupilot.com/wiki/flying-arducopter/flight-modes/>`_,
    `Plane <http://plane.ardupilot.com/wiki/flying/flight-modes/>`_,
    `Rover <http://rover.ardupilot.com/wiki/configuration-2/#mode_meanings>`_). If an unsupported mode is set the script
    will raise a ``KeyError`` exception.

    The :py:attr:`Vehicle.mode <dronekit.lib.Vehicle.mode>` attribute can be queried for the current mode. 
    The code snippet below shows how to observe changes to the mode and then read the value:

    .. code:: python

        #Callback definition for mode observer
        def mode_callback(self, attr_name):
            print "Vehicle Mode", self.mode

        #Add observer callback for attribute `mode`
        vehicle.add_attribute_listener('mode', mode_callback)

    The code snippet below shows how to change the vehicle mode to AUTO:

    .. code:: python

        # Set the vehicle into auto mode
        vehicle.mode = VehicleMode("AUTO")

    For more information on getting/setting/observing the :py:attr:`Vehicle.mode <dronekit.lib.Vehicle.mode>` 
    (and other attributes) see the :ref:`attributes guide <vehicle_state_attributes>`.

    .. py:attribute:: name

        The mode name, as a ``string``.
    """
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return "VehicleMode:%s" % self.name

    def __eq__(self, other):
        return self.name == other

    def __ne__(self, other):
        return self.name != other

class HasObservers(object):
    def __init__(self):
        # A mapping from attr_name to a list of observers
        self.__observers = {}

    """
    Provides callback based notification on attribute changes.

    The argument list for observer is ``observer(object, attr_name, attribute_value)``.
    """
    def add_attribute_listener(self, attr_name, observer):
        """
        Add an attribute listener callback.

        The callback function (``observer``) is invoked every time the associated attribute is updated 
        from the vehicle (the attribute value may not have *changed*). The callback can be removed 
        using :py:func:`remove_attribute_listener`.
        
        .. note::
        
            The :py:func:`on_attribute` decorator performs the same operation as this method, but with 
            a more elegant syntax. Use ``add_attribute_listener`` by preference if you will need to remove 
            the observer.

        The callback arguments are:
        
        * ``self`` - the associated :py:class:`Vehicle`. This may be compared to a global vehicle handle 
          to implement vehicle-specific callback handling (if needed).
        * ``attr_name`` - the attribute name. This can be used to infer which attribute has triggered
          if the same callback is used for watching several attributes.
        * ``msg`` - the attribute value (so you don't need to re-query the vehicle object).

        The example below shows how to get callbacks for (global) location changes:

        .. code:: python

            #Callback to print the location in global frame
            def location_callback(self, attr_name, msg):
                print "Location (Global): ", msg

            #Add observer for the vehicle's current location
            vehicle.add_attribute_listener('global_frame', location_callback)     
        
        :param String attr_name: The name of the attribute to watch (or '*' to watch all attributes).
        :param observer: The callback to invoke when a change in the attribute is detected.

        """
        l = self.__observers.get(attr_name)
        if l is None:
            l = []
            self.__observers[attr_name] = l
        if not observer in l:
            l.append(observer)

    def remove_attribute_listener(self, attr_name, observer):
        """
        Remove an attribute listener (observer) that was previously added using :py:func:`add_attribute_listener`.

        For example, the following line would remove a previously added vehicle 'global_frame' 
        observer called ``location_callback``:

        .. code:: python

            vehicle.remove_attribute_listener('global_frame', location_callback)

        :param String attr_name: The attribute name that is to have an observer removed (or '*' to remove an 'all attribute' observer).
        :param observer: The callback function to remove.

        """
        l = self.__observers.get(attr_name)
        if l is not None:
            l.remove(observer)
            if len(l) == 0:
                del self.__observers[attr_name]

    def notify_attribute_listeners(self, attr_name, value):
        """
        This method calls attribute observers when the named attribute has changed.
        
        It should be called in message listeners after updating an attribute with new information
        from the vehicle. 
        
        :param String attr_name: The name of the attribute that has been updated.
        :param value: The current value of the attribute that has been updated.
        """
        for fn in self.__observers.get(attr_name, []):
            fn(self, attr_name, value)
        for fn in self.__observers.get('*', []):
            fn(self, attr_name, value)

    def on_attribute(self, name):
        """
        Decorator for attribute listeners.
        
        The decorated ``observer`` callback function is invoked every time the associated attribute is updated 
        from the vehicle (the attribute value may not have *changed*). 
        
        The callback arguments are:
        
        * ``self`` - the associated :py:class:`Vehicle`. This may be compared to a global vehicle handle 
          to implement vehicle-specific callback handling (if needed).
        * ``attr_name`` - the attribute name. This can be used to infer which attribute has triggered
          if the same callback is used for watching several attributes.
        * ``msg`` - the attribute value (so you don't need to re-query the vehicle object).
        
        .. note::
        
            There is no way to remove an attribute listener added with this decorator. Use 
            :py:func:`add_attribute_listener` if you need to be able to remove 
            the :py:func:`attribute listener <remove_attribute_listener>`.
            
        The code fragment below shows how you can create a listener for the attitude attribute.

        .. code:: python

            @vehicle.on_attribute('attitude')
            def attitude_listener(self, name, msg):
                print '%s attribute is: %s' % (name, msg)

        :param String attr_name: The name of the attribute to watch (or '*' to watch all attributes).
        :param observer: The callback to invoke when a change in the attribute is detected.
        """
        def decorator(fn):
            if isinstance(name, list):
                for n in name:
                    self.add_attribute_listener(n, fn)
            else:
                self.add_attribute_listener(name, fn)
        return decorator

class Vehicle(HasObservers):
    """
    The main vehicle API

    Asynchronous notification on change of vehicle state is available by registering observers (callbacks) for attribute changes.

    Most vehicle state is exposed through python attributes (e.g. ``vehicle.location``). Most of these attributes are
    auto-populated based on the capabilities of the connected autopilot/vehicle.

    Particular autopilots/vehicles may define different attributes from this standard list (extra batteries, GPIOs, etc.)
    However if a standard attribute is defined it must follow the rules specified below.

    **Autopilot specific attributes & types:**

    To prevent name clashes the following naming convention should be used:

    * ``ap_<name>`` - For autopilot specific parameters (apm 2.5, pixhawk etc.). For example "ap_pin5_mode" and "ap_pin5_value".
    * ``user_<name>`` - For user specific parameters

    **Standard attributes & types:**

    .. py:attribute:: location.global_frame

        Current :py:class:`LocationGlobal`.

    .. py:attribute:: location.local_frame

        Current :py:class:`LocationLocal`.

    .. py:attribute:: attitude

        Current vehicle :py:class:`Attitude` (pitch, yaw, roll).

    .. py:attribute:: velocity

        Current velocity as a three element list ``[ vx, vy, vz ]`` (in meter/sec).

    .. py:attribute:: mode

        This attribute is used to get and set the current flight mode (:py:class:`VehicleMode`).

    .. py:attribute:: airspeed

        Current airspeed in metres/second (``double``).

        .. todo:: FIXME: Should airspeed value move somewhere else from "Standard attributes & types" table?

    .. py:attribute:: groundspeed

        Groundspeed in metres/second (``double``).

    .. py:attribute:: gps_0

        GPS position information (:py:class:`GPSInfo`).

    .. py:attribute:: armed

        This attribute can be used to get and set the ``armed`` state of the vehicle (``boolean``).

        The code below shows how to read the state, and to arm/disam the vehicle:

        .. code:: python

            # Print the armed state for the vehicle
            print "Armed: %s" % vehicle.armed

            # Disarm the vehicle
            vehicle.armed = False

            # Arm the vehicle
            vehicle.armed = True

    .. py:attribute:: mount_status

        Current status of the camera mount (gimbal) as a three element list: ``[ pitch, yaw, roll ]``.

        The values in the list are set to ``None`` if no mount is configured.

    .. py:attribute:: battery

        Current system :py:class:`Battery` status.

    .. py:attribute:: rangefinder

        :py:class:`Rangefinder` distance and voltage values.

    .. py:attribute:: channel_override

        .. warning::

            RC override may be useful for simulating user input and when implementing certain types of joystick control.
            It should not be used for direct control of vehicle channels unless there is no other choice!

            Instead use the appropriate MAVLink commands like DO_SET_SERVO/DO_SET_RELAY, or more generally
            set the desired position or direction/speed.

        This attribute takes a dictionary argument defining the RC *output* channels to be overridden (specified by channel number), and their new values.
        Channels that are not specified in the dictionary are not overridden. All multi-channel updates are atomic.

        To cancel an override call ``channel_override`` again, setting zero for the overridden channels.

        The values of the first four channels map to the main flight controls: 1=Roll, 2=Pitch, 3=Throttle, 4=Yaw (the mapping is defined in ``RCMAP_`` parameters:
        `Plane <http://plane.ardupilot.com/wiki/arduplane-parameters/#rcmap__parameters>`_,
        `Copter <http://copter.ardupilot.com/wiki/configuration/arducopter-parameters/#rcmap__parameters>`_ ,
        `Rover <http://rover.ardupilot.com/wiki/apmrover2-parameters/#rcmap__parameters>`_).

        The remaining channel values are configurable, and their purpose can be determined using the
        `RCn_FUNCTION parameters <http://plane.ardupilot.com/wiki/flight-features/channel-output-functions/>`_.
        In general a value of 0 set for a specific ``RCn_FUNCTION`` indicates that the channel can be
        `mission controlled <http://plane.ardupilot.com/wiki/flight-features/channel-output-functions/#disabled>`_ (i.e. it will not directly be
        controlled by normal autopilot code).

        An example of setting and clearing the override is given below:

        .. code:: python

            # Override channels 1 and 4 (only).
            vehicle.channel_override = { "1" : 900, "4" : 1000 }

            # Cancel override on channel 1 and 4 by sending 0
            vehicle.channel_override = { "1" : 0, "4" : 0 }

        .. versionchanged:: 1.0

            This update replaces ``rc_override`` with ``channel_override``/``channel_readback`` documentation.

        .. todo:: Add note to the examples/guide like warning above not to use this mechanism except as intended:

            https://github.com/dronekit/dronekit-python/issues/72

        .. todo::

            channel_override/channel_readback documentation

            In a future update strings will be defined per vehicle type ('pitch', 'yaw', 'roll' etc...)
            and rather than setting channel 3 to 1400 (for instance), you will pass in a dict with
            'throttle':1200.

            This change will be useful in two ways:

            * we can hide (eventually we can deprecate) any notion of rc channel numbers at all.
            * vehicles can eventually define new 'channels' for overridden values.

            FIXME: Remaining channel_override/channel_readback FIXMEs:

            * how to address the units issue?  Merely with documentation or some other way?
            * is there any benefit of using lists rather than tuples for these attributes

    .. py:attribute:: channel_readback

        This read-only attribute returns a dictionary containing the *original* vehicle RC channel values (ignoring any overrides set using
        :py:attr:`channel_override <dronekit.lib.Vehicle.channel_override>`). Dictionary entries have the format ``channelName -> value``.

        For example, the returned dictionary might look like this:

        .. code:: python

            RC readback: {'1': 1500, '3': 1000, '2': 1500, '5': 1800, '4': 1500, '7': 1000, '6': 1000, '8': 1800} ? Dictionary () (read only)

    .. todo:: In V2, there may be ardupilot specific attributes & types (as in the introduction). If so, text below might be useful.

        **Autopilot specific attributes & types:**

        .. py:attribute:: ap_pin5_mode

            string (adc, dout, din)

        .. py:attribute:: ap_pin5_value

            ? double (0, 1, 2.3 etc...)

    .. todo:: Add waypoint_home attribute IF this is added: https://github.com/dronekit/dronekit-python/issues/105

    """

    def __init__(self, handler):
        super(Vehicle, self).__init__()

        self._handler = handler
        self._master = handler.master

        # Cache all updated attributes for wait_ready.
        # By default, we presume all "commands" are loaded.
        self._ready_attrs = set(['commands'])

        # Default parameters when calling wait_ready() or wait_ready(True).
        self._default_ready_attrs = ['parameters', 'gps_0', 'armed', 'mode', 'attitude']

        @self.on_attribute('*')
        def listener(_, name, value):
            self._ready_attrs.add(name)

        # Attaches message listeners.
        self._message_listeners = dict()

        @handler.forward_message
        def listener(_, msg):
            self.notify_message_listeners(msg.get_type(), msg)

        self._lat = None
        self._lon = None
        self._vx = None
        self._vy = None
        self._vz = None

        @self.on_message('GLOBAL_POSITION_INT')
        def listener(self, name, m):
            (self._lat, self._lon) = (m.lat / 1.0e7, m.lon / 1.0e7)
            self.notify_attribute_listeners('location', self.location)
            (self._vx, self._vy, self._vz) = (m.vx / 100.0, m.vy / 100.0, m.vz / 100.0)
            self.notify_attribute_listeners('velocity', self.velocity)

        self._north = None
        self._east = None
        self._down = None

        @self.on_message('LOCAL_POSITION_NED')
        def listener(self, name, m):
            self._north = m.x
            self._east = m.y
            self._down = m.z
            self.notify_attribute_listeners('local_position', self.location.local_frame)

        self._pitch = None
        self._yaw = None
        self._roll = None
        self._pitchspeed = None
        self._yawspeed = None
        self._rollspeed = None

        @self.on_message('ATTITUDE')
        def listener(self, name, m):
            self._pitch = m.pitch
            self._yaw = m.yaw
            self._roll = m.roll
            self._pitchspeed = m.pitchspeed
            self._yawspeed = m.yawspeed
            self._rollspeed = m.rollspeed
            self.notify_attribute_listeners('attitude', self.attitude)

        self._heading = None
        self._alt = None
        self._airspeed = None
        self._groundspeed = None

        @self.on_message('VFR_HUD')
        def listener(self, name, m):
            self._heading = m.heading
            self.notify_attribute_listeners('heading', self.heading)
            self._alt = m.alt
            self.notify_attribute_listeners('location', self.location)
            self._airspeed = m.airspeed
            self.notify_attribute_listeners('airspeed', self.airspeed)
            self._groundspeed = m.groundspeed
            self.notify_attribute_listeners('groundspeed', self.groundspeed)

        self._rngfnd_distance = None
        self._rngfnd_voltage = None

        @self.on_message('RANGEFINDER')
        def listener(self, name, m):
            self._rngfnd_distance = m.distance
            self._rngfnd_voltage = m.voltage
            self.notify_attribute_listeners('rangefinder', self.rangefinder)

        self._mount_pitch = None
        self._mount_yaw = None
        self._mount_roll = None

        @self.on_message('MOUNT_STATUS')
        def listener(self, name, m):
            self._mount_pitch = m.pointing_a / 100
            self._mount_roll = m.pointing_b / 100
            self._mount_yaw = m.pointing_c / 100
            self.notify_attribute_listeners('mount', self.mount_status)

        self._rc_readback = {}

        @self.on_message('RC_CHANNELS_RAW')
        def listener(self, name, m):
            def set_rc(chnum, v):
                '''Private utility for handling rc channel messages'''
                # use port to allow ch nums greater than 8
                self._rc_readback[str(m.port * 8 + chnum)] = v

            set_rc(1, m.chan1_raw)
            set_rc(2, m.chan2_raw)
            set_rc(3, m.chan3_raw)
            set_rc(4, m.chan4_raw)
            set_rc(5, m.chan5_raw)
            set_rc(6, m.chan6_raw)
            set_rc(7, m.chan7_raw)
            set_rc(8, m.chan8_raw)

        self._voltage = None
        self._current = None
        self._level = None

        @self.on_message('SYS_STATUS')
        def listener(self, name, m):
            self._voltage = m.voltage_battery
            self._current = m.current_battery
            self._level = m.battery_remaining
            self.notify_attribute_listeners('battery', self.battery)

        self._eph = None
        self._epv = None
        self._satellites_visible = None
        self._fix_type = None  # FIXME support multiple GPSs per vehicle - possibly by using componentId

        @self.on_message('GPS_RAW_INT')
        def listener(self, name, m):
            self._eph = m.eph
            self._epv = m.epv
            self._satellites_visible = m.satellites_visible
            self._fix_type = m.fix_type
            self.notify_attribute_listeners('gps_0', self.gps_0)

        self._current_waypoint = 0

        @self.on_message(['WAYPOINT_CURRENT', 'MISSION_CURRENT'])
        def listener(self, name, m):
            self._current_waypoint = m.seq

        self._ekf_poshorizabs = False
        self._ekf_constposmode = False
        self._ekf_predposhorizabs = False

        @self.on_message('EKF_STATUS_REPORT')
        def listener(self, name, m):
            # boolean: EKF's horizontal position (absolute) estimate is good
            self._ekf_poshorizabs = (m.flags & ardupilotmega.EKF_POS_HORIZ_ABS) > 0
            # boolean: EKF is in constant position mode and does not know it's absolute or relative position
            self._ekf_constposmode = (m.flags & ardupilotmega.EKF_CONST_POS_MODE) > 0
            # boolean: EKF's predicted horizontal position (absolute) estimate is good
            self._ekf_predposhorizabs = (m.flags & ardupilotmega.EKF_PRED_POS_HORIZ_ABS) > 0

            self.notify_attribute_listeners('ekf_ok', self.ekf_ok)

        self._flightmode = 'AUTO'
        self._armed = False
        self._system_status = None

        @self.on_message('HEARTBEAT')
        def listener(self, name, m):
            self._armed = (m.base_mode & mavutil.mavlink.MAV_MODE_FLAG_SAFETY_ARMED) != 0
            self.notify_attribute_listeners('armed', self.armed)
            self._flightmode = {v: k for k, v in self._master.mode_mapping().items()}[m.custom_mode]
            self._system_status = m.system_status
            self.notify_attribute_listeners('mode', self.mode)

        # Waypoints.

        self._wploader = mavwp.MAVWPLoader()
        self._wp_loaded = True
        self._wp_uploaded = None
        self._wpts_dirty = False
        self._commands = CommandSequence(self)

        @self.on_message(['WAYPOINT_COUNT','MISSION_COUNT'])
        def listener(self, name, msg):
            if not self._wp_loaded:
                self._wploader.clear()
                self._wploader.expected_count = msg.count
                self._master.waypoint_request_send(0)

        @self.on_message(['WAYPOINT', 'MISSION_ITEM'])
        def listener(self, name, msg):
            if not self._wp_loaded:
                if msg.seq > self._wploader.count():
                    # Unexpected waypoint
                    pass
                elif msg.seq < self._wploader.count():
                    # Waypoint duplicate
                    pass
                else:
                    self._wploader.add(msg)

                    if msg.seq + 1 < self._wploader.expected_count:
                        self._master.waypoint_request_send(msg.seq + 1)
                    else:
                        self._wp_loaded = True
                        self.notify_attribute_listeners('commands', self.commands)

        # Waypoint send to master
        @self.on_message(['WAYPOINT_REQUEST', 'MISSION_REQUEST'])
        def listener(self, name, msg):
            if self._wp_uploaded != None:
                wp = self._wploader.wp(msg.seq)
                handler.fix_targets(wp)
                self._master.mav.send(wp)
                self._wp_uploaded[msg.seq] = True

        # TODO: Waypoint loop listeners

        # Parameters.

        start_duration = 0.2
        repeat_duration = 1

        self._params_count = -1
        self._params_set = []
        self._params_loaded = False
        self._params_start = False
        self._params_map = {}
        self._params_last = time.time() # Last new param.
        self._params_duration = start_duration
        self._parameters = Parameters(self)

        @handler.forward_loop
        def listener(_):
            # Check the time duration for last "new" params exceeds watchdog.
            if self._params_start:
                if None not in self._params_set and not self._params_loaded:
                    self._params_loaded = True
                    self.notify_attribute_listeners('parameters', self.parameters)

                if not self._params_loaded and time.time() - self._params_last > self._params_duration:
                    c = 0
                    for i, v in enumerate(self._params_set):
                        if v == None:
                            self._master.mav.param_request_read_send(0, 0, '', i)
                            c += 1
                            if c > 50:
                                break
                    self._params_duration = repeat_duration
                    self._params_last = time.time()

        @self.on_message(['PARAM_VALUE'])
        def listener(self, name, msg):
            # If we discover a new param count, assume we
            # are receiving a new param set.
            if self._params_count != msg.param_count:
                self._params_loaded = False
                self._params_start = True
                self._params_count = msg.param_count
                self._params_set = [None]*msg.param_count

            # Attempt to set the params. We throw an error
            # if the index is out of range of the count or
            # we lack a param_id.
            try:
                if msg.param_index < msg.param_count and msg:
                    if self._params_set[msg.param_index] == None:
                        self._params_last = time.time()
                        self._params_duration = start_duration
                    self._params_set[msg.param_index] = msg
                self._params_map[msg.param_id] = msg.param_value
            except:
                import traceback
                traceback.print_exc()

        # Heartbeats.

        self._heartbeat_started = False
        self._heartbeat_lastsent = 0
        self._heartbeat_lastreceived = 0
        self._heartbeat_timeout = False

        self._heartbeat_warning = 5
        self._heartbeat_error = 30

        @handler.forward_loop
        def listener(_):
            # Send 1 heartbeat per second
            if time.time() - self._heartbeat_lastsent > 1:
                self._master.mav.heartbeat_send(mavutil.mavlink.MAV_TYPE_GCS, mavutil.mavlink.MAV_AUTOPILOT_INVALID, 0, 0, 0)
                self._heartbeat_lastsent = time.time()

            # Timeouts.
            if self._heartbeat_started:
                if self._heartbeat_error and self._heartbeat_error > 0 and time.time() - self._heartbeat_lastreceived > self._heartbeat_error:
                    raise Exception('>>> No heartbeat in %s seconds, aborting.' % self._heartbeat_error)
                elif time.time() - self._heartbeat_lastreceived > self._heartbeat_warning:
                    if self._heartbeat_timeout == False:
                        errprinter('>>> Link timeout, no heartbeat in last %s seconds' % self._heartbeat_warning)
                        self._heartbeat_timeout = True

        @self.on_message(['HEARTBEAT'])
        def listener(self, name, msg):
            self._heartbeat_lastreceived = time.time()
            if self._heartbeat_timeout:
                errprinter('>>> ...link restored.')
            self._heartbeat_timeout = False

    def on_message(self, name):
        """
        Decorator for message listener callback functions.
        
        .. tip::
        
            This is the most elegant way to define message listener callback functions. 
            Use :py:func:`add_message_listener` only if you need to be able to
            :py:func:`remove the listener <remove_message_listener>` later.
        
        A decorated message listener function is called with three arguments every time the 
        specified message is received: 
        
        * ``self`` - the current vehicle.
        * ``name`` - the name of the message that was intercepted.
        * ``message`` - the actual message (a `pymavlink <http://www.qgroundcontrol.org/mavlink/pymavlink>`_
          `class <https://www.samba.org/tridge/UAV/pymavlink/apidocs/classIndex.html>`_).        

        For example, in the fragment below ``my_method`` will be called for every heartbeat message:
        
        .. code:: python

            @vehicle.on_message('HEARTBEAT')
            def my_method(self, name, msg):
                pass
                
        :param String name: The name of the message to be intercepted by the decorated listener function (or '*' to get all messages).
        """
        def decorator(fn):
            if isinstance(name, list):
                for n in name:
                    self.add_message_listener(n, fn)
            else:
                self.add_message_listener(name, fn)
        return decorator

    def add_message_listener(self, name, fn):
        """
        Adds a message listener function that will be called every time the specified message is received.
        
        .. tip::
        
            We recommend you use :py:func:`on_message` instead of this method as it has a more elegant syntax.
            This method is only preferred if you need to be able to 
            :py:func:`remove the listener <remove_message_listener>`.
        
        The callback function must have three arguments:
        
        * ``self`` - the current vehicle.
        * ``name`` - the name of the message that was intercepted.
        * ``message`` - the actual message (a `pymavlink <http://www.qgroundcontrol.org/mavlink/pymavlink>`_
          `class <https://www.samba.org/tridge/UAV/pymavlink/apidocs/classIndex.html>`_).        

        For example, in the fragment below ``my_method`` will be called for every heartbeat message:
        
        .. code:: python

            #Callback method for new messages
            def my_method(self, name, msg):
                pass

            vehicle.add_message_listener('HEARTBEAT',my_method)
        
        :param String name: The name of the message to be intercepted by the listener function (or '*' to get all messages).
        :param fn: The listener function that will be called if a message is received.        
        """
        name = str(name)
        if name not in self._message_listeners:
            self._message_listeners[name] = []
        if fn not in self._message_listeners[name]:
            self._message_listeners[name].append(fn)

    def remove_message_listener(self, name, fn):
        """
        Removes a message listener (that was previously added using :py:func:`add_message_listener`).
        
        :param String name: The name of the message for which the listener is to be removed (or '*' to remove an 'all messages' observer).
        :param fn: The listener callback function to remove.            
        
        """
        name = str(name)
        if name in self._message_listeners:
            self._message_listeners[name].remove(fn)
            if len(self._message_listeners[name]) == 0:
                del self._message_listeners[name]

    def notify_message_listeners(self, name, msg):
        for fn in self._message_listeners.get(name, []):
            fn(self, name, msg)
        for fn in self._message_listeners.get('*', []):
            fn(self, name, msg)

    def close(self):
        return self._handler.close()

    def flush(self):
        """
        Call ``flush()`` after :py:func:`adding <CommandSequence.add>` or :py:func:`clearing <CommandSequence.clear>` mission commands.

        After the return from ``flush()`` any writes are guaranteed to have completed (or thrown an
        exception) and future reads will see their effects.

        .. warning:: 

            This has been replaced by :py:func:`Vehicle.commands.upload() <Vehicle.commands.upload>`.
        """
        return self.commands.upload()

    #
    # Private sugar methods
    #

    @property
    def _mode_mapping(self):
        return self._master.mode_mapping()

    #
    # Operations to support the standard API.
    #

    @property
    def mode(self):
        if not self._flightmode:
            return None
        return VehicleMode(self._flightmode)

    @mode.setter
    def mode(self, v):
        self._master.set_mode(self._mode_mapping[v.name])

    @property
    def location(self):
        # For backward compatibility, this is (itself) a LocationLocal object.
        ret = LocationGlobal(self._lat, self._lon, self._alt, is_relative=False)
        ret.local_frame = LocationLocal(self._north, self._east, self._down)
        ret.global_frame = LocationGlobal(self._lat, self._lon, self._alt, is_relative=False)
        return ret

    @property
    def battery(self):
        return Battery(self._voltage, self._current, self._level)

    @property
    def rangefinder(self):
        return Rangefinder(self._rngfnd_distance, self._rngfnd_voltage)

    @property
    def velocity(self):
        return [ self._vx, self._vy, self._vz ]

    @property
    def attitude(self):
        return Attitude(self._pitch, self._yaw, self._roll)

    @property
    def gps_0(self):
        return GPSInfo(self._eph, self._epv, self._fix_type, self._satellites_visible)

    @property
    def armed(self):
        return self._armed

    @armed.setter
    def armed(self, value):
        if value:
            self._master.arducopter_arm()
        else:
            self._master.arducopter_disarm()

    @property
    def system_status(self):
        return self._system_status

    @property
    def heading(self):
        return self._heading<commit_msg>Addition file changes (this should have been part of last commit)<commit_after>"""
This is the API Reference for the DroneKit-Python API.

The main API is the :py:class:`Vehicle <dronekit.lib.Vehicle>` class.
The code snippet below shows how to use :py:func:`connect` to obtain an instance a connected vehicle:

.. code:: python

    from dronekit import connect

    # Connect to the Vehicle using "connection string" (in this case an address on network)
    vehicle = connect('127.0.0.1:14550', wait_ready=True)

:py:class:`Vehicle <dronekit.lib.Vehicle>` provides access to vehicle *state* through python attributes
(e.g. :py:attr:`Vehicle.mode <dronekit.lib.Vehicle.mode>`)
and to settings/parameters though the :py:attr:`Vehicle.parameters <dronekit.lib.Vehicle.parameters>` attribute.
Asynchronous notification on vehicle attribute changes is available by registering observers.

:py:class:`Vehicle <dronekit.lib.Vehicle>` provides two main ways to control vehicle movement and other operations:

* Direct control of movement outside of missions is also supported. To set a target position you can use
  :py:func:`CommandSequence.goto <dronekit.lib.CommandSequence.goto>`.
  Control over speed, direction, altitude, camera trigger and any other aspect of the vehicle is supported using custom MAVLink messages
  (:py:func:`Vehicle.send_mavlink <dronekit.lib.Vehicle.send_mavlink>`, :py:func:`Vehicle.message_factory <dronekit.lib.Vehicle.message_factory>`).
* Missions are downloaded and uploaded through the :py:attr:`Vehicle.commands <dronekit.lib.Vehicle.commands>` attribute
  (see :py:class:`CommandSequence <dronekit.lib.CommandSequence>` for more information).

A number of other useful classes and methods are listed below.

----

.. todo:: Update this when have confirmed how to register for parameter notifications.

.. py:function:: connect(ip, wait_ready=False, status_printer=errprinter, vehicle_class=Vehicle, rate=4, baud=115200)

    Returns a :py:class:`Vehicle` object connected to the address specified by string parameter ``ip``. 
    Connection string parameters for different targets are listed in the :ref:`getting started guide <get_started_connecting>`.

    :param String ip: Connection string for target address - e.g. 127.0.0.1:14550.
    :param Bool wait_ready: Wait until all :py:func:`Vehicle.parameters` have downloaded before the method returns (default is false)
    :param status_printer: NA    
    :param Vehicle vehicle_class: NA     
    :param int rate: NA
    :param int baud: The baud rate for the connection. The default is 115200.
    
    :returns: A connected :py:class:`Vehicle` object.

----

    .. todo:: 
    
        Confirm what status_printer, vehicle_class and rate "mean" (https://github.com/dronekit/dronekit-python/issues/395#issuecomment-153527657)
        Can we hide in API. Can we get method defined in this file or connect method file exported
        
"""

# DroneAPI module

import threading, time, math, copy
import CloudClient
from pymavlink.dialects.v10 import ardupilotmega
from pymavlink import mavutil, mavwp
from dronekit.util import errprinter

local_path = ''

class APIException(Exception):
    """
    Base class for DroneKit related exceptions.

    :param String msg: Message string describing the exception
    """

    def __init__(self, msg):
        self.msg = msg

class Attitude(object):
    """
    Attitude information.

    .. figure:: http://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Yaw_Axis_Corrected.svg/500px-Yaw_Axis_Corrected.svg.png
        :width: 400px
        :alt: Diagram showing Pitch, Roll, Yaw
        :target: http://commons.wikimedia.org/wiki/File:Yaw_Axis_Corrected.svg

        Diagram showing Pitch, Roll, Yaw (`Creative Commons <http://commons.wikimedia.org/wiki/File:Yaw_Axis_Corrected.svg>`_)

    :param pitch: Pitch in radians
    :param yaw: Yaw in radians
    :param roll: Roll in radians
    """
    def __init__(self, pitch, yaw, roll):
        self.pitch = pitch
        self.yaw = yaw
        self.roll = roll

    def __str__(self):
        return "Attitude:pitch=%s,yaw=%s,roll=%s" % (self.pitch, self.yaw, self.roll)

class LocationGlobal(object):
    """
    A global location object.

    The latitude and longitude are relative to the `WGS84 coordinate system <http://en.wikipedia.org/wiki/World_Geodetic_System>`_.
    The altitude is relative to either the *home position* or "mean sea-level", depending on the value of the ``is_relative``.

    For example, a global location object might be defined as:

    .. code:: python

       LocationGlobal(-34.364114, 149.166022, 30, is_relative=True)

    .. todo:: FIXME: Location class - possibly add a vector3 representation.

    :param lat: Latitude.
    :param lon: Longitude.
    :param alt: Altitude in meters (either relative or absolute).
    :param is_relative: ``True`` if the specified altitude is relative to a 'home' location (this is usually desirable). ``False`` to set altitude relative to "mean sea-level".
    """
    def __init__(self, lat, lon, alt=None, is_relative=True):
        self.lat = lat
        self.lon = lon
        self.alt = alt
        self.is_relative = is_relative

        # This is for backward compatibility.
        self.local_frame = None
        self.global_fame = None

    def __str__(self):
        return "LocationGlobal:lat=%s,lon=%s,alt=%s,is_relative=%s" % (self.lat, self.lon, self.alt, self.is_relative)

# Back-compatibility for earlier clients.
Location = LocationGlobal

class LocationLocal(object):
    """
    A local location object.

    The north, east and down are relative to the EKF origin.  This is most likely the location where the vehicle was turned on.  

    :param north: Position north of the EKF origin in meters.
    :param east: Position east of the EKF origin in meters.
    :param down: Position down from the EKF origin in meters. (i.e. negative altitude in meters)
    """
    def __init__(self, north, east, down):
        self.north = north
        self.east = east
        self.down = down

    def __str__(self):
        return "LocationLocal:north=%s,east=%s,down=%s" % (self.north, self.east, self.down)

class GPSInfo(object):
    """
    Standard information about GPS.

    If there is no GPS lock the parameters are set to ``None``.

    :param IntType eph: GPS horizontal dilution of position (HDOP).
    :param IntType epv: GPS horizontal dilution of position (VDOP).
    :param IntType fix_type: 0-1: no fix, 2: 2D fix, 3: 3D fix
    :param IntType satellites_visible: Number of satellites visible.

    .. todo:: FIXME: GPSInfo class - possibly normalize eph/epv?  report fix type as string?
    """
    def __init__(self, eph, epv, fix_type, satellites_visible):
        self.eph = eph
        self.epv = epv
        self.fix_type = fix_type
        self.satellites_visible = satellites_visible

    def __str__(self):
        return "GPSInfo:fix=%s,num_sat=%s" % (self.fix_type, self.satellites_visible)

class Battery(object):
    """
    System battery information.

    :param voltage: Battery voltage in millivolts.
    :param current: Battery current, in 10 * milliamperes. ``None`` if the autopilot does not support current measurement.
    :param level: Remaining battery energy. ``None`` if the autopilot cannot estimate the remaining battery.
    """
    def __init__(self, voltage, current, level):
        self.voltage = voltage / 1000.0
        if current == -1:
            self.current = None
        else:
            self.current = current / 100.0
        if level == -1:
            self.level = None
        else:
            self.level = level

    def __str__(self):
        return "Battery:voltage={},current={},level={}".format(self.voltage, self.current, self.level)

class Rangefinder(object):
    """
    Rangefinder readings.

    :param distance: Distance (metres). ``None`` if the vehicle doesn't have a rangefinder.
    :param voltage: Voltage (volts). ``None`` if the vehicle doesn't have a rangefinder.
    """
    def __init__(self, distance, voltage):
        self.distance = distance
        self.voltage = voltage

    def __str__(self):
        return "Rangefinder: distance={}, voltage={}".format(self.distance, self.voltage)

class VehicleMode(object):
    """
    This object is used to get and set the current "flight mode".

    The flight mode determines the behaviour of the vehicle and what commands it can obey.
    The recommended flight modes for *DroneKit-Python* apps depend on the vehicle type:

    * Copter apps should use ``AUTO`` mode for "normal" waypoint missions and ``GUIDED`` mode otherwise.
    * Plane and Rover apps should use the ``AUTO`` mode in all cases, re-writing the mission commands if "dynamic"
      behaviour is required (they support only a limited subset of commands in ``GUIDED`` mode).
    * Some modes like ``RETURN_TO_LAUNCH`` can be used on all platforms. Care should be taken
      when using manual modes as these may require remote control input from the user.

    The available set of supported flight modes is vehicle-specific (see
    `Copter <http://copter.ardupilot.com/wiki/flying-arducopter/flight-modes/>`_,
    `Plane <http://plane.ardupilot.com/wiki/flying/flight-modes/>`_,
    `Rover <http://rover.ardupilot.com/wiki/configuration-2/#mode_meanings>`_). If an unsupported mode is set the script
    will raise a ``KeyError`` exception.

    The :py:attr:`Vehicle.mode <dronekit.lib.Vehicle.mode>` attribute can be queried for the current mode. 
    The code snippet below shows how to observe changes to the mode and then read the value:

    .. code:: python

        #Callback definition for mode observer
        def mode_callback(self, attr_name):
            print "Vehicle Mode", self.mode

        #Add observer callback for attribute `mode`
        vehicle.add_attribute_listener('mode', mode_callback)

    The code snippet below shows how to change the vehicle mode to AUTO:

    .. code:: python

        # Set the vehicle into auto mode
        vehicle.mode = VehicleMode("AUTO")

    For more information on getting/setting/observing the :py:attr:`Vehicle.mode <dronekit.lib.Vehicle.mode>` 
    (and other attributes) see the :ref:`attributes guide <vehicle_state_attributes>`.

    .. py:attribute:: name

        The mode name, as a ``string``.
    """
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return "VehicleMode:%s" % self.name

    def __eq__(self, other):
        return self.name == other

    def __ne__(self, other):
        return self.name != other

class HasObservers(object):
    def __init__(self):
        # A mapping from attr_name to a list of observers
        self.__observers = {}

    """
    Provides callback based notification on attribute changes.

    The argument list for observer is ``observer(object, attr_name, attribute_value)``.
    """
    def add_attribute_listener(self, attr_name, observer):
        """
        Add an attribute listener callback.

        The callback function (``observer``) is invoked every time the associated attribute is updated 
        from the vehicle (the attribute value may not have *changed*). The callback can be removed 
        using :py:func:`remove_attribute_listener`.
        
        .. note::
        
            The :py:func:`on_attribute` decorator performs the same operation as this method, but with 
            a more elegant syntax. Use ``add_attribute_listener`` by preference if you will need to remove 
            the observer.

        The callback arguments are:
        
        * ``self`` - the associated :py:class:`Vehicle`. This may be compared to a global vehicle handle 
          to implement vehicle-specific callback handling (if needed).
        * ``attr_name`` - the attribute name. This can be used to infer which attribute has triggered
          if the same callback is used for watching several attributes.
        * ``msg`` - the attribute value (so you don't need to re-query the vehicle object).

        The example below shows how to get callbacks for (global) location changes:

        .. code:: python

            #Callback to print the location in global frame
            def location_callback(self, attr_name, msg):
                print "Location (Global): ", msg

            #Add observer for the vehicle's current location
            vehicle.add_attribute_listener('global_frame', location_callback)     
        
        :param String attr_name: The name of the attribute to watch (or '*' to watch all attributes).
        :param observer: The callback to invoke when a change in the attribute is detected.

        """
        l = self.__observers.get(attr_name)
        if l is None:
            l = []
            self.__observers[attr_name] = l
        if not observer in l:
            l.append(observer)

    def remove_attribute_listener(self, attr_name, observer):
        """
        Remove an attribute listener (observer) that was previously added using :py:func:`add_attribute_listener`.

        For example, the following line would remove a previously added vehicle 'global_frame' 
        observer called ``location_callback``:

        .. code:: python

            vehicle.remove_attribute_listener('global_frame', location_callback)

        :param String attr_name: The attribute name that is to have an observer removed (or '*' to remove an 'all attribute' observer).
        :param observer: The callback function to remove.

        """
        l = self.__observers.get(attr_name)
        if l is not None:
            l.remove(observer)
            if len(l) == 0:
                del self.__observers[attr_name]

    def notify_attribute_listeners(self, attr_name, value):
        """
        This method calls attribute observers when the named attribute has changed.
        
        It should be called in message listeners after updating an attribute with new information
        from the vehicle. 
        
        :param String attr_name: The name of the attribute that has been updated.
        :param value: The current value of the attribute that has been updated.
        """
        for fn in self.__observers.get(attr_name, []):
            fn(self, attr_name, value)
        for fn in self.__observers.get('*', []):
            fn(self, attr_name, value)

    def on_attribute(self, name):
        """
        Decorator for attribute listeners.
        
        The decorated ``observer`` callback function is invoked every time the associated attribute is updated 
        from the vehicle (the attribute value may not have *changed*). 
        
        The callback arguments are:
        
        * ``self`` - the associated :py:class:`Vehicle`. This may be compared to a global vehicle handle 
          to implement vehicle-specific callback handling (if needed).
        * ``attr_name`` - the attribute name. This can be used to infer which attribute has triggered
          if the same callback is used for watching several attributes.
        * ``msg`` - the attribute value (so you don't need to re-query the vehicle object).
        
        .. note::
        
            There is no way to remove an attribute listener added with this decorator. Use 
            :py:func:`add_attribute_listener` if you need to be able to remove 
            the :py:func:`attribute listener <remove_attribute_listener>`.
            
        The code fragment below shows how you can create a listener for the attitude attribute.

        .. code:: python

            @vehicle.on_attribute('attitude')
            def attitude_listener(self, name, msg):
                print '%s attribute is: %s' % (name, msg)

        :param String attr_name: The name of the attribute to watch (or '*' to watch all attributes).
        :param observer: The callback to invoke when a change in the attribute is detected.
        """
        def decorator(fn):
            if isinstance(name, list):
                for n in name:
                    self.add_attribute_listener(n, fn)
            else:
                self.add_attribute_listener(name, fn)
        return decorator

class Vehicle(HasObservers):
    """
    The main vehicle API

    Asynchronous notification on change of vehicle state is available by registering observers (callbacks) for attribute changes.

    Most vehicle state is exposed through python attributes (e.g. ``vehicle.location``). Most of these attributes are
    auto-populated based on the capabilities of the connected autopilot/vehicle.

    Particular autopilots/vehicles may define different attributes from this standard list (extra batteries, GPIOs, etc.)
    However if a standard attribute is defined it must follow the rules specified below.

    **Autopilot specific attributes & types:**

    To prevent name clashes the following naming convention should be used:

    * ``ap_<name>`` - For autopilot specific parameters (apm 2.5, pixhawk etc.). For example "ap_pin5_mode" and "ap_pin5_value".
    * ``user_<name>`` - For user specific parameters

    **Standard attributes & types:**

    .. py:attribute:: location.global_frame

        Current :py:class:`LocationGlobal`.

    .. py:attribute:: location.local_frame

        Current :py:class:`LocationLocal`.

    .. py:attribute:: attitude

        Current vehicle :py:class:`Attitude` (pitch, yaw, roll).

    .. py:attribute:: velocity

        Current velocity as a three element list ``[ vx, vy, vz ]`` (in meter/sec).

    .. py:attribute:: mode

        This attribute is used to get and set the current flight mode (:py:class:`VehicleMode`).

    .. py:attribute:: airspeed

        Current airspeed in metres/second (``double``).

        .. todo:: FIXME: Should airspeed value move somewhere else from "Standard attributes & types" table?

    .. py:attribute:: groundspeed

        Groundspeed in metres/second (``double``).

    .. py:attribute:: gps_0

        GPS position information (:py:class:`GPSInfo`).

    .. py:attribute:: armed

        This attribute can be used to get and set the ``armed`` state of the vehicle (``boolean``).

        The code below shows how to read the state, and to arm/disam the vehicle:

        .. code:: python

            # Print the armed state for the vehicle
            print "Armed: %s" % vehicle.armed

            # Disarm the vehicle
            vehicle.armed = False

            # Arm the vehicle
            vehicle.armed = True

    .. py:attribute:: mount_status

        Current status of the camera mount (gimbal) as a three element list: ``[ pitch, yaw, roll ]``.

        The values in the list are set to ``None`` if no mount is configured.

    .. py:attribute:: battery

        Current system :py:class:`Battery` status.

    .. py:attribute:: rangefinder

        :py:class:`Rangefinder` distance and voltage values.

    .. py:attribute:: channel_override

        .. warning::

            RC override may be useful for simulating user input and when implementing certain types of joystick control.
            It should not be used for direct control of vehicle channels unless there is no other choice!

            Instead use the appropriate MAVLink commands like DO_SET_SERVO/DO_SET_RELAY, or more generally
            set the desired position or direction/speed.

        This attribute takes a dictionary argument defining the RC *output* channels to be overridden (specified by channel number), and their new values.
        Channels that are not specified in the dictionary are not overridden. All multi-channel updates are atomic.

        To cancel an override call ``channel_override`` again, setting zero for the overridden channels.

        The values of the first four channels map to the main flight controls: 1=Roll, 2=Pitch, 3=Throttle, 4=Yaw (the mapping is defined in ``RCMAP_`` parameters:
        `Plane <http://plane.ardupilot.com/wiki/arduplane-parameters/#rcmap__parameters>`_,
        `Copter <http://copter.ardupilot.com/wiki/configuration/arducopter-parameters/#rcmap__parameters>`_ ,
        `Rover <http://rover.ardupilot.com/wiki/apmrover2-parameters/#rcmap__parameters>`_).

        The remaining channel values are configurable, and their purpose can be determined using the
        `RCn_FUNCTION parameters <http://plane.ardupilot.com/wiki/flight-features/channel-output-functions/>`_.
        In general a value of 0 set for a specific ``RCn_FUNCTION`` indicates that the channel can be
        `mission controlled <http://plane.ardupilot.com/wiki/flight-features/channel-output-functions/#disabled>`_ (i.e. it will not directly be
        controlled by normal autopilot code).

        An example of setting and clearing the override is given below:

        .. code:: python

            # Override channels 1 and 4 (only).
            vehicle.channel_override = { "1" : 900, "4" : 1000 }

            # Cancel override on channel 1 and 4 by sending 0
            vehicle.channel_override = { "1" : 0, "4" : 0 }

        .. versionchanged:: 1.0

            This update replaces ``rc_override`` with ``channel_override``/``channel_readback`` documentation.

        .. todo:: Add note to the examples/guide like warning above not to use this mechanism except as intended:

            https://github.com/dronekit/dronekit-python/issues/72

        .. todo::

            channel_override/channel_readback documentation

            In a future update strings will be defined per vehicle type ('pitch', 'yaw', 'roll' etc...)
            and rather than setting channel 3 to 1400 (for instance), you will pass in a dict with
            'throttle':1200.

            This change will be useful in two ways:

            * we can hide (eventually we can deprecate) any notion of rc channel numbers at all.
            * vehicles can eventually define new 'channels' for overridden values.

            FIXME: Remaining channel_override/channel_readback FIXMEs:

            * how to address the units issue?  Merely with documentation or some other way?
            * is there any benefit of using lists rather than tuples for these attributes

    .. py:attribute:: channel_readback

        This read-only attribute returns a dictionary containing the *original* vehicle RC channel values (ignoring any overrides set using
        :py:attr:`channel_override <dronekit.lib.Vehicle.channel_override>`). Dictionary entries have the format ``channelName -> value``.

        For example, the returned dictionary might look like this:

        .. code:: python

            RC readback: {'1': 1500, '3': 1000, '2': 1500, '5': 1800, '4': 1500, '7': 1000, '6': 1000, '8': 1800} ? Dictionary () (read only)

    .. todo:: In V2, there may be ardupilot specific attributes & types (as in the introduction). If so, text below might be useful.

        **Autopilot specific attributes & types:**

        .. py:attribute:: ap_pin5_mode

            string (adc, dout, din)

        .. py:attribute:: ap_pin5_value

            ? double (0, 1, 2.3 etc...)

    .. todo:: Add waypoint_home attribute IF this is added: https://github.com/dronekit/dronekit-python/issues/105

    """

    def __init__(self, handler):
        super(Vehicle, self).__init__()

        self._handler = handler
        self._master = handler.master

        # Cache all updated attributes for wait_ready.
        # By default, we presume all "commands" are loaded.
        self._ready_attrs = set(['commands'])

        # Default parameters when calling wait_ready() or wait_ready(True).
        self._default_ready_attrs = ['parameters', 'gps_0', 'armed', 'mode', 'attitude']

        @self.on_attribute('*')
        def listener(_, name, value):
            self._ready_attrs.add(name)

        # Attaches message listeners.
        self._message_listeners = dict()

        @handler.forward_message
        def listener(_, msg):
            self.notify_message_listeners(msg.get_type(), msg)

        self._lat = None
        self._lon = None
        self._vx = None
        self._vy = None
        self._vz = None

        @self.on_message('GLOBAL_POSITION_INT')
        def listener(self, name, m):
            (self._lat, self._lon) = (m.lat / 1.0e7, m.lon / 1.0e7)
            self.notify_attribute_listeners('location', self.location)
            (self._vx, self._vy, self._vz) = (m.vx / 100.0, m.vy / 100.0, m.vz / 100.0)
            self.notify_attribute_listeners('velocity', self.velocity)

        self._north = None
        self._east = None
        self._down = None

        @self.on_message('LOCAL_POSITION_NED')
        def listener(self, name, m):
            self._north = m.x
            self._east = m.y
            self._down = m.z
            self.notify_attribute_listeners('local_position', self.location.local_frame)

        self._pitch = None
        self._yaw = None
        self._roll = None
        self._pitchspeed = None
        self._yawspeed = None
        self._rollspeed = None

        @self.on_message('ATTITUDE')
        def listener(self, name, m):
            self._pitch = m.pitch
            self._yaw = m.yaw
            self._roll = m.roll
            self._pitchspeed = m.pitchspeed
            self._yawspeed = m.yawspeed
            self._rollspeed = m.rollspeed
            self.notify_attribute_listeners('attitude', self.attitude)

        self._heading = None
        self._alt = None
        self._airspeed = None
        self._groundspeed = None

        @self.on_message('VFR_HUD')
        def listener(self, name, m):
            self._heading = m.heading
            self.notify_attribute_listeners('heading', self.heading)
            self._alt = m.alt
            self.notify_attribute_listeners('location', self.location)
            self._airspeed = m.airspeed
            self.notify_attribute_listeners('airspeed', self.airspeed)
            self._groundspeed = m.groundspeed
            self.notify_attribute_listeners('groundspeed', self.groundspeed)

        self._rngfnd_distance = None
        self._rngfnd_voltage = None

        @self.on_message('RANGEFINDER')
        def listener(self, name, m):
            self._rngfnd_distance = m.distance
            self._rngfnd_voltage = m.voltage
            self.notify_attribute_listeners('rangefinder', self.rangefinder)

        self._mount_pitch = None
        self._mount_yaw = None
        self._mount_roll = None

        @self.on_message('MOUNT_STATUS')
        def listener(self, name, m):
            self._mount_pitch = m.pointing_a / 100
            self._mount_roll = m.pointing_b / 100
            self._mount_yaw = m.pointing_c / 100
            self.notify_attribute_listeners('mount', self.mount_status)

        self._rc_readback = {}

        @self.on_message('RC_CHANNELS_RAW')
        def listener(self, name, m):
            def set_rc(chnum, v):
                '''Private utility for handling rc channel messages'''
                # use port to allow ch nums greater than 8
                self._rc_readback[str(m.port * 8 + chnum)] = v

            set_rc(1, m.chan1_raw)
            set_rc(2, m.chan2_raw)
            set_rc(3, m.chan3_raw)
            set_rc(4, m.chan4_raw)
            set_rc(5, m.chan5_raw)
            set_rc(6, m.chan6_raw)
            set_rc(7, m.chan7_raw)
            set_rc(8, m.chan8_raw)

        self._voltage = None
        self._current = None
        self._level = None

        @self.on_message('SYS_STATUS')
        def listener(self, name, m):
            self._voltage = m.voltage_battery
            self._current = m.current_battery
            self._level = m.battery_remaining
            self.notify_attribute_listeners('battery', self.battery)

        self._eph = None
        self._epv = None
        self._satellites_visible = None
        self._fix_type = None  # FIXME support multiple GPSs per vehicle - possibly by using componentId

        @self.on_message('GPS_RAW_INT')
        def listener(self, name, m):
            self._eph = m.eph
            self._epv = m.epv
            self._satellites_visible = m.satellites_visible
            self._fix_type = m.fix_type
            self.notify_attribute_listeners('gps_0', self.gps_0)

        self._current_waypoint = 0

        @self.on_message(['WAYPOINT_CURRENT', 'MISSION_CURRENT'])
        def listener(self, name, m):
            self._current_waypoint = m.seq

        self._ekf_poshorizabs = False
        self._ekf_constposmode = False
        self._ekf_predposhorizabs = False

        @self.on_message('EKF_STATUS_REPORT')
        def listener(self, name, m):
            # boolean: EKF's horizontal position (absolute) estimate is good
            self._ekf_poshorizabs = (m.flags & ardupilotmega.EKF_POS_HORIZ_ABS) > 0
            # boolean: EKF is in constant position mode and does not know it's absolute or relative position
            self._ekf_constposmode = (m.flags & ardupilotmega.EKF_CONST_POS_MODE) > 0
            # boolean: EKF's predicted horizontal position (absolute) estimate is good
            self._ekf_predposhorizabs = (m.flags & ardupilotmega.EKF_PRED_POS_HORIZ_ABS) > 0

            self.notify_attribute_listeners('ekf_ok', self.ekf_ok)

        self._flightmode = 'AUTO'
        self._armed = False
        self._system_status = None

        @self.on_message('HEARTBEAT')
        def listener(self, name, m):
            self._armed = (m.base_mode & mavutil.mavlink.MAV_MODE_FLAG_SAFETY_ARMED) != 0
            self.notify_attribute_listeners('armed', self.armed)
            self._flightmode = {v: k for k, v in self._master.mode_mapping().items()}[m.custom_mode]
            self._system_status = m.system_status
            self.notify_attribute_listeners('mode', self.mode)

        # Waypoints.

        self._wploader = mavwp.MAVWPLoader()
        self._wp_loaded = True
        self._wp_uploaded = None
        self._wpts_dirty = False
        self._commands = CommandSequence(self)

        @self.on_message(['WAYPOINT_COUNT','MISSION_COUNT'])
        def listener(self, name, msg):
            if not self._wp_loaded:
                self._wploader.clear()
                self._wploader.expected_count = msg.count
                self._master.waypoint_request_send(0)

        @self.on_message(['WAYPOINT', 'MISSION_ITEM'])
        def listener(self, name, msg):
            if not self._wp_loaded:
                if msg.seq > self._wploader.count():
                    # Unexpected waypoint
                    pass
                elif msg.seq < self._wploader.count():
                    # Waypoint duplicate
                    pass
                else:
                    self._wploader.add(msg)

                    if msg.seq + 1 < self._wploader.expected_count:
                        self._master.waypoint_request_send(msg.seq + 1)
                    else:
                        self._wp_loaded = True
                        self.notify_attribute_listeners('commands', self.commands)

        # Waypoint send to master
        @self.on_message(['WAYPOINT_REQUEST', 'MISSION_REQUEST'])
        def listener(self, name, msg):
            if self._wp_uploaded != None:
                wp = self._wploader.wp(msg.seq)
                handler.fix_targets(wp)
                self._master.mav.send(wp)
                self._wp_uploaded[msg.seq] = True

        # TODO: Waypoint loop listeners

        # Parameters.

        start_duration = 0.2
        repeat_duration = 1

        self._params_count = -1
        self._params_set = []
        self._params_loaded = False
        self._params_start = False
        self._params_map = {}
        self._params_last = time.time() # Last new param.
        self._params_duration = start_duration
        self._parameters = Parameters(self)

        @handler.forward_loop
        def listener(_):
            # Check the time duration for last "new" params exceeds watchdog.
            if self._params_start:
                if None not in self._params_set and not self._params_loaded:
                    self._params_loaded = True
                    self.notify_attribute_listeners('parameters', self.parameters)

                if not self._params_loaded and time.time() - self._params_last > self._params_duration:
                    c = 0
                    for i, v in enumerate(self._params_set):
                        if v == None:
                            self._master.mav.param_request_read_send(0, 0, '', i)
                            c += 1
                            if c > 50:
                                break
                    self._params_duration = repeat_duration
                    self._params_last = time.time()

        @self.on_message(['PARAM_VALUE'])
        def listener(self, name, msg):
            # If we discover a new param count, assume we
            # are receiving a new param set.
            if self._params_count != msg.param_count:
                self._params_loaded = False
                self._params_start = True
                self._params_count = msg.param_count
                self._params_set = [None]*msg.param_count

            # Attempt to set the params. We throw an error
            # if the index is out of range of the count or
            # we lack a param_id.
            try:
                if msg.param_index < msg.param_count and msg:
                    if self._params_set[msg.param_index] == None:
                        self._params_last = time.time()
                        self._params_duration = start_duration
                    self._params_set[msg.param_index] = msg
                self._params_map[msg.param_id] = msg.param_value
            except:
                import traceback
                traceback.print_exc()

        # Heartbeats.

        self._heartbeat_started = False
        self._heartbeat_lastsent = 0
        self._heartbeat_lastreceived = 0
        self._heartbeat_timeout = False

        self._heartbeat_warning = 5
        self._heartbeat_error = 30

        @handler.forward_loop
        def listener(_):
            # Send 1 heartbeat per second
            if time.time() - self._heartbeat_lastsent > 1:
                self._master.mav.heartbeat_send(mavutil.mavlink.MAV_TYPE_GCS, mavutil.mavlink.MAV_AUTOPILOT_INVALID, 0, 0, 0)
                self._heartbeat_lastsent = time.time()

            # Timeouts.
            if self._heartbeat_started:
                if self._heartbeat_error and self._heartbeat_error > 0 and time.time() - self._heartbeat_lastreceived > self._heartbeat_error:
                    raise Exception('>>> No heartbeat in %s seconds, aborting.' % self._heartbeat_error)
                elif time.time() - self._heartbeat_lastreceived > self._heartbeat_warning:
                    if self._heartbeat_timeout == False:
                        errprinter('>>> Link timeout, no heartbeat in last %s seconds' % self._heartbeat_warning)
                        self._heartbeat_timeout = True

        @self.on_message(['HEARTBEAT'])
        def listener(self, name, msg):
            self._heartbeat_lastreceived = time.time()
            if self._heartbeat_timeout:
                errprinter('>>> ...link restored.')
            self._heartbeat_timeout = False

    def on_message(self, name):
        """
        Decorator for message listener callback functions.
        
        .. tip::
        
            This is the most elegant way to define message listener callback functions. 
            Use :py:func:`add_message_listener` only if you need to be able to
            :py:func:`remove the listener <remove_message_listener>` later.
        
        A decorated message listener function is called with three arguments every time the 
        specified message is received: 
        
        * ``self`` - the current vehicle.
        * ``name`` - the name of the message that was intercepted.
        * ``message`` - the actual message (a `pymavlink <http://www.qgroundcontrol.org/mavlink/pymavlink>`_
          `class <https://www.samba.org/tridge/UAV/pymavlink/apidocs/classIndex.html>`_).        

        For example, in the fragment below ``my_method`` will be called for every heartbeat message:
        
        .. code:: python

            @vehicle.on_message('HEARTBEAT')
            def my_method(self, name, msg):
                pass
                
        :param String name: The name of the message to be intercepted by the decorated listener function (or '*' to get all messages).
        """
        def decorator(fn):
            if isinstance(name, list):
                for n in name:
                    self.add_message_listener(n, fn)
            else:
                self.add_message_listener(name, fn)
        return decorator

    def add_message_listener(self, name, fn):
        """
        Adds a message listener function that will be called every time the specified message is received.
        
        .. tip::
        
            We recommend you use :py:func:`on_message` instead of this method as it has a more elegant syntax.
            This method is only preferred if you need to be able to 
            :py:func:`remove the listener <remove_message_listener>`.
        
        The callback function must have three arguments:
        
        * ``self`` - the current vehicle.
        * ``name`` - the name of the message that was intercepted.
        * ``message`` - the actual message (a `pymavlink <http://www.qgroundcontrol.org/mavlink/pymavlink>`_
          `class <https://www.samba.org/tridge/UAV/pymavlink/apidocs/classIndex.html>`_).        

        For example, in the fragment below ``my_method`` will be called for every heartbeat message:
        
        .. code:: python

            #Callback method for new messages
            def my_method(self, name, msg):
                pass

            vehicle.add_message_listener('HEARTBEAT',my_method)
        
        :param String name: The name of the message to be intercepted by the listener function (or '*' to get all messages).
        :param fn: The listener function that will be called if a message is received.        
        """
        name = str(name)
        if name not in self._message_listeners:
            self._message_listeners[name] = []
        if fn not in self._message_listeners[name]:
            self._message_listeners[name].append(fn)

    def remove_message_listener(self, name, fn):
        """
        Removes a message listener (that was previously added using :py:func:`add_message_listener`).
        
        :param String name: The name of the message for which the listener is to be removed (or '*' to remove an 'all messages' observer).
        :param fn: The listener callback function to remove.            
        
        """
        name = str(name)
        if name in self._message_listeners:
            self._message_listeners[name].remove(fn)
            if len(self._message_listeners[name]) == 0:
                del self._message_listeners[name]

    def notify_message_listeners(self, name, msg):
        for fn in self._message_listeners.get(name, []):
            fn(self, name, msg)
        for fn in self._message_listeners.get('*', []):
            fn(self, name, msg)

    def close(self):
        return self._handler.close()

    def flush(self):
        """
        Call ``flush()`` after :py:func:`adding <CommandSequence.add>` or :py:func:`clearing <CommandSequence.clear>` mission commands.

        After the return from ``flush()`` any writes are guaranteed to have completed (or thrown an
        exception) and future reads will see their effects.

        .. warning:: 

            This has been replaced by :py:func:`Vehicle.commands.upload() <Vehicle.commands.upload>`.
        """
        return self.commands.upload()

    #
    # Private sugar methods
    #

    @property
    def _mode_mapping(self):
        return self._master.mode_mapping()

    #
    # Operations to support the standard API.
    #

    @property
    def mode(self):
        if not self._flightmode:
            return None
        return VehicleMode(self._flightmode)

    @mode.setter
    def mode(self, v):
        self._master.set_mode(self._mode_mapping[v.name])

    @property
    def location(self):
        # For backward compatibility, this is (itself) a LocationLocal object.
        ret = LocationGlobal(self._lat, self._lon, self._alt, is_relative=False)
        ret.local_frame = LocationLocal(self._north, self._east, self._down)
        ret.global_frame = LocationGlobal(self._lat, self._lon, self._alt, is_relative=False)
        return ret

    @property
    def battery(self):
        return Battery(self._voltage, self._current, self._level)

    @property
    def rangefinder(self):
        return Rangefinder(self._rngfnd_distance, self._rngfnd_voltage)

    @property
    def velocity(self):
        return [ self._vx, self._vy, self._vz ]

    @property
    def attitude(self):
        return Attitude(self._pitch, self._yaw, self._roll)

    @property
    def gps_0(self):
        return GPSInfo(self._eph, self._epv, self._fix_type, self._satellites_visible)

    @property
    def armed(self):
        return self._armed

    @armed.setter
    def armed(self, value):
        if value:
            self._master.arducopter_arm()
        else:
            self._master.arducopter_disarm()

    @property
    def system_status(self):
        """
        System status flag according to the MAVLink 
        `MAV_STATE <http://mavlink.org/messages/common#MAV_STATE_UNINIT>`_ enum.
        
        States include:
        
        * ``MAV_STATE_UNINIT`` (0): Uninitialized system, state is unknown.
        * ``MAV_STATE_BOOT`` (1): System is booting up.
        * ``MAV_STATE_CALIBRATING`` (2): System is calibrating and not flight-ready.
        * ``MAV_STATE_STANDBY`` (3): System is grounded and on standby. It can be launched any time.
        * ``MAV_STATE_ACTIVE`` (4): System is active and might be already airborne. Motors are engaged.
        * ``MAV_STATE_CRITICAL`` (5): System is in a non-normal flight mode. It can however still navigate.
        * ``MAV_STATE_EMERGENCY`` (6): System is in a non-normal flight mode. It lost control over parts 
          or over the whole airframe. It is in mayday and going down.
        * ``MAV_STATE_POWEROFF`` (7): System just initialized its power-down sequence, will shut down now.
        """
        return self._system_status

    @property
    def heading(self):
        """
        Current heading in degrees (0..360, where North = 0).
        """
        return self._heading
{'fraction_non_alphanumeric': 0.0908160715772942, 'fraction_numerical': 0.0065794952944115935, 'mean_word_length': 2.3504743681734834, 'pattern_counts': {'":': 0, '<': 84, '<?xml version=': 0, '>': 118, 'https://': 10, 'lorem ipsum': 0, 'www.': 8, 'xml': 0}, 'pii_count': 4, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6056052', 'n_tokens_mistral': 23847, 'n_tokens_neox': 22581, 'n_words': 7455}