So recently in my project I’ve been trying to get the State Machine working, if you are part of the Slack group for MARTe2 you’ve probably seen my confusing journey in understanding how it works and how to appropriately get it working.

For context, my project needs the ability to start in one state or another depending on whether we’re actually running the production version or whether we’re running our simulated version in a Gitlab CI pipeline where we’re testing individual functions in one state or another.

Now before yammering on about my project as I personally enjoy it and my push for best practices has really produced an effective team. Anyway, so, getting the state machine to do my requirements was a bit hectic. I found that when I started in the second state declared in the state machine then it wouldn’t work. There’s good reasons for this and to save you the time of going through the state machine code like I did, I shall highlight it here.

The State Machine is actually quite simple, the first concept you need to get is this:

  • The State Machine is completely decoupled from the application object, it has no awareness of what states have been defined in the application.

With that in mind you need to be sure that you define your State Machine correctly. You also need to stop making assumptions like I did, or maybe you don’t and won’t fall down the trap I did! So, the next thing we need to know about the State Machine:

  • The State Machine does not get passed the -s parameter you use when starting MARTe2 and so does not know what state you start it, it only presumes that this must be the first state in the configuration of the State Machine.

You can see this in:

https://github.com/aneto0/MARTe2/blob/master/Source/Core/Scheduler/L4StateMachine/StateMachine.cpp

    //Install the event listeners for the first state
    if (err.ErrorsCleared()) {
        currentState = Get(0u);
        err.fatalError = !currentState.IsValid();
        if (!err.ErrorsCleared()) {
            REPORT_ERROR(ErrorManagement::FatalError, "No state at position zero was defined");
        }
    }
    if (err.ErrorsCleared()) {
        uint32 z;
        for (z = 0u; (z < currentState->Size()) && (ok); z++) {
            ReferenceT<StateMachineEvent> nextStateEventJ = currentState->Get(z);
            if (nextStateEventJ.IsValid()) {
                err = InstallMessageFilter(nextStateEventJ);
                ok = err.ErrorsCleared();
            }
        }
    }
    if (err.ErrorsCleared()) {
        QueuedMessageI::SetQueueName(GetName());
        err = Start();
    }
    if (err.ErrorsCleared()) {
        currentStateStatus = Executing;
    }
    return err;

At line 155 by the way. Note here the second line: Get the first state in the state machine configuration and set this to currentState and then only listen for the events declared in that state.

This means that if you don’t start in the first state in your State Machine with the -s parameter you run a high probability will setup the wrong listeners and never respond to any messages to the State Machine for the actual state the application is in, and then like me, you’ll see that your application doesn’t change state.

The next thing you need to know from my lessons to myself is how the State Machine and Real Time Application work together. This is where it gets a bit more complex.

Let’s start by looking at a typical configuration for a StateMachine, we’re not going to use the Padova demo’s or example configs as I’ve found that while they do display the behaviour you want, they also heavily dilute it with more complex things that can confuse a first time user.

+StateMachine = {
    Class = StateMachine
    +PREPULSE = {
        Class = ReferenceContainer
        +GOTOSTATE1 = {
            Class = StateMachineEvent
            NextState = STATE1
            Timeout = 0
            +PrepareChangeToState1Msg = {
                Class = Message
                Destination = App
                Mode = ExpectsReply
                Function = PrepareNextState
                +Parameters = {
                    Class = ConfigurationDatabase
                    param1 = State1
                }
            }
            +StopCurrentStateExecutionMsg = {
                Class = Message
                Destination = App
                Function = StopCurrentStateExecution
                Mode = ExpectsReply
            }
            +StartNextStateExecutionMsg = {
                Class = Message
                Destination = App
                Function = StartNextStateExecution
                Mode = ExpectsReply
            }
        }
    }
    +STATE1 = {
        Class = ReferenceContainer
        +GOTOSTATE2 = {
            Class = StateMachineEvent
            NextState = STATE2
            Timeout = 0
            +PrepareChangeToState2Msg = {
                Class = Message
                Destination = App
                Function = PrepareNextState
                Mode = ExpectsReply
                +Parameters = {
                    Class = ConfigurationDatabase
                    param1 = State2
                }
            }
            +StopCurrentStateExecutionMsg = {
                Class = Message
                Destination = App
                Function = StopCurrentStateExecution
                Mode = ExpectsReply
            }
            +StartNextStateExecutionMsg = {
                Class = Message
                Destination = App
                Function = StartNextStateExecution
                Mode = ExpectsReply
            }
        }
    }
    +STATE2 = {
        Class = ReferenceContainer
        +GOTOSTATE1 = {
            Class = StateMachineEvent
            NextState = STATE1
            Timeout = 0
            +PrepareChangeToState1Msg = {
                Class = Message
                Destination = App
                Mode = ExpectsReply
                Function = PrepareNextState
                +Parameters = {
                    Class = ConfigurationDatabase
                    param1 = State1
                }
            }
            +StopCurrentStateExecutionMsg = {
                Class = Message
                Destination = App
                Function = StopCurrentStateExecution
                Mode = ExpectsReply
            }
            +StartNextStateExecutionMsg = {
                Class = Message
                Destination = App
                Function = StartNextStateExecution
                Mode = ExpectsReply
            }
        }
    }
    +ERROR = {
        Class = ReferenceContainer
        +ENTER = {
            Class = ReferenceContainer
            +StopCurrentStateExecutionMsg = {
                Class = Message
                Destination = App
                Function = StopCurrentStateExecution
                Mode = ExpectsReply
            }
            +PrepareChangeToErrorMsg = {
                Class = Message
                Destination = App
                Function = PrepareNextState
                Mode = ExpectsReply
                +Parameters = {
                    Class = ConfigurationDatabase
                    param1 = StateError
                }
            }
            +StartNextStateExecutionMsg = {
                Class = Message
                Destination = App
                Function = StartNextStateExecution
                Mode = ExpectsReply
            }
        }
        +RESET = {
            Class = StateMachineEvent
            NextState = STATE1
            NextStateError = STATE1
            Timeout = 0
            +StopCurrentStateExecutionMsg = {
                Class = Message
                Destination = App
                Function = StopCurrentStateExecution
                Mode = ExpectsReply
            }
            +PrepareChangeToState1Msg = {
                Class = Message
                Destination = App
                Function = PrepareNextState
                Mode = ExpectsReply
                +Parameters = {
                    Class = ConfigurationDatabase
                    param1 = State1
                }
            }
            +StartNextStateExecutionMsg = {
                Class = Message
                Destination = App
                Function = StartNextStateExecution
                Mode = ExpectsReply
            }
        }
    }
}
$App = {
    Class = RealTimeApplication
    +Functions = {
        Class = ReferenceContainer
        +file_inputs = {
            Class = IOGAM
            InputSignals = {
                InputTrigger = {
                    DataSource = LogInput
                    Alias = InputTrigger
                    Type = uint64
                }
            }
            OutputSignals = {
                InputTrigger = {
                    DataSource = DDB0
                    Alias = InputTrigger
                    Type = uint64
                }
            }
        }
        +PrePulse = {
            Class = MessageGAM
            TriggerOnChange = 0
            +Events = {
                Class = ReferenceContainer
                +Event1 = {
                    Class = EventConditionTrigger
                    EventTrigger = {
                        Command = 1
                    }
                    +Message = {
                        Class = Message
                        Destination = StateMachine
                        Function = GOTOSTATE1
                        Mode = ExpectsReply
                    }
                }
            }
            InputSignals = {
                Command = {
                    DataSource = DDB0
                    Alias = InputTrigger
                    Type = uint64
                }
            }
            OutputSignals = {
                PendingMessages = {
                    DataSource = DDB0
                    Type = uint32
                }
            }
        }
        +EndPulse = {
            Class = MessageGAM
            TriggerOnChange = 0
            +Events = {
                Class = ReferenceContainer
                +Event1 = {
                    Class = EventConditionTrigger
                    EventTrigger = {
                        Command = 2
                    }
                    +Message = {
                        Class = Message
                        Destination = StateMachine
                        Function = GOTOSTATE2
                        Mode = ExpectsReply
                    }
                }
            }
            InputSignals = {
                Command = {
                    DataSource = DDB0
                    Alias = InputTrigger
                    Type = uint64
                }
            }
            OutputSignals = {
                PendingMessages = {
                    DataSource = DDB0
                    Type = uint32
                }
            }
        }
        +GAMDisplay = {
            Class = IOGAM
            InputSignals = {
                Counter = {
                    DataSource = Timer
                    Type = uint32
                    Frequency = 1
                }
            }
            OutputSignals = {
                Counter = {
                    DataSource = LoggerDataSource
                    Alias = Time
                    Type = uint32
                }
            }
        }
        +State1 = {
            Class = ConstantGAM
            OutputSignals = {
                State = {
                    Default = 1
                    DataSource = DDB0
                    Alias = State
                    Type = uint32
                }
            }
        }
        +State2 = {
            Class = ConstantGAM
            OutputSignals = {
                State = {
                    Default = 2
                    DataSource = DDB0
                    Alias = State
                    Type = uint32
                }
            }
        }
        +State3 = {
            Class = ConstantGAM
            OutputSignals = {
                State = {
                    Default = 3
                    DataSource = DDB0
                    Alias = State
                    Type = uint32
                }
                InputTrigger = {
                    Default = 0
                    DataSource = DDB0
                    Alias = InputTrigger
                    Type = uint64
                }
            }
        }
        +file_out = {
            Class = IOGAM
            InputSignals = {
                State = {
                    DataSource = DDB0
                    Alias = State
                    Type = uint32
                }
                InputTrigger = {
                    DataSource = DDB0
                    Alias = InputTrigger
                    Type = uint64
                }
            }
            OutputSignals = {
                State = {
                    DataSource = LoggingOut
                    Type = uint32
                    Trigger = 1
                }
                InputTrigger = {
                    DataSource = LoggingOut
                    Type = uint64
                }
            }
        }
    }
    +Data = {
        Class = ReferenceContainer
        DefaultDataSource = DDB0
        +Timer = {
            Class = LinuxTimer
            SleepNature = "Default"
            ExecutionMode = "IndependentThread"
            CPUMask = 0x1
            Signals = {
                Counter = {
                    Type = uint32
                }
                Time = {
                    Type = uint32
                }
            }
        }
        +LogInput = {
            Class = FileDataSource::FileReader
            Filename = "test_inputs.csv"
            FileFormat = "csv"
            CSVSeparator = ","
            Interpolate = "no"
            EOF = "Error"
            Preload = "yes"
        }
        +LoggerDataSource = {
            Class = LoggerDataSource
        }
        +Timings = {
            Class = TimingDataSource
        }
        +DDB0 = {
            Class = GAMDataSource
        }
        +LoggingOut = {
            Class = FileDataSource::FileWriter
            NumberOfBuffers = 1000
            CPUMask = 0x3
            StackSize = 100000000
            Filename = "log_0.csv"
            Overwrite = "yes"
            FileFormat = "csv"
            CSVSeparator = ","
            StoreOnTrigger = 0
            Signals = {
                State = {
                    Type = uint32
                }
                InputTrigger = {
                    Type = uint64
                }
            }
        }
    }
    +States = {
        Class = ReferenceContainer
        +PrePulse = {
            Class = RealTimeState
            +Threads = {
                Class = ReferenceContainer
                +Thread0 = {
                    Class = RealTimeThread
                    CPUs = 0x2
                    Functions = { file_inputs file_out State1 PrePulse GAMDisplay }
                }
            }
        }
        +State1 = {
            Class = RealTimeState
            +Threads = {
                Class = ReferenceContainer
                +Thread0 = {
                    Class = RealTimeThread
                    CPUs = 0x2
                    Functions = { file_inputs file_out State2 EndPulse GAMDisplay }
                }
            }
        }
        +State2 = {
            Class = RealTimeState
            +Threads = {
                Class = ReferenceContainer
                +Thread0 = {
                    Class = RealTimeThread
                    CPUs = 0x2
                    Functions = { State3 file_out GAMDisplay }
                }
            }
        }
        +ErrorState = {
            Class = RealTimeState
            +Threads = {
                Class = ReferenceContainer
                +Thread0 = {
                    Class = RealTimeThread
                    CPUs = 0x2
                    Functions = { State3 file_out GAMDisplay }
                }
            }
        }
    }
    +Scheduler = {
        Class = GAMBareScheduler
        TimingDataSource = Timings
        MaxCycles = 60
    }
}

As a quick brief on this:

  • Here I am using the GAMBareScheduler, I’m doing this intentionally as a bug to show you a behavior shortly.
  • You must have a error state in the StateMachine defined, but you don’t need to define the NextErrorState parameter in your State events.
  • This config reads in a csv file and when the signal InputTrigger from this file increments, the MessageGAM in that state then sends a message to the StateMachine for “GoToNext” and whatever state is defined as the next state for that particular MessageGAM and corresponding StateMachine.

Understanding how Messages work are also somewhat important but let’s go one step at a time. So, within the State Machine we know that it takes the first state described in it’s configuration and then sets up the listeners for the events in that state. For each event, this is a message it expects to receive. That is a key thing to get, you can have multiple possible events in a state in the State Machine.

So in this configuration case, the State Machine will setup to listen for a Message that has function “PrePulse”:

+StateMachine = {
    Class = StateMachine
    +PREPULSE = {
        Class = ReferenceContainer
        +GOTOSTATE1 = {
            Class = StateMachineEvent
            NextState = STATE1
            Timeout = 0
            +PrepareChangeToState1Msg = {
                Class = Message
                Destination = App
                Mode = ExpectsReply
                Function = PrepareNextState
                +Parameters = {
                    Class = ConfigurationDatabase
                    param1 = State1
                }
            }
            +StopCurrentStateExecutionMsg = {
                Class = Message
                Destination = App
                Function = StopCurrentStateExecution
                Mode = ExpectsReply
            }
            +StartNextStateExecutionMsg = {
                Class = Message
                Destination = App
                Function = StartNextStateExecution
                Mode = ExpectsReply
            }
        }
    }

When it receives this message it will then send out the messages defined in the event to their destination with their function. i.e. it will send to the application, prepare next state, stop current and then start next state.

You’ll notice that the first message has a parameter field, the start next state however does not – this is because the RealTimeApplication when executing Start Next State will execute whatever state it was last called to prepare and holds that state internally. So why the parameter field? Now is the time to digest the message process a bit more, hopefully your brains stomach is not full yet!

When you send a message, you call:

MessageI::SendMessage(mymessage, this);

And this executes function:

https://github.com/aneto0/MARTe2/blob/master/Source/Core/BareMetal/L4Messages/MessageI.cpp

ErrorManagement::ErrorType MessageI::SendMessage(ReferenceT<Message> &message,
                                                 const Object * const sender) {
    Reference destination;
    ErrorManagement::ErrorType ret;

    if (!message.IsValid()) {
        ret.parametersError = true;
        REPORT_ERROR_STATIC_0(ErrorManagement::ParametersError, "Invalid message.");
    }

    // compute actual message parameters
    if (ret.ErrorsCleared()) {

        // is this is a reply (must be an indirect reply)
        // a direct reply does not need sending
        // therefore we will refuse sending it
        if (message->IsReply()) {

            if (!message->ExpectsIndirectReply()) {
                ret.communicationError = true;
                REPORT_ERROR_STATIC_0(ErrorManagement::CommunicationError, "Message does not expect and indirect reply as it should.");
            }
            else {
                // if it is a reply then the destination is the original sender
                // Check if it exists in the tree!
                Reference ref(const_cast<Object *>(message->GetSender()));
                ReferenceContainer result;
                ReferenceContainerFilterReferences filter(1, ReferenceContainerFilterMode::RECURSIVE, ref);
                ReferenceContainer *ord = dynamic_cast<ReferenceContainer*>(ObjectRegistryDatabase::Instance());
                if (ord != NULL_PTR(ReferenceContainer *)) {
                    ord->Find(result, filter);
                }
                if (result.Size() > 0u) {
                    destination = result.Get(0u);
                }
            }

        }
        else { // not a reply

            // if it is not a reply then use the proper destination
            destination = FindDestination(message->GetDestination());

            // assigns the sender
            if (sender != NULL) {
                message->SetSender(sender);
            }
            else {
                // no Object ==> no reply possible
                // therefore refuse sending
                if (message->ExpectsReply()) {
                    REPORT_ERROR_STATIC_0(ErrorManagement::CommunicationError, "Message expects reply but no sender was set.");
                    ret.parametersError = true;
                }
            }
        }
    }

    if (ret.ErrorsCleared()) {
        // implicit dynamic cast here
        ReferenceT<MessageI> destinationObject = destination;

        if (destinationObject.IsValid()) {
            ret = destinationObject->messageFilters.ReceiveMessage(message);
        }
        else {
            /*lint -e{1793} GetList() is used to allow CCString to be passed to the REPORT_ERROR_STATIC*/
            REPORT_ERROR_STATIC(ErrorManagement::UnsupportedFeature, "The destination object with name %s does not have a MessageI interface.", message->GetDestination().GetList());
            ret.unsupportedFeature = true;
        }
    }

    return ret;
}

Most of this code is only verifying the message, the destination and the sender, this is the code you really want to be concerned with:

ret = destinationObject->messageFilters.ReceiveMessage(message);

My first assumption before reviewing this code was that Messages ended up in some shared memory collection that the destination was monitoring and then would pull and execute the message. However we can see here that actually, The sender calls a function within the destination to execute the message pools of the destination to receive the message.

The key parts with that is that the destination will setup a message pool, within which it will have message filters and define what messages it is listening to.

Given that our State Machine like any in most configuration will mostly be sending messages to the RealTimeApplication object we should now inspect how that sets itself up with respect to messaging. Note: We’ll later look at messages to other destinations.

I also before proceeding just want you to keep in mind that the application and the state machine are completely disconnected, separate entities, these can exist in MARTe2, our known concept of the name “Application” is a little different here, application is simple where we run our GAMs and DataSources and how we run them.

Maybe I should change this article title to “Understanding the State Machine” because that’s all we’re doing right now… anyway. Also, if you’re wondering when I’ll mention the Enter concept, we’ll get to that, don’t worry.

So the part we need to inspect is here:

https://github.com/aneto0/MARTe2/blob/master/Source/Core/BareMetal/L5GAMs/RealTimeApplication.cpp

Thankfully, the first code present is what we’re interested in:

RealTimeApplication::RealTimeApplication() :
        ReferenceContainer(), MessageI() {
    filter = ReferenceT<RegisteredMethodsMessageFilter>(GlobalObjectsDatabase::Instance()->GetStandardHeap());
    filter->SetDestination(this);
    ErrorManagement::ErrorType ret = MessageI::InstallMessageFilter(filter);
    if (!ret.ErrorsCleared()) {
        REPORT_ERROR(ErrorManagement::FatalError, "Failed to install message filters");
    }
    defaultDataSourceName = "";
    index=1u;
    checkSameGamInMoreThreads=true;
    checkMultipleProducersWrites=true;
}

So the RealTimeApplication setups up the message filter, RegisteredMethodsMessageFilter in it’s message pool. I like to think of each part of a configuration that is on it’s own as a entity. To make it more clear what I mean, within your configuration it is basically a binary tree where each attribute is a objects parameters except the class attribute which defines the object type. So for instance, the event within a StateMachine has the messages objects and can have any number of which you define as the messages it sends. That’ll probably become more clear once we get onto the HTTP Service.. Given the current length of this article I might reserve that for another article.

So, anyway, let’s take a look at the filter we setup:

https://github.com/aneto0/MARTe2/blob/master/Source/Core/BareMetal/L4Messages/RegisteredMethodsMessageFilter.cpp

ErrorManagement::ErrorType RegisteredMethodsMessageFilter::ConsumeMessage(ReferenceT<Message> &messageToTest) {

    ErrorManagement::ErrorType ret;

    //This filter does not handle replies...
    bool valid = messageToTest.IsValid();
    bool isReply = messageToTest->IsReply();
    if ((destinationObject != NULL_PTR(Object *)) && (valid) && (!isReply)) {

        // try calling the method
        CCString methodName = messageToTest->GetFunction();
        ret = destinationObject->CallRegisteredMethod(methodName, *(messageToTest.operator->()));

        // the registered method has no responsibility to handle the reply mechanism
        // therefore it is handled here
        if (ret.ErrorsCleared()) {
            if (messageToTest->ExpectsReply()) {
                messageToTest->SetAsReply();

                // handles indirect reply
                if (messageToTest->ExpectsIndirectReply()) {
                    // simply produce a warning
                    // destination in reply is known so should not be set
                    ret = MessageI::SendMessage(messageToTest, NULL_PTR(Object *));
                }
            }
        }
    }
    else {
        ret.unsupportedFeature = true;
    }

    return ret;

}

The key part here is this line:

ret = destinationObject->CallRegisteredMethod(methodName, *(messageToTest.operator->()));

It’s funny how MARTe2 is mostly validation code and then one line is usually the golden ticket. All objects support this function as they all inherit from Object:

https://github.com/aneto0/MARTe2/blob/master/Source/Core/BareMetal/L2Objects/Object.cpp

ErrorManagement::ErrorType Object::CallRegisteredMethod(const CCString &methodName) {
    ErrorManagement::ErrorType err;

    ClassRegistryItem * cri = GetClassRegistryItem();
    ClassMethodCaller *caller = NULL_PTR(ClassMethodCaller *);

    err.fatalError = (cri == NULL_PTR(ClassRegistryItem *));

    if (err.ErrorsCleared()) {
        caller = cri->FindMethod(methodName);
        err.unsupportedFeature = (caller == NULL_PTR(ClassMethodCaller *));
    }

    if (err.ErrorsCleared()) {
        /*lint -e{613} err.unsupportedFeature protects from using caller = NULL*/
        err = caller->Call(this);
    }

    return err;
}

This is where the importance of using the Class Method Register comes in when you create your own GAMs, Datasources etc… in the RealTimeApplication C++ file if we go back to it, at the end it does this:

CLASS_REGISTER(RealTimeApplication, "1.0")

CLASS_METHOD_REGISTER(RealTimeApplication, PrepareNextState)
CLASS_METHOD_REGISTER(RealTimeApplication, StartNextStateExecution)
CLASS_METHOD_REGISTER(RealTimeApplication, StopCurrentStateExecution)

So those messages we were sending in the StateMachine are actually methods we can actually execute by sending the message using this message filter at the destination, this being the RealTimeApplication. And that’s how it works!

Now, I mentioned I would show a bug with the GAMBareScheduler. As we know from my explanation, the StateMachine is simply calling ReceiveMessage directly within the destination object, so technically, the StateMachine thread actually executes these, that includes the PrepareNextState, Stop current and StartNext. When you are using the GAMBareScheduler however, this is your start state:

https://github.com/aneto0/MARTe2/blob/master/Source/Core/BareMetal/L5GAMs/GAMBareScheduler.cpp

ErrorManagement::ErrorType GAMBareScheduler::StartNextStateExecution() {
    isAlive = true;
    
    ScheduledState *nextState = GetSchedulableStates()[realTimeApplication->GetIndex()];
    uint64 lastCycleTimeStamp = 0u;

    while(isAlive) { 
        Cycle(0u); 

        uint32 absTime = 0u;
        if (lastCycleTimeStamp != 0u) {
            uint64 tmp = (HighResolutionTimer::Counter() - lastCycleTimeStamp);
            float64 ticksToTime = (static_cast<float64>(tmp) * clockPeriod) * 1e6;
            absTime = static_cast<uint32>(ticksToTime);  //us
        }
        uint32 sizeToCopy = static_cast<uint32>(sizeof(uint32));
        if (!MemoryOperationsHelper::Copy(nextState->threads[0].cycleTime, &absTime, sizeToCopy)) {
            REPORT_ERROR(ErrorManagement::FatalError, "Could not copy cycle time information.");
        }
        lastCycleTimeStamp = HighResolutionTimer::Counter();

        if (maxCycles != 0u) {
            isAlive = (nCycle < maxCycles);
            nCycle++;
        }
    }
    
    return ErrorManagement::NoError;
}

Notice how it’s an infinite while loop while the max cycle i.e. end to the application has not been reached? That why, I’ve applied this change to the documentation:

https://github.com/aneto0/MARTe2/pull/6

So, now you’re aware of that, you won’t fall into that trap. So, how do we get out of the main trap? You probably forgot it by this point right?

  • How we do support the state machine working when we may start in one of multiple different states.

The key part here is that we were using the -s parameter to define what state to start in and this was passed to the RealTimeApplication. But let’s take a different tac, what if we have the StateMachine control this whole process? Let’s start by defining our first state which, when the StateMachine’s Initialise function is called, which happens way before the RealTimeApplication gets executed and give it a new way of working.

We’re going to give it a state called initializing where it has several events that could get triggered – those events will define our starting point in the RealTimeApplication.

+StateMachine = {
    Class = StateMachine
    +INITIAL = {
        Class = ReferenceContainer      
        +PrePulse = {
            Class = StateMachineEvent
            NextState = "PrePulse"
            NextStateError = "ERROR"
            Timeout = 0
            +ChangeToStateIdleMsg = {
                Class = Message
                Destination = App
                Mode = ExpectsReply
                Function = PrepareNextState
                +Parameters = {
                    Class = ConfigurationDatabase
                    param1 = PrePulse
                }
            }
            +StartNextStateExecutionMsg = {
                Class = Message
                Destination = App
                Function = StartNextStateExecution
                Mode = ExpectsReply
            }
        } 
        +State1 = {
            Class = StateMachineEvent
            NextState = "State1"
            NextStateError = "ERROR"
            Timeout = 0
            +ChangeToStateIdleMsg = {
                Class = Message
                Destination = App
                Mode = ExpectsReply
                Function = PrepareNextState
                +Parameters = {
                    Class = ConfigurationDatabase
                    param1 = State1
                }
            }
            +StartNextStateExecutionMsg = {
                Class = Message
                Destination = App
                Function = StartNextStateExecution
                Mode = ExpectsReply
            }
        }
    }

So here I have defined that when the State Machine starts it will expect one of two messages, or rather, setup listeners for them. This could be, PrePulse or State1, not that PrePulse is kind of my State0. Depending on the event selected, it then sends to the RealTimeApplication the messages related to starting those states, not that theres no stop state message as we don’t expect the RealTimeApplication to actually be running a state at this point.

And now, we have that, the only other behaviour you need to change is to stop using the -s parameter and instead use the -m parameter which stands for message. It means, send message, and you have it as:

-m <destination>:<function>

If you’re thinking, but if I don’t define -s then marte2 just throws up errors, it doesn’t if you use the -m with the state machine in the way we’ve described above!

So here I would run, let’s say I’m running my simulation Gitlab CI:

/root/marte.sh -l RealTimeLoader -f Simulation.cfg -m StateMachine:State1

And voila! Now this works brilliantly because:

  • We utilise our first state in the State Machine, so it’s setup correctly to listen to the messages and whatever state we move to using this, it’s setup correctly after and following this point for the runtime of the application.
  • We avoid the GAMBareScheduler now knowing how everything works to avoid getting the State Machine stalled.

So lessons learned:

  • Avoid the GAMBareScheduler when you need to utilise the State Machine. If you have a baremetal application, design your GAM setup in a way which handles state types internally (like using the selector GAM).
  • Use the State Machine for application control instead of the parameter inputs.
  • Know how it works, and it will be your friend, don’t know, and it will be your worst and most annoying enemy.

Leave a Reply

All fields marked with an asterisk (*) are required