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

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