• Specification
    • 1. Introduction
    • 2. Basic concepts
    • 3. Data structure description language
    • 4.1 CAN bus transport layer
    • 4.2 UDP bus transport layer
    • 4.3 MAVLink bus transport layer
    • 4.4 CANFD bus transport layer
    • 5. Application level conventions
    • 6. Application level functions
    • 7. List of standard data types
    • 8. Hardware design recommendations
    • dsdl
      • ardupilot
      • uavcan
  • 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
    • Pydronecan
      • Examples
        • Automated ESC enumeration
        • Dump All Messages
        • ESC throttle control
      • Tutorials
        • 1. Setup
        • 2. Basic usage
        • 3. Advanced usage
        • 4. Parsing DSDL definitions
    • AP Periph
    • Libcanard
    • dronecan dsdlc
  • GUI Tool
    • Overview
    • Examples
    • User guide
  • Examples
    • Simple sensor node
  • Discussions
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)


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

Contents

  • Services
    • Server
    • Client
      • Compatibility with older C++ standards
    • Running on Linux

License

This work is licensed under a Creative Commons Attribution 4.0 International License.
Much of the content of this site is based upon prior work by Pavel Kirienko and the UAVCAN Development Team.
  • https://dronecan.org/discord
  • https://github.com/DroneCAN
  • Report a problem with this website

Generated Sun, 02 Jun 2024 21:47:21 +0000 © DroneCAN development team

  • 1. Introduction
  • 2. Basic concepts
  • 3. Data structure description language
  • 4.1 CAN bus transport layer
  • 4.2 UDP bus transport layer
  • 4.3 MAVLink bus transport layer
  • 4.4 CANFD bus transport layer
  • 5. Application level conventions
  • 6. Application level functions
  • 7. List of standard data types
  • 8. Hardware design recommendations
  • dsdl
    • ardupilot
    • uavcan
  • 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
  • Pydronecan
    • Examples
      • Automated ESC enumeration
      • Dump All Messages
      • ESC throttle control
    • Tutorials
      • 1. Setup
      • 2. Basic usage
      • 3. Advanced usage
      • 4. Parsing DSDL definitions
  • AP Periph
  • Libcanard
  • dronecan dsdlc
  • Overview
  • Examples
  • User guide
  • Simple sensor node
Specification1. Introduction2. Basic concepts3. Data structure description language4.1 CAN bus transport layer4.2 UDP bus transport layer4.3 MAVLink bus transport layer4.4 CANFD bus transport layer5. Application level conventions6. Application level functions7. List of standard data types8. Hardware design recommendationsdsdlardupilotuavcanImplementationsLibuavcanPlatformsTutorials1. Library build configuration2. Node initialization and startup3. Publishers and subscribers4. Services5. Timers6. Time synchronization7. Remote node reconfiguration8. Custom data types9. Node discovery10. Dynamic node ID allocation11. Firmware update12. Multithreading13. CAN acceptance filtersFAQPydronecanExamplesAutomated ESC enumerationDump All MessagesESC throttle controlTutorials1. Setup2. Basic usage3. Advanced usage4. Parsing DSDL definitionsAP PeriphLibcanarddronecan dsdlcGUI ToolOverviewExamplesUser guideExamplesSimple sensor nodeDiscussions