Skip to content

Aircraft example

Prerequisites: 2 - Inheritance (class hierarchy, STL inheritance). Familiarity with HLA FOMs is helpful but not required.

Here you can see how to use an HLA-based definition to implement a simple aircraft simulation using the update() method.

Interface and implementation

In the dummy_aircraft.stl file you can see how we inherit from rpr.Aircraft, and in the implementation dummy_aircraft_impl.cpp you can see a (dummy) implementation of the update function.

import "rpr/RPR-Physical_v2.0.xml"

package aircrafts;

// speed quantity
quantity<f64, m_per_s> Speed;

// speed vector in NED coordinates
struct NedSpeedStruct
{
  north : Speed,
  east  : Speed,
  down  : Speed
}

// A dummy implementation of an aircraft
class DummyAircraft : extends rpr.Aircraft
{
  var speed : NedSpeedStruct [writable];
}
// === dummy_aircraft_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 "rpr/rpr-base_v2.0.xml.h"
#include "stl/dummy_aircraft.stl.h"

// sen
#include "sen/core/base/compiler_macros.h"
#include "sen/core/meta/class_type.h"
#include "sen/kernel/component_api.h"
#include "sen/util/dr/detail/dead_reckoner_base.h"
#include "sen/util/dr/settable_dead_reckoner.h"

// std
#include <cmath>
#include <memory>
#include <utility>

namespace aircrafts
{

// An aircraft that updates its position.
class DummyAircraftImpl: public DummyAircraftBase<>
{
  SEN_NOCOPY_NOMOVE(DummyAircraftImpl)

  // type alias
  using SettableDr = sen::util::SettableDeadReckoner<rpr::BaseEntityBase<>>;
  using DeadReckonerBase = sen::util::DeadReckonerBase;

public:
  using DummyAircraftBase<>::DummyAircraftBase;
  ~DummyAircraftImpl() override = default;

public:
  void registered(sen::kernel::RegistrationApi& api) override
  {
    std::ignore = api;

    // used to update the spatial field of the aircraft
    settableDeadReckoner_ =
      std::make_unique<SettableDr>(*this, sen::util::DrThreshold {0.0, 0.0f, sen::util::ReferenceSystem::world});

    // commanded speed can be modified by the user
    speedGuard_ = onSpeedChanged({this,
                                  [this]()
                                  {
                                    auto situation = deadReckoner_.geodeticSituation(api_->getTime());
                                    const auto& [north, east, down] = getSpeed();
                                    situation.velocityVector = {north, east, down};
                                    deadReckoner_.updateGeodeticSituation(std::move(situation));
                                  }});
  }

  void update(sen::kernel::RunApi& runApi) override
  {
    api_ = &runApi;

    if (const auto currentTime = runApi.getTime(); currentTime == runApi.getStartTime())
    {
      sen::util::GeodeticSituation initialSituation;
      initialSituation.timeStamp = currentTime;
      initialSituation.worldLocation = {40.0, 0.0, 10000.0};
      const auto& [north, east, down] = getSpeed();
      initialSituation.velocityVector = {north, east, down};
      deadReckoner_.updateGeodeticSituation(std::move(initialSituation));
    }
    // move the entity using the dead reckoner with the specified speed
    auto situation = deadReckoner_.geodeticSituation(runApi.getTime());

    // update the spatial using the settable dead reckoner
    settableDeadReckoner_->setSpatial(situation);
  }

private:
  DeadReckonerBase deadReckoner_;
  std::unique_ptr<SettableDr> settableDeadReckoner_;
  sen::ConnectionGuard speedGuard_;
  sen::kernel::RunApi* api_ = nullptr;
};

SEN_EXPORT_CLASS(DummyAircraftImpl)

}  // namespace aircrafts

How to run it

Let's define what we want to run in our Sen kernel.

Configuration file
# $schema: ../base/schema.json
include:
  - ../base/term.yaml

build:
  - name: myComponent
    freqHz: 30
    imports: [aircrafts]
    group: 3
    objects:
      - class: aircrafts.DummyAircraftImpl
        name: myAircraft
        bus: my.tutorial
        speed:
          north: 250
          east: 0
          down: 0
        entityType: &theType
          entityKind: 1
          domain: 2
          countryCode: 198
          category: 1
          subcategory: 3
          specific: 0
          extra: 0
        alternateEntityType: *theType
        entityIdentifier:
          entityNumber: 1
          federateIdentifier:
            siteID: 1
            applicationID: 1

This will open a shell and tell Sen to instantiate one aircraft in the my.tutorial bus.

To run it, let's call sen run:

sen run config/3_aircraft/1_aircraft.yaml

You should be able to repeatedly call my.tutorial.myAircraft.getSpatial and see the evolution of the position.

Using the explorer

You can also load the explorer to have an easier time following the updates.

sen run config/3_aircraft/2_aircraft_exp.yaml

Using virtualized time

Let's now start the kernel in virtualized time mode by changing our config file.

Configuration file
# $schema: ../base/schema.json
include:
  - 1_aircraft.yaml

# In this example we start the kernel with the virtualized time.
# Here the time will not advance until we explicitly request it.
# You will find that the kernel will be publishing an object called
# 'clock' in the 'my.tutorial' bus, that you can use to control the
# virtual time and see how components advance accordingly.
kernel:
  runMode: virtualTime
  clockMaster: true
  clockBus: my.tutorial

We can run it with the following command:

sen run config/3_aircraft/3_aircraft_virtual_time.yaml

Now, if you execute my.tutorial.myAircraft.getSpatial you will see that there are no updates.

Let's now advance one cycle by using the clock API.

my.tutorial.master.step
my.tutorial.myAircraft.getSpatial

You can see how the component got triggered.

If you want to advance the time more, say 60 seconds:

my.tutorial.master.advanceTime "60 s"

This should run relatively fast, as we are not performing expensive computations.