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

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