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

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