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

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