前言 最近在做自己的相机管理框架的时候,又回想起了Pylon(Basler的相机控制软件)API中的各种事件机制,感觉非常有用。但由于Pylon的代码不是开源的,只能尝试自己实现。
在本文中先简单介绍Pylon API中提供的的配置事件处理程序(即Pylon::CConfigurationEventHandler
)及其调用机制,然后尝试自己实现。
只是尽可能地尝试复现其内部调用机制,很多细节在本文中不会给出。
Pylon的实现 Pylon::CConfigurationEventHandler 首先先简单介绍一下Pylon::CConfigurationEventHandler
,它是一个用于相机配置的事件处理类,允许用户在相机的状态变更过程前后的关键点上自定义配置。例如:
在Pylon示例中展示了它的使用方法:
①首先在头文件创建一个自定义的配置事件处理类,它继承自ConfigurationEventHandler
,并在这个类中重写有需要的回调函数。在示例中,将每个回调函数的内容定义为打印相应事件的名称,方便观察。
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 #ifndef INCLUDED_CONFIGURATIONEVENTPRINTER_H_663006 #define INCLUDED_CONFIGURATIONEVENTPRINTER_H_663006 #include <pylon/ConfigurationEventHandler.h> #include <iostream> namespace Pylon{ class CInstantCamera ; class CConfigurationEventPrinter : public CConfigurationEventHandler { public : void OnOpen ( CInstantCamera& camera ) { std::cout << "OnOpen event" << std::endl; } void OnOpened ( CInstantCamera& camera ) { std::cout << "OnOpened event" << std::endl; } ... void OnCameraDeviceRemoved ( CInstantCamera& camera ) { std::cout << "OnCameraDeviceRemoved event" << std::endl; } }; } #endif
②使用时通过RegisterConfiguration 在相机对象中注册。注册完成后,当遇到相机行为的关键节点,重写的相应的回调函数会被调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <pylon/PylonIncludes.h> #include "ConfigurationEventPrinter.h" int main ( int , char * [] ) { ... CTlFactory& tlFactory = CTlFactory::GetInstance (); CInstantCamera camera ( tlFactory.CreateFirstDevice() ) ; camera.RegisterConfiguration (new CConfigurationEventPrinter, RegistrationMode_Append, Cleanup_Delete); camera.open (); ... }
ERegistrationMode 定义了如何注册一个handler
1 2 3 4 5 enum ERegistrationMode { RegistrationMode_Replace, RegistrationMode_Append };
ECleanup 定义在handler不再被使用时由谁删除
1 2 3 4 5 enum ECleanup { Cleanup_None, Cleanup_Delete };
自己实现 先分析一下要实现像Pylon::CConfigurationEventHandler
这样的事件处理程序,都需要设计哪些组件或步骤。
注册/注销机制:事件机制的核心,用于注册/注销事件,同时也兼具管理handler列表以及控制handler生命周期(可选)的职能。
可拓展的handler:不同的handler可用于实现不同的逻辑。
通知机制:相机状态变化时,由内部逻辑统一通知各handler,触发相应逻辑。
组件
关键设计思想
作用
注册/注销机制
观察者模式
事件机制的核心,用于注册/注销事件,同时也兼具管理Handler列表以及控制Handler生命周期(可选)的职能。
可拓展的Handler
策略模式
不同的Handler可用于实现不同的逻辑。
通知机制
观察者模式+事件驱动
相机状态变化时,由内部逻辑统一通知各Handler,触发相应逻辑。
可拓展的Handler 先尝试实现可拓展的Handler,因为它是不依赖于其他两个机制的独立部分。
实现思路是先定义一个事件处理基类ConfigurationEventHandler
,用户可通过继承该类并重写其中的虚函数,来自定义具体的事件响应策略。
首先实现一个基类ConfigurationEventHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #ifndef CONFIGURATIONEVENTHANDLER_H #define CONFIGURATIONEVENTHANDLER_H class CameraDevice ;class ConfigurationEventHandler { public : ConfigurationEventHandler (){}; virtual ~ConfigurationEventHandler (){}; public : virtual void OnGrabStart (CameraDevice& camera) {} virtual void OnGrabStarted (CameraDevice& camera) {} virtual void OnOpen (CameraDevice& camera) {} virtual void OnOpened (CameraDevice& camera) {} }; #endif
基类中的回调函数定义为空实现,即使用户注册了此类,也不会有什么操作。
然后我们可以定义一个子类ConfigurationEventPrinter
,其中每个重写的回调函数的内容定义为打印相应事件的名称,方便观察调试。
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 #ifndef CONFIGURATIONEVENTPRINTER_H #define CONFIGURATIONEVENTPRINTER_H #include "ConfigurationEventHandler.h" class CameraDevice ;class ConfigurationEventPrinter : public ConfigurationEventHandler{ public : void OnGrabStart (CameraDevice& camera) override { std::cout << "ConfigurationEventPrinter::OnGrabStart" << std::endl; } void OnGrabStarted (CameraDevice& camera) override { std::cout << "ConfigurationEventPrinter::OnGrabStarted" << std::endl; } void OnOpen (CameraDevice& camera) override { std::cout << "ConfigurationEventPrinter::OnOpen" << std::endl; } void OnOpened (CameraDevice& camera) override { std::cout << "ConfigurationEventPrinter::OnOpened" << std::endl; } }; #endif
当然用户还可以定义各种各样的事件响应策略,如断线重连
注册/注销机制 在介绍注册/注销机制前,我们先引入一个抽象相机类CameraDevice
,所有不同品牌的具体相机类继承自此类。此类充当被观察者的角色,被观察者内部包含了观察者的集合,并提供了注册,注销,通知观察者的统一接口。同时也维护了观察者的列表,自身状态发生变化时通知所有的观察者。
而观察者就是所有继承自ConfigurationEventHandler
的类的实例,其提供了具体的响应细节。
Handler列表 我们先来看被观察者中的观察者列表,考虑到Handler可以被自动清理或由使用者手动清理,需要自定义一个结构体封装一个Handler及其生命周期管理策略:
1 2 3 4 5 6 7 8 9 10 class CameraDevice { protected : struct HandlerEntry { ConfigurationEventHandler* handler; Cleanup cleanup; }; };
然后我们在CameraDevice
内部维护一个Handler列表,以便实现多个Handler的注册与注销
1 2 3 4 5 6 7 8 9 10 11 12 13 class CameraDevice { protected : struct HandlerEntry { ConfigurationEventHandler* handler; Cleanup cleanup; }; protected : std::vector<HandlerEntry> configuration_event_handlers_; };
注册 现在我们关注Handler的注册环节,参考Pylon,注册环节除了要在相机对象上传入Handler的实例,还要传入两个额外的参数:注册策略(RegistrationMode
)和生命周期管理策略(Cleanup
),因此函数的声明如下:
1 2 3 virtual void RegisterConfiguration (ConfigurationEventHandler* handler, RegistrationMode mode, Cleanup cleanup_procedure) ;
函数的实现逻辑如下
若handler的注册策略为RegistrationMode_ReplaceAll
:
会先遍历原有列表,删除所有带Cleanup_Delete
策略的handler
然后清空列表
清空列表后,仅注册当前handler
若handler的注册策略为RegistrationMode_Append
直接将当前handler注册到列表末尾,列表内原handler都保持不变
因此参考实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void CameraDevice::RegisterConfiguration (ConfigurationEventHandler* handler, RegistrationMode mode, Cleanup cleanup_procedure) { if (RegistrationMode_ReplaceAll == mode) { for (auto & entry : configuration_event_handlers_) { if (Cleanup_Delete == entry.cleanup) delete entry.handler; } configuration_event_handlers_.clear (); configuration_event_handlers_.push_back ({handler, cleanup_procedure}); } else if (RegistrationMode_Append == mode) { configuration_event_handlers_.push_back ({handler, cleanup_procedure}); } }
注销 注销环节要在Handler列表中移除相关Handler,并根据对应的生命周期管理策略选择是否释放Handler:
1 2 virtual void DeregisterConfiguration (ConfigurationEventHandler* handler) ;
函数的实现逻辑为:遍历所有已注册的handler,并将与参数匹配的项从列表中删除。如果该handler在注册时使用了Cleanup_Delete
策略,则会在删除前对其执行delete操作,释放内存资源。
参考实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void CameraDevice::DeregisterConfiguration (ConfigurationEventHandler* handler) { bool deleted = false ; auto it = configuration_event_handlers_.begin (); while (it != configuration_event_handlers_.end ()) { if (handler == it->handler) { if (Cleanup_Delete == it->cleanup && !deleted) { delete it->handler; deleted = true ; } it = configuration_event_handlers_.erase (it); } else { ++it; } } }
通知机制 当相机到达特定状态的关键点时,配置事件机制应将该状态变化通知所有已注册的Handler,以触发相应的响应逻辑。
首先为了标准化事件类型,我们定义一个枚举类ConfigurationEvent
,用于表示当前支持通知的事件类型:
1 2 3 4 5 6 7 8 9 enum ConfigurationEvent { OnOpen, OnOpened, OnGrabStart, OnGrabStarted };
然后我们需要定义一个函数NotifyEventHandeler
,用于分发事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void CameraDevice::NotifyEventHandeler (ConfigurationEvent event) { for (auto it : configuration_event_handlers_) { switch (event) { case ConfigurationEvent::OnGrabStart: it.handler->OnGrabStart (*this ); break ; case ConfigurationEvent::OnGrabStarted: it.handler->OnGrabStarted (*this ); break ; case ConfigurationEvent::OnOpen: it.handler->OnOpen (*this ); break ; case ConfigurationEvent::OnOpened: it.handler->OnOpened (*this ); } } }
如此,当相机到达特定状态的关键点时,每一个Handler都能“感知”到相机内部状态的变化,并在响应函数中实现自定义逻辑。状态变更时的通知是这么实现的:
1 2 3 4 5 6 7 8 9 bool CameraDevice::StartGrabbing () { NotifyEventHandeler (ConfigurationEvent::OnGrabStart); bool ret = SpecificStartGrabbing (); if (ret) NotifyEventHandeler (ConfigurationEvent::OnGrabStarted); return ret; }
通过在函数中插入NotifyEventHandeler
调用,实现了“状态变化即事件驱动”的自动通知逻辑。
代码汇总 现将上述代码汇总:
抽象相机类 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 #ifndef CAMERADEVICE_H #define CAMERADEVICE_H class CameraDevice { protected : struct HandlerEntry { ConfigurationEventHandler* handler; Cleanup cleanup; }; public : virtual bool StartGrabbing () final ; protected : virtual void RegisterConfiguration (ConfigurationEventHandler* handler, RegistrationMode mode, Cleanup cleanup_procedure) ; virtual void DeregisterConfiguration (ConfigurationEventHandler* handler) ; virtual bool SpecificStartGrabbing () = 0 ; protected : std::vector<HandlerEntry> configuration_event_handlers_; }; #endif
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 void CameraDevice::RegisterConfiguration (ConfigurationEventHandler* handler, RegistrationMode mode, Cleanup cleanup_procedure) { if (RegistrationMode_ReplaceAll == mode) { for (auto & entry : configuration_event_handlers_) { if (Cleanup_Delete == entry.cleanup) delete entry.handler; } configuration_event_handlers_.clear (); configuration_event_handlers_.push_back ({handler, cleanup_procedure}); } else if (RegistrationMode_Append == mode) { configuration_event_handlers_.push_back ({handler, cleanup_procedure}); } } void CameraDevice::DeregisterConfiguration (ConfigurationEventHandler* handler) { bool deleted = false ; auto it = configuration_event_handlers_.begin (); while (it != configuration_event_handlers_.end ()) { if (handler == it->handler) { if (Cleanup_Delete == it->cleanup && !deleted) { delete it->handler; deleted = true ; } it = configuration_event_handlers_.erase (it); } else { ++it; } } } bool CameraDevice::StartGrabbing () { NotifyEventHandeler (ConfigurationEvent::OnGrabStart); bool ret = SpecificStartGrabbing (); if (ret) NotifyEventHandeler (ConfigurationEvent::OnGrabStarted); return ret; }
事件处理器基类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #ifndef CONFIGURATIONEVENTHANDLER_H #define CONFIGURATIONEVENTHANDLER_H class CameraDevice ;class ConfigurationEventHandler { public : ConfigurationEventHandler (){}; virtual ~ConfigurationEventHandler (){}; public : virtual void OnGrabStart (CameraDevice& camera) {} virtual void OnGrabStarted (CameraDevice& camera) {} virtual void OnOpen (CameraDevice& camera) {} virtual void OnOpened (CameraDevice& camera) {} }; #endif
辅助结构体 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 #ifndef MISCSSTRUCT_H #define MISCSSTRUCT_H enum RegistrationMode { RegistrationMode_Append, RegistrationMode_ReplaceAll }; enum Cleanup { Cleanup_None, Cleanup_Delete }; enum ConfigurationEvent { OnOpen, OnOpened, OnGrabStart, OnGrabStarted }; #endif
示例 在实际使用的过程中,我们需要将ConfigurationEventPrinter
更换为自定义的事件处理器类即可,里面的回调函数依照实际需求进行重写。(上述示例中的回调相关的条目,碍于篇幅,没有展示完全,用户使用时需自己补充,具体条目见Pylon::CConfigurationEventHandler )
关键节点打印 我们现在展示一下如何在相机状态关键节点打印相应事件: 首先我们通过继承ConfigurationEventHandler
并重写相关虚函数的方式实现事件打印策略:
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 #ifndef CONFIGURATIONEVENTPRINTER_H #define CONFIGURATIONEVENTPRINTER_H #include "ConfigurationEventHandler.h" class CameraDevice ;class ConfigurationEventPrinter : public ConfigurationEventHandler{ public : void OnGrabStart (CameraDevice& camera) override { std::cout << "ConfigurationEventPrinter::OnGrabStart" << std::endl; } void OnGrabStarted (CameraDevice& camera) override { std::cout << "ConfigurationEventPrinter::OnGrabStarted" << std::endl; } void OnOpen (CameraDevice& camera) override { std::cout << "ConfigurationEventPrinter::OnOpen" << std::endl; } void OnOpened (CameraDevice& camera) override { std::cout << "ConfigurationEventPrinter::OnOpened" << std::endl; } }; #endif
然后可以在相机初始化时进行如下调用,以完成对此策略的注册
1 RegisterConfiguration (new ConfigurationEventPrinter (), RegistrationMode_ReplaceAll, Cleanup_Delete);
断线重连 现在演示一下通过事件通知机制实现相机的断线重连功能: 首先在CameraDevice
,ConfigurationEvent
和ConfigurationEventHandler
中增加CameraDeviceRemoved
相关条目(具体代`码略)
然后需要定义:
连接保活函数:SendHeartBeat
,用于维持和检测与相机的连接。其为纯虚函数,子类必须实现。由于子类代表着不同品牌的相机,因此在这个函数中调用品牌提供的可以访问相机寄存器的函数即可。如果相机离线,则无法成功访问相机寄存器,此时直接在此函数中抛出异常。
线程启动函数:StartHealthCheckThread
,用于在相机打开成功后循环调用连接保活函数。循环调用连接保活函数,并负责捕获其抛出的异常。收到异常后直接发出相机离线事件并返回,结束保活行为。
断线重连事件处理器:ReconnectHandler
,继承自ConfigurationEventHandler
并重写其中的OnCameraDeviceRemoved
,OnOpened
和OnClose
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 79 80 81 82 83 84 #ifndef CAMERADEVICE_H #define CAMERADEVICE_H #include "ConfigurationEventHandler.h" #include "MiscStruct.h" class CameraDevice { public : virtual bool OpenDevice () final ; virtual bool StartGrabbing () final ; void StartHealthCheckThread () ; void StopHealthCheckThread () ; protected : virtual void RegisterConfiguration (ConfigurationEventHandler* handler, RegistrationMode mode, Cleanup cleanup_procedure) ; virtual void DeregisterConfiguration (ConfigurationEventHandler* handler) ; virtual void NotifyEventHandeler (ConfigurationEvent event) ; virtual bool SpecificOpenDevice () = 0 ; virtual bool SpecificStartGrabbing () = 0 ; virtual void SendHeartBeat () = 0 ; protected : std::vector<HandlerEntry> configuration_event_handlers_; std::atomic<bool > is_health_check_thread_running_; }; class ReconnectHandler : public ConfigurationEventHandler{ public : void OnCameraDeviceRemoved (CameraDevice& camera) override { ... int loop_count = 600 ; bool ret = false ; while (loop_count > 0 && !ret) { if (loop_count % 10 == 0 ) { ret = camera.OpenDevice (); } --loop_count; std::this_thread::sleep_for (std::chrono::seconds (1 )); } } void OnOpened (CameraDevice& camera) override { ... camera.StartHealthCheckThread (); } void OnClose (CameraDevice& camera) override { camera.StopHealthCheckThread (); ... } }; #endif
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 #include "CameraDevice.h" ... void CameraDevice::StartHealthCheckThread () { if (is_health_check_thread_running_) { return ; } is_health_check_thread_running_ = true ; health_check_thread_ = std::thread ([this ]() { try { while (is_health_check_thread_running_) { SendHeartBeat (); std::this_thread::sleep_for (std::chrono::seconds (1 )); } } catch (const std::exception& e) { is_health_check_thread_running_ = false ; NotifyEventHandeler (ConfigurationEvent::OnCameraDeviceRemoved); return ; } }); }
可以参考下面代码注册:
1 RegisterConfiguration (new ReconnectHandler (), RegistrationMode_Append, Cleanup_Delete);