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

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