第三方控制器的编写

FsuOS侧重于一个采集控制系统,有时候,我们想要做一个复杂的控制逻辑,比如一个大型机柜,接了各种外设,我们有自己的控制逻辑,这时候,我们就可以编写控制器插件来实现。

使用方式:

假如我们实现了一个水冷空调控制器,waterac.so
我们需要将其放置到/FsuOS/3rdController/文件夹
此时我们编辑SMDDevice.conf

controller=waterac

就指定加载了此控制器。

注意: 当前只能设置一个控制器,其实可以支持设置多个,工作起来类似filter,一个接一个,但是没有看到使用价值。

waterac.so怎么编写的?

class SMD3rdController
{
public:
	SMD3rdController(std::shared_ptr<SMDDeviceIoControl> driverManager, boost::asio::io_service& ioService, std::vector<boost::shared_ptr<device>> &devList);
	~SMD3rdController();
    
	virtual bool SendDeviceData(uint32_t data_id, int dev_type, int c_type, const uint8_t *data, int len) = 0;

	virtual int DeviceIoControl(const int64_t data_id, const int32_t action, const std::string& parameter) = 0;

	virtual void start() = 0;
protected:
    std::shared_ptr<SMDDeviceIoControl> gDriverManager;
    boost::asio::io_service& gIoService;
};

首先所有的控制器都继承于SMD3rdController基类,构造函数SMD3rdController会传递3个参数进来
driverManager: 就是SMDDevice本地提供的接口
ioService: SMDDevice提供的一个ioService,用的是线程池里的线程,可以直接用
devList: SMDDevice.db里device表的记录

device表来自DeviceDAO.hxx

#pragma db object
class device
{
public:
	friend class odb::access;
	device(): id(0), data_id(0) {}

#pragma db id auto
	unsigned int id;
	unsigned int data_id;
	int dev_type;
	int  port;

#pragma db type("varchar(128)")
	std::string device_name;
    
#pragma db type("varchar(30)")
	std::string model;
    
#pragma db type("varchar(20)")
	std::string device_id;
    
#pragma db type("varchar(500)")
	std::string extra_para;

#pragma db type("TEXT")
	std::string threshold_setting;
};

控制器拿到后可以根据设备名称对实际设备进行关联

WaterAC::WaterAC(std::shared_ptr<SMDDeviceIoControl> driverManager, boost::asio::io_service& ioService, std::vector<boost::shared_ptr<device>> &devList):SMD3rdController(driverManager, ioService, devList)
{
	for(auto &devObj : devList) {
        std::cout<<"device_name:"<<devObj->device_name<<" data_id:"<<devObj->data_id<<std::endl;
        if(devObj->device_name == "1#水泵"){
            bump_data_id_[0] = devObj->data_id;
        }else if(devObj->device_name == "2#水泵"){
            bump_data_id_[1] = devObj->data_id;
        }else if(devObj->device_name == "3#水泵"){
            bump_data_id_[2] = devObj->data_id;
        }
    }
}

SendDeviceData(uint32_t data_id, int dev_type, int c_type, const uint8_t *data, int len) 是SMDDevice每采集完一个数据,都会调用一次,其中data_id就是设备的data_id,dev_type是0 DI,1 AI,2 SP, c_type一般没啥用,就是写驱动的时候那个RT_XXX的值,有些系统需要给驱动编号,后面的data就是&cData, len就是sizeof(cData),自己解析回来。

DeviceIoControl(const int64_t data_id, const int32_t action, const std::string& parameter) 是控制器配套的界面调用功能,比如开机,下发下来,实际开机要对各个外接设备做控制,此时data_id一般会传0,由于SMDDevice对data_id不等于0的会直接调用对应设备的DeviceIoControl,0的会调用第三方控制器。

start() 这个就是SMDDevice让控制器做默认的启动,自由发挥,比如可以开线程。

现在控制器可以拿到数据,可以接收下发的命令,然后怎么控制外接设备,

class SMDDeviceIoControl
{
public:
    virtual int DeviceIoControl(uint32_t data_id, int action, std::string parameter, int retryTime = 3) = 0;
    virtual bool SendSP(unsigned int data_id, int c_type, const unsigned char* data, size_t len, bool bSave = false) = 0;
    virtual void LockSpPort(uint32_t data_id, bool action) = 0; 
    virtual bool AddEvent(unsigned int data_id, const std::string& signal_name, const std::string& subject) = 0;
};

DeviceIoControl 调用外接设备的控制接口。
SendSP 可以发送一个汇总的cData到缓存,供界面显示内部状态。
LockSpPort 由于SMDDevice调用串口的正常工作逻辑是一直在招测,偶发控制,控制时需要复杂的抢断,并且控制一结束,SMDDevice又会立即招测,我们可以通过locksp的方法, 这样可以连续下发控制命令,不会中间被SMDDevice加入招测命令。
AddEvent SMDDevice添加事件,本身也是告警记录。

通过以上接口,控制器程序就完成了数据获取,命令接收,外部设备控制,对外暴露数据的功能,控制器程序只需要关心自己的逻辑,不要考虑外接设备协议驱动层面的动作,SMDDevice会保证采集和控制稳定可靠,大大提高了工作效率。

后面我们会在SDK2中提供一个完整的控制器工程,用户可以基于工程进行修改。