@Pigmon
2021-08-09T16:36:26.000000Z
字数 10004
阅读 649
NOX
版本 | 日期 | 修改人 | 修改内容 |
---|---|---|---|
V 20200220 | 2020年2月20日 | 袁胜 | 补全框架使用流程(3)的后半部分; 增加参数系统教程 |
V 20200224 | 2020年2月24日 | 袁胜 | 参数在程序中的使用; 参数配置文件的位置; 参数命名冲突 |
V 20200302 | 2020年3月2日 | 袁胜 | 添加第5部分(NOXAPK发布与部署);修改参数系统图1中命名冲突问题; |
V 20200326 | 2020年3月26日 | 袁胜 | 添加第6部分 noxdriver; |
V 20200326 | 2020年3月27日 | 袁胜 | 修复noxdriver中的程序错误; |
在增加了Breeder模块中的banana信道传输的内容后,我们可以在Monkey模块中接收该信道的信息。跟Breeder模块一样,我们在Monkey.h中也重载一下Process函数:
#pragma once
#include ".MonkeyModule.h"
namespace nox::app
{
class Monkey
: public MonkeyModule
{
/// Override your process functions ...
void Process(RecvMessage recv, SendMessage &send) override;
};
}
Monkey.cpp中的实现部分:
void Monkey::Process(RecvMessage recv, SendMessage &send)
{
int cnt = recv.banana.data;
Logger::I("Monkey") << "Get " << cnt << " bananas from Breeder.";
}
然后回到工作区根目录,执行:
noxmake
noxrun
可以看到如下输出信息:
[Information][Breeder]: Feed Monkey a banana.
[Information][Monkey]: Get 1 bananas from Breeder.
以上就是为多个模块建立通信信道的最基本的流程。
参数组,参数和参数实例的关系如前文所述。我们现在来实践一下创建参数和使用参数的过程。
基本流程:
日常使用:
创建一个参数描述图:
这里,我们使用FlowChart流程图中的“数据库”图例来创建参数组,取名为 param_group1; 然后用类图中的“类”图例来创建参数,这里我们创建2个参数:Info 和 Lessons,里面的内容要符合 yaml 格式。
将改文件存储为.pos文件,保存在工作空间中的
.nox/parameter/description
目录中。
创建参数dependence图:
该图描述模块和参数之间的引用关系。模块即上文中我们创建的 Breeder 和 Monkey,图中,我们让 Monkey 引用 Lessons 和 Info 两个参数,而 Breeder 只引用 Info 一个参数。
模块在这里还是使用UML类图中的“简单类”图例,而参数在这里使用 FlowChart流程图 中的“文档”图例。
将这个文件下载为.pos,放在工作空间中的
.nox/parameter/dependence
目录中。
刷新工作空间:
保存好两个参数配置图,回到工作空间根目录,执行:
noxcreate refresh
执行完成后,检查工作空间的
src/.param/template/header
目录,是否生成了两个参数对应的头文件,以及,检查
src/.param/template/description
目录,是否有两个参数对应的yaml文件。
在工作区根目录执行
noxparam list
可以看到目前的所有参数:
ParameterName @ IDName: Value1 Value2 ...
- - - - - - - - - - - - - - - - - - - - - - -
Info@param_group1:
Lessons@param_group1:
可以看到每个参数的冒号后面都是空的,因为我们还没有参数组的副本。
创建参数组副本
执行:
noxparam add param_group1 group1
为参数组 param_group1 创建一个名为 group1 的副本。
再次执行
noxparam list
可以看到:
ParameterName @ IDName: Value1 Value2 ...
- - - - - - - - - - - - - - - - - - - - - - -
Info@param_group1: group1
Lessons@param_group1: group1
修改参数值
例如,我们想修改当前参数组副本中的 Info 的值,可以通过:
noxparam edit Info group1
这样就打开了vim可以编辑Info参数的内容。如果想使用其他编辑器,可以用 -use 来指定:
noxparam edit Info group1 -use nano
这样就是使用 nano 来进行编辑。
头文件目录:
虽然参数的头文件已经生成,但是目前(2020年2月24日)该目录的引用需要手动配置。可以直接在 include 的时候写出全路径,也可以配置CMake。
例如,配置CMake:
打开 zoo 项目的CMakeLists.txt,在include_directories里添加Paramter.h所在的目录:
include_directories(
include
../.param/template
${catkin_INCLUDE_DIRS}
)
使用参数配置:
我们在Breeder.cpp中,之前操作banana信道的地方,加上读取参数配置中的数值的部分。修改Process函数:
void Breeder::Process(RecvMessage recv, SendMessage &send)
{
send.banana.emplace();
send.banana.value().data = 1;
/// -> 这里开始
// 读取参数配置文件中的数值发送到banana通道
nox::parameter::InfoParameter info_param;
bool read_ok = info_param.Read("/home/pigmon/nox_demo/src/.param/instance/param_group1/group1/InfoParameter.yaml");
if (read_ok)
{
send.banana.value().data = info_param.basic.age;
}
/// <- 这里结束
Logger::I("Breeder") << "Feed Monkey a banana.";
}
首先建立一个InfoParameter类型的对象,它针对的是我们前面加入的 Info 参数。然后使用nox::file::Parameter的Read接口来读取参数配置文件,进而得到参数配置中所有的值。
修改之后,回到工作空间根目录,执行
noxmake
noxrun
可以看到输出如下:
[Information][Monkey]: Get 35 bananas from Breeder.
35即是group1实例中,Info参数中的age的数值。
参数组的描述文件:
[工作空间根目录]/.nox/environment/[工程名].yaml
如:
~/nox_demo/.nox/evironment/zoo.yaml
这里是根据.pos文件生成的参数和参数组的关系描述,noxparam相关命令大部分的执行过程都是从读取这个文件开始。
参数组实例配置文件:
所有的参数组实例配置文件都在:
[工作空间根目录]/src/.param/instance
目录下。
各参数头文件:
每个参数的头文件自动生成在
[工作空间根目录]/src/.param/template/header/
下,但在
[工作空间根目录]/src/.param/template
目录下会生成一个自动include这些头文件的 Parameter.h,每次参数配置变化执行 noxcreate refresh 以后,这个头文件也会更新,为了避免开发时频繁修改头文件,可以直接在工程中引用这个头文件。
虽然参数组在制作和维护的时候是放在特定工程里的,但其实参数的可用范围是系统全局的,所以即便是在不同工作空间中,所有的参数和参数组都不能重名,否则会引起命名冲突。
在日常使用NOX框架进行开发的过程中,我们经常会在开发用的电脑上编写程序,做好测试,然后将开发的结果部署到车辆的工控机器上。NOXAPK 是NOX框架中的命令行工具之一,其功能是将一个NOX工程的运行环境和可执行文件等必要内容进行打包发布,并可以在目标机器上进行部署。
首先使用上面修改过的nox_demo工程,尝试一个最常见的操作:
作为准备,在开发机上,首先开启 roscore,然后新打开一个命令行。
先来到 zoo 项目所在的工作空间的根目录,在我的机器上,它是 ~/nox_demo:
cd nox_demo
然后,使用 noxapk 命令对该工作区进行打包发布:
noxapk create -min
因为我们只想打包必要的运行环境,所以这里使用 -min 参数,如果想指定发布文件的存储路径,还可以用 -output 参数进行指定:
noxapk create -min -output [存储位置的绝对路径]
noxapk 命令的其他参数说明可以参考后面的表格。
这样,我们就得到了一个 [工作空间名].nox 文件,在我这里,它的名字是:nox_demo.nox。这就是我们发布出来的安装文件。
来到我们的目标机器上,打开一个命令行,来到我们想要部署该工作区的目录下。
然后执行:
noxapk install nox_demo.nox -hard -not-make
因为我们这是第一次部署这个工作区,所以使用 -hard 参数,代表删除旧文件再部署(这里就是代表不考虑重叠覆盖的问题,使用其他参数也可)。
因为我们打包发布的内容中不包含源代码,所以也没必要再 make,因此加上参数 -not-make。
noxapk 命令的其他参数说明可以参考后面的表格。
执行这条命令后,可以看到它输出的:
Begin to install apk ...
等这个进程结束,即代表部署完成。我们可以在当前目录查看一下有没有工作空间的目录。
确保roscore启动,进入部署好的工作空间目录,我这里是 nox_demo 目录。
然后执行:
noxrun
如果看到的输出和之前教程中的一样,就说明我们已经部署成功:
Running List:
1. zoo/Breeder
2. zoo/Monkey
[Information][Module]: --- START ---
root: /home/pigmon/Downloads/nox_demo2/nox_demo
package: zoom
node: Breeder
[Information][Module]: --- START ---
root: /home/pigmon/Downloads/nox_demo2/nox_demo
package: zoom
node: Monkey
[Information][Breeder]: Feed Monkey a banana.
...
[Information][Monkey]: Get 35 bananas from Breeder.
...
参数 | 描述 | 用法 | 执行目录 |
---|---|---|---|
create | 为NOX工作空间创建APK包 | ||
-output | 指定apk的存放目录 | create -output [output directory] | [workspace_root] |
-min | 打包最小集,只包括启动工作空间的必要文件 | create -output [output directory] -min | [workspace_root] |
-all | 打包最大集,包括所有文件 | create -output [output directory] -all | [workspace_root] |
-full | 开发中的选项,包括必要文件和源文件 | create -output [output directory] -full | [workspace_root] |
-f | 即使Make出错也强制打包 | create -output [output directory] -[min/all/full] -f | [workspace_root] |
install | 将APK安装到NOX工作空间 | ||
-none | 保留旧文件且不更新 | noxapk install [apk_name] [installed directory] -none | [workspace_root] |
-soft | 只更新重叠文件 | noxapk install [apk_name] [installed directory] -soft | [workspace_root] |
-cover | 用新文件覆盖旧文件 | noxapk install [apk_name] [installed directory] -cover | [workspace_root] |
-keep | 保留被覆盖的旧文件 | noxapk install [apk_name] [installed directory] -keep | [workspace_root] |
-hard | 删除旧文件并更新 | noxapk install [apk_name] [installed directory] -hard | [workspace_root] |
-exclude | 只保留重叠文件 | noxapk install [apk_name] [installed directory] -exclude | [workspace_root] |
-not-make | 安装完成后不设置工作空间 | noxapk install [apk_name] [installed directory] -[none/soft/cover/keep/hard/exclude] -not-make | [workspace_root] |
NOX Driver是NOX框架中处理车辆CAN驱动的部分。它可以根据开发人员编写的CAN协议配置文件,自动生成CAN报文收发和处理的程序框架,之后开发人员只需补充业务逻辑的代码,而复杂的报文解析合成的处理由自动生成的程序完成,大大的节省了车辆CAN报文部分开发处理的时间。
图6.1 NOX Driver运行流程
开发人员使用NOX Driver对CAN驱动进行配置的工作主要分为以下3个部分:
以下对步骤进行操作说明。
描述报文内容的配置文件为 yaml 格式,格式描述如下:
%YAML:1.0
devices:
- name: <device_name>
interface: <interface_name> | [<interface_name>]
frames:
- name: <frame_name>
id-recv: <id/id_bool_expr> | [<id/id_bool_expr>]
id-send: <id/id_cal_expr>
id: <id>
type: standard | extended | remote | error
fields:
- name: <field_name>
type: double | bool | string | int | …
start: <bit_number>
length: <bits_count>
format: intel | motorola
resolution: <data_resolution>
offset: <data_offset>
min: <data_ minimum>
max: <data_maximum>
signed: false | true
上述内容仅为一个示意,不是标准YAML语法,其中“|”代表“或”,表示隔开可能的多种取值;“<…>”表示一个变量,由开发人员填写;“[…]”表示YAML的数组。在本框架特有的CAN数据协议格式中,涉及到的各个字段含义及其取值范围如下:
字段名 | 字段含义 | 字段变量名 | 变量含义 |
---|---|---|---|
devices | CAN设备数组 | ||
devices/name | CAN设备名字,将作为C++类名 | device_name | 自定义的名字,需要符合C++变量命名标准 |
interface | CAN设备接口名字(可选,可为数组) | interface_name | CAN设备接口名字,从操作系统获得 |
frames | CAN设备包含的所有CAN帧信息 | ||
frames/name | 帧名,将作为C++类名和函数名的一部分 | frame_name | 自定义的名字,需要符合C++变量命名标准 |
id-recv | 本帧从CAN设备接收的报文ID(可选,可为数组) | id/id_bool_expr | 一个整数,或一个含变量id的布尔表达式,用以在线匹配 |
id-send | 本帧发送到CAN设备的ID(可选) | id/id_cal_expr | 一个整数,或一个包含变量id的计算表达式,用以在线计算发送的真正ID |
id | 本帧接收和发送用的ID,会被id-recv或id-send对应覆盖(可选) | id | 整数 |
type | 指定本帧的格式 | standard | extended | remote | error | 标准帧,扩展帧,远程帧,或错误帧 |
fields | 本帧包含的所有字段的数组 | ||
fields/name | 字段的名字,将作为变量名 | field_name | 自定义的名字,需要符合C++变量命名标准 |
start | 字段起始位 | bit_number | 一个整数(0-63) |
length | 字段的位长度 | bit_count | 一个整数(1-64) |
format | 字段的格式 | intel | motorola |
resolution | 字段数据的分辨率(可选,默认为1) | data_resolution | 一个实数 |
offset | 字段数据的偏移量(可选,默认为0) | 一个实数 | |
min | 字段数据的最小值,用于限制最小发送值(可选,默认无限制) | data_minimum | 一个实数 |
max | 字段数据的最大值,用于限制最大发送值(可选,默认无限制) | data_maximum | 一个实数 |
signed | 字段数据的符号性(可选,默认无符号) | false | true | 无符号或有符号 |
编写好yaml文件后,使用 命令进行生成:
参数 | 描述 | 用法 | 执行目录 |
---|---|---|---|
[yaml_file] | yaml配置文件名 | nox-can-driver-gen [yaml_file] (output [out_path]) | 任意目录 |
output [out_path] 为可选输入,缺省在当前目录。
将生成的工程放入某个NOX工作空间,即可通过 Noxmake 进行编译。
在生成的协议处理程序框架中,分别重载收发部分的处理函数,详见后面操作实例。
启动CAN接口使用 noxdriver 命令:
命令 | 描述 | 用法 | 执行目录 |
---|---|---|---|
noxdriver | 启动CAN接口node | noxdriver [project] [node] --socketcan [can_port] | [workspace_root] |
例如:
noxdriver driver tongli --socketcan can0
这里我们从一个简单的,有收发各一个报文的协议开始,配置文件内容如下:
%YAML:1.0
#--------------------------------------------------------------------------------------------
# Write your description ...
#--------------------------------------------------------------------------------------------
devices:
- name: Chassis
frames:
- name: GearSend
id-send: 0x66
type: standard
fields:
- {name: gear_state, type: int, start: 0, length: 3, format: intel}
- name: GearRecv
id-recv: 0x67
type: standard
fields:
- {name: gear_state, type: int, start: 0, length: 3, format: intel}
- {name: is_driver_override, type: bool, start: 3, length: 1, format: intel}
- {name: reported_gear_state, type: int, start: 4, length: 3, format: intel}
- {name: is_canbus_fault, type: bool, start: 7, length: 1, format: intel}
我们新建一个名为Chassis的协议,其中发送给车端CAN的报文为 GearSend,接收车端的报文为 GearRecv,报文ID分别为 0x66 和 0x67。
文件命名为 test_dgram.yaml。
如上文描述,使用 nox-can-driver-gen 命令来根据上面的配置文件生成工程。
nox-can-driver-gen test_dgram.yaml
之后我们可以看到,在当前目录下,生成了一个 test_dgram 目录,目录内部结构即为典型的 catkin 工程,包括 src 目录和 Package.xml 以及 CMakeLists.txt。src目录下可以看到生成的源程序:
其中,Chassis.h\cpp 是我们主要需要进行业务逻辑编写的地方。
将生成的 test_dgram 目录拷贝至我们之前使用的 nox_demo 工作空间的 src 目录中,然后执行:
noxmake
编译结束,可以在 /home/pigmon/nox_demo/src/.plugin/lib/test_dgram/ 目录下看到库文件 test_dgram.so,即说明编译成功。
注6.1 :2020年3月27日为止,项目名必须改成driver。今后会修复。
要添加接收到CAN报文,和发送CAN报文的业务逻辑,我们需要重载 ChassisModule 类中定义的虚函数,首先以接收到报文的处理函数为例。
我们在生成的 Chassis.h 中添加 ProcessGearSendFrame 函数的重载声明:
#pragma once
#include <drivers/.ChassisModule.h>
namespace nox::driver
{
class Chassis :
public ChassisModule
{
/// Override your process functions ...
void ProcessGearRecvFrame(const std::string & name, unsigned int id, GearRecvFrame frame) override;
};
}
然后在 Chassis.cpp 下实现该函数:
void Chassis::ProcessGearRecvFrame(const std::string & name, unsigned int id, GearRecvFrame frame)
{
Logger::I("Chassis") << "Recieved " << id << " with gear " << frame.gear_state;
}
在 ChassisModule 类中,我们可以看到 GearSendFrame 的定义和实现,报文中的字段都定义成了通用类型以供读写,除此以外,ToCANMessage 和 FromCANMessage 中自动生成的程序已经实现了参数化的报文转换功能,这部分已经不需要我们自己来实现了。
发送报文的部分,我们只要调用 SendGearSendFrame 函数即可。
启动刚刚实现的CAN协议处理程序,需要使用 noxdriver 命令,其基本格式是:
noxdriver [project] [node] -socketcan [can_port]
这里我们的启动方式(潍柴车)是:
noxdriver driver driver -socketcan can2
参考 注6.1
可以看到车辆输出:
[Information][Chassis]: Recieved 103 with gear 4