Pylon配置事件机制的实现

paw5zx Lv4

前言

最近在做自己的相机管理框架的时候,又回想起了Pylon(Basler的相机控制软件)API中的各种事件机制,感觉非常有用。但由于Pylon的代码不是开源的,只能尝试自己实现。

在本文中先简单介绍Pylon API中提供的的配置事件处理程序(即Pylon::CConfigurationEventHandler)及其调用机制,然后尝试自己实现。

只是尽可能地尝试复现其内部调用机制,很多细节在本文中不会给出。

Pylon的实现

Pylon::CConfigurationEventHandler

首先先简单介绍一下Pylon::CConfigurationEventHandler,它是一个用于相机配置的事件处理类,允许用户在相机的状态变更过程前后的关键点上自定义配置。例如:

  • OnOpened方法,将会在相机成功打开后被调用,用户可以重写此方法以实现自定义操作;
  • OnGrabStopped方法,将会在抓取行为停止完成后被调用,用户可以重写此方法以实现自定义操作;
  • 等,更多回调函数的声明请见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
//file: ConfigurationEventPrinter.h
#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 /* INCLUDED_CONFIGURATIONEVENTPRINTER_H_663006 */

②使用时通过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 /*argc*/, char* /*argv*/[] )
{
// 必要的初始化操作
...

// 实例化相机对象
CTlFactory& tlFactory = CTlFactory::GetInstance();
CInstantCamera camera( tlFactory.CreateFirstDevice() );

// 注册事件
camera.RegisterConfiguration(new CConfigurationEventPrinter, RegistrationMode_Append, Cleanup_Delete);

// 打开相机,输出OnOpen event,当相机打开成功后,输出OnOpened event
camera.open();

// 必要的资源释放等操作
...
}

ERegistrationMode

定义了如何注册一个handler

1
2
3
4
5
enum ERegistrationMode
{
RegistrationMode_Replace, // 替换已有的所有handler,只保留当前注册的
RegistrationMode_Append // 添加到handler列表的末尾
};

ECleanup

定义在handler不再被使用时由谁删除

1
2
3
4
5
enum ECleanup
{
Cleanup_None, // 不做任何清理,用户自己负责delete
Cleanup_Delete // 由CInstantCamera析构时自动delete handler
};

自己实现

先分析一下要实现像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
//file: ConfigurationEventHandler.h
#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 // CONFIGURATIONEVENTHANDLER_H

基类中的回调函数定义为空实现,即使用户注册了此类,也不会有什么操作。

然后我们可以定义一个子类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
//file: ConfigurationEventPrinter.h
#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 // CONFIGURATIONEVENTPRINTER_H

当然用户还可以定义各种各样的事件响应策略,如断线重连

注册/注销机制

在介绍注册/注销机制前,我们先引入一个抽象相机类CameraDevice,所有不同品牌的具体相机类继承自此类。此类充当被观察者的角色,被观察者内部包含了观察者的集合,并提供了注册,注销,通知观察者的统一接口。同时也维护了观察者的列表,自身状态发生变化时通知所有的观察者。

而观察者就是所有继承自ConfigurationEventHandler的类的实例,其提供了具体的响应细节。

Handler列表

我们先来看被观察者中的观察者列表,考虑到Handler可以被自动清理或由使用者手动清理,需要自定义一个结构体封装一个Handler及其生命周期管理策略:

1
2
3
4
5
6
7
8
9
10
//file: CameraDevice.h
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
//file: CameraDevice.h
class CameraDevice
{
protected:
struct HandlerEntry
{
ConfigurationEventHandler* handler;
Cleanup cleanup;
};

protected:
std::vector<HandlerEntry> configuration_event_handlers_; //事件处理器的注册和注销不频繁,且遍历操作占主导,vector综合性能高
};

注册

现在我们关注Handler的注册环节,参考Pylon,注册环节除了要在相机对象上传入Handler的实例,还要传入两个额外的参数:注册策略(RegistrationMode)和生命周期管理策略(Cleanup),因此函数的声明如下:

1
2
3
//file: CameraDevice.h
// 允许重复注册
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
//file: CameraDevice.cpp
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
//file: CameraDevice.h
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
//file: CameraDevice.cpp
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
//file: MiscStruct.h
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
//file: CameraDevice.cpp
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
//file: CameraDevice.cpp
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

//file: 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_; //事件处理器的注册和注销不频繁,且遍历操作占主导,vector综合性能高
};

#endif //CAMERADEVICE_H
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
//file: CameraDevice.cpp

// 省略构造函数等
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
//file: ConfigurationEventHandler.h
#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 // CONFIGURATIONEVENTHANDLER_H

辅助结构体

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
//file: MiscStruct.h
#ifndef MISCSSTRUCT_H
#define MISCSSTRUCT_H

enum RegistrationMode
{
RegistrationMode_Append, //The item is appended to the list of registered items.
RegistrationMode_ReplaceAll //The item replaces all other registered items.
};

enum Cleanup
{
Cleanup_None, //The caller is responsible for deleting the passed object. The object needs to be detached or deregistered before deletion.
Cleanup_Delete //The passed object is deleted if it is not needed anymore.
};

enum ConfigurationEvent
{
//仅定义部分事件标识
OnOpen,
OnOpened,
OnGrabStart,
OnGrabStarted
};

#endif // MISCSSTRUCT_H

示例

在实际使用的过程中,我们需要将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
//file: ConfigurationEventPrinter.h
#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 // CONFIGURATIONEVENTPRINTER_H

然后可以在相机初始化时进行如下调用,以完成对此策略的注册

1
RegisterConfiguration(new ConfigurationEventPrinter(), RegistrationMode_ReplaceAll, Cleanup_Delete);

断线重连

现在演示一下通过事件通知机制实现相机的断线重连功能:
首先在CameraDeviceConfigurationEventConfigurationEventHandler中增加CameraDeviceRemoved相关条目(具体代`码略)

然后需要定义:

  • 连接保活函数:SendHeartBeat,用于维持和检测与相机的连接。其为纯虚函数,子类必须实现。由于子类代表着不同品牌的相机,因此在这个函数中调用品牌提供的可以访问相机寄存器的函数即可。如果相机离线,则无法成功访问相机寄存器,此时直接在此函数中抛出异常。
  • 线程启动函数:StartHealthCheckThread,用于在相机打开成功后循环调用连接保活函数。循环调用连接保活函数,并负责捕获其抛出的异常。收到异常后直接发出相机离线事件并返回,结束保活行为。
  • 断线重连事件处理器:ReconnectHandler,继承自ConfigurationEventHandler并重写其中的OnCameraDeviceRemovedOnOpenedOnClose
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
//file: CameraDevice.h
#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_; //事件处理器的注册和注销不频繁,且遍历操作占主导,vector综合性能高

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 //CAMERADEVICE_H
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
//file: CameraDevice.cpp
#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);
  • 标题: Pylon配置事件机制的实现
  • 作者: paw5zx
  • 创建于 : 2024-11-15 13:53:16
  • 更新于 : 2025-05-12 15:49:46
  • 链接: https://paw5zx.github.io/pylon-configuration-events-handling/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论