GNU Radio教程 8.消息传递

Message Passing

 

内容:

1   介绍

2   消息传递 API

    2.1 消息处理函数

    2.2 通过流程图连接消息

3   从外部来源发布         Posting from External Sources

4   使用消息作为命令

5   代码示例

    5.1 C++

    5.2 Python

6   流程图示例

    6.1 PDU

7   流程图示例:聊天应用程序

  • 简介

    GNU Radio 最初是一个流系统,没有其他机制在块之间传递数据。数据流是一种适用于样本、比特等的模型,但实际上并不是控制数据、元数据或数据包结构的正确机制(至少在处理链中的某个点)。

    我们通过引入标签流解决了部分问题(请参阅流标签)。这是数据流的并行流。不同之处在于标签旨在保存元数据和控制信息。标签与数据流中的特定样本特别相关,并与数据一起向下游流动。流标签同步传递模型允许其他模块识别已经发生或应该发生在特定item上事件或动作。缺点是标签流实际上只能在工作函数内部访问,并且只能沿一个方向流动。它的好处是它与数据是同步的。

    我们想要一个更通用的消息传递系统有几个原因。第一个是允许下游模块与上游模块通信(不是流标签的单方向)。第二个是允许我们以更简单的方式在外部应用程序和 GNU Radio 之间来回通信。GNU Radio 的消息传递接口在异步的基础上处理这些情况。

   消息传递接口的实现依赖于 GNU Radio 中的多态类型 (PMT)。

二、消息传递 API

    消息传递接口设计在 gr::basic_block 中,它是 GNU Radio 中所有块的父类。每个块都有一组消息队列来保存传入的消息,并且可以将消息发布到其他块的消息队列中。这些模块还区分输入和输出端口。

    块必须在其构造函数中声明其输入和输出消息端口。消息端口由名称来描述,该名称实际上是 PMT 符号(i.e., an interned string)。注册新端口的 API 调用是:

在 Python 中:

    消息端口现在可以通过该端口名称来识别。其他可能想要在端口上发布或接收消息的块必须订阅它。当一个块有消息要发送时,它们会使用以下 API 在特定端口上发布:

在 Python 中:

    订阅通常以连接消息端口的形式完成,作为流程图的一部分,稍后将讨论。在内部,当消息端口连接时,调用 gr::basic_block::message_port_sub 方法。

    任何订阅了另一个块的输出消息端口的块都将在其发布时收到该消息。在内部,当一个块发布消息时,它只是遍历所有已订阅它模块并使用 gr::basic_block::_post 方法将消息发送到该块的消息队列。

2.1消息处理函数

    一个模块如果订阅了其他模块的message,那它必须声明一个消息处理函数来处理发送到它的消息。使用gr::basic_block::message_port_register_in方法来注册一个端口之后,将消息处理程序绑定到此端口。

    从使用 C++11 的 GNU Radio V3.8 开始,使用 lambda 函数来做到这一点:

在 Python 中:

当一条新消息被推送到端口的消息队列时,正是这个函数用于处理该消息。'port_id' 与注册输入端口时使用的 PMT 相同。'block_class::message_handler_function' 是类的成员函数,指定用于处理发送到此端口的消息。

所有消息处理函数的原型是:

在 Python 中,等效函数为:

下面给出了示例:

2.2通过流程图连接消息

    从流程图看,我们使用 gr::hier_block2::msg_connect 方法来订阅块到其他块的消息。假设块src有一个名为pdus的输出消息端口 ,而块dbg有一个名为print的输入端口。流程图中的消息连接(在 Python 中)如下所示:

    端口pdus上的src块发布的所有消息都将由端口print上的dbg接收。请注意,我们只是使用字符串来定义端口,而不是 PMT 符号。这便于用户更轻松地输入端口名称(作为参考,您可以在 Python 中使用 pmt::intern 函数作为 pmt.intern("string") 创建一个 PMT 符号)。

用户还可以使用以下 API 调用,来查询模块的消息输入和消息输出端口的名称:

这些的返回值是一个用 PMT 符号填充的 PMT 向量,因此必须使用 PMT 运算符来操作它们。

    每个块都有内部方法来处理消息的发布和接收。gr::basic_block::_post 方法接收一条消息并将其放入其队列中。发布消息的模块使用块的 gr::basic_block::_post 方法作为访问消息队列的方式。所以正确名字的消息队列会有一条新消息。发布消息还具有唤醒处于等待状态的模块的线程。因此,如果接受消息模块处于空闲等待状态,一旦发布消息,它就会唤醒并调用消息处理程序。

三、从外部源发布消息

    消息传递架构的一个重要特性是如何使用它来接收来自外部源的消息。我们可以直接调用块的 gr::basic_block::_post 方法并传递消息。因此任何带有输入消息端口的块都可以通过这种方式接收来自外部的消息。

    以下示例中使用 pdu_to_tagged_stream 模块作为流程图的source block。它的目的是等待消息(PDU,协议数据单元,对等实体间通信的数据包) ,并将它们转换为普通流将其发布。有效负载将作为普通流发送,而元数据将被解码为标签并标记在流上发送。

    因此,如果我们创建了一个src块作为 PDU to stream,它有一个 PDU 输入端口,这就是我们将 PDU 消息注入流程图的方式。这些 PDU 可能来自另一个块或流程图,但在这里,我们将手动创建和插入它们。

    PDU 的元数据部分是空的,因此是 pmt::PMT_NIL 对象。有效载荷现在只是一个 16 个字节全为 1 的简单向量。要发布消息,我们必须访问模块的 gr::basic_block 类,我们使用 gr::basic_block::to_basic_block 方法,然后调用 gr::basic_block::_post 方法将 PDU 传递到正确的端口.

所有这些机制都在文件 qa_pdu.py 的 QA 代码中进行了探索和测试。

有一些通过 GRC 使用消息传递基础设施的示例:

四、使用消息作为命令

消息的一个重要用途是将命令发送到下游或上游模块。这方面的例子包括:

    1.gr::qtgui::freq_sink_c:频率轴的缩放可以通过消息改变

    2.gr::uhd::usrp_source 和 gr::uhd::usrp_sink:许多与收发器相关的设    置可以通过命令消息进行操作,例如频率、增益和 LO 偏移

    3.gr::digital::header_payload_demux,它从头解析器块接收关于有多少  有效负载项要处理的确认

没有特殊的 PMT 类型来编码命令,但是,强烈建议使用以下格式之一:

    1.pmt::cons(KEY, VALUE):这种格式对于采用单个值的命令很有用。将 KEY    和 VALUE 分别视为参数名称和值。对于 QT GUI 频率接收器,KEY 为   “freq”,VALUE 为新的中心频率,单位为 Hz。

    2.pmt::dict((KEY1: VALUE1), (KEY2: VALUE2), ...):这个和前面的格式    基  本一样,但是可以提供多个key/value对。这在单个命令采用多个不能 分解为多个命令消息的参数时特别有用(例如,USRP 块可能在命令消息 中同时具有时间戳和中心频率,它们密切相关)。

在这两种情况下,所有 KEY 都应该是 pmt::symbols(即字符串)。值可以是块需要的任何值。

偏离这种格式可能很诱人,例如,QT 频率接收器可以简单地将浮点值作为命令消息,它仍然可以正常工作。但是,有一些很好的理由坚持这种格式:

    互操作性:使用标准格式的人越多,来自不同来源的块就越有可能一起工作

    可检查性:如果消息同时包含值和键,则消息调试块将显示有关消息的更多  有用信息

    直觉:这种格式非常通用,不太可能造成不够用的情况(特别是考虑到值本  身就是 PMT)。作为一个反例,使用位置参数(例如“第一个参数是频率,第 二个参数是增益”)很容易被遗忘,或者在一个地方而不是另一个地方改变,   等等。

五、代码示例

请注意,除了下面的 C++ 或 Python 代码之外,如果将消息传递添加到块,您还需要编辑块的 YAML 文件。添加相应的输入或输出条目(确保域设置为“消息”)。您不需要指定数据类型。您还应该确保标签值对应于注册的端口名称。有关更多详细信息,请参见此处。

5.1C++

5.2Python

六、流程图示例

下面是一个使用流和消息的流程图的简单示例:

    有几件有趣的事情需要指出。首先,有两个源块,它们都固定间隔地输出item,每 1000 毫秒一个和每 750 毫秒一个。虚线表示连接的消息端口,而实线表示连接的流端口。在流程图的上半部分,我们可以看到实际上可以在消息传递端口和流端口之间切换,但前提是 PMT 的类型与流端口的类型匹配(在本例中,流端口的粉红色表示字节,这意味着如果我们想要流式传输我们作为 PMT 发送的相同数据,则 PMT 应该是一个 u8vector)。

    另一个有趣的事实是,我们可以将多个消息输出端口连接到单个消息输入端口,这对于流端口是不可能的。这是由于消息的异步特性:接收块将在有机会时处理所有消息,而不必按任何特定顺序。从多个块接收消息仅仅意味着可能有更多消息需要处理。

    消息发布到一个模块后会发生什么情况?这取决于实际的块实现,但有两种可能性:

    1) 调用消息处理程序,立即处理消息。

    2) 消息被写入 FIFO 缓冲区,块可以随时使用它,通常是在工作函数中。

    对于同时具有消息端口和流端口的块,这两个选项中的任何一个都可以,具体取决于应用程序。但是,我们强烈反对在工作函数内部处理消息,而是建议使用消息处理程序。在工作函数中使用消息会鼓励我们在工作中阻塞等待消息到达。这对于永远不应该阻塞的工作函数来说是不好的行为。如果块依赖于消息来操作,则使用消息处理程序概念来接收消息,然后当调用工作函数时,该消息可用于通知块的操作。只有在特别明确的情况下,我们才应该在一个块中使用上面的方法 2。

    使用消息传递接口,我们可以编写没有流端口的块,然后工作函数变得无用,因为它是设计用于处理流项目的函数。事实上,没有流端口的块通常甚至没有工作功能。

七、PDUs

在前面的流程图中,我们有一个称为PDU to Tagged Stream的块。GNU Radio 中的 PDU(协议数据单元)有一个特殊的 PMT 类型,它是字典里的一个键值对(在 CAR 上)和一个统一向量类型。请参阅Polymorphic_Types_(PMTs)#Pairs。因此,这将产生一个有效的 PDU,没有元数据和 10 个零作为流数据:

然后字典中的键/值对被解释为流标签的键/值对。

流程图示例:聊天应用程序

让我们构建一个使用消息传递的应用程序。聊天程序是一个理想的用例,因为它等待用户输入消息,然后发送。因此,不需要throttle块。

创建以下流程图并将其保存为“chat_app2.grc”:

ZMQ 消息块的地址为“tcp://127.0.0.1:50261”。一旦按下 Enter 键,将发送在QT GUI 消息编辑框中键入的文本。输出在启动 gnuradio-companion 的终端屏幕上。

如果您想与其他用户(而不仅仅是您自己)交谈,您可以创建一个具有不同名称的附加流程图,例如“chat_app3.grc”。然后按如下方式更改 ZMQ 端口号:

    聊天应用程序2

       ZMQ PUSH Sink:tcp://127.0.0.1:50261

       ZMQ PULL 来源:tcp://127.0.0.1:50262

    聊天应用程序3

       ZMQ PUSH Sink:tcp://127.0.0.1:50262

       ZMQ PULL 来源:tcp://127.0.0.1:50261

使用 GRC 时,执行 Generate 和/或 Run 会创建一个与 .grc 文件同名的 Python 文件。您可以执行 Python 文件而无需再次运行 GRC。

为了测试这个系统,我们将使用两个进程,所以我们需要两个终端窗口。

Terminal 1:

    由于您刚刚building chat_app3 流程图,因此您可以执行run。

Terminal 2:

    打开另一个终端窗口。

    更改为您用于生成chat_app2.grc的任何目录。

    执行以下命令:

在终端 1 屏幕 (chat_app3) 上键入 chat_app2 的消息编辑框,反之亦然。

要干净地终止每个进程,请单击 GUI 上角的“X”,而不是使用 Control-C。

¹ 在旧的 GNU Radio 3.7 中,我们使用了 Boost 的“绑定”功能:

'this' 和 '_1' 是使用 Boost 绑定函数将 'this' 指针作为第一个参数传递给类的标准方法(标准 OOP 实践),_1 是函数需要一个附加参数的指示符。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值