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>());
    }
};

#endif

3) 添加回调函数

上面已经将插件注册的代码完成,此时需要完成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模式可控

文末二维码.png