1. 说明
Observer插件开发是基于AFSim的培训PPT(见翻译版《8_AFSIM_开发培训_观察者》)进行一些简化并增加一些输出内容完成的一个wsf的Demo插件,在此插件基础上可以增加其他内容。
1) 删除了定义从txt文件中获取UDP组播地址和端口的功能;
2) 删除了UDP发送到外部接收端的功能(采用控制台输出)
3) 增加了AdvanceTime事件监听函数,输出当前时间
4) 增加了获取场景所有平台的位置和姿态的方法(在AdvanceTime回调函数中获取)
2. Observer说明
AFSim提供的Observer机制,允许AFSim的WSF框架来通知某些事件,扩展的插件可以监听这些事件并添加回调处理,这样就不必修改核心框架(即WSF源码),常用于将仿真数据进行收集和记录,并发送到外部处理程序。

3. wsf提供的Observer
AFSim提供的可以订阅的事件都在WsfObserver namespace中,如下图是AFSim项目工程中wsf核心框架提供的所有Observer,当然还有其他插件的项目自定义扩展了Observer,每个Observer中都定义了相关的回调函数原型:


每个Observer中定义的回调原型可以通过以下订阅语法与一个处理函数进行绑定:
语法:mCallbacks.Add(WsfObserver::AdvanceTime(&GetSimulation()).Connect(&JCSObserver::AdvanceTime, this));
mCallbacks:JCSObserver的成员变量,类型为UtCallbackHolder
Add:订阅函数,参数为WsfObserver及其子类中声明的可订阅的事件
Connect:用于连接事件发生后,需要通知的回调函数,参数为JCSObserver中定义的成员函数
下面来根据文档用vs2019来搭建一个wsf插件,此插件可作为开发其他wsf插件的模板框架。
4. 插件开发步骤
我这里是用VS进行开发的,需要创建dll工程,并引入AFSim的头文件和库文件。
1) 插件导出宏定义
这里导出宏就沿用示例教程中给的代码即可,并在VS的预处理器中添加hobserver_EXPORTS宏:
// hobserver_export.h
#ifndef HOBSERVER_EXPORT_H
#define HOBSERVER_EXPORT_H
#ifdef HOBSERVER_STATIC_DEFINE
# define HOBSERVER_EXPORT
# define HOBSERVER_NO_EXPORT
#else
# ifndef HOBSERVER_EXPORT
# ifdef hobserver_EXPORTS
/* We are building this library */
# define HOBSERVER_EXPORT __declspec(dllexport)
# else
/* We are using this library */
# define HOBSERVER_EXPORT __declspec(dllimport)
# endif
# endif
# ifndef HOBSERVER_NO_EXPORT
# define HOBSERVER_NO_EXPORT
# endif
#endif
#ifndef HOBSERVER_DEPRECATED
# define HOBSERVER_DEPRECATED __declspec(deprecated)
#endif
#ifndef HOBSERVER_DEPRECATED_EXPORT
# define HOBSERVER_DEPRECATED_EXPORT HOBSERVER_EXPORT HOBSERVER_DEPRECATED
#endif
#ifndef HOBSERVER_DEPRECATED_NO_EXPORT
# define HOBSERVER_DEPRECATED_NO_EXPORT HOBSERVER_NO_EXPORT HOBSERVER_DEPRECATED
#endif
#if 0 /* DEFINE_NO_DEPRECATED */
# ifndef HOBSERVER_NO_DEPRECATED
# define HOBSERVER_NO_DEPRECATED
# endif
#endif
#endif /* HOBSERVER_EXPORT_H */
2) 插件注册类
插件注册一般来说是比较固定的写法,除非有特殊的处理。这里按简单的插件注册书写代码:
// PluginRegistration.cpp
#include "hobserver_export.h"
#include "RegisterHObserver.hpp"
#include "WsfPlugin.hpp"
#include "WsfApplication.hpp"
#include "WsfApplicationExtension.hpp"
#include "UtMemory.hpp"
extern "C"
{
HOBSERVER_EXPORT void WsfPluginVersion(UtPluginVersion& aVersion)
{
aVersion = UtPluginVersion(
WSF_PLUGIN_API_MAJOR_VERSION,
WSF_PLUGIN_API_MINOR_VERSION,
WSF_PLUGIN_API_COMPILER_STRING
);
}
HOBSERVER_EXPORT void WsfPluginSetup(WsfApplication& aApplication)
{
// 注册本插件工程
// 因为RegisterJCSObserver是Scenario扩展类,因此此处使用默认Application扩展
aApplication.RegisterExtension("register_hobserver", ut::make_unique<WsfDefaultApplicationExtension<RegisterHObserver>>());
}
}以上代码即告知WSF框架需要加载我们自己开发的插件。(运行流程如果不清楚,请查看《4_AFSIM_开发培训_传感器》第31页开始的关于AFSim的启动加载流程内容)
上述代码中出现的RegisterHObserver是一个Scenario扩展类,为了将我们的Simulation扩展类HObserver注册到Simulation对象中,代码如下:
// RegisterHObserver.hpp
#ifndef RegisterHObserver_HPP
#define RegisterHObserver_HPP
#include "HObserver.hpp"
#include "WsfScenarioExtension.hpp"
#include "WsfSimulation.hpp"
#include "UtMemory.hpp"
class RegisterHObserver: public WsfScenarioExtension
{
public:
~RegisterHObserver() noexcept override = default;
void SimulationCreated(WsfSimulation& aSimulation) override
{
// Simulation对象创建完成后,注册Simulation扩展
aSimulation.RegisterExtension("hobserver", ut::make_unique<HObserver>());
}
};
#endif3) 添加回调函数
上面已经将插件注册的代码完成,此时需要完成HObserver类,用于绑定事件回调处理函数。
此类的主要功能:
初始化时添加回调函数的绑定
在不同的回调函数中完成数据获取并打印到控制台
// HObserver.hpp
#ifndef HObserver_HPP
#define HObserver_HPP
#include "WsfSimulationExtension.hpp"
#include "UtCallbackHolder.hpp"
struct UtPluginObjectParameters;
class WsfPlatform;
class WsfSensor;
class WsfTrack;
class HObserver: public WsfSimulationExtension
{
public:
HObserver();
~HObserver() noexcept override;
bool Initialize() override;
private:
// 以下三个是AFSim教程中的回调函数
void PlatformAdded(double aSimTime, WsfPlatform* aPlatformPtr);
void PlatformDeleted(double aSimTime, WsfPlatform* aPlatformPtr);
void SensorTrackUpdated(double aSimTime, WsfSensor* aSensorPtr, const WsfTrack* aTrackPtr);
// AdvanceTime是新增的回调函数
void AdvanceTime(double aSimTime); // 推进时间
private:
UtCallbackHolder mCallbacks;
double mPreSimTime = 0.0; // 记录上一次的仿真时间,时间未推进时不做任何处理
};
#endif// HObserver.cpp
#include "HObserver.hpp"
#include "RegisterHObserver.hpp"
// WSF
#include "WsfApplication.hpp"
#include "observer/WsfPlatformObserver.hpp"
#include "observer/WsfTrackObserver.hpp"
#include "observer/WsfSimulationObserver.hpp"
#include "WsfPlatform.hpp"
#include "sensor/WsfSensor.hpp"
#include "WsfSimulation.hpp"
#include "WsfTrack.hpp"
#include <iostream>
HObserver::HObserver()
{
}
HObserver::~HObserver() noexcept
{
}
bool HObserver::Initialize()
{
mCallbacks.Add(WsfObserver::SensorTrackUpdated(&GetSimulation()).Connect(&HObserver::SensorTrackUpdated, this));
mCallbacks.Add(WsfObserver::SensorTrackInitiated(&GetSimulation()).Connect(&HObserver::SensorTrackUpdated, this));
mCallbacks.Add(WsfObserver::PlatformAdded(&GetSimulation()).Connect(&HObserver::PlatformAdded, this));
mCallbacks.Add(WsfObserver::PlatformDeleted(&GetSimulation()).Connect(&HObserver::PlatformDeleted, this));
mCallbacks.Add(WsfObserver::AdvanceTime(&GetSimulation()).Connect(&HObserver::AdvanceTime, this));
return true;
}
void HObserver::PlatformAdded(double aSimTime, WsfPlatform* aPlatformPtr)
{
std::cout
<< "PlatformAdded: " << aSimTime << ", "
<< aPlatformPtr->GetName() << ", "
<< aPlatformPtr->GetType() << std::endl;
}
void HObserver::PlatformDeleted(double aSimTime, WsfPlatform* aPlatformPtr)
{
std::cout
<< "PlatformDeleted: " << aSimTime << ", "
<< aPlatformPtr->GetName() << ", "
<< aPlatformPtr->GetType() << std::endl;
}
void HObserver::SensorTrackUpdated(double aSimTime, WsfSensor* aSensorPtr, const WsfTrack* aTrackPtr)
{
double longitude, latitude, altitude;
aTrackPtr->GetLocationLLA(latitude, longitude, altitude);
std::cout
<< "SensorTrackUpdated: " << aSimTime << ", "
<< aSensorPtr->GetName() << ", "
<< aSensorPtr->GetPlatform()->GetIndex() << ", "
<< aTrackPtr->GetTargetIndex() << ", "
<< latitude << ", "
<< longitude << ", "
<< altitude << std::endl;
}
void HObserver::AdvanceTime(double aSimTime)
{
double deltaTime = aSimTime - mPreSimTime;
if (deltaTime < 0.0000001) return; // 未推进,则不处理
mPreSimTime = aSimTime;
std::cout << "AdvanceTime: " << aSimTime << std::endl;
// 获取场景所有平台,并获取平台的位置和姿态
int platformCount = GetSimulation().GetPlatformCount();
for (int i = 0; i < platformCount; ++i)
{
auto platform = GetSimulation().GetPlatformEntry(i);
std::cout << "PlatformName: " << platform->GetName() << std::endl;
std::cout << "PlatformType: " << platform->GetType() << std::endl;
// 位置(经纬高)
auto lla = platform->GetLocationLLA();
std::cout << "PlatformLocation: ";
std::cout << " Longitude: " << lla.mLon
<< " Latitude: " << lla.mLat
<< " Altitude: " << lla.mAlt << std::endl;
// 姿态(横滚 俯仰 航向)
auto ned = platform->GetOrientationNED();
std::cout << "PlatformOrientation: ";
std::cout << " Roll: " << ned.mPhi
<< " Pitch: " << ned.mTheta
<< " Heading: " << ned.mPsi << std::endl;
}
}4) 输出插件到指定目录
将代码进行编译生成dll文件。由于wsf框架在加载插件时,会主动查询wsf_plugins目录下的所有插件并加载,因此需将生成的dll拷贝到可执行程序的wsf_plugins目录下(当然也可以直接生成到此目录,减少每次修改后需要手动拷贝的麻烦):

5. 测试
准备工作做完并成功编译出插件dll后,可通过mission.exe程序加载运行,这里通过加载AFSim自带的demos中的simple_scenario场景来测试运行。此场景中只有一个平台,名称是SimpleStriker,类型是BLUE_STRiKER。

通过mission.exe以实时仿真方式启动:

启动后会加载我们开发的demo插件:

初始化完成后,控制台输出结果为:

说明:这里面的SensorTrackUpdated回调没有触发,是因为我们选择的场景不涉及Track,因此不会产生Track事件,也就无法触发此回调函数。
6. 后记
上述即完成了插件的开发,完成了获取仿真过程的当前时间和平台信息并打印到控制台中。当然这个插件也可以被仿真引擎加载,与mission.exe加载运行的效果是一样的。
另外,这里只是将获取到的信息打印到控制台,那么既然数据都已经获取到了,当然也可以通过其他方式进行处理,如发送到可视化界面展示、保存到数据库、记录到文件等等。
这里也仅仅是搭建了流程,其他更多仿真过程的内容和数据,需要查看第3节Wsf本身提供的那些Observer来自己摸索了。
往期推荐
魔改MapDisplay实现二三维同步
服务端引擎增加fs模式可控

评论