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

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