Adding a New Analyzer

DroneKit-LA has been designed from the ground-up to provide an extensible framework for supporting different log input and output formats, and for creating tests (analyzers) which are independent of those formats and of each other.

This article provides a brief overview of the main framework classes and some suggestions on the how to write a new analyzer. There is additional information in the Analyzer Class Reference.

Tip

We highly recommend that you check out the existing analyzers for ideas/examples.

Framework overview

The main framework classes are the analyzer base class (Analyzer), the results base classes (Analyzer_Result, Analyzer_Result_Period, Analyzer_Result_Event, Analyzer_Result_Summary) and the vehicle/log class which is used to expose log information to the analyzer (AnalyzerVehicle::Base).

Analyzers must be derived from Analyzer and override its virtual methods. The first two methods set the name and description reported by the analyzer in the output log:

/* Analyzer name. This string is displayed in the test output's
``name`` field for the test. */
virtual const std::string name() const = 0;

/* Analyzer description. This string is displayed in the test
output's ``description`` field. */
virtual const std::string description() const = 0;

The Analyzer::evaluate() and Analyzer::end_of_log() methods are called by the framework for every logged message, to gather relevant information and report the results (respectively). Some analyzers may choose to report the results only at the end of the available log data.

/* Evaluate message. This method is called by the analyzer framework for every message in the log.
Implementations should evaluate available data and store anything needed for generating a result. */
virtual void evaluate() { }

/* Generate results. This method is called by the analyzer framework for every message in the log.
Implementations should store results based on the evaluation information generated by the `evaluate()` method */
virtual void end_of_log(uint32_t packet_count UNUSED) { }

The evaluate() method gets data about the vehicle (log) to analyze using the base-classes protected _vehicle member (an AnalyzerVehicle::Base, cast to the the actual vehicle type). The key information from the evaluation is typically saved as part of the new analyzer’s private data.

The new analyzer should also define a results class (an instance of which is usually owned by your Analyzer-derived class). The results class must be derived from one of the base classes listed below:

In your end_of_log() implementation you can populate this results object using the Analyzer_Result::set_status(), set_reason(), add_evidence(), add_source() and set_severity_score() or increase_severity_score() (severity-score) methods and then save it to the analyzer using Analyzer::add_result(). An example from the Arming Check analyzer is shown below:

result->set_status(analyzer_status_fail);
result->set_reason("Some of the arming checks were disabled when the craft was  armed");
result->add_evidence(string_format("Arming flags: %u", result->arming_check()));
result->add_source(_data_sources.get("PARAM"));
result->add_source(_data_sources.get("ARMING"));
...
//Add the result to the Analyzer
add_result(result);

If the analyzer detects multiple issues it can repopulate its result object and call add_result() for each issue.

Analyzer provides a number of other methods that you can optionally use/over ride in your classes. One example is configure(), which can be overridden to support setting failure levels using a configuration file.

Creating an Analyzer

The easiest way to start creating a new analyzer is to duplicate an existing analyzer and modify the virtual methods described in the previous section.

Tip

A good one to copy might be the Battery Analyzer (header, source) because it is relatively small and straightforward.

All of the existing analyzers have been implemented in just two files (the header and source). They are placed in the /analyzer/ folder and prefixed with the string analyzer_.

You’ll need to add the source files into the project Makefile:

SRCS_CPP += analyzer/analyzer_your_problem.cpp

You’ll also need to setup the code to instantiate the new analyzer in analyze.cpp. This code usually also sets up any trigger values from the configuration file, as shown:

#include "analyzer/analyzer_your_problem.h"

...

Analyzer_Your_Problem* analyzer_your_problem = new Analyzer_Your_Problem(vehicle,_data_sources);
if (analyzer_your_problem != NULL) {
    configure_analyzer(config, analyzer_your_problem);
} else {
    syslog(LOG_INFO, "Failed to create analyzer_your_problem");
}

Tip

Not all information your analyzer might need will necessary be available in the AnalyzerVehicle::Base class (accessed through analyzer’s _vehicle member). If required information is present in the original log but not the analyzer’s vehicle model then you can add it using the process described in Extending the Vehicle-Log Model.