# 1. AWFlow简介

本章导读

AWFlow是ZLG自主研发的面向数据流的软件框架(核心部分使用纯C语言实现)。在嵌入式系统中,各式各样的应用往往都会涉及到"数据流动",比如典型的传感器数据流:采集、处理(比如滤波)、本地显示、本地存储、上"云"等等。ZLG针对"数据流"应用场景,设计了"AWFlow"这一通用的软件框架。

# 1.1 管道过滤器模式简介

AWFlow是基于管道过滤器(Pipe And Filter)模式开发的数据流处理框架。管道过滤器模式在处理数据流方面天赋异禀。基于管道过滤器模式的系统,主要由"过滤器"和"管道"组成。每个过滤器都是一个可重用的组件,用以完成对数据的特定处理(滤波、存储、转换、显示等等)。管道用于将各个过滤器连接起来,进而组合成功能强大的系统。

Unix系统的Shell就充分的发挥了管道过滤器模式的作用,Unix系统提供了很多功能单一的命令,通过管道把这些命令组合起来,可以完成各种不同功能的任务。比如,要统计当前目录下C语言代码的行数,可以将cat命令(查看文件内容)和wc命令(统计文件内容)使用字符"|"连接起来,例如:

cat *.c | wc -l

这里的cat和wc是过滤器,"|"是管道,cat的输出是标准输出(stdout),wc的输入是标准输入(stdin),管道则把cat的输出对接到wc的输入上。

Unix的Shell功能强大而灵活,但也只是管道过滤器模式的一个应用场景,管道过滤器本身的应用要更加强大和灵活:Shell的管道只有一条流水线,而管道过滤器模式支持任意多条流水线,每个过滤器也可以有多个输入和输出,甚至一条流水线也支持并行处理。

# 1.1.1 管道

管道是数据流动的通道。可能是真实的管道,可能是消息系统,可能是一个函数的调用,或者其它数据传递方式。

在嵌入式系统开发中,最常用的可能也就是最简单的函数调用。通过函数调用这种最基础的形式,可以方便的将数据从一个过滤器传递到另一个过滤器。

# 1.1.2 过滤器

过滤器是对数据进行处理的节点。"过滤"仅仅是一个形象的名称,过滤器的核心是对数据进行处理,"处理"并不局限于数据的"过滤",具体处理形式可以多种多样,例如:过滤、格式转换、数据加工、数据分析或根据输入做相应的工作(点亮LED、打开蜂鸣器、控制GPIO输出等等)。

# 1.1.3 示例

在嵌入式系统中,存在各式各样的协议转换模块。例如:UART转Wi-Fi、UART转以太网、CAN转Wi-Fi、CAN转以太网、CAN转蓝牙等等。这些转换模块本质上都是将一种协议的数据转换为另外一种协议的数据,它们在程序结构上有极大的相似性,为便于理解,接下来以一种CAN转蓝牙模块为例进行图示说明。

CAN是一种通讯协议,蓝牙也是一种通讯协议,这里先抛开CAN和蓝牙的内部技术细节不谈,直接基于管道过滤器模式来构建整个系统的框架。

版本1------单向透传

单向透传时,需要一个"CAN Reader"过滤器负责读取来自CAN的数据,一个"BlueTooth Writer"过滤器负责通过蓝牙发送数据。对应框图详见下图。

图1.1 CAN蓝牙协议转换器(V1)
图1.1 CAN蓝牙协议转换器(V1)

如果是其它类型的协议转换(比如UART转Wi-Fi),只需将相应的读写操作修改为对应协议的读写操作即可。

版本2------双向透传

协议转换一般都是双向的,为了支持双向透传,需要新增一条流水线,以及两个新的过滤器。"CAN Writer"过滤器负责发送数据到CAN,"BlueTooth Reader"过滤器负责读取来自蓝牙的数据。对应框图详见下图。

图1.2 CAN蓝牙协议转换器(V2)
图1.2 CAN蓝牙协议转换器(V2)

灰底部分的过滤器是相比于上一个版本所新增的过滤器,下同。

版本3------缓冲和分段

到目前为止,看起来目标似乎已经达到了。但是,不同协议的数据包载荷往往并不相同,比如CAN传输的数据包是很小的(传统CAN每个数据帧最多携带8字节数据),而蓝牙数据包相对于CAN来讲可能会大很多,可能多达200多字节,至少也支持20多字节。

对于这种不对等的协议传输,出于效率的考虑,可以把来自CAN的数据包缓存起来,直到收到指定长度的数据或者超时为止,再一次性将数据通过蓝牙发送出去。这里引入一个新的过滤器:Buffer。用于将多个小的数据包组合起来,把它放在"CAN Reader"和"BlueTooth Writer"之间,对应框图详见图1.3。

反之,来自蓝牙端的数据包相对于CAN来讲可能很大,无法一次性通过CAN发送,此时可以将其拆分为多个数据包,以分多次发送。这里引入一个新的过滤器:Segment。用于将数据包进行拆分,把它放在"BlueTooth Reader"和"CAN Writer"之间,对应框图详见下图。

图1.3 CAN蓝牙协议转换器(V3)
图1.3 CAN蓝牙协议转换器(V3)

版本4------动态参数配置

前面的"Buffer"过滤器和"Segment"过滤器通常都需要一些参数来控制它们的行为,比如分包大小、超时时间等等。这些参数在系统中均有缺省的配置,但有时用户可能想动态的调整这些参数。为此,可以引入一个"Settings"过滤器,用户可以通过蓝牙端发送配置请求,由"Settings"过滤器对系统进行配置,再把执行结果通过蓝牙返回给用户,对应框图详见下图。

图1.4 CAN蓝牙协议转换器(V4)
图1.4 CAN蓝牙协议转换器(V4)

版本5------引入协议

版本4看起来不错,但是它是无法工作的。原因就是最初的设计一直使用透传协议,无法区分从蓝牙来的数据是业务数据还是配置命令。这就需要引入协议来区分它们,至于使用什么协议,完全可以自由决定。比如简单的,每帧蓝牙数据前增加一个标识,表明该帧数据是配置命令还是业务数据。

虽然协议可以自由决定,但出于对稳定性、通用性、复用性等因素的考虑,往往还是推荐选用一些成熟的标准协议。这里假定使用ProtoBuffer,进而引入"ProtoBuffer Decoder"和"ProtoBuffer Encoder"两个过滤器。"ProtoBuffer Decoder"用于解析蓝牙端接收的数据,如果是业务数据,仍然按之前的流程进行,如果是配置命令,则交给"Settings"过滤器处理,处理完成之后,再把执行结果通过"ProtoBuffer Encoder"进行打包,最后通过蓝牙发送结果给用户。对应框图详见下图。

图1.5 CAN蓝牙协议转换器(V5)
图1.5 CAN蓝牙协议转换器(V5)

版本6------系统状态

到目前为止,基本功能差不多了。我们再加入一个"Status"过滤器,接受来自蓝牙的请求,返回系统的一些状态,这个功能无论对最终用户和开发者都有用处(监视模块的运行状态)。对应框图详见下图。

图1.6 CAN蓝牙协议转换器(V6)
图1.6 CAN蓝牙协议转换器(V6)

版本7------更换通信协议

在版本6的基础上,如果基于CAN转蓝牙的设计,新做一个UART转CAN,那么仅需将图1.6中的BlueTooth Reader过滤器修改为UART Reader过滤器,BlueTooth Writer过滤器修改为UART Writer过滤器即可,对应框图详见下图。

图1.7 UART CAN协议转换器
图1.7 UART CAN协议转换器

由此可见,其它所有过滤器,都可以直接复用!

版本8------支持更多的通讯协议

随着系统的演化,可以重用的过滤器越来越多,支持新的通讯协议,只需添加对应的过滤器即可。例如,需要在CAN转蓝牙的基础上,增加UART转蓝牙的功能,则仅需在图1.6的基础上,增加UART Reader和UART Writer过滤器即可,对应框图详见下图。

图1.8 UART&CAN蓝牙转换器
图1.8 UART&CAN蓝牙转换器

上面介绍了各个版本的转换器,当然,这里仅仅只是着重于概念层的介绍,使读者对管道过滤器模式有一个初步的认识。在实际应用项目中,可能会使用更多的过滤器。

通过上面各个版本的演进过程可以发现,基于管道过滤器模型进行设计时,很容易通过添加新的"过滤器"完成功能的完善与新增,不会对原系统造成破坏,不需要修改已有的代码,这就很好的遵循了"开闭原则"。

# 1.1.4 管道过滤器模式的优点

通过上面整个CAN蓝牙转换器的设计过程可以体会到,基于管道过滤器模式设计时,具有以下几个明显的优点:

  • 易实现:每个过滤器的功能都很单一;
  • 易重用:各个过滤器的功能独立,很容易在不同的应用项目中得以复用;
  • 易扩展:无论是扩展内部功能,还是与外部系统建立连接,增加过滤器即可;
  • 易理解和使用:系统逻辑可以很便捷的通过图形展示,借助可视化的设计器(第2章会介绍ZLG自主研发的图形化设计工具:AWFlow-Designer),可以很清晰的看到系统的结构,并对其进行调整和配置。

# 1.2 AWFlow基础概念

AWFlow是基于管道过滤器模式开发的数据流处理框架,框架的核心部分采用纯C语言实现,运行效率高、可移植性强、资源消耗低(可以在32K FLASH、8K RAM的嵌入式微控制器上运行)。

AWFlow中的很多概念是从管道过滤器模式中引出来的,了解了管道过滤器模式,就可以很容易理解AWFlow中的一些基础概念:节点、节点类型、节点id、消费者。

为便于描述和理解,这里以第5版的CAN蓝牙协议转换器为例进行说明,详见图1.5。

# 1.2.1 节点和流图

在AWFlow中,将"过滤器"统一称之为"节点",由节点及节点之间的关系构成的框图称之为"流图"(graph)。

例如,图1.5所示的系统框图就是一张典型的流图,图中的各个过滤器就是节点。图1.5中共计有9个节点,简要统计详见下表,在功能简介一栏中,简要说明了该节点对应的输入和输出数据。

序号 节点名 输入 输出
1 CAN Reader CAN报文
2 Buffer 单个CAN报文 将多个CAN报文打包后的透传数据
3 ProtoBuffer Encoder 透传数据、状态信息或配置数据 编码后的数据,带类型区分标识
4 BlueTooth Writer 编码后的数据
5 Settings 对配置的请求 配置的结果
6 BlueTooth Reader 蓝牙端读取到的数据
7 ProtoBuffer Decoder 未解码的原始数据 透传数据、状态信息、配置数据
8 Segment 透传数据 分片的透传数据
9 CAN Writer 需要发送到CAN端的数据

输入为"无"表示该节点之前无任何节点,输出为"无"表示该节点之后无任何节点。

# 1.2.2 节点分类

根据是否存在输入或输出,AWFlow中将节点分为4类,详见下表。

类别 输入 输出 简介 图1.5中的相关节点
pump 负责从系统外部接收数据或者自身产生一些数据,通常作为一条数据流的起点 CAN Reader、BlueTooth Reader
filter 普通节点,对输入数据进行过滤、转换和处理 Buffer、ProtoBuffer、Encoder、Settings、ProtoBuffer、Decoder、Segment
sink 负责将数据传递给外部,比如控制台、图形用户节点、数据库或通过某种通信接口将数据传递出去,通常作为一条数据流的终点 CAN Writer、BlueTooth Writer
config 特殊节点,既没有输入,也没有输出,通常仅用于完成对系统中某一功能参数的配置,配置信息初始指定,在流图中没有数据流过 暂无

pump有"水泵"的意思,这里将数据流形象的比作了水流。对于水流来讲,水泵就是"抽水"的节点,是整个水流的起点。在AWFlow中,pump节点作为整个数据流的起点,负责从外部接收数据或者自身产生一些数据,进而"流"入系统中。

# 1.2.3 节点id

在AWFlow中,每个节点都具有一个唯一ID,作为节点的唯一标识,以区分不同的节点。以图1.5为例,可以为各个节点指定一个唯一id,示意图详见下图。

图1.9 每个节点都具有一个唯一ID
图1.9 每个节点都具有一个唯一ID

为了便于id的展示,图中各个节点以类图的形式(矩形框)进行呈现。在实际应用中,为确保唯一性,id通常由工具自动生成。

# 1.2.4 节点的消费者

消费者是指某一节点的后继节点。当前节点把数据处理完毕后,数据将继续流向其消费者,消费者继续处理,然后继续传递,以此形成数据流。

例如,在图1.9中,BlueTooth Reader的消费者为Protobuffer Decoder,Protobuffer Decoder的消费者为Settings和Segment。

由于节点ID具有唯一性,因此,在AWFlow中,使用节点ID来表示消费者关系。例如,BlueTooth Reader的消费者为"8",Protobuffer Decoder的消费者为"6,9"。