Timers
This application demonstrates how to use software timers.
The code
C++11
This is the recommended example. It requires C++11 or a newer version of the C++ standard.
#include <iostream>
#include <unistd.h>
#include <uavcan/uavcan.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.timers_cpp11");
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));
}
/*
* Creating timers.
* Timers are objects that instruct the libuavcan core to pass control to their callbacks either periodically
* at specified interval, or once at some specific time point in the future.
* Note that timer objects are noncopyable.
*
* A timer callback accepts a reference to an object of type uavcan::TimerEvent, which contains two fields:
* - The time when the callback was expected to be invoked.
* - The actual time when the callback was invoked.
*
* Timers do not require initialization and never fail (because of the very simple logic).
*
* Note that periodic timers do not accumulate phase error over time.
*/
uavcan::Timer periodic_timer(node);
uavcan::Timer one_shot_timer(node);
periodic_timer.setCallback([&](const uavcan::TimerEvent& event)
{
node.logInfo("Periodic Timer", "scheduled_time: %*, real_time: %*",
event.scheduled_time.toMSec(), event.real_time.toMSec());
// Timers can be checked whether there are pending events
if (one_shot_timer.isRunning())
{
node.logError("Periodic Timer", "Someone started the one-shot timer! Period: %*",
one_shot_timer.getPeriod().toMSec());
one_shot_timer.stop(); // And stopped like that
}
/*
* Restart the second timer for one shot. It will be stopped automatically after the first event.
* There are two ways to generate a one-shot timer event:
* - at a specified time point (absolute) - use the method startOneShotWithDeadline();
* - after a specified timeout (relative) - use the method startOneShotWithDelay().
* Here we restart it in absolute mode.
*/
auto one_shot_deadline = event.scheduled_time + uavcan::MonotonicDuration::fromMSec(200);
one_shot_timer.startOneShotWithDeadline(one_shot_deadline);
});
one_shot_timer.setCallback([&](const uavcan::TimerEvent& event)
{
node.logInfo("One-Shot Timer", "scheduled_time: %*, real_time: %*",
event.scheduled_time.toMSec(), event.real_time.toMSec());
});
/*
* Starting the timer at 1 Hz.
* Start cannot fail.
*/
periodic_timer.startPeriodic(uavcan::MonotonicDuration::fromMSec(1000));
/*
* Node loop.
*/
node.getLogger().setLevel(uavcan::protocol::debug::LogLevel::DEBUG);
node.setModeOperational();
while (true)
{
const int res = node.spin(uavcan::MonotonicDuration::fromMSec(1000));
if (res < 0)
{
std::cerr << "Transient failure: " << res << std::endl;
}
}
}
C++03
This example shows how to work-around the lack of lambdas and std::function<>
in C++03.
#include <iostream>
#include <sstream>
#include <unistd.h>
#include <uavcan/uavcan.hpp>
#include <uavcan/util/method_binder.hpp> // Sic!
extern uavcan::ICanDriver& getCanDriver();
extern uavcan::ISystemClock& getSystemClock();
/**
* This class demonstrates how to use uavcan::MethodBinder and uavcan::TimerEventForwarder 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 NodeWithTimers
{
static const unsigned NodeMemoryPoolSize = 16384;
uavcan::Node<NodeMemoryPoolSize> node_;
// Instantiation of uavcan::MethodBinder
typedef uavcan::MethodBinder<NodeWithTimers*, void (NodeWithTimers::*)(const uavcan::TimerEvent&)>
TimerCallbackBinder;
// Now we can use the newly instantiated timer type
uavcan::TimerEventForwarder<TimerCallbackBinder> periodic_timer_;
uavcan::TimerEventForwarder<TimerCallbackBinder> one_shot_timer_;
void periodicCallback(const uavcan::TimerEvent& event)
{
std::cout << "Periodic: scheduled_time: " << event.scheduled_time
<< ", real_time: " << event.real_time << std::endl;
uavcan::MonotonicTime one_shot_deadline = event.scheduled_time + uavcan::MonotonicDuration::fromMSec(200);
one_shot_timer_.startOneShotWithDeadline(one_shot_deadline);
}
void oneShotCallback(const uavcan::TimerEvent& event)
{
std::cout << "One-shot: scheduled_time: " << event.scheduled_time
<< ", real_time: " << event.real_time << std::endl;
}
public:
NodeWithTimers() :
node_(getCanDriver(), getSystemClock()),
periodic_timer_(node_, TimerCallbackBinder(this, &NodeWithTimers::periodicCallback)),
one_shot_timer_(node_, TimerCallbackBinder(this, &NodeWithTimers::oneShotCallback))
{
node_.getLogger().setLevel(uavcan::protocol::debug::LogLevel::DEBUG);
}
int start(uavcan::NodeID self_node_id, const std::string& node_name)
{
node_.setNodeID(self_node_id);
node_.setName(node_name.c_str());
return node_.start();
}
void runForever()
{
periodic_timer_.startPeriodic(uavcan::MonotonicDuration::fromMSec(1000)); // Start cannot fail
node_.setModeOperational();
while (true)
{
const int res = node_.spin(uavcan::MonotonicDuration::getInfinite());
if (res < 0)
{
std::cerr << "Transient failure: " << res << std::endl;
}
}
}
};
int main(int argc, const char** argv)
{
if (argc < 2)
{
std::cerr << "Usage: " << argv[0] << " <node-id>" << std::endl;
return 1;
}
NodeWithTimers node;
const int node_start_res = node.start(std::atoi(argv[1]), "org.uavcan.tutorial.timers_cpp03");
if (node_start_res < 0)
{
std::ostringstream os;
os << "Failed to start the node; error: " << node_start_res;
throw std::runtime_error(os.str());
}
node.runForever();
}
Running on Linux
Both versions of the application can be built with 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(node_cpp11 node_cpp11.cpp ${CMAKE_SOURCE_DIR}/../2._Node_initialization_and_startup/platform_linux.cpp)
target_link_libraries(node_cpp11 ${UAVCAN_LIB} rt)
add_executable(node_cpp03 node_cpp03.cpp ${CMAKE_SOURCE_DIR}/../2._Node_initialization_and_startup/platform_linux.cpp)
target_link_libraries(node_cpp03 ${UAVCAN_LIB} rt)