网站建设尾款收取,建设银行网站查询工资,百度手机app下载并安装,物联网软件开发简介
Libuavcan是一个用C编写的可移植的跨平台库#xff0c;对C标准库的依赖小。它可以由几乎任何符合标准的C编译器编译#xff0c;并且可以在几乎任何体系结构/OS上使用。
在 DroneCAN 中#xff0c;Libuavcan 有一个 DSDL 编译器#xff0c;将 DSDL 文件转换为 hpp 头…简介
Libuavcan是一个用C编写的可移植的跨平台库对C标准库的依赖小。它可以由几乎任何符合标准的C编译器编译并且可以在几乎任何体系结构/OS上使用。
在 DroneCAN 中Libuavcan 有一个 DSDL 编译器将 DSDL 文件转换为 hpp 头文件。编译器名为 libuavcan_dsdlc调用方式如下
libuavcan_dsdlc source_dir其中 source_dir 是包含要编译的数据类型定义的根命名空间目录例如 dsdl/uavcan。
在编译应用程序和/或库之前必须在构建过程中调用DSDL编译器来生成头文件。
PX4中DSDL编译流程
结合上述的编译命令再结合《UAVCAN_V1的实现库libcanard与数据格式DSDL》中 uavcan_v1 调用脚本生成头文件的过程。在 PX4 中可以看出 DroneCAN 在 PX4 中的编译方式
在 uavcan 模块的 CMakeLists 文件中先设置 DSDL 文件的路径以及输出头文件的路径之后调用 libuavcan_dsdlc 命令生成响应的文件 节点初始化和启动
为了将UAVCAN功能添加到应用程序中我们必须创建UAVCAN:node类的节点对象这是库 调用 API 函数的基础。该类需要访问 CAN 底层驱动程序和系统时钟这是特定于平台的组件因此它们通过以下接口与库隔离
uavcan::ICanDriver //CAN驱动
uavcan::ICanIface //CAN实例
uavcan::ISystemClock //系统时钟
具体在 PX4 中的体现在 uavcannode 进程中的 uavcannode 类在创建节点的时候都会获取 can 设备以及系统时钟 在 STM32 底层的 can 驱动中会调用 ICanIface 类来获取实例 也就是说创建一个 uavcan 节点必须包含上面的这三个类。
内存管理
Libuavcan 不使用堆。
动态内存
动态内存分配由恒定时间的无碎片块内存分配器管理。专用于块分配的内存池的大小通过节点类uavcan:node 的模板参数来定义。如果未提供模板参数则节点将期望应用程序向构造函数提供对自定义分配器的引用。
库使用动态内存完成的功能为
1为接收多帧传输分配临时缓冲器
2保持接收器状态
3保存传输ID映射
4优先发送队列。 内存池大小的计算
通常内存池的大小在 4KB对于简单节点和 512KB对于非常复杂的节点之间。
内存池的最小安全大小可以计算为以下值总和的两倍
对于每个传入的数据类型将其最大序列化长度乘以可能发布它的节点数并将结果相加。
将所有传出数据类型的最大串行化长度相加乘以节点可用的CAN接口数加1。
示例
CAN 实例数2传入数据类型 A 的发布节点数3传入数据类型 A 的序列化后最大字节数100 bytes传入数据类型 B 的发布节点数32传入数据类型 B 的序列化后最大字节数24 bytes传出数据类型 X 的序列化后最大字节数256 bytes传出数据类型 Z 的序列化后最大字节数10 bytes
最终需要分配的内存大小为
2 * ((3 * 100 32 * 24) (2 1) * (256 10)) 3732 bytes 线程
从线程的角度来看可以使用以下配置
单线程 ---完全在一个线程中运行。
多线程 ---在两个线程中运行
主线程适用于硬实时、低延迟通信
辅助线程适用于阻塞、I/O密集型或CPU密集型任务但不适用于实时任务。
单线程配置
在单线程配置中库的线程应该在 Node::spin() 内阻塞。或者周期性地调用 Node::spin() 或Node::spinOnce()。
典型示例
for (;;)
{/*线程调用时间 100ms*/const int error node.spin(uavcan::MonotonicDuration::fromMSec(100));if (error 0){std::cerr Transient error: error std::endl; // 处理发送错误}
}Node::spin() 和 Node::spinOnce() 之间的区别如下
spin() ---阻塞超时然后返回即使某些CAN帧或计时器事件仍在等待处理。
spinOnce() ---在处理完所有可用的CAN帧和计时器事件后它立即返回而不是阻塞。
应用示例
以下举出了常用功能的示例
1发布与订阅
2服务类消息
3时间同步
4固件升级
还有许多其余的例程可以进入官网查看相关的解释https://dronecan.github.io/Implementations/Libuavcan/Tutorials/10._Dynamic_node_ID_allocation/
发布与订阅
以下为一个示例发布与订阅调试主题的消息。
发布
#include iostream
#include cstdlib
#include unistd.h
#include uavcan/uavcan.hpp
//将使用uavcan.protocol.debug.KeyValue类型的消息来进行测试
#include uavcan/protocol/debug/KeyValue.hpp // uavcan.protocol.debug.KeyValue
extern uavcan::ICanDriver getCanDriver(); //获取CAN驱动
extern uavcan::ISystemClock getSystemClock(); //获取系统时钟
constexpr unsigned NodeMemoryPoolSize 16384;
typedef uavcan::NodeNodeMemoryPoolSize 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);//启动节点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));}//创建对应的消息类对象发布器uavcan::Publisheruavcan::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));}//设置发布时间间隔kv_pub.setTxTimeout(uavcan::MonotonicDuration::fromMSec(1000));//设置优先级kv_pub.setPriority(uavcan::TransferPriority::MiddleLower);//运行节点node.setModeOperational();while (true){//创建进程阻塞时间设置为1秒(1秒调用一次)const int spin_res node.spin(uavcan::MonotonicDuration::fromMSec(1000));if (spin_res 0){std::cerr Transient failure: spin_res std::endl;}//创建消息实例对象uavcan::protocol::debug::KeyValue kv_msg; // Always zero initializedkv_msg.value std::rand() / float(RAND_MAX);//设置消息kv_msg.key a; // akv_msg.key b; // abkv_msg.key c; // abc//发布消息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::NodeNodeMemoryPoolSize 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);//运行节点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));}/**订阅uavcan.protocol.debug.LogMessage类型的标准日志消息。*收到的消息将通过回调传递给应用程序。*回调参数的类型可以是以下两种类型之一*-T*-uavcan::接收数据结构T*对于第一个选项ReceivedDataStructureT将隐式转换为T。*类uavcan:ReceivedDataStructure使用从*传输层如源节点ID、时间戳、传输ID、冗余接口的索引提取*/uavcan::Subscriberuavcan::protocol::debug::LogMessage log_sub(node);const int log_sub_start_res log_sub.start([](const uavcan::ReceivedDataStructureuavcan::protocol::debug::LogMessage msg){//消息流以 YAML 格式传输std::cout msg std::endl;});if (log_sub_start_res 0){throw std::runtime_error(Failed to start the log subscriber; error: std::to_string(log_sub_start_res));}/**订阅uavcan.protocol.debug.KeyValue类型的消息。*这一次设置另一种类型的消息订阅采用另一种回调参数类型*则将只是T而不是uavcan:ReceivedDataStructureT。*回调将通过std:cout也可参考uavcan:OStream以YAML格式打印消息。*/uavcan::Subscriberuavcan::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));}//运行节点node.setModeOperational();while (true){//创建线程const int res node.spin(uavcan::MonotonicDuration::getInfinite());if (res 0){std::cerr Transient failure: res std::endl;}}
}服务类型消息
以下是服务类型消息的代码示例
服务端 ---提供固件升级 uavcan.protocol.file.BeginFirmwareUpdate 类型的服务消息。
客户端 ---在指定节点上调用相同的服务类型。服务器的节点 ID 作为 main 函数的命令行参数提供给应用程序。
服务端
#include cstdio
#include iostream
#include unistd.h
#include uavcan/uavcan.hpp
#include uavcan/protocol/file/BeginFirmwareUpdate.hpp
extern uavcan::ICanDriver getCanDriver();
extern uavcan::ISystemClock getSystemClock();
constexpr unsigned NodeMemoryPoolSize 16384;
typedef uavcan::NodeNodeMemoryPoolSize 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));}/**创建服务器。 此程序中服务器只是接收请求没有做其他的处理程序*服务回调接受两个参数*-对请求结构输入的引用*-对默认初始化响应结构输出的引用*输入的类型可以是以下两种之一*-T:请求*-uavcan:ReceivedDataStructureT:Request*输出的类型严格为TResponse。*注意对于服务数据结构不可能实例化T本身*/using uavcan::protocol::file::BeginFirmwareUpdate;uavcan::ServiceServerBeginFirmwareUpdate srv(node);const int srv_start_res srv.start([](const uavcan::ReceivedDataStructureBeginFirmwareUpdate::Request req, BeginFirmwareUpdate::Response rsp){std::cout req std::endl;rsp.error rsp.ERROR_UNKNOWN;rsp.optional_error_message Our sun is dying;});if (srv_start_res 0){std::exit(1); // 错误处理}//开启节点node.setModeOperational();while (true){const int res node.spin(uavcan::MonotonicDuration::fromMSec(1000));if (res 0){std::printf(Transient failure: %d\n, res);}}
}客户端
#include iostream
#include unistd.h
#include uavcan/uavcan.hpp
#include uavcan/protocol/file/BeginFirmwareUpdate.hpp
extern uavcan::ICanDriver getCanDriver();
extern uavcan::ISystemClock getSystemClock();
constexpr unsigned NodeMemoryPoolSize 16384;
typedef uavcan::NodeNodeMemoryPoolSize 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));}//创建客户端using uavcan::protocol::file::BeginFirmwareUpdate;uavcan::ServiceClientBeginFirmwareUpdate 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));}//设置回调client.setCallback([](const uavcan::ServiceCallResultBeginFirmwareUpdate 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_castint(call_result.getCallID().server_node_id.get()) has failed std::endl;}});//设置请求时间间隔client.setRequestTimeout(uavcan::MonotonicDuration::fromMSec(200));//设置优先级client.setPriority(uavcan::TransferPriority::OneHigherThanLowest);//调用远程服务设置固件路径BeginFirmwareUpdate::Request request;request.image_file_remote_path.path /foo/bar;/* 可以使用一个客户端执行多个请求的回调* int call(NodeID server_node_id, const RequestType request)* 初始化一个新的请求回调** int call(NodeID server_node_id, const RequestType request, ServiceCallID out_call_id)* 启动新的非阻塞调用并通过引用返回其ServiceCallID描述符。* 描述符允许查询呼叫进度或稍后取消呼叫。** void cancelCall(ServiceCallID call_id)* 使用描述符取消回调** void cancelAllCalls()* 取消所有回调** bool hasPendingCallToServer(NodeID server_node_id) const* 客户端对象当前是否对给定服务器有挂起的调用。** unsigned getNumPendingCalls() const* 返回当前挂起的回调总数。** bool hasPendingCalls() const* 客户端对象当前是否有任何挂起的回调。*/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));}//创建线程node.setModeOperational();while (client.hasPendingCalls()) // 是否有回调产生被挂起还未执行const int res node.spin(uavcan::MonotonicDuration::fromMSec(10));if (res 0){std::cerr Transient failure: res std::endl;}}return 0;
}时间同步
以下示例演示了如何使用 libuavcan 实现网络范围的时间同步。
包含两个应用程序
时间同步从机 ---一个非常简单的应用程序用于将本地时钟与网络时间同步。
时间同步主机 ---功能齐全的主机可以与同一网络中的其他冗余主机一起工作。
时间同步中的主机逻辑稍微复杂了一些 如果当前模块是主机的话会同时再创建一个从机运行这是做的一个冗余机制当检测到网络中有更高优先级的主机时当前节点会启用从机 (suppress(false))之后与新主机进行同步。
从机
#include iostream
#include cstdio
#include unistd.h
#include uavcan/uavcan.hpp
/**标准应用程序级函数的实现位于uavcan/protocol/中。*同一路径还包含标准数据类型uavcan.procol.*。*/
#include uavcan/protocol/global_time_sync_slave.hpp
extern uavcan::ICanDriver getCanDriver();
extern uavcan::ISystemClock getSystemClock();
constexpr unsigned NodeMemoryPoolSize 16384;
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]);uavcan::NodeNodeMemoryPoolSize node(getCanDriver(), getSystemClock());node.setNodeID(self_node_id);node.setName(org.uavcan.tutorial.time_sync_slave);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));}//创建时间同步从设备 每个节点只能存在一个时间同步从设备//每次从设备能够确定时钟相位误差时它都会调用//平台驱动程序方法 //uavcan:ISystemClock:adjustUtcuavcanUtcDuration adjustment。//通常从设备每1-2秒调整一次不过取决于主设备的广播时间uavcan::GlobalTimeSyncSlave slave(node);const int slave_init_res slave.start();if (slave_init_res 0){throw std::runtime_error(Failed to start the time sync slave; error: std::to_string(slave_init_res));}//创建线程node.setModeOperational();while (true){const int spin_res node.spin(uavcan::MonotonicDuration::fromMSec(1000));if (spin_res 0){std::cerr Transient failure: spin_res std::endl;}//每秒打印一次从设备状态const bool active slave.isActive(); //是否可以与主机进行同步const int master_node_id slave.getMasterNodeID().get(); // 如果 (active false)则返回无效节点const long msec_since_last_adjustment (node.getMonotonicTime() - slave.getLastAdjustmentTime()).toMSec();std::printf(Time sync slave status:\n Active: %d\n Master Node ID: %d\n Last clock adjustment was %ld ms ago\n\n,int(active), master_node_id, msec_since_last_adjustment);/** 注意libuavcan使用两种不同的时间尺度** 1.单调时间-由类uavcan:MonotonicTime和uavcanMonotomicDuration表示。*这个时间是稳定和单调的它测量了一些从过去未指定的开始的时间量它通常不会跳跃或*明显改变速率。*在Linux上它通过clock_gettimeclock_MONOTONIC…访问。**2.UTC时间-由类uavcan:UtcTime和uavcanUtcDuration表示。*这是可以但不一定与网络时间同步的实时时间。*这个时间不是稳定单调的因为它可以改变速率并前后跳跃以消除与全球网络时间的差异。*请注意尽管它的名称是UTC但这个时间刻度不需要严格是UTC时间不过建议使用UTC。*在Linux上它可以通过gettimeofday…访问。**这两个时钟都可以通过方法INode::getMonotonicTime()和INode::getUtcTime()访问。*注意时间表示是类型安全的因为不可能在同一表达式中混合UTC和单调时间编译将失败。*/uavcan::MonotonicTime mono_time node.getMonotonicTime();uavcan::UtcTime utc_time node.getUtcTime();//打印当前时间std::printf(Current time in seconds: Monotonic: %s UTC: %s\n,mono_time.toString().c_str(), utc_time.toString().c_str());//从 1234 us 开始计时加上此时计算后的时间mono_time uavcan::MonotonicDuration::fromUSec(1234);utc_time uavcan::UtcDuration::fromUSec(1234);//打印从 1234 us 计时之后计算的时间std::printf(1234 usec later: Monotonic: %s UTC: %s\n,mono_time.toString().c_str(), utc_time.toString().c_str());}
}fromUSec 函数用于设置从哪个 us 时间开始计时即设置开始时间点。
主机
#include iostream
#include cstdio
#include unistd.h
#include uavcan/uavcan.hpp
#include uavcan/protocol/global_time_sync_master.hpp
#include uavcan/protocol/global_time_sync_slave.hpp
extern uavcan::ICanDriver getCanDriver();
extern uavcan::ISystemClock getSystemClock();
constexpr unsigned NodeMemoryPoolSize 16384;
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]);uavcan::NodeNodeMemoryPoolSize node(getCanDriver(), getSystemClock());node.setNodeID(self_node_id);node.setName(org.uavcan.tutorial.time_sync_master);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));}//初始化时间同步主机每个节点最多只能存在一个时间同步主机uavcan::GlobalTimeSyncMaster master(node);const int master_init_res master.init();if (master_init_res 0){throw std::runtime_error(Failed to start the time sync master; error: std::to_string(master_init_res));}//再创建了一个时间同步从机这是做了一个冗余机制如果发现总线中有优先级//更高的主机则此节点由主机变为从机与新主机进行同步每个节点也最多只有//一个从机。uavcan::GlobalTimeSyncSlave slave(node);const int slave_init_res slave.start();if (slave_init_res 0){throw std::runtime_error(Failed to start the time sync slave; error: std::to_string(slave_init_res));}//创建一个定时器每秒发布一次时间同步主题消息uavcan::Timer master_timer(node);master_timer.setCallback([](const uavcan::TimerEvent){//检查网络中是否由优先级更高的主机如果有则节点切换到从机if (slave.isActive()) // Active means that the slave tracks at least one remote master in the network{if (node.getNodeID() slave.getMasterNodeID()){//当前节点是最高优先级的主机 调用 suppress 函数压制从机模式slave.suppress(true); // SUPPRESSstd::cout I am the highest priority master; the next one has Node ID int(slave.getMasterNodeID().get()) std::endl;}else{//当前网络有更高优先级的主机不再压制从机模式slave.suppress(false); // UNSUPPRESSstd::cout Syncing with a higher priority master with Node ID int(slave.getMasterNodeID().get()) std::endl;}}else{//当前节点主机是唯一时钟源slave.suppress(true);std::cout No other masters detected in the network std::endl;}//发布同步消息const int res master.publish();if (res 0){std::cout Time sync master transient failure: res std::endl;}});master_timer.startPeriodic(uavcan::MonotonicDuration::fromMSec(1000));//创建线程node.setModeOperational();while (true){const int spin_res node.spin(uavcan::MonotonicDuration::getInfinite());if (spin_res 0){std::cerr Transient failure: spin_res std::endl;}}
}固件升级
固件升级分两个模块一个是升级固件模块(类比飞控)另一个是被升级模块(类比电调)。
升级固件模块 ---此模块运行活动节点监视器当远程节点响应 uavcan.protocol.GetNodeInfo请求时模块会检查其固件映像是否比当前运行的节点固件版本更新。如果是这种情况应用程序会向节点发送 uavcan.protocol.file.BeginFirmwareUpdate 类型的请求。该模块还实现了文件服务器用于固件更新过程。
被升级模块 ---响应 uavcan.protocol.file.BeginFirmwareUpdate 类型的请求以及使用服务uavcan.propertiesfile.Read 下载文件。
升级固件模块
#include iostream
#include cstdlib
#include cctype
#include vector
#include algorithm
#include unistd.h
#include uavcan/uavcan.hpp
#include uavcan/protocol/firmware_update_trigger.hpp // uavcan::FirmwareUpdateTrigger
#include uavcan/protocol/node_info_retriever.hpp // uavcan::NodeInfoRetriever (see tutorial Node discovery)
//需要使用 POSIX 标准函数
#include uavcan_posix/basic_file_server_backend.hpp
#include glob.h // POSIX glob() function
extern uavcan::ICanDriver getCanDriver();
extern uavcan::ISystemClock getSystemClock();/*
工作方式如下uavcan::FirmwareUpdateTrigger通过接口 uavcan::INodeInfoListener
订阅来自 uavcan::NodeInfoRetriever 的节点信息报告。
每当 FirmwareUpdateTrigger 接收到有关新节点的信息时它都会通过
界面 uavcan::IFirmwareVersionChecker将此信息转发给应用程序。
然后应用程序将使用提供的信息检查节点是否需要固件更新并将检查结果
报告回 FirmwareUpdateTrigger。如果节点需要更新FirmwareUpdateTrigger将向其
发送请求 uavcan.protocol.file.BeginFirmwareUpdate否则该节点将被忽略
直到它重新启动或重新出现在总线上。如果节点未响应更新请求FirmwareUpdateTrigger
将重试。
如果节点响应错误FirmwareUpdateTrigger 将使用相同的接口询问应用程序是否需要重试。
*/
class ExampleFirmwareVersionChecker final : public uavcan::IFirmwareVersionChecker
{/*** 当类获得对GetNodeInfo请求的响应时将调用此方法。* param node_id 从中接收此GetNodeInfo响应的节点ID。* param node_info 实际节点信息结构* param out_firmware_file_path 通过此参数返回固件路径* 注意必须通过uavcan.protocol.file.Read服务访问此路径。* return True - 开始发送更新请求* False - 改节点被忽略*/bool shouldRequestFirmwareUpdate(uavcan::NodeID node_id,const uavcan::protocol::GetNodeInfo::Response node_info,FirmwareFilePath out_firmware_file_path)override{//需要根据输入来决定节点是否需要更新。//PX4和APM都利用libuavcan的POSIX平台驱动程序提供的类-//uavcan_posixFirmwareVersionChecker-实现通用固件版本检查算法。//该算法的工作原理如下//1. 检查文件系统是否具有给定节点的固件文件。//2. 将给定节点的本地固件映像的CRC与该节点当前正在运行的固件的CRC进行比较//(后者可通过该方法中的节点信息参数获得)。//3. 如果CRC不匹配则请求更新否则不要请求更新。 std::cout Checking firmware version of node int(node_id.get()) ; node info:\n node_info std::endl;/** 正在查找匹配的固件*/auto files findAvailableFirmwareFiles(node_info);if (files.empty()){std::cout No firmware files found for this node std::endl;return false;}std::cout Matching firmware files:;for (auto x: files){std::cout \n\t x \n parseFirmwareFileName(x.c_str()) std::endl;}/** 查找具有最高版本号的固件*/std::string best_file_name;unsigned best_combined_version 0;for (auto x: files){const auto inf parseFirmwareFileName(x.c_str());const unsigned combined_version (unsigned(inf.software_version.major) 8) inf.software_version.minor;if (combined_version best_combined_version){best_combined_version combined_version;best_file_name x;}}std::cout Preferred firmware: best_file_name std::endl;const auto best_firmware_info parseFirmwareFileName(best_file_name.c_str());/** 将最新固件与实际固件进行比较如果两者不同才会更新*/if (best_firmware_info.software_version.major node_info.software_version.major best_firmware_info.software_version.minor node_info.software_version.minor best_firmware_info.software_version.vcs_commit node_info.software_version.vcs_commit){std::cout Firmware is already up-to-date std::endl;return false;}/**固件文件路径不得超过40个字符。*当前使用的固件文件名格式可能会导致超过长度限制因此*通过计算哈希CRC64 并将其用作固件文件的符号链接的名称来解决。*/out_firmware_file_path makeFirmwareFileSymlinkName(best_file_name.c_str(), best_file_name.length());(void)std::remove(out_firmware_file_path.c_str());const int symlink_res ::symlink(best_file_name.c_str(), out_firmware_file_path.c_str());if (symlink_res 0){std::cout Could not create symlink: symlink_res std::endl;return false;}std::cout Firmware file symlink: out_firmware_file_path.c_str() std::endl;return true;}/***当节点以错误响应更新请求时将调用此方法。如果请求只是超时则不会调用此方法。*注意如果在响应到达时节点已被删除则不会调用此方法。*特殊情况如果节点以ERROR_IN_PROGRESS响应则类将假定不再需要进一步的请求。*不会调用此方法。** param node_id 返回错误的节点ID * param error_response 错误响应内容* param out_firmware_file_path 固件路径如果需要设置新的路径则设置如果* 还是旧路径则采用原来的值 * return True - 该类将继续发送具有新固件路径的更新请求。* False - 改节点将忽略*/bool shouldRetryFirmwareUpdate(uavcan::NodeID node_id,const uavcan::protocol::file::BeginFirmwareUpdate::Response error_response,FirmwareFilePath out_firmware_file_path)override{/** 如果节点响应错误则取消更新*/std::cout The node int(node_id.get()) has rejected the update request; file path was:\n \t out_firmware_file_path.c_str() \nresponse was:\n error_response std::endl;return false;}/*** 当节点响应更新请求并进行确认时将调用此节点。* param node_id 确认请求的节点ID* param response 实际响应*/void handleFirmwareUpdateConfirmation(uavcan::NodeID node_id,const uavcan::protocol::file::BeginFirmwareUpdate::Response response)override{std::cout Node int(node_id.get()) has confirmed the update request; response was:\n response std::endl;}//计算固件的符号链接的名称static FirmwareFilePath makeFirmwareFileSymlinkName(const char* file_name, unsigned file_name_length){uavcan::DataTypeSignatureCRC hash;hash.add(reinterpret_castconst std::uint8_t*(file_name), file_name_length);auto hash_val hash.get();static const char Charset[] 0123456789abcdefghijklmnopqrstuvwxyz;static const unsigned CharsetSize sizeof(Charset) - 1;FirmwareFilePath out;while (hash_val 0){out.push_back(Charset[hash_val % CharsetSize]);hash_val / CharsetSize;}out .bin;return out;}//从固件中提取版本信息static uavcan::protocol::GetNodeInfo::Response parseFirmwareFileName(const char* name){// 必须使用静态变量避免堆分配static const auto extract_uint8 [](unsigned pos, const char* name){std::uint8_t res 0;pos;while (std::isdigit(name[pos])){res res * 10 int(name[pos] - 0);pos;}return res;};uavcan::protocol::GetNodeInfo::Response res;unsigned pos 0;while (name[pos] ! -){res.name.push_back(name[pos]);pos;}res.hardware_version.major extract_uint8(pos, name);res.hardware_version.minor extract_uint8(pos, name);res.software_version.major extract_uint8(pos, name);res.software_version.minor extract_uint8(pos, name);pos;res.software_version.vcs_commit ::strtoul(name pos, nullptr, 16);res.software_version.optional_field_flags res.software_version.OPTIONAL_FIELD_FLAG_VCS_COMMIT;return res;}//返回可用于给定节点信息结构的固件文件static std::vectorstd::string findAvailableFirmwareFiles(const uavcan::protocol::GetNodeInfo::Response info){std::vectorstd::string glob_matches;const std::string glob_pattern std::string(info.name.c_str()) - std::to_string(info.hardware_version.major) . std::to_string(info.hardware_version.minor) -*.uavcan.bin;auto result ::glob_t();const int res ::glob(glob_pattern.c_str(), 0, nullptr, result);if (res ! 0){::globfree(result);if (res GLOB_NOMATCH){return glob_matches;}throw std::runtime_error(Cant glob());}for(unsigned i 0; i result.gl_pathc; i){glob_matches.emplace_back(result.gl_pathv[i]);}::globfree(result);return glob_matches;}
};
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]);/** 初始化节点*通常充当固件更新器的节点还将实现动态节点ID分配器。**在大多数实际应用程序中依赖于阻塞API的功能例如此固件更新功能*必须在辅助线程中实现以便不干扰主线程的实时处理。*在该固件更新程序的情况下干扰可能由相对密集的处理*和阻止对文件系统API的调用引起。*/uavcan::Node16384 node(getCanDriver(), getSystemClock());node.setNodeID(self_node_id);node.setName(org.uavcan.tutorial.updater);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));}/** 初始化节点信息检索器* 它将由固件版本检查器来调用*/uavcan::NodeInfoRetriever node_info_retriever(node);const int retriever_res node_info_retriever.start();if (retriever_res 0){throw std::runtime_error(Failed to start the node info retriever: std::to_string(retriever_res));}/** 初始化固件更新触发器**此类监视uavcan:NodeInfoRetriever的输出并使用该输出决定哪些节点需要*更新固件。当检测到需要更新的节点时该类发送服务请求*uavcan.protocol.file.BeginFirmware 更新。*/ExampleFirmwareVersionChecker checker;uavcan::FirmwareUpdateTrigger trigger(node, checker);const int trigger_res trigger.start(node_info_retriever);if (trigger_res 0){throw std::runtime_error(Failed to start the firmware update trigger: std::to_string(trigger_res));}/** 初始化文件服务器*/uavcan_posix::BasicFileServerBackend file_server_backend(node);uavcan::FileServer file_server(node, file_server_backend);const int file_server_res file_server.start();if (file_server_res 0){throw std::runtime_error(Failed to start the file server: std::to_string(file_server_res));}std::cout Started successfully std::endl;/** 创建进程运行节点*/node.setModeOperational();while (true){const int res node.spin(uavcan::MonotonicDuration::getInfinite());if (res 0){std::cerr Transient failure: res std::endl;}}
}被升级模块
#include iostream
#include cstdlib
#include vector
#include iomanip
#include unistd.h
#include uavcan/uavcan.hpp
#include uavcan/protocol/file/BeginFirmwareUpdate.hpp
#include uavcan/protocol/file/Read.hpp
extern uavcan::ICanDriver getCanDriver();
extern uavcan::ISystemClock getSystemClock();
/*** 此类将从指定位置下载固件到内存*/
class FirmwareLoader : private uavcan::TimerBase
{
public:/*** 状态转换过程* 正在进行 ---[后台工作中]---- 成功* \ -- 失败*/enum class Status{InProgress,Success,Failure};
private:const uavcan::NodeID source_node_id_;const uavcan::protocol::file::Path::FieldTypes::path source_path_;std::vectorstd::uint8_t image_;typedef uavcan::MethodBinderFirmwareLoader*,void (FirmwareLoader::*)(const uavcan::ServiceCallResultuavcan::protocol::file::Read)ReadResponseCallback;uavcan::ServiceClientuavcan::protocol::file::Read, ReadResponseCallback read_client_;Status status_ Status::InProgress;void handleTimerEvent(const uavcan::TimerEvent) final override{if (!read_client_.hasPendingCalls()){uavcan::protocol::file::Read::Request req;req.path.path source_path_;req.offset image_.size();const int res read_client_.call(source_node_id_, req);if (res 0){std::cerr Read call failed: res std::endl;}}}void handleReadResponse(const uavcan::ServiceCallResultuavcan::protocol::file::Read result){if (result.isSuccessful() result.getResponse().error.value 0){auto data result.getResponse().data;image_.insert(image_.end(), data.begin(), data.end());if (data.size() data.capacity()) // Termination condition{status_ Status::Success;uavcan::TimerBase::stop();}}else{status_ Status::Failure;uavcan::TimerBase::stop();}}
public:/*** 创建对象后开始下载* 销毁对象可以取消进程*/FirmwareLoader(uavcan::INode node,uavcan::NodeID source_node_id,const uavcan::protocol::file::Path::FieldTypes::path source_path) :uavcan::TimerBase(node),source_node_id_(source_node_id),source_path_(source_path),read_client_(node){image_.reserve(1024); // 任意值/** 响应优先级等于请求优先级* 通常情况下文件 IO 执行的优先级应设置得非常低*/read_client_.setPriority(uavcan::TransferPriority::OneHigherThanLowest);read_client_.setCallback(ReadResponseCallback(this, FirmwareLoader::handleReadResponse));/** 设置频率进行限速*/uavcan::TimerBase::startPeriodic(uavcan::MonotonicDuration::fromMSec(200));}/*** 此函数可以用于检测是否完成下载*/Status getStatus() const { return status_; }/*** 返回下载的固件镜像*/const std::vectorstd::uint8_t getImage() const { return image_; }
};
/*** This function is used to display the downloaded image.*/
template typename InputIterator
void printHexDump(InputIterator begin, const InputIterator end)
{struct RAIIFlagsSaver{const std::ios::fmtflags flags_ std::cout.flags();~RAIIFlagsSaver() { std::cout.flags(flags_); }} _flags_saver;static constexpr unsigned BytesPerRow 16;unsigned offset 0;std::cout std::hex std::setfill(0);do{std::cout std::setw(8) offset ;offset BytesPerRow;{auto it begin;for (unsigned i 0; i BytesPerRow; i){if (i 8){std::cout ;}if (it ! end){std::cout std::setw(2) unsigned(*it) ;it;}else{std::cout ;}}}std::cout ;for (unsigned i 0; i BytesPerRow; i){if (begin ! end){std::cout ((unsigned(*begin) 32U unsigned(*begin) 126U) ? char(*begin) : .);begin;}else{std::cout ;}}std::cout std::endl;}while (begin ! end);
}
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]);/** 初始化节点* 软件版本与硬件版本在固件更新中至关重要*/uavcan::Node16384 node(getCanDriver(), getSystemClock());node.setNodeID(self_node_id);node.setName(org.uavcan.tutorial.updatee);uavcan::protocol::HardwareVersion hwver; // TODO initialize correct valueshwver.major 1;node.setHardwareVersion(hwver);uavcan::protocol::SoftwareVersion swver; // TODO initialize correct valuesswver.major 1;node.setSoftwareVersion(swver);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));}/** 固件下载的存储对象*/uavcan::LazyConstructorFirmwareLoader fw_loader;/** 初始化 BeginFirmwareUpdate 服务.*/uavcan::ServiceServeruavcan::protocol::file::BeginFirmwareUpdate bfu_server(node);const int bfu_res bfu_server.start([fw_loader, node](const uavcan::ReceivedDataStructureuavcan::protocol::file::BeginFirmwareUpdate::Request req,uavcan::protocol::file::BeginFirmwareUpdate::Response resp){std::cout Firmware update request:\n req std::endl;if (fw_loader.isConstructed()){resp.error resp.ERROR_IN_PROGRESS;}else{const auto source_node_id (req.source_node_id 0) ? req.getSrcNodeID() : req.source_node_id;fw_loader.constructuavcan::INode, uavcan::NodeID, decltype(req.image_file_remote_path.path)(node, source_node_id, req.image_file_remote_path.path);}std::cout Response:\n resp std::endl;});if (bfu_res 0){throw std::runtime_error(Failed to start the BeginFirmwareUpdate server: std::to_string(bfu_res));}/** 运行节点(后台运行)*/while (true){if (fw_loader.isConstructed()){node.setModeSoftwareUpdate();if (fw_loader-getStatus() ! FirmwareLoader::Status::InProgress){if (fw_loader-getStatus() FirmwareLoader::Status::Success){auto image fw_loader-getImage();std::cout Firmware download succeeded [ image.size() bytes] std::endl;printHexDump(std::begin(image), std::end(image));// TODO: save the firmware image somewhere.}else{std::cout Firmware download failed std::endl;// TODO: handle the error, e.g. retry download, send a log message, etc.}fw_loader.destroy();}}else{node.setModeOperational();}const int res node.spin(uavcan::MonotonicDuration::fromMSec(500));if (res 0){std::cerr Transient failure: res std::endl;}}
}