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

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