- Simple Plugin Example
- UAV Gadget plugin Example
- Gadget Implementation
- map.pro project file
- MapGadget.pluginspec file
- Gadget Implementation
- Dials plugin
- System Health plugin
- Primary Flight Display plugin
- Development tips
This page is intended as a step by step guide on how to start creating plugins for the OpenPilot GCS. Besides this page it would also be beneficial to refer to the following resources:
- : Basic design
- Writing Qt Creator Plugins: Since the GCS is based on the Qt Creator, this provides good background.
- Noobie Mistakes: Before checking in code, you should read this
Simple Plugin Example
The best way to learn about writing a OpenPilot GCS plugin is to start by writing a very simple plugin. In this example we will chose the most basic case and create a plugin that simply does nothing, hence we will call it DoNothing. This will enable us to discover the basic classes provided in the OpenPilot GCS and to get our plugin to be listed in the plugin list within the GCS application.
Besides its use as a tutorial, the code here is a good template for new plugins and is in SVN but is not compiled by default.
Create a GCS Plugin Project
Create a folder called
donothing in “trunk/ground/openpilotgcs/src/plugins”. The entire source code of the plugin will be put into this directory. While it is possible to write and build OpenPilot GCS plugins outside of its source tree– this will be covered later– it is easier to write plugins within the OpenPilot GCS source tree, as we will do this for our first example.
The first step is to create the donothing.pro project file with the following contents:
This project file configures the following aspects of the plugin:
- Declares that DoNothing is a library. For example under Windows the output will be DoNothing.dll
- Configures DoNothing to make use of settings defined in openpilotgcsplugin.pri
- Configures DoNothing to make use of settings defined in coreplugin.pri
- Provides information about the .h and .cpp files that make up the plugin
Including the Plugin in the build
trunk/ground/openpilotgcs/src/plugins/plugins.pro file and include the following lines at the end of the file and save the changes.
The above lines make sure that the next time we build the OpenPilot GCS, the donothing plugin is compiled along with the rest of the OpenPilot GCS.
So far we have only written the project file and marked our plugin for compilation. We will now do the actual implementation of the plugin.
All OpenPilot GCS Plugins implement the IPlugin interface. Let\’s take a look at how the DoNothing plugin implements the interface and then step through it in stages.
trunk/ground/openpilotgcs/src/plugins/donothing/donothingplugin.h enter the following code:
As you can see the DoNothingPlugin class implements the IPlugin interface and nothing else. Let\’s look at how the functions are implemented.
trunk/ground/openpilotgcs/src/plugins/donothing/donothingplugin.cpp enter the following code:
- As you can see above, apart from initializing local (non widget and non action) variables; the constructor and destructor don\’t do much else.
- The \’\’initialize()\’\’ member function is called when the OpenPilot GCS wants a plugin to initialize itself. This function is ideally used to initialize the internal state of the plugin and register actions/objects with the GCS. The function is called after all the dependencies of this plugin have been loaded. Since our plugin really does nothing, we return true signifying that the initialization was successful. If the initialization was unsuccessful the errMsg string should be set to a human readable error message.
- The \’\’extensionsInitialized()\’\’ member function is called after this plugin has been initialized (ie. after initialize() method has been called). This member function is called on plugins that depend on this plugin first.
- The \’\’\’shutdown()\’\’\’ member function is called when the plugin is about to be unloaded.
- Finally we export the plugin class by making use of the \’\’\’Q_EXPORT_PLUGIN()\’\’\’ macro.
The pluginspec file
Each plugin should accompany a pluginspec file that provides some metadata about the plugin. For our DoNothing plugin the pluginspec should be created as:
trunk/ground/openpilotgcs/src/plugins/donothing/DoNothing.pluginspec and enter the following code:
The pluginspec file provides the following fields of information:
- Name of the plugin, which is also used as the name of the library file that provides the plugin implementation. (In our case DoNothing.dll on Windows, libDoNothing.so on Unix)
- Version of the plugin
- Required OpenPilot GCS version
- Vendor name
- Copyright information
- License text
- URL of the plugin vendor
- Dependency List – provides all the plugins that this plugin depends on. Qt Creator ensures that dependencies are loaded and initialized before this plugin.
\’\’\’Note:\’\’\’ The pluginspec file should have the filename TARGET.pluginspec where TARGET is the variable defined in the project file (donothing.pro). Case matters!
\’\’\’Note2:\’\’\’ The pluginspec file should be in the same directory as the plugin\’s project file, in our case it will be called
DoNothing.pluginspec. Just to make things clear, the contents of the DoNothing plugin directory is as shown below:
Compiling the Plugin
Now that the DoNothing Plugin is in the tree and is added to the plugins.pro, it will be included in the build of the GCS.
To compile the GCS:
- Open Terminal (alt F2).
- Change directory to OpenPilot: cd ~/OpenPilot
- Compile archives: make gcs
To compile the GCS please refer to the instructions on the main [GCS Development#Compiling].
Once the compile has finished, in the OpenPilot GCS you can select about, installed plugins and then click details with the DoNothing plugin highlighted, you will see the DoNothing plugin information shown in the screen shot below.
UAV Gadget plugin Example
UAV Gadgets are a specific type of plugin which is displayed on the various GCS workspaces. In this example, we will show how to implement a Map Gadget.
First some preliminaries. In GCS you can use buttons at the bottom of the screen to switch between different workspaces. A workspace is divided into one or more views, and each view contains exactly one UAVGadget. In the picture below we can see that “Workspace 1” is the current workspace, and it contains three views. The large view contains our object of interest, a map gadget which simply displays a map.
Our Map Gadget has three options that the user can set in Tools->Options:
A UAVGadget can have many different configurations, which are specific choices of settings. In the picture we can see that there is only one configuration for Map Gadget, “default”. You can show the same Map Gadget (that is, same class, different object instances) in different views, and they can use the same or different configurations. All Map Gadgets will choose from the same set of configurations, though. You select which configuration to use from the drop down list in the view\’s toolbar.
To implement Map Gadget, we need to create six classes:
MapGadget\’\’\’ — contains the logic of the gadget,
MapGadgetConfiguration\’\’\’ — the data representation of settings,
MapGadgetWidget\’\’\’ — the graphical representation of the gadget,
MapGadgetOptionsPage\’\’\’ — the graphical representation plus logic of the settings,
MapGadgetFactory\’\’\’ — factory for gadgets, optionspages and configurations and
MapPlugin\’\’\’ — creates a MapGadgetFactory object and adds it to plugin space.
\’\’\’Note:\’\’\’ The source code for the Map Gadget is available in the http://svn.openpilot.org/listing.php?repname=OpenPilot&path=%2Fground%2Fsrc%2Fplugins%2Fmap%2F svn. Some
namespace and other bits are missing in the code below.
IUAVGadget\’\’\’ declares that two functions should be implemented:
widget()\’\’\’ returns a
QWidget which is what will be shown in the view and
loadConfiguration()\’\’\’ loads a configuration to update its internal state/behavior.
loadConfiguration() is called whenever a user changes the settings of an gadget in the options page and presses Apply/Ok and whenever a user selects a different configuration for the gadget. In short,
loadConfiguration() typically gets called many times.
IUAVGadgetConfiguration\’\’\’ declares that two functions should be implemented:
saveState()\’\’\’ encodes the settings for persistent storage and
clone()\’\’\’ creates a configuration with a copy of the settings.
IUAVGadgetConfiguration does not mandate a function
restoreState(), instead we use the constructor for that. See also the interface for factories:
Our Map Gadget has three settings: the zoom level, latitude and longitude.
MapGadgetConfiguration therefore needs to have three variables to store these. We\’ll call them
state in the constructor can be empty, and the “default” configuration should then be created.
MapGadgetWidget uses a MapControl object (http://www.medieninf.de/qmapcontrol/), which does the real magic of showing a map. We will just accept for now that QMapControl has been added as a library to the GCS code base, and that we can use it.
IOptionsPage\’\’\’ declares that three functions should be implemented:
createPage()\’\’\’ creates a
QWidgetthat shows the settings,
apply()\’\’\’ stores the state of the GUI components back to the configuration object and
finish()\’\’\’ does some cleanup when we are done.
IUAVGadgetFactory\’\’\’ declares that three functions should be implemented:
createGadget()\’\’\’ creates a uav gadget,
createConfiguration()\’\’\’ creates a configuration for this type of gadget and
createOptionsPage()\’\’\’ creates an options page for this type of gadget.
MapPlugin\’\’\’ class is really simple. \’\’\’
MapPlugin\’\’\’ inherits from \’\’\’
ExtensionSystem::IPlugin\’\’\’ and implements the function \’\’\’
initialize()\’\’\’. There we create a \’\’\’
MapGadgetFactory\’\’\’ object and add it to the plugin space with the function \’\’\’
map.pro project file
The Dials plugin is designed in such a way that anyone with design abilities can create graphical dial renderings which will be directly useable from within the GCS. The only requirement is that the dial is created with vector editing software, such as Illustrator or Inkscape, and that some elements inside the drawing have specific names.
The [Plugin Development] page summarizes the main steps to create a dial.
System Health plugin
The system health plugin is similar to the dials plugin: the plugin itself only receives alarm objects, and it toggles elements from a system health SVG diagram. Like the dials plugin, this enables non-developers to create system health schematics very easily.
The [Plugin Development] page summarizes the procedure to create a system health diagram.
Primary Flight Display plugin
The PFD plugin also works using a master SVG file: it is especially interesting in the case of this plugin, because this makes it possible to configure the entire look and feel of the PFD by using a graphics editor and no code whatsoever.
The [Plugin Development] page will summarise how to create a PFD master layout.
Receiving UAVTalk messages from within a plugin
Plugins will usually want to do actions based on messages received from various systems components. The way to achieve this is the following: the object name (eg ExampleObject1) and the field name (eg Field1) have to be known, the following code retrieves the latest value as a double:
To update the gadget every time an update is received from the autopilot then connect to the obj->objectUpdated() signal.
Options Dialog guidelines
The user interface for the options page is made in Qt Designer. The layout is a grid layout and the margins are 0 for gadgets, see image below. For gadgets with options that choose a file or directory, Utils::PathChooser should be used, for consistent look and feel. To use Utils::PathChooser with Qt Designer you add a QWidget to the canvas and then promote it to a PathChooser.
Handling of Changes in the Configuration
When a plugin writes its configuration to a file, it might well be that this configuration is read by a newer version of the pugin, or it might be, that you import a configuration from someone who has a newer version of that plugin. These changes in confiruration formats shuld be handled gracefully. Central for this is the Class Core::UAVConfigInfo.
The UAVConfigInfo provides version-information for the configuration-data and callback functions to ask the user how to handle incompatible (old or newer) configurations.
When the config is created from a QSettings instance, an UAVConfigInfo object is passed to the factory-method. With the version-data the plugin can decide whether the presented config-data is compatible to the current implementation. It may migrate old data to the current format or abort the import.
When the config is written to the QSettings instance, an UAVConfigInfo object is passed to the writer-function. The version of the config-format should be written to the UAVConfigInfo object. This version will be passed to factory-method when creating the config-object from this configuration.
Typically a plugin can handle version-changes like this:
The Version numbers are in the form “major.minor.patch” (e.g. “3.1.4”) with the following meaning:
- major: Differences in this number indicate completely incompatible formats. The config can\’t be imported.
- minor: Differences in this number indicate backwards compatible formats. Old configs can be imported or will be (should be) automatically migrated by the new plugin but configs written by this plugin can\’t be reasonably read by old versions of the plugin.
- patch: Differences in this number indicate backwards and forward compatible formats. Configs written by this plugin can be read by old versions of the plugin. Old configs should be extended with reasonable defaults by the newer plugin.
All parts (major, minor, patch) must be numeric values.
bool UAVConfigInfo::standardVersionHandlingOK(UAVConfigVersion programVersion)
Default version handling. With this function the plugin can test compatibility of the current version with the imported version. If there are differences, the user is asked whether he or she wants to import the settings or abort the import.
Returns true when the import should be done, false otherwise.