Skip to content

HLA Servers Examples

Here we implement a terrain server (and a client) and a weather server.

Terrain Server

Interface

The terrain server interface is defined like this:

terrain_server.stl
import "basic_types.stl"

package terrain_server;

// Provides information about the terrain.
class TerrainServer
{
  // Where to publish the objects that might be created by this server.
  var targetBus : string [static];

  // Get information about a line of sight.
  //
  // @param ref the place for the query.
  // @param range maximum distance for the test.
  // @param noClouds if true, clouds are ignored.
  // @param asObject if true, an LosQuery object is created.
  fn requestLos(ref: netn.GeoReferenceVariant, range: Range, noClouds: bool, asObject: bool) -> LoS;

  // Get information about the height of the terrain.
  //
  // @param ref the place for the query.
  // @param asObject if true, an HatHotQuery object is created.
  fn requestHatHot(ref: netn.GeoReferenceVariant, asObject: bool) -> HatHot;
}

The main functions are requestLos and requestHatHot.

This class uses the types defined in the following STL file:

basic_types.stl
import "rpr/RPR-Base_v2.0.xml"
import "netn/NETN-BASE.xml"
import "netn/NETN-METOC.xml"

package terrain_server;

// Helper to encode azimuth and elevation information (degrees)
struct AzimElev
{
  azimuth   : netn.DirectionDegreesFloat32,
  elevation : netn.DirectionDegreesFloat32
}

// Helper to encode min/max information (meters)
struct Range
{
  min : rpr.MeterFloat64,
  max : rpr.MeterFloat64
}

// Line of Sight information/
struct LoS
{
  intersection : netn.GeodeticPoint,  // point of intersection
  range        : rpr.MeterFloat32,    // distance from the start to the intersection
  normalVector : AzimElev,            // normal vector at the intersection point
  materialCode : u32                  // material of the object/terrain at the intersection
}

// Height of/above terrain information
struct HatHot
{
  point        : netn.GeodeticPoint,         // geographic location
  hat          : netn.AltitudeMeterFloat64,  // height above terrain at the point
  hot          : netn.AltitudeMeterFloat64,  // height of terrain at the point
  normalVector : AzimElev,                   // unitary normal vector of the terrain
  materialCode : u32                         // material of the terrain at the point
}

// Represents a HaT/HoT query that will be continuously updated by its owner
class HatHotQuery
{
  var ref    : netn.GeoReferenceVariant [static]; // tracked object, position or entity
  var hatHot : HatHot;                            // HaT/HoT information
}

// Represents a line of sight query that will be continuously updated by its owner
class LosQuery
{
  var ref : netn.GeoReferenceVariant [static]; // tracked object, position or entity
  var los : LoS;                               // line of sight information
}

Server Implementation

This terrain server implementation provides answers with random values.

terrain_server_impl.cpp
// === terrain_server_impl.cpp =========================================================================================
//                                               Sen Infrastructure
//                   Released under the Apache License v2.0 (SPDX-License-Identifier Apache-2.0).
//                                    See the LICENSE.txt file for more information.
//                   © Airbus SAS, Airbus Helicopters, and Airbus Defence and Space SAU/GmbH/SAS.
// =====================================================================================================================

// generated code
#include "netn/netn-base.xml.h"
#include "netn/netn-metoc.xml.h"
#include "stl/terrain_server.stl.h"

// sen
#include "sen/core/base/compiler_macros.h"
#include "sen/core/base/numbers.h"
#include "sen/core/base/uuid.h"
#include "sen/core/meta/class_type.h"
#include "sen/core/obj/native_object.h"
#include "sen/core/obj/object_source.h"
#include "sen/kernel/component_api.h"

// std
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>

namespace terrain_server
{

//------------------------------------------------------------------------------------------------------------//
// Random data generators
//------------------------------------------------------------------------------------------------------------//

/// A very basic random number generator.
template <typename T = float32_t>
[[nodiscard]] T getRand(T low, T high) noexcept
{
  return low + static_cast<T>(rand()) / (static_cast<T>(static_cast<T>(RAND_MAX) / (high - low)));  // NOLINT
}

/// Returns a HatHot structure filled in with random data.
HatHot makeRandomHatHot()
{
  HatHot hatHot {};
  hatHot.hat = getRand<float64_t>(0.0, 10000.0);
  hatHot.hot = getRand<float64_t>(0.0, 10000.0);
  hatHot.materialCode = getRand<uint32_t>(0, 127);
  hatHot.normalVector.azimuth = getRand<float32_t>(-180.0f, 180.0f);
  hatHot.normalVector.elevation = getRand<float32_t>(-90.0f, 90.0f);
  hatHot.point.altitude = getRand<float64_t>(0.0, 10000.0);
  hatHot.point.latitude = getRand<float64_t>(-90.0, 90.0);
  hatHot.point.longitude = getRand<float64_t>(-180.0, 180.0);
  return hatHot;
}

/// Returns a LoS structure filled in with random data.
LoS makeRandomLoS()
{
  LoS los {};
  los.range = getRand<float64_t>(0.0, 10000.0);
  los.materialCode = getRand<uint32_t>(0, 127);
  los.normalVector.azimuth = getRand<float32_t>(-180.0f, 180.0f);
  los.normalVector.elevation = getRand<float32_t>(-90.0f, 90.0f);
  los.intersection.altitude = getRand<float64_t>(0.0, 10000.0);
  los.intersection.latitude = getRand<float64_t>(-90.0, 90.0);
  los.intersection.longitude = getRand<float64_t>(-180.0, 180.0);
  return los;
}

/// Implementation of a HatHotQuery that generates random data.
class HatHotQuery: public HatHotQueryBase
{
public:
  SEN_NOCOPY_NOMOVE(HatHotQuery)

public:
  using HatHotQueryBase::HatHotQueryBase;
  ~HatHotQuery() override = default;

public:
  void update(sen::kernel::RunApi& /*api*/) override { setNextHatHot(makeRandomHatHot()); }
};

/// Implementation of a LineOfSightQuery that generates random data.
class LineOfSightQuery: public LosQueryBase
{
public:
  SEN_NOCOPY_NOMOVE(LineOfSightQuery)

public:
  using LosQueryBase::LosQueryBase;
  ~LineOfSightQuery() override = default;

public:
  void update(sen::kernel::RunApi& /*api*/) override { setNextLos(makeRandomLoS()); }
};

//------------------------------------------------------------------------------------------------------------//
// TerrainServerImpl
//------------------------------------------------------------------------------------------------------------//

class TerrainServerImpl final: public TerrainServerBase
{
public:
  SEN_NOCOPY_NOMOVE(TerrainServerImpl)

public:
  using TerrainServerBase::TerrainServerBase;

  ~TerrainServerImpl() override
  {
    env_->remove(objs_);  // remove all the objects that we published.
  }

protected:
  void registered(sen::kernel::RegistrationApi& api) override
  {
    env_ = api.getSource(getTargetBus());  // open and store the bus where we will publish
  }

protected:  // TerrainServer methods
  [[nodiscard]] LoS requestLosImpl(const netn::GeoReferenceVariant& ref,
                                   const Range& /*range*/,
                                   bool /*noClouds*/,
                                   bool asObject) override
  {
    addObjectIfNeeded<LineOfSightQuery>(asObject, ref);
    return makeRandomLoS();
  }

  [[nodiscard]] HatHot requestHatHotImpl(const netn::GeoReferenceVariant& ref, bool asObject) override
  {
    addObjectIfNeeded<HatHotQuery>(asObject, ref);
    return makeRandomHatHot();
  }

private:
  template <typename T>
  void addObjectIfNeeded(bool needed, const netn::GeoReferenceVariant& geoReference)
  {
    if (needed)
    {
      objs_.push_back(std::make_shared<T>(getName() + ".object" + std::to_string(objs_.size()), geoReference));
      env_->add(objs_.back());
    }
  }

private:
  std::shared_ptr<sen::ObjectSource> env_;
  std::vector<std::shared_ptr<sen::NativeObject>> objs_;
};

SEN_EXPORT_CLASS(TerrainServerImpl)

}  // namespace terrain_server

Client Implementation

This terrain client implementation looks for all the servers and continuously requests information.

terrain_server_impl.cpp
// === terrain_client_impl.cpp =========================================================================================
//                                               Sen Infrastructure
//                   Released under the Apache License v2.0 (SPDX-License-Identifier Apache-2.0).
//                                    See the LICENSE.txt file for more information.
//                   © Airbus SAS, Airbus Helicopters, and Airbus Defence and Space SAU/GmbH/SAS.
// =====================================================================================================================

// generated code
#include "netn/netn-metoc.xml.h"
#include "stl/terrain_client.stl.h"
#include "stl/terrain_server.stl.h"

// sen
#include "sen/core/base/compiler_macros.h"
#include "sen/core/meta/class_type.h"
#include "sen/core/obj/subscription.h"
#include "sen/kernel/component_api.h"

// std
#include <cstdio>
#include <iostream>
#include <memory>
#include <utility>

namespace terrain_server
{

class TerrainClientImpl: public TerrainClientBase
{
  SEN_NOCOPY_NOMOVE(TerrainClientImpl)

public:
  using TerrainClientBase::TerrainClientBase;
  ~TerrainClientImpl() override = default;

public:
  void registered(sen::kernel::RegistrationApi& api) override
  {
    sub_ = api.selectAllFrom<TerrainServerInterface>(getServerBus());  // declare an interest on all terrain servers
  }

  void update(sen::kernel::RunApi& api) override
  {
    for (auto server: sub_->list.getObjects())
    {
      std::puts("requesting hat/hot");
      server->requestHatHot(netn::GeoReferenceVariant(),
                            false,
                            {api.getWorkQueue(),
                             [](const auto& response)
                             {
                               if (response)
                               {
                                 std::cout << response.getValue() << "\n";
                               }
                             }});
    }
  }

private:
  std::shared_ptr<sen::Subscription<TerrainServerInterface>> sub_;
};

SEN_EXPORT_CLASS(TerrainClientImpl)

}  // namespace terrain_server

How to run it

In one terminal:

sen run config/9_hla_servers/1_terrain_client.yaml

In another terminal:

sen run config/9_hla_servers/1_terrain_server.yaml

You should see how the client detects the server and prints the query results.

Weather Server

Interface

The weather server interface is structured similarly to the terrain example:

weather_server.stl
import "rpr/RPR-Base_v2.0.xml"
import "netn/NETN-BASE.xml"
import "netn/NETN-METOC.xml"

package weather_server;

// Provides information about weather conditions.
class WeatherServer
{
  var targetBus : string [static]; // Where to publish the objects created by this server

  // Request information about the weather.
  //
  // @param ref the place where we want to get the information.
  // @param asObject true if we also want the response to be an object that auto-updates.
  fn reqWeather(ref: netn.GeoReferenceVariant, asObject: bool) -> netn.WeatherCondition;

  // Request information about the land surface.
  //
  // @param ref the place where we want to get the information.
  // @param asObject true if we also want the response to be an object that auto-updates.
  fn reqLandSurface(ref: netn.GeoReferenceVariant, asObject: bool) -> netn.LandSurfaceCondition;

  // Request information about the troposphere.
  //
  // @param ref the place where we want to get the information.
  // @param asObject true if we also want the response to be an object that auto-updates.
  fn reqTroposphereLayer(ref: netn.GeoReferenceVariant, asObject: bool, layer: netn.LayerStruct) -> netn.TroposphereLayerCondition;

  // Request information about a surface of water.
  //
  // @param ref the place where we want to get the information.
  // @param asObject true if we also want the response to be an object that auto-updates.
  fn reqWaterSurface(ref: netn.GeoReferenceVariant, asObject: bool) -> netn.WaterSurfaceCondition;

  // Request information about underwater conditions.
  //
  // @param ref the place where we want to get the information.
  // @param asObject true if we also want the response to be an object that auto-updates.
  // @param layer the particular layer for which we want information.
  fn reqSubsurfaceLayer(ref: netn.GeoReferenceVariant, asObject: bool, layer: netn.LayerStruct) -> netn.SubsurfaceLayerCondition;
}

Implementation

The implementation is also very simple. We generate random data as well, but we need to take care of more object types and handle UUIDs.

weather_server.cpp
// === weather_server.cpp ==============================================================================================
//                                               Sen Infrastructure
//                   Released under the Apache License v2.0 (SPDX-License-Identifier Apache-2.0).
//                                    See the LICENSE.txt file for more information.
//                   © Airbus SAS, Airbus Helicopters, and Airbus Defence and Space SAU/GmbH/SAS.
// =====================================================================================================================

// generated code
#include "netn/netn-base.xml.h"
#include "netn/netn-metoc.xml.h"
#include "stl/weather_server.stl.h"

// sen
#include "sen/core/base/compiler_macros.h"
#include "sen/core/base/uuid.h"
#include "sen/core/meta/class_type.h"
#include "sen/core/obj/object_source.h"
#include "sen/kernel/component_api.h"

// package
#include "randomize.h"

// std
#include <memory>
#include <string>
#include <utility>
#include <vector>

namespace weather_server  // NOLINT
{

/// The skeleton of a weather server that produces random data
class WeatherServerImpl final: public WeatherServerBase
{
public:
  SEN_NOCOPY_NOMOVE(WeatherServerImpl)

public:
  using WeatherServerBase::WeatherServerBase;
  ~WeatherServerImpl() final { env_->remove(objs_); }

public:
  void registered(sen::kernel::RegistrationApi& api) final
  {
    sen::UuidRandomGenerator()().copy(serviceId_);  // create a UUID for us
    env_ = api.getSource(getTargetBus());           // get the bus we will use to publish our objects
  }

protected:  // WeatherServer methods
  netn::WeatherCondition reqWeatherImpl(const netn::GeoReferenceVariant& ref, bool asObject) final
  {
    return makeResponse<netn::WeatherCondition, netn::WeatherBase<>>(ref, asObject);
  }

  netn::LandSurfaceCondition reqLandSurfaceImpl(const netn::GeoReferenceVariant& ref, bool asObject) final
  {
    return makeResponse<netn::LandSurfaceCondition, netn::LandSurfaceBase<>>(ref, asObject);
  }

  netn::TroposphereLayerCondition reqTroposphereLayerImpl(const netn::GeoReferenceVariant& ref,
                                                          bool asObject,
                                                          const netn::LayerStruct& layer) final
  {
    std::ignore = layer;
    return makeResponse<netn::TroposphereLayerCondition, netn::TroposphereLayerBase<>>(ref, asObject);
  }

  netn::WaterSurfaceCondition reqWaterSurfaceImpl(const netn::GeoReferenceVariant& ref, bool asObject) final
  {
    return makeResponse<netn::WaterSurfaceCondition, netn::WaterSurfaceBase<>>(ref, asObject);
  }

  netn::SubsurfaceLayerCondition reqSubsurfaceLayerImpl(const netn::GeoReferenceVariant& ref,
                                                        bool asObject,
                                                        const netn::LayerStruct& layer) final
  {
    std::ignore = layer;
    return makeResponse<netn::SubsurfaceLayerCondition, netn::SubsurfaceLayerBase<>>(ref, asObject);
  }

private:
  template <typename R, typename O>
  [[nodiscard]] R makeResponse(const netn::GeoReferenceVariant& ref, bool asObject)
  {
    auto response = MakeRandom<R>::make(ref);                                     // create a random response
    response.environmentObjectId = asObject ? addObject<O>(ref) : netn::UUID {};  // if needed, create the object
    return response;
  }

  template <typename T>
  [[nodiscard]] netn::UUID addObject(const netn::GeoReferenceVariant& ref)
  {
    netn::UUID id;
    sen::UuidRandomGenerator()().copy(id);                                   // create an id for our object
    const auto name = getName() + ".object" + std::to_string(objs_.size());  // create a name
    objs_.push_back(std::make_shared<T>(name, id, name, serviceId_, ref));   // save our new instance
    env_->add(objs_.back());                                                 // publish it to the bus
    return id;
  }

private:
  std::shared_ptr<sen::ObjectSource> env_;
  std::vector<std::shared_ptr<netn::METOCRootBase<>>> objs_;
  netn::UUID serviceId_;
};

SEN_EXPORT_CLASS(WeatherServerImpl)

}  // namespace weather_server

How to run it

sen run sen run config/9_hla_servers/3_weather_server.yaml

In this case you need to populate the parameters in a more involved manner due to the nature of the data model:

my.tutorial.weatherServer.reqWeather {"type": "GeodeticLocation", "value": { "lat": 0, "lon": 0}}, false