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

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