VSM C++ SDK
Vehicle Specific Modules SDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
"Hello world" VSM application

Here is an example of a simple VSM application which demonstrates basic usage of the SDK. The source code of this example consists of two files:

C++ code

Single header file should be included to get an access to the whole SDK functionality:

#include <ugcs/vsm/vsm.h>

All the SDK functionality resides under ugcs::vsm namespace.

Custom vehicle class should be defined by deriving from a ugcs::vsm::Device class:

class Custom_vehicle:public ugcs::vsm::Device
{
/* To add shared pointer capability to this class. */

To pass the instance of custom class to SDK, shared pointer of the form std::shared_ptr<Custom_vehicle> should be used. Macro DEFINE_COMMON_CLASS defines methods and member types needed for shared pointers usage:

  • Class::Ptr member type which effectively is std::shared_ptr<Class>.

  • Class::Weak_ptr member type which effectively is std::weak_ptr<Class>.

  • static Class::Create() template method for convenient creation of shared classes dynamically:

    /* Create vehicle instance with hard-coded serial number. */
    Custom_vehicle::Ptr vehicle = Custom_vehicle::Create("asd123456");
  • Class::Shared_from_this() method which returns appropriate Class::Ptr shared pointer:

    ugcs::vsm::Make_callback(&Custom_vehicle::Send_telemetry, Shared_from_this()),

UgCS differentiates vehicles based on serial number and name. We use required parameters for that in our vehicle constructor:

Custom_vehicle(const std::string& serial_number):
Device(ugcs::vsm::proto::DEVICE_TYPE_VEHICLE)
{

Currently, vehicle and autopilot types are defined by using types from ugcs::vsm::mavlink namespace, but it will be changed in next releases. It is fully up to VSM application developer to decide about custom vehicle constructor arguments, but usually at least vehicle serial number is configurable while vehicle/autopilot types, model name and capabilities are fixed.

Next step is to decide whether override of ugcs::vsm::Device::On_enable method is needed or not. As a general rule, override is needed if some class-level initialization actions require this shared pointer usage, for example, telemetry simulation timer creation and setting of system status:

virtual void
On_enable() override
{
/* Telemetry with 1 second granularity. */
std::chrono::seconds(1),
/* Send_telemetry method will be called on timer. Smart pointer
* is used to reference vehicle instance. Plain 'this' could also
* be used here, but it is generally not recommended. */
ugcs::vsm::Make_callback(&Custom_vehicle::Send_telemetry, Shared_from_this()),
/* Execution will be done in default vehicle context,
* which is served by dedicated vehicle thread together with
* vehicle requests so additional synchronization is not necessary.
*/
Get_completion_ctx());
/* Report current control mode to the UgCS */
t_control_mode->Set_value(ugcs::vsm::proto::CONTROL_MODE_MANUAL);
/* Report link state to the UgCS */
t_downlink_present->Set_value(true);
t_uplink_present->Set_value(true);
/* Tell UgCS about available commands.
* Command availability can be changed during runtime via this call and
* VSM can determine which command buttons to show in the client UI. */
c_arm->Set_available();
c_disarm->Set_available();
c_mission_upload->Set_available();
/* Mission upload is always enabled.
* Command state (enabled/disabled) can be changed during runtime via this call and
* VSM can determine which command buttons to show as enabled in the client UI. */
c_mission_upload->Set_enabled();
Commit_to_ucs();
}

As a rule, ugcs::vsm::Device::On_disable method override is also necessary if ugcs::vsm::Device::On_enable is overridden. In the example, the counterpart action is performed in On_disable method, namely timer cancellation to avoid memory leaks:

virtual void
On_disable() override
{
/* Turn off the timer. */
timer->Cancel();
}

Timer created in the On_enable method is used to simulate the telemetry generation by the vehicle and to make the "Hello world" example to be more interesting and 'alive'.

All custom vehicles should override ugcs::vsm::Vehicle::Handle_ucs_request processing methods (see Vehicle commands) to provide vehicle specific implementation of processing logic.

In a real application, all saved missions should be erased from the vehicle. In this example vehicle requests are processed synchronously, i.e. in the scope of the ugcs::vsm::Vehicle::Handle_ucs_request method itself. This is not mandatory. VSM application developer may save a copy of the request handle for deferred processing and return from the overridden method without completing the request. This way, multiple vehicle related activities can be processed in parallel, for example sending telemetry to UgCS and in the same time processing the VSM request as a complex sequence of actions executed with, probably, notable delays.

ugcs::vsm::Ucs_request contains a parsed protobuf message and user is able to access all its fields directly. Later there will be a wrapper to hide the housekeeping code (native protobuf calls and exceptions).

Here some interesting behavior is simulated. Namely, vehicle yaw spinning and altitude climbing is started/stopped based on command.

The most complex and important vehicle command is mission_upload which denotes a mission to be loaded into the vehicle for latter execution. The payload of the request contains a list of sub-commands which are previously registered in Fill_register_msg() call.

void
Handle_ucs_command(ugcs::vsm::Ucs_request::Ptr ucs_request)
{
for (int c = 0; c < ucs_request->request.device_commands_size(); c++) {
auto &vsm_cmd = ucs_request->request.device_commands(c);
auto cmd = Get_command(vsm_cmd.command_id());
LOG("COMMAND %s (%d) received",
cmd->Get_name().c_str(),
vsm_cmd.command_id());
try {
if (cmd == c_arm) {
LOG_DEBUG("Vehicle armed!");
/* Start yaw spinning and climbing. */
yaw_speed = 0.1;
climb_speed = 0.5;
/* Simulate armed vehicle */
is_armed = true;
} else if (cmd == c_disarm) {
LOG_DEBUG("Vehicle disarmed.");
/* Stop yaw spinning and climbing. */
yaw_speed = 0;
climb_speed = 0;
/* Simulate disarmed vehicle */
is_armed = false;
} else if (cmd == c_disarm) {
LOG_DEBUG("Vehicle disarmed.");
/* Stop yaw spinning and climbing. */
yaw_speed = 0;
climb_speed = 0;
/* Simulate disarmed vehicle */
is_armed = false;
} else if (cmd == c_mission_upload) {
/* Iterate over all mission commands */
for (int item_count = 0; item_count < vsm_cmd.sub_commands_size(); item_count++) {
auto vsm_scmd = vsm_cmd.sub_commands(item_count);
auto cmd = Get_command(vsm_scmd.command_id());
if (cmd == c_move) {
/* Only move is supported by this vehicle */
LOG("MISSION item %d %s (%d)",
item_count, cmd->Get_name().c_str(),
vsm_scmd.command_id());
params = cmd->Build_parameter_list(vsm_scmd);
float alt;
params.Get_value("altitude", alt);
LOG_DEBUG("Move to altitude of %.2f meters.", alt);
}
}
} else {
VSM_EXCEPTION(ugcs::vsm::Action::Format_exception, "Unsupported command");
}
ucs_request->Complete();
} catch (const std::exception& ex) {
ucs_request->Complete(ugcs::vsm::proto::STATUS_INVALID_PARAM, std::string(ex.what()));
}
}
}

Telemetry simulation is done in a single method which is regularly called by timer:

bool
Send_telemetry()
{
/* Report current heading. */
t_heading->Set_value(yaw);
/* Simulate some spinning between [-Pi;+Pi]. */
if ((yaw += yaw_speed) >= M_PI) {
yaw = -M_PI;
}
/* Simulate climbing high to the sky. */
altitude += climb_speed;
t_altitude_raw->Set_value(altitude);
/* Report also some battery value. */
t_main_voltage->Set_value(13.5);
/* Enable ARM command if vehicle is disarmed. */
c_arm->Set_enabled(!is_armed);
/* Enable DISARM command if vehicle is armed. */
c_disarm->Set_enabled(is_armed);
/* Report armed state to the UgCS */
t_is_armed->Set_value(is_armed);
/* Send the updated telemetry fields and command states to UgCS
* SDK will send only modified values thus saving bandwidth */
Commit_to_ucs();
LOG("send tm");
/* Return true to reschedule the same timer again. */
return true;
}

Telemetry is sent to UgCS by populating the previously registered telemetry fileds with values and then issuing Commit_to_ucs() call. , then filling it with telemetry parameters.

In the example yaw, absolute altitude and battery voltage parameters are sent every second. yaw_speed and climb_speed control the current delta of related parameters to simulate some activity on a vehicle. This activity will be visible in telemetry window when mission is launched.

Now, custom vehicle class is ready to be instantiated and used, but VSM should be initialized before any other VSM services can be used. It is usually done somewhere in the beginning of main function:

/* First of all, initialize SDK infrastructure and services. */

In a real world VSM application there should be some vehicle specific mechanism which monitors the presence of a vehicle(-s) connection(-s), does initial identifications of a connected vehicle, like serial number and model name reading and then instantiation of a custom vehicle class instance. In the example, however, it is assumed that vehicle is always connected, so instantiation is done immediately after VSM initialization:

/* Create vehicle instance with hard-coded serial number. */
Custom_vehicle::Ptr vehicle = Custom_vehicle::Create("asd123456");
/* Should be always called right after vehicle instance creation. */
vehicle->Enable();
/* Should be always called after Enable once all the necessary detection is done
* and parameters set.
*/
vehicle->Register();

ugcs::vsm::Device::Enable method should be always called to complete the vehicle instance initialization. As a rule, it should be called right after instance creation with Class::Create method.

After vehicle is enabled, VSM application should control the connection with a vehicle and disable it when connection is totally lost. This logic is VSM specific, but as a general rule, short-term telemetry breaks shouldn't be considered as connection lost situation. In the example, there is just an infinite wait, assuming that vehicle is always connected and stays alive

/* Now vehicle is visible to UgCS. Requests for the vehicle will be executed
* in a dedicated vehicle thread. In a real VSM, there should be some
* supervision logic which monitors the connection with a vehicle and
* disables it when connection is totally lost. Here it is assumed that
* vehicle is always connected, so just wait indefinitely.
*/
std::condition_variable cond;
std::mutex mutex;
std::unique_lock<std::mutex> lock(mutex);
cond.wait(lock); /* Infinite wait. */

To gracefully shutdown VSM application, all vehicles should be disabled and VSM terminated:

/* Disable the vehicle, considered to be deleted after that. */
vehicle->Disable();
/* Gracefully terminate SDK infrastructure and services. */

CMake makefile

Hello_world_VSM/CMakeLists.txt is a minimal makefile for VSM application which provides static linkage with SDK library defined by VSM_SDK_DIR variable (either CMake or environment) and a default VSM configuration file via Build_vsm_config() function, which can be given custom source and destination config file locations. It can be considered as a template makefile for VSM applications. Makefile should be self-explanatory. For additional details refer to CMake official site.

Compilation

Prior to compiling the VSM application, SDK should be compiled and installed according to Building VSM C++ SDK. In the example, it is assumed that source and make file reside in the same directory and VSM_SDK_DIR environment variable points to the SDK installation folder.

Building

Now VSM application can be compiled and built in the same way as SDK using a single command with a build folder argument, for example:

cmake --build build-debug-win -- -j4