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

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