Hi,
I wasn't aware of the Eclipse Plug-In for leJOS state machines. It does look quite impressive, but I haven't yet checked whether it actually still works.
The framework currently consists of 7 types (4 classes, 1 abstract class, 1 interface, 1 enum). 6 of them are to be used by implementing code, the enum is private to the framework.
The API of the actual state machine is rather simple. It consists of the constructor taking a list of state instances, the class of the initial state, a list of classes of terminal states and optionally an event rejection handler object instance, and four life cycle methods. They are
start(),
handleEvent(Event<?> event),
kill(), and
shutDown(). They should be self-explaining. The difference between kill and shutDown is that kill destroys the state machine instantly while shutDown clears the state stack letting each state perform its exit action. If no event rejection handler object is given the default implementation is used which does just nothing about unhandled events.
Event creation is done by implementing code. The Event class acts as a container for an event type and its associated payload data. The event types for a specific implementation need to be defined in an enum implementing the EventType interface. This interface defines a single method
isAcceptablePayloadItem(String key, Object value). Implementing this method allows each event type to define which payload items can be associated with it in terms of item name, type and value range. An enum defining event types may look like this:
- Code: Select all
package com.matthiasrothe.robotics;
import com.matthiasrothe.lejos.lhsm.EventType;
public enum EventTypes implements EventType {
BATTERY_EMPTY {
@Override
public boolean isAcceptablePayloadItem(String key, Object value) {
return false; // doesn't accept any payload
}
},
APPROACHING_OBSTACLE {
@Override
public boolean isAcceptablePayloadItem(String key, Object value) {
if ("angle".equals(key) && value instanceof Float &&
(Float) value >= -45 && (Float) value <= 45) {
return true;
}
if ("distance".equals(key) && value instanceof Float &&
(Float) value >= 0 && (Float) value <= 5) {
return true;
}
return false;
}
};
}
An event to be handled might than be created by
- Code: Select all
stateMachine.handleEvent(new Event<EventTypes>(EventTypes.BATTERY_EMPTY));
or
- Code: Select all
Event<EventTypes> event = new Event<EventTypes>(EventTypes.APPROACHING_OBSTACLE);
event.addPayloadItem("angle", 10.0f);
event.addPayloadItem("distance", 4.0f);
stateMachine.handleEvent(event);
The most extensive API is the State class. This is the only abstract class and implementations need to implement at least the abstract methods
isAcceptableEvent(Event<?> event) and
getCurrentTransition(). Furthermore they can override the default implementations of various life cycle methods. These are
getExtendedState() (allowing for hierachical states),
waitForAnotherEvent() and
discardAllExceptedEvents() (allowing for several events for a single transition),
performEntryAction() and
performExitAction() (providing hooks for the actual application code). The default implementations of these methods provide basic support for non-hierarchical states accepting only a single event per transition and doing nothing on entry and exit. Depending on the needs of the actual application implementing the states can therefore be very simple like
- Code: Select all
package com.matthiasrothe.robotics;
import com.matthiasrothe.lejos.lhsm.Event;
import com.matthiasrothe.lejos.lhsm.State;
import com.matthiasrothe.lejos.lhsm.Transition;
public class BatteryConsumingState extends State {
private static final Transition TRANSITION = new Transition(BatteryLoadingState.class);
@Override
protected boolean isAcceptableEvent(Event<?> event) {
if (event.getEventType() == EventTypes.BATTERY_EMPTY) {
return true;
} else {
return false;
}
}
@Override
protected Transition getCurrentTransition() {
return TRANSITION;
}
}
or rather complicated.
Last but not least the Transition class has two methods that can be overridden:
canProceed() and
performTransitionAction(). The first method acts as a transition guard. The transition is only executed if this method returns true. The default implementation always returns true. The latter provides a hook for application code to be executed during the transition. The default implementation does just nothing. If the defaults suffice the Transition class can be used as it is. Instances would than be created by using the constructor taking the class of the target state as seen in the last code example.
I hope these explanations enlighten you and am looking forward to reading your feedback.
