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

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