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中提供一个完整的控制器工程,用户可以基于工程进行修改。