Paparazzi interface

Paparazzi UAV (Unmanned Aerial Vehicle) is an open-source drone hardware and software project encompassing autopilot systems and ground station software for multicopters/multirotors, fixed-wing, helicopters and hybrid aircraft that was founded in 2003. Paparazzi UAV was designed with autonomous flight as the primary focus and manual flying as the secondary. From the beginning it was designed with portability in mind and the ability to control multiple aircraft within the same system. Paparazzi features a dynamic flight plan system that is defined by mission states and using way points as “variables”. This makes it easy to create very complex fully automated missions without the operators intervention. For more project information, see here.

Paparazzi and Nephelae

Since Paparazzi is an autopilot, its job has many objectives related to the control of the aircrafts.
These jobs can be : control of the fleet and setting objectives using a flight plan, acquiring and displaying data, and much more. Paparazzi can also manage missions that have not a static objective (like adaptative missions) that uses data to update itself. However, since Paparazzi does not analyzes nor processes raw data in real-time, we need the intervention of CAMS.

You'll find in this package how we achieve a communication with Paparazzi (middleware, sortof), and how we update the update the state of UAVs abstractions CAMS-side with Paparazzi and sending information to the real UAVs, using the processed data.

Communication between Paparazzi and CAMS

The Ivy bus

Paparazzi data are exchanged between software components into the form ASCII messages. These ASCII messages are broadcasted onto the local network by all the software components of the Paparazzi framework, and are available to any software with access to local network (a software BUS behavior). The process of transmitting and receiving data from/to the network is handled by the Ivy bus, which provides a simple Python API.

In the Paparazzi framework, these ASCII messages are formatted, and define many messages types (for the full list of Paparazzi messages see here). For more information on the message system in general see here.

Paparazzi comes natively with Python APIs that manages the communication between softwares and also the parsing and encoding of messages. All of these functions are inside the pprzlink library.
To use the pprzlink lib :

PPRZ_HOME = os.getenv("PAPARAZZI_HOME", None)
if PPRZ_HOME is not None:
    sys.path.append(PPRZ_HOME + "/var/lib/python")
else:    PPRZLINK_HOME = os.getenv("PAPARAZZI_PPRZLINK", None)                                                        
    if PPRZLINK_HOME is not None:
        print(os.path.join(PPRZLINK_HOME, 'lib/v2.0/python'))
        sys.path.append(os.path.join(PPRZLINK_HOME, 'lib/v2.0/python'))
# IvyMessagesInterface for communication (using the Ivy bus)
from pprzlink.ivy import IvyMessagesInterface
# PprzMessage for encoding/decoding messages (using a specific format)
from pprzlink.message import PprzMessage


To ease the usage of these libraries, CAMS uses an object called MessageInterface specifc for Paparazzi. With this object, it is possible to call aliases (sending and receiving messages) of IvyMessagesInterface and manages "bindings" (sortof topics). An example of alias implemented in MessageInterface :

def bind(self, callback, ivyRegex):                                                                                                                  
Binds a function to call whenever a message is received, containing
the regex passed in parameter. The message received is then processed
using the prettify_message function of the class.

callback : function                                                                                                                              
    Function to call when a matching message is received

ivyRegex : str                                                                                                                                   
    Used to find the matching messages

    Id of the message
return self.messageInterface.subscribe(                                                                                                          
                 lambda sender, msg: callback(MessageInterface.prettify_message(msg)), ivyRegex) 

Only one single instance of MessageInterface is created by instance of CAMS, meaning this instance is shared by multiple interfaces.


CAMS also uses for Paparazzi a specific object called PprzMsgHarmonizer. This object is meant to centralize the usage of PprzMessages object format and to process and retrieve the mandatory information into a CAMS generic object.

/!\ Since the implementation of the PprzMsgHarmonizer is poorly made at the moment, there may be variations over time. /!\

It can subscribe to messages, by passing a single string. This string is then transformed into an ivy regex, and this regex itself is used to check if a message correponds to this regex. It can also builds messages. Based on the string passed, it'll need a certain amount of data to build the message and once done, will send it on the ivy bus, for Paparazzi.
Sending and subscribing messages are always managed by the MessageInterface object so if a binding is needed for instance, the PprzMsgHarmonizer will start by first finding the right regex, then calling the MessageInterface bind function with the regex and the callback function.
An example of code you can find in the class, when you want to subscribe to a type of messages :

def subscribe_to(self, callback, msgType, raw=False):
Subscribe to a topic using the msgType. Each time a message correponding
to the topic is received, the callback function passed in parameters is
called. An optional boolean parameter raw is used to subscribe to
messages that have not a PprzMessage structure.
callback : function
    The function called when a message corresponding to the regex of the
    topic is used.
msgType : str
    The topic we subscribe to
raw : bool
    Optional parameter specific for paparazzi. Used to subscribe to
    messages that does not have a PprzMessage structure
    The bindId associated to subscription (used for unsubscribing)
    if msgType not in self.subscribeTo.keys():
        raise ValueError('The type of subscribe message ' + msgType + ' is not defined for this class')
    if msgType in self.messagesToProcess:
        usedCallback = lamda msg : callback(self.__process_message(msg, msgType))
        usedCallback = callback
    if raw:
        bindId = self.msgInterface.bind_raw(usedCallback, self.subscribeTo[msgType])
        bindId = self.msgInterface.bind(usedCallback, self.subscribeTo[msgType])
    return bindId

Updating CAMS via messages

Once a message is received, we need to update the state of CAMS. There are two types of messages, those which changes the state of the aircraft and the database and those which only changes the state of the database. The AircraftPprz class and the plugins are here to solve those two problems by using the different functions of PprzMsgHarmonizer and MessageInterface and callbacks. Whenever a callback is triggered, it will call functions that will change the state of the database and if needed, the state of the aircraft.

An example of callback :

def flight_param_callback(self, flightParam):
Sets the currentFlightParam attribute with the message passed in
parameters. If the statusNotified attribute is still False, sends a
message on the add_status to add it in the database.

flightParam : pprzlink.message.PprzMessage
    The new currentFlightParam attribute
    self.currentFlightParam = flightParam
    #---------------- REPLAY ----------------
    utmUav = utm.from_latlon(flightParam['lat'], flightParam['long'])
    update_parameters = {
        'lat'       : flightParam['lat'],
        'long'      : flightParam['long'],
        'alt'       : flightParam['alt'],
        'agl'       : flightParam['agl'],
        'roll'      : flightParam['roll'],
        'pitch'     : flightParam['pitch'],
        'heading'   : flightParam['heading'],
        'course'    : flightParam['course'],
        'speed'     : flightParam['speed'],
        'air_speed' : flightParam['airspeed'],
        'climb'     : flightParam['climb'],
        'itow'      : flightParam['itow'],
        'utm_east'  : utmUav[0],
        'utm_north' : utmUav[1],
        'utm_zone'  : str(utmUav[2]) + utmUav[3]
    pos = Position()
    pos.x = utmUav[0] - self.navFrame.position.x
    pos.y = utmUav[1] - self.navFrame.position.y
    pos.z = flightParam['alt'] - self.navFrame.position.z
    if self.replayTime is None:
        pos.t = flightParam.timestamp - self.navFrame.position.t
        pos.t = self.replayTime
    update_parameters['position'] = pos
    # changes the state of the AircraftStatus object
    #---------------- REPLAY ----------------
    if not self.statusNotified:
        # notifying status if not notified in ap_status_callback, changes the state of the database
    self.statusNotified = False

Updated by Rafael Bailon-Ruiz about 3 years ago · 1 revisions