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

Last change on this file was 16871, checked in by tbretz, 8 years ago
Use a to_string instead of an ostringstream for conversion
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 return i==fStateNames.end() || i->second.first.empty() ? to_string(state) : i->second.first;
688}
689
690// --------------------------------------------------------------------------
691//
692//! @param state
693//! The state for which should be checked
694//!
695//! @returns
696//! true if a nam for this state already exists, false otherwise
697//!
698bool StateMachineImp::HasState(int state) const
699{
700 return fStateNames.find(state) != fStateNames.end();
701}
702
703// --------------------------------------------------------------------------
704//
705//! @param state
706//! The state for which the name should be returned.
707//!
708//! @returns
709//! The description of a state name as stored in fStateNames is returned,
710//! corresponding to the state given. If no name exists an empty string is
711//! returned.
712//!
713const string StateMachineImp::GetStateDesc(int state) const
714{
715 const StateNames::const_iterator i = fStateNames.find(state);
716 return i==fStateNames.end() ? "" : i->second.second;
717}
718
719// --------------------------------------------------------------------------
720//
721//! This functions works in analogy to GetStateName, but the state number
722//! is added in []-parenthesis after the state name if it is available.
723//!
724//! @param state
725//! The state for which the name should be returned.
726//!
727//! @returns
728//! The state name as stored in fStateName is returned corresponding
729//! to the state given plus the state number added in []-parenthesis.
730//! If no name exists the number is returned as string.
731//!
732//
733const string StateMachineImp::GetStateDescription(int state) const
734{
735 const string &str = GetStateName(state);
736
737 ostringstream s;
738 s << state;
739 if (str==s.str())
740 return str;
741
742 return str.empty() ? s.str() : (str+'['+s.str()+']');
743}
744
745// --------------------------------------------------------------------------
746//
747//! This function is a helpter function to do all the corresponding action
748//! if the state machine decides to change its state.
749//!
750//! If state is equal to the current state (fCurrentState) nothing is done.
751//! Then the service STATE (fSrcState) is updated with the new state
752//! and the text message and updateService() is called to distribute
753//! the update to all clients.
754//!
755//! In addition a log message is created and set via UpdateMsg.
756//!
757//! @param state
758//! The new state which should be applied
759//!
760//! @param txt
761//! A text corresponding to the state change which is distributed
762//! together with the state itself for convinience.
763//!
764//! @param cmd
765//! This argument can be used to give an additional name of the function
766//! which is reponsible for the state change. It will be included in the
767//! message
768//!
769//! @return
770//! return the new state which was set or -1 in case of no change
771//
772string StateMachineImp::SetCurrentState(int state, const char *txt, const std::string &cmd)
773{
774 if (state==fCurrentState)
775 {
776 Out() << " -- " << Time().GetAsStr("%H:%M:%S.%f") << " - State " << GetStateDescription(state) << " already set... ";
777 if (!cmd.empty())
778 Out() << "'" << cmd << "' ignored.";
779 Out() << endl;
780 return "";
781 }
782
783 const int old = fCurrentState;
784
785 const string nold = GetStateDescription(old);
786 const string nnew = GetStateDescription(state);
787
788 string msg = nnew + " " + txt;
789 if (!cmd.empty())
790 msg += " (" + cmd + ")";
791
792 fCurrentState = state;
793
794 // State might have changed already again...
795 // Not very likely, but possible. That's why state is used
796 // instead of fCurrentState.
797
798 ostringstream str;
799 str << "State Transition from " << nold << " to " << nnew << " (" << txt;
800 if (!cmd.empty())
801 str << ": " << cmd;
802 str << ")";
803 Message(str);
804
805 return msg;
806}
807
808// --------------------------------------------------------------------------
809//
810//! This function handles a new state issued by one of the event handlers.
811//!
812//! @param newstate
813//! A possible new state
814//!
815//! @param evt
816//! A pointer to the event which was responsible for the state change,
817//! NULL if no event was responsible.
818//!
819//! @param txt
820//! Text which is issued if the current state has changed and the new
821//! state is identical to the target state as stored in the event
822//! reference, or when no alternative text was given, or the pointer to
823//! evt is NULL.
824//!
825//! @param alt
826//! An alternative text which is issues when the newstate of a state change
827//! doesn't match the expected target state.
828//!
829//! @returns
830//! false if newstate is kSM_FatalError, true otherwise
831//
832bool StateMachineImp::HandleNewState(int newstate, const EventImp *evt,
833 const char *txt)
834{
835 if (newstate==kSM_FatalError)
836 return false;
837
838 if (newstate==fCurrentState || newstate==kSM_KeepState)
839 return true;
840
841 SetCurrentState(newstate, txt, evt ? evt->GetName() : "");
842
843 return true;
844}
845
846// --------------------------------------------------------------------------
847//
848//! This is the event handler. Depending on the type of event it calles
849//! the function associated with the event, the Transition() or
850//! Configure() function.
851//!
852//! It first checks if the given even is valid in the current state. If
853//! it is not valid the function returns with true.
854//!
855//! If it is valid, it is checked whether a function is associated with
856//! the event. If this is the case, evt.Exec() is called and HandleNewState
857//! called with its return value.
858//!
859//! If the event's target state is negative (unnamed Event) the Configure()
860//! function is called with the event as argument and HandleNewState with
861//! its returned new state.
862//!
863//! If the event's target state is 0 or positive (named Event) the
864//! Transition() function is called with the event as argument and
865//! HandleNewState with its returned new state.
866//!
867//! In all three cases the return value of HandleNewState is returned.
868//!
869//! Any of the three commands should usually return the current state
870//! or (in case of the Transition() command) return the new state. However,
871//! all three command can issue a state change by returning a new state.
872//! However, this will just change the internal state. Any action which
873//! is connected with the state change must have been executed already.
874//!
875//! @param evt
876//! a reference to the event which should be handled
877//!
878//! @returns
879//! false in case one of the commands changed the state to kSM_FataError,
880//! true otherwise
881//
882bool StateMachineImp::HandleEvent(const EventImp &evt)
883{
884 if (!evt.HasFunc())
885 {
886 Warn(evt.GetName()+": No function assigned... ignored.");
887 return true;
888
889 }
890
891#ifdef DEBUG
892 ostringstream out;
893 out << "Handle: " << evt.GetName() << "[" << evt.GetSize() << "]";
894 Debug(out);
895#endif
896
897 // Check if the received command is allow in the current state
898 if (!evt.IsStateAllowed(fCurrentState))
899 {
900 Warn(evt.GetName()+": Not allowed in state "+GetStateDescription()+"... ignored.");
901 return true;
902 }
903
904 return HandleNewState(evt.ExecFunc(), &evt,
905 "by ExecFunc function-call");
906}
907
908// --------------------------------------------------------------------------
909//
910//! This is the main loop, or what could be called the running state
911//! machine. The flow diagram below shows what the loop is actually doing.
912//! It's main purpose is to serialize command excecution and the main
913//! loop in the state machine (e.g. the tracking loop)
914//!
915//! Leaving the loop can be forced by setting fExitRequested to another
916//! value than zero. This is done automatically if dim's EXIT command
917//! is received or can be forced by calling Stop().
918//!
919//! As long as no new command arrives the Execute() command is called
920//! continously. This should implement the current action which
921//! should be performed in the current state, e.g. calculating a
922//! new command value and sending it to the hardware.
923//!
924//! If a command is received it is put into the fifo by the commandHandler().
925//! The main loop now checks the fifo. If commands are in the fifo, it is
926//! checked whether the command is valid ithin this state or not. If it is
927//! not valid it is ignored. If it is valid the corresponding action
928//! is performed. This can either be a call to Configure() (when no state
929//! change is connected to the command) or Transition() (if the command
930//! involves a state change).
931//! In both cases areference to the received command (Command) is
932//! passed to the function. Note that after the functions have finished
933//! the command will go out of scope and be deleted.
934//!
935//! None of the commands should take to long for execution. Otherwise the
936//! response time of the main loop will become too slow.
937//!
938//! Any of the three commands should usually return the current state
939//! or (in case of the Transition() command) return the new state. However,
940//! all three command can issue a state change by returning a new state.
941//! However, this will just change the internal state. Any action which
942//! is connected with the state change must have been executed already.
943//!
944//!
945//!
946//! \dot
947//! digraph Run {
948//! node [ shape=record, fontname=Helvetica, fontsize=10 ];
949//! edge [ labelfontname=Helvetica, labelfontsize=8 ];
950//! start0 [ label="Run()" style="rounded"];
951//! start1 [ label="fExitRequested=0\nfRunning=true\nSetCurrentState(kSM_Ready)"];
952//! cond1 [ label="Is fExitRequested==0?"];
953//! exec [ label="HandleNewState(Execute())"];
954//! fifo [ label="Any event in FIFO?"];
955//! get [ label="Get event from FIFO\n Is event allowed within the current state?" ];
956//! handle [ label="HandleEvent()" ];
957//! exit1 [ label="fRunning=false\nSetCurrentState(kSM_FatalError)\n return -1" style="rounded"];
958//! exit2 [ label="fRunning=false\nSetCurrentState(kSM_NotReady)\n return fExitRequested-1" style="rounded"];
959//!
960//! start0 -> start1 [ weight=8 ];
961//! start1 -> cond1 [ weight=8 ];
962//!
963//! cond1:e -> exit2:n [ taillabel="true" ];
964//! cond1 -> exec [ taillabel="false" weight=8 ];
965//!
966//! exec -> fifo [ taillabel="true" weight=8 ];
967//! exec:e -> exit1:e [ taillabel="false" ];
968//!
969//! fifo -> cond1 [ taillabel="false" ];
970//! fifo -> get [ taillabel="true" weight=8 ];
971//!
972//! get -> handle [ taillabel="true" ];
973//!
974//! handle:s -> exit1:n [ taillabel="false" weight=8 ];
975//! handle -> cond1 [ taillabel="true" ];
976//! }
977//! \enddot
978//!
979//! @param dummy
980//! If this parameter is set to treu then no action is executed
981//! and now events are dispatched from the event list. It is usefull
982//! if functions are assigned directly to any event to simulate
983//! a running loop (e.g. block until Stop() was called or fExitRequested
984//! was set by an EXIT command. If dummy==true, fRunning is not set
985//! to true to allow handling events directly from the event handler.
986//!
987//! @returns
988//! In the case of a a fatal error -1 is returned, fExitRequested-1 in all
989//! other cases (This corresponds to the exit code either received by the
990//! EXIT event or given to the Stop() function)
991//!
992//! @todo Fix docu (kSM_SetReady, HandleEvent)
993//
994int StateMachineImp::Run(bool dummy)
995{
996 if (fCurrentState>=kSM_Ready)
997 {
998 Error("Run() can only be called in the NotReady state.");
999 return -1;
1000 }
1001
1002 if (!fExitRequested)
1003 {
1004 fRunning = !dummy;
1005
1006 SetCurrentState(kSM_Ready, "by Run()");
1007
1008 std::unique_lock<std::mutex> lock(fMutex);
1009 fMutex.unlock();
1010
1011 while (1)
1012 {
1013 fMutex.lock();
1014 if (IsQueueEmpty())
1015 fCond.wait_for(lock, chrono::microseconds(10000));
1016 fMutex.unlock();
1017
1018 if (fExitRequested)
1019 break;
1020
1021 if (dummy)
1022 continue;
1023
1024 // If the command stack is empty go on with processing in the
1025 // current state
1026 if (!IsQueueEmpty())
1027 {
1028 // Pop the next command which arrived from the stack
1029 const shared_ptr<Event> cmd(PopEvent());
1030 if (!HandleEvent(*cmd))
1031 break;
1032 }
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
1039 fRunning = false;
1040
1041 if (!fExitRequested)
1042 {
1043 Fatal("Fatal Error occured... shutting down.");
1044 return -1;
1045 }
1046
1047 SetCurrentState(kSM_NotReady, "due to return from Run().");
1048 }
1049
1050 const int exitcode = fExitRequested-1;
1051
1052 // Prepare for next call
1053 fExitRequested = 0;
1054
1055 return exitcode;
1056}
1057
1058// --------------------------------------------------------------------------
1059//
1060//! This function can be called to stop the loop of a running state machine.
1061//! Run() will then return with a return value corresponding to the value
1062//! given as argument.
1063//!
1064//! Note that this is a dangerous operation, because as soon as one of the
1065//! three state machine commands returns (Execute(), Configure() and
1066//! Transition()) the loop will be left and Run(9 will return. The program
1067//! is then responsible of correctly cleaning up the mess which might be left
1068//! behind.
1069//!
1070//! @param code
1071//! int with which Run() should return when returning.
1072//
1073void StateMachineImp::Stop(int code)
1074{
1075 fExitRequested = code+1;
1076}
Note: See TracBrowser for help on using the repository browser.