Socket型驱动编写

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

在最开始设计时,主要是针对摄像头和网络门禁这种使用私有协议的网络设备,需要让驱动可以和设备进行数据交互,于是创建了Socket型驱动这个分支。

但在后续的应用中,我们就发现Socket型驱动其实和串口驱动,在协议解析上类似,但是编写方法不同,造成维护不一致。

并且后续我们又加入了 “虚拟网络串口” 这个功能,虚拟网络串口 可以将一个IP:port模拟成FsuOS系统中的一个串口,然后串口驱动就可以正常的收发,只是开关串口实际没有效果。

当前Socket型驱动,主要用于那些需要调用第三方SDK的的驱动,比如使用TCP-Modbus的驱动。

注意: 要区分协议的工作模式,异步模式的协议,程序调度都是由SMDDevice或者SMDServer控制的,这种驱动,无论在设备上运行,还是在高性能高并发的服务端运行,很难出现性能问题。而同步模式工作的驱动,会使用调度器的RefreshDevice时间,会造成整体设备数据刷新变慢,甚至驱动程序有死锁的情况还会造成整体采集异常,但是同步模式程序易于理解,符合人思维习惯,我调用我拿到结果。由于DI,AI,SP驱动采用FSUOS标准接口本身就工作在异步模式,但是Socket驱动,主要依靠用户写代码调用其他库,需要用户格外注意。

用户虽然可以通过std::async将代码段异步化,但是实际运行情况,由于当前大多数二代FSU都是单核arm9 500MHz以下,开async后反而观测到性能降低,可见多线程对单核cpu会造成额外的负担,低配CPU比较尴尬,在配置较高的设备上没有感到影响,用户自己把握。

ModbusTcp协议支持
FsuOS SDK中集成了libmodbus开发库,用户可以直接使用modbus-tcp相关代码

#include <modbus/modbus.h>
#include <modbus/modbus-tcp.h>


class UnicomLL11: public UniDataDevice<UnicomLL11_Data_t,SMDSocketDevice, RT_UNICOMLL11>
类可以从SMDSocketDevice直接继承


在RefreshStatus里直接请求数据

pCtx = modbus_new_tcp(ip_.c_str(), port_);
int nRet = modbus_connect(pCtx);
if(-1 == modbus_read_registers(pCtx, 27-1, 12, regs))

BacNet协议支持 BacNet在楼宇自动化对接中比较常见,水泵,冷循环设备多提供此接口,FsuOS在2024-10-17号以后的版本提供此协议的支持。 使用方法:

#include "SMDBacNetDevice.hpp"

class BTBacnetLeng: public UniDataDevice<BTBacnetLeng_Data_t,SMDBacNetDevice, RT_BTBacnetLeng>
类需要从SMDBacNetDevice继承

逻辑参数:需要提供:ip, port:47808默认, device_id:对方设备ID
读取数据:
根据BACNET的规范,可以读,很多类型,一般设备导出的数据点表会注明类型
OBJECT_ANALOG_INPUT = 0,
    OBJECT_ANALOG_OUTPUT = 1,
    OBJECT_ANALOG_VALUE = 2,
    OBJECT_BINARY_INPUT = 3,
    OBJECT_BINARY_OUTPUT = 4,
    OBJECT_BINARY_VALUE = 5,

10005 是属性ID,即objectId, PROP_PRESENT_VALUE 是要读取属性的值

nt r =  Send_Read_Property_Request_Address((BACNET_OBJECT_TYPE)0, 10005, PROP_PRESENT_VALUE);
if(r){
    int len = Receive();
    if(len)
    {
        //这个appValue.type.Real是如果返回值是float型,就用这个读appValue就是从上面命令读取到的值
        cData.v0[i] = appValue.type.Real;
        std::cout<<"get 0 "<<v0addr[i]<<" value:"<<cData.v0[i]<<std::endl;
    }else{
        return false;
    }
}else{
    return false;
}

SNMP协议支持
FsuOS SDK中集成了net-snmp开发库,用户可以直接使用net-snmp相关代码,例如:snmp_sess_init,snmp_open,snmp_pdu_create,snmp_synch_response等
示例,海悟微模块是一款使用SNMP的智能设备,首先要包含net-snmp的头文件,使用我们提供的模板则已有包含代码.

#include "SMDSocketDevice.hpp"
#include "HaiWuWMK_interface.h"
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>

class HaiWuWMK: public SMDSocketDevice
{
public:
        HaiWuWMK();
        ~HaiWuWMK();
        bool Init(std::weak_ptr<SMDController> controller,unsigned int  data_id, const Json::Value& settingRoot) override;
        bool RefreshStatus() override;
        bool process_data(tcp::socket::native_handle_type fd, uint8_t *buffer, int size);
        void RoundDone();
private:
        netsnmp_session session, *ss = NULL;
    netsnmp_pdu    *pdu = NULL, *response = NULL;
    netsnmp_variable_list *vars;
        oid            root[MAX_OID_LEN] = {1,3,6,1,4,1};
        size_t          rootlen = 6;
        oid             name[MAX_OID_LEN];
    size_t          name_length;
        bool is_snmp_running_ = false;
        HaiWuWMK_Data_t  cData;

        boost::posix_time::ptime lastTime;
};

实现代码

HaiWuWMK::HaiWuWMK()
{
        device_type_ = "haiwuwmk";
        snmp_sess_init(&session);                        /* initialize session */
    session.version = SNMP_VERSION_2c;
    //session.peername = "194.109.65.33";
    session.community = (uint8_t*)"public";
    session.community_len = strlen((const char*)session.community);
    session.timeout = 10000000L;
    session.retries = 1;

}

bool HaiWuWMK::Init ( std::weak_ptr<SMDController> controller, unsigned int  data_id, const Json::Value& settingRoot )
{
    SMDVDevice::Init ( controller, data_id, settingRoot );
    //app settings
    if ( settingRoot["appSetting"] != Json::nullValue ) {
        if ( settingRoot["appSetting"].type() == Json::objectValue ) {
            if ( settingRoot["appSetting"]["ip"] != Json::nullValue ) {
                ip_ = settingRoot["appSetting"]["ip"].asString();
				session.peername = (char*)ip_.c_str();
            }
        }
    }
    return true;
}



bool HaiWuWMK::RefreshStatus()
{
	if(!is_snmp_running_)
	{
		is_snmp_running_ = true;
	}else{
		//要判断,多上卡住多长时间了
		boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
        boost::posix_time::time_duration diff = now - lastTime;
        if( diff.total_seconds() > 30) {
			//下一轮
                    printf("going to restart,will crash\n");
                    return false;
                        
        }else{
                    printf("abort going to restart,will crash\n");
			return false;
		}
		
	}
	std::cout<<"HaiWu "<<data_id_<<"RefrehsStatus"<<std::endl;
	//CheckThresholdWork([this]{
        std::async( std::launch::async,
	        [&]() {
		lastTime = boost::posix_time::second_clock::local_time();
                if (response){
                    snmp_free_pdu(response);
                }
                if(ss){
                    snmp_close(ss);
                }

		
		ss = snmp_open(&session);
		if (ss == NULL) {
			/*
			 * diagnose snmp_open errors with the input netsnmp_session pointer 
			 */
			snmp_sess_perror("snmpbulkwalk", &session);
			is_snmp_running_ = false;
			return false;
		}
                ss->timeout = 10000000L;
		memmove(name, root, rootlen * sizeof(oid));
		name_length = rootlen;
		
		int running = true;
		int  exitval = 0;
		int  status = STAT_ERROR;
		while(running)
		{
		        lastTime = boost::posix_time::second_clock::local_time();
			pdu = snmp_pdu_create(SNMP_MSG_GETBULK);
			pdu->non_repeaters = 1;
			pdu->max_repetitions = 100;    /* fill the packet */
			snmp_add_null_var(pdu, name, name_length);

			/*
			 * do the request 
			 */
			status = snmp_synch_response(ss, pdu, &response);
			if (status == STAT_SUCCESS && response != NULL) {

				if (response->errstat == SNMP_ERR_NOERROR) {
					/*
					 * check resulting variables 
					 */
					for (vars = response->variables; vars;
						 vars = vars->next_variable) {
						if ((vars->name_length < rootlen)
							|| (memcmp(root, vars->name, rootlen * sizeof(oid))
								!= 0)) {
							/*
							 * not part of this subtree 
							 */
							running = 0;
							continue;
						}
						//这个地方解析数据					
						std::string newVal;
						newVal.assign((const char*)vars->buf, vars->val_len);
						if(vars->name[6] == 11 || vars->name[6] == 12)
						{
							switch(vars->name[7])
							{
								case 1:
									cData.env[vars->name[6] - 11].online_status = atof(newVal.c_str());
									break;
								case 2:
									cData.env[vars->name[6] - 11].temperature = atof(newVal.c_str());
									break;
								case 3:
									cData.env[vars->name[6] - 11].humid = atof(newVal.c_str());
									break;
							}						
						}else if(vars->name[6] == 50)
						{
							switch(vars->name[7])
							{
								case 1:
									cData.io_status = atof(newVal.c_str());
									break;
								case 2:
									cData.smoke[0] = atof(newVal.c_str());//1,正常;0,告警
									break;
								case 3:
									cData.smoke[1] = atof(newVal.c_str());//1,正常;0,告警
									break;
								case 4:
									cData.fire_alarm = atof(newVal.c_str());//外部消防,0,正常;1,告警
									break;
								case 5:
									cData.water = atof(newVal.c_str());//漏水,0,正常;1,告警
									break;
								case 13:
									cData.beeper = atof(newVal.c_str());//峰鸣器,0,断开;1,告警
									break;
								case 14:
									cData.open_win = atof(newVal.c_str());//天窗联动,0,保持;1,动作
									break;
							}
						}else if(vars->name[6] == 179)
						{
							if(vars->name[7] == 1)
							{
								cData.cabinet_status = atof(newVal.c_str());
							}else
							{
								cData.cabinet_value[vars->name[7]-2] = atof(newVal.c_str());
							}
						}else if(vars->name[6] == 302 || vars->name[6] == 303 || vars->name[6] == 304 || vars->name[6] == 305)
						{
							if(vars->name[7] == 1)
							{
								cData.ac[vars->name[6] - 302].online_status = atof(newVal.c_str());
							}else
							{
								cData.ac[vars->name[6] - 302].value[vars->name[7] - 2] = atof(newVal.c_str());
							}
						}else if(vars->name[6] == 326 || vars->name[6] == 327)
						{
							cData.door[vars->name[6] - 326].value[vars->name[7] - 1] = atof(newVal.c_str());
						}
						if ((vars->type != SNMP_ENDOFMIBVIEW) &&
							(vars->type != SNMP_NOSUCHOBJECT) &&
							(vars->type != SNMP_NOSUCHINSTANCE)) {
							/*
							 * not an exception value 
							 */
							/*
							 * Check if last variable, and if so, save for next request.  
							 */
							if (vars->next_variable == NULL) {
								memmove(name, vars->name,
										vars->name_length * sizeof(oid));
								name_length = vars->name_length;
							}
						} else {
							/*
							 * an exception value, so stop 
							 */
							running = 0;
						}
					}
				} else {
					/*
					 * error in response, print it 
					 */
					running = 0;
					if (response->errstat == SNMP_ERR_NOSUCHNAME) {
						printf("End of MIB\n");
					} else {
						fprintf(stderr, "Error in packet.\nReason: %s\n",
								snmp_errstring(response->errstat));
						exitval = 2;
					}
				}
			} else if (status == STAT_TIMEOUT) {
				fprintf(stderr, "Timeout: No Response from %s\n",
						session.peername);
				running = 0;
				exitval = 1;
			} else {                /* status == STAT_ERROR */
				//snmp_sess_perror("snmpbulkwalk", ss);
                                printf("snmp get error %d\n", status);
				running = 0;
				exitval = 1;
			}
			if (response){
				snmp_free_pdu(response);
                                response = NULL;
                        }
		}
		snmp_close(ss);
                ss = NULL;
		if(exitval == 0)
		{
			RoundDone();
			//return false;
		}
		
		
		is_snmp_running_ = false;
	});
	return false;
}