1. 抽象协议AbstractProtocol
抽象协议AbstractProtocol定义CommManager与协议之间的接口。AbstractProtocol中的一些属性(如enabled)用于设置是否能够处理数据。
processData是AbstractProtocol中定义的回调函数,当设备中有数据返回时,数据将会被交给该方法进行处理。
若该方法返回true,则表明当前处理数据的协议已经找到相应的数据并且不需要将数据再交给其它协议处理,CommManager便会停止向其它协议发送这些数据;
若该方法返回为false,表明当前处理数据的协议并未找到相应的数据,并且希望交由其它协议进行处理,此时Com会继续转发这些数据。
lastQueryFailed 是在查询失败时由协议实现类进行处理的回调函数。
AbstractProtocol(生产者)实例将会维护各自的命令队列,CommManager(消费者)在完成一次查询后拉取AbstractProtocol(生产者)生成的指令。因此CommManager会根据指令消耗的速度决定是否应该进行下一次查询。有关生产者和消费者的更多信息,可以查看 https://www.cnblogs.com/charlesblc/p/6045238.html。
2. 通讯器构造工厂
CommFactory是通讯器的构造工厂类,要实例化某个AbstractComm的通讯器实现类,需要通过addComm方法将通讯器注册到CommFactory中,工厂会保存各个通讯器的实现类对象,在构造之后进行切换会尝试找出已经构造过的对象,避免重复构造通讯器对象。
CommManager中可用的串口实现类对象由CommFactory进行例化,通过createComm方法将参数传递给工厂类的实例化方法,获取可用的实现类对象指针。
Comm层作为库,暴露给其它代码使用的部分主要是CommManager,因此要在程序运行时改变串口工作模式或切换成网络接口,调用 CommManager::resetMode 方法即可。
若该模块内部已经有实现好的通讯器,则可以调用 CommManager::resetMode(const QString &type, const bool halfDuplex),根据类型和全/半双工模式选择特定的通讯器,若要使用扩展的自定义通讯器,可以使用 CommManager::resetMode(const QString &className) 传入具体的类名选择通讯器。
如果需要添加新的通讯器,创建一个继承于AbstractComm的子类(构造函数需要用Q_INVOKABLE修饰),通过addComm方法将元对象和描述信息添加到工厂类中,之后可以传递类名来构造其它的通讯器。
构造工厂的使用使得CommManager只用传递类名或通讯器类型即可获得通讯器对象,从而使CommManager的功能只需要对通讯器对象进行管理和收发数据,简化CommManager 的实现。
3. 虚拟串口示例(待完善)
虚拟串口VirtualCom是为了方便上位机单独进行调试而编写的AbstractCom的子类。以虚拟串口为例,介绍如何继承实现AbstractCom的子类,以设计新模式的串口。
虚拟串口VirtualCom的接口:
如图所示,VirtualCom的init(), close(), openComm, writeCmd, setCommProperty, query, onRead等方法都是继承自AbstractCom的接口(在方法声明后增加了 Q_DECL_OVERRIDE宏,即override 关键字)。
init()方法中进行成员变量的初始化(多线程中的QIODevice只能在创建其实例的线程中进行操作,否则在运行时会警告/报错),需要在该方法中可以实例化QTimer,QSerialPort或QTcpSocket等对象(这些对象不能在构造函数中实例化)。
虚拟串口中没有使用到串口对象,因此不需要实例化串口/QIODevice对象,这里的init方法中实例化timer对象并连接相应的信号和槽。
openComm方法用于打开设备(串口/Socket),由于虚拟串口中没有串口对象,这里只修改设备状态即可。打开虚拟串口后,timer开始计时。
打开串口后即可调用write写入命令:一般该方法只需要传递参数给query函数执行即可,但半双工模式下,需要保存指令,待线路空闲时发送。
若接受到命令后可以立即查询,那么需要通过QMetaObject::invokeMethod静态方法在子线程中运行(以致访问串口/Socket对象时不会出现错误)。虚拟串口中的该方法就是将cmd参数传递给query方法通过子线程执行。
要使用虚拟串口发送一些特定的数据,需要修改VirtualCom的实现,在实际使用过程中比较麻烦。目前可用的解决方法有两个:
- 把虚拟串口编译成动态链接库,在不同的程序中使用不同的动态库文件即可,但是这样做实际上在开发过程中变得非常麻烦。
- 自定义xml文件格式,从指定的xml文件中读取信息,根据信息来决定发送的数据内容,这样的话就能够使VirtualCom作为库文件与实际的信息回复实现分离。如果要这样实现,首先要定义xml文件的格式,然后编写相应的解析器,并将解析器解析的结果传递给VirtualCom对象,当接收到来自上层的指令后根据解析器的结果进行响应。这样做实际上是在模拟下位机收发数据的行为,看上去工作量较大,但比较实际的下位机应用应该是简单不少了。