电力协议处理框架C++版(一)
博主这几年一直从事于电力行业,很多接触这个行业的小伙伴也清楚接触最多的有modbus 104 61850 103等。在一个稍微有点规模的公司,大家开发都应该是基于公司的框架去做,比如基于这个框架进行报文的接收发送,打印等。但是很少有人能从事到这个框架的开发或者学习到这个框架是如何设计的,博主准备写一系列相关的文章,结合我开源的电力协议处理框架一起剖析剖析。本系列文章适合电力行业开发者,且有一定代码经验的。
一、项目的开源地址
https://github.com/LinuxZQ93/DataEngine.git
基于此框架,用户可以迅速的开发各种协议插件,提高开发效率及稳定性。在对这个项目的讲解中呢,也会带一些设计模式或者理念的讲解。
二、框架的设计结构
学习一份代码,最好的方式,是以面带点,首先从战略上俯视,了解其功能,现在功能上我们已经知道了,是一份电力协议的通用开发框架。第二,了解其设计架构了,该框架是基于C++语言开发的,那先了解的就是其继承类图了,如下:
此类图展示了该框架的静态结构,因为篇幅原因,有些依赖类并没有全部画上,仅画了核心部分。图片整体有些不清晰,下面讲一下每个类的作用的时候会局部贴上
首先是框架的前三层
IDataEngine:框架的接口层,提供了获取配置,发送接收报文,已经更新获取数据等接口,后期的规约插件开发都需要依赖此接口进行
CDataEngine:IDataEngine接口的实现类
CConfigParse:配置解析类,整个工程基于json配置驱动的,此类完成对配置的解析并提供接口
CTaskCenter:任务中心类,此类依赖于各个类完成整个框架的调度,是核心。比如配置解析的开始,通道的创建,规约插件的创建等
接着看任务中心的依赖:
CChannelManager:通道管理类,负责各个通道的创建,比如串口,TCP,UDP等,并且也负责通道和消息处理的桥接
CProtocolManager:规约管理类,负责各个规约插件的调度,根据配置加载各个规约的动态库,然后创建相应的线程进行处理
CDataStorage:数据存储类,负责遥信 遥测包括一些特殊数据的存储,此类的存储数据类型不限制于遥信必须为bool量,遥测必须为浮点,可以根据项目的需求,存储任意类型,比如字符串,64位整形等
CLogManager:日志管理类,负责日志的打开关闭,及存储功能
接下来通道管理类
IChannel:通道接口类,包含了消息处理回调,发送接收报文等接口
CChannelBase:通道基类,这里实现了一些通道公共的一些处理
CRS485Channel:485处理类,封装了CSerial类,并调用其方法完成相应的消息发送回调处理等
CSerial:实际的串口处理类,对串口的一些参数配置,波特率,停止位等,并留有发送接收接口
CChannelMsgHandle:消息处理类,所以通道的消息都会进入此类进行通用化处理
CTCPServerChannel:TCP服务端,作用同CRS485Channel类
CTcpServer:TCP服务端的实际实现,作用同CSerial类
其他通道类也都是同样的处理和作用,篇幅原因就不展开了
接着看规约管理类和其他
IProtocol:规约接口类,所有的规约实现,需继承此类,定义了初始化、处理、定帧、控制等接口
CCollModbus,CTransIEC104等:具体的规约插件实现库,都可以独立编写成动态库,根据配置动态加载。
CLog:具体的日志实现类
通过以上分析,可以看出,用户后期插件的编写只需要关注IDataEngine和IProtocol两个接口。
IDataEngine.h
#ifndef __DATAENGINE_IDATAENGINE_H__
#define __DATAENGINE_IDATAENGINE_H__#include "Component/IUnknown.h"
#include "Component/ComponentMacroDef.h"
#include <string>
#include <mutex>
#include "json/json.h"
#include "Data/ValueWrap.h"
#include "Function/Function.h"namespace engine {enum FILE_TYPE
{F_DIR = 0,F_REG,F_UNKNOWN
};enum CfgType {ATTRIBUTE,CHANNEL,POINTTABLE,PROTOCOL,
};enum DATA_TYPE {SIGNAL,MEASURE,PULSE,CONTROL,PARAM,UNKNOWN,
};enum FIX_FRAME_VALUE {INVALID,COMPLETE,INCOMPLETE,
};enum CTRL_STATUS {OFF,ON,
};enum CONNECT_STATUS {CONNECT,DISCONNECT,
};/*** @brief 连接回调函数* void 返回值* CONNECT_STATUS 连接状态* int socket fd*/
using connectFunc = TFunction<void, CONNECT_STATUS, int>;
/*** @brief 消息处理回调函数* void 返回值* int socket fd* const char * 接收的报文信息* int 报文长度*/
using msgFunc = TFunction<void, int, const char*, int>;class IDataEngine : public base::IUnknown {
SIMPLE_DEF_I(DataEngine, "DataEngine")public:virtual bool init() { return true; }virtual bool start() { return true; }virtual bool stop() { return true; }virtual bool destroy() { return true; }public:/*** @brief 获取设备的配置信息** @param name 设备名称* @param type 配置类型* @param cfg 配置信息* @return 返回值*/virtual bool getDevicecfg(const std::string &name, CfgType type, Json::Value &cfg) = 0;/*** @brief 获取转发配置信息** @param name 转发名称* @param type 配置类型* @param cfg 配置信息* @return 返回值*/virtual bool getTranscfg(const std::string &name, CfgType type, Json::Value &cfg) = 0;/*** @brief 注册连接状态函数** @param name 设备或转发名称* @param func 回调函数* @return true 成功* @return false 失败*/virtual bool attachConnectStatusFunc(const std::string &name, const connectFunc &func) = 0;/*** @brief 注册消息回调函数,这个是异步调用,注册后,recvFrame函数不可用,消息与发送异步处理。仅用于tcpServer* 多客户端的情况下** @param name 转发或设备名称* @param func 消息回调* @return true 成功* @return false 失败*/virtual bool attachMsgAsyncFunc(const std::string &name, const msgFunc &func) = 0;/*** @brief 关闭连接,一般仅使用与tcpServer,因为存在多个连接** @param name 转发或设备名称* @param fd 描述符* @return true 成功* @return false 失败*/virtual bool closeLink(const std::string &name, int fd = -1) = 0;/*** @brief 发送报文** @param name 设备名称* @param sendBuf 发送内容* @return 返回值*/virtual bool sendFrame(const std::string &name, const std::string &sendBuf) = 0;/*** @brief 精确某个通道发送函数,一般tcp server使用,因为存在多个连接,其余单连接不需要调用** @param fd 通道描述符* @param name 转发或设备名称* @param sendBuf 发送缓冲区* @return true 成功* @return false 失败*/virtual bool sendFrame(int fd, const std::string &name, const std::string &sendBuf) = 0;/*** @brief 接受报文** @param name 设备名称* @param recvBuf 接受内容* @param timeOut 超时时间(ms)* @return 返回值*/virtual bool recvFrame(const std::string &name, std::string &recvBuf, int timeOut) = 0;/*** @brief 同步发送报文并接受返回报文,用于不仅数据处理的协议,还有遥控遥调协议的设备。** @param name 设备名称* @param mutex 锁* @param sendBuf 发送缓冲区* @param recvBuf 接受报文* @return true 成功* @return false 失败*/virtual bool sendSyncRecv(const std::string &name, std::mutex *mutex, DATA_TYPE type, const std::string &sendBuf, std::string &recvBuf, int timeOut) = 0;/*** @brief 打印发送报文信息** @param name 设备名称* @param frameBuf 内容*/virtual void printSendFrame(const std::string &name, DATA_TYPE type, const std::string &frameBuf) = 0;/*** @brief 打印发送报文信息** @param name 设备名称* @param strType 内容名称* @param frameBuf 内容*/virtual void printSendFrame(const std::string &name, const std::string &strType, const std::string &frameBuf) = 0;/*** @brief 打印接受报文信息** @param name 设备名称* @param frameBuf 内容*/virtual void printRecvFrame(const std::string &name, DATA_TYPE type, const std::string &frameBuf) = 0;/*** @brief 打印接受报文信息** @param name 设备名称* @param strType 内容名称* @param frameBuf 内容*/virtual void printRecvFrame(const std::string &name, const std::string &strType, const std::string &frameBuf) = 0;/*** @brief 更新设备点值** @param name 设备名称* @param type 数据类型* @param pointId 点号* @param data 数据内容* @return 返回值*/virtual bool updateValue(const std::string &name, DATA_TYPE type, int pointId, const base::ValueWrap &data) = 0;/*** @brief 更新多个设备点值** @param name 设备名称* @param type 数据类型* @param pointId 点号* @param vecData 数据内容* @return 返回值*/virtual bool updateMultiValue(const std::string &name, DATA_TYPE type, int pointId, const std::vector<base::ValueWrap> &vecData) = 0;/*** @brief 更新设备点值(条件更新)** @param name 设备名称* @param type 数据类型* @param condition 条件* @param data 数据内容* @return 返回值*/virtual bool updateValue(const std::string &name, DATA_TYPE type, const std::pair<std::string, base::ValueWrap> &condition, const base::ValueWrap &data) = 0;/*** @brief 更新多个设备点值(条件更新)** @param name 设备名称* @param type 数据类型* @param condition 条件* @param vecData 值* @return 返回值*/virtual bool updateMultiValue(const std::string &name, DATA_TYPE type, const std::pair<std::string, base::ValueWrap> &condition, const std::vector<base::ValueWrap> &vecData) = 0;/*** @brief 获取设备点的值** @param name 设备名称* @param type 数据类型* @param pointId 点号* @param data 返回数据* @return 返回值*/virtual bool getValue(const std::string &name, DATA_TYPE type, int pointId, base::ValueWrap &data) = 0;/*** @brief 获取多个设备点的值** @param name 设备名称* @param type 数据类型* @param pointId 点号* @param num 数目* @param vecDataNew 返回的数据* @return 返回值*/virtual bool getMultiValue(const std::string &name, DATA_TYPE type, int pointId, int num, std::vector<base::ValueWrap> &vecDataNew) = 0;/*** @brief 获取设备点值(条件获取)** @param name 设备名称* @param type 数据类型* @param condition 条件* @param data 返回数据* @return 返回值*/virtual bool getValue(const std::string &name, DATA_TYPE type, const std::pair<std::string, base::ValueWrap> &condition, base::ValueWrap &data) = 0;/*** @brief 获取多个设备点值(条件获取)** @param name 设备名称* @param type 数据类型* @param condition 条件* @param num 数目* @param vecDataNew 返回数据* @return 返回值*/virtual bool getMultiValue(const std::string &name, DATA_TYPE type, const std::pair<std::string, base::ValueWrap> &condition, int num, std::vector<base::ValueWrap> &vecDataNew) = 0;/*** @brief 转发获取点值** @param name 转发名称* @param type 数据类型* @param transId 转发点号* @param data 返回数据* @return 返回值*/virtual bool getTransValue(const std::string &name, DATA_TYPE type, int transId, base::ValueWrap &data) = 0;/*** @brief 获取多个转发点值** @param name 转发名称* @param type 数据类型* @param transId 转发点号* @param num 数目* @param data 返回数据* @return 返回值*/virtual bool getTransMultiValue(const std::string &name, DATA_TYPE type, int transId, int num, std::vector<base::ValueWrap> &data) = 0;/*** @brief 控制命令(选择)由转发任务调用** @param name 转发名称* @param transId 转发点号* @param status 控制状态* @return 返回值*/virtual bool controlSelect(const std::string &name, int transId, CTRL_STATUS status) = 0;/*** @brief 控制命令(执行)由转发任务调用** @param name 转发名称* @param transId 转发点号* @param status 控制状态* @return 返回值*/virtual bool controlExecute(const std::string &name, int transId, CTRL_STATUS status) = 0;/*** @brief 控制命令(取消)由转发任务调用** @param name 转发名称* @param transId 转发点号* @param status 控制状态* @return 返回值*/virtual bool controlCancel(const std::string &name, int transId, CTRL_STATUS status) = 0;/*** @brief 遥调命令(选择)由转发任务调用** @param name 转发名称* @param transId 转发点号* @param data 下发值* @return 返回值*/virtual bool paramSelect(const std::string &name, int transId, double data) = 0;/*** @brief 遥调命令(执行)由转发任务调用** @param name 转发名称* @param transId 转发点号* @param data 下发值* @return 返回值*/virtual bool paramExecute(const std::string &name, int transId, double data) = 0;/*** @brief 遥调命令(取消)由转发任务调用** @param name 转发名称* @param transId 转发点号* @param data 下发值* @return 返回值*/virtual bool paramCancel(const std::string &name, int transId, double data) = 0;/*** @brief 表达式计算** @param expr 表达式,支持加减乘除与或非等,传入格式为<温湿度2#0> + <温湿度2#1>尖括号内为设备名称与点号,以井号分隔* @return double*/virtual bool getExprValue(DATA_TYPE type, const std::string &expr, base::ValueWrap &data) = 0;};
}#endif /* __DATAENGINE_IDATAENGINE_H__ */
IProtocol.h
#ifndef __DATAENGINE_SRC_PROTOCOL_IPROCOTOL_H__
#define __DATAENGINE_SRC_PROTOCOL_IPROCOTOL_H__#include <vector>
#include <string>
#include <mutex>
#include "Export/Export.h"
#include "json/json.h"#include "IDataEngine.h"#undef SIMPLE_DEF_PROTOCOL#define SIMPLE_DEF_PROTOCOL(ProtocolName) \
public: \class ProtocolName##ProtocolFactory : public IProtocolFactory { \public: \ProtocolName##ProtocolFactory(const std::string &taskName) : IProtocolFactory(taskName) { \m_protocol = new ProtocolName(); \if (!registerProtocol(taskName)) { \warnf("register protocol %s failed\n", taskName.c_str());\} \else { \infof("register protocol %s success\n", taskName.c_str());\} \} \virtual ~ProtocolName##ProtocolFactory() { } \};namespace engine {class DLL_EXPORT IProtocol {
public:IProtocol();virtual ~IProtocol();
public:/// 规约工厂创建类class DLL_EXPORT IProtocolFactory {public:IProtocolFactory(const std::string &taskName);virtual ~IProtocolFactory();bool registerProtocol(const std::string &taskName);protected:IProtocol *m_protocol;};
public:/*** @brief 初始化,此接口在插件运行的时候仅进入一次** @param cfgValue 规约配置信息* @return true 成功* @return false 失败*/virtual bool init(const Json::Value &cfgValue) = 0;/*** @brief 处理接口,此接口由框架定时调用,在这个接口内,用户完成报文的发送接收解析,数据更新等操作不要在这个接口内用while(1) for(;;)等方式,会引起线程超时** @param vecDeviceName 设备名称的组合* @return true 成功* @return false 失败*/virtual bool process(const std::vector<std::string> &vecDeviceName) = 0;/*** @brief 定帧函数,在调用接受报文接口时自动调用,在这个接口内完成报文的有效性判断,是未完成、有效还是无效** @param devName 设备名称* @param buf 报文缓冲区* @param len 报文长度* @param validLen 有效报文的长度,系统会根据这个字段进行截取* @return FIX_FRAME_VALUE 返回状态*/virtual FIX_FRAME_VALUE fixFrame(const std::string &devName, const char *buf, int len, int &validLen) = 0;/*** @brief 控制选择接口,通常转发插件主动调用,采集插件被动调用** @param devName 设备名称* @param pointId 点号* @param status 控制状态* @return true 成功* @return false 失败*/virtual bool controlSelect(const std::string &devName, int pointId, CTRL_STATUS status){return true;}/*** @brief 控制执行接口,调用时机同上** @param devName 设备名称* @param pointId 点号* @param status 控制状态* @return true 成功* @return false 失败*/virtual bool controlExecute(const std::string &devName, int pointId, CTRL_STATUS status) {return true;}/*** @brief 控制取消接口,调用时机同上** @param devName 设备名称* @param pointId 点号* @param status 控制状态* @return true 成功* @return false 失败*/virtual bool controlCancel(const std::string &devName, int pointId, CTRL_STATUS status){return true;}/*** @brief 遥调选择接口,调用时机同上** @param devName 设备名称* @param pointId 点号* @param data 下发值* @return true 成功* @return false 失败*/virtual bool paramSelect(const std::string &devName, int pointId, double data){return true;}/*** @brief 遥调执行接口,调用时机同上** @param devName 设备名称* @param pointId 点号* @param data 下发值* @return true 成功* @return false 失败*/virtual bool paramExecute(const std::string &devName, int pointId, double data){return true;}/*** @brief 遥调取消接口,调用时机同上** @param devName 设备名称* @param pointId 点号* @param data 下发值* @return true 成功* @return false 失败*/virtual bool paramCancel(const std::string &devName, int pointId, double data){return true;}/*** @brief 获取组件名称接口** @return std::string*/virtual std::string getProtocolName() = 0;public:void setLockSource(std::mutex *mutex){m_mutex = mutex;}protected:IDataEngine *m_pDataEngine;std::mutex *m_mutex;
};}#endif /* __DATAENGINE_SRC_PROTOCOL_IPROCOTOL_H__ */
通过框架的统一管理,可以方便的对各个规约库进行统一,比如从采集到转发数据源的共享,点表的挑选等。同时也可以规范团队内成员的代码开发,提高整体质量和效率。
本篇主要是对框架的整体介绍,后续的文章中,会深入细节讨论