驱动编写基础

我们推荐使用代码生成工具进行驱动的编写,并在生成的代码上进行微调。

虽然FsuOS的驱动分为4大类,但是不同类型间还是有共同的部分

代码的组织,一般分为3个文件:

  • XXXX_interface.h 用于定义本驱动数据结构
#ifndef XXXX_INTERFACE_H
#define XXXX_INTERFACE_H
#include "common_interface.h"

#pragma pack(push)
#pragma pack(1)

struct XXXX_Data_t {
	unsigned int data_id;
	/*uint8_t value;
    数据成员定义 */
	tele_c_time update_time;
};

#pragma pack(pop)
#endif

这里面要注意的是XXXX_Data_t这个结构体,最开始是个data_id,这个是网页创建设备时候生成的唯一ID,tele_c_time 是本次数据的保存时间。

  • XXXX.h 本驱动的头文件
#ifndef XXXX_H
#define XXXX_H
#include "SMDDIDevice.hpp"
#include "XXXX_interface.h"
#include "UniDataDevice.h"

#define RT_XXXX  5043
class XXXX: public UniDataDevice<XXXX_Data_t, SMDDIDevice, RT_XXXX>
{
public:
	XXXX();
	bool InitSetting(const Json::Value &settingRoot) override;
	bool process(char di, bool first_time);
	void RunCheckThreshold() override;
	int DeviceIoControl(int ioControlCode, const void* inBuffer, int inBufferSize, void* outBuffer, int outBufferSize, int& bytesReturned) override;
};

PLUMA_INHERIT_PROVIDER(XXXX, SMDDIDevice);
#endif 

根据本设备的分类,开始要选择include SMDDIDevice.hpp/SMDAIDevice.hpp/SMDSPDevice.hpp/SMDSocketDevice中的一个。
然后include第一步的XXXX_interface.h
然后include我们的模板类"UniDataDevice.h",UniDataDevice.h是封装了很多重复的代码,大家可以读这个代码了解里面是如何工作的。
定义RT_XXXX,这个当有程序使用SC的C接口接收到XXXX_Data_t的数据时,来区分是哪个型号的设备数据,平时没有用到的地方。
然后从UniDataDevice继承。
PLUMA_INHERIT_PROVIDER声明XXXX为SMDDIDevice的设备。

下面的4个函数都是我们最常用的:

  1. InitSetting 用来接收逻辑参数并解析。
  2. process 这个是FsuOS App当接收到新值时,传入驱动进行处理。 对于AI,SP,Socket驱动,process的参数有所不同。
    SP驱动针对比较常见的电总类和Modbus类设备,会集成于SPModbus和SPPMBusProtocol类,这时会使用process_payload函数处理数据,并且数据是核验过的,可以直接使用。
  3. RunCheckThreshold 本函数是写告警判断代码
  4. DeviceIoControl 本函数是接收远程控制命令的。
  • XXXX.cpp 本驱动的cpp文件
#include "XXXX.h"

#include "UniDataDevice.cpp"
XXXX::XXXX()
{
	device_type_ = "xxxx";
}

bool XXXX::InitSetting(const Json::Value &settingRoot)
{
	cData.data_id = data_id_;
	if(settingRoot["appSetting"] != Json::nullValue &&
        settingRoot["appSetting"].type() == Json::objectValue) {
        if(settingRoot["appSetting"]["xx"] != Json::nullValue) {
            xx_ = settingRoot["appSetting"]["xx"].asInt();
        }
	}
	return UniDataDevice<XXXX_Data_t, SMDDIDevice, RT_XXXX>::InitSetting(settingRoot);
}

bool XXXX::process(char di, bool first_time)
{
    //进行处理,处理完成后,调用RoundDone进行告警判断和数据保存
    RoundDone();
    return true;//false
}

void XXXX::RunCheckThreshold()
{
}

int XXXX::DeviceIoControl(int ioControlCode, const void* inBuffer, int inBufferSize, void* outBuffer, int outBufferSize, int& bytesReturned)
{
}
#ifdef USE_SEPERATE_DRIVER

extern "C"
std::vector<std::shared_ptr<Provider>> get_providers()
{
	std::vector<std::shared_ptr<Provider>> providerVec;
	providerVec.push_back(std::make_shared<XXXXProvider>());
	return std::move(providerVec);
}
#endif
  1. 最重要的device_type_这儿要小写,一般是XXXX全小写,这个就是驱动文件的名字。
  2. InitSetting解析逻辑参数xx放到xx_变量里。
  3. process 返回是本设备的实际值,process的返回值主要是为了兼容老代码。
  4. RunCheckThreshold 在本轮采集结束前调用,主要用于处理告警,DI/AI变动推送等 告警的函数有2种:
    1. CheckThreshold 需要配合网站设置的告警规则,才能起作用,适合用户需要设置阈值的场景
    2. CheckThresholdBool 只要判断true就出告警,适合设备自身协议的场景
      后面我们专门对告警模块进行讲解。
  5. DeviceIoControl
  6. get_providers 将XXXXProvider返回给FsuOS App的加载程序。

驱动中常用函数: 在驱动中,有一些常用函数:

  1. float ReadAi(int index) 可读取到index的AI的值,需要注意的是AI的值有以下可能:
    1. 0-5V电压值,需要换算为设备实际值
    2. 0-10V电压值,需要换算为设备实际值
    3. 0-20ma电流值,需要换算为设备实际值
    4. 设备的实际值,比如540V,22.5度
      这是由于不同的FSU对AI通道输出不同的值。
  2. int ReadDi(int index) 可读取到index的DI的值
  3. bool SendDoData(int num, char data), 向num的DO通道发送data值,以控制DO

具体参考 SMDVDevice.h 文件

DeviceIoControl 的返回值

= 0 的值都是驱动返回的,以前比较乱,我们不考虑错误的情况,只要>=0都认为是对的
实际可以细分为:
0 代表设备执行命令成功
0 个别设备执行出问题,返回了错误代码
< 0 代表 SMDDevice调用驱动有问题,没找到设备的可能大,一般返回-1 代表调用设备后,获取执行结果超时,原因主要是设备通信异常,命令错导致不返回
-3 代表没有这个设备
-5 代表有这个设备,但是没有加载驱动,即没有驱动支持命令