前言 简单分析一下SICK Ranger3源码中断线重连的实现,这一块算是比较容易的,先择出来分析一下。
代码示例仅贴出关键部分以便分析
使用SDK版本为3.4.2.6
断线重连官方例程:Demo_R3_callback_with_heartbeat.cpp
断线检测 断线重连可以划分为两步,首先检测相机断线并通知,然后用户在收到通知后进行重连操作。我们先看SICK如何实现断线检测。
断线检测机制内置于SICK SDK中,由SICK SDK管理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 EXPORT_TO_DLL CAM_STATUS Ranger3::connectCamera (CallbackEvent_HeartBeats pCallback, const uint32_t & microSecond, void * any) { try { auto e = connectCamera (); if (e == CAM_STATUS::All_OK) { m_heartbeat_is_on = 1 ; ... auto _thread = std::make_shared <std::thread>(&Ranger3::_check_HeartBeats_run, this ); _thread->detach (); } return e; } ... } void Ranger3::_check_HeartBeats_run() { while (m_heartbeat_is_on==1 ) { __sleep1MS(m_heartbeat_interval); ... try { Str value ("" ) ; m_Param.getParameter (m_deviceNodeMap, "DeviceTemperature" , value); ... } catch (...) { ... } } }
可以看出,断线检测机制很简单,就是分离一个线程,循环访问相机寄存器(SICK的实现是通过定时获取设备温度访问相机寄存器),若访问不到(失败),就意味着相机已离线。
断线通知 断线通知机制同样内置于SICK SDK中:在检测到设备离线后,调用注册好的回调函数(注册过程将在下文介绍)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void Ranger3::_check_HeartBeats_run() { while (m_heartbeat_is_on==1 ) { __sleep1MS(m_heartbeat_interval); ... try { Str value ("" ) ; m_Param.getParameter (m_deviceNodeMap, "DeviceTemperature" , value); ... } catch (...) { ... auto _thread = std::make_shared <std::thread>(m_on_lost_function, &m_DeviceName, &m_DeviceIP, &m_on_lost_mac, &msg, m_on_lost_inputs); _thread->join (); return ; } } }
重连实现 重连机制的具体实现由用户进行。在例程Demo_R3_callback_with_heartbeat.cpp
中,由用户自定义一个回调函数(在相机离线时会被调用),回调内循环对相机进行重连操作。用户在连接相机时注册这个回调
用户层代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void SICK_CALLBACKon_lost_device_Demo_R3_callback_with_heartbeat (std::string* name, std::string* ip, std::string* mac, std::string* msg, void * pR3) { auto pCam = (SickCam::Ranger3*)pR3; while (true ) { auto ec = pCam->reconnectCamera (); ... __sleep1MS(1000 ); } } auto err = pCam1->connectCamera (on_lost_device_Demo_R3_callback_with_heartbeat, 1000 , pCam1. get ());
在SICK SDK中,注册过程会:
将用户注册的on_lost_device_Demo_R3_callback_with_heartbeat
赋值给m_on_lost_function
将用户传递上下文信息any
赋值给m_on_lost_inputs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 typedef std::function<void (std::string* name, std::string* ip, std::string* mac, std::string* msg, void * any)> CallbackEvent_HeartBeats;EXPORT_TO_DLL CAM_STATUS Ranger3::connectCamera (CallbackEvent_HeartBeats pCallback, const uint32_t & microSecond, void * any) { try { auto e = connectCamera (); if (e == CAM_STATUS::All_OK) { ... m_on_lost_function = pCallback; m_on_lost_inputs = any; auto _thread = std::make_shared <std::thread>(&Ranger3::_check_HeartBeats_run, this ); _thread->detach (); } return e; } ... }
注册完毕后,当相机出现离线情况,就如上文所述 ,SDK会调用注册的回调函数进行重连。
遇到的问题 在此仅记录一下我自己使用时发现的小问题。套个盾,我使用的SDK版本到目前为止肯定不是最新版本,以下提到的问题很有可能已在新版本被修复。
重连时失败 此问题由多个因素综合导致,先汇总现象,后逐个分析
①设备掉线后进程崩溃。点此快速跳转
②修复①后,在设备断线后重连时无法正常连接,且有报错GenTL call failed: -1006, Message: Requested handle not found in the pool
。点此快速跳转
在逐步分析之前,我们先总结一下相关的成员变量与函数。
成员变量 m_connectedDevices 声明及初始化 1 2 3 typedef std::map<Str, SPtr<DeviceConnection>> deviceList;static deviceList m_connectedDevices;deviceList CameraShared::m_connectedDevices= deviceList ();
描述 已连接设备表,用于管理设备名称和设备连接对象的映射关系。表中管理所有连接至主机的GenICam设备(包含SICK设备和非SICK设备)
元素增加 在_add_device()
中
对于非SICK的GenICam设备,如果没有在m_connectedDevices
中被管理,则将其加入;
对于SICK的GenICam设备,如果其已经被管理,则什么都不做;如果没有在m_connectedDevices
中被管理,则将其加入。
代码折叠,点击展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 void CameraShared::_add_device( cStr& interfaceName, GenTL::IF_HANDLE interfaceHandle, const SiDeviceList& devices, const int64_t & id, int & nDevices) { ... if (device_name == "" ) { if (m_connectedDevices.count (it->first) == 0 ) m_connectedDevices.insert ({ it->first, std::make_shared <DeviceConnection>(it->first)}); return ; } if (m_connectedDevices.count (device_name) == 1 ) { ... return ; } auto pDev = std::make_shared <DeviceConnection>( m_pconsumer, interfaceHandle, devices, id, interfaceName); ... m_connectedDevices.insert ({pDev->mDeviceName, pDev}); }
元素减少 在这里我们仅总结与断线重连问题相关的地方。
在_clear_invalid_device()
中,直接删除名称为函数参数name
的条目
代码折叠,点击展开
1 2 3 4 5 6 void CameraShared::_clear_invalid_device(cStr& name) { m_connectedDevices.erase (name); ... }
m_Interfaces_Map 声明及初始化 1 2 3 static std::map<std::string, GenTL::IF_HANDLE> m_Interfaces_Map;std::map<std::string, GenTL::IF_HANDLE> CameraShared::m_Interfaces_Map;
描述 可用接口表,用于管理接口名称与接口句柄的映射关系,例如对于以太网口,名称可能是enp3s0
。表中管理所有挂载了GenICam设备(包含SICK设备和非SICK设备)的接口
元素增加 只有一处涉及到其元素的增加,即在_scan_interface()
中,当检测到当前物理接口下挂载了GenICam设备(包含SICK设备和非SICK设备)且当前物理接口还没有在此表中管理,则向此表中插入一条{<接口名称>, <接口句柄>}
代码折叠,点击展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void CameraShared::_scan_interface(cStr& interfaceName, GenTL::IF_HANDLE interfaceHandle, int & nDevices) { ... auto devices = m_pconsumer->getDevices_is_changed (interfaceHandle); if (!devices.empty ()) { if (m_Interfaces_Map.count (interfaceName) == 0 ) m_Interfaces_Map.insert ({interfaceName, interfaceHandle}); ... } ... }
元素减少 在这里我们仅总结与断线重连问题相关的地方。
在_clear_invalid_device()
中,遍历接口-设备表 中的每个元素:
若接口下的设备名称集合为空(这里先不讨论什么情况下会为空),则说明接口不需要了,先通过IFClose
关闭接口句柄(SDK源码中并没有这一步),然后移除此映射表中与当前接口对应的条目
若接口下的设备名称集合不为空,且存在着要删除的SICK设备(其名称通过_clear_invalid_device()
的参数name
传入),则将其名称移出设备名称集合,并再次检查当前接口下的设备名称集合是否为空,若此时为空,则移除此映射表中与当前接口对应的条目
我认为_clear_invalid_device
中的代码逻辑和实现是有问题的,这里先展示SDK提供的源码,在《问题分析》 中会继续分析
代码折叠,点击展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 void CameraShared::_clear_invalid_device(cStr& name) { ... for (auto devs = m_Device_inInterface_Map.begin (); devs!= m_Device_inInterface_Map.end ();) { if (devs->second.empty ()) { m_Interfaces_Map.erase (devs->first); ... continue ; } if (devs->second.count (name) == 1 ) { devs->second.erase (name); ... } if (devs->second.empty ()) { ... m_pconsumer->closeInterface (m_Interfaces_Map[devs->first]); m_Interfaces_Map.erase (devs->first); ... } else ++devs; } }
m_Device_inInterface_Map 声明及初始化 1 2 static std::map<std::string, std::set<std::string>> m_Device_inInterface_Map;std::map<std::string, std::set<std::string>> CameraShared::m_Device_inInterface_Map;
描述 接口-设备表,用于管理接口名称与其下挂载的GenICam设备(仅SICK设备)集合的映射关系。表中管理所有挂载了GenICam设备(包含SICK设备和非SICK设备)的接口,但是其对应的设备名称集合下,仅记录SICK设备。
元素增加 映射表元素的增加是在_scan_interface()
中,当检测到当前物理接口下挂载了GenICam设备(包含SICK设备和非SICK设备),且当前物理接口还没有在此表中管理,则向表中插入一条{<接口名称>, {}}
,其中{}
代表空设备集
代码折叠,点击展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void CameraShared::_scan_interface(cStr& interfaceName, GenTL::IF_HANDLE interfaceHandle, int & nDevices) { ... auto devices = m_pconsumer->getDevices_is_changed (interfaceHandle); if (!devices.empty ()) { ... if (m_Device_inInterface_Map.count (interfaceName) == 0 ) m_Device_inInterface_Map.insert ({interfaceName, std::set <std::string>()}); ... } ... }
而设备集内元素的增加则是在_add_device()
中,当成功创建了SICK设备的连接后,会将此SICK设备的名称加入到映射表中对应物理接口条目下的设备名称集合中。
代码折叠,点击展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void CameraShared::_add_device( cStr& interfaceName, GenTL::IF_HANDLE interfaceHandle, const SiDeviceList& devices, const int64_t & id, int & nDevices) { ... auto pDev = std::make_shared <DeviceConnection>( m_pconsumer, interfaceHandle, devices, id, interfaceName); ... m_Device_inInterface_Map[interfaceName].insert (pDev->mDeviceName); }
元素减少 在_clear_invalid_device()
中,遍历此映射表中的每个元素:
若接口下的设备名称集合为空(这里先不讨论什么情况下会为空),则说明接口不需要了,移除此映射表中与当前接口对应的条目
若接口下的设备名称集合不为空,且存在着要删除的SICK设备(其名称通过_clear_invalid_device()
的参数name
传入),则将其名称移出接口对应的设备名称集合,并再次检查当前接口下的设备名称集合是否为空,若此时为空,则移除此映射表中与当前接口对应的条目
代码折叠,点击展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 void CameraShared::_clear_invalid_device(cStr& name) { ... for (auto devs = m_Device_inInterface_Map.begin (); devs!= m_Device_inInterface_Map.end ();) { if (devs->second.empty ()) { m_Device_inInterface_Map.erase (devs++); ... continue ; } if (devs->second.count (name) == 1 ) { devs->second.erase (name); ... } if (devs->second.empty ()) { ... m_pconsumer->closeInterface (m_Interfaces_Map[devs->first]); m_Device_inInterface_Map.erase (devs++); ... } else ++devs; } }
函数 主要的函数都位于CameraShared
这个类中,这个类感觉可以理解为一个SDK中的最顶层的管理类,管理了要使用的接口和设备等
_scan_Device 声明 1 CAM_STATUS _scan_Device(int & numberDeviceNewFound);
描述 扫描当前系统中所有可用接口下的设备,并更新m_Device_inInterface_Map
,m_Interfaces_Map
和m_connectedDevices
内部逻辑
调用TLUpdateInterfaceList()
更新Producer内部管理的可用接口列表,并在当前函数中记录一个临时的可用接口列表interfaces
,列表内各元素为{<接口模块ID>, <接口的用户可读名称>}
为interfaces
中的每个可用接口创建类型为GenTL::IF_HANDLE
的接口句柄interfaceHandle
:
如果接口已经在m_Interfaces_Map
中被管理,意味着其对应的接口句柄还存在,所以直接令interfaceHandle
等于m_Interfaces_Map
中管理的句柄即可
若接口不在m_Interfaces_Map
中,意味着这可能是一个新的可用接口,会调用TLOpenInterface()
打开接口,然后将句柄赋值给interfaceHandle
对interfaces
中的每个成功打开的可用接口调用_scan_interface()
代码 代码折叠,点击展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 CAM_STATUS CameraShared::_scan_Device(int & numberDeviceNewFound) { #ifdef CALLBACK_NEW std::unique_lock<std::mutex> lock (m_mutex_scan) ; #endif *m_log << CustomerLog::time () << "[CameraShared::scan_Device]: scan_Device() start \n" ; if (m_tlHandle == GENTL_INVALID_HANDLE) return CAM_STATUS::ERROR_OPEN_TL_HANDLE; #ifdef __linux__ auto interfaces = m_pconsumer->getInterfaces (m_tlHandle); #endif #ifdef _WIN32 auto interfaces = _getInterfaces(m_tlHandle, m_sTl); #endif *m_log << CustomerLog::time () << "[CameraShared::scan_Device]: interfaces.size() = " << interfaces.size () << "\n" ; for (size_t i = 0 ; i < interfaces.size (); ++i) { auto interfaceName = interfaces[i].second; GenTL::TL_HANDLE interfaceHandle = GENTL_INVALID_HANDLE; if (m_Interfaces_Map.count (interfaceName) == 1 ) { interfaceHandle = m_Interfaces_Map[interfaceName]; } else { #ifdef __linux__ interfaceHandle = m_pconsumer->openInterfaceById (_findInterfaceByIndex(interfaces, i)); #endif #ifdef _WIN32 interfaceHandle = _openInterfaceById(_findInterfaceByIndex(interfaces, i), m_tlHandle, m_sTl); #endif } if (interfaceHandle == GENTL_INVALID_HANDLE) continue ; *m_log << CustomerLog::time () << "[CameraShared::scan_Device]: Open Interface : " << interfaceName << "\n" ; _scan_interface(interfaceName, interfaceHandle, numberDeviceNewFound); } *m_log << CustomerLog::time () << "[CameraShared::scan_Device]: The number of available interfaces: " << m_Device_inInterface_Map.size () << "\n" ; for (auto sub : m_Device_inInterface_Map) { *m_log << CustomerLog::time () << "[CameraShared::scan_Device]: Interface: " << sub.first << ": \n" ; for (auto sub_second : sub.second) { *m_log << CustomerLog::time () << "[CameraShared::scan_Device]: Dev - " << sub_second << "\n" ; } } m_isDevFound = false ; for (auto sub:m_connectedDevices) { if (sub.second->mDeviceName != "" ) { m_isDevFound = true ; break ; } } if (numberDeviceNewFound > 0 ) { *m_log << CustomerLog::time () << "[CameraShared::scan_Device]: After scanning all interfaces, " << numberDeviceNewFound << " new device(s) found!\n\n\n" ; return CAM_STATUS::All_OK; } *m_log << CustomerLog::time () << "[CameraShared::scan_Device]: After scanning all interfaces, no new device found!\n\n\n" ; return CAM_STATUS::All_OK; }
_scan_interface 声明 1 2 3 4 5 6 7 8 void _scan_interface(cStr& interfaceName, GenTL::IF_HANDLE interfaceHandle, int & nDevices);
描述 此函数对单个接口调用,用于扫描指定接口下的设备,并更新m_Device_inInterface_Map
,m_Interfaces_Map
和m_connectedDevices
内部逻辑
调用IFUpdateDeviceList()
更新Producer内部管理的可用设备列表,并在当前函数中记录一个临时的可用设备列表devices
,列表中管理这当前接口下挂载的所有GenICam设备(包含SICK设备和非SICK设备)。
对于非SICK设备,其在devices
中对应的元素格式为{<modelName>(<sn>), ""}
对于SICK设备,其在devices
中对应的元素格式为{<deviceId>, <deviceId>_<deviceUserId>}
如果devices
非空(即当前接口下挂载了GenICam设备),则
若m_Interfaces_Map
中没有管理当前接口,则将接口加入
若m_Device_inInterface_Map
中没有管理当前接口,则将接口加入,并且设置接口下设备名称集合为空
对于devices
中的每个设备调用_add_device()
如果devices
为空(即当前接口下未挂载任何GenICam设备),则
若m_Interfaces_Map
中没有管理当前接口,则调用IFClose()
关闭当前接口句柄
代码 代码折叠,点击展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 void CameraShared::_scan_interface(cStr& interfaceName, GenTL::IF_HANDLE interfaceHandle, int & nDevices) { #ifdef __linux__ auto devices = m_pconsumer->getDevices_is_changed (interfaceHandle); #else auto devices = _getDevices_is_changed(interfaceHandle, m_sTl); #endif if (!devices.empty ()) { if (m_Interfaces_Map.count (interfaceName) == 0 ) m_Interfaces_Map.insert ({interfaceName, interfaceHandle}); if (m_Device_inInterface_Map.count (interfaceName) == 0 ) m_Device_inInterface_Map.insert ({interfaceName, std::set <std::string>()}); *m_log << CustomerLog::time () << " [CameraShared::_scan_interface]: # Interface: " << interfaceName << " has GenICam device(s)! \n" ; *m_log << CustomerLog::time () << " [CameraShared::_scan_interface]: # Find total " << devices.size () << " GenICam device(s) in Interface. \n" ; nDevices = 0 ; for (size_t j = 0 ; j < devices.size (); ++j) _add_device(interfaceName, interfaceHandle, devices, j, nDevices); } else { if (m_Interfaces_Map.count (interfaceName) == 0 ) { #ifdef __linux__ m_pconsumer->closeInterface (interfaceHandle); #else _closeInterface(interfaceHandle, m_sTl); #endif *m_log << CustomerLog::time () << "[CameraShared::_scan_interface]: Warning: No device in Interface: " << interfaceName << ").\n" ; } } }
_add_device 声明 1 2 3 4 5 6 7 8 9 10 void _add_device ( cStr& interfaceName, GenTL::IF_HANDLE interfaceHandle, const SiDeviceList& devices, const int64_t & id, int & nDevices);
描述 此函数在单个接口下对单个设备调用,用于为新GenICam设备创建设备对象(封装了设备句柄的DeviceConnection
)。注意:
对于SICK和非SICK设备,创建设备对象的逻辑是不同的,但二者都会被加入m_connectedDevices
(若之前没加入)。
SICK设备会被加入到m_Device_inInterface_Map
中当前接口下的设备名称集合,而非SICK设备则不会
内部逻辑
接口有效性检查,检查当前接口是否在m_Device_inInterface_Map
中被管理
在GenIcam设备列表中寻找第id
个设备
若此设备为非SICK设备且在m_connectedDevices
中未被管理,则将其加入管理,然后返回。注意此设备对应的DeviceConnection
为为非SICK设备准备的空构造
若此设备为SICK设备且已在m_connectedDevices
中被管理,直接返回。
若此设备为SICK设备且在m_connectedDevices
中未被管理(意味着是新发现的SICK设备),则为其创建DeviceConnection
。若成功创建,则更新m_connectedDevices
,并更新m_Device_inInterface_Map
中当前接口下的设备名称集合
代码 代码折叠,点击展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 void CameraShared::_add_device( cStr& interfaceName, GenTL::IF_HANDLE interfaceHandle, const SiDeviceList& devices, const int64_t & id, int & nDevices) { if (m_Device_inInterface_Map.count (interfaceName) != 1 ) { *m_log << CustomerLog::time () << " [CameraShared::_add_device]: Error: interfaceName: " << interfaceName << " not found!\n" ; return ; } auto it = devices.begin (); std::advance (it, id); auto device_name = it->second; if (device_name == "" ) { *m_log << CustomerLog::time () << " [CameraShared::_add_device]: [x ] Unsupported device: " << it->first << " \n" ; if (m_connectedDevices.count (it->first) == 0 ) m_connectedDevices.insert ({ it->first, std::make_shared <DeviceConnection>(it->first)}); return ; } if (m_connectedDevices.count (device_name) == 1 ) { if (m_connectedDevices[device_name]->mDeviceName != "" ) *m_log << CustomerLog::time () << " [CameraShared::_add_device]: [! ] Ignore [SICK] device: " << device_name << ", already found! \n" ; return ; } auto pDev = std::make_shared <DeviceConnection>( m_pconsumer, interfaceHandle, devices, id, interfaceName); if (pDev->isOccupied ()) { *m_log << CustomerLog::time () << " [CameraShared::_add_device]: [! ] Error [SICK] device: " << device_name << ", occupied, device is busy! \n" ; return ; } if (!pDev->isReachable ()) { *m_log << CustomerLog::time () << " [CameraShared::_add_device]: [! ] Error [SICK] device: " << device_name << ", unreachable, device ip = " <<pDev->getIp () << ", please check computer ip! \n" ; return ; } ++nDevices; m_connectedDevices.insert ({pDev->mDeviceName, pDev}); m_Device_inInterface_Map[interfaceName].insert (pDev->mDeviceName); *m_log << CustomerLog::time () << " [CameraShared::_add_device]: [ok] Add [SICK] device: " << pDev->mDeviceName << ", successfully! \n" ; }
_clear_invalid_device 声明 1 void _clear_invalid_device (cStr& name);
描述 此函数对单个设备调用,用于移除name
对应的设备,包括其在m_connectedDevices
,m_Device_inInterface_Map
中的所有记录。如果某个接口下的设备都被移除完毕,还会顺便关闭这个接口
(这个描述是我根据SICK SDK的实现总结的,因为我认为这个SDK中这个函数实现的不太好,所以说不好是我总结的描述不符合SDK设计的意图,还是函数实现的有问题)
内部逻辑
从m_connectedDevices
中移除目标设备
对于m_Device_inInterface_Map
中的每个条目(接口),检查当前接口
①若未挂载任何SICK设备,则从m_Interfaces_Map
,m_Device_inInterface_Map
中移除当前接口项(我认为SDK在这步之前少了一步关闭接口,并且对于迭代器重复++了)
②若挂载了SICK设备,则在m_Interfaces_Map
中接口项的设备名称集合中查找是否有目标设备,若有则将其从设备名称集合中移除。移除后再次进行①中逻辑
代码 代码折叠,点击展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 void CameraShared::_clear_invalid_device(cStr& name) { m_connectedDevices.erase (name); for (auto devs = m_Device_inInterface_Map.begin (); devs!= m_Device_inInterface_Map.end ();) { *m_log << CustomerLog::time () << "[CameraShared::_clear_invalid_device]: Try to close device: " << name << " in NetCad: " << devs->first << "\n" ; if (devs->second.empty ()) { m_Interfaces_Map.erase (devs->first); m_Device_inInterface_Map.erase (devs++); *m_log << CustomerLog::time () << "[CameraShared::_clear_invalid_device]: Not found device: " << name << " in NetCad: " << devs->first << "\n" ; ++devs; continue ; } if (devs->second.count (name) == 1 ) { devs->second.erase (name); *m_log << CustomerLog::time () << "[CameraShared::_clear_invalid_device]: Close device: " << name << " in NetCad: " << devs->first << "\n" ; } if (devs->second.empty ()) { *m_log << CustomerLog::time () << "[CameraShared::_clear_invalid_device]: Interface is empty in NetCad: " << devs->first << "\n" ; #ifdef __linux__ m_pconsumer->closeInterface (m_Interfaces_Map[devs->first]); #endif #ifdef _WIN32 CC (m_sTl, m_sTl->IFClose (m_Interfaces_Map[devs->first])); #endif *m_log << CustomerLog::time () << "[CameraShared::_clear_invalid_device]: Close Interface: " << devs->first << "\n" ; m_Interfaces_Map.erase (devs->first); m_Device_inInterface_Map.erase (devs++); } else ++devs; } }
问题分析 上面我们总结完了相关的成员变量和函数,现在我们重新看下问题:
①设备掉线后进程崩溃
②修复①后,在设备断线后重连时无法正常连接,且有报错GenTL call failed: -1006, Message: Requested handle not found in the pool
设备掉线后进程崩溃 这个问题比较明显也比较好解决,原因就是SDK中访问了已释放的内存:
在分离运行的_check_HeartBeats_run()
中有一个函数_freeDevice()
,其中有:
1 m_pR3S->_clear_invalid_device(m_DeviceName);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void CameraShared::_clear_invalid_device(cStr& name){ m_connectedDevices.erase (name); for (auto devs = m_Device_inInterface_Map.begin (); devs!= m_Device_inInterface_Map.end ();) { *m_log << CustomerLog::time () << "[CameraShared::_clear_invalid_device]: Try to close device: " << name << " in NetCad: " << devs->first << "\n" ; if (devs->second.empty ()) { m_Interfaces_Map.erase (devs->first); m_Device_inInterface_Map.erase (devs++); *m_log << CustomerLog::time () << "[CameraShared::_clear_invalid_device]: Not found device: " << name << " in NetCad: " << devs->first << "\n" ; ++devs; continue ; } ... } }
解决方法:
1 2 3 4 5 6 7 8 9 if (devs->second.empty ()) { auto to_erase = devs++; m_Interfaces_Map.erase (to_erase->first); *m_log << CustomerLog::time () << "<" << std::this_thread::get_id () << ">" << "[CameraShared::_clear_invalid_device]: No device: " << name << " in NetCad: " << to_erase->first << "close now" << "\n" ; m_Device_inInterface_Map.erase (to_erase); continue ; }
断线后无法正常重连 先上结论,断线时未调用IFClose()
关闭接口,然后重连时调用TLOpenInterface()
尝试打开未关闭的接口,导致错误。
分析这个问题,首先要从调用链的最开始来看,例程中的调用链大概是这样子:
1 2 3 4 5 6 7 BEGIN → CameraShared::CameraShared() → CameraShared::_scan_Device() → CameraShared::_scan_interface() → CameraShared::_add_device() → Ranger3::Ranger3() → 忽略 → Ranger3::connectCamera(cb_on_lost) → Ranger3::_connectCamera() → 忽略 → Ranger3::_check_HeartBeats_run()(if lost) → Ranger3::_freeDevice() → CameraShared::_clear_invalid_device() → cb_on_lost() → Ranger3::reconnectCamera() → CameraShared::_scan_Device() → 省略 → Ranger3::connectCamera(cb_on_lost) → 省略 → 忽略
其中cb_on_lost
是用户注册的回调,在相机断线时SDK会在CameraShared::_freeDevice()
后调用此函数。本文中我参考SICK例程,在回调中循环定时调用Ranger3::reconnectCamera()
然后需要关注的另一个点是,主机上都挂载了哪些GenICam设备,那么简单分为以下几类:
①主机上仅挂载单个SICK设备;
②主机上挂载两个及以上SICK设备(不同网口)
③主机上挂载两个及以上SICK设备(同一网口下)
④主机上挂载一个SICK设备和一个非SICK设备(不同网口)
⑤主机上挂载一个SICK设备和一个非SICK设备(同一个网口)
对于①,目测SDK内的实现没有什么大问题 对于②③,暂时没条件测试,暂时过,后面可能会验证。 现在来谈谈④:
主机上挂载一个SICK设备和一个非SICK设备(不同网口) 在分析之前,我先例举一下主机上连接的设备,以便后续的流程梳理。
主机的两个网口enp3s0
,enp4s0
(忽略其他网口)各挂载着一个GenICam设备:
enp3s0
上挂载的是非SICK设备A
enp4s0
上挂载的是SICK设备B
然后我们按照函数调用链开始梳理
在构造CameraShared
时,会调用_scan_Device() ,其中会对每个物理接口做一些操作,先看enp3s0
:
通过openInterfaceById()
间接调用TLOpenInterface()
打开enp3s0
,并返回其句柄。
对其调用_scan_interface()
。在_scan_interface()
中,enp3s0
被添加进m_Interfaces_Map
和m_Device_inInterface_Map
。
对enp3s0
下每个设备调用_add_device()
。在_add_device()
中,由于A为非SICK设备,所以将其加入m_connectedDevices
但不会更新m_Device_inInterface_Map
中的设备集。
enp3s0
的扫描过程结束,最终结果是:
m_Interfaces_Map
:{{"enp3s0", < enp3s0的接口句柄 >}}
m_Device_inInterface_Map
:{{"enp3s0", {}}}
m_connectedDevices
:{{< A的名称 >, < DeviceConnection for A >}}
对于enp4s0
,由于其下挂载的B是SICK设备,所以与enp3s0
不同的是,在_add_device
中,会将其名称加入到m_Device_inInterface_Map
中接口对应的设备集下,因此enp4s0
扫描过程结束后的结果是:
m_Interfaces_Map
:{{"enp3s0", < enp3s0的接口句柄 >}, {"enp4s0", < enp4s0的接口句柄 >}}
m_Device_inInterface_Map
:{{"enp3s0", {}}, {"enp4s0", {< B的名称 >}}}
m_connectedDevices
:{{< A的名称 >, < DeviceConnection for A >}, {< B的名称 >, < DeviceConnection for B >}}
_scan_Device()
还有一个需要注意的Postcondition是,所有挂载了GenICam设备的接口,都被打开了并在Producer中被管理。
现在我们关注,当设备断线时,会被调用的_clear_invalid_device() ,因为问题就是出现在这里。注意!!!只有SICK设备才会调用到这个函数。所以对于B:
从m_connectedDevices
中移除B对应的元素
对于m_Device_inInterface_Map
中的每个接口:
首先是enp3s0
,由于其下的设备集合为空,因此在m_Interfaces_Map
和m_Device_inInterface_Map
中删除相关项。注意!!!问题出现在这里,这里并没有调用IFClose
关闭接口,导致接口在Producer中没释放。
然后是enp4s0
,其下挂载了B,首先删除m_Device_inInterface_Map
中enp4s0
对应的设备集合中B的相关元素,然后调用IFClose
关闭接口,最后在m_Interfaces_Map
和m_Device_inInterface_Map
中删除相关项
至此B设备的清理完成,最终的结果:
m_Interfaces_Map
:{}
m_Device_inInterface_Map
:{}
m_connectedDevices
:{{< A的名称 >, < DeviceConnection for A >}}
这里的问题是没有调用IFClose
关闭接口enp3s0
,但是清除了其在Consumer中的相关记录,这就会导致Consumer这边认为enp3s0
已关闭,而实际上Producer中可能仍管理着这个接口(具体取决于Producer实现,俺不知道)。然后在后续的reconnectCamera()
调用中会再次调用到_scan_interface()
,会有报错:
1 GenTL call failed: -1006, Message: Requested handle not found in the pool
排查一下,上述报错是_scan_Device()
中第一个用宏CR
修饰的涉及enp3s0
接口的GenTL调用报出的,即
1 CR (mTl->IFUpdateDeviceList (ifHandle, &changed, 500 ));
但仔细分析,其实这不是源头,因为_scan_Device()
中的第一个涉及enp3s0
接口的GenTL调用并不是IFUpdateDeviceList()
,而是TLOpenInterface()
,只不过其没有使用CR
修饰,因此不会抛出异常和打印报错信息。现在为了测试,我们为其添加CR
:
1 2 3 4 5 6 7 8 9 GenTL::IF_HANDLE Consumer::openInterfaceById (const InterfaceId& interfaceId) { ... CR (mTl->TLOpenInterface (mTlHandle, interfaceId.c_str (), &interfaceHandle)); ... }
然后报错会变为:
1 GenTL call failed: -1005, Message: ItfOpen: the requested interface exists in the list, but is marked as inaccessible
可以看出这里打开接口是失败了的,错误码-1005 GC_ERR_ACCESS_DENIED
,因此接口句柄无效,所以在IFUpdateDeviceList
才会报出-1006 GC_ERR_INVALID_HANDLE
错误码。
初步的解决措施是在_clear_invalid_device()
中对未挂载设备的接口增加一个调用closeInterface()
,手动关闭接口即可。(其实_clear_invalid_device()
大逻辑还是可以优化的,但还是等要到SDK最新版本看一下官方的解决方案吧)
主机上挂载一个SICK设备和一个非SICK设备(相同网口) 暂略,未做实验验证
声明错误 m_Interfaces_Map 1 2 3 4 static std::map<std::string, GenTL::TL_HANDLE> m_Interfaces_Map;
这个结构体应该是接口名称到接口句柄的映射表,用于记录和管理当前已打开的接口(对于GigE就是网口)。在GenTL中对应的句柄应该是GenTL::IF_HANDLE
,所以此变量的声明理应改为:
1 2 3 4 static std::map<std::string, GenTL::IF_HANDLE> m_Interfaces_Map;
只不过由于:
1 2 3 typedef void * TL_HANDLE; typedef void * IF_HANDLE;
并且实际使用中向m_Interfaces_Map
传入的都是GenTL::IF_HANDLE
,因此代码实际上并不会出现什么错误。
上述结论也可以通过SDK中其他与m_Interfaces_Map
相关的调用来验证。
interfaceHandle 错误原因与m_Interfaces_Map 相同:
1 2 3 4 5 6 7 8 9 10 11 12 13 CAM_STATUS CameraShared::_scan_Device(int & numberDeviceNewFound) { ... for (size_t i = 0 ; i < interfaces.size (); ++i) { ... GenTL::TL_HANDLE interfaceHandle = GENTL_INVALID_HANDLE; } }
应改为
1 2 3 4 5 6 7 8 9 10 11 12 13 CAM_STATUS CameraShared::_scan_Device(int & numberDeviceNewFound) { ... for (size_t i = 0 ; i < interfaces.size (); ++i) { ... GenTL::IF_HANDLE interfaceHandle = GENTL_INVALID_HANDLE; } }