DUECA/DUSIME
Loading...
Searching...
No Matches
Simulation example

This section describes a large part of the SpacePlane model simulation that has been used as an example in this manual.

This simulation derived from an experiment implemented on the Human-Machine Systems lab of the Control and Simulation Division, Faculty of Aerospace Engineering The simulation model of the equations of motion of the spaceplane, including the control augmentation system, was programmed in one Simulink model. Using the Real-Time Workshop (RTW) toolbox, C code was generated for this Simulink model. Input of the RTW code are the pitch and roll stick inputs, and the output of the code is an output vector with all spaceplane motions.

The simulation was controlled with a control loaded side stick. This stick uses a hydraulic servo motor to simulate mechanical properties, in this case the mechanical properties of a passive (mass, spring and damper) stick.

Instrument display was done on a PC with a fast graphics card. The display is drawn using the output of the RTW model.

In addition to the display output, output to file was needed. The stick force and position data, and the spaceplane model output data, were recorded at a 50 Hz rate. Output to the control position of the experiment leader was also needed, during the run some key variables were shown in a window at the experiment leader's position, and key variables at certain points in the experiment (points along the pre-defined trajectory) were also shown and recorded. In this way it was for example possible to see the sink rate at touch down directly after a run.

example_flow

Data Flow diagram

Modules (or Blocks)

The first step in designing the simulation is setting up a (DUECA) data flow diagram. This diagram defines the modules used in the simulation, and the data (types) they send to each other.

DUECA data flow diagram for the spaceplane

The data flow diagram contains the following blocks:

  • A module MMSLabStick, which reads the input force on the stick (i.e. directly from hardware), calculates the dynamics of the stick simulation, outputs the result of this calculation to the stick (to hardware), and also sends out stick position and force over a channel.

  • A module SpacePlane, which reads the stick input (position), feeds that to the RTW model, makes an update of the model, and sends out the output vector of the model over a channel.

  • A module DisplaySpace, which reads the spaceplane output vector, and draws the instrument display.

  • A module NumericOutput, which reads the spaceplane output vector the stick position and force data, and prints these to a file.

  • A module Evaluator, which reads the spaceplane output vector, calculates the deviation from the nominal path and presents this and a few other key variables in a window.

These are all the modules that are needed from a model point-of-view. One other module is added for practical reasons. As will be explained in the following, the stick control loading and the spaceplane model run on different computers. The control loading has to run at a high update rate (2000 Hz), and it also produces data at 2000 Hz. The spaceplane model runs slower (50 Hz), and thus it needs every 40th output of the control loading model. Now, without modifications, DUECA would send the stick data at 2000 Hz to the other computers, where only 1/40 th of this data would be used. An additional routine, called RateReduction, which runs in the computer with the stick module, reads the stick data and writes with only 50 Hz.

Data types communicated over channels

The following model-related data types were communicated over the channels:

  • PrimaryControls, a class that contains all data from the side stick.
  • SpacePlaneY, the output vector of the space plane.
  • DisplaySelect, the selected display.

Here is the file input for the code generator:

(Type double)

(EventAndStream PrimaryControls
        (double stick_roll )
        (double stick_pitch )
        (double roll_moment)
        (double pitch_moment)
        (double roll_rate)
        (double pitch_rate))

(EventAndStream SpacePlaneY
        (double Y 29 ))

(Type int )
(Event DisplaySelect
        (int type ))

Header and implementation files

Here are all header and implementation files for the modules.

  • First the stick input:

    /* ------------------------------------------------------------------ */
    /* item : MMSLabStick.hxx
    made by : Rene' van Paassen
    date : 001027
    category : header file
    description :
    changes : 001027 first version
    language : C++
    copyright : (c) 2016 TUDelft-AE-C&S
    copyright : (c) 2022 René van Paassen
    license : EUPL-1.2
    WARNING : This example has not been updated to current
    DUECA interfacing specifications
    */
    #ifndef MMSLabStick_hh
    #define MMSLabStick_hh
    #ifdef MMSLabStick_cc
    #endif
    #include <SimulationModule.hxx>
    #include <StreamAccessToken.hxx>
    #include <PrimaryControls.hxx>
    class PassiveManipulator;
    class ParameterTable;
    class MMSLabStick: public SimulationModule
    {
    // definition of the stick roll properties
    double roll_mass, roll_damping,
    roll_spring_left, roll_spring_middle, roll_spring_right,
    roll_breakout, roll_friction, roll_stiction,
    roll_x_min, roll_x_trans_lower, roll_x_neutral,
    roll_x_trans_upper, roll_x_max,
    roll_output_k_force, roll_output_k_pos, roll_output_offset;
    // definition of the stick pitch properties
    double pitch_mass, pitch_damping,
    pitch_spring_aft, pitch_spring_middle, pitch_spring_forward,
    pitch_breakout, pitch_friction, pitch_stiction,
    pitch_x_min, pitch_x_trans_lower, pitch_x_neutral,
    pitch_x_trans_upper, pitch_x_max,
    pitch_output_k_force, pitch_output_k_pos, pitch_output_offset;
    // definition of generic properties
    double dt, stick_arm;
    bool kickstart_io;
    // two systems, roll and pitch channel
    PassiveManipulator *roll, *pitch;
    // scale factors for the input
    static const double i_scale[6], i_offset[6];
    static const double o_scale[3], o_offset[3];
    // input force check
    int force_check_count;
    double fcheck[2];
    // output channels for the controls, and inco output channel
    StreamChannelWriteToken<PrimaryControls> controls;
    EventChannelWriteToken<PrimaryControls> i_controls;
    // activity
    Callback<MMSLabStick> cb1, cb2;
    ActivityCallback measure_control;
    ActivityCallback calculate_inco;
    public:
    const static ParameterTable* getMyParameterTable();
    static const char* const classname;
    static const IncoTable* getMyIncoTable();
    /// constructor, should be called from scheme, via the ModuleCreator
    MMSLabStick(Entity* e, const char* part, const PrioritySpec& ts);
    /// destructor
    ~MMSLabStick();
    /// further specification, update rate
    bool setTiming(const TimeSpec& t);
    public:
    /// start responsiveness to input data
    void startModule(const TimeSpec &time);
    /// stop responsiveness to input data
    void stopModule(const TimeSpec &time);
    /// check sanity of all data and report ok or not
    bool isPrepared();
    private:
    /// the method that implements the main calculation
    void doStep(const TimeSpec& ts);
    /** performs an inco calculation. Should NOT use current state
    uses event channels parallel to the stream data channels,
    calculates based on the event channel input, the steady state
    output. */
    void doIncoCalculation(const TimeSpec& ts);
    /// snapshot capability, sending
    Snapshot* sendSnapshot(const TimeSpec& t, bool inco);
    /// restoring state from snapshot
    void loadSnapshot(const Snapshot* );
    };
    #endif
    /* ------------------------------------------------------------------ */
    /* item : MMSLabStick.cxx
    made by : Rene' van Paassen
    date : 001027
    category : body file
    description :
    changes : 001027 first version
    language : C++
    copyright : (c) 2016 TUDelft-AE-C&S
    copyright : (c) 2022 René van Paassen
    license : EUPL-1.2
    WARNING : This example has not been updated to current
    DUECA interfacing specifications
    */
    #define MMSLabStick_cc
    #include "MMSLabStick.hxx"
    #include "PassiveManipulator.hxx"
    #include <Ticker.hxx>
    #include <IncoTable.hxx>
    #include <ParameterTable.hxx>
    extern "C" {
    #include "natmio.h"
    #include "dda-06.h"
    }
    #include "config.h"
    #define DO_INSTANTIATE
    #include <TypeCreator.hxx>
    #include <VarProbe.hxx>
    #include <MemberCall.hxx>
    #include <Callback.hxx>
    #include <StreamAccessToken.hxx>
    #include <EventAccessToken.hxx>
    #include <Event.hxx>
    //#define SKIP_STICK_IO
    const char* const MMSLabStick::classname = "mmslab-stick";
    // input scale factors
    // for roll and pitch, assuming that these were scaled to give 100N/10V
    // for roll and 81.33N/10V for pitch
    // These gains give the moment. (100N * 0.09)
    // A/D scale has been set to 5V, so gains must be multiplied by 0.5
    const double MMSLabStick::i_scale[6] = {-0.00458 * 0.5, // roll [Nm/-]
    -8.784e-4, // roll [rad/-]
    -0.00366 * 0.5, // pitch [Nm/-]
    6.608e-4, // pitch [rad/-]
    0.0, 0.0};
    const double MMSLabStick::i_offset[6] =
    {41.0,
    -2.958e-3, //roll [rad]
    48.0,
    4.503e-4, //pitch [rad]
    0.0,
    0.0};
    //const double MMSLabStick::o_scale[3] = {-4228.0*3.3, // roll [-/rad]
    // 5402.0*3.3, // pitch [-/rad]
    // 0.0}; // yaw
    const double DEG_RAD = 3.1415927/180.0;
    // this gain has been guessed. Adri adjusted input gain for stick with
    // factor 1/3.3 Above (commented out) calculation gives unrealistic values,
    // with outputs way over 2000 for max roll/pitch position (30, 20 deg resp,
    // converted to radians. A quick test revealed a digital output value of
    // 1900 is close to max excursion for both roll and pitch. this has been
    // implemented below.
    const double MMSLabStick::o_scale[3] = {-1900.0/(30.0*DEG_RAD), // roll [-/rad]
    1900.0/(20.0*DEG_RAD), // pitch [-/rad]
    0.0}; // yaw
    const double MMSLabStick::o_offset[3] = {0.0, 0.0, 0.0};
    const IncoTable* MMSLabStick::getMyIncoTable()
    {
    static const IncoTable my_inco_table[] =
    {
    {(new IncoVariable("stick pitch trim", -1.0, 1.0))
    ->forMode(FlightPath, Control),
    new VarProbe<MMSLabStick,double>
    (&MMSLabStick::pitch_output_offset) },
    { NULL, NULL}
    };
    return my_inco_table;
    }
    MMSLabStick::MMSLabStick(Entity* e,
    const char* part,
    const PrioritySpec& ps) :
    SimulationModule(e, classname, part, getMyIncoTable()),
    // default stick roll properties
    roll_mass(2.0),
    roll_damping(30.0),
    roll_spring_left(400.0),
    roll_spring_middle(400.0),
    roll_spring_right(400.0),
    roll_breakout(0.0),
    roll_friction(0.0),
    roll_stiction(0.0),
    roll_x_min(-25.0),
    roll_x_trans_lower(-25.0),
    roll_x_neutral(0.0),
    roll_x_trans_upper(25.0),
    roll_x_max(25.0),
    roll_output_k_force(0.0),
    roll_output_k_pos(1.0),
    roll_output_offset(0.0),
    // default stick pitch properties
    pitch_mass(2.0),
    pitch_damping(30.0),
    pitch_spring_aft(400.0),
    pitch_spring_middle(400.0),
    pitch_spring_forward(400.0),
    pitch_breakout(0.0),
    pitch_friction(0.0),
    pitch_stiction(0.0),
    pitch_x_min(-19.0),
    pitch_x_trans_lower(-19.0),
    pitch_x_neutral(0.0),
    pitch_x_trans_upper(19.0),
    pitch_x_max(19.0),
    pitch_output_k_force(0.0),
    pitch_output_k_pos(1.0),
    pitch_output_offset(0.0),
    // generic simulation properties
    dt(Ticker::single()->getDT()),
    stick_arm(0.09),
    kickstart_io(false),
    // the actual systems
    roll(NULL), pitch(NULL),
    // output channels
    controls(getId(), NameSet(getEntity(), "PrimaryControls", part)),
    // NOTE: inco output with part = "" is a hack, depending on a rate
    // reductor that reduces/interfaces the controls channel, and direct
    // communication of the inco channel.
    i_controls(getId(), NameSet(getEntity(), "inco_PrimaryControls", "")),
    // callback functions and activities
    cb1(this, &MMSLabStick::doStep),
    cb2(this, &MMSLabStick::doIncoCalculation),
    measure_control(getId(), "control input", &cb1, ps),
    calculate_inco(getId(), "inco", &cb2, PrioritySpec(0, 0))
    {
    measure_control.setTrigger(*Ticker::single());
    calculate_inco.setTrigger(t_inco_input);
    calculate_inco.switchOn(TimeSpec(0,0));
    // initialise board
    int status = Init_AT_MIO();
    if (status != 0) {
    cerr << "Cannot init AT_MIO" << endl;
    }
    // so set-up with my preferences
    Setup_AT_MIO();
    // zero the dda outputs
    for (int ii = 6; ii--; )
    outputVolt(ii, 0.0);
    }
    MMSLabStick::~MMSLabStick()
    {
    //
    }
    bool MMSLabStick::isPrepared()
    {
    // checks
    if (roll_mass < 0.5 || roll_damping < 0.0 ||
    roll_spring_left < 0.0 || roll_spring_middle < 0.0 ||
    roll_spring_right < 0.0 || roll_x_min > roll_x_neutral ||
    roll_x_neutral > roll_x_max ||
    roll_x_trans_upper < roll_x_trans_lower) {
    cerr << getId() << "Error in roll parameters" << endl;
    return false;
    }
    if (pitch_mass < 0.5 || pitch_damping < 0.0 ||
    pitch_spring_aft < 0.0 || pitch_spring_middle < 0.0 ||
    pitch_spring_forward < 0.0 || pitch_x_min > pitch_x_neutral ||
    pitch_x_neutral > pitch_x_max ||
    pitch_x_trans_upper < pitch_x_trans_lower) {
    cerr << getId() << "Error in pitch parameters" << endl;
    return false;
    }
    if (!controls.isValid()) {
    return false;
    }
    // just to make sure
    if (pitch != NULL) return true;
    // make a pitch stick system
    if (pitch != NULL) delete(pitch);
    // note: forward is positive
    pitch = new PassiveManipulator
    (stick_arm, pitch_mass, pitch_damping,
    pitch_spring_aft, pitch_spring_middle, pitch_spring_forward,
    pitch_breakout, pitch_friction, pitch_stiction,
    pitch_x_min, pitch_x_trans_lower, pitch_x_neutral,
    pitch_x_trans_upper, pitch_x_max,
    pitch_output_k_force, pitch_output_k_pos, pitch_output_offset, dt);
    // note: left is positive, bigger
    if (roll != NULL) delete(roll);
    roll = new PassiveManipulator
    (stick_arm, roll_mass, roll_damping,
    roll_spring_right, roll_spring_middle, roll_spring_left,
    roll_breakout, roll_friction, roll_stiction,
    roll_x_min, roll_x_trans_lower, roll_x_neutral,
    roll_x_trans_upper, roll_x_max,
    roll_output_k_force, roll_output_k_pos, roll_output_offset, dt);
    // prepared
    return true;
    }
    bool MMSLabStick::setTiming(const TimeSpec& ts)
    {
    measure_control.setTimeSpec(ts);
    dt = Ticker::single()->getDT()*ts.getValiditySpan();
    return true;
    }
    void MMSLabStick::startModule(const TimeSpec &time)
    {
    measure_control.switchOn(time);
    #ifdef STICK_ASYNC_IO
    kickstart_io = true;
    #endif
    }
    void MMSLabStick::stopModule(const TimeSpec &time)
    {
    measure_control.switchOff(time);
    }
    void MMSLabStick::doIncoCalculation(const TimeSpec& ts)
    {
    // there is only one mode
    assert(getIncoMode() == FlightPath);
    // only thing to do is to send the inco output
    i_controls.sendEvent
    (new PrimaryControls(0.0, pitch_output_offset, 0.0, 0.0, 0.0, 0.0), ts);
    }
    void MMSLabStick::doStep(const TimeSpec& ts)
    {
    double roll_moment, roll_pos, pitch_moment, pitch_pos;
    // measure the input, start a single aquisition and wait
    #ifdef STICK_ASYNC_IO
    if (kickstart_io) {
    kickstart_io = false;
    Clear_FIFO();
    AI_Start_The_Acquisition();
    }
    #elif !defined(SKIP_STICK_IO)
    AI_Start_The_Acquisition();
    #endif
    #ifdef SKIP_STICK_IO
    roll_moment = 0.0;
    roll_pos = 0.0;
    pitch_moment =0.0;
    pitch_pos = 0.0;
    #else
    Wait_AT_MIO();
    int16_t tmp = Board_Read(ADC_FIFO_Data_Register) & 0xffff;
    roll_moment = (tmp +i_offset[0]) * i_scale[0];
    Wait_AT_MIO();
    tmp = Board_Read(ADC_FIFO_Data_Register) & 0xffff;
    roll_pos = tmp * i_scale[1] + i_offset[1];
    Wait_AT_MIO();
    tmp = Board_Read(ADC_FIFO_Data_Register) & 0xffff;
    pitch_moment = (tmp +i_offset[2]) * i_scale[2];
    Wait_AT_MIO();
    tmp = Board_Read(ADC_FIFO_Data_Register) & 0xffff;
    pitch_pos = tmp * i_scale[3] + i_offset[3];
    #endif
    #ifdef STICK_ASYNC_IO
    // starts the acquisition for the NEXT cycle. In this way we don't wait
    // for the conversion
    AI_Start_The_Acquisition();
    #endif
    switch (getAndCheckState(ts)) {
    case HoldCurrent:
    // simple trick. use 0 input on the system. Without much friction,
    // it will more or less return to the middle position
    pitch->doStep(0.0);
    roll->doStep(0.0);
    break;
    case Advance:
    // do the simulation
    pitch->doStep(pitch_moment); //*stick_arm);
    roll->doStep(roll_moment); //*stick_arm);
    }
    // output back to the stick
    int16_t out0 =
    int16_t(rint(pitch->getState()[0] * o_scale[0] + o_offset[0]));
    if (out0 > 2047) out0 = 2047;
    if (out0 < -2048) out0 = -2048;
    int16_t out1 =
    int16_t(rint(roll->getState()[0] * o_scale[1] + o_offset[1]));
    if (out1 > 2047) out1 = 2047;
    if (out1 < -2048) out1 = -2048;
    Board_Write(AO_DAC_0_Data_Register, out0);
    Board_Write(AO_DAC_1_Data_Register, out1);
    // send to the rest of the simulation
    PrimaryControls *c;
    controls.getAccess(c, ts);
    c->stick_roll = roll->getOutput()[0];
    c->stick_pitch = pitch->getOutput()[0];
    c->roll_moment = roll_moment;
    c->pitch_moment= pitch_moment;
    c->roll_rate = roll_pos;
    c->pitch_rate = pitch_pos;
    controls.releaseAccess(c);
    }
    Snapshot* MMSLabStick::sendSnapshot(const TimeSpec& ts, bool inco)
    {
    // the snapshot contains the time, and the initial pitch input
    AmorphStore s(new char[16], 16);
    if (inco) {
    packData(s, double(0.0)); // roll pos
    packData(s, double(0.0)); // roll vel
    packData(s, double(0.0)); // pitch pos
    packData(s, double(0.0)); // pitch vel
    // packData(s, i_pitch_output_offset); // trim
    }
    else {
    packData(s, double(0.0)); // roll pos
    packData(s, double(0.0)); // roll vel
    packData(s, double(0.0)); // pitch pos
    packData(s, double(0.0)); // pitch vel
    // packData(s, pitch_output_offset); // trim
    }
    return new Snapshot(s.getToData(), s.getSize(), getNameSet());
    }
    void MMSLabStick::loadSnapshot(const Snapshot* d)
    {
    AmorphReStore s(d->data, d->data_size);
    // unPackData(s, t);
    // unPackData(s, u0_y);
    }
    const ParameterTable* MMSLabStick::getMyParameterTable()
    {
    static const ParameterTable table[] = {
    { "set-timing", new MemberCall<MMSLabStick,TimeSpec>
    (&MMSLabStick::setTiming)},
    { "roll-mass", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::roll_mass) },
    { "roll-damping", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::roll_damping) },
    { "roll-spring-left", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::roll_spring_left) },
    { "roll-spring-middle", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::roll_spring_middle) },
    { "roll-spring-right", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::roll_spring_right) },
    { "roll-breakout", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::roll_breakout) },
    { "roll-friction", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::roll_friction) },
    { "roll-stiction", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::roll_stiction) },
    { "roll-x-min", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::roll_x_min) },
    { "roll-x-trans-lower", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::roll_x_trans_lower) },
    { "roll-x-neutral", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::roll_x_neutral) },
    { "roll-x-trans-upper", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::roll_x_trans_upper) },
    { "roll-x-max", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::roll_x_max) },
    { "roll-output-k-force", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::roll_output_k_force) },
    { "roll-output-k-pos", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::roll_output_k_pos) },
    { "roll-output-offset", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::roll_output_offset) },
    { "pitch-mass", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::pitch_mass) },
    { "pitch-damping", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::pitch_damping) },
    { "pitch-spring-aft", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::pitch_spring_aft) },
    { "pitch-spring-middle", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::pitch_spring_middle) },
    { "pitch-spring-forward", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::pitch_spring_forward) },
    { "pitch-breakout", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::pitch_breakout) },
    { "pitch-friction", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::pitch_friction) },
    { "pitch-stiction", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::pitch_stiction) },
    { "pitch-x-min", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::pitch_x_min) },
    { "pitch-x-trans-lower", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::pitch_x_trans_lower) },
    { "pitch-x-neutral", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::pitch_x_neutral) },
    { "pitch-x-trans-upper", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::pitch_x_trans_upper) },
    { "pitch-x-max", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::pitch_x_max) },
    { "pitch-output-k-force", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::pitch_output_k_force) },
    { "pitch-output-k-pos", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::pitch_output_k_pos) },
    { "pitch-output-offset", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::pitch_output_offset) },
    { "stick-arm", new VarProbe<MMSLabStick,double>
    (&MMSLabStick::stick_arm) },
    {NULL, NULL}};
    return table;
    }
    static TypeCreator<MMSLabStick> a(MMSLabStick::getMyParameterTable());

    These files use an auxiliary class PassiveStick, which is not included in this manual. Since the HardwareModule class is not complete yet, this uses the SimulationModule class.

  • The rate reduction class, which simply reads in the stick input and writes it back again. By specifying an update rate for its activity, it assures itself of reading only at the proper times, and writing with the proper TimeSpec.

    /* ------------------------------------------------------------------ */
    /* item : RateReduction.hxx
    made by : Rene' van Paassen
    date : 001027
    category : header file
    description :
    changes : 001027 first version
    language : C++
    copyright : (c) 2016 TUDelft-AE-C&S
    copyright : (c) 2022 René van Paassen
    license : EUPL-1.2
    WARNING : This example has not been updated to current
    DUECA interfacing specifications
    */
    #ifndef RateReduction_hh
    #define RateReduction_hh
    #ifdef RateReduction_cc
    #endif
    #include <dueca.h>
    #include <PrimaryControls.hxx>
    class PassiveManipulator;
    class ParameterTable;
    class RateReduction: public Module
    {
    // output channels for the controls, and inco output channel
    StreamChannelWriteToken<PrimaryControls> controls_out;
    StreamChannelReadToken<PrimaryControls> controls_in;
    // activity
    Callback<RateReduction> cb1;
    ActivityCallback reduce_rate;
    public:
    const static ParameterTable* getMyParameterTable();
    static const char* const classname;
    /// constructor, should be called from scheme, via the ModuleCreator
    RateReduction(Entity* e, const char* part, const PrioritySpec& ts);
    /// destructor
    ~RateReduction();
    /// further specification, update rate
    bool setTiming(const TimeSpec& t);
    public:
    /// start responsiveness to input data
    void startModule(const TimeSpec &time);
    /// stop responsiveness to input data
    void stopModule(const TimeSpec &time);
    /// check sanity of all data and report ok or not
    bool isPrepared();
    private:
    /// the method that implements the main calculation
    void doStep(const TimeSpec& ts);
    };
    #endif
    /* ------------------------------------------------------------------ */
    /* item : RateReduction.cxx
    made by : Rene' van Paassen
    date : 001027
    category : body file
    description :
    changes : 001027 first version
    language : C++
    copyright : (c) 2016 TUDelft-AE-C&S
    copyright : (c) 2022 René van Paassen
    license : EUPL-1.2
    WARNING : This example has not been updated to current
    DUECA interfacing specifications
    */
    #define RateReduction_cc
    #include "RateReduction.hxx"
    #include <Ticker.hxx>
    extern "C" {
    #include "natmio.h"
    }
    #include "config.h"
    #define DO_INSTANTIATE
    #include <dueca.h>
    const char* const RateReduction::classname = "rate-reduction";
    RateReduction::RateReduction(Entity* e,
    const char* part,
    const PrioritySpec& ps) :
    Module(e, classname, part),
    // output channels
    controls_out(getId(), NameSet(getEntity(), "PrimaryControls", part)),
    controls_in(getId(), NameSet(getEntity(), "PrimaryControls", "stick")),
    // callback functions and activities
    cb1(this, &RateReduction::doStep),
    reduce_rate(getId(), "control input", &cb1, ps)
    {
    reduce_rate.setTrigger(controls_in);
    }
    RateReduction::~RateReduction()
    {
    //
    }
    bool RateReduction::isPrepared()
    {
    return controls_in.isValid() && controls_out.isValid();
    }
    bool RateReduction::setTiming(const TimeSpec& ts)
    {
    reduce_rate.setTimeSpec(ts);
    return true;
    }
    void RateReduction::startModule(const TimeSpec &time)
    {
    reduce_rate.switchOn(time);
    }
    void RateReduction::stopModule(const TimeSpec &time)
    {
    reduce_rate.switchOff(time);
    }
    void RateReduction::doStep(const TimeSpec& ts)
    {
    // measure the input, copy to the output
    const PrimaryControls *in; controls_in.getAccess(in, ts);
    PrimaryControls *out; controls_out.getAccess(out, ts);
    out->stick_roll = in->stick_roll;
    out->stick_pitch = in->stick_pitch;
    out->roll_moment = in->roll_moment;
    out->pitch_moment= in->pitch_moment;
    out->roll_rate = in->roll_rate;
    out->pitch_rate = in->pitch_rate;
    controls_in.releaseAccess(in);
    controls_out.releaseAccess(out);
    }
    const ParameterTable* RateReduction::getMyParameterTable()
    {
    static const ParameterTable table[] = {
    { "set-timing", new MemberCall<RateReduction,TimeSpec>
    (&RateReduction::setTiming)},
    {NULL, NULL}};
    return table;
    }
    static TypeCreator<RateReduction> a(RateReduction::getMyParameterTable());

  • The space plane module.

    /* ------------------------------------------------------------------ */
    /* item : SpacePlane.hxx
    made by : Rene' van Paassen
    date : 001003
    category : header file
    description :
    changes : 001003 first version
    language : C++
    copyright : (c) 2016 TUDelft-AE-C&S
    copyright : (c) 2022 René van Paassen
    license : EUPL-1.2
    */
    #ifndef SpacePlane_hh
    #define SpacePlane_hh
    #ifdef SpacePlane_cc
    #endif
    // include the dusime header
    #include <dusime.h>
    // include headers for the objects that are sent over the channels
    #include "PrimaryControls.hxx"
    #include "SpacePlaneY.hxx"
    #include "SpacePlaneState.hxx"
    // include headers for functions/classes you need in the module
    // This module is based on code generated with real-time workshop from
    // a SimuLink model. Therefore we include the simstruc and the
    // simulink model, called "complete"
    extern "C" {
    #define RT
    #define RTW_GENERATED_S_FUNCTION
    #include "simstruc.h"
    #include "complete.h"
    }
    /** e01 */
    /* Provides a class that implements Crew Rescue Vehicle dynamics.
    Implements the dynamics of a Crew Rescue Vehicle with low L/D
    characteristics. The dynamic model is created in SimuLink and
    exported with Real-Time Workshop. The module encapsulates the C
    source with the Simulink Model. Inputs are the control vector,
    outputs contain most of the state and instrument related data. */
    class SpacePlane: public SimulationModule
    {
    /** e02 */
    private: // Simulation data
    /* Simulink system variable pointers, normal, one for inco. These
    also contain the state data. */
    SimStruct *S; /* *i_S */
    // prediction time
    int t_predict;
    // stop height
    double z_stop;
    private: // trim calculation data
    // trim calculation is not implemented yet.
    private: // snapshot data
    // snapshot state
    real_T s_x[NSTATES];
    private: // channel access
    // input and output channels for the normal running
    StreamChannelReadToken<PrimaryControls> controls;
    StreamChannelWriteToken<SpacePlaneY> output;
    StreamChannelWriteToken<SpacePlaneState> state;
    private: // activity allocation
    // Callback object for simulation calculation
    Callback<SpacePlane> cb1;
    // Activity for simulation calculation
    ActivityCallback do_calc;
    public: // class name and trim/parameter tables
    // Name of the module.
    static const char* const classname;
    // Return the initial condition table
    static const IncoTable* getMyIncoTable();
    // Return the parameter table
    static const ParameterTable* getMyParameterTable();
    public: // construction and further specification
    /* Constructor. Is normally called from scheme/the creation script. */
    SpacePlane(Entity* e, const char* part, const PrioritySpec& ts);
    /* Continued construction. This is called after all script
    parameters have been read and filled in, according to the
    parameter table. Your running environment, e.g. for OpenGL
    drawing, is also prepared. Any lengty initialisations (like
    reading the 4 GB of wind tables) should be done here.
    Return false if something in the parameters is wrong (by
    the way, it would help if you printed what!) May be deleted. */
    bool complete();
    /* Destructor. */
    ~SpacePlane();
    // add here the member functions you want to be called with further
    // parameters. These are then also added in the parameter table
    // The most common one (addition of time spec) is given here.
    // Delete if not needed!
    /** s01 */
    /* Specify a time specification for the simulation activity. */
    bool setTimeSpec(const TimeSpec& ts);
    /* Request check on the timing. */
    bool checkTiming(const vector<int>& i);
    public: // member functions for cooperation with DUECA
    // indicate that everything is ready
    bool isPrepared();
    // start responsiveness to input data
    void startModule(const TimeSpec &time);
    // stop responsiveness to input data
    void stopModule(const TimeSpec &time);
    /** s03 */
    public: // the member functions that are called for activities
    // the method that implements the main calculation
    void doCalculation(const TimeSpec& ts);
    public: // member functions for cooperation with DUSIME
    // For the Snapshot capability, fill the snapshot "snap" with the
    // data saved at a point in your simulation (if from_trim is false)
    // or with the state data calculated in the trim calculation (if
    // from_trim is true)
    void fillSnapshot(const TimeSpec& ts,
    Snapshot& snap, bool from_trim);
    // Restoring the state of the simulation from a snapshot
    void loadSnapshot(const TimeSpec& t, const Snapshot& snap);
    /* Perform a trim calculation. Should NOT use current state
    uses event channels parallel to the stream data channels,
    calculates, based on the event channel input, the steady state
    output. */
    // this has not been implemented in DUECA, so we comment it out
    // void trimCalculation(const TimeSpec& ts, const IncoMode& mode);
    };
    #endif
    /* ------------------------------------------------------------------ */
    /* item : SpacePlane.cxx
    made by : Rene' van Paassen
    date : 001003
    category : body file
    description :
    changes : 001003 first version
    language : C++
    copyright : (c) 2016 TUDelft-AE-C&S
    copyright : (c) 2022 René van Paassen
    license : EUPL-1.2
    */
    #define SpacePlane_cxx
    // include the definition of the module class
    #include "SpacePlane.hxx"
    // include additional files needed for your calculation here
    #include "StatesOutputs.hxx"
    #define D_MOD
    #define E_MOD
    #include <debug.h>
    extern "C" {
    // here we include the file with (static) routines for the RTW/Simulink model
    #define RTW_MODEL complete_reg
    #include "complete.c"
    #include "rtw_prototypes.h"
    }
    // the standard package for DUSIME, including template source
    #define DO_INSTANTIATE
    #include <dusime.h>
    // define conversion factor to degrees
    const double RAD_DEG = 180.0 / 3.1415926535897931E+000;
    // class/module name
    const char* const SpacePlane::classname = "space-plane";
    // initial condition/trim table
    // this does not work yet, so the table is empty
    const IncoTable* SpacePlane::getMyIncoTable()
    {
    static IncoTable inco_table[] = {
    // enter pairs of IncoVariable and VarProbe pointers (i.e.
    // objects made with new), in this table.
    // For example
    // {(new IncoVariable("example", 0.0, 1.0, 0.01))
    // ->forMode(FlightPath, Constraint)
    // ->forMode(Speed, Control),
    // new VarProbe<SpacePlane,double>
    // (REF_MEMBER(&SpacePlane::i_example))}
    // always close off with:
    { NULL, NULL} };
    return inco_table;
    }
    // parameters to be inserted
    const ParameterTable* SpacePlane::getMyParameterTable()
    {
    static const ParameterTable parameter_table[] = {
    { "set-timing",
    new MemberCall<SpacePlane,TimeSpec>
    (&SpacePlane::setTimeSpec)},
    { "set-stop-height",
    new VarProbe<SpacePlane,double>
    (REF_MEMBER(&SpacePlane::z_stop))},
    { "check-timing",
    new MemberCall<SpacePlane,vector<int> >
    (&SpacePlane::checkTiming)},
    // always close off with:
    { NULL, NULL} };
    return parameter_table;
    }
    /** e06 */
    // constructor
    SpacePlane::SpacePlane(Entity* e, const char* part, const
    PrioritySpec& ps) :
    /* The following line initialises the SimulationModule base class.
    You always pass the pointer to the entity, give the classname and the
    part arguments.
    If you give a NULL pointer instead of the inco table, you will not be
    called for trim condition calculations, which is normal if you for
    example implement logging or a display.
    If you give 0 for the snapshot state, you will not be called to
    fill a snapshot, or to restore your state from a snapshot. Only
    applicable if you have no state. */
    SimulationModule(e, classname, part, getMyIncoTable(),
    NSTATES*sizeof(real_T)),
    // initialize the data you need in your simulation
    S(NULL),
    z_stop(0.0),
    // initialize the data you need for the trim calculation
    // initialize the channel access tokens
    controls(getId(), NameSet(getEntity(), "PrimaryControls", part)),
    output(getId(), NameSet(getEntity(), "SpacePlaneY", part)),
    state(getId(), NameSet(getEntity(), "SpacePlaneState", part), 21),
    // activity initialization
    cb1(this, &SpacePlane::doCalculation),
    do_calc(getId(), "simulation step", &cb1, ps)
    {
    // for a simulink model, you need to initialise infinity, - inf and
    // Not-a-number.
    rt_InitInfAndNaN(sizeof(real_T));
    // create a SimuLink/rtw model
    S = RTW_MODEL ();
    // initialize it
    MdlInitializeSizes(S);
    MdlInitializeSampleTimes(S);
    rt_CreateIntegrationData(S);
    const char* ret;
    if ((ret = rt_InitTimingEngine(S)) != NULL) {
    E_MOD(getId() << ' ' << ret);
    }
    MdlStart(S);
    // set the initial time
    ssSetT(S, 0.0);
    // and also calculate the initial state
    MdlUpdate(S, 0);
    /** e00 */
    // specify that the control input is the trigger for the calculation
    do_calc.setTrigger(controls);
    // just a check on the states defined and the model
    assert(X_no_states == NSTATES);
    assert(Y_no_outputs == NOUTPUTS);
    }
    /** e01 */
    bool SpacePlane::complete()
    {
    /* All your parameters have been set. You may do extended
    initialisation here. Return false if something is wrong. */
    return true;
    }
    /** e02 */
    // destructor
    SpacePlane::~SpacePlane()
    {
    // delete the model
    MdlTerminate(S);
    }
    /** e03 */
    /** s02 */
    // the setTimeSpec function
    bool SpacePlane::setTimeSpec(const TimeSpec& ts)
    {
    // a time span of 0 is not acceptable
    if (ts.getValiditySpan() == 0) return false;
    // specify the timespec to the activity
    do_calc.setTimeSpec(ts);
    // do whatever else you need to process this in your model
    // hint: ts.getDtInSeconds()
    // in this case, the Simulink model has to be told of the time base
    ssSetStepSize(S, ts.getDtInSeconds());
    // return true if everything is acceptable
    return true;
    }
    /** e04 */
    // and the checkTiming function
    bool SpacePlane::checkTiming(const vector<int>& i)
    {
    if (i.size() == 3) {
    new TimingCheck(do_calc, i[0], i[1], i[2]);
    }
    else if (i.size() == 2) {
    new TimingCheck(do_calc, i[0], i[1]);
    }
    else {
    return false;
    }
    return true;
    }
    /** e05 */
    // tell DUECA you are prepared
    bool SpacePlane::isPrepared()
    {
    // assume I am ready if all tokens are valid
    return controls.isValid() && output.isValid();
    }
    // start the module
    void SpacePlane::startModule(const TimeSpec &time)
    {
    do_calc.switchOn(time);
    }
    // stop the module
    void SpacePlane::stopModule(const TimeSpec &time)
    {
    do_calc.switchOff(time);
    }
    /** e07 */
    // fill a snapshot with state data. You may remove this method (and the
    // declaration) if you specified to the SimulationModule that the size of
    // state snapshots is zero
    void SpacePlane::fillSnapshot(const TimeSpec& ts,
    Snapshot& snap, bool from_trim)
    {
    // The most efficient way of filling a snapshot is with an AmorphStore
    // object.
    AmorphStore s(snap.accessData(), snap.getDataSize());
    if (from_trim) {
    // not implemented
    }
    else {
    // this is a snapshot from the running simulation. Dusime takes care
    // that no other snapshot is taken at the same time, so you can safely
    // pack the data you copied into (or left into) the snapshot state
    // variables in here
    // use packData(s, snapshot_state_variable1); ...
    for (int ii = 0; ii < NSTATES; ii++) {
    packData(s, s_x[ii]);
    }
    }
    }
    // reload from a snapshot. You may remove this method (and the
    // declaration) if you specified to the SimulationModule that the size of
    // state snapshots is zero
    void SpacePlane::loadSnapshot(const TimeSpec& ts, const Snapshot& snap)
    {
    // access the data in the snapshot with an AmorphReStore object
    AmorphReStore store(snap.data, snap.data_size);
    D_MOD(getId() << " loading snapshot");
    // copy the state data into the state vector
    real_T* X = ssGetX(S);
    double t;
    for (int ii = 0; ii < NSTATES; ii++ ) {
    unPackData(store, t);
    X[ii] = t;
    }
    }
    /** s07 */
    void SpacePlane::doCalculation(const TimeSpec& ts)
    {
    // check the state we are supposed to be in
    switch (getAndCheckState(t)) {
    case SimulationState::HoldCurrent: {
    // only repeat the output, don not change the model state
    StreamWriter<SpacePlaneY> y(output, ts);
    real_T* Y = ssGetY(S);
    for (int ii = NOUTPUTS; ii--; ) {
    y.data().Y[ii] = Y[ii];
    }
    // send out the state also
    StreamWriter<SpacePlaneState> x(state, ts);
    real_T *X = ssGetX(S);
    for (int ii = NSTATES; ii--; ) {
    x.data().X[ii] = X[ii];
    }
    }
    break;
    /** e08a */
    case SimulationState::Replay:
    case SimulationState::Advance: {
    // access the input
    StreamReader<PrimaryControls> u(controls, ts);
    // copy the input to the simulink input
    double* U = ssGetU(S);
    U[0] = u.data().stick_pitch * RAD_DEG * 0.05;
    U[1] = u.data().stick_roll * RAD_DEG;
    // calculate the output/update the normal model
    for (int ii = 0; ii < NSAMPLE_TIMES; ii++)
    MdlOutputs(S, ii);
    // at this point, the output should be taken
    // so request access to the output channel
    StreamWriter<SpacePlaneY> y(output, ts);
    real_T* Y = ssGetY(S);
    for (int ii = NOUTPUTS; ii--; ) {
    y.data().Y[ii] = Y[ii];
    }
    /** s08a */
    // only do a model update if the height is above the stop height
    // below the stop height the thing will be frozen
    if (Y[Y_z] > z_stop) {
    // discrete update of the model,
    for (int ii = 0; ii < NSAMPLE_TIMES; ii++)
    MdlUpdate(S, ii);
    // specify the end time of the integration step
    ssSetSolverStopTime(S, ssGetT(S) + ssGetStepSize(S));
    // and do a continuous time step
    rt_UpdateContinuousStates(S);
    // and check for discrete time jumps
    rt_UpdateDiscreteTaskSampleHits(S);
    }
    // send out the state also
    StreamWriter<SpacePlaneState> x(state, ts);
    real_T *X = ssGetX(S);
    for (int ii = NSTATES; ii--; ) {
    x.data().X[ii] = X[ii];
    }
    }
    break;
    default:
    // other states should never be entered for a SimulationModule,
    // HardwareModules on the other hand have more states. Throw an
    // exception if we get here,
    throw CannotHandleState(getId(),GlobalId(), "state unhandled");
    }
    /** s08b */
    if (snapshotNow()) {
    // keep a copy of the model state. Snapshot sending is done in the
    // sendSnapshot routine, later, and possibly at lower priority
    // e.g.
    // snapshot_state_variable1 = state_variable1; ...
    // (or maybe if your state is very large, there is a cleverer way ...)
    real_T *X = ssGetX(S);
    for (int ii = NSTATES; ii--; )
    s_x[ii] = X[ii];
    }
    }
    /** e09 */
    /* the following is not implemented
    void SpacePlane::trimCalculation(const TimeSpec& ts, const TrimMode& mode)
    */
    /** s10 */
    // Make a TypeCreator object for this module, the TypeCreator
    // will check in with the scheme-interpreting code, and enable the
    // creation of modules of this type
    static TypeCreator<SpacePlane> a(SpacePlane::getMyParameterTable());
    Here macros for debugging/messaging are defined.
    #define D_MOD(A)
    Debug messages for application module code.
    Definition newlog-macros.hxx:565
    #define E_MOD(A)
    Error messages for application module code.
    Definition newlog-macros.hxx:620

    This module's calculations are based on a model that was implemented in SimuLink and converted with Real-Time Workshop. For the conversion you should specify the "real time malloc" option, this will enable the code to start up several instances of the same model in one program. A script called "correctSimulink", which is installed with the DUECA program, makes some small corrections to the generated Simulink code. Run this once on the RTW files.

  • The numeric output module.

    /* ------------------------------------------------------------------ */
    /* item : NumericPlaneOutput.hxx
    made by : Rene' van Paassen
    date : 001004
    category : header file
    description :
    changes : 001004 first version
    language : C++
    copyright : (c) 2016 TUDelft-AE-C&S
    copyright : (c) 2022 René van Paassen
    license : EUPL-1.2
    */
    #ifndef NumericPlaneOutput_hh
    #define NumericPlaneOutput_hh
    #ifdef NumericPlaneOutput_cc
    #endif
    #include <dusime.h>
    #include <stringoptions.h>
    #include "SpacePlaneY.hxx"
    #include "PrimaryControls.hxx"
    #include "DisplaySelect.hxx"
    #include <fstream>
    #include <iostream>
    /** provides numeric output of a (space) plane output vector */
    class NumericPlaneOutput: public SimulationModule
    {
    ofstream out0, out1;
    vstring basename;
    int number;
    bool header_written;
    int display_plus_tunnel;
    public:
    StreamChannelReadToken<PrimaryControls> t_controls;
    StreamChannelReadToken<SpacePlaneY> t_output;
    EventChannelReadToken<DisplaySelect> t_display_type;
    Callback<NumericPlaneOutput> cb;
    ActivityCallback do_output;
    public:
    static const char* const classname;
    static const ParameterTable* getMyParameterTable();
    public:
    /// constructor, should be called from scheme, via the ModuleCreator
    NumericPlaneOutput(Entity* e, const char* part, const PrioritySpec& ts);
    /// destructor
    ~NumericPlaneOutput();
    /// further specification, output file
    bool setOutputFile(const vstring& f);
    public:
    /// start responsiveness to input data
    void startModule(const TimeSpec &time);
    /// stop responsiveness to input data
    void stopModule(const TimeSpec &time);
    bool isPrepared();
    void loadSnapshot(const TimeSpec& ts, const Snapshot& snap);
    private:
    /// check whether header has been written, and if needed, write one
    void checkHeader();
    /// actions to take when using a new output file
    void switchFiles();
    /// the method that implements the main calculation
    void doStep(const TimeSpec& ts);
    };
    #endif
    /* ------------------------------------------------------------------ */
    /* item : NumericPlaneOutput.cxx
    made by : Rene' van Paassen
    date : 001004
    category : body file
    description :
    changes : 001004 first version
    language : C++
    copyright : (c) 2016 TUDelft-AE-C&S
    copyright : (c) 2022 René van Paassen
    license : EUPL-1.2
    */
    #define NumericPlaneOutput_cc
    #include "NumericPlaneOutput.hxx"
    extern "C" {
    #define RT
    #define RTW_GENERATED_S_FUNCTION
    #include "complete.h"
    }
    #include <sys/stat.h>
    #include <time.h>
    #include <unistd.h>
    #include <iomanip>
    #include <errno.h>
    #include <strstream>
    #define DO_INSTANTIATE
    #include <dusime.h>
    const char* const NumericPlaneOutput::classname = "numeric-plane-output";
    NumericPlaneOutput::NumericPlaneOutput(Entity* e,
    const char* part,
    const PrioritySpec& ps) :
    SimulationModule(e, classname, part, NULL),
    number(0),
    header_written(false),
    display_plus_tunnel(0),
    t_controls(getId(), NameSet(getEntity(), "PrimaryControls", part)),
    t_output(getId(), NameSet(getEntity(), "SpacePlaneY", part)),
    t_display_type(getId(), NameSet(getEntity(), "DisplaySelect", part)),
    cb(this, &NumericPlaneOutput::doStep),
    do_output(getId(), "numeric output", &cb, ps)
    {
    out0.open("/dev/null");
    out1.open("/dev/null");
    do_output.setTrigger(t_output && t_controls);
    }
    NumericPlaneOutput::~NumericPlaneOutput()
    {
    //
    }
    static const char* fileName(vstring& basename, int no)
    {
    static char buf[256];
    strstream s(buf, 256);
    s << basename << setw(4) << setfill('0') << no << ".m" << '\000';
    return buf;
    }
    static const char* fileNameData(vstring& basename, int no)
    {
    static char buf[256];
    strstream s(buf, 256);
    s << basename << setw(4) << setfill('0') << no << ".dat" << '\000';
    return buf;
    }
    void NumericPlaneOutput::checkHeader()
    {
    if (header_written) return;
    time_t tim = time(NULL);
    out1 << "% Configuration data from a simulation session with the CRV\n"
    << "% experiment date and time " << ctime(&tim)
    << "\n%\n"
    << "% Display configuration\ndisplay_conf="
    << display_plus_tunnel / 3 + 1
    << "\n\n% Tunnel/path no\ntunnel_no="
    << display_plus_tunnel % 3 + 1
    << endl;
    header_written = true;
    }
    void NumericPlaneOutput::switchFiles()
    {
    if (!header_written) {
    // make do with the present file
    cout << "Keeping output file " << fileName(basename, number)
    << endl;
    }
    else {
    // close off the present file
    out0 << endl; out0.close(); out1.close();
    // find an extension number that has not been used with this base name
    struct stat buf;
    for (; number < 10000 &&
    stat(fileName(basename, number), &buf) != -1 &&
    stat(fileNameData(basename, number), &buf) != -1 &&
    errno != ENOENT; number++);
    if (number < 10000) {
    // open the file
    out0.open(fileNameData(basename, number));
    out1.open(fileName(basename, number));
    header_written = false;
    cout << "Opening output file " << fileNameData(basename, number)
    << endl;
    }
    else {
    cerr << "Cannot find a free file among " << fileNameData(basename, 0)
    << " to " << fileNameData(basename, number - 1) << endl;
    out0.open("/dev/null");
    out1.open("/dev/null");
    }
    }
    }
    bool NumericPlaneOutput::setOutputFile(const vstring& f)
    {
    // this carries the base name for the file
    basename = f;
    // reset the file search number to zero
    number = 0;
    // switch output files
    header_written = true; switchFiles();
    return true;
    }
    void NumericPlaneOutput::startModule(const TimeSpec &time)
    {
    do_output.switchOn(time);
    }
    void NumericPlaneOutput::stopModule(const TimeSpec &time)
    {
    do_output.switchOff(time);
    }
    bool NumericPlaneOutput::isPrepared()
    {
    return t_output.isValid() && t_controls.isValid()
    && t_display_type.isValid();
    }
    void NumericPlaneOutput::doStep(const TimeSpec& ts)
    {
    const SpacePlaneY* y;
    const PrimaryControls* u;
    // check for changes in display type
    if (t_display_type.getNumWaitingEvents(ts)) {
    const Event<DisplaySelect>* e;
    t_display_type.getNextEvent(e, ts);
    display_plus_tunnel = e->getEventData()->type;
    }
    if (getAndCheckState(ts) == SimulationState::Advance) {
    t_output.getAccess(y, ts);
    t_controls.getAccess(u, ts);
    // before sending data, write, if applicable, the header
    checkHeader();
    // read out the pilot inputs, and write to file
    out0 << ts.getValidityStart() << ' ' << ts.getValidityEnd() << ' '
    << u->stick_roll << ' ' << u->stick_pitch << ' '
    << u->roll_moment << ' ' << u->pitch_moment << ' '
    << u->roll_rate << ' ' << u->pitch_rate;
    // read out all output data, and write to file
    for (int ii = 0; ii < NOUTPUTS; ii++) {
    out0 << ' ' << y->Y[ii];
    }
    out0 << endl;
    t_output.releaseAccess(y);
    t_controls.releaseAccess(u);
    }
    }
    void NumericPlaneOutput::loadSnapshot(const TimeSpec& ts, const Snapshot& snap)
    {
    switchFiles();
    }
    const ParameterTable* NumericPlaneOutput::getMyParameterTable()
    {
    static const ParameterTable table[] = {
    { "set-output-file", new MemberCall<NumericPlaneOutput,vstring>
    (&NumericPlaneOutput::setOutputFile)},
    { NULL, NULL}
    };
    return table;
    }
    static TypeCreator<NumericPlaneOutput>
    a(NumericPlaneOutput::getMyParameterTable());

  • The evaluator shows a window on the screen of the experimenter's PC, with current values and overview values. The windowing toolkit that is used is gtk+. With a GUI designer glade, it is easy to draw such a window. A few callback functions (which must have a "C" linkage specification) are written to handle the events from the interface.

    /* ------------------------------------------------------------------ */
    /* item : Evaluator.hxx
    made by : Rene' van Paassen
    date : 001214
    category : header file
    description :
    changes : 001214 first version
    language : C++
    copyright : (c) 2016 TUDelft-AE-C&S
    copyright : (c) 2022 René van Paassen
    license : EUPL-1.2
    */
    #ifndef Evaluator_hh
    #define Evaluator_hh
    #ifdef Evaluator_cc
    #endif
    #include <libguile.h>
    #include <gtk/gtk.h>
    #include <dusime.h>
    #include "SpacePlaneY.hxx"
    #include "DisplaySelect.hxx"
    #include <Snapshot.hxx>
    #include "Variance.hxx"
    #include "Interpolator.hxx"
    class DirectPacker;
    class Evaluator: public SimulationModule
    {
    StreamChannelReadToken<SpacePlaneY> input;
    EventChannelReadToken<DisplaySelect> display_select;
    GtkWidget *window;
    GtkEntry *h_altitude, *h_sinkrate,
    *h_sigma_y_1, *h_sigma_z_1,
    *h_sigma_y_2, *h_sigma_z_2,
    *h_sigma_y_3, *h_sigma_z_3,
    *h_delta_y, *h_delta_z;
    bool is_reset;
    Variance v_initialy, v_initialz,
    v_flarey, v_flarez,
    v_finaly, v_finalz;
    int tunnel;
    static const unsigned int NTUNNELS;
    vector<Interpolator> path;
    vector<double> x_flare, x_final;
    double z_stop;
    float altitude, sinkrate,
    present_x,
    delta_y, delta_z;
    Callback<Evaluator> cb, cb2;
    ActivityCallback keep_up;
    ActivityCallback feedback;
    bool feedback_timing_set;
    public:
    static const char* const classname;
    static const ParameterTable* getMyParameterTable();
    public:
    Evaluator(Entity* e, const char* part, const PrioritySpec& ts);
    /// destructor
    ~Evaluator();
    /// further specification, update rate
    bool isPrepared();
    /// start responsiveness to input data
    void startModule(const TimeSpec &time);
    /// stop responsiveness to input data
    void stopModule(const TimeSpec &time);
    /// specify prediction time
    bool setFile(const vstring& f);
    /// specify rate at which display is updated
    bool setFeedbackRate(const TimeSpec& ts);
    bool setFlare(const double& f);
    bool setFinal(const double& f);
    private:
    /// the method that implements the main calculation
    void doCalculation(const TimeSpec& ts);
    void feedBack(const TimeSpec& ts);
    protected:
    /// restoring state from snapshot
    void loadSnapshot(const TimeSpec& t, const Snapshot& snap);
    };
    #endif
    /* ------------------------------------------------------------------ */
    /* item : Evaluator.cxx
    made by : Rene' van Paassen
    date : 001214
    category : body file
    description :
    changes : 001214 first version
    language : C++
    copyright : (c) 2016 TUDelft-AE-C&S
    copyright : (c) 2022 René van Paassen
    license : EUPL-1.2
    */
    #define Evaluator_cc
    #include "Evaluator.hxx"
    #include <strstream>
    #define RT
    #define RTW_GENERATED_S_FUNCTION
    #include "StatesOutputs.hxx"
    #include <Ticker.hxx>
    extern "C" {
    #include "interface.h"
    #include <support.h>
    }
    #define DO_INSTANTIATE
    #include <dusime.h>
    const char* const Evaluator::classname = "evaluator";
    const unsigned int Evaluator::NTUNNELS = 3;
    Evaluator::Evaluator(Entity* e, const char* part, const
    PrioritySpec& ps) :
    SimulationModule(e, classname, part, NULL),
    input(getId(), NameSet(getEntity(), "SpacePlaneY", part)),
    display_select(getId(), NameSet(getEntity(), "DisplaySelect", part)),
    is_reset(true),
    tunnel(0),
    z_stop(0.0),
    cb(this, &Evaluator::doCalculation),
    cb2(this, &Evaluator::feedBack),
    keep_up(getId(), "evaluation calculation", &cb, ps),
    feedback(getId(), "evaluation feedback", &cb2, PrioritySpec(0,0)),
    feedback_timing_set(false)
    {
    // create the gtk window. This version assumes singleton
    window = create_evaluator();
    gtk_widget_show(window);
    #define LOOGUP_ENTRY(A) \
    h_ ## A = GTK_ENTRY(lookup_widget(window, #A ))
    LOOGUP_ENTRY(sinkrate);
    LOOGUP_ENTRY(altitude);
    LOOGUP_ENTRY(sigma_z_1);
    LOOGUP_ENTRY(sigma_y_1);
    LOOGUP_ENTRY(sigma_z_2);
    LOOGUP_ENTRY(sigma_y_2);
    LOOGUP_ENTRY(sigma_z_3);
    LOOGUP_ENTRY(sigma_y_3);
    LOOGUP_ENTRY(delta_y);
    LOOGUP_ENTRY(delta_z);
    #undef LOOGUP_ENTRY
    // attach my activity to incoming data from the pane
    keep_up.setTrigger(input);
    feedback.setTrigger(*Ticker::single());
    }
    Evaluator::~Evaluator()
    {
    //
    }
    bool Evaluator::isPrepared()
    {
    return display_select.isValid()
    && input.isValid() // channels valid
    && path.size() == NTUNNELS // (3) paths defined
    && x_flare.size() == NTUNNELS // (3) start of flare
    && x_final.size() == NTUNNELS // (3) start of final
    && feedback_timing_set; // feedback timing set
    }
    void Evaluator::startModule(const TimeSpec &time)
    {
    keep_up.switchOn(time);
    feedback.switchOn(time);
    }
    void Evaluator::stopModule(const TimeSpec &time)
    {
    keep_up.switchOff(time);
    feedback.switchOff(time);
    }
    void Evaluator::loadSnapshot(const TimeSpec& ts, const Snapshot& snap)
    {
    // this just means that we start anew, reset the counters
    // and zero the deviations
    v_initialy.zero(); v_initialz.zero();
    v_flarey.zero(); v_flarez.zero();
    v_finaly.zero(); v_finalz.zero();
    altitude = 0.0; sinkrate = 0.0;
    is_reset = true;
    }
    void Evaluator::doCalculation(const TimeSpec& ts)
    {
    // check for changes in path
    if (display_select.getNumWaitingEvents(ts)) {
    const Event<DisplaySelect>* e;
    display_select.getNextEvent(e, ts);
    tunnel = e->getEventData()->type % NTUNNELS;
    }
    // do normal simulation/feedback stuff
    switch (getAndCheckState(ts)) {
    case SimulationState::Advance:
    {
    const SpacePlaneY* y;
    input.getAccess(y, ts);
    // read out values from y
    altitude = y->Y[Y_z];
    sinkrate = y->Y[Y_sink_rate];
    // get the normative value for the path
    path[tunnel].charge(y->Y[Y_x]);
    // add the deviations to the variance trackers
    present_x = y->Y[Y_x];
    delta_z = y->Y[Y_z] - path[tunnel].getZ();
    delta_y = y->Y[Y_y];
    if (altitude > z_stop) {
    if (present_x > x_flare[tunnel]) {
    v_initialy.add(y->Y[Y_y]);
    v_initialz.add(y->Y[Y_z] - path[tunnel].getZ());
    }
    else if (present_x > x_final[tunnel]) {
    v_flarey.add(y->Y[Y_y]);
    v_flarez.add(y->Y[Y_z] - path[tunnel].getZ());
    }
    else if (present_x > 0.0) {
    v_finaly.add(y->Y[Y_y]);
    v_finalz.add(y->Y[Y_z] - path[tunnel].getZ());
    }
    }
    input.releaseAccess(y);
    break;
    }
    case SimulationState::HoldCurrent:
    // nothing
    break;
    }
    }
    void Evaluator::feedBack(const TimeSpec& ts)
    {
    // zero the display after a reset/inco load
    if (is_reset) {
    char buf[] = "0.0";
    gtk_entry_set_text(h_altitude, buf);
    gtk_entry_set_text(h_sinkrate, buf);
    gtk_entry_set_text(h_sigma_y_1, buf);
    gtk_entry_set_text(h_sigma_z_1, buf);
    gtk_entry_set_text(h_sigma_y_2, buf);
    gtk_entry_set_text(h_sigma_z_2, buf);
    gtk_entry_set_text(h_sigma_y_3, buf);
    gtk_entry_set_text(h_sigma_z_3, buf);
    is_reset = false;
    }
    else {
    // feed back results to the screen
    char buf[16];
    {
    strstream s1(buf, 16);s1 << delta_y << '\000';
    gtk_entry_set_text(h_delta_y, buf);
    strstream s2(buf, 16);s2 << delta_z << '\000';
    gtk_entry_set_text(h_delta_z, buf);
    }
    {
    strstream s1(buf, 16);s1 << altitude << '\000';
    gtk_entry_set_text(h_altitude, buf);
    strstream s2(buf, 16);s2 << sinkrate << '\000';
    gtk_entry_set_text(h_sinkrate, buf);
    }
    {
    strstream s1(buf, 16); s1 << v_initialy.sigma() << '\000';
    gtk_entry_set_text(h_sigma_y_1, buf);
    strstream s2(buf, 16); s2 << v_initialz.sigma() << '\000';
    gtk_entry_set_text(h_sigma_z_1, buf);
    }
    {
    strstream s1(buf, 16); s1 << v_flarey.sigma() << '\000';
    gtk_entry_set_text(h_sigma_y_2, buf);
    strstream s2(buf, 16); s2 << v_flarez.sigma() << '\000';
    gtk_entry_set_text(h_sigma_z_2, buf);
    }
    {
    strstream s1(buf, 16); s1 << v_finaly.sigma() << '\000';
    gtk_entry_set_text(h_sigma_y_3, buf);
    strstream s2(buf, 16); s2 << v_finalz.sigma() << '\000';
    gtk_entry_set_text(h_sigma_z_3, buf);
    }
    }
    }
    bool Evaluator::setFile(const vstring& f)
    {
    // make a new interpolator
    path.push_back(Interpolator());
    // tell it to read the file
    bool result = path.back().load(f.c_str());
    // undo changes in case of no success
    if (!result) {
    path.pop_back();
    return result;
    }
    return true;
    }
    #define FT_TO_METER 0.3048
    bool Evaluator::setFlare(const double& f)
    {
    x_flare.push_back(f * FT_TO_METER);
    return true;
    }
    bool Evaluator::setFinal(const double& f)
    {
    x_final.push_back(f * FT_TO_METER);
    return true;
    }
    bool Evaluator::setFeedbackRate(const TimeSpec& ts)
    {
    feedback.setTimeSpec(ts);
    feedback_timing_set = true;
    return feedback_timing_set;
    }
    const ParameterTable* Evaluator::getMyParameterTable()
    {
    static const ParameterTable table[] = {
    { "set-path",
    new MemberCall<Evaluator,vstring>(&Evaluator::setFile)},
    { "set-flare",
    new MemberCall<Evaluator,double>(&Evaluator::setFlare)},
    { "set-final",
    new MemberCall<Evaluator,double>(&Evaluator::setFinal)},
    { "set-feedback-timing",
    new MemberCall<Evaluator,TimeSpec>(&Evaluator::setFeedbackRate)},
    { "set-stop-height",
    new VarProbe<Evaluator,double>(REF_MEMBER(&Evaluator::z_stop))},
    {NULL, NULL}
    };
    return table;
    }
    static TypeCreator<Evaluator> a(Evaluator::getMyParameterTable());

You see that there is certainly some code writing in making a module. Fortunately, most of the code can be generated by the script "newModule". This script is installed with the dueca program. Run it with the name of your new module (e.g. MyModule), and it will produce a MyModule.cxx and a MyModule.hxx file. The comments in these files should guide you with the job of tailoring the module to your application.

Model configuration file.

The example simulation runs on 3 PC's, connected with fast ethernet. The division of modules over the PCs is as follows:

  • Node number 0 is the experimenter's PC. DUECA runs all administrative duties, and also reads the model data from the dueca.mod file, on node number 0. The following modules are implemented here:

    • The space plane module
    • The numeric output module
    • The evaluator module.

  • Node number 1 is the control loading PC, running QNX. This PC does the control loading simulation, and it runs the rate reduction module. Since this is the fastest-running PC (cycle-time wise), it is the clock master.

  • Node number 2 is the display PC. It only runs the display module.

Scheme is a full-fledged programming language, and therefore the model configuration file in fact contains a simple program. To make life a bit easier, we can define often-used variables in the model configuration file as Scheme variables. That is what this first part of the configuration file does:

;; document the set-up of the simulation
;; nodes
(define ecs-node 0) ; ecs, dutmms1, send order 1
(define cl-node 1) ; control loading dutmms4, send order 0
(define pfd-node 2) ; primary flight display dutmms2, send order 2
;; default priority set-up
(define admin-priority (make-priority-spec 0 0)) ; admin, interface, logging
(define sim-priority (make-priority-spec 1 0)) ; simulation, unpacking
;; difference; control loading
(if (equal? this-node-id cl-node)
(list
(define stick-priority (make-priority-spec 3 0)) ; cl simulation
)
)
;; timing parameters, basis is a granule size of 500 us
(define stick-timing (make-time-spec 0 1)) ; cl calculation, 2000 Hz
(define sim-timing (make-time-spec 0 40)) ; sim, 50 Hz
(define feedback-timing (make-time-spec 0 400)) ; eval feedback, 5 Hz

Dueca itself also needs some modules. Here they are:

;;; the modules needed for dueca itself
;;; the modules needed for dueca itself
(dueca-list
(make-entity "dueca"
(if (equal? 0 this-node-id)
(list
(make-module 'dusime "" admin-priority 'min-interval 4000)
(make-module 'dueca-view "" admin-priority)
(make-module 'activity-view "" admin-priority)
(make-module 'timing-view "" admin-priority)
)
(list)
)))

And then the modules for the spaceplane are created.

;;; the spaceplane
(define spaceplane
(make-entity "spaceplane"
(if (equal? this-node-id ecs-node)
(list
(make-module 'space-plane "" sim-priority
'set-timing sim-timing 'set-stop-height 0.6)
(make-module 'numeric-plane-output "" sim-priority
'set-output-file "dump")
(make-module 'evaluator "" subsim-priority
'set-path "path1.txt"
'set-flare 7331 'set-final 3879
'set-path "path2.txt"
'set-flare 9280 'set-final 4673
'set-path "path3.txt"
'set-flare 11416 'set-final 5971
'set-feedback-timing feedback-timing
'set-stop-height 0.6)
(make-module 'spaceplane-control "" admin-priority)
)
)
(if (equal? this-node-id 1)
(list
(make-module 'mmslab-stick "stick" stick-priority
'set-timing stick-timing)
(make-module 'rate-reduction "" stick-priority
'set-timing sim-timing)
)
)
(if (equal? this-node-id 2)
(list
(make-module 'display-space "" admin-priority
'set-fullscreen #t)
)
)
(list) ; for nodes that get no modules, empty list
)) ; spaceplane-complete

Notice the use of the "if (equal? this-node-id cl-node)" construction. All modules defined within the if statement are designated for that node. If this is not used, the modules will be created in all nodes in parallel, and this would certainly give channel conflicts.