Events
State machines are inherently event oriented since events determine which transitions to take and when, controlling the entire flow of a state machine. When executing state machines, Moxie checks which events are relevant to the currently active states, and then finds the moments in time when those events will occur in order to determine which one comes next. This allows Moxie to model continuous time, precisely jumping the simulation time forward from event to event instead of having to advance the simulation time forward in explicit steps. This means you can model behavior without having to worry about numerical details of the simulation. In particular, using precise moments in continuous time allows for more natural and accurate use of change events. Moxie also allows you to delegate the detection of event times, especially change event times, to analysis tools like STK. This means you can let the analysis tools handle events based on the broader environment or context of your system (e.g., gravity, relative location, etc.) while you focus on modeling the behavior of your system.
Moxie supports the following events:
- Completion event - Occurs automatically on an active state when all regions inside the state have reached a final state, or immediately if the state has no internal regions.
- Time event - Occurs when the simulation time reaches the event's specified absolute or relative time.
- Call event - Occurs whenever its corresponding operation is executed.
- Signal event - Occurs whenever its corresponding signal is sent to it. For Moxie simulations, signals can only be sent from delegate code.
- Change event - Occurs whenever its boolean expression changes from
false
totrue
.
All of the following examples, unless noted otherwise, are from the hot air balloon ride example.
Time events
A time event occurs when the simulation time reaches the event's specified absolute or relative time.
Moxie requires absolute time events' expressions to evaluate to a
Moxie-Base::Structure::TimeInstant
.
Similarly, Moxie requires relative time events' expression to evaluate to a
Moxie-Base::Structure::TimeDuration
, which is measured from the time that the state was entered.
Moxie allows you to specify time expressions either as opaque expressions or as literal strings with the same
syntax, but Moxie does not support freeform inline strings like 25 seconds
.
Figure 1 shows examples of both an absolute and a relative time event.
First, the at(passenger.appointmentTime)
absolute time event triggers its transition to launch the balloon at its
specified instant in time.
Then, the after(maxFlightDuration)
relative time event triggers its transition to land the balloon after its
specified duration of time has elapsed.
Figure 1: An operator waiting for specific times
Call events
A call event occurs whenever its corresponding operation is executed, typically as an effect of another
transition.
You can use call events to trigger transitions in active regions or state machines sessions owned by
the instance specification that owns the operation.
A single operation execution can even trigger multiple call event transitions at the same time.
You can also still use an operation for call events even if the operation has a
delegate implementation that does something else like interact with an analysis tool.
To do so, either use auto-implementation in the delegate or implement the call event yourself
using the StateMachineRemoteControl
.
Moxie provides an operation's parameters to its delegate implementation if it has one, but Moxie does not provide them for use in
the effects of transitions triggered by call events that use the operation.
Figure 2 shows two examples of a call event.
The embark( vehicle : Balloon )
and disembark()
call events trigger the transitions
from Waiting to Ride
to Riding
and then to the final state.
These call events occur when the passenger.embark(balloon)
and passenger.disembark()
operations are executed by the balloon operator in Figure 1.
Figure 2: A passenger reacting to calls from an operator
Signal events
A signal event occurs whenever its corresponding signal is sent to it. Just like with call events, you can use signal events to trigger transitions in active regions or state machines sessions owned by the instance specification that the signal was sent to, and a single signal can even trigger multiple signal event transitions at the same time. SysML state machines do not provide an explicit way for you to indicate that a signal should be sent, so Moxie requires that signals be sent from delegate code. Because of this, you may find call events to be more convenient for triggering other state machines or regions. Moxie provides a StateMachineRemoteControl delegate dependency that you can use in your delegates to easily send signals.
Figure 3 shows two examples of sending a signal.
The sendBuckleUpSignal(passenger)
and sendReadyForPhotoSignal(passenger)
operations
are backed by delegate methods that call sendSignalEvent("Structure::Passenger::BuckleUp", target)
and
sendSignalEvent("Structure::Passenger::ReadyForPhoto", target)
respectively using the
StateMachineRemoteControl
.
These signals to the passenger cause the BuckleUp
and ReadyForPhoto
signal events in
Figure 2 to trigger their transitions between Enjoying the Ride
and
Taking Photo
.
Figure 3: An operator sending signals to a passenger
Change events
A change event occurs whenever its boolean expression changes from false
to true
.
Change events are especially useful for detecting when an environmental condition meets a threshold.
Moxie requires change event expressions to both be specified as opaque expressions and to
evaluate to either a boolean primitive (SysML::Libraries::PrimitiveValueTypes::Boolean
) or a
Moxie-Base::Structure::BooleanValue
.
A boolean primitive is just a value that can be either true
or false
, but a
BooleanValue
is a boolean value over time, including the precise time of each value change into
the future.
Figure 4 shows two examples of changing a boolean primitive expression to cause a change event.
The Burner Active
state sets the isOperating
property to
true
upon entry and false
upon exit.
These changes cause the boolean primitive change events in Figure 3, when(isOperating == [true/false])
, to immediately trigger their respective transitions (assuming that the
guard on the latter transition also evaluates to true).
You can use this kind of boolean primitive toggle as an alternative to a call or signal event between
orthogonal regions or state machine sessions.
Figure 4 also shows two examples of BooleanValue
change events.
The balloon altitude is tracked as a Moxie-Core::Structure::ScalarValue
which, similarly to a
BooleanValue
, is a number over time. The change events in Figure 4,
when(balloon.altitude.is[Less/Greater]Than(targetAltitude))
, compare this number over time
to a target altitude threshold to produce a boolean value over time that indicates when the change events will occur.
Figure 4: An operator reacting to changes in altitude
Analysis tool example
Using analysis tools like STK, you can check for much more complex conditions than a simple altitude threshold.
Your analysis tools can use analytical equations, numerical propagation, or anything else to detect
when a condition is true, and then your delegate code can convert that analysis into a
BooleanValue
, allowing Moxie to use the precise times when it becomes true for change events.
Figure 5 shows an example (not from the hot air balloon ride) of using a
BooleanValue
from an STK analysis in change events.
The signalDetected
property is of type BooleanValue
and is used for both of the change
events (as well as a guard just after the state is entered).
The delegate code for signalDetected
performs an STK access calculation, which accounts for obstructions, distance,
direction, etc., to determine at what times a radar sensor on an aircraft is able to detect an incoming radar signal.
Figure 5: An aircraft sensor reacting to incoming radar