前言
本节本章讨论与组件开发相关的更高级主题,在阅读本内容之前,请先熟悉组件中的信息。
一、自动生成的组件文件
REDHAWK IDE 提供了一个用于自动生成 C++、Python 或 Java 语言组件代码的工具。此过程负责确保 REDHAWK 规范,并允许开发人员插入自己的自定义处理算法来处理组件发送/接收的数据。以下部分提供了生成文件的简要说明。请注意,某些文件可以随意修改,而其他文件则不应修改。
不鼓励修改特定文件有两个原因:
- 如果用户使用 IDE 重新生成组件(例如,添加端口),特定文件将被代码生成器覆盖。
- 修改实现 REDHAWK 接口的文件可能会影响与其他 REDHAWK 模块的兼容性。
1、为所有组件生成的文件
- 编译相关的文件
- 代码生成器创建以下文件,用于使用 Autotools 构建和安装组件:
build.sh
- 生成其中两个文件:其中一个位于顶级组件目录中,另一个位于源目录中。两者中的第一个 build.sh 调用源目录中的脚本。build.sh 源目录中的脚本运行./reconf
;./configure
;make
。configure.ac
- 标准 Autoconf 配置文件。Makefile.am
- 标准 Automake 配置文件。Makefile.am.ide
- 提供 REDHAWK IDE 的构建信息。reconf
- 用于在 Autotools 链中生成配置脚本的脚本。componentName.spec
- 提供 rpmbuild 配置信息以方便构建组件的 RPM。
- 代码生成器创建以下文件,用于使用 Autotools 构建和安装组件:
- 组件 XML 描述符
- REDHAWK IDE 创建以下文件来描述组件的属性、端口、接口和描述:
componentName.prf.xml
- 描述组件的属性。componentName.scd.xml
- 描述组件的端口和接口。componentName.spd.xml
- 提供组件的顶级描述,包括组件入口点和 XML 文件的名称和位置。
- REDHAWK IDE 创建以下文件来描述组件的属性、端口、接口和描述:
- 单元测试文件
- 代码生成器为练习组件创建以下单元测试文件
test_componentName.py
- 包含基于标准 Python 框架构建的单元测试 unittest。提供的测试练习生成的代码。可以添加额外的测试来测试开发人员生成的算法。
- 代码生成器为练习组件创建以下单元测试文件
2、为 C++ 组件生成的文件
- 以下文件包含 C++ 组件的特定于实现的代码:
componentName.h
- 支持 componentName.cpp 的头文件相关代码应该放在合适的位置。componentName.cpp
- 开发人员添加自己的处理行为的适当位置。port_impl.h
(可选)- 仅针对使用批量输入/输出 (BulkIO)、突发输入/输出 (BurstIO)、前端接口 (FEI) 和消息传递以外的接口的端口生成此文件。包含组件的端口相关代码。如果端口类型发生变化,则需要重新生成此文件,覆盖特定于应用程序的代码。port_impl.cpp
(可选)- 仅针对使用 BulkIO、BurstIO、FEI 和 Messaging 以外的接口的端口生成此文件。包含组件的端口相关代码。如果端口类型发生变化,则需要重新生成此文件,覆盖特定于应用程序的代码。main.cpp
- 包含用于创建组件实例的函数。对于共享库组件,这是一个名为 make_component() 的动态可加载函数。对于可执行组件,这是 main() 进程的函数。不建议修改此文件。struct_props.h
- 包含在代码生成接口中定义的结构属性的支持类。不建议修改此文件。componentName_base.h
- 根据组件的端口和属性为组件提供接口实现。不建议修改此文件。componentName_base.cpp
- 根据组件的端口和属性为组件提供接口实现。不建议修改此文件。
这里不介绍为 Python 组件生成的文件和为 Java 组件生成的文件
二、自动生成的组件方法
- serviceFunction()
- 组件的核心功能位于 C++ 的
serviceFunction()
方法、Python 的process()
方法以及 Java 的 serviceFunction() 方法中。在组件的基类上调用 start() 后,serviceFunction()
方法会定期被调用。
- 组件的核心功能位于 C++ 的
- constructor()
- 这是组件/设备的构造函数。当这个函数被调用时,类型为 property 的属性会被初始化到它们的默认状态或者被重载的状态。
三、基础组件成员
本节概述了组件类可用的成员。有四种成员:ports
、Properties
、Domain Awareness
和 Network Interface
。
1、Ports
通过使用端口,可以实现数据流入和流出组件。端口被描述为提供端口(输入)或使用端口(输出)。这种命名约定通常被视为违反直觉的,因此需要解释。端口是组件的 RPC 接口。因此,一个输入端口提供的功能可以被一个输出端口使用。
REDHAWK 包含多种标准化接口,以促进互操作性。这些接口由端口实现。当在 REDHAWK IDE 的组件生成向导中选择一个端口时,实现这些接口的代码将自动生成。
不管方向如何,端口都作为组件基类的成员被访问。假设组件中存在一个名为 myport 的任何接口的端口,在 C++、Python 和 Java 中,分别以以下方式访问它:
this->myport
self.port_myport
this.port_myport
2、Properties
就像端口一样,属性通过生成的成员可用于组件的基类。可以通过属性的名称(如果有的话)或其 ID 来找到属性。例如,如果一个属性被定义了一个 ID 为 foo 和一个名称为 abc,那么它将分别在 C++、Python 和 Java 中以以下方式被访问:
this->abc
self.abc
self.abc
如果属性没有定义名称,那么在 C++、Python 和 Java 中将分别通过以下方式访问它:
this->foo
self.foo
this.foo
3、枚举
简单属性可以有枚举值,这将符号名称与值关联起来。代码生成会为这些值创建常量,允许组件开发者使用符号名称而不是字面值。对于结构体或结构体序列属性中的简单属性,生成的常量会嵌套在结构体的名称下。
①、C++
在 C++ 中,生成的枚举常量是嵌套命名空间中的静态变量,位于顶级命名空间下 enums:
enums::simple::LABEL
enums::structprop::field::LABEL
enums::structseq_struct::field::LABEL
②、Java
在 Java 中,生成的枚举常量是嵌套静态类中的公共静态变量,位于名为 的顶级类下 enums:
enums.simple.LABEL
enums.structprop.field.LABEL
enums.structseq_struct.field.LABEL
③、Python
在 C++ 中,生成的枚举常量是嵌套命名空间中的静态变量,位于顶级命名空间下 enums:
enums.simple.LABEL
enums.structprop.field.LABEL
enums.structseq_struct.field.LABEL
4、Domain Awareness
每个组件都有两个成员,它们提供了对组件正在操作的域和应用的引用。要检索域管理器和应用,可以访问成员函数 getDomainManager()
和 getApplication()
,它们分别返回 DomainManagerContainer 和 ApplicationContainer。DomainManagerContainer
有一个成员 getRef()
,它返回指向域管理器对象的 CORBA 指针。ApplicationContainer 有一个成员 getRef()
,它返回指向应用对象的 CORBA 指针。
在设备的情况下,基类包含
getDeviceManager()
而不是getApplication()
,它返回一个 DeviceManagerContainer。DeviceManagerContainer 有一个成员getRef()
,它返回指向 DeviceManager 对象的 CORBA 指针。
5、网络接口
如果组件包含对 GPP 分配属性的任何成员的依赖项 nic_allocation,则框架将在部署时确保这些网络资源可供该组件使用。无论哪个 NIC 满足分配要求,都会将其提供给组件,并通过 getNetwork() 成员将其提供给开发人员,该成员返回一个 NetworkContainer. NetworkContainer 有成员函数 getNic(),它是满足要求的网卡的字符串名称(即:eth0)。
四、组件实现
组件可以指定特定的依赖关系,例如操作系统、处理器架构或所需的设备 属性(例如,处理器速度或内存容量)。设置这些依赖关系可确保组件在运行时部署到适当的设备。
虽然 REDHAWK 支持单个组件的多种实现,但它可能会令人困惑,尤其是在调试系统时。除了一些有限的场景外,建议开发人员将单个实现与每个组件关联起来。
五、管理和定义属性
属性通过其结构、种类和类型来定义。四种不同的属性结构包括:
simple
- 单个值,例如 1.0 或“字符串”simple sequence
- 零个或多个简单的列表/数组,例如 [1, 2, 3] 或 [“first”, “second”]struct
- 将几个 simple 的和 simple sequence 组合在一起struct sequence
- 零个或多个 struct 实例的列表/数组
REDHAWK 中三种常用的属性包括:
property
- 表示用于配置和状态的属性allocation
- 表示 REDHAWK设备将满足的要求message
- 仅与结构一起使用,并指示该结构将用作 REDHAWK 中的事件消息
属性的类型对应于基本编程语言的原始类型,如浮点数、长整数、布尔值等。此外,数值类型可以是复杂的。
通过使用生成的代码和 REDHAWK 库,属性的操作使用由 C++、Python 或 Java 提供的基本类型,如在属性中所见。例如,一个 simple sequence
、复数浮点属性通过 C++ 中的 std::vector< std::complex<float> >
变量和 Python 中的 Python 复数对象列表来操作。生成的组件代码为该组件的每个属性提供了一个类数据字段。
支持的 simple
和 simple sequence
属性的原始数据类型包括:boolean、octet、float、double、short、ushort、long、longlong、ulong、ulonglong、string、objref、char 和 utctime。utctime 类型用于描述时间,并可以用来同步组件或设备上的属性更改事件和查询。要为时间设置一个默认值作为属性,使用形式为 “YYYY:MM:DD::hh:mm:ss.sss” 的字符串,其中 YYYY 是年份,MM 是月份,DD 是日期,hh 是小时(0-23),mm 是分钟,ss.sss 是小数秒。
在某些情况下,希望 utctime 属性初始化为当前时间。为此,将默认值(无论是在组件的默认属性值中还是作为波形级别的重载)设置为“now”,即部署组件时的时间。字符串“now”也可以在 Python sandbox 中使用,将 utctime 属性的值设置为当前时间。在组件代码内部,有助手可用于将 utctime 属性值设置为当前时间;例如,在 C++ 中,以下代码将属性设置为现在:
my_prop = redhawk::time::utils::now();
以下原始数据类型可以标记为复数值:boolean、octet、float、double、short、ushort、long、longlong、ulong 和 ulonglong。
每个组件实现 CF::PropertySet 接口,该接口通过 query()
和 configure()
方法提供对组件属性的远程访问。query()
方法提供了读取组件当前属性设置的手段,而 configure()
方法提供了设置组件属性值的手段。这些方法中标识的属性将使用属性标识符值来解决标识符访问。属性模式属性(readonly、writeonly 或 readwrite)控制对 query()
和 configure()
方法的访问。
REDHAWK 库基类提供了 configure()
的完整实现,特定属性的创建由生成的基类按组件处理。除了基本的本地值更新外,标准的 configure() 实现还提供:
- 通过互斥实现的线程安全更新
- 数值类型的自动转换
- 对属性值变化的通知
- 通过事件对外报告变化
- 对无效输入的异常抛出
由于这些增强功能,强烈建议开发者不要重载 query()
或 configure()
方法。
1、属性 ID
属性通过 ID 和名称来识别。ID 必须在组件或设备的范围内唯一。这种唯一性适用于所有属性,包括结构体和结构体序列属性的成员。因此,如果同一组件中的两个不同结构体属性各有一个名为 abc 的成员,则这两个成员不能使用 ID abc。
为了消除 ID 冲突,REDHAWK 提供了一种命名约定,允许多个结构体属性使用相同的成员名称而不产生 ID 冲突。对于结构体的成员,通过结合成员的名称和结构体的 ID 来创建 ID。例如,如果结构体属性 foo 有一个简单成员 bar,则该成员的名称为 bar,ID 为 foo::bar。这种命名约定也适用于结构体序列属性。
2、属性名
如果提供了属性名称,它将用于生成代码中的成员变量以及在 IDE 内的显示。如果没有提供,则使用 ID 代替。
3、属性访问
模式设置仅适用于属性种类为 property 的属性。模式的值(readonly、writeonly 或 readwrite)决定了属性支持 configure() 或 query() 的能力。
不要对 kind 设置不包括 property 的属性执行 configure() 或 query()。此操作可能没有任何效果,或者更糟,返回一些未定义的值。
如果未为属性提供值,则它会接收一个默认值。或者,用户可以在 IDE 中设置值,或等效地,在 PRF 中设置。这个值(默认的或用户提供的)可以被认为是在组件定义时设置的。对于 kind 设置为 property 的属性,这个初始值可以在稍后的组件使用时被覆盖,如下所示:
- 在 Python 沙箱中,通过 sb.launch() 命令的 properties 参数
- 在 SAD 文件中
这种覆盖发生在语言提供的构造函数运行之后以及生成的 constructor() 函数运行之前。因此,不建议在语言提供的构造函数中访问属性。相反,应该等到属性值覆盖完成后。然后,在生成的 constructor() 函数中访问属性。
4、属性变更监听器
通常,当属性值发生变化时触发额外操作是非常有用的。组件支持一种称为属性变更监听器的通知类型,使开发者能够注册回调方法,每当使用新值调用 configure() 为特定属性时,这些方法就会被执行。
属性变更监听器在持有保护组件所有属性访问的锁的同时执行。这确保在响应属性变化时不会发生外部变化。回调方法可能会更改属性的值或调用额外的函数;然而,避免进行计算成本高昂或阻塞操作。
①、C++
C++ 组件支持使用成员函数回调来通知属性值变化。
以下示例解释了如何为名为 MyComponent 的组件中类型为 float 的 freqMHz 简单属性添加属性变更监听器。
在 [component].h 中,为你的回调添加一个私有方法声明。回调接收两个参数,旧值和新值:
void freqMHz_changed(float oldValue, float newValue);
在 [component].cpp 中实现该函数。
然后,在组件的构造函数 constructor()
中,注册变更监听器:
this->addPropertyListener(freqMHz, this, &MyComponent_i::freqMHz_changed);
addPropertyListener 接受三个参数:属性的成员变量、目标对象(通常是 this)以及指向成员函数的指针。
当为结构体或序列属性定义属性监听器时,新值和旧值通过 const 引用传递:
void taps_changed(const std::vector<float>& oldValue, const std::vector<float>& newValue);
②、Python 和 Java 略
5、自定义查询和配置
此功能仅在 C++ 中可用。
REDHAWK库和生成的组件代码自动处理所有定义的属性的 query()
和 configure()
。然而,在某些情况下,响应 query()
时检索属性的当前值可能更可取,例如从外部库获取状态时。开发者可能还希望对如何设置属性值有更多的控制权。组件支持每个属性的回调函数,以自定义查询和配置行为。
当组件收到该属性的 query()
时,将调用查询回调,代替查询本地状态。同样,当组件收到该属性的 configure()
时,将调用配置回调,而不是更新组件本地状态。
与属性侦听器不同,无论新值是否等于旧值,都会调用配置回调。
查询和配置回调是在持有保护组件所有属性访问的锁的情况下执行的。这确保了回调函数对组件属性有独占访问权。如果可能的话,避免计算成本高昂或阻塞操作,以确保组件保持响应性。
①、C++
在 C++ 中,查询和配置回调在组件上注册。注册一个新的回调将替换旧的回调。
- 查询回调
- 要创建查询回调,请在
[component].h
中添加私有成员函数声明。它不带参数并返回值:float get_freqMHz();
- 实现
[component].cpp
中的功能。 - 然后,在
constructor()
的正文中,注册查询函数:this->setPropertyQueryImpl(freqMHz, this, &MyComponent_i::get_freqMHz);
- setPropertyQueryImpl 接受三个参数:属性的成员变量、目标对象(通常为this)和指向成员函数的指针。
- 要创建查询回调,请在
- 配置回调
- 要创建配置回调,请在
[component].h
中添加私有成员函数声明。它接受一个参数,即新值,并返回 void:void set_freqMHz(float value);
- 实现 [component].cpp 中的功能。
- 然后,在 的主体中
constructor()
,注册配置函数:this->setPropertyConfigureImpl(freqMHz, this, &MyComponent_i::set_freqMHz);
- setPropertyConfigureImpl 接受三个参数:属性的成员变量、目标对象(通常为this)和指向成员函数的指针。
- 当设置配置回调时,成员变量不会自动更新。如果需要,由组件开发人员更新成员变量。
- 要创建配置回调,请在
②、Python 和 Java 略
6、同步
registerPropertyListener 通过使用组件上的函数,可以向属性的外部侦听器通知组件属性的更改。该 registerPropertyListener 函数允许事件使用者向组件注册。注册后,组件将启动一个线程来监视所请求属性的值。当任何受监视属性的值发生更改时,会发出一个事件,通知使用者哪个组件上的哪个属性发生了更改、何时更改以及更改为什么新值。
为了保持属性更改事件与对组件的查询调用之间的同步,可以在查询中添加一个 QUERY_TIMESTAMP 属性。查询中的 QUERY_TIMESTAMP 属性会用这个查询的时间戳来填充。返回的时间戳可以与异步接收到的属性更改事件进行比较,以评估请求的属性的最新已知值是什么。
7、查询和配置组件和设备
本节重点讨论从外部源调用 query 或 configure 调用的过程。
属性被打包为一系列 CF::DataType 结构序列,其中每个 CF::DataType 结构由字符串元素 id 和 CORBA::Any 元素值组成,为任意一个属性形成一个 id/值对。CORBA::Any 元素是一个可以容纳任意数据类型的结构(包括自定义定义的结构和对象);这个结构既持有值本身,也持有关于值的类型的信息。REDHAWK 结构属性被打包为 CF::DataType 的嵌套序列。外部结构是属性,值元素包含一系列 CF::DataType 元素,每个成员结构对应一个。
REDHAWK中的属性是强类型的,所以值元素的数据类型必须与组件或设备期望的特定属性的类型相匹配。如果错误的类型被打包进 CORBA::Any,属性配置将失败。例如,如果一个属性被定义为 long 类型,而打包进值元素的值是 short 类型,那么操作将失败。
只有当属性的种类包括 ‘property’ 时,才使用 query()
和 configure()
。
①、从 C++ 访问
从 C++ 程序访问组件或设备属性可能很困难,因为它要求开发人员遵守 CORBA API。为了简化属性操作,REDHAWK 引入了redhawk::PropertyMap
,它将 std::map
API覆盖到 CF::DataType
序列上,使开发者能够检查、添加或从属性序列中移除一个属性。
- 配置一个值:
include <ossie/PropertyMap.h> CF::Properties my_props; redhawk::PropertyMap &tmp = redhawk::PropertyMap::cast(my_props); short num_value = 2; std::string str_value("hello"); tmp["property_id_a"] = num_value; tmp["property_id_b"] = str_value; comp->configure(my_props); short retval = tmp["property_id"].toShort();
- 查询一个值:
include <ossie/PropertyMap.h> CF::Properties my_props; redhawk::PropertyMap &tmp = redhawk::PropertyMap::cast(my_props); tmp["property_id_a"] = redhawk::Value(); tmp["property_id_b"] = redhawk::Value(); comp->query(my_props); short num_value = tmp["property_id_a"].toShort(); std::string str_value = tmp["property_id_b"].toString();
在头文件 ossie/CorbaUtils.h 中声明了额外的便利函数。这些函数使得直接与 CORBA::Any 类型交互变得更加容易,但它们已被 redhawk::PropertyMap 取代,只是为了保持与旧软件的 API 兼容性而包含。
②、从 Python 或 Java 访问略
六、处理事件
除了使用消息事件属性和消息端口之外,REDHAWK 库还使开发人员能够与事件通道交互,使用 CORBA Any 对象发送和接收非 REDHAWK 结构化消息。该库为发送和接收数据提供了发布者和订阅者接口。这些库利用现有的简单数据类型(即int、float、string等)、REDHAWK核心框架(CF)事件消息和由你的组件使用的定义的结构化消息的编组和解组支持。对于自定义结构化数据,开发人员有责任实现将数据编组和解组进出 CORBA Any 对象的方法,或将数据结构序列化为可以被编组的字符串类型。这个 API 被认为是支持使用 CORBA 事件频道自定义行为的高级主题。
1、发布者支持
要将数据发布到事件通道,发布者提供以下方法:
push
- 接受数据以转发到事件频道。对于 C++,结构化数据类型需要重载运算符<<=
。
①、发布者的 C++ 示例
本节提供了 C++ 发布器的示例。
Component.h
struct MsgData {
int id;
std::string data;
};
redhawk::events::ManagerPtr ecm;
redhawk::events::PublisherPtr pub;
Component.cpp
Component::constructor {
// get access to the [Event Channel](/manual/glossary/#event-channel) Manager
ecm = redhawk::events::GetManager( this );
// request a Publisher object for an event channel
if ( ecm ) {
pub = ecm->Publisher("test1");
}
}
// required to marshall data into the CORBA::Any
void operator<<=( CORBA::Any &any, const MsgData &msg ) {
// marshall MsgData to Any
};
Component::serviceFunction {
// create a structured message
MsgData my_msg = generateMsg();
if ( pub ) {
pub->push( my_msg);
}
// or simple text based messages
if ( pub ) pub->push( "simple message to send" );
}
②、发布者的 Python 和 Java 示例略
2、订阅者支持
订阅者提供两种模式(轮询 vs 回调),用于从事件频道接收数据。这两种方法都要求开发人员从 CORBA Any 对象中解组数据。对于 C++,结构化数据类型需要重载运算符>>=
。
getData
- (轮询)从事件频道抓取一条消息。如果没有可用消息,则返回 -1。对于 Python,返回一个 CORBA Any 对象,如果没有可用消息,则返回 None。callback
- 为订阅者对象提供一个回调。当数据从事件频道到达时,此回调会被通知。
①、使用 getData 方法的 C++ 订阅者示例
以下是一个使用 getData 方法的 C++ 订阅者示例。
Component.h 轮询示例
redhawk::events::ManagerPtr ecm;
redhawk::events::SubscriberPtr sub;
struct MsgData {
int id;
std::string data;
};
Component.cpp 轮询示例
// required to unmarshall data from the CORBA::Any
bool operator>>=( const CORBA::Any &any, const MsgData *&msg ) {
//unmarshall Any into MsgData
};
Component::constructor {
// get access to the Event Channel Manager
ecm = redhawk::events::GetManager( this );
// request a Subscriber object for an event channel
if ( ecm ) {
sub = ecm->Subscriber("test1");
}
}
Component::serviceFunction {
MsgData msgin;
if ( sub && sub->getData( msgin ) == 0 ) {
RH_NL_INFO("mylogger", "Received msg =" << msgin.id);
}
}
②、使用回调方法的 C++ 订阅者示例
以下是使用该回调方法的 C++ 订阅者的示例。
Component.h 回调示例
redhawk::events::ManagerPtr ecm;
redhawk::events::SubscriberPtr sub;
struct MsgData {
int id;
std::string data;
};
void my_msg_cb( const CORBA::Any &data );
Component.cpp 回调示例
// required to unmarshall data from the CORBA::Any
bool operator>>=( const CORBA::Any &any, const MsgData *&msg ) {
//unmarshall Any into MsgData
};
void Component::my_msg_cb( const CORBA::Any &data ) {
// structure msg
MsgData msg;
if ( data >>= msg ) {
LOG_INFO( Component, "Received message " << msg.id );
}
}
Component::constructor {
// get access to the Event Channel Manager
ecm = redhawk::events::GetManager( this );
// request a Subscriber object for an event channel
if ( ecm ) {
sub = ecm->Subscriber("test1");
sub->setDataArrivedListener( this , &Component::my_msg_cb );
}
}
我的qq:2442391036,欢迎交流!