• Specification
    • 1. Introduction
    • 2. Basic concepts
    • 3. Data structure description language
    • 4. CAN bus transport layer
    • 5. Application level conventions
    • 6. Application level functions
    • 7. List of standard data types
    • 8. Hardware design recommendations
  • Implementations
    • Libuavcan
      • Platforms
      • Tutorials
        • 1. Library build configuration
        • 2. Node initialization and startup
        • 3. Publishers and subscribers
        • 4. Services
        • 5. Timers
        • 6. Time synchronization
        • 7. Remote node reconfiguration
        • 8. Custom data types
        • 9. Node discovery
        • 10. Dynamic node ID allocation
        • 11. Firmware update
        • 12. Multithreading
        • 13. CAN acceptance filters
      • FAQ
    • Pyuavcan
      • Examples
        • Automated ESC enumeration
        • ESC throttle control
      • Tutorials
        • 1. Setup
        • 2. Basic usage
        • 3. Advanced usage
        • 4. Parsing DSDL definitions
    • Libcanard
    • Uavcan.rs
  • GUI Tool
    • Overview
    • Examples
    • User guide
  • Examples
    • Simple sensor node
  • Forum
Implementations /  Libuavcan /  Tutorials /  4. Services

Services

This tutorial presents two applications:

  • Server - provides a service of type uavcan.protocol.file.BeginFirmwareUpdate.
  • Client - calls the same service type on a specified node. Server’s node ID is provided to the application as a command-line argument.

Server

#include <cstdio>
#include <iostream>
#include <unistd.h>
#include <uavcan/uavcan.hpp>

/*
 * This example uses the service type uavcan.protocol.file.BeginFirmwareUpdate.
 */
#include <uavcan/protocol/file/BeginFirmwareUpdate.hpp>


extern uavcan::ICanDriver& getCanDriver();
extern uavcan::ISystemClock& getSystemClock();

constexpr unsigned NodeMemoryPoolSize = 16384;
typedef uavcan::Node<NodeMemoryPoolSize> Node;

static Node& getNode()
{
    static Node node(getCanDriver(), getSystemClock());
    return node;
}

int main(int argc, const char** argv)
{
    if (argc < 2)
    {
        std::cerr << "Usage: " << argv[0] << " <node-id>" << std::endl;
        return 1;
    }

    const int self_node_id = std::stoi(argv[1]);

    auto& node = getNode();
    node.setNodeID(self_node_id);
    node.setName("org.uavcan.tutorial.server");

    const int node_start_res = node.start();
    if (node_start_res < 0)
    {
        throw std::runtime_error("Failed to start the node; error: " + std::to_string(node_start_res));
    }

    /*
     * Starting the server.
     * This server doesn't do anything useful; it just prints the received request and returns some meaningless
     * response.
     *
     * The service callback accepts two arguments:
     *  - a reference to a request structure (input)
     *  - a reference to a default-initialized response structure (output)
     * The type of the input can be either of these two:
     *  - T::Request&
     *  - uavcan::ReceivedDataStructure<T::Request>&
     * The type of the output is strictly T::Response&.
     * Note that for the service data structure, it is not possible to instantiate T itself, nor does it make any
     * sense.
     *
     * In C++11 mode, callback type defaults to std::function<>.
     * In C++03 mode, callback type defaults to a plain function pointer; use a binder object to call member
     * functions as callbacks (refer to uavcan::MethodBinder<>).
     */
    using uavcan::protocol::file::BeginFirmwareUpdate;
    uavcan::ServiceServer<BeginFirmwareUpdate> srv(node);

    const int srv_start_res = srv.start(
        [&](const uavcan::ReceivedDataStructure<BeginFirmwareUpdate::Request>& req, BeginFirmwareUpdate::Response& rsp)
        {
            std::cout << req << std::endl;
            rsp.error = rsp.ERROR_UNKNOWN;
            rsp.optional_error_message = "Our sun is dying";
        });
    /*
     * C++03 WARNING
     * The code above will not compile in C++03, because it uses a lambda function.
     * In order to compile the code in C++03, move the code from the lambda to a standalone static function.
     * Use uavcan::MethodBinder<> to invoke member functions.
     */

    if (srv_start_res < 0)
    {
        std::exit(1);                   // TODO proper error handling
    }

    /*
     * Node loop.
     */
    node.setModeOperational();
    while (true)
    {
        const int res = node.spin(uavcan::MonotonicDuration::fromMSec(1000));
        if (res < 0)
        {
            std::printf("Transient failure: %d\n", res);
        }
    }
}

Client

#include <iostream>
#include <unistd.h>
#include <uavcan/uavcan.hpp>

/*
 * This example uses the service type uavcan.protocol.file.BeginFirmwareUpdate.
 */
#include <uavcan/protocol/file/BeginFirmwareUpdate.hpp>


extern uavcan::ICanDriver& getCanDriver();
extern uavcan::ISystemClock& getSystemClock();

constexpr unsigned NodeMemoryPoolSize = 16384;
typedef uavcan::Node<NodeMemoryPoolSize> Node;

static Node& getNode()
{
    static Node node(getCanDriver(), getSystemClock());
    return node;
}

int main(int argc, const char** argv)
{
    if (argc < 3)
    {
        std::cerr << "Usage: " << argv[0] << " <node-id> <server-node-id>" << std::endl;
        return 1;
    }

    const uavcan::NodeID self_node_id = std::stoi(argv[1]);
    const uavcan::NodeID server_node_id = std::stoi(argv[2]);

    auto& node = getNode();
    node.setNodeID(self_node_id);
    node.setName("org.uavcan.tutorial.client");

    const int node_start_res = node.start();
    if (node_start_res < 0)
    {
        throw std::runtime_error("Failed to start the node; error: " + std::to_string(node_start_res));
    }

    /*
     * Initializing the client. Remember that client objects are noncopyable.
     * Note that calling client.init() is not necessary - the object can be initialized ad hoc during the first call.
     */
    using uavcan::protocol::file::BeginFirmwareUpdate;
    uavcan::ServiceClient<BeginFirmwareUpdate> client(node);

    const int client_init_res = client.init();
    if (client_init_res < 0)
    {
        throw std::runtime_error("Failed to init the client; error: " + std::to_string(client_init_res));
    }

    /*
     * Setting the callback.
     * This callback will be executed when the call is completed.
     * Note that the callback will ALWAYS be called even if the service call has timed out; this guarantee
     * allows to simplify error handling in the application.
     */
    client.setCallback([](const uavcan::ServiceCallResult<BeginFirmwareUpdate>& call_result)
        {
            if (call_result.isSuccessful())  // Whether the call was successful, i.e. whether the response was received
            {
                // The result can be directly streamed; the output will be formatted in human-readable YAML.
                std::cout << call_result << std::endl;
            }
            else
            {
                std::cerr << "Service call to node "
                          << static_cast<int>(call_result.getCallID().server_node_id.get())
                          << " has failed" << std::endl;
            }
        });
    /*
     * C++03 WARNING
     * The code above will not compile in C++03, because it uses a lambda function.
     * In order to compile the code in C++03, move the code from the lambda to a standalone static function.
     * Use uavcan::MethodBinder<> to invoke member functions.
     */

    /*
     * Service call timeout can be overridden if needed, though it's not recommended.
     */
    client.setRequestTimeout(uavcan::MonotonicDuration::fromMSec(200));

    /*
     * It is possible to adjust priority of the outgoing service request transfers.
     * According to the specification, the services are supposed to use the same priority for response transfers.
     * Default priority is medium, which is 16.
     */
    client.setPriority(uavcan::TransferPriority::OneHigherThanLowest);

    /*
     * Calling the remote service.
     * Generated service data types have two nested types:
     *   T::Request  - request data structure
     *   T::Response - response data structure
     * For the service data structure, it is not possible to instantiate T itself, nor does it make any sense.
     */
    BeginFirmwareUpdate::Request request;
    request.image_file_remote_path.path = "/foo/bar";

    /*
     * It is possible to perform multiple concurrent calls using the same client object.
     * The class ServiceClient provides the following methods that allow to control execution of each call:
     *
     *  int call(NodeID server_node_id, const RequestType& request)
     *      Initiate a new non-blocking call.
     *
     *  int call(NodeID server_node_id, const RequestType& request, ServiceCallID& out_call_id)
     *      Initiate a new non-blocking call and return its ServiceCallID descriptor by reference.
     *      The descriptor allows to query the progress of the call or cancel it later.
     *
     *  void cancelCall(ServiceCallID call_id)
     *      Cancel a specific call using its descriptor.
     *
     *  void cancelAllCalls()
     *      Cancel all calls.
     *
     *  bool hasPendingCallToServer(NodeID server_node_id) const
     *      Whether the client object has pending calls to the given server at the moment.
     *
     *  unsigned getNumPendingCalls() const
     *      Returns the total number of pending calls at the moment.
     *
     *  bool hasPendingCalls() const
     *      Whether the client object has any pending calls at the moment.
     */
    const int call_res = client.call(server_node_id, request);
    if (call_res < 0)
    {
        throw std::runtime_error("Unable to perform service call: " + std::to_string(call_res));
    }

    /*
     * Spin until the call is completed, then exit.
     */
    node.setModeOperational();
    while (client.hasPendingCalls())  // Whether the call has completed (doesn't matter successfully or not)
    {
        const int res = node.spin(uavcan::MonotonicDuration::fromMSec(10));
        if (res < 0)
        {
            std::cerr << "Transient failure: " << res << std::endl;
        }
    }

    return 0;
}

Compatibility with older C++ standards

The following code demonstrates how to work-around the lack of important features in older C++ standards (prior C++11).

#include <iostream>
#include <unistd.h>
#include <uavcan/uavcan.hpp>
#include <uavcan/protocol/file/BeginFirmwareUpdate.hpp>

extern uavcan::ICanDriver& getCanDriver();
extern uavcan::ISystemClock& getSystemClock();

using uavcan::protocol::file::BeginFirmwareUpdate;

/**
 * This class demonstrates how to use uavcan::MethodBinder with service clients in C++03.
 * In C++11 and newer standards it is recommended to use lambdas and std::function<> instead, as this approach
 * would be much easier to implement and to understand.
 */
class Node
{
    static const unsigned NodeMemoryPoolSize = 16384;

    uavcan::Node<NodeMemoryPoolSize> node_;

    /*
     * Instantiation of MethodBinder
     */
    typedef uavcan::MethodBinder<Node*, void (Node::*)(const uavcan::ServiceCallResult<BeginFirmwareUpdate>&) const>
        BeginFirmwareUpdateCallbackBinder;

    void beginFirmwareUpdateCallback(const uavcan::ServiceCallResult<BeginFirmwareUpdate>& res) const
    {
        if (res.isSuccessful())
        {
            std::cout << res << std::endl;
        }
        else
        {
            std::cerr << "Service call to node "
                      << static_cast<int>(res.getCallID().server_node_id.get())
                      << " has failed" << std::endl;
        }
    }

public:
    Node(uavcan::NodeID self_node_id, const std::string& self_node_name) :
        node_(getCanDriver(), getSystemClock())
    {
        node_.setNodeID(self_node_id);
        node_.setName(self_node_name.c_str());
    }

    void start()
    {
        const int start_res = node_.start();
        if (start_res < 0)
        {
            throw std::runtime_error("Failed to start the node: " + std::to_string(start_res));
        }
    }

    void execute(uavcan::NodeID server_node_id)
    {
        /*
         * Initializing the request structure
         */
        BeginFirmwareUpdate::Request request;
        request.image_file_remote_path.path = "/foo/bar";

        /*
         * Initializing the client object
         */
        uavcan::ServiceClient<BeginFirmwareUpdate, BeginFirmwareUpdateCallbackBinder> client(node_);

        client.setCallback(BeginFirmwareUpdateCallbackBinder(this, &Node::beginFirmwareUpdateCallback));

        /*
         * Calling
         */
        const int call_res = client.call(server_node_id, request);
        if (call_res < 0)
        {
            throw std::runtime_error("Unable to perform service call: " + std::to_string(call_res));
        }

        /*
         * Spinning the node until the call is completed
         */
        node_.setModeOperational();
        while (client.hasPendingCalls())  // Whether the call has completed (doesn't matter successfully or not)
        {
            const int res = node_.spin(uavcan::MonotonicDuration::fromMSec(10));
            if (res < 0)
            {
                std::cerr << "Transient failure: " << res << std::endl;
            }
        }
    }
};

int main(int argc, const char** argv)
{
    if (argc < 3)
    {
        std::cerr << "Usage: " << argv[0] << " <node-id> <server-node-id>" << std::endl;
        return 1;
    }

    const uavcan::NodeID self_node_id = std::stoi(argv[1]);
    const uavcan::NodeID server_node_id = std::stoi(argv[2]);

    Node node(self_node_id, "org.uavcan.tutorial.clientcpp03");

    node.start();

    node.execute(server_node_id);

    return 0;
}

Running on Linux

Build the applications using the following CMake script:

cmake_minimum_required(VERSION 2.8)

project(tutorial_project)

find_library(UAVCAN_LIB uavcan REQUIRED)

set(CMAKE_CXX_FLAGS "-Wall -Wextra -pedantic -std=c++11")

# Make sure to provide correct path to 'platform_linux.cpp'! See earlier tutorials for more info.
add_executable(server server.cpp ${CMAKE_SOURCE_DIR}/../2._Node_initialization_and_startup/platform_linux.cpp)
target_link_libraries(server ${UAVCAN_LIB} rt)

add_executable(client client.cpp ${CMAKE_SOURCE_DIR}/../2._Node_initialization_and_startup/platform_linux.cpp)
target_link_libraries(client ${UAVCAN_LIB} rt)

add_executable(client_cpp03 client_cpp03.cpp ${CMAKE_SOURCE_DIR}/../2._Node_initialization_and_startup/platform_linux.cpp)
target_link_libraries(client_cpp03 ${UAVCAN_LIB} rt)

Obsolete

This website is dedicated to the experimental version of the protocol known as UAVCAN v0 that is now obsolete. To learn more about the stable release UAVCAN v1.0, please visit the main website.

Libuavcan

  • Platforms
  • Tutorials
    • 1. Library build configuration
    • 2. Node initialization and startup
    • 3. Publishers and subscribers
    • 4. Services
    • 5. Timers
    • 6. Time synchronization
    • 7. Remote node reconfiguration
    • 8. Custom data types
    • 9. Node discovery
    • 10. Dynamic node ID allocation
    • 11. Firmware update
    • 12. Multithreading
    • 13. CAN acceptance filters
  • FAQ

License

This work is licensed under a Creative Commons Attribution 4.0 International License.
  • Discussion forum
  • GitHub organization
  • Report a problem with this website

Generated Thu, 17 Feb 2022 16:27:38 +0000 © UAVCAN development team