source: trunk/FACT++/src/StateMachineImp.cc @ 10314

Last change on this file since 10314 was 10314, checked in by tbretz, 9 years ago
Added descriptions for the default states; added PrintListOfStates member function.
File size: 37.7 KB
Line 
1// **************************************************************************
2/** @class StateMachineImp
3
4 @brief Base class for a state machine implementation
5
6 \dot
7  digraph example {
8      node [shape=record, fontname=Helvetica, fontsize=10];
9      s [ label="Constructor" style="rounded"  color="red"   URL="\ref StateMachineImp::StateMachineImp"];
10      a [ label="State -3 (kSM_NotReady)"      color="red"   URL="\ref StateMachineImp::StateMachineImp"];
11      b [ label="State -2 (kSM_Initializing)"  color="red"   URL="\ref StateMachineImp::StateMachineImp"];
12      c [ label="State -1 (kSM_Configuring)"   color="red"   URL="\ref StateMachineImp::StateMachineImp"];
13      y [ label="State 0 (kSM_Ready)"                        URL="\ref StateMachineImp::Run"];
14      r [ label="User states (Running)" ];
15      e [ label="State 256 (kSM_Error)" ];
16      f [ label="State 65535 (kSM_FatalError)" color="red"   URL="\ref StateMachineImp::Run"];
17
18      // ---- manual means: command or program introduced ----
19
20      // Startup from Run() to Ready
21      s -> a [ arrowhead="open" color="red"  style="solid"  ]; // automatic (mandatory)
22      a -> b [ arrowhead="open" color="red"  style="solid"  ]; // automatic (mandatory)
23      b -> c [ arrowhead="open" color="red"  style="solid"  ]; // automatic (mandatory)
24
25      c -> y [ arrowhead="open" color="red"  style="solid" URL="\ref StateMachineImp::Run" ]; // prg: Run()
26
27      y -> c [ arrowhead="open" style="dashed" URL="\ref StateMachineDim::exitHandler" ]; // CMD: EXIT
28      r -> c [ arrowhead="open" style="dashed" URL="\ref StateMachineDim::exitHandler" ]; // CMD: EXIT
29      e -> c [ arrowhead="open" style="dashed" URL="\ref StateMachineDim::exitHandler" ]; // CMD: EXIT
30
31      e -> y [ arrowhead="open" color="red"  style="dashed" ]; // CMD: RESET (e.g.)
32
33      y -> e [ arrowhead="open" color="blue" style="solid"  ]; // prg
34      r -> e [ arrowhead="open" color="blue" style="solid"  ]; // prg
35
36      y -> r [ arrowhead="open" color="blue" style="dashed" ]; // CMD/PRG
37      r -> y [ arrowhead="open" color="blue" style="dashed" ]; // CMD/PRG
38
39      y -> f [ arrowhead="open" color="blue" style="solid"  ]; // prg
40      r -> f [ arrowhead="open" color="blue" style="solid"  ]; // prg
41      e -> f [ arrowhead="open" color="blue" style="solid"  ]; // prg
42  }
43  \enddot
44
45  - <B>Red box</B>: Internal states. Events which are received are
46    discarded.
47  - <B>Black box</B>: State machine running. Events are accepted and
48    processed according to the implemented functions Transition(),
49    Configuration() and Execute(). Events are accepted accoding to the
50    lookup table of allowed transitions.
51  - <B>Red solid arrow</B>: A transition initiated by the program itself.
52  - <b>Dashed arrows in general</b>: Transitions which can be initiated
53    by a dim-command or get inistiated by the program.
54  - <b>Solid arrows in general</b>: These transitions are always initiated by
55    the program.
56  - <B>Red dashed</B>: Suggested RESET event (should be implemented by
57    the derived class)
58  - <B>Black dashed arrow</B>: Exit from the main loop. This can either
59    happen by the Dim-provided EXIT-command or a call to StateMachineDim::Stop.
60  - <B>Black arrows</B>: Other events or transitions which can be
61    implemented by the derived class.
62  - <B>Dotted black arrow</B>: Exit from the main-loop which is initiated
63    by the program itself through StateMachineDim::Stop() and not by the
64    state machine itself (Execute(), Configure() and Transition())
65  - <b>Blue dashed arrows</b>: Transitions which happen either by receiving
66    a event or are initiated from the state machine itself
67    (by return values of (Execute(), Configure() and Transition())
68  - <b>Blue solid</b>: Transitions which cannot be initiated by dim
69    event but only by the state machine itself.
70  - From the program point of view the fatal error is identical with
71    the kSM_Configuring state, i.e. it is returned from the main-loop.
72    Usually this will result in program termination. However, depending
73    on the state the program might decide to use different cleaning
74    routines.
75
76@todo
77   - A proper and correct cleanup after an EXIT or Stop() is missing.
78     maybe we have to force a state 0 first?
79*/
80// **************************************************************************
81#include "StateMachineImp.h"
82
83#include "Time.h"
84#include "Event.h"
85
86#include "WindowLog.h"
87#include "Converter.h"
88
89#include "tools.h"
90
91using namespace std;
92
93// --------------------------------------------------------------------------
94//
95//! The state of the state machine (fCurrentState) is initialized with
96//! kSM_NotReady
97//!
98//! Default state names for kSM_NotReady, kSM_Ready, kSM_Error and
99//! kSM_FatalError are set via AddStateName.
100//!
101//! fExitRequested is set to 0, fRunning to false.
102//!
103//! Furthermore, the ostream is propagated to MessageImp, as well as
104//! stored in fOut.
105//!
106//! MessageImp is used for messages which are distributed (e.g. via DIM),
107//! fOut is used for messages which are only displayed on the local console.
108//!
109//! Subsequent, i.e. derived classes should setup all allowed state
110//! transitions as well as all allowed configuration event by
111//! AddTransition, AddConfiguration and AddStateName.
112//!
113//! @param out
114//!    A refrence to an ostream which allows to redirect the log-output
115//!    to something else than cout. The default is cout. The reference
116//!    is propagated to fLog
117//!
118//! @param name
119//!    The server name stored in fName
120//!
121//
122StateMachineImp::StateMachineImp(ostream &out, const std::string &name)
123    : MessageImp(out), fName(name), fCurrentState(kSM_NotReady),
124    fRunning(false), fExitRequested(0)
125{
126    AddStateName(kSM_NotReady,   "NotReady", "State machine not ready, events are ignored.");
127    AddStateName(kSM_Ready,      "Ready",    "State machine ready to receive events.");
128    AddStateName(kSM_Error,      "ERROR",    "Common error state.");
129    AddStateName(kSM_FatalError, "FATAL",    "A fatal error occured, the eventloop is stopped.");
130}
131
132// --------------------------------------------------------------------------
133//
134//! delete all object stored in fListOfEvent and in fEventQueue
135//
136StateMachineImp::~StateMachineImp()
137{
138    // For this to work EventImp must be the first class from which
139    // the object inherits
140    for (vector<EventImp*>::iterator cmd=fListOfEvents.begin(); cmd!=fListOfEvents.end(); cmd++)
141        delete *cmd;
142
143    // Unfortunately, front() doesn't necessarily return 0 if
144    // queue is empty
145    if (fEventQueue.size())
146        for (Event *q=fEventQueue.front(); q; fEventQueue.pop())
147            delete q;
148}
149
150// --------------------------------------------------------------------------
151//
152//! Puts the given event into the fifo. The fifo will take over ownership.
153//! Access to fEventQueue is encapsulated by fMutex.
154//!
155//! @param cmd
156//!    Pointer to an object of type Event to be stored in the fifo
157//!
158//! @todo
159//!    Can we also allow EventImp?
160//
161void StateMachineImp::PushEvent(Event *cmd)
162{
163    fMutex.lock();
164    fEventQueue.push(cmd);
165    fMutex.unlock();
166}
167
168// --------------------------------------------------------------------------
169//
170//! Get an event from the fifo. We will take over the owenership of the
171//! object. The pointer is deleted from the fifo. Access of fEventQueue
172//! is encapsulated by fMutex.
173//!
174//! @returns
175//!    A pointer to an Event object
176//
177Event *StateMachineImp::PopEvent()
178{
179    fMutex.lock();
180
181    // Get the next event from the stack
182    // and remove event from the stack
183    Event *cmd = fEventQueue.front();
184    fEventQueue.pop();
185
186    fMutex.unlock();
187
188    return cmd;
189}
190
191// --------------------------------------------------------------------------
192//
193//! With this function commands are posted to the event queue. The data
194//! is not given as binary data but as a string instead. It is converted
195//! according to the format of the corresponding event and an event
196//! is posted to the queue if successfull.
197//!
198//! @param lout
199//!    Stream to which output should be redirected
200//!    event should be for.
201//!
202//! @param str
203//!    Command with data, e.g. "COMMAND 1 2 3 4 5 test"
204//!
205//! @returns
206//!    false if no event was posted to the queue. If
207//!    PostEvent(EventImp&,const char*, size_t) was called return its
208//!    return value
209//
210bool StateMachineImp::PostEvent(ostream &lout, const string &str)
211{
212    // Find the delimiter between the command name and the data
213    size_t p0 = str.find_first_of(' ');
214    if (p0==string::npos)
215        p0 = str.length();
216
217    // Compile the command which will be sent to the state-machine
218    const string name = fName + "/" + str.substr(0, p0);
219
220    // Check if this command is existing at all
221    EventImp *evt = FindEvent(name);
222    if (!evt)
223    {
224        lout << kRed << "Unknown command '" << name << "'" << endl;
225        return false;
226    }
227
228    // Get the format of the event data
229    const string fmt = evt->GetFormat();
230
231    // Convert the user enetered data according to the format string
232    // into a data block which will be attached to the event
233    lout << kBlue << name;
234
235    const Converter conv(lout, fmt, false);
236    if (!conv)
237    {
238        lout << kRed << "Couldn't properly parse the format... ignored." << endl;
239        return false;
240    }
241
242    try
243    {
244        const vector<char> v = conv.GetVector(str.substr(p0));
245        return PostEvent(*evt, v.data(), v.size());
246    }
247    catch (const std::runtime_error &e)
248    {
249        lout << kRed << e.what() << endl;
250    }
251
252    return false;
253}
254
255// --------------------------------------------------------------------------
256//
257//! With this function commands are posted to the event queue. If the
258//! event loop has not yet been started with Run() the command is directly
259//! handled by HandleEvent.
260//!
261//! Events posted when the state machine is in a negative state or
262//! kSM_FatalError are ignored.
263//!
264//! A new event is created and its data contents initialized with the
265//! specified memory.
266//!
267//! @param evt
268//!    The event to be posted. The precise contents depend on what the
269//!    event should be for.
270//!
271//! @param ptr
272//!    pointer to the memory which should be attached to the event
273//!
274//! @param siz
275//!    size of the memory which should be attached to the event
276//!
277//! @todo
278//!    - Shell we check for the validity of a command at the current state, too?
279//!    - should we also get the output stream as an argument here?
280//
281bool StateMachineImp::PostEvent(const EventImp &evt, const char *ptr, size_t siz)
282{
283    if (GetCurrentState()<0 || GetCurrentState()==kSM_FatalError)
284    {
285        Out() << kYellow << "Event ignored." << endl;
286        return false;
287    }
288
289    if (IsRunning())
290        PushEvent(new Event(evt, ptr, siz));
291    else
292    {
293        // FIXME: Is this thread safe? (Yes, because the data is copied)
294        // But two handlers could be called at the same time. Do we
295        // need to lock the handlers?
296        const Event event(evt, ptr, siz);
297        HandleEvent(event);
298    }
299    return true;
300}
301
302// --------------------------------------------------------------------------
303//
304//! With this function commands are posted to the event queue. If the
305//! event loop has not yet been started with Run() the command is directly
306//! handled by HandleEvent.
307//!
308//! Events posted when the state machine is in a negative state or
309//! kSM_FatalError are ignored.
310//!
311//! @param evt
312//!    The event to be posted. The precise contents depend on what the
313//!    event should be for.
314//!
315//! @todo
316//!    - Shell we check for the validity of a command at the current state, too?
317//!    - should we also get the output stream as an argument here?
318//
319bool StateMachineImp::PostEvent(const EventImp &evt)
320{
321    if (GetCurrentState()<0 || GetCurrentState()==kSM_FatalError)
322    {
323        Out() << kYellow << "Event ignored." << endl;
324        return false;
325    }
326
327    if (IsRunning())
328        PushEvent(new Event(evt));
329    else
330    {
331        // FIXME: Is this thread safe? (Yes, because it is only used
332        // by Dim and this is thread safe) But two handlers could
333        // be called at the same time. Do we need to lock the handlers?
334        HandleEvent(evt);
335    }
336    return true;
337}
338
339const vector<string> StateMachineImp::GetEventNames() const
340{
341    vector<string> v;
342
343    const string &name = fName + "/";
344    const int     len  = name.length();
345
346    for (vector<EventImp*>::const_iterator i=fListOfEvents.begin();
347         i!=fListOfEvents.end(); i++)
348    {
349        const string evt = (*i)->GetName();
350
351        v.push_back(evt.substr(0, len)==name ? evt.substr(len) : evt);
352    }
353
354    return v;
355}
356
357// --------------------------------------------------------------------------
358//
359//! Call for each event in fListEvents its Print function with the given
360//! stream.
361//!
362//! @param out
363//!    ostream to which the output should be redirected
364//
365void StateMachineImp::PrintListOfEvents(std::ostream &out) const
366{
367    for (vector<EventImp*>::const_iterator c=fListOfEvents.begin(); c!=fListOfEvents.end(); c++)
368        (*c)->Print(out);
369}
370
371// --------------------------------------------------------------------------
372//
373//! Call PrintListOfEvents with fOut as the output stream
374//
375void StateMachineImp::PrintListOfEvents() const
376{
377    PrintListOfEvents(Out());
378}
379
380// --------------------------------------------------------------------------
381//
382//! Print a list of all states with descriptions.
383//!
384//! @param out
385//!    ostream to which the output should be redirected
386//
387void StateMachineImp::PrintListOfStates(std::ostream &out) const
388{
389    out << endl;
390    out << kBold << "List of available states:" << endl;
391    out << endl;
392    for (StateNames::const_iterator i=fStateNames.begin(); i!=fStateNames.end(); i++)
393        out << kBold << setw(5) << i->first << kReset << " - " << kBold << i->second.first << kReset << ": " << i->second.second << endl;
394    out << endl;
395}
396
397// --------------------------------------------------------------------------
398//
399//! Print a list of all states with descriptions.
400//
401void StateMachineImp::PrintListOfStates() const
402{
403    PrintListOfStates(Out());
404}
405
406// --------------------------------------------------------------------------
407//
408//! Check whether an event (same pointer!) is in fListOfEvents
409//!
410//! @returns
411//!    true if the event was found, false otherwise
412//
413bool StateMachineImp::HasEvent(const EventImp *cmd) const
414{
415    // Find the event from the list of commands and queue it
416    for (vector<EventImp*>::const_iterator c=fListOfEvents.begin(); c!=fListOfEvents.end(); c++)
417        if (*c == cmd)
418            return true;
419
420    return false;
421}
422
423// --------------------------------------------------------------------------
424//
425//! Check whether an event with the given name is found in fListOfEvents.
426//! Note that currently there is no mechanism which ensures that not two
427//! events have the same name.
428//!
429//! @returns
430//!    true if the event was found, false otherwise
431//
432EventImp *StateMachineImp::FindEvent(const std::string &evt) const
433{
434    // Find the command from the list of commands and queue it
435    for (vector<EventImp*>::const_iterator c=fListOfEvents.begin(); c!=fListOfEvents.end(); c++)
436        if (evt == (*c)->GetName())
437            return *c;
438
439    return 0;
440}
441
442// --------------------------------------------------------------------------
443//
444//! Returns a pointer to a newly allocated object of base EventImp.
445//! It is meant to be overloaded by derived classes to create their
446//! own kind of events.
447//!
448//! @param targetstate
449//!    Defines the target state of the new transition. If \b must be
450//!    greater or equal zero. A negative target state is used to flag
451//!    commands which do not initiate a state transition. If this is
452//!    desired use AddConfiguration instead.
453//!
454//! @param name
455//!    The command name which should initiate the transition. The DimCommand
456//!    will be constructed with the name given to the constructor and this
457//!    name, e.g. "DRIVE/CHANGE_STATE_TO_NEW_STATE"
458//!
459//! @param fmt
460//!    A format as defined by the dim system can be given for the command.
461//!    However, it has no real meaning except that it is stored within the
462//!    DimCommand object. However, the user must make sure that the data of
463//!    received commands is properly extracted. No check is done.
464//
465EventImp *StateMachineImp::CreateEvent(int targetstate, const char *, const char *)
466{
467    return new EventImp(targetstate);
468}
469
470// --------------------------------------------------------------------------
471//
472//! Calling this function a new allowed transition is added to the state
473//! machine. Via a call to CreateEvent a new event is created with the
474//! given targetstate, name and format.
475//!
476//! The allowed states are passed to the new event and a message
477//! is written to the output-stream.
478//!
479//! @param targetstate
480//!    Defines the target state of the new transition. If \b must be
481//!    greater or equal zero. A negative target state is used to flag
482//!    commands which do not initiate a state transition. If this is
483//!    desired use AddConfiguration instead.
484//!
485//! @param name
486//!    The command name which should initiate the transition. The DimCommand
487//!    will be constructed with the name given to the constructor and this
488//!    name, e.g. "DRIVE/CHANGE_STATE_TO_NEW_STATE"
489//!
490//! @param states
491//!    A comma sepeareted list of ints, e.g. "1, 4, 5, 9" with states
492//!    in which this new state transition is allowed and will be accepted.
493//!
494//! @param fmt
495//!    A format as defined by the dim system can be given for the command.
496//!    However, it has no real meaning except that it is stored within the
497//!    DimCommand object. However, the user must make sure that the data of
498//!    received commands is properly extracted. No check is done.
499//
500EventImp &StateMachineImp::AddTransition(int targetstate, const char *name, const char *states, const char *fmt)
501{
502    EventImp *evt = CreateEvent(targetstate, name, fmt);
503
504    evt->AddAllowedStates(states);
505
506    Out() << ":   " << Time().GetAsStr() << " - Adding command " << evt->GetName();
507    Out() << " (transition to " << GetStateDescription(evt->GetTargetState()) << ")" << endl;
508
509    fListOfEvents.push_back(evt);
510
511    return *evt;
512}
513
514// --------------------------------------------------------------------------
515//
516//! Calling this function a new allowed transition is added to the state
517//! machine. Therefore an instance of type DimEvent is created and added
518//! to the list of available commands fListOfEvents.
519//!
520//! @param targetstate
521//!    Defines the target state of the new transition. If \b must be
522//!    greater or equal zero. A negative target state is used to flag
523//!    commands which do not initiate a state transition. If this is
524//!    desired use AddConfiguration instead.
525//!
526//! @param name
527//!    The command name which should initiate the transition. The DimCommand
528//!    will be constructed with the name given to the constructor and this
529//!    name, e.g. "DRIVE/CHANGE_STATE_TO_NEW_STATE"
530//!
531//! @param s1, s2, s3, s4, s5
532//!    A list of states from which a transition to targetstate is allowed
533//!    by this command.
534//
535EventImp &StateMachineImp::AddTransition(int targetstate, const char *name, int s1, int s2, int s3, int s4, int s5)
536{
537    return AddTransition(targetstate, name, Form("%d %d %d %d %d", s1, s2, s3, s4, s5).c_str(), "");
538}
539
540// --------------------------------------------------------------------------
541//
542//! Calling this function a new allowed transition is added to the state
543//! machine. Therefore an instance of type DimEvent is created and added
544//! to the list of available commands fListOfEvents.
545//!
546//! @param targetstate
547//!    Defines the target state of the new transition. If \b must be
548//!    greater or equal zero. A negative target state is used to flag
549//!    commands which do not initiate a state transition. If this is
550//!    desired use AddConfiguration instead.
551//!
552//! @param name
553//!    The command name which should initiate the transition. The DimCommand
554//!    will be constructed with the name given to the constructor and this
555//!    name, e.g. "DRIVE/CHANGE_STATE_TO_NEW_STATE"
556//!
557//! @param fmt
558//!    A format as defined by the dim system can be given for the command.
559//!    However, it has no real meaning except that it is stored within the
560//!    DimCommand object. However, the user must make sure that the data of
561//!    received commands is properly extracted. No check is done.
562//!
563//! @param s1, s2, s3, s4, s5
564//!    A list of states from which a transition to targetstate is allowed
565//!    by this command.
566//
567EventImp &StateMachineImp::AddTransition(int targetstate, const char *name, const char *fmt, int s1, int s2, int s3, int s4, int s5)
568{
569    return AddTransition(targetstate, name, Form("%d %d %d %d %d", s1, s2, s3, s4, s5).c_str(), fmt);
570}
571
572// --------------------------------------------------------------------------
573//
574//! This function calls AddTransition with a target-state of -1 which means
575//! that the command will not change the state at all. This shell be used
576//! for configuration commands. As well as in AddTransition the states in
577//! which such a configuration command is accepted can be given.
578//!
579//! @param name
580//!    The command name which should initiate the transition. The DimCommand
581//!    will be constructed with the name given to the constructor and this
582//!    name, e.g. "DRIVE/CHANGE_STATE_TO_NEW_STATE"
583//!
584//! @param states
585//!    A comma sepeareted list of ints, e.g. "1, 4, 5, 9" with states
586//!    in which this new state transition is allowed and will be accepted.
587//!
588//! @param fmt
589//!    A format as defined by the dim system can be given for the command.
590//!    However, it has no real meaning except that it is stored within the
591//!    DimCommand object. However, the user must make sure that the data of
592//!    received commands is properly extracted. No check is done.
593//!
594EventImp &StateMachineImp::AddConfiguration(const char *name, const char *states, const char *fmt)
595{
596    return AddTransition(-1, name, states, fmt);
597}
598
599// --------------------------------------------------------------------------
600//
601//! This function calls AddTransition with a target-state of -1 which means
602//! that the command will not change the state at all. This shell be used
603//! for configuration commands. As well as in AddTransition the states in
604//! which such a configuration command is accepted can be given.
605//!
606//! @param name
607//!    The command name which should initiate the transition. The DimCommand
608//!    will be constructed with the name given to the constructor and this
609//!    name, e.g. "DRIVE/CHANGE_STATE_TO_NEW_STATE"
610//!
611//! @param s1, s2, s3, s4, s5
612//!    A list of states from which a transition to targetstate is allowed
613//!    by this command.
614//
615EventImp &StateMachineImp::AddConfiguration(const char *name, int s1, int s2, int s3, int s4, int s5)
616{
617    return AddTransition(-1, name, s1, s2, s3, s4, s5);
618}
619
620// --------------------------------------------------------------------------
621//
622//! This function calls AddTransition with a target-state of -1 which means
623//! that the command will not change the state at all. This shell be used
624//! for configuration commands. As well as in AddTransition the states in
625//! which such a configuration command is accepted can be given.
626//!
627//! @param name
628//!    The command name which should initiate the transition. The DimCommand
629//!    will be constructed with the name given to the constructor and this
630//!    name, e.g. "DRIVE/CHANGE_STATE_TO_NEW_STATE"
631//!
632//! @param fmt
633//!    A format as defined by the dim system can be given for the command.
634//!    However, it has no real meaning except that it is stored within the
635//!    DimCommand object. However, the user must make sure that the data of
636//!    received commands is properly extracted. No check is done.
637//!
638//! @param s1, s2, s3, s4, s5
639//!    A list of states from which a transition to targetstate is allowed
640//!    by this command.
641//
642EventImp &StateMachineImp::AddConfiguration(const char *name, const char *fmt, int s1, int s2, int s3, int s4, int s5)
643{
644    return AddTransition(-1, name, fmt, s1, s2, s3, s4, s5);
645}
646
647// --------------------------------------------------------------------------
648//
649//! To be able to name states, i.e. present the current state in human
650//! readable for to the user, a string can be assigned to each state.
651//! For each state this function can be called only once, i.e. state name
652//! cannot be overwritten. Negative states are ignore.d
653//!
654//! @param state
655//!    Number of the state to which a name should be assigned
656//!
657//! @param name
658//!    A name which should be assigned to the state, e.g. "Tracking"
659//!
660//! @todo  FIX THE DOCU
661//
662void StateMachineImp::AddStateName(const int state, const std::string &name, const std::string &doc)
663{
664    if (fStateNames[state].first.empty())
665        fStateNames[state] = make_pair(name, doc);
666}
667
668// --------------------------------------------------------------------------
669//
670//! @param state
671//!    The state for which the name should be returned.
672//!
673//! @returns
674//!    The state name as stored in fStateNames is returned, corresponding
675//!    to the state given. If no name exists the number is returned
676//!    as string.
677//!
678const string StateMachineImp::GetStateName(int state) const
679{
680    const StateNames::const_iterator i = fStateNames.find(state);
681    return i==fStateNames.end() || i->second.first.empty() ? Form("%d", state) : i->second.first;
682}
683
684// --------------------------------------------------------------------------
685//
686//! @param state
687//!    The state for which the name should be returned.
688//!
689//! @returns
690//!    The description of a state name as stored in fStateNames is returned,
691//!    corresponding to the state given. If no name exists an empty string is
692//!    returned.
693//!
694const string StateMachineImp::GetStateDesc(int state) const
695{
696    const StateNames::const_iterator i = fStateNames.find(state);
697    return i==fStateNames.end() ? "" : i->second.second;
698}
699
700// --------------------------------------------------------------------------
701//
702//! This functions works in analogy to GetStateName, but the state number
703//! is added in []-parenthesis after the state name if it is available.
704//!
705//! @param state
706//!    The state for which the name should be returned.
707//!
708//! @returns
709//!    The state name as stored in fStateName is returned corresponding
710//!    to the state given plus the state number added in []-parenthesis.
711//!    If no name exists the number is returned as string.
712//!
713//
714const string StateMachineImp::GetStateDescription(int state) const
715{
716    const string &str = GetStateName(state);
717    if (str==Form("%d", state))
718        return str;
719    return str.empty() ? Form("%d", state) : (str+Form("[%d]", state));
720}
721
722// --------------------------------------------------------------------------
723//
724//! This function is a helpter function to do all the corresponding action
725//! if the state machine decides to change its state.
726//!
727//! If state is equal to the current state (fCurrentState) nothing is done.
728//! Then the service STATE (fSrcState) is updated with the new state
729//! and the text message and updateService() is called to distribute
730//! the update to all clients.
731//!
732//! In addition a log message is created and set via UpdateMsg.
733//!
734//! @param state
735//!    The new state which should be applied
736//!
737//! @param txt
738//!    A text corresponding to the state change which is distributed
739//!    together with the state itself for convinience.
740//!
741//! @param cmd
742//!    This argument can be used to give an additional name of the function
743//!    which is reponsible for the state change. It will be included in the
744//!    message
745//!
746//! @return
747//!    return the new state which was set or -1 in case of no change
748//
749string StateMachineImp::SetCurrentState(int state, const char *txt, const std::string &cmd)
750{
751    if (state==fCurrentState)
752    {
753        Out() << " -- " << Time().GetAsStr() << ": State " << GetStateDescription(state) << " already set... ";
754        if (!cmd.empty())
755            Out() << "'" << cmd << "' ignored.";
756        Out() << endl;
757        return "";
758    }
759
760    const int old = fCurrentState;
761
762    const string nold = GetStateDescription(old);
763    const string nnew = GetStateDescription(state);
764
765    string msg = nnew + " " + txt;
766    if (!cmd.empty())
767        msg += " (" + cmd + ")";
768
769    fCurrentState = state;
770
771    // State might have changed already again...
772    // Not very likely, but possible. That's why state is used
773    // instead of fCurrentState.
774
775    stringstream str;
776    str << "State Transition from " << nold << " to " << nnew << " (" << txt;
777    if (!cmd.empty())
778        str << ": " << cmd;
779    str << ")";
780    Message(str);
781
782    return msg;
783}
784
785// --------------------------------------------------------------------------
786//
787//! This function handles a new state issued by one of the event handlers.
788//!
789//! @param newstate
790//!    A possible new state
791//!
792//! @param evt
793//!    A pointer to the event which was responsible for the state change,
794//!    NULL if no event was responsible.
795//!
796//! @param txt
797//!    Text which is issued if the current state has changed and the new
798//!    state is identical to the target state as stored in the event
799//!    reference, or when no alternative text was given, or the pointer to
800//!    evt is NULL.
801//!
802//! @param alt
803//!    An alternative text which is issues when the newstate of a state change
804//!    doesn't match the expected target state.
805//!
806//! @returns
807//!    false if newstate is kSM_FatalError, true otherwise
808//
809bool StateMachineImp::HandleNewState(int newstate, const EventImp *evt,
810                                     const char *txt, const char *alt)
811{
812    if (newstate==kSM_FatalError)
813        return false;
814
815    if (newstate==fCurrentState)
816        return true;
817
818    if (!evt || !alt || newstate==evt->GetTargetState())
819        SetCurrentState(newstate, txt, evt ? evt->GetName() : "");
820    else
821        SetCurrentState(newstate, alt, evt->GetName());
822
823    return true;
824}
825
826// --------------------------------------------------------------------------
827//
828//! This is the event handler. Depending on the type of event it calles
829//! the function associated with the evenet, the Transition() or
830//! Configure() function.
831//!
832//! It first checks if the given even is valid in the current state. If
833//! it is not valid the function returns with true.
834//!
835//! If it is valid, it is checked whether a function is associated with
836//! the event. If this is the case, evt.Exec() is called and HandleNewState
837//! called with its return value.
838//!
839//! If the event's target state is negative the Configure() function is
840//! called with the event as argument and HandleNewState with its
841//! returned new state.
842//!
843//! If the event's target state is 0 or positive the Transition() function is
844//! called with the event as argument and HandleNewState with its
845//! returned new state.
846//!
847//! In all three cases the return value of HandleNewState is returned.
848//!
849//! Any of the three commands should usually return the current state
850//! or (in case of the Transition() command) return the new state. However,
851//! all three command can issue a state change by returning a new state.
852//! However, this will just change the internal state. Any action which
853//! is connected with the state change must have been executed already.
854//!
855//! @param evt
856//!    a reference to the event which should be handled
857//!
858//! @returns
859//!    false in case one of the commands changed teh state to kSM_FataError,
860//!    true otherwise
861//
862bool StateMachineImp::HandleEvent(const EventImp &evt)
863{
864    // Get the new state from the command
865    const int commandstate = evt.GetTargetState();
866
867    // Check if the received command is allow in the current state
868    if (!evt.IsStateAllowed(fCurrentState))
869    {
870        stringstream msg;
871        msg << evt.GetName() << ": Not allowed in state ";
872        msg << GetStateDescription() << "... ignored.";
873        Warn(msg);
874        return true;
875    }
876
877    if (evt.HasFunc())
878        return HandleNewState(evt.ExecFunc(), &evt,
879                              "by ExecFunc function-call");
880
881    // Check if this is a configuration command (a command which
882    // intention is not to change the state of our state-machine
883    if (commandstate<0)
884        return HandleNewState(Configure(evt), &evt, "by Configure-command");
885    else
886        return HandleNewState(Transition(evt), &evt,
887                              "by Transition-command (expected)",
888                              "by Transition-command (unexpected)");
889
890    // This is a fatal error, because it can never happen
891    return false;
892}
893
894// --------------------------------------------------------------------------
895//
896//! This is the main loop, or what could be called the running state
897//! machine. The flow diagram below shows what the loop is actually doing.
898//! It's main purpose is to serialize command excecution and the main
899//! loop in the state machine (e.g. the tracking loop)
900//!
901//! Leaving the loop can be forced by setting fExitRequested to another
902//! value than zero. This is done automatically if dim's EXIT command
903//! is received or can be forced by calling Stop().
904//!
905//! As long as no new command arrives the Execute() command is called
906//! continously. This should implement the current action which
907//! should be performed in the current state, e.g. calculating a
908//! new command value and sending it to the hardware.
909//!
910//! If a command is received it is put into the fifo by the commandHandler().
911//! The main loop now checks the fifo. If commands are in the fifo, it is
912//! checked whether the command is valid ithin this state or not. If it is
913//! not valid it is ignored. If it is valid the corresponding action
914//! is performed. This can either be a call to Configure() (when no state
915//! change is connected to the command) or Transition() (if the command
916//! involves a state change).
917//! In both cases areference to the received command (Command) is
918//! passed to the function. Note that after the functions have finished
919//! the command will go out of scope and be deleted.
920//!
921//! None of the commands should take to long for execution. Otherwise the
922//! response time of the main loop will become too slow.
923//!
924//! Any of the three commands should usually return the current state
925//! or (in case of the Transition() command) return the new state. However,
926//! all three command can issue a state change by returning a new state.
927//! However, this will just change the internal state. Any action which
928//! is connected with the state change must have been executed already.
929//!
930//!
931//!
932//!  \dot
933//!   digraph Run {
934//!       node  [ shape=record, fontname=Helvetica, fontsize=10 ];
935//!       edge  [ labelfontname=Helvetica, labelfontsize=8 ];
936//!       start0 [ label="Run()" style="rounded"];
937//!       start1 [ label="fExitRequested=0\nfRunning=true\nSetCurrentState(kSM_Ready)"];
938//!       cond1  [ label="Is fExitRequested==0?"];
939//!       exec   [ label="HandleNewState(Execute())"];
940//!       fifo   [ label="Any event in FIFO?"];
941//!       get    [ label="Get event from FIFO\n Is event allowed within the current state?" ];
942//!       handle [ label="HandleEvent()" ];
943//!       exit1  [ label="fRunning=false\nSetCurrentState(kSM_FatalError)\n return -1" style="rounded"];
944//!       exit2  [ label="fRunning=false\nSetCurrentState(kSM_NotReady)\n return fExitRequested-1" style="rounded"];
945//!
946//!       start0   -> start1   [ weight=8 ];
947//!       start1   -> cond1    [ weight=8 ];
948//!
949//!       cond1:e  -> exit2:n  [ taillabel="true"  ];
950//!       cond1    -> exec     [ taillabel="false"  weight=8 ];
951//!
952//!       exec     -> fifo     [ taillabel="true"   weight=8 ];
953//!       exec:e   -> exit1:e  [ taillabel="false" ];
954//!
955//!       fifo     -> cond1    [ taillabel="false" ];
956//!       fifo     -> get      [ taillabel="true"   weight=8 ];
957//!
958//!       get      -> handle   [ taillabel="true"  ];
959//!
960//!       handle:s -> exit1:n  [ taillabel="false"  weight=8 ];
961//!       handle   -> cond1    [ taillabel="true"  ];
962//!   }
963//!   \enddot
964//!
965//! @param dummy
966//!    If this parameter is set to treu then no action is executed
967//!    and now events are dispatched from the event list. It is usefull
968//!    if functions are assigned directly to any event to simulate
969//!    a running loop (e.g. block until Stop() was called or fExitRequested
970//!    was set by an EXIT command.
971//!
972//! @returns
973//!    In the case of a a fatal error -1 is returned, fExitRequested-1 in all
974//!    other cases (This corresponds to the exit code either received by the
975//!    EXIT event or given to the Stop() function)
976//!
977//! @todo  Fix docu (kSM_SetReady, HandleEvent)
978//
979int StateMachineImp::Run(bool dummy)
980{
981    if (fCurrentState>=kSM_Ready)
982    {
983        Error("Run() can only be called in the NotReady state.");
984        return -1;
985    }
986
987    fRunning       = true;
988    fExitRequested = 0;
989
990    SetCurrentState(kSM_Ready, "by constructor");
991
992    while (!fExitRequested)
993    {
994        usleep(1);
995        if (dummy)
996            continue;
997
998        // Execute a step in the current state of the state machine
999        if (!HandleNewState(Execute(), "by Execute-command"))
1000            break;
1001
1002        // If the command stack is empty go on with processing in the
1003        // current state
1004        if (IsQueueEmpty())
1005            continue;
1006
1007        // Pop the next command which arrived from the stack
1008        const auto_ptr<Event> cmd(PopEvent());
1009
1010        if (!HandleEvent(*cmd))
1011            break;
1012    }
1013
1014    fRunning = false;
1015
1016    if (!fExitRequested)
1017    {
1018        Fatal("Fatal Error occured... shutting down.");
1019        return -1;
1020    }
1021
1022    SetCurrentState(kSM_NotReady, "due to return from Run().");
1023
1024    return fExitRequested-1;
1025}
1026
1027// --------------------------------------------------------------------------
1028//
1029//! This function can be called to stop the loop of a running state machine.
1030//! Run() will then return with a return value corresponding to the value
1031//! given as argument.
1032//!
1033//! Note that this is a dangerous operation, because as soon as one of the
1034//! three state machine commands returns (Execute(), Configure() and
1035//! Transition()) the loop will be left and Run(9 will return. The program
1036//! is then responsible of correctly cleaning up the mess which might be left
1037//! behind.
1038//!
1039//! @param code
1040//!    int with which Run() should return when returning.
1041//
1042void StateMachineImp::Stop(int code)
1043{
1044    fExitRequested = code+1;
1045}
Note: See TracBrowser for help on using the repository browser.