API Development

This is a technical guide on how to approach adding new features and functionality to the mJackets API.

Organization

The API components of mJackets are organized into two directories:

  • include - This directory contains all of the public headers for the mJackets API functions. When adding a new API source file to mJackets, its corresponding header file MUST be included in this directory.

  • api - This directory contains the source code for any API specific functions and classes.

Additionally, the drivers directory can be used to store the source code and include headers for any peripheral drivers. For example, if you wanted to add a driver for a Lidar Lite V3, you would add the source code, header files, and CMakeList.txt file in drivers/sensors/lidar_lite_v3/.

Format

Define Guards

All header files should have #define guards to prevent multiple inclusion. The format of the symbol name should be <PROJECT>_<PATH>_<FILE>_H_.

To guarantee uniqueness, they should be based on the full path in a project’s source tree. For example, the file foo/src/bar/baz.h in project foo should have the following guard:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_

...

#endif  // FOO_BAR_BAZ_H_

Naming Conventions

Naming for API classes, member functions, and parameters must adhere to the Google C++ coding standards

General Naming Rules

  • Optimize for readability using names that would be clear even to people on a different team.

  • Use names that describe the purpose or intent of the object. Do not worry about saving horizontal space as it is far more important to make your code immediately understandable by a new reader. Minimize the use of abbreviations that would likely be unknown to someone outside your project (especially acronyms and initialisms). Do not abbreviate by deleting letters within a word. As a rule of thumb, an abbreviation is probably OK if it’s listed in Wikipedia. Generally speaking, descriptiveness should be proportional to the name’s scope of visibility. For example, n may be a fine name within a 5-line function, but within the scope of a class, it’s likely too vague.

Class Naming

All class names should adhere to PascalCase convention.

  • Use upper case letters as word separators, lower case for the rest of a word

  • First character in a name is upper case

  • No underbars (‘_’)

Example: AnalogIn

Function Naming

Functions, including class member functions, use the same rule as for class names.

Accessors and mutators (get and set functions) may be named like variables. These often correspond to actual member variables, but this is not required.

Example:

class NameOneTwo
{
    public:
        int DoIt();
        void HandleError();
    private:
        int count()
        void set_count(int count)
}
AddTableEntry()
DeleteUrl()
OpenFileOrDie()

Variable Names

The names of variables (including function parameters) and data members are all lowercase, with underscores between words. Data members of classes (but not structs) additionally have trailing underscores. For instance: a_local_variable, a_struct_data_member, a_class_data_member_.

Class Data Members

Data members of classes, both static and non-static, are named like ordinary nonmember variables, but with a trailing underscore.

class TableInfo {
    ...
    private:
        std::string table_name_;
        static Pool<TableInfo>* pool_;
};

Struct Data Members

Data members of structs, both static and non-static, are named like ordinary nonmember variables. They do not have the trailing underscores that data members in classes have.

struct UrlTableProperties {
    std::string name;
    int num_entries;
    static Pool<UrlTableProperties>* pool;
};

Constant Names

Variables declared constexpr or const, and whose value is fixed for the duration of the program, are named with a leading “k” followed by mixed case. Underscores can be used as separators in the rare cases where capitalization cannot be used for separation. For example:

const int kDaysInAWeek = 7;
const int kAndroid8_0_0 = 24;  // Android 8.0.0

Type Names

When possible for types based on native types make a typedef. Typedef names should use the same naming policy as for a class with the word Type appended. Example:

typedef uint16  ModuleType;
typedef uint32  SystemType;

Enumerated Type Names

Enumerators (for both scoped and unscoped enums) should be named like constants, not like macros. That is, use kEnumName not ENUM_NAME.

Example:

enum class UrlTableError {
    kOk = 0,
    kOutOfMemory,
    kMalformedInput,
};

Comments

Comments are absolutely vital to keeping our code readable. The following rules describe what you should comment and where. But remember: while comments are very important, the best code is self-documenting. Giving sensible names to types and variables is much better than using obscure names that you must then explain through comments.

When writing your comments, write for your audience: the next contributor who will need to understand your code. Be generous — the next one may be you!

For automatically generating api documentation as well as for coverage checks, it is important that Doxygen Javadoc-style formatting is used when writing comments for functions and classes.

File Comments

Start each file with license and project boilerplate. A template is provided here.

File comments describe the contents of a file. If a file declares, implements, or tests exactly one abstraction that is documented by a comment at the point of declaration, file comments are not required. All other files must have file comments.

If a .h declares multiple abstractions, the file-level comment should broadly describe the contents of the file, and how the abstractions are related. A 1 or 2 sentence file-level comment may be sufficient. The detailed documentation about individual abstractions belongs with those abstractions, not at the file level.

Do not duplicate comments in both the .h and the .cpp. Duplicated comments diverge.

Class Comments

All classes should be commented with the following at a minimum:

  • Class description

  • Member function comments with description, paramaters, and return values

  • Public enums, typedefs, and structs comments with description

Example:

/**
*  A test class. A more elaborate class description.
*/

class Javadoc_Test
{
public:

    /**
    * An enum.
    * More detailed enum description.
    */

    enum TEnum {
        TVal1, /**< enum value TVal1. */
        TVal2, /**< enum value TVal2. */
        TVal3  /**< enum value TVal3. */
        }
    *enumPtr, /**< enum pointer. Details. */
    enumVar;  /**< enum variable. Details. */

    /**
    * A constructor.
    * A more elaborate description of the constructor.
    */
    Javadoc_Test();

    /**
    * A destructor.
    * A more elaborate description of the destructor.
    */
    ~Javadoc_Test();

    /**
    * a normal member taking two arguments and returning an integer value.
    * @param a an integer argument.
    * @param s a constant character pointer.
    * @see Javadoc_Test()
    * @see ~Javadoc_Test()
    * @see testMeToo()
    * @see publicVar()
    * @return The test results
    */
    int testMe(int a,const char *s);

    /**
    * A pure virtual member.
    * @see testMe()
    * @param c1 the first argument.
    * @param c2 the second argument.
    */
    virtual void testMeToo(char c1,char c2) = 0;

    /**
    * a public variable.
    * Details.
    */
    int publicVar;

    /**
    * a function variable.
    * Details.
    */
    int (*handler)(int a,int b);
};

Doxygen Comment Structure

/**
* A brief history of JavaDoc-style (C-style) comments.
*
* This is the typical JavaDoc-style C-style comment. It starts with two
* asterisks.
*
* @param theory Even if there is only one possible unified theory. it is just a
*               set of rules and equations.
*/
void cstyle( int theory );

/*******************************************************************************
* A brief history of JavaDoc-style (C-style) banner comments.
*
* This is the typical JavaDoc-style C-style "banner" comment. It starts with
* a forward slash followed by some number, n, of asterisks, where n > 2. It's
* written this way to be more "visible" to developers who are reading the
* source code.
*
* Often, developers are unaware that this is not (by default) a valid Doxygen
* comment block!
*
* However, as long as JAVADOC_BLOCK = YES is added to the Doxyfile, it will
* work as expected.
*
* This style of commenting behaves well with clang-format.
*
* @param theory Even if there is only one possible unified theory. it is just a
*               set of rules and equations.
******************************************************************************/
void javadocBanner( int theory );

JavaDoc Tags

Example:

/*******************************************************************************
* Validates a chess move.
*
* @param fromFile file from which a piece is being moved
* @param fromRank rank from which a piece is being moved
* @param toFile   file to which a piece is being moved
* @param toRank   rank to which a piece is being moved
* @return            true if the move is valid, otherwise false
*******************************************************************************/
boolean isValidMove(int fromFile, int fromRank, int toFile, int toRank) {
    // ...body
}

/**
* Moves a chess piece.
*/
void doMove(int fromFile, int fromRank, int toFile, int toRank)  {
    // ...body
}

Online Documentation

After making sure that your code is thouroughly documented according to the above specifications, you will need to add the corresponding API reference page for the online documentation. The source files for the API documentation, written in the reStructured Text format, are located in the docs/api_reference directory.

Make sure to name the documentation page the same as the source file or class you are documenting, and add a link to the new documentation page in the toc-tree of the doc/api_reference/index.rst file.

At a minimum, you need to add the code documentation and example usage to your documentation page.

Code Documentation

This is the part where all of your hard work adding comments to your code pays off! By adding various Breathe Directives, you can automatically pull in a complete reference of your code for the end user to use.

Below is an example for pulling in documentation for an API Class:

.. doxygenclass:: <YOUR_CLASS_NAME>
    :project: mjackets-api
    :members:

Code Example

Make sure to add some code snippets demonstrating how to use the API in an application. Below is an example of adding an example for a delay API.

Example Usage:
**************

.. code-block:: cpp

    while(1)
    {
        led1.Toggle();
        DelayMs(100)    // wait 100 milliseconds
        led2.Toggle();
        DelayUs(1000);  // wait for 1000 microseconds
    }

Including HAL Components

You can directly use HAL components from your API source code without needing include statements. The mJackets.hpp file automatically includes the appropriate HAL drivers into the project, which are selected by the target device definition in the build system as well as the HAL configuration file.

You will also need to make sure that the appropriate HAL driver libraries are linked with the API to ensure that the driver source files, linker and compiler flags, and header files are imported into the project. This can all be accomplished by adding a single line to the CMakeLists.txt file in the top level mjacket-api directory. For each driver library you want to link, add a line with the format HAL::STM32::${FAMILY}::<your_driver_name> in the target_link_libraries function. The ${FAMILY} variable will automatically be populated by the build system, and ensure the appropriate driver is included for the target device. An example is shown below for implementation of a few HAL drivers including the ADC, DAC, and GPIO drivers.

target_link_libraries(API
    HAL::STM32::${FAMILY}
    HAL::STM32::${FAMILY}::ADC
    HAL::STM32::${FAMILY}::CORTEX
    HAL::STM32::${FAMILY}::DAC
    HAL::STM32::${FAMILY}::GPIO
    HAL::STM32::${FAMILY}::RCC
    CMSIS::STM32::${DEVICE}
    STM32::NoSys
)

The below libraries are required as a bare minimum:

  • HAL::STM32::${FAMILY}

  • HAL::STM32::${FAMILY}::CORTEX

  • HAL::STM32::${FAMILY}::RCC

  • CMSIS::STM32::${DEVICE}

  • STM32::NoSys

Peripheral Drivers

As mentioned in the Organization section above, peripheral drivers should be stored in an appropriate category with the folder structure being drivers/<category>/<driver_name>/. Under that directory you will need three files at a minimum.

  • C++ source code file

  • C++ header file

  • CMakeList.txt build system file

The build system will generate a library for each driver. A driver can be included in an API function or application code by including the driver header file and linking with the library in the appropriate API or application CMakeLists.txt file. For example, if you want to use a Lidar Lite V3 driver in your application code, your application CMakeList.txt file would need to include the following:

find_package(DRIVERS COMPONENTS SENSORS REQUIRED)

...

target_link_libraries(myProject
    DRIVERS::SENSORS::LIDAR_LITE_V3
)

The CMakeLists.txt file in the driver directory will need to have the following components to ensure compatability with the build system:

add_library(DRIVERS::<CATEGORY>::<DRIVER_NAME> INTERFACE IMPORTED
    <LIST_SOURCE_FILES_HERE>
)

target_include_directories(DRIVERS::<CATEGORY>::<DRIVER_NAME> INTERFACE
    .
)

Additionally, if any HAL drivers or API functions are used, they will need to be linked with the peripheral driver using:

target_link_libraries(DRIVERS::<CATEGORY>::<DRIVER_NAME> INTERFACE
    <Libraries_To_Link>
)