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

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