Samek 0008
Samek 0008
Samek 0008
f e a t u r e
State-Oriented
Programming
Implementing hierarchical state machines doesn’t mean you have to use code-synthesizing tools.
Here are some techniques for simple, efficient, and direct mapping of state machines to C or C++.
T
he Unified Modeling Language (UML) pro- patterns7 built on meta-patterns of inheritance and poly-
vides a number of conceptual and graphical morphism. Following this analogy to OOP, we propose the
views for capturing and conveying designs. term state-oriented programming (SOP) to describe a pro-
Among these views, hierarchical state gramming style based on HSMs.
machines (HSMs), based on Harel state- The primary goal of this article is to present a simple
charts, are of key importance and provide the and efficient implementation of the HSM design pattern.
foundation for automatic code generation from object By providing easy-to-use C and C++ recipes for generating
models.1 Unfortunately, this creates the impression that HSMs, we hope to make the major benefits of the technol-
the methodology is only accessible through use of code ogy more accessible to the software community. The pro-
synthesizing tools. This is similar to the common belief posed implementation techniques are valuable in that they
that object-oriented programming (OOP) is only possi- raise the level of abstraction and allow for straightforward
ble with object-oriented (OO) languages. However, the mapping of UML statecharts to compact and efficient code
key OO concepts of encapsulation, inheritance, and in C or C++. We have used the technique extensively in
polymorphism may be implemented as design patterns2 deeply embedded, hard real-time RF receiver applications
in a non-OO language such as C. Similarly, hierarchical where both high speed and small memory footprint were
state machines can be viewed as another such fundamen- crucial.
tal pattern. In an effort to maximize efficiency and minimize imple-
From a more abstract perspective, one may view hierar- mentation complexity, many of the more advanced features
chical state machines as a meta-pattern, in that various of UML statecharts have been omitted. The implemented
structured uses become design patterns (behavioral pat- features form a proper subset of UML statecharts and
terns3) in their own right. This is analogous to OO design include:
• Nested states with proper handling the discriminator in the first level (including entry/exit, state reac-
of group transitions and group of the switch and event-type in the tions, and actions associated with
reactions second.4 This is, perhaps, the most transitions) are most commonly
• Guaranteed execution of common technique and works well represented as pointers to func-
entry/exit actions upon enter- for classical “flat” state machines tions. Representing state hierarchy
ing/exiting states and is widely employed by automat- in a flat action-state table is cum-
• Straightforward implementation of ic code synthesizing tools.5 Manual bersome. Also, this approach
conditional event responses coding of entry/exit actions and requires a large (and consequently
(guards) nested states is, however, cumber- wasteful) action/state array and
• Design that enables inheriting and some, mainly because code pertain- many fine-granularity functions
specializing state models ing to one state becomes distrib- representing actions
uted and repeated in many places, • Generic “state machine inter-
More advanced features of UML making it difficult to modify and preters” driven by typically com-
statecharts (such as history mecha- maintain when the topology of plex data structures that represent
nisms and orthogonal regions) can be state machine changes the hierarchy of states together
added as behavioral patterns built on • Action-state tables containing typi- with entry/exit actions and transi-
top of the implementation presented cally sparse arrays of actions and tions.6 This is a generalized action-
here. transitions for each state.4 Actions state table approach that attempts
We begin with a brief summary of
approaches that have been document-
ed in the relevant literature or imple- FIGURE 1 Structure of HSM design pattern
mented in commercial products. We
then describe our implementation
super
using UML class diagrams and pro-
vide a complete implementation in
both C and C++. Taking the example State Hsm
curr
of a simple digital watch, we demon-
super curr
strate how to map a UML state dia- handler top top
gram to code and how to use most fea- Msg ... ...
tures of the HSM implementation in a
evt : Event onEvent (Hsm*, Msg*) onEvent (Msg *)
concrete fashion. We briefly discuss
how the HSM pattern, when com- *
bined with RTOS facilities, can be
used to build a powerful real-time
framework. We conclude by drawing a
comparison between SOP and OOP
ConcreteHsm
and propose some modifications and 1
extensions to UML statecharts. We MsgWithData stateA
stateB
assume that the reader is familiar with dataA ...
basic concepts of state machines and dataB
...
UML notation.3,11
me->curr = me->next;
• Pressing the “set” button switches
the watch into setting mode. The
me->next = 0;
sequence of adjustments in this
}
mode is: hour, minute, day, month.
}
Adjustments are made by pressing
break; /* event processed */
the “mode” button, which incre-
}
ments the chosen quantity by one.
}
Pressing the “set” button while
}
adjusting month puts the watch
/* exit current states and all superstates up to LCA */
back into timekeeping mode
void HsmExit_(Hsm *me, unsigned char toLca) {
register State *s;
• While in setting mode the watch
ignores tick events
for (s = me->curr; toLca > 0; toLca, s = s->super)
StateOnEvent(s, me, &exitMsg);
• Upon return to timekeeping mode
the watch displays the most recent-
me->curr = s;
ly selected information, that is, if
}
date was selected prior to leaving
/* find # of levels to Least Common Ancestor */
timekeeping mode, the watch
unsigned char HsmToLCA_(Hsm *me, State *target) {
resumes displaying the date, other-
State *s, *t;
wise it displays the current time
unsigned char toLca = 1;
for (s = me->curr->super; s != 0; ++toLca, s = s->super)
A state diagram for the specifica-
for (t = target; t != 0; t = t->super)
tion given above is depicted in Figure
if (s == t)
3b and its partial implementation is
return toLca;
shown in Listing 3. We apply the HSM
return 0;
pattern according to the following
}
recipe:
EXIT_EVT, and START_EVT, respec- 8. Arrange to invoke Hsm::onEvent() of active objects exists in many model-
tively. Provide code for other events for each incoming event ing languages under different names:
using STATE_TRAN() macro for state active objects stereotype in UML9,
transitions. Remember to return 0 Real-time framework active instance in Schlaer-Mellor10,
(NULL pointer) if you handle the The HSM design pattern is particular- and actor in ROOM6. We use the term
event and the initial message point- ly suited for implementing behavior “actor” because it’s most compact.
er if you don’t associated with active objects (objects The typical structure of a frame-
7. Execute the initial start transition that are the roots of threads-of-control work based on active objects is shown
by invoking Hsm::onStart() in a multitasking system). The concept in Figure 4. The Actor class inherits
a b Null
top top
super
s2
s1
e1 s21 s1 s2
super super
e3 e4
e2 s22
s21 s22
super super
a b
top
timekeeping
setting
H
SET_EVT
time
SET_EVT SET_EVT
MODE_EVT
TICK_EVT SET_EVT
SET_EVT
and superstates and should not be and finalization is similar to entering of invocation of these methods is the
confused with inheritance of entire and exiting a state. In both cases spe- same: constructors are invoked start-
state models.3 cial methods are invoked: constructors ing from most remote ancestor classes
The analogy between SOP and and destructors for objects, entry and (destructors are invoked in reverse
OOP goes further. Class instantiation exit actions for states. Even the order order), and entry actions are invoked
starting from the topmost superstate
(exit actions are invoked in reverse
LISTING 2b, cont’d. hsm.cpp—C++ implementation order).
A final similarity between OOP
break; /* event processed */ and SOP lies in the way they are most
} efficiently implemented. Although
} polymorphism can be implemented
} in many ways, virtually all C++ com-
/* exit current states and all superstates up to LCA */ pilers implement it in the same way:
void Hsm::exit_(unsigned char toLca) { by using function pointers grouped
register State *s; into virtual tables. In view of the
for (s = curr; toLca > 0; toLca, s = s->super) deep analogy between SOP and OOP,
s->onEvent(this, &exitMsg); it is therefore not surprising that
curr = s; arguably the most efficient imple-
} mentation of HSMs is also based on
/* find # of levels to Least Common Ancestor */ function pointers grouped into
unsigned char Hsm::toLCA_(State *target) { states. These simple state objects
State *s, *t; define both behavior and hierarchy
unsigned char toLca = 1; but are not specific to any particular
for (s = curr->super; s != 0; ++toLca, s = s->super) instance of a state machine. The
for (t = target; t != 0; t = t->super) same holds for virtual functions,
if (s == t) which are characteristics of the
return toLca; whole class rather than specific to
return 0; any particular object instance. For
} this reason we observe that state
objects could (and probably should)
be placed in v-tables and be support-
ed as a native language feature.
FIGURE 4 Structure of real-time framework based on HSM
Improvments to UML?
UML state diagrams do not provide
void Actor: :run() {
onStart (); any graphical representation of state
for (;;) { reactions, which are reactions to
Msg *msg =–>mesgQ–>get();
Hsm onEvent(msg);
events not causing state transitions. In
} practice however, reactions are com-
} mon and sometimes the whole state
Actor void ActorRun(Actor *me) { hierarchy is most naturally designed to
msgQ HsmOnStart((Hsm *) me); reuse group reactions rather than
taskId for (;;) {
group transitions. In these cases a
Msg *msg = MsgQget(me–>msgQ);
run() HsmOnEvent((Hsm *)me, msg); UML state diagram cannot be proper-
} ly understood because it is simply
}
incomplete.
We propose to include reactions in
state diagrams as directed lines start-
ConcreteActorA ConcreteActorB
ing and finishing in the same state and
stateA stateA
stateB stateB
lying entirely within that state. (An
... ... example of this notation is shown in
Figure 3 for reactions to TICK_EVT and
MODE_EVT.) Please note that this nota-
LISTING 3a, cont’d. Simple watch HSM: C implementation STATE_TRAN() macro, which causes
execution of all exit-actions up to least
StateCtor(&me->minute, “minute”, &me->setting, common ancestor, the order of execu-
(EvtHndlr)Watch_minute); tion is left to the designer. The UML-
StateCtor(&me->day, “day”, &me->setting, compliant implementation requires
(EvtHndlr)Watch_day); invoking the STATE_TRAN() macro
StateCtor(&me->month, “month”, &me->setting, before other actions associated with the
(EvtHndlr)Watch_month); transition. With our approach, the
me->tsec = me->tmin = me->thour = 0; designer may choose a different order
me->dday = me->dmonth = 1; (for example, reaction-guard-transi-
me->timekeepingHist = NULL; tion), if it would simplify the problem
} at hand.
A final (proposed) extension to
void main() { /* test harness */ UML statecharts is associated with
Watch watch; event handling at different levels of
WatchCtor(&watch); a hierarchical state machine. UML
HsmOnStart((Hsm *)&watch); statecharts require that the same
for (;;) { event be presented for processing to
Msg *msg = getEvt(); /* block until event arrives */ all levels of the hierarchy, starting
HsmOnEvent((Hsm *)&watch, msg); with the active state. We propose to
} allow an event to change as it propa-
} gates upward through the state hier-
archy. In our implementation this is
simple to achieve. This feature
would facilitate “throwing” an excep-
LISTING 3b Simple watch HSM: C++ implementation tion to a higher scope, which could
in turn either handle or “throw” it
#include “hsm.h” again. Because outer states of an
HSM are typically behavioral gener-
class Watch : public Hsm { alizations of inner states, this tech-
int tsec, tmin, thour, dday, dmonth; nique for handling exceptions is nat-
timeTick(); /* process tick event in the ‘time’ state */ ural and arguably, makes more sense
protected: than the traditional exception han-
State timekeeping, time, date; dling technique of unwinding the
State setting, hour, minute, day, month; call stack.
State *tkeepingHist; /* to record history */
Msg const *topHndlr(Msg const *msg); The benefits
Msg const *timekeepingHndlr(Msg const *msg); The HSM design pattern allows hier-
Msg const *settingHndlr(Msg const *msg); archical state machines to be direct-
Msg const *hourHndlr(Msg const *msg); ly and efficiently implemented in C
/*... other state handler methods */ or C++ without code synthesizing
public: tools. The event-handler methods
Watch(); /* ctor for defining state hierarchy */ provide a concise textual representa-
}; tion of the state model and allow
high-level structure and low-level
enum WatchEvents { details to be accessed easily. The sim-
MODE_EVT, plicity of the event-handlers leads to
SET_EVT, “housekeeping” code3—portions of
TICK_EVT software that can be automatically
}; generated by tools—that is trivial to
write by hand.
The HSM pattern is flexible,
allowing even fundamental changes
Resources
1. Bell, Rodney, “Code Generation form
Object Models,” Embedded Systems
Programming, March 1998, p. 74.
2. Samek, Miro, “Portable Inheritance and
Polymorphism in C,” Embedded Systems
Programming, December 1997, p. 54.
3. Douglass, Bruce Powell. Doing Hard
Time. Reading MA: Addison-Wesley
Longman, 1999.
4. Douglass, Bruce Powell. Real-Time
UML: Developing Efficient Objects for
Embedded Systems. Reading, MA:
Addison-Wesley, 1998.
5. www.i-logix.com
6. Selic, Bran, Garth Gullekson, and Paul T.
Ward. Real-Time Object-Oriented
Modeling. New York City: John Wiley &
Sons Inc., 1994.
7. Gamma, Erich, Richard Helm, Ralph
Johnson, and John Vlissides. Design
Patterns: Elements of Reusable Object-
Oriented Software. Reading, MA:
Addison-Wesley, 1995.
8. www.omg.org/docs/97-96-02.pdf, UML
1.1 Alpha—Behavioral Elements: State
Machines.
9. Douglass, Bruce Powell and Srini Vason,
“Temporal Models in UML,” Dr. Dobbs
Journal, December 1999.
10. Levkoff, Bruce, “A Schlaer-Mellor
Architecture for Embedded Systems,”
Embedded Systems Programming,
November 1999, p. 88.
11. Douglass, Bruce Powell, “UML
Statecharts,” Embedded Systems
Programming, January 1999, p. 22.