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

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