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

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