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

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