1. 程式人生 > 其它 >UAVCAN教程(4)釋出訂閱實現

UAVCAN教程(4)釋出訂閱實現

技術標籤:UAVCAN

講一下libuavcan如何通過釋出訂閱模式實現程序通訊。

釋出者對應uavcan::Publisher類,這是一個模板類,模板引數指定要釋出的資料型別,釋出者類構造時需要一個引數,就是我們前面介紹的節點Node。

釋出者廣播訊息,在這裡例子裡訊息型別是uavcan.protocol.debug.KeyValue。

釋出者程序:

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

/*
 * We're going to use messages of type uavcan.protocol.debug.KeyValue, so the appropriate header must be included.
 * Given a data type named X, the header file name would be:
 *      X.replace('.', '/') + ".hpp"
 */
#include <uavcan/protocol/debug/KeyValue.hpp> // uavcan.protocol.debug.KeyValue 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.publisher"); /* * Dependent objects (e.g. publishers, subscribers, servers, callers, timers, ...) can be initialized only * if the node is running. Note that all dependent objects always keep a reference to the node object. */ 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)); } /* * Create the publisher object to broadcast standard key-value messages of type uavcan.protocol.debug.KeyValue. * Keep in mind that most classes defined in the library are not copyable; attempt to copy objects of * such classes will result in compilation failure. * A publishing node won't see its own messages. */ uavcan::Publisher<uavcan::protocol::debug::KeyValue> kv_pub(node); const int kv_pub_init_res = kv_pub.init(); if (kv_pub_init_res < 0) { throw std::runtime_error("Failed to start the publisher; error: " + std::to_string(kv_pub_init_res)); } /* * This would fail because most of the objects - including publishers - are noncopyable. * The error message may look like this: * "error: ‘uavcan::Noncopyable::Noncopyable(const uavcan::Noncopyable&)’ is private" */ // auto pub_copy = kv_pub; // Don't try this at home. /* * TX timeout can be overridden if needed. * Default value should be OK for most use cases. */ kv_pub.setTxTimeout(uavcan::MonotonicDuration::fromMSec(1000)); /* * Priority of outgoing tranfers can be changed as follows. * Default priority is 16 (medium). */ kv_pub.setPriority(uavcan::TransferPriority::MiddleLower); /* * Running the node. */ node.setModeOperational(); while (true) { /* * Spinning for 1 second. * The method spin() may return earlier if an error occurs (e.g. driver failure). * All error codes are listed in the header uavcan/error.hpp. */ const int spin_res = node.spin(uavcan::MonotonicDuration::fromMSec(1000)); if (spin_res < 0) { std::cerr << "Transient failure: " << spin_res << std::endl; } /* * Publishing a random value using the publisher created above. * All message types have zero-initializing default constructors. * Relevant usage info for every data type is provided in its DSDL definition. */ uavcan::protocol::debug::KeyValue kv_msg; // Always zero initialized kv_msg.value = std::rand() / float(RAND_MAX); /* * Arrays in DSDL types are quite extensive in the sense that they can be static, * or dynamic (no heap needed - all memory is pre-allocated), or they can emulate std::string. * The last one is called string-like arrays. * ASCII strings can be directly assigned or appended to string-like arrays. * For more info, please read the documentation for the class uavcan::Array<>. */ kv_msg.key = "a"; // "a" kv_msg.key += "b"; // "ab" kv_msg.key += "c"; // "abc" /* * Publishing the message. */ const int pub_res = kv_pub.broadcast(kv_msg); if (pub_res < 0) { std::cerr << "KV publication failure: " << pub_res << std::endl; } } }

訂閱者程序:

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <uavcan/uavcan.hpp>
#include <uavcan/protocol/debug/KeyValue.hpp>
#include <uavcan/protocol/debug/LogMessage.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.subscriber");

    /*
     * Dependent objects (e.g. publishers, subscribers, servers, callers, timers, ...) can be initialized only
     * if the node is running. Note that all dependent objects always keep a reference to the node object.
     */
    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));
    }

    /*
     * Subscribing to standard log messages of type uavcan.protocol.debug.LogMessage.
     *
     * Received messages will be passed to the application via a callback, the type of which can be set via the second
     * template argument.
     * 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<>).
     *
     * N.B.: Some libuavcan users report that C++ lambda functions when used with GCC may actually break the code
     *       on some embedded targets, particularly ARM Cortex M0. These reports still remain unconfirmed though;
     *       please refer to the UAVCAN mailing list to learn more.
     *
     * The type of the argument of the callback can be either of these two:
     *  - T&
     *  - uavcan::ReceivedDataStructure<T>&
     * For the first option, ReceivedDataStructure<T>& will be cast into a T& implicitly.
     *
     * The class uavcan::ReceivedDataStructure extends the received data structure with extra information obtained from
     * the transport layer, such as Source Node ID, timestamps, Transfer ID, index of the redundant interface this
     * transfer was picked up from, etc.
     */
    uavcan::Subscriber<uavcan::protocol::debug::LogMessage> log_sub(node);

    const int log_sub_start_res = log_sub.start(
        [&](const uavcan::ReceivedDataStructure<uavcan::protocol::debug::LogMessage>& msg)
        {
            /*
             * The message will be streamed in YAML format.
             */
            std::cout << msg << std::endl;
            /*
             * If the standard iostreams are not available (they rarely available in embedded environments),
             * use the helper class uavcan::OStream defined in the header file <uavcan/helpers/ostream.hpp>.
             */
            // uavcan::OStream::instance() << msg << uavcan::OStream::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.
     */

    if (log_sub_start_res < 0)
    {
        throw std::runtime_error("Failed to start the log subscriber; error: " + std::to_string(log_sub_start_res));
    }

    /*
     * Subscribing to messages of type uavcan.protocol.debug.KeyValue.
     * This time we don't want to receive extra information about the received message, so the callback's argument type
     * would be just T& instead of uavcan::ReceivedDataStructure<T>&.
     * The callback will print the message in YAML format via std::cout (also refer to uavcan::OStream).
     */
    uavcan::Subscriber<uavcan::protocol::debug::KeyValue> kv_sub(node);

    const int kv_sub_start_res =
        kv_sub.start([&](const uavcan::protocol::debug::KeyValue& msg) { std::cout << msg << std::endl; });

    if (kv_sub_start_res < 0)
    {
        throw std::runtime_error("Failed to start the key/value subscriber; error: " + std::to_string(kv_sub_start_res));
    }

    /*
     * Running the node.
     */
    node.setModeOperational();

    while (true)
    {
        /*
         * The method spin() may return earlier if an error occurs (e.g. driver failure).
         * All error codes are listed in the header uavcan/error.hpp.
         */
        const int res = node.spin(uavcan::MonotonicDuration::getInfinite());
        if (res < 0)
        {
            std::cerr << "Transient failure: " << res << std::endl;
        }
    }
}

訂閱者訂閱特定型別的訊息,收到訊息之後回撥函式被自動呼叫。

上面的例子使用C++11的lamba表示式方式來註冊回撥函式,如果不支援C++11,如C++03,則可以用uavcan::MethodBinder<>,這個模板類,它接受兩個模板引數,將物件和物件的成員函式實現繫結,生成一個可用於註冊的回撥函式,其作用類似於C++11的std::bind。

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <uavcan/uavcan.hpp>
#include <uavcan/protocol/debug/KeyValue.hpp>
#include <uavcan/protocol/debug/LogMessage.hpp>

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

/**
 * This class demonstrates how to use uavcan::MethodBinder with subscriber objects 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_;

    /*
     * Instantiations of uavcan::MethodBinder<>
     */
    typedef uavcan::MethodBinder<Node*, void (Node::*)(const uavcan::protocol::debug::LogMessage&) const>
        LogMessageCallbackBinder;

    typedef uavcan::MethodBinder<Node*,
        void (Node::*)(const uavcan::ReceivedDataStructure<uavcan::protocol::debug::KeyValue>&) const>
            KeyValueCallbackBinder;

    uavcan::Subscriber<uavcan::protocol::debug::LogMessage, LogMessageCallbackBinder> log_sub_;
    uavcan::Subscriber<uavcan::protocol::debug::KeyValue, KeyValueCallbackBinder> kv_sub_;

    void logMessageCallback(const uavcan::protocol::debug::LogMessage& msg) const
    {
        std::cout << "Log message:\n" << msg << std::endl;
    }

    void keyValueCallback(const uavcan::ReceivedDataStructure<uavcan::protocol::debug::KeyValue>& msg) const
    {
        std::cout << "KV message:\n" << msg << std::endl;
    }

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

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

        const int log_sub_start_res = log_sub_.start(LogMessageCallbackBinder(this, &Node::logMessageCallback));
        if (log_sub_start_res < 0)
        {
            throw std::runtime_error("Failed to start the log subscriber; error: " + std::to_string(log_sub_start_res));
        }

        const int kv_sub_start_res = kv_sub_.start(KeyValueCallbackBinder(this, &Node::keyValueCallback));
        if (kv_sub_start_res < 0)
        {
            throw std::runtime_error("Failed to start the KV subscriber; error: " + std::to_string(kv_sub_start_res));
        }

        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;
    }

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

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

    node.run();
}

cmake檔案:

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")

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

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

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