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)